Index: head/contrib/nvi/docs/man/vi.1 =================================================================== --- head/contrib/nvi/docs/man/vi.1 (revision 365498) +++ head/contrib/nvi/docs/man/vi.1 (nonexistent) @@ -1,2747 +0,0 @@ -.\" Copyright (c) 1994 -.\" The Regents of the University of California. All rights reserved. -.\" Copyright (c) 1994, 1995, 1996 -.\" Keith Bostic. All rights reserved. -.\" Copyright (c) 2011 -.\" Zhihao Yuan. All rights reserved. -.\" -.\" The vi program is freely redistributable. -.\" You are welcome to copy, modify and share it with others -.\" under the conditions listed in the LICENSE file. -.\" If any company (not individual!) finds vi sufficiently useful -.\" that you would have purchased it, or if any company wishes to -.\" redistribute it, contributions to the authors would be appreciated. -.\" -.\" $Id: vi.1,v 9.0 2013/11/02 12:11:56 zy Exp $ -.\" -.Dd December 9, 2019 -.Dt VI 1 -.Os -.Sh NAME -.Nm ex , vi , view -.Nd text editors -.Sh SYNOPSIS -.Nm ex -.Op Fl FRrSsv -.Op Fl c Ar cmd -.Op Fl t Ar tag -.Op Fl w Ar size -.Op Ar -.Nm vi\ \& -.Op Fl eFRrS -.Op Fl c Ar cmd -.Op Fl t Ar tag -.Op Fl w Ar size -.Op Ar -.Nm view -.Op Fl eFrS -.Op Fl c Ar cmd -.Op Fl t Ar tag -.Op Fl w Ar size -.Op Ar -.Sh DESCRIPTION -.Nm vi -is a screen-oriented text editor. -.Nm ex -is a line-oriented text editor. -.Nm ex -and -.Nm vi -are different interfaces to the same program, -and it is possible to switch back and forth during an edit session. -.Nm view -is the equivalent of using the -.Fl R -.Pq read-only -option of -.Nm vi . -.Pp -This manual page is the one provided with the -.Nm nex Ns / Ns Nm nvi -versions of the -.Nm ex Ns / Ns Nm vi -text editors. -.Nm nex Ns / Ns Nm nvi -are intended as bug-for-bug compatible replacements for the original -Fourth Berkeley Software Distribution -.Pq 4BSD -.Nm ex -and -.Nm vi -programs. -For the rest of this manual page, -.Nm nex Ns / Ns Nm nvi -is used only when it's necessary to distinguish it from the historic -implementations of -.Nm ex Ns / Ns Nm vi . -.Pp -This manual page is intended for users already familiar with -.Nm ex Ns / Ns Nm vi . -Anyone else should almost certainly read a good tutorial on the -editor before this manual page. -If you're in an unfamiliar environment, -and you absolutely have to get work done immediately, -read the section after the options description, entitled -.Sx FAST STARTUP . -It's probably enough to get you going. -.Pp -The following options are available: -.Bl -tag -width "-w size " -.It Fl c Ar cmd -Execute -.Ar cmd -on the first file loaded. -Particularly useful for initial positioning in the file, although -.Ar cmd -is not limited to positioning commands. -This is the POSIX 1003.2 interface for the historic -.Dq +cmd -syntax. -.Nm nex Ns / Ns Nm nvi -supports both the old and new syntax. -.It Fl e -Start editing in ex mode, as if the command name were -.Nm ex . -.It Fl F -Don't copy the entire file when first starting to edit. -(The default is to make a copy in case someone else modifies -the file during your edit session.) -.\" .It Fl l -.\" Start editing with the lisp and showmatch options set. -.It Fl R -Start editing in read-only mode, as if the command name was -.Nm view , -or the -.Cm readonly -option was set. -.It Fl r -Recover the specified files, or, if no files are specified, -list the files that could be recovered. -If no recoverable files by the specified name exist, -the file is edited as if the -.Fl r -option had not been specified. -.It Fl S -Run with the -.Cm secure -edit option set, disallowing all access to external programs. -.It Fl s -Enter batch mode; applicable only to -.Nm ex -edit sessions. -Batch mode is useful when running -.Nm ex -scripts. -Prompts, informative messages and other user oriented messages are turned off, -and no startup files or environment variables are read. -This is the POSIX 1003.2 interface for the historic -.Dq - -argument. -.Nm nex Ns / Ns Nm nvi -supports both the old and new syntax. -.It Fl t Ar tag -Start editing at the specified -.Ar tag -.Pq see Xr ctags 1 . -.It Fl v -Start editing in vi mode, as if the command name was -.Nm vi . -.It Fl w Ar size -Set the initial window size to the specified number of lines. -.El -.Pp -Command input for -.Nm ex Ns / Ns Nm vi -is read from the standard input. -In the -.Nm vi -interface, it is an error if standard input is not a terminal. -In the -.Nm ex -interface, if standard input is not a terminal, -.Nm ex -will read commands from it regardless; however, the session will be a -batch mode session, exactly as if the -.Fl s -option had been specified. -.Sh FAST STARTUP -This section will tell you the minimum amount that you need to -do simple editing tasks using -.Nm vi . -If you've never used any screen editor before, -you're likely to have problems even with this simple introduction. -In that case you should find someone that already knows -.Nm vi -and have them walk you through this section. -.Pp -.Nm vi -is a screen editor. -This means that it takes up almost the entire screen, -displaying part of the file on each screen line, -except for the last line of the screen. -The last line of the screen is used for you to give commands to -.Nm vi , -and for -.Nm vi -to give information to you. -.Pp -The other fact that you need to understand is that -.Nm vi -is a modeful editor, -i.e., you are either entering text or you are executing commands, -and you have to be in the right mode to do one or the other. -You will be in command mode when you first start editing a file. -There are commands that switch you into input mode. -There is only one key that takes you out of input mode, -and that is the -.Aq escape -key. -.Pp -In this manual, -key names are denoted with \(la and \(ra, e.g., -.Aq escape -means the -.Dq escape -key, usually labeled -.Dq Esc -on your terminal's keyboard. -If you're ever confused as to which mode you're in, -keep entering the -.Aq escape -key until -.Nm vi -beeps at you. -Generally, -.Nm vi -will beep at you if you try and do something that's not allowed. -It will also display error messages. -.Pp -To start editing a file, enter the following command: -.Pp -.Dl $ vi file -.Pp -The command you should enter as soon as you start editing is: -.Pp -.Dl :set verbose showmode -.Pp -This will make the editor give you verbose error messages and display -the current mode at the bottom of the screen. -.Pp -The commands to move around the file are: -.Bl -tag -width Ds -.It Cm h -Move the cursor left one character. -.It Cm j -Move the cursor down one line. -.It Cm k -Move the cursor up one line. -.It Cm l -Move the cursor right one character. -.It Aq Cm cursor-arrows -The cursor arrow keys should work, too. -.It Cm / Ns Ar text -Search for the string -.Dq Ar text -in the file, -and move the cursor to its first character. -.El -.Pp -The commands to enter new text are: -.Bl -tag -width "" -.It Cm a -Append new text, after the cursor. -.It Cm i -Insert new text, before the cursor. -.It Cm o -Open a new line below the line the cursor is on, and start entering text. -.It Cm O -Open a new line above the line the cursor is on, and start entering text. -.It Aq Cm escape -Once you've entered input mode using one of the -.Cm a , -.Cm i , -.Cm o -or -.Cm O -commands, use -.Aq Cm escape -to quit entering text and return to command mode. -.El -.Pp -The commands to copy text are: -.Bl -tag -width Ds -.It Cm yy -Copy the line the cursor is on. -.It Cm p -Append the copied line after the line the cursor is on. -.El -.Pp -The commands to delete text are: -.Bl -tag -width Ds -.It Cm dd -Delete the line the cursor is on. -.It Cm x -Delete the character the cursor is on. -.El -.Pp -The commands to write the file are: -.Bl -tag -width Ds -.It Cm :w -Write the file back to the file with the name that you originally used -as an argument on the -.Nm vi -command line. -.It Cm :w Ar file_name -Write the file back to the file with the name -.Ar file_name . -.El -.Pp -The commands to quit editing and exit the editor are: -.Bl -tag -width Ds -.It Cm :q -Quit editing and leave -.Nm vi -(if you've modified the file, but not saved your changes, -.Nm vi -will refuse to quit). -.It Cm :q! -Quit, discarding any modifications that you may have made. -.El -.Pp -One final caution: -Unusual characters can take up more than one column on the screen, -and long lines can take up more than a single screen line. -The above commands work on -.Dq physical -characters and lines, -i.e., they affect the entire line no matter how many screen lines it takes up -and the entire character no matter how many screen columns it takes up. -.Sh REGULAR EXPRESSIONS -.Nm ex Ns / Ns Nm vi -supports regular expressions -.Pq REs , -as documented in -.Xr re_format 7 , -for line addresses, as the first part of the -.Nm ex Cm substitute , -.Cm global -and -.Cm v -commands, and in search patterns. -Basic regular expressions -.Pq BREs -are enabled by default; -extended regular expressions -.Pq EREs -are used if the -.Cm extended -option is enabled. -The use of regular expressions can be largely disabled using the -.Cm magic -option. -.Pp -The following strings have special meanings in the -.Nm ex Ns / Ns Nm vi -version of regular expressions: -.Bl -bullet -offset 6u -.It -An empty regular expression is equivalent to the last regular expression used. -.It -.Sq \e< -matches the beginning of the word. -.It -.Sq \e> -matches the end of the word. -.It -.Sq \(a~ -matches the replacement part of the last -.Cm substitute -command. -.El -.Sh BUFFERS -A buffer is an area where commands can save changed or deleted text -for later use. -.Nm vi -buffers are named with a single character preceded by a double quote, -for example -.Cm \&" Ns Aq Ar c ; -.Nm ex -buffers are the same, -but without the double quote. -.Nm nex Ns / Ns Nm nvi -permits the use of any character without another meaning in the position where -a buffer name is expected. -.Pp -All buffers are either in -.Em line mode -or -.Em character mode . -Inserting a buffer in line mode into the text creates new lines for each of the -lines it contains, while a buffer in character mode creates new lines for any -lines -.Em other -than the first and last lines it contains. -The first and last lines are inserted at the current cursor position, becoming -part of the current line. -If there is more than one line in the buffer, -the current line itself will be split. -All -.Nm ex -commands which store text into buffers do so in line mode. -The behaviour of -.Nm vi -commands depend on their associated motion command: -.Bl -bullet -offset 6u -.It -.Aq Cm control-A , -.Cm h , -.Cm l , -.Cm ,\& , -.Cm 0 , -.Cm B , -.Cm E , -.Cm F , -.Cm T , -.Cm W , -.Cm ^ , -.Cm b , -.Cm e , -.Cm f -and -.Cm t -make the destination buffer character-oriented. -.It -.Cm j , -.Aq Cm control-M , -.Cm k , -.Cm \(aq , -.Cm - , -.Cm G , -.Cm H , -.Cm L , -.Cm M , -.Cm _ -and -.Cm |\& -make the destination buffer line-oriented. -.It -.Cm $ , -.Cm % , -.Cm \` , -.Cm (\& , -.Cm )\& , -.Cm / , -.Cm ?\& , -.Cm [[ , -.Cm ]] , -.Cm { -and -.Cm } -make the destination buffer character-oriented, unless the starting and -end positions are the first and last characters on a line. -In that case, the buffer is line-oriented. -.El -.Pp -The -.Nm ex -command -.Cm display buffers -displays the current mode for each buffer. -.Pp -Buffers named -.Sq a -through -.Sq z -may be referred to using their uppercase equivalent, in which case new content -will be appended to the buffer, instead of replacing it. -.Pp -Buffers named -.Sq 1 -through -.Sq 9 -are special. -A region of text modified using the -.Cm c -.Pq change -or -.Cm d -.Pq delete -commands is placed into the numeric buffer -.Sq 1 -if no other buffer is specified and if it meets one of the following conditions: -.Bl -bullet -offset 6u -.It -It includes characters from more than one line. -.It -It is specified using a line-oriented motion. -.It -It is specified using one of the following motion commands: -.Aq Cm control-A , -.Cm \` Ns Aq Ar character , -.Cm n , -.Cm N , -.Cm % , -.Cm / , -.Cm { , -.Cm } , -.Cm \&( , -.Cm \&) , -and -.Cm \&? . -.El -.Pp -Before this copy is done, the previous contents of buffer -.Sq 1 -are moved into buffer -.Sq 2 , -.Sq 2 -into buffer -.Sq 3 , -and so on. -The contents of buffer -.Sq 9 -are discarded. -Note that this rotation occurs -.Em regardless -of the user specifying another buffer. -In -.Nm vi , -text may be explicitly stored into the numeric buffers. -In this case, the buffer rotation occurs before the replacement of the buffer's -contents. -The numeric buffers are only available in -.Nm vi -mode. -.Sh VI COMMANDS -The following section describes the commands available in the command -mode of the -.Nm vi -editor. -The following words have a special meaning in the commands description: -.Pp -.Bl -tag -width bigword -compact -offset 3u -.It Ar bigword -A set of non-whitespace characters. -.It Ar buffer -Temporary area where commands may place text. -If not specified, the default buffer is used. -See also -.Sx BUFFERS , -above. -.It Ar count -A positive number used to specify the desired number of iterations -of a command. -It defaults to 1 if not specified. -.It Ar motion -A cursor movement command which indicates the other end of the affected region -of text, the first being the current cursor position. -Repeating the command character makes it affect the whole current line. -.It Ar word -A sequence of letters, digits or underscores. -.El -.Pp -.Ar buffer -and -.Ar count , -if both present, may be specified in any order. -.Ar motion -and -.Ar count , -if both present, are effectively multiplied together -and considered part of the motion. -.Pp -.Bl -tag -width Ds -compact -.It Xo -.Aq Cm control-A -.Xc -Search forward -for the word starting at the cursor position. -.Pp -.It Xo -.Op Ar count -.Aq Cm control-B -.Xc -Page backwards -.Ar count -screens. -Two lines of overlap are maintained, if possible. -.Pp -.It Xo -.Op Ar count -.Aq Cm control-D -.Xc -Scroll forward -.Ar count -lines. -If -.Ar count -is not given, scroll forward the number of lines specified by the last -.Aq Cm control-D -or -.Aq Cm control-U -command. -If this is the first -.Aq Cm control-D -command, scroll half the number of lines in the current screen. -.Pp -.It Xo -.Op Ar count -.Aq Cm control-E -.Xc -Scroll forward -.Ar count -lines, leaving the current line and column as is, if possible. -.Pp -.It Xo -.Op Ar count -.Aq Cm control-F -.Xc -Page forward -.Ar count -screens. -Two lines of overlap are maintained, if possible. -.Pp -.It Aq Cm control-G -Display the following file information: -the file name -.Pq as given to Nm vi ; -whether the file has been modified since it was last written; -if the file is read-only; -the current line number; -the total number of lines in the file; -and the current line number as a percentage of the total lines in the file. -.Pp -.It Xo -.Op Ar count -.Aq Cm control-H -.Xc -.It Xo -.Op Ar count -.Cm h -.Xc -Move the cursor back -.Ar count -characters in the current line. -.Pp -.It Xo -.Op Ar count -.Aq Cm control-J -.Xc -.It Xo -.Op Ar count -.Aq Cm control-N -.Xc -.It Xo -.Op Ar count -.Cm j -.Xc -Move the cursor down -.Ar count -lines without changing the current column. -.Pp -.It Aq Cm control-L -.It Aq Cm control-R -Repaint the screen. -.Pp -.It Xo -.Op Ar count -.Aq Cm control-M -.Xc -.It Xo -.Op Ar count -.Cm + -.Xc -Move the cursor down -.Ar count -lines to the first non-blank character of that line. -.Pp -.It Xo -.Op Ar count -.Aq Cm control-P -.Xc -.It Xo -.Op Ar count -.Cm k -.Xc -Move the cursor up -.Ar count -lines, without changing the current column. -.Pp -.It Aq Cm control-T -Return to the most recent tag context. -.Pp -.It Xo -.Op Ar count -.Aq Cm control-U -.Xc -Scroll backwards -.Ar count -lines. -If -.Ar count -is not given, scroll backwards the number of lines specified by the last -.Aq Cm control-D -or -.Aq Cm control-U -command. -If this is the first -.Aq Cm control-U -command, scroll half the number of lines in the current screen. -.Pp -.It Aq Cm control-W -Switch to the next lower screen in the window, -or to the first screen if there are no lower screens in the window. -.Pp -.It Xo -.Op Ar count -.Aq Cm control-Y -.Xc -Scroll backwards -.Ar count -lines, leaving the current line and column as is, if possible. -.Pp -.It Aq Cm control-Z -Suspend the current editor session. -.Pp -.It Aq Cm escape -Execute the -.Nm ex -command being entered, or cancel it if it is only partial. -.Pp -.It Aq Cm control-] -Push a tag reference onto the tag stack. -.Pp -.It Aq Cm control-^ -Switch to the most recently edited file. -.Pp -.It Xo -.Op Ar count -.Aq Cm space -.Xc -.It Xo -.Op Ar count -.Cm l -.Xc -Move the cursor forward -.Ar count -characters without changing the current line. -.Pp -.It Xo -.Op Ar count -.Cm !\& -.Ar motion shell-argument(s) -.Aq Li carriage-return -.Xc -Replace the lines spanned by -.Ar count -and -.Ar motion -with the output -.Pq standard output and standard error -of the program named by the -.Cm shell -option, called with a -.Fl c -flag followed by the -.Ar shell-argument(s) -.Pq bundled into a single argument . -Within -.Ar shell-argument(s) , -the -.Sq % , -.Sq # -and -.Sq !\& -characters are expanded to the current file name, -the previous current file name, -and the command text of the previous -.Cm !\& -or -.Cm :! -commands, respectively. -The special meaning of -.Sq % , -.Sq # -and -.Sq !\& -can be overridden by escaping them with a backslash. -.Pp -.It Xo -.Op Ar count -.Cm # -.Sm off -.Cm # | + | - -.Sm on -.Xc -Increment -.Pq trailing So # Sc or So + Sc -or decrement -.Pq trailing Sq - -the number under the cursor by -.Ar count , -starting at the cursor position or at the first non-blank -character following it. -Numbers with a leading -.Sq 0x -or -.Sq 0X -are interpreted as hexadecimal numbers. -Numbers with a leading -.Sq 0 -are interpreted as octal numbers unless they contain a non-octal digit. -Other numbers may be prefixed with a -.Sq + -or -.Sq - -sign. -.Pp -.It Xo -.Op Ar count -.Cm $ -.Xc -Move the cursor to the end of a line. -If -.Ar count -is specified, additionally move the cursor down -.Ar count -\(mi 1 lines. -.Pp -.It Cm % -Move to the -.Cm matchchars -character matching -the one found at the cursor position or the closest to the right of it. -.Pp -.It Cm & -Repeat the previous substitution command on the current line. -.Pp -.It Xo -.Cm \(aq Ns Aq Ar character -.Xc -.It Xo -.Cm \` Ns Aq Ar character -.Xc -Return to the cursor position marked by the character -.Ar character , -or, if -.Ar character -is -.Sq \(aq -or -.Sq \` , -to the position of the cursor before the last of the following commands: -.Aq Cm control-A , -.Aq Cm control-T , -.Aq Cm control-] , -.Cm % , -.Cm \(aq , -.Cm \` , -.Cm (\& , -.Cm )\& , -.Cm / , -.Cm ?\& , -.Cm G , -.Cm H , -.Cm L , -.Cm [[ , -.Cm ]] , -.Cm { , -.Cm } . -The first form returns to the first non-blank character of the line marked by -.Ar character . -The second form returns to the line and column marked by -.Ar character . -.Pp -.It Xo -.Op Ar count -.Cm \&( -.Xc -.It Xo -.Op Ar count -.Cm \&) -.Xc -Move -.Ar count -sentences backward or forward, respectively. -A sentence is an area of text that begins with the first nonblank character -following the previous sentence, paragraph, or section -boundary and continues until the next period, exclamation point, -or question mark character, followed by any number of closing parentheses, -brackets, double or single quote characters, followed by -either an end-of-line or two whitespace characters. -Groups of empty lines -.Pq or lines containing only whitespace characters -are treated as a single sentence. -.Pp -.It Xo -.Op Ar count -.Cm ,\& -.Xc -Reverse find character -.Pq i.e., the last Cm F , f , T No or Cm t No command -.Ar count -times. -.Pp -.It Xo -.Op Ar count -.Cm - -.Xc -Move to the first non-blank character of the previous line, -.Ar count -times. -.Pp -.It Xo -.Op Ar count -.Cm .\& -.Xc -Repeat the last -.Nm vi -command that modified text. -.Ar count -replaces both the -.Ar count -argument of the repeated command and that of the associated -.Ar motion . -If the -.Cm .\& -command repeats the -.Cm u -command, the change log is rolled forward or backward, depending on the action -of the -.Cm u -command. -.Pp -.It Xo -.Pf / Ns Ar RE -.Aq Li carriage-return -.Xc -.It Xo -.Pf / Ns Ar RE Ns / -.Op Ar offset -.Op Cm z -.Aq Li carriage-return -.Xc -.It Xo -.Pf ?\& Ns Ar RE -.Aq Li carriage-return -.Xc -.It Xo -.Pf ?\& Ns Ar RE Ns ?\& -.Op Ar offset -.Op Cm z -.Aq Li carriage-return -.Xc -.It Cm N -.It Cm n -Search forward -.Pq Sq / -or backward -.Pq Sq ?\& -for a regular expression. -.Cm n -and -.Cm N -repeat the last search in the same or opposite directions, respectively. -If -.Ar RE -is empty, the last search regular expression is used. -If -.Ar offset -is specified, the cursor is placed -.Ar offset -lines before or after the matched regular expression. -If either -.Cm n -or -.Cm N -commands are used as motion components for the -.Cm !\& -command, there will be no prompt for the text of the command and the previous -.Cm !\& -will be executed. -Multiple search patterns may be grouped together by delimiting them with -semicolons and zero or more whitespace characters. -These patterns are evaluated from left to right with the final cursor position -determined by the last search pattern. -A -.Cm z -command may be appended to the closed search expressions to reposition the -result line. -.Pp -.It Cm 0 -Move to the first character in the current line. -.Pp -.It Cm :\& -Execute an -.Nm ex -command. -.Pp -.It Xo -.Op Ar count -.Cm ;\& -.Xc -Repeat the last character find -.Pq i.e., the last Cm F , f , T No or Cm t No command -.Ar count -times. -.Pp -.It Xo -.Op Ar count -.Cm < -.Ar motion -.Xc -.It Xo -.Op Ar count -.Cm > -.Ar motion -.Xc -Shift -.Ar count -lines left or right, respectively, by an amount of -.Cm shiftwidth . -.Pp -.It Cm @ Ar buffer -Execute a named -.Ar buffer -as -.Nm vi -commands. -The buffer may include -.Nm ex -commands too, but they must be expressed as a -.Cm \&: -command. -If -.Ar buffer -is -.Sq @ -or -.Sq * , -then the last buffer executed shall be used. -.Pp -.It Xo -.Op Ar count -.Cm A -.Xc -Enter input mode, appending the text after the end of the line. -If a -.Ar count -argument is given, the characters input are repeated -.Ar count -\(mi 1 times after input mode is exited. -.Pp -.It Xo -.Op Ar count -.Cm B -.Xc -Move backwards -.Ar count -bigwords. -.Pp -.It Xo -.Op Ar buffer -.Cm C -.Xc -Change text from the current position to the end-of-line. -If -.Ar buffer -is specified, -.Dq yank -the deleted text into -.Ar buffer . -.Pp -.It Xo -.Op Ar buffer -.Cm D -.Xc -Delete text from the current position to the end-of-line. -If -.Ar buffer -is specified, -.Dq yank -the deleted text into -.Ar buffer . -.Pp -.It Xo -.Op Ar count -.Cm E -.Xc -Move forward -.Ar count -end-of-bigwords. -.Pp -.It Xo -.Op Ar count -.Cm F Aq Ar character -.Xc -Search -.Ar count -times backward through the current line for -.Aq Ar character . -.Pp -.It Xo -.Op Ar count -.Cm G -.Xc -Move to line -.Ar count , -or the last line of the file if -.Ar count -is not specified. -.Pp -.It Xo -.Op Ar count -.Cm H -.Xc -Move to the screen line -.Ar count -\(mi 1 lines below the top of the screen. -.Pp -.It Xo -.Op Ar count -.Cm I -.Xc -Enter input mode, inserting the text at the beginning of the line. -If a -.Ar count -argument is given, -the characters input are repeated -.Ar count -\(mi 1 more times. -.Pp -.It Xo -.Op Ar count -.Cm J -.Xc -Join -.Ar count -lines with the current line. -The spacing between two joined lines is set to two whitespace characters if the -former ends with a question mark, a period or an exclamation point. -It is set to one whitespace character otherwise. -.Pp -.It Xo -.Op Ar count -.Cm L -.Xc -Move to the screen line -.Ar count -\(mi 1 lines above the bottom of the screen. -.Pp -.It Cm M -Move to the screen line in the middle of the screen. -.Pp -.It Xo -.Op Ar count -.Cm O -.Xc -Enter input mode, appending text in a new line above the current line. -If a -.Ar count -argument is given, -the characters input are repeated -.Ar count -\(mi 1 more times. -.Pp -.It Xo -.Op Ar buffer -.Cm P -.Xc -Insert text from -.Ar buffer -before the current column if -.Ar buffer -is character-oriented or before the current line if it is line-oriented. -.Pp -.It Cm Q -Exit -.Nm vi -.Pq or visual -mode and switch to -.Nm ex -mode. -.Pp -.It Xo -.Op Ar count -.Cm R -.Xc -Enter input mode, replacing the characters in the current line. -If a -.Ar count -argument is given, -the characters input are repeated -.Ar count -\(mi 1 more times upon exit from insert mode. -.Pp -.It Xo -.Op Ar buffer -.Op Ar count -.Cm S -.Xc -Substitute -.Ar count -lines. -If -.Ar buffer -is specified, -.Dq yank -the deleted text into -.Ar buffer . -.Pp -.It Xo -.Op Ar count -.Cm T -.Aq Ar character -.Xc -Search backwards, -.Ar count -times, through the current line for the character after the specified -.Aq Ar character . -.Pp -.It Cm U -Restore the current line to its state before the cursor last moved to it. -.Pp -.It Xo -.Op Ar count -.Cm W -.Xc -Move forward -.Ar count -bigwords. -.Pp -.It Xo -.Op Ar buffer -.Op Ar count -.Cm X -.Xc -Delete -.Ar count -characters before the cursor, on the current line. -If -.Ar buffer -is specified, -.Dq yank -the deleted text into -.Ar buffer . -.Pp -.It Xo -.Op Ar buffer -.Op Ar count -.Cm Y -.Xc -Copy -.Pq or Dq yank -.Ar count -lines into -.Ar buffer . -.Pp -.It Cm ZZ -Write the file and exit -.Nm vi -if there are no more files to edit. -Entering two -.Dq quit -commands in a row ignores any remaining file to edit. -.Pp -.It Xo -.Op Ar count -.Cm [[ -.Xc -Back up -.Ar count -section boundaries. -.Pp -.It Xo -.Op Ar count -.Cm ]] -.Xc -Move forward -.Ar count -section boundaries. -.Pp -.It Cm ^ -Move to the first non-blank character on the current line. -.Pp -.It Xo -.Op Ar count -.Cm _ -.Xc -Move down -.Ar count -\(mi 1 lines, to the first non-blank character. -.Pp -.It Xo -.Op Ar count -.Cm a -.Xc -Enter input mode, appending the text after the cursor. -If a -.Ar count -argument is given, -the characters input are repeated -.Ar count -number of times. -.Pp -.It Xo -.Op Ar count -.Cm b -.Xc -Move backwards -.Ar count -words. -.Pp -.It Xo -.Op Ar buffer -.Op Ar count -.Cm c -.Ar motion -.Xc -Change the region of text described by -.Ar count -and -.Ar motion . -If -.Ar buffer -is specified, -.Dq yank -the changed text into -.Ar buffer . -.Pp -.It Xo -.Op Ar buffer -.Op Ar count -.Cm d -.Ar motion -.Xc -Delete the region of text described by -.Ar count -and -.Ar motion . -If -.Ar buffer -is specified, -.Dq yank -the deleted text into -.Ar buffer . -.Pp -.It Xo -.Op Ar count -.Cm e -.Xc -Move forward -.Ar count -end-of-words. -.Pp -.It Xo -.Op Ar count -.Cm f Aq Ar character -.Xc -Search forward, -.Ar count -times, through the rest of the current line for -.Aq Ar character . -.Pp -.It Xo -.Op Ar count -.Cm i -.Xc -Enter input mode, inserting the text before the cursor. -If a -.Ar count -argument is given, -the characters input are repeated -.Ar count -number of times. -.Pp -.It Xo -.Cm m -.Aq Ar character -.Xc -Save the current context -.Pq line and column -as -.Aq Ar character . -.Pp -.It Xo -.Op Ar count -.Cm o -.Xc -Enter input mode, appending text in a new line under the current line. -If a -.Ar count -argument is given, -the characters input are repeated -.Ar count -\(mi 1 more times. -.Pp -.It Xo -.Op Ar buffer -.Cm p -.Xc -Append text from -.Ar buffer . -Text is appended after the current column if -.Ar buffer -is character oriented, or after the current line otherwise. -.Pp -.It Xo -.Op Ar count -.Cm r -.Aq Ar character -.Xc -Replace -.Ar count -characters with -.Ar character . -.Pp -.It Xo -.Op Ar buffer -.Op Ar count -.Cm s -.Xc -Substitute -.Ar count -characters in the current line starting with the current character. -If -.Ar buffer -is specified, -.Dq yank -the substituted text into -.Ar buffer . -.Pp -.It Xo -.Op Ar count -.Cm t -.Aq Ar character -.Xc -Search forward, -.Ar count -times, through the current line for the character immediately before -.Aq Ar character . -.Pp -.It Cm u -Undo the last change made to the file. -If repeated, the -.Cm u -command alternates between these two states. -The -.Cm .\& -command, when used immediately after -.Cm u , -causes the change log to be rolled forward or backward, depending on the action -of the -.Cm u -command. -.Pp -.It Xo -.Op Ar count -.Cm w -.Xc -Move forward -.Ar count -words. -.Pp -.It Xo -.Op Ar buffer -.Op Ar count -.Cm x -.Xc -Delete -.Ar count -characters at the current cursor position, but no more than there are till the -end of the line. -.Pp -.It Xo -.Op Ar buffer -.Op Ar count -.Cm y -.Ar motion -.Xc -Copy -.Pq or Dq yank -a text region specified by -.Ar count -and -.Ar motion -into a buffer. -.Pp -.It Xo -.Op Ar count1 -.Cm z -.Op Ar count2 -.Cm type -.Xc -Redraw, optionally repositioning and resizing the screen. -If -.Ar count2 -is specified, limit the screen size to -.Ar count2 -lines. -The following -.Cm type -characters may be used: -.Bl -tag -width Ds -.It Cm + -If -.Ar count1 -is specified, place the line -.Ar count1 -at the top of the screen. -Otherwise, display the screen after the current screen. -.It Aq Cm carriage-return -Place the line -.Ar count1 -at the top of the screen. -.It Cm .\& -Place the line -.Ar count1 -in the center of the screen. -.It Cm - -Place the line -.Ar count1 -at the bottom of the screen. -.It Cm ^ -If -.Ar count1 -is given, -display the screen before the screen before -.Ar count1 -.Pq i.e., 2 screens before . -Otherwise, display the screen before the current screen. -.El -.Pp -.It Xo -.Op Ar count -.Cm {\& -.Xc -Move backward -.Ar count -paragraphs. -.Pp -.It Xo -.Op Ar column -.Cm |\& -.Xc -Move to a specific -.Ar column -position on the current line. -If -.Ar column -is omitted, -move to the start of the current line. -.Pp -.It Xo -.Op Ar count -.Cm }\& -.Xc -Move forward -.Ar count -paragraphs. -.Pp -.It Xo -.Op Ar count -.Cm ~ -.Ar motion -.Xc -If the -.Cm tildeop -option is not set, reverse the case of the next -.Ar count -character(s) and no -.Ar motion -can be specified. -Otherwise -.Ar motion -is mandatory and -.Cm ~ -reverses the case of the characters in a text region specified by the -.Ar count -and -.Ar motion . -.Pp -.It Aq Cm interrupt -Interrupt the current operation. -The -.Aq interrupt -character is usually -.Aq control-C . -.El -.Sh VI TEXT INPUT COMMANDS -The following section describes the commands available in the text input mode -of the -.Nm vi -editor. -.Pp -.Bl -tag -width Ds -compact -.It Aq Cm nul -Replay the previous input. -.Pp -.It Aq Cm control-D -Erase to the previous -.Ar shiftwidth -column boundary. -.Pp -.It Cm ^ Ns Aq Cm control-D -Erase all of the autoindent characters, and reset the autoindent level. -.Pp -.It Cm 0 Ns Aq Cm control-D -Erase all of the autoindent characters. -.Pp -.It Aq Cm control-T -Insert sufficient -.Aq tab -and -.Aq space -characters to move forward to the next -.Ar shiftwidth -column boundary. -.Pp -.It Aq Cm erase -.It Aq Cm control-H -Erase the last character. -.Pp -.It Aq Cm literal next -Escape the next character from any special meaning. -The -.Aq literal\ \&next -character is usually -.Aq control-V . -.Pp -.It Aq Cm escape -Resolve all text input into the file, and return to command mode. -.Pp -.It Aq Cm line erase -Erase the current line. -.Pp -.It Aq Cm control-W -.It Aq Cm word erase -Erase the last word. -The definition of word is dependent on the -.Cm altwerase -and -.Cm ttywerase -options. -.Pp -.Sm off -.It Xo -.Aq Cm control-X -.Bq Cm 0-9A-Fa-f -.Cm + -.Xc -.Sm on -Insert a character with the specified hexadecimal value into the text. -.Pp -.It Aq Cm interrupt -Interrupt text input mode, returning to command mode. -The -.Aq interrupt -character is usually -.Aq control-C . -.El -.Sh EX COMMANDS -The following section describes the commands available in the -.Nm ex -editor. -In each entry below, the tag line is a usage synopsis for the command. -.Pp -.Bl -tag -width Ds -compact -.It Aq Cm end-of-file -Scroll the screen. -.Pp -.It Cm !\& Ar argument(s) -.It Xo -.Op Ar range -.Cm !\& -.Ar argument(s) -.Xc -Execute a shell command, or filter lines through a shell command. -.Pp -.It Cm \&" -A comment. -.Pp -.It Xo -.Op Ar range -.Cm nu Ns Op Cm mber -.Op Ar count -.Op Ar flags -.Xc -.It Xo -.Op Ar range -.Cm # -.Op Ar count -.Op Ar flags -.Xc -Display the selected lines, each preceded with its line number. -.Pp -.It Cm @ Ar buffer -.It Cm * Ar buffer -Execute a buffer. -.Pp -.It Xo -.Op Ar range -.Cm < Ns Op Cm < ... -.Op Ar count -.Op Ar flags -.Xc -Shift lines left. -.Pp -.It Xo -.Op Ar line -.Cm = -.Op Ar flags -.Xc -Display the line number of -.Ar line . -If -.Ar line -is not specified, display the line number of the last line in the file. -.Pp -.It Xo -.Op Ar range -.Cm > Ns Op Cm > ... -.Op Ar count -.Op Ar flags -.Xc -Shift lines right. -.Pp -.It Xo -.Cm ab Ns Op Cm breviate -.Ar lhs rhs -.Xc -.Nm vi -only. -Add -.Ar lhs -as an abbreviation for -.Ar rhs -to the abbreviation list. -.Pp -.It Xo -.Op Ar line -.Cm a Ns Op Cm ppend Ns -.Op Cm !\& -.Xc -The input text is appended after the specified line. -.Pp -.It Cm ar Ns Op Cm gs -Display the argument list. -.Pp -.It Cm bg -.Nm vi -only. -Background the current screen. -.Pp -.It Xo -.Op Ar range -.Cm c Ns Op Cm hange Ns -.Op Cm !\& -.Op Ar count -.Xc -The input text replaces the specified range. -.Pp -.It Xo -.Cm chd Ns Op Cm ir Ns -.Op Cm !\& -.Op Ar directory -.Xc -.It Xo -.Cm cd Ns Op Cm !\& -.Op Ar directory -.Xc -Change the current working directory. -.Pp -.It Xo -.Op Ar range -.Cm co Ns Op Cm py -.Ar line -.Op Ar flags -.Xc -.It Xo -.Op Ar range -.Cm t -.Ar line -.Op Ar flags -.Xc -Copy the specified lines after the destination -.Ar line . -.Pp -.It Xo -.Cm cs Ns Op Cm cope -.Cm add | find | help | kill | reset -.Xc -Execute a Cscope command. -.Pp -.It Xo -.Op Ar range -.Cm d Ns Op Cm elete -.Op Ar buffer -.Op Ar count -.Op Ar flags -.Xc -Delete the lines from the file. -.Pp -.It Xo -.Cm di Ns Op Cm splay -.Cm b Ns Oo Cm uffers Oc | -.Cm c Ns Oo Cm onnections Oc | -.Cm s Ns Oo Cm creens Oc | -.Cm t Ns Op Cm ags -.Xc -Display buffers, Cscope connections, screens or tags. -.Pp -.It Xo -.Op Cm Ee Ns -.Op Cm dit Ns -.Op Cm !\& -.Op Ar +cmd -.Op Ar file -.Xc -.It Xo -.Op Cm Ee Ns -.Cm x Ns Op Cm !\& -.Op Ar +cmd -.Op Ar file -.Xc -Edit a different file. -.Pp -.It Xo -.Cm exu Ns Op Cm sage -.Op Ar command -.Xc -Display usage for an -.Nm ex -command. -.Pp -.It Xo -.Cm f Ns Op Cm ile -.Op Ar file -.Xc -Display and optionally change the file name. -.Pp -.It Xo -.Op Cm Ff Ns -.Cm g -.Op Ar name -.Xc -.Nm vi -mode only. -Foreground the specified screen. -.Pp -.It Xo -.Op Ar range -.Cm g Ns Op Cm lobal -.No / Ns Ar pattern Ns / -.Op Ar commands -.Xc -.It Xo -.Op Ar range -.Cm v -.No / Ns Ar pattern Ns / -.Op Ar commands -.Xc -Apply commands to lines matching -.Pq Sq global -or not matching -.Pq Sq v -a pattern. -.Pp -.It Cm he Ns Op Cm lp -Display a help message. -.Pp -.It Xo -.Op Ar line -.Cm i Ns Op Cm nsert Ns -.Op Cm !\& -.Xc -The input text is inserted before the specified line. -.Pp -.It Xo -.Op Ar range -.Cm j Ns Op Cm oin Ns -.Op Cm !\& -.Op Ar count -.Op Ar flags -.Xc -Join lines of text together. -.Pp -.It Xo -.Op Ar range -.Cm l Ns Op Cm ist -.Op Ar count -.Op Ar flags -.Xc -Display the lines unambiguously. -.Pp -.It Xo -.Cm map Ns Op Cm !\& -.Op Ar lhs rhs -.Xc -Define or display maps -.Pq for Nm vi No only . -.Pp -.It Xo -.Op Ar line -.Cm ma Ns Op Cm rk -.Aq Ar character -.Xc -.It Xo -.Op Ar line -.Cm k Aq Ar character -.Xc -Mark the line with the mark -.Aq Ar character . -.Pp -.It Xo -.Op Ar range -.Cm m Ns Op Cm ove -.Ar line -.Xc -Move the specified lines after the target line. -.Pp -.It Xo -.Cm mk Ns Op Cm exrc Ns -.Op Cm !\& -.Ar file -.Xc -Write the abbreviations, editor options and maps to the specified -.Ar file . -.Pp -.It Xo -.Op Cm Nn Ns -.Op Cm ext Ns -.Op Cm !\& -.Op Ar -.Xc -Edit the next file from the argument list. -.\" .Pp -.\" .It Xo -.\" .Op Ar line -.\" .Cm o Ns Op Cm pen -.\" .No / Ns Ar pattern Ns / -.\" .Op Ar flags -.\" .Xc -.\" Enter open mode. -.Pp -.It Cm pre Ns Op Cm serve -Save the file in a form that can later be recovered using the -.Nm ex -.Fl r -option. -.Pp -.It Xo -.Op Cm \&Pp Ns -.Cm rev Ns Op Cm ious Ns -.Op Cm !\& -.Xc -Edit the previous file from the argument list. -.Pp -.It Xo -.Op Ar range -.Cm p Ns Op Cm rint -.Op Ar count -.Op Ar flags -.Xc -Display the specified lines. -.Pp -.It Xo -.Op Ar line -.Cm pu Ns Op Cm t -.Op Ar buffer -.Xc -Append buffer contents to the current line. -.Pp -.It Xo -.Cm q Ns Op Cm uit Ns -.Op Cm !\& -.Xc -End the editing session. -.Pp -.It Xo -.Op Ar line -.Cm r Ns Op Cm ead Ns -.Op Cm !\& -.Op Ar file -.Xc -Read a file. -.Pp -.It Xo -.Cm rec Ns Op Cm over -.Ar file -.Xc -Recover -.Ar file -if it was previously saved. -.Pp -.It Xo -.Cm res Ns Op Cm ize -.Op Cm + Ns | Ns Cm - Ns -.Ar size -.Xc -.Nm vi -mode only. -Grow or shrink the current screen. -.Pp -.It Xo -.Cm rew Ns Op Cm ind Ns -.Op Cm !\& -.Xc -Rewind the argument list. -.Pp -.It Xo -.Cm se Ns Op Cm t -.Sm off -.Op option Oo = Oo value Oc Oc \ \&... -.Sm on -.Pf \ \& Op nooption ... -.Op option? ... -.Op Ar all -.Xc -Display or set editor options. -.Pp -.It Cm sh Ns Op Cm ell -Run a shell program. -.Pp -.It Xo -.Cm so Ns Op Cm urce -.Ar file -.Xc -Read and execute -.Nm ex -commands from a file. -.Pp -.It Xo -.Op Ar range -.Cm s Ns Op Cm ubstitute -.Sm off -.Op / Ar pattern No / Ar replace No / -.Sm on -.Pf \ \& Op Ar options -.Op Ar count -.Op Ar flags -.Xc -.It Xo -.Op Ar range -.Cm & -.Op Ar options -.Op Ar count -.Op Ar flags -.Xc -.It Xo -.Op Ar range -.Cm ~ -.Op Ar options -.Op Ar count -.Op Ar flags -.Xc -Make substitutions. -The -.Ar replace -field may contain any of the following sequences: -.Bl -tag -width Ds -.It Sq \*(Am -The text matched by -.Ar pattern . -.It Sq \(a~ -The replacement part of the previous -.Cm substitute -command. -.It Sq % -If this is the entire -.Ar replace -pattern, the replacement part of the previous -.Cm substitute -command. -.It Sq \e Ns Ar \(sh -Where -.Sq Ar \(sh -is an integer from 1 to 9, the text matched by the -.Ar # Ns 'th subexpression in -.Ar pattern . -.It Sq \eL -Causes the characters up to the end of the line of the next occurrence of -.Sq \eE -or -.Sq \ee -to be converted to lowercase. -.It Sq \el -Causes the next character to be converted to lowercase. -.It Sq \eU -Causes the characters up to the end of the line of the next occurrence of -.Sq \eE -or -.Sq \ee -to be converted to uppercase. -.It Sq \eu -Causes the next character to be converted to uppercase. -.El -.Pp -.It Xo -.Cm su Ns Op Cm spend Ns -.Op Cm !\& -.Xc -.It Xo -.Cm st Ns Op Cm op Ns -.Op Cm !\& -.Xc -.It Aq Cm suspend -Suspend the edit session. -The -.Aq suspend -character is usually -.Aq control-Z . -.Pp -.It Xo -.Op Cm Tt Ns -.Cm a Ns Op Cm g Ns -.Op Cm !\& -.Ar tagstring -.Xc -Edit the file containing the specified tag. -.Pp -.It Xo -.Cm tagn Ns Op Cm ext Ns -.Op Cm !\& -.Xc -Edit the file containing the next context for the current tag. -.Pp -.It Xo -.Cm tagp Ns Op Cm op Ns -.Op Cm !\& -.Op Ar file | number -.Xc -Pop to the specified tag in the tags stack. -.Pp -.It Xo -.Cm tagpr Ns Op Cm ev Ns -.Op Cm !\& -.Xc -Edit the file containing the previous context for the current tag. -.Pp -.It Xo -.Cm tagt Ns Op Cm op Ns -.Op Cm !\& -.Xc -Pop to the least recent tag on the tags stack, clearing the stack. -.Pp -.It Xo -.Cm una Ns Op Cm bbreviate -.Ar lhs -.Xc -.Nm vi -only. -Delete an abbreviation. -.Pp -.It Cm u Ns Op Cm ndo -Undo the last change made to the file. -.Pp -.It Xo -.Cm unm Ns Op Cm ap Ns -.Op Cm !\& -.Ar lhs -.Xc -Unmap a mapped string. -.Pp -.It Cm ve Ns Op Cm rsion -Display the version of the -.Nm ex Ns / Ns Nm vi -editor. -.Pp -.It Xo -.Op Ar line -.Cm vi Ns Op Cm sual -.Op Ar type -.Op Ar count -.Op Ar flags -.Xc -.Nm ex -mode only. -Enter -.Nm vi . -.Pp -.It Xo -.Op Cm Vi Ns -.Cm i Ns Op Cm sual Ns -.Op Cm !\& -.Op Ar +cmd -.Op Ar file -.Xc -.Nm vi -mode only. -Edit a new file. -.Pp -.It Xo -.Cm viu Ns Op Cm sage -.Op Ar command -.Xc -Display usage for a -.Nm vi -command. -.Pp -.It Xo -.Op Ar range -.Cm w Ns Op Cm rite Ns -.Op Cm !\& -.Op >> -.Op Ar file -.Xc -.It Xo -.Op Ar range -.Cm w Ns Op Cm rite -.Op Cm !\& -.Op Ar file -.Xc -.It Xo -.Op Ar range -.Cm wn Ns Op Cm !\& -.Op >> -.Op Ar file -.Xc -.It Xo -.Op Ar range -.Cm wq Ns Op Cm !\& -.Op >> -.Op Ar file -.Xc -Write the file. -.Pp -.It Xo -.Op Ar range -.Cm x Ns Op Cm it Ns -.Op Cm !\& -.Op Ar file -.Xc -Exit the editor, -writing the file if it has been modified. -.Pp -.It Xo -.Op Ar range -.Cm ya Ns Op Cm nk -.Op Ar buffer -.Op Ar count -.Xc -Copy the specified lines to a buffer. -.Pp -.It Xo -.Op Ar line -.Cm z -.Op Ar type -.Op Ar count -.Op Ar flags -.Xc -Adjust the window. -.El -.Sh SET OPTIONS -There are a large number of options that may be set -.Pq or unset -to change the editor's behavior. -This section describes the options, their abbreviations and their -default values. -.Pp -In each entry below, the first part of the tag line is the full name -of the option, followed by any equivalent abbreviations. -The part in square brackets is the default value of the option. -Most of the options are boolean, i.e., they are either on or off, -and do not have an associated value. -.Pp -Options apply to both -.Nm ex -and -.Nm vi -modes, unless otherwise specified. -.Bl -tag -width Ds -.It Cm altwerase Bq off -.Nm vi -only. -Select an alternate word erase algorithm. -.It Cm autoindent , ai Bq off -Automatically indent new lines. -.It Cm autoprint , ap Bq on -.Nm ex -only. -Display the current line automatically. -.It Cm autowrite , aw Bq off -Write modified files automatically when changing files or suspending the editor -session. -.It Cm backup Bq \&"\&" -Back up files before they are overwritten. -.It Cm beautify , bf Bq off -Discard control characters. -.It Cm cdpath Bo environment variable Ev CDPATH , or current directory Bc -The directory paths used as path prefixes for the -.Cm cd -command. -.It Cm cedit Bq no default -Set the character to edit the colon command-line history. -.It Cm columns , co Bq 80 -Set the number of columns in the screen. -.It Cm comment Bq off -.Nm vi -only. -Skip leading comments in shell, C and C++ language files. -.It Cm directory , dir Bo environment variable Ev TMPDIR , or Pa /tmp Bc -The directory where temporary files are created. -.It Cm edcompatible , ed Bq off -Remember the values of the -.Sq c -and -.Sq g -suffixes to the -.Cm substitute -commands, instead of initializing them as unset for each new command. -.It Cm errorbells , eb Bq off -.Nm ex -only. -Announce error messages with a bell. -.It Cm escapetime Bq 1 -The tenths of a second -.Nm ex Ns / Ns Nm vi -waits for a subsequent key to complete an -.Aq escape -key mapping. -.It Cm exrc , ex Bq off -Read the startup files in the local directory. -.It Cm extended Bq off -Use extended regular expressions -.Pq EREs -rather than basic regular expressions -.Pq BREs . -See -.Xr re_format 7 -for more information on regular expressions. -.It Cm filec Bq Aq tab -Set the character to perform file path completion on the colon command line. -.It Cm fileencoding , fe Bq auto detect -Set the encoding of the current file. -.It Cm flash Bq on -Flash the screen instead of beeping the keyboard on error. -.It Cm hardtabs, ht Bq 0 -Set the spacing between hardware tab settings. -This option currently has no effect. -.It Cm iclower Bq off -Makes all regular expressions case-insensitive, -as long as an upper-case letter does not appear in the search string. -.It Cm ignorecase , ic Bq off -Ignore case differences in regular expressions. -.It Cm inputencoding , ie Bq locale -Set the encoding of your input characters. -.It Cm keytime Bq 6 -The tenths of a second -.Nm ex Ns / Ns Nm vi -waits for a subsequent key to complete a key mapping. -.It Cm leftright Bq off -.Nm vi -only. -Do left-right scrolling. -.It Cm lines , li Bq 24 -.Nm vi -only. -Set the number of lines in the screen. -.It Cm lisp Bq off -.Nm vi -only. -Modify various search commands and options to work with Lisp. -This option is not yet implemented. -.It Cm list Bq off -Display lines in an unambiguous fashion. -.It Cm lock Bq on -Attempt to get an exclusive lock on any file being edited, read or written. -.It Cm magic Bq on -When turned off, all regular expression characters except for -.Sq \(ha -and -.Sq \(Do -are treated as ordinary characters. -Preceding individual characters by -.Sq \e -re-enables them. -.It Cm matchchars Bq []{}() -Character pairs looked for by the -.Cm % -command. -.It Cm matchtime Bq 7 -.Nm vi -only. -The tenths of a second -.Nm ex Ns / Ns Nm vi -pauses on the matching character when the -.Cm showmatch -option is set. -.It Cm mesg Bq on -Permit messages from other users. -.It Cm msgcat Bq /usr/share/vi/catalog/ -Selects a message catalog to be used to display error and informational -messages in a specified language. -.It Cm modelines , modeline Bq off -Read the first and last few lines of each file for -.Nm ex -commands. -This option will never be implemented. -.It Cm noprint Bq \&"\&" -Characters that are never handled as printable characters. -.It Cm number , nu Bq off -Precede each line displayed with its current line number. -.It Cm octal Bq off -Display unknown characters as octal numbers, instead of the default -hexadecimal. -.It Cm open Bq on -.Nm ex -only. -If this option is not set, the -.Cm open -and -.Cm visual -commands are disallowed. -.It Cm optimize , opt Bq on -.Nm vi -only. -Optimize text throughput to dumb terminals. -This option is not yet implemented. -.It Cm paragraphs , para Bq "IPLPPPQPP LIpplpipbp" -.Nm vi -only. -Define additional paragraph boundaries for the -.Cm {\& -and -.Cm }\& -commands. -.It Cm path Bq \&"\&" -Define additional directories to search for files being edited. -.It Cm print Bq \&"\&" -Characters that are always handled as printable characters. -.It Cm prompt Bq on -.Nm ex -only. -Display a command prompt. -.It Cm readonly , ro Bq off -Mark the file and session as read-only. -.It Cm recdir Bq /var/tmp/vi.recover -The directory where recovery files are stored. -.It Cm redraw , re Bq off -.Nm vi -only. -Simulate an intelligent terminal on a dumb one. -This option is not yet implemented. -.It Cm remap Bq on -Remap keys until resolved. -.It Cm report Bq 5 -Set the number of lines about which the editor reports changes or yanks. -.It Cm ruler Bq off -.Nm vi -only. -Display a row/column ruler on the colon command line. -.It Cm scroll , scr Bq "window size / 2" -Set the number of lines scrolled. -.It Cm searchincr Bq off -Makes the -.Cm / -and -.Cm ?\& -commands incremental. -.It Cm sections , sect Bq "NHSHH HUnhsh" -.Nm vi -only. -Define additional section boundaries for the -.Cm [[ -and -.Cm ]] -commands. -.It Cm secure Bq off -Turns off all access to external programs. -.It Cm shell , sh Bo environment variable Ev SHELL , or Pa /bin/sh Bc -Select the shell used by the editor. -.It Cm shellmeta Bq ~{[*?$\`\(aq\&"\e -Set the meta characters checked to determine if file name expansion -is necessary. -.It Cm shiftwidth , sw Bq 8 -Set the autoindent and shift command indentation width. -.It Cm showmatch , sm Bq off -.Nm vi -only. -Note the left matching characters when the right ones are inserted. -.It Cm showmode , smd Bq off -.Nm vi -only. -Display the current editor mode and a -.Dq modified -flag. -.It Cm sidescroll Bq 16 -.Nm vi -only. -Set the amount a left-right scroll will shift. -.It Cm slowopen , slow Bq off -Delay display updating during text input. -This option is not yet implemented. -.It Cm sourceany Bq off -Read startup files not owned by the current user. -This option will never be implemented. -.It Cm tabstop , ts Bq 8 -This option sets tab widths for the editor display. -.It Cm taglength , tl Bq 0 -Set the number of significant characters in tag names. -.It Cm tags , tag Bq tags -Set the list of tags files. -.It Xo -.Cm term , ttytype , tty -.Bq environment variable Ev TERM -.Xc -Set the terminal type. -.It Cm terse Bq off -This option has historically made editor messages less verbose. -It has no effect in this implementation. -.It Cm tildeop Bq off -Modify the -.Cm ~ -command to take an associated motion. -.It Cm timeout , to Bq on -Time out on keys which may be mapped. -.It Cm ttywerase Bq off -.Nm vi -only. -Select an alternate erase algorithm. -.It Cm verbose Bq off -.Nm vi -only. -Display an error message for every error. -.It Cm w300 Bq no default -.Nm vi -only. -Set the window size if the baud rate is less than 1200 baud. -.It Cm w1200 Bq no default -.Nm vi -only. -Set the window size if the baud rate is equal to 1200 baud. -.It Cm w9600 Bq no default -.Nm vi -only. -Set the window size if the baud rate is greater than 1200 baud. -.It Cm warn Bq on -.Nm ex -only. -This option causes a warning message to be printed on the terminal -if the file has been modified since it was last written, before a -.Cm !\& -command. -.It Xo -.Cm window , w , wi -.Bq environment variable Ev LINES No \(mi 1 -.Xc -Set the window size for the screen. -.It Cm windowname Bq off -Change the icon/window name to the current file name. -.It Cm wraplen , wl Bq 0 -.Nm vi -only. -Break lines automatically, -the specified number of columns from the left-hand margin. -If both the -.Cm wraplen -and -.Cm wrapmargin -edit options are set, the -.Cm wrapmargin -value is used. -.It Cm wrapmargin , wm Bq 0 -.Nm vi -only. -Break lines automatically, -the specified number of columns from the right-hand margin. -If both the -.Cm wraplen -and -.Cm wrapmargin -edit options are set, the -.Cm wrapmargin -value is used. -.It Cm wrapscan , ws Bq on -Set searches to wrap around the end or beginning of the file. -.It Cm writeany , wa Bq off -Turn off file-overwriting checks. -.El -.Sh ENVIRONMENT -.Bl -tag -width "COLUMNS" -.It Ev COLUMNS -The number of columns on the screen. -This value overrides any system or terminal specific values. -If the -.Ev COLUMNS -environment variable is not set when -.Nm ex Ns / Ns Nm vi -runs, or the -.Cm columns -option is explicitly reset by the user, -.Nm ex Ns / Ns Nm vi -enters the value into the environment. -.It Ev EXINIT -A list of -.Nm ex -startup commands, read after -.Pa /etc/vi.exrc -unless the variable -.Ev NEXINIT -is also set. -.It Ev HOME -The user's home directory, used as the initial directory path for the startup -.Pa $HOME/.nexrc -and -.Pa $HOME/.exrc -files. -This value is also used as the default directory for the -.Cm cd -command. -.It Ev LINES -The number of rows on the screen. -This value overrides any system or terminal specific values. -If the -.Ev LINES -environment variable is not set when -.Nm ex Ns / Ns Nm vi -runs, or the -.Cm lines -option is explicitly reset by the user, -.Nm ex Ns / Ns Nm vi -enters the value into the environment. -.It Ev NEXINIT -A list of -.Nm ex -startup commands, read after -.Pa /etc/vi.exrc . -.It Ev SHELL -The user's shell of choice -.Pq see also the Cm shell No option . -.It Ev TERM -The user's terminal type. -The default is the type -.Dq unknown . -If the -.Ev TERM -environment variable is not set when -.Nm ex Ns / Ns Nm vi -runs, or the -.Cm term -option is explicitly reset by the user, -.Nm ex Ns / Ns Nm vi -enters the value into the environment. -.It Ev TMPDIR -The location used to store temporary files -.Pq see also the Cm directory No edit option . -.El -.Sh ASYNCHRONOUS EVENTS -.Bl -tag -width "SIGWINCH" -compact -.It Dv SIGALRM -.Nm vi Ns / Ns Nm ex -uses this signal for periodic backups of file modifications and to display -.Dq busy -messages when operations are likely to take a long time. -.Pp -.It Dv SIGHUP -.It Dv SIGTERM -If the current buffer has changed since it was last written in its entirety, -the editor attempts to save the modified file so it can be later recovered. -See the -.Nm vi Ns / Ns Nm ex -reference manual section -.Sx Recovery -for more information. -.Pp -.It Dv SIGINT -When an interrupt occurs, the current operation is halted -and the editor returns to the command level. -If interrupted during text input, -the text already input is resolved into the file as if the text -input had been normally terminated. -.Pp -.It Dv SIGWINCH -The screen is resized. -See the -.Nm vi Ns / Ns Nm ex -reference manual section -.Sx Sizing the Screen -for more information. -.\" .Pp -.\" .It Dv SIGCONT -.\" .It Dv SIGTSTP -.\" .Nm vi Ns / Ns Nm ex -.\" ignores these signals. -.El -.Sh FILES -.Bl -tag -width "/var/tmp/vi.recover" -.It Pa /bin/sh -The default user shell. -.It Pa /etc/vi.exrc -System-wide -.Nm vi -startup file. -It is read for -.Nm ex -commands first in the startup sequence. -Must be owned by root or the user, -and writable only by the owner. -.It Pa /tmp -Temporary file directory. -.It Pa /var/tmp/vi.recover -The default recovery file directory. -.It Pa $HOME/.nexrc -First choice for user's home directory startup file, read for -.Nm ex -commands right after -.Pa /etc/vi.exrc -unless either -.Ev NEXINIT -or -.Ev EXINIT -are set. -Must be owned by root or the user, -and writable only by the owner. -.It Pa $HOME/.exrc -Second choice for user's home directory startup file, read for -.Nm ex -commands under the same conditions as -.Pa $HOME/.nexrc . -.It Pa .nexrc -First choice for local directory startup file, read for -.Nm ex -commands at the end of the startup sequence if the -.Cm exrc -option was turned on earlier. -Must be owned by the user -and writable only by the owner. -.It Pa .exrc -Second choice for local directory startup file, read for -.Nm ex -commands under the same conditions as -.Pa .nexrc . -.El -.Sh EXIT STATUS -The -.Nm ex -and -.Nm vi -utilities exit 0 on success, -and \*(Gt0 if an error occurs. -.Sh SEE ALSO -.Xr ctags 1 , -.Xr iconv 1 , -.Xr re_format 7 -.Sh STANDARDS -.Nm nex Ns / Ns Nm nvi -is close to -.St -p1003.1-2008 . -That document differs from historical -.Nm ex Ns / Ns Nm vi -practice in several places; there are changes to be made on both sides. -.Sh HISTORY -The -.Nm ex -editor first appeared in -.Bx 1 . -The -.Nm nex Ns / Ns Nm nvi -replacements for the -.Nm ex Ns / Ns Nm vi -editor first appeared in -.Bx 4.4 . -.Sh AUTHORS -.An Bill Joy -wrote the original version of -.Nm ex -in 1977. Property changes on: head/contrib/nvi/docs/man/vi.1 ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Deleted: svn:keywords ## -1 +0,0 ## -FreeBSD=%H \ No newline at end of property Deleted: svn:mime-type ## -1 +0,0 ## -text/plain \ No newline at end of property Index: head/contrib/nvi/docs/man/Makefile =================================================================== --- head/contrib/nvi/docs/man/Makefile (revision 365498) +++ head/contrib/nvi/docs/man/Makefile (nonexistent) @@ -1,16 +0,0 @@ -# @(#)Makefile 8.7 (Berkeley) 8/18/96 - -ROFF= groff - -all: vi.0 vi.0.ps - -vi.0: vi.1 - ${ROFF} -man -Tascii < vi.1 > $@ - chmod 444 $@ - -vi.0.ps: vi.1 - ${ROFF} -man < vi.1 > $@ - chmod 444 $@ - -clean: - rm -f vi.0 vi.0.ps Property changes on: head/contrib/nvi/docs/man/Makefile ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Deleted: svn:keywords ## -1 +0,0 ## -FreeBSD=%H \ No newline at end of property Deleted: svn:mime-type ## -1 +0,0 ## -text/plain \ No newline at end of property Index: head/contrib/nvi/.gitignore =================================================================== --- head/contrib/nvi/.gitignore (nonexistent) +++ head/contrib/nvi/.gitignore (revision 365499) @@ -0,0 +1,9 @@ +*.swp +*~ +*.orig +*.core +extern.h +*_def.h +version.h +tags +build/ Index: head/contrib/nvi/CMakeLists.txt =================================================================== --- head/contrib/nvi/CMakeLists.txt (nonexistent) +++ head/contrib/nvi/CMakeLists.txt (revision 365499) @@ -0,0 +1,190 @@ +cmake_minimum_required(VERSION 3.9) + +get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(is_multi_config) + set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING + "Semicolon separated list of supported configuration types") + mark_as_advanced(CMAKE_CONFIGURATION_TYPES) +elseif(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_C_FLAGS) + message(WARNING "No CMAKE_BUILD_TYPE is selected") +endif() + +project(nvi2 C) + +include(CheckIncludeFiles) +include(CheckFunctionExists) +include(CheckCSourceCompiles) + +mark_as_advanced(CMAKE_INSTALL_PREFIX) + +option(USE_WIDECHAR "Enable wide character support" ON) +option(USE_ICONV "Enable iconv support" ON) + +add_compile_options(-fcolor-diagnostics) +add_compile_options($<$:-Wall>) +add_compile_options($<$:-Wno-parentheses>) +add_compile_options($<$:-Wno-uninitialized>) +add_compile_options($<$:-Wmissing-prototypes>) +add_compile_options($<$:-Wsystem-headers>) +add_compile_options($<$:-Wuninitialized>) +add_compile_options($<$:-Wno-dangling-else>) +add_compile_options(-Wstack-protector -fstack-protector) +add_compile_options(-Wstrict-aliasing -fstrict-aliasing) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +set(MAIN_PROTOS + cl/extern.h common/extern.h ex/extern.h vi/extern.h + common/options_def.h ex/ex_def.h ex/version.h) + +set(CL_SRCS + cl/cl_funcs.c cl/cl_main.c cl/cl_read.c cl/cl_screen.c cl/cl_term.c) + +set(COMMON_SRCS + common/conv.c common/cut.c common/delete.c common/encoding.c common/exf.c + common/key.c common/line.c common/log.c common/main.c common/mark.c + common/msg.c common/options.c common/options_f.c common/put.c + common/recover.c common/screen.c common/search.c common/seq.c + common/util.c) + +set(EX_SRCS + ex/ex.c ex/ex_abbrev.c ex/ex_append.c ex/ex_args.c ex/ex_argv.c ex/ex_at.c + ex/ex_bang.c ex/ex_cd.c ex/ex_cmd.c ex/ex_cscope.c ex/ex_delete.c + ex/ex_display.c ex/ex_edit.c ex/ex_equal.c ex/ex_file.c ex/ex_filter.c + ex/ex_global.c ex/ex_init.c ex/ex_join.c ex/ex_map.c ex/ex_mark.c + ex/ex_mkexrc.c ex/ex_move.c ex/ex_open.c ex/ex_preserve.c ex/ex_print.c + ex/ex_put.c ex/ex_quit.c ex/ex_read.c ex/ex_screen.c ex/ex_script.c + ex/ex_set.c ex/ex_shell.c ex/ex_shift.c ex/ex_source.c ex/ex_stop.c + ex/ex_subst.c ex/ex_tag.c ex/ex_txt.c ex/ex_undo.c ex/ex_usage.c + ex/ex_util.c ex/ex_version.c ex/ex_visual.c ex/ex_write.c ex/ex_yank.c + ex/ex_z.c) + +set(VI_SRCS + vi/getc.c vi/v_at.c vi/v_ch.c vi/v_cmd.c vi/v_delete.c vi/v_ex.c + vi/v_increment.c vi/v_init.c vi/v_itxt.c vi/v_left.c vi/v_mark.c + vi/v_match.c vi/v_paragraph.c vi/v_put.c vi/v_redraw.c vi/v_replace.c + vi/v_right.c vi/v_screen.c vi/v_scroll.c vi/v_search.c vi/v_section.c + vi/v_sentence.c vi/v_status.c vi/v_txt.c vi/v_ulcase.c vi/v_undo.c + vi/v_util.c vi/v_word.c vi/v_xchar.c vi/v_yank.c vi/v_z.c vi/v_zexit.c + vi/vi.c vi/vs_line.c vi/vs_msg.c vi/vs_refresh.c vi/vs_relative.c + vi/vs_smap.c vi/vs_split.c) + +set(REGEX_SRCS + regex/regcomp.c regex/regerror.c regex/regexec.c regex/regfree.c) + +# commands to generate the public headers +set(extract_protos sed -n 's/^ \\* PUBLIC: \\\(.*\\\)/\\1/p') +set(extract_version sed -n + 's/^.*version \\\([^\)]*\)\\\).*/\#define VI_VERSION \\\"\\1\\\"/p') + +add_custom_command(OUTPUT cl/extern.h + COMMAND ${extract_protos} ${CL_SRCS} > cl/extern.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${CL_SRCS}) +add_custom_command(OUTPUT common/extern.h + COMMAND ${extract_protos} ${COMMON_SRCS} > common/extern.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${COMMON_SRCS}) +add_custom_command(OUTPUT ex/extern.h + COMMAND ${extract_protos} ${EX_SRCS} > ex/extern.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${EX_SRCS}) +add_custom_command(OUTPUT vi/extern.h + COMMAND ${extract_protos} ${VI_SRCS} > vi/extern.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${VI_SRCS}) +add_custom_command(OUTPUT common/options_def.h + COMMAND awk -f common/options.awk + common/options.c > common/options_def.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS common/options.c) +add_custom_command(OUTPUT ex/ex_def.h + COMMAND awk -f ex/ex.awk ex/ex_cmd.c > ex/ex_def.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ex/ex_cmd.c) +add_custom_command(OUTPUT ex/version.h + COMMAND ${extract_version} README > ex/version.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS README) + +add_executable(nvi) +target_sources(nvi PRIVATE ${MAIN_PROTOS} ${CL_SRCS} ${COMMON_SRCS} + ${EX_SRCS} ${VI_SRCS}) +target_compile_definitions(nvi PRIVATE $<$:DEBUG> + $<$:COMLOG>) + +check_function_exists(openpty UTIL_IN_LIBC) +if(NOT UTIL_IN_LIBC) + find_library(UTIL_LIBRARY util) + target_link_libraries(nvi PRIVATE ${UTIL_LIBRARY}) +endif() + +check_function_exists(__b64_ntop RESOLV_IN_LIBC) +if(NOT RESOLV_IN_LIBC) + find_library(RESOLV_LIBRARY resolv) + target_link_libraries(nvi PRIVATE ${RESOLV_LIBRARY}) +endif() + +if(USE_WIDECHAR) + find_library(CURSES_LIBRARY NAMES ncursesw cursesw curses HINTS /usr/lib) + + # link to the wchar_t awared BSD libregex.a + add_library(regex STATIC) + target_sources(regex PRIVATE ${REGEX_SRCS}) + target_include_directories(regex PUBLIC regex) + target_compile_definitions(regex PUBLIC __REGEX_PRIVATE) + target_link_libraries(nvi PRIVATE regex) +else() + find_library(CURSES_LIBRARY NAMES ncurses curses HINTS /usr/lib) + target_compile_options(nvi PRIVATE -Wno-pointer-sign) +endif() + +target_link_libraries(nvi PRIVATE ${CURSES_LIBRARY}) + +if(USE_ICONV) + check_function_exists(__iconv ICONV_IN_LIBC) + if(NOT ICONV_IN_LIBC) + find_path(ICONV_INCLUDE_DIR iconv.h) + find_library(ICONV_LIBRARY iconv) + endif() + + # detect the prototype of iconv(3) + set(CMAKE_C_FLAGS_BACKUP "${CMAKE_C_FLAGS}") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror") + set(CMAKE_REQUIRED_INCLUDES "${ICONV_INCLUDE_DIR}") + set(CMAKE_REQUIRED_LIBRARIES "${ICONV_LIBRARY}") + check_c_source_compiles(" + #include + int main() { + iconv_t conv = 0; + char* in = 0; + size_t ilen = 0; + char* out = 0; + size_t olen = 0; + iconv(conv, &in, &ilen, &out, &olen); + return 0; + } + " ICONV_TRADITIONAL) + set(CMAKE_REQUIRED_INCLUDES) + set(CMAKE_REQUIRED_LIBRARIES) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS_BACKUP}") + + target_include_directories(nvi PRIVATE ${ICONV_INCLUDE_DIR}) + target_link_libraries(nvi PRIVATE ${ICONV_LIBRARY}) +endif() + +check_include_files(libutil.h HAVE_LIBUTIL_H) +check_include_files(ncurses.h HAVE_NCURSES_H) +check_include_files(term.h HAVE_TERM_H) + +configure_file(files/config.h.in config.h) + +set(vi_cv_path_preserve /var/tmp/vi.recover/) +if(APPLE) + set(vi_cv_path_msgcat /usr/local/share/vi/catalog/) +else() + set(vi_cv_path_msgcat /usr/share/vi/catalog/) +endif() + +configure_file(files/pathnames.h.in pathnames.h) +configure_file(files/recover.in recover @ONLY) Property changes on: head/contrib/nvi/CMakeLists.txt ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/nvi/LICENSE =================================================================== --- head/contrib/nvi/LICENSE (revision 365498) +++ head/contrib/nvi/LICENSE (revision 365499) @@ -1,41 +1,37 @@ -/*- - * $Id: LICENSE,v 8.18 2011/07/10 11:58:35 zy Exp $ - */ - The following are the copyrights and redistribution conditions that apply to this copy of the Vi software. /* * Copyright (c) 1991, 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000 * Keith Bostic. All rights reserved. * Copyright (c) 1999, 2000 * Sven Verdoolaege. All rights reserved. * Copyright (c) 2011 * Zhihao Yuan. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ Index: head/contrib/nvi/README =================================================================== --- head/contrib/nvi/README (revision 365498) +++ head/contrib/nvi/README (revision 365499) @@ -1,66 +1,64 @@ -# $Id: README,v 9.2 2015/04/08 17:18:56 zy Exp $ - -This is version 2.1.3 (2015-04-08) of nex/nvi, a reimplementation of the ex/vi +This is version 2.2.0 (2020-08-01) of nex/nvi, a reimplementation of the ex/vi text editors originally distributed as part of the Fourth Berkeley Software Distribution (4BSD), by the University of California, Berkeley. The directory layout is as follows: LICENSE ....... Copyright, use and redistribution information. README ........ This file. - build ......... Build directory. catalog ....... Message catalogs; see catalog/README. cl ............ Vi interface to the curses(3) library. common ........ Code shared by ex and vi. - docs .......... Ex/vi documentation, both current and historic. ex ............ Ex source code. + files ......... Template files. + man ........... Ex/vi documentation. regex ......... Modified regex library with wide character support. vi ............ Vi source code. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= o Nvi was written by Keith Bostic, and the last version is 1.79. After that, Sven Verdoolaege added the iconv support and the DB3 locking. Jun-ichiro itojun Hagino developed the file encoding detection techniques in his nvi-m17n. The following acknowledgments were written by Keith Bostic: =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= -o This software is several years old and is the product of many folks' work. +o This software is several years old and is the product of many folks' work. This software was originally derived from software contributed to the University of California, Berkeley by Steve Kirkendall, the author of the vi clone elvis. Without his work, this work would have been far more difficult. IEEE POSIX 1003.2 style regular expression support is courtesy of Henry Spencer, for which I am *very* grateful. Elan Amir did the original 4BSD curses work that made it possible to support a full-screen editor using curses. George Neville-Neil added the Tcl interpreter, and the initial interpreter design was his. Sven Verdoolaege added the Perl interpreter. Rob Mayoff provided the original Cscope support. o Many, many people suggested enhancements, and provided bug reports and testing, far too many to individually thank. o From the original vi acknowledgements, by William Joy and Mark Horton: Bruce Englar encouraged the early development of this display editor. Peter Kessler helped bring sanity to version 2's command layout. Bill Joy wrote versions 1 and 2.0 through 2.7, and created the framework that users see in the present editor. Mark Horton added macros and other features and made the editor work on a large number of terminals and Unix systems. o And... The financial support of UUNET Communications Services is gratefully acknowledged. Index: head/contrib/nvi/catalog/Makefile =================================================================== --- head/contrib/nvi/catalog/Makefile (revision 365498) +++ head/contrib/nvi/catalog/Makefile (revision 365499) @@ -1,78 +1,77 @@ -# $Id: Makefile,v 9.0 2012/10/19 15:13:11 zy Exp $ - -CAT= dutch english french german polish ru_RU.KOI8-R spanish swedish \ - uk_UA.KOI8-U zh_CN.GB2312 +CAT= dutch english french german polish ru_RU.KOI8-R spanish \ + swedish tr_TR.ISO8859-9 tr_TR.UTF-8 uk_UA.KOI8-U zh_CN.GB2312 FILES= ../cl/*.c ../common/*.c ../ex/*.c ../vi/*.c all: dump ${CAT} ${CAT}: english.base @echo "... $@"; \ rm -f $@; \ sort -u $@.base | \ awk '{ \ if ($$1 == 1) { \ print "\nMESSAGE NUMBER 1 IS NOT LEGAL"; \ exit 1; \ } \ if (++nline > $$1) { \ print "DUPLICATE MESSAGE NUMBER " $$1; \ exit 1; \ } \ print $0; \ }' | \ sed -e '1s/^/$$set 1~$$quote "~/; 1y/~/\n/' | \ gencat $@ /dev/stdin; \ chmod 444 $@; \ if grep DUPLICATE $@ > /dev/null; then \ grep DUPLICATE $@; \ fi; \ if grep 'NOT LEGAL' $@ > /dev/null; then \ grep 'NOT LEGAL' $@; \ fi CHK= dutch.check english.check french.check german.check \ polish.check ru_RU.KOI8-R.check spanish.check swedish.check \ - uk_UA.KOI8-U.check zh_CN.GB2312.check + tr_TR.ISO8859-9.check tr_TR.UTF-8.check uk_UA.KOI8-U.check \ + zh_CN.GB2312.check check: ${CHK} ${CHK}: ${CAT} @echo "... $@"; \ f=`basename $@ .check`; \ (echo "Unused message id's (this is okay):"; \ awk '{ \ while (++nline < $$1) \ printf "%03d\n", nline; \ }' < $$f.base; \ echo =========================; \ echo "MISSING ERROR MESSAGES (Please add!):"; \ awk '{print $$1}' < $$f.base | sort -u > __ck1; \ awk '{print $$1}' < english.base | sort -u > __ck2; \ comm -13 __ck1 __ck2; \ echo =========================; \ echo "Extra error messages (just delete them):"; \ comm -23 __ck1 __ck2; \ echo =========================; \ echo "MESSAGES WITH THE SAME MESSAGE ID's (FIX!):"; \ for j in \ `sed '/^$$/d' < $$f.base | sort -u | \ awk '{print $$1}' | uniq -d`; do \ egrep $$j $$f.base; \ done; \ echo =========================; \ echo "Duplicate messages, both id and message (this is okay):"; \ sed '/^$$/d' < $$f.base | sort | uniq -c | \ awk '$$1 != 1 { print $$0 }' | sort -n; \ echo =========================) > $@ english.base: dump ${FILES} #Makefile ./dump ${FILES} |\ sed -e '/|/!d' \ -e 's/|/ "/' \ -e 's/^"//' |\ sort -nu > $@ dump: dump.c ${CC} -O -o dump dump.c clean: rm -f dump dump.o ${CAT} english.base *.check __ck1 __ck2 Index: head/contrib/nvi/catalog/README =================================================================== --- head/contrib/nvi/catalog/README (revision 365498) +++ head/contrib/nvi/catalog/README (revision 365499) @@ -1,127 +1,125 @@ -# $Id: README,v 9.0 2012/10/19 17:06:15 zy Exp $ - Generally, all non-system error and informational messages in nvi are catalog messages, i.e. they can be tailored to a specific langauge. Command strings, usage strings, system errors and other 'known text' are not. Message catalogs in nvi are fairly simple. Every catalog message consists of two parts -- an initial number followed by a pipe (`|') character, followed by the English text for the message. For example: msgq(sp, M_ERR, "001|This is an error message"); would be a typical message. When the msgq() routine is called, if the user has specified a message catalog and the format string (the third argument) has a leading number, then it is converted to a record number, and that record is retrieved from the message catalog and used as a replacement format string. If the record can't be retrieved for any reason, the English text is displayed instead. Each message format string MUST map into the English format string, i.e. it can't display more or different arguments than the English one. For example: msgq(sp, M_ERR, "002|Error: %d %x", arg1, arg2); is a format string that displays two arguments. Arguments to the msgq function are required to contain ONLY printable characters. No further translation is done by the msgq routine before displaying the message on the screen. For example, in the msgq call: msgq(sp, M_ERR, "003|File: %s", file_name); "file_name" must contain only printable characters. The routine msg_print() returns a printable version of a string; the third argument indicates whether the string needs to be freed. For example: char *p; int nf; p = msg_print(sp, file_name, &nf); msgq(sp, M_ERR, "003|File: %s", p); if (nf) FREE_SPACE(sp, p, 0); makes sure that "file_name" is printable before calling the msgq routine. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= The message catalogs themselves are maintained in two files. The first is the "base file" which contains two fields, a record number and the message itself. All base files are named using the convention ".base", e.g. the English one is "english.base". For example: 002 "Line length overflow" 003 "unable to delete line %lu" 004 "unable to append to line %lu" 005 "unable to insert at line %lu" 006 "unable to store line %lu" 007 "unable to get last line" are the first few lines of the current english.base file. Before this file being converted to the second file, the POSIX formatted message catalog file, by gencat(1), two lines: $set 1 $quote " will be inserted before the base text to setup the set_id and the quote character. So the double-quote needs to be escaped by a backslash to be included in a message; same as the backslash itself. These files are named for their language, e.g. "english". However, a locale(1) name is also recommended. To create a new catalog for nvi: Copy the file english.base to a file that you can modify , e.g. "cp english.base german.base". For each of the messages in the file, replace the message with the string that you want to use. If you have doubts about the meaning of a message, just email me. A latest english.base can be created from source by running the command "make english" in the catalog/ directory. Once you've translated all of the strings, then add your catalog to the "CAT=" line of the Makefile, and run the command "make catalog". This will create the second (and corresponding) file for each file named .base. Don't worry about missing line numbers, i.e. base files that look like: 005 Message number 5. 007 Message number 7. This simply means that a message was deleted during the course of nvi's development. It will be taken care of automatically when you create the second form of the file. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= If you add new messages to the nvi sources, you can check your work by doing "make english; make check". The "make check" target lists unused message numbers, duplicate message numbers, and duplicate messages. Unused message numbers are only useful if you are condensing messages. Duplicate message numbers are a serious problem and have to be fixed. Duplicate messages are only interesting if a message appears often enough that it's worth creating a routine so that the string is only need in a single place. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= To select a catalog when running nvi, set the "msgcat" option. If the value of this option ends with a '/', it is treated as the name of a directory that contains a message catalog "$LC_MESSAGES", which is set through the LC_MESSAGES environment variable but returned by setlocale(3). Check the output of locale(1) to validate such a value. If the option doesn't end in a '/', the option is treated as the full path name of the message catalog to use. If any messages are missing from the catalog, the backup text (English) is used instead. Index: head/contrib/nvi/catalog/dump.c =================================================================== --- head/contrib/nvi/catalog/dump.c (revision 365498) +++ head/contrib/nvi/catalog/dump.c (revision 365499) @@ -1,101 +1,98 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ -#ifndef lint -static char sccsid[] = "@(#)dump.c 8.1 (Berkeley) 8/31/94"; -#endif /* not lint */ - +#include #include #include static void parse(FILE *fp) { int ch, s1, s2, s3; #define TESTD(s) { \ if ((s = getc(fp)) == EOF) \ return; \ if (!isdigit(s)) \ continue; \ } #define TESTP { \ if ((ch = getc(fp)) == EOF) \ return; \ if (ch != '|') \ continue; \ } #define MOVEC(t) { \ do { \ if ((ch = getc(fp)) == EOF) \ return; \ } while (ch != (t)); \ } for (;;) { MOVEC('"'); TESTD(s1); TESTD(s2); TESTD(s3); TESTP; putchar('"'); putchar(s1); putchar(s2); putchar(s3); putchar('|'); for (;;) { /* dump to end quote. */ if ((ch = getc(fp)) == EOF) return; putchar(ch); if (ch == '"') break; if (ch == '\\') { if ((ch = getc(fp)) == EOF) return; putchar(ch); } } putchar('\n'); } } int main(int argc, char *argv[]) { FILE *fp; for (; *argv != NULL; ++argv) { if ((fp = fopen(*argv, "r")) == NULL) { perror(*argv); return (1); } parse(fp); (void)fclose(fp); } return (0); } Index: head/contrib/nvi/catalog/tr_TR.ISO8859-9.base =================================================================== --- head/contrib/nvi/catalog/tr_TR.ISO8859-9.base (nonexistent) +++ head/contrib/nvi/catalog/tr_TR.ISO8859-9.base (revision 365499) @@ -0,0 +1,304 @@ +002 "Satýr uzunluðu taþýmý" +003 "%lu numaralý satýr silinemiyor" +004 "%lu numaralý satýra iliþtirilemiyor" +005 "%lu numaralý satýrda ekleme yapýlamýyor" +006 "%lu numaralý satýr kaydedilemiyor" +007 "Son satýr alýnamýyor" +008 "Hata: %lu numaralý satýr okunamýyor" +009 "Günlük dosyasý" +010 "Günlükleme gerçekleþtirilmiyor, geri alma olanaklý deðil" +011 "Geri alýnacak bir deðiþiklik yok" +012 "Günlükleme gerçekleþtirilmiyor, geri alma olanaklý deðil" +013 "Günlükleme gerçekleþtirilmiyor, ileri sarma olanaklý deðil" +014 "Yinelenecek bir deðiþiklik yok" +015 "%s/%d: Günlük kayýt hatasý" +016 "Vi'nin standart girdisi ve çýktýsý bir uçbirim olmalý" +017 "Ým %s: Ayarlanmamýþ" +018 "Ým %s: Satýr silinmiþ" +019 "Ým %s: Ýmleç konumu artýk yok" +020 "Hata: " +021 "yeni dosya +022 "adý deðiþti" +023 "deðiþtirildi" +024 "deðiþtirilmedi" +025 "KÝLÝDÝ AÇILDI" +026 "saltokunur" +027 "satýr %lu/%lu [%ld%%]" +028 "boþ dosya" +029 "%lu. satýr" +030 "%s dosyasý bir ileti kataloðu deðil" +031 "Öntanýmlý %s seçeneði ayarlanamadý" +032 "Kullaným: %s" +033 "set: %s seçeneði yok: 'set all' tüm seçenek deðerlerini verir" +034 "set: [no]%s seçeneði bir deðer almaz" +035 "set: %s seçeneði bir Boole deðeri deðil" +036 "set: %s seçeneði: %s" +037 "set: %s seçeneði: %s: Deðer taþýmý" +038 "set: %s seçeneði: %s izin verilmeyen bir sayý" +039 "set: %s seçeneði bir Boole deðeri deðil" +040 "Ekran sütun sayýsý çok küçük, þundan daha az: %d" +041 "Ekran sütun sayýsý çok büyük, þundan daha çok: %d" +042 "Ekran satýr sayýsý çok küçük, þundan daha az: %d" +043 "Ekran satýr sayýsý çok büyük, þundan daha çok: %d" +044 "Lisp seçeneði eklenmemiþ" +045 "Ýletiler kapatýlmamýþ: %s" +046 "Ýletiler açýlmamýþ: %s" +047 "%s seçeneði iki karakterli öbekler biçiminde olmalý" +053 "Baþlangýç arabelleði boþ" +054 "%s arabelleði boþ" +056 "Oturum baþarýsýz olursa deðiþiklikler kurtarýlamaz" +057 "Dosya, kurtarma için kopyalanýyor..." +058 "Koruma baþarýsýz oldu: %s" +059 "Oturum baþarýsýz olursa deðiþiklikler kurtarýlamaz" +060 "Dosya yedeklemesi baþarýsýz oldu: %s" +061 "Dosya, kurtarma için kopyalanýyor..." +062 "%u kullanýcýsý üzerine bilgi bulunamadý" +063 "Kurtarma dosyasý kilitlenemiyor" +065 "Kurtarma dosyasý" +066 "%s: Hatalý oluþturulmuþ kurtarma dosyasý" +067 "%s: Hatalý oluþturulmuþ kurtarma dosyasý" +068 "Kurtarýlacak %s adýnda sizce okunabilir bir dosya yok" +069 "Bu dosyanýn kurtarabileceðiniz eski sürümleri var" +070 "Kurtarabileceðiniz baþka dosyalar var" +071 "E-posta gönderilmedi: %s" +072 "Dosya boþ, aranacak bir þey yok" +073 "Dizgi bulunamadan dosyanýn sonuna eriþildi" +074 "Arama dizgisi yok" +075 "Dizgi bulunamadý" +076 "Dizgi bulunamadan dosyanýn baþýna eriþildi" +077 "Arama tamamlandý" +078 "Aranýyor..." +079 "Yazdýrýlamaz karakter bulunmadý" +080 "Bilinmeyen komut adý" +082 "%s: Komut ex kipinde kullanýlamaz" +083 "Sayým sýfýr olmayabilir" +084 "%s: Hatalý satýr belirtimi" +085 "Ýç sözdizim tablo hatasý (%s: %s)" +086 "Kullaným: %s" +087 "%s: Geçici arabellek salýverilmedi" +088 "Bayrak göreli konumu birinci satýrdan önceye" +089 "Bayrak göreli konumu dosya sonunu geçiyor" +090 "Erimli @, dosya/ekran deðiþtiðinde çalýþýr" +091 "Global/v komutu dosya/ekran deðiþtiðinde çalýþýr" +092 "Ex komutu baþarýsýz: Sýradaki komutlar çýkarýldý" +093 "Ex komutu baþarýsýz: Eþlemlenen düðmeler çýkarýldý" +094 "Ýkinci adres ilkinden daha küçük" +095 "Bir im adý saðlanmadý" +096 "\\ sonrasýnda / veya ? gelmiyor" +097 "Bir satýr numarasýna yapýlan baþvuru sayýsý sýfýrdan az" +098 "Bilinmeyen komut: %s" +099 "Adres deðeri taþýmý" +100 "Adres deðeri alttaþýmý" +101 "Ýzin verilmeyen adres birleþimi" +102 "Ýzin verilmeyen adres: Dosyada yalnýzca %lu satýr var" +103 "Ýzin verilmeyen adres: Dosya boþ" +104 "%s komutu bir 0 adresine izin vermiyor" +105 "Görüntülenecek kýsaltma yok" +106 "Kýsaltmalar bir \"sözcük\" karakteri ile bitmelidir" +107 "Kýsaltmalar sekme veya boþluk içeremez" +108 "Kýsaltmalar sonu dýþýnda sözcük/sözcük olmayan karakterleri karýþtýramaz" +109 "\"%s\" bir kýsaltma deðil" +110 "Vi komutu baþarýsýz: Eþlemlenen düðmeler çýkarýldý" +111 "Düzenlenecek baþka dosya yok" +112 "Öncesinde düzenlenecek baþka dosya yok" +113 "Öncesinde geri sarýlacak dosyalar yok" +114 "Görüntülenecek dosya yok" +115 "Öncesinde \"!\" ögesinin yerine geçecek bir komut yok" +116 "%% yerine geçecek bir dosya adý yok" +117 "# yerine geçecek bir dosya adý yok" +118 "Hata: execl: %s" +119 "Girdi/Çýktý hatasý: %s" +120 "Dosya son tam yazýmdan bu yana deðiþtirilmiþ; yazýn veya yok saymak için ! kullanýn" +121 "Ev dizini konumu bulunamýyor" +122 "Yeni þimdiki dizin: %s" +123 "Görüntülenecek kesilmiþ arabellek yok" +124 "%s komutu bir global veya v komutunun parçasý olarak kullanýlamaz" +128 "%s: kaynak alýnmýyor: Ýyesi siz veya kök deðil" +129 "%s: kaynak alýnmýyor: Ýyesi siz deðilsiniz" +130 "%s: kaynak alýnmýyor: Ýyesi dýþýnda baþka bir kullanýcý tarafýndan yazýlabilir" +131 "Uç uca eklenecek bir satýr yok" +132 "Girdi eþlem girdisi yok" +133 "Komut eþlem girdisi yok" +134 "%s yeniden eþlemlenemeyebilir" +135 "\"%s\" þu anda eþlemlenmemiþ" +136 "Ým adlarý tek bir karakter olmalýdýr" +137 "%s mevcut, yazýlmamýþ; yok saymak için ! kullanýn" +138 "Yeni exrc dosyasý: %s" +139 "Hedef satýrý taþýma erimi içinde" +140 "'open' komutu 'open' seçeneðinin ayarlý olmasýný gerektirir" +141 "'open' komutu henüz eklenmemiþ" +142 "Bu dosyanýn korunmasý olanaklý deðil" +143 "Dosya korundu" +144 "%s: Çok fazla dosya adýna geniþletilmiþ" +145 "Yalnýzca sýradan dosyalar ve adlandýrýlmýþ veri yollarý okunabilir" +146 "%s: Okuma kilidi kullanýlabilir deðildi" +147 "Okunuyor..." +148 "%s: %lu satýr, %lu karakter" +149 "Görüntülenecek arkaplan ekraný yok" +150 "'script' komutu yalnýzca vi kipinde kullanýlabilir" +151 "Çalýþtýrýlacak komut yok" +152 "'shiftwidth' seçeneði 0 olarak ayarlanmýþ" +153 "Sayým taþýmý" +154 "Sayým alttaþýmý" +155 "Düzenli ifade belirtilmiþ; r bayraðý anlamsýz" +156 "#, l ve p bayraklarý vi kipinde c bayraðý ile birlikte kullanýlamaz" +157 "Eþleþme bulunamadý" +158 "Önceden bir etiket girilmemiþ" +159 "Etiket yýðýnýnda %s sayýsýndan az girdi; :display t[ags] kullanýn" +160 "Etiket yýðýnýnda dönülecek bir %s dosyasý yok; :display t[ags] kullanýn" +161 "Sürdürmek için Enter'a basýn:" +162 "%s: Etiket bulunamadý" +163 "%s: %s içinde hasar görmüþ etiket" +164 "%s: Etiketin satýr numarasý dosya sonunu geçmiþ" +165 "Etiket yýðýný boþ" +166 "%s: Arama dizgisi bulunamadý" +167 "%d dosya daha düzenlenecek" +168 "%s arabelleði boþ" +169 "Deðiþikliði onayla? [n]" +170 "Yarýda kesildi" +171 "Öncesinde çalýþtýrýlacak arabellek yok" +172 "Öncesinde düzenli ifade yok" +173 "%s komutu bir dosyanýn halihazýrda okunmuþ olmasýný gerektirir" +174 "Kullaným: %s" +175 "'visual' komutu 'open' seçeneðinin ayarlanmýþ olmasýný gerektirir" +177 "Boþ dosya" +178 "Öncesinde F, f, T veya t aramasý yok" +179 "%s bulunamadý" +180 "Düzenlenecek dosya yok" +181 "Ýmleç bir sayýda deðil" +182 "Ortaya çýkan sayý çok büyük" +183 "Ortaya çýkan sayý çok küçük" +184 "Bu satýrda eþleþen karakter yok" +185 "Eþleþen karakter bulunamadý" +186 "Deðiþtirilecek karakter yok" +187 "Geçilecek baþka ekran yok" +188 "Arama dizgisinden, satýr ofsetinden ve/veya 'z' komutundan sonraki karakterler" +189 "Öncesinde arama dizgisi yok" +190 "Arama baþlanan konumda tamamlandý" +191 "Kýsaltma geniþleme sýnýrýný aþtý: Karakterler çýkarýldý" +192 "Ýzin verilmeyen karakter; giriþ için týrnak içine alýn" +193 "Halihazýrda eklemenin baþýnda" +194 "Silinecek baþka karakter yok" +195 "Hareket dosya sonunu geçti" +196 "Hareket satýr sonunu geçti" +197 "Ýmleç hareket ettirilmedi" +198 "Halihazýrda dosyanýn baþýnda" +199 "Hareket dosyanýn baþýný geçti" +200 "Halihazýrda ilk sütunda" +201 "Arabellekler komuttan önce belirtilmeli" +202 "Halihazýrda dosya sonunda" +203 "Halihazýrda satýr sonunda" +204 "%s bir vi komutu deðil" +205 "Kullaným: %s" +206 "Silinecek karakter yok" +207 "Q komutu ex uçbirim arabirimini gerektirir" +208 "Yinelenecek komut yok" +209 "Dosya boþ" +210 "%s bir hareket komutu olarak kullanýlamaz" +211 "Halihazýrda komut kipi içinde" +212 "Ýmleç bir sözcükte deðil" +214 "'windows' seçeneði deðeri çok büyük, en çok %u olabilir" +215 "Ýliþtir" +216 "Deðiþiklik yap" +217 "Komut" +218 "Ekle" +219 "Deðiþtir" +220 "Hareket ekran sonunu geçti" +221 "Hareket ekran baþýný geçti" +222 "Ekran bölünebilmesi için %d satýrdan daha geniþ olmalýdýr" +223 "Arkaplan ekraný yok" +224 "Arkaplanda %s dosyasýný düzenleyen bir ekran yok" +225 "Açýk olan tek ekranýnýzý arkaplana alamazsýnýz" +226 "Ekran yalnýzca %d satýra küçültülebilir" +227 "Ekran küçülemez" +228 "Ekran büyüyemez" +230 "Bu ekran askýya alýnamaz" +231 "Yarýda kesildi: Eþlemlenen düðmeler çýkarýldý" +232 "vi: Geçici arabellek salýverilmedi" +233 "Bu uçbirimde %s düðmesi yok" +234 "Yalnýzca bir arabellek belirtilebilir" +235 "Sayý þundan daha büyük: %lu" +236 "Yarýda kesildi" +237 "Geçici dosya oluþturulamýyor" +238 "Uyarý: %s sýradan bir dosya deðil" +239 "%s halihazýrda kilitlenmiþ, oturum saltokunur" +240 "%s: Kaldýr" +241 "%s: Kapat" +242 "%s: Kaldýr" +243 "%s: Kaldýr" +244 "Saltokunur dosya, yazýlmadý; yok saymak için ! kullanýn" +245 "Saltokunur dosya, yazýlmadý" +246 "%s mevcut, yazýlmadý; yok saymak için ! kullanýn" +247 "%s mevcut, yazýlmadý" +248 "Kýsmi dosya, yazýlmadý; yok saymak için ! kullanýn" +249 "Kýsmi dosya, yazýlmadý" +250 "%s: Dosya bu kopyadan daha önce deðiþtirilmiþ; yok saymak için ! kullanýn" +251 "%s: Dosya bu kopyadan daha önce deðiþtirilmiþ" +252 "%s: Yazým korumasý kullanýlabilir deðildi" +253 "Yazýlýyor..." +254 "%s: UYARI: DOSYA KIRPILMIÞ" +255 "Halihazýrda bu grubun ilk etiketinde" +256 "%s: Yeni dosya: %lu satýr, %lu karakter" +257 "%s: %lu satýr, %lu karakter" +258 "%s çok fazla dosya adýna geniþletilmiþ" +259 "%s: Sýradan bir dosya deðil" +260 "%s: Ýyesi siz deðilsiniz" +261 "%s: Ýyesi dýþýnda baþka bir kullanýcý tarafýndan da eriþilebilir" +262 "Dosya son tam yazýmdan bu yana deðiþtirilmiþ; yazýn veya yok saymak için ! kullanýn" +263 "Dosya son tam yazýmdan bu yana deðiþtirilmiþ; yazýn veya yok saymak için :edit! kullanýn" +264 "Dosya son tam yazýmdan bu yana deðiþtirilmiþ; yazýn veya yok saymak için ! kullanýn" +265 "Dosya geçici; çýkarsanýz deðiþiklikler atýlacaktýr" +266 "Dosya saltokunur, deðiþiklikler kendiliðinden yazýlmaz" +267 "Günlük yeniden baþlatýldý" +268 "Onayla? [ynq]" +269 "Sürdürmek için herhangi bir düðmeye basýn: " +270 "Sürdürmek için herhangi bir düðmeye basýn [ex komutu girmek için :]: " +271 "Sürdürmek için herhangi bir düðmeye basýn [çýkýþ için q]: " +272 "Bu biçim %s ex uçbirim arabirimini gerektirir" +273 "ex girdisi kipine giriliyor" +274 "Komut baþarýsýz, henüz bir dosya okunmadý." +275 " sürdür?" +276 "Beklenmedik karakter olayý" +277 "Beklenmedik dosya sonu olayý" +278 "Sorgu eþleþmesi bulunamadý" +279 "Beklenmedik yarýda kesme olayý" +281 "Beklenmedik yeniden boyama olayý" +282 "Halihazýrda bu grubun son etiketinde" +283 "%s komutu ex uçbirim arabirimini gerektirir" +284 "Bu biçim %s 'secure edit' seçeneði ayarlandýðýnda desteklenmez" +285 "Beklenmedik dizi olayý" +286 "Beklenmedik zaman aþýmý olayý" +288 "Bölünebilmesi için ekran %d sütundan daha geniþ olmalýdýr" +289 "Kabuk geniþletmeleri 'secure edit' seçeneði ayarlandýðýnda desteklenmez" +290 "%s komutu 'secure edit' seçeneði ayarlandýðýnda desteklenmez" +291 "set: %s seçeneði kapatýlamaz" +292 "Ekran çok küçük." +293 "eklendi" +294 "deðiþtirildi" +295 "silindi" +296 "uç uca eklendi" +297 "taþýndý" +298 "kaydýrýldý" +299 "kopyalandý" +300 "satýr" +301 "satýr" +303 "Dosya son yazýmdan bu yana deðiþtirilmiþ." +304 "Kabuk geniþletmesi baþarýsýz" +305 "Bir '%s' düzenleme seçeneði belirtilmedi" +307 "Çalýþtýrýlacak ex komutu yok" +308 "Bir komut çalýþtýrmak için , çýkmak için :q girin" +309 "Yardým için \"cscope help\" kullanýn" +310 "Çalýþan bir cscope baðlantýsý yok" +311 "%s: Bilinmeyen arama türü: Þunlardan bir tanesini kullanýn: %s" +312 "%d: Böyle bir cscope oturumu yok" +313 "set: %s seçeneði hiçbir zaman açýlamaz" +314 "set: %s seçeneði hiçbir zaman 0 olarak ayarlanamaz" +315 "%s: Ýliþtirildi: %lu satýr, %lu karakter" +316 "Beklenmedik yeniden boyutlandýrma olayý" +317 "Düzenlenecek %d dosya var" +319 "%d ekran arkaplana alýndý; onlarý listelemek için :display kullanýn" +320 "Bilinmeyen imleç konumu" +321 "Dosya kodlama dönüþtürmesi desteklenmiyor" +322 "Girdi kodlama dönüþtürmesi desteklenmiyor" +323 "Geçersiz girdi. Kýrpýldý." +324 "%d numaralý satýrda dönüþtürme hatasý" Index: head/contrib/nvi/catalog/tr_TR.ISO8859-9.owner =================================================================== --- head/contrib/nvi/catalog/tr_TR.ISO8859-9.owner (nonexistent) +++ head/contrib/nvi/catalog/tr_TR.ISO8859-9.owner (revision 365499) @@ -0,0 +1 @@ +Emir SARI Index: head/contrib/nvi/catalog/tr_TR.UTF-8.base =================================================================== --- head/contrib/nvi/catalog/tr_TR.UTF-8.base (nonexistent) +++ head/contrib/nvi/catalog/tr_TR.UTF-8.base (revision 365499) @@ -0,0 +1,304 @@ +002 "Satır uzunluÄŸu taşımı" +003 "%lu numaralı satır silinemiyor" +004 "%lu numaralı satıra iliÅŸtirilemiyor" +005 "%lu numaralı satırda ekleme yapılamıyor" +006 "%lu numaralı satır kaydedilemiyor" +007 "Son satır alınamıyor" +008 "Hata: %lu numaralı satır okunamıyor" +009 "Günlük dosyası" +010 "Günlükleme gerçekleÅŸtirilmiyor, geri alma olanaklı deÄŸil" +011 "Geri alınacak bir deÄŸiÅŸiklik yok" +012 "Günlükleme gerçekleÅŸtirilmiyor, geri alma olanaklı deÄŸil" +013 "Günlükleme gerçekleÅŸtirilmiyor, ileri sarma olanaklı deÄŸil" +014 "Yinelenecek bir deÄŸiÅŸiklik yok" +015 "%s/%d: Günlük kayıt hatası" +016 "Vi'nin standart girdisi ve çıktısı bir uçbirim olmalı" +017 "Ä°m %s: Ayarlanmamış" +018 "Ä°m %s: Satır silinmiÅŸ" +019 "Ä°m %s: Ä°mleç konumu artık yok" +020 "Hata: " +021 "yeni dosya +022 "adı deÄŸiÅŸti" +023 "deÄŸiÅŸtirildi" +024 "deÄŸiÅŸtirilmedi" +025 "KÄ°LÄ°DÄ° AÇILDI" +026 "saltokunur" +027 "satır %lu/%lu [%ld%%]" +028 "boÅŸ dosya" +029 "%lu. satır" +030 "%s dosyası bir ileti kataloÄŸu deÄŸil" +031 "Öntanımlı %s seçeneÄŸi ayarlanamadı" +032 "Kullanım: %s" +033 "set: %s seçeneÄŸi yok: 'set all' tüm seçenek deÄŸerlerini verir" +034 "set: [no]%s seçeneÄŸi bir deÄŸer almaz" +035 "set: %s seçeneÄŸi bir Boole deÄŸeri deÄŸil" +036 "set: %s seçeneÄŸi: %s" +037 "set: %s seçeneÄŸi: %s: DeÄŸer taşımı" +038 "set: %s seçeneÄŸi: %s izin verilmeyen bir sayı" +039 "set: %s seçeneÄŸi bir Boole deÄŸeri deÄŸil" +040 "Ekran sütun sayısı çok küçük, ÅŸundan daha az: %d" +041 "Ekran sütun sayısı çok büyük, ÅŸundan daha çok: %d" +042 "Ekran satır sayısı çok küçük, ÅŸundan daha az: %d" +043 "Ekran satır sayısı çok büyük, ÅŸundan daha çok: %d" +044 "Lisp seçeneÄŸi eklenmemiÅŸ" +045 "Ä°letiler kapatılmamış: %s" +046 "Ä°letiler açılmamış: %s" +047 "%s seçeneÄŸi iki karakterli öbekler biçiminde olmalı" +053 "BaÅŸlangıç arabelleÄŸi boÅŸ" +054 "%s arabelleÄŸi boÅŸ" +056 "Oturum baÅŸarısız olursa deÄŸiÅŸiklikler kurtarılamaz" +057 "Dosya, kurtarma için kopyalanıyor..." +058 "Koruma baÅŸarısız oldu: %s" +059 "Oturum baÅŸarısız olursa deÄŸiÅŸiklikler kurtarılamaz" +060 "Dosya yedeklemesi baÅŸarısız oldu: %s" +061 "Dosya, kurtarma için kopyalanıyor..." +062 "%u kullanıcısı üzerine bilgi bulunamadı" +063 "Kurtarma dosyası kilitlenemiyor" +065 "Kurtarma dosyası" +066 "%s: Hatalı oluÅŸturulmuÅŸ kurtarma dosyası" +067 "%s: Hatalı oluÅŸturulmuÅŸ kurtarma dosyası" +068 "Kurtarılacak %s adında sizce okunabilir bir dosya yok" +069 "Bu dosyanın kurtarabileceÄŸiniz eski sürümleri var" +070 "KurtarabileceÄŸiniz baÅŸka dosyalar var" +071 "E-posta gönderilmedi: %s" +072 "Dosya boÅŸ, aranacak bir ÅŸey yok" +073 "Dizgi bulunamadan dosyanın sonuna eriÅŸildi" +074 "Arama dizgisi yok" +075 "Dizgi bulunamadı" +076 "Dizgi bulunamadan dosyanın başına eriÅŸildi" +077 "Arama tamamlandı" +078 "Aranıyor..." +079 "Yazdırılamaz karakter bulunmadı" +080 "Bilinmeyen komut adı" +082 "%s: Komut ex kipinde kullanılamaz" +083 "Sayım sıfır olmayabilir" +084 "%s: Hatalı satır belirtimi" +085 "İç sözdizim tablo hatası (%s: %s)" +086 "Kullanım: %s" +087 "%s: Geçici arabellek salıverilmedi" +088 "Bayrak göreli konumu birinci satırdan önceye" +089 "Bayrak göreli konumu dosya sonunu geçiyor" +090 "Erimli @, dosya/ekran deÄŸiÅŸtiÄŸinde çalışır" +091 "Global/v komutu dosya/ekran deÄŸiÅŸtiÄŸinde çalışır" +092 "Ex komutu baÅŸarısız: Sıradaki komutlar çıkarıldı" +093 "Ex komutu baÅŸarısız: EÅŸlemlenen düğmeler çıkarıldı" +094 "Ä°kinci adres ilkinden daha küçük" +095 "Bir im adı saÄŸlanmadı" +096 "\\ sonrasında / veya ? gelmiyor" +097 "Bir satır numarasına yapılan baÅŸvuru sayısı sıfırdan az" +098 "Bilinmeyen komut: %s" +099 "Adres deÄŸeri taşımı" +100 "Adres deÄŸeri alttaşımı" +101 "Ä°zin verilmeyen adres birleÅŸimi" +102 "Ä°zin verilmeyen adres: Dosyada yalnızca %lu satır var" +103 "Ä°zin verilmeyen adres: Dosya boÅŸ" +104 "%s komutu bir 0 adresine izin vermiyor" +105 "Görüntülenecek kısaltma yok" +106 "Kısaltmalar bir \"sözcük\" karakteri ile bitmelidir" +107 "Kısaltmalar sekme veya boÅŸluk içeremez" +108 "Kısaltmalar sonu dışında sözcük/sözcük olmayan karakterleri karıştıramaz" +109 "\"%s\" bir kısaltma deÄŸil" +110 "Vi komutu baÅŸarısız: EÅŸlemlenen düğmeler çıkarıldı" +111 "Düzenlenecek baÅŸka dosya yok" +112 "Öncesinde düzenlenecek baÅŸka dosya yok" +113 "Öncesinde geri sarılacak dosyalar yok" +114 "Görüntülenecek dosya yok" +115 "Öncesinde \"!\" ögesinin yerine geçecek bir komut yok" +116 "%% yerine geçecek bir dosya adı yok" +117 "# yerine geçecek bir dosya adı yok" +118 "Hata: execl: %s" +119 "Girdi/Çıktı hatası: %s" +120 "Dosya son tam yazımdan bu yana deÄŸiÅŸtirilmiÅŸ; yazın veya yok saymak için ! kullanın" +121 "Ev dizini konumu bulunamıyor" +122 "Yeni ÅŸimdiki dizin: %s" +123 "Görüntülenecek kesilmiÅŸ arabellek yok" +124 "%s komutu bir global veya v komutunun parçası olarak kullanılamaz" +128 "%s: kaynak alınmıyor: Ä°yesi siz veya kök deÄŸil" +129 "%s: kaynak alınmıyor: Ä°yesi siz deÄŸilsiniz" +130 "%s: kaynak alınmıyor: Ä°yesi dışında baÅŸka bir kullanıcı tarafından yazılabilir" +131 "Uç uca eklenecek bir satır yok" +132 "Girdi eÅŸlem girdisi yok" +133 "Komut eÅŸlem girdisi yok" +134 "%s yeniden eÅŸlemlenemeyebilir" +135 "\"%s\" ÅŸu anda eÅŸlemlenmemiÅŸ" +136 "Ä°m adları tek bir karakter olmalıdır" +137 "%s mevcut, yazılmamış; yok saymak için ! kullanın" +138 "Yeni exrc dosyası: %s" +139 "Hedef satırı taşıma erimi içinde" +140 "'open' komutu 'open' seçeneÄŸinin ayarlı olmasını gerektirir" +141 "'open' komutu henüz eklenmemiÅŸ" +142 "Bu dosyanın korunması olanaklı deÄŸil" +143 "Dosya korundu" +144 "%s: Çok fazla dosya adına geniÅŸletilmiÅŸ" +145 "Yalnızca sıradan dosyalar ve adlandırılmış veri yolları okunabilir" +146 "%s: Okuma kilidi kullanılabilir deÄŸildi" +147 "Okunuyor..." +148 "%s: %lu satır, %lu karakter" +149 "Görüntülenecek arkaplan ekranı yok" +150 "'script' komutu yalnızca vi kipinde kullanılabilir" +151 "Çalıştırılacak komut yok" +152 "'shiftwidth' seçeneÄŸi 0 olarak ayarlanmış" +153 "Sayım taşımı" +154 "Sayım alttaşımı" +155 "Düzenli ifade belirtilmiÅŸ; r bayrağı anlamsız" +156 "#, l ve p bayrakları vi kipinde c bayrağı ile birlikte kullanılamaz" +157 "EÅŸleÅŸme bulunamadı" +158 "Önceden bir etiket girilmemiÅŸ" +159 "Etiket yığınında %s sayısından az girdi; :display t[ags] kullanın" +160 "Etiket yığınında dönülecek bir %s dosyası yok; :display t[ags] kullanın" +161 "Sürdürmek için Enter'a basın:" +162 "%s: Etiket bulunamadı" +163 "%s: %s içinde hasar görmüş etiket" +164 "%s: Etiketin satır numarası dosya sonunu geçmiÅŸ" +165 "Etiket yığını boÅŸ" +166 "%s: Arama dizgisi bulunamadı" +167 "%d dosya daha düzenlenecek" +168 "%s arabelleÄŸi boÅŸ" +169 "DeÄŸiÅŸikliÄŸi onayla? [n]" +170 "Yarıda kesildi" +171 "Öncesinde çalıştırılacak arabellek yok" +172 "Öncesinde düzenli ifade yok" +173 "%s komutu bir dosyanın halihazırda okunmuÅŸ olmasını gerektirir" +174 "Kullanım: %s" +175 "'visual' komutu 'open' seçeneÄŸinin ayarlanmış olmasını gerektirir" +177 "BoÅŸ dosya" +178 "Öncesinde F, f, T veya t araması yok" +179 "%s bulunamadı" +180 "Düzenlenecek dosya yok" +181 "Ä°mleç bir sayıda deÄŸil" +182 "Ortaya çıkan sayı çok büyük" +183 "Ortaya çıkan sayı çok küçük" +184 "Bu satırda eÅŸleÅŸen karakter yok" +185 "EÅŸleÅŸen karakter bulunamadı" +186 "DeÄŸiÅŸtirilecek karakter yok" +187 "Geçilecek baÅŸka ekran yok" +188 "Arama dizgisinden, satır ofsetinden ve/veya 'z' komutundan sonraki karakterler" +189 "Öncesinde arama dizgisi yok" +190 "Arama baÅŸlanan konumda tamamlandı" +191 "Kısaltma geniÅŸleme sınırını aÅŸtı: Karakterler çıkarıldı" +192 "Ä°zin verilmeyen karakter; giriÅŸ için tırnak içine alın" +193 "Halihazırda eklemenin başında" +194 "Silinecek baÅŸka karakter yok" +195 "Hareket dosya sonunu geçti" +196 "Hareket satır sonunu geçti" +197 "Ä°mleç hareket ettirilmedi" +198 "Halihazırda dosyanın başında" +199 "Hareket dosyanın başını geçti" +200 "Halihazırda ilk sütunda" +201 "Arabellekler komuttan önce belirtilmeli" +202 "Halihazırda dosya sonunda" +203 "Halihazırda satır sonunda" +204 "%s bir vi komutu deÄŸil" +205 "Kullanım: %s" +206 "Silinecek karakter yok" +207 "Q komutu ex uçbirim arabirimini gerektirir" +208 "Yinelenecek komut yok" +209 "Dosya boÅŸ" +210 "%s bir hareket komutu olarak kullanılamaz" +211 "Halihazırda komut kipi içinde" +212 "Ä°mleç bir sözcükte deÄŸil" +214 "'windows' seçeneÄŸi deÄŸeri çok büyük, en çok %u olabilir" +215 "Ä°liÅŸtir" +216 "DeÄŸiÅŸiklik yap" +217 "Komut" +218 "Ekle" +219 "DeÄŸiÅŸtir" +220 "Hareket ekran sonunu geçti" +221 "Hareket ekran başını geçti" +222 "Ekran bölünebilmesi için %d satırdan daha geniÅŸ olmalıdır" +223 "Arkaplan ekranı yok" +224 "Arkaplanda %s dosyasını düzenleyen bir ekran yok" +225 "Açık olan tek ekranınızı arkaplana alamazsınız" +226 "Ekran yalnızca %d satıra küçültülebilir" +227 "Ekran küçülemez" +228 "Ekran büyüyemez" +230 "Bu ekran askıya alınamaz" +231 "Yarıda kesildi: EÅŸlemlenen düğmeler çıkarıldı" +232 "vi: Geçici arabellek salıverilmedi" +233 "Bu uçbirimde %s düğmesi yok" +234 "Yalnızca bir arabellek belirtilebilir" +235 "Sayı ÅŸundan daha büyük: %lu" +236 "Yarıda kesildi" +237 "Geçici dosya oluÅŸturulamıyor" +238 "Uyarı: %s sıradan bir dosya deÄŸil" +239 "%s halihazırda kilitlenmiÅŸ, oturum saltokunur" +240 "%s: Kaldır" +241 "%s: Kapat" +242 "%s: Kaldır" +243 "%s: Kaldır" +244 "Saltokunur dosya, yazılmadı; yok saymak için ! kullanın" +245 "Saltokunur dosya, yazılmadı" +246 "%s mevcut, yazılmadı; yok saymak için ! kullanın" +247 "%s mevcut, yazılmadı" +248 "Kısmi dosya, yazılmadı; yok saymak için ! kullanın" +249 "Kısmi dosya, yazılmadı" +250 "%s: Dosya bu kopyadan daha önce deÄŸiÅŸtirilmiÅŸ; yok saymak için ! kullanın" +251 "%s: Dosya bu kopyadan daha önce deÄŸiÅŸtirilmiÅŸ" +252 "%s: Yazım koruması kullanılabilir deÄŸildi" +253 "Yazılıyor..." +254 "%s: UYARI: DOSYA KIRPILMIÅž" +255 "Halihazırda bu grubun ilk etiketinde" +256 "%s: Yeni dosya: %lu satır, %lu karakter" +257 "%s: %lu satır, %lu karakter" +258 "%s çok fazla dosya adına geniÅŸletilmiÅŸ" +259 "%s: Sıradan bir dosya deÄŸil" +260 "%s: Ä°yesi siz deÄŸilsiniz" +261 "%s: Ä°yesi dışında baÅŸka bir kullanıcı tarafından da eriÅŸilebilir" +262 "Dosya son tam yazımdan bu yana deÄŸiÅŸtirilmiÅŸ; yazın veya yok saymak için ! kullanın" +263 "Dosya son tam yazımdan bu yana deÄŸiÅŸtirilmiÅŸ; yazın veya yok saymak için :edit! kullanın" +264 "Dosya son tam yazımdan bu yana deÄŸiÅŸtirilmiÅŸ; yazın veya yok saymak için ! kullanın" +265 "Dosya geçici; çıkarsanız deÄŸiÅŸiklikler atılacaktır" +266 "Dosya saltokunur, deÄŸiÅŸiklikler kendiliÄŸinden yazılmaz" +267 "Günlük yeniden baÅŸlatıldı" +268 "Onayla? [ynq]" +269 "Sürdürmek için herhangi bir düğmeye basın: " +270 "Sürdürmek için herhangi bir düğmeye basın [ex komutu girmek için :]: " +271 "Sürdürmek için herhangi bir düğmeye basın [çıkış için q]: " +272 "Bu biçim %s ex uçbirim arabirimini gerektirir" +273 "ex girdisi kipine giriliyor" +274 "Komut baÅŸarısız, henüz bir dosya okunmadı." +275 " sürdür?" +276 "Beklenmedik karakter olayı" +277 "Beklenmedik dosya sonu olayı" +278 "Sorgu eÅŸleÅŸmesi bulunamadı" +279 "Beklenmedik yarıda kesme olayı" +281 "Beklenmedik yeniden boyama olayı" +282 "Halihazırda bu grubun son etiketinde" +283 "%s komutu ex uçbirim arabirimini gerektirir" +284 "Bu biçim %s 'secure edit' seçeneÄŸi ayarlandığında desteklenmez" +285 "Beklenmedik dizi olayı" +286 "Beklenmedik zaman aşımı olayı" +288 "Bölünebilmesi için ekran %d sütundan daha geniÅŸ olmalıdır" +289 "Kabuk geniÅŸletmeleri 'secure edit' seçeneÄŸi ayarlandığında desteklenmez" +290 "%s komutu 'secure edit' seçeneÄŸi ayarlandığında desteklenmez" +291 "set: %s seçeneÄŸi kapatılamaz" +292 "Ekran çok küçük." +293 "eklendi" +294 "deÄŸiÅŸtirildi" +295 "silindi" +296 "uç uca eklendi" +297 "taşındı" +298 "kaydırıldı" +299 "kopyalandı" +300 "satır" +301 "satır" +303 "Dosya son yazımdan bu yana deÄŸiÅŸtirilmiÅŸ." +304 "Kabuk geniÅŸletmesi baÅŸarısız" +305 "Bir '%s' düzenleme seçeneÄŸi belirtilmedi" +307 "Çalıştırılacak ex komutu yok" +308 "Bir komut çalıştırmak için , çıkmak için :q girin" +309 "Yardım için \"cscope help\" kullanın" +310 "Çalışan bir cscope baÄŸlantısı yok" +311 "%s: Bilinmeyen arama türü: Åžunlardan bir tanesini kullanın: %s" +312 "%d: Böyle bir cscope oturumu yok" +313 "set: %s seçeneÄŸi hiçbir zaman açılamaz" +314 "set: %s seçeneÄŸi hiçbir zaman 0 olarak ayarlanamaz" +315 "%s: Ä°liÅŸtirildi: %lu satır, %lu karakter" +316 "Beklenmedik yeniden boyutlandırma olayı" +317 "Düzenlenecek %d dosya var" +319 "%d ekran arkaplana alındı; onları listelemek için :display kullanın" +320 "Bilinmeyen imleç konumu" +321 "Dosya kodlama dönüştürmesi desteklenmiyor" +322 "Girdi kodlama dönüştürmesi desteklenmiyor" +323 "Geçersiz girdi. Kırpıldı." +324 "%d numaralı satırda dönüştürme hatası" Index: head/contrib/nvi/catalog/tr_TR.UTF-8.owner =================================================================== --- head/contrib/nvi/catalog/tr_TR.UTF-8.owner (nonexistent) +++ head/contrib/nvi/catalog/tr_TR.UTF-8.owner (revision 365499) @@ -0,0 +1 @@ +Emir SARI Index: head/contrib/nvi/cl/README.signal =================================================================== --- head/contrib/nvi/cl/README.signal (revision 365498) +++ head/contrib/nvi/cl/README.signal (revision 365499) @@ -1,174 +1,172 @@ -# $Id: README.signal,v 10.1 1995/06/23 10:28:17 bostic Exp $ - There are six (normally) asynchronous actions about which vi cares: SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGTSTP and SIGWINCH. The assumptions: 1: The DB routines are not reentrant. 2: The curses routines may not be reentrant. 3: Neither DB nor curses will restart system calls. XXX Note, most C library functions don't restart system calls. So, we should *probably* start blocking around any imported function that we don't know doesn't make a system call. This is going to be a genuine annoyance... SIGHUP, SIGTERM Used for file recovery. The DB routines can't be reentered, nor can they handle interrupted system calls, so the vi routines that call DB block signals. This means that DB routines could be called at interrupt time, if necessary. SIGQUIT Disabled by the signal initialization routines. Historically, ^\ switched vi into ex mode, and we continue that practice. SIGWINCH: The interrupt routine sets a global bit which is checked by the key-read routine, so there are no reentrancy issues. This means that the screen will not resize until vi runs out of keys, but that doesn't seem like a problem. SIGINT and SIGTSTP are a much more difficult issue to resolve. Vi has to permit the user to interrupt long-running operations. Generally, a search, substitution or read/write is done on a large file, or, the user creates a key mapping with an infinite loop. This problem will become worse as more complex semantics are added to vi, especially things like making it a pure text widget. There are four major solutions on the table, each of which have minor permutations. 1: Run in raw mode. The up side is that there's no asynchronous behavior to worry about, and obviously no reentrancy problems. The down side is that it's easy to misinterpret characters (e.g. :w big_file^Mi^V^C is going to look like an interrupt) and it's easy to get into places where we won't see interrupt characters (e.g. ":map a ixx^[hxxaXXX" infinitely loops in historic implementations of vi). Periodically reading the terminal input buffer might solve the latter problem, but it's not going to be pretty. Also, we're going to be checking for ^C's and ^Z's both, all over the place -- I hate to litter the source code with that. For example, the historic version of vi didn't permit you to suspend the screen if you were on the colon command line. This isn't right. ^Z isn't a vi command, it's a terminal event. (Dammit.) 2: Run in cbreak mode. There are two problems in this area. First, the current curses implementations (both System V and Berkeley) don't give you clean cbreak modes. For example, the IEXTEN bit is left on, turning on DISCARD and LNEXT. To clarify, what vi WANTS is 8-bit clean, with the exception that flow control and signals are turned on, and curses cbreak mode doesn't give you this. We can either set raw mode and twiddle the tty, or cbreak mode and twiddle the tty. I chose to use raw mode, on the grounds that raw mode is better defined and I'm less likely to be surprised by a curses implementation down the road. The twiddling consists of setting ISIG, IXON/IXOFF, and disabling some of the interrupt characters (see the comments in cl_init.c). This is all found in historic System V (SVID 3) and POSIX 1003.1-1992, so it should be fairly portable. The second problem is that vi permits you to enter literal signal characters, e.g. ^V^C. There are two possible solutions. First, you can turn off signals when you get a ^V, but that means that a network packet containing ^V and ^C will lose, since the ^C may take effect before vi reads the ^V. (This is particularly problematic if you're talking over a protocol that recognizes signals locally and sends OOB packets when it sees them.) Second, you can turn the ^C into a literal character in vi, but that means that there's a race between entering ^V^C, i.e. the sequence may end up being ^V^C. Also, the second solution doesn't work for flow control characters, as they aren't delivered to the program as signals. Generally, this is what historic vi did. (It didn't have the curses problems because it didn't use curses.) It entered signals following ^V characters into the input stream, (which is why there's no way to enter a literal flow control character). 3: Run in mostly raw mode; turn signals on when doing an operation the user might want to interrupt, but leave them off most of the time. This works well for things like file reads and writes. This doesn't work well for trying to detect infinite maps. The problem is that you can write the code so that you don't have to turn on interrupts per keystroke, but the code isn't pretty and it's hard to make sure that an optimization doesn't cover up an infinite loop. This also requires interaction or state between the vi parser and the key reading routines, as an infinite loop may still be returning keys to the parser. Also, if the user inserts an interrupt into the tty queue while the interrupts are turned off, the key won't be treated as an interrupt, and requiring the user to pound the keyboard to catch an interrupt window is nasty. 4: Run in mostly raw mode, leaving signals on all of the time. Done by setting raw mode, and twiddling the tty's termios ISIG bit. This works well for the interrupt cases, because the code only has to check to see if the interrupt flag has been set, and can otherwise ignore signals. It's also less likely that we'll miss a case, and we don't have to worry about synchronizing between the vi parser and the key read routines. The down side is that we have to turn signals off if the user wants to enter a literal character (e.g. ^V^C). If the user enters the combination fast enough, or as part of a single network packet, the text input routines will treat it as a signal instead of as a literal character. To some extent, we have this problem already, since we turn off flow control so that the user can enter literal XON/XOFF characters. This is probably the easiest to code, and provides the smoothest programming interface. There are a couple of other problems to consider. First, System V's curses doesn't handle SIGTSTP correctly. If you use the newterm() interface, the TSTP signal will leave you in raw mode, and the final endwin() will leave you in the correct shell mode. If you use the initscr() interface, the TSTP signal will return you to the correct shell mode, but the final endwin() will leave you in raw mode. There you have it: proof that drug testing is not making any significant headway in the computer industry. The 4BSD curses is deficient in that it does not have an interface to the terminal keypad. So, regardless, we have to do our own SIGTSTP handling. The problem with this is that if we do our own SIGTSTP handling, in either models #3 or #4, we're going to have to call curses routines at interrupt time, which means that we might be reentering curses, which is something we don't want to do. Second, SIGTSTP has its own little problems. It's broadcast to the entire process group, not sent to a single process. The scenario goes something like this: the shell execs the mail program, which execs vi. The user hits ^Z, and all three programs get the signal, in some random order. The mail program goes to sleep immediately (since it probably didn't have a SIGTSTP handler in place). The shell gets a SIGCHLD, does a wait, and finds out that the only child in its foreground process group (of which it's aware) is asleep. It then optionally resets the terminal (because the modes aren't how it left them), and starts prompting the user for input. The problem is that somewhere in the middle of all of this, vi is resetting the terminal, and getting ready to send a SIGTSTP to the process group in order to put itself to sleep. There's a solution to all of this: when vi starts, it puts itself into its own process group, and then only it (and possible child processes) receive the SIGTSTP. This permits it to clean up the terminal and switch back to the original process group, where it sends that process group a SIGTSTP, putting everyone to sleep and waking the shell. Third, handing SIGTSTP asynchronously is further complicated by the child processes vi may fork off. If vi calls ex, ex resets the terminal and starts running some filter, and SIGTSTP stops them both, vi has to know when it restarts that it can't repaint the screen until ex's child has finished running. This is solveable, but it's annoying. Well, somebody had to make a decision, and this is the way it's going to be (unless I get talked out of it). SIGINT is handled asynchronously, so that we can pretty much guarantee that the user can interrupt any operation at any time. SIGTSTP is handled synchronously, so that we don't have to reenter curses and so that we don't have to play the process group games. ^Z is recognized in the standard text input and command modes. (^Z should also be recognized during operations that may potentially take a long time. The simplest solution is probably to twiddle the tty, install a handler for SIGTSTP, and then restore normal tty modes when the operation is complete.) Index: head/contrib/nvi/cl/cl.h =================================================================== --- head/contrib/nvi/cl/cl.h (revision 365498) +++ head/contrib/nvi/cl/cl.h (revision 365499) @@ -1,83 +1,81 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: cl.h,v 10.34 2011/08/15 20:07:32 zy Exp $ */ #ifdef USE_WIDECHAR #define _XOPEN_SOURCE_EXTENDED #endif #ifdef HAVE_NCURSES_H #include #else #include #endif typedef struct _cl_private { char ibuf[256]; /* Input keys. */ size_t skip; /* Remaining keys. */ CONVWIN cw; /* Conversion buffer. */ int eof_count; /* EOF count. */ struct termios orig; /* Original terminal values. */ struct termios ex_enter;/* Terminal values to enter ex. */ struct termios vi_enter;/* Terminal values to enter vi. */ char *el; /* Clear to EOL terminal string. */ char *cup; /* Cursor movement terminal string. */ char *cuu1; /* Cursor up terminal string. */ char *rmso, *smso; /* Inverse video terminal strings. */ char *smcup, *rmcup; /* Terminal start/stop strings. */ char *oname; /* Original screen window name. */ SCR *focus; /* Screen that has the "focus". */ int killersig; /* Killer signal. */ #define INDX_HUP 0 #define INDX_INT 1 #define INDX_TERM 2 #define INDX_WINCH 3 #define INDX_MAX 4 /* Original signal information. */ struct sigaction oact[INDX_MAX]; enum { /* Tty group write mode. */ TGW_UNKNOWN=0, TGW_SET, TGW_UNSET } tgw; enum { /* Terminal initialization strings. */ TE_SENT=0, TI_SENT } ti_te; #define CL_IN_EX 0x0001 /* Currently running ex. */ #define CL_LAYOUT 0x0002 /* Screen layout changed. */ #define CL_RENAME 0x0004 /* X11 xterm icon/window renamed. */ #define CL_RENAME_OK 0x0008 /* User wants the windows renamed. */ #define CL_SCR_EX_INIT 0x0010 /* Ex screen initialized. */ #define CL_SCR_VI_INIT 0x0020 /* Vi screen initialized. */ #define CL_SIGHUP 0x0040 /* SIGHUP arrived. */ #define CL_SIGINT 0x0080 /* SIGINT arrived. */ #define CL_SIGTERM 0x0100 /* SIGTERM arrived. */ #define CL_SIGWINCH 0x0200 /* SIGWINCH arrived. */ #define CL_STDIN_TTY 0x0400 /* Talking to a terminal. */ u_int32_t flags; } CL_PRIVATE; #define CLP(sp) ((CL_PRIVATE *)((sp)->gp->cl_private)) #define GCLP(gp) ((CL_PRIVATE *)gp->cl_private) #define CLSP(sp) ((WINDOW *)((sp)->cl_private)) /* Return possibilities from the keyboard read routine. */ typedef enum { INP_OK=0, INP_EOF, INP_ERR, INP_INTR, INP_TIMEOUT } input_t; /* The screen position relative to a specific window. */ #define RCNO(sp, cno) (cno) #define RLNO(sp, lno) (lno) #include "extern.h" Index: head/contrib/nvi/cl/cl_funcs.c =================================================================== --- head/contrib/nvi/cl/cl_funcs.c (revision 365498) +++ head/contrib/nvi/cl/cl_funcs.c (revision 365499) @@ -1,852 +1,848 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: cl_funcs.c,v 10.74 2012/10/11 10:30:16 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #ifdef HAVE_TERM_H #include #endif #include #include #include "../common/common.h" #include "../vi/vi.h" #include "cl.h" static void cl_rdiv(SCR *); static int addstr4(SCR *sp, void *str, size_t len, int wide) { CL_PRIVATE *clp; WINDOW *win; size_t y, x; int iv; clp = CLP(sp); win = CLSP(sp) ? CLSP(sp) : stdscr; /* * If ex isn't in control, it's the last line of the screen and * it's a split screen, use inverse video. */ iv = 0; getyx(win, y, x); if (!F_ISSET(sp, SC_SCR_EXWROTE) && y == RLNO(sp, LASTLINE(sp)) && IS_SPLIT(sp)) { iv = 1; (void)wstandout(win); } #ifdef USE_WIDECHAR if (wide) { if (waddnwstr(win, str, len) == ERR) return (1); } else #endif if (waddnstr(win, str, len) == ERR) return (1); if (iv) (void)wstandend(win); return (0); } /* * cl_waddstr -- * Add len bytes from the string at the cursor, advancing the cursor. * * PUBLIC: int cl_waddstr(SCR *, const CHAR_T *, size_t); */ int cl_waddstr(SCR *sp, const CHAR_T *str, size_t len) { return addstr4(sp, (void *)str, len, 1); } /* * cl_addstr -- * Add len bytes from the string at the cursor, advancing the cursor. * * PUBLIC: int cl_addstr(SCR *, const char *, size_t); */ int cl_addstr(SCR *sp, const char *str, size_t len) { return addstr4(sp, (void *)str, len, 0); } /* * cl_attr -- * Toggle a screen attribute on/off. * * PUBLIC: int cl_attr(SCR *, scr_attr_t, int); */ int cl_attr(SCR *sp, scr_attr_t attribute, int on) { CL_PRIVATE *clp; WINDOW *win; clp = CLP(sp); win = CLSP(sp) ? CLSP(sp) : stdscr; switch (attribute) { case SA_ALTERNATE: /* * !!! * There's a major layering violation here. The problem is that the * X11 xterm screen has what's known as an "alternate" screen. Some * xterm termcap/terminfo entries include sequences to switch to/from * that alternate screen as part of the ti/te (smcup/rmcup) strings. * Vi runs in the alternate screen, so that you are returned to the * same screen contents on exit from vi that you had when you entered * vi. Further, when you run :shell, or :!date or similar ex commands, * you also see the original screen contents. This wasn't deliberate * on vi's part, it's just that it historically sent terminal init/end * sequences at those times, and the addition of the alternate screen * sequences to the strings changed the behavior of vi. The problem * caused by this is that we don't want to switch back to the alternate * screen while getting a new command from the user, when the user is * continuing to enter ex commands, e.g.: * * :!date <<< switch to original screen * [Hit return to continue] <<< prompt user to continue * :command <<< get command from user * * Note that the :command input is a true vi input mode, e.g., input * maps and abbreviations are being done. So, we need to be able to * switch back into the vi screen mode, without flashing the screen. * * To make matters worse, the curses initscr() and endwin() calls will * do this automatically -- so, this attribute isn't as controlled by * the higher level screen as closely as one might like. */ if (on) { if (clp->ti_te != TI_SENT) { clp->ti_te = TI_SENT; if (clp->smcup == NULL) (void)cl_getcap(sp, "smcup", &clp->smcup); if (clp->smcup != NULL) (void)tputs(clp->smcup, 1, cl_putchar); } } else if (clp->ti_te != TE_SENT) { clp->ti_te = TE_SENT; if (clp->rmcup == NULL) (void)cl_getcap(sp, "rmcup", &clp->rmcup); if (clp->rmcup != NULL) (void)tputs(clp->rmcup, 1, cl_putchar); (void)fflush(stdout); } (void)fflush(stdout); break; case SA_INVERSE: if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) { if (clp->smso == NULL) return (1); if (on) (void)tputs(clp->smso, 1, cl_putchar); else (void)tputs(clp->rmso, 1, cl_putchar); (void)fflush(stdout); } else { if (on) (void)wstandout(win); else (void)wstandend(win); } break; default: abort(); } return (0); } /* * cl_baud -- * Return the baud rate. * * PUBLIC: int cl_baud(SCR *, u_long *); */ int cl_baud(SCR *sp, u_long *ratep) { CL_PRIVATE *clp; /* * XXX * There's no portable way to get a "baud rate" -- cfgetospeed(3) * returns the value associated with some #define, which we may * never have heard of, or which may be a purely local speed. Vi * only cares if it's SLOW (w300), slow (w1200) or fast (w9600). * Try and detect the slow ones, and default to fast. */ clp = CLP(sp); switch (cfgetospeed(&clp->orig)) { case B50: case B75: case B110: case B134: case B150: case B200: case B300: case B600: *ratep = 600; break; case B1200: *ratep = 1200; break; default: *ratep = 9600; break; } return (0); } /* * cl_bell -- * Ring the bell/flash the screen. * * PUBLIC: int cl_bell(SCR *); */ int cl_bell(SCR *sp) { if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE | SC_SCR_EX)) (void)write(STDOUT_FILENO, "\07", 1); /* \a */ else { /* * Vi has an edit option which determines if the terminal * should be beeped or the screen flashed. */ if (O_ISSET(sp, O_FLASH)) (void)flash(); else (void)beep(); } return (0); } /* * cl_clrtoeol -- * Clear from the current cursor to the end of the line. * * PUBLIC: int cl_clrtoeol(SCR *); */ int cl_clrtoeol(SCR *sp) { WINDOW *win; #if 0 size_t spcnt, y, x; #endif win = CLSP(sp) ? CLSP(sp) : stdscr; #if 0 if (IS_VSPLIT(sp)) { /* The cursor must be returned to its original position. */ getyx(win, y, x); for (spcnt = (sp->coff + sp->cols) - x; spcnt > 0; --spcnt) (void)waddch(win, ' '); (void)wmove(win, y, x); return (0); } else #endif return (wclrtoeol(win) == ERR); } /* * cl_cursor -- * Return the current cursor position. * * PUBLIC: int cl_cursor(SCR *, size_t *, size_t *); */ int cl_cursor(SCR *sp, size_t *yp, size_t *xp) { WINDOW *win; win = CLSP(sp) ? CLSP(sp) : stdscr; /* * The curses screen support splits a single underlying curses screen * into multiple screens to support split screen semantics. For this * reason the returned value must be adjusted to be relative to the * current screen, and not absolute. Screens that implement the split * using physically distinct screens won't need this hack. */ getyx(win, *yp, *xp); /* *yp -= sp->roff; *xp -= sp->coff; */ return (0); } /* * cl_deleteln -- * Delete the current line, scrolling all lines below it. * * PUBLIC: int cl_deleteln(SCR *); */ int cl_deleteln(SCR *sp) { CL_PRIVATE *clp; WINDOW *win; size_t y, x; clp = CLP(sp); win = CLSP(sp) ? CLSP(sp) : stdscr; /* * This clause is required because the curses screen uses reverse * video to delimit split screens. If the screen does not do this, * this code won't be necessary. * * If the bottom line was in reverse video, rewrite it in normal * video before it's scrolled. */ if (!F_ISSET(sp, SC_SCR_EXWROTE) && IS_SPLIT(sp)) { getyx(win, y, x); mvwchgat(win, RLNO(sp, LASTLINE(sp)), 0, -1, A_NORMAL, 0, NULL); (void)wmove(win, y, x); } /* * The bottom line is expected to be blank after this operation, * and other screens must support that semantic. */ return (wdeleteln(win) == ERR); } /* * cl_discard -- * Discard a screen. * * PUBLIC: int cl_discard(SCR *, SCR **); */ int cl_discard(SCR *discardp, SCR **acquirep) { CL_PRIVATE *clp; SCR* tsp; if (discardp) { clp = CLP(discardp); F_SET(clp, CL_LAYOUT); if (CLSP(discardp)) { delwin(CLSP(discardp)); discardp->cl_private = NULL; } } /* no screens got a piece; we're done */ if (!acquirep) return 0; for (; (tsp = *acquirep) != NULL; ++acquirep) { clp = CLP(tsp); F_SET(clp, CL_LAYOUT); if (CLSP(tsp)) delwin(CLSP(tsp)); tsp->cl_private = subwin(stdscr, tsp->rows, tsp->cols, tsp->roff, tsp->coff); } /* discardp is going away, acquirep is taking up its space. */ return (0); } /* * cl_ex_adjust -- * Adjust the screen for ex. This routine is purely for standalone * ex programs. All special purpose, all special case. * * PUBLIC: int cl_ex_adjust(SCR *, exadj_t); */ int cl_ex_adjust(SCR *sp, exadj_t action) { CL_PRIVATE *clp; int cnt; clp = CLP(sp); switch (action) { case EX_TERM_SCROLL: /* Move the cursor up one line if that's possible. */ if (clp->cuu1 != NULL) (void)tputs(clp->cuu1, 1, cl_putchar); else if (clp->cup != NULL) (void)tputs(tgoto(clp->cup, 0, LINES - 2), 1, cl_putchar); else return (0); /* FALLTHROUGH */ case EX_TERM_CE: /* Clear the line. */ if (clp->el != NULL) { (void)putchar('\r'); (void)tputs(clp->el, 1, cl_putchar); } else { /* * Historically, ex didn't erase the line, so, if the * displayed line was only a single glyph, and * was more than one glyph, the output would not fully * overwrite the user's input. To fix this, output * the maxiumum character number of spaces. Note, * this won't help if the user entered extra prompt * or characters before the command character. * We'd have to do a lot of work to make that work, and * it's almost certainly not worth the effort. */ for (cnt = 0; cnt < MAX_CHARACTER_COLUMNS; ++cnt) (void)putchar('\b'); for (cnt = 0; cnt < MAX_CHARACTER_COLUMNS; ++cnt) (void)putchar(' '); (void)putchar('\r'); (void)fflush(stdout); } break; default: abort(); } return (0); } /* * cl_insertln -- * Push down the current line, discarding the bottom line. * * PUBLIC: int cl_insertln(SCR *); */ int cl_insertln(SCR *sp) { WINDOW *win; win = CLSP(sp) ? CLSP(sp) : stdscr; /* * The current line is expected to be blank after this operation, * and the screen must support that semantic. */ return (winsertln(win) == ERR); } /* * cl_keyval -- * Return the value for a special key. * * PUBLIC: int cl_keyval(SCR *, scr_keyval_t, CHAR_T *, int *); */ int cl_keyval(SCR *sp, scr_keyval_t val, CHAR_T *chp, int *dnep) { CL_PRIVATE *clp; /* * VEOF, VERASE and VKILL are required by POSIX 1003.1-1990, * VWERASE is a 4BSD extension. */ clp = CLP(sp); switch (val) { case KEY_VEOF: *dnep = (*chp = clp->orig.c_cc[VEOF]) == _POSIX_VDISABLE; break; case KEY_VERASE: *dnep = (*chp = clp->orig.c_cc[VERASE]) == _POSIX_VDISABLE; break; case KEY_VKILL: *dnep = (*chp = clp->orig.c_cc[VKILL]) == _POSIX_VDISABLE; break; #ifdef VWERASE case KEY_VWERASE: *dnep = (*chp = clp->orig.c_cc[VWERASE]) == _POSIX_VDISABLE; break; #endif default: *dnep = 1; break; } return (0); } /* * cl_move -- * Move the cursor. * * PUBLIC: int cl_move(SCR *, size_t, size_t); */ int cl_move(SCR *sp, size_t lno, size_t cno) { WINDOW *win; win = CLSP(sp) ? CLSP(sp) : stdscr; /* See the comment in cl_cursor. */ if (wmove(win, RLNO(sp, lno), RCNO(sp, cno)) == ERR) { msgq(sp, M_ERR, "Error: move: l(%zu + %zu) c(%zu + %zu)", lno, sp->roff, cno, sp->coff); return (1); } return (0); } /* * cl_refresh -- * Refresh the screen. * * PUBLIC: int cl_refresh(SCR *, int); */ int cl_refresh(SCR *sp, int repaint) { GS *gp; CL_PRIVATE *clp; WINDOW *win; SCR *psp, *tsp; size_t y, x; gp = sp->gp; clp = CLP(sp); win = CLSP(sp) ? CLSP(sp) : stdscr; /* * If we received a killer signal, we're done, there's no point * in refreshing the screen. */ if (clp->killersig) return (0); /* * If repaint is set, the editor is telling us that we don't know * what's on the screen, so we have to repaint from scratch. * * If repaint set or the screen layout changed, we need to redraw * any lines separating vertically split screens. If the horizontal * offsets are the same, then the split was vertical, and need to * draw a dividing line. */ if (repaint || F_ISSET(clp, CL_LAYOUT)) { getyx(stdscr, y, x); for (psp = sp; psp != NULL; psp = TAILQ_NEXT(psp, q)) for (tsp = TAILQ_NEXT(psp, q); tsp != NULL; tsp = TAILQ_NEXT(tsp, q)) if (psp->roff == tsp->roff) { if (psp->coff + psp->cols + 1 == tsp->coff) cl_rdiv(psp); else if (tsp->coff + tsp->cols + 1 == psp->coff) cl_rdiv(tsp); } (void)wmove(stdscr, y, x); F_CLR(clp, CL_LAYOUT); } /* * In the curses library, doing wrefresh(curscr) is okay, but the * screen flashes when we then apply the refresh() to bring it up * to date. So, use clearok(). */ if (repaint) clearok(curscr, 1); /* * Only do an actual refresh, when this is the focus window, * i.e. the one holding the cursor. This assumes that refresh * is called for that window after refreshing the others. * This prevents the cursor being drawn in the other windows. */ return (wnoutrefresh(stdscr) == ERR || wnoutrefresh(win) == ERR || (sp == clp->focus && doupdate() == ERR)); } /* * cl_rdiv -- * Draw a dividing line between two vertically split screens. */ static void cl_rdiv(SCR *sp) { #ifdef __NetBSD__ mvvline(sp->roff, sp->cols + sp->coff, '|', sp->rows); #else mvvline(sp->roff, sp->cols + sp->coff, ACS_VLINE, sp->rows); #endif } /* * cl_rename -- * Rename the file. * * PUBLIC: int cl_rename(SCR *, char *, int); */ int cl_rename(SCR *sp, char *name, int on) { GS *gp; CL_PRIVATE *clp; FILE *pfp; char buf[256], *s, *e; char * wid; char cmd[64]; gp = sp->gp; clp = CLP(sp); /* * XXX * We can only rename windows for xterm. */ if (on) { clp->focus = sp; if (!F_ISSET(clp, CL_RENAME_OK) || strncmp(OG_STR(gp, GO_TERM), "xterm", 5)) return (0); if (clp->oname == NULL && (wid = getenv("WINDOWID"))) { snprintf(cmd, sizeof(cmd), "xprop -id %s WM_NAME", wid); if ((pfp = popen(cmd, "r")) == NULL) goto rename; if (fgets(buf, sizeof(buf), pfp) == NULL) { pclose(pfp); goto rename; } pclose(pfp); if ((s = strchr(buf, '"')) != NULL && (e = strrchr(buf, '"')) != NULL) clp->oname = strndup(s + 1, e - s - 1); } rename: cl_setname(gp, name); F_SET(clp, CL_RENAME); } else if (F_ISSET(clp, CL_RENAME)) { cl_setname(gp, clp->oname); F_CLR(clp, CL_RENAME); } return (0); } /* * cl_setname -- * Set a X11 icon/window name. * * PUBLIC: void cl_setname(GS *, char *); */ void cl_setname(GS *gp, char *name) { /* X11 xterm escape sequence to rename the icon/window. */ #define XTERM_RENAME "\033]0;%s\007" (void)printf(XTERM_RENAME, name == NULL ? OG_STR(gp, GO_TERM) : name); (void)fflush(stdout); #undef XTERM_RENAME } /* * cl_split -- * Split a screen. * * PUBLIC: int cl_split(SCR *, SCR *); */ int cl_split(SCR *origp, SCR *newp) { CL_PRIVATE *clp; clp = CLP(origp); F_SET(clp, CL_LAYOUT); if (CLSP(origp)) delwin(CLSP(origp)); origp->cl_private = subwin(stdscr, origp->rows, origp->cols, origp->roff, origp->coff); newp->cl_private = subwin(stdscr, newp->rows, newp->cols, newp->roff, newp->coff); /* origp is the original screen, giving up space to newp. */ return (0); } /* * cl_suspend -- * Suspend a screen. * * PUBLIC: int cl_suspend(SCR *, int *); */ int cl_suspend(SCR *sp, int *allowedp) { struct termios t; CL_PRIVATE *clp; WINDOW *win; GS *gp; size_t y, x; int changed; gp = sp->gp; clp = CLP(sp); win = CLSP(sp) ? CLSP(sp) : stdscr; *allowedp = 1; /* * The ex implementation of this function isn't needed by screens not * supporting ex commands that require full terminal canonical mode * (e.g. :suspend). * * The vi implementation of this function isn't needed by screens not * supporting vi process suspension, i.e. any screen that isn't backed * by a UNIX shell. * * Setting allowedp to 0 will cause the editor to reject the command. */ if (F_ISSET(sp, SC_EX)) { /* Save the terminal settings, and restore the original ones. */ if (F_ISSET(clp, CL_STDIN_TTY)) { (void)tcgetattr(STDIN_FILENO, &t); (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &clp->orig); } /* Stop the process group. */ (void)kill(0, SIGTSTP); /* Time passes ... */ /* Restore terminal settings. */ if (F_ISSET(clp, CL_STDIN_TTY)) (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &t); return (0); } /* * Move to the lower left-hand corner of the screen. * * XXX * Not sure this is necessary in System V implementations, but it * shouldn't hurt. */ getyx(win, y, x); (void)wmove(win, LINES - 1, 0); (void)wrefresh(win); /* * Temporarily end the screen. System V introduced a semantic where * endwin() could be restarted. We use it because restarting curses * from scratch often fails in System V. 4BSD curses didn't support * restarting after endwin(), so we have to do what clean up we can * without calling it. */ /* Save the terminal settings. */ (void)tcgetattr(STDIN_FILENO, &t); /* Restore the cursor keys to normal mode. */ (void)keypad(stdscr, FALSE); /* Restore the window name. */ (void)cl_rename(sp, NULL, 0); (void)endwin(); /* * XXX * Restore the original terminal settings. This is bad -- the * reset can cause character loss from the tty queue. However, * we can't call endwin() in BSD curses implementations, and too * many System V curses implementations don't get it right. */ (void)tcsetattr(STDIN_FILENO, TCSADRAIN | TCSASOFT, &clp->orig); /* Stop the process group. */ (void)kill(0, SIGTSTP); /* Time passes ... */ /* * If we received a killer signal, we're done. Leave everything * unchanged. In addition, the terminal has already been reset * correctly, so leave it alone. */ if (clp->killersig) { F_CLR(clp, CL_SCR_EX_INIT | CL_SCR_VI_INIT); return (0); } /* Restore terminal settings. */ wrefresh(win); /* Needed on SunOs/Solaris ? */ if (F_ISSET(clp, CL_STDIN_TTY)) (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &t); /* Set the window name. */ (void)cl_rename(sp, sp->frp->name, 1); /* Put the cursor keys into application mode. */ (void)keypad(stdscr, TRUE); /* Refresh and repaint the screen. */ (void)wmove(win, y, x); (void)cl_refresh(sp, 1); /* If the screen changed size, set the SIGWINCH bit. */ if (cl_ssize(sp, 1, NULL, NULL, &changed)) return (1); if (changed) F_SET(CLP(sp), CL_SIGWINCH); return (0); } /* * cl_usage -- * Print out the curses usage messages. * * PUBLIC: void cl_usage(void); */ void cl_usage(void) { #define USAGE "\ usage: ex [-eFRrSsv] [-c command] [-t tag] [-w size] [file ...]\n\ usage: vi [-eFlRrSv] [-c command] [-t tag] [-w size] [file ...]\n" (void)fprintf(stderr, "%s", USAGE); #undef USAGE } #ifdef DEBUG /* * gdbrefresh -- * Stub routine so can flush out curses screen changes using gdb. */ static int __attribute__((unused)) gdbrefresh(void) { refresh(); return (0); /* XXX Convince gdb to run it. */ } #endif Index: head/contrib/nvi/cl/cl_main.c =================================================================== --- head/contrib/nvi/cl/cl_main.c (revision 365498) +++ head/contrib/nvi/cl/cl_main.c (revision 365499) @@ -1,422 +1,391 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: cl_main.c,v 10.56 2015/04/05 06:20:53 zy Exp $"; -#endif /* not lint */ - #include #include #include +#include #include #include #include #include #include #include #ifdef HAVE_TERM_H #include #endif #include #include #include "../common/common.h" #include "cl.h" #include "pathnames.h" GS *__global_list; /* GLOBAL: List of screens. */ sigset_t __sigblockset; /* GLOBAL: Blocked signals. */ static void cl_func_std(GS *); static CL_PRIVATE *cl_init(GS *); -static GS *gs_init(char *); -static void perr(char *, char *); +static GS *gs_init(void); static int setsig(int, struct sigaction *, void (*)(int)); static void sig_end(GS *); -static void term_init(char *, char *); +static void term_init(char *); /* * main -- * This is the main loop for the standalone curses editor. */ int main(int argc, char *argv[]) { static int reenter; CL_PRIVATE *clp; GS *gp; size_t rows, cols; int rval; char **p_av, **t_av, *ttype; /* If loaded at 0 and jumping through a NULL pointer, stop. */ if (reenter++) abort(); /* Create and initialize the global structure. */ - __global_list = gp = gs_init(argv[0]); + __global_list = gp = gs_init(); /* * Strip out any arguments that vi isn't going to understand. There's * no way to portably call getopt twice, so arguments parsed here must * be removed from the argument list. */ for (p_av = t_av = argv;;) { if (*t_av == NULL) { *p_av = NULL; break; } if (!strcmp(*t_av, "--")) { while ((*p_av++ = *t_av++) != NULL); break; } *p_av++ = *t_av++; } /* Create and initialize the CL_PRIVATE structure. */ clp = cl_init(gp); /* * Initialize the terminal information. * * We have to know what terminal it is from the start, since we may * have to use termcap/terminfo to find out how big the screen is. */ if ((ttype = getenv("TERM")) == NULL) ttype = "ansi"; - term_init(gp->progname, ttype); + term_init(ttype); /* Add the terminal type to the global structure. */ if ((OG_D_STR(gp, GO_TERM) = OG_STR(gp, GO_TERM) = strdup(ttype)) == NULL) - perr(gp->progname, NULL); + err(1, NULL); /* Figure out how big the screen is. */ if (cl_ssize(NULL, 0, &rows, &cols, NULL)) exit (1); /* Add the rows and columns to the global structure. */ OG_VAL(gp, GO_LINES) = OG_D_VAL(gp, GO_LINES) = rows; OG_VAL(gp, GO_COLUMNS) = OG_D_VAL(gp, GO_COLUMNS) = cols; /* Ex wants stdout to be buffered. */ (void)setvbuf(stdout, NULL, _IOFBF, 0); /* Start catching signals. */ if (sig_init(gp, NULL)) exit (1); /* Run ex/vi. */ rval = editor(gp, argc, argv); /* Clean up signals. */ sig_end(gp); /* Clean up the terminal. */ (void)cl_quit(gp); /* * XXX * Reset the O_MESG option. */ if (clp->tgw != TGW_UNKNOWN) (void)cl_omesg(NULL, clp, clp->tgw == TGW_SET); /* * XXX * Reset the X11 xterm icon/window name. */ if (F_ISSET(clp, CL_RENAME)) cl_setname(gp, clp->oname); /* If a killer signal arrived, pretend we just got it. */ if (clp->killersig) { (void)signal(clp->killersig, SIG_DFL); (void)kill(getpid(), clp->killersig); /* NOTREACHED */ } /* Free the global and CL private areas. */ #if defined(DEBUG) || defined(PURIFY) - if (clp->oname != NULL) - free(clp->oname); + free(clp->oname); free(clp); free(OG_STR(gp, GO_TERM)); free(gp); #endif exit (rval); } /* * gs_init -- * Create and partially initialize the GS structure. */ static GS * -gs_init(char *name) +gs_init(void) { GS *gp; - char *p; - /* Figure out what our name is. */ - if ((p = strrchr(name, '/')) != NULL) - name = p + 1; - - /* Allocate the global structure. */ - CALLOC_NOMSG(NULL, gp, GS *, 1, sizeof(GS)); + gp = calloc(1, sizeof(GS)); if (gp == NULL) - perr(name, NULL); + err(1, NULL); - gp->progname = name; return (gp); } /* * cl_init -- * Create and partially initialize the CL structure. */ static CL_PRIVATE * cl_init(GS *gp) { CL_PRIVATE *clp; int fd; - /* Allocate the CL private structure. */ - CALLOC_NOMSG(NULL, clp, CL_PRIVATE *, 1, sizeof(CL_PRIVATE)); + clp = calloc(1, sizeof(CL_PRIVATE)); if (clp == NULL) - perr(gp->progname, NULL); + err(1, NULL); gp->cl_private = clp; /* * Set the CL_STDIN_TTY flag. It's purpose is to avoid setting * and resetting the tty if the input isn't from there. We also * use the same test to determine if we're running a script or * not. */ if (isatty(STDIN_FILENO)) F_SET(clp, CL_STDIN_TTY); else F_SET(gp, G_SCRIPTED); /* * We expect that if we've lost our controlling terminal that the * open() (but not the tcgetattr()) will fail. */ if (F_ISSET(clp, CL_STDIN_TTY)) { if (tcgetattr(STDIN_FILENO, &clp->orig) == -1) goto tcfail; } else if ((fd = open(_PATH_TTY, O_RDONLY, 0)) != -1) { if (tcgetattr(fd, &clp->orig) == -1) { -tcfail: perr(gp->progname, "tcgetattr"); +tcfail: err(1, "tcgetattr"); exit (1); } (void)close(fd); } /* Initialize the list of curses functions. */ cl_func_std(gp); return (clp); } /* * term_init -- * Initialize terminal information. */ static void -term_init(char *name, char *ttype) +term_init(char *ttype) { int err; /* Set up the terminal database information. */ setupterm(ttype, STDOUT_FILENO, &err); switch (err) { case -1: - (void)fprintf(stderr, - "%s: No terminal database found\n", name); - exit (1); + errx(1, "No terminal database found"); case 0: - (void)fprintf(stderr, - "%s: %s: unknown terminal type\n", name, ttype); - exit (1); + errx(1, "%s: unknown terminal type", ttype); } } #define GLOBAL_CLP \ CL_PRIVATE *clp = GCLP(__global_list); static void h_hup(int signo) { GLOBAL_CLP; F_SET(clp, CL_SIGHUP); clp->killersig = SIGHUP; } static void h_int(int signo) { GLOBAL_CLP; F_SET(clp, CL_SIGINT); } static void h_term(int signo) { GLOBAL_CLP; F_SET(clp, CL_SIGTERM); clp->killersig = SIGTERM; } static void h_winch(int signo) { GLOBAL_CLP; F_SET(clp, CL_SIGWINCH); } #undef GLOBAL_CLP /* * sig_init -- * Initialize signals. * * PUBLIC: int sig_init(GS *, SCR *); */ int sig_init(GS *gp, SCR *sp) { CL_PRIVATE *clp; clp = GCLP(gp); if (sp == NULL) { (void)sigemptyset(&__sigblockset); if (sigaddset(&__sigblockset, SIGHUP) || setsig(SIGHUP, &clp->oact[INDX_HUP], h_hup) || sigaddset(&__sigblockset, SIGINT) || setsig(SIGINT, &clp->oact[INDX_INT], h_int) || sigaddset(&__sigblockset, SIGTERM) || setsig(SIGTERM, &clp->oact[INDX_TERM], h_term) #ifdef SIGWINCH || sigaddset(&__sigblockset, SIGWINCH) || setsig(SIGWINCH, &clp->oact[INDX_WINCH], h_winch) #endif ) { - perr(gp->progname, NULL); + err(1, NULL); return (1); } } else if (setsig(SIGHUP, NULL, h_hup) || setsig(SIGINT, NULL, h_int) || setsig(SIGTERM, NULL, h_term) #ifdef SIGWINCH || setsig(SIGWINCH, NULL, h_winch) #endif ) { msgq(sp, M_SYSERR, "signal-reset"); } return (0); } /* * setsig -- * Set a signal handler. */ static int setsig(int signo, struct sigaction *oactp, void (*handler)(int)) { struct sigaction act; /* * Use sigaction(2), not signal(3), since we don't always want to * restart system calls. The example is when waiting for a command * mode keystroke and SIGWINCH arrives. Besides, you can't portably * restart system calls (thanks, POSIX!). */ act.sa_handler = handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; return (sigaction(signo, &act, oactp)); } /* * sig_end -- * End signal setup. */ static void sig_end(GS *gp) { CL_PRIVATE *clp; clp = GCLP(gp); (void)sigaction(SIGHUP, NULL, &clp->oact[INDX_HUP]); (void)sigaction(SIGINT, NULL, &clp->oact[INDX_INT]); (void)sigaction(SIGTERM, NULL, &clp->oact[INDX_TERM]); #ifdef SIGWINCH (void)sigaction(SIGWINCH, NULL, &clp->oact[INDX_WINCH]); #endif } /* * cl_func_std -- * Initialize the standard curses functions. */ static void cl_func_std(GS *gp) { gp->scr_addstr = cl_addstr; gp->scr_waddstr = cl_waddstr; gp->scr_attr = cl_attr; gp->scr_baud = cl_baud; gp->scr_bell = cl_bell; gp->scr_busy = NULL; gp->scr_child = NULL; gp->scr_clrtoeol = cl_clrtoeol; gp->scr_cursor = cl_cursor; gp->scr_deleteln = cl_deleteln; gp->scr_reply = NULL; gp->scr_discard = cl_discard; gp->scr_event = cl_event; gp->scr_ex_adjust = cl_ex_adjust; gp->scr_fmap = cl_fmap; gp->scr_insertln = cl_insertln; gp->scr_keyval = cl_keyval; gp->scr_move = cl_move; gp->scr_msg = NULL; gp->scr_optchange = cl_optchange; gp->scr_refresh = cl_refresh; gp->scr_rename = cl_rename; gp->scr_screen = cl_screen; gp->scr_split = cl_split; gp->scr_suspend = cl_suspend; gp->scr_usage = cl_usage; -} - -/* - * perr -- - * Print system error. - */ -static void -perr(char *name, char *msg) -{ - (void)fprintf(stderr, "%s:", name); - if (msg != NULL) - (void)fprintf(stderr, "%s:", msg); - (void)fprintf(stderr, "%s\n", strerror(errno)); - exit(1); } Index: head/contrib/nvi/cl/cl_read.c =================================================================== --- head/contrib/nvi/cl/cl_read.c (revision 365498) +++ head/contrib/nvi/cl/cl_read.c (revision 365499) @@ -1,330 +1,326 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: cl_read.c,v 10.30 2012/07/12 18:28:58 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../ex/script.h" #include "cl.h" /* Pollution by Solaris curses. */ #undef columns #undef lines static input_t cl_read(SCR *, u_int32_t, char *, size_t, int *, struct timeval *); static int cl_resize(SCR *, size_t, size_t); /* * cl_event -- * Return a single event. * * PUBLIC: int cl_event(SCR *, EVENT *, u_int32_t, int); */ int cl_event(SCR *sp, EVENT *evp, u_int32_t flags, int ms) { struct timeval t, *tp; CL_PRIVATE *clp; size_t lines, columns; int changed, nr = 0; CHAR_T *wp; size_t wlen; int rc; /* * Queue signal based events. We never clear SIGHUP or SIGTERM events, * so that we just keep returning them until the editor dies. */ clp = CLP(sp); retest: if (LF_ISSET(EC_INTERRUPT) || F_ISSET(clp, CL_SIGINT)) { if (F_ISSET(clp, CL_SIGINT)) { F_CLR(clp, CL_SIGINT); evp->e_event = E_INTERRUPT; } else evp->e_event = E_TIMEOUT; return (0); } if (F_ISSET(clp, CL_SIGHUP | CL_SIGTERM | CL_SIGWINCH)) { if (F_ISSET(clp, CL_SIGHUP)) { evp->e_event = E_SIGHUP; return (0); } if (F_ISSET(clp, CL_SIGTERM)) { evp->e_event = E_SIGTERM; return (0); } if (F_ISSET(clp, CL_SIGWINCH)) { F_CLR(clp, CL_SIGWINCH); if (cl_ssize(sp, 1, &lines, &columns, &changed)) return (1); if (changed) { (void)cl_resize(sp, lines, columns); evp->e_event = E_WRESIZE; return (0); } /* No real change, ignore the signal. */ } } /* Set timer. */ if (ms == 0) tp = NULL; else { t.tv_sec = ms / 1000; t.tv_usec = (ms % 1000) * 1000; tp = &t; } /* Read input characters. */ read: switch (cl_read(sp, LF_ISSET(EC_QUOTED | EC_RAW), clp->ibuf + clp->skip, SIZE(clp->ibuf) - clp->skip, &nr, tp)) { case INP_OK: rc = INPUT2INT5(sp, clp->cw, clp->ibuf, nr + clp->skip, wp, wlen); evp->e_csp = wp; evp->e_len = wlen; evp->e_event = E_STRING; if (rc < 0) { int n = -rc; memmove(clp->ibuf, clp->ibuf + nr + clp->skip - n, n); clp->skip = n; if (wlen == 0) goto read; } else if (rc == 0) clp->skip = 0; else msgq(sp, M_ERR, "323|Invalid input. Truncated."); break; case INP_EOF: evp->e_event = E_EOF; break; case INP_ERR: evp->e_event = E_ERR; break; case INP_INTR: goto retest; case INP_TIMEOUT: evp->e_event = E_TIMEOUT; break; default: abort(); } return (0); } /* * cl_read -- * Read characters from the input. */ static input_t cl_read(SCR *sp, u_int32_t flags, char *bp, size_t blen, int *nrp, struct timeval *tp) { struct termios term1, term2; CL_PRIVATE *clp; GS *gp; fd_set rdfd; input_t rval; int maxfd, nr, term_reset; gp = sp->gp; clp = CLP(sp); term_reset = 0; /* * 1: A read from a file or a pipe. In this case, the reads * never timeout regardless. This means that we can hang * when trying to complete a map, but we're going to hang * on the next read anyway. */ if (!F_ISSET(clp, CL_STDIN_TTY)) { switch (nr = read(STDIN_FILENO, bp, blen)) { case 0: return (INP_EOF); case -1: goto err; default: *nrp = nr; return (INP_OK); } /* NOTREACHED */ } /* * 2: A read with an associated timeout, e.g., trying to complete * a map sequence. If input exists, we fall into #3. */ if (tp != NULL) { FD_ZERO(&rdfd); FD_SET(STDIN_FILENO, &rdfd); switch (select(STDIN_FILENO + 1, &rdfd, NULL, NULL, tp)) { case 0: return (INP_TIMEOUT); case -1: goto err; default: break; } } /* * The user can enter a key in the editor to quote a character. If we * get here and the next key is supposed to be quoted, do what we can. * Reset the tty so that the user can enter a ^C, ^Q, ^S. There's an * obvious race here, when the key has already been entered, but there's * nothing that we can do to fix that problem. * * The editor can ask for the next literal character even thought it's * generally running in line-at-a-time mode. Do what we can. */ if (LF_ISSET(EC_QUOTED | EC_RAW) && !tcgetattr(STDIN_FILENO, &term1)) { term_reset = 1; if (LF_ISSET(EC_QUOTED)) { term2 = term1; term2.c_lflag &= ~ISIG; term2.c_iflag &= ~(IXON | IXOFF); (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &term2); } else (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &clp->vi_enter); } /* * 3: Wait for input. * * Select on the command input and scripting window file descriptors. * It's ugly that we wait on scripting file descriptors here, but it's * the only way to keep from locking out scripting windows. */ if (F_ISSET(gp, G_SCRWIN)) { loop: FD_ZERO(&rdfd); FD_SET(STDIN_FILENO, &rdfd); maxfd = STDIN_FILENO; if (F_ISSET(sp, SC_SCRIPT)) { FD_SET(sp->script->sh_master, &rdfd); if (sp->script->sh_master > maxfd) maxfd = sp->script->sh_master; } switch (select(maxfd + 1, &rdfd, NULL, NULL, NULL)) { case 0: abort(); case -1: goto err; default: break; } if (!FD_ISSET(STDIN_FILENO, &rdfd)) { if (sscr_input(sp)) return (INP_ERR); goto loop; } } /* * 4: Read the input. * * !!! * What's going on here is some scary stuff. Ex runs the terminal in * canonical mode. So, the character terminating a line of * input is returned in the buffer, but a trailing character is * not similarly included. As ex uses 0 and ^ as autoindent * commands, it has to see the trailing characters to determine * the difference between the user entering "0ab" and "0ab". We * leave an extra slot in the buffer, so that we can add a trailing * character if the buffer isn't terminated by a . We * lose if the buffer is too small for the line and exactly N characters * are entered followed by an character. */ #define ONE_FOR_EOF 1 switch (nr = read(STDIN_FILENO, bp, blen - ONE_FOR_EOF)) { case 0: /* EOF. */ /* * ^D in canonical mode returns a read of 0, i.e. EOF. EOF is * a valid command, but we don't want to loop forever because * the terminal driver is returning EOF because the user has * disconnected. The editor will almost certainly try to write * something before this fires, which should kill us, but You * Never Know. */ if (++clp->eof_count < 50) { bp[0] = clp->orig.c_cc[VEOF]; *nrp = 1; rval = INP_OK; } else rval = INP_EOF; break; case -1: /* Error or interrupt. */ err: if (errno == EINTR) rval = INP_INTR; else { rval = INP_ERR; msgq(sp, M_SYSERR, "input"); } break; default: /* Input characters. */ if (F_ISSET(sp, SC_EX) && bp[nr - 1] != '\n') bp[nr++] = clp->orig.c_cc[VEOF]; *nrp = nr; clp->eof_count = 0; rval = INP_OK; break; } /* Restore the terminal state if it was modified. */ if (term_reset) (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &term1); return (rval); } /* * cl_resize -- * Reset the options for a resize event. */ static int cl_resize(SCR *sp, size_t lines, size_t columns) { ARGS *argv[2], a, b; CHAR_T b1[1024]; a.bp = b1; b.bp = NULL; a.len = b.len = 0; argv[0] = &a; argv[1] = &b; a.len = SPRINTF(b1, sizeof(b1), L("lines=%lu"), (u_long)lines); if (opts_set(sp, argv, NULL)) return (1); a.len = SPRINTF(b1, sizeof(b1), L("columns=%lu"), (u_long)columns); if (opts_set(sp, argv, NULL)) return (1); return (0); } Index: head/contrib/nvi/cl/cl_screen.c =================================================================== --- head/contrib/nvi/cl/cl_screen.c (revision 365498) +++ head/contrib/nvi/cl/cl_screen.c (revision 365499) @@ -1,584 +1,570 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: cl_screen.c,v 10.58 2015/04/08 02:12:11 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #ifdef HAVE_TERM_H #include #endif #include #include #include "../common/common.h" #include "cl.h" static int cl_ex_end(GS *); static int cl_ex_init(SCR *); static void cl_freecap(CL_PRIVATE *); static int cl_vi_end(GS *); static int cl_vi_init(SCR *); static int cl_putenv(char *, char *, u_long); /* * cl_screen -- * Switch screen types. * * PUBLIC: int cl_screen(SCR *, u_int32_t); */ int cl_screen(SCR *sp, u_int32_t flags) { CL_PRIVATE *clp; WINDOW *win; GS *gp; gp = sp->gp; clp = CLP(sp); win = CLSP(sp) ? CLSP(sp) : stdscr; /* See if the current information is incorrect. */ if (F_ISSET(gp, G_SRESTART)) { if ((!F_ISSET(sp, SC_SCR_EX | SC_SCR_VI) || resizeterm(O_VAL(sp, O_LINES), O_VAL(sp, O_COLUMNS))) && cl_quit(gp)) return (1); F_CLR(gp, G_SRESTART); } /* See if we're already in the right mode. */ if ((LF_ISSET(SC_EX) && F_ISSET(sp, SC_SCR_EX)) || (LF_ISSET(SC_VI) && F_ISSET(sp, SC_SCR_VI))) return (0); /* * Fake leaving ex mode. * * We don't actually exit ex or vi mode unless forced (e.g. by a window * size change). This is because many curses implementations can't be * called twice in a single program. Plus, it's faster. If the editor * "leaves" vi to enter ex, when it exits ex we'll just fall back into * vi. */ if (F_ISSET(sp, SC_SCR_EX)) F_CLR(sp, SC_SCR_EX); /* * Fake leaving vi mode. * * Clear out the rest of the screen if we're in the middle of a split * screen. Move to the last line in the current screen -- this makes * terminal scrolling happen naturally. Note: *don't* move past the * end of the screen, as there are ex commands (e.g., :read ! cat file) * that don't want to. Don't clear the info line, its contents may be * valid, e.g. :file|append. */ if (F_ISSET(sp, SC_SCR_VI)) { F_CLR(sp, SC_SCR_VI); if (TAILQ_NEXT(sp, q) != NULL) { (void)wmove(win, RLNO(sp, sp->rows), 0); wclrtobot(win); } (void)wmove(win, RLNO(sp, sp->rows) - 1, 0); wrefresh(win); } /* Enter the requested mode. */ if (LF_ISSET(SC_EX)) { if (cl_ex_init(sp)) return (1); F_SET(clp, CL_IN_EX | CL_SCR_EX_INIT); /* * If doing an ex screen for ex mode, move to the last line * on the screen. */ if (F_ISSET(sp, SC_EX) && clp->cup != NULL) tputs(tgoto(clp->cup, 0, O_VAL(sp, O_LINES) - 1), 1, cl_putchar); } else { if (cl_vi_init(sp)) return (1); F_CLR(clp, CL_IN_EX); F_SET(clp, CL_SCR_VI_INIT); } return (0); } /* * cl_quit -- * Shutdown the screens. * * PUBLIC: int cl_quit(GS *); */ int cl_quit(GS *gp) { CL_PRIVATE *clp; int rval; rval = 0; clp = GCLP(gp); /* * If we weren't really running, ignore it. This happens if the * screen changes size before we've called curses. */ if (!F_ISSET(clp, CL_SCR_EX_INIT | CL_SCR_VI_INIT)) return (0); /* Clean up the terminal mappings. */ if (cl_term_end(gp)) rval = 1; /* Really leave vi mode. */ if (F_ISSET(clp, CL_STDIN_TTY) && F_ISSET(clp, CL_SCR_VI_INIT) && cl_vi_end(gp)) rval = 1; /* Really leave ex mode. */ if (F_ISSET(clp, CL_STDIN_TTY) && F_ISSET(clp, CL_SCR_EX_INIT) && cl_ex_end(gp)) rval = 1; /* * If we were running ex when we quit, or we're using an implementation * of curses where endwin() doesn't get this right, restore the original * terminal modes. * * XXX * We always do this because it's too hard to figure out what curses * implementations get it wrong. It may discard type-ahead characters * from the tty queue. */ (void)tcsetattr(STDIN_FILENO, TCSADRAIN | TCSASOFT, &clp->orig); F_CLR(clp, CL_SCR_EX_INIT | CL_SCR_VI_INIT); return (rval); } /* * cl_vi_init -- * Initialize the curses vi screen. */ static int cl_vi_init(SCR *sp) { CL_PRIVATE *clp; GS *gp; char *o_cols, *o_lines, *o_term, *ttype; gp = sp->gp; clp = CLP(sp); /* If already initialized, just set the terminal modes. */ if (F_ISSET(clp, CL_SCR_VI_INIT)) goto fast; /* Curses vi always reads from (and writes to) a terminal. */ if (!F_ISSET(clp, CL_STDIN_TTY) || !isatty(STDOUT_FILENO)) { msgq(sp, M_ERR, "016|Vi's standard input and output must be a terminal"); return (1); } /* We'll need a terminal type. */ if (opts_empty(sp, O_TERM, 0)) return (1); ttype = O_STR(sp, O_TERM); /* * XXX * Changing the row/column and terminal values is done by putting them * into the environment, which is then read by curses. What this loses * in ugliness, it makes up for in stupidity. We can't simply put the * values into the environment ourselves, because in the presence of a * kernel mechanism for returning the window size, entering values into * the environment will screw up future screen resizing events, e.g. if * the user enters a :shell command and then resizes their window. So, * if they weren't already in the environment, we make sure to delete * them immediately after setting them. * * XXX * Putting the TERM variable into the environment is necessary, even * though we're using newterm() here. We may be using initscr() as * the underlying function. */ o_term = getenv("TERM"); cl_putenv("TERM", ttype, 0); o_lines = getenv("LINES"); cl_putenv("LINES", NULL, (u_long)O_VAL(sp, O_LINES)); o_cols = getenv("COLUMNS"); cl_putenv("COLUMNS", NULL, (u_long)O_VAL(sp, O_COLUMNS)); /* * The terminal is aways initialized, either in `main`, or by a * previous call to newterm(3X). */ (void)del_curterm(cur_term); /* * We never have more than one SCREEN at a time, so set_term(NULL) will * give us the last SCREEN. */ errno = 0; if (newterm(ttype, stdout, stdin) == NULL) { if (errno) msgq(sp, M_SYSERR, "%s", ttype); else msgq(sp, M_ERR, "%s: unknown terminal type", ttype); return (1); } if (o_term == NULL) unsetenv("TERM"); if (o_lines == NULL) unsetenv("LINES"); if (o_cols == NULL) unsetenv("COLUMNS"); /* * XXX * Someone got let out alone without adult supervision -- the SunOS * newterm resets the signal handlers. There's a race, but it's not * worth closing. */ (void)sig_init(sp->gp, sp); /* * We use raw mode. What we want is 8-bit clean, however, signals * and flow control should continue to work. Admittedly, it sounds * like cbreak, but it isn't. Using cbreak() can get you additional * things like IEXTEN, which turns on flags like DISCARD and LNEXT. * * !!! * If raw isn't turning off echo and newlines, something's wrong. * However, it shouldn't hurt. */ noecho(); /* No character echo. */ nonl(); /* No CR/NL translation. */ raw(); /* 8-bit clean. */ idlok(stdscr, 1); /* Use hardware insert/delete line. */ /* Put the cursor keys into application mode. */ (void)keypad(stdscr, TRUE); /* * XXX * The screen TI sequence just got sent. See the comment in * cl_funcs.c:cl_attr(). */ clp->ti_te = TI_SENT; /* * XXX * Historic implementations of curses handled SIGTSTP signals * in one of three ways. They either: * * 1: Set their own handler, regardless. * 2: Did not set a handler if a handler was already installed. * 3: Set their own handler, but then called any previously set * handler after completing their own cleanup. * * We don't try and figure out which behavior is in place, we force * it to SIG_DFL after initializing the curses interface, which means * that curses isn't going to take the signal. Since curses isn't * reentrant (i.e., the whole curses SIGTSTP interface is a fantasy), * we're doing The Right Thing. */ (void)signal(SIGTSTP, SIG_DFL); /* * If flow control was on, turn it back on. Turn signals on. ISIG * turns on VINTR, VQUIT, VDSUSP and VSUSP. The main curses code * already installed a handler for VINTR. We're going to disable the * other three. * * XXX * We want to use ^Y as a vi scrolling command. If the user has the * DSUSP character set to ^Y (common practice) clean it up. As it's * equally possible that the user has VDSUSP set to 'a', we disable * it regardless. It doesn't make much sense to suspend vi at read, * so I don't think anyone will care. Alternatively, we could look * it up in the table of legal command characters and turn it off if * it matches one. VDSUSP wasn't in POSIX 1003.1-1990, so we test for * it. * * XXX * We don't check to see if the user had signals enabled originally. * If they didn't, it's unclear what we're supposed to do here, but * it's also pretty unlikely. */ if (tcgetattr(STDIN_FILENO, &clp->vi_enter)) { msgq(sp, M_SYSERR, "tcgetattr"); goto err; } if (clp->orig.c_iflag & IXON) clp->vi_enter.c_iflag |= IXON; if (clp->orig.c_iflag & IXOFF) clp->vi_enter.c_iflag |= IXOFF; clp->vi_enter.c_lflag |= ISIG; #ifdef VDSUSP clp->vi_enter.c_cc[VDSUSP] = _POSIX_VDISABLE; #endif clp->vi_enter.c_cc[VQUIT] = _POSIX_VDISABLE; clp->vi_enter.c_cc[VSUSP] = _POSIX_VDISABLE; /* * XXX * OSF/1 doesn't turn off the , or * characters when curses switches into raw mode. It should be OK * to do it explicitly for everyone. */ #ifdef VDISCARD clp->vi_enter.c_cc[VDISCARD] = _POSIX_VDISABLE; #endif #ifdef VLNEXT clp->vi_enter.c_cc[VLNEXT] = _POSIX_VDISABLE; #endif #ifdef VSTATUS clp->vi_enter.c_cc[VSTATUS] = _POSIX_VDISABLE; #endif /* Initialize terminal based information. */ if (cl_term_init(sp)) goto err; fast: /* Set the terminal modes. */ if (tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &clp->vi_enter)) { if (errno == EINTR) goto fast; msgq(sp, M_SYSERR, "tcsetattr"); err: (void)cl_vi_end(sp->gp); return (1); } return (0); } /* * cl_vi_end -- * Shutdown the vi screen. */ static int cl_vi_end(GS *gp) { CL_PRIVATE *clp; clp = GCLP(gp); /* Restore the cursor keys to normal mode. */ (void)keypad(stdscr, FALSE); /* * If we were running vi when we quit, scroll the screen up a single * line so we don't lose any information. * * Move to the bottom of the window (some endwin implementations don't * do this for you). */ if (!F_ISSET(clp, CL_IN_EX)) { (void)move(0, 0); (void)deleteln(); (void)move(LINES - 1, 0); (void)refresh(); } cl_freecap(clp); /* End curses window. */ (void)endwin(); /* Free the SCREEN created by newterm(3X). */ delscreen(set_term(NULL)); /* * XXX * The screen TE sequence just got sent. See the comment in * cl_funcs.c:cl_attr(). */ clp->ti_te = TE_SENT; return (0); } /* * cl_ex_init -- * Initialize the ex screen. */ static int cl_ex_init(SCR *sp) { CL_PRIVATE *clp; clp = CLP(sp); /* If already initialized, just set the terminal modes. */ if (F_ISSET(clp, CL_SCR_EX_INIT)) goto fast; /* If not reading from a file, we're done. */ if (!F_ISSET(clp, CL_STDIN_TTY)) return (0); /* Get the ex termcap/terminfo strings. */ (void)cl_getcap(sp, "cup", &clp->cup); (void)cl_getcap(sp, "smso", &clp->smso); (void)cl_getcap(sp, "rmso", &clp->rmso); (void)cl_getcap(sp, "el", &clp->el); (void)cl_getcap(sp, "cuu1", &clp->cuu1); /* Enter_standout_mode and exit_standout_mode are paired. */ if (clp->smso == NULL || clp->rmso == NULL) { - if (clp->smso != NULL) { - free(clp->smso); - clp->smso = NULL; - } - if (clp->rmso != NULL) { - free(clp->rmso); - clp->rmso = NULL; - } + free(clp->smso); + clp->smso = NULL; + + free(clp->rmso); + clp->rmso = NULL; } /* * Turn on canonical mode, with normal input and output processing. * Start with the original terminal settings as the user probably * had them (including any local extensions) set correctly for the * current terminal. * * !!! * We can't get everything that we need portably; for example, ONLCR, * mapping to on output isn't required * by POSIX 1003.1b-1993. If this turns out to be a problem, then * we'll either have to play some games on the mapping, or we'll have * to make all ex printf's output \r\n instead of \n. */ clp->ex_enter = clp->orig; clp->ex_enter.c_lflag |= ECHO | ECHOE | ECHOK | ICANON | IEXTEN | ISIG; #ifdef ECHOCTL clp->ex_enter.c_lflag |= ECHOCTL; #endif #ifdef ECHOKE clp->ex_enter.c_lflag |= ECHOKE; #endif clp->ex_enter.c_iflag |= ICRNL; clp->ex_enter.c_oflag |= OPOST; #ifdef ONLCR clp->ex_enter.c_oflag |= ONLCR; #endif fast: if (tcsetattr(STDIN_FILENO, TCSADRAIN | TCSASOFT, &clp->ex_enter)) { if (errno == EINTR) goto fast; msgq(sp, M_SYSERR, "tcsetattr"); return (1); } return (0); } /* * cl_ex_end -- * Shutdown the ex screen. */ static int cl_ex_end(GS *gp) { CL_PRIVATE *clp; clp = GCLP(gp); cl_freecap(clp); return (0); } /* * cl_getcap -- * Retrieve termcap/terminfo strings. * * PUBLIC: int cl_getcap(SCR *, char *, char **); */ int cl_getcap(SCR *sp, char *name, char **elementp) { size_t len; char *t; if ((t = tigetstr(name)) != NULL && t != (char *)-1 && (len = strlen(t)) != 0) { - MALLOC_RET(sp, *elementp, char *, len + 1); + MALLOC_RET(sp, *elementp, len + 1); memmove(*elementp, t, len + 1); } return (0); } /* * cl_freecap -- * Free any allocated termcap/terminfo strings. */ static void cl_freecap(CL_PRIVATE *clp) { - if (clp->el != NULL) { - free(clp->el); - clp->el = NULL; - } - if (clp->cup != NULL) { - free(clp->cup); - clp->cup = NULL; - } - if (clp->cuu1 != NULL) { - free(clp->cuu1); - clp->cuu1 = NULL; - } - if (clp->rmso != NULL) { - free(clp->rmso); - clp->rmso = NULL; - } - if (clp->smso != NULL) { - free(clp->smso); - clp->smso = NULL; - } + free(clp->el); + clp->el = NULL; + + free(clp->cup); + clp->cup = NULL; + + free(clp->cuu1); + clp->cuu1 = NULL; + + free(clp->rmso); + clp->rmso = NULL; + + free(clp->smso); + clp->smso = NULL; + /* Required by libcursesw :) */ - if (clp->cw.bp1.c != NULL) { - free(clp->cw.bp1.c); - clp->cw.bp1.c = NULL; - clp->cw.blen1 = 0; - } + free(clp->cw.bp1.c); + clp->cw.bp1.c = NULL; + clp->cw.blen1 = 0; } /* * cl_putenv -- * Put a value into the environment. */ static int cl_putenv(char *name, char *str, u_long value) { char buf[40]; if (str == NULL) { (void)snprintf(buf, sizeof(buf), "%lu", value); return (setenv(name, buf, 1)); } else return (setenv(name, str, 1)); } Index: head/contrib/nvi/cl/cl_term.c =================================================================== --- head/contrib/nvi/cl/cl_term.c (revision 365498) +++ head/contrib/nvi/cl/cl_term.c (revision 365499) @@ -1,477 +1,490 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: cl_term.c,v 10.35 2015/04/08 02:12:11 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_TERM_H #include #endif #include #include #include "../common/common.h" #include "cl.h" static int cl_pfmap(SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t); +static size_t atoz_or(const char *, size_t); /* * XXX * THIS REQUIRES THAT ALL SCREENS SHARE A TERMINAL TYPE. */ typedef struct _tklist { char *ts; /* Key's termcap string. */ char *output; /* Corresponding vi command. */ char *name; /* Name. */ u_char value; /* Special value (for lookup). */ } TKLIST; static TKLIST const c_tklist[] = { /* Command mappings. */ {"kil1", "O", "insert line"}, {"kdch1", "x", "delete character"}, {"kcud1", "j", "cursor down"}, {"kel", "D", "delete to eol"}, {"kind", "\004", "scroll down"}, /* ^D */ {"kll", "$", "go to eol"}, {"kend", "$", "go to eol"}, {"khome", "^", "go to sol"}, {"kich1", "i", "insert at cursor"}, {"kdl1", "dd", "delete line"}, {"kcub1", "h", "cursor left"}, {"knp", "\006", "page down"}, /* ^F */ {"kpp", "\002", "page up"}, /* ^B */ {"kri", "\025", "scroll up"}, /* ^U */ {"ked", "dG", "delete to end of screen"}, {"kcuf1", "l", "cursor right"}, {"kcuu1", "k", "cursor up"}, {NULL}, }; static TKLIST const m1_tklist[] = { /* Input mappings (lookup). */ {NULL}, }; static TKLIST const m2_tklist[] = { /* Input mappings (set or delete). */ {"kcud1", "\033ja", "cursor down"}, /* ^[ja */ {"kcub1", "\033ha", "cursor left"}, /* ^[ha */ {"kcuu1", "\033ka", "cursor up"}, /* ^[ka */ {"kcuf1", "\033la", "cursor right"}, /* ^[la */ {NULL}, }; /* * cl_term_init -- * Initialize the special keys defined by the termcap/terminfo entry. * * PUBLIC: int cl_term_init(SCR *); */ int cl_term_init(SCR *sp) { KEYLIST *kp; SEQ *qp; TKLIST const *tkp; char *t; CHAR_T name[60]; CHAR_T output[5]; CHAR_T ts[20]; CHAR_T *wp; size_t wlen; /* Command mappings. */ for (tkp = c_tklist; tkp->name != NULL; ++tkp) { if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1) continue; CHAR2INT(sp, tkp->name, strlen(tkp->name), wp, wlen); MEMCPY(name, wp, wlen); CHAR2INT(sp, t, strlen(t), wp, wlen); MEMCPY(ts, wp, wlen); CHAR2INT(sp, tkp->output, strlen(tkp->output), wp, wlen); MEMCPY(output, wp, wlen); if (seq_set(sp, name, strlen(tkp->name), ts, strlen(t), output, strlen(tkp->output), SEQ_COMMAND, SEQ_NOOVERWRITE | SEQ_SCREEN)) return (1); } /* Input mappings needing to be looked up. */ for (tkp = m1_tklist; tkp->name != NULL; ++tkp) { if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1) continue; for (kp = keylist;; ++kp) if (kp->value == tkp->value) break; if (kp == NULL) continue; CHAR2INT(sp, tkp->name, strlen(tkp->name), wp, wlen); MEMCPY(name, wp, wlen); CHAR2INT(sp, t, strlen(t), wp, wlen); MEMCPY(ts, wp, wlen); output[0] = (UCHAR_T)kp->ch; if (seq_set(sp, name, strlen(tkp->name), ts, strlen(t), output, 1, SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN)) return (1); } /* Input mappings that are already set or are text deletions. */ for (tkp = m2_tklist; tkp->name != NULL; ++tkp) { if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1) continue; /* * !!! * Some terminals' keys send single * characters. This is okay in command mapping, but not okay * in input mapping. That combination is the only one we'll * ever see, hopefully, so kluge it here for now. */ if (!strcmp(t, "\b")) continue; if (tkp->output == NULL) { CHAR2INT(sp, tkp->name, strlen(tkp->name), wp, wlen); MEMCPY(name, wp, wlen); CHAR2INT(sp, t, strlen(t), wp, wlen); MEMCPY(ts, wp, wlen); if (seq_set(sp, name, strlen(tkp->name), ts, strlen(t), NULL, 0, SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN)) return (1); } else { CHAR2INT(sp, tkp->name, strlen(tkp->name), wp, wlen); MEMCPY(name, wp, wlen); CHAR2INT(sp, t, strlen(t), wp, wlen); MEMCPY(ts, wp, wlen); CHAR2INT(sp, tkp->output, strlen(tkp->output), wp, wlen); MEMCPY(output, wp, wlen); if (seq_set(sp, name, strlen(tkp->name), ts, strlen(t), output, strlen(tkp->output), SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN)) return (1); } } /* * Rework any function key mappings that were set before the * screen was initialized. */ SLIST_FOREACH(qp, sp->gp->seqq, q) if (F_ISSET(qp, SEQ_FUNCMAP)) (void)cl_pfmap(sp, qp->stype, qp->input, qp->ilen, qp->output, qp->olen); return (0); } /* * cl_term_end -- * End the special keys defined by the termcap/terminfo entry. * * PUBLIC: int cl_term_end(GS *); */ int cl_term_end(GS *gp) { SEQ *qp, *nqp, *pre_qp = NULL; /* Delete screen specific mappings. */ SLIST_FOREACH_SAFE(qp, gp->seqq, q, nqp) if (F_ISSET(qp, SEQ_SCREEN)) { if (qp == SLIST_FIRST(gp->seqq)) SLIST_REMOVE_HEAD(gp->seqq, q); else SLIST_REMOVE_AFTER(pre_qp, q); (void)seq_free(qp); } else pre_qp = qp; return (0); } /* * cl_fmap -- * Map a function key. * * PUBLIC: int cl_fmap(SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t); */ int cl_fmap(SCR *sp, seq_t stype, CHAR_T *from, size_t flen, CHAR_T *to, size_t tlen) { /* Ignore until the screen is running, do the real work then. */ if (F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_SCR_VI)) return (0); if (F_ISSET(sp, SC_EX) && !F_ISSET(sp, SC_SCR_EX)) return (0); return (cl_pfmap(sp, stype, from, flen, to, tlen)); } /* * cl_pfmap -- * Map a function key (private version). */ static int cl_pfmap(SCR *sp, seq_t stype, CHAR_T *from, size_t flen, CHAR_T *to, size_t tlen) { size_t nlen; char *p; char name[64]; CHAR_T keyname[64]; CHAR_T ts[20]; CHAR_T *wp; size_t wlen; (void)snprintf(name, sizeof(name), "kf%d", (int)STRTOL(from+1,NULL,10)); if ((p = tigetstr(name)) == NULL || p == (char *)-1 || strlen(p) == 0) p = NULL; if (p == NULL) { msgq_wstr(sp, M_ERR, from, "233|This terminal has no %s key"); return (1); } nlen = SPRINTF(keyname, SIZE(keyname), L("function key %d"), (int)STRTOL(from+1,NULL,10)); CHAR2INT(sp, p, strlen(p), wp, wlen); MEMCPY(ts, wp, wlen); return (seq_set(sp, keyname, nlen, ts, strlen(p), to, tlen, stype, SEQ_NOOVERWRITE | SEQ_SCREEN)); } /* * cl_optchange -- * Curses screen specific "option changed" routine. * * PUBLIC: int cl_optchange(SCR *, int, char *, u_long *); */ int cl_optchange(SCR *sp, int opt, char *str, u_long *valp) { CL_PRIVATE *clp; clp = CLP(sp); switch (opt) { case O_TERM: F_CLR(sp, SC_SCR_EX | SC_SCR_VI); /* FALLTHROUGH */ case O_COLUMNS: case O_LINES: /* * Changing the terminal type requires that we reinitialize * curses, while resizing does not. */ F_SET(sp->gp, G_SRESTART); break; case O_MESG: (void)cl_omesg(sp, clp, *valp); break; case O_WINDOWNAME: if (*valp) { F_SET(clp, CL_RENAME_OK); /* * If the screen is live, i.e. we're not reading the * .exrc file, update the window. */ if (sp->frp != NULL && sp->frp->name != NULL) (void)cl_rename(sp, sp->frp->name, 1); } else { F_CLR(clp, CL_RENAME_OK); (void)cl_rename(sp, NULL, 0); } break; } return (0); } /* * cl_omesg -- * Turn the tty write permission on or off. * * PUBLIC: int cl_omesg(SCR *, CL_PRIVATE *, int); */ int cl_omesg(SCR *sp, CL_PRIVATE *clp, int on) { struct stat sb; char *tty; /* Find the tty, get the current permissions. */ if ((tty = ttyname(STDERR_FILENO)) == NULL) { if (sp != NULL) msgq(sp, M_SYSERR, "stderr"); return (1); } if (stat(tty, &sb) < 0) { if (sp != NULL) msgq(sp, M_SYSERR, "%s", tty); return (1); } /* Save the original status if it's unknown. */ if (clp->tgw == TGW_UNKNOWN) clp->tgw = sb.st_mode & S_IWGRP ? TGW_SET : TGW_UNSET; /* Toggle the permissions. */ if (on) { if (chmod(tty, sb.st_mode | S_IWGRP) < 0) { if (sp != NULL) msgq(sp, M_SYSERR, "046|messages not turned on: %s", tty); return (1); } } else if (chmod(tty, sb.st_mode & ~S_IWGRP) < 0) { if (sp != NULL) msgq(sp, M_SYSERR, "045|messages not turned off: %s", tty); return (1); } return (0); } /* * cl_ssize -- * Return the terminal size. * * PUBLIC: int cl_ssize(SCR *, int, size_t *, size_t *, int *); */ int cl_ssize(SCR *sp, int sigwinch, size_t *rowp, size_t *colp, int *changedp) { struct winsize win; size_t col, row; int rval; char *p; /* Assume it's changed. */ if (changedp != NULL) *changedp = 1; /* * !!! * sp may be NULL. * * Get the screen rows and columns. If the values are wrong, it's * not a big deal -- as soon as the user sets them explicitly the * environment will be set and the screen package will use the new * values. * * Try TIOCGWINSZ. */ row = col = 0; if (ioctl(STDERR_FILENO, TIOCGWINSZ, &win) != -1) { row = win.ws_row; col = win.ws_col; } /* If here because of suspend or a signal, only trust TIOCGWINSZ. */ if (sigwinch) { /* * Somebody didn't get TIOCGWINSZ right, or has suspend * without window resizing support. The user just lost, * but there's nothing we can do. */ if (row == 0 || col == 0) { if (changedp != NULL) *changedp = 0; return (0); } /* * SunOS systems deliver SIGWINCH when windows are uncovered * as well as when they change size. In addition, we call * here when continuing after being suspended since the window * may have changed size. Since we don't want to background * all of the screens just because the window was uncovered, * ignore the signal if there's no change. */ if (sp != NULL && row == O_VAL(sp, O_LINES) && col == O_VAL(sp, O_COLUMNS)) { if (changedp != NULL) *changedp = 0; return (0); } if (rowp != NULL) *rowp = row; if (colp != NULL) *colp = col; return (0); } /* * !!! * If TIOCGWINSZ failed, or had entries of 0, try termcap. This * routine is called before any termcap or terminal information * has been set up. If there's no TERM environmental variable set, * let it go, at least ex can run. */ if (row == 0 || col == 0) { if ((p = getenv("TERM")) == NULL) goto noterm; if (row == 0) if ((rval = tigetnum("lines")) < 0) msgq(sp, M_SYSERR, "tigetnum: lines"); else row = rval; if (col == 0) if ((rval = tigetnum("cols")) < 0) msgq(sp, M_SYSERR, "tigetnum: cols"); else col = rval; } /* If nothing else, well, it's probably a VT100. */ noterm: if (row == 0) row = 24; if (col == 0) col = 80; /* * !!! * POSIX 1003.2 requires the environment to override everything. * Often, people can get nvi to stop messing up their screen by * deleting the LINES and COLUMNS environment variables from their * dot-files. */ if ((p = getenv("LINES")) != NULL) - row = strtol(p, NULL, 10); + row = atoz_or(p, row); if ((p = getenv("COLUMNS")) != NULL) - col = strtol(p, NULL, 10); + col = atoz_or(p, col); if (rowp != NULL) *rowp = row; if (colp != NULL) *colp = col; return (0); +} + +/* + * atoz_or -- + * Parse non-zero positive decimal with a fallback. + */ +static size_t +atoz_or(const char *s, size_t y) +{ + char *ep; + long x = strtol(s, &ep, 10); + + if (*ep == '\0' && (0 < x && x < INT_MAX)) + return (size_t)x; + else + return y; } /* * cl_putchar -- * Function version of putchar, for tputs. * * PUBLIC: int cl_putchar(int); */ int cl_putchar(int ch) { return (putchar(ch)); } Index: head/contrib/nvi/common/args.h =================================================================== --- head/contrib/nvi/common/args.h (revision 365498) +++ head/contrib/nvi/common/args.h (revision 365499) @@ -1,29 +1,27 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: args.h,v 10.2 1996/03/06 19:50:07 bostic Exp $ */ /* * Structure for building "argc/argv" vector of arguments. * * !!! * All arguments are nul terminated as well as having an associated length. * The argument vector is NOT necessarily NULL terminated. The proper way * to check the number of arguments is to use the argc value in the EXCMDARG * structure or to walk the array until an ARGS structure with a length of 0 * is found. */ typedef struct _args { CHAR_T *bp; /* Argument. */ size_t blen; /* Buffer length. */ size_t len; /* Argument length. */ #define A_ALLOCATED 0x01 /* If allocated space. */ u_int8_t flags; } ARGS; Index: head/contrib/nvi/common/common.h =================================================================== --- head/contrib/nvi/common/common.h (revision 365498) +++ head/contrib/nvi/common/common.h (revision 365499) @@ -1,93 +1,86 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: common.h,v 10.22 2012/04/13 05:21:50 zy Exp $ */ -/* - * Pseudo-local includes. These are files that are unlikely to exist - * on most machines to which we're porting vi, and we want to include - * them in a very specific order, regardless. - */ -#include -#include +#include "/usr/include/db.h" /* Only include db1. */ +#include /* May refer to the bundled regex. */ /* * Forward structure declarations. Not pretty, but the include files * are far too interrelated for a clean solution. */ typedef struct _cb CB; typedef struct _csc CSC; typedef struct _conv CONV; typedef struct _conv_win CONVWIN; typedef struct _event EVENT; typedef struct _excmd EXCMD; typedef struct _exf EXF; typedef struct _fref FREF; typedef struct _gs GS; typedef struct _lmark LMARK; typedef struct _mark MARK; typedef struct _msg MSGS; typedef struct _option OPTION; typedef struct _optlist OPTLIST; typedef struct _scr SCR; typedef struct _script SCRIPT; typedef struct _seq SEQ; typedef struct _tag TAG; typedef struct _tagf TAGF; typedef struct _tagq TAGQ; typedef struct _text TEXT; /* Autoindent state. */ typedef enum { C_NOTSET, C_CARATSET, C_ZEROSET } carat_t; /* Busy message types. */ typedef enum { BUSY_ON = 1, BUSY_OFF, BUSY_UPDATE } busy_t; /* * Routines that return a confirmation return: * * CONF_NO User answered no. * CONF_QUIT User answered quit, eof or an error. * CONF_YES User answered yes. */ typedef enum { CONF_NO, CONF_QUIT, CONF_YES } conf_t; /* Directions. */ typedef enum { NOTSET, FORWARD, BACKWARD } dir_t; /* Line operations. */ typedef enum { LINE_APPEND, LINE_DELETE, LINE_INSERT, LINE_RESET } lnop_t; /* Lock return values. */ typedef enum { LOCK_FAILED, LOCK_SUCCESS, LOCK_UNAVAIL } lockr_t; /* Sequence types. */ typedef enum { SEQ_ABBREV, SEQ_COMMAND, SEQ_INPUT } seq_t; /* * Local includes. */ #include "key.h" /* Required by args.h. */ #include "args.h" /* Required by options.h. */ #include "options.h" /* Required by screen.h. */ #include "msg.h" /* Required by gs.h. */ #include "cut.h" /* Required by gs.h. */ #include "seq.h" /* Required by screen.h. */ #include "util.h" /* Required by ex.h. */ #include "mark.h" /* Required by gs.h. */ #include "conv.h" /* Required by ex.h and screen.h */ #include "../ex/ex.h" /* Required by gs.h. */ #include "gs.h" /* Required by screen.h. */ #include "screen.h" /* Required by exf.h. */ #include "exf.h" #include "log.h" #include "mem.h" #include "extern.h" Index: head/contrib/nvi/common/conv.c =================================================================== --- head/contrib/nvi/common/conv.c (revision 365498) +++ head/contrib/nvi/common/conv.c (revision 365499) @@ -1,470 +1,465 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2011, 2012 * Zhihao Yuan. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: conv.c,v 2.40 2014/02/27 16:25:29 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" /* * codeset -- * Get the locale encoding. * * PUBLIC: char * codeset(void); */ char * codeset(void) { static char *cs; if (cs == NULL) cs = nl_langinfo(CODESET); return cs; } #ifdef USE_WIDECHAR static int raw2int(SCR *sp, const char * str, ssize_t len, CONVWIN *cw, size_t *tolen, CHAR_T **dst) { int i; CHAR_T **tostr = &cw->bp1.wc; size_t *blen = &cw->blen1; BINC_RETW(NULL, *tostr, *blen, len); *tolen = len; for (i = 0; i < len; ++i) (*tostr)[i] = (u_char) str[i]; *dst = cw->bp1.wc; return 0; } #define CONV_BUFFER_SIZE 512 /* fill the buffer with codeset encoding of string pointed to by str * left has the number of bytes left in str and is adjusted * len contains the number of bytes put in the buffer */ #ifdef USE_ICONV #define CONVERT(str, left, src, len) \ do { \ size_t outleft; \ char *bp = buffer; \ outleft = CONV_BUFFER_SIZE; \ errno = 0; \ if (iconv(id, (iconv_src_t)&str, &left, &bp, &outleft) \ == -1 && errno != E2BIG) \ goto err; \ if ((len = CONV_BUFFER_SIZE - outleft) == 0) { \ error = -left; \ goto err; \ } \ src = buffer; \ } while (0) #define IC_RESET() \ do { \ if (id != (iconv_t)-1) \ iconv(id, NULL, NULL, NULL, NULL); \ } while(0) #else #define CONVERT(str, left, src, len) #define IC_RESET() #endif static int default_char2int(SCR *sp, const char * str, ssize_t len, CONVWIN *cw, size_t *tolen, CHAR_T **dst, iconv_t id) { size_t i = 0, j; CHAR_T **tostr = &cw->bp1.wc; size_t *blen = &cw->blen1; mbstate_t mbs; size_t n; ssize_t nlen = len; char *src = (char *)str; #ifdef USE_ICONV char buffer[CONV_BUFFER_SIZE]; #endif size_t left = len; int error = 1; - BZERO(&mbs, 1); + memset(&mbs, 0, sizeof(mbs)); BINC_RETW(NULL, *tostr, *blen, nlen); #ifdef USE_ICONV if (id != (iconv_t)-1) CONVERT(str, left, src, len); #endif for (i = 0, j = 0; j < len; ) { n = mbrtowc((*tostr)+i, src+j, len-j, &mbs); /* NULL character converted */ if (n == -2) error = -(len-j); if (n == -1 || n == -2) goto err; if (n == 0) n = 1; j += n; if (++i >= *blen) { nlen += 256; BINC_RETW(NULL, *tostr, *blen, nlen); } if (id != (iconv_t)-1 && j == len && left) { CONVERT(str, left, src, len); j = 0; } } error = 0; err: *tolen = i; *dst = cw->bp1.wc; IC_RESET(); return error; } static int fe_char2int(SCR *sp, const char * str, ssize_t len, CONVWIN *cw, size_t *tolen, CHAR_T **dst) { return default_char2int(sp, str, len, cw, tolen, dst, sp->conv.id[IC_FE_CHAR2INT]); } static int ie_char2int(SCR *sp, const char * str, ssize_t len, CONVWIN *cw, size_t *tolen, CHAR_T **dst) { return default_char2int(sp, str, len, cw, tolen, dst, sp->conv.id[IC_IE_CHAR2INT]); } static int cs_char2int(SCR *sp, const char * str, ssize_t len, CONVWIN *cw, size_t *tolen, CHAR_T **dst) { return default_char2int(sp, str, len, cw, tolen, dst, (iconv_t)-1); } static int int2raw(SCR *sp, const CHAR_T * str, ssize_t len, CONVWIN *cw, size_t *tolen, char **dst) { int i; char **tostr = &cw->bp1.c; size_t *blen = &cw->blen1; BINC_RETC(NULL, *tostr, *blen, len); *tolen = len; for (i = 0; i < len; ++i) (*tostr)[i] = str[i]; *dst = cw->bp1.c; return 0; } static int default_int2char(SCR *sp, const CHAR_T * str, ssize_t len, CONVWIN *cw, size_t *tolen, char **pdst, iconv_t id) { size_t i, j, offset = 0; char **tostr = &cw->bp1.c; size_t *blen = &cw->blen1; mbstate_t mbs; size_t n; ssize_t nlen = len + MB_CUR_MAX; char *dst; size_t buflen; #ifdef USE_ICONV char buffer[CONV_BUFFER_SIZE]; #endif int error = 1; /* convert first len bytes of buffer and append it to cw->bp * len is adjusted => 0 * offset contains the offset in cw->bp and is adjusted * cw->bp is grown as required */ #ifdef USE_ICONV #define CONVERT2(_buffer, lenp, cw, offset) \ do { \ char *bp = _buffer; \ int ret; \ do { \ size_t outleft = cw->blen1 - offset; \ char *obp = cw->bp1.c + offset; \ if (cw->blen1 < offset + MB_CUR_MAX) { \ nlen += 256; \ BINC_RETC(NULL, cw->bp1.c, cw->blen1, \ nlen); \ } \ errno = 0; \ ret = iconv(id, (iconv_src_t)&bp, lenp, &obp, \ &outleft); \ if (ret == -1 && errno != E2BIG) \ goto err; \ offset = cw->blen1 - outleft; \ } while (ret != 0); \ } while (0) #else #define CONVERT2(_buffer, lenp, cw, offset) #endif - BZERO(&mbs, 1); + memset(&mbs, 0, sizeof(mbs)); BINC_RETC(NULL, *tostr, *blen, nlen); dst = *tostr; buflen = *blen; #ifdef USE_ICONV if (id != (iconv_t)-1) { dst = buffer; buflen = CONV_BUFFER_SIZE; } #endif for (i = 0, j = 0; i < len; ++i) { n = wcrtomb(dst+j, str[i], &mbs); if (n == -1) goto err; j += n; if (buflen < j + MB_CUR_MAX) { if (id != (iconv_t)-1) { CONVERT2(buffer, &j, cw, offset); } else { nlen += 256; BINC_RETC(NULL, *tostr, *blen, nlen); dst = *tostr; buflen = *blen; } } } n = wcrtomb(dst+j, L'\0', &mbs); j += n - 1; /* don't count NUL at the end */ *tolen = j; if (id != (iconv_t)-1) { CONVERT2(buffer, &j, cw, offset); /* back to the initial state */ CONVERT2(NULL, NULL, cw, offset); *tolen = offset; } error = 0; err: if (error) *tolen = j; *pdst = cw->bp1.c; IC_RESET(); return error; } static int fe_int2char(SCR *sp, const CHAR_T * str, ssize_t len, CONVWIN *cw, size_t *tolen, char **dst) { return default_int2char(sp, str, len, cw, tolen, dst, sp->conv.id[IC_FE_INT2CHAR]); } static int cs_int2char(SCR *sp, const CHAR_T * str, ssize_t len, CONVWIN *cw, size_t *tolen, char **dst) { return default_int2char(sp, str, len, cw, tolen, dst, (iconv_t)-1); } #endif /* * conv_init -- * Initialize the iconv environment. * * PUBLIC: void conv_init(SCR *, SCR *); */ void conv_init(SCR *orig, SCR *sp) { int i; if (orig == NULL) setlocale(LC_ALL, ""); if (orig != NULL) - BCOPY(&orig->conv, &sp->conv, 1); + memmove(&sp->conv, &orig->conv, sizeof(CONV)); #ifdef USE_WIDECHAR else { char *ctype = setlocale(LC_CTYPE, NULL); /* * XXX * This hack fixes the libncursesw issue on FreeBSD. */ if (!strcmp(ctype, "ko_KR.CP949")) setlocale(LC_CTYPE, "ko_KR.eucKR"); else if (!strcmp(ctype, "zh_CN.GB2312")) setlocale(LC_CTYPE, "zh_CN.eucCN"); else if (!strcmp(ctype, "zh_CN.GBK")) setlocale(LC_CTYPE, "zh_CN.GB18030"); /* * Switch to 8bit mode if locale is C; * LC_CTYPE should be reseted to C if unmatched. */ if (!strcmp(ctype, "C") || !strcmp(ctype, "POSIX")) { sp->conv.sys2int = sp->conv.file2int = raw2int; sp->conv.int2sys = sp->conv.int2file = int2raw; sp->conv.input2int = raw2int; } else { sp->conv.sys2int = cs_char2int; sp->conv.int2sys = cs_int2char; sp->conv.file2int = fe_char2int; sp->conv.int2file = fe_int2char; sp->conv.input2int = ie_char2int; } #ifdef USE_ICONV o_set(sp, O_INPUTENCODING, OS_STRDUP, codeset(), 0); #endif } #endif /* iconv descriptors must be distinct to screens. */ for (i = 0; i <= IC_IE_TO_UTF16; ++i) sp->conv.id[i] = (iconv_t)-1; #ifdef USE_ICONV conv_enc(sp, O_INPUTENCODING, 0); #endif } /* * conv_enc -- * Convert file/input encoding. * * PUBLIC: int conv_enc(SCR *, int, char *); */ int conv_enc(SCR *sp, int option, char *enc) { #if defined(USE_WIDECHAR) && defined(USE_ICONV) iconv_t *c2w, *w2c; iconv_t id_c2w, id_w2c; switch (option) { case O_FILEENCODING: c2w = sp->conv.id + IC_FE_CHAR2INT; w2c = sp->conv.id + IC_FE_INT2CHAR; if (!enc) enc = O_STR(sp, O_FILEENCODING); if (strcasecmp(codeset(), enc)) { if ((id_c2w = iconv_open(codeset(), enc)) == (iconv_t)-1) goto err; if ((id_w2c = iconv_open(enc, codeset())) == (iconv_t)-1) goto err; } else { id_c2w = (iconv_t)-1; id_w2c = (iconv_t)-1; } break; case O_INPUTENCODING: c2w = sp->conv.id + IC_IE_CHAR2INT; w2c = sp->conv.id + IC_IE_TO_UTF16; if (!enc) enc = O_STR(sp, O_INPUTENCODING); if (strcasecmp(codeset(), enc)) { if ((id_c2w = iconv_open(codeset(), enc)) == (iconv_t)-1) goto err; } else id_c2w = (iconv_t)-1; /* UTF-16 can not be locale and can not be inputed. */ if ((id_w2c = iconv_open("utf-16be", enc)) == (iconv_t)-1) goto err; break; default: abort(); } if (*c2w != (iconv_t)-1) iconv_close(*c2w); if (*w2c != (iconv_t)-1) iconv_close(*w2c); *c2w = id_c2w; *w2c = id_w2c; F_CLR(sp, SC_CONV_ERROR); F_SET(sp, SC_SCR_REFORMAT); return 0; err: #endif switch (option) { case O_FILEENCODING: msgq(sp, M_ERR, "321|File encoding conversion not supported"); break; case O_INPUTENCODING: msgq(sp, M_ERR, "322|Input encoding conversion not supported"); break; } return 1; } /* * conv_end -- * Close the iconv descriptors, release the buffer. * * PUBLIC: void conv_end(SCR *); */ void conv_end(SCR *sp) { #if defined(USE_WIDECHAR) && defined(USE_ICONV) int i; for (i = 0; i <= IC_IE_TO_UTF16; ++i) if (sp->conv.id[i] != (iconv_t)-1) iconv_close(sp->conv.id[i]); - if (sp->cw.bp1.c != NULL) - free(sp->cw.bp1.c); + free(sp->cw.bp1.c); #endif } Index: head/contrib/nvi/common/conv.h =================================================================== --- head/contrib/nvi/common/conv.h (revision 365498) +++ head/contrib/nvi/common/conv.h (revision 365499) @@ -1,57 +1,55 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2011, 2012 * Zhihao Yuan. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: conv.h,v 2.32 2013/03/11 01:20:53 zy Exp $ */ #ifdef USE_ICONV #include #ifdef ICONV_TRADITIONAL typedef char ** iconv_src_t; #else typedef char const ** iconv_src_t; #endif #else typedef int iconv_t; #endif /* * XXX * We can not use MB_CUR_MAX here, since UTF-8 may report it as 6, but * a sequence longer than 4 is deprecated by RFC 3629. */ #define KEY_NEEDSWIDE(sp, ch) \ (INTISWIDE(ch) && KEY_LEN(sp, ch) <= 4) #define KEY_COL(sp, ch) \ (KEY_NEEDSWIDE(sp, ch) ? CHAR_WIDTH(sp, ch) : KEY_LEN(sp, ch)) enum { IC_FE_CHAR2INT, IC_FE_INT2CHAR, IC_IE_CHAR2INT, IC_IE_TO_UTF16 }; struct _conv_win { union { char *c; CHAR_T *wc; } bp1; size_t blen1; }; typedef int (*char2wchar_t) (SCR *, const char *, ssize_t, struct _conv_win *, size_t *, CHAR_T **); typedef int (*wchar2char_t) (SCR *, const CHAR_T *, ssize_t, struct _conv_win *, size_t *, char **); struct _conv { char2wchar_t sys2int; wchar2char_t int2sys; char2wchar_t file2int; wchar2char_t int2file; char2wchar_t input2int; iconv_t id[IC_IE_TO_UTF16 + 1]; }; Index: head/contrib/nvi/common/cut.c =================================================================== --- head/contrib/nvi/common/cut.c (revision 365498) +++ head/contrib/nvi/common/cut.c (revision 365499) @@ -1,351 +1,333 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: cut.c,v 10.12 2012/02/11 15:52:33 zy Exp $"; -#endif /* not lint */ - #include #include +#include #include #include #include #include #include #include #include #include #include "common.h" static void cb_rotate(SCR *); /* * cut -- * Put a range of lines/columns into a TEXT buffer. * * There are two buffer areas, both found in the global structure. The first * is the linked list of all the buffers the user has named, the second is the * unnamed buffer storage. There is a pointer, too, which is the current * default buffer, i.e. it may point to the unnamed buffer or a named buffer * depending on into what buffer the last text was cut. Logically, in both * delete and yank operations, if the user names a buffer, the text is cut * into it. If it's a delete of information on more than a single line, the * contents of the numbered buffers are rotated up one, the contents of the * buffer named '9' are discarded, and the text is cut into the buffer named * '1'. The text is always cut into the unnamed buffer. * * In all cases, upper-case buffer names are the same as lower-case names, * with the exception that they cause the buffer to be appended to instead * of replaced. Note, however, that if text is appended to a buffer, the * default buffer only contains the appended text, not the entire contents * of the buffer. * * !!! * The contents of the default buffer would disappear after most operations * in historic vi. It's unclear that this is useful, so we don't bother. * * When users explicitly cut text into the numeric buffers, historic vi became * genuinely strange. I've never been able to figure out what was supposed to * happen. It behaved differently if you deleted text than if you yanked text, * and, in the latter case, the text was appended to the buffer instead of * replacing the contents. Hopefully it's not worth getting right, and here * we just treat the numeric buffers like any other named buffer. * * PUBLIC: int cut(SCR *, CHAR_T *, MARK *, MARK *, int); */ int -cut( - SCR *sp, - CHAR_T *namep, - MARK *fm, - MARK *tm, - int flags) +cut(SCR *sp, CHAR_T *namep, MARK *fm, MARK *tm, int flags) { CB *cbp; CHAR_T name = '\0'; recno_t lno; int append, copy_one, copy_def; /* * If the user specified a buffer, put it there. (This may require * a copy into the numeric buffers. We do the copy so that we don't * have to reference count and so we don't have to deal with things * like appends to buffers that are used multiple times.) * * Otherwise, if it's supposed to be put in a numeric buffer (usually * a delete) put it there. The rules for putting things in numeric * buffers were historically a little strange. There were three cases. * * 1: Some motions are always line mode motions, which means * that the cut always goes into the numeric buffers. * 2: Some motions aren't line mode motions, e.g. d10w, but * can cross line boundaries. For these commands, if the * cut crosses a line boundary, it goes into the numeric * buffers. This includes most of the commands. * 3: Some motions aren't line mode motions, e.g. d`, * but always go into the numeric buffers, regardless. This * was the commands: % ` / ? ( ) N n { } -- and nvi adds ^A. * * Otherwise, put it in the unnamed buffer. */ append = copy_one = copy_def = 0; if (namep != NULL) { name = *namep; if (LF_ISSET(CUT_NUMREQ) || (LF_ISSET(CUT_NUMOPT) && (LF_ISSET(CUT_LINEMODE) || fm->lno != tm->lno))) { copy_one = 1; cb_rotate(sp); } if ((append = isupper(name))) { if (!copy_one) copy_def = 1; name = tolower(name); } namecb: CBNAME(sp, cbp, name); } else if (LF_ISSET(CUT_NUMREQ) || (LF_ISSET(CUT_NUMOPT) && (LF_ISSET(CUT_LINEMODE) || fm->lno != tm->lno))) { name = '1'; cb_rotate(sp); goto namecb; } else cbp = &sp->gp->dcb_store; copyloop: /* * If this is a new buffer, create it and add it into the list. * Otherwise, if it's not an append, free its current contents. */ if (cbp == NULL) { - CALLOC_RET(sp, cbp, CB *, 1, sizeof(CB)); + CALLOC_RET(sp, cbp, 1, sizeof(CB)); cbp->name = name; TAILQ_INIT(cbp->textq); SLIST_INSERT_HEAD(sp->gp->cutq, cbp, q); } else if (!append) { text_lfree(cbp->textq); cbp->len = 0; cbp->flags = 0; } /* In line mode, it's pretty easy, just cut the lines. */ if (LF_ISSET(CUT_LINEMODE)) { cbp->flags |= CB_LMODE; for (lno = fm->lno; lno <= tm->lno; ++lno) if (cut_line(sp, lno, 0, ENTIRE_LINE, cbp)) goto cut_line_err; } else { /* * Get the first line. A length of ENTIRE_LINE causes * cut_line to cut from the MARK to the end of the line. */ if (cut_line(sp, fm->lno, fm->cno, fm->lno != tm->lno ? ENTIRE_LINE : (tm->cno - fm->cno) + 1, cbp)) goto cut_line_err; /* Get the intermediate lines. */ for (lno = fm->lno; ++lno < tm->lno;) if (cut_line(sp, lno, 0, ENTIRE_LINE, cbp)) goto cut_line_err; /* Get the last line. */ if (tm->lno != fm->lno && cut_line(sp, lno, 0, tm->cno + 1, cbp)) goto cut_line_err; } append = 0; /* Only append to the named buffer. */ sp->gp->dcbp = cbp; /* Repoint the default buffer on each pass. */ if (copy_one) { /* Copy into numeric buffer 1. */ name = '1'; CBNAME(sp, cbp, name); copy_one = 0; goto copyloop; } if (copy_def) { /* Copy into the default buffer. */ cbp = &sp->gp->dcb_store; copy_def = 0; goto copyloop; } return (0); cut_line_err: text_lfree(cbp->textq); cbp->len = 0; cbp->flags = 0; return (1); } /* * cb_rotate -- * Rotate the numbered buffers up one. */ static void cb_rotate(SCR *sp) { CB *cbp, *del_cbp = NULL, *pre_cbp = NULL; SLIST_FOREACH(cbp, sp->gp->cutq, q) { switch(cbp->name) { case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': cbp->name += 1; break; case '9': if (cbp == SLIST_FIRST(sp->gp->cutq)) SLIST_REMOVE_HEAD(sp->gp->cutq, q); else SLIST_REMOVE_AFTER(pre_cbp, q); del_cbp = cbp; break; } pre_cbp = cbp; } if (del_cbp != NULL) { text_lfree(del_cbp->textq); free(del_cbp); } } /* * cut_line -- * Cut a portion of a single line. * * PUBLIC: int cut_line(SCR *, recno_t, size_t, size_t, CB *); */ int -cut_line( - SCR *sp, - recno_t lno, - size_t fcno, - size_t clen, - CB *cbp) +cut_line(SCR *sp, recno_t lno, size_t fcno, size_t clen, CB *cbp) { TEXT *tp; size_t len; CHAR_T *p; /* Get the line. */ if (db_get(sp, lno, DBG_FATAL, &p, &len)) return (1); /* Create a TEXT structure that can hold the entire line. */ if ((tp = text_init(sp, NULL, 0, len)) == NULL) return (1); /* * If the line isn't empty and it's not the entire line, * copy the portion we want, and reset the TEXT length. */ if (len != 0) { if (clen == ENTIRE_LINE) clen = len - fcno; MEMCPY(tp->lb, p + fcno, clen); tp->len = clen; } /* Append to the end of the cut buffer. */ TAILQ_INSERT_TAIL(cbp->textq, tp, q); cbp->len += tp->len; return (0); } /* * cut_close -- * Discard all cut buffers. * * PUBLIC: void cut_close(GS *); */ void cut_close(GS *gp) { CB *cbp; /* Free cut buffer list. */ while ((cbp = SLIST_FIRST(gp->cutq)) != NULL) { if (!TAILQ_EMPTY(cbp->textq)) text_lfree(cbp->textq); SLIST_REMOVE_HEAD(gp->cutq, q); free(cbp); } /* Free default cut storage. */ cbp = &gp->dcb_store; if (!TAILQ_EMPTY(cbp->textq)) text_lfree(cbp->textq); } /* * text_init -- * Allocate a new TEXT structure. * * PUBLIC: TEXT *text_init(SCR *, const CHAR_T *, size_t, size_t); */ TEXT * -text_init( - SCR *sp, - const CHAR_T *p, - size_t len, - size_t total_len) +text_init(SCR *sp, const CHAR_T *p, size_t len, size_t total_len) { TEXT *tp; - CALLOC(sp, tp, TEXT *, 1, sizeof(TEXT)); + CALLOC(sp, tp, 1, sizeof(TEXT)); if (tp == NULL) return (NULL); /* ANSI C doesn't define a call to malloc(3) for 0 bytes. */ if ((tp->lb_len = total_len * sizeof(CHAR_T)) != 0) { - MALLOC(sp, tp->lb, CHAR_T *, tp->lb_len); + MALLOC(sp, tp->lb, tp->lb_len); if (tp->lb == NULL) { free(tp); return (NULL); } if (p != NULL && len != 0) MEMCPY(tp->lb, p, len); } tp->len = len; return (tp); } /* * text_lfree -- * Free a chain of text structures. * * PUBLIC: void text_lfree(TEXTH *); */ void text_lfree(TEXTH *headp) { TEXT *tp; while ((tp = TAILQ_FIRST(headp)) != NULL) { TAILQ_REMOVE(headp, tp, q); text_free(tp); } } /* * text_free -- * Free a text structure. * * PUBLIC: void text_free(TEXT *); */ void text_free(TEXT *tp) { - if (tp->lb != NULL) - free(tp->lb); + free(tp->lb); free(tp); } Index: head/contrib/nvi/common/cut.h =================================================================== --- head/contrib/nvi/common/cut.h (revision 365498) +++ head/contrib/nvi/common/cut.h (revision 365499) @@ -1,79 +1,77 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: cut.h,v 10.10 2012/02/11 15:52:33 zy Exp $ */ typedef struct _texth TEXTH; /* TEXT list head structure. */ TAILQ_HEAD(_texth, _text); /* Cut buffers. */ struct _cb { SLIST_ENTRY(_cb) q; /* Linked list of cut buffers. */ TEXTH textq[1]; /* Linked list of TEXT structures. */ /* XXXX Needed ? Can non ascii-chars be cut buffer names ? */ CHAR_T name; /* Cut buffer name. */ size_t len; /* Total length of cut text. */ #define CB_LMODE 0x01 /* Cut was in line mode. */ u_int8_t flags; }; /* Lines/blocks of text. */ struct _text { /* Text: a linked list of lines. */ TAILQ_ENTRY(_text) q; /* Linked list of text structures. */ CHAR_T *lb; /* Line buffer. */ size_t lb_len; /* Line buffer length. */ size_t len; /* Line length. */ /* These fields are used by the vi text input routine. */ recno_t lno; /* 1-N: file line. */ #define ENTIRE_LINE ((size_t)-1) /* cno: end of the line. */ size_t cno; /* 0-N: file character in line. */ size_t ai; /* 0-N: autoindent bytes. */ size_t insert; /* 0-N: bytes to insert (push). */ size_t offset; /* 0-N: initial, unerasable chars. */ size_t owrite; /* 0-N: chars to overwrite. */ size_t R_erase; /* 0-N: 'R' erase count. */ size_t sv_cno; /* 0-N: Saved line cursor. */ size_t sv_len; /* 0-N: Saved line length. */ /* * These fields returns information from the vi text input routine. * * The termination condition. Note, this field is only valid if the * text input routine returns success. * TERM_BS: User backspaced over the prompt. * TERM_CEDIT: User entered . * TERM_CR: User entered ; no data. * TERM_ESC: User entered ; no data. * TERM_OK: Data available. * TERM_SEARCH: Incremental search. */ enum { TERM_BS, TERM_CEDIT, TERM_CR, TERM_ESC, TERM_OK, TERM_SEARCH } term; }; /* * Get named buffer 'name'. * Translate upper-case buffer names to lower-case buffer names. */ #define CBNAME(sp, cbp, nch) { \ CHAR_T L__name; \ L__name = isupper(nch) ? tolower(nch) : (nch); \ SLIST_FOREACH(cbp, sp->gp->cutq, q) \ if (cbp->name == L__name) \ break; \ } /* Flags to the cut() routine. */ #define CUT_LINEMODE 0x01 /* Cut in line mode. */ #define CUT_NUMOPT 0x02 /* Numeric buffer: optional. */ #define CUT_NUMREQ 0x04 /* Numeric buffer: required. */ Index: head/contrib/nvi/common/delete.c =================================================================== --- head/contrib/nvi/common/delete.c (revision 365498) +++ head/contrib/nvi/common/delete.c (revision 365499) @@ -1,163 +1,157 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: delete.c,v 10.18 2012/02/11 15:52:33 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "common.h" /* * del -- * Delete a range of text. * * PUBLIC: int del(SCR *, MARK *, MARK *, int); */ int -del( - SCR *sp, - MARK *fm, - MARK *tm, - int lmode) +del(SCR *sp, MARK *fm, MARK *tm, int lmode) { recno_t lno; size_t blen, len, nlen, tlen; CHAR_T *bp, *p; int eof, rval; bp = NULL; /* Case 1 -- delete in line mode. */ if (lmode) { for (lno = tm->lno; lno >= fm->lno; --lno) { if (db_delete(sp, lno)) return (1); ++sp->rptlines[L_DELETED]; if (lno % INTERRUPT_CHECK == 0 && INTERRUPTED(sp)) break; } goto done; } /* * Case 2 -- delete to EOF. This is a special case because it's * easier to pick it off than try and find it in the other cases. */ if (db_last(sp, &lno)) return (1); if (tm->lno >= lno) { if (tm->lno == lno) { if (db_get(sp, lno, DBG_FATAL, &p, &len)) return (1); eof = tm->cno != ENTIRE_LINE && tm->cno >= len ? 1 : 0; } else eof = 1; if (eof) { for (lno = tm->lno; lno > fm->lno; --lno) { if (db_delete(sp, lno)) return (1); ++sp->rptlines[L_DELETED]; if (lno % INTERRUPT_CHECK == 0 && INTERRUPTED(sp)) break; } if (db_get(sp, fm->lno, DBG_FATAL, &p, &len)) return (1); GET_SPACE_RETW(sp, bp, blen, fm->cno); MEMCPY(bp, p, fm->cno); if (db_set(sp, fm->lno, bp, fm->cno)) return (1); goto done; } } /* Case 3 -- delete within a single line. */ if (tm->lno == fm->lno) { if (db_get(sp, fm->lno, DBG_FATAL, &p, &len)) return (1); - GET_SPACE_RETW(sp, bp, blen, len); - if (fm->cno != 0) - MEMCPY(bp, p, fm->cno); - MEMCPY(bp + fm->cno, p + (tm->cno + 1), - len - (tm->cno + 1)); - if (db_set(sp, fm->lno, - bp, len - ((tm->cno - fm->cno) + 1))) - goto err; + if (len != 0) { + GET_SPACE_RETW(sp, bp, blen, len); + if (fm->cno != 0) + MEMCPY(bp, p, fm->cno); + MEMCPY(bp + fm->cno, p + (tm->cno + 1), + len - (tm->cno + 1)); + if (db_set(sp, fm->lno, + bp, len - ((tm->cno - fm->cno) + 1))) + goto err; + } goto done; } /* * Case 4 -- delete over multiple lines. * * Copy the start partial line into place. */ if ((tlen = fm->cno) != 0) { if (db_get(sp, fm->lno, DBG_FATAL, &p, NULL)) return (1); GET_SPACE_RETW(sp, bp, blen, tlen + 256); MEMCPY(bp, p, tlen); } /* Copy the end partial line into place. */ if (db_get(sp, tm->lno, DBG_FATAL, &p, &len)) goto err; if (len != 0 && tm->cno != len - 1) { /* * XXX * We can overflow memory here, if the total length is greater * than SIZE_T_MAX. The only portable way I've found to test * is depending on the overflow being less than the value. */ nlen = (len - (tm->cno + 1)) + tlen; if (tlen > nlen) { msgq(sp, M_ERR, "002|Line length overflow"); goto err; } if (tlen == 0) { GET_SPACE_RETW(sp, bp, blen, nlen); } else ADD_SPACE_RETW(sp, bp, blen, nlen); MEMCPY(bp + tlen, p + (tm->cno + 1), len - (tm->cno + 1)); tlen += len - (tm->cno + 1); } /* Set the current line. */ if (db_set(sp, fm->lno, bp, tlen)) goto err; /* Delete the last and intermediate lines. */ for (lno = tm->lno; lno > fm->lno; --lno) { if (db_delete(sp, lno)) goto err; ++sp->rptlines[L_DELETED]; if (lno % INTERRUPT_CHECK == 0 && INTERRUPTED(sp)) break; } done: rval = 0; if (0) err: rval = 1; if (bp != NULL) FREE_SPACEW(sp, bp, blen); return (rval); } Index: head/contrib/nvi/common/encoding.c =================================================================== --- head/contrib/nvi/common/encoding.c (revision 365498) +++ head/contrib/nvi/common/encoding.c (revision 365499) @@ -1,236 +1,232 @@ /*- * Copyright (c) 2011, 2012 * Zhihao Yuan. All rights reserved. * * See the LICENSE file for redistribution information. */ -#ifndef lint -static const char sccsid[] = "$Id: encoding.c,v 1.4 2011/12/13 19:40:52 zy Exp $"; -#endif /* not lint */ - #include int looks_utf8(const char *, size_t); int looks_utf16(const char *, size_t); int decode_utf8(const char *); int decode_utf16(const char *, int); #define F 0 /* character never appears in text */ #define T 1 /* character appears in plain ASCII text */ #define I 2 /* character appears in ISO-8859 text */ #define X 3 /* character appears in non-ISO extended ASCII (Mac, IBM PC) */ static char text_chars[256] = { /* BEL BS HT LF FF CR */ F, F, F, F, F, F, F, T, T, T, T, F, T, T, F, F, /* 0x0X */ /* ESC */ F, F, F, F, F, F, F, F, F, F, F, T, F, F, F, F, /* 0x1X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x2X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x3X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x4X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x5X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x6X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, F, /* 0x7X */ /* NEL */ X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X, /* 0x8X */ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, /* 0x9X */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xaX */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xbX */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xcX */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xdX */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xeX */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I /* 0xfX */ }; /* * looks_utf8 -- * Decide whether some text looks like UTF-8. Returns: * * -1: invalid UTF-8 * 0: uses odd control characters, so doesn't look like text * 1: 7-bit text * 2: definitely UTF-8 text (valid high-bit set bytes) * * Based on RFC 3629. UTF-8 with BOM is not accepted. * * PUBLIC: int looks_utf8(const char *, size_t); */ int looks_utf8(const char *ibuf, size_t nbytes) { const u_char *buf = (u_char *)ibuf; size_t i; int n; int gotone = 0, ctrl = 0; for (i = 0; i < nbytes; i++) { if ((buf[i] & 0x80) == 0) { /* 0xxxxxxx is plain ASCII */ /* * Even if the whole file is valid UTF-8 sequences, * still reject it if it uses weird control characters. */ if (text_chars[buf[i]] != T) ctrl = 1; } else if ((buf[i] & 0x40) == 0) { /* 10xxxxxx never 1st byte */ return -1; } else { /* 11xxxxxx begins UTF-8 */ int following; if ((buf[i] & 0x20) == 0) /* 110xxxxx */ if (buf[i] > 0xC1) /* C0, C1 */ following = 1; else return -1; else if ((buf[i] & 0x10) == 0) /* 1110xxxx */ following = 2; else if ((buf[i] & 0x08) == 0) /* 11110xxx */ if (buf[i] < 0xF5) following = 3; else return -1; /* F5, F6, F7 */ else return -1; /* F8~FF */ for (n = 0; n < following; n++) { i++; if (i >= nbytes) goto done; if ((buf[i] & 0xc0) != 0x80) /* 10xxxxxx */ return -1; } gotone = 1; } } done: return ctrl ? 0 : (gotone ? 2 : 1); } /* * looks_utf16 -- * Decide whether some text looks like UTF-16. Returns: * * 0: invalid UTF-16 * 1: Little-endian UTF-16 * 2: Big-endian UTF-16 * * PUBLIC: int looks_utf16(const char *, size_t); */ int looks_utf16(const char *ibuf, size_t nbytes) { const u_char *buf = (u_char *)ibuf; int bigend; size_t i; unsigned int c; int bom; int following = 0; if (nbytes < 2) return 0; bom = buf[0] << 8 ^ buf[1]; if (bom == 0xFFFE) bigend = 0; else if (bom == 0xFEFF) bigend = 1; else return 0; for (i = 2; i + 1 < nbytes; i += 2) { if (bigend) c = buf[i] << 8 ^ buf[i + 1]; else c = buf[i] ^ buf[i + 1] << 8; if (!following) if (c < 0xD800 || c > 0xDFFF) if (c < 128 && text_chars[c] != T) return 0; else following = 0; else if (c > 0xDBFF) return 0; else { following = 1; continue; } else if (c < 0xDC00 || c > 0xDFFF) return 0; } return 1 + bigend; } #undef F #undef T #undef I #undef X /* * decode_utf8 -- * Decode a UTF-8 character from byte string to Unicode. * Returns -1 if the first byte is a not UTF-8 leader. * * Based on RFC 3629, but without error detection. * * PUBLIC: int decode_utf8(const char *); */ int decode_utf8(const char *ibuf) { const u_char *buf = (u_char *)ibuf; int u = -1; if ((buf[0] & 0x80) == 0) u = buf[0]; else if ((buf[0] & 0x40) == 0); else { if ((buf[0] & 0x20) == 0) u = (buf[0] ^ 0xC0) << 6 ^ (buf[1] ^ 0x80); else if ((buf[0] & 0x10) == 0) u = (buf[0] ^ 0xE0) << 12 ^ (buf[1] ^ 0x80) << 6 ^ (buf[2] ^ 0x80); else if (((buf[0] & 0x08) == 0)) u = (buf[0] ^ 0xF0) << 18 ^ (buf[1] ^ 0x80) << 12 ^ (buf[2] ^ 0x80) << 6 ^ (buf[3] ^ 0x80); } return u; } /* * decode_utf16 -- * Decode a UTF-16 character from byte string to Unicode. * Returns -1 if the first unsigned integer is invalid. * * No error detection on supplementary bytes. * * PUBLIC: int decode_utf16(const char *, int); */ int decode_utf16(const char* ibuf, int bigend) { const u_char *buf = (u_char *)ibuf; int u = -1; unsigned int w1, w2; if (bigend) w1 = buf[0] << 8 ^ buf[1]; else w1 = buf[0] ^ buf[1] << 8; if (w1 < 0xD800 || w1 > 0xDFFF) u = w1; else if (w1 > 0xDBFF); else { if (bigend) w2 = buf[2] << 8 ^ buf[3]; else w2 = buf[2] ^ buf[3] << 8; u = ((w1 ^ 0xD800) << 10 ^ (w2 ^ 0xDC00)) + 0x10000; } return u; } Index: head/contrib/nvi/common/exf.c =================================================================== --- head/contrib/nvi/common/exf.c (revision 365498) +++ head/contrib/nvi/common/exf.c (revision 365499) @@ -1,1524 +1,1476 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: exf.c,v 10.64 2015/04/05 15:21:55 zy Exp $"; -#endif /* not lint */ - #include #include #include #include /* * We include , because the flock(2) and open(2) #defines * were found there on historical systems. We also include * because the open(2) #defines are found there on newer systems. */ #include #include #include #include #include #include #include #include #include #include #include "common.h" static int file_backup(SCR *, char *, char *); static void file_cinit(SCR *); static void file_encinit(SCR *); static void file_comment(SCR *); static int file_spath(SCR *, FREF *, struct stat *, int *); /* * file_add -- * Insert a file name into the FREF list, if it doesn't already * appear in it. * * !!! * The "if it doesn't already appear" changes vi's semantics slightly. If * you do a "vi foo bar", and then execute "next bar baz", the edit of bar * will reflect the line/column of the previous edit session. Historic nvi * did not do this. The change is a logical extension of the change where * vi now remembers the last location in any file that it has ever edited, * not just the previously edited file. * * PUBLIC: FREF *file_add(SCR *, char *); */ FREF * -file_add( - SCR *sp, - char *name) +file_add(SCR *sp, char *name) { GS *gp; FREF *frp, *tfrp; /* * Return it if it already exists. Note that we test against the * user's name, whatever that happens to be, including if it's a * temporary file. * * If the user added a file but was unable to initialize it, there * can be file list entries where the name field is NULL. Discard * them the next time we see them. */ gp = sp->gp; if (name != NULL) TAILQ_FOREACH_SAFE(frp, gp->frefq, q, tfrp) { if (frp->name == NULL) { TAILQ_REMOVE(gp->frefq, frp, q); - if (frp->name != NULL) - free(frp->name); + free(frp->name); free(frp); continue; } if (!strcmp(frp->name, name)) return (frp); } /* Allocate and initialize the FREF structure. */ - CALLOC(sp, frp, FREF *, 1, sizeof(FREF)); + CALLOC(sp, frp, 1, sizeof(FREF)); if (frp == NULL) return (NULL); /* * If no file name specified, or if the file name is a request * for something temporary, file_init() will allocate the file * name. Temporary files are always ignored. */ if (name != NULL && strcmp(name, TEMPORARY_FILE_STRING) && (frp->name = strdup(name)) == NULL) { free(frp); msgq(sp, M_SYSERR, NULL); return (NULL); } /* Append into the chain of file names. */ TAILQ_INSERT_TAIL(gp->frefq, frp, q); return (frp); } /* * file_init -- * Start editing a file, based on the FREF structure. If successsful, * let go of any previous file. Don't release the previous file until * absolutely sure we have the new one. * * PUBLIC: int file_init(SCR *, FREF *, char *, int); */ int -file_init( - SCR *sp, - FREF *frp, - char *rcv_name, - int flags) +file_init(SCR *sp, FREF *frp, char *rcv_name, int flags) { EXF *ep; RECNOINFO oinfo = { 0 }; struct stat sb; size_t psize; int fd, exists, open_err, readonly; char *oname, *tname; open_err = readonly = 0; /* * If the file is a recovery file, let the recovery code handle it. * Clear the FR_RECOVER flag first -- the recovery code does set up, * and then calls us! If the recovery call fails, it's probably * because the named file doesn't exist. So, move boldly forward, * presuming that there's an error message the user will get to see. */ if (F_ISSET(frp, FR_RECOVER)) { F_CLR(frp, FR_RECOVER); return (rcv_read(sp, frp)); } /* * Required FRP initialization; the only flag we keep is the * cursor information. */ F_CLR(frp, ~FR_CURSORSET); /* * Required EXF initialization: * Flush the line caches. * Default recover mail file fd to -1. * Set initial EXF flag bits. */ - CALLOC_RET(sp, ep, EXF *, 1, sizeof(EXF)); + CALLOC_RET(sp, ep, 1, sizeof(EXF)); ep->c_lno = ep->c_nlines = OOBLNO; ep->rcv_fd = -1; F_SET(ep, F_FIRSTMODIFY); /* * Scan the user's path to find the file that we're going to * try and open. */ if (file_spath(sp, frp, &sb, &exists)) return (1); /* * If no name or backing file, for whatever reason, create a backing * temporary file, saving the temp file name so we can later unlink * it. If the user never named this file, copy the temporary file name * to the real name (we display that until the user renames it). */ oname = frp->name; if (LF_ISSET(FS_OPENERR) || oname == NULL || !exists) { struct stat sb; if (opts_empty(sp, O_TMPDIR, 0)) goto err; if ((tname = join(O_STR(sp, O_TMPDIR), "vi.XXXXXXXXXX")) == NULL) { msgq(sp, M_SYSERR, NULL); goto err; } if ((fd = mkstemp(tname)) == -1 || fstat(fd, &sb)) { free(tname); msgq(sp, M_SYSERR, "237|Unable to create temporary file"); goto err; } (void)close(fd); frp->tname = tname; if (frp->name == NULL) { F_SET(frp, FR_TMPFILE); if ((frp->name = strdup(tname)) == NULL) { msgq(sp, M_SYSERR, NULL); goto err; } } oname = frp->tname; psize = 1024; if (!LF_ISSET(FS_OPENERR)) F_SET(frp, FR_NEWFILE); ep->mtim = sb.st_mtimespec; } else { /* * XXX * A seat of the pants calculation: try to keep the file in * 15 pages or less. Don't use a page size larger than 16K * (vi should have good locality) or smaller than 1K. */ psize = ((sb.st_size / 15) + 1023) / 1024; if (psize > 16) psize = 16; if (psize == 0) psize = 1; psize = p2roundup(psize) << 10; F_SET(ep, F_DEVSET); ep->mdev = sb.st_dev; ep->minode = sb.st_ino; ep->mtim = sb.st_mtimespec; if (!S_ISREG(sb.st_mode)) msgq_str(sp, M_ERR, oname, "238|Warning: %s is not a regular file"); } /* Set up recovery. */ oinfo.bval = '\n'; /* Always set. */ oinfo.psize = psize; oinfo.flags = F_ISSET(sp->gp, G_SNAPSHOT) ? R_SNAPSHOT : 0; if (rcv_name == NULL) { if (!rcv_tmp(sp, ep, frp->name)) oinfo.bfname = ep->rcv_path; } else { if ((ep->rcv_path = strdup(rcv_name)) == NULL) { msgq(sp, M_SYSERR, NULL); goto err; } oinfo.bfname = ep->rcv_path; F_SET(ep, F_MODIFIED); } /* Open a db structure. */ if ((ep->db = dbopen(rcv_name == NULL ? oname : NULL, O_NONBLOCK | O_RDONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, DB_RECNO, &oinfo)) == NULL) { msgq_str(sp, M_SYSERR, rcv_name == NULL ? oname : rcv_name, "%s"); if (F_ISSET(frp, FR_NEWFILE)) goto err; /* * !!! * Historically, vi permitted users to edit files that couldn't * be read. This isn't useful for single files from a command * line, but it's quite useful for "vi *.c", since you can skip * past files that you can't read. */ open_err = 1; goto oerr; } /* * Do the remaining things that can cause failure of the new file, * mark and logging initialization. */ if (mark_init(sp, ep) || log_init(sp, ep)) goto err; /* * Set the alternate file name to be the file we're discarding. * * !!! * Temporary files can't become alternate files, so there's no file * name. This matches historical practice, although it could only * happen in historical vi as the result of the initial command, i.e. * if vi was executed without a file name. */ if (LF_ISSET(FS_SETALT)) set_alt_name(sp, sp->frp == NULL || F_ISSET(sp->frp, FR_TMPFILE) ? NULL : sp->frp->name); /* * Close the previous file; if that fails, close the new one and run * for the border. * * !!! * There's a nasty special case. If the user edits a temporary file, * and then does an ":e! %", we need to re-initialize the backing * file, but we can't change the name. (It's worse -- we're dealing * with *names* here, we can't even detect that it happened.) Set a * flag so that the file_end routine ignores the backing information * of the old file if it happens to be the same as the new one. * * !!! * Side-effect: after the call to file_end(), sp->frp may be NULL. */ if (sp->ep != NULL) { F_SET(frp, FR_DONTDELETE); if (file_end(sp, NULL, LF_ISSET(FS_FORCE))) { (void)file_end(sp, ep, 1); goto err; } F_CLR(frp, FR_DONTDELETE); } /* * Lock the file; if it's a recovery file, it should already be * locked. Note, we acquire the lock after the previous file * has been ended, so that we don't get an "already locked" error * for ":edit!". * * XXX * While the user can't interrupt us between the open and here, * there's a race between the dbopen() and the lock. Not much * we can do about it. * * XXX * We don't make a big deal of not being able to lock the file. As * locking rarely works over NFS, and often fails if the file was * mmap(2)'d, it's far too common to do anything like print an error * message, let alone make the file readonly. At some future time, * when locking is a little more reliable, this should change to be * an error. */ if (rcv_name == NULL) switch (file_lock(sp, oname, ep->db->fd(ep->db), 0)) { case LOCK_FAILED: F_SET(frp, FR_UNLOCKED); break; case LOCK_UNAVAIL: readonly = 1; if (F_ISSET(sp, SC_READONLY)) break; msgq_str(sp, M_INFO, oname, "239|%s already locked, session is read-only"); break; case LOCK_SUCCESS: break; } /* * Historically, the readonly edit option was set per edit buffer in * vi, unless the -R command-line option was specified or the program * was executed as "view". (Well, to be truthful, if the letter 'w' * occurred anywhere in the program name, but let's not get into that.) - * So, the persistant readonly state has to be stored in the screen + * So, the persistent readonly state has to be stored in the screen * structure, and the edit option value toggles with the contents of - * the edit buffer. If the persistant readonly flag is set, set the + * the edit buffer. If the persistent readonly flag is set, set the * readonly edit option. * * Otherwise, try and figure out if a file is readonly. This is a * dangerous thing to do. The kernel is the only arbiter of whether * or not a file is writeable, and the best that a user program can * do is guess. Obvious loopholes are files that are on a file system * mounted readonly (access catches this one on a few systems), or * alternate protection mechanisms, ACL's for example, that we can't * portably check. Lots of fun, and only here because users whined. * * !!! * Historic vi displayed the readonly message if none of the file * write bits were set, or if an an access(2) call on the path * failed. This seems reasonable. If the file is mode 444, root * users may want to know that the owner of the file did not expect * it to be written. * * Historic vi set the readonly bit if no write bits were set for * a file, even if the access call would have succeeded. This makes * the superuser force the write even when vi expects that it will * succeed. I'm less supportive of this semantic, but it's historic * practice and the conservative approach to vi'ing files as root. * * It would be nice if there was some way to update this when the user * does a "^Z; chmod ...". The problem is that we'd first have to * distinguish between readonly bits set because of file permissions * and those set for other reasons. That's not too hard, but deciding * when to reevaluate the permissions is trickier. An alternative * might be to turn off the readonly bit if the user forces a write * and it succeeds. * * XXX * Access(2) doesn't consider the effective uid/gid values. This * probably isn't a problem for vi when it's running standalone. */ if (readonly || F_ISSET(sp, SC_READONLY) || (!F_ISSET(frp, FR_NEWFILE) && (!(sb.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) || access(frp->name, W_OK)))) O_SET(sp, O_READONLY); else O_CLR(sp, O_READONLY); /* Switch... */ ++ep->refcnt; sp->ep = ep; sp->frp = frp; /* Detect and set the file encoding */ file_encinit(sp); /* Set the initial cursor position, queue initial command. */ file_cinit(sp); /* Redraw the screen from scratch, schedule a welcome message. */ F_SET(sp, SC_SCR_REFORMAT | SC_STATUS); return (0); -err: if (frp->name != NULL) { - free(frp->name); - frp->name = NULL; - } +err: free(frp->name); + frp->name = NULL; if (frp->tname != NULL) { (void)unlink(frp->tname); free(frp->tname); frp->tname = NULL; } oerr: if (F_ISSET(ep, F_RCV_ON)) (void)unlink(ep->rcv_path); - if (ep->rcv_path != NULL) { - free(ep->rcv_path); - ep->rcv_path = NULL; - } + free(ep->rcv_path); + ep->rcv_path = NULL; + if (ep->db != NULL) (void)ep->db->close(ep->db); free(ep); return (open_err ? file_init(sp, frp, rcv_name, flags | FS_OPENERR) : 1); } /* * file_spath -- * Scan the user's path to find the file that we're going to * try and open. */ static int -file_spath( - SCR *sp, - FREF *frp, - struct stat *sbp, - int *existsp) +file_spath(SCR *sp, FREF *frp, struct stat *sbp, int *existsp) { int savech; size_t len; int found; char *name, *p, *t, *path; /* * If the name is NULL or an explicit reference (i.e., the first * component is . or ..) ignore the O_PATH option. */ name = frp->name; if (name == NULL) { *existsp = 0; return (0); } if (name[0] == '/' || (name[0] == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/')))) { *existsp = !stat(name, sbp); return (0); } /* Try . */ if (!stat(name, sbp)) { *existsp = 1; return (0); } /* Try the O_PATH option values. */ for (found = 0, p = t = O_STR(sp, O_PATH);; ++p) if (*p == ':' || *p == '\0') { /* * Ignore the empty strings and ".", since we've already * tried the current directory. */ if (t < p && (p - t != 1 || *t != '.')) { savech = *p; *p = '\0'; if ((path = join(t, name)) == NULL) { msgq(sp, M_SYSERR, NULL); break; } len = strlen(path); *p = savech; if (!stat(path, sbp)) { found = 1; break; } free(path); } t = p + 1; if (*p == '\0') break; } /* If we found it, build a new pathname and discard the old one. */ if (found) { free(frp->name); frp->name = path; } *existsp = found; return (0); } /* * file_cinit -- * Set up the initial cursor position. */ static void file_cinit(SCR *sp) { GS *gp; MARK m; size_t len; int nb; CHAR_T *wp; size_t wlen; /* Set some basic defaults. */ sp->lno = 1; sp->cno = 0; /* * Historically, initial commands (the -c option) weren't executed * until a file was loaded, e.g. "vi +10 nofile", followed by an * :edit or :tag command, would execute the +10 on the file loaded * by the subsequent command, (assuming that it existed). This * applied as well to files loaded using the tag commands, and we * follow that historic practice. Also, all initial commands were * ex commands and were always executed on the last line of the file. * * Otherwise, if no initial command for this file: * If in ex mode, move to the last line, first nonblank character. * If the file has previously been edited, move to the last known * position, and check it for validity. * Otherwise, move to the first line, first nonblank. * * This gets called by the file init code, because we may be in a * file of ex commands and we want to execute them from the right * location in the file. */ nb = 0; gp = sp->gp; if (gp->c_option != NULL && !F_ISSET(sp->frp, FR_NEWFILE)) { if (db_last(sp, &sp->lno)) return; if (sp->lno == 0) { sp->lno = 1; sp->cno = 0; } CHAR2INT(sp, gp->c_option, strlen(gp->c_option) + 1, wp, wlen); - if (ex_run_str(sp, "-c option", wp, wlen - 1, 1, 1)) + if (ex_run_str(sp, "-c option", wp, wlen - 1, 1, 0)) return; gp->c_option = NULL; } else if (F_ISSET(sp, SC_EX)) { if (db_last(sp, &sp->lno)) return; if (sp->lno == 0) { sp->lno = 1; sp->cno = 0; return; } nb = 1; } else { if (F_ISSET(sp->frp, FR_CURSORSET)) { sp->lno = sp->frp->lno; sp->cno = sp->frp->cno; /* If returning to a file in vi, center the line. */ F_SET(sp, SC_SCR_CENTER); } else { if (O_ISSET(sp, O_COMMENT)) file_comment(sp); else sp->lno = 1; nb = 1; } if (db_get(sp, sp->lno, 0, NULL, &len)) { sp->lno = 1; sp->cno = 0; return; } if (!nb && sp->cno > len) nb = 1; } if (nb) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } /* * !!! * The initial column is also the most attractive column. */ sp->rcm = sp->cno; /* * !!! * Historically, vi initialized the absolute mark, but ex did not. * Which meant, that if the first command in ex mode was "visual", * or if an ex command was executed first (e.g. vi +10 file) vi was * entered without the mark being initialized. For consistency, if * the file isn't empty, we initialize it for everyone, believing * that it can't hurt, and is generally useful. Not initializing it * if the file is empty is historic practice, although it has always * been possible to set (and use) marks in empty vi files. */ m.lno = sp->lno; m.cno = sp->cno; (void)mark_set(sp, ABSMARK1, &m, 0); } /* * file_end -- * Stop editing a file. * * PUBLIC: int file_end(SCR *, EXF *, int); */ int -file_end( - SCR *sp, - EXF *ep, - int force) +file_end(SCR *sp, EXF *ep, int force) { FREF *frp; /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. * (If argument ep is NULL, use sp->ep.) * * If multiply referenced, just decrement the count and return. */ if (ep == NULL) ep = sp->ep; if (--ep->refcnt != 0) return (0); /* * * Clean up the FREF structure. * * Save the cursor location. * * XXX * It would be cleaner to do this somewhere else, but by the time * ex or vi knows that we're changing files it's already happened. */ frp = sp->frp; frp->lno = sp->lno; frp->cno = sp->cno; F_SET(frp, FR_CURSORSET); /* * We may no longer need the temporary backing file, so clean it * up. We don't need the FREF structure either, if the file was * never named, so lose it. * * !!! * Re: FR_DONTDELETE, see the comment above in file_init(). */ if (!F_ISSET(frp, FR_DONTDELETE) && frp->tname != NULL) { if (unlink(frp->tname)) msgq_str(sp, M_SYSERR, frp->tname, "240|%s: remove"); free(frp->tname); frp->tname = NULL; if (F_ISSET(frp, FR_TMPFILE)) { TAILQ_REMOVE(sp->gp->frefq, frp, q); - if (frp->name != NULL) - free(frp->name); + free(frp->name); free(frp); } sp->frp = NULL; } /* * Clean up the EXF structure. * * Close the db structure. */ if (ep->db->close != NULL && ep->db->close(ep->db) && !force) { msgq_str(sp, M_SYSERR, frp->name, "241|%s: close"); ++ep->refcnt; return (1); } /* COMMITTED TO THE CLOSE. THERE'S NO GOING BACK... */ /* Stop logging. */ (void)log_end(sp, ep); /* Free up any marks. */ (void)mark_end(sp, ep); /* * Delete recovery files, close the open descriptor, free recovery * memory. See recover.c for a description of the protocol. * * XXX * Unlink backup file first, we can detect that the recovery file * doesn't reference anything when the user tries to recover it. * There's a race, here, obviously, but it's fairly small. */ if (!F_ISSET(ep, F_RCV_NORM)) { if (ep->rcv_path != NULL && unlink(ep->rcv_path)) msgq_str(sp, M_SYSERR, ep->rcv_path, "242|%s: remove"); if (ep->rcv_mpath != NULL && unlink(ep->rcv_mpath)) msgq_str(sp, M_SYSERR, ep->rcv_mpath, "243|%s: remove"); } if (ep->rcv_fd != -1) (void)close(ep->rcv_fd); - if (ep->rcv_path != NULL) - free(ep->rcv_path); - if (ep->rcv_mpath != NULL) - free(ep->rcv_mpath); + free(ep->rcv_path); + free(ep->rcv_mpath); if (ep->c_blen > 0) free(ep->c_lp); free(ep); return (0); } /* * file_write -- * Write the file to disk. Historic vi had fairly convoluted * semantics for whether or not writes would happen. That's * why all the flags. * * PUBLIC: int file_write(SCR *, MARK *, MARK *, char *, int); */ int -file_write( - SCR *sp, - MARK *fm, - MARK *tm, - char *name, - int flags) +file_write(SCR *sp, MARK *fm, MARK *tm, char *name, int flags) { enum { NEWFILE, OLDFILE } mtype; struct stat sb; EXF *ep; FILE *fp; FREF *frp; MARK from, to; size_t len; u_long nlno, nch; int fd, nf, noname, oflags, rval; char *p, *s, *t, buf[1024]; const char *msgstr; ep = sp->ep; frp = sp->frp; /* * Writing '%', or naming the current file explicitly, has the * same semantics as writing without a name. */ if (name == NULL || !strcmp(name, frp->name)) { noname = 1; name = frp->name; } else noname = 0; /* Can't write files marked read-only, unless forced. */ if (!LF_ISSET(FS_FORCE) && noname && O_ISSET(sp, O_READONLY)) { msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ? "244|Read-only file, not written; use ! to override" : "245|Read-only file, not written"); return (1); } /* If not forced, not appending, and "writeany" not set ... */ if (!LF_ISSET(FS_FORCE | FS_APPEND) && !O_ISSET(sp, O_WRITEANY)) { /* Don't overwrite anything but the original file. */ if ((!noname || F_ISSET(frp, FR_NAMECHANGE)) && !stat(name, &sb)) { msgq_str(sp, M_ERR, name, LF_ISSET(FS_POSSIBLE) ? "246|%s exists, not written; use ! to override" : "247|%s exists, not written"); return (1); } /* * Don't write part of any existing file. Only test for the * original file, the previous test catches anything else. */ if (!LF_ISSET(FS_ALL) && noname && !stat(name, &sb)) { msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ? "248|Partial file, not written; use ! to override" : "249|Partial file, not written"); return (1); } } /* * Figure out if the file already exists -- if it doesn't, we display * the "new file" message. The stat might not be necessary, but we * just repeat it because it's easier than hacking the previous tests. * The information is only used for the user message and modification * time test, so we can ignore the obvious race condition. * * One final test. If we're not forcing or appending the current file, * and we have a saved modification time, object if the file changed * since we last edited or wrote it, and make them force it. */ if (stat(name, &sb)) mtype = NEWFILE; else { if (noname && !LF_ISSET(FS_FORCE | FS_APPEND) && ((F_ISSET(ep, F_DEVSET) && (sb.st_dev != ep->mdev || sb.st_ino != ep->minode)) || timespeccmp(&sb.st_mtimespec, &ep->mtim, !=))) { msgq_str(sp, M_ERR, name, LF_ISSET(FS_POSSIBLE) ? "250|%s: file modified more recently than this copy; use ! to override" : "251|%s: file modified more recently than this copy"); return (1); } mtype = OLDFILE; } /* Set flags to create, write, and either append or truncate. */ oflags = O_CREAT | O_WRONLY | (LF_ISSET(FS_APPEND) ? O_APPEND : O_TRUNC); /* Backup the file if requested. */ if (!opts_empty(sp, O_BACKUP, 1) && file_backup(sp, name, O_STR(sp, O_BACKUP)) && !LF_ISSET(FS_FORCE)) return (1); /* Open the file. */ if ((fd = open(name, oflags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) < 0) { if (errno == EACCES && LF_ISSET(FS_FORCE)) { /* * If the user owns the file but does not * have write permission on it, grant it * automatically for the duration of the * opening of the file, if possible. */ struct stat sb; mode_t fmode; if (stat(name, &sb) != 0) goto fail_open; fmode = sb.st_mode; if (!(sb.st_mode & S_IWUSR) && sb.st_uid == getuid()) fmode |= S_IWUSR; else goto fail_open; if (chmod(name, fmode) != 0) goto fail_open; fd = open(name, oflags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (fd == -1) goto fail_open; (void)fchmod(fd, sb.st_mode); goto success_open; fail_open: errno = EACCES; } msgq_str(sp, M_SYSERR, name, "%s"); return (1); } success_open: /* Try and get a lock. */ if (!noname && file_lock(sp, NULL, fd, 0) == LOCK_UNAVAIL) msgq_str(sp, M_ERR, name, "252|%s: write lock was unavailable"); /* * Use stdio for buffering. * * XXX * SVR4.2 requires the fdopen mode exactly match the original open * mode, i.e. you have to open with "a" if appending. */ if ((fp = fdopen(fd, LF_ISSET(FS_APPEND) ? "a" : "w")) == NULL) { msgq_str(sp, M_SYSERR, name, "%s"); (void)close(fd); return (1); } /* Build fake addresses, if necessary. */ if (fm == NULL) { from.lno = 1; from.cno = 0; fm = &from; if (db_last(sp, &to.lno)) return (1); to.cno = 0; tm = &to; } rval = ex_writefp(sp, name, fp, fm, tm, &nlno, &nch, 0); /* * Save the new last modification time -- even if the write fails * we re-init the time. That way the user can clean up the disk * and rewrite without having to force it. */ if (noname) if (stat(name, &sb)) timepoint_system(&ep->mtim); else { F_SET(ep, F_DEVSET); ep->mdev = sb.st_dev; ep->minode = sb.st_ino; ep->mtim = sb.st_mtimespec; } /* * If the write failed, complain loudly. ex_writefp() has already * complained about the actual error, reinforce it if data was lost. */ if (rval) { if (!LF_ISSET(FS_APPEND)) msgq_str(sp, M_ERR, name, "254|%s: WARNING: FILE TRUNCATED"); return (1); } /* * Once we've actually written the file, it doesn't matter that the * file name was changed -- if it was, we've already whacked it. */ F_CLR(frp, FR_NAMECHANGE); /* * If wrote the entire file, and it wasn't by appending it to a file, * clear the modified bit. If the file was written to the original * file name and the file is a temporary, set the "no exit" bit. This * permits the user to write the file and use it in the context of the * filesystem, but still keeps them from discarding their changes by * exiting. */ if (LF_ISSET(FS_ALL) && !LF_ISSET(FS_APPEND)) { F_CLR(ep, F_MODIFIED); if (F_ISSET(frp, FR_TMPFILE)) if (noname) F_SET(frp, FR_TMPEXIT); else F_CLR(frp, FR_TMPEXIT); } p = msg_print(sp, name, &nf); switch (mtype) { case NEWFILE: msgstr = msg_cat(sp, "256|%s: new file: %lu lines, %lu characters", NULL); len = snprintf(buf, sizeof(buf), msgstr, p, nlno, nch); break; case OLDFILE: msgstr = msg_cat(sp, LF_ISSET(FS_APPEND) ? "315|%s: appended: %lu lines, %lu characters" : "257|%s: %lu lines, %lu characters", NULL); len = snprintf(buf, sizeof(buf), msgstr, p, nlno, nch); break; default: abort(); } /* * There's a nasty problem with long path names. Cscope and tags files * can result in long paths and vi will request a continuation key from * the user. Unfortunately, the user has typed ahead, and chaos will * result. If we assume that the characters in the filenames only take * a single screen column each, we can trim the filename. */ s = buf; if (len >= sp->cols) { for (s = buf, t = buf + strlen(p); s < t && (*s != '/' || len >= sp->cols - 3); ++s, --len); if (s == t) s = buf; else { *--s = '.'; /* Leading ellipses. */ *--s = '.'; *--s = '.'; } } msgq(sp, M_INFO, "%s", s); if (nf) FREE_SPACE(sp, p, 0); return (0); } /* * file_backup -- * Backup the about-to-be-written file. * * XXX * We do the backup by copying the entire file. It would be nice to do * a rename instead, but: (1) both files may not fit and we want to fail * before doing the rename; (2) the backup file may not be on the same * disk partition as the file being written; (3) there may be optional * file information (MACs, DACs, whatever) that we won't get right if we * recreate the file. So, let's not risk it. */ static int -file_backup( - SCR *sp, - char *name, - char *bname) +file_backup(SCR *sp, char *name, char *bname) { struct dirent *dp; struct stat sb; DIR *dirp; EXCMD cmd; off_t off; size_t blen; int flags, maxnum, nr, num, nw, rfd, wfd, version; char *bp, *estr, *p, *pct, *slash, *t, *wfname, buf[8192]; CHAR_T *wp; size_t wlen; size_t nlen; char *d = NULL; rfd = wfd = -1; bp = estr = wfname = NULL; /* * Open the current file for reading. Do this first, so that * we don't exec a shell before the most likely failure point. * If it doesn't exist, it's okay, there's just nothing to back * up. */ errno = 0; if ((rfd = open(name, O_RDONLY, 0)) < 0) { if (errno == ENOENT) return (0); estr = name; goto err; } /* * If the name starts with an 'N' character, add a version number * to the name. Strip the leading N from the string passed to the * expansion routines, for no particular reason. It would be nice * to permit users to put the version number anywhere in the backup * name, but there isn't a special character that we can use in the * name, and giving a new character a special meaning leads to ugly * hacks both here and in the supporting ex routines. * * Shell and file name expand the option's value. */ ex_cinit(sp, &cmd, 0, 0, 0, 0, 0); if (bname[0] == 'N') { version = 1; ++bname; } else version = 0; CHAR2INT(sp, bname, strlen(bname), wp, wlen); if ((wp = v_wstrdup(sp, wp, wlen)) == NULL) return (1); if (argv_exp2(sp, &cmd, wp, wlen)) { free(wp); return (1); } free(wp); /* * 0 args: impossible. * 1 args: use it. * >1 args: object, too many args. */ if (cmd.argc != 1) { msgq_str(sp, M_ERR, bname, "258|%s expanded into too many file names"); (void)close(rfd); return (1); } /* * If appending a version number, read through the directory, looking * for file names that match the name followed by a number. Make all * of the other % characters in name literal, so the user doesn't get * surprised and sscanf doesn't drop core indirecting through pointers * that don't exist. If any such files are found, increment its number * by one. */ if (version) { GET_SPACE_GOTOC(sp, bp, blen, cmd.argv[0]->len * 2 + 50); INT2CHAR(sp, cmd.argv[0]->bp, cmd.argv[0]->len + 1, p, nlen); d = strdup(p); p = d; for (t = bp, slash = NULL; p[0] != '\0'; *t++ = *p++) if (p[0] == '%') { if (p[1] != '%') *t++ = '%'; } else if (p[0] == '/') slash = t; pct = t; *t++ = '%'; *t++ = 'd'; *t = '\0'; if (slash == NULL) { dirp = opendir("."); p = bp; } else { *slash = '\0'; dirp = opendir(bp); *slash = '/'; p = slash + 1; } if (dirp == NULL) { INT2CHAR(sp, cmd.argv[0]->bp, cmd.argv[0]->len + 1, estr, nlen); goto err; } for (maxnum = 0; (dp = readdir(dirp)) != NULL;) if (sscanf(dp->d_name, p, &num) == 1 && num > maxnum) maxnum = num; (void)closedir(dirp); /* Format the backup file name. */ (void)snprintf(pct, blen - (pct - bp), "%d", maxnum + 1); wfname = bp; } else { bp = NULL; INT2CHAR(sp, cmd.argv[0]->bp, cmd.argv[0]->len + 1, wfname, nlen); } /* Open the backup file, avoiding lurkers. */ if (stat(wfname, &sb) == 0) { if (!S_ISREG(sb.st_mode)) { msgq_str(sp, M_ERR, bname, "259|%s: not a regular file"); goto err; } if (sb.st_uid != getuid()) { msgq_str(sp, M_ERR, bname, "260|%s: not owned by you"); goto err; } if (sb.st_mode & (S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) { msgq_str(sp, M_ERR, bname, "261|%s: accessible by a user other than the owner"); goto err; } flags = O_TRUNC; } else flags = O_CREAT | O_EXCL; if ((wfd = open(wfname, flags | O_WRONLY, S_IRUSR | S_IWUSR)) < 0) { estr = bname; goto err; } /* Copy the file's current contents to its backup value. */ while ((nr = read(rfd, buf, sizeof(buf))) > 0) for (off = 0; nr != 0; nr -= nw, off += nw) if ((nw = write(wfd, buf + off, nr)) < 0) { estr = wfname; goto err; } if (nr < 0) { estr = name; goto err; } if (close(rfd)) { estr = name; goto err; } if (close(wfd)) { estr = wfname; goto err; } + free(d); if (bp != NULL) FREE_SPACE(sp, bp, blen); return (0); alloc_err: err: if (rfd != -1) (void)close(rfd); if (wfd != -1) { (void)unlink(wfname); (void)close(wfd); } if (estr) msgq_str(sp, M_SYSERR, estr, "%s"); - if (d != NULL) - free(d); + free(d); if (bp != NULL) FREE_SPACE(sp, bp, blen); return (1); } /* * file_encinit -- * Read the first line and set the O_FILEENCODING. */ static void file_encinit(SCR *sp) { #if defined(USE_WIDECHAR) && defined(USE_ICONV) size_t len; char *p; size_t blen = 0; char buf[4096]; /* not need to be '\0'-terminated */ recno_t ln = 1; EXF *ep; ep = sp->ep; while (!db_rget(sp, ln++, &p, &len)) { if (blen + len > sizeof(buf)) len = sizeof(buf) - blen; memcpy(buf + blen, p, len); blen += len; if (blen == sizeof(buf)) break; else buf[blen++] = '\n'; } /* * Detect UTF-8 and fallback to the locale/preset encoding. * * XXX * A manually set O_FILEENCODING indicates the "fallback * encoding", but UTF-8, which can be safely detected, is not * inherited from the old screen. */ if (looks_utf8(buf, blen) > 1) o_set(sp, O_FILEENCODING, OS_STRDUP, "utf-8", 0); else if (!O_ISSET(sp, O_FILEENCODING) || !strcasecmp(O_STR(sp, O_FILEENCODING), "utf-8")) o_set(sp, O_FILEENCODING, OS_STRDUP, codeset(), 0); conv_enc(sp, O_FILEENCODING, 0); #endif } /* * file_comment -- * Skip the first comment. */ static void file_comment(SCR *sp) { recno_t lno; size_t len; CHAR_T *p; for (lno = 1; !db_get(sp, lno, 0, &p, &len) && len == 0; ++lno); if (p == NULL) return; if (p[0] == '#') { F_SET(sp, SC_SCR_TOP); while (!db_get(sp, ++lno, 0, &p, &len)) if (len < 1 || p[0] != '#') { sp->lno = lno; return; } } else if (len > 1 && p[0] == '/' && p[1] == '*') { F_SET(sp, SC_SCR_TOP); do { for (; len > 1; --len, ++p) if (p[0] == '*' && p[1] == '/') { sp->lno = lno; return; } } while (!db_get(sp, ++lno, 0, &p, &len)); } else if (len > 1 && p[0] == '/' && p[1] == '/') { F_SET(sp, SC_SCR_TOP); p += 2; len -= 2; do { for (; len > 1; --len, ++p) if (p[0] == '/' && p[1] == '/') { sp->lno = lno; return; } } while (!db_get(sp, ++lno, 0, &p, &len)); } } /* * file_m1 -- * First modification check routine. The :next, :prev, :rewind, :tag, * :tagpush, :tagpop, ^^ modifications check. * * PUBLIC: int file_m1(SCR *, int, int); */ int -file_m1( - SCR *sp, - int force, - int flags) +file_m1(SCR *sp, int force, int flags) { EXF *ep; ep = sp->ep; /* If no file loaded, return no modifications. */ if (ep == NULL) return (0); /* * If the file has been modified, we'll want to write it back or * fail. If autowrite is set, we'll write it back automatically, * unless force is also set. Otherwise, we fail unless forced or * there's another open screen on this file. */ if (F_ISSET(ep, F_MODIFIED)) if (O_ISSET(sp, O_AUTOWRITE)) { if (!force && file_aw(sp, flags)) return (1); } else if (ep->refcnt <= 1 && !force) { msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ? "262|File modified since last complete write; write or use ! to override" : "263|File modified since last complete write; write or use :edit! to override"); return (1); } return (file_m3(sp, force)); } /* * file_m2 -- * Second modification check routine. The :edit, :quit, :recover * modifications check. * * PUBLIC: int file_m2(SCR *, int); */ int -file_m2( - SCR *sp, - int force) +file_m2(SCR *sp, int force) { EXF *ep; ep = sp->ep; /* If no file loaded, return no modifications. */ if (ep == NULL) return (0); /* * If the file has been modified, we'll want to fail, unless forced * or there's another open screen on this file. */ if (F_ISSET(ep, F_MODIFIED) && ep->refcnt <= 1 && !force) { msgq(sp, M_ERR, "264|File modified since last complete write; write or use ! to override"); return (1); } return (file_m3(sp, force)); } /* * file_m3 -- * Third modification check routine. * * PUBLIC: int file_m3(SCR *, int); */ int -file_m3( - SCR *sp, - int force) +file_m3(SCR *sp, int force) { EXF *ep; ep = sp->ep; /* If no file loaded, return no modifications. */ if (ep == NULL) return (0); /* * Don't exit while in a temporary files if the file was ever modified. * The problem is that if the user does a ":wq", we write and quit, * unlinking the temporary file. Not what the user had in mind at all. * We permit writing to temporary files, so that user maps using file * system names work with temporary files. */ if (F_ISSET(sp->frp, FR_TMPEXIT) && ep->refcnt <= 1 && !force) { msgq(sp, M_ERR, "265|File is a temporary; exit will discard modifications"); return (1); } return (0); } /* * file_aw -- * Autowrite routine. If modified, autowrite is set and the readonly bit * is not set, write the file. A routine so there's a place to put the * comment. * * PUBLIC: int file_aw(SCR *, int); */ int -file_aw( - SCR *sp, - int flags) +file_aw(SCR *sp, int flags) { if (!F_ISSET(sp->ep, F_MODIFIED)) return (0); if (!O_ISSET(sp, O_AUTOWRITE)) return (0); /* * !!! * Historic 4BSD vi attempted to write the file if autowrite was set, * regardless of the writeability of the file (as defined by the file * readonly flag). System V changed this as some point, not attempting * autowrite if the file was readonly. This feels like a bug fix to * me (e.g. the principle of least surprise is violated if readonly is * set and vi writes the file), so I'm compatible with System V. */ if (O_ISSET(sp, O_READONLY)) { msgq(sp, M_INFO, "266|File readonly, modifications not auto-written"); return (1); } return (file_write(sp, NULL, NULL, NULL, flags)); } /* * set_alt_name -- * Set the alternate pathname. * * Set the alternate pathname. It's a routine because I wanted some place * to hang this comment. The alternate pathname (normally referenced using * the special character '#' during file expansion and in the vi ^^ command) * is set by almost all ex commands that take file names as arguments. The * rules go something like this: * * 1: If any ex command takes a file name as an argument (except for the * :next command), the alternate pathname is set to that file name. * This excludes the command ":e" and ":w !command" as no file name * was specified. Note, historically, the :source command did not set * the alternate pathname. It does in nvi, for consistency. * * 2: However, if any ex command sets the current pathname, e.g. the * ":e file" or ":rew" commands succeed, then the alternate pathname * is set to the previous file's current pathname, if it had one. * This includes the ":file" command and excludes the ":e" command. * So, by rule #1 and rule #2, if ":edit foo" fails, the alternate * pathname will be "foo", if it succeeds, the alternate pathname will * be the previous current pathname. The ":e" command will not set * the alternate or current pathnames regardless. * * 3: However, if it's a read or write command with a file argument and * the current pathname has not yet been set, the file name becomes * the current pathname, and the alternate pathname is unchanged. * * If the user edits a temporary file, there may be times when there is no * alternative file name. A name argument of NULL turns it off. * * PUBLIC: void set_alt_name(SCR *, char *); */ void -set_alt_name( - SCR *sp, - char *name) +set_alt_name(SCR *sp, char *name) { - if (sp->alt_name != NULL) - free(sp->alt_name); + free(sp->alt_name); if (name == NULL) sp->alt_name = NULL; else if ((sp->alt_name = strdup(name)) == NULL) msgq(sp, M_SYSERR, NULL); } /* * file_lock -- * Get an exclusive lock on a file. * * PUBLIC: lockr_t file_lock(SCR *, char *, int, int); */ lockr_t -file_lock( - SCR *sp, - char *name, - int fd, - int iswrite) +file_lock(SCR *sp, char *name, int fd, int iswrite) { if (!O_ISSET(sp, O_LOCKFILES)) return (LOCK_SUCCESS); /* * !!! * We need to distinguish a lock not being available for the file * from the file system not supporting locking. Flock is documented * as returning EWOULDBLOCK; add EAGAIN for good measure, and assume * they are the former. There's no portable way to do this. */ errno = 0; if (!flock(fd, LOCK_EX | LOCK_NB)) { fcntl(fd, F_SETFD, 1); return (LOCK_SUCCESS); } return (errno == EAGAIN #ifdef EWOULDBLOCK || errno == EWOULDBLOCK #endif ? LOCK_UNAVAIL : LOCK_FAILED); } Index: head/contrib/nvi/common/exf.h =================================================================== --- head/contrib/nvi/common/exf.h (revision 365498) +++ head/contrib/nvi/common/exf.h (revision 365499) @@ -1,78 +1,76 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: exf.h,v 10.10 2012/07/06 16:03:37 zy Exp $ */ /* Undo direction. */ /* * exf -- * The file structure. */ struct _exf { int refcnt; /* Reference count. */ /* Underlying database state. */ DB *db; /* File db structure. */ CHAR_T *c_lp; /* Cached line. */ size_t c_len; /* Cached line length. */ size_t c_blen; /* Cached line buffer length. */ recno_t c_lno; /* Cached line number. */ recno_t c_nlines; /* Cached lines in the file. */ DB *log; /* Log db structure. */ char *l_lp; /* Log buffer. */ size_t l_len; /* Log buffer length. */ recno_t l_high; /* Log last + 1 record number. */ recno_t l_cur; /* Log current record number. */ MARK l_cursor; /* Log cursor position. */ dir_t lundo; /* Last undo direction. */ /* Linked list of file MARK's. */ SLIST_HEAD(_markh, _lmark) marks[1]; dev_t mdev; /* Device. */ ino_t minode; /* Inode. */ struct timespec mtim; /* Last modification time. */ /* * Recovery in general, and these fields specifically, are described * in recover.c. */ #define RCV_PERIOD 120 /* Sync every two minutes. */ char *rcv_path; /* Recover file name. */ char *rcv_mpath; /* Recover mail file name. */ int rcv_fd; /* Locked mail file descriptor. */ #define F_DEVSET 0x001 /* mdev/minode fields initialized. */ #define F_FIRSTMODIFY 0x002 /* File not yet modified. */ #define F_MODIFIED 0x004 /* File is currently dirty. */ #define F_MULTILOCK 0x008 /* Multiple processes running, lock. */ #define F_NOLOG 0x010 /* Logging turned off. */ #define F_RCV_NORM 0x020 /* Don't delete recovery files. */ #define F_RCV_ON 0x040 /* Recovery is possible. */ #define F_UNDO 0x080 /* No change since last undo. */ u_int8_t flags; }; /* Flags to db_get(). */ #define DBG_FATAL 0x001 /* If DNE, error message. */ #define DBG_NOCACHE 0x002 /* Ignore the front-end cache. */ /* Flags to file_init() and file_write(). */ #define FS_ALL 0x001 /* Write the entire file. */ #define FS_APPEND 0x002 /* Append to the file. */ #define FS_FORCE 0x004 /* Force is set. */ #define FS_OPENERR 0x008 /* Open failed, try it again. */ #define FS_POSSIBLE 0x010 /* Force could have been set. */ #define FS_SETALT 0x020 /* Set alternate file name. */ /* Flags to rcv_sync(). */ #define RCV_EMAIL 0x01 /* Send the user email, IFF file modified. */ #define RCV_ENDSESSION 0x02 /* End the file session. */ #define RCV_PRESERVE 0x04 /* Preserve backup file, IFF file modified. */ #define RCV_SNAPSHOT 0x08 /* Snapshot the recovery, and send email. */ Index: head/contrib/nvi/common/extern.h =================================================================== --- head/contrib/nvi/common/extern.h (revision 365498) +++ head/contrib/nvi/common/extern.h (revision 365499) @@ -1,132 +1,131 @@ char * codeset(void); void conv_init(SCR *, SCR *); int conv_enc(SCR *, int, char *); void conv_end(SCR *); int cut(SCR *, CHAR_T *, MARK *, MARK *, int); int cut_line(SCR *, recno_t, size_t, size_t, CB *); void cut_close(GS *); TEXT *text_init(SCR *, const CHAR_T *, size_t, size_t); void text_lfree(TEXTH *); void text_free(TEXT *); int del(SCR *, MARK *, MARK *, int); int looks_utf8(const char *, size_t); int looks_utf16(const char *, size_t); int decode_utf8(const char *); int decode_utf16(const char *, int); FREF *file_add(SCR *, char *); int file_init(SCR *, FREF *, char *, int); int file_end(SCR *, EXF *, int); int file_write(SCR *, MARK *, MARK *, char *, int); int file_m1(SCR *, int, int); int file_m2(SCR *, int); int file_m3(SCR *, int); int file_aw(SCR *, int); void set_alt_name(SCR *, char *); lockr_t file_lock(SCR *, char *, int, int); int v_key_init(SCR *); void v_key_ilookup(SCR *); size_t v_key_len(SCR *, ARG_CHAR_T); char *v_key_name(SCR *, ARG_CHAR_T); e_key_t v_key_val(SCR *, ARG_CHAR_T); int v_event_push(SCR *, EVENT *, CHAR_T *, size_t, u_int); int v_event_get(SCR *, EVENT *, int, u_int32_t); void v_event_err(SCR *, EVENT *); int v_event_flush(SCR *, u_int); int db_eget(SCR *, recno_t, CHAR_T **, size_t *, int *); int db_get(SCR *, recno_t, u_int32_t, CHAR_T **, size_t *); int db_delete(SCR *, recno_t); int db_append(SCR *, int, recno_t, CHAR_T *, size_t); int db_insert(SCR *, recno_t, CHAR_T *, size_t); int db_set(SCR *, recno_t, CHAR_T *, size_t); int db_exist(SCR *, recno_t); int db_last(SCR *, recno_t *); int db_rget(SCR *, recno_t, char **, size_t *); int db_rset(SCR *, recno_t, char *, size_t); void db_err(SCR *, recno_t); int log_init(SCR *, EXF *); int log_end(SCR *, EXF *); int log_cursor(SCR *); int log_line(SCR *, recno_t, u_int); int log_mark(SCR *, LMARK *); int log_backward(SCR *, MARK *); int log_setline(SCR *); int log_forward(SCR *, MARK *); int editor(GS *, int, char *[]); void v_end(GS *); int mark_init(SCR *, EXF *); int mark_end(SCR *, EXF *); int mark_get(SCR *, ARG_CHAR_T, MARK *, mtype_t); int mark_set(SCR *, ARG_CHAR_T, MARK *, int); int mark_insdel(SCR *, lnop_t, recno_t); void msgq(SCR *, mtype_t, const char *, ...); void msgq_wstr(SCR *, mtype_t, const CHAR_T *, const char *); void msgq_str(SCR *, mtype_t, const char *, const char *); void mod_rpt(SCR *); void msgq_status(SCR *, recno_t, u_int); int msg_open(SCR *, char *); void msg_close(GS *); const char *msg_cmsg(SCR *, cmsg_t, size_t *); const char *msg_cat(SCR *, const char *, size_t *); char *msg_print(SCR *, const char *, int *); int opts_init(SCR *, int *); int opts_set(SCR *, ARGS *[], char *); int o_set(SCR *, int, u_int, char *, u_long); int opts_empty(SCR *, int, int); void opts_dump(SCR *, enum optdisp); int opts_save(SCR *, FILE *); OPTLIST const *opts_search(CHAR_T *); void opts_nomatch(SCR *, CHAR_T *); int opts_copy(SCR *, SCR *); void opts_free(SCR *); int f_altwerase(SCR *, OPTION *, char *, u_long *); int f_columns(SCR *, OPTION *, char *, u_long *); int f_lines(SCR *, OPTION *, char *, u_long *); int f_lisp(SCR *, OPTION *, char *, u_long *); int f_msgcat(SCR *, OPTION *, char *, u_long *); int f_print(SCR *, OPTION *, char *, u_long *); int f_readonly(SCR *, OPTION *, char *, u_long *); int f_recompile(SCR *, OPTION *, char *, u_long *); int f_reformat(SCR *, OPTION *, char *, u_long *); int f_ttywerase(SCR *, OPTION *, char *, u_long *); int f_w300(SCR *, OPTION *, char *, u_long *); int f_w1200(SCR *, OPTION *, char *, u_long *); int f_w9600(SCR *, OPTION *, char *, u_long *); int f_window(SCR *, OPTION *, char *, u_long *); int f_encoding(SCR *, OPTION *, char *, u_long *); int put(SCR *, CB *, CHAR_T *, MARK *, MARK *, int); int rcv_tmp(SCR *, EXF *, char *); int rcv_init(SCR *); int rcv_sync(SCR *, u_int); int rcv_list(SCR *); int rcv_read(SCR *, FREF *); int screen_init(GS *, SCR *, SCR **); int screen_end(SCR *); SCR *screen_next(SCR *); int f_search(SCR *, MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int); int b_search(SCR *, MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int); void search_busy(SCR *, busy_t); int seq_set(SCR *, CHAR_T *, size_t, CHAR_T *, size_t, CHAR_T *, size_t, seq_t, int); int seq_delete(SCR *, CHAR_T *, size_t, seq_t); int seq_free(SEQ *); SEQ *seq_find (SCR *, SEQ **, EVENT *, CHAR_T *, size_t, seq_t, int *); void seq_close(GS *); int seq_dump(SCR *, seq_t, int); int seq_save(SCR *, FILE *, char *, seq_t); int e_memcmp(CHAR_T *, EVENT *, size_t); void *binc(SCR *, void *, size_t *, size_t); int nonblank(SCR *, recno_t, size_t *); -char *tail(char *); char *join(char *, char *); char *expanduser(char *); char *quote(char *); char *v_strdup(SCR *, const char *, size_t); CHAR_T *v_wstrdup(SCR *, const CHAR_T *, size_t); enum nresult nget_uslong(u_long *, const CHAR_T *, CHAR_T **, int); enum nresult nget_slong(long *, const CHAR_T *, CHAR_T **, int); void timepoint_steady(struct timespec *); void timepoint_system(struct timespec *); void TRACE(SCR *, const char *, ...); Index: head/contrib/nvi/common/gs.h =================================================================== --- head/contrib/nvi/common/gs.h (revision 365498) +++ head/contrib/nvi/common/gs.h (revision 365499) @@ -1,198 +1,194 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: gs.h,v 11.0 2012/10/17 06:34:37 zy Exp $ */ #define TEMPORARY_FILE_STRING "/tmp" /* Default temporary file name. */ #include /* * File reference structure (FREF). The structure contains the name of the * file, along with the information that follows the name. * * !!! * The read-only bit follows the file name, not the file itself. */ struct _fref { TAILQ_ENTRY(_fref) q; /* Linked list of file references. */ char *name; /* File name. */ char *tname; /* Backing temporary file name. */ recno_t lno; /* 1-N: file cursor line. */ size_t cno; /* 0-N: file cursor column. */ #define FR_CURSORSET 0x0001 /* If lno/cno values valid. */ #define FR_DONTDELETE 0x0002 /* Don't delete the temporary file. */ #define FR_EXNAMED 0x0004 /* Read/write renamed the file. */ #define FR_NAMECHANGE 0x0008 /* If the name changed. */ #define FR_NEWFILE 0x0010 /* File doesn't really exist yet. */ #define FR_RECOVER 0x0020 /* File is being recovered. */ #define FR_TMPEXIT 0x0040 /* Modified temporary file, no exit. */ #define FR_TMPFILE 0x0080 /* If file has no name. */ #define FR_UNLOCKED 0x0100 /* File couldn't be locked. */ u_int16_t flags; }; /* Action arguments to scr_exadjust(). */ typedef enum { EX_TERM_CE, EX_TERM_SCROLL } exadj_t; /* Screen attribute arguments to scr_attr(). */ typedef enum { SA_ALTERNATE, SA_INVERSE } scr_attr_t; /* Key type arguments to scr_keyval(). */ typedef enum { KEY_VEOF, KEY_VERASE, KEY_VKILL, KEY_VWERASE } scr_keyval_t; /* * GS: * * Structure that describes global state of the running program. */ struct _gs { - char *progname; /* Programe name. */ - int id; /* Last allocated screen id. */ TAILQ_HEAD(_dqh, _scr) dq[1]; /* Displayed screens. */ TAILQ_HEAD(_hqh, _scr) hq[1]; /* Hidden screens. */ SCR *ccl_sp; /* Colon command-line screen. */ void *cl_private; /* Curses support private area. */ /* File references. */ TAILQ_HEAD(_frefh, _fref) frefq[1]; #define GO_COLUMNS 0 /* Global options: columns. */ #define GO_LINES 1 /* Global options: lines. */ #define GO_SECURE 2 /* Global options: secure. */ #define GO_TERM 3 /* Global options: terminal type. */ OPTION opts[GO_TERM + 1]; nl_catd catd; /* Message catalog descriptor. */ MSGH msgq[1]; /* User message list. */ #define DEFAULT_NOPRINT '\1' /* Emergency non-printable character. */ int noprint; /* Cached, unprintable character. */ char *tmp_bp; /* Temporary buffer. */ size_t tmp_blen; /* Temporary buffer size. */ /* * Ex command structures (EXCMD). Defined here because ex commands * exist outside of any particular screen or file. */ #define EXCMD_RUNNING(gp) (SLIST_FIRST((gp)->ecq)->clen != 0) /* Ex command linked list. */ SLIST_HEAD(_excmdh, _excmd) ecq[1]; EXCMD excmd; /* Default ex command structure. */ char *if_name; /* Current associated file. */ recno_t if_lno; /* Current associated line number. */ char *c_option; /* Ex initial, command-line command. */ #ifdef DEBUG FILE *tracefp; /* Trace file pointer. */ #endif EVENT *i_event; /* Array of input events. */ size_t i_nelem; /* Number of array elements. */ size_t i_cnt; /* Count of events. */ size_t i_next; /* Offset of next event. */ CB *dcbp; /* Default cut buffer pointer. */ CB dcb_store; /* Default cut buffer storage. */ SLIST_HEAD(_cuth, _cb) cutq[1]; /* Linked list of cut buffers. */ #define MAX_BIT_SEQ 0x7f /* Max + 1 fast check character. */ SLIST_HEAD(_seqh, _seq) seqq[1];/* Linked list of maps, abbrevs. */ bitstr_t bit_decl(seqb, MAX_BIT_SEQ + 1); #define MAX_FAST_KEY 0xff /* Max fast check character.*/ #define KEY_LEN(sp, ch) \ (((ch) & ~MAX_FAST_KEY) == 0 ? \ sp->gp->cname[(unsigned char)ch].len : v_key_len(sp, ch)) #define KEY_NAME(sp, ch) \ (((ch) & ~MAX_FAST_KEY) == 0 ? \ sp->gp->cname[(unsigned char)ch].name : v_key_name(sp, ch)) struct { char name[MAX_CHARACTER_COLUMNS + 1]; u_int8_t len; } cname[MAX_FAST_KEY + 1]; /* Fast lookup table. */ #define KEY_VAL(sp, ch) \ (((ch) & ~MAX_FAST_KEY) == 0 ? \ sp->gp->special_key[(unsigned char)ch] : v_key_val(sp,ch)) e_key_t /* Fast lookup table. */ special_key[MAX_FAST_KEY + 1]; /* Flags. */ #define G_ABBREV 0x0001 /* If have abbreviations. */ #define G_BELLSCHED 0x0002 /* Bell scheduled. */ #define G_INTERRUPTED 0x0004 /* Interrupted. */ #define G_RECOVER_SET 0x0008 /* Recover system initialized. */ #define G_SCRIPTED 0x0010 /* Ex script session. */ #define G_SCRWIN 0x0020 /* Scripting windows running. */ #define G_SNAPSHOT 0x0040 /* Always snapshot files. */ #define G_SRESTART 0x0080 /* Screen restarted. */ #define G_TMP_INUSE 0x0100 /* Temporary buffer in use. */ u_int32_t flags; /* Screen interface functions. */ /* Add a string to the screen. */ int (*scr_addstr)(SCR *, const char *, size_t); /* Add a string to the screen. */ int (*scr_waddstr)(SCR *, const CHAR_T *, size_t); /* Toggle a screen attribute. */ int (*scr_attr)(SCR *, scr_attr_t, int); /* Terminal baud rate. */ int (*scr_baud)(SCR *, u_long *); /* Beep/bell/flash the terminal. */ int (*scr_bell)(SCR *); /* Display a busy message. */ void (*scr_busy)(SCR *, const char *, busy_t); /* Prepare child. */ int (*scr_child)(SCR *); /* Clear to the end of the line. */ int (*scr_clrtoeol)(SCR *); /* Return the cursor location. */ int (*scr_cursor)(SCR *, size_t *, size_t *); /* Delete a line. */ int (*scr_deleteln)(SCR *); /* Discard a screen. */ int (*scr_discard)(SCR *, SCR **); /* Get a keyboard event. */ int (*scr_event)(SCR *, EVENT *, u_int32_t, int); /* Ex: screen adjustment routine. */ int (*scr_ex_adjust)(SCR *, exadj_t); int (*scr_fmap) /* Set a function key. */ (SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t); /* Get terminal key value. */ int (*scr_keyval)(SCR *, scr_keyval_t, CHAR_T *, int *); /* Insert a line. */ int (*scr_insertln)(SCR *); /* Handle an option change. */ int (*scr_optchange)(SCR *, int, char *, u_long *); /* Move the cursor. */ int (*scr_move)(SCR *, size_t, size_t); /* Message or ex output. */ void (*scr_msg)(SCR *, mtype_t, char *, size_t); /* Refresh the screen. */ int (*scr_refresh)(SCR *, int); /* Rename the file. */ int (*scr_rename)(SCR *, char *, int); /* Reply to an event. */ int (*scr_reply)(SCR *, int, char *); /* Set the screen type. */ int (*scr_screen)(SCR *, u_int32_t); /* Split the screen. */ int (*scr_split)(SCR *, SCR *); /* Suspend the editor. */ int (*scr_suspend)(SCR *, int *); /* Print usage message. */ void (*scr_usage)(void); }; Index: head/contrib/nvi/common/key.c =================================================================== --- head/contrib/nvi/common/key.c (revision 365498) +++ head/contrib/nvi/common/key.c (revision 365499) @@ -1,870 +1,839 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: key.c,v 10.54 2013/11/13 12:15:27 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" static int v_event_append(SCR *, EVENT *); static int v_event_grow(SCR *, int); static int v_key_cmp(const void *, const void *); static void v_keyval(SCR *, int, scr_keyval_t); static void v_sync(SCR *, int); /* * !!! * Historic vi always used: * * ^D: autoindent deletion * ^H: last character deletion * ^W: last word deletion * ^Q: quote the next character (if not used in flow control). * ^V: quote the next character * * regardless of the user's choices for these characters. The user's erase * and kill characters worked in addition to these characters. Nvi wires * down the above characters, but in addition permits the VEOF, VERASE, VKILL * and VWERASE characters described by the user's termios structure. * * Ex was not consistent with this scheme, as it historically ran in tty * cooked mode. This meant that the scroll command and autoindent erase * characters were mapped to the user's EOF character, and the character * and word deletion characters were the user's tty character and word * deletion characters. This implementation makes it all consistent, as * described above for vi. * * !!! * This means that all screens share a special key set. */ KEYLIST keylist[] = { {K_BACKSLASH, '\\'}, /* \ */ {K_CARAT, '^'}, /* ^ */ {K_CNTRLD, '\004'}, /* ^D */ {K_CNTRLR, '\022'}, /* ^R */ {K_CNTRLT, '\024'}, /* ^T */ {K_CNTRLZ, '\032'}, /* ^Z */ {K_COLON, ':'}, /* : */ {K_CR, '\r'}, /* \r */ {K_ESCAPE, '\033'}, /* ^[ */ {K_FORMFEED, '\f'}, /* \f */ {K_HEXCHAR, '\030'}, /* ^X */ {K_NL, '\n'}, /* \n */ {K_RIGHTBRACE, '}'}, /* } */ {K_RIGHTPAREN, ')'}, /* ) */ {K_TAB, '\t'}, /* \t */ {K_VERASE, '\b'}, /* \b */ {K_VKILL, '\025'}, /* ^U */ {K_VLNEXT, '\021'}, /* ^Q */ {K_VLNEXT, '\026'}, /* ^V */ {K_VWERASE, '\027'}, /* ^W */ {K_ZERO, '0'}, /* 0 */ #define ADDITIONAL_CHARACTERS 4 {K_NOTUSED, 0}, /* VEOF, VERASE, VKILL, VWERASE */ {K_NOTUSED, 0}, {K_NOTUSED, 0}, {K_NOTUSED, 0}, }; static int nkeylist = (sizeof(keylist) / sizeof(keylist[0])) - ADDITIONAL_CHARACTERS; /* * v_key_init -- * Initialize the special key lookup table. * * PUBLIC: int v_key_init(SCR *); */ int v_key_init(SCR *sp) { int ch; GS *gp; KEYLIST *kp; int cnt; gp = sp->gp; v_key_ilookup(sp); v_keyval(sp, K_CNTRLD, KEY_VEOF); v_keyval(sp, K_VERASE, KEY_VERASE); v_keyval(sp, K_VKILL, KEY_VKILL); v_keyval(sp, K_VWERASE, KEY_VWERASE); /* Sort the special key list. */ qsort(keylist, nkeylist, sizeof(keylist[0]), v_key_cmp); /* Initialize the fast lookup table. */ for (kp = keylist, cnt = nkeylist; cnt--; ++kp) gp->special_key[kp->ch] = kp->value; /* Find a non-printable character to use as a message separator. */ for (ch = 1; ch <= UCHAR_MAX; ++ch) if (!isprint(ch)) { gp->noprint = ch; break; } if (ch != gp->noprint) { msgq(sp, M_ERR, "079|No non-printable character found"); return (1); } return (0); } /* * v_keyval -- * Set key values. * * We've left some open slots in the keylist table, and if these values exist, * we put them into place. Note, they may reset (or duplicate) values already * in the table, so we check for that first. */ static void -v_keyval( - SCR *sp, - int val, - scr_keyval_t name) +v_keyval(SCR *sp, int val, scr_keyval_t name) { KEYLIST *kp; CHAR_T ch; int dne; /* Get the key's value from the screen. */ if (sp->gp->scr_keyval(sp, name, &ch, &dne)) return; if (dne) return; /* Check for duplication. */ for (kp = keylist; kp->value != K_NOTUSED; ++kp) if (kp->ch == ch) { kp->value = val; return; } /* Add a new entry. */ if (kp->value == K_NOTUSED) { keylist[nkeylist].ch = ch; keylist[nkeylist].value = val; ++nkeylist; } } /* * v_key_ilookup -- * Build the fast-lookup key display array. * * PUBLIC: void v_key_ilookup(SCR *); */ void v_key_ilookup(SCR *sp) { UCHAR_T ch; char *p, *t; GS *gp; size_t len; for (gp = sp->gp, ch = 0;; ++ch) { for (p = gp->cname[ch].name, t = v_key_name(sp, ch), len = gp->cname[ch].len = sp->clen; len--;) *p++ = *t++; if (ch == MAX_FAST_KEY) break; } } /* * v_key_len -- * Return the length of the string that will display the key. * This routine is the backup for the KEY_LEN() macro. * * PUBLIC: size_t v_key_len(SCR *, ARG_CHAR_T); */ size_t -v_key_len( - SCR *sp, - ARG_CHAR_T ch) +v_key_len(SCR *sp, ARG_CHAR_T ch) { (void)v_key_name(sp, ch); return (sp->clen); } /* * v_key_name -- * Return the string that will display the key. This routine * is the backup for the KEY_NAME() macro. * * PUBLIC: char *v_key_name(SCR *, ARG_CHAR_T); */ char * -v_key_name( - SCR *sp, - ARG_CHAR_T ach) +v_key_name(SCR *sp, ARG_CHAR_T ach) { static const char hexdigit[] = "0123456789abcdef"; static const char octdigit[] = "01234567"; int ch; size_t len; char *chp; /* * Cache the last checked character. It won't be a problem * since nvi will rescan the mapping when settings changed. */ if (ach && sp->lastc == ach) return (sp->cname); sp->lastc = ach; #ifdef USE_WIDECHAR len = wctomb(sp->cname, ach); if (len > MB_CUR_MAX) #endif sp->cname[(len = 1)-1] = (u_char)ach; ch = (u_char)sp->cname[0]; sp->cname[len] = '\0'; /* See if the character was explicitly declared printable or not. */ if ((chp = O_STR(sp, O_PRINT)) != NULL) if (strstr(chp, sp->cname) != NULL) goto done; if ((chp = O_STR(sp, O_NOPRINT)) != NULL) if (strstr(chp, sp->cname) != NULL) goto nopr; /* * Historical (ARPA standard) mappings. Printable characters are left * alone. Control characters less than 0x20 are represented as '^' * followed by the character offset from the '@' character in the ASCII * character set. Del (0x7f) is represented as '^' followed by '?'. * * XXX * The following code depends on the current locale being identical to * the ASCII map from 0x40 to 0x5f (since 0x1f + 0x40 == 0x5f). I'm * told that this is a reasonable assumption... * * XXX * The code prints non-printable wide characters in 4 or 5 digits * Unicode escape sequences, so only supports plane 0 to 15. */ if (CAN_PRINT(sp, ach)) goto done; nopr: if (iscntrl(ch) && (ch < 0x20 || ch == 0x7f)) { sp->cname[0] = '^'; sp->cname[1] = ch == 0x7f ? '?' : '@' + ch; len = 2; goto done; } #ifdef USE_WIDECHAR if (INTISWIDE(ach)) { int uc = -1; if (!strcmp(codeset(), "UTF-8")) uc = decode_utf8(sp->cname); #ifdef USE_ICONV else { char buf[sizeof(sp->cname)] = ""; size_t left = sizeof(sp->cname); char *in = sp->cname; char *out = buf; iconv(sp->conv.id[IC_IE_TO_UTF16], (iconv_src_t)&in, &len, &out, &left); iconv(sp->conv.id[IC_IE_TO_UTF16], NULL, NULL, NULL, NULL); uc = decode_utf16(buf, 1); } #endif if (uc >= 0) { len = snprintf(sp->cname, sizeof(sp->cname), uc < 0x10000 ? "\\u%04x" : "\\U%05X", uc); goto done; } } #endif if (O_ISSET(sp, O_OCTAL)) { sp->cname[0] = '\\'; sp->cname[1] = octdigit[(ch & 0300) >> 6]; sp->cname[2] = octdigit[(ch & 070) >> 3]; sp->cname[3] = octdigit[ ch & 07 ]; } else { sp->cname[0] = '\\'; sp->cname[1] = 'x'; sp->cname[2] = hexdigit[(ch & 0xf0) >> 4]; sp->cname[3] = hexdigit[ ch & 0x0f ]; } len = 4; done: sp->cname[sp->clen = len] = '\0'; return (sp->cname); } /* * v_key_val -- * Fill in the value for a key. This routine is the backup * for the KEY_VAL() macro. * * PUBLIC: e_key_t v_key_val(SCR *, ARG_CHAR_T); */ e_key_t -v_key_val( - SCR *sp, - ARG_CHAR_T ch) +v_key_val(SCR *sp, ARG_CHAR_T ch) { KEYLIST k, *kp; k.ch = ch; kp = bsearch(&k, keylist, nkeylist, sizeof(keylist[0]), v_key_cmp); return (kp == NULL ? K_NOTUSED : kp->value); } /* * v_event_push -- * Push events/keys onto the front of the buffer. * * There is a single input buffer in ex/vi. Characters are put onto the * end of the buffer by the terminal input routines, and pushed onto the * front of the buffer by various other functions in ex/vi. Each key has * an associated flag value, which indicates if it has already been quoted, * and if it is the result of a mapping or an abbreviation. * * PUBLIC: int v_event_push(SCR *, EVENT *, CHAR_T *, size_t, u_int); */ int -v_event_push( - SCR *sp, +v_event_push(SCR *sp, EVENT *p_evp, /* Push event. */ CHAR_T *p_s, /* Push characters. */ size_t nitems, /* Number of items to push. */ u_int flags) /* CH_* flags. */ { EVENT *evp; GS *gp; size_t total; /* If we have room, stuff the items into the buffer. */ gp = sp->gp; if (nitems <= gp->i_next || (gp->i_event != NULL && gp->i_cnt == 0 && nitems <= gp->i_nelem)) { if (gp->i_cnt != 0) gp->i_next -= nitems; goto copy; } /* * If there are currently items in the queue, shift them up, * leaving some extra room. Get enough space plus a little * extra. */ #define TERM_PUSH_SHIFT 30 total = gp->i_cnt + gp->i_next + nitems + TERM_PUSH_SHIFT; if (total >= gp->i_nelem && v_event_grow(sp, MAX(total, 64))) return (1); if (gp->i_cnt) - BCOPY(gp->i_event + gp->i_next, - gp->i_event + TERM_PUSH_SHIFT + nitems, gp->i_cnt); + memmove(gp->i_event + TERM_PUSH_SHIFT + nitems, + gp->i_event + gp->i_next, gp->i_cnt * sizeof(EVENT)); gp->i_next = TERM_PUSH_SHIFT; /* Put the new items into the queue. */ copy: gp->i_cnt += nitems; for (evp = gp->i_event + gp->i_next; nitems--; ++evp) { if (p_evp != NULL) *evp = *p_evp++; else { evp->e_event = E_CHARACTER; evp->e_c = *p_s++; evp->e_value = KEY_VAL(sp, evp->e_c); F_INIT(&evp->e_ch, flags); } } return (0); } /* * v_event_append -- * Append events onto the tail of the buffer. */ static int -v_event_append( - SCR *sp, - EVENT *argp) +v_event_append(SCR *sp, EVENT *argp) { CHAR_T *s; /* Characters. */ EVENT *evp; GS *gp; size_t nevents; /* Number of events. */ /* Grow the buffer as necessary. */ nevents = argp->e_event == E_STRING ? argp->e_len : 1; gp = sp->gp; if (gp->i_event == NULL || nevents > gp->i_nelem - (gp->i_next + gp->i_cnt)) v_event_grow(sp, MAX(nevents, 64)); evp = gp->i_event + gp->i_next + gp->i_cnt; gp->i_cnt += nevents; /* Transform strings of characters into single events. */ if (argp->e_event == E_STRING) for (s = argp->e_csp; nevents--; ++evp) { evp->e_event = E_CHARACTER; evp->e_c = *s++; evp->e_value = KEY_VAL(sp, evp->e_c); evp->e_flags = 0; } else *evp = *argp; return (0); } /* Remove events from the queue. */ #define QREM(len) { \ if ((gp->i_cnt -= len) == 0) \ gp->i_next = 0; \ else \ gp->i_next += len; \ } /* * v_event_get -- * Return the next event. * * !!! * The flag EC_NODIGIT probably needs some explanation. First, the idea of * mapping keys is that one or more keystrokes act like a function key. * What's going on is that vi is reading a number, and the character following * the number may or may not be mapped (EC_MAPCOMMAND). For example, if the * user is entering the z command, a valid command is "z40+", and we don't want * to map the '+', i.e. if '+' is mapped to "xxx", we don't want to change it * into "z40xxx". However, if the user enters "35x", we want to put all of the * characters through the mapping code. * * Historical practice is a bit muddled here. (Surprise!) It always permitted * mapping digits as long as they weren't the first character of the map, e.g. * ":map ^A1 xxx" was okay. It also permitted the mapping of the digits 1-9 * (the digit 0 was a special case as it doesn't indicate the start of a count) * as the first character of the map, but then ignored those mappings. While * it's probably stupid to map digits, vi isn't your mother. * * The way this works is that the EC_MAPNODIGIT causes term_key to return the * end-of-digit without "looking" at the next character, i.e. leaving it as the * user entered it. Presumably, the next term_key call will tell us how the * user wants it handled. * * There is one more complication. Users might map keys to digits, and, as * it's described above, the commands: * * :map g 1G * d2g * * would return the keys "d21G", when the user probably wanted * "d21G". So, if a map starts off with a digit we continue as * before, otherwise, we pretend we haven't mapped the character, and return * . * * Now that that's out of the way, let's talk about Energizer Bunny macros. * It's easy to create macros that expand to a loop, e.g. map x 3x. It's * fairly easy to detect this example, because it's all internal to term_key. * If we're expanding a macro and it gets big enough, at some point we can * assume it's looping and kill it. The examples that are tough are the ones * where the parser is involved, e.g. map x "ayyx"byy. We do an expansion * on 'x', and get "ayyx"byy. We then return the first 4 characters, and then * find the looping macro again. There is no way that we can detect this * without doing a full parse of the command, because the character that might * cause the loop (in this case 'x') may be a literal character, e.g. the map * map x "ayy"xyy"byy is perfectly legal and won't cause a loop. * * Historic vi tried to detect looping macros by disallowing obvious cases in * the map command, maps that that ended with the same letter as they started * (which wrongly disallowed "map x 'x"), and detecting macros that expanded * too many times before keys were returned to the command parser. It didn't * get many (most?) of the tricky cases right, however, and it was certainly * possible to create macros that ran forever. And, even if it did figure out * what was going on, the user was usually tossed into ex mode. Finally, any * changes made before vi realized that the macro was recursing were left in * place. We recover gracefully, but the only recourse the user has in an * infinite macro loop is to interrupt. * * !!! * It is historic practice that mapping characters to themselves as the first * part of the mapped string was legal, and did not cause infinite loops, i.e. * ":map! { {^M^T" and ":map n nz." were known to work. The initial, matching * characters were returned instead of being remapped. * * !!! * It is also historic practice that the macro "map ] ]]^" caused a single ] * keypress to behave as the command ]] (the ^ got the map past the vi check * for "tail recursion"). Conversely, the mapping "map n nn^" went recursive. * What happened was that, in the historic vi, maps were expanded as the keys * were retrieved, but not all at once and not centrally. So, the keypress ] * pushed ]]^ on the stack, and then the first ] from the stack was passed to * the ]] command code. The ]] command then retrieved a key without entering * the mapping code. This could bite us anytime a user has a map that depends * on secondary keys NOT being mapped. I can't see any possible way to make * this work in here without the complete abandonment of Rationality Itself. * * XXX * The final issue is recovery. It would be possible to undo all of the work * that was done by the macro if we entered a record into the log so that we * knew when the macro started, and, in fact, this might be worth doing at some * point. Given that this might make the log grow unacceptably (consider that * cursor keys are done with maps), for now we leave any changes made in place. * * PUBLIC: int v_event_get(SCR *, EVENT *, int, u_int32_t); */ int -v_event_get( - SCR *sp, - EVENT *argp, - int timeout, - u_int32_t flags) +v_event_get(SCR *sp, EVENT *argp, int timeout, u_int32_t flags) { EVENT *evp, ev; GS *gp; SEQ *qp; int init_nomap, ispartial, istimeout, remap_cnt; gp = sp->gp; /* If simply checking for interrupts, argp may be NULL. */ if (argp == NULL) argp = &ev; retry: istimeout = remap_cnt = 0; /* * If the queue isn't empty and we're timing out for characters, * return immediately. */ if (gp->i_cnt != 0 && LF_ISSET(EC_TIMEOUT)) return (0); /* * If the queue is empty, we're checking for interrupts, or we're * timing out for characters, get more events. */ if (gp->i_cnt == 0 || LF_ISSET(EC_INTERRUPT | EC_TIMEOUT)) { /* * If we're reading new characters, check any scripting * windows for input. */ if (F_ISSET(gp, G_SCRWIN) && sscr_input(sp)) return (1); loop: if (gp->scr_event(sp, argp, LF_ISSET(EC_INTERRUPT | EC_QUOTED | EC_RAW), timeout)) return (1); switch (argp->e_event) { case E_ERR: case E_SIGHUP: case E_SIGTERM: /* * Fatal conditions cause the file to be synced to * disk immediately. */ v_sync(sp, RCV_ENDSESSION | RCV_PRESERVE | (argp->e_event == E_SIGTERM ? 0: RCV_EMAIL)); return (1); case E_TIMEOUT: istimeout = 1; break; case E_INTERRUPT: /* Set the global interrupt flag. */ F_SET(sp->gp, G_INTERRUPTED); /* * If the caller was interested in interrupts, return * immediately. */ if (LF_ISSET(EC_INTERRUPT)) return (0); goto append; default: append: if (v_event_append(sp, argp)) return (1); break; } } /* * If the caller was only interested in interrupts or timeouts, return * immediately. (We may have gotten characters, and that's okay, they * were queued up for later use.) */ if (LF_ISSET(EC_INTERRUPT | EC_TIMEOUT)) return (0); newmap: evp = &gp->i_event[gp->i_next]; /* * If the next event in the queue isn't a character event, return * it, we're done. */ if (evp->e_event != E_CHARACTER) { *argp = *evp; QREM(1); return (0); } /* * If the key isn't mappable because: * * + ... the timeout has expired * + ... it's not a mappable key * + ... neither the command or input map flags are set * + ... there are no maps that can apply to it * * return it forthwith. */ if (istimeout || F_ISSET(&evp->e_ch, CH_NOMAP) || !LF_ISSET(EC_MAPCOMMAND | EC_MAPINPUT) || ((evp->e_c & ~MAX_BIT_SEQ) == 0 && !bit_test(gp->seqb, evp->e_c))) goto nomap; /* Search the map. */ qp = seq_find(sp, NULL, evp, NULL, gp->i_cnt, LF_ISSET(EC_MAPCOMMAND) ? SEQ_COMMAND : SEQ_INPUT, &ispartial); /* * If get a partial match, get more characters and retry the map. * If time out without further characters, return the characters * unmapped. * * !!! * characters are a problem. Cursor keys start with * characters, so there's almost always a map in place that begins with * an character. If we timeout keys in the same way * that we timeout other keys, the user will get a noticeable pause as * they enter to terminate input mode. If key timeout is set * for a slow link, users will get an even longer pause. Nvi used to * simply timeout characters at 1/10th of a second, but this * loses over PPP links where the latency is greater than 100Ms. */ if (ispartial) { if (O_ISSET(sp, O_TIMEOUT)) timeout = (evp->e_value == K_ESCAPE ? O_VAL(sp, O_ESCAPETIME) : O_VAL(sp, O_KEYTIME)) * 100; else timeout = 0; goto loop; } /* If no map, return the character. */ if (qp == NULL) { nomap: if (!ISDIGIT(evp->e_c) && LF_ISSET(EC_MAPNODIGIT)) goto not_digit; *argp = *evp; QREM(1); return (0); } /* * If looking for the end of a digit string, and the first character * of the map is it, pretend we haven't seen the character. */ if (LF_ISSET(EC_MAPNODIGIT) && qp->output != NULL && !ISDIGIT(qp->output[0])) { not_digit: argp->e_c = CH_NOT_DIGIT; argp->e_value = K_NOTUSED; argp->e_event = E_CHARACTER; F_INIT(&argp->e_ch, 0); return (0); } /* Find out if the initial segments are identical. */ init_nomap = !e_memcmp(qp->output, &gp->i_event[gp->i_next], qp->ilen); /* Delete the mapped characters from the queue. */ QREM(qp->ilen); /* If keys mapped to nothing, go get more. */ if (qp->output == NULL) goto retry; /* If remapping characters... */ if (O_ISSET(sp, O_REMAP)) { /* * Periodically check for interrupts. Always check the first * time through, because it's possible to set up a map that * will return a character every time, but will expand to more, * e.g. "map! a aaaa" will always return a 'a', but we'll never * get anywhere useful. */ if ((++remap_cnt == 1 || remap_cnt % 10 == 0) && (gp->scr_event(sp, &ev, EC_INTERRUPT, 0) || ev.e_event == E_INTERRUPT)) { F_SET(sp->gp, G_INTERRUPTED); argp->e_event = E_INTERRUPT; return (0); } /* * If an initial part of the characters mapped, they are not * further remapped -- return the first one. Push the rest * of the characters, or all of the characters if no initial * part mapped, back on the queue. */ if (init_nomap) { if (v_event_push(sp, NULL, qp->output + qp->ilen, qp->olen - qp->ilen, CH_MAPPED)) return (1); if (v_event_push(sp, NULL, qp->output, qp->ilen, CH_NOMAP | CH_MAPPED)) return (1); evp = &gp->i_event[gp->i_next]; goto nomap; } if (v_event_push(sp, NULL, qp->output, qp->olen, CH_MAPPED)) return (1); goto newmap; } /* Else, push the characters on the queue and return one. */ if (v_event_push(sp, NULL, qp->output, qp->olen, CH_MAPPED | CH_NOMAP)) return (1); goto nomap; } /* * v_sync -- * Walk the screen lists, sync'ing files to their backup copies. */ static void -v_sync( - SCR *sp, - int flags) +v_sync(SCR *sp, int flags) { GS *gp; gp = sp->gp; TAILQ_FOREACH(sp, gp->dq, q) rcv_sync(sp, flags); TAILQ_FOREACH(sp, gp->hq, q) rcv_sync(sp, flags); } /* * v_event_err -- * Unexpected event. * * PUBLIC: void v_event_err(SCR *, EVENT *); */ void -v_event_err( - SCR *sp, - EVENT *evp) +v_event_err(SCR *sp, EVENT *evp) { switch (evp->e_event) { case E_CHARACTER: msgq(sp, M_ERR, "276|Unexpected character event"); break; case E_EOF: msgq(sp, M_ERR, "277|Unexpected end-of-file event"); break; case E_INTERRUPT: msgq(sp, M_ERR, "279|Unexpected interrupt event"); break; case E_REPAINT: msgq(sp, M_ERR, "281|Unexpected repaint event"); break; case E_STRING: msgq(sp, M_ERR, "285|Unexpected string event"); break; case E_TIMEOUT: msgq(sp, M_ERR, "286|Unexpected timeout event"); break; case E_WRESIZE: msgq(sp, M_ERR, "316|Unexpected resize event"); break; /* * Theoretically, none of these can occur, as they're handled at the * top editor level. */ case E_ERR: case E_SIGHUP: case E_SIGTERM: default: abort(); } /* Free any allocated memory. */ - if (evp->e_asp != NULL) - free(evp->e_asp); + free(evp->e_asp); } /* * v_event_flush -- * Flush any flagged keys, returning if any keys were flushed. * * PUBLIC: int v_event_flush(SCR *, u_int); */ int -v_event_flush( - SCR *sp, - u_int flags) +v_event_flush(SCR *sp, u_int flags) { GS *gp; int rval; for (rval = 0, gp = sp->gp; gp->i_cnt != 0 && F_ISSET(&gp->i_event[gp->i_next].e_ch, flags); rval = 1) QREM(1); return (rval); } /* * v_event_grow -- * Grow the terminal queue. */ static int -v_event_grow( - SCR *sp, - int add) +v_event_grow(SCR *sp, int add) { GS *gp; size_t new_nelem, olen; gp = sp->gp; new_nelem = gp->i_nelem + add; olen = gp->i_nelem * sizeof(gp->i_event[0]); BINC_RET(sp, EVENT, gp->i_event, olen, new_nelem * sizeof(gp->i_event[0])); gp->i_nelem = olen / sizeof(gp->i_event[0]); return (0); } /* * v_key_cmp -- * Compare two keys for sorting. */ static int -v_key_cmp( - const void *ap, - const void *bp) +v_key_cmp(const void *ap, const void *bp) { return (((KEYLIST *)ap)->ch - ((KEYLIST *)bp)->ch); } Index: head/contrib/nvi/common/key.h =================================================================== --- head/contrib/nvi/common/key.h (revision 365498) +++ head/contrib/nvi/common/key.h (revision 365499) @@ -1,240 +1,240 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: key.h,v 10.56 2013/11/13 12:15:27 zy Exp $ */ #include "multibyte.h" #ifdef USE_WIDECHAR #define FILE2INT5(sp,buf,n,nlen,w,wlen) \ sp->conv.file2int(sp, n, nlen, &buf, &wlen, &w) #define INT2FILE(sp,w,wlen,n,nlen) \ sp->conv.int2file(sp, w, wlen, &sp->cw, &nlen, &n) #define CHAR2INT5(sp,buf,n,nlen,w,wlen) \ sp->conv.sys2int(sp, n, nlen, &buf, &wlen, &w) #define INT2CHAR(sp,w,wlen,n,nlen) \ sp->conv.int2sys(sp, w, wlen, &sp->cw, &nlen, &n) #define INPUT2INT5(sp,cw,n,nlen,w,wlen) \ sp->conv.input2int(sp, n, nlen, &(cw), &wlen, &w) #define CONST #define INTISWIDE(c) (wctob(c) == EOF) #define CHAR_WIDTH(sp, ch) wcwidth(ch) #define CAN_PRINT(sp, ch) (CHAR_WIDTH(sp, ch) > 0) #else #define FILE2INT5(sp,buf,n,nlen,w,wlen) \ (w = n, wlen = nlen, 0) #define INT2FILE(sp,w,wlen,n,nlen) \ (n = w, nlen = wlen, 0) #define CHAR2INT5(sp,buf,n,nlen,w,wlen) \ (w = n, wlen = nlen, 0) #define INT2CHAR(sp,w,wlen,n,nlen) \ (n = w, nlen = wlen, 0) #define INPUT2INT5(sp,buf,n,nlen,w,wlen) \ (w = n, wlen = nlen, 0) #define CONST const #define INTISWIDE(c) 0 #define CHAR_WIDTH(sp, ch) 1 #define CAN_PRINT(sp, ch) isprint(ch) #endif #define FILE2INT(sp,n,nlen,w,wlen) \ FILE2INT5(sp,sp->cw,n,nlen,w,wlen) #define CHAR2INT(sp,n,nlen,w,wlen) \ CHAR2INT5(sp,sp->cw,n,nlen,w,wlen) /* The maximum number of columns any character can take up on a screen. */ #define MAX_CHARACTER_COLUMNS 7 /* * Event types. * * The program structure depends on the event loop being able to return * E_EOF/E_ERR multiple times -- eventually enough things will end due * to the events that vi will reach the command level for the screen, at * which point the exit flags will be set and vi will exit. */ typedef enum { E_NOTUSED = 0, /* Not set. */ E_CHARACTER, /* Input character: e_c set. */ E_EOF, /* End of input (NOT ^D). */ E_ERR, /* Input error. */ E_INTERRUPT, /* Interrupt. */ E_REPAINT, /* Repaint: e_flno, e_tlno set. */ E_SIGHUP, /* SIGHUP. */ E_SIGTERM, /* SIGTERM. */ E_STRING, /* Input string: e_csp, e_len set. */ E_TIMEOUT, /* Timeout. */ E_WRESIZE, /* Window resize. */ } e_event_t; /* * Character values. */ typedef enum { K_NOTUSED = 0, /* Not set. */ K_BACKSLASH, /* \ */ K_CARAT, /* ^ */ K_CNTRLD, /* ^D */ K_CNTRLR, /* ^R */ K_CNTRLT, /* ^T */ K_CNTRLZ, /* ^Z */ K_COLON, /* : */ K_CR, /* \r */ K_ESCAPE, /* ^[ */ K_FORMFEED, /* \f */ K_HEXCHAR, /* ^X */ K_NL, /* \n */ K_RIGHTBRACE, /* } */ K_RIGHTPAREN, /* ) */ K_TAB, /* \t */ K_VERASE, /* set from tty: default ^H */ K_VKILL, /* set from tty: default ^U */ K_VLNEXT, /* set from tty: default ^V */ K_VWERASE, /* set from tty: default ^W */ K_ZERO /* 0 */ } e_key_t; struct _event { TAILQ_ENTRY(_event) q; /* Linked list of events. */ e_event_t e_event; /* Event type. */ union { struct { /* Input character. */ CHAR_T c; /* Character. */ e_key_t value; /* Key type. */ #define CH_ABBREVIATED 0x01 /* Character is from an abbreviation. */ #define CH_MAPPED 0x02 /* Character is from a map. */ #define CH_NOMAP 0x04 /* Do not map the character. */ #define CH_QUOTED 0x08 /* Character is already quoted. */ u_int8_t flags; } _e_ch; #define e_ch _u_event._e_ch /* !!! The structure, not the char. */ #define e_c _u_event._e_ch.c #define e_value _u_event._e_ch.value #define e_flags _u_event._e_ch.flags struct { /* Screen position, size. */ size_t lno1; /* Line number. */ size_t cno1; /* Column number. */ size_t lno2; /* Line number. */ size_t cno2; /* Column number. */ } _e_mark; #define e_lno _u_event._e_mark.lno1 /* Single location. */ #define e_cno _u_event._e_mark.cno1 #define e_flno _u_event._e_mark.lno1 /* Text region. */ #define e_fcno _u_event._e_mark.cno1 #define e_tlno _u_event._e_mark.lno2 #define e_tcno _u_event._e_mark.cno2 struct { /* Input string. */ CHAR_T *asp; /* Allocated string. */ CHAR_T *csp; /* String. */ size_t len; /* String length. */ } _e_str; #define e_asp _u_event._e_str.asp #define e_csp _u_event._e_str.csp #define e_len _u_event._e_str.len } _u_event; }; typedef struct _keylist { e_key_t value; /* Special value. */ int ch; /* Key. */ } KEYLIST; extern KEYLIST keylist[]; /* Return if more keys in queue. */ #define KEYS_WAITING(sp) ((sp)->gp->i_cnt != 0) #define MAPPED_KEYS_WAITING(sp) \ (KEYS_WAITING(sp) && \ F_ISSET(&sp->gp->i_event[sp->gp->i_next].e_ch, CH_MAPPED)) /* * Ex/vi commands are generally separated by whitespace characters. We * can't use the standard isspace(3) macro because it returns true for * characters like ^K in the ASCII character set. The POSIX isblank(3) * has the same problem for non-ASCII locale, so we need a standalone one. - * - * XXX - * Note side effect, ch is evaluated multiple times. */ -#define cmdskip(ch) ((ch) == ' ' || (ch) == '\t') + +static __inline int +cmdskip(CHAR_T ch) +{ + return ch == ' ' || ch == '\t'; +} /* The "standard" tab width, for displaying things to users. */ #define STANDARD_TAB 6 /* Various special characters, messages. */ #define CH_BSEARCH '?' /* Backward search prompt. */ #define CH_CURSOR ' ' /* Cursor character. */ #define CH_ENDMARK '$' /* End of a range. */ #define CH_EXPROMPT ':' /* Ex prompt. */ #define CH_FSEARCH '/' /* Forward search prompt. */ #define CH_HEX '\030' /* Leading hex character. */ #define CH_LITERAL '\026' /* ASCII ^V. */ #define CH_NO 'n' /* No. */ #define CH_NOT_DIGIT 'a' /* A non-isdigit() character. */ #define CH_QUIT 'q' /* Quit. */ #define CH_YES 'y' /* Yes. */ /* * Checking for interrupts means that we look at the bit that gets set if the * screen code supports asynchronous events, and call back into the event code * so that non-asynchronous screens get a chance to post the interrupt. * * INTERRUPT_CHECK is the number of lines "operated" on before checking for * interrupts. */ #define INTERRUPT_CHECK 100 #define INTERRUPTED(sp) \ (F_ISSET((sp)->gp, G_INTERRUPTED) || \ (!v_event_get(sp, NULL, 0, EC_INTERRUPT) && \ F_ISSET((sp)->gp, G_INTERRUPTED))) #define CLR_INTERRUPT(sp) \ F_CLR((sp)->gp, G_INTERRUPTED) /* Flags describing types of characters being requested. */ #define EC_INTERRUPT 0x001 /* Checking for interrupts. */ #define EC_MAPCOMMAND 0x002 /* Apply the command map. */ #define EC_MAPINPUT 0x004 /* Apply the input map. */ #define EC_MAPNODIGIT 0x008 /* Return to a digit. */ #define EC_QUOTED 0x010 /* Try to quote next character */ #define EC_RAW 0x020 /* Any next character. XXX: not used. */ #define EC_TIMEOUT 0x040 /* Timeout to next character. */ /* Flags describing text input special cases. */ #define TXT_ADDNEWLINE 0x00000001 /* Replay starts on a new line. */ #define TXT_AICHARS 0x00000002 /* Leading autoindent chars. */ #define TXT_ALTWERASE 0x00000004 /* Option: altwerase. */ #define TXT_APPENDEOL 0x00000008 /* Appending after EOL. */ #define TXT_AUTOINDENT 0x00000010 /* Autoindent set this line. */ #define TXT_BACKSLASH 0x00000020 /* Backslashes escape characters. */ #define TXT_BEAUTIFY 0x00000040 /* Only printable characters. */ #define TXT_BS 0x00000080 /* Backspace returns the buffer. */ #define TXT_CEDIT 0x00000100 /* Can return TERM_CEDIT. */ #define TXT_CNTRLD 0x00000200 /* Control-D is a command. */ #define TXT_CNTRLT 0x00000400 /* Control-T is an indent special. */ #define TXT_CR 0x00000800 /* CR returns the buffer. */ #define TXT_DOTTERM 0x00001000 /* Leading '.' terminates the input. */ #define TXT_EMARK 0x00002000 /* End of replacement mark. */ #define TXT_EOFCHAR 0x00004000 /* ICANON set, return EOF character. */ #define TXT_ESCAPE 0x00008000 /* Escape returns the buffer. */ #define TXT_FILEC 0x00010000 /* Option: filec. */ #define TXT_INFOLINE 0x00020000 /* Editing the info line. */ #define TXT_MAPINPUT 0x00040000 /* Apply the input map. */ #define TXT_NLECHO 0x00080000 /* Echo the newline. */ #define TXT_NUMBER 0x00100000 /* Number the line. */ #define TXT_OVERWRITE 0x00200000 /* Overwrite characters. */ #define TXT_PROMPT 0x00400000 /* Display a prompt. */ #define TXT_RECORD 0x00800000 /* Record for replay. */ #define TXT_REPLACE 0x01000000 /* Replace; don't delete overwrite. */ #define TXT_REPLAY 0x02000000 /* Replay the last input. */ #define TXT_RESOLVE 0x04000000 /* Resolve the text into the file. */ #define TXT_SEARCHINCR 0x08000000 /* Incremental search. */ #define TXT_SHOWMATCH 0x10000000 /* Option: showmatch. */ #define TXT_TTYWERASE 0x20000000 /* Option: ttywerase. */ #define TXT_WRAPMARGIN 0x40000000 /* Option: wrapmargin. */ Index: head/contrib/nvi/common/line.c =================================================================== --- head/contrib/nvi/common/line.c (revision 365498) +++ head/contrib/nvi/common/line.c (revision 365499) @@ -1,657 +1,621 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: line.c,v 10.27 2015/04/03 14:17:21 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" static int scr_update(SCR *, recno_t, lnop_t, int); /* * db_eget -- * Front-end to db_get, special case handling for empty files. * * PUBLIC: int db_eget(SCR *, recno_t, CHAR_T **, size_t *, int *); */ int -db_eget( - SCR *sp, +db_eget(SCR *sp, recno_t lno, /* Line number. */ CHAR_T **pp, /* Pointer store. */ size_t *lenp, /* Length store. */ int *isemptyp) { recno_t l1; if (isemptyp != NULL) *isemptyp = 0; /* If the line exists, simply return it. */ if (!db_get(sp, lno, 0, pp, lenp)) return (0); /* * If the user asked for line 0 or line 1, i.e. the only possible * line in an empty file, find the last line of the file; db_last * fails loudly. */ if ((lno == 0 || lno == 1) && db_last(sp, &l1)) return (1); /* If the file isn't empty, fail loudly. */ if ((lno != 0 && lno != 1) || l1 != 0) { db_err(sp, lno); return (1); } if (isemptyp != NULL) *isemptyp = 1; return (1); } /* * db_get -- * Look in the text buffers for a line, followed by the cache, followed * by the database. * * PUBLIC: int db_get(SCR *, recno_t, u_int32_t, CHAR_T **, size_t *); */ int -db_get( - SCR *sp, +db_get(SCR *sp, recno_t lno, /* Line number. */ u_int32_t flags, CHAR_T **pp, /* Pointer store. */ size_t *lenp) /* Length store. */ { DBT data, key; EXF *ep; TEXT *tp; recno_t l1, l2; CHAR_T *wp; size_t wlen; /* * The underlying recno stuff handles zero by returning NULL, but * have to have an OOB condition for the look-aside into the input * buffer anyway. */ if (lno == 0) goto err1; /* Check for no underlying file. */ if ((ep = sp->ep) == NULL) { ex_emsg(sp, NULL, EXM_NOFILEYET); goto err3; } if (LF_ISSET(DBG_NOCACHE)) goto nocache; /* * Look-aside into the TEXT buffers and see if the line we want * is there. */ if (F_ISSET(sp, SC_TINPUT)) { l1 = ((TEXT *)TAILQ_FIRST(sp->tiq))->lno; l2 = ((TEXT *)TAILQ_LAST(sp->tiq, _texth))->lno; if (l1 <= lno && l2 >= lno) { #if defined(DEBUG) && 0 TRACE(sp, "retrieve TEXT buffer line %lu\n", (u_long)lno); #endif for (tp = TAILQ_FIRST(sp->tiq); tp->lno != lno; tp = TAILQ_NEXT(tp, q)); if (lenp != NULL) *lenp = tp->len; if (pp != NULL) *pp = tp->lb; return (0); } /* * Adjust the line number for the number of lines used * by the text input buffers. */ if (lno > l2) lno -= l2 - l1; } /* Look-aside into the cache, and see if the line we want is there. */ if (lno == ep->c_lno) { #if defined(DEBUG) && 0 TRACE(sp, "retrieve cached line %lu\n", (u_long)lno); #endif if (lenp != NULL) *lenp = ep->c_len; if (pp != NULL) *pp = ep->c_lp; return (0); } ep->c_lno = OOBLNO; nocache: /* Get the line from the underlying database. */ key.data = &lno; key.size = sizeof(lno); switch (ep->db->get(ep->db, &key, &data, 0)) { case -1: goto err2; case 1: err1: if (LF_ISSET(DBG_FATAL)) err2: db_err(sp, lno); alloc_err: err3: if (lenp != NULL) *lenp = 0; if (pp != NULL) *pp = NULL; return (1); } if (FILE2INT(sp, data.data, data.size, wp, wlen)) { if (!F_ISSET(sp, SC_CONV_ERROR)) { F_SET(sp, SC_CONV_ERROR); msgq(sp, M_ERR, "324|Conversion error on line %d", lno); } goto err3; } /* Reset the cache. */ if (wp != data.data) { BINC_GOTOW(sp, ep->c_lp, ep->c_blen, wlen); MEMCPY(ep->c_lp, wp, wlen); } else ep->c_lp = data.data; ep->c_lno = lno; ep->c_len = wlen; #if defined(DEBUG) && 0 TRACE(sp, "retrieve DB line %lu\n", (u_long)lno); #endif if (lenp != NULL) *lenp = wlen; if (pp != NULL) *pp = ep->c_lp; return (0); } /* * db_delete -- * Delete a line from the file. * * PUBLIC: int db_delete(SCR *, recno_t); */ int -db_delete( - SCR *sp, - recno_t lno) +db_delete(SCR *sp, recno_t lno) { DBT key; EXF *ep; #if defined(DEBUG) && 0 TRACE(sp, "delete line %lu\n", (u_long)lno); #endif /* Check for no underlying file. */ if ((ep = sp->ep) == NULL) { ex_emsg(sp, NULL, EXM_NOFILEYET); return (1); } /* Update marks, @ and global commands. */ if (mark_insdel(sp, LINE_DELETE, lno)) return (1); if (ex_g_insdel(sp, LINE_DELETE, lno)) return (1); /* Log change. */ log_line(sp, lno, LOG_LINE_DELETE); /* Update file. */ key.data = &lno; key.size = sizeof(lno); if (ep->db->del(ep->db, &key, 0) == 1) { msgq(sp, M_SYSERR, "003|unable to delete line %lu", (u_long)lno); return (1); } /* Flush the cache, update line count, before screen update. */ if (lno <= ep->c_lno) ep->c_lno = OOBLNO; if (ep->c_nlines != OOBLNO) --ep->c_nlines; /* File now modified. */ if (F_ISSET(ep, F_FIRSTMODIFY)) (void)rcv_init(sp); F_SET(ep, F_MODIFIED); /* Update screen. */ return (scr_update(sp, lno, LINE_DELETE, 1)); } /* * db_append -- * Append a line into the file. * * PUBLIC: int db_append(SCR *, int, recno_t, CHAR_T *, size_t); */ int -db_append( - SCR *sp, - int update, - recno_t lno, - CHAR_T *p, - size_t len) +db_append(SCR *sp, int update, recno_t lno, CHAR_T *p, size_t len) { DBT data, key; EXF *ep; char *fp; size_t flen; int rval; #if defined(DEBUG) && 0 TRACE(sp, "append to %lu: len %u {%.*s}\n", lno, len, MIN(len, 20), p); #endif /* Check for no underlying file. */ if ((ep = sp->ep) == NULL) { ex_emsg(sp, NULL, EXM_NOFILEYET); return (1); } INT2FILE(sp, p, len, fp, flen); /* Update file. */ key.data = &lno; key.size = sizeof(lno); data.data = fp; data.size = flen; if (ep->db->put(ep->db, &key, &data, R_IAFTER) == -1) { msgq(sp, M_SYSERR, "004|unable to append to line %lu", (u_long)lno); return (1); } /* Flush the cache, update line count, before screen update. */ if (lno < ep->c_lno) ep->c_lno = OOBLNO; if (ep->c_nlines != OOBLNO) ++ep->c_nlines; /* File now dirty. */ if (F_ISSET(ep, F_FIRSTMODIFY)) (void)rcv_init(sp); F_SET(ep, F_MODIFIED); /* Log change. */ log_line(sp, lno + 1, LOG_LINE_APPEND); /* Update marks, @ and global commands. */ rval = 0; if (mark_insdel(sp, LINE_INSERT, lno + 1)) rval = 1; if (ex_g_insdel(sp, LINE_INSERT, lno + 1)) rval = 1; /* * Update screen. * * XXX * Nasty hack. If multiple lines are input by the user, they aren't * committed until an is entered. The problem is the screen was * updated/scrolled as each line was entered. So, when this routine * is called to copy the new lines from the cut buffer into the file, * it has to know not to update the screen again. */ return (scr_update(sp, lno, LINE_APPEND, update) || rval); } /* * db_insert -- * Insert a line into the file. * * PUBLIC: int db_insert(SCR *, recno_t, CHAR_T *, size_t); */ int -db_insert( - SCR *sp, - recno_t lno, - CHAR_T *p, - size_t len) +db_insert(SCR *sp, recno_t lno, CHAR_T *p, size_t len) { DBT data, key; EXF *ep; char *fp; size_t flen; int rval; #if defined(DEBUG) && 0 TRACE(sp, "insert before %lu: len %lu {%.*s}\n", (u_long)lno, (u_long)len, MIN(len, 20), p); #endif /* Check for no underlying file. */ if ((ep = sp->ep) == NULL) { ex_emsg(sp, NULL, EXM_NOFILEYET); return (1); } INT2FILE(sp, p, len, fp, flen); /* Update file. */ key.data = &lno; key.size = sizeof(lno); data.data = fp; data.size = flen; if (ep->db->put(ep->db, &key, &data, R_IBEFORE) == -1) { msgq(sp, M_SYSERR, "005|unable to insert at line %lu", (u_long)lno); return (1); } /* Flush the cache, update line count, before screen update. */ if (lno >= ep->c_lno) ep->c_lno = OOBLNO; if (ep->c_nlines != OOBLNO) ++ep->c_nlines; /* File now dirty. */ if (F_ISSET(ep, F_FIRSTMODIFY)) (void)rcv_init(sp); F_SET(ep, F_MODIFIED); /* Log change. */ log_line(sp, lno, LOG_LINE_INSERT); /* Update marks, @ and global commands. */ rval = 0; if (mark_insdel(sp, LINE_INSERT, lno)) rval = 1; if (ex_g_insdel(sp, LINE_INSERT, lno)) rval = 1; /* Update screen. */ return (scr_update(sp, lno, LINE_INSERT, 1) || rval); } /* * db_set -- * Store a line in the file. * * PUBLIC: int db_set(SCR *, recno_t, CHAR_T *, size_t); */ int -db_set( - SCR *sp, - recno_t lno, - CHAR_T *p, - size_t len) +db_set(SCR *sp, recno_t lno, CHAR_T *p, size_t len) { DBT data, key; EXF *ep; char *fp; size_t flen; #if defined(DEBUG) && 0 TRACE(sp, "replace line %lu: len %lu {%.*s}\n", (u_long)lno, (u_long)len, MIN(len, 20), p); #endif /* Check for no underlying file. */ if ((ep = sp->ep) == NULL) { ex_emsg(sp, NULL, EXM_NOFILEYET); return (1); } /* Log before change. */ log_line(sp, lno, LOG_LINE_RESET_B); INT2FILE(sp, p, len, fp, flen); /* Update file. */ key.data = &lno; key.size = sizeof(lno); data.data = fp; data.size = flen; if (ep->db->put(ep->db, &key, &data, 0) == -1) { msgq(sp, M_SYSERR, "006|unable to store line %lu", (u_long)lno); return (1); } /* Flush the cache, before logging or screen update. */ if (lno == ep->c_lno) ep->c_lno = OOBLNO; /* File now dirty. */ if (F_ISSET(ep, F_FIRSTMODIFY)) (void)rcv_init(sp); F_SET(ep, F_MODIFIED); /* Log after change. */ log_line(sp, lno, LOG_LINE_RESET_F); /* Update screen. */ return (scr_update(sp, lno, LINE_RESET, 1)); } /* * db_exist -- * Return if a line exists. * * PUBLIC: int db_exist(SCR *, recno_t); */ int -db_exist( - SCR *sp, - recno_t lno) +db_exist(SCR *sp, recno_t lno) { EXF *ep; /* Check for no underlying file. */ if ((ep = sp->ep) == NULL) { ex_emsg(sp, NULL, EXM_NOFILEYET); return (1); } if (lno == OOBLNO) return (0); /* * Check the last-line number cache. Adjust the cached line * number for the lines used by the text input buffers. */ if (ep->c_nlines != OOBLNO) return (lno <= (F_ISSET(sp, SC_TINPUT) ? ep->c_nlines + (((TEXT *)TAILQ_LAST(sp->tiq, _texth))->lno - ((TEXT *)TAILQ_FIRST(sp->tiq))->lno) : ep->c_nlines)); /* Go get the line. */ return (!db_get(sp, lno, 0, NULL, NULL)); } /* * db_last -- * Return the number of lines in the file. * * PUBLIC: int db_last(SCR *, recno_t *); */ int -db_last( - SCR *sp, - recno_t *lnop) +db_last(SCR *sp, recno_t *lnop) { DBT data, key; EXF *ep; recno_t lno; CHAR_T *wp; size_t wlen; /* Check for no underlying file. */ if ((ep = sp->ep) == NULL) { ex_emsg(sp, NULL, EXM_NOFILEYET); return (1); } /* * Check the last-line number cache. Adjust the cached line * number for the lines used by the text input buffers. */ if (ep->c_nlines != OOBLNO) { *lnop = ep->c_nlines; if (F_ISSET(sp, SC_TINPUT)) *lnop += ((TEXT *)TAILQ_LAST(sp->tiq, _texth))->lno - ((TEXT *)TAILQ_FIRST(sp->tiq))->lno; return (0); } key.data = &lno; key.size = sizeof(lno); switch (ep->db->seq(ep->db, &key, &data, R_LAST)) { case -1: alloc_err: msgq(sp, M_SYSERR, "007|unable to get last line"); *lnop = 0; return (1); case 1: *lnop = 0; return (0); } memcpy(&lno, key.data, sizeof(lno)); if (lno != ep->c_lno) { FILE2INT(sp, data.data, data.size, wp, wlen); /* Fill the cache. */ if (wp != data.data) { BINC_GOTOW(sp, ep->c_lp, ep->c_blen, wlen); MEMCPY(ep->c_lp, wp, wlen); } else ep->c_lp = data.data; ep->c_lno = lno; ep->c_len = wlen; } ep->c_nlines = lno; /* Return the value. */ *lnop = (F_ISSET(sp, SC_TINPUT) && ((TEXT *)TAILQ_LAST(sp->tiq, _texth))->lno > lno ? ((TEXT *)TAILQ_LAST(sp->tiq, _texth))->lno : lno); return (0); } /* * db_rget -- * Retrieve a raw line from the database. * * PUBLIC: int db_rget(SCR *, recno_t, char **, size_t *); */ int -db_rget( - SCR *sp, +db_rget(SCR *sp, recno_t lno, /* Line number. */ char **pp, /* Pointer store. */ size_t *lenp) /* Length store. */ { DBT data, key; EXF *ep = sp->ep; int rval; /* Get the line from the underlying database. */ key.data = &lno; key.size = sizeof(lno); if ((rval = ep->db->get(ep->db, &key, &data, 0)) == 0) { *lenp = data.size; *pp = data.data; } return (rval); } /* * db_rset -- * Store a raw line into the database. * * PUBLIC: int db_rset(SCR *, recno_t, char *, size_t); */ int -db_rset( - SCR *sp, - recno_t lno, - char *p, - size_t len) +db_rset(SCR *sp, recno_t lno, char *p, size_t len) { DBT data, key; EXF *ep = sp->ep; /* Update file. */ key.data = &lno; key.size = sizeof(lno); data.data = p; data.size = len; return ep->db->put(ep->db, &key, &data, 0); } /* * db_err -- * Report a line error. * * PUBLIC: void db_err(SCR *, recno_t); */ void -db_err( - SCR *sp, - recno_t lno) +db_err(SCR *sp, recno_t lno) { msgq(sp, M_ERR, "008|Error: unable to retrieve line %lu", (u_long)lno); } /* * scr_update -- * Update all of the screens that are backed by the file that * just changed. */ static int -scr_update( - SCR *sp, - recno_t lno, - lnop_t op, - int current) +scr_update(SCR *sp, recno_t lno, lnop_t op, int current) { EXF *ep; SCR *tsp; if (F_ISSET(sp, SC_EX)) return (0); ep = sp->ep; if (ep->refcnt != 1) TAILQ_FOREACH(tsp, sp->gp->dq, q) if (sp != tsp && tsp->ep == ep) if (vs_change(tsp, lno, op)) return (1); return (current ? vs_change(sp, lno, op) : 0); } Index: head/contrib/nvi/common/log.c =================================================================== --- head/contrib/nvi/common/log.c (revision 365498) +++ head/contrib/nvi/common/log.c (revision 365499) @@ -1,767 +1,736 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: log.c,v 10.27 2011/07/13 06:25:50 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include +#include #include #include #include #include #include #include "common.h" /* * The log consists of records, each containing a type byte and a variable * length byte string, as follows: * * LOG_CURSOR_INIT MARK * LOG_CURSOR_END MARK * LOG_LINE_APPEND recno_t char * * LOG_LINE_DELETE recno_t char * * LOG_LINE_INSERT recno_t char * * LOG_LINE_RESET_F recno_t char * * LOG_LINE_RESET_B recno_t char * * LOG_MARK LMARK * * We do before image physical logging. This means that the editor layer * MAY NOT modify records in place, even if simply deleting or overwriting * characters. Since the smallest unit of logging is a line, we're using * up lots of space. This may eventually have to be reduced, probably by * doing logical logging, which is a much cooler database phrase. * * The implementation of the historic vi 'u' command, using roll-forward and * roll-back, is simple. Each set of changes has a LOG_CURSOR_INIT record, * followed by a number of other records, followed by a LOG_CURSOR_END record. * LOG_LINE_RESET records come in pairs. The first is a LOG_LINE_RESET_B * record, and is the line before the change. The second is LOG_LINE_RESET_F, * and is the line after the change. Roll-back is done by backing up to the * first LOG_CURSOR_INIT record before a change. Roll-forward is done in a * similar fashion. * * The 'U' command is implemented by rolling backward to a LOG_CURSOR_END * record for a line different from the current one. It should be noted that * this means that a subsequent 'u' command will make a change based on the * new position of the log's cursor. This is okay, and, in fact, historic vi * behaved that way. */ static int log_cursor1(SCR *, int); static void log_err(SCR *, char *, int); #if defined(DEBUG) && 0 static void log_trace(SCR *, char *, recno_t, u_char *); #endif static int apply_with(int (*)(SCR *, recno_t, CHAR_T *, size_t), SCR *, recno_t, u_char *, size_t); /* Try and restart the log on failure, i.e. if we run out of memory. */ #define LOG_ERR { \ log_err(sp, __FILE__, __LINE__); \ return (1); \ } /* offset of CHAR_T string in log needs to be aligned on some systems * because it is passed to db_set as a string */ typedef struct { char data[sizeof(u_char) /* type */ + sizeof(recno_t)]; CHAR_T str[1]; } log_t; #define CHAR_T_OFFSET ((char *)(((log_t*)0)->str) - (char *)0) /* * log_init -- * Initialize the logging subsystem. * * PUBLIC: int log_init(SCR *, EXF *); */ int -log_init( - SCR *sp, - EXF *ep) +log_init(SCR *sp, EXF *ep) { /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. * * Initialize the buffer. The logging subsystem has its own * buffers because the global ones are almost by definition * going to be in use when the log runs. */ ep->l_lp = NULL; ep->l_len = 0; ep->l_cursor.lno = 1; /* XXX Any valid recno. */ ep->l_cursor.cno = 0; ep->l_high = ep->l_cur = 1; ep->log = dbopen(NULL, O_CREAT | O_NONBLOCK | O_RDWR, S_IRUSR | S_IWUSR, DB_RECNO, NULL); if (ep->log == NULL) { msgq(sp, M_SYSERR, "009|Log file"); F_SET(ep, F_NOLOG); return (1); } return (0); } /* * log_end -- * Close the logging subsystem. * * PUBLIC: int log_end(SCR *, EXF *); */ int -log_end( - SCR *sp, - EXF *ep) +log_end(SCR *sp, EXF *ep) { /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. */ if (ep->log != NULL) { (void)(ep->log->close)(ep->log); ep->log = NULL; } - if (ep->l_lp != NULL) { - free(ep->l_lp); - ep->l_lp = NULL; - } + free(ep->l_lp); + ep->l_lp = NULL; ep->l_len = 0; ep->l_cursor.lno = 1; /* XXX Any valid recno. */ ep->l_cursor.cno = 0; ep->l_high = ep->l_cur = 1; return (0); } /* * log_cursor -- * Log the current cursor position, starting an event. * * PUBLIC: int log_cursor(SCR *); */ int log_cursor(SCR *sp) { EXF *ep; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) return (0); /* * If any changes were made since the last cursor init, * put out the ending cursor record. */ if (ep->l_cursor.lno == OOBLNO) { ep->l_cursor.lno = sp->lno; ep->l_cursor.cno = sp->cno; return (log_cursor1(sp, LOG_CURSOR_END)); } ep->l_cursor.lno = sp->lno; ep->l_cursor.cno = sp->cno; return (0); } /* * log_cursor1 -- * Actually push a cursor record out. */ static int -log_cursor1( - SCR *sp, - int type) +log_cursor1(SCR *sp, int type) { DBT data, key; EXF *ep; ep = sp->ep; BINC_RETC(sp, ep->l_lp, ep->l_len, sizeof(u_char) + sizeof(MARK)); ep->l_lp[0] = type; memmove(ep->l_lp + sizeof(u_char), &ep->l_cursor, sizeof(MARK)); key.data = &ep->l_cur; key.size = sizeof(recno_t); data.data = ep->l_lp; data.size = sizeof(u_char) + sizeof(MARK); if (ep->log->put(ep->log, &key, &data, 0) == -1) LOG_ERR; #if defined(DEBUG) && 0 TRACE(sp, "%lu: %s: %u/%u\n", ep->l_cur, type == LOG_CURSOR_INIT ? "log_cursor_init" : "log_cursor_end", sp->lno, sp->cno); #endif /* Reset high water mark. */ ep->l_high = ++ep->l_cur; return (0); } /* * log_line -- * Log a line change. * * PUBLIC: int log_line(SCR *, recno_t, u_int); */ int -log_line( - SCR *sp, - recno_t lno, - u_int action) +log_line(SCR *sp, recno_t lno, u_int action) { DBT data, key; EXF *ep; size_t len; CHAR_T *lp; recno_t lcur; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) return (0); /* * XXX * * Kluge for vi. Clear the EXF undo flag so that the * next 'u' command does a roll-back, regardless. */ F_CLR(ep, F_UNDO); /* Put out one initial cursor record per set of changes. */ if (ep->l_cursor.lno != OOBLNO) { if (log_cursor1(sp, LOG_CURSOR_INIT)) return (1); ep->l_cursor.lno = OOBLNO; } /* * Put out the changes. If it's a LOG_LINE_RESET_B call, it's a * special case, avoid the caches. Also, if it fails and it's * line 1, it just means that the user started with an empty file, * so fake an empty length line. */ if (action == LOG_LINE_RESET_B) { if (db_get(sp, lno, DBG_NOCACHE, &lp, &len)) { if (lno != 1) { db_err(sp, lno); return (1); } len = 0; lp = L(""); } } else if (db_get(sp, lno, DBG_FATAL, &lp, &len)) return (1); BINC_RETC(sp, ep->l_lp, ep->l_len, len * sizeof(CHAR_T) + CHAR_T_OFFSET); ep->l_lp[0] = action; memmove(ep->l_lp + sizeof(u_char), &lno, sizeof(recno_t)); memmove(ep->l_lp + CHAR_T_OFFSET, lp, len * sizeof(CHAR_T)); lcur = ep->l_cur; key.data = &lcur; key.size = sizeof(recno_t); data.data = ep->l_lp; data.size = len * sizeof(CHAR_T) + CHAR_T_OFFSET; if (ep->log->put(ep->log, &key, &data, 0) == -1) LOG_ERR; #if defined(DEBUG) && 0 switch (action) { case LOG_LINE_APPEND: TRACE(sp, "%lu: log_line: append: %lu {%u}\n", ep->l_cur, lno, len); break; case LOG_LINE_DELETE: TRACE(sp, "%lu: log_line: delete: %lu {%u}\n", ep->l_cur, lno, len); break; case LOG_LINE_INSERT: TRACE(sp, "%lu: log_line: insert: %lu {%u}\n", ep->l_cur, lno, len); break; case LOG_LINE_RESET_F: TRACE(sp, "%lu: log_line: reset_f: %lu {%u}\n", ep->l_cur, lno, len); break; case LOG_LINE_RESET_B: TRACE(sp, "%lu: log_line: reset_b: %lu {%u}\n", ep->l_cur, lno, len); break; } #endif /* Reset high water mark. */ ep->l_high = ++ep->l_cur; return (0); } /* * log_mark -- * Log a mark position. For the log to work, we assume that there * aren't any operations that just put out a log record -- this * would mean that undo operations would only reset marks, and not * cause any other change. * * PUBLIC: int log_mark(SCR *, LMARK *); */ int -log_mark( - SCR *sp, - LMARK *lmp) +log_mark(SCR *sp, LMARK *lmp) { DBT data, key; EXF *ep; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) return (0); /* Put out one initial cursor record per set of changes. */ if (ep->l_cursor.lno != OOBLNO) { if (log_cursor1(sp, LOG_CURSOR_INIT)) return (1); ep->l_cursor.lno = OOBLNO; } BINC_RETC(sp, ep->l_lp, ep->l_len, sizeof(u_char) + sizeof(LMARK)); ep->l_lp[0] = LOG_MARK; memmove(ep->l_lp + sizeof(u_char), lmp, sizeof(LMARK)); key.data = &ep->l_cur; key.size = sizeof(recno_t); data.data = ep->l_lp; data.size = sizeof(u_char) + sizeof(LMARK); if (ep->log->put(ep->log, &key, &data, 0) == -1) LOG_ERR; #if defined(DEBUG) && 0 TRACE(sp, "%lu: mark %c: %lu/%u\n", ep->l_cur, lmp->name, lmp->lno, lmp->cno); #endif /* Reset high water mark. */ ep->l_high = ++ep->l_cur; return (0); } /* * Log_backward -- * Roll the log backward one operation. * * PUBLIC: int log_backward(SCR *, MARK *); */ int -log_backward( - SCR *sp, - MARK *rp) +log_backward(SCR *sp, MARK *rp) { DBT key, data; EXF *ep; LMARK lm; MARK m; recno_t lno; int didop; u_char *p; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) { msgq(sp, M_ERR, "010|Logging not being performed, undo not possible"); return (1); } if (ep->l_cur == 1) { msgq(sp, M_BERR, "011|No changes to undo"); return (1); } F_SET(ep, F_NOLOG); /* Turn off logging. */ key.data = &ep->l_cur; /* Initialize db request. */ key.size = sizeof(recno_t); for (didop = 0;;) { --ep->l_cur; if (ep->log->get(ep->log, &key, &data, 0)) LOG_ERR; #if defined(DEBUG) && 0 log_trace(sp, "log_backward", ep->l_cur, data.data); #endif switch (*(p = (u_char *)data.data)) { case LOG_CURSOR_INIT: if (didop) { memmove(rp, p + sizeof(u_char), sizeof(MARK)); F_CLR(ep, F_NOLOG); return (0); } break; case LOG_CURSOR_END: break; case LOG_LINE_APPEND: case LOG_LINE_INSERT: didop = 1; memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); if (db_delete(sp, lno)) goto err; ++sp->rptlines[L_DELETED]; break; case LOG_LINE_DELETE: didop = 1; memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); if (apply_with(db_insert, sp, lno, p + CHAR_T_OFFSET, data.size - CHAR_T_OFFSET)) goto err; ++sp->rptlines[L_ADDED]; break; case LOG_LINE_RESET_F: break; case LOG_LINE_RESET_B: didop = 1; memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); if (apply_with(db_set, sp, lno, p + CHAR_T_OFFSET, data.size - CHAR_T_OFFSET)) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } break; case LOG_MARK: didop = 1; memmove(&lm, p + sizeof(u_char), sizeof(LMARK)); m.lno = lm.lno; m.cno = lm.cno; if (mark_set(sp, lm.name, &m, 0)) goto err; break; default: abort(); } } err: F_CLR(ep, F_NOLOG); return (1); } /* * Log_setline -- * Reset the line to its original appearance. * * XXX * There's a bug in this code due to our not logging cursor movements * unless a change was made. If you do a change, move off the line, * then move back on and do a 'U', the line will be restored to the way * it was before the original change. * * PUBLIC: int log_setline(SCR *); */ int log_setline(SCR *sp) { DBT key, data; EXF *ep; LMARK lm; MARK m; recno_t lno; u_char *p; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) { msgq(sp, M_ERR, "012|Logging not being performed, undo not possible"); return (1); } if (ep->l_cur == 1) return (1); F_SET(ep, F_NOLOG); /* Turn off logging. */ key.data = &ep->l_cur; /* Initialize db request. */ key.size = sizeof(recno_t); for (;;) { --ep->l_cur; if (ep->log->get(ep->log, &key, &data, 0)) LOG_ERR; #if defined(DEBUG) && 0 log_trace(sp, "log_setline", ep->l_cur, data.data); #endif switch (*(p = (u_char *)data.data)) { case LOG_CURSOR_INIT: memmove(&m, p + sizeof(u_char), sizeof(MARK)); if (m.lno != sp->lno || ep->l_cur == 1) { F_CLR(ep, F_NOLOG); return (0); } break; case LOG_CURSOR_END: memmove(&m, p + sizeof(u_char), sizeof(MARK)); if (m.lno != sp->lno) { ++ep->l_cur; F_CLR(ep, F_NOLOG); return (0); } break; case LOG_LINE_APPEND: case LOG_LINE_INSERT: case LOG_LINE_DELETE: case LOG_LINE_RESET_F: break; case LOG_LINE_RESET_B: memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); if (lno == sp->lno && apply_with(db_set, sp, lno, p + CHAR_T_OFFSET, data.size - CHAR_T_OFFSET)) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } case LOG_MARK: memmove(&lm, p + sizeof(u_char), sizeof(LMARK)); m.lno = lm.lno; m.cno = lm.cno; if (mark_set(sp, lm.name, &m, 0)) goto err; break; default: abort(); } } err: F_CLR(ep, F_NOLOG); return (1); } /* * Log_forward -- * Roll the log forward one operation. * * PUBLIC: int log_forward(SCR *, MARK *); */ int -log_forward( - SCR *sp, - MARK *rp) +log_forward(SCR *sp, MARK *rp) { DBT key, data; EXF *ep; LMARK lm; MARK m; recno_t lno; int didop; u_char *p; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) { msgq(sp, M_ERR, "013|Logging not being performed, roll-forward not possible"); return (1); } if (ep->l_cur == ep->l_high) { msgq(sp, M_BERR, "014|No changes to re-do"); return (1); } F_SET(ep, F_NOLOG); /* Turn off logging. */ key.data = &ep->l_cur; /* Initialize db request. */ key.size = sizeof(recno_t); for (didop = 0;;) { ++ep->l_cur; if (ep->log->get(ep->log, &key, &data, 0)) LOG_ERR; #if defined(DEBUG) && 0 log_trace(sp, "log_forward", ep->l_cur, data.data); #endif switch (*(p = (u_char *)data.data)) { case LOG_CURSOR_END: if (didop) { ++ep->l_cur; memmove(rp, p + sizeof(u_char), sizeof(MARK)); F_CLR(ep, F_NOLOG); return (0); } break; case LOG_CURSOR_INIT: break; case LOG_LINE_APPEND: case LOG_LINE_INSERT: didop = 1; memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); if (apply_with(db_insert, sp, lno, p + CHAR_T_OFFSET, data.size - CHAR_T_OFFSET)) goto err; ++sp->rptlines[L_ADDED]; break; case LOG_LINE_DELETE: didop = 1; memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); if (db_delete(sp, lno)) goto err; ++sp->rptlines[L_DELETED]; break; case LOG_LINE_RESET_B: break; case LOG_LINE_RESET_F: didop = 1; memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); if (apply_with(db_set, sp, lno, p + CHAR_T_OFFSET, data.size - CHAR_T_OFFSET)) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } break; case LOG_MARK: didop = 1; memmove(&lm, p + sizeof(u_char), sizeof(LMARK)); m.lno = lm.lno; m.cno = lm.cno; if (mark_set(sp, lm.name, &m, 0)) goto err; break; default: abort(); } } err: F_CLR(ep, F_NOLOG); return (1); } /* * log_err -- * Try and restart the log on failure, i.e. if we run out of memory. */ static void -log_err( - SCR *sp, - char *file, - int line) +log_err(SCR *sp, char *file, int line) { EXF *ep; - msgq(sp, M_SYSERR, "015|%s/%d: log put error", tail(file), line); + msgq(sp, M_SYSERR, "015|%s/%d: log put error", basename(file), line); ep = sp->ep; (void)ep->log->close(ep->log); if (!log_init(sp, ep)) msgq(sp, M_ERR, "267|Log restarted"); } #if defined(DEBUG) && 0 static void -log_trace( - SCR *sp, - char *msg, - recno_t rno, - u_char *p) +log_trace(SCR *sp, char *msg, recno_t rno, u_char *p) { LMARK lm; MARK m; recno_t lno; switch (*p) { case LOG_CURSOR_INIT: memmove(&m, p + sizeof(u_char), sizeof(MARK)); TRACE(sp, "%lu: %s: C_INIT: %u/%u\n", rno, msg, m.lno, m.cno); break; case LOG_CURSOR_END: memmove(&m, p + sizeof(u_char), sizeof(MARK)); TRACE(sp, "%lu: %s: C_END: %u/%u\n", rno, msg, m.lno, m.cno); break; case LOG_LINE_APPEND: memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); TRACE(sp, "%lu: %s: APPEND: %lu\n", rno, msg, lno); break; case LOG_LINE_INSERT: memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); TRACE(sp, "%lu: %s: INSERT: %lu\n", rno, msg, lno); break; case LOG_LINE_DELETE: memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); TRACE(sp, "%lu: %s: DELETE: %lu\n", rno, msg, lno); break; case LOG_LINE_RESET_F: memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); TRACE(sp, "%lu: %s: RESET_F: %lu\n", rno, msg, lno); break; case LOG_LINE_RESET_B: memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); TRACE(sp, "%lu: %s: RESET_B: %lu\n", rno, msg, lno); break; case LOG_MARK: memmove(&lm, p + sizeof(u_char), sizeof(LMARK)); TRACE(sp, "%lu: %s: MARK: %u/%u\n", rno, msg, lm.lno, lm.cno); break; default: abort(); } } #endif /* * apply_with -- * Apply a realigned line from the log db to the file db. */ static int -apply_with( - int (*db_func)(SCR *, recno_t, CHAR_T *, size_t), - SCR *sp, - recno_t lno, - u_char *p, - size_t len) +apply_with(int (*db_func)(SCR *, recno_t, CHAR_T *, size_t), SCR *sp, + recno_t lno, u_char *p, size_t len) { #ifdef USE_WIDECHAR typedef unsigned long nword; static size_t blen; static nword *bp; nword *lp = (nword *)((uintptr_t)p / sizeof(nword) * sizeof(nword)); if (lp != (nword *)p) { int offl = ((uintptr_t)p - (uintptr_t)lp) << 3; int offr = (sizeof(nword) << 3) - offl; size_t i, cnt = (len + sizeof(nword) / 2) / sizeof(nword); if (len > blen) { blen = p2roundup(MAX(len, 512)); REALLOC(sp, bp, nword *, blen); if (bp == NULL) return (1); } for (i = 0; i < cnt; ++i) #if BYTE_ORDER == BIG_ENDIAN bp[i] = (lp[i] << offl) ^ (lp[i+1] >> offr); #else bp[i] = (lp[i] >> offl) ^ (lp[i+1] << offr); #endif p = (u_char *)bp; } #endif return db_func(sp, lno, (CHAR_T *)p, len / sizeof(CHAR_T)); } Index: head/contrib/nvi/common/log.h =================================================================== --- head/contrib/nvi/common/log.h (revision 365498) +++ head/contrib/nvi/common/log.h (revision 365499) @@ -1,20 +1,18 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * @(#)log.h 10.2 (Berkeley) 3/6/96 */ #define LOG_NOTYPE 0 #define LOG_CURSOR_INIT 1 #define LOG_CURSOR_END 2 #define LOG_LINE_APPEND 3 #define LOG_LINE_DELETE 4 #define LOG_LINE_INSERT 5 #define LOG_LINE_RESET_F 6 #define LOG_LINE_RESET_B 7 #define LOG_MARK 8 Index: head/contrib/nvi/common/main.c =================================================================== --- head/contrib/nvi/common/main.c (revision 365498) +++ head/contrib/nvi/common/main.c (revision 365499) @@ -1,607 +1,575 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: main.c,v 11.0 2012/10/17 06:34:37 zy Exp $"; -#endif /* not lint */ - #include #include #include #include +#include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" #include "pathnames.h" static void attach(GS *); -static void v_estr(char *, int, char *); -static int v_obsolete(char *, char *[]); +static int v_obsolete(char *[]); /* * editor -- * Main editor routine. * * PUBLIC: int editor(GS *, int, char *[]); */ int -editor( - GS *gp, - int argc, - char *argv[]) +editor(GS *gp, int argc, char *argv[]) { extern int optind; extern char *optarg; const char *p; EVENT ev; FREF *frp; SCR *sp; size_t len; u_int flags; int ch, flagchk, lflag, secure, startup, readonly, rval, silent; char *tag_f, *wsizearg, path[256]; CHAR_T *w; size_t wlen; /* Initialize the busy routine, if not defined by the screen. */ if (gp->scr_busy == NULL) gp->scr_busy = vs_busy; /* Initialize the message routine, if not defined by the screen. */ if (gp->scr_msg == NULL) gp->scr_msg = vs_msg; gp->catd = (nl_catd)-1; /* Common global structure initialization. */ TAILQ_INIT(gp->dq); TAILQ_INIT(gp->hq); SLIST_INIT(gp->ecq); SLIST_INSERT_HEAD(gp->ecq, &gp->excmd, q); gp->noprint = DEFAULT_NOPRINT; /* Structures shared by screens so stored in the GS structure. */ TAILQ_INIT(gp->frefq); TAILQ_INIT(gp->dcb_store.textq); SLIST_INIT(gp->cutq); SLIST_INIT(gp->seqq); /* Set initial screen type and mode based on the program name. */ readonly = 0; - if (!strcmp(gp->progname, "ex") || !strcmp(gp->progname, "nex")) + if (!strcmp(getprogname(), "ex") || !strcmp(getprogname(), "nex")) LF_INIT(SC_EX); else { /* Nview, view are readonly. */ - if (!strcmp(gp->progname, "nview") || - !strcmp(gp->progname, "view")) + if (!strcmp(getprogname(), "nview") || + !strcmp(getprogname(), "view")) readonly = 1; /* Vi is the default. */ LF_INIT(SC_VI); } /* Convert old-style arguments into new-style ones. */ - if (v_obsolete(gp->progname, argv)) + if (v_obsolete(argv)) return (1); /* Parse the arguments. */ flagchk = '\0'; tag_f = wsizearg = NULL; lflag = secure = silent = 0; startup = 1; /* Set the file snapshot flag. */ F_SET(gp, G_SNAPSHOT); #ifdef DEBUG while ((ch = getopt(argc, argv, "c:D:eFlRrSsT:t:vw:")) != EOF) #else while ((ch = getopt(argc, argv, "c:eFlRrSst:vw:")) != EOF) #endif switch (ch) { case 'c': /* Run the command. */ /* * XXX * We should support multiple -c options. */ if (gp->c_option != NULL) { - v_estr(gp->progname, 0, - "only one -c command may be specified."); + warnx("only one -c command may be specified."); return (1); } gp->c_option = optarg; break; #ifdef DEBUG case 'D': switch (optarg[0]) { case 's': startup = 0; break; case 'w': attach(gp); break; default: - v_estr(gp->progname, 0, - "usage: -D requires s or w argument."); + warnx("usage: -D requires s or w argument."); return (1); } break; #endif case 'e': /* Ex mode. */ LF_CLR(SC_VI); LF_SET(SC_EX); break; case 'F': /* No snapshot. */ F_CLR(gp, G_SNAPSHOT); break; case 'l': /* Set lisp, showmatch options. */ lflag = 1; break; case 'R': /* Readonly. */ readonly = 1; break; case 'r': /* Recover. */ if (flagchk == 't') { - v_estr(gp->progname, 0, - "only one of -r and -t may be specified."); + warnx("only one of -r and -t may be specified."); return (1); } flagchk = 'r'; break; case 'S': secure = 1; break; case 's': silent = 1; break; #ifdef DEBUG case 'T': /* Trace. */ if ((gp->tracefp = fopen(optarg, "w")) == NULL) { - v_estr(gp->progname, errno, optarg); + warn("%s", optarg); goto err; } (void)fprintf(gp->tracefp, "\n===\ntrace: open %s\n", optarg); break; #endif case 't': /* Tag. */ if (flagchk == 'r') { - v_estr(gp->progname, 0, - "only one of -r and -t may be specified."); + warnx("only one of -r and -t may be specified."); return (1); } if (flagchk == 't') { - v_estr(gp->progname, 0, - "only one tag file may be specified."); + warnx("only one tag file may be specified."); return (1); } flagchk = 't'; tag_f = optarg; break; case 'v': /* Vi mode. */ LF_CLR(SC_EX); LF_SET(SC_VI); break; case 'w': wsizearg = optarg; break; case '?': default: (void)gp->scr_usage(); return (1); } argc -= optind; argv += optind; /* * -s option is only meaningful to ex. * * If not reading from a terminal, it's like -s was specified. */ if (silent && !LF_ISSET(SC_EX)) { - v_estr(gp->progname, 0, "-s option is only applicable to ex."); + warnx("-s option is only applicable to ex."); goto err; } if (LF_ISSET(SC_EX) && F_ISSET(gp, G_SCRIPTED)) silent = 1; /* * Build and initialize the first/current screen. This is a bit * tricky. If an error is returned, we may or may not have a * screen structure. If we have a screen structure, put it on a * display queue so that the error messages get displayed. * * !!! * Everything we do until we go interactive is done in ex mode. */ if (screen_init(gp, NULL, &sp)) { if (sp != NULL) TAILQ_INSERT_HEAD(gp->dq, sp, q); goto err; } F_SET(sp, SC_EX); TAILQ_INSERT_HEAD(gp->dq, sp, q); if (v_key_init(sp)) /* Special key initialization. */ goto err; { int oargs[5], *oargp = oargs; if (lflag) { /* Command-line options. */ *oargp++ = O_LISP; *oargp++ = O_SHOWMATCH; } if (readonly) *oargp++ = O_READONLY; if (secure) *oargp++ = O_SECURE; *oargp = -1; /* Options initialization. */ if (opts_init(sp, oargs)) goto err; } if (wsizearg != NULL) { ARGS *av[2], a, b; (void)snprintf(path, sizeof(path), "window=%s", wsizearg); a.bp = (CHAR_T *)path; a.len = strlen(path); b.bp = NULL; b.len = 0; av[0] = &a; av[1] = &b; (void)opts_set(sp, av, NULL); } if (silent) { /* Ex batch mode option values. */ O_CLR(sp, O_AUTOPRINT); O_CLR(sp, O_PROMPT); O_CLR(sp, O_VERBOSE); O_CLR(sp, O_WARN); F_SET(sp, SC_EX_SILENT); } sp->rows = O_VAL(sp, O_LINES); /* Make ex formatting work. */ sp->cols = O_VAL(sp, O_COLUMNS); if (!silent && startup) { /* Read EXINIT, exrc files. */ if (ex_exrc(sp)) goto err; if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (screen_end(sp)) goto err; goto done; } } /* * List recovery files if -r specified without file arguments. * Note, options must be initialized and startup information * read before doing this. */ if (flagchk == 'r' && argv[0] == NULL) { if (rcv_list(sp)) goto err; if (screen_end(sp)) goto err; goto done; } /* * !!! * Initialize the default ^D, ^U scrolling value here, after the * user has had every opportunity to set the window option. * * It's historic practice that changing the value of the window * option did not alter the default scrolling value, only giving * a count to ^D/^U did that. */ sp->defscroll = (O_VAL(sp, O_WINDOW) + 1) / 2; /* * If we don't have a command-line option, switch into the right * editor now, so that we position default files correctly, and * so that any tags file file-already-locked messages are in the * vi screen, not the ex screen. * * XXX * If we have a command-line option, the error message can end * up in the wrong place, but I think that the combination is * unlikely. */ if (gp->c_option == NULL) { F_CLR(sp, SC_EX | SC_VI); F_SET(sp, LF_ISSET(SC_EX | SC_VI)); } /* Open a tag file if specified. */ if (tag_f != NULL) { CHAR2INT(sp, tag_f, strlen(tag_f) + 1, w, wlen); if (ex_tag_first(sp, w)) goto err; } /* * Append any remaining arguments as file names. Files are recovery * files if -r specified. If the tag option or ex startup commands * loaded a file, then any file arguments are going to come after it. */ if (*argv != NULL) { if (sp->frp != NULL) { /* Cheat -- we know we have an extra argv slot. */ *--argv = strdup(sp->frp->name); if (*argv == NULL) { - v_estr(gp->progname, errno, NULL); + warn(NULL); goto err; } } sp->argv = sp->cargv = argv; F_SET(sp, SC_ARGNOFREE); if (flagchk == 'r') F_SET(sp, SC_ARGRECOVER); } /* * If the ex startup commands and or/the tag option haven't already * created a file, create one. If no command-line files were given, * use a temporary file. */ if (sp->frp == NULL) { if (sp->argv == NULL) { if ((frp = file_add(sp, NULL)) == NULL) goto err; } else { if ((frp = file_add(sp, sp->argv[0])) == NULL) goto err; if (F_ISSET(sp, SC_ARGRECOVER)) F_SET(frp, FR_RECOVER); } if (file_init(sp, frp, NULL, 0)) goto err; if (EXCMD_RUNNING(gp)) { (void)ex_cmd(sp); if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (screen_end(sp)) goto err; goto done; } } } /* * Check to see if we need to wait for ex. If SC_SCR_EX is set, ex * was forced to initialize the screen during startup. We'd like to * wait for a single character from the user, but we can't because * we're not in raw mode. We can't switch to raw mode because the * vi initialization will switch to xterm's alternate screen, causing * us to lose the messages we're pausing to make sure the user read. * So, wait for a complete line. */ if (F_ISSET(sp, SC_SCR_EX)) { p = msg_cmsg(sp, CMSG_CONT_R, &len); (void)write(STDOUT_FILENO, p, len); for (;;) { if (v_event_get(sp, &ev, 0, 0)) goto err; if (ev.e_event == E_INTERRUPT || (ev.e_event == E_CHARACTER && (ev.e_value == K_CR || ev.e_value == K_NL))) break; (void)gp->scr_bell(sp); } } /* Switch into the right editor, regardless. */ F_CLR(sp, SC_EX | SC_VI); F_SET(sp, LF_ISSET(SC_EX | SC_VI) | SC_STATUS_CNT); /* * Main edit loop. Vi handles split screens itself, we only return * here when switching editor modes or restarting the screen. */ while (sp != NULL) if (F_ISSET(sp, SC_EX) ? ex(&sp) : vi(&sp)) goto err; done: rval = 0; if (0) err: rval = 1; /* Clean out the global structure. */ v_end(gp); return (rval); } /* * v_end -- * End the program, discarding screens and most of the global area. * * PUBLIC: void v_end(GS *); */ void v_end(gp) GS *gp; { MSGS *mp; SCR *sp; /* If there are any remaining screens, kill them off. */ if (gp->ccl_sp != NULL) { (void)file_end(gp->ccl_sp, NULL, 1); (void)screen_end(gp->ccl_sp); } while ((sp = TAILQ_FIRST(gp->dq)) != NULL) (void)screen_end(sp); while ((sp = TAILQ_FIRST(gp->hq)) != NULL) (void)screen_end(sp); #if defined(DEBUG) || defined(PURIFY) { FREF *frp; /* Free FREF's. */ while ((frp = TAILQ_FIRST(gp->frefq)) != NULL) { TAILQ_REMOVE(gp->frefq, frp, q); - if (frp->name != NULL) - free(frp->name); - if (frp->tname != NULL) - free(frp->tname); + free(frp->name); + free(frp->tname); free(frp); } } /* Free key input queue. */ - if (gp->i_event != NULL) - free(gp->i_event); + free(gp->i_event); /* Free cut buffers. */ cut_close(gp); /* Free map sequences. */ seq_close(gp); /* Free default buffer storage. */ (void)text_lfree(gp->dcb_store.textq); /* Close message catalogs. */ msg_close(gp); #endif /* Ring the bell if scheduled. */ if (F_ISSET(gp, G_BELLSCHED)) (void)fprintf(stderr, "\07"); /* \a */ /* * Flush any remaining messages. If a message is here, it's almost * certainly the message about the event that killed us (although * it's possible that the user is sourcing a file that exits from the * editor). */ while ((mp = SLIST_FIRST(gp->msgq)) != NULL) { (void)fprintf(stderr, "%s%.*s", mp->mtype == M_ERR ? "ex/vi: " : "", (int)mp->len, mp->buf); SLIST_REMOVE_HEAD(gp->msgq, q); #if defined(DEBUG) || defined(PURIFY) free(mp->buf); free(mp); #endif } #if defined(DEBUG) || defined(PURIFY) /* Free any temporary space. */ - if (gp->tmp_bp != NULL) - free(gp->tmp_bp); + free(gp->tmp_bp); #if defined(DEBUG) /* Close debugging file descriptor. */ if (gp->tracefp != NULL) (void)fclose(gp->tracefp); #endif #endif } /* * v_obsolete -- * Convert historic arguments into something getopt(3) will like. */ static int -v_obsolete( - char *name, - char *argv[]) +v_obsolete(char *argv[]) { size_t len; char *p; /* * Translate old style arguments into something getopt will like. * Make sure it's not text space memory, because ex modifies the * strings. * Change "+" into "-c$". * Change "+" into "-c". * Change "-" into "-s" * The c, T, t and w options take arguments so they can't be * special arguments. * * Stop if we find "--" as an argument, the user may want to edit * a file named "+foo". */ while (*++argv && strcmp(argv[0], "--")) if (argv[0][0] == '+') { if (argv[0][1] == '\0') { argv[0] = strdup("-c$"); if (argv[0] == NULL) goto nomem; } else { p = argv[0]; len = strlen(argv[0]); argv[0] = malloc(len + 2); if (argv[0] == NULL) goto nomem; argv[0][0] = '-'; argv[0][1] = 'c'; (void)strlcpy(argv[0] + 2, p + 1, len); } } else if (argv[0][0] == '-') if (argv[0][1] == '\0') { argv[0] = strdup("-s"); if (argv[0] == NULL) { -nomem: v_estr(name, errno, NULL); +nomem: warn(NULL); return (1); } } else if ((argv[0][1] == 'c' || argv[0][1] == 'T' || argv[0][1] == 't' || argv[0][1] == 'w') && argv[0][2] == '\0') ++argv; return (0); } #ifdef DEBUG static void attach(GS *gp) { int fd; char ch; if ((fd = open(_PATH_TTY, O_RDONLY, 0)) < 0) { - v_estr(gp->progname, errno, _PATH_TTY); + warn("%s", _PATH_TTY); return; } (void)printf("process %lu waiting, enter to continue: ", (u_long)getpid()); (void)fflush(stdout); do { if (read(fd, &ch, 1) != 1) { (void)close(fd); return; } } while (ch != '\n' && ch != '\r'); (void)close(fd); } #endif - -static void -v_estr( - char *name, - int eno, - char *msg) -{ - (void)fprintf(stderr, "%s", name); - if (msg != NULL) - (void)fprintf(stderr, ": %s", msg); - if (eno) - (void)fprintf(stderr, ": %s", strerror(errno)); - (void)fprintf(stderr, "\n"); -} Index: head/contrib/nvi/common/mark.c =================================================================== --- head/contrib/nvi/common/mark.c (revision 365498) +++ head/contrib/nvi/common/mark.c (revision 365499) @@ -1,277 +1,256 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: mark.c,v 10.14 2011/07/04 14:42:58 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "common.h" static LMARK *mark_find(SCR *, ARG_CHAR_T); /* * Marks are maintained in a key sorted singly linked list. We can't * use arrays because we have no idea how big an index key could be. * The underlying assumption is that users don't have more than, say, * 10 marks at any one time, so this will be is fast enough. * * Marks are fixed, and modifications to the line don't update the mark's * position in the line. This can be hard. If you add text to the line, * place a mark in that text, undo the addition and use ` to move to the * mark, the location will have disappeared. It's tempting to try to adjust * the mark with the changes in the line, but this is hard to do, especially * if we've given the line to v_ntext.c:v_ntext() for editing. Historic vi * would move to the first non-blank on the line when the mark location was * past the end of the line. This can be complicated by deleting to a mark * that has disappeared using the ` command. Historic vi treated this as * a line-mode motion and deleted the line. This implementation complains to * the user. * * In historic vi, marks returned if the operation was undone, unless the * mark had been subsequently reset. Tricky. This is hard to start with, * but in the presence of repeated undo it gets nasty. When a line is * deleted, we delete (and log) any marks on that line. An undo will create * the mark. Any mark creations are noted as to whether the user created * it or if it was created by an undo. The former cannot be reset by another * undo, but the latter may. * * All of these routines translate ABSMARK2 to ABSMARK1. Setting either of * the absolute mark locations sets both, so that "m'" and "m`" work like * they, ah, for lack of a better word, "should". */ /* * mark_init -- * Set up the marks. * * PUBLIC: int mark_init(SCR *, EXF *); */ int -mark_init( - SCR *sp, - EXF *ep) +mark_init(SCR *sp, EXF *ep) { /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. * * Set up the marks. */ SLIST_INIT(ep->marks); return (0); } /* * mark_end -- * Free up the marks. * * PUBLIC: int mark_end(SCR *, EXF *); */ int -mark_end( - SCR *sp, - EXF *ep) +mark_end(SCR *sp, EXF *ep) { LMARK *lmp; /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. */ while ((lmp = SLIST_FIRST(ep->marks)) != NULL) { SLIST_REMOVE_HEAD(ep->marks, q); free(lmp); } return (0); } /* * mark_get -- * Get the location referenced by a mark. * * PUBLIC: int mark_get(SCR *, ARG_CHAR_T, MARK *, mtype_t); */ int -mark_get( - SCR *sp, - ARG_CHAR_T key, - MARK *mp, - mtype_t mtype) +mark_get(SCR *sp, ARG_CHAR_T key, MARK *mp, mtype_t mtype) { LMARK *lmp; if (key == ABSMARK2) key = ABSMARK1; lmp = mark_find(sp, key); if (lmp == NULL || lmp->name != key) { msgq(sp, mtype, "017|Mark %s: not set", KEY_NAME(sp, key)); return (1); } if (F_ISSET(lmp, MARK_DELETED)) { msgq(sp, mtype, "018|Mark %s: the line was deleted", KEY_NAME(sp, key)); return (1); } /* * !!! * The absolute mark is initialized to lno 1/cno 0, and historically * you could use it in an empty file. Make such a mark always work. */ if ((lmp->lno != 1 || lmp->cno != 0) && !db_exist(sp, lmp->lno)) { msgq(sp, mtype, "019|Mark %s: cursor position no longer exists", KEY_NAME(sp, key)); return (1); } mp->lno = lmp->lno; mp->cno = lmp->cno; return (0); } /* * mark_set -- * Set the location referenced by a mark. * * PUBLIC: int mark_set(SCR *, ARG_CHAR_T, MARK *, int); */ int -mark_set( - SCR *sp, - ARG_CHAR_T key, - MARK *value, - int userset) +mark_set(SCR *sp, ARG_CHAR_T key, MARK *value, int userset) { LMARK *lmp, *lmt; if (key == ABSMARK2) key = ABSMARK1; /* * The rules are simple. If the user is setting a mark (if it's a * new mark this is always true), it always happens. If not, it's * an undo, and we set it if it's not already set or if it was set * by a previous undo. */ lmp = mark_find(sp, key); if (lmp == NULL || lmp->name != key) { - MALLOC_RET(sp, lmt, LMARK *, sizeof(LMARK)); + MALLOC_RET(sp, lmt, sizeof(LMARK)); if (lmp == NULL) { SLIST_INSERT_HEAD(sp->ep->marks, lmt, q); } else SLIST_INSERT_AFTER(lmp, lmt, q); lmp = lmt; } else if (!userset && !F_ISSET(lmp, MARK_DELETED) && F_ISSET(lmp, MARK_USERSET)) return (0); lmp->lno = value->lno; lmp->cno = value->cno; lmp->name = key; lmp->flags = userset ? MARK_USERSET : 0; return (0); } /* * mark_find -- * Find the requested mark, or, the slot immediately before * where it would go. */ static LMARK * -mark_find( - SCR *sp, - ARG_CHAR_T key) +mark_find(SCR *sp, ARG_CHAR_T key) { LMARK *lmp, *lastlmp = NULL; /* * Return the requested mark or the slot immediately before * where it should go. */ SLIST_FOREACH(lmp, sp->ep->marks, q) { if (lmp->name >= key) return (lmp->name == key ? lmp : lastlmp); lastlmp = lmp; } return (lastlmp); } /* * mark_insdel -- * Update the marks based on an insertion or deletion. * * PUBLIC: int mark_insdel(SCR *, lnop_t, recno_t); */ int -mark_insdel( - SCR *sp, - lnop_t op, - recno_t lno) +mark_insdel(SCR *sp, lnop_t op, recno_t lno) { LMARK *lmp; recno_t lline; switch (op) { case LINE_APPEND: /* All insert/append operations are done as inserts. */ abort(); case LINE_DELETE: SLIST_FOREACH(lmp, sp->ep->marks, q) if (lmp->lno >= lno) if (lmp->lno == lno) { F_SET(lmp, MARK_DELETED); (void)log_mark(sp, lmp); } else --lmp->lno; break; case LINE_INSERT: /* * XXX * Very nasty special case. If the file was empty, then we're * adding the first line, which is a replacement. So, we don't * modify the marks. This is a hack to make: * * mz:r!echo foo'z * * work, i.e. historically you could mark the "line" in an empty * file and replace it, and continue to use the mark. Insane, * well, yes, I know, but someone complained. * * Check for line #2 before going to the end of the file. */ if (!db_exist(sp, 2)) { if (db_last(sp, &lline)) return (1); if (lline == 1) return (0); } SLIST_FOREACH(lmp, sp->ep->marks, q) if (lmp->lno >= lno) ++lmp->lno; break; case LINE_RESET: break; } return (0); } Index: head/contrib/nvi/common/mark.h =================================================================== --- head/contrib/nvi/common/mark.h (revision 365498) +++ head/contrib/nvi/common/mark.h (revision 365499) @@ -1,43 +1,41 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: mark.h,v 10.6 2011/07/04 14:41:51 zy Exp $ */ /* * The MARK and LMARK structures define positions in the file. There are * two structures because the mark subroutines are the only places where * anything cares about something other than line and column. * * Because of the different interfaces used by the db(3) package, curses, * and users, the line number is 1 based and the column number is 0 based. * Additionally, it is known that the out-of-band line number is less than * any legal line number. The line number is of type recno_t, as that's * the underlying type of the database. The column number is of type size_t, * guaranteeing that we can malloc a line. */ struct _mark { #define OOBLNO 0 /* Out-of-band line number. */ recno_t lno; /* Line number. */ size_t cno; /* Column number. */ }; struct _lmark { SLIST_ENTRY(_lmark) q; /* Linked list of marks. */ recno_t lno; /* Line number. */ size_t cno; /* Column number. */ /* XXXX Needed ? Can non ascii-chars be mark names ? */ CHAR_T name; /* Mark name. */ #define MARK_DELETED 0x01 /* Mark was deleted. */ #define MARK_USERSET 0x02 /* User set this mark. */ u_int8_t flags; }; #define ABSMARK1 '\'' /* Absolute mark name. */ #define ABSMARK2 '`' /* Absolute mark name. */ Index: head/contrib/nvi/common/mem.h =================================================================== --- head/contrib/nvi/common/mem.h (revision 365498) +++ head/contrib/nvi/common/mem.h (revision 365499) @@ -1,240 +1,217 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: mem.h,v 10.17 2012/10/07 00:40:29 zy Exp $ */ #ifdef DEBUG #define CHECK_TYPE(type, var) \ type L__lp __attribute__((unused)) = var; #else #define CHECK_TYPE(type, var) #endif /* Increase the size of a malloc'd buffer. Two versions, one that * returns, one that jumps to an error label. */ #define BINC_GOTO(sp, type, lp, llen, nlen) { \ CHECK_TYPE(type *, lp) \ void *L__bincp; \ if ((nlen) > llen) { \ if ((L__bincp = binc(sp, lp, &(llen), nlen)) == NULL) \ goto alloc_err; \ /* \ * !!! \ * Possible pointer conversion. \ */ \ lp = L__bincp; \ } \ } #define BINC_GOTOC(sp, lp, llen, nlen) \ BINC_GOTO(sp, char, lp, llen, nlen) #define BINC_GOTOW(sp, lp, llen, nlen) \ BINC_GOTO(sp, CHAR_T, lp, llen, (nlen) * sizeof(CHAR_T)) #define BINC_RET(sp, type, lp, llen, nlen) { \ CHECK_TYPE(type *, lp) \ void *L__bincp; \ if ((nlen) > llen) { \ if ((L__bincp = binc(sp, lp, &(llen), nlen)) == NULL) \ return (1); \ /* \ * !!! \ * Possible pointer conversion. \ */ \ lp = L__bincp; \ } \ } #define BINC_RETC(sp, lp, llen, nlen) \ BINC_RET(sp, char, lp, llen, nlen) #define BINC_RETW(sp, lp, llen, nlen) \ BINC_RET(sp, CHAR_T, lp, llen, (nlen) * sizeof(CHAR_T)) /* * Get some temporary space, preferably from the global temporary buffer, * from a malloc'd buffer otherwise. Two versions, one that returns, one * that jumps to an error label. */ #define GET_SPACE_GOTO(sp, type, bp, blen, nlen) { \ CHECK_TYPE(type *, bp) \ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \ if (L__gp == NULL || F_ISSET(L__gp, G_TMP_INUSE)) { \ bp = NULL; \ blen = 0; \ BINC_GOTO(sp, type, bp, blen, nlen); \ } else { \ BINC_GOTOC(sp, L__gp->tmp_bp, L__gp->tmp_blen, nlen); \ bp = (type *) L__gp->tmp_bp; \ blen = L__gp->tmp_blen; \ F_SET(L__gp, G_TMP_INUSE); \ } \ } #define GET_SPACE_GOTOC(sp, bp, blen, nlen) \ GET_SPACE_GOTO(sp, char, bp, blen, nlen) #define GET_SPACE_GOTOW(sp, bp, blen, nlen) \ GET_SPACE_GOTO(sp, CHAR_T, bp, blen, (nlen) * sizeof(CHAR_T)) #define GET_SPACE_RET(sp, type, bp, blen, nlen) { \ CHECK_TYPE(type *, bp) \ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \ if (L__gp == NULL || F_ISSET(L__gp, G_TMP_INUSE)) { \ bp = NULL; \ blen = 0; \ BINC_RET(sp, type, bp, blen, nlen); \ } else { \ BINC_RETC(sp, L__gp->tmp_bp, L__gp->tmp_blen, nlen); \ bp = (type *) L__gp->tmp_bp; \ blen = L__gp->tmp_blen; \ F_SET(L__gp, G_TMP_INUSE); \ } \ } #define GET_SPACE_RETC(sp, bp, blen, nlen) \ GET_SPACE_RET(sp, char, bp, blen, nlen) #define GET_SPACE_RETW(sp, bp, blen, nlen) \ GET_SPACE_RET(sp, CHAR_T, bp, blen, (nlen) * sizeof(CHAR_T)) /* * Add space to a GET_SPACE returned buffer. Two versions, one that * returns, one that jumps to an error label. */ #define ADD_SPACE_GOTO(sp, type, bp, blen, nlen) { \ CHECK_TYPE(type *, bp) \ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \ if (L__gp == NULL || bp == (type *)L__gp->tmp_bp) { \ F_CLR(L__gp, G_TMP_INUSE); \ BINC_GOTOC(sp, L__gp->tmp_bp, L__gp->tmp_blen, nlen); \ bp = (type *) L__gp->tmp_bp; \ blen = L__gp->tmp_blen; \ F_SET(L__gp, G_TMP_INUSE); \ } else \ BINC_GOTO(sp, type, bp, blen, nlen); \ } #define ADD_SPACE_GOTOC(sp, bp, blen, nlen) \ ADD_SPACE_GOTO(sp, char, bp, blen, nlen) #define ADD_SPACE_GOTOW(sp, bp, blen, nlen) \ ADD_SPACE_GOTO(sp, CHAR_T, bp, blen, (nlen) * sizeof(CHAR_T)) #define ADD_SPACE_RET(sp, type, bp, blen, nlen) { \ CHECK_TYPE(type *, bp) \ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \ if (L__gp == NULL || bp == (type *)L__gp->tmp_bp) { \ F_CLR(L__gp, G_TMP_INUSE); \ BINC_RETC(sp, L__gp->tmp_bp, L__gp->tmp_blen, nlen); \ bp = (type *) L__gp->tmp_bp; \ blen = L__gp->tmp_blen; \ F_SET(L__gp, G_TMP_INUSE); \ } else \ BINC_RET(sp, type, bp, blen, nlen); \ } #define ADD_SPACE_RETC(sp, bp, blen, nlen) \ ADD_SPACE_RET(sp, char, bp, blen, nlen) #define ADD_SPACE_RETW(sp, bp, blen, nlen) \ ADD_SPACE_RET(sp, CHAR_T, bp, blen, (nlen) * sizeof(CHAR_T)) /* Free a GET_SPACE returned buffer. */ #define FREE_SPACE(sp, bp, blen) { \ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \ if (L__gp != NULL && bp == L__gp->tmp_bp) \ F_CLR(L__gp, G_TMP_INUSE); \ else \ free(bp); \ } #define FREE_SPACEW(sp, bp, blen) { \ CHECK_TYPE(CHAR_T *, bp) \ FREE_SPACE(sp, (char *)bp, blen); \ } /* * Malloc a buffer, casting the return pointer. Various versions. - * - * !!! - * The cast should be unnecessary, malloc(3) and friends return void *'s, - * which is all we need. However, some systems that nvi needs to run on - * don't do it right yet, resulting in the compiler printing out roughly - * a million warnings. After awhile, it seemed easier to put the casts - * in instead of explaining it all the time. */ -#define CALLOC(sp, p, cast, nmemb, size) { \ - if ((p = (cast)calloc(nmemb, size)) == NULL) \ +#define CALLOC(sp, p, nmemb, size) { \ + if ((p = calloc(nmemb, size)) == NULL) \ msgq(sp, M_SYSERR, NULL); \ } -#define CALLOC_GOTO(sp, p, cast, nmemb, size) { \ - if ((p = (cast)calloc(nmemb, size)) == NULL) \ +#define CALLOC_GOTO(sp, p, nmemb, size) { \ + if ((p = calloc(nmemb, size)) == NULL) \ goto alloc_err; \ } -#define CALLOC_NOMSG(sp, p, cast, nmemb, size) { \ - p = (cast)calloc(nmemb, size); \ -} -#define CALLOC_RET(sp, p, cast, nmemb, size) { \ - if ((p = (cast)calloc(nmemb, size)) == NULL) { \ +#define CALLOC_RET(sp, p, nmemb, size) { \ + if ((p = calloc(nmemb, size)) == NULL) { \ msgq(sp, M_SYSERR, NULL); \ return (1); \ } \ } -#define MALLOC(sp, p, cast, size) { \ - if ((p = (cast)malloc(size)) == NULL) \ +#define MALLOC(sp, p, size) { \ + if ((p = malloc(size)) == NULL) \ msgq(sp, M_SYSERR, NULL); \ } -#define MALLOC_GOTO(sp, p, cast, size) { \ - if ((p = (cast)malloc(size)) == NULL) \ +#define MALLOC_GOTO(sp, p, size) { \ + if ((p = malloc(size)) == NULL) \ goto alloc_err; \ } -#define MALLOC_NOMSG(sp, p, cast, size) { \ - p = (cast)malloc(size); \ -} -#define MALLOC_RET(sp, p, cast, size) { \ - if ((p = (cast)malloc(size)) == NULL) { \ +#define MALLOC_RET(sp, p, size) { \ + if ((p = malloc(size)) == NULL) { \ msgq(sp, M_SYSERR, NULL); \ return (1); \ } \ } /* * Resize a buffer, free any already held memory if we can't get more. * FreeBSD's reallocf(3) does the same thing, but it's not portable yet. */ #define REALLOC(sp, p, cast, size) { \ cast newp; \ - if ((newp = (cast)realloc(p, size)) == NULL) { \ - if (p != NULL) \ - free(p); \ + if ((newp = realloc(p, size)) == NULL) { \ + free(p); \ msgq(sp, M_SYSERR, NULL); \ } \ p = newp; \ } - -/* - * Versions of bcopy(3) and bzero(3) that use the size of the - * initial pointer to figure out how much memory to manipulate. - */ -#define BCOPY(p, t, len) bcopy(p, t, (len) * sizeof(*(p))) -#define BZERO(p, len) bzero(p, (len) * sizeof(*(p))) /* * p2roundup -- * Get next power of 2; convenient for realloc. * * Reference: FreeBSD /usr/src/lib/libc/stdio/getdelim.c */ static __inline size_t p2roundup(size_t n) { n--; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; #if SIZE_T_MAX > 0xffffffffU n |= n >> 32; #endif n++; return (n); } /* Additional TAILQ helper. */ #define TAILQ_ENTRY_ISVALID(elm, field) \ ((elm)->field.tqe_prev != NULL) Index: head/contrib/nvi/common/msg.c =================================================================== --- head/contrib/nvi/common/msg.c (revision 365498) +++ head/contrib/nvi/common/msg.c (revision 365499) @@ -1,912 +1,883 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: msg.c,v 11.1 2015/02/09 11:12:44 marc Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" /* * msgq -- * Display a message. * * PUBLIC: void msgq(SCR *, mtype_t, const char *, ...); */ void -msgq( - SCR *sp, - mtype_t mt, - const char *fmt, - ...) +msgq(SCR *sp, mtype_t mt, const char *fmt, ...) { #ifndef NL_ARGMAX #define __NL_ARGMAX 20 /* Set to 9 by System V. */ struct { const char *str; /* String pointer. */ size_t arg; /* Argument number. */ size_t prefix; /* Prefix string length. */ size_t skip; /* Skipped string length. */ size_t suffix; /* Suffix string length. */ } str[__NL_ARGMAX]; #endif static int reenter; /* STATIC: Re-entrancy check. */ GS *gp; size_t blen, len, mlen, nlen; const char *p; char *bp, *mp; va_list ap; #ifndef NL_ARGMAX int ch; char *rbp, *s_rbp; const char *t, *u; size_t cnt1, cnt2, soff; #endif /* * !!! * It's possible to enter msg when there's no screen to hold the * message. If sp is NULL, ignore the special cases and put the * message out to stderr. */ if (sp == NULL) { gp = NULL; if (mt == M_BERR) mt = M_ERR; else if (mt == M_VINFO) mt = M_INFO; } else { gp = sp->gp; switch (mt) { case M_BERR: if (F_ISSET(sp, SC_VI) && !O_ISSET(sp, O_VERBOSE)) { F_SET(gp, G_BELLSCHED); return; } mt = M_ERR; break; case M_VINFO: if (!O_ISSET(sp, O_VERBOSE)) return; mt = M_INFO; /* FALLTHROUGH */ case M_INFO: if (F_ISSET(sp, SC_EX_SILENT)) return; break; case M_ERR: case M_SYSERR: break; default: abort(); } } /* * It's possible to reenter msg when it allocates space. We're * probably dead anyway, but there's no reason to drop core. * * XXX * Yes, there's a race, but it should only be two instructions. */ if (reenter++) return; /* Get space for the message. */ nlen = 1024; if (0) { retry: FREE_SPACE(sp, bp, blen); nlen *= 2; } bp = NULL; blen = 0; GET_SPACE_GOTOC(sp, bp, blen, nlen); /* * Error prefix. * * mp: pointer to the current next character to be written * mlen: length of the already written characters * blen: total length of the buffer */ #define REM (blen - mlen) mp = bp; mlen = 0; if (mt == M_SYSERR) { p = msg_cat(sp, "020|Error: ", &len); if (REM < len) goto retry; memcpy(mp, p, len); mp += len; mlen += len; } /* * If we're running an ex command that the user didn't enter, display * the file name and line number prefix. */ if ((mt == M_ERR || mt == M_SYSERR) && sp != NULL && gp != NULL && gp->if_name != NULL) { CHAR_T *wp; size_t wlen; CHAR2INT(sp, gp->if_name, strlen(gp->if_name) + 1, wp, wlen); for (; *wp != '\0'; ++wp) { len = snprintf(mp, REM, "%s", KEY_NAME(sp, *wp)); mp += len; if ((mlen += len) > blen) goto retry; } len = snprintf(mp, REM, ", %d: ", gp->if_lno); mp += len; if ((mlen += len) > blen) goto retry; } /* If nothing to format, we're done. */ if (fmt == NULL) goto nofmt; fmt = msg_cat(sp, fmt, NULL); #ifndef NL_ARGMAX /* * Nvi should run on machines that don't support the numbered argument * specifications (%[digit]*$). We do this by reformatting the string * so that we can hand it to vsprintf(3) and it will use the arguments * in the right order. When vsprintf returns, we put the string back * into the right order. It's undefined, according to SVID III, to mix * numbered argument specifications with the standard style arguments, * so this should be safe. * * In addition, we also need a character that is known to not occur in * any vi message, for separating the parts of the string. As callers * of msgq are responsible for making sure that all the non-printable * characters are formatted for printing before calling msgq, we use a * random non-printable character selected at terminal initialization * time. This code isn't fast by any means, but as messages should be * relatively short and normally have only a few arguments, it won't be * too bad. Regardless, nobody has come up with any other solution. * * The result of this loop is an array of pointers into the message * string, with associated lengths and argument numbers. The array * is in the "correct" order, and the arg field contains the argument * order. */ for (p = fmt, soff = 0; soff < __NL_ARGMAX;) { for (t = p; *p != '\0' && *p != '%'; ++p); if (*p == '\0') break; ++p; - if (!isdigit(*p)) { + if (!isdigit((u_char)*p)) { if (*p == '%') ++p; continue; } - for (u = p; *++p != '\0' && isdigit(*p);); + for (u = p; *++p != '\0' && isdigit((u_char)*p);); if (*p != '$') continue; /* Up to, and including the % character. */ str[soff].str = t; str[soff].prefix = u - t; /* Up to, and including the $ character. */ str[soff].arg = atoi(u); str[soff].skip = (p - u) + 1; if (str[soff].arg >= __NL_ARGMAX) goto ret; /* Up to, and including the conversion character. */ for (u = p; (ch = *++p) != '\0';) if (isalpha(ch) && strchr("diouxXfeEgGcspn", ch) != NULL) break; str[soff].suffix = p - u; if (ch != '\0') ++p; ++soff; } /* If no magic strings, we're done. */ if (soff == 0) goto format; /* Get space for the reordered strings. */ if ((rbp = malloc(nlen)) == NULL) goto ret; s_rbp = rbp; /* * Reorder the strings into the message string based on argument * order. * * !!! * We ignore arguments that are out of order, i.e. if we don't find * an argument, we continue. Assume (almost certainly incorrectly) * that whoever created the string knew what they were doing. * * !!! * Brute force "sort", but since we don't expect more than one or two * arguments in a string, the setup cost of a fast sort will be more * expensive than the loop. */ for (cnt1 = 1; cnt1 <= soff; ++cnt1) for (cnt2 = 0; cnt2 < soff; ++cnt2) if (cnt1 == str[cnt2].arg) { memmove(s_rbp, str[cnt2].str, str[cnt2].prefix); memmove(s_rbp + str[cnt2].prefix, str[cnt2].str + str[cnt2].prefix + str[cnt2].skip, str[cnt2].suffix); s_rbp += str[cnt2].prefix + str[cnt2].suffix; *s_rbp++ = gp == NULL ? DEFAULT_NOPRINT : gp->noprint; break; } *s_rbp = '\0'; fmt = rbp; #endif #ifndef NL_ARGMAX format: /* Format the arguments into the string. */ #endif va_start(ap, fmt); len = vsnprintf(mp, REM, fmt, ap); va_end(ap); if (len >= nlen) goto retry; #ifndef NL_ARGMAX if (soff == 0) goto nofmt; /* * Go through the resulting string, and, for each separator character * separated string, enter its new starting position and length in the * array. */ for (p = t = mp, cnt1 = 1, ch = gp == NULL ? DEFAULT_NOPRINT : gp->noprint; *p != '\0'; ++p) if (*p == ch) { for (cnt2 = 0; cnt2 < soff; ++cnt2) if (str[cnt2].arg == cnt1) break; str[cnt2].str = t; str[cnt2].prefix = p - t; t = p + 1; ++cnt1; } /* * Reorder the strings once again, putting them back into the * message buffer. * * !!! * Note, the length of the message gets decremented once for * each substring, when we discard the separator character. */ for (s_rbp = rbp, cnt1 = 0; cnt1 < soff; ++cnt1) { memmove(rbp, str[cnt1].str, str[cnt1].prefix); rbp += str[cnt1].prefix; --len; } memmove(mp, s_rbp, rbp - s_rbp); /* Free the reordered string memory. */ free(s_rbp); #endif nofmt: mp += len; if ((mlen += len) > blen) goto retry; if (mt == M_SYSERR) { len = snprintf(mp, REM, ": %s", strerror(errno)); mp += len; if ((mlen += len) > blen) goto retry; mt = M_ERR; } /* Add trailing newline. */ if ((mlen += 1) > blen) goto retry; *mp = '\n'; if (sp != NULL) (void)ex_fflush(sp); if (gp != NULL) gp->scr_msg(sp, mt, bp, mlen); else (void)fprintf(stderr, "%.*s", (int)mlen, bp); /* Cleanup. */ #ifndef NL_ARGMAX ret: #endif FREE_SPACE(sp, bp, blen); alloc_err: reenter = 0; } /* * msgq_wstr -- * Display a message with an embedded string. * * PUBLIC: void msgq_wstr(SCR *, mtype_t, const CHAR_T *, const char *); */ void -msgq_wstr( - SCR *sp, - mtype_t mtype, - const CHAR_T *str, - const char *fmt) +msgq_wstr(SCR *sp, mtype_t mtype, const CHAR_T *str, const char *fmt) { size_t nlen; CONST char *nstr; if (str == NULL) { msgq(sp, mtype, "%s", fmt); return; } INT2CHAR(sp, str, STRLEN(str) + 1, nstr, nlen); msgq_str(sp, mtype, nstr, fmt); } /* * msgq_str -- * Display a message with an embedded string. * * PUBLIC: void msgq_str(SCR *, mtype_t, const char *, const char *); */ void -msgq_str( - SCR *sp, - mtype_t mtype, - const char *str, - const char *fmt) +msgq_str(SCR *sp, mtype_t mtype, const char *str, const char *fmt) { int nf, sv_errno; char *p; if (str == NULL) { msgq(sp, mtype, "%s", fmt); return; } sv_errno = errno; p = msg_print(sp, str, &nf); errno = sv_errno; msgq(sp, mtype, fmt, p); if (nf) FREE_SPACE(sp, p, 0); } /* * mod_rpt -- * Report on the lines that changed. * * !!! * Historic vi documentation (USD:15-8) claimed that "The editor will also * always tell you when a change you make affects text which you cannot see." * This wasn't true -- edit a large file and do "100d|1". We don't implement * this semantic since it requires tracking each line that changes during a * command instead of just keeping count. * * Line counts weren't right in historic vi, either. For example, given the * file: * abc * def * the command 2d}, from the 'b' would report that two lines were deleted, * not one. * * PUBLIC: void mod_rpt(SCR *); */ void mod_rpt(SCR *sp) { static char * const action[] = { "293|added", "294|changed", "295|deleted", "296|joined", "297|moved", "298|shifted", "299|yanked", }; static char * const lines[] = { "300|line", "301|lines", }; recno_t total; u_long rptval; int first, cnt; size_t blen, len, tlen; const char *t; char * const *ap; char *bp, *p; /* Change reports are turned off in batch mode. */ if (F_ISSET(sp, SC_EX_SILENT)) return; /* Reset changing line number. */ sp->rptlchange = OOBLNO; /* * Don't build a message if not enough changed. * * !!! * And now, a vi clone test. Historically, vi reported if the number * of changed lines was > than the value, not >=, unless it was a yank * command, which used >=. No lie. Furthermore, an action was never * reported for a single line action. This is consistent for actions * other than yank, but yank didn't report single line actions even if * the report edit option was set to 1. In addition, setting report to * 0 in the 4BSD historic vi was equivalent to setting it to 1, for an * unknown reason (this bug was fixed in System III/V at some point). * I got complaints, so nvi conforms to System III/V historic practice * except that we report a yank of 1 line if report is set to 1. */ #define ARSIZE(a) sizeof(a) / sizeof (*a) #define MAXNUM 25 rptval = O_VAL(sp, O_REPORT); for (cnt = 0, total = 0; cnt < ARSIZE(action); ++cnt) total += sp->rptlines[cnt]; if (total == 0) return; if (total <= rptval && sp->rptlines[L_YANKED] < rptval) { for (cnt = 0; cnt < ARSIZE(action); ++cnt) sp->rptlines[cnt] = 0; return; } /* Build and display the message. */ GET_SPACE_GOTOC(sp, bp, blen, sizeof(action) * MAXNUM + 1); for (p = bp, first = 1, tlen = 0, ap = action, cnt = 0; cnt < ARSIZE(action); ++ap, ++cnt) if (sp->rptlines[cnt] != 0) { if (first) first = 0; else { *p++ = ';'; *p++ = ' '; tlen += 2; } len = snprintf(p, MAXNUM, "%lu ", (u_long)sp->rptlines[cnt]); p += len; tlen += len; t = msg_cat(sp, lines[sp->rptlines[cnt] == 1 ? 0 : 1], &len); memcpy(p, t, len); p += len; tlen += len; *p++ = ' '; ++tlen; t = msg_cat(sp, *ap, &len); memcpy(p, t, len); p += len; tlen += len; sp->rptlines[cnt] = 0; } /* Add trailing newline. */ *p = '\n'; ++tlen; (void)ex_fflush(sp); sp->gp->scr_msg(sp, M_INFO, bp, tlen); FREE_SPACE(sp, bp, blen); alloc_err: return; #undef ARSIZE #undef MAXNUM } /* * msgq_status -- * Report on the file's status. * * PUBLIC: void msgq_status(SCR *, recno_t, u_int); */ void -msgq_status( - SCR *sp, - recno_t lno, - u_int flags) +msgq_status(SCR *sp, recno_t lno, u_int flags) { recno_t last; size_t blen, len; int cnt, needsep; const char *t; char **ap, *bp, *np, *p, *s, *ep; CHAR_T *wp; size_t wlen; /* Get sufficient memory. */ len = strlen(sp->frp->name); GET_SPACE_GOTOC(sp, bp, blen, len * MAX_CHARACTER_COLUMNS + 128); p = bp; ep = bp + blen; /* Convert the filename. */ CHAR2INT(sp, sp->frp->name, len + 1, wp, wlen); /* Copy in the filename. */ for (; *wp != '\0'; ++wp) { len = KEY_LEN(sp, *wp); memcpy(p, KEY_NAME(sp, *wp), len); p += len; } np = p; *p++ = ':'; *p++ = ' '; /* Copy in the argument count. */ if (F_ISSET(sp, SC_STATUS_CNT) && sp->argv != NULL) { for (cnt = 0, ap = sp->argv; *ap != NULL; ++ap, ++cnt); if (cnt > 1) { (void)snprintf(p, ep - p, msg_cat(sp, "317|%d files to edit", NULL), cnt); p += strlen(p); *p++ = ':'; *p++ = ' '; } F_CLR(sp, SC_STATUS_CNT); } /* * See nvi/exf.c:file_init() for a description of how and when the * read-only bit is set. * * !!! * The historic display for "name changed" was "[Not edited]". */ needsep = 0; if (F_ISSET(sp->frp, FR_NEWFILE)) { F_CLR(sp->frp, FR_NEWFILE); t = msg_cat(sp, "021|new file", &len); memcpy(p, t, len); p += len; needsep = 1; } else { if (F_ISSET(sp->frp, FR_NAMECHANGE)) { t = msg_cat(sp, "022|name changed", &len); memcpy(p, t, len); p += len; needsep = 1; } if (needsep) { *p++ = ','; *p++ = ' '; } if (F_ISSET(sp->ep, F_MODIFIED)) t = msg_cat(sp, "023|modified", &len); else t = msg_cat(sp, "024|unmodified", &len); memcpy(p, t, len); p += len; needsep = 1; } if (F_ISSET(sp->frp, FR_UNLOCKED)) { if (needsep) { *p++ = ','; *p++ = ' '; } t = msg_cat(sp, "025|UNLOCKED", &len); memcpy(p, t, len); p += len; needsep = 1; } if (O_ISSET(sp, O_READONLY)) { if (needsep) { *p++ = ','; *p++ = ' '; } t = msg_cat(sp, "026|readonly", &len); memcpy(p, t, len); p += len; needsep = 1; } if (needsep) { *p++ = ':'; *p++ = ' '; } if (LF_ISSET(MSTAT_SHOWLAST)) { if (db_last(sp, &last)) return; if (last == 0) { t = msg_cat(sp, "028|empty file", &len); memcpy(p, t, len); p += len; } else { t = msg_cat(sp, "027|line %lu of %lu [%ld%%]", &len); (void)snprintf(p, ep - p, t, (u_long)lno, (u_long)last, ((u_long)lno * 100) / last); p += strlen(p); } } else { t = msg_cat(sp, "029|line %lu", &len); (void)snprintf(p, ep - p, t, (u_long)lno); p += strlen(p); } #ifdef DEBUG (void)snprintf(p, ep - p, " (pid %lu)", (u_long)getpid()); p += strlen(p); #endif *p++ = '\n'; len = p - bp; /* * There's a nasty problem with long path names. Cscope and tags files * can result in long paths and vi will request a continuation key from * the user as soon as it starts the screen. Unfortunately, the user * has already typed ahead, and chaos results. If we assume that the * characters in the filenames and informational messages only take a * single screen column each, we can trim the filename. * * XXX * Status lines get put up at fairly awkward times. For example, when * you do a filter read (e.g., :read ! echo foo) in the top screen of a * split screen, we have to repaint the status lines for all the screens * below the top screen. We don't want users having to enter continue * characters for those screens. Make it really hard to screw this up. */ s = bp; if (LF_ISSET(MSTAT_TRUNCATE) && len > sp->cols) { for (; s < np && (*s != '/' || (p - s) > sp->cols - 3); ++s); if (s == np) { s = p - (sp->cols - 5); *--s = ' '; } *--s = '.'; *--s = '.'; *--s = '.'; len = p - s; } /* Flush any waiting ex messages. */ (void)ex_fflush(sp); sp->gp->scr_msg(sp, M_INFO, s, len); FREE_SPACE(sp, bp, blen); alloc_err: return; } /* * msg_open -- * Open the message catalogs. * * PUBLIC: int msg_open(SCR *, char *); */ int -msg_open( - SCR *sp, - char *file) +msg_open(SCR *sp, char *file) { /* * !!! * Assume that the first file opened is the system default, and that * all subsequent ones user defined. Only display error messages * if we can't open the user defined ones -- it's useful to know if * the system one wasn't there, but if nvi is being shipped with an * installed system, the file will be there, if it's not, then the * message will be repeated every time nvi is started up. */ static int first = 1; nl_catd catd; char *p; int rval = 0; if ((p = strrchr(file, '/')) != NULL && p[1] == '\0') { /* Confirms to XPG4. */ if ((p = join(file, setlocale(LC_MESSAGES, NULL))) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } } else { /* Make sure it's recognized as a path by catopen(3). */ if ((p = join(".", file)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } } errno = 0; if ((catd = catopen(p, NL_CAT_LOCALE)) == (nl_catd)-1) { if (first) { first = 0; rval = 1; goto ret; } /* * POSIX.1-2008 gives no instruction on how to report a * corrupt catalog file. Errno == 0 is not rare; add * EFTYPE, which is seen on FreeBSD, for a good measure. */ if (errno == 0 || errno == EFTYPE) msgq_str(sp, M_ERR, p, "030|The file %s is not a message catalog"); else msgq_str(sp, M_SYSERR, p, "%s"); rval = 1; goto ret; } first = 0; msg_close(sp->gp); sp->gp->catd = catd; ret: free(p); return (rval); } /* * msg_close -- * Close the message catalogs. * * PUBLIC: void msg_close(GS *); */ void msg_close(GS *gp) { if (gp->catd != (nl_catd)-1) (void)catclose(gp->catd); } /* * msg_cont -- * Return common continuation messages. * * PUBLIC: const char *msg_cmsg(SCR *, cmsg_t, size_t *); */ const char * -msg_cmsg( - SCR *sp, - cmsg_t which, - size_t *lenp) +msg_cmsg(SCR *sp, cmsg_t which, size_t *lenp) { switch (which) { case CMSG_CONF: return (msg_cat(sp, "268|confirm? [ynq]", lenp)); case CMSG_CONT: return (msg_cat(sp, "269|Press any key to continue: ", lenp)); case CMSG_CONT_EX: return (msg_cat(sp, "270|Press any key to continue [: to enter more ex commands]: ", lenp)); case CMSG_CONT_R: return (msg_cat(sp, "161|Press Enter to continue: ", lenp)); case CMSG_CONT_S: return (msg_cat(sp, "275| cont?", lenp)); case CMSG_CONT_Q: return (msg_cat(sp, "271|Press any key to continue [q to quit]: ", lenp)); default: abort(); } /* NOTREACHED */ } /* * msg_cat -- * Return a single message from the catalog, plus its length. * * !!! * Only a single catalog message can be accessed at a time, if multiple * ones are needed, they must be copied into local memory. * * PUBLIC: const char *msg_cat(SCR *, const char *, size_t *); */ const char * -msg_cat( - SCR *sp, - const char *str, - size_t *lenp) +msg_cat(SCR *sp, const char *str, size_t *lenp) { GS *gp; char *p; int msgno; /* * If it's not a catalog message, i.e. has doesn't have a leading * number and '|' symbol, we're done. */ - if (isdigit(str[0]) && - isdigit(str[1]) && isdigit(str[2]) && str[3] == '|') { + if (isdigit((u_char)str[0]) && + isdigit((u_char)str[1]) && + isdigit((u_char)str[2]) && str[3] == '|') { msgno = atoi(str); str = &str[4]; gp = sp == NULL ? NULL : sp->gp; if (gp != NULL && gp->catd != (nl_catd)-1 && (p = catgets(gp->catd, 1, msgno, str)) != NULL) { if (lenp != NULL) *lenp = strlen(p); return (p); } } if (lenp != NULL) *lenp = strlen(str); return (str); } /* * msg_print -- * Return a printable version of a string, in allocated memory. * * PUBLIC: char *msg_print(SCR *, const char *, int *); */ char * -msg_print( - SCR *sp, - const char *s, - int *needfree) +msg_print(SCR *sp, const char *s, int *needfree) { size_t blen, nlen; char *bp, *ep, *p, *t; CHAR_T *wp, *cp; size_t wlen; *needfree = 0; /* XXX Not good for debugging ex_read & ex_filter.*/ CHAR2INT5(sp, EXP(sp)->ibcw, (char *)s, strlen(s) + 1, wp, wlen); for (cp = wp; *cp != '\0'; ++cp) if (!ISPRINT(*cp)) break; if (*cp == '\0') return ((char *)s); /* SAFE: needfree set to 0. */ nlen = 0; if (0) { retry: if (sp == NULL) free(bp); else FREE_SPACE(sp, bp, blen); *needfree = 0; } nlen += 256; if (sp == NULL) { if ((bp = malloc(nlen)) == NULL) goto alloc_err; } else GET_SPACE_GOTOC(sp, bp, blen, nlen); if (0) { alloc_err: return (""); } *needfree = 1; for (p = bp, ep = (bp + blen) - 1; *wp != '\0' && p < ep; ++wp) for (t = KEY_NAME(sp, *wp); *t != '\0' && p < ep; *p++ = *t++); if (p == ep) goto retry; *p = '\0'; return (bp); } Index: head/contrib/nvi/common/msg.h =================================================================== --- head/contrib/nvi/common/msg.h (revision 365498) +++ head/contrib/nvi/common/msg.h (revision 365499) @@ -1,65 +1,63 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * @(#)msg.h 10.10 (Berkeley) 5/10/96 */ /* * Common messages (continuation or confirmation). */ typedef enum { CMSG_CONF, CMSG_CONT, CMSG_CONT_EX, CMSG_CONT_R, CMSG_CONT_S, CMSG_CONT_Q } cmsg_t; /* * Message types. * * !!! * In historical vi, O_VERBOSE didn't exist, and O_TERSE made the error * messages shorter. In this implementation, O_TERSE has no effect and * O_VERBOSE results in informational displays about common errors, for * naive users. * * M_NONE Display to the user, no reformatting, no nothing. * * M_BERR Error: M_ERR if O_VERBOSE, else bell. * M_ERR Error: Display in inverse video. * M_INFO Info: Display in normal video. * M_SYSERR Error: M_ERR, using strerror(3) message. * M_VINFO Info: M_INFO if O_VERBOSE, else ignore. * * The underlying message display routines only need to know about M_NONE, * M_ERR and M_INFO -- all the other message types are converted into one * of them by the message routines. */ typedef enum { M_NONE = 1, M_BERR, M_ERR, M_INFO, M_SYSERR, M_VINFO } mtype_t; /* * There are major problems with error messages being generated by routines * preparing the screen to display error messages. It's possible for the * editor to generate messages before we have a screen in which to display * them, or during the transition between ex (and vi startup) and a true vi. * There's a queue in the global area to hold them. * * If SC_EX/SC_VI is set, that's the mode that the editor is in. If the flag * S_SCREEN_READY is set, that means that the screen is prepared to display * messages. */ typedef struct _msgh MSGH; /* MSGS list head structure. */ SLIST_HEAD(_msgh, _msg); struct _msg { SLIST_ENTRY(_msg) q; /* Linked list of messages. */ mtype_t mtype; /* Message type: M_NONE, M_ERR, M_INFO. */ char *buf; /* Message buffer. */ size_t len; /* Message length. */ }; /* Flags to msgq_status(). */ #define MSTAT_SHOWLAST 0x01 /* Show the line number of the last line. */ #define MSTAT_TRUNCATE 0x02 /* Truncate the file name if it's too long. */ Index: head/contrib/nvi/common/multibyte.h =================================================================== --- head/contrib/nvi/common/multibyte.h (revision 365498) +++ head/contrib/nvi/common/multibyte.h (revision 365499) @@ -1,115 +1,147 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: multibyte.h,v 1.32 2012/10/07 01:35:58 zy Exp $ */ #ifndef MULTIBYTE_H #define MULTIBYTE_H /* * Fundamental character types. * * CHAR_T An integral type that can hold any character. * ARG_CHAR_T The type of a CHAR_T when passed as an argument using * traditional promotion rules. It should also be able * to be compared against any CHAR_T for equality without * problems. * UCHAR_T The shortest unified character type (8-bit clean). * RCHAR_T The character type used by the internal regex engine. * * If no integral type can hold a character, don't even try the port. */ typedef int ARG_CHAR_T; #ifdef USE_WIDECHAR #include #include typedef wchar_t CHAR_T; typedef wint_t UCHAR_T; typedef wchar_t RCHAR_T; #define REOF WEOF #define STRLEN wcslen #define STRTOL wcstol #define STRTOUL wcstoul #define SPRINTF swprintf #define STRCMP wcscmp #define STRPBRK wcspbrk #define ISBLANK iswblank #define ISCNTRL iswcntrl #define ISDIGIT iswdigit #define ISXDIGIT iswxdigit #define ISGRAPH iswgraph #define ISLOWER iswlower #define ISPRINT iswprint #define ISPUNCT iswpunct #define ISSPACE iswspace #define ISUPPER iswupper #define TOLOWER towlower #define TOUPPER towupper #define STRSET wmemset #define STRCHR wcschr #define STRRCHR wcsrchr #define GETC getwc #define L(ch) L ## ch #define WS "%ls" #define WVS "%*ls" #define WC "%lc" #else typedef u_char CHAR_T; typedef u_char UCHAR_T; typedef char RCHAR_T; #define REOF EOF #define STRLEN strlen #define STRTOL(a,b,c) (strtol(a,(char**)b,c)) #define STRTOUL(a,b,c) (strtoul(a,(char**)b,c)) #define SPRINTF snprintf #define STRCMP strcmp #define STRPBRK strpbrk #define ISBLANK isblank #define ISCNTRL iscntrl #define ISDIGIT isdigit #define ISXDIGIT isxdigit #define ISGRAPH isgraph #define ISLOWER islower #define ISPRINT isprint #define ISPUNCT ispunct #define ISSPACE isspace #define ISUPPER isupper #define TOLOWER tolower #define TOUPPER toupper #define STRSET memset #define STRCHR strchr #define STRRCHR strrchr #define GETC getc #define L(ch) ch #define WS "%s" #define WVS "%*s" #define WC "%c" #endif #if defined(USE_WIDECHAR) && defined(DEBUG) #define MEMCPY wmemcpy #define MEMMOVE wmemmove #define MEMCMP wmemcmp #else #define MEMCPY(p, t, len) memcpy(p, t, (len) * sizeof(CHAR_T)) #define MEMMOVE(p, t, len) memmove(p, t, (len) * sizeof(CHAR_T)) #define MEMCMP(p, t, len) memcmp(p, t, (len) * sizeof(CHAR_T)) #endif #define SIZE(w) (sizeof(w) / sizeof(*w)) + +/* + * Locale insensitive character category detection. + */ + +static __inline int +isatoz(CHAR_T c) +{ + return 'a' <= c && c <= 'z'; +} + +static __inline int +isAtoZ(CHAR_T c) +{ + return 'A' <= c && c <= 'Z'; +} + +static __inline int +is0to9(CHAR_T c) +{ + return '0' <= c && c <= '9'; +} + +static __inline int +isazAZ(CHAR_T c) +{ + return isatoz(c) || isAtoZ(c); +} + +static __inline int +is09azAZ(CHAR_T c) +{ + return is0to9(c) || isazAZ(c); +} #endif Index: head/contrib/nvi/common/options.c =================================================================== --- head/contrib/nvi/common/options.c (revision 365498) +++ head/contrib/nvi/common/options.c (revision 365499) @@ -1,1188 +1,1158 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: options.c,v 10.73 2012/10/09 06:14:07 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" #include "pathnames.h" static int opts_abbcmp(const void *, const void *); static int opts_cmp(const void *, const void *); static int opts_print(SCR *, OPTLIST const *); #ifdef USE_WIDECHAR #define OPT_WC 0 #else #define OPT_WC (OPT_NOSAVE | OPT_NDISP) #endif /* * O'Reilly noted options and abbreviations are from "Learning the VI Editor", * Fifth Edition, May 1992. There's no way of knowing what systems they are * actually from. * * HPUX noted options and abbreviations are from "The Ultimate Guide to the * VI and EX Text Editors", 1990. */ OPTLIST const optlist[] = { /* O_ALTWERASE 4.4BSD */ {L("altwerase"), f_altwerase, OPT_0BOOL, 0}, /* O_AUTOINDENT 4BSD */ {L("autoindent"), NULL, OPT_0BOOL, 0}, /* O_AUTOPRINT 4BSD */ {L("autoprint"), NULL, OPT_1BOOL, 0}, /* O_AUTOWRITE 4BSD */ {L("autowrite"), NULL, OPT_0BOOL, 0}, /* O_BACKUP 4.4BSD */ {L("backup"), NULL, OPT_STR, 0}, /* O_BEAUTIFY 4BSD */ {L("beautify"), NULL, OPT_0BOOL, 0}, /* O_CDPATH 4.4BSD */ {L("cdpath"), NULL, OPT_STR, 0}, /* O_CEDIT 4.4BSD */ {L("cedit"), NULL, OPT_STR, 0}, /* O_COLUMNS 4.4BSD */ {L("columns"), f_columns, OPT_NUM, OPT_NOSAVE}, /* O_COMBINED */ {L("combined"), NULL, OPT_0BOOL, OPT_NOSET|OPT_WC}, /* O_COMMENT 4.4BSD */ {L("comment"), NULL, OPT_0BOOL, 0}, /* O_TMPDIR 4BSD */ {L("directory"), NULL, OPT_STR, 0}, /* O_EDCOMPATIBLE 4BSD */ {L("edcompatible"),NULL, OPT_0BOOL, 0}, /* O_ERRORBELLS 4BSD */ {L("errorbells"), NULL, OPT_0BOOL, 0}, /* O_ESCAPETIME 4.4BSD */ {L("escapetime"), NULL, OPT_NUM, 0}, +/* O_EXPANDTAB NetBSD 5.0 */ + {L("expandtab"), NULL, OPT_0BOOL, 0}, /* O_EXRC System V (undocumented) */ {L("exrc"), NULL, OPT_0BOOL, 0}, /* O_EXTENDED 4.4BSD */ {L("extended"), f_recompile, OPT_0BOOL, 0}, /* O_FILEC 4.4BSD */ {L("filec"), NULL, OPT_STR, 0}, /* O_FILEENCODING */ {L("fileencoding"),f_encoding, OPT_STR, OPT_WC}, /* O_FLASH HPUX */ {L("flash"), NULL, OPT_1BOOL, 0}, /* O_HARDTABS 4BSD */ {L("hardtabs"), NULL, OPT_NUM, 0}, /* O_ICLOWER 4.4BSD */ {L("iclower"), f_recompile, OPT_0BOOL, 0}, /* O_IGNORECASE 4BSD */ {L("ignorecase"), f_recompile, OPT_0BOOL, 0}, /* O_INPUTENCODING */ {L("inputencoding"),f_encoding, OPT_STR, OPT_WC}, /* O_KEYTIME 4.4BSD */ {L("keytime"), NULL, OPT_NUM, 0}, /* O_LEFTRIGHT 4.4BSD */ {L("leftright"), f_reformat, OPT_0BOOL, 0}, /* O_LINES 4.4BSD */ {L("lines"), f_lines, OPT_NUM, OPT_NOSAVE}, /* O_LISP 4BSD * XXX * When the lisp option is implemented, delete the OPT_NOSAVE flag, * so that :mkexrc dumps it. */ {L("lisp"), f_lisp, OPT_0BOOL, OPT_NOSAVE}, /* O_LIST 4BSD */ {L("list"), f_reformat, OPT_0BOOL, 0}, /* O_LOCKFILES 4.4BSD * XXX * Locking isn't reliable enough over NFS to require it, in addition, * it's a serious startup performance problem over some remote links. */ {L("lock"), NULL, OPT_1BOOL, 0}, /* O_MAGIC 4BSD */ {L("magic"), NULL, OPT_1BOOL, 0}, /* O_MATCHCHARS NetBSD 2.0 */ {L("matchchars"), NULL, OPT_STR, OPT_PAIRS}, /* O_MATCHTIME 4.4BSD */ {L("matchtime"), NULL, OPT_NUM, 0}, /* O_MESG 4BSD */ {L("mesg"), NULL, OPT_1BOOL, 0}, /* O_MODELINE 4BSD * !!! * This has been documented in historical systems as both "modeline" * and as "modelines". Regardless of the name, this option represents * a security problem of mammoth proportions, not to mention a stunning * example of what your intro CS professor referred to as the perils of * mixing code and data. Don't add it, or I will kill you. */ {L("modeline"), NULL, OPT_0BOOL, OPT_NOSET}, /* O_MSGCAT 4.4BSD */ {L("msgcat"), f_msgcat, OPT_STR, 0}, /* O_NOPRINT 4.4BSD */ {L("noprint"), f_print, OPT_STR, 0}, /* O_NUMBER 4BSD */ {L("number"), f_reformat, OPT_0BOOL, 0}, /* O_OCTAL 4.4BSD */ {L("octal"), f_print, OPT_0BOOL, 0}, /* O_OPEN 4BSD */ {L("open"), NULL, OPT_1BOOL, 0}, /* O_OPTIMIZE 4BSD */ {L("optimize"), NULL, OPT_1BOOL, 0}, /* O_PARAGRAPHS 4BSD */ {L("paragraphs"), NULL, OPT_STR, OPT_PAIRS}, /* O_PATH 4.4BSD */ {L("path"), NULL, OPT_STR, 0}, /* O_PRINT 4.4BSD */ {L("print"), f_print, OPT_STR, 0}, /* O_PROMPT 4BSD */ {L("prompt"), NULL, OPT_1BOOL, 0}, /* O_READONLY 4BSD (undocumented) */ {L("readonly"), f_readonly, OPT_0BOOL, OPT_ALWAYS}, /* O_RECDIR 4.4BSD */ {L("recdir"), NULL, OPT_STR, 0}, /* O_REDRAW 4BSD */ {L("redraw"), NULL, OPT_0BOOL, 0}, /* O_REMAP 4BSD */ {L("remap"), NULL, OPT_1BOOL, 0}, /* O_REPORT 4BSD */ {L("report"), NULL, OPT_NUM, 0}, /* O_RULER 4.4BSD */ {L("ruler"), NULL, OPT_0BOOL, 0}, /* O_SCROLL 4BSD */ {L("scroll"), NULL, OPT_NUM, 0}, /* O_SEARCHINCR 4.4BSD */ {L("searchincr"), NULL, OPT_0BOOL, 0}, /* O_SECTIONS 4BSD */ {L("sections"), NULL, OPT_STR, OPT_PAIRS}, /* O_SECURE 4.4BSD */ {L("secure"), NULL, OPT_0BOOL, OPT_NOUNSET}, /* O_SHELL 4BSD */ {L("shell"), NULL, OPT_STR, 0}, /* O_SHELLMETA 4.4BSD */ {L("shellmeta"), NULL, OPT_STR, 0}, /* O_SHIFTWIDTH 4BSD */ {L("shiftwidth"), NULL, OPT_NUM, OPT_NOZERO}, /* O_SHOWMATCH 4BSD */ {L("showmatch"), NULL, OPT_0BOOL, 0}, /* O_SHOWMODE 4.4BSD */ {L("showmode"), NULL, OPT_0BOOL, 0}, /* O_SIDESCROLL 4.4BSD */ {L("sidescroll"), NULL, OPT_NUM, OPT_NOZERO}, /* O_SLOWOPEN 4BSD */ {L("slowopen"), NULL, OPT_0BOOL, 0}, /* O_SOURCEANY 4BSD (undocumented) * !!! * Historic vi, on startup, source'd $HOME/.exrc and ./.exrc, if they * were owned by the user. The sourceany option was an undocumented * feature of historic vi which permitted the startup source'ing of * .exrc files the user didn't own. This is an obvious security problem, * and we ignore the option. */ {L("sourceany"), NULL, OPT_0BOOL, OPT_NOSET}, /* O_TABSTOP 4BSD */ {L("tabstop"), f_reformat, OPT_NUM, OPT_NOZERO}, /* O_TAGLENGTH 4BSD */ {L("taglength"), NULL, OPT_NUM, 0}, /* O_TAGS 4BSD */ {L("tags"), NULL, OPT_STR, 0}, /* O_TERM 4BSD * !!! * By default, the historic vi always displayed information about two * options, redraw and term. Term seems sufficient. */ {L("term"), NULL, OPT_STR, OPT_ADISP|OPT_NOSAVE}, /* O_TERSE 4BSD */ {L("terse"), NULL, OPT_0BOOL, 0}, /* O_TILDEOP 4.4BSD */ {L("tildeop"), NULL, OPT_0BOOL, 0}, /* O_TIMEOUT 4BSD (undocumented) */ {L("timeout"), NULL, OPT_1BOOL, 0}, /* O_TTYWERASE 4.4BSD */ {L("ttywerase"), f_ttywerase, OPT_0BOOL, 0}, /* O_VERBOSE 4.4BSD */ {L("verbose"), NULL, OPT_0BOOL, 0}, /* O_W1200 4BSD */ {L("w1200"), f_w1200, OPT_NUM, OPT_NDISP|OPT_NOSAVE}, /* O_W300 4BSD */ {L("w300"), f_w300, OPT_NUM, OPT_NDISP|OPT_NOSAVE}, /* O_W9600 4BSD */ {L("w9600"), f_w9600, OPT_NUM, OPT_NDISP|OPT_NOSAVE}, /* O_WARN 4BSD */ {L("warn"), NULL, OPT_1BOOL, 0}, /* O_WINDOW 4BSD */ {L("window"), f_window, OPT_NUM, 0}, /* O_WINDOWNAME 4BSD */ {L("windowname"), NULL, OPT_0BOOL, 0}, /* O_WRAPLEN 4.4BSD */ {L("wraplen"), NULL, OPT_NUM, 0}, /* O_WRAPMARGIN 4BSD */ {L("wrapmargin"), NULL, OPT_NUM, 0}, /* O_WRAPSCAN 4BSD */ {L("wrapscan"), NULL, OPT_1BOOL, 0}, /* O_WRITEANY 4BSD */ {L("writeany"), NULL, OPT_0BOOL, 0}, {NULL}, }; typedef struct abbrev { CHAR_T *name; int offset; } OABBREV; static OABBREV const abbrev[] = { {L("ai"), O_AUTOINDENT}, /* 4BSD */ {L("ap"), O_AUTOPRINT}, /* 4BSD */ {L("aw"), O_AUTOWRITE}, /* 4BSD */ {L("bf"), O_BEAUTIFY}, /* 4BSD */ {L("co"), O_COLUMNS}, /* 4.4BSD */ {L("dir"), O_TMPDIR}, /* 4BSD */ {L("eb"), O_ERRORBELLS}, /* 4BSD */ {L("ed"), O_EDCOMPATIBLE}, /* 4BSD */ + {L("et"), O_EXPANDTAB}, /* NetBSD 5.0 */ {L("ex"), O_EXRC}, /* System V (undocumented) */ {L("fe"), O_FILEENCODING}, {L("ht"), O_HARDTABS}, /* 4BSD */ {L("ic"), O_IGNORECASE}, /* 4BSD */ {L("ie"), O_INPUTENCODING}, {L("li"), O_LINES}, /* 4.4BSD */ {L("modelines"), O_MODELINE}, /* HPUX */ {L("nu"), O_NUMBER}, /* 4BSD */ {L("opt"), O_OPTIMIZE}, /* 4BSD */ {L("para"), O_PARAGRAPHS}, /* 4BSD */ {L("re"), O_REDRAW}, /* O'Reilly */ {L("ro"), O_READONLY}, /* 4BSD (undocumented) */ {L("scr"), O_SCROLL}, /* 4BSD (undocumented) */ {L("sect"), O_SECTIONS}, /* O'Reilly */ {L("sh"), O_SHELL}, /* 4BSD */ {L("slow"), O_SLOWOPEN}, /* 4BSD */ {L("sm"), O_SHOWMATCH}, /* 4BSD */ {L("smd"), O_SHOWMODE}, /* 4BSD */ {L("sw"), O_SHIFTWIDTH}, /* 4BSD */ {L("tag"), O_TAGS}, /* 4BSD (undocumented) */ {L("tl"), O_TAGLENGTH}, /* 4BSD */ {L("to"), O_TIMEOUT}, /* 4BSD (undocumented) */ {L("ts"), O_TABSTOP}, /* 4BSD */ {L("tty"), O_TERM}, /* 4BSD (undocumented) */ {L("ttytype"), O_TERM}, /* 4BSD (undocumented) */ {L("w"), O_WINDOW}, /* O'Reilly */ {L("wa"), O_WRITEANY}, /* 4BSD */ {L("wi"), O_WINDOW}, /* 4BSD (undocumented) */ {L("wl"), O_WRAPLEN}, /* 4.4BSD */ {L("wm"), O_WRAPMARGIN}, /* 4BSD */ {L("ws"), O_WRAPSCAN}, /* 4BSD */ {NULL}, }; /* * opts_init -- * Initialize some of the options. * * PUBLIC: int opts_init(SCR *, int *); */ int -opts_init( - SCR *sp, - int *oargs) +opts_init(SCR *sp, int *oargs) { ARGS *argv[2], a, b; OPTLIST const *op; u_long v; int cnt, optindx = 0; char *s; CHAR_T b2[1024]; a.bp = b2; b.bp = NULL; a.len = b.len = 0; argv[0] = &a; argv[1] = &b; /* Set numeric and string default values. */ #define OI(indx, str) { \ a.len = STRLEN(str); \ if ((CHAR_T*)str != b2) /* GCC puts strings in text-space. */ \ (void)MEMCPY(b2, str, a.len+1); \ if (opts_set(sp, argv, NULL)) { \ optindx = indx; \ goto err; \ } \ } /* * Indirect global options to global space. Specifically, set up * terminal, lines, columns first, they're used by other options. * Note, don't set the flags until we've set up the indirection. */ if (o_set(sp, O_TERM, 0, NULL, GO_TERM)) goto err; F_SET(&sp->opts[O_TERM], OPT_GLOBAL); if (o_set(sp, O_LINES, 0, NULL, GO_LINES)) goto err; F_SET(&sp->opts[O_LINES], OPT_GLOBAL); if (o_set(sp, O_COLUMNS, 0, NULL, GO_COLUMNS)) goto err; F_SET(&sp->opts[O_COLUMNS], OPT_GLOBAL); if (o_set(sp, O_SECURE, 0, NULL, GO_SECURE)) goto err; F_SET(&sp->opts[O_SECURE], OPT_GLOBAL); /* Initialize string values. */ (void)SPRINTF(b2, SIZE(b2), L("cdpath=%s"), (s = getenv("CDPATH")) == NULL ? ":" : s); OI(O_CDPATH, b2); OI(O_CEDIT, L("cedit=\033")); /* * !!! * Vi historically stored temporary files in /var/tmp. We store them * in /tmp by default, hoping it's a memory based file system. There * are two ways to change this -- the user can set either the directory * option or the TMPDIR environmental variable. */ (void)SPRINTF(b2, SIZE(b2), L("directory=%s"), (s = getenv("TMPDIR")) == NULL ? _PATH_TMP : s); OI(O_TMPDIR, b2); OI(O_ESCAPETIME, L("escapetime=6")); OI(O_FILEC, L("filec=\t")); OI(O_KEYTIME, L("keytime=6")); OI(O_MATCHCHARS, L("matchchars=()[]{}")); OI(O_MATCHTIME, L("matchtime=7")); (void)SPRINTF(b2, SIZE(b2), L("msgcat=%s"), _PATH_MSGCAT); OI(O_MSGCAT, b2); OI(O_REPORT, L("report=5")); OI(O_PARAGRAPHS, L("paragraphs=IPLPPPQPP LIpplpipbp")); (void)SPRINTF(b2, SIZE(b2), L("path=%s"), ""); OI(O_PATH, b2); (void)SPRINTF(b2, SIZE(b2), L("recdir=%s"), _PATH_PRESERVE); OI(O_RECDIR, b2); OI(O_SECTIONS, L("sections=NHSHH HUnhsh")); (void)SPRINTF(b2, SIZE(b2), L("shell=%s"), (s = getenv("SHELL")) == NULL ? _PATH_BSHELL : s); OI(O_SHELL, b2); OI(O_SHELLMETA, L("shellmeta=~{[*?$`'\"\\")); OI(O_SHIFTWIDTH, L("shiftwidth=8")); OI(O_SIDESCROLL, L("sidescroll=16")); OI(O_TABSTOP, L("tabstop=8")); (void)SPRINTF(b2, SIZE(b2), L("tags=%s"), _PATH_TAGS); OI(O_TAGS, b2); /* * XXX * Initialize O_SCROLL here, after term; initializing term should * have created a LINES/COLUMNS value. */ if ((v = (O_VAL(sp, O_LINES) - 1) / 2) == 0) v = 1; (void)SPRINTF(b2, SIZE(b2), L("scroll=%ld"), v); OI(O_SCROLL, b2); /* * The default window option values are: * 8 if baud rate <= 600 * 16 if baud rate <= 1200 * LINES - 1 if baud rate > 1200 * * Note, the windows option code will correct any too-large value * or when the O_LINES value is 1. */ if (sp->gp->scr_baud(sp, &v)) return (1); if (v <= 600) v = 8; else if (v <= 1200) v = 16; else if ((v = O_VAL(sp, O_LINES) - 1) == 0) v = 1; (void)SPRINTF(b2, SIZE(b2), L("window=%lu"), v); OI(O_WINDOW, b2); /* * Set boolean default values, and copy all settings into the default * information. OS_NOFREE is set, we're copying, not replacing. */ for (op = optlist, cnt = 0; op->name != NULL; ++op, ++cnt) { if (F_ISSET(op, OPT_GLOBAL)) continue; switch (op->type) { case OPT_0BOOL: break; case OPT_1BOOL: O_SET(sp, cnt); O_D_SET(sp, cnt); break; case OPT_NUM: o_set(sp, cnt, OS_DEF, NULL, O_VAL(sp, cnt)); break; case OPT_STR: if (O_STR(sp, cnt) != NULL && o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STRDUP, O_STR(sp, cnt), 0)) goto err; break; default: abort(); } } /* * !!! * Some options can be initialized by the command name or the * command-line arguments. They don't set the default values, * it's historic practice. */ for (; *oargs != -1; ++oargs) OI(*oargs, optlist[*oargs].name); #undef OI return (0); err: msgq_wstr(sp, M_ERR, optlist[optindx].name, "031|Unable to set default %s option"); return (1); } /* * opts_set -- * Change the values of one or more options. * * PUBLIC: int opts_set(SCR *, ARGS *[], char *); */ int -opts_set( - SCR *sp, - ARGS *argv[], - char *usage) +opts_set(SCR *sp, ARGS *argv[], char *usage) { enum optdisp disp; enum nresult nret; OPTLIST const *op; OPTION *spo; u_long isset, turnoff, value; int ch, equals, nf, nf2, offset, qmark, rval; CHAR_T *endp, *name, *p, *sep; char *p2, *t2; char *np; size_t nlen; disp = NO_DISPLAY; for (rval = 0; argv[0]->len != 0; ++argv) { /* * The historic vi dumped the options for each occurrence of * "all" in the set list. Puhleeze. */ if (!STRCMP(argv[0]->bp, L("all"))) { disp = ALL_DISPLAY; continue; } /* Find equals sign or question mark. */ for (sep = NULL, equals = qmark = 0, p = name = argv[0]->bp; (ch = *p) != '\0'; ++p) if (ch == '=' || ch == '?') { if (p == name) { if (usage != NULL) msgq(sp, M_ERR, "032|Usage: %s", usage); return (1); } sep = p; if (ch == '=') equals = 1; else qmark = 1; break; } turnoff = 0; op = NULL; if (sep != NULL) *sep++ = '\0'; /* Search for the name, then name without any leading "no". */ if ((op = opts_search(name)) == NULL && name[0] == 'n' && name[1] == 'o') { turnoff = 1; name += 2; op = opts_search(name); } if (op == NULL) { opts_nomatch(sp, name); rval = 1; continue; } /* Find current option values. */ offset = op - optlist; spo = sp->opts + offset; /* * !!! * Historically, the question mark could be a separate * argument. */ if (!equals && !qmark && argv[1]->len == 1 && argv[1]->bp[0] == '?') { ++argv; qmark = 1; } /* Set name, value. */ switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: /* Some options may not be reset. */ if (F_ISSET(op, OPT_NOUNSET) && turnoff) { msgq_wstr(sp, M_ERR, name, "291|set: the %s option may not be turned off"); rval = 1; break; } /* Some options may not be set. */ if (F_ISSET(op, OPT_NOSET) && !turnoff) { msgq_wstr(sp, M_ERR, name, "313|set: the %s option may never be turned on"); rval = 1; break; } if (equals) { msgq_wstr(sp, M_ERR, name, "034|set: [no]%s option doesn't take a value"); rval = 1; break; } if (qmark) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ isset = !turnoff; if (!F_ISSET(op, OPT_ALWAYS)) if (isset) { if (O_ISSET(sp, offset)) break; } else if (!O_ISSET(sp, offset)) break; /* Report to subsystems. */ if ((op->func != NULL && op->func(sp, spo, NULL, &isset)) || ex_optchange(sp, offset, NULL, &isset) || v_optchange(sp, offset, NULL, &isset) || sp->gp->scr_optchange(sp, offset, NULL, &isset)) { rval = 1; break; } /* Set the value. */ if (isset) O_SET(sp, offset); else O_CLR(sp, offset); break; case OPT_NUM: if (turnoff) { msgq_wstr(sp, M_ERR, name, "035|set: %s option isn't a boolean"); rval = 1; break; } if (qmark || !equals) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } if (!ISDIGIT(sep[0])) goto badnum; if ((nret = nget_uslong(&value, sep, &endp, 10)) != NUM_OK) { INT2CHAR(sp, name, STRLEN(name) + 1, np, nlen); p2 = msg_print(sp, np, &nf); INT2CHAR(sp, sep, STRLEN(sep) + 1, np, nlen); t2 = msg_print(sp, np, &nf2); switch (nret) { case NUM_ERR: msgq(sp, M_SYSERR, "036|set: %s option: %s", p2, t2); break; case NUM_OVER: msgq(sp, M_ERR, "037|set: %s option: %s: value overflow", p2, t2); break; case NUM_OK: case NUM_UNDER: abort(); } if (nf) FREE_SPACE(sp, p2, 0); if (nf2) FREE_SPACE(sp, t2, 0); rval = 1; break; } if (*endp && !cmdskip(*endp)) { badnum: INT2CHAR(sp, name, STRLEN(name) + 1, np, nlen); p2 = msg_print(sp, np, &nf); INT2CHAR(sp, sep, STRLEN(sep) + 1, np, nlen); t2 = msg_print(sp, np, &nf2); msgq(sp, M_ERR, "038|set: %s option: %s is an illegal number", p2, t2); if (nf) FREE_SPACE(sp, p2, 0); if (nf2) FREE_SPACE(sp, t2, 0); rval = 1; break; } /* Some options may never be set to zero. */ if (F_ISSET(op, OPT_NOZERO) && value == 0) { msgq_wstr(sp, M_ERR, name, "314|set: the %s option may never be set to 0"); rval = 1; break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ if (!F_ISSET(op, OPT_ALWAYS) && O_VAL(sp, offset) == value) break; /* Report to subsystems. */ INT2CHAR(sp, sep, STRLEN(sep) + 1, np, nlen); if ((op->func != NULL && op->func(sp, spo, np, &value)) || ex_optchange(sp, offset, np, &value) || v_optchange(sp, offset, np, &value) || sp->gp->scr_optchange(sp, offset, np, &value)) { rval = 1; break; } /* Set the value. */ if (o_set(sp, offset, 0, NULL, value)) rval = 1; break; case OPT_STR: if (turnoff) { msgq_wstr(sp, M_ERR, name, "039|set: %s option isn't a boolean"); rval = 1; break; } if (qmark || !equals) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } /* Check for strings that must have even length. */ if (F_ISSET(op, OPT_PAIRS) && STRLEN(sep) & 1) { msgq_wstr(sp, M_ERR, name, "047|The %s option must be in two character groups"); rval = 1; break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ INT2CHAR(sp, sep, STRLEN(sep) + 1, np, nlen); if (!F_ISSET(op, OPT_ALWAYS) && O_STR(sp, offset) != NULL && !strcmp(O_STR(sp, offset), np)) break; /* Report to subsystems. */ if ((op->func != NULL && op->func(sp, spo, np, NULL)) || ex_optchange(sp, offset, np, NULL) || v_optchange(sp, offset, np, NULL) || sp->gp->scr_optchange(sp, offset, np, NULL)) { rval = 1; break; } /* Set the value. */ if (o_set(sp, offset, OS_STRDUP, np, 0)) rval = 1; break; default: abort(); } } if (disp != NO_DISPLAY) opts_dump(sp, disp); return (rval); } /* * o_set -- * Set an option's value. * * PUBLIC: int o_set(SCR *, int, u_int, char *, u_long); */ int -o_set( - SCR *sp, - int opt, - u_int flags, - char *str, - u_long val) +o_set(SCR *sp, int opt, u_int flags, char *str, u_long val) { OPTION *op; /* Set a pointer to the options area. */ op = F_ISSET(&sp->opts[opt], OPT_GLOBAL) ? &sp->gp->opts[sp->opts[opt].o_cur.val] : &sp->opts[opt]; /* Copy the string, if requested. */ if (LF_ISSET(OS_STRDUP) && (str = strdup(str)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* Free the previous string, if requested, and set the value. */ if LF_ISSET(OS_DEF) if (LF_ISSET(OS_STR | OS_STRDUP)) { - if (!LF_ISSET(OS_NOFREE) && op->o_def.str != NULL) + if (!LF_ISSET(OS_NOFREE)) free(op->o_def.str); op->o_def.str = str; } else op->o_def.val = val; else if (LF_ISSET(OS_STR | OS_STRDUP)) { - if (!LF_ISSET(OS_NOFREE) && op->o_cur.str != NULL) + if (!LF_ISSET(OS_NOFREE)) free(op->o_cur.str); op->o_cur.str = str; } else op->o_cur.val = val; return (0); } /* * opts_empty -- * Return 1 if the string option is invalid, 0 if it's OK. * * PUBLIC: int opts_empty(SCR *, int, int); */ int -opts_empty( - SCR *sp, - int off, - int silent) +opts_empty(SCR *sp, int off, int silent) { char *p; if ((p = O_STR(sp, off)) == NULL || p[0] == '\0') { if (!silent) msgq_wstr(sp, M_ERR, optlist[off].name, "305|No %s edit option specified"); return (1); } return (0); } /* * opts_dump -- * List the current values of selected options. * * PUBLIC: void opts_dump(SCR *, enum optdisp); */ void -opts_dump( - SCR *sp, - enum optdisp type) +opts_dump(SCR *sp, enum optdisp type) { OPTLIST const *op; int base, b_num, cnt, col, colwidth, curlen, s_num; int numcols, numrows, row; int b_op[O_OPTIONCOUNT], s_op[O_OPTIONCOUNT]; char nbuf[20]; /* * Options are output in two groups -- those that fit in a column and * those that don't. Output is done on 6 character "tab" boundaries * for no particular reason. (Since we don't output tab characters, * we can ignore the terminal's tab settings.) Ignore the user's tab * setting because we have no idea how reasonable it is. * * Find a column width we can live with, testing from 10 columns to 1. */ for (numcols = 10; numcols > 1; --numcols) { colwidth = sp->cols / numcols & ~(STANDARD_TAB - 1); if (colwidth >= 10) { colwidth = (colwidth + STANDARD_TAB) & ~(STANDARD_TAB - 1); numcols = sp->cols / colwidth; break; } colwidth = 0; } /* * Get the set of options to list, entering them into * the column list or the overflow list. */ for (b_num = s_num = 0, op = optlist; op->name != NULL; ++op) { cnt = op - optlist; /* If OPT_NDISP set, it's never displayed. */ if (F_ISSET(op, OPT_NDISP)) continue; switch (type) { case ALL_DISPLAY: /* Display all. */ break; case CHANGED_DISPLAY: /* Display changed. */ /* If OPT_ADISP set, it's always "changed". */ if (F_ISSET(op, OPT_ADISP)) break; switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: case OPT_NUM: if (O_VAL(sp, cnt) == O_D_VAL(sp, cnt)) continue; break; case OPT_STR: if (O_STR(sp, cnt) == O_D_STR(sp, cnt) || (O_D_STR(sp, cnt) != NULL && !strcmp(O_STR(sp, cnt), O_D_STR(sp, cnt)))) continue; break; } break; case SELECT_DISPLAY: /* Display selected. */ if (!F_ISSET(&sp->opts[cnt], OPT_SELECTED)) continue; break; default: case NO_DISPLAY: abort(); } F_CLR(&sp->opts[cnt], OPT_SELECTED); curlen = STRLEN(op->name); switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: if (!O_ISSET(sp, cnt)) curlen += 2; break; case OPT_NUM: (void)snprintf(nbuf, sizeof(nbuf), "%ld", O_VAL(sp, cnt)); curlen += strlen(nbuf); break; case OPT_STR: if (O_STR(sp, cnt) != NULL) curlen += strlen(O_STR(sp, cnt)); curlen += 3; break; } /* Offset by 2 so there's a gap. */ if (curlen <= colwidth - 2) s_op[s_num++] = cnt; else b_op[b_num++] = cnt; } if (s_num > 0) { /* Figure out the number of rows. */ if (s_num > numcols) { numrows = s_num / numcols; if (s_num % numcols) ++numrows; } else numrows = 1; /* Display the options in sorted order. */ for (row = 0; row < numrows;) { for (base = row, col = 0; col < numcols; ++col) { cnt = opts_print(sp, &optlist[s_op[base]]); if ((base += numrows) >= s_num) break; (void)ex_printf(sp, "%*s", (int)(colwidth - cnt), ""); } if (++row < numrows || b_num) (void)ex_puts(sp, "\n"); } } for (row = 0; row < b_num;) { (void)opts_print(sp, &optlist[b_op[row]]); if (++row < b_num) (void)ex_puts(sp, "\n"); } (void)ex_puts(sp, "\n"); } /* * opts_print -- * Print out an option. */ static int -opts_print( - SCR *sp, - OPTLIST const *op) +opts_print(SCR *sp, OPTLIST const *op) { int curlen, offset; curlen = 0; offset = op - optlist; switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: curlen += ex_printf(sp, "%s"WS, O_ISSET(sp, offset) ? "" : "no", op->name); break; case OPT_NUM: curlen += ex_printf(sp, WS"=%ld", op->name, O_VAL(sp, offset)); break; case OPT_STR: curlen += ex_printf(sp, WS"=\"%s\"", op->name, O_STR(sp, offset) == NULL ? "" : O_STR(sp, offset)); break; } return (curlen); } /* * opts_save -- * Write the current configuration to a file. * * PUBLIC: int opts_save(SCR *, FILE *); */ int -opts_save( - SCR *sp, - FILE *fp) +opts_save(SCR *sp, FILE *fp) { OPTLIST const *op; CHAR_T ch, *p; char nch, *np; int cnt; for (op = optlist; op->name != NULL; ++op) { if (F_ISSET(op, OPT_NOSAVE)) continue; cnt = op - optlist; switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: if (O_ISSET(sp, cnt)) (void)fprintf(fp, "set "WS"\n", op->name); else (void)fprintf(fp, "set no"WS"\n", op->name); break; case OPT_NUM: (void)fprintf(fp, "set "WS"=%-3ld\n", op->name, O_VAL(sp, cnt)); break; case OPT_STR: if (O_STR(sp, cnt) == NULL) break; (void)fprintf(fp, "set "); for (p = op->name; (ch = *p) != '\0'; ++p) { if (cmdskip(ch) || ch == '\\') (void)putc('\\', fp); fprintf(fp, WC, ch); } (void)putc('=', fp); for (np = O_STR(sp, cnt); (nch = *np) != '\0'; ++np) { if (cmdskip(nch) || nch == '\\') (void)putc('\\', fp); (void)putc(nch, fp); } (void)putc('\n', fp); break; } if (ferror(fp)) { msgq(sp, M_SYSERR, NULL); return (1); } } return (0); } /* * opts_search -- * Search for an option. * * PUBLIC: OPTLIST const *opts_search(CHAR_T *); */ OPTLIST const * opts_search(CHAR_T *name) { OPTLIST const *op, *found; OABBREV atmp, *ap; OPTLIST otmp; size_t len; /* Check list of abbreviations. */ atmp.name = name; if ((ap = bsearch(&atmp, abbrev, sizeof(abbrev) / sizeof(OABBREV) - 1, sizeof(OABBREV), opts_abbcmp)) != NULL) return (optlist + ap->offset); /* Check list of options. */ otmp.name = name; if ((op = bsearch(&otmp, optlist, sizeof(optlist) / sizeof(OPTLIST) - 1, sizeof(OPTLIST), opts_cmp)) != NULL) return (op); /* * Check to see if the name is the prefix of one (and only one) * option. If so, return the option. */ len = STRLEN(name); for (found = NULL, op = optlist; op->name != NULL; ++op) { if (op->name[0] < name[0]) continue; if (op->name[0] > name[0]) break; if (!MEMCMP(op->name, name, len)) { if (found != NULL) return (NULL); found = op; } } return (found); } /* * opts_nomatch -- * Standard nomatch error message for options. * * PUBLIC: void opts_nomatch(SCR *, CHAR_T *); */ void -opts_nomatch( - SCR *sp, - CHAR_T *name) +opts_nomatch(SCR *sp, CHAR_T *name) { msgq_wstr(sp, M_ERR, name, "033|set: no %s option: 'set all' gives all option values"); } static int -opts_abbcmp( - const void *a, - const void *b) +opts_abbcmp(const void *a, const void *b) { return(STRCMP(((OABBREV *)a)->name, ((OABBREV *)b)->name)); } static int -opts_cmp( - const void *a, - const void *b) +opts_cmp(const void *a, const void *b) { return(STRCMP(((OPTLIST *)a)->name, ((OPTLIST *)b)->name)); } /* * opts_copy -- * Copy a screen's OPTION array. * * PUBLIC: int opts_copy(SCR *, SCR *); */ int -opts_copy( - SCR *orig, - SCR *sp) +opts_copy(SCR *orig, SCR *sp) { int cnt, rval; /* Copy most everything without change. */ memcpy(sp->opts, orig->opts, sizeof(orig->opts)); /* Copy the string edit options. */ for (cnt = rval = 0; cnt < O_OPTIONCOUNT; ++cnt) { if (optlist[cnt].type != OPT_STR || F_ISSET(&sp->opts[cnt], OPT_GLOBAL)) continue; /* * If never set, or already failed, NULL out the entries -- * have to continue after failure, otherwise would have two * screens referencing the same memory. */ if (rval || O_STR(sp, cnt) == NULL) { o_set(sp, cnt, OS_NOFREE | OS_STR, NULL, 0); o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STR, NULL, 0); continue; } /* Copy the current string. */ if (o_set(sp, cnt, OS_NOFREE | OS_STRDUP, O_STR(sp, cnt), 0)) { o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STR, NULL, 0); goto nomem; } /* Copy the default string. */ if (O_D_STR(sp, cnt) != NULL && o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STRDUP, O_D_STR(sp, cnt), 0)) { nomem: msgq(orig, M_SYSERR, NULL); rval = 1; } } return (rval); } /* * opts_free -- * Free all option strings * * PUBLIC: void opts_free(SCR *); */ void opts_free(SCR *sp) { int cnt; for (cnt = 0; cnt < O_OPTIONCOUNT; ++cnt) { if (optlist[cnt].type != OPT_STR || F_ISSET(&sp->opts[cnt], OPT_GLOBAL)) continue; - if (O_STR(sp, cnt) != NULL) - free(O_STR(sp, cnt)); - if (O_D_STR(sp, cnt) != NULL) - free(O_D_STR(sp, cnt)); + free(O_STR(sp, cnt)); + free(O_D_STR(sp, cnt)); } } Index: head/contrib/nvi/common/options.h =================================================================== --- head/contrib/nvi/common/options.h (revision 365498) +++ head/contrib/nvi/common/options.h (revision 365499) @@ -1,102 +1,100 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: options.h,v 10.21 2012/02/10 20:24:58 zy Exp $ */ /* * Edit option information. Historically, if you set a boolean or numeric * edit option value to its "default" value, it didn't show up in the :set * display, i.e. it wasn't considered "changed". String edit options would * show up as changed, regardless. We maintain a parallel set of values * which are the default values and never consider an edit option changed * if it was reset to the default value. * * Macros to retrieve boolean, integral and string option values, and to * set, clear and test boolean option values. Some options (secure, lines, * columns, terminal type) are global in scope, and are therefore stored * in the global area. The offset in the global options array is stored * in the screen's value field. This is set up when the options are first * initialized. */ #define O_V(sp, o, fld) \ (F_ISSET(&(sp)->opts[(o)], OPT_GLOBAL) ? \ (sp)->gp->opts[(sp)->opts[(o)].o_cur.val].fld : \ (sp)->opts[(o)].fld) /* Global option macros. */ #define OG_CLR(gp, o) ((gp)->opts[(o)].o_cur.val) = 0 #define OG_SET(gp, o) ((gp)->opts[(o)].o_cur.val) = 1 #define OG_STR(gp, o) ((gp)->opts[(o)].o_cur.str) #define OG_VAL(gp, o) ((gp)->opts[(o)].o_cur.val) #define OG_ISSET(gp, o) OG_VAL(gp, o) #define OG_D_STR(gp, o) ((gp)->opts[(o)].o_def.str) #define OG_D_VAL(gp, o) ((gp)->opts[(o)].o_def.val) /* * Flags to o_set(); need explicit OS_STR as can be setting the value to * NULL. */ #define OS_DEF 0x01 /* Set the default value. */ #define OS_NOFREE 0x02 /* Don't free the old string. */ #define OS_STR 0x04 /* Set to string argument. */ #define OS_STRDUP 0x08 /* Copy then set to string argument. */ struct _option { union { u_long val; /* Value or boolean. */ char *str; /* String. */ } o_cur; #define O_CLR(sp, o) o_set(sp, o, 0, NULL, 0) #define O_SET(sp, o) o_set(sp, o, 0, NULL, 1) #define O_STR(sp, o) O_V(sp, o, o_cur.str) #define O_VAL(sp, o) O_V(sp, o, o_cur.val) #define O_ISSET(sp, o) O_VAL(sp, o) union { u_long val; /* Value or boolean. */ char *str; /* String. */ } o_def; #define O_D_CLR(sp, o) o_set(sp, o, OS_DEF, NULL, 0) #define O_D_SET(sp, o) o_set(sp, o, OS_DEF, NULL, 1) #define O_D_STR(sp, o) O_V(sp, o, o_def.str) #define O_D_VAL(sp, o) O_V(sp, o, o_def.val) #define O_D_ISSET(sp, o) O_D_VAL(sp, o) #define OPT_GLOBAL 0x01 /* Option is global. */ #define OPT_SELECTED 0x02 /* Selected for display. */ u_int8_t flags; }; /* List of option names, associated update functions and information. */ struct _optlist { CHAR_T *name; /* Name. */ /* Change function. */ int (*func)(SCR *, OPTION *, char *, u_long *); /* Type of object. */ enum { OPT_0BOOL, OPT_1BOOL, OPT_NUM, OPT_STR } type; #define OPT_ADISP 0x001 /* Always display the option. */ #define OPT_ALWAYS 0x002 /* Always call the support function. */ #define OPT_NDISP 0x004 /* Never display the option. */ #define OPT_NOSAVE 0x008 /* Mkexrc command doesn't save. */ #define OPT_NOSET 0x010 /* Option may not be set. */ #define OPT_NOUNSET 0x020 /* Option may not be unset. */ #define OPT_NOZERO 0x040 /* Option may not be set to 0. */ #define OPT_PAIRS 0x080 /* String with even length. */ u_int8_t flags; }; /* Option argument to opts_dump(). */ enum optdisp { NO_DISPLAY, ALL_DISPLAY, CHANGED_DISPLAY, SELECT_DISPLAY }; /* Options array. */ extern OPTLIST const optlist[]; #include "options_def.h" Index: head/contrib/nvi/common/options_def.h =================================================================== --- head/contrib/nvi/common/options_def.h (revision 365498) +++ head/contrib/nvi/common/options_def.h (revision 365499) @@ -1,83 +1,84 @@ #define O_ALTWERASE 0 #define O_AUTOINDENT 1 #define O_AUTOPRINT 2 #define O_AUTOWRITE 3 #define O_BACKUP 4 #define O_BEAUTIFY 5 #define O_CDPATH 6 #define O_CEDIT 7 #define O_COLUMNS 8 #define O_COMBINED 9 #define O_COMMENT 10 #define O_TMPDIR 11 #define O_EDCOMPATIBLE 12 #define O_ERRORBELLS 13 #define O_ESCAPETIME 14 -#define O_EXRC 15 -#define O_EXTENDED 16 -#define O_FILEC 17 -#define O_FILEENCODING 18 -#define O_FLASH 19 -#define O_HARDTABS 20 -#define O_ICLOWER 21 -#define O_IGNORECASE 22 -#define O_INPUTENCODING 23 -#define O_KEYTIME 24 -#define O_LEFTRIGHT 25 -#define O_LINES 26 -#define O_LISP 27 -#define O_LIST 28 -#define O_LOCKFILES 29 -#define O_MAGIC 30 -#define O_MATCHCHARS 31 -#define O_MATCHTIME 32 -#define O_MESG 33 -#define O_MODELINE 34 -#define O_MSGCAT 35 -#define O_NOPRINT 36 -#define O_NUMBER 37 -#define O_OCTAL 38 -#define O_OPEN 39 -#define O_OPTIMIZE 40 -#define O_PARAGRAPHS 41 -#define O_PATH 42 -#define O_PRINT 43 -#define O_PROMPT 44 -#define O_READONLY 45 -#define O_RECDIR 46 -#define O_REDRAW 47 -#define O_REMAP 48 -#define O_REPORT 49 -#define O_RULER 50 -#define O_SCROLL 51 -#define O_SEARCHINCR 52 -#define O_SECTIONS 53 -#define O_SECURE 54 -#define O_SHELL 55 -#define O_SHELLMETA 56 -#define O_SHIFTWIDTH 57 -#define O_SHOWMATCH 58 -#define O_SHOWMODE 59 -#define O_SIDESCROLL 60 -#define O_SLOWOPEN 61 -#define O_SOURCEANY 62 -#define O_TABSTOP 63 -#define O_TAGLENGTH 64 -#define O_TAGS 65 -#define O_TERM 66 -#define O_TERSE 67 -#define O_TILDEOP 68 -#define O_TIMEOUT 69 -#define O_TTYWERASE 70 -#define O_VERBOSE 71 -#define O_W1200 72 -#define O_W300 73 -#define O_W9600 74 -#define O_WARN 75 -#define O_WINDOW 76 -#define O_WINDOWNAME 77 -#define O_WRAPLEN 78 -#define O_WRAPMARGIN 79 -#define O_WRAPSCAN 80 -#define O_WRITEANY 81 -#define O_OPTIONCOUNT 82 +#define O_EXPANDTAB 15 +#define O_EXRC 16 +#define O_EXTENDED 17 +#define O_FILEC 18 +#define O_FILEENCODING 19 +#define O_FLASH 20 +#define O_HARDTABS 21 +#define O_ICLOWER 22 +#define O_IGNORECASE 23 +#define O_INPUTENCODING 24 +#define O_KEYTIME 25 +#define O_LEFTRIGHT 26 +#define O_LINES 27 +#define O_LISP 28 +#define O_LIST 29 +#define O_LOCKFILES 30 +#define O_MAGIC 31 +#define O_MATCHCHARS 32 +#define O_MATCHTIME 33 +#define O_MESG 34 +#define O_MODELINE 35 +#define O_MSGCAT 36 +#define O_NOPRINT 37 +#define O_NUMBER 38 +#define O_OCTAL 39 +#define O_OPEN 40 +#define O_OPTIMIZE 41 +#define O_PARAGRAPHS 42 +#define O_PATH 43 +#define O_PRINT 44 +#define O_PROMPT 45 +#define O_READONLY 46 +#define O_RECDIR 47 +#define O_REDRAW 48 +#define O_REMAP 49 +#define O_REPORT 50 +#define O_RULER 51 +#define O_SCROLL 52 +#define O_SEARCHINCR 53 +#define O_SECTIONS 54 +#define O_SECURE 55 +#define O_SHELL 56 +#define O_SHELLMETA 57 +#define O_SHIFTWIDTH 58 +#define O_SHOWMATCH 59 +#define O_SHOWMODE 60 +#define O_SIDESCROLL 61 +#define O_SLOWOPEN 62 +#define O_SOURCEANY 63 +#define O_TABSTOP 64 +#define O_TAGLENGTH 65 +#define O_TAGS 66 +#define O_TERM 67 +#define O_TERSE 68 +#define O_TILDEOP 69 +#define O_TIMEOUT 70 +#define O_TTYWERASE 71 +#define O_VERBOSE 72 +#define O_W1200 73 +#define O_W300 74 +#define O_W9600 75 +#define O_WARN 76 +#define O_WINDOW 77 +#define O_WINDOWNAME 78 +#define O_WRAPLEN 79 +#define O_WRAPMARGIN 80 +#define O_WRAPSCAN 81 +#define O_WRITEANY 82 +#define O_OPTIONCOUNT 83 Index: head/contrib/nvi/common/options_f.c =================================================================== --- head/contrib/nvi/common/options_f.c (revision 365498) +++ head/contrib/nvi/common/options_f.c (revision 365499) @@ -1,357 +1,293 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: options_f.c,v 10.34 04/07/11 16:06:29 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include "common.h" /* * PUBLIC: int f_altwerase(SCR *, OPTION *, char *, u_long *); */ int -f_altwerase( - SCR *sp, - OPTION *op, - char *str, - u_long *valp) +f_altwerase(SCR *sp, OPTION *op, char *str, u_long *valp) { if (*valp) O_CLR(sp, O_TTYWERASE); return (0); } /* * PUBLIC: int f_columns(SCR *, OPTION *, char *, u_long *); */ int -f_columns( - SCR *sp, - OPTION *op, - char *str, - u_long *valp) +f_columns(SCR *sp, OPTION *op, char *str, u_long *valp) { /* Validate the number. */ if (*valp < MINIMUM_SCREEN_COLS) { msgq(sp, M_ERR, "040|Screen columns too small, less than %d", MINIMUM_SCREEN_COLS); return (1); } /* * !!! * It's not uncommon for allocation of huge chunks of memory to cause * core dumps on various systems. So, we prune out numbers that are * "obviously" wrong. Vi will not work correctly if it has the wrong * number of lines/columns for the screen, but at least we don't drop * core. */ #define MAXIMUM_SCREEN_COLS 500 if (*valp > MAXIMUM_SCREEN_COLS) { msgq(sp, M_ERR, "041|Screen columns too large, greater than %d", MAXIMUM_SCREEN_COLS); return (1); } return (0); } /* * PUBLIC: int f_lines(SCR *, OPTION *, char *, u_long *); */ int -f_lines( - SCR *sp, - OPTION *op, - char *str, - u_long *valp) +f_lines(SCR *sp, OPTION *op, char *str, u_long *valp) { /* Validate the number. */ if (*valp < MINIMUM_SCREEN_ROWS) { msgq(sp, M_ERR, "042|Screen lines too small, less than %d", MINIMUM_SCREEN_ROWS); return (1); } /* * !!! * It's not uncommon for allocation of huge chunks of memory to cause * core dumps on various systems. So, we prune out numbers that are * "obviously" wrong. Vi will not work correctly if it has the wrong * number of lines/columns for the screen, but at least we don't drop * core. */ #define MAXIMUM_SCREEN_ROWS 500 if (*valp > MAXIMUM_SCREEN_ROWS) { msgq(sp, M_ERR, "043|Screen lines too large, greater than %d", MAXIMUM_SCREEN_ROWS); return (1); } /* * Set the value, and the related scroll value. If no window * value set, set a new default window. */ o_set(sp, O_LINES, 0, NULL, *valp); if (*valp == 1) { sp->defscroll = 1; if (O_VAL(sp, O_WINDOW) == O_D_VAL(sp, O_WINDOW) || O_VAL(sp, O_WINDOW) > *valp) { o_set(sp, O_WINDOW, 0, NULL, 1); o_set(sp, O_WINDOW, OS_DEF, NULL, 1); } } else { sp->defscroll = (*valp - 1) / 2; if (O_VAL(sp, O_WINDOW) == O_D_VAL(sp, O_WINDOW) || O_VAL(sp, O_WINDOW) > *valp) { o_set(sp, O_WINDOW, 0, NULL, *valp - 1); o_set(sp, O_WINDOW, OS_DEF, NULL, *valp - 1); } } return (0); } /* * PUBLIC: int f_lisp(SCR *, OPTION *, char *, u_long *); */ int -f_lisp( - SCR *sp, - OPTION *op, - char *str, - u_long *valp) +f_lisp(SCR *sp, OPTION *op, char *str, u_long *valp) { msgq(sp, M_ERR, "044|The lisp option is not implemented"); return (0); } /* * PUBLIC: int f_msgcat(SCR *, OPTION *, char *, u_long *); */ int -f_msgcat( - SCR *sp, - OPTION *op, - char *str, - u_long *valp) +f_msgcat(SCR *sp, OPTION *op, char *str, u_long *valp) { (void)msg_open(sp, str); return (0); } /* * PUBLIC: int f_print(SCR *, OPTION *, char *, u_long *); */ int -f_print( - SCR *sp, - OPTION *op, - char *str, - u_long *valp) +f_print(SCR *sp, OPTION *op, char *str, u_long *valp) { int offset = op - sp->opts; /* Preset the value, needed for reinitialization of lookup table. */ if (offset == O_OCTAL) { if (*valp) O_SET(sp, offset); else O_CLR(sp, offset); } else if (o_set(sp, offset, OS_STRDUP, str, 0)) return(1); /* Reinitialize the key fast lookup table. */ v_key_ilookup(sp); /* Reformat the screen. */ F_SET(sp, SC_SCR_REFORMAT); return (0); } /* * PUBLIC: int f_readonly(SCR *, OPTION *, char *, u_long *); */ int -f_readonly( - SCR *sp, - OPTION *op, - char *str, - u_long *valp) +f_readonly(SCR *sp, OPTION *op, char *str, u_long *valp) { /* * !!! * See the comment in exf.c. */ if (*valp) F_SET(sp, SC_READONLY); else F_CLR(sp, SC_READONLY); return (0); } /* * PUBLIC: int f_recompile(SCR *, OPTION *, char *, u_long *); */ int -f_recompile( - SCR *sp, - OPTION *op, - char *str, - u_long *valp) +f_recompile(SCR *sp, OPTION *op, char *str, u_long *valp) { if (F_ISSET(sp, SC_RE_SEARCH)) { regfree(&sp->re_c); F_CLR(sp, SC_RE_SEARCH); } if (F_ISSET(sp, SC_RE_SUBST)) { regfree(&sp->subre_c); F_CLR(sp, SC_RE_SUBST); } return (0); } /* * PUBLIC: int f_reformat(SCR *, OPTION *, char *, u_long *); */ int -f_reformat( - SCR *sp, - OPTION *op, - char *str, - u_long *valp) +f_reformat(SCR *sp, OPTION *op, char *str, u_long *valp) { F_SET(sp, SC_SCR_REFORMAT); return (0); } /* * PUBLIC: int f_ttywerase(SCR *, OPTION *, char *, u_long *); */ int -f_ttywerase( - SCR *sp, - OPTION *op, - char *str, - u_long *valp) +f_ttywerase(SCR *sp, OPTION *op, char *str, u_long *valp) { if (*valp) O_CLR(sp, O_ALTWERASE); return (0); } /* * PUBLIC: int f_w300(SCR *, OPTION *, char *, u_long *); */ int -f_w300( - SCR *sp, - OPTION *op, - char *str, - u_long *valp) +f_w300(SCR *sp, OPTION *op, char *str, u_long *valp) { u_long v; /* Historical behavior for w300 was < 1200. */ if (sp->gp->scr_baud(sp, &v)) return (1); if (v >= 1200) return (0); return (f_window(sp, op, str, valp)); } /* * PUBLIC: int f_w1200(SCR *, OPTION *, char *, u_long *); */ int -f_w1200( - SCR *sp, - OPTION *op, - char *str, - u_long *valp) +f_w1200(SCR *sp, OPTION *op, char *str, u_long *valp) { u_long v; /* Historical behavior for w1200 was == 1200. */ if (sp->gp->scr_baud(sp, &v)) return (1); if (v < 1200 || v > 4800) return (0); return (f_window(sp, op, str, valp)); } /* * PUBLIC: int f_w9600(SCR *, OPTION *, char *, u_long *); */ int -f_w9600( - SCR *sp, - OPTION *op, - char *str, - u_long *valp) +f_w9600(SCR *sp, OPTION *op, char *str, u_long *valp) { u_long v; /* Historical behavior for w9600 was > 1200. */ if (sp->gp->scr_baud(sp, &v)) return (1); if (v <= 4800) return (0); return (f_window(sp, op, str, valp)); } /* * PUBLIC: int f_window(SCR *, OPTION *, char *, u_long *); */ int -f_window( - SCR *sp, - OPTION *op, - char *str, - u_long *valp) +f_window(SCR *sp, OPTION *op, char *str, u_long *valp) { if (*valp >= O_VAL(sp, O_LINES) - 1 && (*valp = O_VAL(sp, O_LINES) - 1) == 0) *valp = 1; return (0); } /* * PUBLIC: int f_encoding(SCR *, OPTION *, char *, u_long *); */ int -f_encoding( - SCR *sp, - OPTION *op, - char *str, - u_long *valp) +f_encoding(SCR *sp, OPTION *op, char *str, u_long *valp) { int offset = op - sp->opts; return conv_enc(sp, offset, str); } Index: head/contrib/nvi/common/put.c =================================================================== --- head/contrib/nvi/common/put.c (revision 365498) +++ head/contrib/nvi/common/put.c (revision 365499) @@ -1,233 +1,223 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: put.c,v 10.19 04/07/11 17:00:24 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "common.h" /* * put -- * Put text buffer contents into the file. * * PUBLIC: int put(SCR *, CB *, CHAR_T *, MARK *, MARK *, int); */ int -put( - SCR *sp, - CB *cbp, - CHAR_T *namep, - MARK *cp, - MARK *rp, - int append) +put(SCR *sp, CB *cbp, CHAR_T *namep, MARK *cp, MARK *rp, int append) { CHAR_T name; TEXT *ltp, *tp; recno_t lno; size_t blen, clen, len; int rval; CHAR_T *bp, *t; CHAR_T *p; if (cbp == NULL) if (namep == NULL) { cbp = sp->gp->dcbp; if (cbp == NULL) { msgq(sp, M_ERR, "053|The default buffer is empty"); return (1); } } else { name = *namep; CBNAME(sp, cbp, name); if (cbp == NULL) { msgq(sp, M_ERR, "054|Buffer %s is empty", KEY_NAME(sp, name)); return (1); } } tp = TAILQ_FIRST(cbp->textq); /* * It's possible to do a put into an empty file, meaning that the cut * buffer simply becomes the file. It's a special case so that we can * ignore it in general. * * !!! * Historically, pasting into a file with no lines in vi would preserve * the single blank line. This is surely a result of the fact that the * historic vi couldn't deal with a file that had no lines in it. This * implementation treats that as a bug, and does not retain the blank * line. * * Historical practice is that the cursor ends at the first character * in the file. */ if (cp->lno == 1) { if (db_last(sp, &lno)) return (1); if (lno == 0) { for (; tp != NULL; ++lno, ++sp->rptlines[L_ADDED], tp = TAILQ_NEXT(tp, q)) if (db_append(sp, 1, lno, tp->lb, tp->len)) return (1); rp->lno = 1; rp->cno = 0; return (0); } } /* If a line mode buffer, append each new line into the file. */ if (F_ISSET(cbp, CB_LMODE)) { lno = append ? cp->lno : cp->lno - 1; rp->lno = lno + 1; for (; tp != NULL; ++lno, ++sp->rptlines[L_ADDED], tp = TAILQ_NEXT(tp, q)) if (db_append(sp, 1, lno, tp->lb, tp->len)) return (1); rp->cno = 0; (void)nonblank(sp, rp->lno, &rp->cno); return (0); } /* * If buffer was cut in character mode, replace the current line with * one built from the portion of the first line to the left of the * split plus the first line in the CB. Append each intermediate line * in the CB. Append a line built from the portion of the first line * to the right of the split plus the last line in the CB. * * Get the first line. */ lno = cp->lno; if (db_get(sp, lno, DBG_FATAL, &p, &len)) return (1); GET_SPACE_RETW(sp, bp, blen, tp->len + len + 1); t = bp; /* Original line, left of the split. */ if (len > 0 && (clen = cp->cno + (append ? 1 : 0)) > 0) { MEMCPY(bp, p, clen); p += clen; t += clen; } /* First line from the CB. */ if (tp->len != 0) { MEMCPY(t, tp->lb, tp->len); t += tp->len; } /* Calculate length left in the original line. */ clen = len == 0 ? 0 : len - (cp->cno + (append ? 1 : 0)); /* * !!! * In the historical 4BSD version of vi, character mode puts within * a single line have two cursor behaviors: if the put is from the * unnamed buffer, the cursor moves to the character inserted which * appears last in the file. If the put is from a named buffer, * the cursor moves to the character inserted which appears first * in the file. In System III/V, it was changed at some point and * the cursor always moves to the first character. In both versions * of vi, character mode puts that cross line boundaries leave the * cursor on the first character. Nvi implements the System III/V * behavior, and expect POSIX.2 to do so as well. */ rp->lno = lno; rp->cno = len == 0 ? 0 : sp->cno + (append && tp->len ? 1 : 0); /* * If no more lines in the CB, append the rest of the original * line and quit. Otherwise, build the last line before doing * the intermediate lines, because the line changes will lose * the cached line. */ if (TAILQ_NEXT(tp, q) == NULL) { if (clen > 0) { MEMCPY(t, p, clen); t += clen; } if (db_set(sp, lno, bp, t - bp)) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } } else { /* * Have to build both the first and last lines of the * put before doing any sets or we'll lose the cached * line. Build both the first and last lines in the * same buffer, so we don't have to have another buffer * floating around. * * Last part of original line; check for space, reset * the pointer into the buffer. */ ltp = TAILQ_LAST(cbp->textq, _texth); len = t - bp; ADD_SPACE_RETW(sp, bp, blen, ltp->len + clen); t = bp + len; /* Add in last part of the CB. */ MEMCPY(t, ltp->lb, ltp->len); if (clen) MEMCPY(t + ltp->len, p, clen); clen += ltp->len; /* * Now: bp points to the first character of the first * line, t points to the last character of the last * line, t - bp is the length of the first line, and * clen is the length of the last. Just figured you'd * want to know. * * Output the line replacing the original line. */ if (db_set(sp, lno, bp, t - bp)) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } /* Output any intermediate lines in the CB. */ for (tp = TAILQ_NEXT(tp, q); TAILQ_NEXT(tp, q) != NULL; ++lno, ++sp->rptlines[L_ADDED], tp = TAILQ_NEXT(tp, q)) if (db_append(sp, 1, lno, tp->lb, tp->len)) goto err; if (db_append(sp, 1, lno, t, clen)) goto err; ++sp->rptlines[L_ADDED]; } rval = 0; if (0) err: rval = 1; FREE_SPACEW(sp, bp, blen); return (rval); } Index: head/contrib/nvi/common/recover.c =================================================================== --- head/contrib/nvi/common/recover.c (revision 365498) +++ head/contrib/nvi/common/recover.c (revision 365499) @@ -1,973 +1,941 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: recover.c,v 11.3 2015/04/04 03:50:42 zy Exp $"; -#endif /* not lint */ - #include #include #include /* * We include , because the open #defines were found there * on historical systems. We also include because the open(2) * #defines are found there on newer systems. */ #include #include #include #include #include #include #include #include /* Required by resolv.h. */ #include #include #include #include #include #include #include "../ex/version.h" #include "common.h" #include "pathnames.h" /* * Recovery code. * * The basic scheme is as follows. In the EXF structure, we maintain full * paths of a b+tree file and a mail recovery file. The former is the file * used as backing store by the DB package. The latter is the file that * contains an email message to be sent to the user if we crash. The two * simple states of recovery are: * * + first starting the edit session: * the b+tree file exists and is mode 700, the mail recovery * file doesn't exist. * + after the file has been modified: * the b+tree file exists and is mode 600, the mail recovery * file exists, and is exclusively locked. * * In the EXF structure we maintain a file descriptor that is the locked * file descriptor for the mail recovery file. * * To find out if a recovery file/backing file pair are in use, try to get * a lock on the recovery file. * * To find out if a backing file can be deleted at boot time, check for an * owner execute bit. (Yes, I know it's ugly, but it's either that or put * special stuff into the backing file itself, or correlate the files at * boot time, neither of which looks like fun.) Note also that there's a * window between when the file is created and the X bit is set. It's small, * but it's there. To fix the window, check for 0 length files as well. * * To find out if a file can be recovered, check the F_RCV_ON bit. Note, * this DOES NOT mean that any initialization has been done, only that we * haven't yet failed at setting up or doing recovery. * * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit. * If that bit is not set when ending a file session: * If the EXF structure paths (rcv_path and rcv_mpath) are not NULL, * they are unlink(2)'d, and free(3)'d. * If the EXF file descriptor (rcv_fd) is not -1, it is closed. * * The backing b+tree file is set up when a file is first edited, so that * the DB package can use it for on-disk caching and/or to snapshot the * file. When the file is first modified, the mail recovery file is created, * the backing file permissions are updated, the file is sync(2)'d to disk, * and the timer is started. Then, at RCV_PERIOD second intervals, the * b+tree file is synced to disk. RCV_PERIOD is measured using SIGALRM, which * means that the data structures (SCR, EXF, the underlying tree structures) * must be consistent when the signal arrives. * * The recovery mail file contains normal mail headers, with two additional * * X-vi-data: ; * * MIME headers; the folding character is limited to ' '. * * Btree files are named "vi.XXXXXX" and recovery files are named * "recover.XXXXXX". */ #define VI_DHEADER "X-vi-data:" static int rcv_copy(SCR *, int, char *); static void rcv_email(SCR *, char *); static int rcv_mailfile(SCR *, int, char *); static int rcv_mktemp(SCR *, char *, char *); static int rcv_dlnwrite(SCR *, const char *, const char *, FILE *); static int rcv_dlnread(SCR *, char **, char **, FILE *); /* * rcv_tmp -- * Build a file name that will be used as the recovery file. * * PUBLIC: int rcv_tmp(SCR *, EXF *, char *); */ int -rcv_tmp( - SCR *sp, - EXF *ep, - char *name) +rcv_tmp(SCR *sp, EXF *ep, char *name) { struct stat sb; int fd; char *dp, *path; /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. * * * If the recovery directory doesn't exist, try and create it. As * the recovery files are themselves protected from reading/writing * by other than the owner, the worst that can happen is that a user * would have permission to remove other user's recovery files. If * the sticky bit has the BSD semantics, that too will be impossible. */ if (opts_empty(sp, O_RECDIR, 0)) goto err; dp = O_STR(sp, O_RECDIR); if (stat(dp, &sb)) { if (errno != ENOENT || mkdir(dp, 0)) { msgq(sp, M_SYSERR, "%s", dp); goto err; } (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX); } if ((path = join(dp, "vi.XXXXXX")) == NULL) goto err; if ((fd = rcv_mktemp(sp, path, dp)) == -1) { free(path); goto err; } (void)fchmod(fd, S_IRWXU); (void)close(fd); ep->rcv_path = path; if (0) { err: msgq(sp, M_ERR, "056|Modifications not recoverable if the session fails"); return (1); } /* We believe the file is recoverable. */ F_SET(ep, F_RCV_ON); return (0); } /* * rcv_init -- * Force the file to be snapshotted for recovery. * * PUBLIC: int rcv_init(SCR *); */ int rcv_init(SCR *sp) { EXF *ep; recno_t lno; ep = sp->ep; /* Only do this once. */ F_CLR(ep, F_FIRSTMODIFY); /* If we already know the file isn't recoverable, we're done. */ if (!F_ISSET(ep, F_RCV_ON)) return (0); /* Turn off recoverability until we figure out if this will work. */ F_CLR(ep, F_RCV_ON); /* Test if we're recovering a file, not editing one. */ if (ep->rcv_mpath == NULL) { /* Build a file to mail to the user. */ if (rcv_mailfile(sp, 0, NULL)) goto err; /* Force a read of the entire file. */ if (db_last(sp, &lno)) goto err; /* Turn on a busy message, and sync it to backing store. */ sp->gp->scr_busy(sp, "057|Copying file for recovery...", BUSY_ON); if (ep->db->sync(ep->db, R_RECNOSYNC)) { msgq_str(sp, M_SYSERR, ep->rcv_path, "058|Preservation failed: %s"); sp->gp->scr_busy(sp, NULL, BUSY_OFF); goto err; } sp->gp->scr_busy(sp, NULL, BUSY_OFF); } /* Turn off the owner execute bit. */ (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR); /* We believe the file is recoverable. */ F_SET(ep, F_RCV_ON); return (0); err: msgq(sp, M_ERR, "059|Modifications not recoverable if the session fails"); return (1); } /* * rcv_sync -- * Sync the file, optionally: * flagging the backup file to be preserved * snapshotting the backup file and send email to the user * sending email to the user if the file was modified * ending the file session * * PUBLIC: int rcv_sync(SCR *, u_int); */ int -rcv_sync( - SCR *sp, - u_int flags) +rcv_sync(SCR *sp, u_int flags) { EXF *ep; int fd, rval; char *dp, *buf; /* Make sure that there's something to recover/sync. */ ep = sp->ep; if (ep == NULL || !F_ISSET(ep, F_RCV_ON)) return (0); /* Sync the file if it's been modified. */ if (F_ISSET(ep, F_MODIFIED)) { if (ep->db->sync(ep->db, R_RECNOSYNC)) { F_CLR(ep, F_RCV_ON | F_RCV_NORM); msgq_str(sp, M_SYSERR, ep->rcv_path, "060|File backup failed: %s"); return (1); } /* REQUEST: don't remove backing file on exit. */ if (LF_ISSET(RCV_PRESERVE)) F_SET(ep, F_RCV_NORM); /* REQUEST: send email. */ if (LF_ISSET(RCV_EMAIL)) rcv_email(sp, ep->rcv_mpath); } /* * !!! * Each time the user exec's :preserve, we have to snapshot all of * the recovery information, i.e. it's like the user re-edited the * file. We copy the DB(3) backing file, and then create a new mail * recovery file, it's simpler than exiting and reopening all of the * underlying files. * * REQUEST: snapshot the file. */ rval = 0; if (LF_ISSET(RCV_SNAPSHOT)) { if (opts_empty(sp, O_RECDIR, 0)) goto err; dp = O_STR(sp, O_RECDIR); if ((buf = join(dp, "vi.XXXXXX")) == NULL) { msgq(sp, M_SYSERR, NULL); goto err; } if ((fd = rcv_mktemp(sp, buf, dp)) == -1) { free(buf); goto err; } sp->gp->scr_busy(sp, "061|Copying file for recovery...", BUSY_ON); if (rcv_copy(sp, fd, ep->rcv_path) || close(fd) || rcv_mailfile(sp, 1, buf)) { (void)unlink(buf); (void)close(fd); rval = 1; } free(buf); sp->gp->scr_busy(sp, NULL, BUSY_OFF); } if (0) { err: rval = 1; } /* REQUEST: end the file session. */ if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1)) rval = 1; return (rval); } /* * rcv_mailfile -- * Build the file to mail to the user. */ static int -rcv_mailfile( - SCR *sp, - int issync, - char *cp_path) +rcv_mailfile(SCR *sp, int issync, char *cp_path) { EXF *ep; GS *gp; struct passwd *pw; int len; time_t now; uid_t uid; int fd; FILE *fp; char *dp, *p, *t, *qt, *buf, *mpath; char *t1, *t2, *t3; int st; /* * XXX * MAXHOSTNAMELEN/HOST_NAME_MAX are deprecated. We try sysconf(3) * first, then fallback to _POSIX_HOST_NAME_MAX. */ char *host; long hostmax = sysconf(_SC_HOST_NAME_MAX); if (hostmax < 0) hostmax = _POSIX_HOST_NAME_MAX; gp = sp->gp; if ((pw = getpwuid(uid = getuid())) == NULL) { msgq(sp, M_ERR, "062|Information on user id %u not found", uid); return (1); } if (opts_empty(sp, O_RECDIR, 0)) return (1); dp = O_STR(sp, O_RECDIR); if ((mpath = join(dp, "recover.XXXXXX")) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } if ((fd = rcv_mktemp(sp, mpath, dp)) == -1) { free(mpath); return (1); } if ((fp = fdopen(fd, "w")) == NULL) { free(mpath); close(fd); return (1); } /* * XXX * We keep an open lock on the file so that the recover option can * distinguish between files that are live and those that need to * be recovered. There's an obvious window between the mkstemp call * and the lock, but it's pretty small. */ ep = sp->ep; if (file_lock(sp, NULL, fd, 1) != LOCK_SUCCESS) msgq(sp, M_SYSERR, "063|Unable to lock recovery file"); if (!issync) { /* Save the recover file descriptor, and mail path. */ ep->rcv_fd = dup(fd); ep->rcv_mpath = mpath; cp_path = ep->rcv_path; } t = sp->frp->name; if ((p = strrchr(t, '/')) == NULL) p = t; else ++p; (void)time(&now); if ((st = rcv_dlnwrite(sp, "file", t, fp))) { if (st == 1) goto werr; goto err; } if ((st = rcv_dlnwrite(sp, "path", cp_path, fp))) { if (st == 1) goto werr; goto err; } - MALLOC(sp, host, char *, hostmax + 1); + MALLOC(sp, host, hostmax + 1); if (host == NULL) goto err; (void)gethostname(host, hostmax + 1); len = fprintf(fp, "%s%s%s\n%s%s%s%s\n%s%.40s\n%s\n\n", "From: root@", host, " (Nvi recovery program)", "To: ", pw->pw_name, "@", host, "Subject: Nvi saved the file ", p, "Precedence: bulk"); /* For vacation(1). */ if (len < 0) { free(host); goto werr; } if ((qt = quote(t)) == NULL) { free(host); msgq(sp, M_SYSERR, NULL); goto err; } len = asprintf(&buf, "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n", "On ", ctime(&now), ", the user ", pw->pw_name, " was editing a file named ", t, " on the machine ", host, ", when it was saved for recovery. ", "You can recover most, if not all, of the changes ", - "to this file using the -r option to ", gp->progname, ":\n\n\t", - gp->progname, " -r ", qt); + "to this file using the -r option to ", getprogname(), ":\n\n\t", + getprogname(), " -r ", qt); free(qt); free(host); if (buf == NULL) { msgq(sp, M_SYSERR, NULL); goto err; } /* * Format the message. (Yes, I know it's silly.) * Requires that the message end in a . */ #define FMTCOLS 60 for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) { /* Check for a short length. */ if (len <= FMTCOLS) { t2 = t1 + (len - 1); goto wout; } /* Check for a required . */ t2 = strchr(t1, '\n'); if (t2 - t1 <= FMTCOLS) goto wout; /* Find the closest space, if any. */ for (t3 = t2; t2 > t1; --t2) if (*t2 == ' ') { if (t2 - t1 <= FMTCOLS) goto wout; t3 = t2; } t2 = t3; /* t2 points to the last character to display. */ wout: *t2++ = '\n'; /* t2 points one after the last character to display. */ if (fwrite(t1, 1, t2 - t1, fp) != t2 - t1) { free(buf); goto werr; } } if (issync) { fflush(fp); rcv_email(sp, mpath); free(mpath); } if (fclose(fp)) { free(buf); werr: msgq(sp, M_SYSERR, "065|Recovery file"); goto err; } free(buf); return (0); err: if (!issync) ep->rcv_fd = -1; if (fp != NULL) (void)fclose(fp); return (1); } /* * people making love * never exactly the same * just like a snowflake * * rcv_list -- * List the files that can be recovered by this user. * * PUBLIC: int rcv_list(SCR *); */ int rcv_list(SCR *sp) { struct dirent *dp; struct stat sb; DIR *dirp; FILE *fp; int found; char *p, *file, *path; char *dtype, *data; int st; /* Open the recovery directory for reading. */ if (opts_empty(sp, O_RECDIR, 0)) return (1); p = O_STR(sp, O_RECDIR); if (chdir(p) || (dirp = opendir(".")) == NULL) { msgq_str(sp, M_SYSERR, p, "recdir: %s"); return (1); } /* Read the directory. */ for (found = 0; (dp = readdir(dirp)) != NULL;) { if (strncmp(dp->d_name, "recover.", 8)) continue; /* If it's readable, it's recoverable. */ if ((fp = fopen(dp->d_name, "r")) == NULL) continue; switch (file_lock(sp, NULL, fileno(fp), 1)) { case LOCK_FAILED: /* * XXX * Assume that a lock can't be acquired, but that we * should permit recovery anyway. If this is wrong, * and someone else is using the file, we're going to * die horribly. */ break; case LOCK_SUCCESS: break; case LOCK_UNAVAIL: /* If it's locked, it's live. */ (void)fclose(fp); continue; } /* Check the headers. */ for (file = NULL, path = NULL; file == NULL || path == NULL;) { if ((st = rcv_dlnread(sp, &dtype, &data, fp))) { if (st == 1) msgq_str(sp, M_ERR, dp->d_name, "066|%s: malformed recovery file"); goto next; } if (dtype == NULL) continue; if (!strcmp(dtype, "file")) file = data; else if (!strcmp(dtype, "path")) path = data; else free(data); } /* * If the file doesn't exist, it's an orphaned recovery file, * toss it. * * XXX * This can occur if the backup file was deleted and we crashed * before deleting the email file. */ errno = 0; if (stat(path, &sb) && errno == ENOENT) { (void)unlink(dp->d_name); goto next; } /* Get the last modification time and display. */ (void)fstat(fileno(fp), &sb); (void)printf("%.24s: %s\n", ctime(&sb.st_mtime), file); found = 1; /* Close, discarding lock. */ next: (void)fclose(fp); - if (file != NULL) - free(file); - if (path != NULL) - free(path); + free(file); + free(path); } if (found == 0) - (void)printf("%s: No files to recover\n", sp->gp->progname); + (void)printf("%s: No files to recover\n", getprogname()); (void)closedir(dirp); return (0); } /* * rcv_read -- * Start a recovered file as the file to edit. * * PUBLIC: int rcv_read(SCR *, FREF *); */ int -rcv_read( - SCR *sp, - FREF *frp) +rcv_read(SCR *sp, FREF *frp) { struct dirent *dp; struct stat sb; DIR *dirp; FILE *fp; EXF *ep; struct timespec rec_mtim = { 0, 0 }; int found, locked = 0, requested, sv_fd; char *name, *p, *t, *rp, *recp, *pathp; char *file, *path, *recpath; char *dtype, *data; int st; if (opts_empty(sp, O_RECDIR, 0)) return (1); rp = O_STR(sp, O_RECDIR); if ((dirp = opendir(rp)) == NULL) { msgq_str(sp, M_SYSERR, rp, "%s"); return (1); } name = frp->name; sv_fd = -1; recp = pathp = NULL; for (found = requested = 0; (dp = readdir(dirp)) != NULL;) { if (strncmp(dp->d_name, "recover.", 8)) continue; if ((recpath = join(rp, dp->d_name)) == NULL) { msgq(sp, M_SYSERR, NULL); continue; } /* If it's readable, it's recoverable. */ if ((fp = fopen(recpath, "r")) == NULL) { free(recpath); continue; } switch (file_lock(sp, NULL, fileno(fp), 1)) { case LOCK_FAILED: /* * XXX * Assume that a lock can't be acquired, but that we * should permit recovery anyway. If this is wrong, * and someone else is using the file, we're going to * die horribly. */ locked = 0; break; case LOCK_SUCCESS: locked = 1; break; case LOCK_UNAVAIL: /* If it's locked, it's live. */ (void)fclose(fp); continue; } /* Check the headers. */ for (file = NULL, path = NULL; file == NULL || path == NULL;) { if ((st = rcv_dlnread(sp, &dtype, &data, fp))) { if (st == 1) msgq_str(sp, M_ERR, dp->d_name, "067|%s: malformed recovery file"); goto next; } if (dtype == NULL) continue; if (!strcmp(dtype, "file")) file = data; else if (!strcmp(dtype, "path")) path = data; else free(data); } ++found; /* * If the file doesn't exist, it's an orphaned recovery file, * toss it. * * XXX * This can occur if the backup file was deleted and we crashed * before deleting the email file. */ errno = 0; if (stat(path, &sb) && errno == ENOENT) { (void)unlink(dp->d_name); goto next; } /* Check the file name. */ if (strcmp(file, name)) goto next; ++requested; /* If we've found more than one, take the most recent. */ (void)fstat(fileno(fp), &sb); if (recp == NULL || timespeccmp(&rec_mtim, &sb.st_mtimespec, <)) { p = recp; t = pathp; recp = recpath; pathp = path; if (p != NULL) { free(p); free(t); } rec_mtim = sb.st_mtimespec; if (sv_fd != -1) (void)close(sv_fd); sv_fd = dup(fileno(fp)); } else { next: free(recpath); - if (path != NULL) - free(path); + free(path); } (void)fclose(fp); - if (file != NULL) - free(file); + free(file); } (void)closedir(dirp); if (recp == NULL) { msgq_str(sp, M_INFO, name, "068|No files named %s, readable by you, to recover"); return (1); } if (found) { if (requested > 1) msgq(sp, M_INFO, "069|There are older versions of this file for you to recover"); if (found > requested) msgq(sp, M_INFO, "070|There are other files for you to recover"); } /* * Create the FREF structure, start the btree file. * * XXX * file_init() is going to set ep->rcv_path. */ if (file_init(sp, frp, pathp, 0)) { free(recp); free(pathp); (void)close(sv_fd); return (1); } free(pathp); /* * We keep an open lock on the file so that the recover option can * distinguish between files that are live and those that need to * be recovered. The lock is already acquired, just copy it. */ ep = sp->ep; ep->rcv_mpath = recp; ep->rcv_fd = sv_fd; if (!locked) F_SET(frp, FR_UNLOCKED); /* We believe the file is recoverable. */ F_SET(ep, F_RCV_ON); return (0); } /* * rcv_copy -- * Copy a recovery file. */ static int -rcv_copy( - SCR *sp, - int wfd, - char *fname) +rcv_copy(SCR *sp, int wfd, char *fname) { int nr, nw, off, rfd; char buf[8 * 1024]; if ((rfd = open(fname, O_RDONLY, 0)) == -1) goto err; while ((nr = read(rfd, buf, sizeof(buf))) > 0) for (off = 0; nr; nr -= nw, off += nw) if ((nw = write(wfd, buf + off, nr)) < 0) goto err; if (nr == 0) return (0); err: msgq_str(sp, M_SYSERR, fname, "%s"); return (1); } /* * rcv_mktemp -- * Paranoid make temporary file routine. */ static int -rcv_mktemp( - SCR *sp, - char *path, - char *dname) +rcv_mktemp(SCR *sp, char *path, char *dname) { int fd; if ((fd = mkstemp(path)) == -1) msgq_str(sp, M_SYSERR, dname, "%s"); return (fd); } /* * rcv_email -- * Send email. */ static void -rcv_email( - SCR *sp, - char *fname) +rcv_email(SCR *sp, char *fname) { char *buf; (void)asprintf(&buf, _PATH_SENDMAIL " -odb -t < %s", fname); if (buf == NULL) { msgq_str(sp, M_ERR, strerror(errno), "071|not sending email: %s"); return; } (void)system(buf); free(buf); } /* * rcv_dlnwrite -- * Encode a string into an X-vi-data line and write it. */ static int -rcv_dlnwrite( - SCR *sp, - const char *dtype, - const char *src, - FILE *fp) +rcv_dlnwrite(SCR *sp, const char *dtype, const char *src, FILE *fp) { char *bp = NULL, *p; size_t blen = 0; size_t dlen, len; int plen, xlen; len = strlen(src); dlen = strlen(dtype); GET_SPACE_GOTOC(sp, bp, blen, (len + 2) / 3 * 4 + dlen + 2); (void)memcpy(bp, dtype, dlen); bp[dlen] = ';'; if ((xlen = b64_ntop((u_char *)src, len, bp + dlen + 1, blen)) == -1) goto err; xlen += dlen + 1; /* Output as an MIME folding header. */ if ((plen = fprintf(fp, VI_DHEADER " %.*s\n", FMTCOLS - (int)sizeof(VI_DHEADER), bp)) < 0) goto err; plen -= (int)sizeof(VI_DHEADER) + 1; for (p = bp, xlen -= plen; xlen > 0; xlen -= plen) { p += plen; if ((plen = fprintf(fp, " %.*s\n", FMTCOLS - 1, p)) < 0) goto err; plen -= 2; } FREE_SPACE(sp, bp, blen); return (0); err: FREE_SPACE(sp, bp, blen); return (1); alloc_err: msgq(sp, M_SYSERR, NULL); return (-1); } /* * rcv_dlnread -- * Read an X-vi-data line and decode it. */ static int -rcv_dlnread( - SCR *sp, - char **dtypep, +rcv_dlnread(SCR *sp, char **dtypep, char **datap, /* free *datap if != NULL after use. */ FILE *fp) { int ch; char buf[1024]; char *bp = NULL, *p, *src; size_t blen = 0; size_t len, off, dlen; char *dtype, *data; int xlen; if (fgets(buf, sizeof(buf), fp) == NULL) return (1); if (strncmp(buf, VI_DHEADER, sizeof(VI_DHEADER) - 1)) { *dtypep = NULL; *datap = NULL; return (0); } /* Fetch an MIME folding header. */ len = strlen(buf) - sizeof(VI_DHEADER) + 1; GET_SPACE_GOTOC(sp, bp, blen, len); (void)memcpy(bp, buf + sizeof(VI_DHEADER) - 1, len); p = bp + len; while ((ch = fgetc(fp)) == ' ') { if (fgets(buf, sizeof(buf), fp) == NULL) goto err; off = strlen(buf); len += off; ADD_SPACE_GOTOC(sp, bp, blen, len); p = bp + len - off; (void)memcpy(p, buf, off); } bp[len] = '\0'; (void)ungetc(ch, fp); for (p = bp; *p == ' ' || *p == '\n'; p++); if ((src = strchr(p, ';')) == NULL) goto err; dlen = src - p; src += 1; len -= src - bp; /* Memory looks like: "\0\0". */ - MALLOC(sp, data, char *, dlen + len / 4 * 3 + 2); + MALLOC(sp, data, dlen + len / 4 * 3 + 2); if (data == NULL) goto err; if ((xlen = (b64_pton(p + dlen + 1, (u_char *)data, len / 4 * 3 + 1))) == -1) { free(data); goto err; } data[xlen] = '\0'; dtype = data + xlen + 1; (void)memcpy(dtype, p, dlen); dtype[dlen] = '\0'; FREE_SPACE(sp, bp, blen); *dtypep = dtype; *datap = data; return (0); err: FREE_SPACE(sp, bp, blen); return (1); alloc_err: msgq(sp, M_SYSERR, NULL); return (-1); } Index: head/contrib/nvi/common/screen.c =================================================================== --- head/contrib/nvi/common/screen.c (revision 365498) +++ head/contrib/nvi/common/screen.c (revision 365499) @@ -1,232 +1,220 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: screen.c,v 10.25 2011/12/04 04:06:45 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" /* * screen_init -- * Do the default initialization of an SCR structure. * * PUBLIC: int screen_init(GS *, SCR *, SCR **); */ int -screen_init( - GS *gp, - SCR *orig, - SCR **spp) +screen_init(GS *gp, SCR *orig, SCR **spp) { SCR *sp; size_t len; *spp = NULL; - CALLOC_RET(orig, sp, SCR *, 1, sizeof(SCR)); + CALLOC_RET(orig, sp, 1, sizeof(SCR)); *spp = sp; /* INITIALIZED AT SCREEN CREATE. */ sp->id = ++gp->id; sp->refcnt = 1; sp->gp = gp; /* All ref the GS structure. */ sp->ccnt = 2; /* Anything > 1 */ /* * XXX * sp->defscroll is initialized by the opts_init() code because * we don't have the option information yet. */ TAILQ_INIT(sp->tiq); /* PARTIALLY OR COMPLETELY COPIED FROM PREVIOUS SCREEN. */ if (orig == NULL) { sp->searchdir = NOTSET; } else { /* Alternate file name. */ if (orig->alt_name != NULL && (sp->alt_name = strdup(orig->alt_name)) == NULL) goto mem; /* Last executed at buffer. */ if (F_ISSET(orig, SC_AT_SET)) { F_SET(sp, SC_AT_SET); sp->at_lbuf = orig->at_lbuf; } /* Retain searching/substitution information. */ sp->searchdir = orig->searchdir == NOTSET ? NOTSET : FORWARD; if (orig->re != NULL && (sp->re = v_wstrdup(sp, orig->re, orig->re_len)) == NULL) goto mem; sp->re_len = orig->re_len; if (orig->subre != NULL && (sp->subre = v_wstrdup(sp, orig->subre, orig->subre_len)) == NULL) goto mem; sp->subre_len = orig->subre_len; if (orig->repl != NULL && (sp->repl = v_wstrdup(sp, orig->repl, orig->repl_len)) == NULL) goto mem; sp->repl_len = orig->repl_len; if (orig->newl_len) { len = orig->newl_len * sizeof(size_t); - MALLOC(sp, sp->newl, size_t *, len); + MALLOC(sp, sp->newl, len); if (sp->newl == NULL) { mem: msgq(orig, M_SYSERR, NULL); goto err; } sp->newl_len = orig->newl_len; sp->newl_cnt = orig->newl_cnt; memcpy(sp->newl, orig->newl, len); } if (opts_copy(orig, sp)) goto err; F_SET(sp, F_ISSET(orig, SC_EX | SC_VI)); } if (ex_screen_copy(orig, sp)) /* Ex. */ goto err; if (v_screen_copy(orig, sp)) /* Vi. */ goto err; sp->cl_private = 0; /* XXX */ conv_init(orig, sp); /* XXX */ *spp = sp; return (0); err: screen_end(sp); return (1); } /* * screen_end -- * Release a screen, no matter what had (and had not) been * initialized. * * PUBLIC: int screen_end(SCR *); */ int screen_end(SCR *sp) { int rval; /* If multiply referenced, just decrement the count and return. */ if (--sp->refcnt != 0) return (0); /* * Remove the screen from the displayed queue. * * If a created screen failed during initialization, it may not * be linked into the chain. */ if (TAILQ_ENTRY_ISVALID(sp, q)) TAILQ_REMOVE(sp->gp->dq, sp, q); /* The screen is no longer real. */ F_CLR(sp, SC_SCR_EX | SC_SCR_VI); rval = 0; if (v_screen_end(sp)) /* End vi. */ rval = 1; if (ex_screen_end(sp)) /* End ex. */ rval = 1; /* Free file names. */ { char **ap; if (!F_ISSET(sp, SC_ARGNOFREE) && sp->argv != NULL) { for (ap = sp->argv; *ap != NULL; ++ap) free(*ap); free(sp->argv); } } /* Free any text input. */ if (!TAILQ_EMPTY(sp->tiq)) text_lfree(sp->tiq); /* Free alternate file name. */ - if (sp->alt_name != NULL) - free(sp->alt_name); + free(sp->alt_name); /* Free up search information. */ - if (sp->re != NULL) - free(sp->re); + free(sp->re); if (F_ISSET(sp, SC_RE_SEARCH)) regfree(&sp->re_c); - if (sp->subre != NULL) - free(sp->subre); + free(sp->subre); if (F_ISSET(sp, SC_RE_SUBST)) regfree(&sp->subre_c); - if (sp->repl != NULL) - free(sp->repl); - if (sp->newl != NULL) - free(sp->newl); + free(sp->repl); + free(sp->newl); /* Free the iconv environment */ conv_end(sp); /* Free all the options */ opts_free(sp); /* Free the screen itself. */ free(sp); return (rval); } /* * screen_next -- * Return the next screen in the queue. * * PUBLIC: SCR *screen_next(SCR *); */ SCR * screen_next(SCR *sp) { GS *gp; SCR *next; /* Try the display queue, without returning the current screen. */ gp = sp->gp; TAILQ_FOREACH(next, gp->dq, q) if (next != sp) break; if (next != NULL) return (next); /* Try the hidden queue; if found, move screen to the display queue. */ if (!TAILQ_EMPTY(gp->hq)) { next = TAILQ_FIRST(gp->hq); TAILQ_REMOVE(gp->hq, next, q); TAILQ_INSERT_HEAD(gp->dq, next, q); return (next); } return (NULL); } Index: head/contrib/nvi/common/screen.h =================================================================== --- head/contrib/nvi/common/screen.h (revision 365498) +++ head/contrib/nvi/common/screen.h (revision 365499) @@ -1,211 +1,209 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: screen.h,v 10.26 2011/12/12 22:31:36 zy Exp $ */ /* * There are minimum values that vi has to have to display a screen. The row * minimum is fixed at 1 (the svi code can share a line between the text line * and the colon command/message line). Column calculation is a lot trickier. * For example, you have to have enough columns to display the line number, * not to mention guaranteeing that tabstop and shiftwidth values are smaller * than the current column value. It's simpler to have a fixed value and not * worry about it. * * XXX * MINIMUM_SCREEN_COLS is almost certainly wrong. */ #define MINIMUM_SCREEN_ROWS 1 #define MINIMUM_SCREEN_COLS 20 /* * SCR -- * The screen structure. To the extent possible, all screen information * is stored in the various private areas. The only information here * is used by global routines or is shared by too many screens. */ struct _scr { /* INITIALIZED AT SCREEN CREATE. */ TAILQ_ENTRY(_scr) q; /* Screens. */ int id; /* Screen id #. */ int refcnt; /* Reference count. */ GS *gp; /* Pointer to global area. */ SCR *nextdisp; /* Next display screen. */ SCR *ccl_parent; /* Colon command-line parent screen. */ EXF *ep; /* Screen's current EXF structure. */ FREF *frp; /* FREF being edited. */ char **argv; /* NULL terminated file name array. */ char **cargv; /* Current file name. */ u_long ccnt; /* Command count. */ u_long q_ccnt; /* Quit or ZZ command count. */ /* Screen's: */ size_t rows; /* 1-N: number of rows. */ size_t cols; /* 1-N: number of columns. */ size_t t_rows; /* 1-N: cur number of text rows. */ size_t t_maxrows; /* 1-N: max number of text rows. */ size_t t_minrows; /* 1-N: min number of text rows. */ size_t coff; /* 0-N: screen col offset in display. */ size_t roff; /* 0-N: screen row offset in display. */ /* Cursor's: */ recno_t lno; /* 1-N: file line. */ size_t cno; /* 0-N: file character in line. */ size_t rcm; /* Vi: 0-N: Most attractive column. */ #define L_ADDED 0 /* Added lines. */ #define L_CHANGED 1 /* Changed lines. */ #define L_DELETED 2 /* Deleted lines. */ #define L_JOINED 3 /* Joined lines. */ #define L_MOVED 4 /* Moved lines. */ #define L_SHIFT 5 /* Shift lines. */ #define L_YANKED 6 /* Yanked lines. */ recno_t rptlchange; /* Ex/vi: last L_CHANGED lno. */ recno_t rptlines[L_YANKED + 1];/* Ex/vi: lines changed by last op. */ TEXTH tiq[1]; /* Ex/vi: text input queue. */ SCRIPT *script; /* Vi: script mode information .*/ recno_t defscroll; /* Vi: ^D, ^U scroll information. */ /* Display character. */ char cname[MAX_CHARACTER_COLUMNS + 1]; size_t clen; /* Length of display character. */ ARG_CHAR_T lastc; /* The last display character. */ enum { /* Vi editor mode. */ SM_APPEND = 0, SM_CHANGE, SM_COMMAND, SM_INSERT, SM_REPLACE } showmode; void *ex_private; /* Ex private area. */ void *vi_private; /* Vi private area. */ void *cl_private; /* Curses private area. */ CONV conv; /* Conversion functions. */ CONVWIN cw; /* Conversion buffer. */ /* PARTIALLY OR COMPLETELY COPIED FROM PREVIOUS SCREEN. */ char *alt_name; /* Ex/vi: alternate file name. */ CHAR_T at_lbuf; /* Ex/vi: Last executed at buffer. */ /* Ex/vi: re_compile flags. */ #define RE_C_CSCOPE 0x0001 /* Compile cscope pattern. */ #define RE_C_SEARCH 0x0002 /* Compile search replacement. */ #define RE_C_SILENT 0x0004 /* No error messages. */ #define RE_C_SUBST 0x0008 /* Compile substitute replacement. */ #define RE_C_TAG 0x0010 /* Compile ctag pattern. */ #define RE_WSTART L("[[:<:]]") /* Ex/vi: not-in-word search pattern. */ #define RE_WSTOP L("[[:>:]]") #define RE_WSTART_LEN (SIZE(RE_WSTART) - 1) #define RE_WSTOP_LEN (SIZE(RE_WSTOP) - 1) /* Ex/vi: flags to search routines. */ #define SEARCH_CSCOPE 0x0001 /* Search for a cscope pattern. */ #define SEARCH_EOL 0x0002 /* Offset past EOL is okay. */ #define SEARCH_FILE 0x0004 /* Search the entire file. */ #define SEARCH_INCR 0x0008 /* Search incrementally. */ #define SEARCH_MSG 0x0010 /* Display search messages. */ #define SEARCH_PARSE 0x0020 /* Parse the search pattern. */ #define SEARCH_SET 0x0040 /* Set search direction. */ #define SEARCH_TAG 0x0080 /* Search for a tag pattern. */ #define SEARCH_WMSG 0x0100 /* Display search-wrapped messages. */ /* Ex/vi: RE information. */ dir_t searchdir; /* Last file search direction. */ regex_t re_c; /* Search RE: compiled form. */ CHAR_T *re; /* Search RE: uncompiled form. */ size_t re_len; /* Search RE: uncompiled length. */ regex_t subre_c; /* Substitute RE: compiled form. */ CHAR_T *subre; /* Substitute RE: uncompiled form. */ size_t subre_len; /* Substitute RE: uncompiled length). */ CHAR_T *repl; /* Substitute replacement. */ size_t repl_len; /* Substitute replacement length.*/ size_t *newl; /* Newline offset array. */ size_t newl_len; /* Newline array size. */ size_t newl_cnt; /* Newlines in replacement. */ u_int8_t c_suffix; /* Edcompatible 'c' suffix value. */ u_int8_t g_suffix; /* Edcompatible 'g' suffix value. */ OPTION opts[O_OPTIONCOUNT]; /* Ex/vi: Options. */ /* * Screen flags. * * Editor screens. */ #define SC_EX 0x00000001 /* Ex editor. */ #define SC_VI 0x00000002 /* Vi editor. */ /* * Screen formatting flags, first major, then minor. * * SC_SCR_EX * Ex screen, i.e. cooked mode. * SC_SCR_VI * Vi screen, i.e. raw mode. * SC_SCR_EXWROTE * The editor had to write on the screen behind curses' back, and we can't * let curses change anything until the user agrees, e.g. entering the * commands :!utility followed by :set. We have to switch back into the * vi "editor" to read the user's command input, but we can't touch the * rest of the screen because it's known to be wrong. * SC_SCR_REFORMAT * The expected presentation of the lines on the screen have changed, * requiring that the intended screen lines be recalculated. Implies * SC_SCR_REDRAW. * SC_SCR_REDRAW * The screen doesn't correctly represent the file; repaint it. Note, * setting SC_SCR_REDRAW in the current window causes *all* windows to * be repainted. * SC_SCR_CENTER * If the current line isn't already on the screen, center it. * SC_SCR_TOP * If the current line isn't already on the screen, put it at the to@. */ #define SC_SCR_EX 0x00000004 /* Screen is in ex mode. */ #define SC_SCR_VI 0x00000008 /* Screen is in vi mode. */ #define SC_SCR_EXWROTE 0x00000010 /* Ex overwrite: see comment above. */ #define SC_SCR_REFORMAT 0x00000020 /* Reformat (refresh). */ #define SC_SCR_REDRAW 0x00000040 /* Refresh. */ #define SC_SCR_CENTER 0x00000080 /* Center the line if not visible. */ #define SC_SCR_TOP 0x00000100 /* Top the line if not visible. */ /* Screen/file changes. */ #define SC_EXIT 0x00000200 /* Exiting (not forced). */ #define SC_EXIT_FORCE 0x00000400 /* Exiting (forced). */ #define SC_FSWITCH 0x00000800 /* Switch underlying files. */ #define SC_SSWITCH 0x00001000 /* Switch screens. */ #define SC_ARGNOFREE 0x00002000 /* Argument list wasn't allocated. */ #define SC_ARGRECOVER 0x00004000 /* Argument list is recovery files. */ #define SC_AT_SET 0x00008000 /* Last at buffer set. */ #define SC_COMEDIT 0x00010000 /* Colon command-line edit window. */ #define SC_EX_GLOBAL 0x00020000 /* Ex: executing a global command. */ #define SC_EX_SILENT 0x00040000 /* Ex: batch script. */ #define SC_EX_WAIT_NO 0x00080000 /* Ex: don't wait for the user. */ #define SC_EX_WAIT_YES 0x00100000 /* Ex: do wait for the user. */ #define SC_READONLY 0x00200000 /* Persistent readonly state. */ #define SC_RE_SEARCH 0x00400000 /* Search RE has been compiled. */ #define SC_RE_SUBST 0x00800000 /* Substitute RE has been compiled. */ #define SC_SCRIPT 0x01000000 /* Shell script window. */ #define SC_STATUS 0x02000000 /* Welcome message. */ #define SC_STATUS_CNT 0x04000000 /* Welcome message plus file count. */ #define SC_TINPUT 0x08000000 /* Doing text input. */ #define SC_TINPUT_INFO 0x10000000 /* Doing text input on info line. */ #define SC_CONV_ERROR 0x20000000 /* Met with a conversion error. */ u_int32_t flags; }; Index: head/contrib/nvi/common/search.c =================================================================== --- head/contrib/nvi/common/search.c (revision 365498) +++ head/contrib/nvi/common/search.c (revision 365499) @@ -1,499 +1,474 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: search.c,v 10.27 2015/03/13 18:41:35 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include "common.h" typedef enum { S_EMPTY, S_EOF, S_NOPREV, S_NOTFOUND, S_SOF, S_WRAP } smsg_t; static void search_msg(SCR *, smsg_t); static int search_init(SCR *, dir_t, CHAR_T *, size_t, CHAR_T **, u_int); /* * search_init -- * Set up a search. */ static int -search_init( - SCR *sp, - dir_t dir, - CHAR_T *ptrn, - size_t plen, - CHAR_T **epp, - u_int flags) +search_init(SCR *sp, dir_t dir, CHAR_T *ptrn, size_t plen, CHAR_T **epp, + u_int flags) { recno_t lno; int delim; CHAR_T *p, *t; /* If the file is empty, it's a fast search. */ if (sp->lno <= 1) { if (db_last(sp, &lno)) return (1); if (lno == 0) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_EMPTY); return (1); } } if (LF_ISSET(SEARCH_PARSE)) { /* Parse the string. */ /* * Use the saved pattern if no pattern specified, or if only * one or two delimiter characters specified. * * !!! * Historically, only the pattern itself was saved, vi didn't * preserve addressing or delta information. */ if (ptrn == NULL) goto prev; if (plen == 1) { if (epp != NULL) *epp = ptrn + 1; goto prev; } if (ptrn[0] == ptrn[1]) { if (epp != NULL) *epp = ptrn + 2; /* Complain if we don't have a previous pattern. */ prev: if (sp->re == NULL) { search_msg(sp, S_NOPREV); return (1); } /* Re-compile the search pattern if necessary. */ if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH | (LF_ISSET(SEARCH_MSG) ? 0 : RE_C_SILENT))) return (1); /* Set the search direction. */ if (LF_ISSET(SEARCH_SET)) sp->searchdir = dir; return (0); } /* * Set the delimiter, and move forward to the terminating * delimiter, handling escaped delimiters. * * QUOTING NOTE: * Only discard an escape character if it escapes a delimiter. */ for (delim = *ptrn, p = t = ++ptrn;; *t++ = *p++) { if (--plen == 0 || p[0] == delim) { if (plen != 0) ++p; break; } if (plen > 1 && p[0] == '\\' && p[1] == delim) { ++p; --plen; } } if (epp != NULL) *epp = p; plen = t - ptrn; } /* Compile the RE. */ if (re_compile(sp, ptrn, plen, &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH | (LF_ISSET(SEARCH_MSG) ? 0 : RE_C_SILENT) | (LF_ISSET(SEARCH_TAG) ? RE_C_TAG : 0) | (LF_ISSET(SEARCH_CSCOPE) ? RE_C_CSCOPE : 0))) return (1); /* Set the search direction. */ if (LF_ISSET(SEARCH_SET)) sp->searchdir = dir; return (0); } /* * f_search -- * Do a forward search. * * PUBLIC: int f_search(SCR *, * PUBLIC: MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int); */ int -f_search( - SCR *sp, - MARK *fm, - MARK *rm, - CHAR_T *ptrn, - size_t plen, - CHAR_T **eptrn, - u_int flags) +f_search(SCR *sp, MARK *fm, MARK *rm, CHAR_T *ptrn, size_t plen, + CHAR_T **eptrn, u_int flags) { busy_t btype; recno_t lno; regmatch_t match[1]; size_t coff, len; int cnt, eval, rval, wrapped = 0; CHAR_T *l; if (search_init(sp, FORWARD, ptrn, plen, eptrn, flags)) return (1); if (LF_ISSET(SEARCH_FILE)) { lno = 1; coff = 0; } else { if (db_get(sp, fm->lno, DBG_FATAL, &l, &len)) return (1); lno = fm->lno; /* * If doing incremental search, start searching at the previous * column, so that we search a minimal distance and still match * special patterns, e.g., \< for beginning of a word. * * Otherwise, start searching immediately after the cursor. If * at the end of the line, start searching on the next line. * This is incompatible (read bug fix) with the historic vi -- * searches for the '$' pattern never moved forward, and the * "-t foo" didn't work if the 'f' was the first character in * the file. */ if (LF_ISSET(SEARCH_INCR)) { if ((coff = fm->cno) != 0) --coff; } else if (fm->cno + 1 >= len) { coff = 0; lno = fm->lno + 1; if (db_get(sp, lno, 0, &l, &len)) { if (!O_ISSET(sp, O_WRAPSCAN)) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_EOF); return (1); } lno = 1; wrapped = 1; } } else coff = fm->cno + 1; } btype = BUSY_ON; for (cnt = INTERRUPT_CHECK, rval = 1;; ++lno, coff = 0) { if (cnt-- == 0) { if (INTERRUPTED(sp)) break; if (LF_ISSET(SEARCH_MSG)) { search_busy(sp, btype); btype = BUSY_UPDATE; } cnt = INTERRUPT_CHECK; } if ((wrapped && lno > fm->lno) || db_get(sp, lno, 0, &l, &len)) { if (wrapped) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_NOTFOUND); break; } if (!O_ISSET(sp, O_WRAPSCAN)) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_EOF); break; } lno = 0; wrapped = 1; continue; } /* If already at EOL, just keep going. */ if (len != 0 && coff == len) continue; /* Set the termination. */ match[0].rm_so = coff; match[0].rm_eo = len; #if defined(DEBUG) && 0 TRACE(sp, "F search: %lu from %u to %u\n", lno, coff, len != 0 ? len - 1 : len); #endif /* Search the line. */ eval = regexec(&sp->re_c, l, 1, match, (match[0].rm_so == 0 ? 0 : REG_NOTBOL) | REG_STARTEND); if (eval == REG_NOMATCH) continue; if (eval != 0) { if (LF_ISSET(SEARCH_MSG)) re_error(sp, eval, &sp->re_c); else (void)sp->gp->scr_bell(sp); break; } /* Warn if the search wrapped. */ if (wrapped && LF_ISSET(SEARCH_WMSG)) search_msg(sp, S_WRAP); #if defined(DEBUG) && 0 TRACE(sp, "F search: %qu to %qu\n", match[0].rm_so, match[0].rm_eo); #endif rm->lno = lno; rm->cno = match[0].rm_so; /* * If a change command, it's possible to move beyond the end * of a line. Historic vi generally got this wrong (e.g. try * "c?$"). Not all that sure this gets it right, there * are lots of strange cases. */ if (!LF_ISSET(SEARCH_EOL) && rm->cno >= len) rm->cno = len != 0 ? len - 1 : 0; rval = 0; break; } if (LF_ISSET(SEARCH_MSG)) search_busy(sp, BUSY_OFF); return (rval); } /* * b_search -- * Do a backward search. * * PUBLIC: int b_search(SCR *, * PUBLIC: MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int); */ int -b_search( - SCR *sp, - MARK *fm, - MARK *rm, - CHAR_T *ptrn, - size_t plen, - CHAR_T **eptrn, - u_int flags) +b_search(SCR *sp, MARK *fm, MARK *rm, CHAR_T *ptrn, size_t plen, + CHAR_T **eptrn, u_int flags) { busy_t btype; recno_t lno; regmatch_t match[1]; size_t coff, last, len; int cnt, eval, rval, wrapped; CHAR_T *l; if (search_init(sp, BACKWARD, ptrn, plen, eptrn, flags)) return (1); /* * If doing incremental search, set the "starting" position past the * current column, so that we search a minimal distance and still * match special patterns, e.g., \> for the end of a word. This is * safe when the cursor is at the end of a line because we only use * it for comparison with the location of the match. * * Otherwise, start searching immediately before the cursor. If in * the first column, start search on the previous line. */ if (LF_ISSET(SEARCH_INCR)) { lno = fm->lno; coff = fm->cno + 1; } else { if (fm->cno == 0) { if (fm->lno == 1 && !O_ISSET(sp, O_WRAPSCAN)) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_SOF); return (1); } lno = fm->lno - 1; } else lno = fm->lno; coff = fm->cno; } btype = BUSY_ON; for (cnt = INTERRUPT_CHECK, rval = 1, wrapped = 0;; --lno, coff = 0) { if (cnt-- == 0) { if (INTERRUPTED(sp)) break; if (LF_ISSET(SEARCH_MSG)) { search_busy(sp, btype); btype = BUSY_UPDATE; } cnt = INTERRUPT_CHECK; } if ((wrapped && lno < fm->lno) || lno == 0) { if (wrapped) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_NOTFOUND); break; } if (!O_ISSET(sp, O_WRAPSCAN)) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_SOF); break; } if (db_last(sp, &lno)) break; if (lno == 0) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_EMPTY); break; } ++lno; wrapped = 1; continue; } if (db_get(sp, lno, 0, &l, &len)) break; /* Set the termination. */ match[0].rm_so = 0; match[0].rm_eo = len; #if defined(DEBUG) && 0 TRACE(sp, "B search: %lu from 0 to %qu\n", lno, match[0].rm_eo); #endif /* Search the line. */ eval = regexec(&sp->re_c, l, 1, match, (match[0].rm_eo == len ? 0 : REG_NOTEOL) | REG_STARTEND); if (eval == REG_NOMATCH) continue; if (eval != 0) { if (LF_ISSET(SEARCH_MSG)) re_error(sp, eval, &sp->re_c); else (void)sp->gp->scr_bell(sp); break; } /* Check for a match starting past the cursor. */ if (coff != 0 && match[0].rm_so >= coff) continue; /* Warn if the search wrapped. */ if (wrapped && LF_ISSET(SEARCH_WMSG)) search_msg(sp, S_WRAP); #if defined(DEBUG) && 0 TRACE(sp, "B found: %qu to %qu\n", match[0].rm_so, match[0].rm_eo); #endif /* * We now have the first match on the line. Step through the * line character by character until find the last acceptable * match. This is painful, we need a better interface to regex * to make this work. */ for (;;) { last = match[0].rm_so++; if (match[0].rm_so >= len) break; match[0].rm_eo = len; eval = regexec(&sp->re_c, l, 1, match, (match[0].rm_so == 0 ? 0 : REG_NOTBOL) | REG_STARTEND); if (eval == REG_NOMATCH) break; if (eval != 0) { if (LF_ISSET(SEARCH_MSG)) re_error(sp, eval, &sp->re_c); else (void)sp->gp->scr_bell(sp); goto err; } if (coff && match[0].rm_so >= coff) break; } rm->lno = lno; /* See comment in f_search(). */ if (!LF_ISSET(SEARCH_EOL) && last >= len) rm->cno = len != 0 ? len - 1 : 0; else rm->cno = last; rval = 0; break; } err: if (LF_ISSET(SEARCH_MSG)) search_busy(sp, BUSY_OFF); return (rval); } /* * search_msg -- * Display one of the search messages. */ static void -search_msg( - SCR *sp, - smsg_t msg) +search_msg(SCR *sp, smsg_t msg) { switch (msg) { case S_EMPTY: msgq(sp, M_ERR, "072|File empty; nothing to search"); break; case S_EOF: msgq(sp, M_ERR, "073|Reached end-of-file without finding the pattern"); break; case S_NOPREV: msgq(sp, M_ERR, "074|No previous search pattern"); break; case S_NOTFOUND: msgq(sp, M_ERR, "075|Pattern not found"); break; case S_SOF: msgq(sp, M_ERR, "076|Reached top-of-file without finding the pattern"); break; case S_WRAP: msgq(sp, M_ERR, "077|Search wrapped"); break; default: abort(); } } /* * search_busy -- * Put up the busy searching message. * * PUBLIC: void search_busy(SCR *, busy_t); */ void -search_busy( - SCR *sp, - busy_t btype) +search_busy(SCR *sp, busy_t btype) { sp->gp->scr_busy(sp, "078|Searching...", btype); } Index: head/contrib/nvi/common/seq.c =================================================================== --- head/contrib/nvi/common/seq.c (revision 365498) +++ head/contrib/nvi/common/seq.c (revision 365499) @@ -1,408 +1,371 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: seq.c,v 10.18 2011/12/11 23:13:00 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include "common.h" /* * seq_set -- * Internal version to enter a sequence. * * PUBLIC: int seq_set(SCR *, CHAR_T *, * PUBLIC: size_t, CHAR_T *, size_t, CHAR_T *, size_t, seq_t, int); */ int -seq_set( - SCR *sp, - CHAR_T *name, - size_t nlen, - CHAR_T *input, - size_t ilen, - CHAR_T *output, - size_t olen, - seq_t stype, - int flags) +seq_set(SCR *sp, CHAR_T *name, size_t nlen, CHAR_T *input, size_t ilen, + CHAR_T *output, size_t olen, seq_t stype, int flags) { CHAR_T *p; SEQ *lastqp, *qp; int sv_errno; /* * An input string must always be present. The output string * can be NULL, when set internally, that's how we throw away * input. * * Just replace the output field if the string already set. */ if ((qp = seq_find(sp, &lastqp, NULL, input, ilen, stype, NULL)) != NULL) { if (LF_ISSET(SEQ_NOOVERWRITE)) return (0); if (output == NULL || olen == 0) { p = NULL; olen = 0; } else if ((p = v_wstrdup(sp, output, olen)) == NULL) { sv_errno = errno; goto mem1; } - if (qp->output != NULL) - free(qp->output); + free(qp->output); qp->olen = olen; qp->output = p; return (0); } /* Allocate and initialize SEQ structure. */ - CALLOC(sp, qp, SEQ *, 1, sizeof(SEQ)); + CALLOC(sp, qp, 1, sizeof(SEQ)); if (qp == NULL) { sv_errno = errno; goto mem1; } /* Name. */ if (name == NULL || nlen == 0) qp->name = NULL; else if ((qp->name = v_wstrdup(sp, name, nlen)) == NULL) { sv_errno = errno; goto mem2; } qp->nlen = nlen; /* Input. */ if ((qp->input = v_wstrdup(sp, input, ilen)) == NULL) { sv_errno = errno; goto mem3; } qp->ilen = ilen; /* Output. */ if (output == NULL) { qp->output = NULL; olen = 0; } else if ((qp->output = v_wstrdup(sp, output, olen)) == NULL) { sv_errno = errno; free(qp->input); -mem3: if (qp->name != NULL) - free(qp->name); +mem3: free(qp->name); mem2: free(qp); mem1: errno = sv_errno; msgq(sp, M_SYSERR, NULL); return (1); } qp->olen = olen; /* Type, flags. */ qp->stype = stype; qp->flags = flags; /* Link into the chain. */ if (lastqp == NULL) { SLIST_INSERT_HEAD(sp->gp->seqq, qp, q); } else { SLIST_INSERT_AFTER(lastqp, qp, q); } /* Set the fast lookup bit. */ if ((qp->input[0] & ~MAX_BIT_SEQ) == 0) bit_set(sp->gp->seqb, qp->input[0]); return (0); } /* * seq_delete -- * Delete a sequence. * * PUBLIC: int seq_delete(SCR *, CHAR_T *, size_t, seq_t); */ int -seq_delete( - SCR *sp, - CHAR_T *input, - size_t ilen, - seq_t stype) +seq_delete(SCR *sp, CHAR_T *input, size_t ilen, seq_t stype) { SEQ *qp, *pre_qp = NULL; int diff; SLIST_FOREACH(qp, sp->gp->seqq, q) { if (qp->stype == stype && qp->ilen == ilen) { diff = MEMCMP(qp->input, input, ilen); if (!diff) { if (F_ISSET(qp, SEQ_FUNCMAP)) break; if (qp == SLIST_FIRST(sp->gp->seqq)) SLIST_REMOVE_HEAD(sp->gp->seqq, q); else SLIST_REMOVE_AFTER(pre_qp, q); return (seq_free(qp)); } if (diff > 0) break; } pre_qp = qp; } return (1); } /* * seq_free -- * Free a map entry. * * PUBLIC: int seq_free(SEQ *); */ int seq_free(SEQ *qp) { - if (qp->name != NULL) - free(qp->name); - if (qp->input != NULL) - free(qp->input); - if (qp->output != NULL) - free(qp->output); + free(qp->name); + free(qp->input); + free(qp->output); free(qp); return (0); } /* * seq_find -- * Search the sequence list for a match to a buffer, if ispartial * isn't NULL, partial matches count. * * PUBLIC: SEQ *seq_find * PUBLIC: (SCR *, SEQ **, EVENT *, CHAR_T *, size_t, seq_t, int *); */ SEQ * -seq_find( - SCR *sp, - SEQ **lastqp, - EVENT *e_input, - CHAR_T *c_input, - size_t ilen, - seq_t stype, - int *ispartialp) +seq_find(SCR *sp, SEQ **lastqp, EVENT *e_input, CHAR_T *c_input, size_t ilen, + seq_t stype, int *ispartialp) { SEQ *lqp = NULL, *qp; int diff; /* * Ispartialp is a location where we return if there was a * partial match, i.e. if the string were extended it might * match something. * * XXX * Overload the meaning of ispartialp; only the terminal key * search doesn't want the search limited to complete matches, * i.e. ilen may be longer than the match. */ if (ispartialp != NULL) *ispartialp = 0; for (qp = SLIST_FIRST(sp->gp->seqq); qp != NULL; lqp = qp, qp = SLIST_NEXT(qp, q)) { /* * Fast checks on the first character and type, and then * a real comparison. */ if (e_input == NULL) { if (qp->input[0] > c_input[0]) break; if (qp->input[0] < c_input[0] || qp->stype != stype || F_ISSET(qp, SEQ_FUNCMAP)) continue; diff = MEMCMP(qp->input, c_input, MIN(qp->ilen, ilen)); } else { if (qp->input[0] > e_input->e_c) break; if (qp->input[0] < e_input->e_c || qp->stype != stype || F_ISSET(qp, SEQ_FUNCMAP)) continue; diff = e_memcmp(qp->input, e_input, MIN(qp->ilen, ilen)); } if (diff > 0) break; if (diff < 0) continue; /* * If the entry is the same length as the string, return a * match. If the entry is shorter than the string, return a * match if called from the terminal key routine. Otherwise, * keep searching for a complete match. */ if (qp->ilen <= ilen) { if (qp->ilen == ilen || ispartialp != NULL) { if (lastqp != NULL) *lastqp = lqp; return (qp); } continue; } /* * If the entry longer than the string, return partial match * if called from the terminal key routine. Otherwise, no * match. */ if (ispartialp != NULL) *ispartialp = 1; break; } if (lastqp != NULL) *lastqp = lqp; return (NULL); } /* * seq_close -- * Discard all sequences. * * PUBLIC: void seq_close(GS *); */ void seq_close(GS *gp) { SEQ *qp; while ((qp = SLIST_FIRST(gp->seqq)) != NULL) { SLIST_REMOVE_HEAD(gp->seqq, q); (void)seq_free(qp); } } /* * seq_dump -- * Display the sequence entries of a specified type. * * PUBLIC: int seq_dump(SCR *, seq_t, int); */ int -seq_dump( - SCR *sp, - seq_t stype, - int isname) +seq_dump(SCR *sp, seq_t stype, int isname) { CHAR_T *p; GS *gp; SEQ *qp; int cnt, len, olen; cnt = 0; gp = sp->gp; SLIST_FOREACH(qp, sp->gp->seqq, q) { if (stype != qp->stype || F_ISSET(qp, SEQ_FUNCMAP)) continue; ++cnt; for (p = qp->input, olen = qp->ilen, len = 0; olen > 0; --olen, ++p) len += ex_puts(sp, KEY_NAME(sp, *p)); for (len = STANDARD_TAB - len % STANDARD_TAB; len > 0;) len -= ex_puts(sp, " "); if (qp->output != NULL) for (p = qp->output, olen = qp->olen, len = 0; olen > 0; --olen, ++p) len += ex_puts(sp, KEY_NAME(sp, *p)); else len = 0; if (isname && qp->name != NULL) { for (len = STANDARD_TAB - len % STANDARD_TAB; len > 0;) len -= ex_puts(sp, " "); for (p = qp->name, olen = qp->nlen; olen > 0; --olen, ++p) (void)ex_puts(sp, KEY_NAME(sp, *p)); } (void)ex_puts(sp, "\n"); } return (cnt); } /* * seq_save -- * Save the sequence entries to a file. * * PUBLIC: int seq_save(SCR *, FILE *, char *, seq_t); */ int -seq_save( - SCR *sp, - FILE *fp, - char *prefix, - seq_t stype) +seq_save(SCR *sp, FILE *fp, char *prefix, seq_t stype) { CHAR_T *p; SEQ *qp; size_t olen; int ch; /* Write a sequence command for all keys the user defined. */ SLIST_FOREACH(qp, sp->gp->seqq, q) { if (stype != qp->stype || !F_ISSET(qp, SEQ_USERDEF)) continue; if (prefix) (void)fprintf(fp, "%s", prefix); for (p = qp->input, olen = qp->ilen; olen > 0; --olen) { ch = *p++; if (ch == CH_LITERAL || ch == '|' || cmdskip(ch) || KEY_VAL(sp, ch) == K_NL) (void)putc(CH_LITERAL, fp); (void)putc(ch, fp); } (void)putc(' ', fp); if (qp->output != NULL) for (p = qp->output, olen = qp->olen; olen > 0; --olen) { ch = *p++; if (ch == CH_LITERAL || ch == '|' || KEY_VAL(sp, ch) == K_NL) (void)putc(CH_LITERAL, fp); (void)putc(ch, fp); } (void)putc('\n', fp); } return (0); } /* * e_memcmp -- * Compare a string of EVENT's to a string of CHAR_T's. * * PUBLIC: int e_memcmp(CHAR_T *, EVENT *, size_t); */ int -e_memcmp( - CHAR_T *p1, - EVENT *ep, - size_t n) +e_memcmp(CHAR_T *p1, EVENT *ep, size_t n) { if (n != 0) { do { if (*p1++ != ep->e_c) return (*--p1 - ep->e_c); ++ep; } while (--n != 0); } return (0); } Index: head/contrib/nvi/common/seq.h =================================================================== --- head/contrib/nvi/common/seq.h (revision 365498) +++ head/contrib/nvi/common/seq.h (revision 365499) @@ -1,44 +1,42 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: seq.h,v 10.4 2011/12/11 21:43:39 zy Exp $ */ /* * Map and abbreviation structures. * * The map structure is singly linked list, sorted by input string and by * input length within the string. (The latter is necessary so that short * matches will happen before long matches when the list is searched.) * Additionally, there is a bitmap which has bits set if there are entries * starting with the corresponding character. This keeps us from walking * the list unless it's necessary. * * The name and the output fields of a SEQ can be empty, i.e. NULL. * Only the input field is required. * * XXX * The fast-lookup bits are never turned off -- users don't usually unmap * things, though, so it's probably not a big deal. */ struct _seq { SLIST_ENTRY(_seq) q; /* Linked list of all sequences. */ seq_t stype; /* Sequence type. */ CHAR_T *name; /* Sequence name (if any). */ size_t nlen; /* Name length. */ CHAR_T *input; /* Sequence input keys. */ size_t ilen; /* Input keys length. */ CHAR_T *output; /* Sequence output keys. */ size_t olen; /* Output keys length. */ #define SEQ_FUNCMAP 0x01 /* If unresolved function key.*/ #define SEQ_NOOVERWRITE 0x02 /* Don't replace existing entry. */ #define SEQ_SCREEN 0x04 /* If screen specific. */ #define SEQ_USERDEF 0x08 /* If user defined. */ u_int8_t flags; }; Index: head/contrib/nvi/common/util.c =================================================================== --- head/contrib/nvi/common/util.c (revision 365498) +++ head/contrib/nvi/common/util.c (revision 365499) @@ -1,423 +1,377 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: util.c,v 10.30 2013/03/19 10:00:27 yamt Exp $"; -#endif /* not lint */ - #include #include #ifdef __APPLE__ #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include "common.h" /* * binc -- * Increase the size of a buffer. * * PUBLIC: void *binc(SCR *, void *, size_t *, size_t); */ void * -binc( - SCR *sp, /* sp MAY BE NULL!!! */ - void *bp, - size_t *bsizep, - size_t min) +binc(SCR *sp, /* sp MAY BE NULL!!! */ + void *bp, size_t *bsizep, size_t min) { size_t csize; /* If already larger than the minimum, just return. */ if (min && *bsizep >= min) return (bp); csize = p2roundup(MAX(min, 256)); REALLOC(sp, bp, void *, csize); if (bp == NULL) { *bsizep = 0; return (NULL); } /* * Memory is guaranteed to be zero-filled, various parts of * nvi depend on this. */ memset((char *)bp + *bsizep, 0, csize - *bsizep); *bsizep = csize; return (bp); } /* * nonblank -- * Set the column number of the first non-blank character * including or after the starting column. On error, set * the column to 0, it's safest. * * PUBLIC: int nonblank(SCR *, recno_t, size_t *); */ int -nonblank( - SCR *sp, - recno_t lno, - size_t *cnop) +nonblank(SCR *sp, recno_t lno, size_t *cnop) { CHAR_T *p; size_t cnt, len, off; int isempty; /* Default. */ off = *cnop; *cnop = 0; /* Get the line, succeeding in an empty file. */ if (db_eget(sp, lno, &p, &len, &isempty)) return (!isempty); /* Set the offset. */ if (len == 0 || off >= len) return (0); for (cnt = off, p = &p[off], len -= off; len && ISBLANK(*p); ++cnt, ++p, --len); /* Set the return. */ *cnop = len ? cnt : cnt - 1; return (0); } /* - * tail -- - * Return tail of a path. - * - * PUBLIC: char *tail(char *); - */ -char * -tail(char *path) -{ - char *p; - - if ((p = strrchr(path, '/')) == NULL) - return (path); - return (p + 1); -} - -/* * join -- * Join two paths; need free. * * PUBLIC: char *join(char *, char *); */ char * -join( - char *path1, - char *path2) +join(char *path1, char *path2) { char *p; if (path1[0] == '\0' || path2[0] == '/') return strdup(path2); (void)asprintf(&p, path1[strlen(path1)-1] == '/' ? "%s%s" : "%s/%s", path1, path2); return p; } /* * expanduser -- * Return a "~" or "~user" expanded path; need free. * * PUBLIC: char *expanduser(char *); */ char * expanduser(char *str) { struct passwd *pwd; char *p, *t, *u, *h; /* * This function always expands the content between the * leading '~' and the first '/' or '\0' from the input. * Return NULL whenever we fail to do so. */ if (*str != '~') return (NULL); p = str + 1; for (t = p; *t != '/' && *t != '\0'; ++t) continue; if (t == p) { /* ~ */ if (issetugid() != 0 || (h = getenv("HOME")) == NULL) { if (((h = getlogin()) != NULL && (pwd = getpwnam(h)) != NULL) || (pwd = getpwuid(getuid())) != NULL) h = pwd->pw_dir; else return (NULL); } } else { /* ~user */ if ((u = strndup(p, t - p)) == NULL) return (NULL); if ((pwd = getpwnam(u)) == NULL) { free(u); return (NULL); } else h = pwd->pw_dir; free(u); } for (; *t == '/' && *t != '\0'; ++t) continue; return (join(h, t)); } /* * quote -- * Return a escaped string for /bin/sh; need free. * * PUBLIC: char *quote(char *); */ char * quote(char *str) { char *p, *t; size_t i = 0, n = 0; int unsafe = 0; for (p = str; *p != '\0'; p++, i++) { if (*p == '\'') n++; if (unsafe) continue; - if (isascii(*p)) { - if (isalnum(*p)) + if (isascii((u_char)*p)) { + if (isalnum((u_char)*p)) continue; switch (*p) { case '%': case '+': case ',': case '-': case '.': case '/': case ':': case '=': case '@': case '_': continue; } } unsafe = 1; } if (!unsafe) t = strdup(str); #define SQT "'\\''" else if ((p = t = malloc(i + n * (sizeof(SQT) - 2) + 3)) != NULL) { *p++ = '\''; for (; *str != '\0'; str++) { if (*str == '\'') { (void)memcpy(p, SQT, sizeof(SQT) - 1); p += sizeof(SQT) - 1; } else *p++ = *str; } *p++ = '\''; *p = '\0'; } return t; } /* * v_strdup -- * Strdup for 8-bit character strings with an associated length. * * PUBLIC: char *v_strdup(SCR *, const char *, size_t); */ char * -v_strdup( - SCR *sp, - const char *str, - size_t len) +v_strdup(SCR *sp, const char *str, size_t len) { char *copy; - MALLOC(sp, copy, char *, len + 1); + MALLOC(sp, copy, len + 1); if (copy == NULL) return (NULL); memcpy(copy, str, len); copy[len] = '\0'; return (copy); } /* * v_wstrdup -- * Strdup for wide character strings with an associated length. * * PUBLIC: CHAR_T *v_wstrdup(SCR *, const CHAR_T *, size_t); */ CHAR_T * -v_wstrdup(SCR *sp, - const CHAR_T *str, - size_t len) +v_wstrdup(SCR *sp, const CHAR_T *str, size_t len) { CHAR_T *copy; - MALLOC(sp, copy, CHAR_T *, (len + 1) * sizeof(CHAR_T)); + MALLOC(sp, copy, (len + 1) * sizeof(CHAR_T)); if (copy == NULL) return (NULL); MEMCPY(copy, str, len); copy[len] = '\0'; return (copy); } /* * nget_uslong -- * Get an unsigned long, checking for overflow. * * PUBLIC: enum nresult nget_uslong(u_long *, const CHAR_T *, CHAR_T **, int); */ enum nresult -nget_uslong( - u_long *valp, - const CHAR_T *p, - CHAR_T **endp, - int base) +nget_uslong(u_long *valp, const CHAR_T *p, CHAR_T **endp, int base) { errno = 0; *valp = STRTOUL(p, endp, base); if (errno == 0) return (NUM_OK); if (errno == ERANGE && *valp == ULONG_MAX) return (NUM_OVER); return (NUM_ERR); } /* * nget_slong -- * Convert a signed long, checking for overflow and underflow. * * PUBLIC: enum nresult nget_slong(long *, const CHAR_T *, CHAR_T **, int); */ enum nresult -nget_slong( - long *valp, - const CHAR_T *p, - CHAR_T **endp, - int base) +nget_slong(long *valp, const CHAR_T *p, CHAR_T **endp, int base) { errno = 0; *valp = STRTOL(p, endp, base); if (errno == 0) return (NUM_OK); if (errno == ERANGE) { if (*valp == LONG_MAX) return (NUM_OVER); if (*valp == LONG_MIN) return (NUM_UNDER); } return (NUM_ERR); } /* * timepoint_steady -- * Get a timestamp from a monotonic clock. * * PUBLIC: void timepoint_steady(struct timespec *); */ void -timepoint_steady( - struct timespec *ts) +timepoint_steady(struct timespec *ts) { #ifdef __APPLE__ static mach_timebase_info_data_t base = { 0 }; uint64_t val; uint64_t ns; if (base.denom == 0) (void)mach_timebase_info(&base); val = mach_absolute_time(); ns = val * base.numer / base.denom; ts->tv_sec = ns / 1000000000; ts->tv_nsec = ns % 1000000000; #else #ifdef CLOCK_MONOTONIC_FAST (void)clock_gettime(CLOCK_MONOTONIC_FAST, ts); #else (void)clock_gettime(CLOCK_MONOTONIC, ts); #endif #endif } /* * timepoint_system -- * Get the current calendar time. * * PUBLIC: void timepoint_system(struct timespec *); */ void -timepoint_system( - struct timespec *ts) +timepoint_system(struct timespec *ts) { #ifdef __APPLE__ clock_serv_t clk; mach_timespec_t mts; kern_return_t kr; kr = host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &clk); if (kr != KERN_SUCCESS) return; (void)clock_get_time(clk, &mts); (void)mach_port_deallocate(mach_task_self(), clk); ts->tv_sec = mts.tv_sec; ts->tv_nsec = mts.tv_nsec; #else #ifdef CLOCK_REALTIME_FAST (void)clock_gettime(CLOCK_REALTIME_FAST, ts); #else (void)clock_gettime(CLOCK_REALTIME, ts); #endif #endif } #ifdef DEBUG #include /* * TRACE -- * debugging trace routine. * * PUBLIC: void TRACE(SCR *, const char *, ...); */ void -TRACE( - SCR *sp, - const char *fmt, - ...) +TRACE(SCR *sp, const char *fmt, ...) { FILE *tfp; va_list ap; if ((tfp = sp->gp->tracefp) == NULL) return; va_start(ap, fmt); (void)vfprintf(tfp, fmt, ap); va_end(ap); (void)fflush(tfp); } #endif Index: head/contrib/nvi/common/util.h =================================================================== --- head/contrib/nvi/common/util.h (revision 365498) +++ head/contrib/nvi/common/util.h (revision 365499) @@ -1,93 +1,91 @@ /*- * Copyright (c) 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: util.h,v 10.7 2013/02/24 21:00:10 zy Exp $ */ /* Macros to init/set/clear/test flags. */ #define FL_INIT(l, f) (l) = (f) /* Specific flags location. */ #define FL_SET(l, f) ((l) |= (f)) #define FL_CLR(l, f) ((l) &= ~(f)) #define FL_ISSET(l, f) ((l) & (f)) #define LF_INIT(f) FL_INIT(flags, f) /* Local variable flags. */ #define LF_SET(f) FL_SET(flags, f) #define LF_CLR(f) FL_CLR(flags, f) #define LF_ISSET(f) FL_ISSET(flags, f) #define F_INIT(p, f) FL_INIT((p)->flags, f) /* Structure element flags. */ #define F_SET(p, f) FL_SET((p)->flags, f) #define F_CLR(p, f) FL_CLR((p)->flags, f) #define F_ISSET(p, f) FL_ISSET((p)->flags, f) /* Offset to next column of stop size, e.g. tab offsets. */ #define COL_OFF(c, stop) ((stop) - ((c) % (stop))) /* Busy message types. */ typedef enum { B_NONE, B_OFF, B_READ, B_RECOVER, B_SEARCH, B_WRITE } bmsg_t; /* * Number handling defines and protoypes. * * NNFITS: test for addition of two negative numbers under a limit * NPFITS: test for addition of two positive numbers under a limit * NADD_SLONG: test for addition of two signed longs * NADD_USLONG: test for addition of two unsigned longs */ enum nresult { NUM_ERR, NUM_OK, NUM_OVER, NUM_UNDER }; #define NNFITS(min, cur, add) \ (((long)(min)) - (cur) <= (add)) #define NPFITS(max, cur, add) \ (((unsigned long)(max)) - (cur) >= (add)) #define NADD_SLONG(sp, v1, v2) \ ((v1) < 0 ? \ ((v2) < 0 && \ NNFITS(LONG_MIN, (v1), (v2))) ? NUM_UNDER : NUM_OK : \ (v1) > 0 ? \ (v2) > 0 && \ NPFITS(LONG_MAX, (v1), (v2)) ? NUM_OK : NUM_OVER : \ NUM_OK) #define NADD_USLONG(sp, v1, v2) \ (NPFITS(ULONG_MAX, (v1), (v2)) ? NUM_OK : NUM_OVER) /* Macros for min/max. */ #undef MIN #undef MAX #define MIN(_a,_b) ((_a)<(_b)?(_a):(_b)) #define MAX(_a,_b) ((_a)<(_b)?(_b):(_a)) /* Operations on timespecs */ #undef timespecclear #undef timespecisset #undef timespeccmp #undef timespecadd #undef timespecsub #define timespecclear(tvp) ((tvp)->tv_sec = (tvp)->tv_nsec = 0) #define timespecisset(tvp) ((tvp)->tv_sec || (tvp)->tv_nsec) #define timespeccmp(tvp, uvp, cmp) \ (((tvp)->tv_sec == (uvp)->tv_sec) ? \ ((tvp)->tv_nsec cmp (uvp)->tv_nsec) : \ ((tvp)->tv_sec cmp (uvp)->tv_sec)) #define timespecadd(vvp, uvp) \ do { \ (vvp)->tv_sec += (uvp)->tv_sec; \ (vvp)->tv_nsec += (uvp)->tv_nsec; \ if ((vvp)->tv_nsec >= 1000000000) { \ (vvp)->tv_sec++; \ (vvp)->tv_nsec -= 1000000000; \ } \ } while (0) #define timespecsub(vvp, uvp) \ do { \ (vvp)->tv_sec -= (uvp)->tv_sec; \ (vvp)->tv_nsec -= (uvp)->tv_nsec; \ if ((vvp)->tv_nsec < 0) { \ (vvp)->tv_sec--; \ (vvp)->tv_nsec += 1000000000; \ } \ } while (0) Index: head/contrib/nvi/ex/ex.c =================================================================== --- head/contrib/nvi/ex/ex.c (revision 365498) +++ head/contrib/nvi/ex/ex.c (revision 365499) @@ -1,2369 +1,2364 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex.c,v 10.80 2012/10/03 16:24:40 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" #if defined(DEBUG) && defined(COMLOG) static void ex_comlog(SCR *, EXCMD *); #endif static EXCMDLIST const * ex_comm_search(CHAR_T *, size_t); static int ex_discard(SCR *); static int ex_line(SCR *, EXCMD *, MARK *, int *, int *); static int ex_load(SCR *); static void ex_unknown(SCR *, CHAR_T *, size_t); /* * ex -- * Main ex loop. * * PUBLIC: int ex(SCR **); */ int ex(SCR **spp) { EX_PRIVATE *exp; GS *gp; MSGS *mp; SCR *sp; TEXT *tp; u_int32_t flags; sp = *spp; gp = sp->gp; exp = EXP(sp); /* Start the ex screen. */ if (ex_init(sp)) return (1); /* Flush any saved messages. */ while ((mp = SLIST_FIRST(gp->msgq)) != NULL) { gp->scr_msg(sp, mp->mtype, mp->buf, mp->len); SLIST_REMOVE_HEAD(gp->msgq, q); free(mp->buf); free(mp); } /* If reading from a file, errors should have name and line info. */ if (F_ISSET(gp, G_SCRIPTED)) { gp->excmd.if_lno = 1; gp->excmd.if_name = "script"; } /* * !!! * Initialize the text flags. The beautify edit option historically * applied to ex command input read from a file. In addition, the * first time a ^H was discarded from the input, there was a message, * "^H discarded", that was displayed. We don't bother. */ LF_INIT(TXT_BACKSLASH | TXT_CNTRLD | TXT_CR); for (;; ++gp->excmd.if_lno) { /* Display status line and flush. */ if (F_ISSET(sp, SC_STATUS)) { if (!F_ISSET(sp, SC_EX_SILENT)) msgq_status(sp, sp->lno, 0); F_CLR(sp, SC_STATUS); } (void)ex_fflush(sp); /* Set the flags the user can reset. */ if (O_ISSET(sp, O_BEAUTIFY)) LF_SET(TXT_BEAUTIFY); if (O_ISSET(sp, O_PROMPT)) LF_SET(TXT_PROMPT); /* Clear any current interrupts, and get a command. */ CLR_INTERRUPT(sp); if (ex_txt(sp, sp->tiq, ':', flags)) return (1); if (INTERRUPTED(sp)) { (void)ex_puts(sp, "\n"); (void)ex_fflush(sp); continue; } /* Initialize the command structure. */ CLEAR_EX_PARSER(&gp->excmd); /* * If the user entered a single carriage return, send * ex_cmd() a separator -- it discards single newlines. */ tp = TAILQ_FIRST(sp->tiq); if (tp->len == 0) { gp->excmd.cp = L(" "); /* __TK__ why not |? */ gp->excmd.clen = 1; } else { gp->excmd.cp = tp->lb; gp->excmd.clen = tp->len; } F_INIT(&gp->excmd, E_NRSEP); if (ex_cmd(sp) && F_ISSET(gp, G_SCRIPTED)) return (1); if (INTERRUPTED(sp)) { CLR_INTERRUPT(sp); msgq(sp, M_ERR, "170|Interrupted"); } /* * If the last command caused a restart, or switched screens * or into vi, return. */ if (F_ISSET(gp, G_SRESTART) || F_ISSET(sp, SC_SSWITCH | SC_VI)) { *spp = sp; break; } /* If the last command switched files, we don't care. */ F_CLR(sp, SC_FSWITCH); /* * If we're exiting this screen, move to the next one. By * definition, this means returning into vi, so return to the * main editor loop. The ordering is careful, don't discard * the contents of sp until the end. */ if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (file_end(sp, NULL, F_ISSET(sp, SC_EXIT_FORCE))) return (1); *spp = screen_next(sp); return (screen_end(sp)); } } return (0); } /* * ex_cmd -- * The guts of the ex parser: parse and execute a string containing * ex commands. * * !!! * This code MODIFIES the string that gets passed in, to delete quoting * characters, etc. The string cannot be readonly/text space, nor should * you expect to use it again after ex_cmd() returns. * * !!! * For the fun of it, if you want to see if a vi clone got the ex argument * parsing right, try: * * echo 'foo|bar' > file1; echo 'foo/bar' > file2; * vi * :edit +1|s/|/PIPE/|w file1| e file2|1 | s/\//SLASH/|wq * * or: vi * :set|file|append|set|file * * For extra credit, try them in a startup .exrc file. * * PUBLIC: int ex_cmd(SCR *); */ int ex_cmd(SCR *sp) { enum nresult nret; EX_PRIVATE *exp; EXCMD *ecp; GS *gp; MARK cur; recno_t lno; size_t arg1_len, discard, len; u_int32_t flags; long ltmp; int at_found, gv_found; int cnt, delim, isaddr, namelen; int newscreen, notempty, tmp, vi_address; CHAR_T *arg1, *s, *p, *t; CHAR_T ch = '\0'; CHAR_T *n; char *np; gp = sp->gp; exp = EXP(sp); /* * We always start running the command on the top of the stack. * This means that *everything* must be resolved when we leave * this function for any reason. */ loop: ecp = SLIST_FIRST(gp->ecq); /* If we're reading a command from a file, set up error information. */ if (ecp->if_name != NULL) { gp->if_lno = ecp->if_lno; gp->if_name = ecp->if_name; } /* * If a move to the end of the file is scheduled for this command, * do it now. */ if (F_ISSET(ecp, E_MOVETOEND)) { if (db_last(sp, &sp->lno)) goto rfail; sp->cno = 0; F_CLR(ecp, E_MOVETOEND); } /* If we found a newline, increment the count now. */ if (F_ISSET(ecp, E_NEWLINE)) { ++gp->if_lno; ++ecp->if_lno; F_CLR(ecp, E_NEWLINE); } /* (Re)initialize the EXCMD structure, preserving some flags. */ CLEAR_EX_CMD(ecp); /* Initialize the argument structures. */ if (argv_init(sp, ecp)) goto err; /* Initialize +cmd, saved command information. */ arg1 = NULL; ecp->save_cmdlen = 0; /* Skip s, empty lines. */ for (notempty = 0; ecp->clen > 0; ++ecp->cp, --ecp->clen) if ((ch = *ecp->cp) == '\n') { ++gp->if_lno; ++ecp->if_lno; } else if (cmdskip(ch)) notempty = 1; else break; /* * !!! * Permit extra colons at the start of the line. Historically, * ex/vi allowed a single extra one. It's simpler not to count. * The stripping is done here because, historically, any command * could have preceding colons, e.g. ":g/pattern/:p" worked. */ if (ecp->clen != 0 && ch == ':') { notempty = 1; while (--ecp->clen > 0 && (ch = *++ecp->cp) == ':'); } /* * Command lines that start with a double-quote are comments. * * !!! * Historically, there was no escape or delimiter for a comment, e.g. * :"foo|set was a single comment and nothing was output. Since nvi * permits users to escape characters into command lines, we * have to check for that case. */ if (ecp->clen != 0 && ch == '"') { while (--ecp->clen > 0 && *++ecp->cp != '\n'); if (*ecp->cp == '\n') { F_SET(ecp, E_NEWLINE); ++ecp->cp; --ecp->clen; } goto loop; } /* Skip whitespace. */ for (; ecp->clen > 0; ++ecp->cp, --ecp->clen) { ch = *ecp->cp; if (!cmdskip(ch)) break; } /* * The last point at which an empty line can mean do nothing. * * !!! * Historically, in ex mode, lines containing only characters * were the same as a single , i.e. a default command. * In vi mode, they were ignored. In .exrc files this was a serious * annoyance, as vi kept trying to treat them as print commands. We * ignore backward compatibility in this case, discarding lines that * contain only characters from .exrc files. * * !!! * This is where you end up when you're done a command, i.e. clen has * gone to zero. Continue if there are more commands to run. */ if (ecp->clen == 0 && (!notempty || F_ISSET(sp, SC_VI) || F_ISSET(ecp, E_BLIGNORE))) { if (ex_load(sp)) goto rfail; ecp = SLIST_FIRST(gp->ecq); if (ecp->clen == 0) goto rsuccess; goto loop; } /* * Check to see if this is a command for which we may want to move * the cursor back up to the previous line. (The command :1 * wants a separator, but the command : wants to erase * the command line.) If the line is empty except for s, * or , we'll probably want to move up. I * don't think there's any way to get characters *after* the * command character, but this is the ex parser, and I've been wrong * before. */ if (F_ISSET(ecp, E_NRSEP) && ecp->clen != 0 && (ecp->clen != 1 || ecp->cp[0] != '\004')) F_CLR(ecp, E_NRSEP); /* Parse command addresses. */ if (ex_range(sp, ecp, &tmp)) goto rfail; if (tmp) goto err; /* * Skip s and any more colons (the command :3,5:print * worked, historically). */ for (; ecp->clen > 0; ++ecp->cp, --ecp->clen) { ch = *ecp->cp; if (!cmdskip(ch) && ch != ':') break; } /* * If no command, ex does the last specified of p, l, or #, and vi * moves to the line. Otherwise, determine the length of the command * name by looking for the first non-alphabetic character. (There * are a few non-alphabetic characters in command names, but they're * all single character commands.) This isn't a great test, because * it means that, for the command ":e +cut.c file", we'll report that * the command "cut" wasn't known. However, it makes ":e+35 file" work * correctly. * * !!! * Historically, lines with multiple adjacent (or separated) * command separators were very strange. For example, the command * |||, when the cursor was on line 1, displayed * lines 2, 3 and 5 of the file. In addition, the command " | " * would only display the line after the next line, instead of the * next two lines. No ideas why. It worked reasonably when executed * from vi mode, and displayed lines 2, 3, and 4, so we do a default * command for each separator. */ #define SINGLE_CHAR_COMMANDS L("\004!#&*<=>@~") newscreen = 0; if (ecp->clen != 0 && ecp->cp[0] != '|' && ecp->cp[0] != '\n') { if (STRCHR(SINGLE_CHAR_COMMANDS, *ecp->cp)) { p = ecp->cp; ++ecp->cp; --ecp->clen; namelen = 1; } else { for (p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) - if (!isascii(*ecp->cp) || !isalpha(*ecp->cp)) + if (!isazAZ(*ecp->cp)) break; if ((namelen = ecp->cp - p) == 0) { msgq(sp, M_ERR, "080|Unknown command name"); goto err; } } /* * !!! * Historic vi permitted flags to immediately follow any * subset of the 'delete' command, but then did not permit * further arguments (flag, buffer, count). Make it work. * Permit further arguments for the few shreds of dignity * it offers. * * Adding commands that start with 'd', and match "delete" * up to a l, p, +, - or # character can break this code. * * !!! * Capital letters beginning the command names ex, edit, * next, previous, tag and visual (in vi mode) indicate the * command should happen in a new screen. */ switch (p[0]) { case 'd': for (s = p, n = cmds[C_DELETE].name; *s == *n; ++s, ++n); if (s[0] == 'l' || s[0] == 'p' || s[0] == '+' || s[0] == '-' || s[0] == '^' || s[0] == '#') { len = (ecp->cp - p) - (s - p); ecp->cp -= len; ecp->clen += len; ecp->rcmd = cmds[C_DELETE]; ecp->rcmd.syntax = "1bca1"; ecp->cmd = &ecp->rcmd; goto skip_srch; } break; case 'E': case 'F': case 'N': case 'P': case 'T': case 'V': newscreen = 1; p[0] = tolower(p[0]); break; } /* * Search the table for the command. * * !!! * Historic vi permitted the mark to immediately follow the * 'k' in the 'k' command. Make it work. * * !!! * Historic vi permitted any flag to follow the s command, e.g. * "s/e/E/|s|sgc3p" was legal. Make the command "sgc" work. * Since the following characters all have to be flags, i.e. * alphabetics, we can let the s command routine return errors * if it was some illegal command string. This code will break * if an "sg" or similar command is ever added. The substitute * code doesn't care if it's a "cgr" flag or a "#lp" flag that * follows the 's', but we limit the choices here to "cgr" so * that we get unknown command messages for wrong combinations. */ if ((ecp->cmd = ex_comm_search(p, namelen)) == NULL) switch (p[0]) { case 'k': if (namelen == 2) { ecp->cp -= namelen - 1; ecp->clen += namelen - 1; ecp->cmd = &cmds[C_K]; break; } goto unknown; case 's': for (s = p + 1, cnt = namelen; --cnt; ++s) if (s[0] != 'c' && s[0] != 'g' && s[0] != 'r') break; if (cnt == 0) { ecp->cp -= namelen - 1; ecp->clen += namelen - 1; ecp->rcmd = cmds[C_SUBSTITUTE]; ecp->rcmd.fn = ex_subagain; ecp->cmd = &ecp->rcmd; break; } /* FALLTHROUGH */ default: unknown: if (newscreen) p[0] = toupper(p[0]); ex_unknown(sp, p, namelen); goto err; } /* * The visual command has a different syntax when called * from ex than when called from a vi colon command. FMH. * Make the change now, before we test for the newscreen * semantic, so that we're testing the right one. */ skip_srch: if (ecp->cmd == &cmds[C_VISUAL_EX] && F_ISSET(sp, SC_VI)) ecp->cmd = &cmds[C_VISUAL_VI]; /* * !!! * Historic vi permitted a capital 'P' at the beginning of * any command that started with 'p'. Probably wanted the * P[rint] command for backward compatibility, and the code * just made Preserve and Put work by accident. Nvi uses * Previous to mean previous-in-a-new-screen, so be careful. */ if (newscreen && !F_ISSET(ecp->cmd, E_NEWSCREEN) && (ecp->cmd == &cmds[C_PRINT] || ecp->cmd == &cmds[C_PRESERVE])) newscreen = 0; /* Test for a newscreen associated with this command. */ if (newscreen && !F_ISSET(ecp->cmd, E_NEWSCREEN)) goto unknown; /* Secure means no shell access. */ if (F_ISSET(ecp->cmd, E_SECURE) && O_ISSET(sp, O_SECURE)) { ex_wemsg(sp, ecp->cmd->name, EXM_SECURE); goto err; } /* * Multiple < and > characters; another "feature". Note, * The string passed to the underlying function may not be * nul terminated in this case. */ if ((ecp->cmd == &cmds[C_SHIFTL] && *p == '<') || (ecp->cmd == &cmds[C_SHIFTR] && *p == '>')) { for (ch = *p; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (*ecp->cp != ch) break; if (argv_exp0(sp, ecp, p, ecp->cp - p)) goto err; } /* Set the format style flags for the next command. */ if (ecp->cmd == &cmds[C_HASH]) exp->fdef = E_C_HASH; else if (ecp->cmd == &cmds[C_LIST]) exp->fdef = E_C_LIST; else if (ecp->cmd == &cmds[C_PRINT]) exp->fdef = E_C_PRINT; F_CLR(ecp, E_USELASTCMD); } else { /* Print is the default command. */ ecp->cmd = &cmds[C_PRINT]; /* Set the saved format flags. */ F_SET(ecp, exp->fdef); /* * !!! * If no address was specified, and it's not a global command, * we up the address by one. (I have no idea why globals are * exempted, but it's (ahem) historic practice.) */ if (ecp->addrcnt == 0 && !F_ISSET(sp, SC_EX_GLOBAL)) { ecp->addrcnt = 1; ecp->addr1.lno = sp->lno + 1; ecp->addr1.cno = sp->cno; } F_SET(ecp, E_USELASTCMD); } /* * !!! * Historically, the number option applied to both ex and vi. One * strangeness was that ex didn't switch display formats until a * command was entered, e.g. 's after the set didn't change to * the new format, but :1p would. */ if (O_ISSET(sp, O_NUMBER)) { F_SET(ecp, E_OPTNUM); FL_SET(ecp->iflags, E_C_HASH); } else F_CLR(ecp, E_OPTNUM); /* Check for ex mode legality. */ if (F_ISSET(sp, SC_EX) && (F_ISSET(ecp->cmd, E_VIONLY) || newscreen)) { msgq_wstr(sp, M_ERR, ecp->cmd->name, "082|%s: command not available in ex mode"); goto err; } /* Add standard command flags. */ F_SET(ecp, ecp->cmd->flags); if (!newscreen) F_CLR(ecp, E_NEWSCREEN); /* * There are three normal termination cases for an ex command. They * are the end of the string (ecp->clen), or unescaped (by characters) or '|' characters. As we're now past * possible addresses, we can determine how long the command is, so we * don't have to look for all the possible terminations. Naturally, * there are some exciting special cases: * * 1: The bang, global, v and the filter versions of the read and * write commands are delimited by s (they can contain * shell pipes). * 2: The ex, edit, next and visual in vi mode commands all take ex * commands as their first arguments. * 3: The s command takes an RE as its first argument, and wants it * to be specially delimited. * * Historically, '|' characters in the first argument of the ex, edit, * next, vi visual, and s commands didn't delimit the command. And, * in the filter cases for read and write, and the bang, global and v * commands, they did not delimit the command at all. * * For example, the following commands were legal: * * :edit +25|s/abc/ABC/ file.c * :s/|/PIPE/ * :read !spell % | columnate * :global/pattern/p|l * * It's not quite as simple as it sounds, however. The command: * * :s/a/b/|s/c/d|set * * was also legal, i.e. the historic ex parser (using the word loosely, * since "parser" implies some regularity of syntax) delimited the RE's * based on its delimiter and not anything so irretrievably vulgar as a * command syntax. * * Anyhow, the following code makes this all work. First, for the * special cases we move past their special argument(s). Then, we * do normal command processing on whatever is left. Barf-O-Rama. */ discard = 0; /* Characters discarded from the command. */ arg1_len = 0; ecp->save_cmd = ecp->cp; if (ecp->cmd == &cmds[C_EDIT] || ecp->cmd == &cmds[C_EX] || ecp->cmd == &cmds[C_NEXT] || ecp->cmd == &cmds[C_VISUAL_VI] || ecp->cmd == &cmds[C_VSPLIT]) { /* * Move to the next non-whitespace character. A '!' * immediately following the command is eaten as a * force flag. */ if (ecp->clen > 0 && *ecp->cp == '!') { ++ecp->cp; --ecp->clen; FL_SET(ecp->iflags, E_C_FORCE); /* Reset, don't reparse. */ ecp->save_cmd = ecp->cp; } for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (!cmdskip(*ecp->cp)) break; /* * QUOTING NOTE: * * The historic implementation ignored all escape characters * so there was no way to put a space or newline into the +cmd * field. We do a simplistic job of fixing it by moving to the * first whitespace character that isn't escaped. The escaping * characters are stripped as no longer useful. */ if (ecp->clen > 0 && *ecp->cp == '+') { ++ecp->cp; --ecp->clen; for (arg1 = p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = *ecp->cp; if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { ++discard; --ecp->clen; ch = *++ecp->cp; } else if (cmdskip(ch)) break; *p++ = ch; } arg1_len = ecp->cp - arg1; /* Reset, so the first argument isn't reparsed. */ ecp->save_cmd = ecp->cp; } } else if (ecp->cmd == &cmds[C_BANG] || ecp->cmd == &cmds[C_GLOBAL] || ecp->cmd == &cmds[C_V]) { /* * QUOTING NOTE: * * We use backslashes to escape characters, although * this wasn't historic practice for the bang command. It was * for the global and v commands, and it's common usage when * doing text insert during the command. Escaping characters * are stripped as no longer useful. */ for (p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = *ecp->cp; if (ch == '\\' && ecp->clen > 1 && ecp->cp[1] == '\n') { ++discard; --ecp->clen; ch = *++ecp->cp; ++gp->if_lno; ++ecp->if_lno; } else if (ch == '\n') break; *p++ = ch; } } else if (ecp->cmd == &cmds[C_READ] || ecp->cmd == &cmds[C_WRITE]) { /* * For write commands, if the next character is a , and * the next non-blank character is a '!', it's a filter command * and we want to eat everything up to the . For read * commands, if the next non-blank character is a '!', it's a * filter command and we want to eat everything up to the next * . Otherwise, we're done. */ for (tmp = 0; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = *ecp->cp; if (cmdskip(ch)) tmp = 1; else break; } if (ecp->clen > 0 && ch == '!' && (ecp->cmd == &cmds[C_READ] || tmp)) for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (ecp->cp[0] == '\n') break; } else if (ecp->cmd == &cmds[C_SUBSTITUTE]) { /* * Move to the next non-whitespace character, we'll use it as * the delimiter. If the character isn't an alphanumeric or * a '|', it's the delimiter, so parse it. Otherwise, we're * into something like ":s g", so use the special s command. */ for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (!cmdskip(ecp->cp[0])) break; - if (!isascii(ecp->cp[0]) || - isalnum(ecp->cp[0]) || ecp->cp[0] == '|') { + if (is09azAZ(ecp->cp[0]) || ecp->cp[0] == '|') { ecp->rcmd = cmds[C_SUBSTITUTE]; ecp->rcmd.fn = ex_subagain; ecp->cmd = &ecp->rcmd; } else if (ecp->clen > 0) { /* * QUOTING NOTE: * * Backslashes quote delimiter characters for RE's. * The backslashes are NOT removed since they'll be * used by the RE code. Move to the third delimiter * that's not escaped (or the end of the command). */ delim = *ecp->cp; ++ecp->cp; --ecp->clen; for (cnt = 2; ecp->clen > 0 && cnt != 0; --ecp->clen, ++ecp->cp) if (ecp->cp[0] == '\\' && ecp->clen > 1) { ++ecp->cp; --ecp->clen; } else if (ecp->cp[0] == delim) --cnt; } } /* * Use normal quoting and termination rules to find the end of this * command. * * QUOTING NOTE: * * Historically, vi permitted ^V's to escape 's in the .exrc * file. It was almost certainly a bug, but that's what bug-for-bug * compatibility means, Grasshopper. Also, ^V's escape the command * delimiters. Literal next quote characters in front of the newlines, * '|' characters or literal next characters are stripped as they're * no longer useful. */ vi_address = ecp->clen != 0 && ecp->cp[0] != '\n'; for (p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = ecp->cp[0]; if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { CHAR_T tmp = ecp->cp[1]; if (tmp == '\n' || tmp == '|') { if (tmp == '\n') { ++gp->if_lno; ++ecp->if_lno; } ++discard; --ecp->clen; ++ecp->cp; ch = tmp; } } else if (ch == '\n' || ch == '|') { if (ch == '\n') F_SET(ecp, E_NEWLINE); --ecp->clen; break; } *p++ = ch; } /* * Save off the next command information, go back to the * original start of the command. */ p = ecp->cp + 1; ecp->cp = ecp->save_cmd; ecp->save_cmd = p; ecp->save_cmdlen = ecp->clen; ecp->clen = ((ecp->save_cmd - ecp->cp) - 1) - discard; /* * QUOTING NOTE: * * The "set tags" command historically used a backslash, not the * user's literal next character, to escape whitespace. Handle * it here instead of complicating the argv_exp3() code. Note, * this isn't a particularly complex trap, and if backslashes were * legal in set commands, this would have to be much more complicated. */ if (ecp->cmd == &cmds[C_SET]) for (p = ecp->cp, len = ecp->clen; len > 0; --len, ++p) if (IS_ESCAPE(sp, ecp, *p) && len > 1) { --len; ++p; } else if (*p == '\\') *p = CH_LITERAL; /* * Set the default addresses. It's an error to specify an address for * a command that doesn't take them. If two addresses are specified * for a command that only takes one, lose the first one. Two special * cases here, some commands take 0 or 2 addresses. For most of them * (the E_ADDR2_ALL flag), 0 defaults to the entire file. For one * (the `!' command, the E_ADDR2_NONE flag), 0 defaults to no lines. * * Also, if the file is empty, some commands want to use an address of * 0, i.e. the entire file is 0 to 0, and the default first address is * 0. Otherwise, an entire file is 1 to N and the default line is 1. * Note, we also add the E_ADDR_ZERO flag to the command flags, for the * case where the 0 address is only valid if it's a default address. * * Also, set a flag if we set the default addresses. Some commands * (ex: z) care if the user specified an address or if we just used * the current cursor. */ switch (F_ISSET(ecp, E_ADDR1 | E_ADDR2 | E_ADDR2_ALL | E_ADDR2_NONE)) { case E_ADDR1: /* One address: */ switch (ecp->addrcnt) { case 0: /* Default cursor/empty file. */ ecp->addrcnt = 1; F_SET(ecp, E_ADDR_DEF); if (F_ISSET(ecp, E_ADDR_ZERODEF)) { if (db_last(sp, &lno)) goto err; if (lno == 0) { ecp->addr1.lno = 0; F_SET(ecp, E_ADDR_ZERO); } else ecp->addr1.lno = sp->lno; } else ecp->addr1.lno = sp->lno; ecp->addr1.cno = sp->cno; break; case 1: break; case 2: /* Lose the first address. */ ecp->addrcnt = 1; ecp->addr1 = ecp->addr2; } break; case E_ADDR2_NONE: /* Zero/two addresses: */ if (ecp->addrcnt == 0) /* Default to nothing. */ break; goto two_addr; case E_ADDR2_ALL: /* Zero/two addresses: */ if (ecp->addrcnt == 0) { /* Default entire/empty file. */ F_SET(ecp, E_ADDR_DEF); ecp->addrcnt = 2; if (sp->ep == NULL) ecp->addr2.lno = 0; else if (db_last(sp, &ecp->addr2.lno)) goto err; if (F_ISSET(ecp, E_ADDR_ZERODEF) && ecp->addr2.lno == 0) { ecp->addr1.lno = 0; F_SET(ecp, E_ADDR_ZERO); } else ecp->addr1.lno = 1; ecp->addr1.cno = ecp->addr2.cno = 0; F_SET(ecp, E_ADDR2_ALL); break; } /* FALLTHROUGH */ case E_ADDR2: /* Two addresses: */ two_addr: switch (ecp->addrcnt) { case 0: /* Default cursor/empty file. */ ecp->addrcnt = 2; F_SET(ecp, E_ADDR_DEF); if (sp->lno == 1 && F_ISSET(ecp, E_ADDR_ZERODEF)) { if (db_last(sp, &lno)) goto err; if (lno == 0) { ecp->addr1.lno = ecp->addr2.lno = 0; F_SET(ecp, E_ADDR_ZERO); } else ecp->addr1.lno = ecp->addr2.lno = sp->lno; } else ecp->addr1.lno = ecp->addr2.lno = sp->lno; ecp->addr1.cno = ecp->addr2.cno = sp->cno; break; case 1: /* Default to first address. */ ecp->addrcnt = 2; ecp->addr2 = ecp->addr1; break; case 2: break; } break; default: if (ecp->addrcnt) /* Error. */ goto usage; } /* * !!! * The ^D scroll command historically scrolled the value of the scroll * option or to EOF. It was an error if the cursor was already at EOF. * (Leading addresses were permitted, but were then ignored.) */ if (ecp->cmd == &cmds[C_SCROLL]) { ecp->addrcnt = 2; ecp->addr1.lno = sp->lno + 1; ecp->addr2.lno = sp->lno + O_VAL(sp, O_SCROLL); ecp->addr1.cno = ecp->addr2.cno = sp->cno; if (db_last(sp, &lno)) goto err; if (lno != 0 && lno > sp->lno && ecp->addr2.lno > lno) ecp->addr2.lno = lno; } ecp->flagoff = 0; for (np = ecp->cmd->syntax; *np != '\0'; ++np) { /* * The force flag is sensitive to leading whitespace, i.e. * "next !" is different from "next!". Handle it before * skipping leading s. */ if (*np == '!') { if (ecp->clen > 0 && *ecp->cp == '!') { ++ecp->cp; --ecp->clen; FL_SET(ecp->iflags, E_C_FORCE); } continue; } /* Skip leading s. */ for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (!cmdskip(*ecp->cp)) break; if (ecp->clen == 0) break; switch (*np) { case '1': /* +, -, #, l, p */ /* * !!! * Historically, some flags were ignored depending * on where they occurred in the command line. For * example, in the command, ":3+++p--#", historic vi * acted on the '#' flag, but ignored the '-' flags. * It's unambiguous what the flags mean, so we just * handle them regardless of the stupidity of their * location. */ for (; ecp->clen; --ecp->clen, ++ecp->cp) switch (*ecp->cp) { case '+': ++ecp->flagoff; break; case '-': case '^': --ecp->flagoff; break; case '#': F_CLR(ecp, E_OPTNUM); FL_SET(ecp->iflags, E_C_HASH); exp->fdef |= E_C_HASH; break; case 'l': FL_SET(ecp->iflags, E_C_LIST); exp->fdef |= E_C_LIST; break; case 'p': FL_SET(ecp->iflags, E_C_PRINT); exp->fdef |= E_C_PRINT; break; default: goto end_case1; } end_case1: break; case '2': /* -, ., +, ^ */ case '3': /* -, ., +, ^, = */ for (; ecp->clen; --ecp->clen, ++ecp->cp) switch (*ecp->cp) { case '-': FL_SET(ecp->iflags, E_C_DASH); break; case '.': FL_SET(ecp->iflags, E_C_DOT); break; case '+': FL_SET(ecp->iflags, E_C_PLUS); break; case '^': FL_SET(ecp->iflags, E_C_CARAT); break; case '=': if (*np == '3') { FL_SET(ecp->iflags, E_C_EQUAL); break; } /* FALLTHROUGH */ default: goto end_case23; } end_case23: break; case 'b': /* buffer */ /* * !!! * Historically, "d #" was a delete with a flag, not a * delete into the '#' buffer. If the current command * permits a flag, don't use one as a buffer. However, * the 'l' and 'p' flags were legal buffer names in the * historic ex, and were used as buffers, not flags. */ if ((ecp->cp[0] == '+' || ecp->cp[0] == '-' || ecp->cp[0] == '^' || ecp->cp[0] == '#') && strchr(np, '1') != NULL) break; /* * !!! * Digits can't be buffer names in ex commands, or the * command "d2" would be a delete into buffer '2', and * not a two-line deletion. */ if (!ISDIGIT(ecp->cp[0])) { ecp->buffer = *ecp->cp; ++ecp->cp; --ecp->clen; FL_SET(ecp->iflags, E_C_BUFFER); } break; case 'c': /* count [01+a] */ ++np; /* Validate any signed value. */ if (!ISDIGIT(*ecp->cp) && (*np != '+' || (*ecp->cp != '+' && *ecp->cp != '-'))) break; /* If a signed value, set appropriate flags. */ if (*ecp->cp == '-') FL_SET(ecp->iflags, E_C_COUNT_NEG); else if (*ecp->cp == '+') FL_SET(ecp->iflags, E_C_COUNT_POS); if ((nret = nget_slong(<mp, ecp->cp, &t, 10)) != NUM_OK) { ex_badaddr(sp, NULL, A_NOTSET, nret); goto err; } if (ltmp == 0 && *np != '0') { msgq(sp, M_ERR, "083|Count may not be zero"); goto err; } ecp->clen -= (t - ecp->cp); ecp->cp = t; /* * Counts as address offsets occur in commands taking * two addresses. Historic vi practice was to use * the count as an offset from the *second* address. * * Set a count flag; some underlying commands (see * join) do different things with counts than with * line addresses. */ if (*np == 'a') { ecp->addr1 = ecp->addr2; ecp->addr2.lno = ecp->addr1.lno + ltmp - 1; } else ecp->count = ltmp; FL_SET(ecp->iflags, E_C_COUNT); break; case 'f': /* file */ if (argv_exp2(sp, ecp, ecp->cp, ecp->clen)) goto err; goto arg_cnt_chk; case 'l': /* line */ /* * Get a line specification. * * If the line was a search expression, we may have * changed state during the call, and we're now * searching the file. Push ourselves onto the state * stack. */ if (ex_line(sp, ecp, &cur, &isaddr, &tmp)) goto rfail; if (tmp) goto err; /* Line specifications are always required. */ if (!isaddr) { msgq_wstr(sp, M_ERR, ecp->cp, "084|%s: bad line specification"); goto err; } /* * The target line should exist for these commands, * but 0 is legal for them as well. */ if (cur.lno != 0 && !db_exist(sp, cur.lno)) { ex_badaddr(sp, NULL, A_EOF, NUM_OK); goto err; } ecp->lineno = cur.lno; break; case 'S': /* string, file exp. */ if (ecp->clen != 0) { if (argv_exp1(sp, ecp, ecp->cp, ecp->clen, ecp->cmd == &cmds[C_BANG])) goto err; goto addr_verify; } /* FALLTHROUGH */ case 's': /* string */ if (argv_exp0(sp, ecp, ecp->cp, ecp->clen)) goto err; goto addr_verify; case 'W': /* word string */ /* * QUOTING NOTE: * * Literal next characters escape the following * character. Quoting characters are stripped here * since they are no longer useful. * * First there was the word. */ for (p = t = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = *ecp->cp; if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { --ecp->clen; *p++ = *++ecp->cp; } else if (cmdskip(ch)) { ++ecp->cp; --ecp->clen; break; } else *p++ = ch; } if (argv_exp0(sp, ecp, t, p - t)) goto err; /* Delete intervening whitespace. */ for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = *ecp->cp; if (!cmdskip(ch)) break; } if (ecp->clen == 0) goto usage; /* Followed by the string. */ for (p = t = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp, ++p) { ch = *ecp->cp; if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { --ecp->clen; *p = *++ecp->cp; } else *p = ch; } if (argv_exp0(sp, ecp, t, p - t)) goto err; goto addr_verify; case 'w': /* word */ if (argv_exp3(sp, ecp, ecp->cp, ecp->clen)) goto err; arg_cnt_chk: if (*++np != 'N') { /* N */ /* * If a number is specified, must either be * 0 or that number, if optional, and that * number, if required. */ tmp = *np - '0'; if ((*++np != 'o' || exp->argsoff != 0) && exp->argsoff != tmp) goto usage; } goto addr_verify; default: { size_t nlen; char *nstr; INT2CHAR(sp, ecp->cmd->name, STRLEN(ecp->cmd->name) + 1, nstr, nlen); msgq(sp, M_ERR, "085|Internal syntax table error (%s: %s)", nstr, KEY_NAME(sp, *np)); } } } /* Skip trailing whitespace. */ for (; ecp->clen > 0; --ecp->clen) { ch = *ecp->cp++; if (!cmdskip(ch)) break; } /* * There shouldn't be anything left, and no more required fields, * i.e neither 'l' or 'r' in the syntax string. */ if (ecp->clen != 0 || strpbrk(np, "lr")) { usage: msgq(sp, M_ERR, "086|Usage: %s", ecp->cmd->usage); goto err; } /* * Verify that the addresses are legal. Check the addresses here, * because this is a place where all ex addresses pass through. * (They don't all pass through ex_line(), for instance.) We're * assuming that any non-existent line doesn't exist because it's * past the end-of-file. That's a pretty good guess. * * If it's a "default vi command", an address of zero is okay. */ addr_verify: switch (ecp->addrcnt) { case 2: /* * Historic ex/vi permitted commands with counts to go past * EOF. So, for example, if the file only had 5 lines, the * ex command "1,6>" would fail, but the command ">300" * would succeed. Since we don't want to have to make all * of the underlying commands handle random line numbers, * fix it here. */ if (ecp->addr2.lno == 0) { if (!F_ISSET(ecp, E_ADDR_ZERO) && (F_ISSET(sp, SC_EX) || !F_ISSET(ecp, E_USELASTCMD))) { ex_badaddr(sp, ecp->cmd, A_ZERO, NUM_OK); goto err; } } else if (!db_exist(sp, ecp->addr2.lno)) if (FL_ISSET(ecp->iflags, E_C_COUNT)) { if (db_last(sp, &lno)) goto err; ecp->addr2.lno = lno; } else { ex_badaddr(sp, NULL, A_EOF, NUM_OK); goto err; } /* FALLTHROUGH */ case 1: if (ecp->addr1.lno == 0) { if (!F_ISSET(ecp, E_ADDR_ZERO) && (F_ISSET(sp, SC_EX) || !F_ISSET(ecp, E_USELASTCMD))) { ex_badaddr(sp, ecp->cmd, A_ZERO, NUM_OK); goto err; } } else if (!db_exist(sp, ecp->addr1.lno)) { ex_badaddr(sp, NULL, A_EOF, NUM_OK); goto err; } break; } /* * If doing a default command and there's nothing left on the line, * vi just moves to the line. For example, ":3" and ":'a,'b" just * move to line 3 and line 'b, respectively, but ":3|" prints line 3. * * !!! * In addition, IF THE LINE CHANGES, move to the first nonblank of * the line. * * !!! * This is done before the absolute mark gets set; historically, * "/a/,/b/" did NOT set vi's absolute mark, but "/a/,/b/d" did. */ if ((F_ISSET(sp, SC_VI) || F_ISSET(ecp, E_NOPRDEF)) && F_ISSET(ecp, E_USELASTCMD) && vi_address == 0) { switch (ecp->addrcnt) { case 2: if (sp->lno != (ecp->addr2.lno ? ecp->addr2.lno : 1)) { sp->lno = ecp->addr2.lno ? ecp->addr2.lno : 1; sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } break; case 1: if (sp->lno != (ecp->addr1.lno ? ecp->addr1.lno : 1)) { sp->lno = ecp->addr1.lno ? ecp->addr1.lno : 1; sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } break; } ecp->cp = ecp->save_cmd; ecp->clen = ecp->save_cmdlen; goto loop; } /* * Set the absolute mark -- we have to set it for vi here, in case * it's a compound command, e.g. ":5p|6" should set the absolute * mark for vi. */ if (F_ISSET(ecp, E_ABSMARK)) { cur.lno = sp->lno; cur.cno = sp->cno; F_CLR(ecp, E_ABSMARK); if (mark_set(sp, ABSMARK1, &cur, 1)) goto err; } #if defined(DEBUG) && defined(COMLOG) ex_comlog(sp, ecp); #endif /* Increment the command count if not called from vi. */ if (F_ISSET(sp, SC_EX)) ++sp->ccnt; /* * If file state available, and not doing a global command, * log the start of an action. */ if (sp->ep != NULL && !F_ISSET(sp, SC_EX_GLOBAL)) (void)log_cursor(sp); /* * !!! * There are two special commands for the purposes of this code: the * default command () or the scrolling commands (^D * and ) as the first non- characters in the line. * * If this is the first command in the command line, we received the * command from the ex command loop and we're talking to a tty, and * and there's nothing else on the command line, and it's one of the * special commands, we move back up to the previous line, and erase * the prompt character with the output. Since ex runs in canonical * mode, we don't have to do anything else, a has already * been echoed by the tty driver. It's OK if vi calls us -- we won't * be in ex mode so we'll do nothing. */ if (F_ISSET(ecp, E_NRSEP)) { if (sp->ep != NULL && F_ISSET(sp, SC_EX) && !F_ISSET(gp, G_SCRIPTED) && (F_ISSET(ecp, E_USELASTCMD) || ecp->cmd == &cmds[C_SCROLL])) gp->scr_ex_adjust(sp, EX_TERM_SCROLL); F_CLR(ecp, E_NRSEP); } /* * Call the underlying function for the ex command. * * XXX * Interrupts behave like errors, for now. */ if (ecp->cmd->fn(sp, ecp) || INTERRUPTED(sp)) { if (F_ISSET(gp, G_SCRIPTED)) F_SET(sp, SC_EXIT_FORCE); goto err; } #ifdef DEBUG /* Make sure no function left global temporary space locked. */ if (F_ISSET(gp, G_TMP_INUSE)) { F_CLR(gp, G_TMP_INUSE); msgq_wstr(sp, M_ERR, ecp->cmd->name, "087|%s: temporary buffer not released"); } #endif /* * Ex displayed the number of lines modified immediately after each * command, so the command "1,10d|1,10d" would display: * * 10 lines deleted * 10 lines deleted * * * Executing ex commands from vi only reported the final modified * lines message -- that's wrong enough that we don't match it. */ if (F_ISSET(sp, SC_EX)) mod_rpt(sp); /* * Integrate any offset parsed by the underlying command, and make * sure the referenced line exists. * * XXX * May not match historic practice (which I've never been able to * completely figure out.) For example, the '=' command from vi * mode often got the offset wrong, and complained it was too large, * but didn't seem to have a problem with the cursor. If anyone * complains, ask them how it's supposed to work, they might know. */ if (sp->ep != NULL && ecp->flagoff) { if (ecp->flagoff < 0) { if (sp->lno <= -ecp->flagoff) { msgq(sp, M_ERR, "088|Flag offset to before line 1"); goto err; } } else { if (!NPFITS(MAX_REC_NUMBER, sp->lno, ecp->flagoff)) { ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER); goto err; } if (!db_exist(sp, sp->lno + ecp->flagoff)) { msgq(sp, M_ERR, "089|Flag offset past end-of-file"); goto err; } } sp->lno += ecp->flagoff; } /* * If the command executed successfully, we may want to display a line * based on the autoprint option or an explicit print flag. (Make sure * that there's a line to display.) Also, the autoprint edit option is * turned off for the duration of global commands. */ if (F_ISSET(sp, SC_EX) && sp->ep != NULL && sp->lno != 0) { /* * The print commands have already handled the `print' flags. * If so, clear them. */ if (FL_ISSET(ecp->iflags, E_CLRFLAG)) FL_CLR(ecp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT); /* If hash set only because of the number option, discard it. */ if (F_ISSET(ecp, E_OPTNUM)) FL_CLR(ecp->iflags, E_C_HASH); /* * If there was an explicit flag to display the new cursor line, * or autoprint is set and a change was made, display the line. * If any print flags were set use them, else default to print. */ LF_INIT(FL_ISSET(ecp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT)); if (!LF_ISSET(E_C_HASH | E_C_LIST | E_C_PRINT | E_NOAUTO) && !F_ISSET(sp, SC_EX_GLOBAL) && O_ISSET(sp, O_AUTOPRINT) && F_ISSET(ecp, E_AUTOPRINT)) LF_INIT(E_C_PRINT); if (LF_ISSET(E_C_HASH | E_C_LIST | E_C_PRINT)) { cur.lno = sp->lno; cur.cno = 0; (void)ex_print(sp, ecp, &cur, &cur, flags); } } /* * If the command had an associated "+cmd", it has to be executed * before we finish executing any more of this ex command. For * example, consider a .exrc file that contains the following lines: * * :set all * :edit +25 file.c|s/abc/ABC/|1 * :3,5 print * * This can happen more than once -- the historic vi simply hung or * dropped core, of course. Prepend the + command back into the * current command and continue. We may have to add an additional * character. We know that it will fit because we * discarded at least one space and the + character. */ if (arg1_len != 0) { /* * If the last character of the + command was a * character, it would be treated differently because of the * append. Quote it, if necessary. */ if (IS_ESCAPE(sp, ecp, arg1[arg1_len - 1])) { *--ecp->save_cmd = CH_LITERAL; ++ecp->save_cmdlen; } ecp->save_cmd -= arg1_len; ecp->save_cmdlen += arg1_len; - MEMCPY(ecp->save_cmd, arg1, arg1_len); + MEMMOVE(ecp->save_cmd, arg1, arg1_len); /* * Any commands executed from a +cmd are executed starting at * the first column of the last line of the file -- NOT the * first nonblank.) The main file startup code doesn't know * that a +cmd was set, however, so it may have put us at the * top of the file. (Note, this is safe because we must have * switched files to get here.) */ F_SET(ecp, E_MOVETOEND); } /* Update the current command. */ ecp->cp = ecp->save_cmd; ecp->clen = ecp->save_cmdlen; /* * !!! * If we've changed screens or underlying files, any pending global or * v command, or @ buffer that has associated addresses, has to be * discarded. This is historic practice for globals, and necessary for * @ buffers that had associated addresses. * * Otherwise, if we've changed underlying files, it's not a problem, * we continue with the rest of the ex command(s), operating on the * new file. However, if we switch screens (either by exiting or by * an explicit command), we have no way of knowing where to put output * messages, and, since we don't control screens here, we could screw * up the upper layers, (e.g. we could exit/reenter a screen multiple * times). So, return and continue after we've got a new screen. */ if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE | SC_FSWITCH | SC_SSWITCH)) { at_found = gv_found = 0; SLIST_FOREACH(ecp, sp->gp->ecq, q) switch (ecp->agv_flags) { case 0: case AGV_AT_NORANGE: break; case AGV_AT: if (!at_found) { at_found = 1; msgq(sp, M_ERR, "090|@ with range running when the file/screen changed"); } break; case AGV_GLOBAL: case AGV_V: if (!gv_found) { gv_found = 1; msgq(sp, M_ERR, "091|Global/v command running when the file/screen changed"); } break; default: abort(); } if (at_found || gv_found) goto discard; if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE | SC_SSWITCH)) goto rsuccess; } goto loop; /* NOTREACHED */ err: /* * On command failure, we discard keys and pending commands remaining, * as well as any keys that were mapped and waiting. The save_cmdlen * test is not necessarily correct. If we fail early enough we don't * know if the entire string was a single command or not. Guess, as * it's useful to know if commands other than the current one are being * discarded. */ if (ecp->save_cmdlen == 0) for (; ecp->clen; --ecp->clen) { ch = *ecp->cp++; if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { --ecp->clen; ++ecp->cp; } else if (ch == '\n' || ch == '|') { if (ecp->clen > 1) ecp->save_cmdlen = 1; break; } } if (ecp->save_cmdlen != 0 || SLIST_FIRST(gp->ecq) != &gp->excmd) { discard: msgq(sp, M_BERR, "092|Ex command failed: pending commands discarded"); ex_discard(sp); } if (v_event_flush(sp, CH_MAPPED)) msgq(sp, M_BERR, "093|Ex command failed: mapped keys discarded"); rfail: tmp = 1; if (0) rsuccess: tmp = 0; /* Turn off any file name error information. */ gp->if_name = NULL; /* Turn off the global bit. */ F_CLR(sp, SC_EX_GLOBAL); return (tmp); } /* * ex_range -- * Get a line range for ex commands, or perform a vi ex address search. * * PUBLIC: int ex_range(SCR *, EXCMD *, int *); */ int ex_range(SCR *sp, EXCMD *ecp, int *errp) { enum { ADDR_FOUND, ADDR_NEED, ADDR_NONE } addr; GS *gp; EX_PRIVATE *exp; MARK m; int isaddr; *errp = 0; /* * Parse comma or semi-colon delimited line specs. * * Semi-colon delimiters update the current address to be the last * address. For example, the command * * :3;/pattern/ecp->cp * * will search for pattern from line 3. In addition, if ecp->cp * is not a valid command, the current line will be left at 3, not * at the original address. * * Extra addresses are discarded, starting with the first. * * !!! * If any addresses are missing, they default to the current line. * This was historically true for both leading and trailing comma * delimited addresses as well as for trailing semicolon delimited * addresses. For consistency, we make it true for leading semicolon * addresses as well. */ gp = sp->gp; exp = EXP(sp); for (addr = ADDR_NONE, ecp->addrcnt = 0; ecp->clen > 0;) switch (*ecp->cp) { case '%': /* Entire file. */ /* Vi ex address searches didn't permit % signs. */ if (F_ISSET(ecp, E_VISEARCH)) goto ret; /* It's an error if the file is empty. */ if (sp->ep == NULL) { ex_badaddr(sp, NULL, A_EMPTY, NUM_OK); *errp = 1; return (0); } /* * !!! * A percent character addresses all of the lines in * the file. Historically, it couldn't be followed by * any other address. We do it as a text substitution * for simplicity. POSIX 1003.2 is expected to follow * this practice. * * If it's an empty file, the first line is 0, not 1. */ if (addr == ADDR_FOUND) { ex_badaddr(sp, NULL, A_COMBO, NUM_OK); *errp = 1; return (0); } if (db_last(sp, &ecp->addr2.lno)) return (1); ecp->addr1.lno = ecp->addr2.lno == 0 ? 0 : 1; ecp->addr1.cno = ecp->addr2.cno = 0; ecp->addrcnt = 2; addr = ADDR_FOUND; ++ecp->cp; --ecp->clen; break; case ',': /* Comma delimiter. */ /* Vi ex address searches didn't permit commas. */ if (F_ISSET(ecp, E_VISEARCH)) goto ret; /* FALLTHROUGH */ case ';': /* Semi-colon delimiter. */ if (sp->ep == NULL) { ex_badaddr(sp, NULL, A_EMPTY, NUM_OK); *errp = 1; return (0); } if (addr != ADDR_FOUND) switch (ecp->addrcnt) { case 0: ecp->addr1.lno = sp->lno; ecp->addr1.cno = sp->cno; ecp->addrcnt = 1; break; case 2: ecp->addr1 = ecp->addr2; /* FALLTHROUGH */ case 1: ecp->addr2.lno = sp->lno; ecp->addr2.cno = sp->cno; ecp->addrcnt = 2; break; } if (*ecp->cp == ';') switch (ecp->addrcnt) { case 0: abort(); /* NOTREACHED */ case 1: sp->lno = ecp->addr1.lno; sp->cno = ecp->addr1.cno; break; case 2: sp->lno = ecp->addr2.lno; sp->cno = ecp->addr2.cno; break; } addr = ADDR_NEED; /* FALLTHROUGH */ case ' ': /* Whitespace. */ case '\t': /* Whitespace. */ ++ecp->cp; --ecp->clen; break; default: /* Get a line specification. */ if (ex_line(sp, ecp, &m, &isaddr, errp)) return (1); if (*errp) return (0); if (!isaddr) goto ret; if (addr == ADDR_FOUND) { ex_badaddr(sp, NULL, A_COMBO, NUM_OK); *errp = 1; return (0); } switch (ecp->addrcnt) { case 0: ecp->addr1 = m; ecp->addrcnt = 1; break; case 1: ecp->addr2 = m; ecp->addrcnt = 2; break; case 2: ecp->addr1 = ecp->addr2; ecp->addr2 = m; break; } addr = ADDR_FOUND; break; } /* * !!! * Vi ex address searches are indifferent to order or trailing * semi-colons. */ ret: if (F_ISSET(ecp, E_VISEARCH)) return (0); if (addr == ADDR_NEED) switch (ecp->addrcnt) { case 0: ecp->addr1.lno = sp->lno; ecp->addr1.cno = sp->cno; ecp->addrcnt = 1; break; case 2: ecp->addr1 = ecp->addr2; /* FALLTHROUGH */ case 1: ecp->addr2.lno = sp->lno; ecp->addr2.cno = sp->cno; ecp->addrcnt = 2; break; } if (ecp->addrcnt == 2 && ecp->addr2.lno < ecp->addr1.lno) { msgq(sp, M_ERR, "094|The second address is smaller than the first"); *errp = 1; } return (0); } /* * ex_line -- * Get a single line address specifier. * * The way the "previous context" mark worked was that any "non-relative" * motion set it. While ex/vi wasn't totally consistent about this, ANY * numeric address, search pattern, '$', or mark reference in an address * was considered non-relative, and set the value. Which should explain * why we're hacking marks down here. The problem was that the mark was * only set if the command was called, i.e. we have to set a flag and test * it later. * * XXX * This is probably still not exactly historic practice, although I think * it's fairly close. */ static int ex_line(SCR *sp, EXCMD *ecp, MARK *mp, int *isaddrp, int *errp) { enum nresult nret; EX_PRIVATE *exp; GS *gp; long total, val; int isneg; int (*sf)(SCR *, MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int); CHAR_T *endp; gp = sp->gp; exp = EXP(sp); *isaddrp = *errp = 0; F_CLR(ecp, E_DELTA); /* No addresses permitted until a file has been read in. */ if (sp->ep == NULL && STRCHR(L("$0123456789'\\/?.+-^"), *ecp->cp)) { ex_badaddr(sp, NULL, A_EMPTY, NUM_OK); *errp = 1; return (0); } switch (*ecp->cp) { case '$': /* Last line in the file. */ *isaddrp = 1; F_SET(ecp, E_ABSMARK); mp->cno = 0; if (db_last(sp, &mp->lno)) return (1); ++ecp->cp; --ecp->clen; break; /* Absolute line number. */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': *isaddrp = 1; F_SET(ecp, E_ABSMARK); if ((nret = nget_slong(&val, ecp->cp, &endp, 10)) != NUM_OK) { ex_badaddr(sp, NULL, A_NOTSET, nret); *errp = 1; return (0); } if (!NPFITS(MAX_REC_NUMBER, 0, val)) { ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER); *errp = 1; return (0); } mp->lno = val; mp->cno = 0; ecp->clen -= (endp - ecp->cp); ecp->cp = endp; break; case '\'': /* Use a mark. */ *isaddrp = 1; F_SET(ecp, E_ABSMARK); if (ecp->clen == 1) { msgq(sp, M_ERR, "095|No mark name supplied"); *errp = 1; return (0); } if (mark_get(sp, ecp->cp[1], mp, M_ERR)) { *errp = 1; return (0); } ecp->cp += 2; ecp->clen -= 2; break; case '\\': /* Search: forward/backward. */ /* * !!! * I can't find any difference between // and \/ or between * ?? and \?. Mark Horton doesn't remember there being any * difference. C'est la vie. */ if (ecp->clen < 2 || (ecp->cp[1] != '/' && ecp->cp[1] != '?')) { msgq(sp, M_ERR, "096|\\ not followed by / or ?"); *errp = 1; return (0); } ++ecp->cp; --ecp->clen; sf = ecp->cp[0] == '/' ? f_search : b_search; goto search; case '/': /* Search forward. */ sf = f_search; goto search; case '?': /* Search backward. */ sf = b_search; search: mp->lno = sp->lno; mp->cno = sp->cno; if (sf(sp, mp, mp, ecp->cp, ecp->clen, &endp, SEARCH_MSG | SEARCH_PARSE | SEARCH_SET | (F_ISSET(ecp, E_SEARCH_WMSG) ? SEARCH_WMSG : 0))) { *errp = 1; return (0); } /* Fix up the command pointers. */ ecp->clen -= (endp - ecp->cp); ecp->cp = endp; *isaddrp = 1; F_SET(ecp, E_ABSMARK); break; case '.': /* Current position. */ *isaddrp = 1; mp->cno = sp->cno; /* If an empty file, then '.' is 0, not 1. */ if (sp->lno == 1) { if (db_last(sp, &mp->lno)) return (1); if (mp->lno != 0) mp->lno = 1; } else mp->lno = sp->lno; /* * !!! * Historically, . was the same as .+, i.e. * the '+' could be omitted. (This feature is found in ed * as well.) */ if (ecp->clen > 1 && ISDIGIT(ecp->cp[1])) *ecp->cp = '+'; else { ++ecp->cp; --ecp->clen; } break; } /* Skip trailing s. */ for (; ecp->clen > 0 && cmdskip(ecp->cp[0]); ++ecp->cp, --ecp->clen); /* * Evaluate any offset. If no address yet found, the offset * is relative to ".". */ total = 0; if (ecp->clen != 0 && (ISDIGIT(ecp->cp[0]) || ecp->cp[0] == '+' || ecp->cp[0] == '-' || ecp->cp[0] == '^')) { if (!*isaddrp) { *isaddrp = 1; mp->lno = sp->lno; mp->cno = sp->cno; } /* * Evaluate an offset, defined as: * * [+-^]*[]*[0-9]* * * The rough translation is any number of signs, optionally * followed by numbers, or a number by itself, all * separated. * * !!! * All address offsets were additive, e.g. "2 2 3p" was the * same as "7p", or, "/ZZZ/ 2" was the same as "/ZZZ/+2". * Note, however, "2 /ZZZ/" was an error. It was also legal * to insert signs without numbers, so "3 - 2" was legal, and * equal to 4. * * !!! * Offsets were historically permitted for any line address, * e.g. the command "1,2 copy 2 2 2 2" copied lines 1,2 after * line 8. * * !!! * Offsets were historically permitted for search commands, * and handled as addresses: "/pattern/2 2 2" was legal, and * referenced the 6th line after pattern. */ F_SET(ecp, E_DELTA); for (;;) { for (; ecp->clen > 0 && cmdskip(ecp->cp[0]); ++ecp->cp, --ecp->clen); if (ecp->clen == 0 || (!ISDIGIT(ecp->cp[0]) && ecp->cp[0] != '+' && ecp->cp[0] != '-' && ecp->cp[0] != '^')) break; if (!ISDIGIT(ecp->cp[0]) && !ISDIGIT(ecp->cp[1])) { total += ecp->cp[0] == '+' ? 1 : -1; --ecp->clen; ++ecp->cp; } else { if (ecp->cp[0] == '-' || ecp->cp[0] == '^') { ++ecp->cp; --ecp->clen; isneg = 1; } else isneg = 0; /* Get a signed long, add it to the total. */ if ((nret = nget_slong(&val, ecp->cp, &endp, 10)) != NUM_OK || (nret = NADD_SLONG(sp, total, val)) != NUM_OK) { ex_badaddr(sp, NULL, A_NOTSET, nret); *errp = 1; return (0); } total += isneg ? -val : val; ecp->clen -= (endp - ecp->cp); ecp->cp = endp; } } } /* * Any value less than 0 is an error. Make sure that the new value * will fit into a recno_t. */ if (*isaddrp && total != 0) { if (total < 0) { if (-total > mp->lno) { msgq(sp, M_ERR, "097|Reference to a line number less than 0"); *errp = 1; return (0); } } else if (!NPFITS(MAX_REC_NUMBER, mp->lno, total)) { ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER); *errp = 1; return (0); } mp->lno += total; } return (0); } /* * ex_load -- * Load up the next command, which may be an @ buffer or global command. */ static int ex_load(SCR *sp) { GS *gp; EXCMD *ecp; RANGE *rp; F_CLR(sp, SC_EX_GLOBAL); /* * Lose any exhausted commands. We know that the first command * can't be an AGV command, which makes things a bit easier. */ for (gp = sp->gp;;) { ecp = SLIST_FIRST(gp->ecq); /* Discard the allocated source name as requested. */ if (F_ISSET(ecp, E_NAMEDISCARD)) free(ecp->if_name); /* * If we're back to the original structure, leave it around, * since we've returned to the beginning of the command stack. */ if (ecp == &gp->excmd) { ecp->if_name = NULL; return (0); } /* * ecp->clen will be 0 for the first discarded command, but * may not be 0 for subsequent ones, e.g. if the original * command was ":g/xx/@a|s/b/c/", then when we discard the * command pushed on the stack by the @a, we have to resume * the global command which included the substitute command. */ if (ecp->clen != 0) return (0); /* * If it's an @, global or v command, we may need to continue * the command on a different line. */ if (FL_ISSET(ecp->agv_flags, AGV_ALL)) { /* Discard any exhausted ranges. */ while ((rp = TAILQ_FIRST(ecp->rq)) != NULL) if (rp->start > rp->stop) { TAILQ_REMOVE(ecp->rq, rp, q); free(rp); } else break; /* If there's another range, continue with it. */ if (rp != NULL) break; /* If it's a global/v command, fix up the last line. */ if (FL_ISSET(ecp->agv_flags, AGV_GLOBAL | AGV_V) && ecp->range_lno != OOBLNO) if (db_exist(sp, ecp->range_lno)) sp->lno = ecp->range_lno; else { if (db_last(sp, &sp->lno)) return (1); if (sp->lno == 0) sp->lno = 1; } free(ecp->o_cp); } /* Discard the EXCMD. */ SLIST_REMOVE_HEAD(gp->ecq, q); free(ecp); } /* * We only get here if it's an active @, global or v command. Set * the current line number, and get a new copy of the command for * the parser. Note, the original pointer almost certainly moved, * so we have play games. */ ecp->cp = ecp->o_cp; MEMCPY(ecp->cp, ecp->cp + ecp->o_clen, ecp->o_clen); ecp->clen = ecp->o_clen; ecp->range_lno = sp->lno = rp->start++; if (FL_ISSET(ecp->agv_flags, AGV_GLOBAL | AGV_V)) F_SET(sp, SC_EX_GLOBAL); return (0); } /* * ex_discard -- * Discard any pending ex commands. */ static int ex_discard(SCR *sp) { GS *gp; EXCMD *ecp; RANGE *rp; /* * We know the first command can't be an AGV command, so we don't * process it specially. We do, however, nail the command itself. */ for (gp = sp->gp;;) { ecp = SLIST_FIRST(gp->ecq); if (F_ISSET(ecp, E_NAMEDISCARD)) free(ecp->if_name); /* Reset the last command without dropping it. */ if (ecp == &gp->excmd) break; if (FL_ISSET(ecp->agv_flags, AGV_ALL)) { while ((rp = TAILQ_FIRST(ecp->rq)) != NULL) { TAILQ_REMOVE(ecp->rq, rp, q); free(rp); } free(ecp->o_cp); } SLIST_REMOVE_HEAD(gp->ecq, q); free(ecp); } ecp->if_name = NULL; ecp->clen = 0; return (0); } /* * ex_unknown -- * Display an unknown command name. */ static void ex_unknown(SCR *sp, CHAR_T *cmd, size_t len) { size_t blen; CHAR_T *bp; GET_SPACE_GOTOW(sp, bp, blen, len + 1); bp[len] = '\0'; MEMCPY(bp, cmd, len); msgq_wstr(sp, M_ERR, bp, "098|The %s command is unknown"); FREE_SPACEW(sp, bp, blen); alloc_err: return; } /* * ex_is_abbrev - * The vi text input routine needs to know if ex thinks this is an * [un]abbreviate command, so it can turn off abbreviations. See * the usual ranting in the vi/v_txt_ev.c:txt_abbrev() routine. * * PUBLIC: int ex_is_abbrev(CHAR_T *, size_t); */ int ex_is_abbrev(CHAR_T *name, size_t len) { EXCMDLIST const *cp; return ((cp = ex_comm_search(name, len)) != NULL && (cp == &cmds[C_ABBR] || cp == &cmds[C_UNABBREVIATE])); } /* * ex_is_unmap - * The vi text input routine needs to know if ex thinks this is an * unmap command, so it can turn off input mapping. See the usual * ranting in the vi/v_txt_ev.c:txt_unmap() routine. * * PUBLIC: int ex_is_unmap(CHAR_T *, size_t); */ int ex_is_unmap(CHAR_T *name, size_t len) { EXCMDLIST const *cp; /* * The command the vi input routines are really interested in * is "unmap!", not just unmap. */ if (name[len - 1] != '!') return (0); --len; return ((cp = ex_comm_search(name, len)) != NULL && cp == &cmds[C_UNMAP]); } /* * ex_comm_search -- * Search for a command name. */ static EXCMDLIST const * ex_comm_search(CHAR_T *name, size_t len) { EXCMDLIST const *cp; for (cp = cmds; cp->name != NULL; ++cp) { if (cp->name[0] > name[0]) return (NULL); if (cp->name[0] != name[0]) continue; if (!MEMCMP(name, cp->name, len)) return (cp); } return (NULL); } /* * ex_badaddr -- * Display a bad address message. * * PUBLIC: void ex_badaddr * PUBLIC: (SCR *, EXCMDLIST const *, enum badaddr, enum nresult); */ void ex_badaddr(SCR *sp, const EXCMDLIST *cp, enum badaddr ba, enum nresult nret) { recno_t lno; switch (nret) { case NUM_OK: break; case NUM_ERR: msgq(sp, M_SYSERR, NULL); return; case NUM_OVER: msgq(sp, M_ERR, "099|Address value overflow"); return; case NUM_UNDER: msgq(sp, M_ERR, "100|Address value underflow"); return; } /* * When encountering an address error, tell the user if there's no * underlying file, that's the real problem. */ if (sp->ep == NULL) { ex_wemsg(sp, cp ? cp->name : NULL, EXM_NOFILEYET); return; } switch (ba) { case A_COMBO: msgq(sp, M_ERR, "101|Illegal address combination"); break; case A_EOF: if (db_last(sp, &lno)) return; if (lno != 0) { msgq(sp, M_ERR, "102|Illegal address: only %lu lines in the file", (u_long)lno); break; } /* FALLTHROUGH */ case A_EMPTY: msgq(sp, M_ERR, "103|Illegal address: the file is empty"); break; case A_NOTSET: abort(); /* NOTREACHED */ case A_ZERO: msgq_wstr(sp, M_ERR, cp->name, "104|The %s command doesn't permit an address of 0"); break; } return; } #if defined(DEBUG) && defined(COMLOG) /* * ex_comlog -- * Log ex commands. */ static void ex_comlog(sp, ecp) SCR *sp; EXCMD *ecp; { TRACE(sp, "ecmd: "WS, ecp->cmd->name); if (ecp->addrcnt > 0) { TRACE(sp, " a1 %d", ecp->addr1.lno); if (ecp->addrcnt > 1) TRACE(sp, " a2: %d", ecp->addr2.lno); } if (ecp->lineno) TRACE(sp, " line %d", ecp->lineno); if (ecp->flags) TRACE(sp, " flags 0x%x", ecp->flags); if (FL_ISSET(ecp->iflags, E_C_BUFFER)) TRACE(sp, " buffer "WC, ecp->buffer); if (ecp->argc) { int cnt; for (cnt = 0; cnt < ecp->argc; ++cnt) TRACE(sp, " arg %d: {"WS"}", cnt, ecp->argv[cnt]->bp); } TRACE(sp, "\n"); } #endif Index: head/contrib/nvi/ex/ex.h =================================================================== --- head/contrib/nvi/ex/ex.h (revision 365498) +++ head/contrib/nvi/ex/ex.h (revision 365499) @@ -1,233 +1,231 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: ex.h,v 10.31 2012/10/03 02:33:24 zy Exp $ */ #define PROMPTCHAR ':' /* Prompt using a colon. */ typedef struct _excmdlist { /* Ex command table structure. */ CHAR_T *name; /* Command name, underlying function. */ int (*fn)(SCR *, EXCMD *); #define E_ADDR1 0x00000001 /* One address. */ #define E_ADDR2 0x00000002 /* Two addresses. */ #define E_ADDR2_ALL 0x00000004 /* Zero/two addresses; zero == all. */ #define E_ADDR2_NONE 0x00000008 /* Zero/two addresses; zero == none. */ #define E_ADDR_ZERO 0x00000010 /* 0 is a legal addr1. */ #define E_ADDR_ZERODEF 0x00000020 /* 0 is default addr1 of empty files. */ #define E_AUTOPRINT 0x00000040 /* Command always sets autoprint. */ #define E_CLRFLAG 0x00000080 /* Clear the print (#, l, p) flags. */ #define E_NEWSCREEN 0x00000100 /* Create a new screen. */ #define E_SECURE 0x00000200 /* Permission denied if O_SECURE set. */ #define E_VIONLY 0x00000400 /* Meaningful only in vi. */ #define __INUSE1 0xfffff800 /* Same name space as EX_PRIVATE. */ u_int16_t flags; char *syntax; /* Syntax script. */ char *usage; /* Usage line. */ char *help; /* Help line. */ } EXCMDLIST; #define MAXCMDNAMELEN 12 /* Longest command name. */ extern EXCMDLIST const cmds[]; /* Table of ex commands. */ /* * !!! * QUOTING NOTE: * * Historically, .exrc files and EXINIT variables could only use ^V as an * escape character, neither ^Q or a user specified character worked. We * enforce that here, just in case someone depends on it. */ #define IS_ESCAPE(sp, cmdp, ch) \ (F_ISSET(cmdp, E_VLITONLY) ? \ (ch) == CH_LITERAL : KEY_VAL(sp, ch) == K_VLNEXT) #define IS_SHELLMETA(sp, ch) \ ((ch) <= CHAR_MAX && strchr(O_STR(sp, O_SHELLMETA), ch) != NULL) /* * File state must be checked for each command -- any ex command may be entered * at any time, and most of them won't work well if a file hasn't yet been read * in. Historic vi generally took the easy way out and dropped core. */ #define NEEDFILE(sp, cmdp) { \ if ((sp)->ep == NULL) { \ ex_wemsg(sp, (cmdp)->cmd->name, EXM_NOFILEYET); \ return (1); \ } \ } /* Range structures for global and @ commands. */ typedef struct _range RANGE; struct _range { /* Global command range. */ TAILQ_ENTRY(_range) q; /* Linked list of ranges. */ recno_t start, stop; /* Start/stop of the range. */ }; /* Ex command structure. */ struct _excmd { SLIST_ENTRY(_excmd) q; /* Linked list of commands. */ char *if_name; /* Associated file. */ recno_t if_lno; /* Associated line number. */ /* Clear the structure for the ex parser. */ #define CLEAR_EX_PARSER(cmdp) \ memset(&((cmdp)->cp), 0, ((char *)&(cmdp)->flags - \ (char *)&((cmdp)->cp)) + sizeof((cmdp)->flags)) CHAR_T *cp; /* Current command text. */ size_t clen; /* Current command length. */ CHAR_T *save_cmd; /* Remaining command. */ size_t save_cmdlen; /* Remaining command length. */ EXCMDLIST const *cmd; /* Command: entry in command table. */ EXCMDLIST rcmd; /* Command: table entry/replacement. */ TAILQ_HEAD(_rh, _range) rq[1]; /* @/global range: linked list. */ recno_t range_lno; /* @/global range: set line number. */ CHAR_T *o_cp; /* Original @/global command. */ size_t o_clen; /* Original @/global command length. */ #define AGV_AT 0x01 /* @ buffer execution. */ #define AGV_AT_NORANGE 0x02 /* @ buffer execution without range. */ #define AGV_GLOBAL 0x04 /* global command. */ #define AGV_V 0x08 /* v command. */ #define AGV_ALL (AGV_AT | AGV_AT_NORANGE | AGV_GLOBAL | AGV_V) u_int8_t agv_flags; /* Clear the structure before each ex command. */ #define CLEAR_EX_CMD(cmdp) { \ u_int32_t L__f = F_ISSET(cmdp, E_PRESERVE); \ memset(&((cmdp)->buffer), 0, ((char *)&(cmdp)->flags - \ (char *)&((cmdp)->buffer)) + sizeof((cmdp)->flags)); \ F_SET(cmdp, L__f); \ } CHAR_T buffer; /* Command: named buffer. */ recno_t lineno; /* Command: line number. */ long count; /* Command: signed count. */ long flagoff; /* Command: signed flag offset. */ int addrcnt; /* Command: addresses (0, 1 or 2). */ MARK addr1; /* Command: 1st address. */ MARK addr2; /* Command: 2nd address. */ ARGS **argv; /* Command: array of arguments. */ int argc; /* Command: count of arguments. */ #define E_C_BUFFER 0x00001 /* Buffer name specified. */ #define E_C_CARAT 0x00002 /* ^ flag. */ #define E_C_COUNT 0x00004 /* Count specified. */ #define E_C_COUNT_NEG 0x00008 /* Count was signed negative. */ #define E_C_COUNT_POS 0x00010 /* Count was signed positive. */ #define E_C_DASH 0x00020 /* - flag. */ #define E_C_DOT 0x00040 /* . flag. */ #define E_C_EQUAL 0x00080 /* = flag. */ #define E_C_FORCE 0x00100 /* ! flag. */ #define E_C_HASH 0x00200 /* # flag. */ #define E_C_LIST 0x00400 /* l flag. */ #define E_C_PLUS 0x00800 /* + flag. */ #define E_C_PRINT 0x01000 /* p flag. */ u_int16_t iflags; /* User input information. */ #define __INUSE2 0x000007ff /* Same name space as EXCMDLIST. */ #define E_BLIGNORE 0x00000800 /* Ignore blank lines. */ #define E_NAMEDISCARD 0x00001000 /* Free/discard the name. */ #define E_NOAUTO 0x00002000 /* Don't do autoprint output. */ #define E_NOPRDEF 0x00004000 /* Don't print as default. */ #define E_NRSEP 0x00008000 /* Need to line adjust ex output. */ #define E_OPTNUM 0x00010000 /* Number edit option affected. */ #define E_VLITONLY 0x00020000 /* Use ^V quoting only. */ #define E_PRESERVE 0x0003f800 /* Bits to preserve across commands. */ #define E_ABSMARK 0x00040000 /* Set the absolute mark. */ #define E_ADDR_DEF 0x00080000 /* Default addresses used. */ #define E_DELTA 0x00100000 /* Search address with delta. */ #define E_MODIFY 0x00200000 /* File name expansion modified arg. */ #define E_MOVETOEND 0x00400000 /* Move to the end of the file first. */ #define E_NEWLINE 0x00800000 /* Found ending . */ #define E_SEARCH_WMSG 0x01000000 /* Display search-wrapped message. */ #define E_USELASTCMD 0x02000000 /* Use the last command. */ #define E_VISEARCH 0x04000000 /* It's really a vi search command. */ u_int32_t flags; /* Current flags. */ }; /* Ex private, per-screen memory. */ typedef struct _ex_private { /* Tag file list. */ TAILQ_HEAD(_tagfh, _tagf) tagfq[1]; TAILQ_HEAD(_tqh, _tagq) tq[1]; /* Tag queue. */ SLIST_HEAD(_csch, _csc) cscq[1];/* Cscope connection list. */ CHAR_T *tag_last; /* Saved last tag string. */ CHAR_T *lastbcomm; /* Last bang command. */ ARGS **args; /* Command: argument list. */ int argscnt; /* Command: argument list count. */ int argsoff; /* Command: offset into arguments. */ u_int32_t fdef; /* Saved E_C_* default command flags. */ char *ibp; /* File line input buffer. */ size_t ibp_len; /* File line input buffer length. */ CONVWIN ibcw; /* File line input conversion buffer. */ /* * Buffers for the ex output. The screen/vi support doesn't do any * character buffering of any kind. We do it here so that we're not * calling the screen output routines on every character. * * XXX * Change to grow dynamically. */ char obp[1024]; /* Ex output buffer. */ size_t obp_len; /* Ex output buffer length. */ #define EXP_CSCINIT 0x01 /* Cscope initialized. */ u_int8_t flags; } EX_PRIVATE; #define EXP(sp) ((EX_PRIVATE *)((sp)->ex_private)) /* * Filter actions: * * FILTER_BANG !: filter text through the utility. * FILTER_RBANG !: read from the utility (without stdin). * FILTER_READ read: read from the utility (with stdin). * FILTER_WRITE write: write to the utility, display its output. */ enum filtertype { FILTER_BANG, FILTER_RBANG, FILTER_READ, FILTER_WRITE }; /* Ex common error messages. */ typedef enum { EXM_EMPTYBUF, /* Empty buffer. */ EXM_FILECOUNT, /* Too many file names. */ EXM_NOCANON, /* No terminal interface. */ EXM_NOCANON_F, /* EXM_NOCANO: filter version. */ EXM_NOFILEYET, /* Illegal until a file read in. */ EXM_NOPREVBUF, /* No previous buffer specified. */ EXM_NOPREVRE, /* No previous RE specified. */ EXM_NOSUSPEND, /* No suspension. */ EXM_SECURE, /* Illegal if secure edit option set. */ EXM_SECURE_F, /* EXM_SECURE: filter version */ EXM_USAGE /* Standard usage message. */ } exm_t; /* Ex address error types. */ enum badaddr { A_COMBO, A_EMPTY, A_EOF, A_NOTSET, A_ZERO }; /* Ex common tag error messages. */ typedef enum { TAG_BADLNO, /* Tag line doesn't exist. */ TAG_EMPTY, /* Tags stack is empty. */ TAG_SEARCH /* Tags search pattern wasn't found. */ } tagmsg_t; #include "ex_def.h" #include "extern.h" Index: head/contrib/nvi/ex/ex_abbrev.c =================================================================== --- head/contrib/nvi/ex/ex_abbrev.c (revision 365498) +++ head/contrib/nvi/ex/ex_abbrev.c (revision 365499) @@ -1,113 +1,109 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_abbrev.c,v 10.10 2001/12/16 18:18:54 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" /* * ex_abbr -- :abbreviate [key replacement] * Create an abbreviation or display abbreviations. * * PUBLIC: int ex_abbr(SCR *, EXCMD *); */ int ex_abbr(SCR *sp, EXCMD *cmdp) { CHAR_T *p; size_t len; switch (cmdp->argc) { case 0: if (seq_dump(sp, SEQ_ABBREV, 0) == 0) msgq(sp, M_INFO, "105|No abbreviations to display"); return (0); case 2: break; default: abort(); } /* * Check for illegal characters. * * !!! * Another fun one, historically. See vi/v_ntext.c:txt_abbrev() for * details. The bottom line is that all abbreviations have to end * with a "word" character, because it's the transition from word to * non-word characters that triggers the test for an abbreviation. In * addition, because of the way the test is done, there can't be any * transitions from word to non-word character (or vice-versa) other * than between the next-to-last and last characters of the string, * and there can't be any characters. Warn the user. */ if (!inword(cmdp->argv[0]->bp[cmdp->argv[0]->len - 1])) { msgq(sp, M_ERR, "106|Abbreviations must end with a \"word\" character"); return (1); } for (p = cmdp->argv[0]->bp; *p != '\0'; ++p) if (ISBLANK(p[0])) { msgq(sp, M_ERR, "107|Abbreviations may not contain tabs or spaces"); return (1); } if (cmdp->argv[0]->len > 2) for (p = cmdp->argv[0]->bp, len = cmdp->argv[0]->len - 2; len; --len, ++p) if (inword(p[0]) != inword(p[1])) { msgq(sp, M_ERR, "108|Abbreviations may not mix word/non-word characters, except at the end"); return (1); } if (seq_set(sp, NULL, 0, cmdp->argv[0]->bp, cmdp->argv[0]->len, cmdp->argv[1]->bp, cmdp->argv[1]->len, SEQ_ABBREV, SEQ_USERDEF)) return (1); F_SET(sp->gp, G_ABBREV); return (0); } /* * ex_unabbr -- :unabbreviate key * Delete an abbreviation. * * PUBLIC: int ex_unabbr(SCR *, EXCMD *); */ int ex_unabbr(SCR *sp, EXCMD *cmdp) { ARGS *ap; ap = cmdp->argv[0]; if (!F_ISSET(sp->gp, G_ABBREV) || seq_delete(sp, ap->bp, ap->len, SEQ_ABBREV)) { msgq_wstr(sp, M_ERR, ap->bp, "109|\"%s\" is not an abbreviation"); return (1); } return (0); } Index: head/contrib/nvi/ex/ex_append.c =================================================================== --- head/contrib/nvi/ex/ex_append.c (revision 365498) +++ head/contrib/nvi/ex/ex_append.c (revision 365499) @@ -1,269 +1,265 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_append.c,v 10.34 2001/06/25 15:19:14 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include "../common/common.h" enum which {APPEND, CHANGE, INSERT}; static int ex_aci(SCR *, EXCMD *, enum which); /* * ex_append -- :[line] a[ppend][!] * Append one or more lines of new text after the specified line, * or the current line if no address is specified. * * PUBLIC: int ex_append(SCR *, EXCMD *); */ int ex_append(SCR *sp, EXCMD *cmdp) { return (ex_aci(sp, cmdp, APPEND)); } /* * ex_change -- :[line[,line]] c[hange][!] [count] * Change one or more lines to the input text. * * PUBLIC: int ex_change(SCR *, EXCMD *); */ int ex_change(SCR *sp, EXCMD *cmdp) { return (ex_aci(sp, cmdp, CHANGE)); } /* * ex_insert -- :[line] i[nsert][!] * Insert one or more lines of new text before the specified line, * or the current line if no address is specified. * * PUBLIC: int ex_insert(SCR *, EXCMD *); */ int ex_insert(SCR *sp, EXCMD *cmdp) { return (ex_aci(sp, cmdp, INSERT)); } /* * ex_aci -- * Append, change, insert in ex. */ static int ex_aci(SCR *sp, EXCMD *cmdp, enum which cmd) { CHAR_T *p, *t; GS *gp; TEXT *tp; TEXTH tiq[] = {{ 0 }}; recno_t cnt = 0, lno; size_t len; u_int32_t flags; int need_newline; gp = sp->gp; NEEDFILE(sp, cmdp); /* * If doing a change, replace lines for as long as possible. Then, * append more lines or delete remaining lines. Changes to an empty * file are appends, inserts are the same as appends to the previous * line. * * !!! * Set the address to which we'll append. We set sp->lno to this * address as well so that autoindent works correctly when get text * from the user. */ lno = cmdp->addr1.lno; sp->lno = lno; if ((cmd == CHANGE || cmd == INSERT) && lno != 0) --lno; /* * !!! * If the file isn't empty, cut changes into the unnamed buffer. */ if (cmd == CHANGE && cmdp->addr1.lno != 0 && (cut(sp, NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE) || del(sp, &cmdp->addr1, &cmdp->addr2, 1))) return (1); /* * !!! * Anything that was left after the command separator becomes part * of the inserted text. Apparently, it was common usage to enter: * * :g/pattern/append|stuff1 * * and append the line of text "stuff1" to the lines containing the * pattern. It was also historically legal to enter: * * :append|stuff1 * stuff2 * . * * and the text on the ex command line would be appended as well as * the text inserted after it. There was an historic bug however, * that the user had to enter *two* terminating lines (the '.' lines) * to terminate text input mode, in this case. This whole thing * could be taken too far, however. Entering: * * :append|stuff1\ * stuff2 * stuff3 * . * * i.e. mixing and matching the forms confused the historic vi, and, * not only did it take two terminating lines to terminate text input * mode, but the trailing backslashes were retained on the input. We * match historic practice except that we discard the backslashes. * * Input lines specified on the ex command line lines are separated by * s. If there is a trailing delimiter an empty line was * inserted. There may also be a leading delimiter, which is ignored * unless it's also a trailing delimiter. It is possible to encounter * a termination line, i.e. a single '.', in a global command, but not * necessary if the text insert command was the last of the global * commands. */ if (cmdp->save_cmdlen != 0) { for (p = cmdp->save_cmd, len = cmdp->save_cmdlen; len > 0; p = t) { for (t = p; len > 0 && t[0] != '\n'; ++t, --len); if (t != p || len == 0) { if (F_ISSET(sp, SC_EX_GLOBAL) && t - p == 1 && p[0] == '.') { ++t; if (len > 0) --len; break; } if (db_append(sp, 1, lno++, p, t - p)) return (1); } if (len != 0) { ++t; if (--len == 0 && db_append(sp, 1, lno++, NULL, 0)) return (1); } } /* * If there's any remaining text, we're in a global, and * there's more command to parse. * * !!! * We depend on the fact that non-global commands will eat the * rest of the command line as text input, and before getting * any text input from the user. Otherwise, we'd have to save * off the command text before or during the call to the text * input function below. */ if (len != 0) cmdp->save_cmd = t; cmdp->save_cmdlen = len; } if (F_ISSET(sp, SC_EX_GLOBAL)) { if ((sp->lno = lno) == 0 && db_exist(sp, 1)) sp->lno = 1; return (0); } /* * If not in a global command, read from the terminal. * * If this code is called by vi, we want to reset the terminal and use * ex's line get routine. It actually works fine if we use vi's get * routine, but it doesn't look as nice. Maybe if we had a separate * window or something, but getting a line at a time looks awkward. * However, depending on the screen that we're using, that may not * be possible. */ if (F_ISSET(sp, SC_VI)) { if (gp->scr_screen(sp, SC_EX)) { ex_wemsg(sp, cmdp->cmd->name, EXM_NOCANON); return (1); } /* If we're still in the vi screen, move out explicitly. */ need_newline = !F_ISSET(sp, SC_SCR_EXWROTE); F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE); if (need_newline) (void)ex_puts(sp, "\n"); /* * !!! * Users of historical versions of vi sometimes get confused * when they enter append mode, and can't seem to get out of * it. Give them an informational message. */ (void)ex_puts(sp, msg_cat(sp, "273|Entering ex input mode.", NULL)); (void)ex_puts(sp, "\n"); (void)ex_fflush(sp); } /* * Set input flags; the ! flag turns off autoindent for append, * change and insert. */ LF_INIT(TXT_DOTTERM | TXT_NUMBER); if (!FL_ISSET(cmdp->iflags, E_C_FORCE) && O_ISSET(sp, O_AUTOINDENT)) LF_SET(TXT_AUTOINDENT); if (O_ISSET(sp, O_BEAUTIFY)) LF_SET(TXT_BEAUTIFY); /* * This code can't use the common screen TEXTH structure (sp->tiq), * as it may already be in use, e.g. ":append|s/abc/ABC/" would fail * as we are only halfway through the text when the append code fires. * Use a local structure instead. (The ex code would have to use a * local structure except that we're guaranteed to finish remaining * characters in the common TEXTH structure when they were inserted * into the file, above.) */ TAILQ_INIT(tiq); if (ex_txt(sp, tiq, 0, flags)) return (1); TAILQ_FOREACH(tp, tiq, q) { if (db_append(sp, 1, lno++, tp->lb, tp->len)) return (1); ++cnt; } /* * Set sp->lno to the final line number value (correcting for a * possible 0 value) as that's historically correct for the final * line value, whether or not the user entered any text. */ if ((sp->lno = lno) == 0 && db_exist(sp, 1)) sp->lno = 1; return (0); } Index: head/contrib/nvi/ex/ex_args.c =================================================================== --- head/contrib/nvi/ex/ex_args.c (revision 365498) +++ head/contrib/nvi/ex/ex_args.c (revision 365499) @@ -1,330 +1,325 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_args.c,v 10.19 2011/12/16 16:18:10 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" static int ex_N_next(SCR *, EXCMD *); /* * ex_next -- :next [+cmd] [files] * Edit the next file, optionally setting the list of files. * * !!! * The :next command behaved differently from the :rewind command in * historic vi. See nvi/docs/autowrite for details, but the basic * idea was that it ignored the force flag if the autowrite flag was * set. This implementation handles them all identically. * * PUBLIC: int ex_next(SCR *, EXCMD *); */ int ex_next(SCR *sp, EXCMD *cmdp) { ARGS **argv; FREF *frp; int noargs; char **ap; CHAR_T *wp; size_t wlen; char *np; size_t nlen; /* Check for file to move to. */ if (cmdp->argc == 0 && (sp->cargv == NULL || sp->cargv[1] == NULL)) { msgq(sp, M_ERR, "111|No more files to edit"); return (1); } if (F_ISSET(cmdp, E_NEWSCREEN)) { /* By default, edit the next file in the old argument list. */ if (cmdp->argc == 0) { CHAR2INT(sp, sp->cargv[1], strlen(sp->cargv[1]) + 1, wp, wlen); if (argv_exp0(sp, cmdp, wp, wlen - 1)) return (1); return (ex_edit(sp, cmdp)); } return (ex_N_next(sp, cmdp)); } /* Check modification. */ if (file_m1(sp, FL_ISSET(cmdp->iflags, E_C_FORCE), FS_ALL | FS_POSSIBLE)) return (1); /* Any arguments are a replacement file list. */ if (cmdp->argc) { /* Free the current list. */ if (!F_ISSET(sp, SC_ARGNOFREE) && sp->argv != NULL) { for (ap = sp->argv; *ap != NULL; ++ap) free(*ap); free(sp->argv); } F_CLR(sp, SC_ARGNOFREE | SC_ARGRECOVER); sp->cargv = NULL; /* Create a new list. */ - CALLOC_RET(sp, - sp->argv, char **, cmdp->argc + 1, sizeof(char *)); + CALLOC_RET(sp, sp->argv, cmdp->argc + 1, sizeof(char *)); for (ap = sp->argv, argv = cmdp->argv; argv[0]->len != 0; ++ap, ++argv) { INT2CHAR(sp, argv[0]->bp, argv[0]->len, np, nlen); if ((*ap = v_strdup(sp, np, nlen)) == NULL) return (1); } *ap = NULL; /* Switch to the first file. */ sp->cargv = sp->argv; if ((frp = file_add(sp, *sp->cargv)) == NULL) return (1); noargs = 0; /* Display a file count with the welcome message. */ F_SET(sp, SC_STATUS_CNT); } else { if ((frp = file_add(sp, sp->cargv[1])) == NULL) return (1); if (F_ISSET(sp, SC_ARGRECOVER)) F_SET(frp, FR_RECOVER); noargs = 1; } if (file_init(sp, frp, NULL, FS_SETALT | (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) return (1); if (noargs) ++sp->cargv; F_SET(sp, SC_FSWITCH); return (0); } /* * ex_N_next -- * New screen version of ex_next. */ static int ex_N_next(SCR *sp, EXCMD *cmdp) { SCR *new; FREF *frp; char *np; size_t nlen; /* Get a new screen. */ if (screen_init(sp->gp, sp, &new)) return (1); if (vs_split(sp, new, 0)) { (void)screen_end(new); return (1); } /* Get a backing file. */ INT2CHAR(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len + 1, np, nlen); if ((frp = file_add(new, np)) == NULL || file_init(new, frp, NULL, (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) { (void)vs_discard(new, NULL); (void)screen_end(new); return (1); } /* The arguments are a replacement file list. */ new->cargv = new->argv = ex_buildargv(sp, cmdp, NULL); /* Display a file count with the welcome message. */ F_SET(new, SC_STATUS_CNT); /* Set up the switch. */ sp->nextdisp = new; F_SET(sp, SC_SSWITCH); return (0); } /* * ex_prev -- :prev * Edit the previous file. * * PUBLIC: int ex_prev(SCR *, EXCMD *); */ int ex_prev(SCR *sp, EXCMD *cmdp) { FREF *frp; size_t wlen; CHAR_T *wp; if (sp->cargv == sp->argv) { msgq(sp, M_ERR, "112|No previous files to edit"); return (1); } if (F_ISSET(cmdp, E_NEWSCREEN)) { CHAR2INT(sp, sp->cargv[-1], strlen(sp->cargv[-1]) + 1, wp, wlen); if (argv_exp0(sp, cmdp, wp, wlen - 1)) return (1); return (ex_edit(sp, cmdp)); } if (file_m1(sp, FL_ISSET(cmdp->iflags, E_C_FORCE), FS_ALL | FS_POSSIBLE)) return (1); if ((frp = file_add(sp, sp->cargv[-1])) == NULL) return (1); if (file_init(sp, frp, NULL, FS_SETALT | (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) return (1); --sp->cargv; F_SET(sp, SC_FSWITCH); return (0); } /* * ex_rew -- :rew * Re-edit the list of files. * * !!! * Historic practice was that all files would start editing at the beginning * of the file. We don't get this right because we may have multiple screens * and we can't clear the FR_CURSORSET bit for a single screen. I don't see * anyone noticing, but if they do, we'll have to put information into the SCR * structure so we can keep track of it. * * PUBLIC: int ex_rew(SCR *, EXCMD *); */ int ex_rew(SCR *sp, EXCMD *cmdp) { FREF *frp; /* * !!! * Historic practice -- you can rewind to the current file. */ if (sp->argv == NULL) { msgq(sp, M_ERR, "113|No previous files to rewind"); return (1); } if (file_m1(sp, FL_ISSET(cmdp->iflags, E_C_FORCE), FS_ALL | FS_POSSIBLE)) return (1); /* Switch to the first one. */ sp->cargv = sp->argv; if ((frp = file_add(sp, *sp->cargv)) == NULL) return (1); if (file_init(sp, frp, NULL, FS_SETALT | (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) return (1); /* Switch and display a file count with the welcome message. */ F_SET(sp, SC_FSWITCH | SC_STATUS_CNT); return (0); } /* * ex_args -- :args * Display the list of files. * * PUBLIC: int ex_args(SCR *, EXCMD *); */ int ex_args(SCR *sp, EXCMD *cmdp) { GS *gp; int cnt, col, len, sep; char **ap; if (sp->argv == NULL) { (void)msgq(sp, M_ERR, "114|No file list to display"); return (0); } gp = sp->gp; col = len = sep = 0; for (cnt = 1, ap = sp->argv; *ap != NULL; ++ap) { col += len = strlen(*ap) + sep + (ap == sp->cargv ? 2 : 0); if (col >= sp->cols - 1) { col = len; sep = 0; (void)ex_puts(sp, "\n"); } else if (cnt != 1) { sep = 1; (void)ex_puts(sp, " "); } ++cnt; (void)ex_printf(sp, "%s%s%s", ap == sp->cargv ? "[" : "", *ap, ap == sp->cargv ? "]" : ""); if (INTERRUPTED(sp)) break; } (void)ex_puts(sp, "\n"); return (0); } /* * ex_buildargv -- * Build a new file argument list. * * PUBLIC: char **ex_buildargv(SCR *, EXCMD *, char *); */ char ** ex_buildargv(SCR *sp, EXCMD *cmdp, char *name) { ARGS **argv; int argc; char **ap, **s_argv; char *np; size_t nlen; argc = cmdp == NULL ? 1 : cmdp->argc; - CALLOC(sp, s_argv, char **, argc + 1, sizeof(char *)); + CALLOC(sp, s_argv, argc + 1, sizeof(char *)); if ((ap = s_argv) == NULL) return (NULL); if (cmdp == NULL) { if ((*ap = v_strdup(sp, name, strlen(name))) == NULL) return (NULL); ++ap; } else for (argv = cmdp->argv; argv[0]->len != 0; ++ap, ++argv) { INT2CHAR(sp, argv[0]->bp, argv[0]->len, np, nlen); if ((*ap = v_strdup(sp, np, nlen)) == NULL) return (NULL); } *ap = NULL; return (s_argv); } Index: head/contrib/nvi/ex/ex_argv.c =================================================================== --- head/contrib/nvi/ex/ex_argv.c (revision 365498) +++ head/contrib/nvi/ex/ex_argv.c (revision 365499) @@ -1,914 +1,910 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_argv.c,v 11.2 2012/10/09 23:00:29 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" static int argv_alloc(SCR *, size_t); static int argv_comp(const void *, const void *); static int argv_fexp(SCR *, EXCMD *, CHAR_T *, size_t, CHAR_T *, size_t *, CHAR_T **, size_t *, int); static int argv_sexp(SCR *, CHAR_T **, size_t *, size_t *); static int argv_flt_user(SCR *, EXCMD *, CHAR_T *, size_t); /* * argv_init -- * Build a prototype arguments list. * * PUBLIC: int argv_init(SCR *, EXCMD *); */ int argv_init(SCR *sp, EXCMD *excp) { EX_PRIVATE *exp; exp = EXP(sp); exp->argsoff = 0; argv_alloc(sp, 1); excp->argv = exp->args; excp->argc = exp->argsoff; return (0); } /* * argv_exp0 -- * Append a string to the argument list. * * PUBLIC: int argv_exp0(SCR *, EXCMD *, CHAR_T *, size_t); */ int argv_exp0(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) { EX_PRIVATE *exp; exp = EXP(sp); argv_alloc(sp, cmdlen); MEMCPY(exp->args[exp->argsoff]->bp, cmd, cmdlen); exp->args[exp->argsoff]->bp[cmdlen] = '\0'; exp->args[exp->argsoff]->len = cmdlen; ++exp->argsoff; excp->argv = exp->args; excp->argc = exp->argsoff; return (0); } /* * argv_exp1 -- * Do file name expansion on a string, and append it to the * argument list. * * PUBLIC: int argv_exp1(SCR *, EXCMD *, CHAR_T *, size_t, int); */ int argv_exp1(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen, int is_bang) { EX_PRIVATE *exp; size_t blen, len; CHAR_T *p, *t, *bp; GET_SPACE_RETW(sp, bp, blen, 512); len = 0; exp = EXP(sp); if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) { FREE_SPACEW(sp, bp, blen); return (1); } /* If it's empty, we're done. */ if (len != 0) { for (p = bp, t = bp + len; p < t; ++p) if (!cmdskip(*p)) break; if (p == t) goto ret; } else goto ret; (void)argv_exp0(sp, excp, bp, len); ret: FREE_SPACEW(sp, bp, blen); return (0); } /* * argv_exp2 -- * Do file name and shell expansion on a string, and append it to * the argument list. * * PUBLIC: int argv_exp2(SCR *, EXCMD *, CHAR_T *, size_t); */ int argv_exp2(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) { size_t blen, len, n; int rval; CHAR_T *bp, *p; GET_SPACE_RETW(sp, bp, blen, 512); #define SHELLECHO L("echo ") #define SHELLOFFSET (SIZE(SHELLECHO) - 1) MEMCPY(bp, SHELLECHO, SHELLOFFSET); p = bp + SHELLOFFSET; len = SHELLOFFSET; #if defined(DEBUG) && 0 TRACE(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd); #endif if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) { rval = 1; goto err; } #if defined(DEBUG) && 0 TRACE(sp, "before shell: %d: {%s}\n", len, bp); #endif /* * Do shell word expansion -- it's very, very hard to figure out what * magic characters the user's shell expects. Historically, it was a * union of v7 shell and csh meta characters. We match that practice * by default, so ":read \%" tries to read a file named '%'. It would * make more sense to pass any special characters through the shell, * but then, if your shell was csh, the above example will behave * differently in nvi than in vi. If you want to get other characters * passed through to your shell, change the "meta" option. */ if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1)) n = 0; else { p = bp + SHELLOFFSET; n = len - SHELLOFFSET; for (; n > 0; --n, ++p) if (IS_SHELLMETA(sp, *p)) break; } /* * If we found a meta character in the string, fork a shell to expand * it. Unfortunately, this is comparatively slow. Historically, it * didn't matter much, since users don't enter meta characters as part * of pathnames that frequently. The addition of filename completion * broke that assumption because it's easy to use. To increase the * completion performance, nvi used to have an internal routine to * handle "filename*". However, the shell special characters does not * limit to "shellmeta", so such a hack breaks historic practice. * After it all, we split the completion logic out from here. */ switch (n) { case 0: p = bp + SHELLOFFSET; len -= SHELLOFFSET; rval = argv_exp3(sp, excp, p, len); break; default: if (argv_sexp(sp, &bp, &blen, &len)) { rval = 1; goto err; } p = bp; rval = argv_exp3(sp, excp, p, len); break; } err: FREE_SPACEW(sp, bp, blen); return (rval); } /* * argv_exp3 -- * Take a string and break it up into an argv, which is appended * to the argument list. * * PUBLIC: int argv_exp3(SCR *, EXCMD *, CHAR_T *, size_t); */ int argv_exp3(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) { EX_PRIVATE *exp; size_t len; int ch, off; CHAR_T *ap, *p; for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) { /* Skip any leading whitespace. */ for (; cmdlen > 0; --cmdlen, ++cmd) { ch = *cmd; if (!cmdskip(ch)) break; } if (cmdlen == 0) break; /* * Determine the length of this whitespace delimited * argument. * * QUOTING NOTE: * * Skip any character preceded by the user's quoting * character. */ for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) { ch = *cmd; if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) { ++cmd; --cmdlen; } else if (cmdskip(ch)) break; } /* * Copy the argument into place. * * QUOTING NOTE: * * Lose quote chars. */ argv_alloc(sp, len); off = exp->argsoff; exp->args[off]->len = len; for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++) if (IS_ESCAPE(sp, excp, *ap)) ++ap; *p = '\0'; } excp->argv = exp->args; excp->argc = exp->argsoff; #if defined(DEBUG) && 0 for (cnt = 0; cnt < exp->argsoff; ++cnt) TRACE(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]); #endif return (0); } /* * argv_flt_ex -- * Filter the ex commands with a prefix, and append the results to * the argument list. * * PUBLIC: int argv_flt_ex(SCR *, EXCMD *, CHAR_T *, size_t); */ int argv_flt_ex(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) { EX_PRIVATE *exp; EXCMDLIST const *cp; int off; size_t len; exp = EXP(sp); for (off = exp->argsoff, cp = cmds; cp->name != NULL; ++cp) { len = STRLEN(cp->name); if (cmdlen > 0 && (cmdlen > len || MEMCMP(cmd, cp->name, cmdlen))) continue; /* Copy the matched ex command name. */ argv_alloc(sp, len + 1); MEMCPY(exp->args[exp->argsoff]->bp, cp->name, len + 1); exp->args[exp->argsoff]->len = len; ++exp->argsoff; excp->argv = exp->args; excp->argc = exp->argsoff; } return (0); } /* * argv_flt_user -- * Filter the ~user list on the system with a prefix, and append * the results to the argument list. */ static int argv_flt_user(SCR *sp, EXCMD *excp, CHAR_T *uname, size_t ulen) { EX_PRIVATE *exp; struct passwd *pw; int off; char *np; size_t len, nlen; exp = EXP(sp); off = exp->argsoff; /* The input must come with a leading '~'. */ INT2CHAR(sp, uname + 1, ulen - 1, np, nlen); if ((np = v_strdup(sp, np, nlen)) == NULL) return (1); setpwent(); while ((pw = getpwent()) != NULL) { len = strlen(pw->pw_name); if (nlen > 0 && (nlen > len || memcmp(np, pw->pw_name, nlen))) continue; /* Copy '~' + the matched user name. */ CHAR2INT(sp, pw->pw_name, len + 1, uname, ulen); argv_alloc(sp, ulen + 1); exp->args[exp->argsoff]->bp[0] = '~'; MEMCPY(exp->args[exp->argsoff]->bp + 1, uname, ulen); exp->args[exp->argsoff]->len = ulen; ++exp->argsoff; excp->argv = exp->args; excp->argc = exp->argsoff; } endpwent(); free(np); qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp); return (0); } /* * argv_fexp -- * Do file name and bang command expansion. */ static int argv_fexp(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen, CHAR_T *p, size_t *lenp, CHAR_T **bpp, size_t *blenp, int is_bang) { EX_PRIVATE *exp; char *t; size_t blen, len, off, tlen; CHAR_T *bp; CHAR_T *wp; size_t wlen; /* Replace file name characters. */ for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd) switch (*cmd) { case '!': if (!is_bang) goto ins_ch; exp = EXP(sp); if (exp->lastbcomm == NULL) { msgq(sp, M_ERR, "115|No previous command to replace \"!\""); return (1); } len += tlen = STRLEN(exp->lastbcomm); off = p - bp; ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; MEMCPY(p, exp->lastbcomm, tlen); p += tlen; F_SET(excp, E_MODIFY); break; case '%': if ((t = sp->frp->name) == NULL) { msgq(sp, M_ERR, "116|No filename to substitute for %%"); return (1); } tlen = strlen(t); len += tlen; off = p - bp; ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; CHAR2INT(sp, t, tlen, wp, wlen); MEMCPY(p, wp, wlen); p += wlen; F_SET(excp, E_MODIFY); break; case '#': if ((t = sp->alt_name) == NULL) { msgq(sp, M_ERR, "117|No filename to substitute for #"); return (1); } len += tlen = strlen(t); off = p - bp; ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; CHAR2INT(sp, t, tlen, wp, wlen); MEMCPY(p, wp, wlen); p += wlen; F_SET(excp, E_MODIFY); break; case '\\': /* * QUOTING NOTE: * * Strip any backslashes that protected the file * expansion characters. */ if (cmdlen > 1 && (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) { ++cmd; --cmdlen; } /* FALLTHROUGH */ default: ins_ch: ++len; off = p - bp; ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; *p++ = *cmd; } /* Nul termination. */ ++len; off = p - bp; ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; *p = '\0'; /* Return the new string length, buffer, buffer length. */ *lenp = len - 1; *bpp = bp; *blenp = blen; return (0); } /* * argv_alloc -- * Make more space for arguments. */ static int argv_alloc(SCR *sp, size_t len) { ARGS *ap; EX_PRIVATE *exp; int cnt, off; /* * Allocate room for another argument, always leaving * enough room for an ARGS structure with a length of 0. */ #define INCREMENT 20 exp = EXP(sp); off = exp->argsoff; if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) { cnt = exp->argscnt + INCREMENT; REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *)); if (exp->args == NULL) { (void)argv_free(sp); goto mem; } memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *)); exp->argscnt = cnt; } /* First argument. */ if (exp->args[off] == NULL) { - CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS)); + CALLOC(sp, exp->args[off], 1, sizeof(ARGS)); if (exp->args[off] == NULL) goto mem; } /* First argument buffer. */ ap = exp->args[off]; ap->len = 0; if (ap->blen < len + 1) { ap->blen = len + 1; REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T)); if (ap->bp == NULL) { ap->bp = NULL; ap->blen = 0; F_CLR(ap, A_ALLOCATED); mem: msgq(sp, M_SYSERR, NULL); return (1); } F_SET(ap, A_ALLOCATED); } /* Second argument. */ if (exp->args[++off] == NULL) { - CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS)); + CALLOC(sp, exp->args[off], 1, sizeof(ARGS)); if (exp->args[off] == NULL) goto mem; } /* 0 length serves as end-of-argument marker. */ exp->args[off]->len = 0; return (0); } /* * argv_free -- * Free up argument structures. * * PUBLIC: int argv_free(SCR *); */ int argv_free(SCR *sp) { EX_PRIVATE *exp; int off; exp = EXP(sp); if (exp->args != NULL) { for (off = 0; off < exp->argscnt; ++off) { if (exp->args[off] == NULL) continue; if (F_ISSET(exp->args[off], A_ALLOCATED)) free(exp->args[off]->bp); free(exp->args[off]); } free(exp->args); } exp->args = NULL; exp->argscnt = 0; exp->argsoff = 0; return (0); } /* * argv_flt_path -- * Find all file names matching the prefix and append them to the * argument list. * * PUBLIC: int argv_flt_path(SCR *, EXCMD *, CHAR_T *, size_t); */ int argv_flt_path(SCR *sp, EXCMD *excp, CHAR_T *path, size_t plen) { struct dirent *dp; DIR *dirp; EX_PRIVATE *exp; int off; size_t dlen, len, nlen; CHAR_T *dname; CHAR_T *p, *np, *n; char *name, *tp, *epd = NULL; CHAR_T *wp; size_t wlen; exp = EXP(sp); /* Set up the name and length for comparison. */ if ((path = v_wstrdup(sp, path, plen)) == NULL) return (1); if ((p = STRRCHR(path, '/')) == NULL) { if (*path == '~') { int rc; /* Filter ~user list instead. */ rc = argv_flt_user(sp, excp, path, plen); free(path); return (rc); } dname = L("."); dlen = 0; np = path; } else { if (p == path) { dname = L("/"); dlen = 1; } else { *p = '\0'; dname = path; dlen = p - path; } np = p + 1; } INT2CHAR(sp, dname, dlen + 1, tp, nlen); if ((epd = expanduser(tp)) != NULL) tp = epd; if ((dirp = opendir(tp)) == NULL) { free(epd); free(path); return (1); } free(epd); INT2CHAR(sp, np, STRLEN(np), tp, nlen); if ((name = v_strdup(sp, tp, nlen)) == NULL) { free(path); return (1); } for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) { if (nlen == 0) { if (dp->d_name[0] == '.') continue; len = dp->d_namlen; } else { len = dp->d_namlen; if (len < nlen || memcmp(dp->d_name, name, nlen)) continue; } /* Directory + name + slash + null. */ CHAR2INT(sp, dp->d_name, len + 1, wp, wlen); argv_alloc(sp, dlen + wlen + 1); n = exp->args[exp->argsoff]->bp; if (dlen != 0) { MEMCPY(n, dname, dlen); n += dlen; if (dlen > 1 || dname[0] != '/') *n++ = '/'; exp->args[exp->argsoff]->len = dlen + 1; } MEMCPY(n, wp, wlen); exp->args[exp->argsoff]->len += wlen - 1; ++exp->argsoff; excp->argv = exp->args; excp->argc = exp->argsoff; } closedir(dirp); free(name); free(path); qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp); return (0); } /* * argv_comp -- * Alphabetic comparison. */ static int argv_comp(const void *a, const void *b) { return (STRCMP((*(ARGS **)a)->bp, (*(ARGS **)b)->bp)); } /* * argv_sexp -- * Fork a shell, pipe a command through it, and read the output into * a buffer. */ static int argv_sexp(SCR *sp, CHAR_T **bpp, size_t *blenp, size_t *lenp) { enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval; FILE *ifp; pid_t pid; size_t blen, len; int ch, std_output[2]; CHAR_T *bp, *p; char *sh, *sh_path; char *np; size_t nlen; /* Secure means no shell access. */ if (O_ISSET(sp, O_SECURE)) { msgq(sp, M_ERR, "289|Shell expansions not supported when the secure edit option is set"); return (1); } sh_path = O_STR(sp, O_SHELL); if ((sh = strrchr(sh_path, '/')) == NULL) sh = sh_path; else ++sh; /* Local copies of the buffer variables. */ bp = *bpp; blen = *blenp; /* * There are two different processes running through this code, named * the utility (the shell) and the parent. The utility reads standard * input and writes standard output and standard error output. The * parent writes to the utility, reads its standard output and ignores * its standard error output. Historically, the standard error output * was discarded by vi, as it produces a lot of noise when file patterns * don't match. * * The parent reads std_output[0], and the utility writes std_output[1]. */ ifp = NULL; std_output[0] = std_output[1] = -1; if (pipe(std_output) < 0) { msgq(sp, M_SYSERR, "pipe"); return (1); } if ((ifp = fdopen(std_output[0], "r")) == NULL) { msgq(sp, M_SYSERR, "fdopen"); goto err; } /* * Do the minimal amount of work possible, the shell is going to run * briefly and then exit. We sincerely hope. */ switch (pid = vfork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "vfork"); err: if (ifp != NULL) (void)fclose(ifp); else if (std_output[0] != -1) close(std_output[0]); if (std_output[1] != -1) close(std_output[0]); return (1); case 0: /* Utility. */ /* Redirect stdout to the write end of the pipe. */ (void)dup2(std_output[1], STDOUT_FILENO); /* Close the utility's file descriptors. */ (void)close(std_output[0]); (void)close(std_output[1]); (void)close(STDERR_FILENO); /* * XXX * Assume that all shells have -c. */ INT2CHAR(sp, bp, STRLEN(bp)+1, np, nlen); execl(sh_path, sh, "-c", np, (char *)NULL); msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s"); _exit(127); default: /* Parent. */ /* Close the pipe ends the parent won't use. */ (void)close(std_output[1]); break; } /* * Copy process standard output into a buffer. * * !!! * Historic vi apparently discarded leading \n and \r's from * the shell output stream. We don't on the grounds that any * shell that does that is broken. */ for (p = bp, len = 0, ch = EOF; (ch = GETC(ifp)) != EOF; *p++ = ch, blen-=sizeof(CHAR_T), ++len) if (blen < 5) { ADD_SPACE_GOTOW(sp, bp, *blenp, *blenp * 2); p = bp + len; blen = *blenp - len; } /* Delete the final newline, nul terminate the string. */ if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) { --p; --len; } *p = '\0'; *lenp = len; *bpp = bp; /* *blenp is already updated. */ if (ferror(ifp)) goto ioerr; if (fclose(ifp)) { ioerr: msgq_str(sp, M_ERR, sh, "119|I/O error: %s"); alloc_err: rval = SEXP_ERR; } else rval = SEXP_OK; /* * Wait for the process. If the shell process fails (e.g., "echo $q" * where q wasn't a defined variable) or if the returned string has * no characters or only blank characters, (e.g., "echo $5"), complain * that the shell expansion failed. We can't know for certain that's * the error, but it's a good guess, and it matches historic practice. * This won't catch "echo foo_$5", but that's not a common error and * historic vi didn't catch it either. */ if (proc_wait(sp, (long)pid, sh, 1, 0)) rval = SEXP_EXPANSION_ERR; for (p = bp; len; ++p, --len) if (!cmdskip(*p)) break; if (len == 0) rval = SEXP_EXPANSION_ERR; if (rval == SEXP_EXPANSION_ERR) msgq(sp, M_ERR, "304|Shell expansion failed"); return (rval == SEXP_OK ? 0 : 1); } /* * argv_esc -- * Escape a string into an ex and shell argument. * * PUBLIC: CHAR_T *argv_esc(SCR *, EXCMD *, CHAR_T *, size_t); */ CHAR_T * argv_esc(SCR *sp, EXCMD *excp, CHAR_T *str, size_t len) { size_t blen, off; CHAR_T *bp, *p; int ch; GET_SPACE_GOTOW(sp, bp, blen, len + 1); /* * Leaving the first '~' unescaped causes the user to need a * "./" prefix to edit a file which really starts with a '~'. * However, the file completion happens to not work for these * files without the prefix. * * All ex expansion characters, "!%#", are double escaped. */ for (p = bp; len > 0; ++str, --len) { ch = *str; off = p - bp; if (blen / sizeof(CHAR_T) - off < 3) { ADD_SPACE_GOTOW(sp, bp, blen, off + 3); p = bp + off; } if (cmdskip(ch) || ch == '\n' || IS_ESCAPE(sp, excp, ch)) /* Ex. */ *p++ = CH_LITERAL; else switch (ch) { case '~': /* ~user. */ if (p != bp) *p++ = '\\'; break; case '+': /* Ex +cmd. */ if (p == bp) *p++ = '\\'; break; case '!': case '%': case '#': /* Ex exp. */ *p++ = '\\'; *p++ = '\\'; break; case ',': case '-': case '.': case '/': /* Safe. */ case ':': case '=': case '@': case '_': break; default: /* Unsafe. */ if (isascii(ch) && !isalnum(ch)) *p++ = '\\'; } *p++ = ch; } *p = '\0'; return bp; alloc_err: return NULL; } /* * argv_uesc -- * Unescape an escaped ex and shell argument. * * PUBLIC: CHAR_T *argv_uesc(SCR *, EXCMD *, CHAR_T *, size_t); */ CHAR_T * argv_uesc(SCR *sp, EXCMD *excp, CHAR_T *str, size_t len) { size_t blen; CHAR_T *bp, *p; GET_SPACE_GOTOW(sp, bp, blen, len + 1); for (p = bp; len > 0; ++str, --len) { if (IS_ESCAPE(sp, excp, *str)) { if (--len < 1) break; ++str; } else if (*str == '\\') { if (--len < 1) break; ++str; /* Check for double escaping. */ if (*str == '\\' && len > 1) switch (str[1]) { case '!': case '%': case '#': ++str; --len; } } *p++ = *str; } *p = '\0'; return bp; alloc_err: return NULL; } Index: head/contrib/nvi/ex/ex_at.c =================================================================== --- head/contrib/nvi/ex/ex_at.c (revision 365498) +++ head/contrib/nvi/ex/ex_at.c (revision 365499) @@ -1,124 +1,120 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_at.c,v 10.16 2001/06/25 15:19:14 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_at -- :@[@ | buffer] * :*[* | buffer] * * Execute the contents of the buffer. * * PUBLIC: int ex_at(SCR *, EXCMD *); */ int ex_at(SCR *sp, EXCMD *cmdp) { CB *cbp; CHAR_T name; EXCMD *ecp; RANGE *rp; TEXT *tp; size_t len = 0; CHAR_T *p; /* * !!! * Historically, [@*] and [@*][@*] executed the most * recently executed buffer in ex mode. */ name = FL_ISSET(cmdp->iflags, E_C_BUFFER) ? cmdp->buffer : '@'; if (name == '@' || name == '*') { if (!F_ISSET(sp, SC_AT_SET)) { ex_emsg(sp, NULL, EXM_NOPREVBUF); return (1); } name = sp->at_lbuf; } sp->at_lbuf = name; F_SET(sp, SC_AT_SET); CBNAME(sp, cbp, name); if (cbp == NULL) { ex_emsg(sp, KEY_NAME(sp, name), EXM_EMPTYBUF); return (1); } /* * !!! * Historically the @ command took a range of lines, and the @ buffer * was executed once per line. The historic vi could be trashed by * this because it didn't notice if the underlying file changed, or, * for that matter, if there were no more lines on which to operate. * For example, take a 10 line file, load "%delete" into a buffer, * and enter :8,10@. * * The solution is a bit tricky. If the user specifies a range, take * the same approach as for global commands, and discard the command * if exit or switch to a new file/screen. If the user doesn't specify * the range, continue to execute after a file/screen switch, which * means @ buffers are still useful in a multi-screen environment. */ - CALLOC_RET(sp, ecp, EXCMD *, 1, sizeof(EXCMD)); + CALLOC_RET(sp, ecp, 1, sizeof(EXCMD)); TAILQ_INIT(ecp->rq); - CALLOC_RET(sp, rp, RANGE *, 1, sizeof(RANGE)); + CALLOC_RET(sp, rp, 1, sizeof(RANGE)); rp->start = cmdp->addr1.lno; if (F_ISSET(cmdp, E_ADDR_DEF)) { rp->stop = rp->start; FL_SET(ecp->agv_flags, AGV_AT_NORANGE); } else { rp->stop = cmdp->addr2.lno; FL_SET(ecp->agv_flags, AGV_AT); } TAILQ_INSERT_HEAD(ecp->rq, rp, q); /* * Buffers executed in ex mode or from the colon command line in vi * were ex commands. We can't push it on the terminal queue, since * it has to be executed immediately, and we may be in the middle of * an ex command already. Push the command on the ex command stack. * Build two copies of the command. We need two copies because the * ex parser may step on the command string when it's parsing it. */ TAILQ_FOREACH_REVERSE(tp, cbp->textq, _texth, q) len += tp->len + 1; - MALLOC_RET(sp, ecp->cp, CHAR_T *, len * 2 * sizeof(CHAR_T)); + MALLOC_RET(sp, ecp->cp, len * 2 * sizeof(CHAR_T)); ecp->o_cp = ecp->cp; ecp->o_clen = len; ecp->cp[len] = '\0'; /* Copy the buffer into the command space. */ p = ecp->cp + len; TAILQ_FOREACH_REVERSE(tp, cbp->textq, _texth, q) { MEMCPY(p, tp->lb, tp->len); p += tp->len; *p++ = '\n'; } SLIST_INSERT_HEAD(sp->gp->ecq, ecp, q); return (0); } Index: head/contrib/nvi/ex/ex_bang.c =================================================================== --- head/contrib/nvi/ex/ex_bang.c (revision 365498) +++ head/contrib/nvi/ex/ex_bang.c (revision 365499) @@ -1,187 +1,186 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_bang.c,v 10.36 2001/06/25 15:19:14 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" /* * ex_bang -- :[line [,line]] ! command * * Pass the rest of the line after the ! character to the program named by * the O_SHELL option. * * Historical vi did NOT do shell expansion on the arguments before passing * them, only file name expansion. This means that the O_SHELL program got * "$t" as an argument if that is what the user entered. Also, there's a * special expansion done for the bang command. Any exclamation points in * the user's argument are replaced by the last, expanded ! command. * * There's some fairly amazing slop in this routine to make the different * ways of getting here display the right things. It took a long time to * get it right (wrong?), so be careful. * * PUBLIC: int ex_bang(SCR *, EXCMD *); */ int ex_bang(SCR *sp, EXCMD *cmdp) { enum filtertype ftype; ARGS *ap; EX_PRIVATE *exp; MARK rm; recno_t lno; int rval; const char *msg; char *np; size_t nlen; ap = cmdp->argv[0]; if (ap->len == 0) { ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } /* Set the "last bang command" remembered value. */ exp = EXP(sp); - if (exp->lastbcomm != NULL) - free(exp->lastbcomm); + free(exp->lastbcomm); if ((exp->lastbcomm = v_wstrdup(sp, ap->bp, ap->len)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* * If the command was modified by the expansion, it was historically * redisplayed. */ if (F_ISSET(cmdp, E_MODIFY) && !F_ISSET(sp, SC_EX_SILENT)) { /* * Display the command if modified. Historic ex/vi displayed * the command if it was modified due to file name and/or bang * expansion. If piping lines in vi, it would be immediately * overwritten by any error or line change reporting. */ if (F_ISSET(sp, SC_VI)) vs_update(sp, "!", ap->bp); else { (void)ex_printf(sp, "!"WS"\n", ap->bp); (void)ex_fflush(sp); } } /* * If no addresses were specified, run the command. If there's an * underlying file, it's been modified and autowrite is set, write * the file back. If the file has been modified, autowrite is not * set and the warn option is set, tell the user about the file. */ if (cmdp->addrcnt == 0) { msg = NULL; if (sp->ep != NULL && F_ISSET(sp->ep, F_MODIFIED)) if (O_ISSET(sp, O_AUTOWRITE)) { if (file_aw(sp, FS_ALL)) return (0); } else if (O_ISSET(sp, O_WARN) && !F_ISSET(sp, SC_EX_SILENT)) msg = msg_cat(sp, "303|File modified since last write.", NULL); /* If we're still in a vi screen, move out explicitly. */ INT2CHAR(sp, ap->bp, ap->len+1, np, nlen); (void)ex_exec_proc(sp, cmdp, np, msg, !F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)); } /* * If addresses were specified, pipe lines from the file through the * command. * * Historically, vi lines were replaced by both the stdout and stderr * lines of the command, but ex lines by only the stdout lines. This * makes no sense to me, so nvi makes it consistent for both, and * matches vi's historic behavior. */ else { NEEDFILE(sp, cmdp); /* Autoprint is set historically, even if the command fails. */ F_SET(cmdp, E_AUTOPRINT); /* * !!! * Historical vi permitted "!!" in an empty file. When this * happens, we arrive here with two addresses of 1,1 and a * bad attitude. The simple solution is to turn it into a * FILTER_READ operation, with the exception that stdin isn't * opened for the utility, and the cursor position isn't the * same. The only historic glitch (I think) is that we don't * put an empty line into the default cut buffer, as historic * vi did. Imagine, if you can, my disappointment. */ ftype = FILTER_BANG; if (cmdp->addr1.lno == 1 && cmdp->addr2.lno == 1) { if (db_last(sp, &lno)) return (1); if (lno == 0) { cmdp->addr1.lno = cmdp->addr2.lno = 0; ftype = FILTER_RBANG; } } rval = ex_filter(sp, cmdp, &cmdp->addr1, &cmdp->addr2, &rm, ap->bp, ftype); /* * If in vi mode, move to the first nonblank. * * !!! * Historic vi wasn't consistent in this area -- if you used * a forward motion it moved to the first nonblank, but if you * did a backward motion it didn't. And, if you followed a * backward motion with a forward motion, it wouldn't move to * the nonblank for either. Going to the nonblank generally * seems more useful and consistent, so we do it. */ sp->lno = rm.lno; if (F_ISSET(sp, SC_VI)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } else sp->cno = rm.cno; } /* Ex terminates with a bang, even if the command fails. */ if (!F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_EX_SILENT)) (void)ex_puts(sp, "!\n"); + + /* Apply expandtab to the new text */ + if (O_ISSET(sp, O_EXPANDTAB)) + ex_retab(sp, cmdp); /* * XXX * The ! commands never return an error, so that autoprint always * happens in the ex parser. */ return (0); } Index: head/contrib/nvi/ex/ex_cd.c =================================================================== --- head/contrib/nvi/ex/ex_cd.c (revision 365498) +++ head/contrib/nvi/ex/ex_cd.c (revision 365499) @@ -1,131 +1,127 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_cd.c,v 10.13 2012/04/12 06:28:27 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_cd -- :cd[!] [directory] * Change directories. * * PUBLIC: int ex_cd(SCR *, EXCMD *); */ int ex_cd(SCR *sp, EXCMD *cmdp) { struct passwd *pw; ARGS *ap; int savech; char *dir, *p, *t; char *buf; size_t dlen; /* * !!! * Historic practice is that the cd isn't attempted if the file has * been modified, unless its name begins with a leading '/' or the * force flag is set. */ if (F_ISSET(sp->ep, F_MODIFIED) && !FL_ISSET(cmdp->iflags, E_C_FORCE) && sp->frp->name[0] != '/') { msgq(sp, M_ERR, "120|File modified since last complete write; write or use ! to override"); return (1); } switch (cmdp->argc) { case 0: /* If no argument, change to the user's home directory. */ if ((dir = getenv("HOME")) == NULL) { if ((pw = getpwuid(getuid())) == NULL || pw->pw_dir == NULL || pw->pw_dir[0] == '\0') { msgq(sp, M_ERR, "121|Unable to find home directory location"); return (1); } dir = pw->pw_dir; } break; case 1: INT2CHAR(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len + 1, dir, dlen); break; default: abort(); } /* * Try the current directory first. If this succeeds, don't display * a message, vi didn't historically, and it should be obvious to the * user where they are. */ if (!chdir(dir)) return (0); /* * If moving to the user's home directory, or, the path begins with * "/", "./" or "../", it's the only place we try. */ if (cmdp->argc == 0 || (ap = cmdp->argv[0])->bp[0] == '/' || (ap->len == 1 && ap->bp[0] == '.') || (ap->len >= 2 && ap->bp[0] == '.' && ap->bp[1] == '.' && (ap->bp[2] == '/' || ap->bp[2] == '\0'))) goto err; /* Try the O_CDPATH option values. */ for (p = t = O_STR(sp, O_CDPATH);; ++p) if (*p == '\0' || *p == ':') { /* * Ignore the empty strings and ".", since we've already * tried the current directory. */ if (t < p && (p - t != 1 || *t != '.')) { savech = *p; *p = '\0'; if ((buf = join(t, dir)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } *p = savech; if (!chdir(buf)) { free(buf); if ((buf = getcwd(NULL, 0)) != NULL) { msgq_str(sp, M_INFO, buf, "122|New current directory: %s"); free(buf); } return (0); } free(buf); } t = p + 1; if (*p == '\0') break; } err: msgq_str(sp, M_SYSERR, dir, "%s"); return (1); } Index: head/contrib/nvi/ex/ex_cmd.c =================================================================== --- head/contrib/nvi/ex/ex_cmd.c (revision 365498) +++ head/contrib/nvi/ex/ex_cmd.c (revision 365499) @@ -1,445 +1,441 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_cmd.c,v 10.26 2011/07/14 15:11:16 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" /* * This array maps ex command names to command functions. * * The order in which command names are listed below is important -- * ambiguous abbreviations are resolved to be the first possible match, * e.g. "r" means "read", not "rewind", because "read" is listed before * "rewind". * * The syntax of the ex commands is unbelievably irregular, and a special * case from beginning to end. Each command has an associated "syntax * script" which describes the "arguments" that are possible. The script * syntax is as follows: * * ! -- ! flag * 1 -- flags: [+-]*[pl#][+-]* * 2 -- flags: [-.+^] * 3 -- flags: [-.+^=] * b -- buffer * c[01+a] -- count (0-N, 1-N, signed 1-N, address offset) * f[N#][or] -- file (a number or N, optional or required) * l -- line * S -- string with file name expansion * s -- string * W -- word string * w[N#][or] -- word (a number or N, optional or required) */ EXCMDLIST const cmds[] = { /* C_SCROLL */ {L("\004"), ex_pr, E_ADDR2, "", "^D", "scroll lines"}, /* C_BANG */ {L("!"), ex_bang, E_ADDR2_NONE|E_SECURE, "S", "[line [,line]] ! command", "filter lines through commands or run commands"}, /* C_HASH */ {L("#"), ex_number, E_ADDR2|E_CLRFLAG, "ca1", "[line [,line]] # [count] [l]", "display numbered lines"}, /* C_SUBAGAIN */ {L("&"), ex_subagain, E_ADDR2|E_ADDR_ZERO, "s", "[line [,line]] & [cgr] [count] [#lp]", "repeat the last subsitution"}, /* C_STAR */ {L("*"), ex_at, 0, "b", "* [buffer]", "execute a buffer"}, /* C_SHIFTL */ {L("<"), ex_shiftl, E_ADDR2|E_AUTOPRINT, "ca1", "[line [,line]] <[<...] [count] [flags]", "shift lines left"}, /* C_EQUAL */ {L("="), ex_equal, E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF, "1", "[line] = [flags]", "display line number"}, /* C_SHIFTR */ {L(">"), ex_shiftr, E_ADDR2|E_AUTOPRINT, "ca1", "[line [,line]] >[>...] [count] [flags]", "shift lines right"}, /* C_AT */ {L("@"), ex_at, E_ADDR2, "b", "@ [buffer]", "execute a buffer"}, /* C_APPEND */ {L("append"), ex_append, E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF, "!", "[line] a[ppend][!]", "append input to a line"}, /* C_ABBR */ {L("abbreviate"), ex_abbr, 0, "W", "ab[brev] [word replace]", "specify an input abbreviation"}, /* C_ARGS */ {L("args"), ex_args, 0, "", "ar[gs]", "display file argument list"}, /* C_BG */ {L("bg"), ex_bg, E_VIONLY, "", "bg", "put a foreground screen into the background"}, /* C_CHANGE */ {L("change"), ex_change, E_ADDR2|E_ADDR_ZERODEF, "!ca", "[line [,line]] c[hange][!] [count]", "change lines to input"}, /* C_CD */ {L("cd"), ex_cd, 0, "!f1o", "cd[!] [directory]", "change the current directory"}, /* C_CHDIR */ {L("chdir"), ex_cd, 0, "!f1o", "chd[ir][!] [directory]", "change the current directory"}, /* C_COPY */ {L("copy"), ex_copy, E_ADDR2|E_AUTOPRINT, "l1", "[line [,line]] co[py] line [flags]", "copy lines elsewhere in the file"}, /* C_CSCOPE */ {L("cscope"), ex_cscope, 0, "!s", "cs[cope] command [args]", "create a set of tags using a cscope command"}, /* * !!! * Adding new commands starting with 'd' may break the delete command code * in ex_cmd() (the ex parser). Read through the comments there, first. */ /* C_DELETE */ {L("delete"), ex_delete, E_ADDR2|E_AUTOPRINT, "bca1", "[line [,line]] d[elete][flags] [buffer] [count] [flags]", "delete lines from the file"}, /* C_DISPLAY */ {L("display"), ex_display, 0, "w1r", "display b[uffers] | c[onnections] | s[creens] | t[ags]", "display buffers, connections, screens or tags"}, /* C_EDIT */ {L("edit"), ex_edit, E_NEWSCREEN, "f1o", "[Ee][dit][!] [+cmd] [file]", "begin editing another file"}, /* C_EX */ {L("ex"), ex_edit, E_NEWSCREEN, "f1o", "[Ee]x[!] [+cmd] [file]", "begin editing another file"}, /* C_EXUSAGE */ {L("exusage"), ex_usage, 0, "w1o", "[exu]sage [command]", "display ex command usage statement"}, /* C_FILE */ {L("file"), ex_file, 0, "f1o", "f[ile] [name]", "display (and optionally set) file name"}, /* C_FG */ {L("fg"), ex_fg, E_NEWSCREEN|E_VIONLY, "f1o", "[Ff]g [file]", "bring a backgrounded screen into the foreground"}, /* C_GLOBAL */ {L("global"), ex_global, E_ADDR2_ALL, "!s", "[line [,line]] g[lobal][!] [;/]RE[;/] [commands]", "execute a global command on lines matching an RE"}, /* C_HELP */ {L("help"), ex_help, 0, "", "he[lp]", "display help statement"}, /* C_INSERT */ {L("insert"), ex_insert, E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF, "!", "[line] i[nsert][!]", "insert input before a line"}, /* C_JOIN */ {L("join"), ex_join, E_ADDR2|E_AUTOPRINT, "!ca1", "[line [,line]] j[oin][!] [count] [flags]", "join lines into a single line"}, /* C_K */ {L("k"), ex_mark, E_ADDR1, "w1r", "[line] k key", "mark a line position"}, /* C_LIST */ {L("list"), ex_list, E_ADDR2|E_CLRFLAG, "ca1", "[line [,line]] l[ist] [count] [#]", "display lines in an unambiguous form"}, /* C_MOVE */ {L("move"), ex_move, E_ADDR2|E_AUTOPRINT, "l", "[line [,line]] m[ove] line", "move lines elsewhere in the file"}, /* C_MARK */ {L("mark"), ex_mark, E_ADDR1, "w1r", "[line] ma[rk] key", "mark a line position"}, /* C_MAP */ {L("map"), ex_map, 0, "!W", "map[!] [keys replace]", "map input or commands to one or more keys"}, /* C_MKEXRC */ {L("mkexrc"), ex_mkexrc, 0, "!f1r", "mkexrc[!] file", "write a .exrc file"}, /* C_NEXT */ {L("next"), ex_next, E_NEWSCREEN, "!fN", "[Nn][ext][!] [+cmd] [file ...]", "edit (and optionally specify) the next file"}, /* C_NUMBER */ {L("number"), ex_number, E_ADDR2|E_CLRFLAG, "ca1", "[line [,line]] nu[mber] [count] [l]", "change display to number lines"}, /* C_OPEN */ {L("open"), ex_open, E_ADDR1, "s", "[line] o[pen] [/RE/] [flags]", "enter \"open\" mode (not implemented)"}, /* C_PRINT */ {L("print"), ex_pr, E_ADDR2|E_CLRFLAG, "ca1", "[line [,line]] p[rint] [count] [#l]", "display lines"}, /* C_PRESERVE */ {L("preserve"), ex_preserve, 0, "", "pre[serve]", "preserve an edit session for recovery"}, /* C_PREVIOUS */ {L("previous"), ex_prev, E_NEWSCREEN, "!", "[Pp]rev[ious][!]", "edit the previous file in the file argument list"}, /* C_PUT */ {L("put"), ex_put, E_ADDR1|E_AUTOPRINT|E_ADDR_ZERO|E_ADDR_ZERODEF, "b", "[line] pu[t] [buffer]", "append a cut buffer to the line"}, /* C_QUIT */ {L("quit"), ex_quit, 0, "!", "q[uit][!]", "exit ex/vi"}, /* C_READ */ {L("read"), ex_read, E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF, "s", "[line] r[ead] [!cmd | [file]]", "append input from a command or file to the line"}, /* C_RECOVER */ {L("recover"), ex_recover, 0, "!f1r", "recover[!] file", "recover a saved file"}, /* C_RESIZE */ {L("resize"), ex_resize, E_VIONLY, "c+", "resize [+-]rows", "grow or shrink the current screen"}, /* C_REWIND */ {L("rewind"), ex_rew, 0, "!", "rew[ind][!]", "re-edit all the files in the file argument list"}, /* * !!! * Adding new commands starting with 's' may break the substitute command code * in ex_cmd() (the ex parser). Read through the comments there, first. */ /* C_SUBSTITUTE */ {L("s"), ex_s, E_ADDR2|E_ADDR_ZERO, "s", "[line [,line]] s [[/;]RE[/;]repl[/;] [cgr] [count] [#lp]]", "substitute on lines matching an RE"}, /* C_SCRIPT */ {L("script"), ex_script, E_SECURE, "!f1o", "sc[ript][!] [file]", "run a shell in a screen"}, /* C_SET */ {L("set"), ex_set, 0, "wN", "se[t] [option[=[value]]...] [nooption ...] [option? ...] [all]", "set options (use \":set all\" to see all options)"}, /* C_SHELL */ {L("shell"), ex_shell, E_SECURE, "", "sh[ell]", "suspend editing and run a shell"}, /* C_SOURCE */ {L("source"), ex_source, 0, "f1r", "so[urce] file", "read a file of ex commands"}, /* C_STOP */ {L("stop"), ex_stop, E_SECURE, "!", "st[op][!]", "suspend the edit session"}, /* C_SUSPEND */ {L("suspend"), ex_stop, E_SECURE, "!", "su[spend][!]", "suspend the edit session"}, /* C_T */ {L("t"), ex_copy, E_ADDR2|E_AUTOPRINT, "l1", "[line [,line]] t line [flags]", "copy lines elsewhere in the file"}, /* C_TAG */ {L("tag"), ex_tag_push, E_NEWSCREEN, "!w1o", "[Tt]a[g][!] [string]", "edit the file containing the tag"}, /* C_TAGNEXT */ {L("tagnext"), ex_tag_next, 0, "!", "tagn[ext][!]", "move to the next tag"}, /* C_TAGPOP */ {L("tagpop"), ex_tag_pop, 0, "!w1o", "tagp[op][!] [number | file]", "return to the previous group of tags"}, /* C_TAGPREV */ {L("tagprev"), ex_tag_prev, 0, "!", "tagpr[ev][!]", "move to the previous tag"}, /* C_TAGTOP */ {L("tagtop"), ex_tag_top, 0, "!", "tagt[op][!]", "discard all tags"}, /* C_UNDO */ {L("undo"), ex_undo, E_AUTOPRINT, "", "u[ndo]", "undo the most recent change"}, /* C_UNABBREVIATE */ {L("unabbreviate"),ex_unabbr, 0, "w1r", "una[bbrev] word", "delete an abbreviation"}, /* C_UNMAP */ {L("unmap"), ex_unmap, 0, "!w1r", "unm[ap][!] word", "delete an input or command map"}, /* C_V */ {L("v"), ex_v, E_ADDR2_ALL, "s", "[line [,line]] v [;/]RE[;/] [commands]", "execute a global command on lines NOT matching an RE"}, /* C_VERSION */ {L("version"), ex_version, 0, "", "version", "display the program version information"}, /* C_VISUAL_EX */ {L("visual"), ex_visual, E_ADDR1|E_ADDR_ZERODEF, "2c11", "[line] vi[sual] [-|.|+|^] [window_size] [flags]", "enter visual (vi) mode from ex mode"}, /* C_VISUAL_VI */ {L("visual"), ex_edit, E_NEWSCREEN, "f1o", "[Vv]i[sual][!] [+cmd] [file]", "edit another file (from vi mode only)"}, /* C_VIUSAGE */ {L("viusage"), ex_viusage, 0, "w1o", "[viu]sage [key]", "display vi key usage statement"}, /* C_VSPLIT */ {L("vsplit"), ex_edit, E_VIONLY, "f1o", "vs[plit] [+cmd] [file]", "split the current screen vertically"}, /* C_WRITE */ {L("write"), ex_write, E_ADDR2_ALL|E_ADDR_ZERODEF, "!s", "[line [,line]] w[rite][!] [ !cmd | [>>] [file]]", "write the file"}, /* C_WN */ {L("wn"), ex_wn, E_ADDR2_ALL|E_ADDR_ZERODEF, "!s", "[line [,line]] wn[!] [>>] [file]", "write the file and switch to the next file"}, /* C_WQ */ {L("wq"), ex_wq, E_ADDR2_ALL|E_ADDR_ZERODEF, "!s", "[line [,line]] wq[!] [>>] [file]", "write the file and exit"}, /* C_XIT */ {L("xit"), ex_xit, E_ADDR2_ALL|E_ADDR_ZERODEF, "!f1o", "[line [,line]] x[it][!] [file]", "exit"}, /* C_YANK */ {L("yank"), ex_yank, E_ADDR2, "bca", "[line [,line]] ya[nk] [buffer] [count]", "copy lines to a cut buffer"}, /* C_Z */ {L("z"), ex_z, E_ADDR1, "3c01", "[line] z [-|.|+|^|=] [count] [flags]", "display different screens of the file"}, /* C_SUBTILDE */ {L("~"), ex_subtilde, E_ADDR2|E_ADDR_ZERO, "s", "[line [,line]] ~ [cgr] [count] [#lp]", "replace previous RE with previous replacement string,"}, {NULL}, }; Index: head/contrib/nvi/ex/ex_cscope.c =================================================================== --- head/contrib/nvi/ex/ex_cscope.c (revision 365498) +++ head/contrib/nvi/ex/ex_cscope.c (revision 365499) @@ -1,1097 +1,1084 @@ /*- * Copyright (c) 1994, 1996 * Rob Mayoff. All rights reserved. * Copyright (c) 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_cscope.c,v 10.25 2012/10/04 09:23:03 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "pathnames.h" #include "tag.h" #define CSCOPE_DBFILE "cscope.out" #define CSCOPE_PATHS "cscope.tpath" /* * 0name find all uses of name * 1name find definition of name * 2name find all function calls made from name * 3name find callers of name * 4string find text string (cscope 12.9) * 4name find assignments to name (cscope 13.3) * 5pattern change pattern -- NOT USED * 6pattern find pattern * 7name find files with name as substring * 8name find files #including name */ #define FINDHELP "\ find c|d|e|f|g|i|s|t buffer|pattern\n\ c: find callers of name\n\ d: find all function calls made from name\n\ e: find pattern\n\ f: find files with name as substring\n\ g: find definition of name\n\ i: find files #including name\n\ s: find all uses of name\n\ t: find assignments to name" static int cscope_add(SCR *, EXCMD *, CHAR_T *); static int cscope_find(SCR *, EXCMD*, CHAR_T *); static int cscope_help(SCR *, EXCMD *, CHAR_T *); static int cscope_kill(SCR *, EXCMD *, CHAR_T *); static int cscope_reset(SCR *, EXCMD *, CHAR_T *); typedef struct _cc { char *name; int (*function)(SCR *, EXCMD *, CHAR_T *); char *help_msg; char *usage_msg; } CC; static CC const cscope_cmds[] = { { "add", cscope_add, "Add a new cscope database", "add file | directory" }, { "find", cscope_find, "Query the databases for a pattern", FINDHELP }, { "help", cscope_help, "Show help for cscope commands", "help [command]" }, { "kill", cscope_kill, "Kill a cscope connection", "kill number" }, { "reset", cscope_reset, "Discard all current cscope connections", "reset" }, { NULL } }; static TAGQ *create_cs_cmd(SCR *, char *, size_t *); static int csc_help(SCR *, char *); static void csc_file(SCR *, CSC *, char *, char **, size_t *, int *); static int get_paths(SCR *, CSC *); static CC const *lookup_ccmd(char *); static int parse(SCR *, CSC *, TAGQ *, int *); static int read_prompt(SCR *, CSC *); static int run_cscope(SCR *, CSC *, char *); static int start_cscopes(SCR *, EXCMD *); static int terminate(SCR *, CSC *, int); /* * ex_cscope -- * Perform an ex cscope. * * PUBLIC: int ex_cscope(SCR *, EXCMD *); */ int ex_cscope(SCR *sp, EXCMD *cmdp) { CC const *ccp; EX_PRIVATE *exp; int i; CHAR_T *cmd; CHAR_T *p; char *np; size_t nlen; /* Initialize the default cscope directories. */ exp = EXP(sp); if (!F_ISSET(exp, EXP_CSCINIT) && start_cscopes(sp, cmdp)) return (1); F_SET(exp, EXP_CSCINIT); /* Skip leading whitespace. */ for (p = cmdp->argv[0]->bp, i = cmdp->argv[0]->len; i > 0; --i, ++p) if (!isspace(*p)) break; if (i == 0) goto usage; /* Skip the command to any arguments. */ for (cmd = p; i > 0; --i, ++p) if (isspace(*p)) break; if (*p != '\0') { *p++ = '\0'; for (; *p && isspace(*p); ++p); } INT2CHAR(sp, cmd, STRLEN(cmd) + 1, np, nlen); if ((ccp = lookup_ccmd(np)) == NULL) { usage: msgq(sp, M_ERR, "309|Use \"cscope help\" for help"); return (1); } /* Call the underlying function. */ return (ccp->function(sp, cmdp, p)); } /* * start_cscopes -- * Initialize the cscope package. */ static int start_cscopes(SCR *sp, EXCMD *cmdp) { size_t blen, len; char *bp, *cscopes, *p, *t; CHAR_T *wp; size_t wlen; /* * EXTENSION #1: * * If the CSCOPE_DIRS environment variable is set, we treat it as a * list of cscope directories that we're using, similar to the tags * edit option. * * XXX * This should probably be an edit option, although that implies that * we start/stop cscope processes periodically, instead of once when * the editor starts. */ if ((cscopes = getenv("CSCOPE_DIRS")) == NULL) return (0); len = strlen(cscopes); GET_SPACE_RETC(sp, bp, blen, len); memcpy(bp, cscopes, len + 1); for (cscopes = t = bp; (p = strsep(&t, "\t :")) != NULL;) if (*p != '\0') { CHAR2INT(sp, p, strlen(p) + 1, wp, wlen); (void)cscope_add(sp, cmdp, wp); } FREE_SPACE(sp, bp, blen); return (0); } /* * cscope_add -- * The cscope add command. */ static int cscope_add(SCR *sp, EXCMD *cmdp, CHAR_T *dname) { struct stat sb; EX_PRIVATE *exp; CSC *csc; size_t len; int cur_argc; char *dbname, *path; char *np = NULL; size_t nlen; exp = EXP(sp); /* * 0 additional args: usage. * 1 additional args: matched a file. * >1 additional args: object, too many args. */ cur_argc = cmdp->argc; if (argv_exp2(sp, cmdp, dname, STRLEN(dname))) { return (1); } if (cmdp->argc == cur_argc) { (void)csc_help(sp, "add"); return (1); } if (cmdp->argc == cur_argc + 1) dname = cmdp->argv[cur_argc]->bp; else { ex_emsg(sp, np, EXM_FILECOUNT); return (1); } INT2CHAR(sp, dname, STRLEN(dname)+1, np, nlen); /* * The user can specify a specific file (so they can have multiple * Cscope databases in a single directory) or a directory. If the * file doesn't exist, we're done. If it's a directory, append the * standard database file name and try again. Store the directory * name regardless so that we can use it as a base for searches. */ if (stat(np, &sb)) { msgq(sp, M_SYSERR, "%s", np); return (1); } if (S_ISDIR(sb.st_mode)) { if ((path = join(np, CSCOPE_DBFILE)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } if (stat(path, &sb)) { msgq(sp, M_SYSERR, "%s", path); free(path); return (1); } free(path); dbname = CSCOPE_DBFILE; } else if ((dbname = strrchr(np, '/')) != NULL) *dbname++ = '\0'; else { dbname = np; np = "."; } /* Allocate a cscope connection structure and initialize its fields. */ len = strlen(np); - CALLOC_RET(sp, csc, CSC *, 1, sizeof(CSC) + len); + CALLOC_RET(sp, csc, 1, sizeof(CSC) + len); csc->dname = csc->buf; csc->dlen = len; memcpy(csc->dname, np, len); csc->mtim = sb.st_mtimespec; /* Get the search paths for the cscope. */ if (get_paths(sp, csc)) goto err; /* Start the cscope process. */ if (run_cscope(sp, csc, dbname)) goto err; /* * Add the cscope connection to the screen's list. From now on, * on error, we have to call terminate, which expects the csc to * be on the chain. */ SLIST_INSERT_HEAD(exp->cscq, csc, q); /* Read the initial prompt from the cscope to make sure it's okay. */ return read_prompt(sp, csc); err: free(csc); return (1); } /* * get_paths -- * Get the directories to search for the files associated with this * cscope database. */ static int get_paths(SCR *sp, CSC *csc) { struct stat sb; int fd, nentries; size_t len; char *p, **pathp, *buf; /* * EXTENSION #2: * * If there's a cscope directory with a file named CSCOPE_PATHS, it * contains a colon-separated list of paths in which to search for * files returned by cscope. * * XXX * These paths are absolute paths, and not relative to the cscope * directory. To fix this, rewrite the each path using the cscope * directory as a prefix. */ if ((buf = join(csc->dname, CSCOPE_PATHS)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } if (stat(buf, &sb) == 0) { /* Read in the CSCOPE_PATHS file. */ len = sb.st_size; - MALLOC_RET(sp, csc->pbuf, char *, len + 1); + MALLOC_RET(sp, csc->pbuf, len + 1); if ((fd = open(buf, O_RDONLY, 0)) < 0 || read(fd, csc->pbuf, len) != len) { msgq_str(sp, M_SYSERR, buf, "%s"); if (fd >= 0) (void)close(fd); free(buf); return (1); } (void)close(fd); free(buf); csc->pbuf[len] = '\0'; /* Count up the entries. */ for (nentries = 0, p = csc->pbuf; *p != '\0'; ++p) if (p[0] == ':' && p[1] != '\0') ++nentries; /* Build an array of pointers to the paths. */ - CALLOC_GOTO(sp, - csc->paths, char **, nentries + 1, sizeof(char **)); + CALLOC_GOTO(sp, csc->paths, nentries + 1, sizeof(char **)); for (pathp = csc->paths, p = strtok(csc->pbuf, ":"); p != NULL; p = strtok(NULL, ":")) *pathp++ = p; return (0); } free(buf); /* * If the CSCOPE_PATHS file doesn't exist, we look for files * relative to the cscope directory. */ if ((csc->pbuf = strdup(csc->dname)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } - CALLOC_GOTO(sp, csc->paths, char **, 2, sizeof(char *)); + CALLOC_GOTO(sp, csc->paths, 2, sizeof(char *)); csc->paths[0] = csc->pbuf; return (0); alloc_err: - if (csc->pbuf != NULL) { - free(csc->pbuf); - csc->pbuf = NULL; - } + free(csc->pbuf); + csc->pbuf = NULL; return (1); } /* * run_cscope -- * Fork off the cscope process. */ static int run_cscope(SCR *sp, CSC *csc, char *dbname) { int to_cs[2], from_cs[2]; char *cmd; /* * Cscope reads from to_cs[0] and writes to from_cs[1]; vi reads from * from_cs[0] and writes to to_cs[1]. */ to_cs[0] = to_cs[1] = from_cs[0] = from_cs[1] = -1; if (pipe(to_cs) < 0 || pipe(from_cs) < 0) { msgq(sp, M_SYSERR, "pipe"); goto err; } switch (csc->pid = vfork()) { char *dn, *dbn; case -1: msgq(sp, M_SYSERR, "vfork"); err: if (to_cs[0] != -1) (void)close(to_cs[0]); if (to_cs[1] != -1) (void)close(to_cs[1]); if (from_cs[0] != -1) (void)close(from_cs[0]); if (from_cs[1] != -1) (void)close(from_cs[1]); return (1); case 0: /* child: run cscope. */ (void)dup2(to_cs[0], STDIN_FILENO); (void)dup2(from_cs[1], STDOUT_FILENO); (void)dup2(from_cs[1], STDERR_FILENO); /* Close unused file descriptors. */ (void)close(to_cs[1]); (void)close(from_cs[0]); /* Run the cscope command. */ #define CSCOPE_CMD_FMT "cd %s && exec cscope -dl -f %s" if ((dn = quote(csc->dname)) == NULL) goto nomem; if ((dbn = quote(dbname)) == NULL) { free(dn); goto nomem; } (void)asprintf(&cmd, CSCOPE_CMD_FMT, dn, dbn); free(dbn); free(dn); if (cmd == NULL) { nomem: msgq(sp, M_SYSERR, NULL); _exit (1); } (void)execl(_PATH_BSHELL, "sh", "-c", cmd, (char *)NULL); msgq_str(sp, M_SYSERR, cmd, "execl: %s"); free(cmd); _exit (127); /* NOTREACHED */ default: /* parent. */ /* Close unused file descriptors. */ (void)close(to_cs[0]); (void)close(from_cs[1]); /* * Save the file descriptors for later duplication, and * reopen as streams. */ csc->to_fd = to_cs[1]; csc->to_fp = fdopen(to_cs[1], "w"); csc->from_fd = from_cs[0]; csc->from_fp = fdopen(from_cs[0], "r"); break; } return (0); } /* * cscope_find -- * The cscope find command. */ static int cscope_find(SCR *sp, EXCMD *cmdp, CHAR_T *pattern) { CSC *csc, *csc_next; EX_PRIVATE *exp; FREF *frp; TAGQ *rtqp, *tqp; TAG *rtp; recno_t lno; size_t cno, search; int force, istmp, matches; char *np = NULL; size_t nlen; exp = EXP(sp); /* Check for connections. */ if (SLIST_EMPTY(exp->cscq)) { msgq(sp, M_ERR, "310|No cscope connections running"); return (1); } /* * Allocate all necessary memory before doing anything hard. If the * tags stack is empty, we'll need the `local context' TAGQ structure * later. */ rtp = NULL; rtqp = NULL; if (TAILQ_EMPTY(exp->tq)) { /* Initialize the `local context' tag queue structure. */ - CALLOC_GOTO(sp, rtqp, TAGQ *, 1, sizeof(TAGQ)); + CALLOC_GOTO(sp, rtqp, 1, sizeof(TAGQ)); TAILQ_INIT(rtqp->tagq); /* Initialize and link in its tag structure. */ - CALLOC_GOTO(sp, rtp, TAG *, 1, sizeof(TAG)); + CALLOC_GOTO(sp, rtp, 1, sizeof(TAG)); TAILQ_INSERT_HEAD(rtqp->tagq, rtp, q); rtqp->current = rtp; } /* Create the cscope command. */ INT2CHAR(sp, pattern, STRLEN(pattern) + 1, np, nlen); np = strdup(np); if ((tqp = create_cs_cmd(sp, np, &search)) == NULL) goto err; - if (np != NULL) - free(np); + free(np); + np = NULL; /* * Stick the current context in a convenient place, we'll lose it * when we switch files. */ frp = sp->frp; lno = sp->lno; cno = sp->cno; istmp = F_ISSET(sp->frp, FR_TMPFILE) && !F_ISSET(cmdp, E_NEWSCREEN); /* Search all open connections for a match. */ matches = 0; /* Copy next connect here in case csc is killed. */ SLIST_FOREACH_SAFE(csc, exp->cscq, q, csc_next) { /* * Send the command to the cscope program. (We skip the * first two bytes of the command, because we stored the * search cscope command character and a leading space * there.) */ (void)fprintf(csc->to_fp, "%lu%s\n", search, tqp->tag + 2); (void)fflush(csc->to_fp); /* Read the output. */ if (parse(sp, csc, tqp, &matches)) goto nomatch; } if (matches == 0) { msgq(sp, M_INFO, "278|No matches for query"); -nomatch: if (rtp != NULL) - free(rtp); - if (rtqp != NULL) - free(rtqp); +nomatch: free(rtp); + free(rtqp); tagq_free(sp, tqp); return (1); } /* Try to switch to the first tag. */ force = FL_ISSET(cmdp->iflags, E_C_FORCE); if (F_ISSET(cmdp, E_NEWSCREEN)) { if (ex_tag_Nswitch(sp, tqp->current, force)) goto err; /* Everything else gets done in the new screen. */ sp = sp->nextdisp; exp = EXP(sp); } else if (ex_tag_nswitch(sp, tqp->current, force)) goto err; /* * If this is the first tag, put a `current location' queue entry * in place, so we can pop all the way back to the current mark. * Note, it doesn't point to much of anything, it's a placeholder. */ if (TAILQ_EMPTY(exp->tq)) { TAILQ_INSERT_HEAD(exp->tq, rtqp, q); } else rtqp = TAILQ_FIRST(exp->tq); /* Link the current TAGQ structure into place. */ TAILQ_INSERT_HEAD(exp->tq, tqp, q); (void)cscope_search(sp, tqp, tqp->current); /* * Move the current context from the temporary save area into the * right structure. * * If we were in a temporary file, we don't have a context to which * we can return, so just make it be the same as what we're moving * to. It will be a little odd that ^T doesn't change anything, but * I don't think it's a big deal. */ if (istmp) { rtqp->current->frp = sp->frp; rtqp->current->lno = sp->lno; rtqp->current->cno = sp->cno; } else { rtqp->current->frp = frp; rtqp->current->lno = lno; rtqp->current->cno = cno; } return (0); err: alloc_err: - if (rtqp != NULL) - free(rtqp); - if (rtp != NULL) - free(rtp); - if (np != NULL) - free(np); + free(rtqp); + free(rtp); + free(np); return (1); } /* * create_cs_cmd -- * Build a cscope command, creating and initializing the base TAGQ. */ static TAGQ * create_cs_cmd(SCR *sp, char *pattern, size_t *searchp) { CB *cbp; TAGQ *tqp; size_t tlen; char *p; /* * Cscope supports a "change pattern" command which we never use, * cscope command 5. Set CSCOPE_QUERIES[5] to " " since the user * can't pass " " as the first character of pattern. That way the * user can't ask for pattern 5 so we don't need any special-case * code. */ #define CSCOPE_QUERIES "sgdct efi" if (pattern == NULL) goto usage; /* Skip leading blanks, check for command character. */ for (; cmdskip(pattern[0]); ++pattern); if (pattern[0] == '\0' || !cmdskip(pattern[1])) goto usage; for (*searchp = 0, p = CSCOPE_QUERIES; *p != '\0' && *p != pattern[0]; ++*searchp, ++p); if (*p == '\0') { msgq(sp, M_ERR, "311|%s: unknown search type: use one of %s", KEY_NAME(sp, pattern[0]), CSCOPE_QUERIES); return (NULL); } /* Skip characters to the pattern. */ for (p = pattern + 1; *p != '\0' && cmdskip(*p); ++p); if (*p == '\0') { usage: (void)csc_help(sp, "find"); return (NULL); } /* The user can specify the contents of a buffer as the pattern. */ cbp = NULL; if (p[0] == '"' && p[1] != '\0' && p[2] == '\0') CBNAME(sp, cbp, p[1]); if (cbp != NULL) { INT2CHAR(sp, TAILQ_FIRST(cbp->textq)->lb, TAILQ_FIRST(cbp->textq)->len, p, tlen); } else tlen = strlen(p); /* Allocate and initialize the TAGQ structure. */ - CALLOC(sp, tqp, TAGQ *, 1, sizeof(TAGQ) + tlen + 3); + CALLOC(sp, tqp, 1, sizeof(TAGQ) + tlen + 3); if (tqp == NULL) return (NULL); TAILQ_INIT(tqp->tagq); tqp->tag = tqp->buf; tqp->tag[0] = pattern[0]; tqp->tag[1] = ' '; tqp->tlen = tlen + 2; memcpy(tqp->tag + 2, p, tlen); tqp->tag[tlen + 2] = '\0'; F_SET(tqp, TAG_CSCOPE); return (tqp); } /* * parse -- * Parse the cscope output. */ static int parse(SCR *sp, CSC *csc, TAGQ *tqp, int *matchesp) { TAG *tp; recno_t slno = 0; size_t dlen, nlen = 0, slen = 0; int ch, i, isolder = 0, nlines; char *dname = NULL, *name = NULL, *search, *p, *t, dummy[2], buf[2048]; CHAR_T *wp; size_t wlen; for (;;) { if (!fgets(buf, sizeof(buf), csc->from_fp)) goto io_err; /* * If the database is out of date, or there's some other * problem, cscope will output error messages before the * number-of-lines output. Display/discard any output * that doesn't match what we want. */ #define CSCOPE_NLINES_FMT "cscope: %d lines%1[\n]" if (sscanf(buf, CSCOPE_NLINES_FMT, &nlines, dummy) == 2) break; if ((p = strchr(buf, '\n')) != NULL) *p = '\0'; msgq(sp, M_ERR, "%s: \"%s\"", csc->dname, buf); } while (nlines--) { if (fgets(buf, sizeof(buf), csc->from_fp) == NULL) goto io_err; /* If the line's too long for the buffer, discard it. */ if ((p = strchr(buf, '\n')) == NULL) { while ((ch = getc(csc->from_fp)) != EOF && ch != '\n'); continue; } *p = '\0'; /* * The cscope output is in the following format: * * * * Figure out how long everything is so we can allocate in one * swell foop, but discard anything that looks wrong. */ for (p = buf, i = 0; i < 3 && (t = strsep(&p, "\t ")) != NULL; ++i) switch (i) { case 0: /* Filename. */ name = t; nlen = strlen(name); break; case 1: /* Context. */ break; case 2: /* Line number. */ slno = (recno_t)atol(t); break; } if (i != 3 || p == NULL || t == NULL) continue; /* The rest of the string is the search pattern. */ search = p; slen = strlen(p); /* Resolve the file name. */ csc_file(sp, csc, name, &dname, &dlen, &isolder); /* * If the file is older than the cscope database, that is, * the database was built since the file was last modified, * or there wasn't a search string, use the line number. */ if (isolder || strcmp(search, "") == 0) { search = NULL; slen = 0; } /* * Allocate and initialize a tag structure plus the variable * length cscope information that follows it. */ - CALLOC_RET(sp, tp, - TAG *, 1, sizeof(TAG) + dlen + 2 + nlen + 1 + - (slen + 1) * sizeof(CHAR_T)); + CALLOC_RET(sp, tp, 1, + sizeof(TAG) + dlen + 2 + nlen + 1 + (slen + 1) * sizeof(CHAR_T)); tp->fname = (char *)tp->buf; if (dlen == 1 && *dname == '.') --dlen; else if (dlen != 0) { memcpy(tp->fname, dname, dlen); tp->fname[dlen] = '/'; ++dlen; } memcpy(tp->fname + dlen, name, nlen + 1); tp->fnlen = dlen + nlen; tp->slno = slno; - tp->search = (CHAR_T*)(tp->fname + tp->fnlen + 1); - CHAR2INT(sp, search, slen + 1, wp, wlen); - MEMCPY(tp->search, wp, (tp->slen = slen) + 1); + if (slen != 0) { + tp->search = (CHAR_T*)(tp->fname + tp->fnlen + 1); + CHAR2INT(sp, search, slen + 1, wp, wlen); + MEMCPY(tp->search, wp, (tp->slen = slen) + 1); + } TAILQ_INSERT_TAIL(tqp->tagq, tp, q); /* Try to preset the tag within the current file. */ if (sp->frp != NULL && sp->frp->name != NULL && tqp->current == NULL && !strcmp(tp->fname, sp->frp->name)) tqp->current = tp; ++*matchesp; } if (tqp->current == NULL) tqp->current = TAILQ_FIRST(tqp->tagq); return read_prompt(sp, csc); io_err: if (feof(csc->from_fp)) errno = EIO; msgq_str(sp, M_SYSERR, "%s", csc->dname); terminate(sp, csc, 0); return (1); } /* * csc_file -- * Search for the right path to this file. */ static void csc_file(SCR *sp, CSC *csc, char *name, char **dirp, size_t *dlenp, int *isolderp) { struct stat sb; char **pp, *buf; /* * Check for the file in all of the listed paths. If we don't * find it, we simply return it unchanged. We have to do this * now, even though it's expensive, because if the user changes * directories, we can't change our minds as to where the file * lives. */ for (pp = csc->paths; *pp != NULL; ++pp) { if ((buf = join(*pp, name)) == NULL) { msgq(sp, M_SYSERR, NULL); *dlenp = 0; return; } if (stat(buf, &sb) == 0) { free(buf); *dirp = *pp; *dlenp = strlen(*pp); *isolderp = timespeccmp( &sb.st_mtimespec, &csc->mtim, <); return; } free(buf); } *dlenp = 0; } /* * cscope_help -- * The cscope help command. */ static int cscope_help(SCR *sp, EXCMD *cmdp, CHAR_T *subcmd) { char *np; size_t nlen; INT2CHAR(sp, subcmd, STRLEN(subcmd) + 1, np, nlen); return (csc_help(sp, np)); } /* * csc_help -- * Display help/usage messages. */ static int csc_help(SCR *sp, char *cmd) { CC const *ccp; if (cmd != NULL && *cmd != '\0') if ((ccp = lookup_ccmd(cmd)) == NULL) { ex_printf(sp, "%s doesn't match any cscope command\n", cmd); return (1); } else { ex_printf(sp, "Command: %s (%s)\n", ccp->name, ccp->help_msg); ex_printf(sp, " Usage: %s\n", ccp->usage_msg); return (0); } ex_printf(sp, "cscope commands:\n"); for (ccp = cscope_cmds; ccp->name != NULL; ++ccp) ex_printf(sp, " %*s: %s\n", 5, ccp->name, ccp->help_msg); return (0); } /* * cscope_kill -- * The cscope kill command. */ static int cscope_kill(SCR *sp, EXCMD *cmdp, CHAR_T *cn) { char *np; size_t nlen; int n = 1; if (*cn) { INT2CHAR(sp, cn, STRLEN(cn) + 1, np, nlen); n = atoi(np); } return (terminate(sp, NULL, n)); } /* * terminate -- * Detach from a cscope process. */ static int terminate(SCR *sp, CSC *csc, int n) { EX_PRIVATE *exp; int i = 0, pstat; CSC *cp, *pre_cp = NULL; exp = EXP(sp); /* * We either get a csc structure or a number. Locate and remove * the candidate which matches the structure or the number. */ if (csc == NULL && n < 1) goto badno; SLIST_FOREACH(cp, exp->cscq, q) { ++i; if (csc == NULL ? i != n : cp != csc) { pre_cp = cp; continue; } if (cp == SLIST_FIRST(exp->cscq)) SLIST_REMOVE_HEAD(exp->cscq, q); else SLIST_REMOVE_AFTER(pre_cp, q); csc = cp; break; } if (csc == NULL) { badno: msgq(sp, M_ERR, "312|%d: no such cscope session", n); return (1); } /* * XXX * Theoretically, we have the only file descriptors to the process, * so closing them should let it exit gracefully, deleting temporary * files, etc. However, the earlier created cscope processes seems * to refuse to quit unless we send a SIGTERM signal. */ if (csc->from_fp != NULL) (void)fclose(csc->from_fp); if (csc->to_fp != NULL) (void)fclose(csc->to_fp); if (i > 1) (void)kill(csc->pid, SIGTERM); (void)waitpid(csc->pid, &pstat, 0); /* Discard cscope connection information. */ - if (csc->pbuf != NULL) - free(csc->pbuf); - if (csc->paths != NULL) - free(csc->paths); + free(csc->pbuf); + free(csc->paths); free(csc); return (0); } /* * cscope_reset -- * The cscope reset command. */ static int cscope_reset(SCR *sp, EXCMD *cmdp, CHAR_T *notusedp) { return cscope_end(sp); } /* * cscope_end -- * End all cscope connections. * * PUBLIC: int cscope_end(SCR *); */ int cscope_end(SCR *sp) { EX_PRIVATE *exp; for (exp = EXP(sp); !SLIST_EMPTY(exp->cscq);) if (terminate(sp, NULL, 1)) return (1); return (0); } /* * cscope_display -- * Display current connections. * * PUBLIC: int cscope_display(SCR *); */ int cscope_display(SCR *sp) { EX_PRIVATE *exp; CSC *csc; int i = 0; exp = EXP(sp); if (SLIST_EMPTY(exp->cscq)) { ex_printf(sp, "No cscope connections.\n"); return (0); } SLIST_FOREACH(csc, exp->cscq, q) ex_printf(sp, "%2d %s (process %lu)\n", ++i, csc->dname, (u_long)csc->pid); return (0); } /* * cscope_search -- * Search a file for a cscope entry. * * PUBLIC: int cscope_search(SCR *, TAGQ *, TAG *); */ int cscope_search(SCR *sp, TAGQ *tqp, TAG *tp) { MARK m; /* If we don't have a search pattern, use the line number. */ if (tp->search == NULL) { if (!db_exist(sp, tp->slno)) { tag_msg(sp, TAG_BADLNO, tqp->tag); return (1); } m.lno = tp->slno; } else { /* * Search for the tag; cheap fallback for C functions * if the name is the same but the arguments have changed. */ m.lno = 1; m.cno = 0; if (f_search(sp, &m, &m, tp->search, tp->slen, NULL, SEARCH_CSCOPE | SEARCH_FILE)) { tag_msg(sp, TAG_SEARCH, tqp->tag); return (1); } /* * !!! * Historically, tags set the search direction if it wasn't * already set. */ if (sp->searchdir == NOTSET) sp->searchdir = FORWARD; } /* * !!! * Tags move to the first non-blank, NOT the search pattern start. */ sp->lno = m.lno; sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); return (0); } /* * lookup_ccmd -- * Return a pointer to the command structure. */ static CC const * lookup_ccmd(char *name) { CC const *ccp; size_t len; len = strlen(name); for (ccp = cscope_cmds; ccp->name != NULL; ++ccp) if (strncmp(name, ccp->name, len) == 0) return (ccp); return (NULL); } /* * read_prompt -- * Read a prompt from cscope. */ static int read_prompt(SCR *sp, CSC *csc) { int ch; #define CSCOPE_PROMPT ">> " for (;;) { while ((ch = getc(csc->from_fp)) != EOF && ch != CSCOPE_PROMPT[0]); if (ch == EOF) { terminate(sp, csc, 0); return (1); } if (getc(csc->from_fp) != CSCOPE_PROMPT[1]) continue; if (getc(csc->from_fp) != CSCOPE_PROMPT[2]) continue; break; } return (0); } Index: head/contrib/nvi/ex/ex_delete.c =================================================================== --- head/contrib/nvi/ex/ex_delete.c (revision 365498) +++ head/contrib/nvi/ex/ex_delete.c (revision 365499) @@ -1,64 +1,60 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_delete.c,v 10.11 2001/06/25 15:19:15 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" /* * ex_delete: [line [,line]] d[elete] [buffer] [count] [flags] * * Delete lines from the file. * * PUBLIC: int ex_delete(SCR *, EXCMD *); */ int ex_delete(SCR *sp, EXCMD *cmdp) { recno_t lno; NEEDFILE(sp, cmdp); /* * !!! * Historically, lines deleted in ex were not placed in the numeric * buffers. We follow historic practice so that we don't overwrite * vi buffers accidentally. */ if (cut(sp, FL_ISSET(cmdp->iflags, E_C_BUFFER) ? &cmdp->buffer : NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE)) return (1); /* Delete the lines. */ if (del(sp, &cmdp->addr1, &cmdp->addr2, 1)) return (1); /* Set the cursor to the line after the last line deleted. */ sp->lno = cmdp->addr1.lno; /* Or the last line in the file if deleted to the end of the file. */ if (db_last(sp, &lno)) return (1); if (sp->lno > lno) sp->lno = lno; return (0); } Index: head/contrib/nvi/ex/ex_display.c =================================================================== --- head/contrib/nvi/ex/ex_display.c (revision 365498) +++ head/contrib/nvi/ex/ex_display.c (revision 365499) @@ -1,143 +1,139 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_display.c,v 10.15 2001/06/25 15:19:15 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include "../common/common.h" #include "tag.h" static int is_prefix(ARGS *, CHAR_T *); static int bdisplay(SCR *); static void db(SCR *, CB *, const char *); /* * ex_display -- :display b[uffers] | c[onnections] | s[creens] | t[ags] * * Display cscope connections, buffers, tags or screens. * * PUBLIC: int ex_display(SCR *, EXCMD *); */ int ex_display(SCR *sp, EXCMD *cmdp) { ARGS *arg; arg = cmdp->argv[0]; switch (arg->bp[0]) { case 'b': if (!is_prefix(arg, L("buffers"))) break; return (bdisplay(sp)); case 'c': if (!is_prefix(arg, L("connections"))) break; return (cscope_display(sp)); case 's': if (!is_prefix(arg, L("screens"))) break; return (ex_sdisplay(sp)); case 't': if (!is_prefix(arg, L("tags"))) break; return (ex_tag_display(sp)); } ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } /* * is_prefix -- * * Check that a command argument matches a prefix of a given string. */ static int is_prefix(ARGS *arg, CHAR_T *str) { return arg->len <= STRLEN(str) && !MEMCMP(arg->bp, str, arg->len); } /* * bdisplay -- * * Display buffers. */ static int bdisplay(SCR *sp) { CB *cbp; if (SLIST_EMPTY(sp->gp->cutq) && sp->gp->dcbp == NULL) { msgq(sp, M_INFO, "123|No cut buffers to display"); return (0); } /* Display regular cut buffers. */ SLIST_FOREACH(cbp, sp->gp->cutq, q) { if (isdigit(cbp->name)) continue; if (!TAILQ_EMPTY(cbp->textq)) db(sp, cbp, NULL); if (INTERRUPTED(sp)) return (0); } /* Display numbered buffers. */ SLIST_FOREACH(cbp, sp->gp->cutq, q) { if (!isdigit(cbp->name)) continue; if (!TAILQ_EMPTY(cbp->textq)) db(sp, cbp, NULL); if (INTERRUPTED(sp)) return (0); } /* Display default buffer. */ if ((cbp = sp->gp->dcbp) != NULL) db(sp, cbp, "default buffer"); return (0); } /* * db -- * Display a buffer. */ static void db(SCR *sp, CB *cbp, const char *name) { CHAR_T *p; GS *gp; TEXT *tp; size_t len; gp = sp->gp; (void)ex_printf(sp, "********** %s%s\n", name == NULL ? KEY_NAME(sp, cbp->name) : name, F_ISSET(cbp, CB_LMODE) ? " (line mode)" : " (character mode)"); TAILQ_FOREACH(tp, cbp->textq, q) { for (len = tp->len, p = tp->lb; len--; ++p) { (void)ex_puts(sp, KEY_NAME(sp, *p)); if (INTERRUPTED(sp)) return; } (void)ex_puts(sp, "\n"); } } Index: head/contrib/nvi/ex/ex_edit.c =================================================================== --- head/contrib/nvi/ex/ex_edit.c (revision 365498) +++ head/contrib/nvi/ex/ex_edit.c (revision 365499) @@ -1,159 +1,155 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_edit.c,v 10.15 2011/12/22 23:26:50 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" static int ex_N_edit(SCR *, EXCMD *, FREF *, int); /* * ex_edit -- :e[dit][!] [+cmd] [file] * :ex[!] [+cmd] [file] * :vi[sual][!] [+cmd] [file] * * Edit a file; if none specified, re-edit the current file. The third * form of the command can only be executed while in vi mode. See the * hack in ex.c:ex_cmd(). * * !!! * Historic vi didn't permit the '+' command form without specifying * a file name as well. This seems unreasonable, so we support it * regardless. * * PUBLIC: int ex_edit(SCR *, EXCMD *); */ int ex_edit(SCR *sp, EXCMD *cmdp) { FREF *frp; int attach, setalt; char *np; size_t nlen; switch (cmdp->argc) { case 0: /* * If the name has been changed, we edit that file, not the * original name. If the user was editing a temporary file * (or wasn't editing any file), create another one. The * reason for not reusing temporary files is that there is * special exit processing of them, and reuse is tricky. */ frp = sp->frp; if (sp->ep == NULL || F_ISSET(frp, FR_TMPFILE)) { if ((frp = file_add(sp, NULL)) == NULL) return (1); attach = 0; } else attach = 1; setalt = 0; break; case 1: INT2CHAR(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len + 1, np, nlen); if ((frp = file_add(sp, np)) == NULL) return (1); attach = 0; setalt = 1; set_alt_name(sp, np); break; default: abort(); } if (F_ISSET(cmdp, E_NEWSCREEN) || cmdp->cmd == &cmds[C_VSPLIT]) return (ex_N_edit(sp, cmdp, frp, attach)); /* * Check for modifications. * * !!! * Contrary to POSIX 1003.2-1992, autowrite did not affect :edit. */ if (file_m2(sp, FL_ISSET(cmdp->iflags, E_C_FORCE))) return (1); /* Switch files. */ if (file_init(sp, frp, NULL, (setalt ? FS_SETALT : 0) | (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) return (1); F_SET(sp, SC_FSWITCH); return (0); } /* * ex_N_edit -- * New screen version of ex_edit. */ static int ex_N_edit(SCR *sp, EXCMD *cmdp, FREF *frp, int attach) { SCR *new; /* Get a new screen. */ if (screen_init(sp->gp, sp, &new)) return (1); if ((cmdp->cmd == &cmds[C_VSPLIT] && vs_vsplit(sp, new)) || (cmdp->cmd != &cmds[C_VSPLIT] && vs_split(sp, new, 0))) { (void)screen_end(new); return (1); } /* Get a backing file. */ if (attach) { /* Copy file state, keep the screen and cursor the same. */ new->ep = sp->ep; ++new->ep->refcnt; new->frp = frp; new->frp->flags = sp->frp->flags; new->lno = sp->lno; new->cno = sp->cno; #if defined(USE_WIDECHAR) && defined(USE_ICONV) /* Synchronize the iconv environments. */ o_set(new, O_FILEENCODING, OS_STRDUP, O_STR(sp, O_FILEENCODING), 0); conv_enc(new, O_FILEENCODING, 0); #endif } else if (file_init(new, frp, NULL, (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) { (void)vs_discard(new, NULL); (void)screen_end(new); return (1); } /* Create the argument list. */ new->cargv = new->argv = ex_buildargv(sp, NULL, frp->name); /* Set up the switch. */ sp->nextdisp = new; F_SET(sp, SC_SSWITCH); return (0); } Index: head/contrib/nvi/ex/ex_equal.c =================================================================== --- head/contrib/nvi/ex/ex_equal.c (revision 365498) +++ head/contrib/nvi/ex/ex_equal.c (revision 365499) @@ -1,58 +1,54 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_equal.c,v 10.12 2001/06/25 15:19:15 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" /* * ex_equal -- :address = * * PUBLIC: int ex_equal(SCR *, EXCMD *); */ int ex_equal(SCR *sp, EXCMD *cmdp) { recno_t lno; NEEDFILE(sp, cmdp); /* * Print out the line number matching the specified address, * or the number of the last line in the file if no address * specified. * * !!! * Historically, ":0=" displayed 0, and ":=" or ":1=" in an * empty file displayed 1. Until somebody complains loudly, * we're going to do it right. The tables in excmd.c permit * lno to get away with any address from 0 to the end of the * file, which, in an empty file, is 0. */ if (F_ISSET(cmdp, E_ADDR_DEF)) { if (db_last(sp, &lno)) return (1); } else lno = cmdp->addr1.lno; (void)ex_printf(sp, "%ld\n", lno); return (0); } Index: head/contrib/nvi/ex/ex_file.c =================================================================== --- head/contrib/nvi/ex/ex_file.c (revision 365498) +++ head/contrib/nvi/ex/ex_file.c (revision 365499) @@ -1,82 +1,78 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_file.c,v 10.14 2001/06/25 15:19:16 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_file -- :f[ile] [name] * Change the file's name and display the status line. * * PUBLIC: int ex_file(SCR *, EXCMD *); */ int ex_file(SCR *sp, EXCMD *cmdp) { char *p; FREF *frp; char *np; size_t nlen; NEEDFILE(sp, cmdp); switch (cmdp->argc) { case 0: break; case 1: frp = sp->frp; /* Make sure can allocate enough space. */ INT2CHAR(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len + 1, np, nlen); if ((p = v_strdup(sp, np, nlen - 1)) == NULL) return (1); /* If already have a file name, it becomes the alternate. */ if (!F_ISSET(frp, FR_TMPFILE)) set_alt_name(sp, frp->name); /* Free the previous name. */ free(frp->name); frp->name = p; /* * The file has a real name, it's no longer a temporary, * clear the temporary file flags. */ F_CLR(frp, FR_TMPEXIT | FR_TMPFILE); /* Have to force a write if the file exists, next time. */ F_SET(frp, FR_NAMECHANGE); /* Notify the screen. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); break; default: abort(); } msgq_status(sp, sp->lno, MSTAT_SHOWLAST); return (0); } Index: head/contrib/nvi/ex/ex_filter.c =================================================================== --- head/contrib/nvi/ex/ex_filter.c (revision 365498) +++ head/contrib/nvi/ex/ex_filter.c (revision 365499) @@ -1,317 +1,314 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_filter.c,v 10.44 2003/11/05 17:11:54 skimo Exp $"; -#endif /* not lint */ - #include #include +#include #include #include #include #include #include #include #include #include #include "../common/common.h" static int filter_ldisplay(SCR *, FILE *); /* * ex_filter -- * Run a range of lines through a filter utility and optionally * replace the original text with the stdout/stderr output of * the utility. * * PUBLIC: int ex_filter(SCR *, * PUBLIC: EXCMD *, MARK *, MARK *, MARK *, CHAR_T *, enum filtertype); */ int ex_filter(SCR *sp, EXCMD *cmdp, MARK *fm, MARK *tm, MARK *rp, CHAR_T *cmd, enum filtertype ftype) { FILE *ifp, *ofp; pid_t parent_writer_pid, utility_pid; recno_t nread; int input[2], output[2], rval; char *name; char *np; size_t nlen; rval = 0; /* Set return cursor position, which is never less than line 1. */ *rp = *fm; if (rp->lno == 0) rp->lno = 1; /* We're going to need a shell. */ if (opts_empty(sp, O_SHELL, 0)) return (1); /* * There are three different processes running through this code. * They are the utility, the parent-writer and the parent-reader. * The parent-writer is the process that writes from the file to * the utility, the parent reader is the process that reads from * the utility. * * Input and output are named from the utility's point of view. * The utility reads from input[0] and the parent(s) write to * input[1]. The parent(s) read from output[0] and the utility * writes to output[1]. * * !!! * Historically, in the FILTER_READ case, the utility reads from * the terminal (e.g. :r! cat works). Otherwise open up utility * input pipe. */ ofp = NULL; input[0] = input[1] = output[0] = output[1] = -1; if (ftype != FILTER_READ && pipe(input) < 0) { msgq(sp, M_SYSERR, "pipe"); goto err; } /* Open up utility output pipe. */ if (pipe(output) < 0) { msgq(sp, M_SYSERR, "pipe"); goto err; } if ((ofp = fdopen(output[0], "r")) == NULL) { msgq(sp, M_SYSERR, "fdopen"); goto err; } /* Fork off the utility process. */ switch (utility_pid = vfork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "vfork"); err: if (input[0] != -1) (void)close(input[0]); if (input[1] != -1) (void)close(input[1]); if (ofp != NULL) (void)fclose(ofp); else if (output[0] != -1) (void)close(output[0]); if (output[1] != -1) (void)close(output[1]); return (1); case 0: /* Utility. */ /* * Redirect stdin from the read end of the input pipe, and * redirect stdout/stderr to the write end of the output pipe. * * !!! * Historically, ex only directed stdout into the input pipe, * letting stderr come out on the terminal as usual. Vi did * not, directing both stdout and stderr into the input pipe. * We match that practice in both ex and vi for consistency. */ if (input[0] != -1) (void)dup2(input[0], STDIN_FILENO); (void)dup2(output[1], STDOUT_FILENO); (void)dup2(output[1], STDERR_FILENO); /* Close the utility's file descriptors. */ if (input[0] != -1) (void)close(input[0]); if (input[1] != -1) (void)close(input[1]); (void)close(output[0]); (void)close(output[1]); if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL) name = O_STR(sp, O_SHELL); else ++name; INT2CHAR(sp, cmd, STRLEN(cmd)+1, np, nlen); execl(O_STR(sp, O_SHELL), name, "-c", np, (char *)NULL); msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s"); _exit (127); /* NOTREACHED */ default: /* Parent-reader, parent-writer. */ /* Close the pipe ends neither parent will use. */ if (input[0] != -1) (void)close(input[0]); (void)close(output[1]); break; } /* * FILTER_RBANG, FILTER_READ: * * Reading is the simple case -- we don't need a parent writer, * so the parent reads the output from the read end of the output * pipe until it finishes, then waits for the child. Ex_readfp * appends to the MARK, and closes ofp. * * For FILTER_RBANG, there is nothing to write to the utility. * Make sure it doesn't wait forever by closing its standard * input. * * !!! * Set the return cursor to the last line read in for FILTER_READ. * Historically, this behaves differently from ":r file" command, * which leaves the cursor at the first line read in. Check to * make sure that it's not past EOF because we were reading into an * empty file. */ if (ftype == FILTER_RBANG || ftype == FILTER_READ) { if (ftype == FILTER_RBANG) (void)close(input[1]); if (ex_readfp(sp, "filter", ofp, fm, &nread, 1)) rval = 1; sp->rptlines[L_ADDED] += nread; if (ftype == FILTER_READ) if (fm->lno == 0) rp->lno = nread; else rp->lno += nread; goto uwait; } /* * FILTER_BANG, FILTER_WRITE * * Here we need both a reader and a writer. Temporary files are * expensive and we'd like to avoid disk I/O. Using pipes has the * obvious starvation conditions. It's done as follows: * * fork * child * write lines out * exit * parent * FILTER_BANG: * read lines into the file * delete old lines * FILTER_WRITE * read and display lines * wait for child * * XXX * We get away without locking the underlying database because we know * that none of the records that we're reading will be modified until * after we've read them. This depends on the fact that the current * B+tree implementation doesn't balance pages or similar things when * it inserts new records. When the DB code has locking, we should * treat vi as if it were multiple applications sharing a database, and * do the required locking. If necessary a work-around would be to do * explicit locking in the line.c:db_get() code, based on the flag set * here. */ F_SET(sp->ep, F_MULTILOCK); switch (parent_writer_pid = fork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "fork"); (void)close(input[1]); (void)close(output[0]); rval = 1; break; case 0: /* Parent-writer. */ /* * Write the selected lines to the write end of the input * pipe. This instance of ifp is closed by ex_writefp. */ (void)close(output[0]); if ((ifp = fdopen(input[1], "w")) == NULL) _exit (1); _exit(ex_writefp(sp, "filter", ifp, fm, tm, NULL, NULL, 1)); /* NOTREACHED */ default: /* Parent-reader. */ (void)close(input[1]); if (ftype == FILTER_WRITE) { /* * Read the output from the read end of the output * pipe and display it. Filter_ldisplay closes ofp. */ if (filter_ldisplay(sp, ofp)) rval = 1; } else { /* * Read the output from the read end of the output * pipe. Ex_readfp appends to the MARK and closes * ofp. */ if (ex_readfp(sp, "filter", ofp, tm, &nread, 1)) rval = 1; sp->rptlines[L_ADDED] += nread; } /* Wait for the parent-writer. */ if (proc_wait(sp, (long)parent_writer_pid, "parent-writer", 0, 1)) rval = 1; /* Delete any lines written to the utility. */ if (rval == 0 && ftype == FILTER_BANG && (cut(sp, NULL, fm, tm, CUT_LINEMODE) || del(sp, fm, tm, 1))) { rval = 1; break; } /* * If the filter had no output, we may have just deleted * the cursor. Don't do any real error correction, we'll * try and recover later. */ if (rp->lno > 1 && !db_exist(sp, rp->lno)) --rp->lno; break; } F_CLR(sp->ep, F_MULTILOCK); /* * !!! * Ignore errors on vi file reads, to make reads prettier. It's * completely inconsistent, and historic practice. */ uwait: INT2CHAR(sp, cmd, STRLEN(cmd) + 1, np, nlen); return (proc_wait(sp, (long)utility_pid, np, ftype == FILTER_READ && F_ISSET(sp, SC_VI) ? 1 : 0, 0) || rval); } /* * filter_ldisplay -- * Display output from a utility. * * !!! * Historically, the characters were passed unmodified to the terminal. * We use the ex print routines to make sure they're printable. */ static int filter_ldisplay(SCR *sp, FILE *fp) { size_t len; size_t wlen; CHAR_T *wp; EX_PRIVATE *exp; for (exp = EXP(sp); !ex_getline(sp, fp, &len) && !INTERRUPTED(sp);) { FILE2INT5(sp, exp->ibcw, exp->ibp, len, wp, wlen); if (ex_ldisplay(sp, wp, wlen, 0, 0)) break; } if (ferror(fp)) msgq(sp, M_SYSERR, "filter read"); (void)fclose(fp); return (0); } Index: head/contrib/nvi/ex/ex_global.c =================================================================== --- head/contrib/nvi/ex/ex_global.c (revision 365498) +++ head/contrib/nvi/ex/ex_global.c (revision 365499) @@ -1,316 +1,312 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_global.c,v 10.32 2011/12/26 23:37:01 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" enum which {GLOBAL, V}; static int ex_g_setup(SCR *, EXCMD *, enum which); /* * ex_global -- [line [,line]] g[lobal][!] /pattern/ [commands] * Exec on lines matching a pattern. * * PUBLIC: int ex_global(SCR *, EXCMD *); */ int ex_global(SCR *sp, EXCMD *cmdp) { return (ex_g_setup(sp, cmdp, FL_ISSET(cmdp->iflags, E_C_FORCE) ? V : GLOBAL)); } /* * ex_v -- [line [,line]] v /pattern/ [commands] * Exec on lines not matching a pattern. * * PUBLIC: int ex_v(SCR *, EXCMD *); */ int ex_v(SCR *sp, EXCMD *cmdp) { return (ex_g_setup(sp, cmdp, V)); } /* * ex_g_setup -- * Ex global and v commands. */ static int ex_g_setup(SCR *sp, EXCMD *cmdp, enum which cmd) { CHAR_T *ptrn, *p, *t; EXCMD *ecp; MARK abs; RANGE *rp; busy_t btype; recno_t start, end; regex_t *re; regmatch_t match[1]; size_t len; int cnt, delim, eval; CHAR_T *dbp; NEEDFILE(sp, cmdp); if (F_ISSET(sp, SC_EX_GLOBAL)) { msgq_wstr(sp, M_ERR, cmdp->cmd->name, "124|The %s command can't be used as part of a global or v command"); return (1); } /* * Skip leading white space. Historic vi allowed any non-alphanumeric * to serve as the global command delimiter. */ if (cmdp->argc == 0) goto usage; for (p = cmdp->argv[0]->bp; cmdskip(*p); ++p); - if (!isascii(*p) || *p == '\0' || isalnum(*p) || + if (*p == '\0' || is09azAZ(*p) || *p == '\\' || *p == '|' || *p == '\n') { usage: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } delim = *p++; /* * Get the pattern string, toss escaped characters. * * QUOTING NOTE: * Only toss an escaped character if it escapes a delimiter. */ for (ptrn = t = p;;) { if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; /* * !!! * Nul terminate the pattern string -- it's passed * to regcomp which doesn't understand anything else. */ *t = '\0'; break; } if (p[0] == '\\') if (p[1] == delim) ++p; else if (p[1] == '\\') *t++ = *p++; *t++ = *p++; } /* If the pattern string is empty, use the last one. */ if (*ptrn == '\0') { if (sp->re == NULL) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } /* Re-compile the RE if necessary. */ if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH)) return (1); } else { /* Compile the RE. */ if (re_compile(sp, ptrn, t - ptrn, &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH)) return (1); /* * Set saved RE. Historic practice is that globals set * direction as well as the RE. */ sp->searchdir = FORWARD; } re = &sp->re_c; /* The global commands always set the previous context mark. */ abs.lno = sp->lno; abs.cno = sp->cno; if (mark_set(sp, ABSMARK1, &abs, 1)) return (1); /* Get an EXCMD structure. */ - CALLOC_RET(sp, ecp, EXCMD *, 1, sizeof(EXCMD)); + CALLOC_RET(sp, ecp, 1, sizeof(EXCMD)); TAILQ_INIT(ecp->rq); /* * Get a copy of the command string; the default command is print. * Don't worry about a set of s with no command, that will * default to print in the ex parser. We need to have two copies * because the ex parser may step on the command string when it's * parsing it. */ if ((len = cmdp->argv[0]->len - (p - cmdp->argv[0]->bp)) == 0) { p = L("p"); len = 1; } - MALLOC_RET(sp, ecp->cp, CHAR_T *, (len * 2) * sizeof(CHAR_T)); + MALLOC_RET(sp, ecp->cp, (len * 2) * sizeof(CHAR_T)); ecp->o_cp = ecp->cp; ecp->o_clen = len; MEMCPY(ecp->cp + len, p, len); ecp->range_lno = OOBLNO; FL_SET(ecp->agv_flags, cmd == GLOBAL ? AGV_GLOBAL : AGV_V); SLIST_INSERT_HEAD(sp->gp->ecq, ecp, q); /* * For each line... The semantics of global matching are that we first * have to decide which lines are going to get passed to the command, * and then pass them to the command, ignoring other changes. There's * really no way to do this in a single pass, since arbitrary line * creation, deletion and movement can be done in the ex command. For * example, a good vi clone test is ":g/X/mo.-3", or "g/X/.,.+1d". * What we do is create linked list of lines that are tracked through * each ex command. There's a callback routine which the DB interface * routines call when a line is created or deleted. This doesn't help * the layering much. */ btype = BUSY_ON; cnt = INTERRUPT_CHECK; for (start = cmdp->addr1.lno, end = cmdp->addr2.lno; start <= end; ++start) { if (cnt-- == 0) { if (INTERRUPTED(sp)) { SLIST_REMOVE_HEAD(sp->gp->ecq, q); free(ecp->cp); free(ecp); break; } search_busy(sp, btype); btype = BUSY_UPDATE; cnt = INTERRUPT_CHECK; } if (db_get(sp, start, DBG_FATAL, &dbp, &len)) return (1); match[0].rm_so = 0; match[0].rm_eo = len; switch (eval = regexec(&sp->re_c, dbp, 0, match, REG_STARTEND)) { case 0: if (cmd == V) continue; break; case REG_NOMATCH: if (cmd == GLOBAL) continue; break; default: re_error(sp, eval, &sp->re_c); break; } /* If follows the last entry, extend the last entry's range. */ if ((rp = TAILQ_LAST(ecp->rq, _rh)) != NULL && rp->stop == start - 1) { ++rp->stop; continue; } /* Allocate a new range, and append it to the list. */ - CALLOC(sp, rp, RANGE *, 1, sizeof(RANGE)); + CALLOC(sp, rp, 1, sizeof(RANGE)); if (rp == NULL) return (1); rp->start = rp->stop = start; TAILQ_INSERT_TAIL(ecp->rq, rp, q); } search_busy(sp, BUSY_OFF); return (0); } /* * ex_g_insdel -- * Update the ranges based on an insertion or deletion. * * PUBLIC: int ex_g_insdel(SCR *, lnop_t, recno_t); */ int ex_g_insdel(SCR *sp, lnop_t op, recno_t lno) { EXCMD *ecp; RANGE *nrp, *rp; /* All insert/append operations are done as inserts. */ if (op == LINE_APPEND) abort(); if (op == LINE_RESET) return (0); SLIST_FOREACH(ecp, sp->gp->ecq, q) { if (!FL_ISSET(ecp->agv_flags, AGV_AT | AGV_GLOBAL | AGV_V)) continue; TAILQ_FOREACH_SAFE(rp, ecp->rq, q, nrp) { /* If range less than the line, ignore it. */ if (rp->stop < lno) continue; /* * If range greater than the line, decrement or * increment the range. */ if (rp->start > lno) { if (op == LINE_DELETE) { --rp->start; --rp->stop; } else { ++rp->start; ++rp->stop; } continue; } /* * Lno is inside the range, decrement the end point * for deletion, and split the range for insertion. * In the latter case, since we're inserting a new * element, neither range can be exhausted. */ if (op == LINE_DELETE) { if (rp->start > --rp->stop) { TAILQ_REMOVE(ecp->rq, rp, q); free(rp); } } else { - CALLOC_RET(sp, nrp, RANGE *, 1, sizeof(RANGE)); + CALLOC_RET(sp, nrp, 1, sizeof(RANGE)); nrp->start = lno + 1; nrp->stop = rp->stop + 1; rp->stop = lno - 1; TAILQ_INSERT_AFTER(ecp->rq, rp, nrp, q); } } /* * If the command deleted/inserted lines, the cursor moves to * the line after the deleted/inserted line. */ ecp->range_lno = lno; } return (0); } Index: head/contrib/nvi/ex/ex_init.c =================================================================== --- head/contrib/nvi/ex/ex_init.c (revision 365498) +++ head/contrib/nvi/ex/ex_init.c (revision 365499) @@ -1,430 +1,423 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_init.c,v 10.33 2012/04/11 19:12:34 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "tag.h" #include "pathnames.h" enum rc { NOEXIST, NOPERM, RCOK }; static enum rc exrc_isok(SCR *, struct stat *, char *, int, int); static int ex_run_file(SCR *, char *); /* * ex_screen_copy -- * Copy ex screen. * * PUBLIC: int ex_screen_copy(SCR *, SCR *); */ int ex_screen_copy(SCR *orig, SCR *sp) { EX_PRIVATE *oexp, *nexp; /* Create the private ex structure. */ - CALLOC_RET(orig, nexp, EX_PRIVATE *, 1, sizeof(EX_PRIVATE)); + CALLOC_RET(orig, nexp, 1, sizeof(EX_PRIVATE)); sp->ex_private = nexp; /* Initialize queues. */ TAILQ_INIT(nexp->tq); TAILQ_INIT(nexp->tagfq); SLIST_INIT(nexp->cscq); if (orig == NULL) { } else { oexp = EXP(orig); if (oexp->lastbcomm != NULL && (nexp->lastbcomm = v_wstrdup(sp, oexp->lastbcomm, STRLEN(oexp->lastbcomm))) == NULL) { msgq(sp, M_SYSERR, NULL); return(1); } if (ex_tag_copy(orig, sp)) return (1); } return (0); } /* * ex_screen_end -- * End a vi screen. * * PUBLIC: int ex_screen_end(SCR *); */ int ex_screen_end(SCR *sp) { EX_PRIVATE *exp; int rval; if ((exp = EXP(sp)) == NULL) return (0); rval = 0; /* Close down script connections. */ if (F_ISSET(sp, SC_SCRIPT) && sscr_end(sp)) rval = 1; if (argv_free(sp)) rval = 1; - if (exp->ibp != NULL) - free(exp->ibp); + free(exp->ibp); - if (exp->lastbcomm != NULL) - free(exp->lastbcomm); + free(exp->lastbcomm); - if (exp->ibcw.bp1.c != NULL) - free(exp->ibcw.bp1.c); + free(exp->ibcw.bp1.c); if (ex_tag_free(sp)) rval = 1; if (cscope_end(sp)) rval = 1; /* Free private memory. */ free(exp); sp->ex_private = NULL; return (rval); } /* * ex_optchange -- * Handle change of options for ex. * * PUBLIC: int ex_optchange(SCR *, int, char *, u_long *); */ int ex_optchange(SCR *sp, int offset, char *str, u_long *valp) { switch (offset) { case O_TAGS: return (ex_tagf_alloc(sp, str)); } return (0); } /* * ex_exrc -- * Read the EXINIT environment variable and the startup exrc files, * and execute their commands. * * PUBLIC: int ex_exrc(SCR *); */ int ex_exrc(SCR *sp) { struct stat hsb, lsb; char *p, *path; CHAR_T *wp; size_t wlen; /* * Source the system, environment, $HOME and local .exrc values. * Vi historically didn't check $HOME/.exrc if the environment * variable EXINIT was set. This is all done before the file is * read in, because things in the .exrc information can set, for * example, the recovery directory. * * !!! * While nvi can handle any of the options settings of historic vi, * the converse is not true. Since users are going to have to have * files and environmental variables that work with both, we use nvi * versions of both the $HOME and local startup files if they exist, * otherwise the historic ones. * * !!! * For a discussion of permissions and when what .exrc files are * read, see the comment above the exrc_isok() function below. * * !!! * If the user started the historic of vi in $HOME, vi read the user's * .exrc file twice, as $HOME/.exrc and as ./.exrc. We avoid this, as * it's going to make some commands behave oddly, and I can't imagine * anyone depending on it. */ switch (exrc_isok(sp, &hsb, _PATH_SYSEXRC, 1, 0)) { case NOEXIST: case NOPERM: break; case RCOK: if (ex_run_file(sp, _PATH_SYSEXRC)) return (1); break; } /* Run the commands. */ if (EXCMD_RUNNING(sp->gp)) (void)ex_cmd(sp); if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) return (0); if ((p = getenv("NEXINIT")) != NULL) { CHAR2INT(sp, p, strlen(p) + 1, wp, wlen); if (ex_run_str(sp, "NEXINIT", wp, wlen - 1, 1, 0)) return (1); } else if ((p = getenv("EXINIT")) != NULL) { CHAR2INT(sp, p, strlen(p) + 1, wp, wlen); if (ex_run_str(sp, "EXINIT", wp, wlen - 1, 1, 0)) return (1); } else if ((p = getenv("HOME")) != NULL && *p) { int st = 0; if ((path = join(p, _PATH_NEXRC)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } switch (exrc_isok(sp, &hsb, path, 0, 1)) { case NOEXIST: free(path); if ((path = join(p, _PATH_EXRC)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } if (exrc_isok(sp, &hsb, path, 0, 1) == RCOK && ex_run_file(sp, path)) st = 1; break; case NOPERM: break; case RCOK: if (ex_run_file(sp, path)) st = 1; break; } free(path); if (st) return st; } /* Run the commands. */ if (EXCMD_RUNNING(sp->gp)) (void)ex_cmd(sp); if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) return (0); /* Previous commands may have set the exrc option. */ if (O_ISSET(sp, O_EXRC)) { switch (exrc_isok(sp, &lsb, _PATH_NEXRC, 0, 0)) { case NOEXIST: if (exrc_isok(sp, &lsb, _PATH_EXRC, 0, 0) == RCOK && (lsb.st_dev != hsb.st_dev || lsb.st_ino != hsb.st_ino) && ex_run_file(sp, _PATH_EXRC)) return (1); break; case NOPERM: break; case RCOK: if ((lsb.st_dev != hsb.st_dev || lsb.st_ino != hsb.st_ino) && ex_run_file(sp, _PATH_NEXRC)) return (1); break; } /* Run the commands. */ if (EXCMD_RUNNING(sp->gp)) (void)ex_cmd(sp); if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) return (0); } return (0); } /* * ex_run_file -- * Set up a file of ex commands to run. */ static int ex_run_file(SCR *sp, char *name) { EXCMD cmd; CHAR_T *wp; size_t wlen; ex_cinit(sp, &cmd, C_SOURCE, 0, OOBLNO, OOBLNO, 0); CHAR2INT(sp, name, strlen(name)+1, wp, wlen); argv_exp0(sp, &cmd, wp, wlen - 1); return (ex_source(sp, &cmd)); } /* * ex_run_str -- * Set up a string of ex commands to run. * * PUBLIC: int ex_run_str(SCR *, char *, CHAR_T *, size_t, int, int); */ int ex_run_str(SCR *sp, char *name, CHAR_T *str, size_t len, int ex_flags, int nocopy) { GS *gp; EXCMD *ecp; gp = sp->gp; if (EXCMD_RUNNING(gp)) { - CALLOC_RET(sp, ecp, EXCMD *, 1, sizeof(EXCMD)); + CALLOC_RET(sp, ecp, 1, sizeof(EXCMD)); SLIST_INSERT_HEAD(gp->ecq, ecp, q); } else ecp = &gp->excmd; F_INIT(ecp, ex_flags ? E_BLIGNORE | E_NOAUTO | E_NOPRDEF | E_VLITONLY : 0); if (nocopy) ecp->cp = str; else if ((ecp->cp = v_wstrdup(sp, str, len)) == NULL) return (1); ecp->clen = len; if (name == NULL) ecp->if_name = NULL; else { if ((ecp->if_name = v_strdup(sp, name, strlen(name))) == NULL) return (1); ecp->if_lno = 1; F_SET(ecp, E_NAMEDISCARD); } return (0); } /* * exrc_isok -- * Check a .exrc file for source-ability. * * !!! * Historically, vi read the $HOME and local .exrc files if they were owned * by the user's real ID, or the "sourceany" option was set, regardless of * any other considerations. We no longer support the sourceany option as * it's a security problem of mammoth proportions. We require the system * .exrc file to be owned by root, the $HOME .exrc file to be owned by the * user's effective ID (or that the user's effective ID be root) and the * local .exrc files to be owned by the user's effective ID. In all cases, * the file cannot be writeable by anyone other than its owner. * * In O'Reilly ("Learning the VI Editor", Fifth Ed., May 1992, page 106), * it notes that System V release 3.2 and later has an option "[no]exrc". * The behavior is that local .exrc files are read only if the exrc option * is set. The default for the exrc option was off, so, by default, local * .exrc files were not read. The problem this was intended to solve was * that System V permitted users to give away files, so there's no possible * ownership or writeability test to ensure that the file is safe. * * POSIX 1003.2-1992 standardized exrc as an option. It required the exrc * option to be off by default, thus local .exrc files are not to be read * by default. The Rationale noted (incorrectly) that this was a change * to historic practice, but correctly noted that a default of off improves * system security. POSIX also required that vi check the effective user * ID instead of the real user ID, which is why we've switched from historic * practice. * * We initialize the exrc variable to off. If it's turned on by the system * or $HOME .exrc files, and the local .exrc file passes the ownership and * writeability tests, then we read it. This breaks historic 4BSD practice, * but it gives us a measure of security on systems where users can give away * files. */ static enum rc exrc_isok(SCR *sp, struct stat *sbp, char *path, int rootown, int rootid) { enum { ROOTOWN, OWN, WRITER } etype; uid_t euid; int nf1, nf2; char *a, *b, *buf; /* Check for the file's existence. */ if (stat(path, sbp)) return (NOEXIST); /* Check ownership permissions. */ euid = geteuid(); if (!(rootown && sbp->st_uid == 0) && !(rootid && euid == 0) && sbp->st_uid != euid) { etype = rootown ? ROOTOWN : OWN; goto denied; } /* Check writeability. */ if (sbp->st_mode & (S_IWGRP | S_IWOTH)) { etype = WRITER; goto denied; } return (RCOK); denied: a = msg_print(sp, path, &nf1); if (strchr(path, '/') == NULL && (buf = getcwd(NULL, 0)) != NULL) { char *p; b = msg_print(sp, buf, &nf2); if ((p = join(b, a)) == NULL) { msgq(sp, M_SYSERR, NULL); goto err; } switch (etype) { case ROOTOWN: msgq(sp, M_ERR, "128|%s: not sourced: not owned by you or root", p); break; case OWN: msgq(sp, M_ERR, "129|%s: not sourced: not owned by you", p); break; case WRITER: msgq(sp, M_ERR, "130|%s: not sourced: writeable by a user other than the owner", p); break; } free(p); err: free(buf); if (nf2) FREE_SPACE(sp, b, 0); } else switch (etype) { case ROOTOWN: msgq(sp, M_ERR, "128|%s: not sourced: not owned by you or root", a); break; case OWN: msgq(sp, M_ERR, "129|%s: not sourced: not owned by you", a); break; case WRITER: msgq(sp, M_ERR, "130|%s: not sourced: writeable by a user other than the owner", a); break; } if (nf1) FREE_SPACE(sp, a, 0); return (NOPERM); } Index: head/contrib/nvi/ex/ex_join.c =================================================================== --- head/contrib/nvi/ex/ex_join.c (revision 365498) +++ head/contrib/nvi/ex/ex_join.c (revision 365499) @@ -1,170 +1,166 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_join.c,v 10.17 2004/03/16 14:14:04 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_join -- :[line [,line]] j[oin][!] [count] [flags] * Join lines. * * PUBLIC: int ex_join(SCR *, EXCMD *); */ int ex_join(SCR *sp, EXCMD *cmdp) { recno_t from, to; size_t blen, clen, len, tlen; int echar = 0, extra, first; CHAR_T *bp, *tbp = NULL; CHAR_T *p; NEEDFILE(sp, cmdp); from = cmdp->addr1.lno; to = cmdp->addr2.lno; /* Check for no lines to join. */ if (!db_exist(sp, from + 1)) { msgq(sp, M_ERR, "131|No following lines to join"); return (1); } GET_SPACE_RETW(sp, bp, blen, 256); /* * The count for the join command was off-by-one, * historically, to other counts for other commands. */ if (F_ISSET(cmdp, E_ADDR_DEF) || cmdp->addrcnt == 1) ++cmdp->addr2.lno; clen = tlen = 0; for (first = 1, from = cmdp->addr1.lno, to = cmdp->addr2.lno; from <= to; ++from) { /* * Get next line. Historic versions of vi allowed "10J" while * less than 10 lines from the end-of-file, so we do too. */ if (db_get(sp, from, 0, &p, &len)) { cmdp->addr2.lno = from - 1; break; } /* Empty lines just go away. */ if (len == 0) continue; /* * Get more space if necessary. Note, tlen isn't the length * of the new line, it's roughly the amount of space needed. * tbp - bp is the length of the new line. */ tlen += len + 2; ADD_SPACE_RETW(sp, bp, blen, tlen); tbp = bp + clen; /* * Historic practice: * * If force specified, join without modification. * If the current line ends with whitespace, strip leading * whitespace from the joined line. * If the next line starts with a ), do nothing. * If the current line ends with ., insert two spaces. * Else, insert one space. * * One change -- add ? and ! to the list of characters for * which we insert two spaces. I expect that POSIX 1003.2 * will require this as well. * * Echar is the last character in the last line joined. */ extra = 0; if (!first && !FL_ISSET(cmdp->iflags, E_C_FORCE)) { if (isblank(echar)) for (; len && isblank(*p); --len, ++p); else if (p[0] != ')') { if (STRCHR(L(".?!"), echar)) { *tbp++ = ' '; ++clen; extra = 1; } *tbp++ = ' '; ++clen; for (; len && isblank(*p); --len, ++p); } } if (len != 0) { MEMCPY(tbp, p, len); tbp += len; clen += len; echar = p[len - 1]; } else echar = ' '; /* * Historic practice for vi was to put the cursor at the first * inserted whitespace character, if there was one, or the * first character of the joined line, if there wasn't, or the * last character of the line if joined to an empty line. If * a count was specified, the cursor was moved as described * for the first line joined, ignoring subsequent lines. If * the join was a ':' command, the cursor was placed at the * first non-blank character of the line unless the cursor was * "attracted" to the end of line when the command was executed * in which case it moved to the new end of line. There are * probably several more special cases, but frankly, my dear, * I don't give a damn. This implementation puts the cursor * on the first inserted whitespace character, the first * character of the joined line, or the last character of the * line regardless. Note, if the cursor isn't on the joined * line (possible with : commands), it is reset to the starting * line. */ if (first) { sp->cno = (tbp - bp) - (1 + extra); first = 0; } else sp->cno = (tbp - bp) - len - (1 + extra); } sp->lno = cmdp->addr1.lno; /* Delete the joined lines. */ for (from = cmdp->addr1.lno, to = cmdp->addr2.lno; to > from; --to) if (db_delete(sp, to)) goto err; /* If the original line changed, reset it. */ if (!first && db_set(sp, from, bp, tbp - bp)) { err: FREE_SPACEW(sp, bp, blen); return (1); } FREE_SPACEW(sp, bp, blen); sp->rptlines[L_JOINED] += (cmdp->addr2.lno - cmdp->addr1.lno) + 1; return (0); } Index: head/contrib/nvi/ex/ex_map.c =================================================================== --- head/contrib/nvi/ex/ex_map.c (revision 365498) +++ head/contrib/nvi/ex/ex_map.c (revision 365499) @@ -1,118 +1,114 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_map.c,v 10.11 2001/06/25 15:19:17 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_map -- :map[!] [input] [replacement] * Map a key/string or display mapped keys. * * Historical note: * Historic vi maps were fairly bizarre, and likely to differ in * very subtle and strange ways from this implementation. Two * things worth noting are that vi would often hang or drop core * if the map was strange enough (ex: map X "xy$@x^V), or, simply * not work. One trick worth remembering is that if you put a * mark at the start of the map, e.g. map X mx"xy ...), or if you * put the map in a .exrc file, things would often work much better. * No clue why. * * PUBLIC: int ex_map(SCR *, EXCMD *); */ int ex_map(SCR *sp, EXCMD *cmdp) { seq_t stype; CHAR_T *input, *p; stype = FL_ISSET(cmdp->iflags, E_C_FORCE) ? SEQ_INPUT : SEQ_COMMAND; switch (cmdp->argc) { case 0: if (seq_dump(sp, stype, 1) == 0) msgq(sp, M_INFO, stype == SEQ_INPUT ? "132|No input map entries" : "133|No command map entries"); return (0); case 2: input = cmdp->argv[0]->bp; break; default: abort(); } /* * If the mapped string is #[0-9]* (and wasn't quoted) then store the * function key mapping. If the screen specific routine has been set, * call it as well. Note, the SEQ_FUNCMAP type is persistent across * screen types, maybe the next screen type will get it right. */ if (input[0] == '#' && isdigit(input[1])) { for (p = input + 2; isdigit(*p); ++p); if (p[0] != '\0') goto nofunc; if (seq_set(sp, NULL, 0, input, cmdp->argv[0]->len, cmdp->argv[1]->bp, cmdp->argv[1]->len, stype, SEQ_FUNCMAP | SEQ_USERDEF)) return (1); return (sp->gp->scr_fmap == NULL ? 0 : sp->gp->scr_fmap(sp, stype, input, cmdp->argv[0]->len, cmdp->argv[1]->bp, cmdp->argv[1]->len)); } /* Some single keys may not be remapped in command mode. */ nofunc: if (stype == SEQ_COMMAND && input[1] == '\0') switch (KEY_VAL(sp, input[0])) { case K_COLON: case K_ESCAPE: case K_NL: msgq(sp, M_ERR, "134|The %s character may not be remapped", KEY_NAME(sp, input[0])); return (1); } return (seq_set(sp, NULL, 0, input, cmdp->argv[0]->len, cmdp->argv[1]->bp, cmdp->argv[1]->len, stype, SEQ_USERDEF)); } /* * ex_unmap -- (:unmap[!] key) * Unmap a key. * * PUBLIC: int ex_unmap(SCR *, EXCMD *); */ int ex_unmap(SCR *sp, EXCMD *cmdp) { if (seq_delete(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len, FL_ISSET(cmdp->iflags, E_C_FORCE) ? SEQ_INPUT : SEQ_COMMAND)) { msgq_wstr(sp, M_INFO, cmdp->argv[0]->bp, "135|\"%s\" isn't currently mapped"); return (1); } return (0); } Index: head/contrib/nvi/ex/ex_mark.c =================================================================== --- head/contrib/nvi/ex/ex_mark.c (revision 365498) +++ head/contrib/nvi/ex/ex_mark.c (revision 365499) @@ -1,44 +1,40 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_mark.c,v 10.9 2001/06/25 15:19:17 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" /* * ex_mark -- :mark char * :k char * Mark lines. * * * PUBLIC: int ex_mark(SCR *, EXCMD *); */ int ex_mark(SCR *sp, EXCMD *cmdp) { NEEDFILE(sp, cmdp); if (cmdp->argv[0]->len != 1) { msgq(sp, M_ERR, "136|Mark names must be a single character"); return (1); } return (mark_set(sp, cmdp->argv[0]->bp[0], &cmdp->addr1, 1)); } Index: head/contrib/nvi/ex/ex_mkexrc.c =================================================================== --- head/contrib/nvi/ex/ex_mkexrc.c (revision 365498) +++ head/contrib/nvi/ex/ex_mkexrc.c (revision 365499) @@ -1,101 +1,97 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_mkexrc.c,v 10.13 2001/06/25 15:19:17 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "pathnames.h" /* * ex_mkexrc -- :mkexrc[!] [file] * * Create (or overwrite) a .exrc file with the current info. * * PUBLIC: int ex_mkexrc(SCR *, EXCMD *); */ int ex_mkexrc(SCR *sp, EXCMD *cmdp) { struct stat sb; FILE *fp; int fd, sverrno; char *fname; size_t flen; switch (cmdp->argc) { case 0: fname = _PATH_EXRC; break; case 1: INT2CHAR(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len + 1, fname, flen); set_alt_name(sp, fname); break; default: abort(); } if (!FL_ISSET(cmdp->iflags, E_C_FORCE) && !stat(fname, &sb)) { msgq_str(sp, M_ERR, fname, "137|%s exists, not written; use ! to override"); return (1); } /* Create with max permissions of rw-r--r--. */ if ((fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) { msgq_str(sp, M_SYSERR, fname, "%s"); return (1); } if ((fp = fdopen(fd, "w")) == NULL) { sverrno = errno; (void)close(fd); goto e2; } if (seq_save(sp, fp, "abbreviate ", SEQ_ABBREV) || ferror(fp)) goto e1; if (seq_save(sp, fp, "map ", SEQ_COMMAND) || ferror(fp)) goto e1; if (seq_save(sp, fp, "map! ", SEQ_INPUT) || ferror(fp)) goto e1; if (opts_save(sp, fp) || ferror(fp)) goto e1; if (fclose(fp)) { sverrno = errno; goto e2; } msgq_str(sp, M_INFO, fname, "138|New exrc file: %s"); return (0); e1: sverrno = errno; (void)fclose(fp); e2: errno = sverrno; msgq_str(sp, M_SYSERR, fname, "%s"); return (1); } Index: head/contrib/nvi/ex/ex_move.c =================================================================== --- head/contrib/nvi/ex/ex_move.c (revision 365498) +++ head/contrib/nvi/ex/ex_move.c (revision 365499) @@ -1,192 +1,188 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_move.c,v 10.16 2012/02/11 15:52:33 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_copy -- :[line [,line]] co[py] line [flags] * Copy selected lines. * * PUBLIC: int ex_copy(SCR *, EXCMD *); */ int ex_copy(SCR *sp, EXCMD *cmdp) { CB cb = {{ 0 }}; MARK fm1, fm2, m, tm; recno_t cnt; int rval; rval = 0; NEEDFILE(sp, cmdp); /* * It's possible to copy things into the area that's being * copied, e.g. "2,5copy3" is legitimate. Save the text to * a cut buffer. */ fm1 = cmdp->addr1; fm2 = cmdp->addr2; TAILQ_INIT(cb.textq); for (cnt = fm1.lno; cnt <= fm2.lno; ++cnt) if (cut_line(sp, cnt, 0, ENTIRE_LINE, &cb)) { rval = 1; goto err; } cb.flags |= CB_LMODE; /* Put the text into place. */ tm.lno = cmdp->lineno; tm.cno = 0; if (put(sp, &cb, NULL, &tm, &m, 1)) rval = 1; else { /* * Copy puts the cursor on the last line copied. The cursor * returned by the put routine is the first line put, not the * last, because that's the historic semantic of vi. */ cnt = (fm2.lno - fm1.lno) + 1; sp->lno = m.lno + (cnt - 1); sp->cno = 0; } err: text_lfree(cb.textq); return (rval); } /* * ex_move -- :[line [,line]] mo[ve] line * Move selected lines. * * PUBLIC: int ex_move(SCR *, EXCMD *); */ int ex_move(SCR *sp, EXCMD *cmdp) { LMARK *lmp; MARK fm1, fm2; recno_t cnt, diff, fl, tl, mfl, mtl; size_t blen, len; int mark_reset; CHAR_T *bp; CHAR_T *p; NEEDFILE(sp, cmdp); /* * It's not possible to move things into the area that's being * moved. */ fm1 = cmdp->addr1; fm2 = cmdp->addr2; if (cmdp->lineno >= fm1.lno && cmdp->lineno <= fm2.lno) { msgq(sp, M_ERR, "139|Destination line is inside move range"); return (1); } /* * Log the positions of any marks in the to-be-deleted lines. This * has to work with the logging code. What happens is that we log * the old mark positions, make the changes, then log the new mark * positions. Then the marks end up in the right positions no matter * which way the log is traversed. * * XXX * Reset the MARK_USERSET flag so that the log can undo the mark. * This isn't very clean, and should probably be fixed. */ fl = fm1.lno; tl = cmdp->lineno; /* Log the old positions of the marks. */ mark_reset = 0; SLIST_FOREACH(lmp, sp->ep->marks, q) if (lmp->name != ABSMARK1 && lmp->lno >= fl && lmp->lno <= tl) { mark_reset = 1; F_CLR(lmp, MARK_USERSET); (void)log_mark(sp, lmp); } /* Get memory for the copy. */ GET_SPACE_RETW(sp, bp, blen, 256); /* Move the lines. */ diff = (fm2.lno - fm1.lno) + 1; if (tl > fl) { /* Destination > source. */ mfl = tl - diff; mtl = tl; for (cnt = diff; cnt--;) { if (db_get(sp, fl, DBG_FATAL, &p, &len)) return (1); BINC_RETW(sp, bp, blen, len); MEMCPY(bp, p, len); if (db_append(sp, 1, tl, bp, len)) return (1); if (mark_reset) SLIST_FOREACH(lmp, sp->ep->marks, q) if (lmp->name != ABSMARK1 && lmp->lno == fl) lmp->lno = tl + 1; if (db_delete(sp, fl)) return (1); } } else { /* Destination < source. */ mfl = tl; mtl = tl + diff; for (cnt = diff; cnt--;) { if (db_get(sp, fl, DBG_FATAL, &p, &len)) return (1); BINC_RETW(sp, bp, blen, len); MEMCPY(bp, p, len); if (db_append(sp, 1, tl++, bp, len)) return (1); if (mark_reset) SLIST_FOREACH(lmp, sp->ep->marks, q) if (lmp->name != ABSMARK1 && lmp->lno == fl) lmp->lno = tl; ++fl; if (db_delete(sp, fl)) return (1); } } FREE_SPACEW(sp, bp, blen); sp->lno = tl; /* Last line moved. */ sp->cno = 0; /* Log the new positions of the marks. */ if (mark_reset) SLIST_FOREACH(lmp, sp->ep->marks, q) if (lmp->name != ABSMARK1 && lmp->lno >= mfl && lmp->lno <= mtl) (void)log_mark(sp, lmp); sp->rptlines[L_MOVED] += diff; return (0); } Index: head/contrib/nvi/ex/ex_open.c =================================================================== --- head/contrib/nvi/ex/ex_open.c (revision 365498) +++ head/contrib/nvi/ex/ex_open.c (revision 365499) @@ -1,45 +1,41 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_open.c,v 10.8 2001/06/25 15:19:17 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" /* * ex_open -- :[line] o[pen] [/pattern/] [flags] * * Switch to single line "open" mode. * * PUBLIC: int ex_open(SCR *, EXCMD *); */ int ex_open(SCR *sp, EXCMD *cmdp) { /* If open option off, disallow open command. */ if (!O_ISSET(sp, O_OPEN)) { msgq(sp, M_ERR, "140|The open command requires that the open option be set"); return (1); } msgq(sp, M_ERR, "141|The open command is not yet implemented"); return (1); } Index: head/contrib/nvi/ex/ex_preserve.c =================================================================== --- head/contrib/nvi/ex/ex_preserve.c (revision 365498) +++ head/contrib/nvi/ex/ex_preserve.c (revision 365499) @@ -1,104 +1,100 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_preserve.c,v 10.15 2001/06/25 15:19:18 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_preserve -- :pre[serve] * Push the file to recovery. * * PUBLIC: int ex_preserve(SCR *, EXCMD *); */ int ex_preserve(SCR *sp, EXCMD *cmdp) { recno_t lno; NEEDFILE(sp, cmdp); if (!F_ISSET(sp->ep, F_RCV_ON)) { msgq(sp, M_ERR, "142|Preservation of this file not possible"); return (1); } /* If recovery not initialized, do so. */ if (F_ISSET(sp->ep, F_FIRSTMODIFY) && rcv_init(sp)) return (1); /* Force the file to be read in, in case it hasn't yet. */ if (db_last(sp, &lno)) return (1); /* Sync to disk. */ if (rcv_sync(sp, RCV_SNAPSHOT)) return (1); msgq(sp, M_INFO, "143|File preserved"); return (0); } /* * ex_recover -- :rec[over][!] file * Recover the file. * * PUBLIC: int ex_recover(SCR *, EXCMD *); */ int ex_recover(SCR *sp, EXCMD *cmdp) { ARGS *ap; FREF *frp; char *np; size_t nlen; ap = cmdp->argv[0]; /* Set the alternate file name. */ INT2CHAR(sp, ap->bp, ap->len+1, np, nlen); set_alt_name(sp, np); /* * Check for modifications. Autowrite did not historically * affect :recover. */ if (file_m2(sp, FL_ISSET(cmdp->iflags, E_C_FORCE))) return (1); /* Get a file structure for the file. */ INT2CHAR(sp, ap->bp, ap->len+1, np, nlen); if ((frp = file_add(sp, np)) == NULL) return (1); /* Set the recover bit. */ F_SET(frp, FR_RECOVER); /* Switch files. */ if (file_init(sp, frp, NULL, FS_SETALT | (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) return (1); F_SET(sp, SC_FSWITCH); return (0); } Index: head/contrib/nvi/ex/ex_print.c =================================================================== --- head/contrib/nvi/ex/ex_print.c (revision 365498) +++ head/contrib/nvi/ex/ex_print.c (revision 365499) @@ -1,331 +1,327 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_print.c,v 10.26 2013/11/02 02:11:07 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" static int ex_prchars(SCR *, const CHAR_T *, size_t *, size_t, u_int, int); /* * ex_list -- :[line [,line]] l[ist] [count] [flags] * * Display the addressed lines such that the output is unambiguous. * * PUBLIC: int ex_list(SCR *, EXCMD *); */ int ex_list(SCR *sp, EXCMD *cmdp) { if (ex_print(sp, cmdp, &cmdp->addr1, &cmdp->addr2, cmdp->iflags | E_C_LIST)) return (1); sp->lno = cmdp->addr2.lno; sp->cno = cmdp->addr2.cno; return (0); } /* * ex_number -- :[line [,line]] nu[mber] [count] [flags] * * Display the addressed lines with a leading line number. * * PUBLIC: int ex_number(SCR *, EXCMD *); */ int ex_number(SCR *sp, EXCMD *cmdp) { if (ex_print(sp, cmdp, &cmdp->addr1, &cmdp->addr2, cmdp->iflags | E_C_HASH)) return (1); sp->lno = cmdp->addr2.lno; sp->cno = cmdp->addr2.cno; return (0); } /* * ex_pr -- :[line [,line]] p[rint] [count] [flags] * * Display the addressed lines. * * PUBLIC: int ex_pr(SCR *, EXCMD *); */ int ex_pr(SCR *sp, EXCMD *cmdp) { if (ex_print(sp, cmdp, &cmdp->addr1, &cmdp->addr2, cmdp->iflags)) return (1); sp->lno = cmdp->addr2.lno; sp->cno = cmdp->addr2.cno; return (0); } /* * ex_print -- * Print the selected lines. * * PUBLIC: int ex_print(SCR *, EXCMD *, MARK *, MARK *, u_int32_t); */ int ex_print(SCR *sp, EXCMD *cmdp, MARK *fp, MARK *tp, u_int32_t flags) { GS *gp; recno_t from, to; size_t col, len; CHAR_T *p; CHAR_T buf[10]; NEEDFILE(sp, cmdp); gp = sp->gp; for (from = fp->lno, to = tp->lno; from <= to; ++from) { col = 0; /* * Display the line number. The %6 format is specified * by POSIX 1003.2, and is almost certainly large enough. * Check, though, just in case. */ if (LF_ISSET(E_C_HASH)) { if (from <= 999999) { SPRINTF(buf, SIZE(buf), L("%6u "), from); p = buf; } else p = L("TOOBIG "); if (ex_prchars(sp, p, &col, 8, 0, 0)) return (1); } /* * Display the line. The format for E_C_PRINT isn't very good, * especially in handling end-of-line tabs, but they're almost * backward compatible. */ if (db_get(sp, from, DBG_FATAL, &p, &len)) return (1); if (len == 0 && !LF_ISSET(E_C_LIST)) (void)ex_puts(sp, "\n"); else if (ex_ldisplay(sp, p, len, col, flags)) return (1); if (INTERRUPTED(sp)) break; } return (0); } /* * ex_ldisplay -- * Display a line without any preceding number. * * PUBLIC: int ex_ldisplay(SCR *, const CHAR_T *, size_t, size_t, u_int); */ int ex_ldisplay(SCR *sp, const CHAR_T *p, size_t len, size_t col, u_int flags) { if (len > 0 && ex_prchars(sp, p, &col, len, LF_ISSET(E_C_LIST), 0)) return (1); if (!INTERRUPTED(sp) && LF_ISSET(E_C_LIST)) { p = L("$"); if (ex_prchars(sp, p, &col, 1, LF_ISSET(E_C_LIST), 0)) return (1); } if (!INTERRUPTED(sp)) (void)ex_puts(sp, "\n"); return (0); } /* * ex_scprint -- * Display a line for the substitute with confirmation routine. * * PUBLIC: int ex_scprint(SCR *, MARK *, MARK *); */ int ex_scprint(SCR *sp, MARK *fp, MARK *tp) { CHAR_T *p; size_t col, len; col = 0; if (O_ISSET(sp, O_NUMBER)) { p = L(" "); if (ex_prchars(sp, p, &col, 8, 0, 0)) return (1); } if (db_get(sp, fp->lno, DBG_FATAL, &p, &len)) return (1); if (ex_prchars(sp, p, &col, fp->cno, 0, ' ')) return (1); p += fp->cno; if (ex_prchars(sp, p, &col, tp->cno == fp->cno ? 1 : tp->cno - fp->cno, 0, '^')) return (1); if (INTERRUPTED(sp)) return (1); p = L("[ynq]"); /* XXX: should be msg_cat. */ if (ex_prchars(sp, p, &col, 5, 0, 0)) return (1); (void)ex_fflush(sp); return (0); } /* * ex_prchars -- * Local routine to dump characters to the screen. */ static int ex_prchars(SCR *sp, const CHAR_T *p, size_t *colp, size_t len, u_int flags, int repeatc) { CHAR_T ch; char *kp; GS *gp; size_t col, tlen, ts; if (O_ISSET(sp, O_LIST)) LF_SET(E_C_LIST); gp = sp->gp; ts = O_VAL(sp, O_TABSTOP); for (col = *colp; len--;) - if ((ch = *p++) == L('\t') && !LF_ISSET(E_C_LIST)) + if ((ch = *p++) == '\t' && !LF_ISSET(E_C_LIST)) for (tlen = ts - col % ts; col < sp->cols && tlen--; ++col) { (void)ex_printf(sp, "%c", repeatc ? repeatc : ' '); if (INTERRUPTED(sp)) goto intr; } else { kp = KEY_NAME(sp, ch); tlen = KEY_COL(sp, ch); /* * Start a new line if the last character does not fit * into the current line. The implicit new lines are * not interruptible. */ if (col + tlen > sp->cols) { col = 0; (void)ex_puts(sp, "\n"); } col += tlen; if (!repeatc) { (void)ex_puts(sp, kp); if (INTERRUPTED(sp)) goto intr; } else while (tlen--) { (void)ex_printf(sp, "%c", repeatc); if (INTERRUPTED(sp)) goto intr; } if (col == sp->cols) { col = 0; (void)ex_puts(sp, "\n"); } } intr: *colp = col; return (0); } /* * ex_printf -- * Ex's version of printf. * * PUBLIC: int ex_printf(SCR *, const char *, ...); */ int ex_printf( SCR *sp, const char *fmt, ...) { EX_PRIVATE *exp; va_list ap; size_t n; exp = EXP(sp); va_start(ap, fmt); exp->obp_len += n = vsnprintf(exp->obp + exp->obp_len, sizeof(exp->obp) - exp->obp_len, fmt, ap); va_end(ap); /* Flush when reach a or half the buffer. */ if (exp->obp[exp->obp_len - 1] == '\n' || exp->obp_len > sizeof(exp->obp) / 2) (void)ex_fflush(sp); return (n); } /* * ex_puts -- * Ex's version of puts. * * PUBLIC: int ex_puts(SCR *, const char *); */ int ex_puts(SCR *sp, const char *str) { EX_PRIVATE *exp; int doflush, n; exp = EXP(sp); /* Flush when reach a or the end of the buffer. */ for (doflush = n = 0; *str != '\0'; ++n) { if (exp->obp_len > sizeof(exp->obp)) (void)ex_fflush(sp); if ((exp->obp[exp->obp_len++] = *str++) == '\n') doflush = 1; } if (doflush) (void)ex_fflush(sp); return (n); } /* * ex_fflush -- * Ex's version of fflush. * * PUBLIC: int ex_fflush(SCR *sp); */ int ex_fflush(SCR *sp) { EX_PRIVATE *exp; exp = EXP(sp); if (exp->obp_len != 0) { sp->gp->scr_msg(sp, M_NONE, exp->obp, exp->obp_len); exp->obp_len = 0; } return (0); } Index: head/contrib/nvi/ex/ex_put.c =================================================================== --- head/contrib/nvi/ex/ex_put.c (revision 365498) +++ head/contrib/nvi/ex/ex_put.c (revision 365499) @@ -1,50 +1,46 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_put.c,v 10.8 2001/06/25 15:19:18 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_put -- [line] pu[t] [buffer] * Append a cut buffer into the file. * * PUBLIC: int ex_put(SCR *, EXCMD *); */ int ex_put(SCR *sp, EXCMD *cmdp) { MARK m; NEEDFILE(sp, cmdp); m.lno = sp->lno; m.cno = sp->cno; if (put(sp, NULL, FL_ISSET(cmdp->iflags, E_C_BUFFER) ? &cmdp->buffer : NULL, &cmdp->addr1, &m, 1)) return (1); sp->lno = m.lno; sp->cno = m.cno; return (0); } Index: head/contrib/nvi/ex/ex_quit.c =================================================================== --- head/contrib/nvi/ex/ex_quit.c (revision 365498) +++ head/contrib/nvi/ex/ex_quit.c (revision 365499) @@ -1,45 +1,41 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_quit.c,v 10.8 2001/06/25 15:19:18 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" /* * ex_quit -- :quit[!] * Quit. * * PUBLIC: int ex_quit(SCR *, EXCMD *); */ int ex_quit(SCR *sp, EXCMD *cmdp) { int force; force = FL_ISSET(cmdp->iflags, E_C_FORCE); /* Check for file modifications, or more files to edit. */ if (file_m2(sp, force) || ex_ncheck(sp, force)) return (1); F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT); return (0); } Index: head/contrib/nvi/ex/ex_read.c =================================================================== --- head/contrib/nvi/ex/ex_read.c (revision 365498) +++ head/contrib/nvi/ex/ex_read.c (revision 365499) @@ -1,361 +1,356 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_read.c,v 10.44 2001/06/25 15:19:19 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" /* * ex_read -- :read [file] * :read [!cmd] * Read from a file or utility. * * !!! * Historical vi wouldn't undo a filter read, for no apparent reason. * * PUBLIC: int ex_read(SCR *, EXCMD *); */ int ex_read(SCR *sp, EXCMD *cmdp) { enum { R_ARG, R_EXPANDARG, R_FILTER } which; struct stat sb; CHAR_T *arg = NULL; char *name = NULL; size_t nlen; EX_PRIVATE *exp; FILE *fp; FREF *frp; GS *gp; MARK rm; recno_t nlines; size_t arglen = 0; int argc, rval; char *p; gp = sp->gp; /* * 0 args: read the current pathname. * 1 args: check for "read !arg". */ switch (cmdp->argc) { case 0: which = R_ARG; break; case 1: arg = cmdp->argv[0]->bp; arglen = cmdp->argv[0]->len; if (*arg == '!') { ++arg; --arglen; which = R_FILTER; /* Secure means no shell access. */ if (O_ISSET(sp, O_SECURE)) { ex_wemsg(sp, cmdp->cmd->name, EXM_SECURE_F); return (1); } } else which = R_EXPANDARG; break; default: abort(); /* NOTREACHED */ } /* Load a temporary file if no file being edited. */ if (sp->ep == NULL) { if ((frp = file_add(sp, NULL)) == NULL) return (1); if (file_init(sp, frp, NULL, 0)) return (1); } switch (which) { case R_FILTER: /* * File name and bang expand the user's argument. If * we don't get an additional argument, it's illegal. */ argc = cmdp->argc; if (argv_exp1(sp, cmdp, arg, arglen, 1)) return (1); if (argc == cmdp->argc) { ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } argc = cmdp->argc - 1; /* Set the last bang command. */ exp = EXP(sp); - if (exp->lastbcomm != NULL) - free(exp->lastbcomm); + free(exp->lastbcomm); if ((exp->lastbcomm = v_wstrdup(sp, cmdp->argv[argc]->bp, cmdp->argv[argc]->len)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* * Vi redisplayed the user's argument if it changed, ex * always displayed a !, plus the user's argument if it * changed. */ if (F_ISSET(sp, SC_VI)) { if (F_ISSET(cmdp, E_MODIFY)) (void)vs_update(sp, "!", cmdp->argv[argc]->bp); } else { if (F_ISSET(cmdp, E_MODIFY)) (void)ex_printf(sp, "!"WS"\n", cmdp->argv[argc]->bp); else (void)ex_puts(sp, "!\n"); (void)ex_fflush(sp); } /* * Historically, filter reads as the first ex command didn't * wait for the user. If SC_SCR_EXWROTE not already set, set * the don't-wait flag. */ if (!F_ISSET(sp, SC_SCR_EXWROTE)) F_SET(sp, SC_EX_WAIT_NO); /* * Switch into ex canonical mode. The reason to restore the * original terminal modes for read filters is so that users * can do things like ":r! cat /dev/tty". * * !!! * We do not output an extra , so that we don't touch * the screen on a normal read. */ if (F_ISSET(sp, SC_VI)) { if (gp->scr_screen(sp, SC_EX)) { ex_wemsg(sp, cmdp->cmd->name, EXM_NOCANON_F); return (1); } /* * !!! * Historically, the read command doesn't switch to * the alternate X11 xterm screen, if doing a filter * read -- don't set SA_ALTERNATE. */ F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE); } if (ex_filter(sp, cmdp, &cmdp->addr1, NULL, &rm, cmdp->argv[argc]->bp, FILTER_READ)) return (1); /* The filter version of read set the autoprint flag. */ F_SET(cmdp, E_AUTOPRINT); /* * If in vi mode, move to the first nonblank. Might have * switched into ex mode, so saved the original SC_VI value. */ sp->lno = rm.lno; if (F_ISSET(sp, SC_VI)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } return (0); case R_ARG: name = sp->frp->name; break; case R_EXPANDARG: if (argv_exp2(sp, cmdp, arg, arglen)) return (1); /* * 0 args: impossible. * 1 args: impossible (I hope). * 2 args: read it. * >2 args: object, too many args. * * The 1 args case depends on the argv_sexp() function refusing * to return success without at least one non-blank character. */ switch (cmdp->argc) { case 0: case 1: abort(); /* NOTREACHED */ case 2: INT2CHAR(sp, cmdp->argv[1]->bp, cmdp->argv[1]->len + 1, name, nlen); /* * !!! * Historically, the read and write commands renamed * "unnamed" files, or, if the file had a name, set * the alternate file name. */ if (F_ISSET(sp->frp, FR_TMPFILE) && !F_ISSET(sp->frp, FR_EXNAMED)) { if ((p = strdup(name)) != NULL) { free(sp->frp->name); sp->frp->name = p; } /* * The file has a real name, it's no longer a * temporary, clear the temporary file flags. */ F_CLR(sp->frp, FR_TMPEXIT | FR_TMPFILE); F_SET(sp->frp, FR_NAMECHANGE | FR_EXNAMED); /* Notify the screen. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); name = sp->frp->name; } else { set_alt_name(sp, name); name = sp->alt_name; } break; default: ex_wemsg(sp, cmdp->argv[0]->bp, EXM_FILECOUNT); return (1); } break; } /* * !!! * Historically, vi did not permit reads from non-regular files, nor * did it distinguish between "read !" and "read!", so there was no * way to "force" it. We permit reading from named pipes too, since * they didn't exist when the original implementation of vi was done * and they seem a reasonable addition. */ if ((fp = fopen(name, "r")) == NULL || fstat(fileno(fp), &sb)) { msgq_str(sp, M_SYSERR, name, "%s"); return (1); } if (!S_ISFIFO(sb.st_mode) && !S_ISREG(sb.st_mode)) { (void)fclose(fp); msgq(sp, M_ERR, "145|Only regular files and named pipes may be read"); return (1); } /* Try and get a lock. */ if (file_lock(sp, NULL, fileno(fp), 0) == LOCK_UNAVAIL) msgq(sp, M_ERR, "146|%s: read lock was unavailable", name); rval = ex_readfp(sp, name, fp, &cmdp->addr1, &nlines, 0); /* * In vi, set the cursor to the first line read in, if anything read * in, otherwise, the address. (Historic vi set it to the line after * the address regardless, but since that line may not exist we don't * bother.) * * In ex, set the cursor to the last line read in, if anything read in, * otherwise, the address. */ if (F_ISSET(sp, SC_VI)) { sp->lno = cmdp->addr1.lno; if (nlines) ++sp->lno; } else sp->lno = cmdp->addr1.lno + nlines; return (rval); } /* * ex_readfp -- * Read lines into the file. * * PUBLIC: int ex_readfp(SCR *, char *, FILE *, MARK *, recno_t *, int); */ int ex_readfp(SCR *sp, char *name, FILE *fp, MARK *fm, recno_t *nlinesp, int silent) { EX_PRIVATE *exp; GS *gp; recno_t lcnt, lno; size_t len; u_long ccnt; /* XXX: can't print off_t portably. */ int nf, rval; char *p; size_t wlen; CHAR_T *wp; gp = sp->gp; exp = EXP(sp); /* * Add in the lines from the output. Insertion starts at the line * following the address. */ ccnt = 0; lcnt = 0; p = "147|Reading..."; for (lno = fm->lno; !ex_getline(sp, fp, &len); ++lno, ++lcnt) { if ((lcnt + 1) % INTERRUPT_CHECK == 0) { if (INTERRUPTED(sp)) break; if (!silent) { gp->scr_busy(sp, p, p == NULL ? BUSY_UPDATE : BUSY_ON); p = NULL; } } FILE2INT5(sp, exp->ibcw, exp->ibp, len, wp, wlen); if (db_append(sp, 1, lno, wp, wlen)) goto err; ccnt += len; } if (ferror(fp) || fclose(fp)) goto err; /* Return the number of lines read in. */ if (nlinesp != NULL) *nlinesp = lcnt; if (!silent) { p = msg_print(sp, name, &nf); msgq(sp, M_INFO, "148|%s: %lu lines, %lu characters", p, (u_long)lcnt, ccnt); if (nf) FREE_SPACE(sp, p, 0); } rval = 0; if (0) { err: msgq_str(sp, M_SYSERR, name, "%s"); (void)fclose(fp); rval = 1; } if (!silent) gp->scr_busy(sp, NULL, BUSY_OFF); return (rval); } Index: head/contrib/nvi/ex/ex_screen.c =================================================================== --- head/contrib/nvi/ex/ex_screen.c (revision 365498) +++ head/contrib/nvi/ex/ex_screen.c (revision 365499) @@ -1,131 +1,127 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_screen.c,v 10.12 2001/06/25 15:19:19 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" /* * ex_bg -- :bg * Hide the screen. * * PUBLIC: int ex_bg(SCR *, EXCMD *); */ int ex_bg(SCR *sp, EXCMD *cmdp) { return (vs_bg(sp)); } /* * ex_fg -- :fg [file] * Show the screen. * * PUBLIC: int ex_fg(SCR *, EXCMD *); */ int ex_fg(SCR *sp, EXCMD *cmdp) { SCR *nsp; int newscreen; newscreen = F_ISSET(cmdp, E_NEWSCREEN); if (vs_fg(sp, &nsp, cmdp->argc ? cmdp->argv[0]->bp : NULL, newscreen)) return (1); /* Set up the switch. */ if (newscreen) { sp->nextdisp = nsp; F_SET(sp, SC_SSWITCH); } return (0); } /* * ex_resize -- :resize [+-]rows * Change the screen size. * * PUBLIC: int ex_resize(SCR *, EXCMD *); */ int ex_resize(SCR *sp, EXCMD *cmdp) { adj_t adj; switch (FL_ISSET(cmdp->iflags, E_C_COUNT | E_C_COUNT_NEG | E_C_COUNT_POS)) { case E_C_COUNT: adj = A_SET; break; case E_C_COUNT | E_C_COUNT_NEG: adj = A_DECREASE; break; case E_C_COUNT | E_C_COUNT_POS: adj = A_INCREASE; break; default: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } return (vs_resize(sp, cmdp->count, adj)); } /* * ex_sdisplay -- * Display the list of screens. * * PUBLIC: int ex_sdisplay(SCR *); */ int ex_sdisplay(SCR *sp) { GS *gp; SCR *tsp; int cnt, col, len, sep; gp = sp->gp; if ((tsp = TAILQ_FIRST(gp->hq)) == NULL) { msgq(sp, M_INFO, "149|No background screens to display"); return (0); } col = len = sep = 0; for (cnt = 1; tsp != NULL && !INTERRUPTED(sp); tsp = TAILQ_NEXT(tsp, q)) { col += len = strlen(tsp->frp->name) + sep; if (col >= sp->cols - 1) { col = len; sep = 0; (void)ex_puts(sp, "\n"); } else if (cnt != 1) { sep = 1; (void)ex_puts(sp, " "); } (void)ex_puts(sp, tsp->frp->name); ++cnt; } if (!INTERRUPTED(sp)) (void)ex_puts(sp, "\n"); return (0); } Index: head/contrib/nvi/ex/ex_script.c =================================================================== --- head/contrib/nvi/ex/ex_script.c (revision 365498) +++ head/contrib/nvi/ex/ex_script.c (revision 365499) @@ -1,627 +1,622 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * This code is derived from software contributed to Berkeley by * Brian Hirt. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_script.c,v 10.44 2012/10/05 10:17:47 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBUTIL_H #include #else #include #endif #include "../common/common.h" #include "../vi/vi.h" #include "script.h" #include "pathnames.h" static void sscr_check(SCR *); static int sscr_getprompt(SCR *); static int sscr_init(SCR *); static int sscr_insert(SCR *); static int sscr_matchprompt(SCR *, char *, size_t, size_t *); static int sscr_setprompt(SCR *, char *, size_t); /* * ex_script -- : sc[ript][!] [file] * Switch to script mode. * * PUBLIC: int ex_script(SCR *, EXCMD *); */ int ex_script(SCR *sp, EXCMD *cmdp) { /* Vi only command. */ if (!F_ISSET(sp, SC_VI)) { msgq(sp, M_ERR, "150|The script command is only available in vi mode"); return (1); } /* Switch to the new file. */ if (cmdp->argc != 0 && ex_edit(sp, cmdp)) return (1); /* Create the shell, figure out the prompt. */ if (sscr_init(sp)) return (1); return (0); } /* * sscr_init -- * Create a pty setup for a shell. */ static int sscr_init(SCR *sp) { SCRIPT *sc; char *sh, *sh_path; /* We're going to need a shell. */ if (opts_empty(sp, O_SHELL, 0)) return (1); - MALLOC_RET(sp, sc, SCRIPT *, sizeof(SCRIPT)); + MALLOC_RET(sp, sc, sizeof(SCRIPT)); sp->script = sc; sc->sh_prompt = NULL; sc->sh_prompt_len = 0; /* * There are two different processes running through this code. * They are the shell and the parent. */ sc->sh_master = sc->sh_slave = -1; if (tcgetattr(STDIN_FILENO, &sc->sh_term) == -1) { msgq(sp, M_SYSERR, "tcgetattr"); goto err; } /* * Turn off output postprocessing and echo. */ sc->sh_term.c_oflag &= ~OPOST; sc->sh_term.c_cflag &= ~(ECHO|ECHOE|ECHONL|ECHOK); if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) { msgq(sp, M_SYSERR, "tcgetattr"); goto err; } if (openpty(&sc->sh_master, &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) { msgq(sp, M_SYSERR, "openpty"); goto err; } /* * __TK__ huh? * Don't use vfork() here, because the signal semantics differ from * implementation to implementation. */ switch (sc->sh_pid = fork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "fork"); err: if (sc->sh_master != -1) (void)close(sc->sh_master); if (sc->sh_slave != -1) (void)close(sc->sh_slave); return (1); case 0: /* Utility. */ /* * XXX * So that shells that do command line editing turn it off. */ (void)setenv("TERM", "emacs", 1); (void)setenv("TERMCAP", "emacs:", 1); (void)setenv("EMACS", "t", 1); (void)setsid(); #ifdef TIOCSCTTY /* * 4.4BSD allocates a controlling terminal using the TIOCSCTTY * ioctl, not by opening a terminal device file. POSIX 1003.1 * doesn't define a portable way to do this. If TIOCSCTTY is * not available, hope that the open does it. */ (void)ioctl(sc->sh_slave, TIOCSCTTY, 0); #endif (void)close(sc->sh_master); (void)dup2(sc->sh_slave, STDIN_FILENO); (void)dup2(sc->sh_slave, STDOUT_FILENO); (void)dup2(sc->sh_slave, STDERR_FILENO); (void)close(sc->sh_slave); /* Assumes that all shells have -i. */ sh_path = O_STR(sp, O_SHELL); if ((sh = strrchr(sh_path, '/')) == NULL) sh = sh_path; else ++sh; execl(sh_path, sh, "-i", NULL); msgq_str(sp, M_SYSERR, sh_path, "execl: %s"); _exit(127); default: /* Parent. */ break; } if (sscr_getprompt(sp)) return (1); F_SET(sp, SC_SCRIPT); F_SET(sp->gp, G_SCRWIN); return (0); } /* * sscr_getprompt -- * Eat lines printed by the shell until a line with no trailing * carriage return comes; set the prompt from that line. */ static int sscr_getprompt(SCR *sp) { EX_PRIVATE *exp; struct timeval tv; char *endp, *p, *t, buf[1024]; SCRIPT *sc; fd_set fdset; recno_t lline; size_t llen, len; int nr; CHAR_T *wp; size_t wlen; exp = EXP(sp); FD_ZERO(&fdset); endp = buf; len = sizeof(buf); /* Wait up to a second for characters to read. */ tv.tv_sec = 5; tv.tv_usec = 0; sc = sp->script; FD_SET(sc->sh_master, &fdset); switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) { case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "select"); goto prompterr; case 0: /* Timeout */ msgq(sp, M_ERR, "Error: timed out"); goto prompterr; case 1: /* Characters to read. */ break; } /* Read the characters. */ more: len = sizeof(buf) - (endp - buf); switch (nr = read(sc->sh_master, endp, len)) { case 0: /* EOF. */ msgq(sp, M_ERR, "Error: shell: EOF"); goto prompterr; case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "shell"); goto prompterr; default: endp += nr; break; } /* If any complete lines, push them into the file. */ for (p = t = buf; p < endp; ++p) { if (*p == '\r' || *p == '\n') { if (CHAR2INT5(sp, exp->ibcw, t, p - t, wp, wlen)) goto conv_err; if (db_last(sp, &lline) || db_append(sp, 0, lline, wp, wlen)) goto prompterr; t = p + 1; } } if (p > buf) { memmove(buf, t, endp - t); endp = buf + (endp - t); } if (endp == buf) goto more; /* Wait up 1/10 of a second to make sure that we got it all. */ tv.tv_sec = 0; tv.tv_usec = 100000; switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) { case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "select"); goto prompterr; case 0: /* Timeout */ break; case 1: /* Characters to read. */ goto more; } /* Timed out, so theoretically we have a prompt. */ llen = endp - buf; endp = buf; /* Append the line into the file. */ if (CHAR2INT5(sp, exp->ibcw, buf, llen, wp, wlen)) goto conv_err; if (db_last(sp, &lline) || db_append(sp, 0, lline, wp, wlen)) { if (0) conv_err: msgq(sp, M_ERR, "323|Invalid input. Truncated."); prompterr: sscr_end(sp); return (1); } return (sscr_setprompt(sp, buf, llen)); } /* * sscr_exec -- * Take a line and hand it off to the shell. * * PUBLIC: int sscr_exec(SCR *, recno_t); */ int sscr_exec(SCR *sp, recno_t lno) { SCRIPT *sc; recno_t last_lno; size_t blen, len, last_len, tlen; int isempty, matchprompt, nw, rval; char *bp = NULL, *p; CHAR_T *wp; size_t wlen; /* If there's a prompt on the last line, append the command. */ if (db_last(sp, &last_lno)) return (1); if (db_get(sp, last_lno, DBG_FATAL, &wp, &wlen)) return (1); INT2CHAR(sp, wp, wlen, p, last_len); if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) { matchprompt = 1; GET_SPACE_RETC(sp, bp, blen, last_len + 128); memmove(bp, p, last_len); } else matchprompt = 0; /* Get something to execute. */ if (db_eget(sp, lno, &wp, &wlen, &isempty)) { if (isempty) goto empty; goto err1; } /* Empty lines aren't interesting. */ if (wlen == 0) goto empty; INT2CHAR(sp, wp, wlen, p, len); /* Delete any prompt. */ if (sscr_matchprompt(sp, p, len, &tlen)) { if (tlen == len) { empty: msgq(sp, M_BERR, "151|No command to execute"); goto err1; } p += (len - tlen); len = tlen; } /* Push the line to the shell. */ sc = sp->script; if ((nw = write(sc->sh_master, p, len)) != len) goto err2; rval = 0; if (write(sc->sh_master, "\n", 1) != 1) { err2: if (nw == 0) errno = EIO; msgq(sp, M_SYSERR, "shell"); goto err1; } if (matchprompt) { ADD_SPACE_RETC(sp, bp, blen, last_len + len); memmove(bp + last_len, p, len); CHAR2INT(sp, bp, last_len + len, wp, wlen); if (db_set(sp, last_lno, wp, wlen)) err1: rval = 1; } if (matchprompt) FREE_SPACE(sp, bp, blen); return (rval); } /* * sscr_input -- * Read any waiting shell input. * * PUBLIC: int sscr_input(SCR *); */ int sscr_input(SCR *sp) { GS *gp; struct timeval poll; fd_set rdfd; int maxfd; gp = sp->gp; loop: maxfd = 0; FD_ZERO(&rdfd); poll.tv_sec = 0; poll.tv_usec = 0; /* Set up the input mask. */ TAILQ_FOREACH(sp, gp->dq, q) if (F_ISSET(sp, SC_SCRIPT)) { FD_SET(sp->script->sh_master, &rdfd); if (sp->script->sh_master > maxfd) maxfd = sp->script->sh_master; } /* Check for input. */ switch (select(maxfd + 1, &rdfd, NULL, NULL, &poll)) { case -1: msgq(sp, M_SYSERR, "select"); return (1); case 0: return (0); default: break; } /* Read the input. */ TAILQ_FOREACH(sp, gp->dq, q) if (F_ISSET(sp, SC_SCRIPT) && FD_ISSET(sp->script->sh_master, &rdfd) && sscr_insert(sp)) return (1); goto loop; } /* * sscr_insert -- * Take a line from the shell and insert it into the file. */ static int sscr_insert(SCR *sp) { EX_PRIVATE *exp; struct timeval tv; char *endp, *p, *t; SCRIPT *sc; fd_set rdfd; recno_t lno; size_t blen, len, tlen; int nr, rval; char *bp; CHAR_T *wp; size_t wlen = 0; exp = EXP(sp); /* Find out where the end of the file is. */ if (db_last(sp, &lno)) return (1); #define MINREAD 1024 GET_SPACE_RETC(sp, bp, blen, MINREAD); endp = bp; /* Read the characters. */ rval = 1; sc = sp->script; more: switch (nr = read(sc->sh_master, endp, MINREAD)) { case 0: /* EOF; shell just exited. */ sscr_end(sp); rval = 0; goto ret; case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "shell"); goto ret; default: endp += nr; break; } /* Append the lines into the file. */ for (p = t = bp; p < endp; ++p) { if (*p == '\r' || *p == '\n') { len = p - t; if (CHAR2INT5(sp, exp->ibcw, t, len, wp, wlen)) goto conv_err; if (db_append(sp, 1, lno++, wp, wlen)) goto ret; t = p + 1; } } if (p > t) { len = p - t; /* * If the last thing from the shell isn't another prompt, wait * up to 1/10 of a second for more stuff to show up, so that * we don't break the output into two separate lines. Don't * want to hang indefinitely because some program is hanging, * confused the shell, or whatever. */ if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) { tv.tv_sec = 0; tv.tv_usec = 100000; FD_ZERO(&rdfd); FD_SET(sc->sh_master, &rdfd); if (select(sc->sh_master + 1, &rdfd, NULL, NULL, &tv) == 1) { memmove(bp, t, len); endp = bp + len; goto more; } } if (sscr_setprompt(sp, t, len)) return (1); if (CHAR2INT5(sp, exp->ibcw, t, len, wp, wlen)) goto conv_err; if (db_append(sp, 1, lno++, wp, wlen)) goto ret; } /* The cursor moves to EOF. */ sp->lno = lno; sp->cno = wlen ? wlen - 1 : 0; rval = vs_refresh(sp, 1); if (0) conv_err: msgq(sp, M_ERR, "323|Invalid input. Truncated."); ret: FREE_SPACE(sp, bp, blen); return (rval); } /* * sscr_setprompt -- * * Set the prompt to the last line we got from the shell. * */ static int sscr_setprompt(SCR *sp, char *buf, size_t len) { SCRIPT *sc; sc = sp->script; - if (sc->sh_prompt) - free(sc->sh_prompt); - MALLOC(sp, sc->sh_prompt, char *, len + 1); + free(sc->sh_prompt); + MALLOC(sp, sc->sh_prompt, len + 1); if (sc->sh_prompt == NULL) { sscr_end(sp); return (1); } memmove(sc->sh_prompt, buf, len); sc->sh_prompt_len = len; sc->sh_prompt[len] = '\0'; return (0); } /* * sscr_matchprompt -- * Check to see if a line matches the prompt. Nul's indicate * parts that can change, in both content and size. */ static int sscr_matchprompt(SCR *sp, char *lp, size_t line_len, size_t *lenp) { SCRIPT *sc; size_t prompt_len; char *pp; sc = sp->script; if (line_len < (prompt_len = sc->sh_prompt_len)) return (0); for (pp = sc->sh_prompt; prompt_len && line_len; --prompt_len, --line_len) { if (*pp == '\0') { for (; prompt_len && *pp == '\0'; --prompt_len, ++pp); if (!prompt_len) return (0); for (; line_len && *lp != *pp; --line_len, ++lp); if (!line_len) return (0); } if (*pp++ != *lp++) break; } if (prompt_len) return (0); if (lenp != NULL) *lenp = line_len; return (1); } /* * sscr_end -- * End the pipe to a shell. * * PUBLIC: int sscr_end(SCR *); */ int sscr_end(SCR *sp) { SCRIPT *sc; if ((sc = sp->script) == NULL) return (0); /* Turn off the script flags. */ F_CLR(sp, SC_SCRIPT); sscr_check(sp); /* Close down the parent's file descriptors. */ if (sc->sh_master != -1) (void)close(sc->sh_master); if (sc->sh_slave != -1) (void)close(sc->sh_slave); /* This should have killed the child. */ (void)proc_wait(sp, (long)sc->sh_pid, "script-shell", 0, 0); /* Free memory. */ free(sc->sh_prompt); free(sc); sp->script = NULL; return (0); } /* * sscr_check -- * Set/clear the global scripting bit. */ static void sscr_check(SCR *sp) { GS *gp; gp = sp->gp; TAILQ_FOREACH(sp, gp->dq, q) if (F_ISSET(sp, SC_SCRIPT)) { F_SET(gp, G_SCRWIN); return; } F_CLR(gp, G_SCRWIN); } Index: head/contrib/nvi/ex/ex_set.c =================================================================== --- head/contrib/nvi/ex/ex_set.c (revision 365498) +++ head/contrib/nvi/ex/ex_set.c (revision 365499) @@ -1,45 +1,41 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_set.c,v 10.8 2001/06/25 15:19:19 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" /* * ex_set -- :set * Ex set option. * * PUBLIC: int ex_set(SCR *, EXCMD *); */ int ex_set(SCR *sp, EXCMD *cmdp) { switch(cmdp->argc) { case 0: opts_dump(sp, CHANGED_DISPLAY); break; default: if (opts_set(sp, cmdp->argv, cmdp->cmd->usage)) return (1); break; } return (0); } Index: head/contrib/nvi/ex/ex_shell.c =================================================================== --- head/contrib/nvi/ex/ex_shell.c (revision 365498) +++ head/contrib/nvi/ex/ex_shell.c (revision 365499) @@ -1,227 +1,223 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_shell.c,v 10.44 2012/07/06 06:51:26 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" static const char *sigmsg(int); /* * ex_shell -- :sh[ell] * Invoke the program named in the SHELL environment variable * with the argument -i. * * PUBLIC: int ex_shell(SCR *, EXCMD *); */ int ex_shell(SCR *sp, EXCMD *cmdp) { int rval; char *buf; /* We'll need a shell. */ if (opts_empty(sp, O_SHELL, 0)) return (1); /* * XXX * Assumes all shells use -i. */ (void)asprintf(&buf, "%s -i", O_STR(sp, O_SHELL)); if (buf == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* Restore the window name. */ (void)sp->gp->scr_rename(sp, NULL, 0); /* If we're still in a vi screen, move out explicitly. */ rval = ex_exec_proc(sp, cmdp, buf, NULL, !F_ISSET(sp, SC_SCR_EXWROTE)); free(buf); /* Set the window name. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); /* * !!! * Historically, vi didn't require a continue message after the * return of the shell. Match it. */ F_SET(sp, SC_EX_WAIT_NO); return (rval); } /* * ex_exec_proc -- * Run a separate process. * * PUBLIC: int ex_exec_proc(SCR *, EXCMD *, char *, const char *, int); */ int ex_exec_proc(SCR *sp, EXCMD *cmdp, char *cmd, const char *msg, int need_newline) { GS *gp; const char *name; pid_t pid; gp = sp->gp; /* We'll need a shell. */ if (opts_empty(sp, O_SHELL, 0)) return (1); /* Enter ex mode. */ if (F_ISSET(sp, SC_VI)) { if (gp->scr_screen(sp, SC_EX)) { ex_wemsg(sp, cmdp->cmd->name, EXM_NOCANON); return (1); } (void)gp->scr_attr(sp, SA_ALTERNATE, 0); F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE); } /* Put out additional newline, message. */ if (need_newline) (void)ex_puts(sp, "\n"); if (msg != NULL) { (void)ex_puts(sp, msg); (void)ex_puts(sp, "\n"); } (void)ex_fflush(sp); switch (pid = vfork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "vfork"); return (1); case 0: /* Utility. */ if (gp->scr_child) gp->scr_child(sp); if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL) name = O_STR(sp, O_SHELL); else ++name; execl(O_STR(sp, O_SHELL), name, "-c", cmd, (char *)NULL); msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s"); _exit(127); /* NOTREACHED */ default: /* Parent. */ return (proc_wait(sp, (long)pid, cmd, 0, 0)); } /* NOTREACHED */ } /* * proc_wait -- * Wait for one of the processes. * * !!! * The pid_t type varies in size from a short to a long depending on the * system. It has to be cast into something or the standard promotion * rules get you. I'm using a long based on the belief that nobody is * going to make it unsigned and it's unlikely to be a quad. * * PUBLIC: int proc_wait(SCR *, long, const char *, int, int); */ int proc_wait(SCR *sp, long int pid, const char *cmd, int silent, int okpipe) { size_t len; int nf, pstat; char *p; /* Wait for the utility, ignoring interruptions. */ for (;;) { errno = 0; if (waitpid((pid_t)pid, &pstat, 0) != -1) break; if (errno != EINTR) { msgq(sp, M_SYSERR, "waitpid"); return (1); } } /* * Display the utility's exit status. Ignore SIGPIPE from the * parent-writer, as that only means that the utility chose to * exit before reading all of its input. */ if (WIFSIGNALED(pstat) && (!okpipe || WTERMSIG(pstat) != SIGPIPE)) { for (; cmdskip(*cmd); ++cmd); p = msg_print(sp, cmd, &nf); len = strlen(p); msgq(sp, M_ERR, "%.*s%s: received signal: %s%s", (int)MIN(len, 20), p, len > 20 ? " ..." : "", sigmsg(WTERMSIG(pstat)), WCOREDUMP(pstat) ? "; core dumped" : ""); if (nf) FREE_SPACE(sp, p, 0); return (1); } if (WIFEXITED(pstat) && WEXITSTATUS(pstat)) { /* * Remain silent for "normal" errors when doing shell file * name expansions, they almost certainly indicate nothing * more than a failure to match. * * Remain silent for vi read filter errors. It's historic * practice. */ if (!silent) { for (; cmdskip(*cmd); ++cmd); p = msg_print(sp, cmd, &nf); len = strlen(p); msgq(sp, M_ERR, "%.*s%s: exited with status %d", (int)MIN(len, 20), p, len > 20 ? " ..." : "", WEXITSTATUS(pstat)); if (nf) FREE_SPACE(sp, p, 0); } return (1); } return (0); } /* * sigmsg -- * Return a pointer to a message describing a signal. */ static const char * sigmsg(int signo) { static char buf[40]; char *message; /* POSIX.1-2008 leaves strsignal(3)'s return value unspecified. */ if ((message = strsignal(signo)) != NULL) return message; (void)snprintf(buf, sizeof(buf), "Unknown signal: %d", signo); return (buf); } Index: head/contrib/nvi/ex/ex_shift.c =================================================================== --- head/contrib/nvi/ex/ex_shift.c (revision 365498) +++ head/contrib/nvi/ex/ex_shift.c (revision 365499) @@ -1,186 +1,199 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_shift.c,v 10.17 2001/06/25 15:19:20 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include "../common/common.h" -enum which {LEFT, RIGHT}; +enum which {RETAB, LEFT, RIGHT}; static int shift(SCR *, EXCMD *, enum which); /* * ex_shiftl -- :<[<...] * * * PUBLIC: int ex_shiftl(SCR *, EXCMD *); */ int ex_shiftl(SCR *sp, EXCMD *cmdp) { return (shift(sp, cmdp, LEFT)); } /* * ex_shiftr -- :>[>...] * * PUBLIC: int ex_shiftr(SCR *, EXCMD *); */ int ex_shiftr(SCR *sp, EXCMD *cmdp) { return (shift(sp, cmdp, RIGHT)); } /* + * ex_retab -- Expand tabs (if enabled) + * + * + * PUBLIC: int ex_retab(SCR *, EXCMD *); + */ +int +ex_retab(SCR *sp, EXCMD *cmdp) +{ + return (shift(sp, cmdp, RETAB)); +} + +/* * shift -- * Ex shift support. */ static int shift(SCR *sp, EXCMD *cmdp, enum which rl) { recno_t from, to; size_t blen, len, newcol, newidx, oldcol, oldidx, sw; int curset; CHAR_T *p; CHAR_T *bp, *tbp; NEEDFILE(sp, cmdp); if (O_VAL(sp, O_SHIFTWIDTH) == 0) { msgq(sp, M_INFO, "152|shiftwidth option set to 0"); return (0); } /* Copy the lines being shifted into the unnamed buffer. */ if (cut(sp, NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE)) return (1); /* * The historic version of vi permitted the user to string any number * of '>' or '<' characters together, resulting in an indent of the * appropriate levels. There's a special hack in ex_cmd() so that * cmdp->argv[0] points to the string of '>' or '<' characters. * * Q: What's the difference between the people adding features * to vi and the Girl Scouts? * A: The Girl Scouts have mint cookies and adult supervision. */ for (p = cmdp->argv[0]->bp, sw = 0; *p == '>' || *p == '<'; ++p) sw += O_VAL(sp, O_SHIFTWIDTH); GET_SPACE_RETW(sp, bp, blen, 256); curset = 0; for (from = cmdp->addr1.lno, to = cmdp->addr2.lno; from <= to; ++from) { if (db_get(sp, from, DBG_FATAL, &p, &len)) goto err; if (!len) { if (sp->lno == from) curset = 1; continue; } /* * Calculate the old indent amount and the number of * characters it used. */ for (oldidx = 0, oldcol = 0; oldidx < len; ++oldidx) if (p[oldidx] == ' ') ++oldcol; else if (p[oldidx] == '\t') oldcol += O_VAL(sp, O_TABSTOP) - oldcol % O_VAL(sp, O_TABSTOP); else break; /* Calculate the new indent amount. */ - if (rl == RIGHT) + if (rl == RETAB) + newcol = oldcol; + else if (rl == RIGHT) newcol = oldcol + sw; else { newcol = oldcol < sw ? 0 : oldcol - sw; if (newcol == oldcol) { if (sp->lno == from) curset = 1; continue; } } /* Get a buffer that will hold the new line. */ ADD_SPACE_RETW(sp, bp, blen, newcol + len); /* * Build a new indent string and count the number of * characters it uses. */ - for (tbp = bp, newidx = 0; - newcol >= O_VAL(sp, O_TABSTOP); ++newidx) { - *tbp++ = '\t'; - newcol -= O_VAL(sp, O_TABSTOP); + tbp = bp; + newidx = 0; + if (!O_ISSET(sp, O_EXPANDTAB)) { + for (; newcol >= O_VAL(sp, O_TABSTOP); ++newidx) { + *tbp++ = '\t'; + newcol -= O_VAL(sp, O_TABSTOP); + } } for (; newcol > 0; --newcol, ++newidx) *tbp++ = ' '; /* Add the original line. */ MEMCPY(tbp, p + oldidx, len - oldidx); /* Set the replacement line. */ if (db_set(sp, from, bp, (tbp + (len - oldidx)) - bp)) { err: FREE_SPACEW(sp, bp, blen); return (1); } /* * !!! * The shift command in historic vi had the usual bizarre * collection of cursor semantics. If called from vi, the * cursor was repositioned to the first non-blank character * of the lowest numbered line shifted. If called from ex, * the cursor was repositioned to the first non-blank of the * highest numbered line shifted. Here, if the cursor isn't * part of the set of lines that are moved, move it to the * first non-blank of the last line shifted. (This makes * ":3>>" in vi work reasonably.) If the cursor is part of * the shifted lines, it doesn't get moved at all. This * permits shifting of marked areas, i.e. ">'a." shifts the * marked area twice, something that couldn't be done with * historic vi. */ if (sp->lno == from) { curset = 1; if (newidx > oldidx) sp->cno += newidx - oldidx; else if (sp->cno >= oldidx - newidx) sp->cno -= oldidx - newidx; } } if (!curset) { sp->lno = to; sp->cno = 0; (void)nonblank(sp, to, &sp->cno); } FREE_SPACEW(sp, bp, blen); sp->rptlines[L_SHIFT] += cmdp->addr2.lno - cmdp->addr1.lno + 1; return (0); } Index: head/contrib/nvi/ex/ex_source.c =================================================================== --- head/contrib/nvi/ex/ex_source.c (revision 365498) +++ head/contrib/nvi/ex/ex_source.c (revision 365499) @@ -1,95 +1,90 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_source.c,v 10.17 2011/12/19 16:17:06 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_source -- :source file * Execute ex commands from a file. * * PUBLIC: int ex_source(SCR *, EXCMD *); */ int ex_source(SCR *sp, EXCMD *cmdp) { struct stat sb; int fd, len; char *bp; char *name, *np; size_t nlen; CHAR_T *wp; size_t wlen; int rc; INT2CHAR(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len + 1, name, nlen); if ((fd = open(name, O_RDONLY, 0)) < 0 || fstat(fd, &sb)) goto err; /* * XXX * I'd like to test to see if the file is too large to malloc. Since * we don't know what size or type off_t's or size_t's are, what the * largest unsigned integral type is, or what random insanity the local * C compiler will perpetrate, doing the comparison in a portable way * is flatly impossible. So, put an fairly unreasonable limit on it, * I don't want to be dropping core here. */ #define MEGABYTE 1048576 if (sb.st_size > MEGABYTE) { errno = ENOMEM; goto err; } - MALLOC(sp, bp, char *, (size_t)sb.st_size + 1); + MALLOC(sp, bp, (size_t)sb.st_size + 1); if (bp == NULL) { (void)close(fd); return (1); } bp[sb.st_size] = '\0'; /* Read the file into memory. */ len = read(fd, bp, (int)sb.st_size); (void)close(fd); if (len == -1 || len != sb.st_size) { if (len != sb.st_size) errno = EIO; free(bp); err: msgq_str(sp, M_SYSERR, name, "%s"); return (1); } np = strdup(name); if (CHAR2INT(sp, bp, (size_t)sb.st_size + 1, wp, wlen)) msgq(sp, M_ERR, "323|Invalid input. Truncated."); /* Put it on the ex queue. */ rc = ex_run_str(sp, np, wp, wlen - 1, 1, 0); - if (np != NULL) - free(np); + free(np); free(bp); return (rc); } Index: head/contrib/nvi/ex/ex_stop.c =================================================================== --- head/contrib/nvi/ex/ex_stop.c (revision 365498) +++ head/contrib/nvi/ex/ex_stop.c (revision 365499) @@ -1,50 +1,46 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_stop.c,v 10.11 2001/06/25 15:19:20 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_stop -- :stop[!] * :suspend[!] * Suspend execution. * * PUBLIC: int ex_stop(SCR *, EXCMD *); */ int ex_stop(SCR *sp, EXCMD *cmdp) { int allowed; /* For some strange reason, the force flag turns off autowrite. */ if (!FL_ISSET(cmdp->iflags, E_C_FORCE) && file_aw(sp, FS_ALL)) return (1); if (sp->gp->scr_suspend(sp, &allowed)) return (1); if (!allowed) ex_emsg(sp, NULL, EXM_NOSUSPEND); return (0); } Index: head/contrib/nvi/ex/ex_subst.c =================================================================== --- head/contrib/nvi/ex/ex_subst.c (revision 365498) +++ head/contrib/nvi/ex/ex_subst.c (revision 365499) @@ -1,1441 +1,1433 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_subst.c,v 10.53 2011/12/21 20:40:35 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" #define SUB_FIRST 0x01 /* The 'r' flag isn't reasonable. */ #define SUB_MUSTSETR 0x02 /* The 'r' flag is required. */ static int re_conv(SCR *, CHAR_T **, size_t *, int *); static int re_cscope_conv(SCR *, CHAR_T **, size_t *, int *); static int re_sub(SCR *, CHAR_T *, CHAR_T **, size_t *, size_t *, regmatch_t [10]); static int re_tag_conv(SCR *, CHAR_T **, size_t *, int *); static int s(SCR *, EXCMD *, CHAR_T *, regex_t *, u_int); /* * ex_s -- * [line [,line]] s[ubstitute] [[/;]pat[/;]/repl[/;] [cgr] [count] [#lp]] * * Substitute on lines matching a pattern. * * PUBLIC: int ex_s(SCR *, EXCMD *); */ int ex_s(SCR *sp, EXCMD *cmdp) { regex_t *re; size_t blen, len; u_int flags; int delim; CHAR_T *bp, *p, *ptrn, *rep, *t; /* * Skip leading white space. * * !!! * Historic vi allowed any non-alphanumeric to serve as the * substitution command delimiter. * * !!! * If the arguments are empty, it's the same as &, i.e. we * repeat the last substitution. */ if (cmdp->argc == 0) goto subagain; for (p = cmdp->argv[0]->bp, len = cmdp->argv[0]->len; len > 0; --len, ++p) { if (!cmdskip(*p)) break; } if (len == 0) subagain: return (ex_subagain(sp, cmdp)); delim = *p++; - if (!isascii(delim) || isalnum(delim) || delim == '\\') + if (is09azAZ(delim) || delim == '\\') return (s(sp, cmdp, p, &sp->subre_c, SUB_MUSTSETR)); /* * !!! * The full-blown substitute command reset the remembered * state of the 'c' and 'g' suffices. */ sp->c_suffix = sp->g_suffix = 0; /* * Get the pattern string, toss escaping characters. * * !!! * Historic vi accepted any of the following forms: * * :s/abc/def/ change "abc" to "def" * :s/abc/def change "abc" to "def" * :s/abc/ delete "abc" * :s/abc delete "abc" * * QUOTING NOTE: * * Only toss an escaping character if it escapes a delimiter. * This means that "s/A/\\\\f" replaces "A" with "\\f". It * would be nice to be more regular, i.e. for each layer of * escaping a single escaping character is removed, but that's * not how the historic vi worked. */ for (ptrn = t = p;;) { if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; /* * !!! * Nul terminate the pattern string -- it's passed * to regcomp which doesn't understand anything else. */ *t = '\0'; break; } if (p[0] == '\\') if (p[1] == delim) ++p; else if (p[1] == '\\') *t++ = *p++; *t++ = *p++; } /* * If the pattern string is empty, use the last RE (not just the * last substitution RE). */ if (*ptrn == '\0') { if (sp->re == NULL) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } /* Re-compile the RE if necessary. */ if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH)) return (1); flags = 0; } else { /* * !!! * Compile the RE. Historic practice is that substitutes set * the search direction as well as both substitute and search * RE's. We compile the RE twice, as we don't want to bother * ref counting the pattern string and (opaque) structure. */ if (re_compile(sp, ptrn, t - ptrn, &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH)) return (1); if (re_compile(sp, ptrn, t - ptrn, &sp->subre, &sp->subre_len, &sp->subre_c, RE_C_SUBST)) return (1); flags = SUB_FIRST; sp->searchdir = FORWARD; } re = &sp->re_c; /* * Get the replacement string. * * The special character & (\& if O_MAGIC not set) matches the * entire RE. No handling of & is required here, it's done by * re_sub(). * * The special character ~ (\~ if O_MAGIC not set) inserts the * previous replacement string into this replacement string. * Count ~'s to figure out how much space we need. We could * special case nonexistent last patterns or whether or not * O_MAGIC is set, but it's probably not worth the effort. * * QUOTING NOTE: * * Only toss an escaping character if it escapes a delimiter or * if O_MAGIC is set and it escapes a tilde. * * !!! * If the entire replacement pattern is "%", then use the last * replacement pattern. This semantic was added to vi in System * V and then percolated elsewhere, presumably around the time * that it was added to their version of ed(1). */ if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; - if (sp->repl != NULL) - free(sp->repl); + free(sp->repl); sp->repl = NULL; sp->repl_len = 0; } else if (p[0] == '%' && (p[1] == '\0' || p[1] == delim)) p += p[1] == delim ? 2 : 1; else { for (rep = p, len = 0; p[0] != '\0' && p[0] != delim; ++p, ++len) if (p[0] == '~') len += sp->repl_len; GET_SPACE_RETW(sp, bp, blen, len); for (t = bp, len = 0, p = rep;;) { if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; break; } if (p[0] == '\\') { if (p[1] == delim) ++p; else if (p[1] == '\\') { *t++ = *p++; ++len; } else if (p[1] == '~') { ++p; if (!O_ISSET(sp, O_MAGIC)) goto tilde; } } else if (p[0] == '~' && O_ISSET(sp, O_MAGIC)) { tilde: ++p; MEMCPY(t, sp->repl, sp->repl_len); t += sp->repl_len; len += sp->repl_len; continue; } *t++ = *p++; ++len; } if ((sp->repl_len = len) != 0) { - if (sp->repl != NULL) - free(sp->repl); - MALLOC(sp, sp->repl, CHAR_T *, len * sizeof(CHAR_T)); + free(sp->repl); + MALLOC(sp, sp->repl, len * sizeof(CHAR_T)); if (sp->repl == NULL) { FREE_SPACEW(sp, bp, blen); return (1); } MEMCPY(sp->repl, bp, len); } FREE_SPACEW(sp, bp, blen); } return (s(sp, cmdp, p, re, flags)); } /* * ex_subagain -- * [line [,line]] & [cgr] [count] [#lp]] * * Substitute using the last substitute RE and replacement pattern. * * PUBLIC: int ex_subagain(SCR *, EXCMD *); */ int ex_subagain(SCR *sp, EXCMD *cmdp) { if (sp->subre == NULL) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } if (!F_ISSET(sp, SC_RE_SUBST) && re_compile(sp, sp->subre, sp->subre_len, NULL, NULL, &sp->subre_c, RE_C_SUBST)) return (1); return (s(sp, cmdp, cmdp->argc ? cmdp->argv[0]->bp : NULL, &sp->subre_c, 0)); } /* * ex_subtilde -- * [line [,line]] ~ [cgr] [count] [#lp]] * * Substitute using the last RE and last substitute replacement pattern. * * PUBLIC: int ex_subtilde(SCR *, EXCMD *); */ int ex_subtilde(SCR *sp, EXCMD *cmdp) { if (sp->re == NULL) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH)) return (1); return (s(sp, cmdp, cmdp->argc ? cmdp->argv[0]->bp : NULL, &sp->re_c, 0)); } /* * s -- * Do the substitution. This stuff is *really* tricky. There are lots of * special cases, and general nastiness. Don't mess with it unless you're * pretty confident. * * The nasty part of the substitution is what happens when the replacement * string contains newlines. It's a bit tricky -- consider the information * that has to be retained for "s/f\(o\)o/^M\1^M\1/". The solution here is * to build a set of newline offsets which we use to break the line up later, * when the replacement is done. Don't change it unless you're *damned* * confident. */ #define NEEDNEWLINE(sp) { \ if (sp->newl_len == sp->newl_cnt) { \ sp->newl_len += 25; \ REALLOC(sp, sp->newl, size_t *, \ sp->newl_len * sizeof(size_t)); \ if (sp->newl == NULL) { \ sp->newl_len = 0; \ return (1); \ } \ } \ } #define BUILD(sp, l, len) { \ if (lbclen + (len) > lblen) { \ lblen = p2roundup(MAX(lbclen + (len), 256)); \ REALLOC(sp, lb, CHAR_T *, lblen * sizeof(CHAR_T)); \ if (lb == NULL) { \ lbclen = 0; \ return (1); \ } \ } \ MEMCPY(lb + lbclen, l, len); \ lbclen += len; \ } #define NEEDSP(sp, len, pnt) { \ if (lbclen + (len) > lblen) { \ lblen = p2roundup(MAX(lbclen + (len), 256)); \ REALLOC(sp, lb, CHAR_T *, lblen * sizeof(CHAR_T)); \ if (lb == NULL) { \ lbclen = 0; \ return (1); \ } \ pnt = lb + lbclen; \ } \ } static int s(SCR *sp, EXCMD *cmdp, CHAR_T *s, regex_t *re, u_int flags) { EVENT ev; MARK from, to; TEXTH tiq[] = {{ 0 }}; recno_t elno, lno, slno; u_long ul; regmatch_t match[10]; size_t blen, cnt, last, lbclen, lblen, len, llen; size_t offset, saved_offset, scno; int cflag, lflag, nflag, pflag, rflag; int didsub, do_eol_match, eflags, empty_ok, eval; int linechanged, matched, quit, rval; CHAR_T *bp, *lb; enum nresult nret; NEEDFILE(sp, cmdp); slno = sp->lno; scno = sp->cno; /* * !!! * Historically, the 'g' and 'c' suffices were always toggled as flags, * so ":s/A/B/" was the same as ":s/A/B/ccgg". If O_EDCOMPATIBLE was * not set, they were initialized to 0 for all substitute commands. If * O_EDCOMPATIBLE was set, they were initialized to 0 only if the user * specified substitute/replacement patterns (see ex_s()). */ if (!O_ISSET(sp, O_EDCOMPATIBLE)) sp->c_suffix = sp->g_suffix = 0; /* * Historic vi permitted the '#', 'l' and 'p' options in vi mode, but * it only displayed the last change. I'd disallow them, but they are * useful in combination with the [v]global commands. In the current * model the problem is combining them with the 'c' flag -- the screen * would have to flip back and forth between the confirm screen and the * ex print screen, which would be pretty awful. We do display all * changes, though, for what that's worth. * * !!! * Historic vi was fairly strict about the order of "options", the * count, and "flags". I'm somewhat fuzzy on the difference between * options and flags, anyway, so this is a simpler approach, and we * just take it them in whatever order the user gives them. (The ex * usage statement doesn't reflect this.) */ cflag = lflag = nflag = pflag = rflag = 0; if (s == NULL) goto noargs; for (lno = OOBLNO; *s != '\0'; ++s) switch (*s) { case ' ': case '\t': continue; case '+': ++cmdp->flagoff; break; case '-': --cmdp->flagoff; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (lno != OOBLNO) goto usage; errno = 0; nret = nget_uslong(&ul, s, &s, 10); lno = ul; if (*s == '\0') /* Loop increment correction. */ --s; if (nret != NUM_OK) { if (nret == NUM_OVER) msgq(sp, M_ERR, "153|Count overflow"); else if (nret == NUM_UNDER) msgq(sp, M_ERR, "154|Count underflow"); else msgq(sp, M_SYSERR, NULL); return (1); } /* * In historic vi, the count was inclusive from the * second address. */ cmdp->addr1.lno = cmdp->addr2.lno; cmdp->addr2.lno += lno - 1; if (!db_exist(sp, cmdp->addr2.lno) && db_last(sp, &cmdp->addr2.lno)) return (1); break; case '#': nflag = 1; break; case 'c': sp->c_suffix = !sp->c_suffix; /* Ex text structure initialization. */ if (F_ISSET(sp, SC_EX)) TAILQ_INIT(tiq); break; case 'g': sp->g_suffix = !sp->g_suffix; break; case 'l': lflag = 1; break; case 'p': pflag = 1; break; case 'r': if (LF_ISSET(SUB_FIRST)) { msgq(sp, M_ERR, "155|Regular expression specified; r flag meaningless"); return (1); } if (!F_ISSET(sp, SC_RE_SEARCH)) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } rflag = 1; re = &sp->re_c; break; default: goto usage; } if (*s != '\0' || (!rflag && LF_ISSET(SUB_MUSTSETR))) { usage: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } noargs: if (F_ISSET(sp, SC_VI) && sp->c_suffix && (lflag || nflag || pflag)) { msgq(sp, M_ERR, "156|The #, l and p flags may not be combined with the c flag in vi mode"); return (1); } /* * bp: if interactive, line cache * blen: if interactive, line cache length * lb: build buffer pointer. * lbclen: current length of built buffer. * lblen; length of build buffer. */ bp = lb = NULL; blen = lbclen = lblen = 0; /* For each line... */ lno = cmdp->addr1.lno == 0 ? 1 : cmdp->addr1.lno; for (matched = quit = 0, elno = cmdp->addr2.lno; !quit && lno <= elno; ++lno) { /* Someone's unhappy, time to stop. */ if (INTERRUPTED(sp)) break; /* Get the line. */ if (db_get(sp, lno, DBG_FATAL, &s, &llen)) goto err; /* * Make a local copy if doing confirmation -- when calling * the confirm routine we're likely to lose the cached copy. */ if (sp->c_suffix) { if (bp == NULL) { GET_SPACE_RETW(sp, bp, blen, llen); } else ADD_SPACE_RETW(sp, bp, blen, llen); MEMCPY(bp, s, llen); s = bp; } /* Start searching from the beginning. */ offset = 0; len = llen; /* Reset the build buffer offset. */ lbclen = 0; /* Reset empty match flag. */ empty_ok = 1; /* * We don't want to have to do a setline if the line didn't * change -- keep track of whether or not this line changed. * If doing confirmations, don't want to keep setting the * line if change is refused -- keep track of substitutions. */ didsub = linechanged = 0; /* New line, do an EOL match. */ do_eol_match = 1; /* It's not nul terminated, but we pretend it is. */ eflags = REG_STARTEND; /* * The search area is from s + offset to the EOL. * * Generally, match[0].rm_so is the offset of the start * of the match from the start of the search, and offset * is the offset of the start of the last search. */ nextmatch: match[0].rm_so = 0; match[0].rm_eo = len; /* Get the next match. */ eval = regexec(re, s + offset, 10, match, eflags); /* * There wasn't a match or if there was an error, deal with * it. If there was a previous match in this line, resolve * the changes into the database. Otherwise, just move on. */ if (eval == REG_NOMATCH) goto endmatch; if (eval != 0) { re_error(sp, eval, re); goto err; } matched = 1; /* Only the first search can match an anchored expression. */ eflags |= REG_NOTBOL; /* * !!! * It's possible to match 0-length strings -- for example, the * command s;a*;X;, when matched against the string "aabb" will * result in "XbXbX", i.e. the matches are "aa", the space * between the b's and the space between the b's and the end of * the string. There is a similar space between the beginning * of the string and the a's. The rule that we use (because vi * historically used it) is that any 0-length match, occurring * immediately after a match, is ignored. Otherwise, the above * example would have resulted in "XXbXbX". Another example is * incorrectly using " *" to replace groups of spaces with one * space. * * The way we do this is that if we just had a successful match, * the starting offset does not skip characters, and the match * is empty, ignore the match and move forward. If there's no * more characters in the string, we were attempting to match * after the last character, so quit. */ if (!empty_ok && match[0].rm_so == 0 && match[0].rm_eo == 0) { empty_ok = 1; if (len == 0) goto endmatch; BUILD(sp, s + offset, 1) ++offset; --len; goto nextmatch; } /* Confirm change. */ if (sp->c_suffix) { /* * Set the cursor position for confirmation. Note, * if we matched on a '$', the cursor may be past * the end of line. */ from.lno = to.lno = lno; from.cno = match[0].rm_so + offset; to.cno = match[0].rm_eo + offset; /* * Both ex and vi have to correct for a change before * the first character in the line. */ if (llen == 0) from.cno = to.cno = 0; if (F_ISSET(sp, SC_VI)) { /* * Only vi has to correct for a change after * the last character in the line. * * XXX * It would be nice to change the vi code so * that we could display a cursor past EOL. */ if (to.cno >= llen) to.cno = llen - 1; if (from.cno >= llen) from.cno = llen - 1; sp->lno = from.lno; sp->cno = from.cno; if (vs_refresh(sp, 1)) goto err; vs_update(sp, msg_cat(sp, "169|Confirm change? [n]", NULL), NULL); if (v_event_get(sp, &ev, 0, 0)) goto err; switch (ev.e_event) { case E_CHARACTER: break; case E_EOF: case E_ERR: case E_INTERRUPT: goto lquit; default: v_event_err(sp, &ev); goto lquit; } } else { if (ex_print(sp, cmdp, &from, &to, 0) || ex_scprint(sp, &from, &to)) goto lquit; if (ex_txt(sp, tiq, 0, TXT_CR)) goto err; ev.e_c = TAILQ_FIRST(tiq)->lb[0]; } switch (ev.e_c) { case CH_YES: break; default: case CH_NO: didsub = 0; BUILD(sp, s +offset, match[0].rm_eo); goto skip; case CH_QUIT: /* Set the quit/interrupted flags. */ lquit: quit = 1; F_SET(sp->gp, G_INTERRUPTED); /* * Resolve any changes, then return to (and * exit from) the main loop. */ goto endmatch; } } /* * Set the cursor to the last position changed, converting * from 1-based to 0-based. */ sp->lno = lno; sp->cno = match[0].rm_so; /* Copy the bytes before the match into the build buffer. */ BUILD(sp, s + offset, match[0].rm_so); /* Substitute the matching bytes. */ didsub = 1; if (re_sub(sp, s + offset, &lb, &lbclen, &lblen, match)) goto err; /* Set the change flag so we know this line was modified. */ linechanged = 1; /* Move past the matched bytes. */ skip: offset += match[0].rm_eo; len -= match[0].rm_eo; /* A match cannot be followed by an empty pattern. */ empty_ok = 0; /* * If doing a global change with confirmation, we have to * update the screen. The basic idea is to store the line * so the screen update routines can find it, and restart. */ if (didsub && sp->c_suffix && sp->g_suffix) { /* * The new search offset will be the end of the * modified line. */ saved_offset = lbclen; /* Copy the rest of the line. */ if (len) BUILD(sp, s + offset, len) /* Set the new offset. */ offset = saved_offset; /* Store inserted lines, adjusting the build buffer. */ last = 0; if (sp->newl_cnt) { for (cnt = 0; cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) { if (db_insert(sp, lno, lb + last, sp->newl[cnt] - last)) goto err; last = sp->newl[cnt] + 1; ++sp->rptlines[L_ADDED]; } lbclen -= last; offset -= last; sp->newl_cnt = 0; } /* Store and retrieve the line. */ if (db_set(sp, lno, lb + last, lbclen)) goto err; if (db_get(sp, lno, DBG_FATAL, &s, &llen)) goto err; ADD_SPACE_RETW(sp, bp, blen, llen) MEMCPY(bp, s, llen); s = bp; len = llen - offset; /* Restart the build. */ lbclen = 0; BUILD(sp, s, offset); /* * If we haven't already done the after-the-string * match, do one. Set REG_NOTEOL so the '$' pattern * only matches once. */ if (!do_eol_match) goto endmatch; if (offset == len) { do_eol_match = 0; eflags |= REG_NOTEOL; } goto nextmatch; } /* * If it's a global: * * If at the end of the string, do a test for the after * the string match. Set REG_NOTEOL so the '$' pattern * only matches once. */ if (sp->g_suffix && do_eol_match) { if (len == 0) { do_eol_match = 0; eflags |= REG_NOTEOL; } goto nextmatch; } endmatch: if (!linechanged) continue; /* Copy any remaining bytes into the build buffer. */ if (len) BUILD(sp, s + offset, len) /* Store inserted lines, adjusting the build buffer. */ last = 0; if (sp->newl_cnt) { for (cnt = 0; cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) { if (db_insert(sp, lno, lb + last, sp->newl[cnt] - last)) goto err; last = sp->newl[cnt] + 1; ++sp->rptlines[L_ADDED]; } lbclen -= last; sp->newl_cnt = 0; } /* Store the changed line. */ if (db_set(sp, lno, lb + last, lbclen)) goto err; /* Update changed line counter. */ if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } /* * !!! * Display as necessary. Historic practice is to only * display the last line of a line split into multiple * lines. */ if (lflag || nflag || pflag) { from.lno = to.lno = lno; from.cno = to.cno = 0; if (lflag) (void)ex_print(sp, cmdp, &from, &to, E_C_LIST); if (nflag) (void)ex_print(sp, cmdp, &from, &to, E_C_HASH); if (pflag) (void)ex_print(sp, cmdp, &from, &to, E_C_PRINT); } } /* * !!! * Historically, vi attempted to leave the cursor at the same place if * the substitution was done at the current cursor position. Otherwise * it moved it to the first non-blank of the last line changed. There * were some problems: for example, :s/$/foo/ with the cursor on the * last character of the line left the cursor on the last character, or * the & command with multiple occurrences of the matching string in the * line usually left the cursor in a fairly random position. * * We try to do the same thing, with the exception that if the user is * doing substitution with confirmation, we move to the last line about * which the user was consulted, as opposed to the last line that they * actually changed. This prevents a screen flash if the user doesn't * change many of the possible lines. */ if (!sp->c_suffix && (sp->lno != slno || sp->cno != scno)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } /* * If not in a global command, and nothing matched, say so. * Else, if none of the lines displayed, put something up. */ rval = 0; if (!matched) { if (!F_ISSET(sp, SC_EX_GLOBAL)) { msgq(sp, M_ERR, "157|No match found"); goto err; } } else if (!lflag && !nflag && !pflag) F_SET(cmdp, E_AUTOPRINT); if (0) { err: rval = 1; } if (bp != NULL) FREE_SPACEW(sp, bp, blen); - if (lb != NULL) - free(lb); + free(lb); return (rval); } /* * re_compile -- * Compile the RE. * * PUBLIC: int re_compile(SCR *, * PUBLIC: CHAR_T *, size_t, CHAR_T **, size_t *, regex_t *, u_int); */ int re_compile(SCR *sp, CHAR_T *ptrn, size_t plen, CHAR_T **ptrnp, size_t *lenp, regex_t *rep, u_int flags) { size_t len; int reflags, replaced, rval; CHAR_T *p; /* Set RE flags. */ reflags = 0; if (!LF_ISSET(RE_C_CSCOPE | RE_C_TAG)) { if (O_ISSET(sp, O_EXTENDED)) reflags |= REG_EXTENDED; if (O_ISSET(sp, O_IGNORECASE)) reflags |= REG_ICASE; if (O_ISSET(sp, O_ICLOWER)) { for (p = ptrn, len = plen; len > 0; ++p, --len) if (ISUPPER(*p)) break; if (len == 0) reflags |= REG_ICASE; } } /* If we're replacing a saved value, clear the old one. */ if (LF_ISSET(RE_C_SEARCH) && F_ISSET(sp, SC_RE_SEARCH)) { regfree(&sp->re_c); F_CLR(sp, SC_RE_SEARCH); } if (LF_ISSET(RE_C_SUBST) && F_ISSET(sp, SC_RE_SUBST)) { regfree(&sp->subre_c); F_CLR(sp, SC_RE_SUBST); } /* * If we're saving the string, it's a pattern we haven't seen before, * so convert the vi-style RE's to POSIX 1003.2 RE's. Save a copy for * later recompilation. Free any previously saved value. */ if (ptrnp != NULL) { replaced = 0; if (LF_ISSET(RE_C_CSCOPE)) { if (re_cscope_conv(sp, &ptrn, &plen, &replaced)) return (1); /* * XXX * Currently, the match-any- expression used in * re_cscope_conv() requires extended RE's. This may * not be right or safe. */ reflags |= REG_EXTENDED; } else if (LF_ISSET(RE_C_TAG)) { if (re_tag_conv(sp, &ptrn, &plen, &replaced)) return (1); } else if (re_conv(sp, &ptrn, &plen, &replaced)) return (1); /* Discard previous pattern. */ - if (*ptrnp != NULL) { - free(*ptrnp); - *ptrnp = NULL; - } + free(*ptrnp); + *ptrnp = NULL; + if (lenp != NULL) *lenp = plen; /* * Copy the string into allocated memory. * * XXX * Regcomp isn't 8-bit clean, so the pattern is nul-terminated * for now. There's just no other solution. */ - MALLOC(sp, *ptrnp, CHAR_T *, (plen + 1) * sizeof(CHAR_T)); + MALLOC(sp, *ptrnp, (plen + 1) * sizeof(CHAR_T)); if (*ptrnp != NULL) { MEMCPY(*ptrnp, ptrn, plen); (*ptrnp)[plen] = '\0'; } /* Free up conversion-routine-allocated memory. */ if (replaced) FREE_SPACEW(sp, ptrn, 0); if (*ptrnp == NULL) return (1); ptrn = *ptrnp; } /* * XXX * Regcomp isn't 8-bit clean, so we just lost if the pattern * contained a nul. Bummer! */ if ((rval = regcomp(rep, ptrn, /* plen, */ reflags)) != 0) { if (!LF_ISSET(RE_C_SILENT)) re_error(sp, rval, rep); return (1); } if (LF_ISSET(RE_C_SEARCH)) F_SET(sp, SC_RE_SEARCH); if (LF_ISSET(RE_C_SUBST)) F_SET(sp, SC_RE_SUBST); return (0); } /* * re_conv -- * Convert vi's regular expressions into something that the * the POSIX 1003.2 RE functions can handle. * * There are three conversions we make to make vi's RE's (specifically * the global, search, and substitute patterns) work with POSIX RE's. * * 1: If O_MAGIC is not set, strip backslashes from the magic character * set (.[*~) that have them, and add them to the ones that don't. * 2: If O_MAGIC is not set, the string "\~" is replaced with the text * from the last substitute command's replacement string. If O_MAGIC * is set, it's the string "~". * 3: The pattern \ does "word" searches, convert it to use the * new RE escapes. * * !!!/XXX * This doesn't exactly match the historic behavior of vi because we do * the ~ substitution before calling the RE engine, so magic characters * in the replacement string will be expanded by the RE engine, and they * weren't historically. It's a bug. */ static int re_conv(SCR *sp, CHAR_T **ptrnp, size_t *plenp, int *replacedp) { size_t blen, len, needlen; int magic; CHAR_T *bp, *p, *t; /* * First pass through, we figure out how much space we'll need. * We do it in two passes, on the grounds that most of the time * the user is doing a search and won't have magic characters. * That way we can skip most of the memory allocation and copies. */ magic = 0; for (p = *ptrnp, len = *plenp, needlen = 0; len > 0; ++p, --len) switch (*p) { case '\\': if (len > 1) { --len; switch (*++p) { case '<': magic = 1; needlen += RE_WSTART_LEN + 1; break; case '>': magic = 1; needlen += RE_WSTOP_LEN + 1; break; case '~': if (!O_ISSET(sp, O_MAGIC)) { magic = 1; needlen += sp->repl_len; } break; case '.': case '[': case '*': if (!O_ISSET(sp, O_MAGIC)) { magic = 1; needlen += 1; } break; default: needlen += 2; } } else needlen += 1; break; case '~': if (O_ISSET(sp, O_MAGIC)) { magic = 1; needlen += sp->repl_len; } break; case '.': case '[': case '*': if (!O_ISSET(sp, O_MAGIC)) { magic = 1; needlen += 2; } break; default: needlen += 1; break; } if (!magic) { *replacedp = 0; return (0); } /* Get enough memory to hold the final pattern. */ *replacedp = 1; GET_SPACE_RETW(sp, bp, blen, needlen); for (p = *ptrnp, len = *plenp, t = bp; len > 0; ++p, --len) switch (*p) { case '\\': if (len > 1) { --len; switch (*++p) { case '<': MEMCPY(t, RE_WSTART, RE_WSTART_LEN); t += RE_WSTART_LEN; break; case '>': MEMCPY(t, RE_WSTOP, RE_WSTOP_LEN); t += RE_WSTOP_LEN; break; case '~': if (O_ISSET(sp, O_MAGIC)) *t++ = '~'; else { MEMCPY(t, sp->repl, sp->repl_len); t += sp->repl_len; } break; case '.': case '[': case '*': if (O_ISSET(sp, O_MAGIC)) *t++ = '\\'; *t++ = *p; break; default: *t++ = '\\'; *t++ = *p; } } else *t++ = '\\'; break; case '~': if (O_ISSET(sp, O_MAGIC)) { MEMCPY(t, sp->repl, sp->repl_len); t += sp->repl_len; } else *t++ = '~'; break; case '.': case '[': case '*': if (!O_ISSET(sp, O_MAGIC)) *t++ = '\\'; *t++ = *p; break; default: *t++ = *p; break; } *ptrnp = bp; *plenp = t - bp; return (0); } /* * re_tag_conv -- * Convert a tags search path into something that the POSIX * 1003.2 RE functions can handle. */ static int re_tag_conv(SCR *sp, CHAR_T **ptrnp, size_t *plenp, int *replacedp) { size_t blen, len; int lastdollar; CHAR_T *bp, *p, *t; len = *plenp; /* Max memory usage is 2 times the length of the string. */ *replacedp = 1; GET_SPACE_RETW(sp, bp, blen, len * 2); p = *ptrnp; t = bp; /* If the last character is a '/' or '?', we just strip it. */ if (len > 0 && (p[len - 1] == '/' || p[len - 1] == '?')) --len; /* If the next-to-last or last character is a '$', it's magic. */ if (len > 0 && p[len - 1] == '$') { --len; lastdollar = 1; } else lastdollar = 0; /* If the first character is a '/' or '?', we just strip it. */ if (len > 0 && (p[0] == '/' || p[0] == '?')) { ++p; --len; } /* If the first or second character is a '^', it's magic. */ if (p[0] == '^') { *t++ = *p++; --len; } /* * Escape every other magic character we can find, meanwhile stripping * the backslashes ctags inserts when escaping the search delimiter * characters. */ for (; len > 0; --len) { if (p[0] == '\\' && (p[1] == '/' || p[1] == '?')) { ++p; --len; } else if (STRCHR(L("^.[]$*"), p[0])) *t++ = '\\'; *t++ = *p++; } if (lastdollar) *t++ = '$'; *ptrnp = bp; *plenp = t - bp; return (0); } /* * re_cscope_conv -- * Convert a cscope search path into something that the POSIX * 1003.2 RE functions can handle. */ static int re_cscope_conv(SCR *sp, CHAR_T **ptrnp, size_t *plenp, int *replacedp) { size_t blen, len, nspaces; CHAR_T *bp, *t; CHAR_T *p; CHAR_T *wp; size_t wlen; /* * Each space in the source line printed by cscope represents an * arbitrary sequence of spaces, tabs, and comments. */ #define CSCOPE_RE_SPACE "([ \t]|/\\*([^*]|\\*/)*\\*/)*" #define CSCOPE_LEN sizeof(CSCOPE_RE_SPACE) - 1 CHAR2INT(sp, CSCOPE_RE_SPACE, CSCOPE_LEN, wp, wlen); for (nspaces = 0, p = *ptrnp, len = *plenp; len > 0; ++p, --len) if (*p == ' ') ++nspaces; /* * Allocate plenty of space: * the string, plus potential escaping characters; * nspaces + 2 copies of CSCOPE_RE_SPACE; * ^, $, nul terminator characters. */ *replacedp = 1; len = (p - *ptrnp) * 2 + (nspaces + 2) * sizeof(CSCOPE_RE_SPACE) + 3; GET_SPACE_RETW(sp, bp, blen, len); p = *ptrnp; t = bp; *t++ = '^'; MEMCPY(t, wp, wlen); t += wlen; for (len = *plenp; len > 0; ++p, --len) if (*p == ' ') { MEMCPY(t, wp, wlen); t += wlen; } else { if (STRCHR(L("\\^.[]$*+?()|{}"), *p)) *t++ = '\\'; *t++ = *p; } MEMCPY(t, wp, wlen); t += wlen; *t++ = '$'; *ptrnp = bp; *plenp = t - bp; return (0); } /* * re_error -- * Report a regular expression error. * * PUBLIC: void re_error(SCR *, int, regex_t *); */ void re_error(SCR *sp, int errcode, regex_t *preg) { size_t s; char *oe; s = regerror(errcode, preg, "", 0); - MALLOC(sp, oe, char *, s); + MALLOC(sp, oe, s); if (oe != NULL) { (void)regerror(errcode, preg, oe, s); msgq(sp, M_ERR, "RE error: %s", oe); free(oe); } } /* * re_sub -- * Do the substitution for a regular expression. */ static int re_sub( SCR *sp, CHAR_T *ip, /* Input line. */ CHAR_T **lbp, size_t *lbclenp, size_t *lblenp, regmatch_t match[10]) { enum { C_NOTSET, C_LOWER, C_ONELOWER, C_ONEUPPER, C_UPPER } conv; size_t lbclen, lblen; /* Local copies. */ size_t mlen; /* Match length. */ size_t rpl; /* Remaining replacement length. */ CHAR_T *rp; /* Replacement pointer. */ int ch; int no; /* Match replacement offset. */ CHAR_T *p, *t; /* Buffer pointers. */ CHAR_T *lb; /* Local copies. */ lb = *lbp; /* Get local copies. */ lbclen = *lbclenp; lblen = *lblenp; /* * QUOTING NOTE: * * There are some special sequences that vi provides in the * replacement patterns. * & string the RE matched (\& if nomagic set) * \# n-th regular subexpression * \E end \U, \L conversion * \e end \U, \L conversion * \l convert the next character to lower-case * \L convert to lower-case, until \E, \e, or end of replacement * \u convert the next character to upper-case * \U convert to upper-case, until \E, \e, or end of replacement * * Otherwise, since this is the lowest level of replacement, discard * all escaping characters. This (hopefully) matches historic practice. */ #define OUTCH(ch, nltrans) { \ ARG_CHAR_T __ch = (ch); \ e_key_t __value = KEY_VAL(sp, __ch); \ if (nltrans && (__value == K_CR || __value == K_NL)) { \ NEEDNEWLINE(sp); \ sp->newl[sp->newl_cnt++] = lbclen; \ } else if (conv != C_NOTSET) { \ switch (conv) { \ case C_ONELOWER: \ conv = C_NOTSET; \ /* FALLTHROUGH */ \ case C_LOWER: \ if (ISUPPER(__ch)) \ __ch = TOLOWER(__ch); \ break; \ case C_ONEUPPER: \ conv = C_NOTSET; \ /* FALLTHROUGH */ \ case C_UPPER: \ if (ISLOWER(__ch)) \ __ch = TOUPPER(__ch); \ break; \ default: \ abort(); \ } \ } \ NEEDSP(sp, 1, p); \ *p++ = __ch; \ ++lbclen; \ } conv = C_NOTSET; for (rp = sp->repl, rpl = sp->repl_len, p = lb + lbclen; rpl--;) { switch (ch = *rp++) { case '&': if (O_ISSET(sp, O_MAGIC)) { no = 0; goto subzero; } break; case '\\': if (rpl == 0) break; --rpl; switch (ch = *rp) { case '&': ++rp; if (!O_ISSET(sp, O_MAGIC)) { no = 0; goto subzero; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': no = *rp++ - '0'; subzero: if (match[no].rm_so == -1 || match[no].rm_eo == -1) break; mlen = match[no].rm_eo - match[no].rm_so; for (t = ip + match[no].rm_so; mlen--; ++t) OUTCH(*t, 0); continue; case 'e': case 'E': ++rp; conv = C_NOTSET; continue; case 'l': ++rp; conv = C_ONELOWER; continue; case 'L': ++rp; conv = C_LOWER; continue; case 'u': ++rp; conv = C_ONEUPPER; continue; case 'U': ++rp; conv = C_UPPER; continue; case '\r': OUTCH(ch, 0); continue; default: ++rp; break; } } OUTCH(ch, 1); } *lbp = lb; /* Update caller's information. */ *lbclenp = lbclen; *lblenp = lblen; return (0); } Index: head/contrib/nvi/ex/ex_tag.c =================================================================== --- head/contrib/nvi/ex/ex_tag.c (revision 365498) +++ head/contrib/nvi/ex/ex_tag.c (revision 365499) @@ -1,1314 +1,1307 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * This code is derived from software contributed to Berkeley by * David Hitz of Auspex Systems, Inc. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_tag.c,v 10.54 2012/04/12 07:17:30 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" #include "tag.h" static char *binary_search(char *, char *, char *); static int compare(char *, char *, char *); static void ctag_file(SCR *, TAGF *, char *, char **, size_t *); static int ctag_search(SCR *, CHAR_T *, size_t, char *); static int ctag_sfile(SCR *, TAGF *, TAGQ *, char *); static TAGQ *ctag_slist(SCR *, CHAR_T *); static char *linear_search(char *, char *, char *, long); static int tag_copy(SCR *, TAG *, TAG **); static int tag_pop(SCR *, TAGQ *, int); static int tagf_copy(SCR *, TAGF *, TAGF **); static int tagf_free(SCR *, TAGF *); static int tagq_copy(SCR *, TAGQ *, TAGQ **); /* * ex_tag_first -- * The tag code can be entered from main, e.g., "vi -t tag". * * PUBLIC: int ex_tag_first(SCR *, CHAR_T *); */ int ex_tag_first(SCR *sp, CHAR_T *tagarg) { EXCMD cmd; /* Build an argument for the ex :tag command. */ ex_cinit(sp, &cmd, C_TAG, 0, OOBLNO, OOBLNO, 0); argv_exp0(sp, &cmd, tagarg, STRLEN(tagarg)); /* * XXX * Historic vi went ahead and created a temporary file when it failed * to find the tag. We match historic practice, but don't distinguish * between real error and failure to find the tag. */ if (ex_tag_push(sp, &cmd)) return (0); /* Display tags in the center of the screen. */ F_CLR(sp, SC_SCR_TOP); F_SET(sp, SC_SCR_CENTER); return (0); } /* * ex_tag_push -- ^] * :tag[!] [string] * * Enter a new TAGQ context based on a ctag string. * * PUBLIC: int ex_tag_push(SCR *, EXCMD *); */ int ex_tag_push(SCR *sp, EXCMD *cmdp) { EX_PRIVATE *exp; TAGQ *tqp; long tl; exp = EXP(sp); switch (cmdp->argc) { case 1: - if (exp->tag_last != NULL) - free(exp->tag_last); + free(exp->tag_last); if ((exp->tag_last = v_wstrdup(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* Taglength may limit the number of characters. */ if ((tl = O_VAL(sp, O_TAGLENGTH)) != 0 && STRLEN(exp->tag_last) > tl) exp->tag_last[tl] = '\0'; break; case 0: if (exp->tag_last == NULL) { msgq(sp, M_ERR, "158|No previous tag entered"); return (1); } break; default: abort(); } /* Get the tag information. */ if ((tqp = ctag_slist(sp, exp->tag_last)) == NULL) return (1); if (tagq_push(sp, tqp, F_ISSET(cmdp, E_NEWSCREEN), FL_ISSET(cmdp->iflags, E_C_FORCE))) return 1; return 0; } /* * ex_tag_next -- * Switch context to the next TAG. * * PUBLIC: int ex_tag_next(SCR *, EXCMD *); */ int ex_tag_next(SCR *sp, EXCMD *cmdp) { EX_PRIVATE *exp; TAG *tp; TAGQ *tqp; char *np; size_t nlen; exp = EXP(sp); if ((tqp = TAILQ_FIRST(exp->tq)) == NULL) { tag_msg(sp, TAG_EMPTY, NULL); return (1); } if ((tp = TAILQ_NEXT(tqp->current, q)) == NULL) { msgq(sp, M_ERR, "282|Already at the last tag of this group"); return (1); } if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE))) return (1); tqp->current = tp; if (F_ISSET(tqp, TAG_CSCOPE)) (void)cscope_search(sp, tqp, tp); else (void)ctag_search(sp, tp->search, tp->slen, tqp->tag); if (tqp->current->msg) { INT2CHAR(sp, tqp->current->msg, tqp->current->mlen + 1, np, nlen); msgq(sp, M_INFO, "%s", np); } return (0); } /* * ex_tag_prev -- * Switch context to the next TAG. * * PUBLIC: int ex_tag_prev(SCR *, EXCMD *); */ int ex_tag_prev(SCR *sp, EXCMD *cmdp) { EX_PRIVATE *exp; TAG *tp; TAGQ *tqp; char *np; size_t nlen; exp = EXP(sp); if ((tqp = TAILQ_FIRST(exp->tq)) == NULL) { tag_msg(sp, TAG_EMPTY, NULL); return (0); } if ((tp = TAILQ_PREV(tqp->current, _tagqh, q)) == NULL) { msgq(sp, M_ERR, "255|Already at the first tag of this group"); return (1); } if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE))) return (1); tqp->current = tp; if (F_ISSET(tqp, TAG_CSCOPE)) (void)cscope_search(sp, tqp, tp); else (void)ctag_search(sp, tp->search, tp->slen, tqp->tag); if (tqp->current->msg) { INT2CHAR(sp, tqp->current->msg, tqp->current->mlen + 1, np, nlen); msgq(sp, M_INFO, "%s", np); } return (0); } /* * ex_tag_nswitch -- * Switch context to the specified TAG. * * PUBLIC: int ex_tag_nswitch(SCR *, TAG *, int); */ int ex_tag_nswitch(SCR *sp, TAG *tp, int force) { /* Get a file structure. */ if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL) return (1); /* If not changing files, return, we're done. */ if (tp->frp == sp->frp) return (0); /* Check for permission to leave. */ if (file_m1(sp, force, FS_ALL | FS_POSSIBLE)) return (1); /* Initialize the new file. */ if (file_init(sp, tp->frp, NULL, FS_SETALT)) return (1); /* Display tags in the center of the screen. */ F_CLR(sp, SC_SCR_TOP); F_SET(sp, SC_SCR_CENTER); /* Switch. */ F_SET(sp, SC_FSWITCH); return (0); } /* * ex_tag_Nswitch -- * Switch context to the specified TAG in a new screen. * * PUBLIC: int ex_tag_Nswitch(SCR *, TAG *, int); */ int ex_tag_Nswitch(SCR *sp, TAG *tp, int force) { SCR *new; /* Get a file structure. */ if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL) return (1); /* Get a new screen. */ if (screen_init(sp->gp, sp, &new)) return (1); if (vs_split(sp, new, 0)) { (void)file_end(new, new->ep, 1); (void)screen_end(new); return (1); } /* Get a backing file. */ if (tp->frp == sp->frp) { /* Copy file state. */ new->ep = sp->ep; ++new->ep->refcnt; new->frp = tp->frp; new->frp->flags = sp->frp->flags; } else if (file_init(new, tp->frp, NULL, force)) { (void)vs_discard(new, NULL); (void)screen_end(new); return (1); } /* Create the argument list. */ new->cargv = new->argv = ex_buildargv(sp, NULL, tp->frp->name); /* Display tags in the center of the screen. */ F_CLR(new, SC_SCR_TOP); F_SET(new, SC_SCR_CENTER); /* Switch. */ sp->nextdisp = new; F_SET(sp, SC_SSWITCH); return (0); } /* * ex_tag_pop -- ^T * :tagp[op][!] [number | file] * * Pop to a previous TAGQ context. * * PUBLIC: int ex_tag_pop(SCR *, EXCMD *); */ int ex_tag_pop(SCR *sp, EXCMD *cmdp) { EX_PRIVATE *exp; TAGQ *tqp, *dtqp; size_t arglen; long off; char *arg, *p, *t; size_t nlen; /* Check for an empty stack. */ exp = EXP(sp); if (TAILQ_EMPTY(exp->tq)) { tag_msg(sp, TAG_EMPTY, NULL); return (1); } /* Find the last TAG structure that we're going to DISCARD! */ switch (cmdp->argc) { case 0: /* Pop one tag. */ dtqp = TAILQ_FIRST(exp->tq); break; case 1: /* Name or number. */ INT2CHAR(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len+1, arg, nlen); off = strtol(arg, &p, 10); if (*p != '\0') goto filearg; /* Number: pop that many queue entries. */ if (off < 1) return (0); TAILQ_FOREACH(tqp, exp->tq, q) if (--off <= 1) break; if (tqp == NULL) { msgq(sp, M_ERR, "159|Less than %s entries on the tags stack; use :display t[ags]", arg); return (1); } dtqp = tqp; break; /* File argument: pop to that queue entry. */ filearg: arglen = strlen(arg); for (tqp = TAILQ_FIRST(exp->tq); tqp; dtqp = tqp, tqp = TAILQ_NEXT(tqp, q)) { /* Don't pop to the current file. */ if (tqp == TAILQ_FIRST(exp->tq)) continue; p = tqp->current->frp->name; if ((t = strrchr(p, '/')) == NULL) t = p; else ++t; if (!strncmp(arg, t, arglen)) break; } if (tqp == NULL) { msgq_str(sp, M_ERR, arg, "160|No file %s on the tags stack to return to; use :display t[ags]"); return (1); } if (tqp == TAILQ_FIRST(exp->tq)) return (0); break; default: abort(); } return (tag_pop(sp, dtqp, FL_ISSET(cmdp->iflags, E_C_FORCE))); } /* * ex_tag_top -- :tagt[op][!] * Clear the tag stack. * * PUBLIC: int ex_tag_top(SCR *, EXCMD *); */ int ex_tag_top(SCR *sp, EXCMD *cmdp) { EX_PRIVATE *exp; exp = EXP(sp); /* Check for an empty stack. */ if (TAILQ_EMPTY(exp->tq)) { tag_msg(sp, TAG_EMPTY, NULL); return (1); } /* Return to the oldest information. */ return (tag_pop(sp, TAILQ_PREV(TAILQ_LAST(exp->tq, _tqh), _tqh, q), FL_ISSET(cmdp->iflags, E_C_FORCE))); } /* * tag_pop -- * Pop up to and including the specified TAGQ context. */ static int tag_pop(SCR *sp, TAGQ *dtqp, int force) { EX_PRIVATE *exp; TAG *tp; TAGQ *tqp; exp = EXP(sp); /* * Update the cursor from the saved TAG information of the TAG * structure we're moving to. */ tp = TAILQ_NEXT(dtqp, q)->current; if (tp->frp == sp->frp) { sp->lno = tp->lno; sp->cno = tp->cno; } else { if (file_m1(sp, force, FS_ALL | FS_POSSIBLE)) return (1); tp->frp->lno = tp->lno; tp->frp->cno = tp->cno; F_SET(sp->frp, FR_CURSORSET); if (file_init(sp, tp->frp, NULL, FS_SETALT)) return (1); F_SET(sp, SC_FSWITCH); } /* Pop entries off the queue up to and including dtqp. */ do { tqp = TAILQ_FIRST(exp->tq); if (tagq_free(sp, tqp)) return (0); } while (tqp != dtqp); /* * If only a single tag left, we've returned to the first tag point, * and the stack is now empty. */ if (TAILQ_NEXT(TAILQ_FIRST(exp->tq), q) == NULL) tagq_free(sp, TAILQ_FIRST(exp->tq)); return (0); } /* * ex_tag_display -- * Display the list of tags. * * PUBLIC: int ex_tag_display(SCR *); */ int ex_tag_display(SCR *sp) { EX_PRIVATE *exp; TAG *tp; TAGQ *tqp; int cnt; size_t len; char *p; exp = EXP(sp); if (TAILQ_EMPTY(exp->tq)) { tag_msg(sp, TAG_EMPTY, NULL); return (0); } /* * We give the file name 20 columns and the search string the rest. * If there's not enough room, we don't do anything special, it's * not worth the effort, it just makes the display more confusing. * * We also assume that characters in file names map 1-1 to printing * characters. This might not be true, but I don't think it's worth * fixing. (The obvious fix is to pass the filenames through the * msg_print function.) */ #define L_NAME 30 /* Name. */ #define L_SLOP 4 /* Leading number plus trailing *. */ #define L_SPACE 5 /* Spaces after name, before tag. */ #define L_TAG 20 /* Tag. */ if (sp->cols <= L_NAME + L_SLOP) { msgq(sp, M_ERR, "292|Display too small."); return (0); } /* * Display the list of tags for each queue entry. The first entry * is numbered, and the current tag entry has an asterisk appended. */ for (cnt = 1, tqp = TAILQ_FIRST(exp->tq); !INTERRUPTED(sp) && tqp != NULL; ++cnt, tqp = TAILQ_NEXT(tqp, q)) TAILQ_FOREACH(tp, tqp->tagq, q) { if (tp == TAILQ_FIRST(tqp->tagq)) (void)ex_printf(sp, "%2d ", cnt); else (void)ex_printf(sp, " "); p = tp->frp == NULL ? tp->fname : tp->frp->name; if ((len = strlen(p)) > L_NAME) { len = len - (L_NAME - 4); (void)ex_printf(sp, " ... %*.*s", L_NAME - 4, L_NAME - 4, p + len); } else (void)ex_printf(sp, " %*.*s", L_NAME, L_NAME, p); if (tqp->current == tp) (void)ex_printf(sp, "*"); if (tp == TAILQ_FIRST(tqp->tagq) && tqp->tag != NULL && (sp->cols - L_NAME) >= L_TAG + L_SPACE) { len = strlen(tqp->tag); if (len > sp->cols - (L_NAME + L_SPACE)) len = sp->cols - (L_NAME + L_SPACE); (void)ex_printf(sp, "%s%.*s", tqp->current == tp ? " " : " ", (int)len, tqp->tag); } (void)ex_printf(sp, "\n"); } return (0); } /* * ex_tag_copy -- * Copy a screen's tag structures. * * PUBLIC: int ex_tag_copy(SCR *, SCR *); */ int ex_tag_copy(SCR *orig, SCR *sp) { EX_PRIVATE *oexp, *nexp; TAGQ *aqp, *tqp; TAG *ap, *tp; TAGF *atfp, *tfp; oexp = EXP(orig); nexp = EXP(sp); /* Copy tag queue and tags stack. */ TAILQ_FOREACH(aqp, oexp->tq, q) { if (tagq_copy(sp, aqp, &tqp)) return (1); TAILQ_FOREACH(ap, aqp->tagq, q) { if (tag_copy(sp, ap, &tp)) return (1); /* Set the current pointer. */ if (aqp->current == ap) tqp->current = tp; TAILQ_INSERT_TAIL(tqp->tagq, tp, q); } TAILQ_INSERT_TAIL(nexp->tq, tqp, q); } /* Copy list of tag files. */ TAILQ_FOREACH(atfp, oexp->tagfq, q) { if (tagf_copy(sp, atfp, &tfp)) return (1); TAILQ_INSERT_TAIL(nexp->tagfq, tfp, q); } /* Copy the last tag. */ if (oexp->tag_last != NULL && (nexp->tag_last = v_wstrdup(sp, oexp->tag_last, STRLEN(oexp->tag_last))) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } return (0); } /* * tagf_copy -- * Copy a TAGF structure and return it in new memory. */ static int tagf_copy(SCR *sp, TAGF *otfp, TAGF **tfpp) { TAGF *tfp; - MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF)); + MALLOC_RET(sp, tfp, sizeof(TAGF)); *tfp = *otfp; /* XXX: Allocate as part of the TAGF structure!!! */ - if ((tfp->name = strdup(otfp->name)) == NULL) + if ((tfp->name = strdup(otfp->name)) == NULL) { + free(tfp); return (1); + } *tfpp = tfp; return (0); } /* * tagq_copy -- * Copy a TAGQ structure and return it in new memory. */ static int tagq_copy(SCR *sp, TAGQ *otqp, TAGQ **tqpp) { TAGQ *tqp; size_t len; len = sizeof(TAGQ); if (otqp->tag != NULL) len += otqp->tlen + 1; - MALLOC_RET(sp, tqp, TAGQ *, len); + MALLOC_RET(sp, tqp, len); memcpy(tqp, otqp, len); TAILQ_INIT(tqp->tagq); tqp->current = NULL; if (otqp->tag != NULL) tqp->tag = tqp->buf; *tqpp = tqp; return (0); } /* * tag_copy -- * Copy a TAG structure and return it in new memory. */ static int tag_copy(SCR *sp, TAG *otp, TAG **tpp) { TAG *tp; size_t len; len = sizeof(TAG); if (otp->fname != NULL) len += otp->fnlen + 1; if (otp->search != NULL) len += otp->slen + 1; if (otp->msg != NULL) len += otp->mlen + 1; - MALLOC_RET(sp, tp, TAG *, len); + MALLOC_RET(sp, tp, len); memcpy(tp, otp, len); if (otp->fname != NULL) tp->fname = (char *)tp->buf; if (otp->search != NULL) tp->search = tp->buf + (otp->search - otp->buf); if (otp->msg != NULL) tp->msg = tp->buf + (otp->msg - otp->buf); *tpp = tp; return (0); } /* * tagf_free -- * Free a TAGF structure. */ static int tagf_free(SCR *sp, TAGF *tfp) { EX_PRIVATE *exp; exp = EXP(sp); TAILQ_REMOVE(exp->tagfq, tfp, q); free(tfp->name); free(tfp); return (0); } /* * tagq_free -- * Free a TAGQ structure (and associated TAG structures). * * PUBLIC: int tagq_free(SCR *, TAGQ *); */ int tagq_free(SCR *sp, TAGQ *tqp) { EX_PRIVATE *exp; TAG *tp; exp = EXP(sp); while ((tp = TAILQ_FIRST(tqp->tagq)) != NULL) { TAILQ_REMOVE(tqp->tagq, tp, q); free(tp); } /* * !!! * If allocated and then the user failed to switch files, the TAGQ * structure was never attached to any list. */ if (TAILQ_ENTRY_ISVALID(tqp, q)) TAILQ_REMOVE(exp->tq, tqp, q); free(tqp); return (0); } /* * PUBLIC: int tagq_push(SCR*, TAGQ*, int, int ); */ int tagq_push(SCR *sp, TAGQ *tqp, int new_screen, int force) { EX_PRIVATE *exp; FREF *frp; TAG *rtp; TAGQ *rtqp; recno_t lno; size_t cno; int istmp; char *np; size_t nlen; exp = EXP(sp); /* * Allocate all necessary memory before swapping screens. Initialize * flags so we know what to free. */ rtp = NULL; rtqp = NULL; if (TAILQ_EMPTY(exp->tq)) { /* Initialize the `local context' tag queue structure. */ - CALLOC_GOTO(sp, rtqp, TAGQ *, 1, sizeof(TAGQ)); + CALLOC_GOTO(sp, rtqp, 1, sizeof(TAGQ)); TAILQ_INIT(rtqp->tagq); /* Initialize and link in its tag structure. */ - CALLOC_GOTO(sp, rtp, TAG *, 1, sizeof(TAG)); + CALLOC_GOTO(sp, rtp, 1, sizeof(TAG)); TAILQ_INSERT_HEAD(rtqp->tagq, rtp, q); rtqp->current = rtp; } /* * Stick the current context information in a convenient place, we're * about to lose it. Note, if we're called on editor startup, there * will be no FREF structure. */ frp = sp->frp; lno = sp->lno; cno = sp->cno; istmp = frp == NULL || (F_ISSET(frp, FR_TMPFILE) && !new_screen); /* Try to switch to the preset tag. */ if (new_screen) { if (ex_tag_Nswitch(sp, tqp->current, force)) goto err; /* Everything else gets done in the new screen. */ sp = sp->nextdisp; exp = EXP(sp); } else if (ex_tag_nswitch(sp, tqp->current, force)) goto err; /* * If this is the first tag, put a `current location' queue entry * in place, so we can pop all the way back to the current mark. * Note, it doesn't point to much of anything, it's a placeholder. */ if (TAILQ_EMPTY(exp->tq)) { TAILQ_INSERT_HEAD(exp->tq, rtqp, q); } else rtqp = TAILQ_FIRST(exp->tq); /* Link the new TAGQ structure into place. */ TAILQ_INSERT_HEAD(exp->tq, tqp, q); (void)ctag_search(sp, tqp->current->search, tqp->current->slen, tqp->tag); if (tqp->current->msg) { INT2CHAR(sp, tqp->current->msg, tqp->current->mlen + 1, np, nlen); msgq(sp, M_INFO, "%s", np); } /* * Move the current context from the temporary save area into the * right structure. * * If we were in a temporary file, we don't have a context to which * we can return, so just make it be the same as what we're moving * to. It will be a little odd that ^T doesn't change anything, but * I don't think it's a big deal. */ if (istmp) { rtqp->current->frp = sp->frp; rtqp->current->lno = sp->lno; rtqp->current->cno = sp->cno; } else { rtqp->current->frp = frp; rtqp->current->lno = lno; rtqp->current->cno = cno; } return (0); err: alloc_err: - if (rtqp != NULL) - free(rtqp); - if (rtp != NULL) - free(rtp); + free(rtqp); + free(rtp); tagq_free(sp, tqp); return (1); } /* * tag_msg * A few common messages. * * PUBLIC: void tag_msg(SCR *, tagmsg_t, char *); */ void tag_msg(SCR *sp, tagmsg_t msg, char *tag) { switch (msg) { case TAG_BADLNO: msgq_str(sp, M_ERR, tag, "164|%s: the tag's line number is past the end of the file"); break; case TAG_EMPTY: msgq(sp, M_INFO, "165|The tags stack is empty"); break; case TAG_SEARCH: msgq_str(sp, M_ERR, tag, "166|%s: search pattern not found"); break; default: abort(); } } /* * ex_tagf_alloc -- * Create a new list of ctag files. * * PUBLIC: int ex_tagf_alloc(SCR *, char *); */ int ex_tagf_alloc(SCR *sp, char *str) { EX_PRIVATE *exp; TAGF *tfp; size_t len; char *p, *t; /* Free current queue. */ exp = EXP(sp); while ((tfp = TAILQ_FIRST(exp->tagfq)) != NULL) tagf_free(sp, tfp); /* Create new queue. */ for (p = t = str;; ++p) { if (*p == '\0' || cmdskip(*p)) { if ((len = p - t)) { - MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF)); - MALLOC(sp, tfp->name, char *, len + 1); + MALLOC_RET(sp, tfp, sizeof(TAGF)); + MALLOC(sp, tfp->name, len + 1); if (tfp->name == NULL) { free(tfp); return (1); } memcpy(tfp->name, t, len); tfp->name[len] = '\0'; tfp->flags = 0; TAILQ_INSERT_TAIL(exp->tagfq, tfp, q); } t = p + 1; } if (*p == '\0') break; } return (0); } /* Free previous queue. */ /* * ex_tag_free -- * Free the ex tag information. * * PUBLIC: int ex_tag_free(SCR *); */ int ex_tag_free(SCR *sp) { EX_PRIVATE *exp; TAGF *tfp; TAGQ *tqp; /* Free up tag information. */ exp = EXP(sp); while ((tqp = TAILQ_FIRST(exp->tq)) != NULL) tagq_free(sp, tqp); while ((tfp = TAILQ_FIRST(exp->tagfq)) != NULL) tagf_free(sp, tfp); - if (exp->tag_last != NULL) - free(exp->tag_last); + free(exp->tag_last); return (0); } /* * ctag_search -- * Search a file for a tag. */ static int ctag_search(SCR *sp, CHAR_T *search, size_t slen, char *tag) { MARK m; char *p; char *np; size_t nlen; /* * !!! * The historic tags file format (from a long, long time ago...) * used a line number, not a search string. I got complaints, so * people are still using the format. POSIX 1003.2 permits it. */ if (ISDIGIT(search[0])) { INT2CHAR(sp, search, slen+1, np, nlen); m.lno = atoi(np); if (!db_exist(sp, m.lno)) { tag_msg(sp, TAG_BADLNO, tag); return (1); } } else { /* * Search for the tag; cheap fallback for C functions * if the name is the same but the arguments have changed. */ m.lno = 1; m.cno = 0; if (f_search(sp, &m, &m, search, slen, NULL, SEARCH_FILE | SEARCH_TAG)) { INT2CHAR(sp, search, slen, np, nlen); if ((p = strrchr(np, '(')) != NULL) { slen = p - np; if (f_search(sp, &m, &m, search, slen, NULL, SEARCH_FILE | SEARCH_TAG)) goto notfound; } else { notfound: tag_msg(sp, TAG_SEARCH, tag); return (1); } } /* * !!! * Historically, tags set the search direction if it wasn't * already set. */ if (sp->searchdir == NOTSET) sp->searchdir = FORWARD; } /* * !!! * Tags move to the first non-blank, NOT the search pattern start. */ sp->lno = m.lno; sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); return (0); } /* * ctag_slist -- * Search the list of tags files for a tag, and return tag queue. */ static TAGQ * ctag_slist(SCR *sp, CHAR_T *tag) { EX_PRIVATE *exp; TAGF *tfp; TAGQ *tqp; size_t len; int echk = 0; char *np; size_t nlen; exp = EXP(sp); /* Allocate and initialize the tag queue structure. */ INT2CHAR(sp, tag, STRLEN(tag) + 1, np, nlen); len = nlen - 1; - CALLOC_GOTO(sp, tqp, TAGQ *, 1, sizeof(TAGQ) + len + 1); + CALLOC_GOTO(sp, tqp, 1, sizeof(TAGQ) + len + 1); TAILQ_INIT(tqp->tagq); tqp->tag = tqp->buf; memcpy(tqp->tag, np, (tqp->tlen = len) + 1); /* * Find the tag, only display missing file messages once, and * then only if we didn't find the tag. */ TAILQ_FOREACH(tfp, exp->tagfq, q) if (ctag_sfile(sp, tfp, tqp, tqp->tag)) { echk = 1; F_SET(tfp, TAGF_ERR); } else F_CLR(tfp, TAGF_ERR | TAGF_ERR_WARN); /* Check to see if we found anything. */ if (TAILQ_EMPTY(tqp->tagq)) { msgq_str(sp, M_ERR, tqp->tag, "162|%s: tag not found"); if (echk) TAILQ_FOREACH(tfp, exp->tagfq, q) if (F_ISSET(tfp, TAGF_ERR) && !F_ISSET(tfp, TAGF_ERR_WARN)) { errno = tfp->errnum; msgq_str(sp, M_SYSERR, tfp->name, "%s"); F_SET(tfp, TAGF_ERR_WARN); } free(tqp); return (NULL); } return (tqp); alloc_err: return (NULL); } /* * ctag_sfile -- * Search a tags file for a tag, adding any found to the tag queue. */ static int ctag_sfile(SCR *sp, TAGF *tfp, TAGQ *tqp, char *tname) { struct stat sb; TAG *tp; size_t dlen, nlen = 0, slen; int fd, i, nf1, nf2; char *back, *front, *map, *p, *search, *t; char *cname = NULL, *dname = NULL, *name = NULL; CHAR_T *wp; size_t wlen; long tl; if ((fd = open(tfp->name, O_RDONLY, 0)) < 0) { tfp->errnum = errno; return (1); } if (fstat(fd, &sb) != 0 || (map = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0)) == MAP_FAILED) { tfp->errnum = errno; (void)close(fd); return (1); } tl = O_VAL(sp, O_TAGLENGTH); front = map; back = front + sb.st_size; front = binary_search(tname, front, back); front = linear_search(tname, front, back, tl); if (front == NULL) goto done; /* * Initialize and link in the tag structure(s). The historic ctags * file format only permitted a single tag location per tag. The * obvious extension to permit multiple tags locations per tag is to * output multiple records in the standard format. Unfortunately, * this won't work correctly with historic ex/vi implementations, * because their binary search assumes that there's only one record * per tag, and so will use a random tag entry if there si more than * one. This code handles either format. * * The tags file is in the following format: * * | * * Figure out how long everything is so we can allocate in one swell * foop, but discard anything that looks wrong. */ for (;;) { /* Nul-terminate the end of the line. */ for (p = front; p < back && *p != '\n'; ++p); if (p == back || *p != '\n') break; *p = '\0'; /* Update the pointers for the next time. */ t = p + 1; p = front; front = t; /* Break the line into tokens. */ for (i = 0; i < 2 && (t = strsep(&p, "\t ")) != NULL; ++i) switch (i) { case 0: /* Tag. */ cname = t; break; case 1: /* Filename. */ name = t; nlen = strlen(name); break; } /* Check for corruption. */ if (i != 2 || p == NULL || t == NULL) goto corrupt; /* The rest of the string is the search pattern. */ search = p; if ((slen = strlen(p)) == 0) { corrupt: p = msg_print(sp, tname, &nf1); t = msg_print(sp, tfp->name, &nf2); msgq(sp, M_ERR, "163|%s: corrupted tag in %s", p, t); if (nf1) FREE_SPACE(sp, p, 0); if (nf2) FREE_SPACE(sp, t, 0); continue; } /* Check for passing the last entry. */ if (tl ? strncmp(tname, cname, tl) : strcmp(tname, cname)) break; /* Resolve the file name. */ ctag_file(sp, tfp, name, &dname, &dlen); - CALLOC_GOTO(sp, tp, - TAG *, 1, sizeof(TAG) + dlen + 2 + nlen + 1 + - (slen + 1) * sizeof(CHAR_T)); + CALLOC_GOTO(sp, tp, 1, + sizeof(TAG) + dlen + 2 + nlen + 1 + (slen + 1) * sizeof(CHAR_T)); tp->fname = (char *)tp->buf; if (dlen == 1 && *dname == '.') --dlen; else if (dlen != 0) { memcpy(tp->fname, dname, dlen); tp->fname[dlen] = '/'; ++dlen; } memcpy(tp->fname + dlen, name, nlen + 1); tp->fnlen = dlen + nlen; tp->search = (CHAR_T*)(tp->fname + tp->fnlen + 1); CHAR2INT(sp, search, slen + 1, wp, wlen); MEMCPY(tp->search, wp, (tp->slen = slen) + 1); TAILQ_INSERT_TAIL(tqp->tagq, tp, q); /* Try to preset the tag within the current file. */ if (sp->frp != NULL && sp->frp->name != NULL && tqp->current == NULL && !strcmp(tp->fname, sp->frp->name)) tqp->current = tp; } if (tqp->current == NULL) tqp->current = TAILQ_FIRST(tqp->tagq); alloc_err: done: if (munmap(map, sb.st_size)) msgq(sp, M_SYSERR, "munmap"); if (close(fd)) msgq(sp, M_SYSERR, "close"); return (0); } /* * ctag_file -- * Search for the right path to this file. */ static void ctag_file(SCR *sp, TAGF *tfp, char *name, char **dirp, size_t *dlenp) { struct stat sb; char *p, *buf; /* * !!! * If the tag file path is a relative path, see if it exists. If it * doesn't, look relative to the tags file path. It's okay for a tag * file to not exist, and historically, vi simply displayed a "new" * file. However, if the path exists relative to the tag file, it's * pretty clear what's happening, so we may as well get it right. */ *dlenp = 0; if (name[0] != '/' && stat(name, &sb) && (p = strrchr(tfp->name, '/')) != NULL) { *p = '\0'; if ((buf = join(tfp->name, name)) == NULL) { msgq(sp, M_SYSERR, NULL); return; } if (stat(buf, &sb) == 0) { *dirp = tfp->name; *dlenp = strlen(*dirp); } free(buf); *p = '/'; } } /* * Binary search for "string" in memory between "front" and "back". * * This routine is expected to return a pointer to the start of a line at * *or before* the first word matching "string". Relaxing the constraint * this way simplifies the algorithm. * * Invariants: * front points to the beginning of a line at or before the first * matching string. * * back points to the beginning of a line at or after the first * matching line. * * Base of the Invariants. * front = NULL; * back = EOF; * * Advancing the Invariants: * * p = first newline after halfway point from front to back. * * If the string at "p" is not greater than the string to match, * p is the new front. Otherwise it is the new back. * * Termination: * * The definition of the routine allows it return at any point, * since front is always at or before the line to print. * * In fact, it returns when the chosen "p" equals "back". This * implies that there exists a string is least half as long as * (back - front), which in turn implies that a linear search will * be no more expensive than the cost of simply printing a string or two. * * Trying to continue with binary search at this point would be * more trouble than it's worth. */ #define EQUAL 0 #define GREATER 1 #define LESS (-1) #define SKIP_PAST_NEWLINE(p, back) \ while (p < back && *p++ != '\n') continue; static char * binary_search(char *string, char *front, char *back) { char *p; p = front + (back - front) / 2; SKIP_PAST_NEWLINE(p, back); while (p != back) { if (compare(string, p, back) == GREATER) front = p; else back = p; p = front + (back - front) / 2; SKIP_PAST_NEWLINE(p, back); } return (front); } /* * Find the first line that starts with string, linearly searching from front * to back. * * Return NULL for no such line. * * This routine assumes: * * o front points at the first character in a line. * o front is before or at the first line to be printed. */ static char * linear_search(char *string, char *front, char *back, long tl) { char *end; while (front < back) { end = tl && back-front > tl ? front+tl : back; switch (compare(string, front, end)) { case EQUAL: /* Found it. */ return (front); case LESS: /* No such string. */ return (NULL); case GREATER: /* Keep going. */ break; } SKIP_PAST_NEWLINE(front, back); } return (NULL); } /* * Return LESS, GREATER, or EQUAL depending on how the string1 compares * with string2 (s1 ??? s2). * * o Matches up to len(s1) are EQUAL. * o Matches up to len(s2) are GREATER. * * The string "s1" is null terminated. The string s2 is '\t', space, (or * "back") terminated. * * !!! * Reasonably modern ctags programs use tabs as separators, not spaces. * However, historic programs did use spaces, and, I got complaints. */ static int compare(char *s1, char *s2, char *back) { for (; *s1 && s2 < back && (*s2 != '\t' && *s2 != ' '); ++s1, ++s2) if (*s1 != *s2) return (*s1 < *s2 ? LESS : GREATER); return (*s1 ? GREATER : s2 < back && (*s2 != '\t' && *s2 != ' ') ? LESS : EQUAL); } Index: head/contrib/nvi/ex/ex_txt.c =================================================================== --- head/contrib/nvi/ex/ex_txt.c (revision 365498) +++ head/contrib/nvi/ex/ex_txt.c (revision 365499) @@ -1,425 +1,425 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_txt.c,v 10.23 2001/06/25 15:19:21 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" /* * !!! * The backslash characters was special when it preceded a newline as part of * a substitution replacement pattern. For example, the input ":a\" would * failed immediately with an error, as the wasn't part of a substitution * replacement pattern. This implies a frightening integration of the editor * and the parser and/or the RE engine. There's no way I'm going to reproduce * those semantics. * * So, if backslashes are special, this code inserts the backslash and the next * character into the string, without regard for the character or the command * being entered. Since "\" was illegal historically (except for the one * special case), and the command will fail eventually, no historical scripts * should break (presuming they didn't depend on the failure mode itself or the * characters remaining when failure occurred. */ static int txt_dent(SCR *, TEXT *); static void txt_prompt(SCR *, TEXT *, ARG_CHAR_T, u_int32_t); /* * ex_txt -- * Get lines from the terminal for ex. * * PUBLIC: int ex_txt(SCR *, TEXTH *, ARG_CHAR_T, u_int32_t); */ int ex_txt(SCR *sp, TEXTH *tiqh, ARG_CHAR_T prompt, u_int32_t flags) { EVENT ev; GS *gp; TEXT ait, *ntp, *tp; carat_t carat_st; size_t cnt; int rval; int nochange; rval = 0; /* * Get a TEXT structure with some initial buffer space, reusing the * last one if it's big enough. (All TEXT bookkeeping fields default * to 0 -- text_init() handles this.) */ if (!TAILQ_EMPTY(tiqh)) { tp = TAILQ_FIRST(tiqh); if (TAILQ_NEXT(tp, q) != NULL || tp->lb_len < 32) { text_lfree(tiqh); goto newtp; } tp->len = 0; } else { newtp: if ((tp = text_init(sp, NULL, 0, 32)) == NULL) goto err; TAILQ_INSERT_HEAD(tiqh, tp, q); } /* Set the starting line number. */ tp->lno = sp->lno + 1; /* * If it's a terminal, set up autoindent, put out the prompt, and * set it up so we know we were suspended. Otherwise, turn off * the autoindent flag, as that requires less special casing below. * * XXX * Historic practice is that ^Z suspended command mode (but, because * it ran in cooked mode, it was unaffected by the autowrite option.) * On restart, any "current" input was discarded, whether in insert * mode or not, and ex was in command mode. This code matches historic * practice, but not 'cause it's easier. */ gp = sp->gp; if (F_ISSET(gp, G_SCRIPTED)) LF_CLR(TXT_AUTOINDENT); else { if (LF_ISSET(TXT_AUTOINDENT)) { LF_SET(TXT_EOFCHAR); if (v_txt_auto(sp, sp->lno, NULL, 0, tp)) goto err; } txt_prompt(sp, tp, prompt, flags); } for (carat_st = C_NOTSET, nochange = 0;;) { if (v_event_get(sp, &ev, 0, 0)) goto err; /* Deal with all non-character events. */ switch (ev.e_event) { case E_CHARACTER: break; case E_ERR: goto err; case E_REPAINT: case E_WRESIZE: continue; case E_EOF: rval = 1; /* FALLTHROUGH */ case E_INTERRUPT: /* * Handle EOF/SIGINT events by discarding partially * entered text and returning. EOF returns failure, * E_INTERRUPT returns success. */ goto notlast; default: v_event_err(sp, &ev); goto notlast; } /* * Deal with character events. * * Check to see if the character fits into the input buffer. * (Use tp->len, ignore overwrite and non-printable chars.) */ BINC_GOTOW(sp, tp->lb, tp->lb_len, tp->len + 1); switch (ev.e_value) { case K_CR: /* * !!! * Historically, 's in the command * weren't special, so the ex parser would return an * unknown command error message. However, if they * terminated the command if they were in a map. I'm * pretty sure this still isn't right, but it handles * what I've seen so far. */ if (!F_ISSET(&ev.e_ch, CH_MAPPED)) goto ins_ch; /* FALLTHROUGH */ case K_NL: /* * '\' can escape /. We * don't discard the backslash because we need it * to get the through the ex parser. */ if (LF_ISSET(TXT_BACKSLASH) && tp->len != 0 && tp->lb[tp->len - 1] == '\\') goto ins_ch; /* * CR returns from the ex command line. * * XXX * Terminate with a nul, needed by filter. */ if (LF_ISSET(TXT_CR)) { tp->lb[tp->len] = '\0'; goto done; } /* * '.' may terminate text input mode; free the current * TEXT. */ if (LF_ISSET(TXT_DOTTERM) && tp->len == tp->ai + 1 && tp->lb[tp->len - 1] == '.') { notlast: TAILQ_REMOVE(tiqh, tp, q); text_free(tp); goto done; } /* Set up bookkeeping for the new line. */ if ((ntp = text_init(sp, NULL, 0, 32)) == NULL) goto err; ntp->lno = tp->lno + 1; /* * Reset the autoindent line value. 0^D keeps the ai * line from changing, ^D changes the level, even if * there were no characters in the old line. Note, if * using the current tp structure, use the cursor as * the length, the autoindent characters may have been * erased. */ if (LF_ISSET(TXT_AUTOINDENT)) { if (nochange) { nochange = 0; if (v_txt_auto(sp, OOBLNO, &ait, ait.ai, ntp)) goto err; free(ait.lb); } else if (v_txt_auto(sp, OOBLNO, tp, tp->len, ntp)) goto err; carat_st = C_NOTSET; } txt_prompt(sp, ntp, prompt, flags); /* * Swap old and new TEXT's, and insert the new TEXT * into the queue. */ tp = ntp; TAILQ_INSERT_TAIL(tiqh, tp, q); break; case K_CARAT: /* Delete autoindent chars. */ if (tp->len <= tp->ai && LF_ISSET(TXT_AUTOINDENT)) carat_st = C_CARATSET; goto ins_ch; case K_ZERO: /* Delete autoindent chars. */ if (tp->len <= tp->ai && LF_ISSET(TXT_AUTOINDENT)) carat_st = C_ZEROSET; goto ins_ch; case K_CNTRLD: /* Delete autoindent char. */ /* * !!! * Historically, the ^D command took (but then ignored) * a count. For simplicity, we don't return it unless * it's the first character entered. The check for len * equal to 0 is okay, TXT_AUTOINDENT won't be set. */ if (LF_ISSET(TXT_CNTRLD)) { for (cnt = 0; cnt < tp->len; ++cnt) if (!isblank(tp->lb[cnt])) break; if (cnt == tp->len) { tp->len = 1; tp->lb[0] = ev.e_c; tp->lb[1] = '\0'; /* * Put out a line separator, in case * the command fails. */ (void)putchar('\n'); goto done; } } /* * POSIX 1003.1b-1993, paragraph 7.1.1.9, states that * the EOF characters are discarded if there are other * characters to process in the line, i.e. if the EOF * is not the first character in the line. For this * reason, historic ex discarded the EOF characters, * even if occurring in the middle of the input line. * We match that historic practice. * * !!! * The test for discarding in the middle of the line is * done in the switch, because the CARAT forms are N+1, * not N. * * !!! * There's considerable magic to make the terminal code * return the EOF character at all. See that code for * details. */ if (!LF_ISSET(TXT_AUTOINDENT) || tp->len == 0) continue; switch (carat_st) { case C_CARATSET: /* ^^D */ if (tp->len > tp->ai + 1) continue; /* Save the ai string for later. */ ait.lb = NULL; ait.lb_len = 0; BINC_GOTOW(sp, ait.lb, ait.lb_len, tp->ai); MEMCPY(ait.lb, tp->lb, tp->ai); ait.ai = ait.len = tp->ai; carat_st = C_NOTSET; nochange = 1; goto leftmargin; case C_ZEROSET: /* 0^D */ if (tp->len > tp->ai + 1) continue; carat_st = C_NOTSET; leftmargin: (void)gp->scr_ex_adjust(sp, EX_TERM_CE); tp->ai = tp->len = 0; break; case C_NOTSET: /* ^D */ if (tp->len > tp->ai) continue; if (txt_dent(sp, tp)) goto err; break; default: abort(); } /* Clear and redisplay the line. */ (void)gp->scr_ex_adjust(sp, EX_TERM_CE); txt_prompt(sp, tp, prompt, flags); break; default: /* * See the TXT_BEAUTIFY comment in vi/v_txt_ev.c. * * Silently eliminate any iscntrl() character that was * not already handled specially, except for and * . */ ins_ch: if (LF_ISSET(TXT_BEAUTIFY) && ISCNTRL(ev.e_c) && ev.e_value != K_FORMFEED && ev.e_value != K_TAB) break; tp->lb[tp->len++] = ev.e_c; break; } } /* NOTREACHED */ done: return (rval); err: alloc_err: return (1); } /* * txt_prompt -- * Display the ex prompt, line number, ai characters. Characters had * better be printable by the terminal driver, but that's its problem, * not ours. */ static void txt_prompt(SCR *sp, TEXT *tp, ARG_CHAR_T prompt, u_int32_t flags) { /* Display the prompt. */ if (LF_ISSET(TXT_PROMPT)) (void)ex_printf(sp, "%c", prompt); /* Display the line number. */ if (LF_ISSET(TXT_NUMBER) && O_ISSET(sp, O_NUMBER)) (void)ex_printf(sp, "%6lu ", (u_long)tp->lno); /* Print out autoindent string. */ if (LF_ISSET(TXT_AUTOINDENT)) (void)ex_printf(sp, WVS, (int)tp->ai, tp->lb); (void)ex_fflush(sp); } /* * txt_dent -- * Handle ^D outdents. * * Ex version of vi/v_ntext.c:txt_dent(). See that code for the (usual) * ranting and raving. This is a fair bit simpler as ^T isn't special. */ static int txt_dent(SCR *sp, TEXT *tp) { u_long sw, ts; size_t cno, off, scno, spaces, tabs; ts = O_VAL(sp, O_TABSTOP); sw = O_VAL(sp, O_SHIFTWIDTH); /* Get the current screen column. */ for (off = scno = 0; off < tp->len; ++off) if (tp->lb[off] == '\t') scno += COL_OFF(scno, ts); else ++scno; /* Get the previous shiftwidth column. */ cno = scno--; scno -= scno % sw; /* * Since we don't know what comes before the character(s) being * deleted, we have to resolve the autoindent characters . The * example is a , which doesn't take up a full shiftwidth * number of columns because it's preceded by s. This is * easy to get if the user sets shiftwidth to a value less than * tabstop, and then uses ^T to indent, and ^D to outdent. * * Count up spaces/tabs needed to get to the target. */ - for (cno = 0, tabs = 0; cno + COL_OFF(cno, ts) <= scno; ++tabs) - cno += COL_OFF(cno, ts); + cno = 0; + tabs = 0; + if (!O_ISSET(sp, O_EXPANDTAB)) { + for (; cno + COL_OFF(cno, ts) <= scno; ++tabs) + cno += COL_OFF(cno, ts); + } spaces = scno - cno; /* Make sure there's enough room. */ BINC_RETW(sp, tp->lb, tp->lb_len, tabs + spaces + 1); /* Adjust the final ai character count. */ tp->ai = tabs + spaces; /* Enter the replacement characters. */ for (tp->len = 0; tabs > 0; --tabs) tp->lb[tp->len++] = '\t'; for (; spaces > 0; --spaces) tp->lb[tp->len++] = ' '; return (0); } Index: head/contrib/nvi/ex/ex_undo.c =================================================================== --- head/contrib/nvi/ex/ex_undo.c (revision 365498) +++ head/contrib/nvi/ex/ex_undo.c (revision 365499) @@ -1,76 +1,72 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_undo.c,v 10.7 2001/06/25 15:19:21 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_undo -- u * Undo the last change. * * PUBLIC: int ex_undo(SCR *, EXCMD *); */ int ex_undo(SCR *sp, EXCMD *cmdp) { EXF *ep; MARK m; /* * !!! * Historic undo always set the previous context mark. */ m.lno = sp->lno; m.cno = sp->cno; if (mark_set(sp, ABSMARK1, &m, 1)) return (1); /* * !!! * Multiple undo isn't available in ex, as there's no '.' command. * Whether 'u' is undo or redo is toggled each time, unless there * was a change since the last undo, in which case it's an undo. */ ep = sp->ep; if (!F_ISSET(ep, F_UNDO)) { F_SET(ep, F_UNDO); ep->lundo = FORWARD; } switch (ep->lundo) { case BACKWARD: if (log_forward(sp, &m)) return (1); ep->lundo = FORWARD; break; case FORWARD: if (log_backward(sp, &m)) return (1); ep->lundo = BACKWARD; break; case NOTSET: abort(); } sp->lno = m.lno; sp->cno = m.cno; return (0); } Index: head/contrib/nvi/ex/ex_usage.c =================================================================== --- head/contrib/nvi/ex/ex_usage.c (revision 365498) +++ head/contrib/nvi/ex/ex_usage.c (revision 365499) @@ -1,190 +1,187 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_usage.c,v 10.16 2011/12/21 19:26:48 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" /* * ex_help -- :help * Display help message. * * PUBLIC: int ex_help(SCR *, EXCMD *); */ int ex_help(SCR *sp, EXCMD *cmdp) { (void)ex_puts(sp, "To see the list of vi commands, enter \":viusage\"\n"); (void)ex_puts(sp, "To see the list of ex commands, enter \":exusage\"\n"); (void)ex_puts(sp, "For an ex command usage statement enter \":exusage [cmd]\"\n"); (void)ex_puts(sp, "For a vi key usage statement enter \":viusage [key]\"\n"); (void)ex_puts(sp, "To exit, enter \":q!\"\n"); return (0); } /* * ex_usage -- :exusage [cmd] * Display ex usage strings. * * PUBLIC: int ex_usage(SCR *, EXCMD *); */ int ex_usage(SCR *sp, EXCMD *cmdp) { ARGS *ap; EXCMDLIST const *cp; int newscreen; CHAR_T *name, *p, nb[MAXCMDNAMELEN + 5]; switch (cmdp->argc) { case 1: ap = cmdp->argv[0]; if (ISUPPER(ap->bp[0])) { newscreen = 1; ap->bp[0] = TOLOWER(ap->bp[0]); } else newscreen = 0; for (cp = cmds; cp->name != NULL && MEMCMP(ap->bp, cp->name, ap->len); ++cp); if (cp->name == NULL || (newscreen && !F_ISSET(cp, E_NEWSCREEN))) { if (newscreen) ap->bp[0] = TOUPPER(ap->bp[0]); (void)ex_printf(sp, "The "WVS" command is unknown\n", (int)ap->len, ap->bp); } else { (void)ex_printf(sp, "Command: %s\n Usage: %s\n", cp->help, cp->usage); /* * !!! * The "visual" command has two modes, one from ex, * one from the vi colon line. Don't ask. */ if (cp != &cmds[C_VISUAL_EX] && cp != &cmds[C_VISUAL_VI]) break; if (cp == &cmds[C_VISUAL_EX]) cp = &cmds[C_VISUAL_VI]; else cp = &cmds[C_VISUAL_EX]; (void)ex_printf(sp, "Command: %s\n Usage: %s\n", cp->help, cp->usage); } break; case 0: for (cp = cmds; cp->name != NULL && !INTERRUPTED(sp); ++cp) { /* * The ^D command has an unprintable name. * * XXX * We display both capital and lower-case versions of * the appropriate commands -- no need to add in extra * room, they're all short names. */ if (cp == &cmds[C_SCROLL]) name = L("^D"); else if (F_ISSET(cp, E_NEWSCREEN)) { nb[0] = '['; nb[1] = TOUPPER(cp->name[0]); nb[2] = cp->name[0]; nb[3] = ']'; for (name = cp->name + 1, p = nb + 4; (*p++ = *name++) != '\0';); name = nb; } else name = cp->name; (void)ex_printf(sp, WVS": %s\n", MAXCMDNAMELEN, name, cp->help); } break; default: abort(); } return (0); } /* * ex_viusage -- :viusage [key] * Display vi usage strings. * * PUBLIC: int ex_viusage(SCR *, EXCMD *); */ int ex_viusage(SCR *sp, EXCMD *cmdp) { GS *gp; VIKEYS const *kp; int key; gp = sp->gp; switch (cmdp->argc) { case 1: if (cmdp->argv[0]->len != 1) { ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } key = cmdp->argv[0]->bp[0]; if (key > MAXVIKEY) goto nokey; /* Special case: '[' and ']' commands. */ if ((key == '[' || key == ']') && cmdp->argv[0]->bp[1] != key) goto nokey; /* Special case: ~ command. */ if (key == '~' && O_ISSET(sp, O_TILDEOP)) kp = &tmotion; else kp = &vikeys[key]; if (kp->usage == NULL) nokey: (void)ex_printf(sp, "The %s key has no current meaning\n", KEY_NAME(sp, key)); else (void)ex_printf(sp, " Key:%s%s\nUsage: %s\n", - isblank(*kp->help) ? "" : " ", kp->help, kp->usage); + isblank((u_char)*kp->help) ? "" : " ", + kp->help, kp->usage); break; case 0: for (key = 0; key <= MAXVIKEY && !INTERRUPTED(sp); ++key) { /* Special case: ~ command. */ if (key == '~' && O_ISSET(sp, O_TILDEOP)) kp = &tmotion; else kp = &vikeys[key]; if (kp->help != NULL) (void)ex_printf(sp, "%s\n", kp->help); } break; default: abort(); } return (0); } Index: head/contrib/nvi/ex/ex_util.c =================================================================== --- head/contrib/nvi/ex/ex_util.c (revision 365498) +++ head/contrib/nvi/ex/ex_util.c (revision 365499) @@ -1,216 +1,212 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_util.c,v 10.32 2001/06/25 15:19:21 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_cinit -- * Create an EX command structure. * * PUBLIC: void ex_cinit(SCR *, EXCMD *, int, int, recno_t, recno_t, int); */ void ex_cinit(SCR *sp, EXCMD *cmdp, int cmd_id, int naddr, recno_t lno1, recno_t lno2, int force) { memset(cmdp, 0, sizeof(EXCMD)); cmdp->cmd = &cmds[cmd_id]; cmdp->addrcnt = naddr; cmdp->addr1.lno = lno1; cmdp->addr2.lno = lno2; cmdp->addr1.cno = cmdp->addr2.cno = 1; if (force) cmdp->iflags |= E_C_FORCE; (void)argv_init(sp, cmdp); } /* * ex_getline -- * Return a line from the file. * * PUBLIC: int ex_getline(SCR *, FILE *, size_t *); */ int ex_getline(SCR *sp, FILE *fp, size_t *lenp) { EX_PRIVATE *exp; size_t off; int ch; char *p; exp = EXP(sp); for (errno = 0, off = 0, p = exp->ibp;;) { if (off >= exp->ibp_len) { BINC_RETC(sp, exp->ibp, exp->ibp_len, off + 1); p = exp->ibp + off; } if ((ch = getc(fp)) == EOF && !feof(fp)) { if (errno == EINTR) { errno = 0; clearerr(fp); continue; } return (1); } if (ch == EOF || ch == '\n') { if (ch == EOF && !off) return (1); *lenp = off; return (0); } *p++ = ch; ++off; } /* NOTREACHED */ } /* * ex_ncheck -- * Check for more files to edit. * * PUBLIC: int ex_ncheck(SCR *, int); */ int ex_ncheck(SCR *sp, int force) { char **ap; /* * !!! * Historic practice: quit! or two quit's done in succession * (where ZZ counts as a quit) didn't check for other files. */ if (!force && sp->ccnt != sp->q_ccnt + 1 && sp->cargv != NULL && sp->cargv[1] != NULL) { sp->q_ccnt = sp->ccnt; for (ap = sp->cargv + 1; *ap != NULL; ++ap); msgq(sp, M_ERR, "167|%d more files to edit", (int)(ap - sp->cargv) - 1); return (1); } return (0); } /* * ex_init -- * Init the screen for ex. * * PUBLIC: int ex_init(SCR *); */ int ex_init(SCR *sp) { GS *gp; gp = sp->gp; if (gp->scr_screen(sp, SC_EX)) return (1); (void)gp->scr_attr(sp, SA_ALTERNATE, 0); sp->rows = O_VAL(sp, O_LINES); sp->cols = O_VAL(sp, O_COLUMNS); F_CLR(sp, SC_VI); F_SET(sp, SC_EX | SC_SCR_EX); return (0); } /* * ex_emsg -- * Display a few common ex and vi error messages. * * PUBLIC: void ex_wemsg(SCR *, CHAR_T *, exm_t); */ void ex_wemsg(SCR* sp, CHAR_T *p, exm_t which) { char *np; size_t nlen; if (p) INT2CHAR(sp, p, STRLEN(p), np, nlen); else np = NULL; ex_emsg(sp, np, which); } /* * ex_emsg -- * Display a few common ex and vi error messages. * * PUBLIC: void ex_emsg(SCR *, char *, exm_t); */ void ex_emsg(SCR *sp, char *p, exm_t which) { switch (which) { case EXM_EMPTYBUF: msgq(sp, M_ERR, "168|Buffer %s is empty", p); break; case EXM_FILECOUNT: msgq_str(sp, M_ERR, p, "144|%s: expanded into too many file names"); break; case EXM_NOCANON: msgq(sp, M_ERR, "283|The %s command requires the ex terminal interface", p); break; case EXM_NOCANON_F: msgq(sp, M_ERR, "272|That form of %s requires the ex terminal interface", p); break; case EXM_NOFILEYET: if (p == NULL) msgq(sp, M_ERR, "274|Command failed, no file read in yet."); else msgq(sp, M_ERR, "173|The %s command requires that a file have already been read in", p); break; case EXM_NOPREVBUF: msgq(sp, M_ERR, "171|No previous buffer to execute"); break; case EXM_NOPREVRE: msgq(sp, M_ERR, "172|No previous regular expression"); break; case EXM_NOSUSPEND: msgq(sp, M_ERR, "230|This screen may not be suspended"); break; case EXM_SECURE: msgq(sp, M_ERR, "290|The %s command is not supported when the secure edit option is set", p); break; case EXM_SECURE_F: msgq(sp, M_ERR, "284|That form of %s is not supported when the secure edit option is set", p); break; case EXM_USAGE: msgq(sp, M_ERR, "174|Usage: %s", p); break; } } Index: head/contrib/nvi/ex/ex_version.c =================================================================== --- head/contrib/nvi/ex/ex_version.c (revision 365498) +++ head/contrib/nvi/ex/ex_version.c (revision 365499) @@ -1,39 +1,35 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_version.c,v 10.32 2001/06/25 15:19:22 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" #include "version.h" /* * ex_version -- :version * Display the program version. * * PUBLIC: int ex_version(SCR *, EXCMD *); */ int ex_version(SCR *sp, EXCMD *cmdp) { msgq(sp, M_INFO, "Version "VI_VERSION " The CSRG, University of California, Berkeley."); return (0); } Index: head/contrib/nvi/ex/ex_visual.c =================================================================== --- head/contrib/nvi/ex/ex_visual.c (revision 365498) +++ head/contrib/nvi/ex/ex_visual.c (revision 365499) @@ -1,163 +1,159 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_visual.c,v 10.16 2001/08/29 11:04:13 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" /* * ex_visual -- :[line] vi[sual] [^-.+] [window_size] [flags] * Switch to visual mode. * * PUBLIC: int ex_visual(SCR *, EXCMD *); */ int ex_visual(SCR *sp, EXCMD *cmdp) { SCR *tsp; size_t len; int pos; char buf[256]; size_t wlen; CHAR_T *wp; /* If open option off, disallow visual command. */ if (!O_ISSET(sp, O_OPEN)) { msgq(sp, M_ERR, "175|The visual command requires that the open option be set"); return (1); } /* Move to the address. */ sp->lno = cmdp->addr1.lno == 0 ? 1 : cmdp->addr1.lno; /* * Push a command based on the line position flags. If no * flag specified, the line goes at the top of the screen. */ switch (FL_ISSET(cmdp->iflags, E_C_CARAT | E_C_DASH | E_C_DOT | E_C_PLUS)) { case E_C_CARAT: pos = '^'; break; case E_C_DASH: pos = '-'; break; case E_C_DOT: pos = '.'; break; case E_C_PLUS: pos = '+'; break; default: sp->frp->lno = sp->lno; sp->frp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); F_SET(sp->frp, FR_CURSORSET); goto nopush; } if (FL_ISSET(cmdp->iflags, E_C_COUNT)) len = snprintf(buf, sizeof(buf), "%luz%c%lu", (u_long)sp->lno, pos, cmdp->count); else len = snprintf(buf, sizeof(buf), "%luz%c", (u_long)sp->lno, pos); CHAR2INT(sp, buf, len, wp, wlen); (void)v_event_push(sp, NULL, wp, wlen, CH_NOMAP | CH_QUOTED); /* * !!! * Historically, if no line address was specified, the [p#l] flags * caused the cursor to be moved to the last line of the file, which * was then positioned as described above. This seems useless, so * I haven't implemented it. */ switch (FL_ISSET(cmdp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT)) { case E_C_HASH: O_SET(sp, O_NUMBER); break; case E_C_LIST: O_SET(sp, O_LIST); break; case E_C_PRINT: break; } nopush: /* * !!! * You can call the visual part of the editor from within an ex * global command. * * XXX * Historically, undoing a visual session was a single undo command, * i.e. you could undo all of the changes you made in visual mode. * We don't get this right; I'm waiting for the new logging code to * be available. * * It's explicit, don't have to wait for the user, unless there's * already a reason to wait. */ if (!F_ISSET(sp, SC_SCR_EXWROTE)) F_SET(sp, SC_EX_WAIT_NO); if (F_ISSET(sp, SC_EX_GLOBAL)) { /* * When the vi screen(s) exit, we don't want to lose our hold * on this screen or this file, otherwise we're going to fail * fairly spectacularly. */ ++sp->refcnt; ++sp->ep->refcnt; /* XXXX where is this decremented ? */ /* * Fake up a screen pointer -- vi doesn't get to change our * underlying file, regardless. */ tsp = sp; if (vi(&tsp)) return (1); /* * !!! * Historically, if the user exited the vi screen(s) using an * ex quit command (e.g. :wq, :q) ex/vi exited, it was only if * they exited vi using the Q command that ex continued. Some * early versions of nvi continued in ex regardless, but users * didn't like the semantic. * * Reset the screen. */ if (ex_init(sp)) return (1); /* Move out of the vi screen. */ (void)ex_puts(sp, "\n"); } else { F_CLR(sp, SC_EX | SC_SCR_EX); F_SET(sp, SC_VI); } return (0); } Index: head/contrib/nvi/ex/ex_write.c =================================================================== --- head/contrib/nvi/ex/ex_write.c (revision 365498) +++ head/contrib/nvi/ex/ex_write.c (revision 365499) @@ -1,375 +1,371 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_write.c,v 10.43 2015/04/03 15:18:45 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" enum which {WN, WQ, WRITE, XIT}; static int exwr(SCR *, EXCMD *, enum which); /* * ex_wn -- :wn[!] [>>] [file] * Write to a file and switch to the next one. * * PUBLIC: int ex_wn(SCR *, EXCMD *); */ int ex_wn(SCR *sp, EXCMD *cmdp) { if (exwr(sp, cmdp, WN)) return (1); if (file_m3(sp, 0)) return (1); /* The file name isn't a new file to edit. */ cmdp->argc = 0; return (ex_next(sp, cmdp)); } /* * ex_wq -- :wq[!] [>>] [file] * Write to a file and quit. * * PUBLIC: int ex_wq(SCR *, EXCMD *); */ int ex_wq(SCR *sp, EXCMD *cmdp) { int force; if (exwr(sp, cmdp, WQ)) return (1); if (file_m3(sp, 0)) return (1); force = FL_ISSET(cmdp->iflags, E_C_FORCE); if (ex_ncheck(sp, force)) return (1); F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT); return (0); } /* * ex_write -- :write[!] [>>] [file] * :write [!] [cmd] * Write to a file. * * PUBLIC: int ex_write(SCR *, EXCMD *); */ int ex_write(SCR *sp, EXCMD *cmdp) { return (exwr(sp, cmdp, WRITE)); } /* * ex_xit -- :x[it]! [file] * Write out any modifications and quit. * * PUBLIC: int ex_xit(SCR *, EXCMD *); */ int ex_xit(SCR *sp, EXCMD *cmdp) { int force; NEEDFILE(sp, cmdp); if (F_ISSET(sp->ep, F_MODIFIED) && exwr(sp, cmdp, XIT)) return (1); if (file_m3(sp, 0)) return (1); force = FL_ISSET(cmdp->iflags, E_C_FORCE); if (ex_ncheck(sp, force)) return (1); F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT); return (0); } /* * exwr -- * The guts of the ex write commands. */ static int exwr(SCR *sp, EXCMD *cmdp, enum which cmd) { MARK rm; int flags; char *name; CHAR_T *p = NULL; size_t nlen; char *n; int rc; EX_PRIVATE *exp; NEEDFILE(sp, cmdp); /* All write commands can have an associated '!'. */ LF_INIT(FS_POSSIBLE); if (FL_ISSET(cmdp->iflags, E_C_FORCE)) LF_SET(FS_FORCE); /* Skip any leading whitespace. */ if (cmdp->argc != 0) for (p = cmdp->argv[0]->bp; *p != '\0' && cmdskip(*p); ++p); /* If "write !" it's a pipe to a utility. */ if (cmdp->argc != 0 && cmd == WRITE && *p == '!') { /* Secure means no shell access. */ if (O_ISSET(sp, O_SECURE)) { ex_wemsg(sp, cmdp->cmd->name, EXM_SECURE_F); return (1); } /* Expand the argument. */ for (++p; *p && cmdskip(*p); ++p); if (*p == '\0') { ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } if (argv_exp1(sp, cmdp, p, STRLEN(p), 1)) return (1); /* Set the last bang command */ exp = EXP(sp); free(exp->lastbcomm); exp->lastbcomm = v_wstrdup(sp, cmdp->argv[1]->bp, cmdp->argv[1]->len); /* * Historically, vi waited after a write filter even if there * wasn't any output from the command. People complained when * nvi waited only if there was output, wanting the visual cue * that the program hadn't written anything. */ F_SET(sp, SC_EX_WAIT_YES); /* * !!! * Ignore the return cursor position, the cursor doesn't * move. */ if (ex_filter(sp, cmdp, &cmdp->addr1, &cmdp->addr2, &rm, cmdp->argv[1]->bp, FILTER_WRITE)) return (1); /* Ex terminates with a bang, even if the command fails. */ if (!F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_EX_SILENT)) (void)ex_puts(sp, "!\n"); return (0); } /* Set the FS_ALL flag if we're writing the entire file. */ if (cmdp->addr1.lno <= 1 && !db_exist(sp, cmdp->addr2.lno + 1)) LF_SET(FS_ALL); /* If "write >>" it's an append to a file. */ if (cmdp->argc != 0 && cmd != XIT && p[0] == '>' && p[1] == '>') { LF_SET(FS_APPEND); /* Skip ">>" and whitespace. */ for (p += 2; *p && cmdskip(*p); ++p); } /* If no other arguments, just write the file back. */ if (cmdp->argc == 0 || *p == '\0') return (file_write(sp, &cmdp->addr1, &cmdp->addr2, NULL, flags)); /* Build an argv so we get an argument count and file expansion. */ if (argv_exp2(sp, cmdp, p, STRLEN(p))) return (1); /* * 0 args: impossible. * 1 args: impossible (I hope). * 2 args: read it. * >2 args: object, too many args. * * The 1 args case depends on the argv_sexp() function refusing * to return success without at least one non-blank character. */ switch (cmdp->argc) { case 0: case 1: abort(); /* NOTREACHED */ case 2: INT2CHAR(sp, cmdp->argv[1]->bp, cmdp->argv[1]->len+1, n, nlen); name = v_strdup(sp, n, nlen - 1); /* * !!! * Historically, the read and write commands renamed * "unnamed" files, or, if the file had a name, set * the alternate file name. */ if (F_ISSET(sp->frp, FR_TMPFILE) && !F_ISSET(sp->frp, FR_EXNAMED)) { if ((n = v_strdup(sp, name, nlen - 1)) != NULL) { free(sp->frp->name); sp->frp->name = n; } /* * The file has a real name, it's no longer a * temporary, clear the temporary file flags. * * !!! * If we're writing the whole file, FR_NAMECHANGE * will be cleared by the write routine -- this is * historic practice. */ F_CLR(sp->frp, FR_TMPEXIT | FR_TMPFILE); F_SET(sp->frp, FR_NAMECHANGE | FR_EXNAMED); /* Notify the screen. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); } else set_alt_name(sp, name); break; default: INT2CHAR(sp, p, STRLEN(p) + 1, n, nlen); ex_emsg(sp, n, EXM_FILECOUNT); return (1); } rc = file_write(sp, &cmdp->addr1, &cmdp->addr2, name, flags); free(name); return rc; } /* * ex_writefp -- * Write a range of lines to a FILE *. * * PUBLIC: int ex_writefp(SCR *, * PUBLIC: char *, FILE *, MARK *, MARK *, u_long *, u_long *, int); */ int ex_writefp(SCR *sp, char *name, FILE *fp, MARK *fm, MARK *tm, u_long *nlno, u_long *nch, int silent) { struct stat sb; GS *gp; u_long ccnt; /* XXX: can't print off_t portably. */ recno_t fline, tline, lcnt; size_t len; int rval; char *msg, *p; gp = sp->gp; fline = fm->lno; tline = tm->lno; if (nlno != NULL) { *nch = 0; *nlno = 0; } /* * The vi filter code has multiple processes running simultaneously, * and one of them calls ex_writefp(). The "unsafe" function calls * in this code are to db_get() and msgq(). Db_get() is safe, see * the comment in ex_filter.c:ex_filter() for details. We don't call * msgq if the multiple process bit in the EXF is set. * * !!! * Historic vi permitted files of 0 length to be written. However, * since the way vi got around dealing with "empty" files was to * always have a line in the file no matter what, it wrote them as * files of a single, empty line. We write empty files. * * "Alex, I'll take vi trivia for $1000." */ ccnt = 0; lcnt = 0; msg = "253|Writing..."; if (tline != 0) for (; fline <= tline; ++fline, ++lcnt) { /* Caller has to provide any interrupt message. */ if ((lcnt + 1) % INTERRUPT_CHECK == 0) { if (INTERRUPTED(sp)) break; if (!silent) { gp->scr_busy(sp, msg, msg == NULL ? BUSY_UPDATE : BUSY_ON); msg = NULL; } } if (db_rget(sp, fline, &p, &len)) goto err; if (fwrite(p, 1, len, fp) != len) goto err; ccnt += len; if (putc('\n', fp) != '\n') break; ++ccnt; } if (fflush(fp)) goto err; /* * XXX * I don't trust NFS -- check to make sure that we're talking to * a regular file and sync so that NFS is forced to flush. */ if (!fstat(fileno(fp), &sb) && S_ISREG(sb.st_mode) && fsync(fileno(fp))) goto err; if (fclose(fp)) goto err; rval = 0; if (0) { err: if (!F_ISSET(sp->ep, F_MULTILOCK)) msgq_str(sp, M_SYSERR, name, "%s"); (void)fclose(fp); rval = 1; } if (!silent) gp->scr_busy(sp, NULL, BUSY_OFF); /* Report the possibly partial transfer. */ if (nlno != NULL) { *nch = ccnt; *nlno = lcnt; } return (rval); } Index: head/contrib/nvi/ex/ex_yank.c =================================================================== --- head/contrib/nvi/ex/ex_yank.c (revision 365498) +++ head/contrib/nvi/ex/ex_yank.c (revision 365499) @@ -1,45 +1,41 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_yank.c,v 10.8 2001/06/25 15:19:22 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" /* * ex_yank -- :[line [,line]] ya[nk] [buffer] [count] * Yank the lines into a buffer. * * PUBLIC: int ex_yank(SCR *, EXCMD *); */ int ex_yank(SCR *sp, EXCMD *cmdp) { NEEDFILE(sp, cmdp); /* * !!! * Historically, yanking lines in ex didn't count toward the * number-of-lines-yanked report. */ return (cut(sp, FL_ISSET(cmdp->iflags, E_C_BUFFER) ? &cmdp->buffer : NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE)); } Index: head/contrib/nvi/ex/ex_z.c =================================================================== --- head/contrib/nvi/ex/ex_z.c (revision 365498) +++ head/contrib/nvi/ex/ex_z.c (revision 365499) @@ -1,145 +1,141 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: ex_z.c,v 10.12 2001/06/25 15:19:22 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_z -- :[line] z [^-.+=] [count] [flags] * Adjust window. * * PUBLIC: int ex_z(SCR *, EXCMD *); */ int ex_z(SCR *sp, EXCMD *cmdp) { MARK abs; recno_t cnt, equals, lno; int eofcheck; NEEDFILE(sp, cmdp); /* * !!! * If no count specified, use either two times the size of the * scrolling region, or the size of the window option. POSIX * 1003.2 claims that the latter is correct, but historic ex/vi * documentation and practice appear to use the scrolling region. * I'm using the window size as it means that the entire screen * is used instead of losing a line to roundoff. Note, we drop * a line from the cnt if using the window size to leave room for * the next ex prompt. */ if (FL_ISSET(cmdp->iflags, E_C_COUNT)) cnt = cmdp->count; else cnt = O_VAL(sp, O_WINDOW) - 1; equals = 0; eofcheck = 0; lno = cmdp->addr1.lno; switch (FL_ISSET(cmdp->iflags, E_C_CARAT | E_C_DASH | E_C_DOT | E_C_EQUAL | E_C_PLUS)) { case E_C_CARAT: /* Display cnt * 2 before the line. */ eofcheck = 1; if (lno > cnt * 2) cmdp->addr1.lno = (lno - cnt * 2) + 1; else cmdp->addr1.lno = 1; cmdp->addr2.lno = (cmdp->addr1.lno + cnt) - 1; break; case E_C_DASH: /* Line goes at the bottom of the screen. */ cmdp->addr1.lno = lno > cnt ? (lno - cnt) + 1 : 1; cmdp->addr2.lno = lno; break; case E_C_DOT: /* Line goes in the middle of the screen. */ /* * !!! * Historically, the "middleness" of the line overrode the * count, so that "3z.19" or "3z.20" would display the first * 12 lines of the file, i.e. (N - 1) / 2 lines before and * after the specified line. */ eofcheck = 1; cnt = (cnt - 1) / 2; cmdp->addr1.lno = lno > cnt ? lno - cnt : 1; cmdp->addr2.lno = lno + cnt; /* * !!! * Historically, z. set the absolute cursor mark. */ abs.lno = sp->lno; abs.cno = sp->cno; (void)mark_set(sp, ABSMARK1, &abs, 1); break; case E_C_EQUAL: /* Center with hyphens. */ /* * !!! * Strangeness. The '=' flag is like the '.' flag (see the * above comment, it applies here as well) but with a special * little hack. Print out lines of hyphens before and after * the specified line. Additionally, the cursor remains set * on that line. */ eofcheck = 1; cnt = (cnt - 1) / 2; cmdp->addr1.lno = lno > cnt ? lno - cnt : 1; cmdp->addr2.lno = lno - 1; if (ex_pr(sp, cmdp)) return (1); (void)ex_puts(sp, "----------------------------------------\n"); cmdp->addr2.lno = cmdp->addr1.lno = equals = lno; if (ex_pr(sp, cmdp)) return (1); (void)ex_puts(sp, "----------------------------------------\n"); cmdp->addr1.lno = lno + 1; cmdp->addr2.lno = (lno + cnt) - 1; break; default: /* If no line specified, move to the next one. */ if (F_ISSET(cmdp, E_ADDR_DEF)) ++lno; /* FALLTHROUGH */ case E_C_PLUS: /* Line goes at the top of the screen. */ eofcheck = 1; cmdp->addr1.lno = lno; cmdp->addr2.lno = (lno + cnt) - 1; break; } if (eofcheck) { if (db_last(sp, &lno)) return (1); if (cmdp->addr2.lno > lno) cmdp->addr2.lno = lno; } if (ex_pr(sp, cmdp)) return (1); if (equals) sp->lno = equals; return (0); } Index: head/contrib/nvi/ex/extern.h =================================================================== --- head/contrib/nvi/ex/extern.h (revision 365498) +++ head/contrib/nvi/ex/extern.h (revision 365499) @@ -1,130 +1,131 @@ int ex(SCR **); int ex_cmd(SCR *); int ex_range(SCR *, EXCMD *, int *); int ex_is_abbrev(CHAR_T *, size_t); int ex_is_unmap(CHAR_T *, size_t); void ex_badaddr (SCR *, EXCMDLIST const *, enum badaddr, enum nresult); int ex_abbr(SCR *, EXCMD *); int ex_unabbr(SCR *, EXCMD *); int ex_append(SCR *, EXCMD *); int ex_change(SCR *, EXCMD *); int ex_insert(SCR *, EXCMD *); int ex_next(SCR *, EXCMD *); int ex_prev(SCR *, EXCMD *); int ex_rew(SCR *, EXCMD *); int ex_args(SCR *, EXCMD *); char **ex_buildargv(SCR *, EXCMD *, char *); int argv_init(SCR *, EXCMD *); int argv_exp0(SCR *, EXCMD *, CHAR_T *, size_t); int argv_exp1(SCR *, EXCMD *, CHAR_T *, size_t, int); int argv_exp2(SCR *, EXCMD *, CHAR_T *, size_t); int argv_exp3(SCR *, EXCMD *, CHAR_T *, size_t); int argv_flt_ex(SCR *, EXCMD *, CHAR_T *, size_t); int argv_free(SCR *); int argv_flt_path(SCR *, EXCMD *, CHAR_T *, size_t); CHAR_T *argv_esc(SCR *, EXCMD *, CHAR_T *, size_t); CHAR_T *argv_uesc(SCR *, EXCMD *, CHAR_T *, size_t); int ex_at(SCR *, EXCMD *); int ex_bang(SCR *, EXCMD *); int ex_cd(SCR *, EXCMD *); int ex_cscope(SCR *, EXCMD *); int cscope_end(SCR *); int cscope_display(SCR *); int cscope_search(SCR *, TAGQ *, TAG *); int ex_delete(SCR *, EXCMD *); int ex_display(SCR *, EXCMD *); int ex_edit(SCR *, EXCMD *); int ex_equal(SCR *, EXCMD *); int ex_file(SCR *, EXCMD *); int ex_filter(SCR *, EXCMD *, MARK *, MARK *, MARK *, CHAR_T *, enum filtertype); int ex_global(SCR *, EXCMD *); int ex_v(SCR *, EXCMD *); int ex_g_insdel(SCR *, lnop_t, recno_t); int ex_screen_copy(SCR *, SCR *); int ex_screen_end(SCR *); int ex_optchange(SCR *, int, char *, u_long *); int ex_exrc(SCR *); int ex_run_str(SCR *, char *, CHAR_T *, size_t, int, int); int ex_join(SCR *, EXCMD *); int ex_map(SCR *, EXCMD *); int ex_unmap(SCR *, EXCMD *); int ex_mark(SCR *, EXCMD *); int ex_mkexrc(SCR *, EXCMD *); int ex_copy(SCR *, EXCMD *); int ex_move(SCR *, EXCMD *); int ex_open(SCR *, EXCMD *); int ex_preserve(SCR *, EXCMD *); int ex_recover(SCR *, EXCMD *); int ex_list(SCR *, EXCMD *); int ex_number(SCR *, EXCMD *); int ex_pr(SCR *, EXCMD *); int ex_print(SCR *, EXCMD *, MARK *, MARK *, u_int32_t); int ex_ldisplay(SCR *, const CHAR_T *, size_t, size_t, u_int); int ex_scprint(SCR *, MARK *, MARK *); int ex_printf(SCR *, const char *, ...); int ex_puts(SCR *, const char *); int ex_fflush(SCR *sp); int ex_put(SCR *, EXCMD *); int ex_quit(SCR *, EXCMD *); int ex_read(SCR *, EXCMD *); int ex_readfp(SCR *, char *, FILE *, MARK *, recno_t *, int); int ex_bg(SCR *, EXCMD *); int ex_fg(SCR *, EXCMD *); int ex_resize(SCR *, EXCMD *); int ex_sdisplay(SCR *); int ex_script(SCR *, EXCMD *); int sscr_exec(SCR *, recno_t); int sscr_input(SCR *); int sscr_end(SCR *); int ex_set(SCR *, EXCMD *); int ex_shell(SCR *, EXCMD *); int ex_exec_proc(SCR *, EXCMD *, char *, const char *, int); int proc_wait(SCR *, long, const char *, int, int); int ex_shiftl(SCR *, EXCMD *); int ex_shiftr(SCR *, EXCMD *); +int ex_retab(SCR *, EXCMD *); int ex_source(SCR *, EXCMD *); int ex_stop(SCR *, EXCMD *); int ex_s(SCR *, EXCMD *); int ex_subagain(SCR *, EXCMD *); int ex_subtilde(SCR *, EXCMD *); int re_compile(SCR *, CHAR_T *, size_t, CHAR_T **, size_t *, regex_t *, u_int); void re_error(SCR *, int, regex_t *); int ex_tag_first(SCR *, CHAR_T *); int ex_tag_push(SCR *, EXCMD *); int ex_tag_next(SCR *, EXCMD *); int ex_tag_prev(SCR *, EXCMD *); int ex_tag_nswitch(SCR *, TAG *, int); int ex_tag_Nswitch(SCR *, TAG *, int); int ex_tag_pop(SCR *, EXCMD *); int ex_tag_top(SCR *, EXCMD *); int ex_tag_display(SCR *); int ex_tag_copy(SCR *, SCR *); int tagq_free(SCR *, TAGQ *); int tagq_push(SCR*, TAGQ*, int, int ); void tag_msg(SCR *, tagmsg_t, char *); int ex_tagf_alloc(SCR *, char *); int ex_tag_free(SCR *); int ex_txt(SCR *, TEXTH *, ARG_CHAR_T, u_int32_t); int ex_undo(SCR *, EXCMD *); int ex_help(SCR *, EXCMD *); int ex_usage(SCR *, EXCMD *); int ex_viusage(SCR *, EXCMD *); void ex_cinit(SCR *, EXCMD *, int, int, recno_t, recno_t, int); int ex_getline(SCR *, FILE *, size_t *); int ex_ncheck(SCR *, int); int ex_init(SCR *); void ex_wemsg(SCR *, CHAR_T *, exm_t); void ex_emsg(SCR *, char *, exm_t); int ex_version(SCR *, EXCMD *); int ex_visual(SCR *, EXCMD *); int ex_wn(SCR *, EXCMD *); int ex_wq(SCR *, EXCMD *); int ex_write(SCR *, EXCMD *); int ex_xit(SCR *, EXCMD *); int ex_writefp(SCR *, char *, FILE *, MARK *, MARK *, u_long *, u_long *, int); int ex_yank(SCR *, EXCMD *); int ex_z(SCR *, EXCMD *); Index: head/contrib/nvi/ex/script.h =================================================================== --- head/contrib/nvi/ex/script.h (revision 365498) +++ head/contrib/nvi/ex/script.h (revision 365499) @@ -1,21 +1,19 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: script.h,v 10.3 2012/04/21 23:51:46 zy Exp $ */ struct _script { pid_t sh_pid; /* Shell pid. */ int sh_master; /* Master pty fd. */ int sh_slave; /* Slave pty fd. */ char *sh_prompt; /* Prompt. */ size_t sh_prompt_len; /* Prompt length. */ char sh_name[64]; /* Pty name */ struct winsize sh_win; /* Window size. */ struct termios sh_term; /* Terminal information. */ }; Index: head/contrib/nvi/ex/tag.h =================================================================== --- head/contrib/nvi/ex/tag.h (revision 365498) +++ head/contrib/nvi/ex/tag.h (revision 365499) @@ -1,109 +1,107 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 1994, 1996 * Rob Mayoff. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: tag.h,v 10.9 2012/07/06 16:38:36 zy Exp $ */ /* * Cscope connection information. One of these is maintained per cscope * connection, linked from the EX_PRIVATE structure. */ struct _csc { SLIST_ENTRY(_csc) q; /* Linked list of cscope connections. */ char *dname; /* Base directory of this cscope connection. */ size_t dlen; /* Length of base directory. */ pid_t pid; /* PID of the connected cscope process. */ struct timespec mtim; /* Last modification time of cscope database. */ FILE *from_fp; /* from cscope: FILE. */ int from_fd; /* from cscope: file descriptor. */ FILE *to_fp; /* to cscope: FILE. */ int to_fd; /* to cscope: file descriptor. */ char **paths; /* Array of search paths for this cscope. */ char *pbuf; /* Search path buffer. */ size_t pblen; /* Search path buffer length. */ char buf[1]; /* Variable length buffer. */ }; /* * Tag file information. One of these is maintained per tag file, linked * from the EXPRIVATE structure. */ struct _tagf { /* Tag files. */ TAILQ_ENTRY(_tagf) q; /* Linked list of tag files. */ char *name; /* Tag file name. */ int errnum; /* Errno. */ #define TAGF_ERR 0x01 /* Error occurred. */ #define TAGF_ERR_WARN 0x02 /* Error reported. */ u_int8_t flags; }; /* * Tags are structured internally as follows: * * +----+ +----+ +----+ +----+ * | EP | -> | Q1 | <-- | T1 | <-- | T2 | * +----+ +----+ --> +----+ --> +----+ * | * +----+ +----+ * | Q2 | <-- | T1 | * +----+ --> +----+ * | * +----+ +----+ * | Q3 | <-- | T1 | * +----+ --> +----+ * * Each Q is a TAGQ, or tag "query", which is the result of one tag or cscope * command. Each Q references one or more TAG's, or tagged file locations. * * tag: put a new Q at the head (^]) * tagnext: T1 -> T2 inside Q (^N) * tagprev: T2 -> T1 inside Q (^P) * tagpop: discard Q (^T) * tagtop: discard all Q */ struct _tag { /* Tag list. */ TAILQ_ENTRY(_tag) q; /* Linked list of tags. */ /* Tag pop/return information. */ FREF *frp; /* Saved file. */ recno_t lno; /* Saved line number. */ size_t cno; /* Saved column number. */ char *fname; /* Filename. */ size_t fnlen; /* Filename length. */ recno_t slno; /* Search line number. */ CHAR_T *search; /* Search string. */ size_t slen; /* Search string length. */ CHAR_T *msg; /* Message string. */ size_t mlen; /* Message string length. */ CHAR_T buf[1]; /* Variable length buffer. */ }; struct _tagq { /* Tag queue. */ TAILQ_ENTRY(_tagq) q; /* Linked list of tag queues. */ /* This queue's tag list. */ TAILQ_HEAD(_tagqh, _tag) tagq[1]; TAG *current; /* Current TAG within the queue. */ char *tag; /* Tag string. */ size_t tlen; /* Tag string length. */ #define TAG_CSCOPE 0x01 /* Cscope tag. */ u_int8_t flags; char buf[1]; /* Variable length buffer. */ }; Index: head/contrib/nvi/ex/version.h =================================================================== --- head/contrib/nvi/ex/version.h (revision 365498) +++ head/contrib/nvi/ex/version.h (revision 365499) @@ -1 +1 @@ -#define VI_VERSION "2.1.3 (2015-04-08)" +#define VI_VERSION "2.2.0 (2020-08-01)" Property changes on: head/contrib/nvi/ex/version.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/nvi/files/config.h.in =================================================================== --- head/contrib/nvi/files/config.h.in (nonexistent) +++ head/contrib/nvi/files/config.h.in (revision 365499) @@ -0,0 +1,17 @@ +/* Define when using wide characters */ +#cmakedefine USE_WIDECHAR + +/* Define when iconv can be used */ +#cmakedefine USE_ICONV + +/* Define when the 2nd argument of iconv(3) is not const */ +#cmakedefine ICONV_TRADITIONAL + +/* Define if you have */ +#cmakedefine HAVE_LIBUTIL_H + +/* Define if you have */ +#cmakedefine HAVE_NCURSES_H + +/* Define if you have */ +#cmakedefine HAVE_TERM_H Property changes on: head/contrib/nvi/files/config.h.in ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/nvi/files/pathnames.h.in =================================================================== --- head/contrib/nvi/files/pathnames.h.in (nonexistent) +++ head/contrib/nvi/files/pathnames.h.in (revision 365499) @@ -0,0 +1,26 @@ +/* Read standard system paths first. */ +#include + +#ifndef _PATH_EXRC +#define _PATH_EXRC ".exrc" +#endif + +#ifndef _PATH_MSGCAT +#define _PATH_MSGCAT "@vi_cv_path_msgcat@" +#endif + +#ifndef _PATH_NEXRC +#define _PATH_NEXRC ".nexrc" +#endif + +#ifndef _PATH_PRESERVE +#define _PATH_PRESERVE "@vi_cv_path_preserve@" +#endif + +#ifndef _PATH_SYSEXRC +#define _PATH_SYSEXRC "/etc/vi.exrc" +#endif + +#ifndef _PATH_TAGS +#define _PATH_TAGS "tags" +#endif Property changes on: head/contrib/nvi/files/pathnames.h.in ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/nvi/files/recover.in =================================================================== --- head/contrib/nvi/files/recover.in (nonexistent) +++ head/contrib/nvi/files/recover.in (revision 365499) @@ -0,0 +1,53 @@ +#!/bin/sh +# +# Script to recover nvi edit sessions. + +RECDIR="@vi_cv_path_preserve@" + +[ -d ${RECDIR} ] || exit 1 +find ${RECDIR} ! -type f -a ! -type d -delete + +# Check editor backup files. +vibackup=`echo ${RECDIR}/vi.*` +if [ "${vibackup}" != '${RECDIR}/vi.*' ]; then + echo -n 'Recovering vi editor sessions:' + for i in ${RECDIR}/vi.*; do + # Only test files that are readable. + if [ ! -r "${i}" ]; then + continue + fi + + # Unmodified nvi editor backup files either have the + # execute bit set or are zero length. Delete them. + if [ -x "${i}" -o ! -s "${i}" ]; then + rm -f "${i}" + fi + done +else exit +fi + +# It is possible to get incomplete recovery files, if the editor crashes +# at the right time. +virecovery=`echo ${RECDIR}/recover.*` +if [ "${virecovery}" != "${RECDIR}/recover.*" ]; then + for i in ${RECDIR}/recover.*; do + # Only test files that are readable. + if [ ! -r "${i}" ]; then + continue + fi + + # Delete any recovery files that are zero length, corrupted, + # or that have no corresponding backup file. Else send mail + # to the user. + recfile=`awk '/^X-vi-data: *file;/ { sub(/^.*;/, " "); \ + do { if (substr($0,1,1) == " ") print; else exit } \ + while(getline) }' < "${i}" | uudecode -mr` + if [ -n "${recfile}" -a -s "${recfile}" ]; then + sendmail -odb -t < "${i}" + echo -n '.' + else + rm -f "${i}" + fi + done +fi +echo ' done.' Property changes on: head/contrib/nvi/files/recover.in ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/nvi/man/vi.1 =================================================================== --- head/contrib/nvi/man/vi.1 (nonexistent) +++ head/contrib/nvi/man/vi.1 (revision 365499) @@ -0,0 +1,2768 @@ +.\" Copyright (c) 1994 +.\" The Regents of the University of California. All rights reserved. +.\" Copyright (c) 1994, 1995, 1996 +.\" Keith Bostic. All rights reserved. +.\" Copyright (c) 2011 +.\" Zhihao Yuan. All rights reserved. +.\" +.\" The vi program is freely redistributable. +.\" You are welcome to copy, modify and share it with others +.\" under the conditions listed in the LICENSE file. +.\" If any company (not individual!) finds vi sufficiently useful +.\" that you would have purchased it, or if any company wishes to +.\" redistribute it, contributions to the authors would be appreciated. +.\" +.Dd November 2, 2013 +.Dt VI 1 +.Os +.Sh NAME +.Nm ex , vi , view +.Nd text editors +.Sh SYNOPSIS +.Nm ex +.Op Fl FRrSsv +.Op Fl c Ar cmd +.Op Fl t Ar tag +.Op Fl w Ar size +.Op Ar +.Nm vi\ \& +.Op Fl eFRrS +.Op Fl c Ar cmd +.Op Fl t Ar tag +.Op Fl w Ar size +.Op Ar +.Nm view +.Op Fl eFrS +.Op Fl c Ar cmd +.Op Fl t Ar tag +.Op Fl w Ar size +.Op Ar +.Sh DESCRIPTION +.Nm vi +is a screen-oriented text editor. +.Nm ex +is a line-oriented text editor. +.Nm ex +and +.Nm vi +are different interfaces to the same program, +and it is possible to switch back and forth during an edit session. +.Nm view +is the equivalent of using the +.Fl R +.Pq read-only +option of +.Nm vi . +.Pp +This manual page is the one provided with the +.Nm nex Ns / Ns Nm nvi +versions of the +.Nm ex Ns / Ns Nm vi +text editors. +.Nm nex Ns / Ns Nm nvi +are intended as bug-for-bug compatible replacements for the original +Fourth Berkeley Software Distribution +.Pq 4BSD +.Nm ex +and +.Nm vi +programs. +For the rest of this manual page, +.Nm nex Ns / Ns Nm nvi +is used only when it's necessary to distinguish it from the historic +implementations of +.Nm ex Ns / Ns Nm vi . +.Pp +This manual page is intended for users already familiar with +.Nm ex Ns / Ns Nm vi . +Anyone else should almost certainly read a good tutorial on the +editor before this manual page. +If you're in an unfamiliar environment, +and you absolutely have to get work done immediately, +read the section after the options description, entitled +.Sx FAST STARTUP . +It's probably enough to get you going. +.Pp +The following options are available: +.Bl -tag -width "-w size " +.It Fl c Ar cmd +Execute +.Ar cmd +on the first file loaded. +Particularly useful for initial positioning in the file, although +.Ar cmd +is not limited to positioning commands. +This is the POSIX 1003.2 interface for the historic +.Dq +cmd +syntax. +.Nm nex Ns / Ns Nm nvi +supports both the old and new syntax. +.It Fl e +Start editing in ex mode, as if the command name were +.Nm ex . +.It Fl F +Don't copy the entire file when first starting to edit. +(The default is to make a copy in case someone else modifies +the file during your edit session.) +.\" .It Fl l +.\" Start editing with the lisp and showmatch options set. +.It Fl R +Start editing in read-only mode, as if the command name was +.Nm view , +or the +.Cm readonly +option was set. +.It Fl r +Recover the specified files, or, if no files are specified, +list the files that could be recovered. +If no recoverable files by the specified name exist, +the file is edited as if the +.Fl r +option had not been specified. +.It Fl S +Run with the +.Cm secure +edit option set, disallowing all access to external programs. +.It Fl s +Enter batch mode; applicable only to +.Nm ex +edit sessions. +Batch mode is useful when running +.Nm ex +scripts. +Prompts, informative messages and other user oriented messages are turned off, +and no startup files or environment variables are read. +This is the POSIX 1003.2 interface for the historic +.Dq - +argument. +.Nm nex Ns / Ns Nm nvi +supports both the old and new syntax. +.It Fl t Ar tag +Start editing at the specified +.Ar tag +.Pq see Xr ctags 1 . +.It Fl v +Start editing in vi mode, as if the command name was +.Nm vi . +.It Fl w Ar size +Set the initial window size to the specified number of lines. +.El +.Pp +Command input for +.Nm ex Ns / Ns Nm vi +is read from the standard input. +In the +.Nm vi +interface, it is an error if standard input is not a terminal. +In the +.Nm ex +interface, if standard input is not a terminal, +.Nm ex +will read commands from it regardless; however, the session will be a +batch mode session, exactly as if the +.Fl s +option had been specified. +.Sh FAST STARTUP +This section will tell you the minimum amount that you need to +do simple editing tasks using +.Nm vi . +If you've never used any screen editor before, +you're likely to have problems even with this simple introduction. +In that case you should find someone that already knows +.Nm vi +and have them walk you through this section. +.Pp +.Nm vi +is a screen editor. +This means that it takes up almost the entire screen, +displaying part of the file on each screen line, +except for the last line of the screen. +The last line of the screen is used for you to give commands to +.Nm vi , +and for +.Nm vi +to give information to you. +.Pp +The other fact that you need to understand is that +.Nm vi +is a modeful editor, +i.e., you are either entering text or you are executing commands, +and you have to be in the right mode to do one or the other. +You will be in command mode when you first start editing a file. +There are commands that switch you into input mode. +There is only one key that takes you out of input mode, +and that is the +.Aq escape +key. +.Pp +In this manual, +key names are denoted with \(la and \(ra, e.g., +.Aq escape +means the +.Dq escape +key, usually labeled +.Dq Esc +on your terminal's keyboard. +If you're ever confused as to which mode you're in, +keep entering the +.Aq escape +key until +.Nm vi +beeps at you. +Generally, +.Nm vi +will beep at you if you try and do something that's not allowed. +It will also display error messages. +.Pp +To start editing a file, enter the following command: +.Pp +.Dl $ vi file +.Pp +The command you should enter as soon as you start editing is: +.Pp +.Dl :set verbose showmode +.Pp +This will make the editor give you verbose error messages and display +the current mode at the bottom of the screen. +.Pp +The commands to move around the file are: +.Bl -tag -width Ds +.It Cm h +Move the cursor left one character. +.It Cm j +Move the cursor down one line. +.It Cm k +Move the cursor up one line. +.It Cm l +Move the cursor right one character. +.It Aq Cm cursor-arrows +The cursor arrow keys should work, too. +.It Cm / Ns Ar text +Search for the string +.Dq Ar text +in the file, +and move the cursor to its first character. +.El +.Pp +The commands to enter new text are: +.Bl -tag -width "" +.It Cm a +Append new text, after the cursor. +.It Cm i +Insert new text, before the cursor. +.It Cm o +Open a new line below the line the cursor is on, and start entering text. +.It Cm O +Open a new line above the line the cursor is on, and start entering text. +.It Aq Cm escape +Once you've entered input mode using one of the +.Cm a , +.Cm i , +.Cm o +or +.Cm O +commands, use +.Aq Cm escape +to quit entering text and return to command mode. +.El +.Pp +The commands to copy text are: +.Bl -tag -width Ds +.It Cm yy +Copy the line the cursor is on. +.It Cm p +Append the copied line after the line the cursor is on. +.El +.Pp +The commands to delete text are: +.Bl -tag -width Ds +.It Cm dd +Delete the line the cursor is on. +.It Cm x +Delete the character the cursor is on. +.El +.Pp +The commands to write the file are: +.Bl -tag -width Ds +.It Cm :w +Write the file back to the file with the name that you originally used +as an argument on the +.Nm vi +command line. +.It Cm :w Ar file_name +Write the file back to the file with the name +.Ar file_name . +.El +.Pp +The commands to quit editing and exit the editor are: +.Bl -tag -width Ds +.It Cm :q +Quit editing and leave +.Nm vi +(if you've modified the file, but not saved your changes, +.Nm vi +will refuse to quit). +.It Cm :q! +Quit, discarding any modifications that you may have made. +.El +.Pp +One final caution: +Unusual characters can take up more than one column on the screen, +and long lines can take up more than a single screen line. +The above commands work on +.Dq physical +characters and lines, +i.e., they affect the entire line no matter how many screen lines it takes up +and the entire character no matter how many screen columns it takes up. +.Sh REGULAR EXPRESSIONS +.Nm ex Ns / Ns Nm vi +supports regular expressions +.Pq REs , +as documented in +.Xr re_format 7 , +for line addresses, as the first part of the +.Nm ex Cm substitute , +.Cm global +and +.Cm v +commands, and in search patterns. +Basic regular expressions +.Pq BREs +are enabled by default; +extended regular expressions +.Pq EREs +are used if the +.Cm extended +option is enabled. +The use of regular expressions can be largely disabled using the +.Cm magic +option. +.Pp +The following strings have special meanings in the +.Nm ex Ns / Ns Nm vi +version of regular expressions: +.Bl -bullet -offset 6u +.It +An empty regular expression is equivalent to the last regular expression used. +.It +.Sq \e< +matches the beginning of the word. +.It +.Sq \e> +matches the end of the word. +.It +.Sq \(ti +matches the replacement part of the last +.Cm substitute +command. +.El +.Sh BUFFERS +A buffer is an area where commands can save changed or deleted text +for later use. +.Nm vi +buffers are named with a single character preceded by a double quote, +for example +.Cm \&" Ns Aq Ar c ; +.Nm ex +buffers are the same, +but without the double quote. +.Nm nex Ns / Ns Nm nvi +permits the use of any character without another meaning in the position where +a buffer name is expected. +.Pp +All buffers are either in +.Em line mode +or +.Em character mode . +Inserting a buffer in line mode into the text creates new lines for each of the +lines it contains, while a buffer in character mode creates new lines for any +lines +.Em other +than the first and last lines it contains. +The first and last lines are inserted at the current cursor position, becoming +part of the current line. +If there is more than one line in the buffer, +the current line itself will be split. +All +.Nm ex +commands which store text into buffers do so in line mode. +The behaviour of +.Nm vi +commands depend on their associated motion command: +.Bl -bullet -offset 6u +.It +.Aq Cm control-A , +.Cm h , +.Cm l , +.Cm ,\& , +.Cm 0 , +.Cm B , +.Cm E , +.Cm F , +.Cm T , +.Cm W , +.Cm \(ha , +.Cm b , +.Cm e , +.Cm f +and +.Cm t +make the destination buffer character-oriented. +.It +.Cm j , +.Aq Cm control-M , +.Cm k , +.Cm \(aq , +.Cm - , +.Cm G , +.Cm H , +.Cm L , +.Cm M , +.Cm _ +and +.Cm |\& +make the destination buffer line-oriented. +.It +.Cm $ , +.Cm % , +.Cm \` , +.Cm (\& , +.Cm )\& , +.Cm / , +.Cm ?\& , +.Cm [[ , +.Cm ]] , +.Cm { +and +.Cm } +make the destination buffer character-oriented, unless the starting and +end positions are the first and last characters on a line. +In that case, the buffer is line-oriented. +.El +.Pp +The +.Nm ex +command +.Cm display buffers +displays the current mode for each buffer. +.Pp +Buffers named +.Sq a +through +.Sq z +may be referred to using their uppercase equivalent, in which case new content +will be appended to the buffer, instead of replacing it. +.Pp +Buffers named +.Sq 1 +through +.Sq 9 +are special. +A region of text modified using the +.Cm c +.Pq change +or +.Cm d +.Pq delete +commands is placed into the numeric buffer +.Sq 1 +if no other buffer is specified and if it meets one of the following conditions: +.Bl -bullet -offset 6u +.It +It includes characters from more than one line. +.It +It is specified using a line-oriented motion. +.It +It is specified using one of the following motion commands: +.Aq Cm control-A , +.Cm \` Ns Aq Ar character , +.Cm n , +.Cm N , +.Cm % , +.Cm / , +.Cm { , +.Cm } , +.Cm \&( , +.Cm \&) , +and +.Cm \&? . +.El +.Pp +Before this copy is done, the previous contents of buffer +.Sq 1 +are moved into buffer +.Sq 2 , +.Sq 2 +into buffer +.Sq 3 , +and so on. +The contents of buffer +.Sq 9 +are discarded. +Note that this rotation occurs +.Em regardless +of the user specifying another buffer. +In +.Nm vi , +text may be explicitly stored into the numeric buffers. +In this case, the buffer rotation occurs before the replacement of the buffer's +contents. +The numeric buffers are only available in +.Nm vi +mode. +.Sh VI COMMANDS +The following section describes the commands available in the command +mode of the +.Nm vi +editor. +The following words have a special meaning in the commands description: +.Pp +.Bl -tag -width bigword -compact -offset 3u +.It Ar bigword +A set of non-whitespace characters. +.It Ar buffer +Temporary area where commands may place text. +If not specified, the default buffer is used. +See also +.Sx BUFFERS , +above. +.It Ar count +A positive number used to specify the desired number of iterations +of a command. +It defaults to 1 if not specified. +.It Ar motion +A cursor movement command which indicates the other end of the affected region +of text, the first being the current cursor position. +Repeating the command character makes it affect the whole current line. +.It Ar word +A sequence of letters, digits or underscores. +.El +.Pp +.Ar buffer +and +.Ar count , +if both present, may be specified in any order. +.Ar motion +and +.Ar count , +if both present, are effectively multiplied together +and considered part of the motion. +.Pp +.Bl -tag -width Ds -compact +.It Xo +.Aq Cm control-A +.Xc +Search forward +for the word starting at the cursor position. +.Pp +.It Xo +.Op Ar count +.Aq Cm control-B +.Xc +Page backwards +.Ar count +screens. +Two lines of overlap are maintained, if possible. +.Pp +.It Xo +.Op Ar count +.Aq Cm control-D +.Xc +Scroll forward +.Ar count +lines. +If +.Ar count +is not given, scroll forward the number of lines specified by the last +.Aq Cm control-D +or +.Aq Cm control-U +command. +If this is the first +.Aq Cm control-D +command, scroll half the number of lines in the current screen. +.Pp +.It Xo +.Op Ar count +.Aq Cm control-E +.Xc +Scroll forward +.Ar count +lines, leaving the current line and column as is, if possible. +.Pp +.It Xo +.Op Ar count +.Aq Cm control-F +.Xc +Page forward +.Ar count +screens. +Two lines of overlap are maintained, if possible. +.Pp +.It Aq Cm control-G +Display the following file information: +the file name +.Pq as given to Nm vi ; +whether the file has been modified since it was last written; +if the file is read-only; +the current line number; +the total number of lines in the file; +and the current line number as a percentage of the total lines in the file. +.Pp +.It Xo +.Op Ar count +.Aq Cm control-H +.Xc +.It Xo +.Op Ar count +.Cm h +.Xc +Move the cursor back +.Ar count +characters in the current line. +.Pp +.It Xo +.Op Ar count +.Aq Cm control-J +.Xc +.It Xo +.Op Ar count +.Aq Cm control-N +.Xc +.It Xo +.Op Ar count +.Cm j +.Xc +Move the cursor down +.Ar count +lines without changing the current column. +.Pp +.It Aq Cm control-L +.It Aq Cm control-R +Repaint the screen. +.Pp +.It Xo +.Op Ar count +.Aq Cm control-M +.Xc +.It Xo +.Op Ar count +.Cm + +.Xc +Move the cursor down +.Ar count +lines to the first non-blank character of that line. +.Pp +.It Xo +.Op Ar count +.Aq Cm control-P +.Xc +.It Xo +.Op Ar count +.Cm k +.Xc +Move the cursor up +.Ar count +lines, without changing the current column. +.Pp +.It Aq Cm control-T +Return to the most recent tag context. +.Pp +.It Xo +.Op Ar count +.Aq Cm control-U +.Xc +Scroll backwards +.Ar count +lines. +If +.Ar count +is not given, scroll backwards the number of lines specified by the last +.Aq Cm control-D +or +.Aq Cm control-U +command. +If this is the first +.Aq Cm control-U +command, scroll half the number of lines in the current screen. +.Pp +.It Aq Cm control-W +Switch to the next lower screen in the window, +or to the first screen if there are no lower screens in the window. +.Pp +.It Xo +.Op Ar count +.Aq Cm control-Y +.Xc +Scroll backwards +.Ar count +lines, leaving the current line and column as is, if possible. +.Pp +.It Aq Cm control-Z +Suspend the current editor session. +.Pp +.It Aq Cm escape +Execute the +.Nm ex +command being entered, or cancel it if it is only partial. +.Pp +.It Aq Cm control-] +Push a tag reference onto the tag stack. +.Pp +.It Aq Cm control-\(ha +Switch to the most recently edited file. +.Pp +.It Xo +.Op Ar count +.Aq Cm space +.Xc +.It Xo +.Op Ar count +.Cm l +.Xc +Move the cursor forward +.Ar count +characters without changing the current line. +.Pp +.It Xo +.Op Ar count +.Cm !\& +.Ar motion shell-argument(s) +.Aq Li carriage-return +.Xc +Replace the lines spanned by +.Ar count +and +.Ar motion +with the output +.Pq standard output and standard error +of the program named by the +.Cm shell +option, called with a +.Fl c +flag followed by the +.Ar shell-argument(s) +.Pq bundled into a single argument . +Within +.Ar shell-argument(s) , +the +.Sq % , +.Sq # +and +.Sq !\& +characters are expanded to the current file name, +the previous current file name, +and the command text of the previous +.Cm !\& +or +.Cm :! +commands, respectively. +The special meaning of +.Sq % , +.Sq # +and +.Sq !\& +can be overridden by escaping them with a backslash. +.Pp +.It Xo +.Op Ar count +.Cm # +.Sm off +.Cm # | + | - +.Sm on +.Xc +Increment +.Pq trailing So # Sc or So + Sc +or decrement +.Pq trailing Sq - +the number under the cursor by +.Ar count , +starting at the cursor position or at the first non-blank +character following it. +Numbers with a leading +.Sq 0x +or +.Sq 0X +are interpreted as hexadecimal numbers. +Numbers with a leading +.Sq 0 +are interpreted as octal numbers unless they contain a non-octal digit. +Other numbers may be prefixed with a +.Sq + +or +.Sq - +sign. +.Pp +.It Xo +.Op Ar count +.Cm $ +.Xc +Move the cursor to the end of a line. +If +.Ar count +is specified, additionally move the cursor down +.Ar count +\(mi 1 lines. +.Pp +.It Cm % +Move to the +.Cm matchchars +character matching +the one found at the cursor position or the closest to the right of it. +.Pp +.It Cm & +Repeat the previous substitution command on the current line. +.Pp +.It Xo +.Cm \(aq Ns Aq Ar character +.Xc +.It Xo +.Cm \` Ns Aq Ar character +.Xc +Return to the cursor position marked by the character +.Ar character , +or, if +.Ar character +is +.Sq \(aq +or +.Sq \` , +to the position of the cursor before the last of the following commands: +.Aq Cm control-A , +.Aq Cm control-T , +.Aq Cm control-] , +.Cm % , +.Cm \(aq , +.Cm \` , +.Cm (\& , +.Cm )\& , +.Cm / , +.Cm ?\& , +.Cm G , +.Cm H , +.Cm L , +.Cm [[ , +.Cm ]] , +.Cm { , +.Cm } . +The first form returns to the first non-blank character of the line marked by +.Ar character . +The second form returns to the line and column marked by +.Ar character . +.Pp +.It Xo +.Op Ar count +.Cm \&( +.Xc +.It Xo +.Op Ar count +.Cm \&) +.Xc +Move +.Ar count +sentences backward or forward, respectively. +A sentence is an area of text that begins with the first nonblank character +following the previous sentence, paragraph, or section +boundary and continues until the next period, exclamation point, +or question mark character, followed by any number of closing parentheses, +brackets, double or single quote characters, followed by +either an end-of-line or two whitespace characters. +Groups of empty lines +.Pq or lines containing only whitespace characters +are treated as a single sentence. +.Pp +.It Xo +.Op Ar count +.Cm ,\& +.Xc +Reverse find character +.Pq i.e., the last Cm F , f , T No or Cm t No command +.Ar count +times. +.Pp +.It Xo +.Op Ar count +.Cm - +.Xc +Move to the first non-blank character of the previous line, +.Ar count +times. +.Pp +.It Xo +.Op Ar count +.Cm .\& +.Xc +Repeat the last +.Nm vi +command that modified text. +.Ar count +replaces both the +.Ar count +argument of the repeated command and that of the associated +.Ar motion . +If the +.Cm .\& +command repeats the +.Cm u +command, the change log is rolled forward or backward, depending on the action +of the +.Cm u +command. +.Pp +.It Xo +.Pf / Ns Ar RE +.Aq Li carriage-return +.Xc +.It Xo +.Pf / Ns Ar RE Ns / +.Op Ar offset +.Op Cm z +.Aq Li carriage-return +.Xc +.It Xo +.Pf ?\& Ns Ar RE +.Aq Li carriage-return +.Xc +.It Xo +.Pf ?\& Ns Ar RE Ns ?\& +.Op Ar offset +.Op Cm z +.Aq Li carriage-return +.Xc +.It Cm N +.It Cm n +Search forward +.Pq Sq / +or backward +.Pq Sq ?\& +for a regular expression. +.Cm n +and +.Cm N +repeat the last search in the same or opposite directions, respectively. +If +.Ar RE +is empty, the last search regular expression is used. +If +.Ar offset +is specified, the cursor is placed +.Ar offset +lines before or after the matched regular expression. +If either +.Cm n +or +.Cm N +commands are used as motion components for the +.Cm !\& +command, there will be no prompt for the text of the command and the previous +.Cm !\& +will be executed. +Multiple search patterns may be grouped together by delimiting them with +semicolons and zero or more whitespace characters. +These patterns are evaluated from left to right with the final cursor position +determined by the last search pattern. +A +.Cm z +command may be appended to the closed search expressions to reposition the +result line. +.Pp +.It Cm 0 +Move to the first character in the current line. +.Pp +.It Cm :\& +Execute an +.Nm ex +command. +.Pp +.It Xo +.Op Ar count +.Cm ;\& +.Xc +Repeat the last character find +(i.e., the last +.Cm F , f , T +or +.Cm t +command) +.Ar count +times. +.Pp +.It Xo +.Op Ar count +.Cm < +.Ar motion +.Xc +.It Xo +.Op Ar count +.Cm > +.Ar motion +.Xc +Shift +.Ar count +lines left or right, respectively, by an amount of +.Cm shiftwidth . +.Pp +.It Cm @ Ar buffer +Execute a named +.Ar buffer +as +.Nm vi +commands. +The buffer may include +.Nm ex +commands too, but they must be expressed as a +.Cm \&: +command. +If +.Ar buffer +is +.Sq @ +or +.Sq * , +then the last buffer executed shall be used. +.Pp +.It Xo +.Op Ar count +.Cm A +.Xc +Enter input mode, appending the text after the end of the line. +If a +.Ar count +argument is given, the characters input are repeated +.Ar count +\(mi 1 times after input mode is exited. +.Pp +.It Xo +.Op Ar count +.Cm B +.Xc +Move backwards +.Ar count +bigwords. +.Pp +.It Xo +.Op Ar buffer +.Cm C +.Xc +Change text from the current position to the end-of-line. +If +.Ar buffer +is specified, +.Dq yank +the deleted text into +.Ar buffer . +.Pp +.It Xo +.Op Ar buffer +.Cm D +.Xc +Delete text from the current position to the end-of-line. +If +.Ar buffer +is specified, +.Dq yank +the deleted text into +.Ar buffer . +.Pp +.It Xo +.Op Ar count +.Cm E +.Xc +Move forward +.Ar count +end-of-bigwords. +.Pp +.It Xo +.Op Ar count +.Cm F Aq Ar character +.Xc +Search +.Ar count +times backward through the current line for +.Aq Ar character . +.Pp +.It Xo +.Op Ar count +.Cm G +.Xc +Move to line +.Ar count , +or the last line of the file if +.Ar count +is not specified. +.Pp +.It Xo +.Op Ar count +.Cm H +.Xc +Move to the screen line +.Ar count +\(mi 1 lines below the top of the screen. +.Pp +.It Xo +.Op Ar count +.Cm I +.Xc +Enter input mode, inserting the text at the beginning of the line. +If a +.Ar count +argument is given, +the characters input are repeated +.Ar count +\(mi 1 more times. +.Pp +.It Xo +.Op Ar count +.Cm J +.Xc +Join +.Ar count +lines with the current line. +The spacing between two joined lines is set to two whitespace characters if the +former ends with a question mark, a period or an exclamation point. +It is set to one whitespace character otherwise. +.Pp +.It Xo +.Op Ar count +.Cm L +.Xc +Move to the screen line +.Ar count +\(mi 1 lines above the bottom of the screen. +.Pp +.It Cm M +Move to the screen line in the middle of the screen. +.Pp +.It Xo +.Op Ar count +.Cm O +.Xc +Enter input mode, appending text in a new line above the current line. +If a +.Ar count +argument is given, +the characters input are repeated +.Ar count +\(mi 1 more times. +.Pp +.It Xo +.Op Ar buffer +.Cm P +.Xc +Insert text from +.Ar buffer +before the current column if +.Ar buffer +is character-oriented or before the current line if it is line-oriented. +.Pp +.It Cm Q +Exit +.Nm vi +.Pq or visual +mode and switch to +.Nm ex +mode. +.Pp +.It Xo +.Op Ar count +.Cm R +.Xc +Enter input mode, replacing the characters in the current line. +If a +.Ar count +argument is given, +the characters input are repeated +.Ar count +\(mi 1 more times upon exit from insert mode. +.Pp +.It Xo +.Op Ar buffer +.Op Ar count +.Cm S +.Xc +Substitute +.Ar count +lines. +If +.Ar buffer +is specified, +.Dq yank +the deleted text into +.Ar buffer . +.Pp +.It Xo +.Op Ar count +.Cm T +.Aq Ar character +.Xc +Search backwards, +.Ar count +times, through the current line for the character after the specified +.Aq Ar character . +.Pp +.It Cm U +Restore the current line to its state before the cursor last moved to it. +.Pp +.It Xo +.Op Ar count +.Cm W +.Xc +Move forward +.Ar count +bigwords. +.Pp +.It Xo +.Op Ar buffer +.Op Ar count +.Cm X +.Xc +Delete +.Ar count +characters before the cursor, on the current line. +If +.Ar buffer +is specified, +.Dq yank +the deleted text into +.Ar buffer . +.Pp +.It Xo +.Op Ar buffer +.Op Ar count +.Cm Y +.Xc +Copy +.Pq or Dq yank +.Ar count +lines into +.Ar buffer . +.Pp +.It Cm ZZ +Write the file and exit +.Nm vi +if there are no more files to edit. +Entering two +.Dq quit +commands in a row ignores any remaining file to edit. +.Pp +.It Xo +.Op Ar count +.Cm [[ +.Xc +Back up +.Ar count +section boundaries. +.Pp +.It Xo +.Op Ar count +.Cm ]] +.Xc +Move forward +.Ar count +section boundaries. +.Pp +.It Cm \(ha +Move to the first non-blank character on the current line. +.Pp +.It Xo +.Op Ar count +.Cm _ +.Xc +Move down +.Ar count +\(mi 1 lines, to the first non-blank character. +.Pp +.It Xo +.Op Ar count +.Cm a +.Xc +Enter input mode, appending the text after the cursor. +If a +.Ar count +argument is given, +the characters input are repeated +.Ar count +number of times. +.Pp +.It Xo +.Op Ar count +.Cm b +.Xc +Move backwards +.Ar count +words. +.Pp +.It Xo +.Op Ar buffer +.Op Ar count +.Cm c +.Ar motion +.Xc +Change the region of text described by +.Ar count +and +.Ar motion . +If +.Ar buffer +is specified, +.Dq yank +the changed text into +.Ar buffer . +.Pp +.It Xo +.Op Ar buffer +.Op Ar count +.Cm d +.Ar motion +.Xc +Delete the region of text described by +.Ar count +and +.Ar motion . +If +.Ar buffer +is specified, +.Dq yank +the deleted text into +.Ar buffer . +.Pp +.It Xo +.Op Ar count +.Cm e +.Xc +Move forward +.Ar count +end-of-words. +.Pp +.It Xo +.Op Ar count +.Cm f Aq Ar character +.Xc +Search forward, +.Ar count +times, through the rest of the current line for +.Aq Ar character . +.Pp +.It Xo +.Op Ar count +.Cm i +.Xc +Enter input mode, inserting the text before the cursor. +If a +.Ar count +argument is given, +the characters input are repeated +.Ar count +number of times. +.Pp +.It Xo +.Cm m +.Aq Ar character +.Xc +Save the current context +.Pq line and column +as +.Aq Ar character . +.Pp +.It Xo +.Op Ar count +.Cm o +.Xc +Enter input mode, appending text in a new line under the current line. +If a +.Ar count +argument is given, +the characters input are repeated +.Ar count +\(mi 1 more times. +.Pp +.It Xo +.Op Ar buffer +.Cm p +.Xc +Append text from +.Ar buffer . +Text is appended after the current column if +.Ar buffer +is character oriented, or after the current line otherwise. +.Pp +.It Xo +.Op Ar count +.Cm r +.Aq Ar character +.Xc +Replace +.Ar count +characters with +.Ar character . +.Pp +.It Xo +.Op Ar buffer +.Op Ar count +.Cm s +.Xc +Substitute +.Ar count +characters in the current line starting with the current character. +If +.Ar buffer +is specified, +.Dq yank +the substituted text into +.Ar buffer . +.Pp +.It Xo +.Op Ar count +.Cm t +.Aq Ar character +.Xc +Search forward, +.Ar count +times, through the current line for the character immediately before +.Aq Ar character . +.Pp +.It Cm u +Undo the last change made to the file. +If repeated, the +.Cm u +command alternates between these two states. +The +.Cm .\& +command, when used immediately after +.Cm u , +causes the change log to be rolled forward or backward, depending on the action +of the +.Cm u +command. +.Pp +.It Xo +.Op Ar count +.Cm w +.Xc +Move forward +.Ar count +words. +.Pp +.It Xo +.Op Ar buffer +.Op Ar count +.Cm x +.Xc +Delete +.Ar count +characters at the current cursor position, but no more than there are till the +end of the line. +.Pp +.It Xo +.Op Ar buffer +.Op Ar count +.Cm y +.Ar motion +.Xc +Copy +.Pq or Dq yank +a text region specified by +.Ar count +and +.Ar motion +into a buffer. +.Pp +.It Xo +.Op Ar count1 +.Cm z +.Op Ar count2 +.Cm type +.Xc +Redraw, optionally repositioning and resizing the screen. +If +.Ar count2 +is specified, limit the screen size to +.Ar count2 +lines. +The following +.Cm type +characters may be used: +.Bl -tag -width Ds +.It Cm + +If +.Ar count1 +is specified, place the line +.Ar count1 +at the top of the screen. +Otherwise, display the screen after the current screen. +.It Aq Cm carriage-return +Place the line +.Ar count1 +at the top of the screen. +.It Cm .\& +Place the line +.Ar count1 +in the center of the screen. +.It Cm - +Place the line +.Ar count1 +at the bottom of the screen. +.It Cm \(ha +If +.Ar count1 +is given, +display the screen before the screen before +.Ar count1 +.Pq i.e., 2 screens before . +Otherwise, display the screen before the current screen. +.El +.Pp +.It Xo +.Op Ar count +.Cm {\& +.Xc +Move backward +.Ar count +paragraphs. +.Pp +.It Xo +.Op Ar column +.Cm |\& +.Xc +Move to a specific +.Ar column +position on the current line. +If +.Ar column +is omitted, +move to the start of the current line. +.Pp +.It Xo +.Op Ar count +.Cm }\& +.Xc +Move forward +.Ar count +paragraphs. +.Pp +.It Xo +.Op Ar count +.Cm \(ti +.Ar motion +.Xc +If the +.Cm tildeop +option is not set, reverse the case of the next +.Ar count +character(s) and no +.Ar motion +can be specified. +Otherwise +.Ar motion +is mandatory and +.Cm \(ti +reverses the case of the characters in a text region specified by the +.Ar count +and +.Ar motion . +.Pp +.It Aq Cm interrupt +Interrupt the current operation. +The +.Aq interrupt +character is usually +.Aq control-C . +.El +.Sh VI TEXT INPUT COMMANDS +The following section describes the commands available in the text input mode +of the +.Nm vi +editor. +.Pp +.Bl -tag -width Ds -compact +.It Aq Cm nul +Replay the previous input. +.Pp +.It Aq Cm control-D +Erase to the previous +.Ar shiftwidth +column boundary. +.Pp +.It Cm \(ha Ns Aq Cm control-D +Erase all of the autoindent characters, and reset the autoindent level. +.Pp +.It Cm 0 Ns Aq Cm control-D +Erase all of the autoindent characters. +.Pp +.It Aq Cm control-T +Insert sufficient +.Aq tab +and +.Aq space +characters to move forward to the next +.Ar shiftwidth +column boundary. +If the +.Cm expandtab +option is set, only insert +.Aq space +characters. +.Pp +.It Aq Cm erase +.It Aq Cm control-H +Erase the last character. +.Pp +.It Aq Cm literal next +Escape the next character from any special meaning. +The +.Aq literal\ \&next +character is usually +.Aq control-V . +.Pp +.It Aq Cm escape +Resolve all text input into the file, and return to command mode. +.Pp +.It Aq Cm line erase +Erase the current line. +.Pp +.It Aq Cm control-W +.It Aq Cm word erase +Erase the last word. +The definition of word is dependent on the +.Cm altwerase +and +.Cm ttywerase +options. +.Pp +.Sm off +.It Xo +.Aq Cm control-X +.Bq Cm 0-9A-Fa-f +.Cm + +.Xc +.Sm on +Insert a character with the specified hexadecimal value into the text. +.Pp +.It Aq Cm interrupt +Interrupt text input mode, returning to command mode. +The +.Aq interrupt +character is usually +.Aq control-C . +.El +.Sh EX COMMANDS +The following section describes the commands available in the +.Nm ex +editor. +In each entry below, the tag line is a usage synopsis for the command. +.Pp +.Bl -tag -width Ds -compact +.It Aq Cm end-of-file +Scroll the screen. +.Pp +.It Cm !\& Ar argument(s) +.It Xo +.Op Ar range +.Cm !\& +.Ar argument(s) +.Xc +Execute a shell command, or filter lines through a shell command. +.Pp +.It Cm \&" +A comment. +.Pp +.It Xo +.Op Ar range +.Cm nu Ns Op Cm mber +.Op Ar count +.Op Ar flags +.Xc +.It Xo +.Op Ar range +.Cm # +.Op Ar count +.Op Ar flags +.Xc +Display the selected lines, each preceded with its line number. +.Pp +.It Cm @ Ar buffer +.It Cm * Ar buffer +Execute a buffer. +.Pp +.It Xo +.Op Ar range +.Cm < Ns Op Cm < ... +.Op Ar count +.Op Ar flags +.Xc +Shift lines left. +.Pp +.It Xo +.Op Ar line +.Cm = +.Op Ar flags +.Xc +Display the line number of +.Ar line . +If +.Ar line +is not specified, display the line number of the last line in the file. +.Pp +.It Xo +.Op Ar range +.Cm > Ns Op Cm > ... +.Op Ar count +.Op Ar flags +.Xc +Shift lines right. +.Pp +.It Xo +.Cm ab Ns Op Cm breviate +.Ar lhs rhs +.Xc +.Nm vi +only. +Add +.Ar lhs +as an abbreviation for +.Ar rhs +to the abbreviation list. +.Pp +.It Xo +.Op Ar line +.Cm a Ns Op Cm ppend Ns +.Op Cm !\& +.Xc +The input text is appended after the specified line. +.Pp +.It Cm ar Ns Op Cm gs +Display the argument list. +.Pp +.It Cm bg +.Nm vi +only. +Background the current screen. +.Pp +.It Xo +.Op Ar range +.Cm c Ns Op Cm hange Ns +.Op Cm !\& +.Op Ar count +.Xc +The input text replaces the specified range. +.Pp +.It Xo +.Cm chd Ns Op Cm ir Ns +.Op Cm !\& +.Op Ar directory +.Xc +.It Xo +.Cm cd Ns Op Cm !\& +.Op Ar directory +.Xc +Change the current working directory. +.Pp +.It Xo +.Op Ar range +.Cm co Ns Op Cm py +.Ar line +.Op Ar flags +.Xc +.It Xo +.Op Ar range +.Cm t +.Ar line +.Op Ar flags +.Xc +Copy the specified lines after the destination +.Ar line . +.Pp +.It Xo +.Cm cs Ns Op Cm cope +.Cm add | find | help | kill | reset +.Xc +Execute a Cscope command. +.Pp +.It Xo +.Op Ar range +.Cm d Ns Op Cm elete +.Op Ar buffer +.Op Ar count +.Op Ar flags +.Xc +Delete the lines from the file. +.Pp +.It Xo +.Cm di Ns Op Cm splay +.Cm b Ns Oo Cm uffers Oc | +.Cm c Ns Oo Cm onnections Oc | +.Cm s Ns Oo Cm creens Oc | +.Cm t Ns Op Cm ags +.Xc +Display buffers, Cscope connections, screens or tags. +.Pp +.It Xo +.Op Cm Ee Ns +.Op Cm dit Ns +.Op Cm !\& +.Op Ar +cmd +.Op Ar file +.Xc +.It Xo +.Op Cm Ee Ns +.Cm x Ns Op Cm !\& +.Op Ar +cmd +.Op Ar file +.Xc +Edit a different file. +.Pp +.It Xo +.Cm exu Ns Op Cm sage +.Op Ar command +.Xc +Display usage for an +.Nm ex +command. +.Pp +.It Xo +.Cm f Ns Op Cm ile +.Op Ar file +.Xc +Display and optionally change the file name. +.Pp +.It Xo +.Op Cm Ff Ns +.Cm g +.Op Ar name +.Xc +.Nm vi +mode only. +Foreground the specified screen. +.Pp +.It Xo +.Op Ar range +.Cm g Ns Op Cm lobal +.No / Ns Ar pattern Ns / +.Op Ar commands +.Xc +.It Xo +.Op Ar range +.Cm v +.No / Ns Ar pattern Ns / +.Op Ar commands +.Xc +Apply commands to lines matching +.Pq Sq global +or not matching +.Pq Sq v +a pattern. +.Pp +.It Cm he Ns Op Cm lp +Display a help message. +.Pp +.It Xo +.Op Ar line +.Cm i Ns Op Cm nsert Ns +.Op Cm !\& +.Xc +The input text is inserted before the specified line. +.Pp +.It Xo +.Op Ar range +.Cm j Ns Op Cm oin Ns +.Op Cm !\& +.Op Ar count +.Op Ar flags +.Xc +Join lines of text together. +.Pp +.It Xo +.Op Ar range +.Cm l Ns Op Cm ist +.Op Ar count +.Op Ar flags +.Xc +Display the lines unambiguously. +.Pp +.It Xo +.Cm map Ns Op Cm !\& +.Op Ar lhs rhs +.Xc +Define or display maps +.Pq for Nm vi No only . +.Pp +.It Xo +.Op Ar line +.Cm ma Ns Op Cm rk +.Aq Ar character +.Xc +.It Xo +.Op Ar line +.Cm k Aq Ar character +.Xc +Mark the line with the mark +.Aq Ar character . +.Pp +.It Xo +.Op Ar range +.Cm m Ns Op Cm ove +.Ar line +.Xc +Move the specified lines after the target line. +.Pp +.It Xo +.Cm mk Ns Op Cm exrc Ns +.Op Cm !\& +.Ar file +.Xc +Write the abbreviations, editor options and maps to the specified +.Ar file . +.Pp +.It Xo +.Op Cm Nn Ns +.Op Cm ext Ns +.Op Cm !\& +.Op Ar +.Xc +Edit the next file from the argument list. +.\" .Pp +.\" .It Xo +.\" .Op Ar line +.\" .Cm o Ns Op Cm pen +.\" .No / Ns Ar pattern Ns / +.\" .Op Ar flags +.\" .Xc +.\" Enter open mode. +.Pp +.It Cm pre Ns Op Cm serve +Save the file in a form that can later be recovered using the +.Nm ex +.Fl r +option. +.Pp +.It Xo +.Op Cm \&Pp Ns +.Cm rev Ns Op Cm ious Ns +.Op Cm !\& +.Xc +Edit the previous file from the argument list. +.Pp +.It Xo +.Op Ar range +.Cm p Ns Op Cm rint +.Op Ar count +.Op Ar flags +.Xc +Display the specified lines. +.Pp +.It Xo +.Op Ar line +.Cm pu Ns Op Cm t +.Op Ar buffer +.Xc +Append buffer contents to the current line. +.Pp +.It Xo +.Cm q Ns Op Cm uit Ns +.Op Cm !\& +.Xc +End the editing session. +.Pp +.It Xo +.Op Ar line +.Cm r Ns Op Cm ead Ns +.Op Cm !\& +.Op Ar file +.Xc +Read a file. +.Pp +.It Xo +.Cm rec Ns Op Cm over +.Ar file +.Xc +Recover +.Ar file +if it was previously saved. +.Pp +.It Xo +.Cm res Ns Op Cm ize +.Op Cm + Ns | Ns Cm - Ns +.Ar size +.Xc +.Nm vi +mode only. +Grow or shrink the current screen. +.Pp +.It Xo +.Cm rew Ns Op Cm ind Ns +.Op Cm !\& +.Xc +Rewind the argument list. +.Pp +.It Xo +.Cm se Ns Op Cm t +.Sm off +.Op option Oo = Oo value Oc Oc \ \&... +.Sm on +.Pf \ \& Op nooption ... +.Op option? ... +.Op Ar all +.Xc +Display or set editor options. +.Pp +.It Cm sh Ns Op Cm ell +Run a shell program. +.Pp +.It Xo +.Cm so Ns Op Cm urce +.Ar file +.Xc +Read and execute +.Nm ex +commands from a file. +.Pp +.It Xo +.Op Ar range +.Cm s Ns Op Cm ubstitute +.Sm off +.Op / Ar pattern No / Ar replace No / +.Sm on +.Pf \ \& Op Ar options +.Op Ar count +.Op Ar flags +.Xc +.It Xo +.Op Ar range +.Cm & +.Op Ar options +.Op Ar count +.Op Ar flags +.Xc +.It Xo +.Op Ar range +.Cm \(ti +.Op Ar options +.Op Ar count +.Op Ar flags +.Xc +Make substitutions. +The +.Ar replace +field may contain any of the following sequences: +.Bl -tag -width Ds +.It Sq \*(Am +The text matched by +.Ar pattern . +.It Sq \(ti +The replacement part of the previous +.Cm substitute +command. +.It Sq % +If this is the entire +.Ar replace +pattern, the replacement part of the previous +.Cm substitute +command. +.It Sq \e Ns Ar \(sh +Where +.Sq Ar \(sh +is an integer from 1 to 9, the text matched by the +.Ar # Ns 'th subexpression in +.Ar pattern . +.It Sq \eL +Causes the characters up to the end of the line of the next occurrence of +.Sq \eE +or +.Sq \ee +to be converted to lowercase. +.It Sq \el +Causes the next character to be converted to lowercase. +.It Sq \eU +Causes the characters up to the end of the line of the next occurrence of +.Sq \eE +or +.Sq \ee +to be converted to uppercase. +.It Sq \eu +Causes the next character to be converted to uppercase. +.El +.Pp +.It Xo +.Cm su Ns Op Cm spend Ns +.Op Cm !\& +.Xc +.It Xo +.Cm st Ns Op Cm op Ns +.Op Cm !\& +.Xc +.It Aq Cm suspend +Suspend the edit session. +The +.Aq suspend +character is usually +.Aq control-Z . +.Pp +.It Xo +.Op Cm Tt Ns +.Cm a Ns Op Cm g Ns +.Op Cm !\& +.Ar tagstring +.Xc +Edit the file containing the specified tag. +.Pp +.It Xo +.Cm tagn Ns Op Cm ext Ns +.Op Cm !\& +.Xc +Edit the file containing the next context for the current tag. +.Pp +.It Xo +.Cm tagp Ns Op Cm op Ns +.Op Cm !\& +.Op Ar file | number +.Xc +Pop to the specified tag in the tags stack. +.Pp +.It Xo +.Cm tagpr Ns Op Cm ev Ns +.Op Cm !\& +.Xc +Edit the file containing the previous context for the current tag. +.Pp +.It Xo +.Cm tagt Ns Op Cm op Ns +.Op Cm !\& +.Xc +Pop to the least recent tag on the tags stack, clearing the stack. +.Pp +.It Xo +.Cm una Ns Op Cm bbreviate +.Ar lhs +.Xc +.Nm vi +only. +Delete an abbreviation. +.Pp +.It Cm u Ns Op Cm ndo +Undo the last change made to the file. +.Pp +.It Xo +.Cm unm Ns Op Cm ap Ns +.Op Cm !\& +.Ar lhs +.Xc +Unmap a mapped string. +.Pp +.It Cm ve Ns Op Cm rsion +Display the version of the +.Nm ex Ns / Ns Nm vi +editor. +.Pp +.It Xo +.Op Ar line +.Cm vi Ns Op Cm sual +.Op Ar type +.Op Ar count +.Op Ar flags +.Xc +.Nm ex +mode only. +Enter +.Nm vi . +.Pp +.It Xo +.Op Cm Vi Ns +.Cm i Ns Op Cm sual Ns +.Op Cm !\& +.Op Ar +cmd +.Op Ar file +.Xc +.Nm vi +mode only. +Edit a new file. +.Pp +.It Xo +.Cm viu Ns Op Cm sage +.Op Ar command +.Xc +Display usage for a +.Nm vi +command. +.Pp +.It Xo +.Op Ar range +.Cm w Ns Op Cm rite Ns +.Op Cm !\& +.Op >> +.Op Ar file +.Xc +.It Xo +.Op Ar range +.Cm w Ns Op Cm rite +.Op Cm !\& +.Op Ar file +.Xc +.It Xo +.Op Ar range +.Cm wn Ns Op Cm !\& +.Op >> +.Op Ar file +.Xc +.It Xo +.Op Ar range +.Cm wq Ns Op Cm !\& +.Op >> +.Op Ar file +.Xc +Write the file. +.Pp +.It Xo +.Op Ar range +.Cm x Ns Op Cm it Ns +.Op Cm !\& +.Op Ar file +.Xc +Exit the editor, +writing the file if it has been modified. +.Pp +.It Xo +.Op Ar range +.Cm ya Ns Op Cm nk +.Op Ar buffer +.Op Ar count +.Xc +Copy the specified lines to a buffer. +.Pp +.It Xo +.Op Ar line +.Cm z +.Op Ar type +.Op Ar count +.Op Ar flags +.Xc +Adjust the window. +.El +.Sh SET OPTIONS +There are a large number of options that may be set +.Pq or unset +to change the editor's behavior. +This section describes the options, their abbreviations and their +default values. +.Pp +In each entry below, the first part of the tag line is the full name +of the option, followed by any equivalent abbreviations. +The part in square brackets is the default value of the option. +Most of the options are boolean, i.e., they are either on or off, +and do not have an associated value. +.Pp +Options apply to both +.Nm ex +and +.Nm vi +modes, unless otherwise specified. +.Bl -tag -width Ds +.It Cm altwerase Bq off +.Nm vi +only. +Select an alternate word erase algorithm. +.It Cm autoindent , ai Bq off +Automatically indent new lines. +.It Cm autoprint , ap Bq on +.Nm ex +only. +Display the current line automatically. +.It Cm autowrite , aw Bq off +Write modified files automatically when changing files or suspending the editor +session. +.It Cm backup Bq \&"\&" +Back up files before they are overwritten. +.It Cm beautify , bf Bq off +Discard control characters. +.It Cm cdpath Bo environment variable Ev CDPATH , or current directory Bc +The directory paths used as path prefixes for the +.Cm cd +command. +.It Cm cedit Bq no default +Set the character to edit the colon command-line history. +.It Cm columns , co Bq 80 +Set the number of columns in the screen. +.It Cm comment Bq off +.Nm vi +only. +Skip leading comments in shell, C and C++ language files. +.It Cm directory , dir Bo environment variable Ev TMPDIR , or Pa /tmp Bc +The directory where temporary files are created. +.It Cm edcompatible , ed Bq off +Remember the values of the +.Sq c +and +.Sq g +suffixes to the +.Cm substitute +commands, instead of initializing them as unset for each new command. +.It Cm errorbells , eb Bq off +.Nm ex +only. +Announce error messages with a bell. +.It Cm escapetime Bq 1 +The tenths of a second +.Nm ex Ns / Ns Nm vi +waits for a subsequent key to complete an +.Aq escape +key mapping. +.It Cm expandtab , et Bq off +Expand +.Aq tab +characters to +.Aq space +when inserting, replacing or shifting text, autoindenting, +indenting with +.Aq Ic control-T , +outdenting with +.Aq Ic control-D , +or +when filtering lines with the +.Cm !\& +command. +.It Cm exrc , ex Bq off +Read the startup files in the local directory. +.It Cm extended Bq off +Use extended regular expressions +.Pq EREs +rather than basic regular expressions +.Pq BREs . +See +.Xr re_format 7 +for more information on regular expressions. +.It Cm filec Bq Aq tab +Set the character to perform file path completion on the colon command line. +.It Cm fileencoding , fe Bq auto detect +Set the encoding of the current file. +.It Cm flash Bq on +Flash the screen instead of beeping the keyboard on error. +.It Cm hardtabs, ht Bq 0 +Set the spacing between hardware tab settings. +This option currently has no effect. +.It Cm iclower Bq off +Makes all regular expressions case-insensitive, +as long as an upper-case letter does not appear in the search string. +.It Cm ignorecase , ic Bq off +Ignore case differences in regular expressions. +.It Cm inputencoding , ie Bq locale +Set the encoding of your input characters. +.It Cm keytime Bq 6 +The tenths of a second +.Nm ex Ns / Ns Nm vi +waits for a subsequent key to complete a key mapping. +.It Cm leftright Bq off +.Nm vi +only. +Do left-right scrolling. +.It Cm lines , li Bq 24 +.Nm vi +only. +Set the number of lines in the screen. +.It Cm lisp Bq off +.Nm vi +only. +Modify various search commands and options to work with Lisp. +This option is not yet implemented. +.It Cm list Bq off +Display lines in an unambiguous fashion. +.It Cm lock Bq on +Attempt to get an exclusive lock on any file being edited, read or written. +.It Cm magic Bq on +When turned off, all regular expression characters except for +.Sq \(ha +and +.Sq \(Do +are treated as ordinary characters. +Preceding individual characters by +.Sq \e +re-enables them. +.It Cm matchchars Bq []{}() +Character pairs looked for by the +.Cm % +command. +.It Cm matchtime Bq 7 +.Nm vi +only. +The tenths of a second +.Nm ex Ns / Ns Nm vi +pauses on the matching character when the +.Cm showmatch +option is set. +.It Cm mesg Bq on +Permit messages from other users. +.It Cm msgcat Bq /usr/share/vi/catalog/ +Selects a message catalog to be used to display error and informational +messages in a specified language. +.It Cm modelines , modeline Bq off +Read the first and last few lines of each file for +.Nm ex +commands. +This option will never be implemented. +.It Cm noprint Bq \&"\&" +Characters that are never handled as printable characters. +.It Cm number , nu Bq off +Precede each line displayed with its current line number. +.It Cm octal Bq off +Display unknown characters as octal numbers, instead of the default +hexadecimal. +.It Cm open Bq on +.Nm ex +only. +If this option is not set, the +.Cm open +and +.Cm visual +commands are disallowed. +.It Cm optimize , opt Bq on +.Nm vi +only. +Optimize text throughput to dumb terminals. +This option is not yet implemented. +.It Cm paragraphs , para Bq "IPLPPPQPP LIpplpipbp" +.Nm vi +only. +Define additional paragraph boundaries for the +.Cm {\& +and +.Cm }\& +commands. +.It Cm path Bq \&"\&" +Define additional directories to search for files being edited. +.It Cm print Bq \&"\&" +Characters that are always handled as printable characters. +.It Cm prompt Bq on +.Nm ex +only. +Display a command prompt. +.It Cm readonly , ro Bq off +Mark the file and session as read-only. +.It Cm recdir Bq /var/tmp/vi.recover +The directory where recovery files are stored. +.It Cm redraw , re Bq off +.Nm vi +only. +Simulate an intelligent terminal on a dumb one. +This option is not yet implemented. +.It Cm remap Bq on +Remap keys until resolved. +.It Cm report Bq 5 +Set the number of lines about which the editor reports changes or yanks. +.It Cm ruler Bq off +.Nm vi +only. +Display a row/column ruler on the colon command line. +.It Cm scroll , scr Bq "window size / 2" +Set the number of lines scrolled. +.It Cm searchincr Bq off +Makes the +.Cm / +and +.Cm ?\& +commands incremental. +.It Cm sections , sect Bq "NHSHH HUnhsh" +.Nm vi +only. +Define additional section boundaries for the +.Cm [[ +and +.Cm ]] +commands. +.It Cm secure Bq off +Turns off all access to external programs. +.It Cm shell , sh Bo environment variable Ev SHELL , or Pa /bin/sh Bc +Select the shell used by the editor. +.It Cm shellmeta Bq \(ti{[*?$\`\(aq\&"\e +Set the meta characters checked to determine if file name expansion +is necessary. +.It Cm shiftwidth , sw Bq 8 +Set the autoindent and shift command indentation width. +.It Cm showmatch , sm Bq off +.Nm vi +only. +Note the left matching characters when the right ones are inserted. +.It Cm showmode , smd Bq off +.Nm vi +only. +Display the current editor mode and a +.Dq modified +flag. +.It Cm sidescroll Bq 16 +.Nm vi +only. +Set the amount a left-right scroll will shift. +.It Cm slowopen , slow Bq off +Delay display updating during text input. +This option is not yet implemented. +.It Cm sourceany Bq off +Read startup files not owned by the current user. +This option will never be implemented. +.It Cm tabstop , ts Bq 8 +This option sets tab widths for the editor display. +.It Cm taglength , tl Bq 0 +Set the number of significant characters in tag names. +.It Cm tags , tag Bq tags +Set the list of tags files. +.It Xo +.Cm term , ttytype , tty +.Bq environment variable Ev TERM +.Xc +Set the terminal type. +.It Cm terse Bq off +This option has historically made editor messages less verbose. +It has no effect in this implementation. +.It Cm tildeop Bq off +Modify the +.Cm \(ti +command to take an associated motion. +.It Cm timeout , to Bq on +Time out on keys which may be mapped. +.It Cm ttywerase Bq off +.Nm vi +only. +Select an alternate erase algorithm. +.It Cm verbose Bq off +.Nm vi +only. +Display an error message for every error. +.It Cm w300 Bq no default +.Nm vi +only. +Set the window size if the baud rate is less than 1200 baud. +.It Cm w1200 Bq no default +.Nm vi +only. +Set the window size if the baud rate is equal to 1200 baud. +.It Cm w9600 Bq no default +.Nm vi +only. +Set the window size if the baud rate is greater than 1200 baud. +.It Cm warn Bq on +.Nm ex +only. +This option causes a warning message to be printed on the terminal +if the file has been modified since it was last written, before a +.Cm !\& +command. +.It Xo +.Cm window , w , wi +.Bq environment variable Ev LINES No \(mi 1 +.Xc +Set the window size for the screen. +.It Cm windowname Bq off +Change the icon/window name to the current file name. +.It Cm wraplen , wl Bq 0 +.Nm vi +only. +Break lines automatically, +the specified number of columns from the left-hand margin. +If both the +.Cm wraplen +and +.Cm wrapmargin +edit options are set, the +.Cm wrapmargin +value is used. +.It Cm wrapmargin , wm Bq 0 +.Nm vi +only. +Break lines automatically, +the specified number of columns from the right-hand margin. +If both the +.Cm wraplen +and +.Cm wrapmargin +edit options are set, the +.Cm wrapmargin +value is used. +.It Cm wrapscan , ws Bq on +Set searches to wrap around the end or beginning of the file. +.It Cm writeany , wa Bq off +Turn off file-overwriting checks. +.El +.Sh ENVIRONMENT +.Bl -tag -width "COLUMNS" +.It Ev COLUMNS +The number of columns on the screen. +This value overrides any system or terminal specific values. +If the +.Ev COLUMNS +environment variable is not set when +.Nm ex Ns / Ns Nm vi +runs, or the +.Cm columns +option is explicitly reset by the user, +.Nm ex Ns / Ns Nm vi +enters the value into the environment. +.It Ev EXINIT +A list of +.Nm ex +startup commands, read after +.Pa /etc/vi.exrc +unless the variable +.Ev NEXINIT +is also set. +.It Ev HOME +The user's home directory, used as the initial directory path for the startup +.Pa $HOME/.nexrc +and +.Pa $HOME/.exrc +files. +This value is also used as the default directory for the +.Cm cd +command. +.It Ev LINES +The number of rows on the screen. +This value overrides any system or terminal specific values. +If the +.Ev LINES +environment variable is not set when +.Nm ex Ns / Ns Nm vi +runs, or the +.Cm lines +option is explicitly reset by the user, +.Nm ex Ns / Ns Nm vi +enters the value into the environment. +.It Ev NEXINIT +A list of +.Nm ex +startup commands, read after +.Pa /etc/vi.exrc . +.It Ev SHELL +The user's shell of choice +.Pq see also the Cm shell No option . +.It Ev TERM +The user's terminal type. +The default is the type +.Dq unknown . +If the +.Ev TERM +environment variable is not set when +.Nm ex Ns / Ns Nm vi +runs, or the +.Cm term +option is explicitly reset by the user, +.Nm ex Ns / Ns Nm vi +enters the value into the environment. +.It Ev TMPDIR +The location used to store temporary files +.Pq see also the Cm directory No edit option . +.El +.Sh ASYNCHRONOUS EVENTS +.Bl -tag -width "SIGWINCH" -compact +.It Dv SIGALRM +.Nm vi Ns / Ns Nm ex +uses this signal for periodic backups of file modifications and to display +.Dq busy +messages when operations are likely to take a long time. +.Pp +.It Dv SIGHUP +.It Dv SIGTERM +If the current buffer has changed since it was last written in its entirety, +the editor attempts to save the modified file so it can be later recovered. +See the +.Nm vi Ns / Ns Nm ex +reference manual section +.Sx Recovery +for more information. +.Pp +.It Dv SIGINT +When an interrupt occurs, the current operation is halted +and the editor returns to the command level. +If interrupted during text input, +the text already input is resolved into the file as if the text +input had been normally terminated. +.Pp +.It Dv SIGWINCH +The screen is resized. +See the +.Nm vi Ns / Ns Nm ex +reference manual section +.Sx Sizing the Screen +for more information. +.\" .Pp +.\" .It Dv SIGCONT +.\" .It Dv SIGTSTP +.\" .Nm vi Ns / Ns Nm ex +.\" ignores these signals. +.El +.Sh FILES +.Bl -tag -width "/var/tmp/vi.recover" +.It Pa /bin/sh +The default user shell. +.It Pa /etc/vi.exrc +System-wide +.Nm vi +startup file. +It is read for +.Nm ex +commands first in the startup sequence. +Must be owned by root or the user, +and writable only by the owner. +.It Pa /tmp +Temporary file directory. +.It Pa /var/tmp/vi.recover +The default recovery file directory. +.It Pa $HOME/.nexrc +First choice for user's home directory startup file, read for +.Nm ex +commands right after +.Pa /etc/vi.exrc +unless either +.Ev NEXINIT +or +.Ev EXINIT +are set. +Must be owned by root or the user, +and writable only by the owner. +.It Pa $HOME/.exrc +Second choice for user's home directory startup file, read for +.Nm ex +commands under the same conditions as +.Pa $HOME/.nexrc . +.It Pa .nexrc +First choice for local directory startup file, read for +.Nm ex +commands at the end of the startup sequence if the +.Cm exrc +option was turned on earlier. +Must be owned by the user +and writable only by the owner. +.It Pa .exrc +Second choice for local directory startup file, read for +.Nm ex +commands under the same conditions as +.Pa .nexrc . +.El +.Sh EXIT STATUS +The +.Nm ex +and +.Nm vi +utilities exit 0 on success, +and \*(Gt0 if an error occurs. +.Sh SEE ALSO +.Xr ctags 1 , +.Xr iconv 1 , +.Xr re_format 7 +.Sh STANDARDS +.Nm nex Ns / Ns Nm nvi +is close to +.St -p1003.1-2008 . +That document differs from historical +.Nm ex Ns / Ns Nm vi +practice in several places; there are changes to be made on both sides. +.Sh HISTORY +The +.Nm ex +editor first appeared in +.Bx 1 . +The +.Nm nex Ns / Ns Nm nvi +replacements for the +.Nm ex Ns / Ns Nm vi +editor first appeared in +.Bx 4.4 . +.Sh AUTHORS +.An Bill Joy +wrote the original version of +.Nm ex +in 1977. Property changes on: head/contrib/nvi/man/vi.1 ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/contrib/nvi/vi/getc.c =================================================================== --- head/contrib/nvi/vi/getc.c (revision 365498) +++ head/contrib/nvi/vi/getc.c (revision 365499) @@ -1,222 +1,218 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: getc.c,v 10.13 2011/12/27 00:49:31 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * Character stream routines -- * These routines return the file a character at a time. There are two * special cases. First, the end of a line, end of a file, start of a * file and empty lines are returned as special cases, and no character * is returned. Second, empty lines include lines that have only white * space in them, because the vi search functions don't care about white * space, and this makes it easier for them to be consistent. */ /* * cs_init -- * Initialize character stream routines. * * PUBLIC: int cs_init(SCR *, VCS *); */ int cs_init(SCR *sp, VCS *csp) { int isempty; if (db_eget(sp, csp->cs_lno, &csp->cs_bp, &csp->cs_len, &isempty)) { if (isempty) msgq(sp, M_BERR, "177|Empty file"); return (1); } if (csp->cs_len == 0 || v_isempty(csp->cs_bp, csp->cs_len)) { csp->cs_cno = 0; csp->cs_flags = CS_EMP; } else { csp->cs_flags = 0; csp->cs_ch = csp->cs_bp[csp->cs_cno]; } return (0); } /* * cs_next -- * Retrieve the next character. * * PUBLIC: int cs_next(SCR *, VCS *); */ int cs_next(SCR *sp, VCS *csp) { CHAR_T *p; switch (csp->cs_flags) { case CS_EMP: /* EMP; get next line. */ case CS_EOL: /* EOL; get next line. */ if (db_get(sp, ++csp->cs_lno, 0, &p, &csp->cs_len)) { --csp->cs_lno; csp->cs_flags = CS_EOF; } else { csp->cs_bp = p; if (csp->cs_len == 0 || v_isempty(csp->cs_bp, csp->cs_len)) { csp->cs_cno = 0; csp->cs_flags = CS_EMP; } else { csp->cs_flags = 0; csp->cs_ch = csp->cs_bp[csp->cs_cno = 0]; } } break; case 0: if (csp->cs_cno == csp->cs_len - 1) csp->cs_flags = CS_EOL; else csp->cs_ch = csp->cs_bp[++csp->cs_cno]; break; case CS_EOF: /* EOF. */ break; default: abort(); /* NOTREACHED */ } return (0); } /* * cs_fspace -- * If on a space, eat forward until something other than a * whitespace character. * * XXX * Semantics of checking the current character were coded for the fword() * function -- once the other word routines are converted, they may have * to change. * * PUBLIC: int cs_fspace(SCR *, VCS *); */ int cs_fspace(SCR *sp, VCS *csp) { if (csp->cs_flags != 0 || !ISBLANK(csp->cs_ch)) return (0); for (;;) { if (cs_next(sp, csp)) return (1); if (csp->cs_flags != 0 || !ISBLANK(csp->cs_ch)) break; } return (0); } /* * cs_fblank -- * Eat forward to the next non-whitespace character. * * PUBLIC: int cs_fblank(SCR *, VCS *); */ int cs_fblank(SCR *sp, VCS *csp) { for (;;) { if (cs_next(sp, csp)) return (1); if (csp->cs_flags == CS_EOL || csp->cs_flags == CS_EMP || (csp->cs_flags == 0 && ISBLANK(csp->cs_ch))) continue; break; } return (0); } /* * cs_prev -- * Retrieve the previous character. * * PUBLIC: int cs_prev(SCR *, VCS *); */ int cs_prev(SCR *sp, VCS *csp) { switch (csp->cs_flags) { case CS_EMP: /* EMP; get previous line. */ case CS_EOL: /* EOL; get previous line. */ if (csp->cs_lno == 1) { /* SOF. */ csp->cs_flags = CS_SOF; break; } if (db_get(sp, /* The line should exist. */ --csp->cs_lno, DBG_FATAL, &csp->cs_bp, &csp->cs_len)) { ++csp->cs_lno; return (1); } if (csp->cs_len == 0 || v_isempty(csp->cs_bp, csp->cs_len)) { csp->cs_cno = 0; csp->cs_flags = CS_EMP; } else { csp->cs_flags = 0; csp->cs_cno = csp->cs_len - 1; csp->cs_ch = csp->cs_bp[csp->cs_cno]; } break; case CS_EOF: /* EOF: get previous char. */ case 0: if (csp->cs_cno == 0) if (csp->cs_lno == 1) csp->cs_flags = CS_SOF; else csp->cs_flags = CS_EOL; else csp->cs_ch = csp->cs_bp[--csp->cs_cno]; break; case CS_SOF: /* SOF. */ break; default: abort(); /* NOTREACHED */ } return (0); } /* * cs_bblank -- * Eat backward to the next non-whitespace character. * * PUBLIC: int cs_bblank(SCR *, VCS *); */ int cs_bblank(SCR *sp, VCS *csp) { for (;;) { if (cs_prev(sp, csp)) return (1); if (csp->cs_flags == CS_EOL || csp->cs_flags == CS_EMP || (csp->cs_flags == 0 && ISBLANK(csp->cs_ch))) continue; break; } return (0); } Index: head/contrib/nvi/vi/v_at.c =================================================================== --- head/contrib/nvi/vi/v_at.c (revision 365498) +++ head/contrib/nvi/vi/v_at.c (revision 365499) @@ -1,113 +1,109 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_at.c,v 10.11 2001/06/25 15:19:30 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_at -- @ * Execute a buffer. * * PUBLIC: int v_at(SCR *, VICMD *); */ int v_at(SCR *sp, VICMD *vp) { CB *cbp; CHAR_T name; TEXT *tp; size_t len; char nbuf[20]; CHAR_T wbuf[20]; CHAR_T *wp; size_t wlen; /* * !!! * Historically, [@*] and [@*][@*] executed the most * recently executed buffer in ex mode. In vi mode, only @@ repeated * the last buffer. We change historic practice and make @* work from * vi mode as well, it's simpler and more consistent. * * My intent is that *[buffer] will, in the future, pass the buffer to * whatever interpreter is loaded. */ name = F_ISSET(vp, VC_BUFFER) ? vp->buffer : '@'; if (name == '@' || name == '*') { if (!F_ISSET(sp, SC_AT_SET)) { ex_emsg(sp, NULL, EXM_NOPREVBUF); return (1); } name = sp->at_lbuf; } F_SET(sp, SC_AT_SET); CBNAME(sp, cbp, name); if (cbp == NULL) { ex_emsg(sp, KEY_NAME(sp, name), EXM_EMPTYBUF); return (1); } /* Save for reuse. */ sp->at_lbuf = name; /* * The buffer is executed in vi mode, while in vi mode, so simply * push it onto the terminal queue and continue. * * !!! * Historic practice is that if the buffer was cut in line mode, * were appended to each line as it was pushed onto * the stack. If the buffer was cut in character mode, * were appended to all lines but the last one. * * XXX * Historic practice is that execution of an @ buffer could be * undone by a single 'u' command, i.e. the changes were grouped * together. We don't get this right; I'm waiting for the new DB * logging code to be available. */ TAILQ_FOREACH_REVERSE(tp, cbp->textq, _texth, q) { if (((F_ISSET(cbp, CB_LMODE) || TAILQ_NEXT(tp, q) != NULL) && v_event_push(sp, NULL, L("\n"), 1, 0)) || v_event_push(sp, NULL, tp->lb, tp->len, 0)) return (1); } /* * !!! * If any count was supplied, it applies to the first command in the * at buffer. */ if (F_ISSET(vp, VC_C1SET)) { len = snprintf(nbuf, sizeof(nbuf), "%lu", vp->count); CHAR2INT(sp, nbuf, len, wp, wlen); MEMCPY(wbuf, wp, wlen); if (v_event_push(sp, NULL, wp, wlen, 0)) return (1); } return (0); } Index: head/contrib/nvi/vi/v_ch.c =================================================================== --- head/contrib/nvi/vi/v_ch.c (revision 365498) +++ head/contrib/nvi/vi/v_ch.c (revision 365499) @@ -1,282 +1,287 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_ch.c,v 10.11 2011/12/02 19:49:50 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static void notfound(SCR *, ARG_CHAR_T); static void noprev(SCR *); /* * v_chrepeat -- [count]; * Repeat the last F, f, T or t search. * * PUBLIC: int v_chrepeat(SCR *, VICMD *); */ int v_chrepeat(SCR *sp, VICMD *vp) { vp->character = VIP(sp)->lastckey; switch (VIP(sp)->csearchdir) { case CNOTSET: noprev(sp); return (1); case FSEARCH: return (v_chF(sp, vp)); case fSEARCH: return (v_chf(sp, vp)); case TSEARCH: return (v_chT(sp, vp)); case tSEARCH: return (v_cht(sp, vp)); default: abort(); } /* NOTREACHED */ } /* * v_chrrepeat -- [count], * Repeat the last F, f, T or t search in the reverse direction. * * PUBLIC: int v_chrrepeat(SCR *, VICMD *); */ int v_chrrepeat(SCR *sp, VICMD *vp) { cdir_t savedir; int rval; vp->character = VIP(sp)->lastckey; savedir = VIP(sp)->csearchdir; switch (VIP(sp)->csearchdir) { case CNOTSET: noprev(sp); return (1); case FSEARCH: rval = v_chf(sp, vp); break; case fSEARCH: rval = v_chF(sp, vp); break; case TSEARCH: rval = v_cht(sp, vp); break; case tSEARCH: rval = v_chT(sp, vp); break; default: abort(); } VIP(sp)->csearchdir = savedir; return (rval); } /* * v_cht -- [count]tc * Search forward in the line for the character before the next * occurrence of the specified character. * * PUBLIC: int v_cht(SCR *, VICMD *); */ int v_cht(SCR *sp, VICMD *vp) { if (v_chf(sp, vp)) return (1); /* * v_chf places the cursor on the character, where the 't' * command wants it to its left. We know this is safe since * we had to move right for v_chf() to have succeeded. */ --vp->m_stop.cno; /* * Make any necessary correction to the motion decision made * by the v_chf routine. */ if (!ISMOTION(vp)) vp->m_final = vp->m_stop; VIP(sp)->csearchdir = tSEARCH; return (0); } /* * v_chf -- [count]fc * Search forward in the line for the next occurrence of the * specified character. * * PUBLIC: int v_chf(SCR *, VICMD *); */ int v_chf(SCR *sp, VICMD *vp) { size_t len; u_long cnt; int isempty; ARG_CHAR_T key; CHAR_T *endp, *p, *startp; /* * !!! * If it's a dot command, it doesn't reset the key for which we're * searching, e.g. in "df1|f2|.|;", the ';' searches for a '2'. */ key = vp->character; if (!F_ISSET(vp, VC_ISDOT)) VIP(sp)->lastckey = key; VIP(sp)->csearchdir = fSEARCH; if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto empty; return (1); } if (len == 0) { empty: notfound(sp, key); return (1); } endp = (startp = p) + len; p += vp->m_start.cno; for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) { while (++p < endp && *p != key); if (p == endp) { notfound(sp, key); return (1); } } vp->m_stop.cno = p - startp; /* * Non-motion commands move to the end of the range. * Delete and yank stay at the start, ignore others. */ vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; return (0); } /* * v_chT -- [count]Tc * Search backward in the line for the character after the next * occurrence of the specified character. * * PUBLIC: int v_chT(SCR *, VICMD *); */ int v_chT(SCR *sp, VICMD *vp) { if (v_chF(sp, vp)) return (1); + + /* + * Check whether the matching character is to the immediate left + * of the original cursor position, offset adjusted for a motion + * command. If so, no movement is required. + */ + if (vp->m_start.cno == vp->m_stop.cno) { + return (1); + } /* * v_chF places the cursor on the character, where the 'T' * command wants it to its right. We know this is safe since * we had to move left for v_chF() to have succeeded. */ ++vp->m_stop.cno; vp->m_final = vp->m_stop; VIP(sp)->csearchdir = TSEARCH; return (0); } /* * v_chF -- [count]Fc * Search backward in the line for the next occurrence of the * specified character. * * PUBLIC: int v_chF(SCR *, VICMD *); */ int v_chF(SCR *sp, VICMD *vp) { size_t len; u_long cnt; int isempty; ARG_CHAR_T key; CHAR_T *endp, *p; /* * !!! * If it's a dot command, it doesn't reset the key for which * we're searching, e.g. in "df1|f2|.|;", the ';' searches * for a '2'. */ key = vp->character; if (!F_ISSET(vp, VC_ISDOT)) VIP(sp)->lastckey = key; VIP(sp)->csearchdir = FSEARCH; if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto empty; return (1); } if (len == 0) { empty: notfound(sp, key); return (1); } endp = p - 1; p += vp->m_start.cno; for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) { while (--p > endp && *p != key); if (p == endp) { notfound(sp, key); return (1); } } vp->m_stop.cno = (p - endp) - 1; /* * All commands move to the end of the range. Motion commands * adjust the starting point to the character before the current * one. */ vp->m_final = vp->m_stop; if (ISMOTION(vp)) --vp->m_start.cno; return (0); } static void noprev(SCR *sp) { msgq(sp, M_BERR, "178|No previous F, f, T or t search"); } static void notfound(SCR *sp, ARG_CHAR_T ch) { msgq(sp, M_BERR, "179|%s not found", KEY_NAME(sp, ch)); } Index: head/contrib/nvi/vi/v_cmd.c =================================================================== --- head/contrib/nvi/vi/v_cmd.c (revision 365498) +++ head/contrib/nvi/vi/v_cmd.c (revision 365499) @@ -1,505 +1,501 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_cmd.c,v 10.9 1996/03/28 15:18:39 bostic Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * This array maps keystrokes to vi command functions. It is known * in ex/ex_usage.c that it takes four columns to name a vi character. */ VIKEYS const vikeys [MAXVIKEY + 1] = { /* 000 NUL -- The code in vi.c expects key 0 to be undefined. */ {NULL}, /* 001 ^A */ {v_searchw, V_ABS|V_CNT|V_MOVE|V_KEYW|VM_CUTREQ|VM_RCM_SET, "[count]^A", "^A search forward for cursor word"}, /* 002 ^B */ {v_pageup, V_CNT|VM_RCM_SET, "[count]^B", "^B scroll up by screens"}, /* 003 ^C */ {NULL, 0, "^C", "^C interrupt an operation (e.g. read, write, search)"}, /* 004 ^D */ {v_hpagedown, V_CNT|VM_RCM_SET, "[count]^D", "^D scroll down by half screens (setting count)"}, /* 005 ^E */ {v_linedown, V_CNT, "[count]^E", "^E scroll down by lines"}, /* 006 ^F */ {v_pagedown, V_CNT|VM_RCM_SET, "[count]^F", "^F scroll down by screens"}, /* 007 ^G */ {v_status, 0, "^G", "^G file status"}, /* 010 ^H */ {v_left, V_CNT|V_MOVE|VM_RCM_SET, "[count]^H", "^H move left by characters"}, /* 011 ^I */ {NULL}, /* 012 ^J */ {v_down, V_CNT|V_MOVE|VM_LMODE|VM_RCM, "[count]^J", "^J move down by lines"}, /* 013 ^K */ {NULL}, /* 014 ^L */ {v_redraw, 0, "^L", "^L redraw screen"}, /* 015 ^M */ {v_cr, V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB, "[count]^M", "^M move down by lines (to first non-blank)"}, /* 016 ^N */ {v_down, V_CNT|V_MOVE|VM_LMODE|VM_RCM, "[count]^N", "^N move down by lines"}, /* 017 ^O */ {NULL}, /* 020 ^P */ {v_up, V_CNT|V_MOVE|VM_LMODE|VM_RCM, "[count]^P", "^P move up by lines"}, /* 021 ^Q -- same as ^V if not used for hardware flow control. */ {NULL}, /* 022 ^R */ {v_redraw, 0, "^R", "^R redraw screen"}, /* 023 ^S -- not available, used for hardware flow control. */ {NULL}, /* 024 ^T */ {v_tagpop, V_ABS|VM_RCM_SET, "^T", "^T tag pop"}, /* 025 ^U */ {v_hpageup, V_CNT|VM_RCM_SET, "[count]^U", "^U half page up (set count)"}, /* 026 ^V */ {NULL, 0, "^V", "^V input a literal character"}, /* 027 ^W */ {v_screen, 0, "^W", "^W move to next screen"}, /* 030 ^X */ {NULL}, /* 031 ^Y */ {v_lineup, V_CNT, "[count]^Y", "^Y page up by lines"}, /* 032 ^Z */ {v_suspend, V_SECURE, "^Z", "^Z suspend editor"}, /* 033 ^[ */ {NULL, 0, "^[ ", "^[ exit input mode, cancel partial commands"}, /* 034 ^\ */ {v_exmode, 0, "^\\", "^\\ switch to ex mode"}, /* 035 ^] */ {v_tagpush, V_ABS|V_KEYW|VM_RCM_SET, "^]", "^] tag push cursor word"}, /* 036 ^^ */ {v_switch, 0, "^^", "^^ switch to previous file"}, /* 037 ^_ */ {NULL}, /* 040 ' ' */ {v_right, V_CNT|V_MOVE|VM_RCM_SET, "[count]' '", " move right by columns"}, /* 041 ! */ {v_filter, V_CNT|V_DOT|V_MOTION|V_SECURE|VM_RCM_SET, "[count]![count]motion command(s)", " ! filter through command(s) to motion"}, /* 042 " */ {NULL}, /* 043 # */ {v_increment, V_CHAR|V_CNT|V_DOT|VM_RCM_SET, "[count]# +|-|#", " # number increment/decrement"}, /* 044 $ */ {v_dollar, V_CNT|V_MOVE|VM_RCM_SETLAST, " [count]$", " $ move to last column"}, /* 045 % */ {v_match, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET, "%", " % move to match"}, /* 046 & */ {v_again, 0, "&", " & repeat substitution"}, /* 047 ' */ {v_fmark, V_ABS_L|V_CHAR|V_MOVE|VM_LMODE|VM_RCM_SET, "'['a-z]", " ' move to mark (to first non-blank)"}, /* 050 ( */ {v_sentenceb, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET, "[count](", " ( move back sentence"}, /* 051 ) */ {v_sentencef, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET, "[count])", " ) move forward sentence"}, /* 052 * */ {NULL}, /* 053 + */ {v_down, V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB, "[count]+", " + move down by lines (to first non-blank)"}, /* 054 , */ {v_chrrepeat, V_CNT|V_MOVE|VM_RCM_SET, "[count],", " , reverse last F, f, T or t search"}, /* 055 - */ {v_up, V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB, "[count]-", " - move up by lines (to first non-blank)"}, /* 056 . */ {NULL, 0, ".", " . repeat the last command"}, /* 057 / */ {v_searchf, V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET, "/RE[/ offset]", " / search forward"}, /* 060 0 */ {v_zero, V_MOVE|VM_RCM_SET, "0", " 0 move to first character"}, /* 061 1 */ {NULL}, /* 062 2 */ {NULL}, /* 063 3 */ {NULL}, /* 064 4 */ {NULL}, /* 065 5 */ {NULL}, /* 066 6 */ {NULL}, /* 067 7 */ {NULL}, /* 070 8 */ {NULL}, /* 071 9 */ {NULL}, /* 072 : */ {v_ex, 0, ":command [| command] ...", " : ex command"}, /* 073 ; */ {v_chrepeat, V_CNT|V_MOVE|VM_RCM_SET, "[count];", " ; repeat last F, f, T or t search"}, /* 074 < */ {v_shiftl, V_CNT|V_DOT|V_MOTION|VM_RCM_SET, "[count]<[count]motion", " < shift lines left to motion"}, /* 075 = */ {NULL}, /* 076 > */ {v_shiftr, V_CNT|V_DOT|V_MOTION|VM_RCM_SET, "[count]>[count]motion", " > shift lines right to motion"}, /* 077 ? */ {v_searchb, V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET, "?RE[? offset]", " ? search backward"}, /* 100 @ */ {v_at, V_CNT|V_RBUF|VM_RCM_SET, "@buffer", " @ execute buffer"}, /* 101 A */ {v_iA, V_CNT|V_DOT|VM_RCM_SET, "[count]A", " A append to the line"}, /* 102 B */ {v_wordB, V_CNT|V_MOVE|VM_RCM_SET, "[count]B", " B move back bigword"}, /* 103 C */ {NULL, 0, "[buffer][count]C", " C change to end-of-line"}, /* 104 D */ {NULL, 0, "[buffer]D", " D delete to end-of-line"}, /* 105 E */ {v_wordE, V_CNT|V_MOVE|VM_RCM_SET, "[count]E", " E move to end of bigword"}, /* 106 F */ {v_chF, V_CHAR|V_CNT|V_MOVE|VM_RCM_SET, "[count]F character", " F character in line backward search"}, /* 107 G */ {v_lgoto, V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB, "[count]G", " G move to line"}, /* 110 H */ {v_home, V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETNNB, "[count]H", " H move to count lines from screen top"}, /* 111 I */ {v_iI, V_CNT|V_DOT|VM_RCM_SET, "[count]I", " I insert before first nonblank"}, /* 112 J */ {v_join, V_CNT|V_DOT|VM_RCM_SET, "[count]J", " J join lines"}, /* 113 K */ {NULL}, /* 114 L */ {v_bottom, V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETNNB, "[count]L", " L move to screen bottom"}, /* 115 M */ {v_middle, V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETNNB, "M", " M move to screen middle"}, /* 116 N */ {v_searchN, V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET, "n", " N reverse last search"}, /* 117 O */ {v_iO, V_CNT|V_DOT|VM_RCM_SET, "[count]O", " O insert above line"}, /* 120 P */ {v_Put, V_CNT|V_DOT|V_OBUF|VM_RCM_SET, "[buffer]P", " P insert before cursor from buffer"}, /* 121 Q */ {v_exmode, 0, "Q", " Q switch to ex mode"}, /* 122 R */ {v_Replace, V_CNT|V_DOT|VM_RCM_SET, "[count]R", " R replace characters"}, /* 123 S */ {NULL, 0, "[buffer][count]S", " S substitute for the line(s)"}, /* 124 T */ {v_chT, V_CHAR|V_CNT|V_MOVE|VM_RCM_SET, "[count]T character", " T before character in line backward search"}, /* 125 U */ {v_Undo, VM_RCM_SET, "U", " U Restore the current line"}, /* 126 V */ {NULL}, /* 127 W */ {v_wordW, V_CNT|V_MOVE|VM_RCM_SET, "[count]W", " W move to next bigword"}, /* 130 X */ {v_Xchar, V_CNT|V_DOT|V_OBUF|VM_RCM_SET, "[buffer][count]X", " X delete character before cursor"}, /* 131 Y */ {NULL, 0, "[buffer][count]Y", " Y copy line"}, /* 132 Z */ {v_zexit, 0, "ZZ", "ZZ save file and exit"}, /* 133 [ */ {v_sectionb, V_ABS|V_CNT|V_MOVE|VM_RCM_SET, "[[", "[[ move back section"}, /* 134 \ */ {NULL}, /* 135 ] */ {v_sectionf, V_ABS|V_CNT|V_MOVE|VM_RCM_SET, "]]", "]] move forward section"}, /* 136 ^ */ /* * DON'T set the VM_RCM_SETFNB flag, the function has to do the work * anyway, in case it's a motion component. DO set VM_RCM_SET, so * that any motion that's part of a command is preserved. */ {v_first, V_CNT|V_MOVE|VM_RCM_SET, "^", " ^ move to first non-blank"}, /* 137 _ */ /* * Needs both to set the VM_RCM_SETFNB flag, and to do the work * in the function, in case it's a delete. */ {v_cfirst, V_CNT|V_MOVE|VM_RCM_SETFNB, "_", " _ move to first non-blank"}, /* 140 ` */ {v_bmark, V_ABS_C|V_CHAR|V_MOVE|VM_CUTREQ|VM_RCM_SET, "`[`a-z]", " ` move to mark"}, /* 141 a */ {v_ia, V_CNT|V_DOT|VM_RCM_SET, "[count]a", " a append after cursor"}, /* 142 b */ {v_wordb, V_CNT|V_MOVE|VM_RCM_SET, "[count]b", " b move back word"}, /* 143 c */ {v_change, V_CNT|V_DOT|V_MOTION|V_OBUF|VM_RCM_SET, "[buffer][count]c[count]motion", " c change to motion"}, /* 144 d */ {v_delete, V_CNT|V_DOT|V_MOTION|V_OBUF|VM_RCM_SET, "[buffer][count]d[count]motion", " d delete to motion"}, /* 145 e */ {v_worde, V_CNT|V_MOVE|VM_RCM_SET, "[count]e", " e move to end of word"}, /* 146 f */ {v_chf, V_CHAR|V_CNT|V_MOVE|VM_RCM_SET, "[count]f character", " f character in line forward search"}, /* 147 g */ {NULL}, /* 150 h */ {v_left, V_CNT|V_MOVE|VM_RCM_SET, "[count]h", " h move left by columns"}, /* 151 i */ {v_ii, V_CNT|V_DOT|VM_RCM_SET, "[count]i", " i insert before cursor"}, /* 152 j */ {v_down, V_CNT|V_MOVE|VM_LMODE|VM_RCM, "[count]j", " j move down by lines"}, /* 153 k */ {v_up, V_CNT|V_MOVE|VM_LMODE|VM_RCM, "[count]k", " k move up by lines"}, /* 154 l */ {v_right, V_CNT|V_MOVE|VM_RCM_SET, "[count]l", " l move right by columns"}, /* 155 m */ {v_mark, V_CHAR, "m[a-z]", " m set mark"}, /* 156 n */ {v_searchn, V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET, "n", " n repeat last search"}, /* 157 o */ {v_io, V_CNT|V_DOT|VM_RCM_SET, "[count]o", " o append after line"}, /* 160 p */ {v_put, V_CNT|V_DOT|V_OBUF|VM_RCM_SET, "[buffer]p", " p insert after cursor from buffer"}, /* 161 q */ {NULL}, /* 162 r */ {v_replace, V_CNT|V_DOT|VM_RCM_SET, "[count]r character", " r replace character"}, /* 163 s */ {v_subst, V_CNT|V_DOT|V_OBUF|VM_RCM_SET, "[buffer][count]s", " s substitute character"}, /* 164 t */ {v_cht, V_CHAR|V_CNT|V_MOVE|VM_RCM_SET, "[count]t character", " t before character in line forward search"}, /* 165 u */ /* * DON'T set the V_DOT flag, it' more complicated than that. * See vi/vi.c for details. */ {v_undo, VM_RCM_SET, "u", " u undo last change"}, /* 166 v */ {NULL}, /* 167 w */ {v_wordw, V_CNT|V_MOVE|VM_RCM_SET, "[count]w", " w move to next word"}, /* 170 x */ {v_xchar, V_CNT|V_DOT|V_OBUF|VM_RCM_SET, "[buffer][count]x", " x delete character"}, /* 171 y */ {v_yank, V_CNT|V_DOT|V_MOTION|V_OBUF|VM_RCM_SET, "[buffer][count]y[count]motion", " y copy text to motion into a cut buffer"}, /* 172 z */ /* * DON'T set the V_CHAR flag, the char isn't required, * so it's handled specially in getcmd(). */ {v_z, V_ABS_L|V_CNT|VM_RCM_SETFNB, "[line]z[window_size][-|.|+|^|]", " z reposition the screen"}, /* 173 { */ {v_paragraphb, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET, "[count]{", " { move back paragraph"}, /* 174 | */ {v_ncol, V_CNT|V_MOVE|VM_RCM_SET, "[count]|", " | move to column"}, /* 175 } */ {v_paragraphf, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET, "[count]}", " } move forward paragraph"}, /* 176 ~ */ {v_ulcase, V_CNT|V_DOT|VM_RCM_SET, "[count]~", " ~ reverse case"}, }; Index: head/contrib/nvi/vi/v_delete.c =================================================================== --- head/contrib/nvi/vi/v_delete.c (revision 365498) +++ head/contrib/nvi/vi/v_delete.c (revision 365499) @@ -1,105 +1,101 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_delete.c,v 10.11 2001/06/25 15:19:31 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_delete -- [buffer][count]d[count]motion * [buffer][count]D * Delete a range of text. * * PUBLIC: int v_delete(SCR *, VICMD *); */ int v_delete(SCR *sp, VICMD *vp) { recno_t nlines; size_t len; int lmode; lmode = F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0; /* Yank the lines. */ if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, lmode | (F_ISSET(vp, VM_CUTREQ) ? CUT_NUMREQ : CUT_NUMOPT))) return (1); /* Delete the lines. */ if (del(sp, &vp->m_start, &vp->m_stop, lmode)) return (1); /* * Check for deletion of the entire file. Try to check a close * by line so we don't go to the end of the file unnecessarily. */ if (!db_exist(sp, vp->m_final.lno + 1)) { if (db_last(sp, &nlines)) return (1); if (nlines == 0) { vp->m_final.lno = 1; vp->m_final.cno = 0; return (0); } } /* * One special correction, in case we've deleted the current line or * character. We check it here instead of checking in every command * that can be a motion component. */ if (db_get(sp, vp->m_final.lno, 0, NULL, &len)) { if (db_get(sp, nlines, DBG_FATAL, NULL, &len)) return (1); vp->m_final.lno = nlines; } /* * !!! * Cursor movements, other than those caused by a line mode command * moving to another line, historically reset the relative position. * * This currently matches the check made in v_yank(), I'm hoping that * they should be consistent... */ if (!F_ISSET(vp, VM_LMODE)) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SET); /* Make sure the set cursor position exists. */ if (vp->m_final.cno >= len) vp->m_final.cno = len ? len - 1 : 0; } /* * !!! * The "dd" command moved to the first non-blank; "d" * didn't. */ if (F_ISSET(vp, VM_LDOUBLE)) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SETFNB); } return (0); } Index: head/contrib/nvi/vi/v_ex.c =================================================================== --- head/contrib/nvi/vi/v_ex.c (revision 365498) +++ head/contrib/nvi/vi/v_ex.c (revision 365499) @@ -1,650 +1,646 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_ex.c,v 10.61 2011/12/22 18:41:53 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static int v_ecl(SCR *); static int v_ecl_init(SCR *); static int v_ecl_log(SCR *, TEXT *); static int v_ex_done(SCR *, VICMD *); static int v_exec_ex(SCR *, VICMD *, EXCMD *); /* * v_again -- & * Repeat the previous substitution. * * PUBLIC: int v_again(SCR *, VICMD *); */ int v_again(SCR *sp, VICMD *vp) { EXCMD cmd; ex_cinit(sp, &cmd, C_SUBAGAIN, 2, vp->m_start.lno, vp->m_start.lno, 1); argv_exp0(sp, &cmd, L(""), 1); return (v_exec_ex(sp, vp, &cmd)); } /* * v_exmode -- Q * Switch the editor into EX mode. * * PUBLIC: int v_exmode(SCR *, VICMD *); */ int v_exmode(SCR *sp, VICMD *vp) { GS *gp; gp = sp->gp; /* Try and switch screens -- the screen may not permit it. */ if (gp->scr_screen(sp, SC_EX)) { msgq(sp, M_ERR, "207|The Q command requires the ex terminal interface"); return (1); } (void)gp->scr_attr(sp, SA_ALTERNATE, 0); /* Save the current cursor position. */ sp->frp->lno = sp->lno; sp->frp->cno = sp->cno; F_SET(sp->frp, FR_CURSORSET); /* Switch to ex mode. */ F_CLR(sp, SC_VI | SC_SCR_VI); F_SET(sp, SC_EX); /* Move out of the vi screen. */ (void)ex_puts(sp, "\n"); return (0); } /* * v_join -- [count]J * Join lines together. * * PUBLIC: int v_join(SCR *, VICMD *); */ int v_join(SCR *sp, VICMD *vp) { EXCMD cmd; int lno; /* * YASC. * The general rule is that '#J' joins # lines, counting the current * line. However, 'J' and '1J' are the same as '2J', i.e. join the * current and next lines. This doesn't map well into the ex command * (which takes two line numbers), so we handle it here. Note that * we never test for EOF -- historically going past the end of file * worked just fine. */ lno = vp->m_start.lno + 1; if (F_ISSET(vp, VC_C1SET) && vp->count > 2) lno = vp->m_start.lno + (vp->count - 1); ex_cinit(sp, &cmd, C_JOIN, 2, vp->m_start.lno, lno, 0); return (v_exec_ex(sp, vp, &cmd)); } /* * v_shiftl -- [count]m_start.lno, vp->m_stop.lno, 0); argv_exp0(sp, &cmd, L("<"), 2); return (v_exec_ex(sp, vp, &cmd)); } /* * v_shiftr -- [count]>motion * Shift lines right. * * PUBLIC: int v_shiftr(SCR *, VICMD *); */ int v_shiftr(SCR *sp, VICMD *vp) { EXCMD cmd; ex_cinit(sp, &cmd, C_SHIFTR, 2, vp->m_start.lno, vp->m_stop.lno, 0); argv_exp0(sp, &cmd, L(">"), 2); return (v_exec_ex(sp, vp, &cmd)); } /* * v_suspend -- ^Z * Suspend vi. * * PUBLIC: int v_suspend(SCR *, VICMD *); */ int v_suspend(SCR *sp, VICMD *vp) { EXCMD cmd; ex_cinit(sp, &cmd, C_STOP, 0, OOBLNO, OOBLNO, 0); argv_exp0(sp, &cmd, L("suspend"), SIZE(L("suspend"))); return (v_exec_ex(sp, vp, &cmd)); } /* * v_switch -- ^^ * Switch to the previous file. * * PUBLIC: int v_switch(SCR *, VICMD *); */ int v_switch(SCR *sp, VICMD *vp) { EXCMD cmd; char *name; CHAR_T *wp; size_t wlen; /* * Try the alternate file name, then the previous file * name. Use the real name, not the user's current name. */ if ((name = sp->alt_name) == NULL) { msgq(sp, M_ERR, "180|No previous file to edit"); return (1); } /* If autowrite is set, write out the file. */ if (file_m1(sp, 0, FS_ALL)) return (1); ex_cinit(sp, &cmd, C_EDIT, 0, OOBLNO, OOBLNO, 0); CHAR2INT(sp, name, strlen(name) + 1, wp, wlen); argv_exp0(sp, &cmd, wp, wlen); return (v_exec_ex(sp, vp, &cmd)); } /* * v_tagpush -- ^[ * Do a tag search on the cursor keyword. * * PUBLIC: int v_tagpush(SCR *, VICMD *); */ int v_tagpush(SCR *sp, VICMD *vp) { EXCMD cmd; ex_cinit(sp, &cmd, C_TAG, 0, OOBLNO, 0, 0); argv_exp0(sp, &cmd, VIP(sp)->keyw, STRLEN(VIP(sp)->keyw) + 1); return (v_exec_ex(sp, vp, &cmd)); } /* * v_tagpop -- ^T * Pop the tags stack. * * PUBLIC: int v_tagpop(SCR *, VICMD *); */ int v_tagpop(SCR *sp, VICMD *vp) { EXCMD cmd; ex_cinit(sp, &cmd, C_TAGPOP, 0, OOBLNO, 0, 0); return (v_exec_ex(sp, vp, &cmd)); } /* * v_filter -- [count]!motion command(s) * Run range through shell commands, replacing text. * * PUBLIC: int v_filter(SCR *, VICMD *); */ int v_filter(SCR *sp, VICMD *vp) { EXCMD cmd; TEXT *tp; /* * !!! * Historical vi permitted "!!" in an empty file, and it's handled * as a special case in the ex_bang routine. Don't modify this setup * without understanding that one. In particular, note that we're * manipulating the ex argument structures behind ex's back. * * !!! * Historical vi did not permit the '!' command to be associated with * a non-line oriented motion command, in general, although it did * with search commands. So, !f; and !w would fail, but !/; * would succeed, even if they all moved to the same location in the * current line. I don't see any reason to disallow '!' using any of * the possible motion commands. * * !!! * Historical vi ran the last bang command if N or n was used as the * search motion. */ if (F_ISSET(vp, VC_ISDOT) || ISCMD(vp->rkp, 'N') || ISCMD(vp->rkp, 'n')) { ex_cinit(sp, &cmd, C_BANG, 2, vp->m_start.lno, vp->m_stop.lno, 0); EXP(sp)->argsoff = 0; /* XXX */ if (argv_exp1(sp, &cmd, L("!"), 1, 1)) return (1); cmd.argc = EXP(sp)->argsoff; /* XXX */ cmd.argv = EXP(sp)->args; /* XXX */ return (v_exec_ex(sp, vp, &cmd)); } /* Get the command from the user. */ if (v_tcmd(sp, vp, '!', TXT_BS | TXT_CR | TXT_ESCAPE | TXT_FILEC | TXT_PROMPT)) return (1); /* * Check to see if the user changed their mind. * * !!! * Entering on an empty line was historically an error, * this implementation doesn't bother. */ tp = TAILQ_FIRST(sp->tiq); if (tp->term != TERM_OK) { vp->m_final.lno = sp->lno; vp->m_final.cno = sp->cno; return (0); } /* Home the cursor. */ vs_home(sp); ex_cinit(sp, &cmd, C_BANG, 2, vp->m_start.lno, vp->m_stop.lno, 0); EXP(sp)->argsoff = 0; /* XXX */ if (argv_exp1(sp, &cmd, tp->lb + 1, tp->len - 1, 1)) return (1); cmd.argc = EXP(sp)->argsoff; /* XXX */ cmd.argv = EXP(sp)->args; /* XXX */ return (v_exec_ex(sp, vp, &cmd)); } /* * v_exec_ex -- * Execute an ex command. */ static int v_exec_ex(SCR *sp, VICMD *vp, EXCMD *exp) { int rval; rval = exp->cmd->fn(sp, exp); return (v_ex_done(sp, vp) || rval); } /* * v_ex -- : * Execute a colon command line. * * PUBLIC: int v_ex(SCR *, VICMD *); */ int v_ex(SCR *sp, VICMD *vp) { GS *gp; TEXT *tp; int do_cedit, do_resolution, ifcontinue; gp = sp->gp; /* * !!! * If we put out more than a single line of messages, or ex trashes * the screen, the user may continue entering ex commands. We find * this out when we do the screen/message resolution. We can't enter * completely into ex mode however, because the user can elect to * return into vi mode by entering any key, i.e. we have to be in raw * mode. */ for (do_cedit = do_resolution = 0;;) { /* * !!! * There may already be an ex command waiting to run. If * so, we continue with it. */ if (!EXCMD_RUNNING(gp)) { /* Get a command. */ if (v_tcmd(sp, vp, ':', TXT_BS | TXT_CEDIT | TXT_FILEC | TXT_PROMPT)) return (1); tp = TAILQ_FIRST(sp->tiq); /* * If the user entered a single , they want to * edit their colon command history. If they already * entered some text, move it into the edit history. */ if (tp->term == TERM_CEDIT) { if (tp->len > 1 && v_ecl_log(sp, tp)) return (1); do_cedit = 1; break; } /* If the user didn't enter anything, return. */ if (tp->term == TERM_BS) break; /* If the user changed their mind, return. */ if (tp->term != TERM_OK) break; /* Log the command. */ if (O_STR(sp, O_CEDIT) != NULL && v_ecl_log(sp, tp)) return (1); /* Push a command on the command stack. */ if (ex_run_str(sp, NULL, tp->lb, tp->len, 0, 1)) return (1); } /* Home the cursor. */ vs_home(sp); /* * !!! * If the editor wrote the screen behind curses back, put out * a so that we don't overwrite the user's command * with its output or the next want-to-continue? message. This * doesn't belong here, but I can't find another place to put * it. See, we resolved the output from the last ex command, * and the user entered another one. This is the only place * where we have control before the ex command writes output. * We could get control in vs_msg(), but we have no way to know * if command didn't put out any output when we try and resolve * this command. This fixes a bug where combinations of ex * commands, e.g. ":set:!date:set" didn't look right. */ if (F_ISSET(sp, SC_SCR_EXWROTE)) (void)putchar('\n'); /* Call the ex parser. */ (void)ex_cmd(sp); /* Flush ex messages. */ (void)ex_fflush(sp); /* Resolve any messages. */ if (vs_ex_resolve(sp, &ifcontinue)) return (1); /* * Continue or return. If continuing, make sure that we * eventually do resolution. */ if (!ifcontinue) break; do_resolution = 1; /* If we're continuing, it's a new command. */ ++sp->ccnt; } /* * If the user previously continued an ex command, we have to do * resolution to clean up the screen. Don't wait, we already did * that. */ if (do_resolution) { F_SET(sp, SC_EX_WAIT_NO); if (vs_ex_resolve(sp, &ifcontinue)) return (1); } /* Cleanup from the ex command. */ if (v_ex_done(sp, vp)) return (1); /* The user may want to edit their colon command history. */ if (do_cedit) return (v_ecl(sp)); return (0); } /* * v_ex_done -- * Cleanup from an ex command. */ static int v_ex_done(SCR *sp, VICMD *vp) { size_t len; /* * The only cursor modifications are real, however, the underlying * line may have changed; don't trust anything. This code has been * a remarkably fertile place for bugs. Do a reality check on a * cursor value, and make sure it's okay. If necessary, change it. * Ex keeps track of the line number, but it cares less about the * column and it may have disappeared. * * Don't trust ANYTHING. * * XXX * Ex will soon have to start handling the column correctly; see * the POSIX 1003.2 standard. */ if (db_eget(sp, sp->lno, NULL, &len, NULL)) { sp->lno = 1; sp->cno = 0; } else if (sp->cno >= len) sp->cno = len ? len - 1 : 0; vp->m_final.lno = sp->lno; vp->m_final.cno = sp->cno; /* * Don't re-adjust the cursor after executing an ex command, * and ex movements are permanent. */ F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SET); return (0); } /* * v_ecl -- * Start an edit window on the colon command-line commands. */ static int v_ecl(SCR *sp) { GS *gp; SCR *new; /* Initialize the screen, if necessary. */ gp = sp->gp; if (gp->ccl_sp == NULL && v_ecl_init(sp)) return (1); /* Get a new screen. */ if (screen_init(gp, sp, &new)) return (1); if (vs_split(sp, new, 1)) { (void)screen_end(new); return (1); } /* Attach to the screen. */ new->ep = gp->ccl_sp->ep; ++new->ep->refcnt; new->frp = gp->ccl_sp->frp; new->frp->flags = sp->frp->flags; /* Move the cursor to the end. */ (void)db_last(new, &new->lno); if (new->lno == 0) new->lno = 1; /* Remember the originating window. */ sp->ccl_parent = sp; /* It's a special window. */ F_SET(new, SC_COMEDIT); #if defined(USE_WIDECHAR) && defined(USE_ICONV) /* Bypass iconv on writing to DB. */ o_set(new, O_FILEENCODING, OS_STRDUP, codeset(), 0); #endif /* Set up the switch. */ sp->nextdisp = new; F_SET(sp, SC_SSWITCH); return (0); } /* * v_ecl_exec -- * Execute a command from a colon command-line window. * * PUBLIC: int v_ecl_exec(SCR *); */ int v_ecl_exec(SCR *sp) { size_t len; CHAR_T *p; if (db_get(sp, sp->lno, 0, &p, &len) && sp->lno == 1) { v_emsg(sp, NULL, VIM_EMPTY); return (1); } if (len == 0) { msgq(sp, M_BERR, "307|No ex command to execute"); return (1); } /* Push the command on the command stack. */ if (ex_run_str(sp, NULL, p, len, 0, 0)) return (1); /* Set up the switch. */ sp->nextdisp = sp->ccl_parent; F_SET(sp, SC_EXIT); return (0); } /* * v_ecl_log -- * Log a command into the colon command-line log file. */ static int v_ecl_log(SCR *sp, TEXT *tp) { recno_t lno; int rval; CHAR_T *p; size_t len; SCR *ccl_sp; /* Initialize the screen, if necessary. */ if (sp->gp->ccl_sp == NULL && v_ecl_init(sp)) return (1); ccl_sp = sp->gp->ccl_sp; /* * Don't log colon command window commands into the colon command * window... */ if (sp->ep == ccl_sp->ep) return (0); if (db_last(ccl_sp, &lno)) { return (1); } /* Don't log line that is identical to previous one */ if (lno > 0 && !db_get(ccl_sp, lno, 0, &p, &len) && len == tp->len && !MEMCMP(tp->lb, p, len)) rval = 0; else { rval = db_append(ccl_sp, 0, lno, tp->lb, tp->len); /* XXXX end "transaction" on ccl */ /* Is this still necessary now that we no longer hijack sp ? */ log_cursor(ccl_sp); } return (rval); } /* * v_ecl_init -- * Initialize the colon command-line log file. */ static int v_ecl_init(SCR *sp) { FREF *frp; GS *gp; gp = sp->gp; /* Get a temporary file. */ if ((frp = file_add(sp, NULL)) == NULL) return (1); /* * XXX * Create a screen -- the file initialization code wants one. */ if (screen_init(gp, sp, &gp->ccl_sp)) return (1); if (file_init(gp->ccl_sp, frp, NULL, 0)) { (void)screen_end(gp->ccl_sp); gp->ccl_sp = NULL; return (1); } /* The underlying file isn't recoverable. */ F_CLR(gp->ccl_sp->ep, F_RCV_ON); return (0); } Index: head/contrib/nvi/vi/v_increment.c =================================================================== --- head/contrib/nvi/vi/v_increment.c (revision 365498) +++ head/contrib/nvi/vi/v_increment.c (revision 365499) @@ -1,264 +1,260 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_increment.c,v 10.17 2011/12/02 01:17:53 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static CHAR_T * const fmt[] = { #define DEC 0 L("%ld"), #define SDEC 1 L("%+ld"), #define HEXC 2 L("0X%0*lX"), #define HEXL 3 L("0x%0*lx"), #define OCTAL 4 L("%#0*lo"), }; static void inc_err(SCR *, enum nresult); /* * v_increment -- [count]#[#+-] * Increment/decrement a keyword number. * * PUBLIC: int v_increment(SCR *, VICMD *); */ int v_increment(SCR *sp, VICMD *vp) { enum nresult nret; u_long ulval; long change, ltmp, lval; size_t beg, blen, end, len, nlen, wlen; int base, isempty, rval; CHAR_T *ntype, nbuf[100]; CHAR_T *bp, *p, *t; /* Validate the operator. */ if (vp->character == '#') vp->character = '+'; if (vp->character != '+' && vp->character != '-') { v_emsg(sp, vp->kp->usage, VIM_USAGE); return (1); } /* If new value set, save it off, but it has to fit in a long. */ if (F_ISSET(vp, VC_C1SET)) { if (vp->count > LONG_MAX) { inc_err(sp, NUM_OVER); return (1); } change = vp->count; } else change = 1; /* Get the line. */ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto nonum; return (1); } /* * Skip any leading space before the number. Getting a cursor word * implies moving the cursor to its beginning, if we moved, refresh * now. */ for (beg = vp->m_start.cno; beg < len && ISSPACE(p[beg]); ++beg); if (beg >= len) goto nonum; if (beg != vp->m_start.cno) { sp->cno = beg; (void)vs_refresh(sp, 0); } #undef ishex #define ishex(c) (ISXDIGIT(c)) #undef isoctal #define isoctal(c) ((c) >= '0' && (c) <= '7') /* * Look for 0[Xx], or leading + or - signs, guess at the base. * The character after that must be a number. Wlen is set to * the remaining characters in the line that could be part of * the number. */ wlen = len - beg; if (p[beg] == '0' && wlen > 2 && (p[beg + 1] == 'X' || p[beg + 1] == 'x')) { base = 16; end = beg + 2; if (!ishex(p[end])) goto decimal; ntype = p[beg + 1] == 'X' ? fmt[HEXC] : fmt[HEXL]; } else if (p[beg] == '0' && wlen > 1) { base = 8; end = beg + 1; if (!isoctal(p[end])) goto decimal; ntype = fmt[OCTAL]; } else if (wlen >= 1 && (p[beg] == '+' || p[beg] == '-')) { base = 10; end = beg + 1; ntype = fmt[SDEC]; if (!isdigit(p[end])) goto nonum; } else { decimal: base = 10; end = beg; ntype = fmt[DEC]; if (!isdigit(p[end])) { nonum: msgq(sp, M_ERR, "181|Cursor not in a number"); return (1); } } /* Find the end of the word, possibly correcting the base. */ while (++end < len) { switch (base) { case 8: if (isoctal(p[end])) continue; if (p[end] == '8' || p[end] == '9') { base = 10; ntype = fmt[DEC]; continue; } break; case 10: if (isdigit(p[end])) continue; break; case 16: if (ishex(p[end])) continue; break; default: abort(); /* NOTREACHED */ } break; } wlen = (end - beg); /* * XXX * If the line was at the end of the buffer, we have to copy it * so we can guarantee that it's NULL-terminated. We make the * buffer big enough to fit the line changes as well, and only * allocate once. */ GET_SPACE_RETW(sp, bp, blen, len + 50); if (end == len) { MEMMOVE(bp, &p[beg], wlen); bp[wlen] = '\0'; t = bp; } else t = &p[beg]; /* * Octal or hex deal in unsigned longs, everything else is done * in signed longs. */ if (base == 10) { if ((nret = nget_slong(&lval, t, NULL, 10)) != NUM_OK) goto err; ltmp = vp->character == '-' ? -change : change; if (lval > 0 && ltmp > 0 && !NPFITS(LONG_MAX, lval, ltmp)) { nret = NUM_OVER; goto err; } if (lval < 0 && ltmp < 0 && !NNFITS(LONG_MIN, lval, ltmp)) { nret = NUM_UNDER; goto err; } lval += ltmp; /* If we cross 0, signed numbers lose their sign. */ if (lval == 0 && ntype == fmt[SDEC]) ntype = fmt[DEC]; nlen = SPRINTF(nbuf, sizeof(nbuf), ntype, lval); } else { if ((nret = nget_uslong(&ulval, t, NULL, base)) != NUM_OK) goto err; if (vp->character == '+') { if (!NPFITS(ULONG_MAX, ulval, change)) { nret = NUM_OVER; goto err; } ulval += change; } else { if (ulval < change) { nret = NUM_UNDER; goto err; } ulval -= change; } /* Correct for literal "0[Xx]" in format. */ if (base == 16) wlen -= 2; nlen = SPRINTF(nbuf, sizeof(nbuf), ntype, wlen, ulval); } /* Build the new line. */ MEMMOVE(bp, p, beg); MEMMOVE(bp + beg, nbuf, nlen); MEMMOVE(bp + beg + nlen, p + end, len - beg - (end - beg)); len = beg + nlen + (len - beg - (end - beg)); nret = NUM_OK; rval = db_set(sp, vp->m_start.lno, bp, len); if (0) { err: rval = 1; inc_err(sp, nret); } if (bp != NULL) FREE_SPACEW(sp, bp, blen); return (rval); } static void inc_err(SCR *sp, enum nresult nret) { switch (nret) { case NUM_ERR: break; case NUM_OK: abort(); /* NOREACHED */ case NUM_OVER: msgq(sp, M_ERR, "182|Resulting number too large"); break; case NUM_UNDER: msgq(sp, M_ERR, "183|Resulting number too small"); break; } } Index: head/contrib/nvi/vi/v_init.c =================================================================== --- head/contrib/nvi/vi/v_init.c (revision 365498) +++ head/contrib/nvi/vi/v_init.c (revision 365499) @@ -1,129 +1,120 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_init.c,v 10.10 2012/02/11 00:33:46 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_screen_copy -- * Copy vi screen. * * PUBLIC: int v_screen_copy(SCR *, SCR *); */ int v_screen_copy(SCR *orig, SCR *sp) { VI_PRIVATE *ovip, *nvip; /* Create the private vi structure. */ - CALLOC_RET(orig, nvip, VI_PRIVATE *, 1, sizeof(VI_PRIVATE)); + CALLOC_RET(orig, nvip, 1, sizeof(VI_PRIVATE)); sp->vi_private = nvip; /* Invalidate the line size cache. */ VI_SCR_CFLUSH(nvip); if (orig == NULL) { nvip->csearchdir = CNOTSET; } else { ovip = VIP(orig); /* User can replay the last input, but nothing else. */ if (ovip->rep_len != 0) { - MALLOC_RET(orig, nvip->rep, EVENT *, ovip->rep_len); + MALLOC_RET(orig, nvip->rep, ovip->rep_len); memmove(nvip->rep, ovip->rep, ovip->rep_len); nvip->rep_len = ovip->rep_len; } /* Copy the match characters information. */ if (ovip->mcs != NULL && (nvip->mcs = v_wstrdup(sp, ovip->mcs, STRLEN(ovip->mcs))) == NULL) return (1); /* Copy the paragraph/section information. */ if (ovip->ps != NULL && (nvip->ps = v_strdup(sp, ovip->ps, strlen(ovip->ps))) == NULL) return (1); nvip->lastckey = ovip->lastckey; nvip->csearchdir = ovip->csearchdir; nvip->srows = ovip->srows; } return (0); } /* * v_screen_end -- * End a vi screen. * * PUBLIC: int v_screen_end(SCR *); */ int v_screen_end(SCR *sp) { VI_PRIVATE *vip; if ((vip = VIP(sp)) == NULL) return (0); - if (vip->keyw != NULL) - free(vip->keyw); - if (vip->rep != NULL) - free(vip->rep); - if (vip->mcs != NULL) - free(vip->mcs); - if (vip->ps != NULL) - free(vip->ps); + free(vip->keyw); + free(vip->rep); + free(vip->mcs); + free(vip->ps); - if (HMAP != NULL) - free(HMAP); + free(HMAP); free(vip); sp->vi_private = NULL; return (0); } /* * v_optchange -- * Handle change of options for vi. * * PUBLIC: int v_optchange(SCR *, int, char *, u_long *); */ int v_optchange(SCR *sp, int offset, char *str, u_long *valp) { switch (offset) { case O_MATCHCHARS: return (v_buildmcs(sp, str)); case O_PARAGRAPHS: return (v_buildps(sp, str, O_STR(sp, O_SECTIONS))); case O_SECTIONS: return (v_buildps(sp, O_STR(sp, O_PARAGRAPHS), str)); case O_WINDOW: return (vs_crel(sp, *valp)); } return (0); } Index: head/contrib/nvi/vi/v_itxt.c =================================================================== --- head/contrib/nvi/vi/v_itxt.c (revision 365498) +++ head/contrib/nvi/vi/v_itxt.c (revision 365499) @@ -1,514 +1,510 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_itxt.c,v 10.21 2001/06/25 15:19:32 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * !!! * Repeated input in the historic vi is mostly wrong and this isn't very * backward compatible. For example, if the user entered "3Aab\ncd" in * the historic vi, the "ab" was repeated 3 times, and the "\ncd" was then * appended to the result. There was also a hack which I don't remember * right now, where "3o" would open 3 lines and then let the user fill them * in, to make screen movements on 300 baud modems more tolerable. I don't * think it's going to be missed. * * !!! * There's a problem with the way that we do logging for change commands with * implied motions (e.g. A, I, O, cc, etc.). Since the main vi loop logs the * starting cursor position before the change command "moves" the cursor, the * cursor position to which we return on undo will be where the user entered * the change command, not the start of the change. Several of the following * routines re-log the cursor to make this work correctly. Historic vi tried * to do the same thing, and mostly got it right. (The only spectacular way * it fails is if the user entered 'o' from anywhere but the last character of * the line, the undo returned the cursor to the start of the line. If the * user was on the last character of the line, the cursor returned to that * position.) We also check for mapped keys waiting, i.e. if we're in the * middle of a map, don't bother logging the cursor. */ #define LOG_CORRECT { \ if (!MAPPED_KEYS_WAITING(sp)) \ (void)log_cursor(sp); \ } static u_int32_t set_txt_std(SCR *, VICMD *, u_int32_t); /* * v_iA -- [count]A * Append text to the end of the line. * * PUBLIC: int v_iA(SCR *, VICMD *); */ int v_iA(SCR *sp, VICMD *vp) { size_t len; if (!db_get(sp, vp->m_start.lno, 0, NULL, &len)) sp->cno = len == 0 ? 0 : len - 1; LOG_CORRECT; return (v_ia(sp, vp)); } /* * v_ia -- [count]a * [count]A * Append text to the cursor position. * * PUBLIC: int v_ia(SCR *, VICMD *); */ int v_ia(SCR *sp, VICMD *vp) { size_t len; u_int32_t flags; int isempty; CHAR_T *p; flags = set_txt_std(sp, vp, 0); sp->showmode = SM_APPEND; sp->lno = vp->m_start.lno; /* Move the cursor one column to the right and repaint the screen. */ if (db_eget(sp, sp->lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; LF_SET(TXT_APPENDEOL); } else if (len) { if (len == sp->cno + 1) { sp->cno = len; LF_SET(TXT_APPENDEOL); } else ++sp->cno; } else LF_SET(TXT_APPENDEOL); return (v_txt(sp, vp, NULL, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); } /* * v_iI -- [count]I * Insert text at the first nonblank. * * PUBLIC: int v_iI(SCR *, VICMD *); */ int v_iI(SCR *sp, VICMD *vp) { sp->cno = 0; if (nonblank(sp, vp->m_start.lno, &sp->cno)) return (1); LOG_CORRECT; return (v_ii(sp, vp)); } /* * v_ii -- [count]i * [count]I * Insert text at the cursor position. * * PUBLIC: int v_ii(SCR *, VICMD *); */ int v_ii(SCR *sp, VICMD *vp) { size_t len; u_int32_t flags; int isempty; CHAR_T *p; flags = set_txt_std(sp, vp, 0); sp->showmode = SM_INSERT; sp->lno = vp->m_start.lno; if (db_eget(sp, sp->lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; } if (len == 0) LF_SET(TXT_APPENDEOL); return (v_txt(sp, vp, NULL, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); } enum which { o_cmd, O_cmd }; static int io(SCR *, VICMD *, enum which); /* * v_iO -- [count]O * Insert text above this line. * * PUBLIC: int v_iO(SCR *, VICMD *); */ int v_iO(SCR *sp, VICMD *vp) { return (io(sp, vp, O_cmd)); } /* * v_io -- [count]o * Insert text after this line. * * PUBLIC: int v_io(SCR *, VICMD *); */ int v_io(SCR *sp, VICMD *vp) { return (io(sp, vp, o_cmd)); } static int io(SCR *sp, VICMD *vp, enum which cmd) { recno_t ai_line, lno; size_t len; u_int32_t flags; CHAR_T *p; flags = set_txt_std(sp, vp, TXT_ADDNEWLINE | TXT_APPENDEOL); sp->showmode = SM_INSERT; if (sp->lno == 1) { if (db_last(sp, &lno)) return (1); if (lno != 0) goto insert; p = NULL; len = 0; ai_line = OOBLNO; } else { insert: p = L(""); sp->cno = 0; LOG_CORRECT; if (cmd == O_cmd) { if (db_insert(sp, sp->lno, p, 0)) return (1); if (db_get(sp, sp->lno, DBG_FATAL, &p, &len)) return (1); ai_line = sp->lno + 1; } else { if (db_append(sp, 1, sp->lno, p, 0)) return (1); if (db_get(sp, ++sp->lno, DBG_FATAL, &p, &len)) return (1); ai_line = sp->lno - 1; } } return (v_txt(sp, vp, NULL, p, len, 0, ai_line, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); } /* * v_change -- [buffer][count]c[count]motion * [buffer][count]C * [buffer][count]S * Change command. * * PUBLIC: int v_change(SCR *, VICMD *); */ int v_change(SCR *sp, VICMD *vp) { size_t blen, len; u_int32_t flags; int isempty, lmode, rval; CHAR_T *bp; CHAR_T *p; /* * 'c' can be combined with motion commands that set the resulting * cursor position, i.e. "cG". Clear the VM_RCM flags and make the * resulting cursor position stick, inserting text has its own rules * for cursor positioning. */ F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SET); /* * Find out if the file is empty, it's easier to handle it as a * special case. */ if (vp->m_start.lno == vp->m_stop.lno && db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); return (v_ia(sp, vp)); } flags = set_txt_std(sp, vp, 0); sp->showmode = SM_CHANGE; /* * Move the cursor to the start of the change. Note, if autoindent * is turned on, the cc command in line mode changes from the first * *non-blank* character of the line, not the first character. And, * to make it just a bit more exciting, the initial space is handled * as auto-indent characters. */ lmode = F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0; if (lmode) { vp->m_start.cno = 0; if (O_ISSET(sp, O_AUTOINDENT)) { if (nonblank(sp, vp->m_start.lno, &vp->m_start.cno)) return (1); LF_SET(TXT_AICHARS); } } sp->lno = vp->m_start.lno; sp->cno = vp->m_start.cno; LOG_CORRECT; /* * If not in line mode and changing within a single line, copy the * text and overwrite it. */ if (!lmode && vp->m_start.lno == vp->m_stop.lno) { /* * !!! * Historic practice, c did not cut into the numeric buffers, * only the unnamed one. */ if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, lmode)) return (1); if (len == 0) LF_SET(TXT_APPENDEOL); LF_SET(TXT_EMARK | TXT_OVERWRITE); return (v_txt(sp, vp, &vp->m_stop, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); } /* * It's trickier if in line mode or changing over multiple lines. If * we're in line mode delete all of the lines and insert a replacement * line which the user edits. If there was leading whitespace in the * first line being changed, we copy it and use it as the replacement. * If we're not in line mode, we delete the text and start inserting. * * !!! * Copy the text. Historic practice, c did not cut into the numeric * buffers, only the unnamed one. */ if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, lmode)) return (1); /* If replacing entire lines and there's leading text. */ if (lmode && vp->m_start.cno) { /* * Get a copy of the first line changed, and copy out the * leading text. */ if (db_get(sp, vp->m_start.lno, DBG_FATAL, &p, &len)) return (1); GET_SPACE_RETW(sp, bp, blen, vp->m_start.cno); MEMMOVE(bp, p, vp->m_start.cno); } else bp = NULL; /* Delete the text. */ if (del(sp, &vp->m_start, &vp->m_stop, lmode)) return (1); /* If replacing entire lines, insert a replacement line. */ if (lmode) { if (db_insert(sp, vp->m_start.lno, bp, vp->m_start.cno)) return (1); sp->lno = vp->m_start.lno; len = sp->cno = vp->m_start.cno; } /* Get the line we're editing. */ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; } /* Check to see if we're appending to the line. */ if (vp->m_start.cno >= len) LF_SET(TXT_APPENDEOL); rval = v_txt(sp, vp, NULL, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags); if (bp != NULL) FREE_SPACEW(sp, bp, blen); return (rval); } /* * v_Replace -- [count]R * Overwrite multiple characters. * * PUBLIC: int v_Replace(SCR *, VICMD *); */ int v_Replace(SCR *sp, VICMD *vp) { size_t len; u_int32_t flags; int isempty; CHAR_T *p; flags = set_txt_std(sp, vp, 0); sp->showmode = SM_REPLACE; if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; LF_SET(TXT_APPENDEOL); } else { if (len == 0) LF_SET(TXT_APPENDEOL); LF_SET(TXT_OVERWRITE | TXT_REPLACE); } vp->m_stop.lno = vp->m_start.lno; vp->m_stop.cno = len ? len - 1 : 0; return (v_txt(sp, vp, &vp->m_stop, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); } /* * v_subst -- [buffer][count]s * Substitute characters. * * PUBLIC: int v_subst(SCR *, VICMD *); */ int v_subst(SCR *sp, VICMD *vp) { size_t len; u_int32_t flags; int isempty; CHAR_T *p; flags = set_txt_std(sp, vp, 0); sp->showmode = SM_CHANGE; if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; LF_SET(TXT_APPENDEOL); } else { if (len == 0) LF_SET(TXT_APPENDEOL); LF_SET(TXT_EMARK | TXT_OVERWRITE); } vp->m_stop.lno = vp->m_start.lno; vp->m_stop.cno = vp->m_start.cno + (F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0); if (vp->m_stop.cno > len - 1) vp->m_stop.cno = len - 1; if (p != NULL && cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, 0)) return (1); return (v_txt(sp, vp, &vp->m_stop, p, len, 0, OOBLNO, 1, flags)); } /* * set_txt_std -- * Initialize text processing flags. */ static u_int32_t set_txt_std(SCR *sp, VICMD *vp, u_int32_t flags) { LF_SET(TXT_CNTRLT | TXT_ESCAPE | TXT_MAPINPUT | TXT_RECORD | TXT_RESOLVE); if (F_ISSET(vp, VC_ISDOT)) LF_SET(TXT_REPLAY); if (O_ISSET(sp, O_ALTWERASE)) LF_SET(TXT_ALTWERASE); if (O_ISSET(sp, O_AUTOINDENT)) LF_SET(TXT_AUTOINDENT); if (O_ISSET(sp, O_BEAUTIFY)) LF_SET(TXT_BEAUTIFY); if (O_ISSET(sp, O_SHOWMATCH)) LF_SET(TXT_SHOWMATCH); if (F_ISSET(sp, SC_SCRIPT)) LF_SET(TXT_CR); if (O_ISSET(sp, O_TTYWERASE)) LF_SET(TXT_TTYWERASE); /* * !!! * Mapped keys were sometimes unaffected by the wrapmargin option * in the historic 4BSD vi. Consider the following commands, where * each is executed on an empty line, in an 80 column screen, with * the wrapmargin value set to 60. * * aABC DEF .... * :map K aABC DEF ^VKKKKK * :map K 5aABC DEF ^VK * * The first and second commands are affected by wrapmargin. The * third is not. (If the inserted text is itself longer than the * wrapmargin value, i.e. if the "ABC DEF " string is replaced by * something that's longer than 60 columns from the beginning of * the line, the first two commands behave as before, but the third * command gets fairly strange.) The problem is that people wrote * macros that depended on the third command NOT being affected by * wrapmargin, as in this gem which centers lines: * * map #c $mq81a ^V^[81^V^V|D`qld0:s/ / /g^V^M$p * * For compatibility reasons, we try and make it all work here. I * offer no hope that this is right, but it's probably pretty close. * * XXX * Once I work my courage up, this is all gonna go away. It's too * evil to survive. */ if ((O_ISSET(sp, O_WRAPLEN) || O_ISSET(sp, O_WRAPMARGIN)) && (!MAPPED_KEYS_WAITING(sp) || !F_ISSET(vp, VC_C1SET))) LF_SET(TXT_WRAPMARGIN); return (flags); } Index: head/contrib/nvi/vi/v_left.c =================================================================== --- head/contrib/nvi/vi/v_left.c (revision 365498) +++ head/contrib/nvi/vi/v_left.c (revision 365499) @@ -1,283 +1,279 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_left.c,v 10.9 2001/06/25 15:19:32 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_left -- [count]^H, [count]h * Move left by columns. * * PUBLIC: int v_left(SCR *, VICMD *); */ int v_left(SCR *sp, VICMD *vp) { recno_t cnt; /* * !!! * The ^H and h commands always failed in the first column. */ if (vp->m_start.cno == 0) { v_sol(sp); return (1); } /* Find the end of the range. */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; if (vp->m_start.cno > cnt) vp->m_stop.cno = vp->m_start.cno - cnt; else vp->m_stop.cno = 0; /* * All commands move to the end of the range. Motion commands * adjust the starting point to the character before the current * one. */ if (ISMOTION(vp)) --vp->m_start.cno; vp->m_final = vp->m_stop; return (0); } /* * v_cfirst -- [count]_ * Move to the first non-blank character in a line. * * PUBLIC: int v_cfirst(SCR *, VICMD *); */ int v_cfirst(SCR *sp, VICMD *vp) { recno_t cnt, lno; /* * !!! * If the _ is a motion component, it makes the command a line motion * e.g. "d_" deletes the line. It also means that the cursor doesn't * move. * * The _ command never failed in the first column. */ if (ISMOTION(vp)) F_SET(vp, VM_LMODE); /* * !!! * Historically a specified count makes _ move down count - 1 * rows, so, "3_" is the same as "2j_". */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; if (cnt != 1) { --vp->count; return (v_down(sp, vp)); } /* * Move to the first non-blank. * * Can't just use RCM_SET_FNB, in case _ is used as the motion * component of another command. */ vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); /* * !!! * The _ command has to fail if the file is empty and we're doing * a delete. If deleting line 1, and 0 is the first nonblank, * make the check. */ if (vp->m_stop.lno == 1 && vp->m_stop.cno == 0 && ISCMD(vp->rkp, 'd')) { if (db_last(sp, &lno)) return (1); if (lno == 0) { v_sol(sp); return (1); } } /* * Delete and non-motion commands move to the end of the range, * yank stays at the start. Ignore others. */ vp->m_final = ISMOTION(vp) && ISCMD(vp->rkp, 'y') ? vp->m_start : vp->m_stop; return (0); } /* * v_first -- ^ * Move to the first non-blank character in this line. * * PUBLIC: int v_first(SCR *, VICMD *); */ int v_first(SCR *sp, VICMD *vp) { /* * !!! * Yielding to none in our quest for compatibility with every * historical blemish of vi, no matter how strange it might be, * we permit the user to enter a count and then ignore it. */ /* * Move to the first non-blank. * * Can't just use RCM_SET_FNB, in case ^ is used as the motion * component of another command. */ vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); /* * !!! * The ^ command succeeded if used as a command when the cursor was * on the first non-blank in the line, but failed if used as a motion * component in the same situation. */ if (ISMOTION(vp) && vp->m_start.cno == vp->m_stop.cno) { v_sol(sp); return (1); } /* * If moving right, non-motion commands move to the end of the range. * Delete and yank stay at the start. Motion commands adjust the * ending point to the character before the current ending charcter. * * If moving left, all commands move to the end of the range. Motion * commands adjust the starting point to the character before the * current starting character. */ if (vp->m_start.cno < vp->m_stop.cno) if (ISMOTION(vp)) { --vp->m_stop.cno; vp->m_final = vp->m_start; } else vp->m_final = vp->m_stop; else { if (ISMOTION(vp)) --vp->m_start.cno; vp->m_final = vp->m_stop; } return (0); } /* * v_ncol -- [count]| * Move to column count or the first column on this line. If the * requested column is past EOL, move to EOL. The nasty part is * that we have to know character column widths to make this work. * * PUBLIC: int v_ncol(SCR *, VICMD *); */ int v_ncol(SCR *sp, VICMD *vp) { if (F_ISSET(vp, VC_C1SET) && vp->count > 1) { --vp->count; vp->m_stop.cno = vs_colpos(sp, vp->m_start.lno, (size_t)vp->count); /* * !!! * The | command succeeded if used as a command and the cursor * didn't move, but failed if used as a motion component in the * same situation. */ if (ISMOTION(vp) && vp->m_stop.cno == vp->m_start.cno) { v_nomove(sp); return (1); } } else { /* * !!! * The | command succeeded if used as a command in column 0 * without a count, but failed if used as a motion component * in the same situation. */ if (ISMOTION(vp) && vp->m_start.cno == 0) { v_sol(sp); return (1); } vp->m_stop.cno = 0; } /* * If moving right, non-motion commands move to the end of the range. * Delete and yank stay at the start. Motion commands adjust the * ending point to the character before the current ending charcter. * * If moving left, all commands move to the end of the range. Motion * commands adjust the starting point to the character before the * current starting character. */ if (vp->m_start.cno < vp->m_stop.cno) if (ISMOTION(vp)) { --vp->m_stop.cno; vp->m_final = vp->m_start; } else vp->m_final = vp->m_stop; else { if (ISMOTION(vp)) --vp->m_start.cno; vp->m_final = vp->m_stop; } return (0); } /* * v_zero -- 0 * Move to the first column on this line. * * PUBLIC: int v_zero(SCR *, VICMD *); */ int v_zero(SCR *sp, VICMD *vp) { /* * !!! * The 0 command succeeded if used as a command in the first column * but failed if used as a motion component in the same situation. */ if (ISMOTION(vp) && vp->m_start.cno == 0) { v_sol(sp); return (1); } /* * All commands move to the end of the range. Motion commands * adjust the starting point to the character before the current * one. */ vp->m_stop.cno = 0; if (ISMOTION(vp)) --vp->m_start.cno; vp->m_final = vp->m_stop; return (0); } Index: head/contrib/nvi/vi/v_mark.c =================================================================== --- head/contrib/nvi/vi/v_mark.c (revision 365498) +++ head/contrib/nvi/vi/v_mark.c (revision 365499) @@ -1,231 +1,227 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_mark.c,v 10.12 2001/06/25 15:19:32 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" enum which {BQMARK, FQMARK}; static int mark(SCR *, VICMD *, int, enum which); /* * v_mark -- m[a-z] * Set a mark. * * PUBLIC: int v_mark(SCR *, VICMD *); */ int v_mark(SCR *sp, VICMD *vp) { return (mark_set(sp, vp->character, &vp->m_start, 1)); } /* * v_bmark -- `['`a-z] * Move to a mark. * * Moves to a mark, setting both row and column. * * !!! * Although not commonly known, the "'`" and "'`" forms are historically * valid. The behavior is determined by the first character, so "`'" is * the same as "``". Remember this fact -- you'll be amazed at how many * people don't know it and will be delighted that you are able to tell * them. * * PUBLIC: int v_bmark(SCR *, VICMD *); */ int v_bmark(SCR *sp, VICMD *vp) { return (mark(sp, vp, 1, BQMARK)); } /* * v_fmark -- '['`a-z] * Move to a mark. * * Move to the first nonblank character of the line containing the mark. * * PUBLIC: int v_fmark(SCR *, VICMD *); */ int v_fmark(SCR *sp, VICMD *vp) { return (mark(sp, vp, 1, FQMARK)); } /* * v_emark -- * Mouse mark. * * PUBLIC: int v_emark(SCR *, VICMD *); */ int v_emark(SCR *sp, VICMD *vp) { SMAP *smp; smp = HMAP + vp->ev.e_lno; if (smp > TMAP) { msgq(sp, M_BERR, "320|Unknown cursor position."); return (1); } vp->m_stop.lno = smp->lno; vp->m_stop.cno = vs_colpos(sp, smp->lno, vp->ev.e_cno + (smp->soff - 1) * sp->cols); return (mark(sp, vp, 0, BQMARK)); } /* * mark -- * Mark commands. */ static int mark(SCR *sp, VICMD *vp, int getmark, enum which cmd) { dir_t dir; MARK m; size_t len; if (getmark && mark_get(sp, vp->character, &vp->m_stop, M_BERR)) return (1); /* * !!! * Historically, BQMARKS for character positions that no longer * existed acted as FQMARKS. * * FQMARKS move to the first non-blank. */ switch (cmd) { case BQMARK: if (db_get(sp, vp->m_stop.lno, DBG_FATAL, NULL, &len)) return (1); if (vp->m_stop.cno < len || (vp->m_stop.cno == len && len == 0)) break; if (ISMOTION(vp)) F_SET(vp, VM_LMODE); cmd = FQMARK; /* FALLTHROUGH */ case FQMARK: vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); break; default: abort(); } /* Non-motion commands move to the end of the range. */ if (!ISMOTION(vp)) { vp->m_final = vp->m_stop; return (0); } /* * !!! * If a motion component to a BQMARK, the cursor has to move. */ if (cmd == BQMARK && vp->m_stop.lno == vp->m_start.lno && vp->m_stop.cno == vp->m_start.cno) { v_nomove(sp); return (1); } /* * If the motion is in the reverse direction, switch the start and * stop MARK's so that it's in a forward direction. (There's no * reason for this other than to make the tests below easier. The * code in vi.c:vi() would have done the switch.) Both forward * and backward motions can happen for any kind of search command. */ if (vp->m_start.lno > vp->m_stop.lno || (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno > vp->m_stop.cno)) { m = vp->m_start; vp->m_start = vp->m_stop; vp->m_stop = m; dir = BACKWARD; } else dir = FORWARD; /* * Yank cursor motion, when associated with marks as motion commands, * historically behaved as follows: * * ` motion ' motion * Line change? Line change? * Y N Y N * -------------- --------------- * FORWARD: | NM NM | NM NM * | | * BACKWARD: | M M | M NM(1) * * where NM means the cursor didn't move, and M means the cursor * moved to the mark. * * As the cursor was usually moved for yank commands associated * with backward motions, this implementation regularizes it by * changing the NM at position (1) to be an M. This makes mark * motions match search motions, which is probably A Good Thing. * * Delete cursor motion was always to the start of the text region, * regardless. Ignore other motion commands. */ vp->m_final = vp->m_start; /* * Forward marks are always line oriented, and it's set in the * vcmd.c table. */ if (cmd == FQMARK) return (0); /* * BQMARK'S moving backward and starting at column 0, and ones moving * forward and ending at column 0 are corrected to the last column of * the previous line. Otherwise, adjust the starting/ending point to * the character before the current one (this is safe because we know * the search had to move to succeed). * * Mark motions become line mode opertions if they start at the first * nonblank and end at column 0 of another line. */ if (vp->m_start.lno < vp->m_stop.lno && vp->m_stop.cno == 0) { if (db_get(sp, --vp->m_stop.lno, DBG_FATAL, NULL, &len)) return (1); vp->m_stop.cno = len ? len - 1 : 0; len = 0; if (nonblank(sp, vp->m_start.lno, &len)) return (1); if (vp->m_start.cno <= len) F_SET(vp, VM_LMODE); } else --vp->m_stop.cno; return (0); } Index: head/contrib/nvi/vi/v_match.c =================================================================== --- head/contrib/nvi/vi/v_match.c (revision 365498) +++ head/contrib/nvi/vi/v_match.c (revision 365499) @@ -1,177 +1,172 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_match.c,v 10.11 2012/02/11 00:33:46 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_match -- % * Search to matching character. * * PUBLIC: int v_match(SCR *, VICMD *); */ int v_match(SCR *sp, VICMD *vp) { VCS cs; MARK *mp; size_t cno, len, off; int cnt, isempty, matchc, startc, (*gc)(SCR *, VCS *); CHAR_T *p; CHAR_T *cp; const CHAR_T *match_chars; /* * Historically vi would match (), {} and [] however * an update included <>. This is ok for editing HTML * but a pain in the butt for C source. * Making it an option lets the user decide what is 'right'. */ match_chars = VIP(sp)->mcs; /* * !!! * Historic practice; ignore the count. * * !!! * Historical practice was to search for the initial character in the * forward direction only. */ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto nomatch; return (1); } for (off = vp->m_start.cno;; ++off) { if (off >= len) { nomatch: msgq(sp, M_BERR, "184|No match character on this line"); return (1); } startc = p[off]; cp = STRCHR(match_chars, startc); if (cp != NULL) { cnt = cp - match_chars; matchc = match_chars[cnt ^ 1]; gc = cnt & 1 ? cs_prev : cs_next; break; } } cs.cs_lno = vp->m_start.lno; cs.cs_cno = off; if (cs_init(sp, &cs)) return (1); for (cnt = 1;;) { if (gc(sp, &cs)) return (1); if (cs.cs_flags != 0) { if (cs.cs_flags == CS_EOF || cs.cs_flags == CS_SOF) break; continue; } if (cs.cs_ch == startc) ++cnt; else if (cs.cs_ch == matchc && --cnt == 0) break; } if (cnt) { msgq(sp, M_BERR, "185|Matching character not found"); return (1); } vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * If moving right, non-motion commands move to the end of the range. * Delete and yank stay at the start. * * If moving left, all commands move to the end of the range. * * !!! * Don't correct for leftward movement -- historic vi deleted the * starting cursor position when deleting to a match. */ if (vp->m_start.lno < vp->m_stop.lno || (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno < vp->m_stop.cno)) vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; else vp->m_final = vp->m_stop; /* * !!! * If the motion is across lines, and the earliest cursor position * is at or before any non-blank characters in the line, i.e. the * movement is cutting all of the line's text, and the later cursor * position has nothing other than whitespace characters between it * and the end of its line, the buffer is in line mode. */ if (!ISMOTION(vp) || vp->m_start.lno == vp->m_stop.lno) return (0); mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_start : &vp->m_stop; if (mp->cno != 0) { cno = 0; if (nonblank(sp, mp->lno, &cno)) return (1); if (cno < mp->cno) return (0); } mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_stop : &vp->m_start; if (db_get(sp, mp->lno, DBG_FATAL, &p, &len)) return (1); for (p += mp->cno + 1, len -= mp->cno; --len; ++p) if (!isblank(*p)) return (0); F_SET(vp, VM_LMODE); return (0); } /* * v_buildmcs -- * Build the match character list. * * PUBLIC: int v_buildmcs(SCR *, char *); */ int v_buildmcs(SCR *sp, char *str) { CHAR_T **mp = &VIP(sp)->mcs; size_t len = strlen(str) + 1; - if (*mp != NULL) - free(*mp); - MALLOC(sp, *mp, CHAR_T *, len * sizeof(CHAR_T)); + free(*mp); + MALLOC(sp, *mp, len * sizeof(CHAR_T)); if (*mp == NULL) return (1); #ifdef USE_WIDECHAR if (mbstowcs(*mp, str, len) == (size_t)-1) return (1); #else memcpy(*mp, str, len); #endif return (0); } Index: head/contrib/nvi/vi/v_paragraph.c =================================================================== --- head/contrib/nvi/vi/v_paragraph.c (revision 365498) +++ head/contrib/nvi/vi/v_paragraph.c (revision 365499) @@ -1,340 +1,335 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_paragraph.c,v 10.10 2001/06/25 15:19:32 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" #define INTEXT_CHECK { \ if (len == 0 || v_isempty(p, len)) { \ if (!--cnt) \ goto found; \ pstate = P_INBLANK; \ } \ /* \ * !!! \ * Historic documentation (USD:15-11, 4.2) said that formfeed \ * characters (^L) in the first column delimited paragraphs. \ * The historic vi code mentions formfeed characters, but never \ * implements them. It seems reasonable, do it. \ */ \ if (p[0] == '\014') { \ if (!--cnt) \ goto found; \ continue; \ } \ if (p[0] != '.' || len < 2) \ continue; \ for (lp = VIP(sp)->ps; *lp != '\0'; lp += 2) \ if (lp[0] == p[1] && \ (lp[1] == ' ' && len == 2 || lp[1] == p[2]) && \ !--cnt) \ goto found; \ } /* * v_paragraphf -- [count]} * Move forward count paragraphs. * * Paragraphs are empty lines after text, formfeed characters, or values * from the paragraph or section options. * * PUBLIC: int v_paragraphf(SCR *, VICMD *); */ int v_paragraphf(SCR *sp, VICMD *vp) { enum { P_INTEXT, P_INBLANK } pstate; size_t lastlen, len; recno_t cnt, lastlno, lno; int isempty; CHAR_T *p; char *lp; /* * !!! * If the starting cursor position is at or before any non-blank * characters in the line, i.e. the movement is cutting all of the * line's text, the buffer is in line mode. It's a lot easier to * check here, because we know that the end is going to be the start * or end of a line. * * This was historical practice in vi, with a single exception. If * the paragraph movement was from the start of the last line to EOF, * then all the characters were deleted from the last line, but the * line itself remained. If somebody complains, don't pause, don't * hesitate, just hit them. */ if (ISMOTION(vp)) if (vp->m_start.cno == 0) F_SET(vp, VM_LMODE); else { vp->m_stop = vp->m_start; vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); if (vp->m_start.cno <= vp->m_stop.cno) F_SET(vp, VM_LMODE); } /* Figure out what state we're currently in. */ lno = vp->m_start.lno; if (db_get(sp, lno, 0, &p, &len)) goto eof; /* * If we start in text, we want to switch states * (2 * N - 1) times, in non-text, (2 * N) times. */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt *= 2; if (len == 0 || v_isempty(p, len)) pstate = P_INBLANK; else { --cnt; pstate = P_INTEXT; } for (;;) { lastlno = lno; lastlen = len; if (db_get(sp, ++lno, 0, &p, &len)) goto eof; switch (pstate) { case P_INTEXT: INTEXT_CHECK; break; case P_INBLANK: if (len == 0 || v_isempty(p, len)) break; if (--cnt) { pstate = P_INTEXT; break; } /* * !!! * Non-motion commands move to the end of the range, * delete and yank stay at the start. Ignore others. * Adjust the end of the range for motion commands; * historically, a motion component was to the end of * the previous line, whereas the movement command was * to the start of the new "paragraph". */ found: if (ISMOTION(vp)) { vp->m_stop.lno = lastlno; vp->m_stop.cno = lastlen ? lastlen - 1 : 0; vp->m_final = vp->m_start; } else { vp->m_stop.lno = lno; vp->m_stop.cno = 0; vp->m_final = vp->m_stop; } return (0); default: abort(); } } /* * !!! * Adjust end of the range for motion commands; EOF is a movement * sink. The } command historically moved to the end of the last * line, not the beginning, from any position before the end of the * last line. It also historically worked on empty files, so we * have to make it okay. */ eof: if (vp->m_start.lno == lno || vp->m_start.lno == lno - 1) { if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); vp->m_start.cno = 0; return (0); } if (vp->m_start.cno == (len ? len - 1 : 0)) { v_eof(sp, NULL); return (1); } } /* * !!! * Non-motion commands move to the end of the range, delete * and yank stay at the start. Ignore others. * * If deleting the line (which happens if deleting to EOF), then * cursor movement is to the first nonblank. */ if (ISMOTION(vp) && ISCMD(vp->rkp, 'd')) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SETFNB); } vp->m_stop.lno = lno - 1; vp->m_stop.cno = len ? len - 1 : 0; vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; return (0); } /* * v_paragraphb -- [count]{ * Move backward count paragraphs. * * PUBLIC: int v_paragraphb(SCR *, VICMD *); */ int v_paragraphb(SCR *sp, VICMD *vp) { enum { P_INTEXT, P_INBLANK } pstate; size_t len; recno_t cnt, lno; CHAR_T *p; char *lp; /* * !!! * Check for SOF. The historic vi didn't complain if users hit SOF * repeatedly, unless it was part of a motion command. There is no * question but that Emerson's editor of choice was vi. * * The { command historically moved to the beginning of the first * line if invoked on the first line. * * !!! * If the starting cursor position is in the first column (backward * paragraph movements did NOT historically pay attention to non-blank * characters) i.e. the movement is cutting the entire line, the buffer * is in line mode. Cuts from the beginning of the line also did not * cut the current line, but started at the previous EOL. * * Correct for a left motion component while we're thinking about it. */ lno = vp->m_start.lno; if (ISMOTION(vp)) if (vp->m_start.cno == 0) { if (vp->m_start.lno == 1) { v_sof(sp, &vp->m_start); return (1); } else --vp->m_start.lno; F_SET(vp, VM_LMODE); } else --vp->m_start.cno; if (vp->m_start.lno <= 1) goto sof; /* Figure out what state we're currently in. */ if (db_get(sp, lno, 0, &p, &len)) goto sof; /* * If we start in text, we want to switch states * (2 * N - 1) times, in non-text, (2 * N) times. */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt *= 2; if (len == 0 || v_isempty(p, len)) pstate = P_INBLANK; else { --cnt; pstate = P_INTEXT; /* * !!! * If the starting cursor is past the first column, * the current line is checked for a paragraph. */ if (vp->m_start.cno > 0) ++lno; } for (;;) { if (db_get(sp, --lno, 0, &p, &len)) goto sof; switch (pstate) { case P_INTEXT: INTEXT_CHECK; break; case P_INBLANK: if (len != 0 && !v_isempty(p, len)) { if (!--cnt) goto found; pstate = P_INTEXT; } break; default: abort(); } } /* SOF is a movement sink. */ sof: lno = 1; found: vp->m_stop.lno = lno; vp->m_stop.cno = 0; /* * All commands move to the end of the range. (We already * adjusted the start of the range for motion commands). */ vp->m_final = vp->m_stop; return (0); } /* * v_buildps -- * Build the paragraph command search pattern. * * PUBLIC: int v_buildps(SCR *, char *, char *); */ int v_buildps(SCR *sp, char *p_p, char *s_p) { VI_PRIVATE *vip; size_t p_len, s_len; char *p; /* * The vi paragraph command searches for either a paragraph or * section option macro. */ p_len = p_p == NULL ? 0 : strlen(p_p); s_len = s_p == NULL ? 0 : strlen(s_p); if (p_len == 0 && s_len == 0) return (0); - MALLOC_RET(sp, p, char *, p_len + s_len + 1); + MALLOC_RET(sp, p, p_len + s_len + 1); vip = VIP(sp); - if (vip->ps != NULL) - free(vip->ps); + free(vip->ps); if (p_p != NULL) memmove(p, p_p, p_len + 1); if (s_p != NULL) memmove(p + p_len, s_p, s_len + 1); vip->ps = p; return (0); } Index: head/contrib/nvi/vi/v_put.c =================================================================== --- head/contrib/nvi/vi/v_put.c (revision 365498) +++ head/contrib/nvi/vi/v_put.c (revision 365499) @@ -1,140 +1,136 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_put.c,v 10.6 2001/06/25 15:19:34 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static void inc_buf(SCR *, VICMD *); /* * v_Put -- [buffer]P * Insert the contents of the buffer before the cursor. * * PUBLIC: int v_Put(SCR *, VICMD *); */ int v_Put(SCR *sp, VICMD *vp) { u_long cnt; if (F_ISSET(vp, VC_ISDOT)) inc_buf(sp, vp); /* * !!! * Historic vi did not support a count with the 'p' and 'P' * commands. It's useful, so we do. */ for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) { if (put(sp, NULL, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_final, 0)) return (1); vp->m_start = vp->m_final; if (INTERRUPTED(sp)) return (1); } return (0); } /* * v_put -- [buffer]p * Insert the contents of the buffer after the cursor. * * PUBLIC: int v_put(SCR *, VICMD *); */ int v_put(SCR *sp, VICMD *vp) { u_long cnt; if (F_ISSET(vp, VC_ISDOT)) inc_buf(sp, vp); /* * !!! * Historic vi did not support a count with the 'p' and 'P' * commands. It's useful, so we do. */ for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) { if (put(sp, NULL, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_final, 1)) return (1); vp->m_start = vp->m_final; if (INTERRUPTED(sp)) return (1); } return (0); } /* * !!! * Historical whackadoo. The dot command `puts' the numbered buffer * after the last one put. For example, `"4p.' would put buffer #4 * and buffer #5. If the user continued to enter '.', the #9 buffer * would be repeatedly output. This was not documented, and is a bit * tricky to reconstruct. Historical versions of vi also dropped the * contents of the default buffer after each put, so after `"4p' the * default buffer would be empty. This makes no sense to me, so we * don't bother. Don't assume sequential order of numeric characters. * * And, if that weren't exciting enough, failed commands don't normally * set the dot command. Well, boys and girls, an exception is that * the buffer increment gets done regardless of the success of the put. */ static void inc_buf(SCR *sp, VICMD *vp) { CHAR_T v; switch (vp->buffer) { case '1': v = '2'; break; case '2': v = '3'; break; case '3': v = '4'; break; case '4': v = '5'; break; case '5': v = '6'; break; case '6': v = '7'; break; case '7': v = '8'; break; case '8': v = '9'; break; default: return; } VIP(sp)->sdot.buffer = vp->buffer = v; } Index: head/contrib/nvi/vi/v_redraw.c =================================================================== --- head/contrib/nvi/vi/v_redraw.c (revision 365498) +++ head/contrib/nvi/vi/v_redraw.c (revision 365499) @@ -1,37 +1,33 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_redraw.c,v 10.7 2001/06/25 15:19:34 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_redraw -- ^L, ^R * Redraw the screen. * * PUBLIC: int v_redraw(SCR *, VICMD *); */ int v_redraw(SCR *sp, VICMD *vp) { return (sp->gp->scr_refresh(sp, 1)); } Index: head/contrib/nvi/vi/v_replace.c =================================================================== --- head/contrib/nvi/vi/v_replace.c (revision 365498) +++ head/contrib/nvi/vi/v_replace.c (revision 365499) @@ -1,202 +1,198 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_replace.c,v 10.24 2001/06/25 15:19:34 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_replace -- [count]r * * !!! * The r command in historic vi was almost beautiful in its badness. For * example, "r" and "r" beeped the terminal and deleted * a single character. "Nr", where N was greater than 1, * inserted a single carriage return. "r" did cancel the command, * but "r" erased a single character. To enter a literal * character, it required three characters after the * command. This may not be right, but at least it's not insane. * * PUBLIC: int v_replace(SCR *, VICMD *); */ int v_replace(SCR *sp, VICMD *vp) { EVENT ev; VI_PRIVATE *vip; TEXT *tp; size_t blen, len; u_long cnt; int quote, rval; CHAR_T *bp; CHAR_T *p; vip = VIP(sp); /* * If the line doesn't exist, or it's empty, replacement isn't * allowed. It's not hard to implement, but: * * 1: It's historic practice (vi beeped before the replacement * character was even entered). * 2: For consistency, this change would require that the more * general case, "Nr", when the user is < N characters from * the end of the line, also work, which would be a bit odd. * 3: Replacing with a has somewhat odd semantics. */ if (db_get(sp, vp->m_start.lno, DBG_FATAL, &p, &len)) return (1); if (len == 0) { msgq(sp, M_BERR, "186|No characters to replace"); return (1); } /* * Figure out how many characters to be replace. For no particular * reason (other than that the semantics of replacing the newline * are confusing) only permit the replacement of the characters in * the current line. I suppose we could append replacement characters * to the line, but I see no compelling reason to do so. Check this * before we get the character to match historic practice, where Nr * failed immediately if there were less than N characters from the * cursor to the end of the line. */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; vp->m_stop.lno = vp->m_start.lno; vp->m_stop.cno = vp->m_start.cno + cnt - 1; if (vp->m_stop.cno > len - 1) { v_eol(sp, &vp->m_start); return (1); } /* * If it's not a repeat, reset the current mode and get a replacement * character. */ quote = 0; if (!F_ISSET(vp, VC_ISDOT)) { sp->showmode = SM_REPLACE; if (vs_refresh(sp, 0)) return (1); next: if (v_event_get(sp, &ev, 0, 0)) return (1); switch (ev.e_event) { case E_CHARACTER: /* * means escape the next character. * means they changed their minds. */ if (!quote) { if (ev.e_value == K_VLNEXT) { quote = 1; goto next; } if (ev.e_value == K_ESCAPE) return (0); } vip->rlast = ev.e_c; vip->rvalue = ev.e_value; break; case E_ERR: case E_EOF: F_SET(sp, SC_EXIT_FORCE); return (1); case E_INTERRUPT: /* means they changed their minds. */ return (0); case E_WRESIZE: /* interrupts the input mode. */ v_emsg(sp, NULL, VIM_WRESIZE); return (0); case E_REPAINT: if (vs_repaint(sp, &ev)) return (1); goto next; default: v_event_err(sp, &ev); return (0); } } /* Copy the line. */ GET_SPACE_RETW(sp, bp, blen, len); MEMMOVE(bp, p, len); p = bp; /* * Versions of nvi before 1.57 created N new lines when they replaced * N characters with or characters. This * is different from the historic vi, which replaced N characters with * a single new line. Users complained, so we match historic practice. */ if ((!quote && vip->rvalue == K_CR) || vip->rvalue == K_NL) { /* Set return line. */ vp->m_stop.lno = vp->m_start.lno + 1; vp->m_stop.cno = 0; /* The first part of the current line. */ if (db_set(sp, vp->m_start.lno, p, vp->m_start.cno)) goto err_ret; /* * The rest of the current line. And, of course, now it gets * tricky. If there are characters left in the line and if * the autoindent edit option is set, white space after the * replaced character is discarded, autoindent is applied, and * the cursor moves to the last indent character. */ p += vp->m_start.cno + cnt; len -= vp->m_start.cno + cnt; if (len != 0 && O_ISSET(sp, O_AUTOINDENT)) for (; len && isblank(*p); --len, ++p); if ((tp = text_init(sp, p, len, len)) == NULL) goto err_ret; if (len != 0 && O_ISSET(sp, O_AUTOINDENT)) { if (v_txt_auto(sp, vp->m_start.lno, NULL, 0, tp)) goto err_ret; vp->m_stop.cno = tp->ai ? tp->ai - 1 : 0; } else vp->m_stop.cno = 0; vp->m_stop.cno = tp->ai ? tp->ai - 1 : 0; if (db_append(sp, 1, vp->m_start.lno, tp->lb, tp->len)) err_ret: rval = 1; else { text_free(tp); rval = 0; } } else { STRSET(bp + vp->m_start.cno, vip->rlast, cnt); rval = db_set(sp, vp->m_start.lno, bp, len); } FREE_SPACEW(sp, bp, blen); vp->m_final = vp->m_stop; return (rval); } Index: head/contrib/nvi/vi/v_right.c =================================================================== --- head/contrib/nvi/vi/v_right.c (revision 365498) +++ head/contrib/nvi/vi/v_right.c (revision 365499) @@ -1,141 +1,137 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_right.c,v 10.8 2001/06/25 15:19:34 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_right -- [count]' ', [count]l * Move right by columns. * * PUBLIC: int v_right(SCR *, VICMD *); */ int v_right(SCR *sp, VICMD *vp) { size_t len; int isempty; if (db_eget(sp, vp->m_start.lno, NULL, &len, &isempty)) { if (isempty) goto eol; return (1); } /* It's always illegal to move right on empty lines. */ if (len == 0) { eol: v_eol(sp, NULL); return (1); } /* * Non-motion commands move to the end of the range. Delete and * yank stay at the start. Ignore others. Adjust the end of the * range for motion commands. * * !!! * Historically, "[cdsy]l" worked at the end of a line. Also, * EOL is a count sink. */ vp->m_stop.cno = vp->m_start.cno + (F_ISSET(vp, VC_C1SET) ? vp->count : 1); if (vp->m_start.cno == len - 1 && !ISMOTION(vp)) { v_eol(sp, NULL); return (1); } if (vp->m_stop.cno >= len) { vp->m_stop.cno = len - 1; vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; } else if (ISMOTION(vp)) { --vp->m_stop.cno; vp->m_final = vp->m_start; } else vp->m_final = vp->m_stop; return (0); } /* * v_dollar -- [count]$ * Move to the last column. * * PUBLIC: int v_dollar(SCR *, VICMD *); */ int v_dollar(SCR *sp, VICMD *vp) { size_t len; int isempty; /* * !!! * A count moves down count - 1 rows, so, "3$" is the same as "2j$". */ if ((F_ISSET(vp, VC_C1SET) ? vp->count : 1) != 1) { /* * !!! * Historically, if the $ is a motion, and deleting from * at or before the first non-blank of the line, it's a * line motion, and the line motion flag is set. */ vp->m_stop.cno = 0; if (nonblank(sp, vp->m_start.lno, &vp->m_stop.cno)) return (1); if (ISMOTION(vp) && vp->m_start.cno <= vp->m_stop.cno) F_SET(vp, VM_LMODE); --vp->count; if (v_down(sp, vp)) return (1); } /* * !!! * Historically, it was illegal to use $ as a motion command on * an empty line. Unfortunately, even though C was historically * aliased to c$, it (and not c$) was special cased to work on * empty lines. Since we alias C to c$ too, we have a problem. * To fix it, we let c$ go through, on the assumption that it's * not a problem for it to work. */ if (db_eget(sp, vp->m_stop.lno, NULL, &len, &isempty)) { if (!isempty) return (1); len = 0; } if (len == 0) { if (ISMOTION(vp) && !ISCMD(vp->rkp, 'c')) { v_eol(sp, NULL); return (1); } return (0); } /* * Non-motion commands move to the end of the range. Delete * and yank stay at the start. Ignore others. */ vp->m_stop.cno = len ? len - 1 : 0; vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; return (0); } Index: head/contrib/nvi/vi/v_screen.c =================================================================== --- head/contrib/nvi/vi/v_screen.c (revision 365498) +++ head/contrib/nvi/vi/v_screen.c (revision 365499) @@ -1,63 +1,59 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_screen.c,v 10.12 2001/06/25 15:19:34 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_screen -- ^W * Switch screens. * * PUBLIC: int v_screen(SCR *, VICMD *); */ int v_screen(SCR *sp, VICMD *vp) { /* * You can't leave a colon command-line edit window -- it's not that * it won't work, but it gets real weird, real fast when you execute * a colon command out of a window that was forked from a window that's * now backgrounded... You get the idea. */ if (F_ISSET(sp, SC_COMEDIT)) { msgq(sp, M_ERR, "308|Enter to execute a command, :q to exit"); return (1); } /* * Try for the next lower screen, or, go back to the first * screen on the stack. */ if (TAILQ_NEXT(sp, q) != NULL) sp->nextdisp = TAILQ_NEXT(sp, q); else if (TAILQ_FIRST(sp->gp->dq) == sp) { msgq(sp, M_ERR, "187|No other screen to switch to"); return (1); } else sp->nextdisp = TAILQ_FIRST(sp->gp->dq); F_SET(sp->nextdisp, SC_STATUS); F_SET(sp, SC_SSWITCH | SC_STATUS); return (0); } Index: head/contrib/nvi/vi/v_scroll.c =================================================================== --- head/contrib/nvi/vi/v_scroll.c (revision 365498) +++ head/contrib/nvi/vi/v_scroll.c (revision 365499) @@ -1,447 +1,443 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_scroll.c,v 10.12 2001/06/25 15:19:34 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static void goto_adjust(VICMD *); /* * The historic vi had a problem in that all movements were by physical * lines, not by logical, or screen lines. Arguments can be made that this * is the right thing to do. For example, single line movements, such as * 'j' or 'k', should probably work on physical lines. Commands like "dj", * or "j.", where '.' is a change command, make more sense for physical lines * than they do for logical lines. * * These arguments, however, don't apply to scrolling commands like ^D and * ^F -- if the window is fairly small, using physical lines can result in * a half-page scroll repainting the entire screen, which is not what the * user wanted. Second, if the line is larger than the screen, using physical * lines can make it impossible to display parts of the line -- there aren't * any commands that don't display the beginning of the line in historic vi, * and if both the beginning and end of the line can't be on the screen at * the same time, you lose. This is even worse in the case of the H, L, and * M commands -- for large lines, they may all refer to the same line and * will result in no movement at all. * * Another issue is that page and half-page scrolling commands historically * moved to the first non-blank character in the new line. If the line is * approximately the same size as the screen, this loses because the cursor * before and after a ^D, may refer to the same location on the screen. In * this implementation, scrolling commands set the cursor to the first non- * blank character if the line changes because of the scroll. Otherwise, * the cursor is left alone. * * This implementation does the scrolling (^B, ^D, ^F, ^U, ^Y, ^E), and the * cursor positioning commands (H, L, M) commands using logical lines, not * physical. */ /* * v_lgoto -- [count]G * Go to first non-blank character of the line count, the last line * of the file by default. * * PUBLIC: int v_lgoto(SCR *, VICMD *); */ int v_lgoto(SCR *sp, VICMD *vp) { recno_t nlines; if (F_ISSET(vp, VC_C1SET)) { if (!db_exist(sp, vp->count)) { /* * !!! * Historically, 1G was legal in an empty file. */ if (vp->count == 1) { if (db_last(sp, &nlines)) return (1); if (nlines == 0) return (0); } v_eof(sp, &vp->m_start); return (1); } vp->m_stop.lno = vp->count; } else { if (db_last(sp, &nlines)) return (1); vp->m_stop.lno = nlines ? nlines : 1; } goto_adjust(vp); return (0); } /* * v_home -- [count]H * Move to the first non-blank character of the logical line * count - 1 from the top of the screen, 0 by default. * * PUBLIC: int v_home(SCR *, VICMD *); */ int v_home(SCR *sp, VICMD *vp) { if (vs_sm_position(sp, &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0, P_TOP)) return (1); goto_adjust(vp); return (0); } /* * v_middle -- M * Move to the first non-blank character of the logical line * in the middle of the screen. * * PUBLIC: int v_middle(SCR *, VICMD *); */ int v_middle(SCR *sp, VICMD *vp) { /* * Yielding to none in our quest for compatibility with every * historical blemish of vi, no matter how strange it might be, * we permit the user to enter a count and then ignore it. */ if (vs_sm_position(sp, &vp->m_stop, 0, P_MIDDLE)) return (1); goto_adjust(vp); return (0); } /* * v_bottom -- [count]L * Move to the first non-blank character of the logical line * count - 1 from the bottom of the screen, 0 by default. * * PUBLIC: int v_bottom(SCR *, VICMD *); */ int v_bottom(SCR *sp, VICMD *vp) { if (vs_sm_position(sp, &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0, P_BOTTOM)) return (1); goto_adjust(vp); return (0); } static void goto_adjust(VICMD *vp) { /* Guess that it's the end of the range. */ vp->m_final = vp->m_stop; /* * Non-motion commands move the cursor to the end of the range, and * then to the NEXT nonblank of the line. Historic vi always moved * to the first nonblank in the line; since the H, M, and L commands * are logical motions in this implementation, we do the next nonblank * so that it looks approximately the same to the user. To make this * happen, the VM_RCM_SETNNB flag is set in the vcmd.c command table. * * If it's a motion, it's more complicated. The best possible solution * is probably to display the first nonblank of the line the cursor * will eventually rest on. This is tricky, particularly given that if * the associated command is a delete, we don't yet know what line that * will be. So, we clear the VM_RCM_SETNNB flag, and set the first * nonblank flag (VM_RCM_SETFNB). Note, if the lines are sufficiently * long, this can cause the cursor to warp out of the screen. It's too * hard to fix. * * XXX * The G command is always first nonblank, so it's okay to reset it. */ if (ISMOTION(vp)) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SETFNB); } else return; /* * If moving backward in the file, delete and yank move to the end * of the range, unless the line didn't change, in which case yank * doesn't move. If moving forward in the file, delete and yank * stay at the start of the range. Ignore others. */ if (vp->m_stop.lno < vp->m_start.lno || (vp->m_stop.lno == vp->m_start.lno && vp->m_stop.cno < vp->m_start.cno)) { if (ISCMD(vp->rkp, 'y') && vp->m_stop.lno == vp->m_start.lno) vp->m_final = vp->m_start; } else vp->m_final = vp->m_start; } /* * v_up -- [count]^P, [count]k, [count]- * Move up by lines. * * PUBLIC: int v_up(SCR *, VICMD *); */ int v_up(SCR *sp, VICMD *vp) { recno_t lno; lno = F_ISSET(vp, VC_C1SET) ? vp->count : 1; if (vp->m_start.lno <= lno) { v_sof(sp, &vp->m_start); return (1); } vp->m_stop.lno = vp->m_start.lno - lno; vp->m_final = vp->m_stop; return (0); } /* * v_cr -- [count]^M * In a script window, send the line to the shell. * In a regular window, move down by lines. * * PUBLIC: int v_cr(SCR *, VICMD *); */ int v_cr(SCR *sp, VICMD *vp) { /* If it's a colon command-line edit window, it's an ex command. */ if (F_ISSET(sp, SC_COMEDIT)) return (v_ecl_exec(sp)); /* If it's a script window, exec the line. */ if (F_ISSET(sp, SC_SCRIPT)) return (sscr_exec(sp, vp->m_start.lno)); /* Otherwise, it's the same as v_down(). */ return (v_down(sp, vp)); } /* * v_down -- [count]^J, [count]^N, [count]j, [count]^M, [count]+ * Move down by lines. * * PUBLIC: int v_down(SCR *, VICMD *); */ int v_down(SCR *sp, VICMD *vp) { recno_t lno; lno = vp->m_start.lno + (F_ISSET(vp, VC_C1SET) ? vp->count : 1); if (!db_exist(sp, lno)) { v_eof(sp, &vp->m_start); return (1); } vp->m_stop.lno = lno; vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; return (0); } /* * v_hpageup -- [count]^U * Page up half screens. * * PUBLIC: int v_hpageup(SCR *, VICMD *); */ int v_hpageup(SCR *sp, VICMD *vp) { /* * Half screens always succeed unless already at SOF. * * !!! * Half screens set the scroll value, even if the command * ultimately failed, in historic vi. Probably a don't care. */ if (F_ISSET(vp, VC_C1SET)) sp->defscroll = vp->count; if (vs_sm_scroll(sp, &vp->m_stop, sp->defscroll, CNTRL_U)) return (1); vp->m_final = vp->m_stop; return (0); } /* * v_hpagedown -- [count]^D * Page down half screens. * * PUBLIC: int v_hpagedown(SCR *, VICMD *); */ int v_hpagedown(SCR *sp, VICMD *vp) { /* * Half screens always succeed unless already at EOF. * * !!! * Half screens set the scroll value, even if the command * ultimately failed, in historic vi. Probably a don't care. */ if (F_ISSET(vp, VC_C1SET)) sp->defscroll = vp->count; if (vs_sm_scroll(sp, &vp->m_stop, sp->defscroll, CNTRL_D)) return (1); vp->m_final = vp->m_stop; return (0); } /* * v_pagedown -- [count]^F * Page down full screens. * !!! * Historic vi did not move to the EOF if the screen couldn't move, i.e. * if EOF was already displayed on the screen. This implementation does - * move to EOF in that case, making ^F more like the the historic ^D. + * move to EOF in that case, making ^F more like the historic ^D. * * PUBLIC: int v_pagedown(SCR *, VICMD *); */ int v_pagedown(SCR *sp, VICMD *vp) { recno_t offset; /* * !!! * The calculation in IEEE Std 1003.2-1992 (POSIX) is: * * top_line = top_line + count * (window - 2); * * which was historically wrong. The correct one is: * * top_line = top_line + count * window - 2; * * i.e. the two line "overlap" was only subtracted once. Which * makes no sense, but then again, an overlap makes no sense for * any screen but the "next" one anyway. We do it the historical * way as there's no good reason to change it. * * If the screen has been split horizontally, use the smaller of * the current window size and the window option value. * * It possible for this calculation to be less than 1; move at * least one line. */ offset = (F_ISSET(vp, VC_C1SET) ? vp->count : 1) * (IS_SPLIT(sp) ? MIN(sp->t_maxrows, O_VAL(sp, O_WINDOW)) : O_VAL(sp, O_WINDOW)); offset = offset <= 2 ? 1 : offset - 2; if (vs_sm_scroll(sp, &vp->m_stop, offset, CNTRL_F)) return (1); vp->m_final = vp->m_stop; return (0); } /* * v_pageup -- [count]^B * Page up full screens. * * !!! * Historic vi did not move to the SOF if the screen couldn't move, i.e. * if SOF was already displayed on the screen. This implementation does - * move to SOF in that case, making ^B more like the the historic ^U. + * move to SOF in that case, making ^B more like the historic ^U. * * PUBLIC: int v_pageup(SCR *, VICMD *); */ int v_pageup(SCR *sp, VICMD *vp) { recno_t offset; /* * !!! * The calculation in IEEE Std 1003.2-1992 (POSIX) is: * * top_line = top_line - count * (window - 2); * * which was historically wrong. The correct one is: * * top_line = (top_line - count * window) + 2; * * A simpler expression is that, as with ^F, we scroll exactly: * * count * window - 2 * * lines. * * Bizarre. As with ^F, an overlap makes no sense for anything * but the first screen. We do it the historical way as there's * no good reason to change it. * * If the screen has been split horizontally, use the smaller of * the current window size and the window option value. * * It possible for this calculation to be less than 1; move at * least one line. */ offset = (F_ISSET(vp, VC_C1SET) ? vp->count : 1) * (IS_SPLIT(sp) ? MIN(sp->t_maxrows, O_VAL(sp, O_WINDOW)) : O_VAL(sp, O_WINDOW)); offset = offset <= 2 ? 1 : offset - 2; if (vs_sm_scroll(sp, &vp->m_stop, offset, CNTRL_B)) return (1); vp->m_final = vp->m_stop; return (0); } /* * v_lineup -- [count]^Y * Page up by lines. * * PUBLIC: int v_lineup(SCR *, VICMD *); */ int v_lineup(SCR *sp, VICMD *vp) { /* * The cursor moves down, staying with its original line, unless it * reaches the bottom of the screen. */ if (vs_sm_scroll(sp, &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count : 1, CNTRL_Y)) return (1); vp->m_final = vp->m_stop; return (0); } /* * v_linedown -- [count]^E * Page down by lines. * * PUBLIC: int v_linedown(SCR *, VICMD *); */ int v_linedown(SCR *sp, VICMD *vp) { /* * The cursor moves up, staying with its original line, unless it * reaches the top of the screen. */ if (vs_sm_scroll(sp, &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count : 1, CNTRL_E)) return (1); vp->m_final = vp->m_stop; return (0); } Index: head/contrib/nvi/vi/v_search.c =================================================================== --- head/contrib/nvi/vi/v_search.c (revision 365498) +++ head/contrib/nvi/vi/v_search.c (revision 365499) @@ -1,548 +1,544 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_search.c,v 10.31 2012/02/08 07:26:59 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static int v_exaddr(SCR *, VICMD *, dir_t); static int v_search(SCR *, VICMD *, CHAR_T *, size_t, u_int, dir_t); /* * v_srch -- [count]?RE[? offset] * Ex address search backward. * * PUBLIC: int v_searchb(SCR *, VICMD *); */ int v_searchb(SCR *sp, VICMD *vp) { return (v_exaddr(sp, vp, BACKWARD)); } /* * v_searchf -- [count]/RE[/ offset] * Ex address search forward. * * PUBLIC: int v_searchf(SCR *, VICMD *); */ int v_searchf(SCR *sp, VICMD *vp) { return (v_exaddr(sp, vp, FORWARD)); } /* * v_exaddr -- * Do a vi search (which is really an ex address). */ static int v_exaddr(SCR *sp, VICMD *vp, dir_t dir) { static EXCMDLIST fake = { L("search") }; EXCMD *cmdp; GS *gp; TEXT *tp; recno_t s_lno; size_t len, s_cno, tlen; int err, nb, type; char buf[20]; CHAR_T *cmd, *t; CHAR_T *w; size_t wlen; /* * !!! * If using the search command as a motion, any addressing components * are lost, i.e. y/ptrn/+2, when repeated, is the same as y/ptrn/. */ if (F_ISSET(vp, VC_ISDOT)) return (v_search(sp, vp, NULL, 0, SEARCH_PARSE | SEARCH_MSG | SEARCH_SET, dir)); /* Get the search pattern. */ if (v_tcmd(sp, vp, dir == BACKWARD ? CH_BSEARCH : CH_FSEARCH, TXT_BS | TXT_CR | TXT_ESCAPE | TXT_PROMPT | (O_ISSET(sp, O_SEARCHINCR) ? TXT_SEARCHINCR : 0))) return (1); tp = TAILQ_FIRST(sp->tiq); /* If the user backspaced over the prompt, do nothing. */ if (tp->term == TERM_BS) return (1); /* * If the user was doing an incremental search, then we've already * updated the cursor and moved to the right location. Return the * correct values, we're done. */ if (tp->term == TERM_SEARCH) { vp->m_stop.lno = sp->lno; vp->m_stop.cno = sp->cno; if (ISMOTION(vp)) return (v_correct(sp, vp, 0)); vp->m_final = vp->m_stop; return (0); } /* * If the user entered or , the length is * 1 and the right thing will happen, i.e. the prompt will be used * as a command character. * * Build a fake ex command structure. */ gp = sp->gp; gp->excmd.cp = tp->lb; gp->excmd.clen = tp->len; F_INIT(&gp->excmd, E_VISEARCH); /* * XXX * Warn if the search wraps. This is a pretty special case, but it's * nice feature that wasn't in the original implementations of ex/vi. * (It was added at some point to System V's version.) This message * is only displayed if there are no keys in the queue. The problem is * the command is going to succeed, and the message is informational, * not an error. If a macro displays it repeatedly, e.g., the pattern * only occurs once in the file and wrapscan is set, you lose big. For * example, if the macro does something like: * * :map K /pattern/^MjK * * Each search will display the message, but the following "/pattern/" * will immediately overwrite it, with strange results. The System V * vi displays the "wrapped" message multiple times, but because it's * overwritten each time, it's not as noticeable. As we don't discard * messages, it's a real problem for us. */ if (!KEYS_WAITING(sp)) F_SET(&gp->excmd, E_SEARCH_WMSG); /* Save the current line/column. */ s_lno = sp->lno; s_cno = sp->cno; /* * !!! * Historically, vi / and ? commands were full-blown ex addresses, * including ';' delimiters, trailing 's, multiple search * strings (separated by semi-colons) and, finally, full-blown z * commands after the / and ? search strings. (If the search was * being used as a motion, the trailing z command was ignored. * Also, we do some argument checking on the z command, to be sure * that it's not some other random command.) For multiple search * strings, leading 's at the second and subsequent strings * were eaten as well. This has some (unintended?) side-effects: * the command /ptrn/;3 is legal and results in moving to line 3. * I suppose you could use it to optionally move to line 3... * * !!! * Historically, if any part of the search command failed, the cursor * remained unmodified (even if ; was used). We have to play games * because the underlying ex parser thinks we're modifying the cursor * as we go, but I think we're compatible with historic practice. * * !!! * Historically, the command "/STRING/; " failed, apparently it * confused the parser. We're not that compatible. */ cmdp = &gp->excmd; if (ex_range(sp, cmdp, &err)) return (1); /* * Remember where any remaining command information is, and clean * up the fake ex command. */ cmd = cmdp->cp; len = cmdp->clen; gp->excmd.clen = 0; if (err) goto err2; /* Copy out the new cursor position and make sure it's okay. */ switch (cmdp->addrcnt) { case 1: vp->m_stop = cmdp->addr1; break; case 2: vp->m_stop = cmdp->addr2; break; } if (!db_exist(sp, vp->m_stop.lno)) { ex_badaddr(sp, &fake, vp->m_stop.lno == 0 ? A_ZERO : A_EOF, NUM_OK); goto err2; } /* * !!! * Historic practice is that a trailing 'z' was ignored if it was a * motion command. Should probably be an error, but not worth the * effort. */ if (ISMOTION(vp)) return (v_correct(sp, vp, F_ISSET(cmdp, E_DELTA))); /* * !!! * Historically, if it wasn't a motion command, a delta in the search * pattern turns it into a first nonblank movement. */ nb = F_ISSET(cmdp, E_DELTA); /* Check for the 'z' command. */ if (len != 0) { if (*cmd != 'z') goto err1; /* No blanks, just like the z command. */ for (t = cmd + 1, tlen = len - 1; tlen > 0; ++t, --tlen) if (!isdigit(*t)) break; if (tlen && (*t == '-' || *t == '.' || *t == '+' || *t == '^')) { ++t; --tlen; type = 1; } else type = 0; if (tlen) goto err1; /* The z command will do the nonblank for us. */ nb = 0; /* Default to z+. */ if (!type && v_event_push(sp, NULL, L("+"), 1, CH_NOMAP | CH_QUOTED)) return (1); /* Push the user's command. */ if (v_event_push(sp, NULL, cmd, len, CH_NOMAP | CH_QUOTED)) return (1); /* Push line number so get correct z display. */ tlen = snprintf(buf, sizeof(buf), "%lu", (u_long)vp->m_stop.lno); CHAR2INT(sp, buf, tlen, w, wlen); if (v_event_push(sp, NULL, w, wlen, CH_NOMAP | CH_QUOTED)) return (1); /* Don't refresh until after 'z' happens. */ F_SET(VIP(sp), VIP_S_REFRESH); } /* Non-motion commands move to the end of the range. */ vp->m_final = vp->m_stop; if (nb) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SETFNB); } return (0); err1: msgq(sp, M_ERR, "188|Characters after search string, line offset and/or z command"); err2: vp->m_final.lno = s_lno; vp->m_final.cno = s_cno; return (1); } /* * v_searchN -- N * Reverse last search. * * PUBLIC: int v_searchN(SCR *, VICMD *); */ int v_searchN(SCR *sp, VICMD *vp) { dir_t dir; switch (sp->searchdir) { case BACKWARD: dir = FORWARD; break; case FORWARD: dir = BACKWARD; break; default: dir = sp->searchdir; break; } return (v_search(sp, vp, NULL, 0, SEARCH_PARSE, dir)); } /* * v_searchn -- n * Repeat last search. * * PUBLIC: int v_searchn(SCR *, VICMD *); */ int v_searchn(SCR *sp, VICMD *vp) { return (v_search(sp, vp, NULL, 0, SEARCH_PARSE, sp->searchdir)); } /* * is_special -- * Test if the character is special in a basic RE. */ static int is_special(CHAR_T c) { /* * !!! * `*' and `$' are ordinary when appear at the beginning of a RE, * but it's safe to distinguish them from the ordinary characters. * The tilde is vi-specific, of course. */ return (STRCHR(L(".[*\\^$~"), c) && c); } /* * Rear delimiter for word search when the keyword ends in * (i.e., consists of) a non-word character. See v_searchw below. */ #define RE_NWSTOP L("([^[:alnum:]_]|$)") #define RE_NWSTOP_LEN (SIZE(RE_NWSTOP) - 1) /* * v_searchw -- [count]^A * Search for the word under the cursor. * * PUBLIC: int v_searchw(SCR *, VICMD *); */ int v_searchw(SCR *sp, VICMD *vp) { size_t blen, len; int rval; CHAR_T *bp, *p; /* An upper bound for the SIZE of the RE under construction. */ len = VIP(sp)->klen + MAX(RE_WSTART_LEN, 1) + MAX(RE_WSTOP_LEN, RE_NWSTOP_LEN); GET_SPACE_RETW(sp, bp, blen, len); p = bp; /* Only the first character can be non-word, see v_curword. */ if (inword(VIP(sp)->keyw[0])) { MEMCPY(p, RE_WSTART, RE_WSTART_LEN); p += RE_WSTART_LEN; } else if (is_special(VIP(sp)->keyw[0])) { MEMCPY(p, L("\\"), 1); p += 1; } MEMCPY(p, VIP(sp)->keyw, VIP(sp)->klen); p += VIP(sp)->klen; if (inword(p[-1])) { MEMCPY(p, RE_WSTOP, RE_WSTOP_LEN); p += RE_WSTOP_LEN; } else { /* * The keyword is a single non-word character. * We want it to stay the same when typing ^A several times * in a row, just the way the other cases behave. */ MEMCPY(p, RE_NWSTOP, RE_NWSTOP_LEN); p += RE_NWSTOP_LEN; } len = p - bp; rval = v_search(sp, vp, bp, len, SEARCH_SET, FORWARD); FREE_SPACEW(sp, bp, blen); return (rval); } /* * v_search -- * The search commands. */ static int v_search(SCR *sp, VICMD *vp, CHAR_T *ptrn, size_t plen, u_int flags, dir_t dir) { /* Display messages. */ LF_SET(SEARCH_MSG); /* If it's a motion search, offset past end-of-line is okay. */ if (ISMOTION(vp)) LF_SET(SEARCH_EOL); /* * XXX * Warn if the search wraps. See the comment above, in v_exaddr(). */ if (!KEYS_WAITING(sp)) LF_SET(SEARCH_WMSG); switch (dir) { case BACKWARD: if (b_search(sp, &vp->m_start, &vp->m_stop, ptrn, plen, NULL, flags)) return (1); break; case FORWARD: if (f_search(sp, &vp->m_start, &vp->m_stop, ptrn, plen, NULL, flags)) return (1); break; case NOTSET: msgq(sp, M_ERR, "189|No previous search pattern"); return (1); default: abort(); } /* Correct motion commands, otherwise, simply move to the location. */ if (ISMOTION(vp)) { if (v_correct(sp, vp, 0)) return(1); } else vp->m_final = vp->m_stop; return (0); } /* * v_correct -- * Handle command with a search as the motion. * * !!! * Historically, commands didn't affect the line searched to/from if the * motion command was a search and the final position was the start/end * of the line. There were some special cases and vi was not consistent; * it was fairly easy to confuse it. For example, given the two lines: * * abcdefghi * ABCDEFGHI * * placing the cursor on the 'A' and doing y?$ would so confuse it that 'h' * 'k' and put would no longer work correctly. In any case, we try to do * the right thing, but it's not going to exactly match historic practice. * * PUBLIC: int v_correct(SCR *, VICMD *, int); */ int v_correct(SCR *sp, VICMD *vp, int isdelta) { dir_t dir; MARK m; size_t len; /* * !!! * We may have wrapped if wrapscan was set, and we may have returned * to the position where the cursor started. Historic vi didn't cope * with this well. Yank wouldn't beep, but the first put after the * yank would move the cursor right one column (without adding any * text) and the second would put a copy of the current line. The * change and delete commands would beep, but would leave the cursor * on the colon command line. I believe that there are macros that * depend on delete, at least, failing. For now, commands that use * search as a motion component fail when the search returns to the * original cursor position. */ if (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno == vp->m_stop.cno) { msgq(sp, M_BERR, "190|Search wrapped to original position"); return (1); } /* * !!! * Searches become line mode operations if there was a delta specified * to the search pattern. */ if (isdelta) F_SET(vp, VM_LMODE); /* * If the motion is in the reverse direction, switch the start and * stop MARK's so that it's in a forward direction. (There's no * reason for this other than to make the tests below easier. The * code in vi.c:vi() would have done the switch.) Both forward * and backward motions can happen for any kind of search command * because of the wrapscan option. */ if (vp->m_start.lno > vp->m_stop.lno || (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno > vp->m_stop.cno)) { m = vp->m_start; vp->m_start = vp->m_stop; vp->m_stop = m; dir = BACKWARD; } else dir = FORWARD; /* * BACKWARD: * Delete and yank commands move to the end of the range. * Ignore others. * * FORWARD: * Delete and yank commands don't move. Ignore others. */ vp->m_final = vp->m_start; /* * !!! * Delta'd searches don't correct based on column positions. */ if (isdelta) return (0); /* * !!! * Backward searches starting at column 0, and forward searches ending * at column 0 are corrected to the last column of the previous line. * Otherwise, adjust the starting/ending point to the character before * the current one (this is safe because we know the search had to move * to succeed). * * Searches become line mode operations if they start at the first * nonblank and end at column 0 of another line. */ if (vp->m_start.lno < vp->m_stop.lno && vp->m_stop.cno == 0) { if (db_get(sp, --vp->m_stop.lno, DBG_FATAL, NULL, &len)) return (1); vp->m_stop.cno = len ? len - 1 : 0; len = 0; if (nonblank(sp, vp->m_start.lno, &len)) return (1); if (vp->m_start.cno <= len) F_SET(vp, VM_LMODE); } else --vp->m_stop.cno; return (0); } Index: head/contrib/nvi/vi/v_section.c =================================================================== --- head/contrib/nvi/vi/v_section.c (revision 365498) +++ head/contrib/nvi/vi/v_section.c (revision 365499) @@ -1,250 +1,246 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_section.c,v 10.10 2001/06/25 15:19:35 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * !!! * In historic vi, the section commands ignored empty lines, unlike the * paragraph commands, which was probably okay. However, they also moved * to the start of the last line when there where no more sections instead * of the end of the last line like the paragraph commands. I've changed * the latter behavior to match the paragraph commands. * * In historic vi, a section was defined as the first character(s) of the * line matching, which could be followed by anything. This implementation * follows that historic practice. * * !!! * The historic vi documentation (USD:15-10) claimed: * The section commands interpret a preceding count as a different * window size in which to redraw the screen at the new location, * and this window size is the base size for newly drawn windows * until another size is specified. This is very useful if you are * on a slow terminal ... * * I can't get the 4BSD vi to do this, it just beeps at me. For now, a * count to the section commands simply repeats the command. */ /* * v_sectionf -- [count]]] * Move forward count sections/functions. * * !!! * Using ]] as a motion command was a bit special, historically. It could * match } as well as the usual { and section values. If it matched a { or * a section, it did NOT include the matched line. If it matched a }, it * did include the line. No clue why. * * PUBLIC: int v_sectionf(SCR *, VICMD *); */ int v_sectionf(SCR *sp, VICMD *vp) { recno_t cnt, lno; size_t len; CHAR_T *p; char *list, *lp; /* Get the macro list. */ if ((list = O_STR(sp, O_SECTIONS)) == NULL) return (1); /* * !!! * If the starting cursor position is at or before any non-blank * characters in the line, i.e. the movement is cutting all of the * line's text, the buffer is in line mode. It's a lot easier to * check here, because we know that the end is going to be the start * or end of a line. */ if (ISMOTION(vp)) if (vp->m_start.cno == 0) F_SET(vp, VM_LMODE); else { vp->m_stop = vp->m_start; vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); if (vp->m_start.cno <= vp->m_stop.cno) F_SET(vp, VM_LMODE); } cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; for (lno = vp->m_start.lno; !db_get(sp, ++lno, 0, &p, &len);) { if (len == 0) continue; if (p[0] == '{' || (ISMOTION(vp) && p[0] == '}')) { if (!--cnt) { if (p[0] == '{') goto adjust1; goto adjust2; } continue; } /* * !!! * Historic documentation (USD:15-11, 4.2) said that formfeed * characters (^L) in the first column delimited sections. * The historic code mentions formfeed characters, but never * implements them. Seems reasonable, do it. */ if (p[0] == '\014') { if (!--cnt) goto adjust1; continue; } if (p[0] != '.' || len < 2) continue; for (lp = list; *lp != '\0'; lp += 2 * sizeof(*lp)) if (lp[0] == p[1] && ((lp[1] == ' ' && len == 2) || lp[1] == p[2]) && !--cnt) { /* * !!! * If not cutting this line, adjust to the end * of the previous one. Otherwise, position to * column 0. */ adjust1: if (ISMOTION(vp)) goto ret1; adjust2: vp->m_stop.lno = lno; vp->m_stop.cno = 0; goto ret2; } } /* If moving forward, reached EOF, check to see if we started there. */ if (vp->m_start.lno == lno - 1) { v_eof(sp, NULL); return (1); } ret1: if (db_get(sp, --lno, DBG_FATAL, NULL, &len)) return (1); vp->m_stop.lno = lno; vp->m_stop.cno = len ? len - 1 : 0; /* * Non-motion commands go to the end of the range. Delete and * yank stay at the start of the range. Ignore others. */ ret2: if (ISMOTION(vp)) { vp->m_final = vp->m_start; if (F_ISSET(vp, VM_LMODE)) vp->m_final.cno = 0; } else vp->m_final = vp->m_stop; return (0); } /* * v_sectionb -- [count][[ * Move backward count sections/functions. * * PUBLIC: int v_sectionb(SCR *, VICMD *); */ int v_sectionb(SCR *sp, VICMD *vp) { size_t len; recno_t cnt, lno; CHAR_T *p; char *list, *lp; /* An empty file or starting from line 1 is always illegal. */ if (vp->m_start.lno <= 1) { v_sof(sp, NULL); return (1); } /* Get the macro list. */ if ((list = O_STR(sp, O_SECTIONS)) == NULL) return (1); cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; for (lno = vp->m_start.lno; !db_get(sp, --lno, 0, &p, &len);) { if (len == 0) continue; if (p[0] == '{') { if (!--cnt) goto adjust1; continue; } /* * !!! * Historic documentation (USD:15-11, 4.2) said that formfeed * characters (^L) in the first column delimited sections. * The historic code mentions formfeed characters, but never * implements them. Seems reasonable, do it. */ if (p[0] == '\014') { if (!--cnt) goto adjust1; continue; } if (p[0] != '.' || len < 2) continue; for (lp = list; *lp != '\0'; lp += 2 * sizeof(*lp)) if (lp[0] == p[1] && ((lp[1] == ' ' && len == 2) || lp[1] == p[2]) && !--cnt) { adjust1: vp->m_stop.lno = lno; vp->m_stop.cno = 0; goto ret1; } } /* * If moving backward, reached SOF, which is a movement sink. * We already checked for starting there. */ vp->m_stop.lno = 1; vp->m_stop.cno = 0; /* * All commands move to the end of the range. * * !!! * Historic practice is the section cut was in line mode if it started * from column 0 and was in the backward direction. Otherwise, left * motion commands adjust the starting point to the character before * the current one. What makes this worse is that if it cut to line * mode it also went to the first non-. */ ret1: if (vp->m_start.cno == 0) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SETFNB); --vp->m_start.lno; F_SET(vp, VM_LMODE); } else --vp->m_start.cno; vp->m_final = vp->m_stop; return (0); } Index: head/contrib/nvi/vi/v_sentence.c =================================================================== --- head/contrib/nvi/vi/v_sentence.c (revision 365498) +++ head/contrib/nvi/vi/v_sentence.c (revision 365499) @@ -1,355 +1,351 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_sentence.c,v 10.9 2001/06/25 15:19:35 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * !!! * In historic vi, a sentence was delimited by a '.', '?' or '!' character * followed by TWO spaces or a newline. One or more empty lines was also * treated as a separate sentence. The Berkeley documentation for historical * vi states that any number of ')', ']', '"' and '\'' characters can be * between the delimiter character and the spaces or end of line, however, * the historical implementation did not handle additional '"' characters. * We follow the documentation here, not the implementation. * * Once again, historical vi didn't do sentence movements associated with * counts consistently, mostly in the presence of lines containing only * white-space characters. * * This implementation also permits a single tab to delimit sentences, and * treats lines containing only white-space characters as empty lines. * Finally, tabs are eaten (along with spaces) when skipping to the start * of the text following a "sentence". */ /* * v_sentencef -- [count]) * Move forward count sentences. * * PUBLIC: int v_sentencef(SCR *, VICMD *); */ int v_sentencef(SCR *sp, VICMD *vp) { enum { BLANK, NONE, PERIOD } state; VCS cs; size_t len; u_long cnt; cs.cs_lno = vp->m_start.lno; cs.cs_cno = vp->m_start.cno; if (cs_init(sp, &cs)) return (1); cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; /* * !!! * If in white-space, the next start of sentence counts as one. * This may not handle " . " correctly, but it's real unclear * what correctly means in that case. */ if (cs.cs_flags == CS_EMP || (cs.cs_flags == 0 && isblank(cs.cs_ch))) { if (cs_fblank(sp, &cs)) return (1); if (--cnt == 0) { if (vp->m_start.lno != cs.cs_lno || vp->m_start.cno != cs.cs_cno) goto okret; return (1); } } for (state = NONE;;) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) break; if (cs.cs_flags == CS_EOL) { if ((state == PERIOD || state == BLANK) && --cnt == 0) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == 0 && isblank(cs.cs_ch) && cs_fblank(sp, &cs)) return (1); goto okret; } state = NONE; continue; } if (cs.cs_flags == CS_EMP) { /* An EMP is two sentences. */ if (--cnt == 0) goto okret; if (cs_fblank(sp, &cs)) return (1); if (--cnt == 0) goto okret; state = NONE; continue; } switch (cs.cs_ch) { case '.': case '?': case '!': state = PERIOD; break; case ')': case ']': case '"': case '\'': if (state != PERIOD) state = NONE; break; case '\t': if (state == PERIOD) state = BLANK; /* FALLTHROUGH */ case ' ': if (state == PERIOD) { state = BLANK; break; } if (state == BLANK && --cnt == 0) { if (cs_fblank(sp, &cs)) return (1); goto okret; } /* FALLTHROUGH */ default: state = NONE; break; } } /* EOF is a movement sink, but it's an error not to have moved. */ if (vp->m_start.lno == cs.cs_lno && vp->m_start.cno == cs.cs_cno) { v_eof(sp, NULL); return (1); } okret: vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * !!! * Historic, uh, features, yeah, that's right, call 'em features. * If the starting and ending cursor positions are at the first * column in their lines, i.e. the movement is cutting entire lines, * the buffer is in line mode, and the ending position is the last * character of the previous line. Note check to make sure that * it's not within a single line. * * Non-motion commands move to the end of the range. Delete and * yank stay at the start. Ignore others. Adjust the end of the * range for motion commands. */ if (ISMOTION(vp)) { if (vp->m_start.cno == 0 && (cs.cs_flags != 0 || vp->m_stop.cno == 0)) { if (vp->m_start.lno < vp->m_stop.lno) { if (db_get(sp, --vp->m_stop.lno, DBG_FATAL, NULL, &len)) return (1); vp->m_stop.cno = len ? len - 1 : 0; } F_SET(vp, VM_LMODE); } else --vp->m_stop.cno; vp->m_final = vp->m_start; } else vp->m_final = vp->m_stop; return (0); } /* * v_sentenceb -- [count]( * Move backward count sentences. * * PUBLIC: int v_sentenceb(SCR *, VICMD *); */ int v_sentenceb(SCR *sp, VICMD *vp) { VCS cs; recno_t slno; size_t len, scno; u_long cnt; int last; /* * !!! * Historic vi permitted the user to hit SOF repeatedly. */ if (vp->m_start.lno == 1 && vp->m_start.cno == 0) return (0); cs.cs_lno = vp->m_start.lno; cs.cs_cno = vp->m_start.cno; if (cs_init(sp, &cs)) return (1); cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; /* * !!! * In empty lines, skip to the previous non-white-space character. * If in text, skip to the prevous white-space character. Believe * it or not, in the paragraph: * ab cd. * AB CD. * if the cursor is on the 'A' or 'B', ( moves to the 'a'. If it * is on the ' ', 'C' or 'D', it moves to the 'A'. Yes, Virginia, * Berkeley was once a major center of drug activity. */ if (cs.cs_flags == CS_EMP) { if (cs_bblank(sp, &cs)) return (1); for (;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags != CS_EOL) break; } } else if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) for (;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags != 0 || isblank(cs.cs_ch)) break; } for (last = 0;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags == CS_SOF) /* SOF is a movement sink. */ break; if (cs.cs_flags == CS_EOL) { last = 1; continue; } if (cs.cs_flags == CS_EMP) { if (--cnt == 0) goto ret; if (cs_bblank(sp, &cs)) return (1); last = 0; continue; } switch (cs.cs_ch) { case '.': case '?': case '!': if (!last || --cnt != 0) { last = 0; continue; } ret: slno = cs.cs_lno; scno = cs.cs_cno; /* * Move to the start of the sentence, skipping blanks * and special characters. */ do { if (cs_next(sp, &cs)) return (1); } while (!cs.cs_flags && (cs.cs_ch == ')' || cs.cs_ch == ']' || cs.cs_ch == '"' || cs.cs_ch == '\'')); if ((cs.cs_flags || isblank(cs.cs_ch)) && cs_fblank(sp, &cs)) return (1); /* * If it was ". xyz", with the cursor on the 'x', or * "end. ", with the cursor in the spaces, or the * beginning of a sentence preceded by an empty line, * we can end up where we started. Fix it. */ if (vp->m_start.lno != cs.cs_lno || - vp->m_start.cno != cs.cs_cno) + vp->m_start.cno > cs.cs_cno) goto okret; /* * Well, if an empty line preceded possible blanks * and the sentence, it could be a real sentence. */ for (;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags == CS_EOL) continue; if (cs.cs_flags == 0 && isblank(cs.cs_ch)) continue; break; } if (cs.cs_flags == CS_EMP) goto okret; /* But it wasn't; try again. */ ++cnt; cs.cs_lno = slno; cs.cs_cno = scno; last = 0; break; case '\t': last = 1; break; default: last = cs.cs_flags == CS_EOL || isblank(cs.cs_ch) || cs.cs_ch == ')' || cs.cs_ch == ']' || cs.cs_ch == '"' || cs.cs_ch == '\'' ? 1 : 0; } } okret: vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * !!! * If the starting and stopping cursor positions are at the first * columns in the line, i.e. the movement is cutting an entire line, * the buffer is in line mode, and the starting position is the last * character of the previous line. * * All commands move to the end of the range. Adjust the start of * the range for motion commands. */ if (ISMOTION(vp)) if (vp->m_start.cno == 0 && (cs.cs_flags != 0 || vp->m_stop.cno == 0)) { if (db_get(sp, --vp->m_start.lno, DBG_FATAL, NULL, &len)) return (1); vp->m_start.cno = len ? len - 1 : 0; F_SET(vp, VM_LMODE); } else --vp->m_start.cno; vp->m_final = vp->m_stop; return (0); } Index: head/contrib/nvi/vi/v_status.c =================================================================== --- head/contrib/nvi/vi/v_status.c (revision 365498) +++ head/contrib/nvi/vi/v_status.c (revision 365499) @@ -1,38 +1,34 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_status.c,v 10.10 2001/06/25 15:19:35 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_status -- ^G * Show the file status. * * PUBLIC: int v_status(SCR *, VICMD *); */ int v_status(SCR *sp, VICMD *vp) { (void)msgq_status(sp, vp->m_start.lno, MSTAT_SHOWLAST); return (0); } Index: head/contrib/nvi/vi/v_txt.c =================================================================== --- head/contrib/nvi/vi/v_txt.c (revision 365498) +++ head/contrib/nvi/vi/v_txt.c (revision 365499) @@ -1,2922 +1,2897 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_txt.c,v 11.5 2013/05/19 20:37:45 bentley Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static int txt_abbrev(SCR *, TEXT *, CHAR_T *, int, int *, int *); static void txt_ai_resolve(SCR *, TEXT *, int *); static TEXT *txt_backup(SCR *, TEXTH *, TEXT *, u_int32_t *); -static int txt_dent(SCR *, TEXT *, int); +static int txt_dent(SCR *, TEXT *, int, int); static int txt_emark(SCR *, TEXT *, size_t); static void txt_err(SCR *, TEXTH *); static int txt_fc(SCR *, TEXT *, int *); static int txt_fc_col(SCR *, int, ARGS **); static int txt_hex(SCR *, TEXT *); static int txt_insch(SCR *, TEXT *, CHAR_T *, u_int); static int txt_isrch(SCR *, VICMD *, TEXT *, u_int8_t *); static int txt_map_end(SCR *); static int txt_map_init(SCR *); static int txt_margin(SCR *, TEXT *, TEXT *, int *, u_int32_t); static void txt_nomorech(SCR *); static void txt_Rresolve(SCR *, TEXTH *, TEXT *, const size_t); static int txt_resolve(SCR *, TEXTH *, u_int32_t); static int txt_showmatch(SCR *, TEXT *); static void txt_unmap(SCR *, TEXT *, u_int32_t *); /* Cursor character (space is hard to track on the screen). */ #if defined(DEBUG) && 0 #undef CH_CURSOR #define CH_CURSOR '+' #endif /* * v_tcmd -- * Fill a buffer from the terminal for vi. * * PUBLIC: int v_tcmd(SCR *, VICMD *, ARG_CHAR_T, u_int); */ int v_tcmd(SCR *sp, VICMD *vp, ARG_CHAR_T prompt, u_int flags) { /* Normally, we end up where we started. */ vp->m_final.lno = sp->lno; vp->m_final.cno = sp->cno; /* Initialize the map. */ if (txt_map_init(sp)) return (1); /* Move to the last line. */ sp->lno = TMAP[0].lno; sp->cno = 0; /* Don't update the modeline for now. */ F_SET(sp, SC_TINPUT_INFO); /* Set the input flags. */ LF_SET(TXT_APPENDEOL | TXT_CR | TXT_ESCAPE | TXT_INFOLINE | TXT_MAPINPUT); if (O_ISSET(sp, O_ALTWERASE)) LF_SET(TXT_ALTWERASE); if (O_ISSET(sp, O_TTYWERASE)) LF_SET(TXT_TTYWERASE); /* Do the input thing. */ if (v_txt(sp, vp, NULL, NULL, 0, prompt, 0, 1, flags)) return (1); /* Reenable the modeline updates. */ F_CLR(sp, SC_TINPUT_INFO); /* Clean up the map. */ if (txt_map_end(sp)) return (1); if (IS_ONELINE(sp)) F_SET(sp, SC_SCR_REDRAW); /* XXX */ /* Set the cursor to the resulting position. */ sp->lno = vp->m_final.lno; sp->cno = vp->m_final.cno; return (0); } /* * txt_map_init * Initialize the screen map for colon command-line input. */ static int txt_map_init(SCR *sp) { SMAP *esmp; VI_PRIVATE *vip; vip = VIP(sp); if (!IS_ONELINE(sp)) { /* * Fake like the user is doing input on the last line of the * screen. This makes all of the scrolling work correctly, * and allows us the use of the vi text editing routines, not * to mention practically infinite length ex commands. * * Save the current location. */ vip->sv_tm_lno = TMAP->lno; vip->sv_tm_soff = TMAP->soff; vip->sv_tm_coff = TMAP->coff; vip->sv_t_maxrows = sp->t_maxrows; vip->sv_t_minrows = sp->t_minrows; vip->sv_t_rows = sp->t_rows; /* * If it's a small screen, TMAP may be small for the screen. * Fix it, filling in fake lines as we go. */ if (IS_SMALL(sp)) for (esmp = HMAP + (sp->t_maxrows - 1); TMAP < esmp; ++TMAP) { TMAP[1].lno = TMAP[0].lno + 1; TMAP[1].coff = HMAP->coff; TMAP[1].soff = 1; } /* Build the fake entry. */ TMAP[1].lno = TMAP[0].lno + 1; TMAP[1].soff = 1; TMAP[1].coff = 0; SMAP_FLUSH(&TMAP[1]); ++TMAP; /* Reset the screen information. */ sp->t_rows = sp->t_minrows = ++sp->t_maxrows; } return (0); } /* * txt_map_end * Reset the screen map for colon command-line input. */ static int txt_map_end(SCR *sp) { VI_PRIVATE *vip; size_t cnt; vip = VIP(sp); if (!IS_ONELINE(sp)) { /* Restore the screen information. */ sp->t_rows = vip->sv_t_rows; sp->t_minrows = vip->sv_t_minrows; sp->t_maxrows = vip->sv_t_maxrows; /* * If it's a small screen, TMAP may be wrong. Clear any * lines that might have been overwritten. */ if (IS_SMALL(sp)) { for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) { (void)sp->gp->scr_move(sp, cnt, 0); (void)sp->gp->scr_clrtoeol(sp); } TMAP = HMAP + (sp->t_rows - 1); } else --TMAP; /* * The map may be wrong if the user entered more than one * (logical) line. Fix it. If the user entered a whole * screen, this will be slow, but we probably don't care. */ if (!O_ISSET(sp, O_LEFTRIGHT)) while (vip->sv_tm_lno != TMAP->lno || vip->sv_tm_soff != TMAP->soff) if (vs_sm_1down(sp)) return (1); } /* * Invalidate the cursor and the line size cache, the line never * really existed. This fixes bugs where the user searches for * the last line on the screen + 1 and the refresh routine thinks * that's where we just were. */ VI_SCR_CFLUSH(vip); F_SET(vip, VIP_CUR_INVALID); return (0); } /* * If doing input mapping on the colon command line, may need to unmap * based on the command. */ #define UNMAP_TST \ FL_ISSET(ec_flags, EC_MAPINPUT) && LF_ISSET(TXT_INFOLINE) /* * Internally, we maintain tp->lno and tp->cno, externally, everyone uses * sp->lno and sp->cno. Make them consistent as necessary. */ #define UPDATE_POSITION(sp, tp) { \ (sp)->lno = (tp)->lno; \ (sp)->cno = (tp)->cno; \ } /* * v_txt -- * Vi text input. * * PUBLIC: int v_txt(SCR *, VICMD *, MARK *, * PUBLIC: const CHAR_T *, size_t, ARG_CHAR_T, recno_t, u_long, u_int32_t); */ int v_txt( SCR *sp, VICMD *vp, MARK *tm, /* To MARK. */ const CHAR_T *lp, /* Input line. */ size_t len, /* Input line length. */ ARG_CHAR_T prompt, /* Prompt to display. */ recno_t ai_line, /* Line number to use for autoindent count. */ u_long rcount, /* Replay count. */ u_int32_t flags) /* TXT_* flags. */ { EVENT ev, *evp = NULL; /* Current event. */ EVENT fc; /* File name completion event. */ GS *gp; TEXT *ntp, *tp; /* Input text structures. */ TEXT ait; /* Autoindent text structure. */ TEXT wmt = {{ 0 }}; /* Wrapmargin text structure. */ TEXTH *tiqh; VI_PRIVATE *vip; abb_t abb; /* State of abbreviation checks. */ carat_t carat; /* State of the "[^0]^D" sequences. */ quote_t quote; /* State of quotation. */ size_t owrite, insert; /* Temporary copies of TEXT fields. */ size_t margin; /* Wrapmargin value. */ size_t rcol; /* 0-N: insert offset in the replay buffer. */ size_t tcol; /* Temporary column. */ u_int32_t ec_flags; /* Input mapping flags. */ #define IS_RESTART 0x01 /* Reset the incremental search. */ #define IS_RUNNING 0x02 /* Incremental search turned on. */ u_int8_t is_flags; int abcnt, ab_turnoff; /* Abbreviation character count, switch. */ int filec_redraw; /* Redraw after the file completion routine. */ int hexcnt; /* Hex character count. */ int showmatch; /* Showmatch set on this character. */ int wm_set, wm_skip; /* Wrapmargin happened, blank skip flags. */ int max, tmp; int nochange; CHAR_T *p; gp = sp->gp; vip = VIP(sp); /* * Set the input flag, so tabs get displayed correctly * and everyone knows that the text buffer is in use. */ F_SET(sp, SC_TINPUT); /* * Get one TEXT structure with some initial buffer space, reusing * the last one if it's big enough. (All TEXT bookkeeping fields * default to 0 -- text_init() handles this.) If changing a line, * copy it into the TEXT buffer. */ tiqh = sp->tiq; if (!TAILQ_EMPTY(tiqh)) { tp = TAILQ_FIRST(tiqh); if (TAILQ_NEXT(tp, q) != NULL || tp->lb_len < (len + 32) * sizeof(CHAR_T)) { text_lfree(tiqh); goto newtp; } tp->ai = tp->insert = tp->offset = tp->owrite = 0; if (lp != NULL) { tp->len = len; BINC_RETW(sp, tp->lb, tp->lb_len, len); MEMMOVE(tp->lb, lp, len); } else tp->len = 0; } else { newtp: if ((tp = text_init(sp, lp, len, len + 32)) == NULL) return (1); TAILQ_INSERT_HEAD(tiqh, tp, q); } /* Set default termination condition. */ tp->term = TERM_OK; /* Set the starting line, column. */ tp->lno = sp->lno; tp->cno = sp->cno; /* * Set the insert and overwrite counts. If overwriting characters, * do insertion afterward. If not overwriting characters, assume * doing insertion. If change is to a mark, emphasize it with an * CH_ENDMARK character. */ if (len) { if (LF_ISSET(TXT_OVERWRITE)) { tp->owrite = (tm->cno - tp->cno) + 1; tp->insert = (len - tm->cno) - 1; } else tp->insert = len - tp->cno; if (LF_ISSET(TXT_EMARK) && txt_emark(sp, tp, tm->cno)) return (1); } /* * Many of the special cases in text input are to handle autoindent * support. Somebody decided that it would be a good idea if "^^D" * and "0^D" deleted all of the autoindented characters. In an editor * that takes single character input from the user, this beggars the * imagination. Note also, "^^D" resets the next lines' autoindent, * but "0^D" doesn't. * * We assume that autoindent only happens on empty lines, so insert * and overwrite will be zero. If doing autoindent, figure out how * much indentation we need and fill it in. Update input column and * screen cursor as necessary. */ if (LF_ISSET(TXT_AUTOINDENT) && ai_line != OOBLNO) { if (v_txt_auto(sp, ai_line, NULL, 0, tp)) return (1); tp->cno = tp->ai; } else { /* * The cc and S commands have a special feature -- leading * characters are handled as autoindent characters. * Beauty! */ if (LF_ISSET(TXT_AICHARS)) { tp->offset = 0; tp->ai = tp->cno; } else tp->offset = tp->cno; } /* If getting a command buffer from the user, there may be a prompt. */ if (LF_ISSET(TXT_PROMPT)) { tp->lb[tp->cno++] = prompt; ++tp->len; ++tp->offset; } /* * If appending after the end-of-line, add a space into the buffer * and move the cursor right. This space is inserted, i.e. pushed * along, and then deleted when the line is resolved. Assumes that * the cursor is already positioned at the end of the line. This * avoids the nastiness of having the cursor reside on a magical * column, i.e. a column that doesn't really exist. The only down * side is that we may wrap lines or scroll the screen before it's * strictly necessary. Not a big deal. */ if (LF_ISSET(TXT_APPENDEOL)) { tp->lb[tp->cno] = CH_CURSOR; ++tp->len; ++tp->insert; (void)vs_change(sp, tp->lno, LINE_RESET); } /* * Historic practice is that the wrapmargin value was a distance * from the RIGHT-HAND margin, not the left. It's more useful to * us as a distance from the left-hand margin, i.e. the same as * the wraplen value. The wrapmargin option is historic practice. * Nvi added the wraplen option so that it would be possible to * edit files with consistent margins without knowing the number of * columns in the window. * * XXX * Setting margin causes a significant performance hit. Normally * we don't update the screen if there are keys waiting, but we * have to if margin is set, otherwise the screen routines don't * know where the cursor is. * * !!! * Abbreviated keys were affected by the wrapmargin option in the * historic 4BSD vi. Mapped keys were usually, but sometimes not. * See the comment in vi/v_text():set_txt_std for more information. * * !!! * One more special case. If an inserted character causes * wrapmargin to split the line, the next user entered character is * discarded if it's a character. */ wm_set = wm_skip = 0; if (LF_ISSET(TXT_WRAPMARGIN)) if ((margin = O_VAL(sp, O_WRAPMARGIN)) != 0) margin = sp->cols - margin; else margin = O_VAL(sp, O_WRAPLEN); else margin = 0; /* Initialize abbreviation checks. */ abcnt = ab_turnoff = 0; abb = F_ISSET(gp, G_ABBREV) && LF_ISSET(TXT_MAPINPUT) ? AB_INWORD : AB_NOTSET; /* * Set up the dot command. Dot commands are done by saving the actual * characters and then reevaluating them so that things like wrapmargin * can change between the insert and the replay. * * !!! * Historically, vi did not remap or reabbreviate replayed input. (It * did beep at you if you changed an abbreviation and then replayed the * input. We're not that compatible.) We don't have to do anything to * avoid remapping, as we're not getting characters from the terminal * routines. Turn the abbreviation check off. * * XXX * It would be nice if we could swallow backspaces and such, but it's * not all that easy to do. What we can do is turn off the common * error messages during the replay. Otherwise, when the user enters * an illegal command, e.g., "Iab", * and then does a '.', they get a list of error messages after command * completion. */ rcol = 0; if (LF_ISSET(TXT_REPLAY)) { abb = AB_NOTSET; LF_CLR(TXT_RECORD); } /* Other text input mode setup. */ quote = Q_NOTSET; carat = C_NOTSET; nochange = 0; FL_INIT(is_flags, LF_ISSET(TXT_SEARCHINCR) ? IS_RESTART | IS_RUNNING : 0); filec_redraw = hexcnt = showmatch = 0; /* Initialize input flags. */ ec_flags = LF_ISSET(TXT_MAPINPUT) ? EC_MAPINPUT : 0; /* Refresh the screen. */ UPDATE_POSITION(sp, tp); if (vs_refresh(sp, 1)) return (1); /* If it's dot, just do it now. */ if (F_ISSET(vp, VC_ISDOT)) goto replay; /* Get an event. */ evp = &ev; next: if (v_event_get(sp, evp, 0, ec_flags)) return (1); /* * If file completion overwrote part of the screen and nothing else has * been displayed, clean up. We don't do this as part of the normal * message resolution because we know the user is on the colon command * line and there's no reason to enter explicit characters to continue. */ if (filec_redraw && !F_ISSET(sp, SC_SCR_EXWROTE)) { filec_redraw = 0; fc.e_event = E_REPAINT; fc.e_flno = vip->totalcount >= sp->rows ? 1 : sp->rows - vip->totalcount; fc.e_tlno = sp->rows; vip->linecount = vip->lcontinue = vip->totalcount = 0; (void)vs_repaint(sp, &fc); (void)vs_refresh(sp, 1); } /* Deal with all non-character events. */ switch (evp->e_event) { case E_CHARACTER: break; case E_ERR: case E_EOF: F_SET(sp, SC_EXIT_FORCE); return (1); case E_INTERRUPT: /* * !!! * Historically, exited the user from text input * mode or cancelled a colon command, and returned to command * mode. It also beeped the terminal, but that seems a bit * excessive. */ goto k_escape; case E_REPAINT: if (vs_repaint(sp, &ev)) return (1); goto next; case E_WRESIZE: /* interrupts the input mode. */ v_emsg(sp, NULL, VIM_WRESIZE); goto k_escape; default: v_event_err(sp, evp); goto k_escape; } /* * !!! * If the first character of the input is a nul, replay the previous * input. (Historically, it's okay to replay non-existent input.) * This was not documented as far as I know, and is a great test of vi * clones. */ if (LF_ISSET(TXT_RECORD) && rcol == 0 && evp->e_c == '\0') { if (vip->rep == NULL) goto done; abb = AB_NOTSET; LF_CLR(TXT_RECORD); LF_SET(TXT_REPLAY); goto replay; } /* * File name completion and colon command-line editing. We don't * have enough meta characters, so we expect people to overload * them. If the two characters are the same, then we do file name * completion if the cursor is past the first column, and do colon * command-line editing if it's not. */ if (quote == Q_NOTSET) { int L__cedit, L__filec; L__cedit = L__filec = 0; if (LF_ISSET(TXT_CEDIT) && O_STR(sp, O_CEDIT) != NULL && O_STR(sp, O_CEDIT)[0] == evp->e_c) L__cedit = 1; if (LF_ISSET(TXT_FILEC) && O_STR(sp, O_FILEC) != NULL && O_STR(sp, O_FILEC)[0] == evp->e_c) L__filec = 1; if (L__cedit == 1 && (L__filec == 0 || tp->cno == tp->offset)) { tp->term = TERM_CEDIT; goto k_escape; } if (L__filec == 1) { if (txt_fc(sp, tp, &filec_redraw)) goto err; goto resolve; } } /* Abbreviation overflow check. See comment in txt_abbrev(). */ #define MAX_ABBREVIATION_EXPANSION 256 if (F_ISSET(&evp->e_ch, CH_ABBREVIATED)) { if (++abcnt > MAX_ABBREVIATION_EXPANSION) { if (v_event_flush(sp, CH_ABBREVIATED)) msgq(sp, M_ERR, "191|Abbreviation exceeded expansion limit: characters discarded"); abcnt = 0; if (LF_ISSET(TXT_REPLAY)) goto done; goto resolve; } } else abcnt = 0; /* Check to see if the character fits into the replay buffers. */ if (LF_ISSET(TXT_RECORD)) { BINC_GOTO(sp, EVENT, vip->rep, vip->rep_len, (rcol + 1) * sizeof(EVENT)); vip->rep[rcol++] = *evp; } replay: if (LF_ISSET(TXT_REPLAY)) { if (rcol == vip->rep_cnt) goto k_escape; evp = vip->rep + rcol++; } /* Wrapmargin check for leading space. */ if (wm_skip) { wm_skip = 0; if (evp->e_c == ' ') goto resolve; } /* If quoted by someone else, simply insert the character. */ if (F_ISSET(&evp->e_ch, CH_QUOTED)) goto insq_ch; /* * !!! - * If this character was quoted by a K_VLNEXT or a backslash, replace - * the placeholder (a carat or a backslash) with the new character. - * If it was quoted by a K_VLNEXT, we've already adjusted the cursor - * because it has to appear on top of the placeholder character. If - * it was quoted by a backslash, adjust the cursor now, the cursor - * doesn't appear on top of it. Historic practice in both cases. + * If this character was quoted by a K_VLNEXT, replace the placeholder + * (a carat) with the new character. We've already adjusted the cursor + * because it has to appear on top of the placeholder character. + * Historic practice. * * Skip tests for abbreviations; ":ab xa XA" followed by "ixa^V" * doesn't perform an abbreviation. Special case, ^V^J (not ^V^M) is * the same as ^J, historically. */ - if (quote == Q_BTHIS || quote == Q_VTHIS) { + if (quote == Q_VTHIS) { FL_CLR(ec_flags, EC_QUOTED); if (LF_ISSET(TXT_MAPINPUT)) FL_SET(ec_flags, EC_MAPINPUT); - if (quote == Q_BTHIS && - (evp->e_value == K_VERASE || evp->e_value == K_VKILL)) { + if (evp->e_value != K_NL) { quote = Q_NOTSET; - --tp->cno; - ++tp->owrite; goto insl_ch; } - if (quote == Q_VTHIS && evp->e_value != K_NL) { - quote = Q_NOTSET; - goto insl_ch; - } quote = Q_NOTSET; } /* * !!! * Translate "[isxdigit()]*" to a character with a hex value: * this test delimits the value by any non-hex character. Offset by * one, we use 0 to mean that we've found . */ if (hexcnt > 1 && !ISXDIGIT(evp->e_c)) { hexcnt = 0; if (txt_hex(sp, tp)) goto err; } switch (evp->e_value) { case K_CR: /* Carriage return. */ case K_NL: /* New line. */ /* Return in script windows and the command line. */ k_cr: if (LF_ISSET(TXT_CR)) { /* * If this was a map, we may have not displayed * the line. Display it, just in case. * * If a script window and not the colon line, * push a so it gets executed. */ if (LF_ISSET(TXT_INFOLINE)) { if (vs_change(sp, tp->lno, LINE_RESET)) goto err; } else if (F_ISSET(sp, SC_SCRIPT)) (void)v_event_push(sp, NULL, L("\r"), 1, CH_NOMAP); /* Set term condition: if empty. */ if (tp->cno <= tp->offset) tp->term = TERM_CR; /* * Set term condition: if searching incrementally and * the user entered a pattern, return a completed * search, regardless if the entire pattern was found. */ if (FL_ISSET(is_flags, IS_RUNNING) && tp->cno >= tp->offset + 1) tp->term = TERM_SEARCH; goto k_escape; } #define LINE_RESOLVE { \ /* \ * Handle abbreviations. If there was one, discard the \ * replay characters. \ */ \ if (abb == AB_INWORD && \ !LF_ISSET(TXT_REPLAY) && F_ISSET(gp, G_ABBREV)) { \ if (txt_abbrev(sp, tp, &evp->e_c, \ LF_ISSET(TXT_INFOLINE), &tmp, \ &ab_turnoff)) \ goto err; \ if (tmp) { \ if (LF_ISSET(TXT_RECORD)) \ rcol -= tmp + 1; \ goto resolve; \ } \ } \ if (abb != AB_NOTSET) \ abb = AB_NOTWORD; \ if (UNMAP_TST) \ txt_unmap(sp, tp, &ec_flags); \ /* \ * Delete any appended cursor. It's possible to get in \ * situations where TXT_APPENDEOL is set but tp->insert \ * is 0 when using the R command and all the characters \ * are tp->owrite characters. \ */ \ if (LF_ISSET(TXT_APPENDEOL) && tp->insert > 0) { \ --tp->len; \ --tp->insert; \ } \ } LINE_RESOLVE; /* * Save the current line information for restoration in * txt_backup(), and set the line final length. */ tp->sv_len = tp->len; tp->sv_cno = tp->cno; tp->len = tp->cno; /* Update the old line. */ if (vs_change(sp, tp->lno, LINE_RESET)) goto err; /* * Historic practice, when the autoindent edit option was set, * was to delete characters following the inserted * newline. This affected the 'R', 'c', and 's' commands; 'c' * and 's' retained the insert characters only, 'R' moved the * overwrite and insert characters into the next TEXT structure. * We keep track of the number of characters erased for the 'R' * command so that the final resolution of the line is correct. */ tp->R_erase = 0; owrite = tp->owrite; insert = tp->insert; if (LF_ISSET(TXT_REPLACE) && owrite != 0) { for (p = tp->lb + tp->cno; owrite > 0 && isblank(*p); ++p, --owrite, ++tp->R_erase); if (owrite == 0) for (; insert > 0 && isblank(*p); ++p, ++tp->R_erase, --insert); } else { p = tp->lb + tp->cno + owrite; if (O_ISSET(sp, O_AUTOINDENT)) for (; insert > 0 && isblank(*p); ++p, --insert); owrite = 0; } /* * !!! * Create a new line and insert the new TEXT into the queue. * DON'T insert until the old line has been updated, or the * inserted line count in line.c:db_get() will be wrong. */ if ((ntp = text_init(sp, p, insert + owrite, insert + owrite + 32)) == NULL) goto err; TAILQ_INSERT_TAIL(sp->tiq, ntp, q); /* Set up bookkeeping for the new line. */ ntp->insert = insert; ntp->owrite = owrite; ntp->lno = tp->lno + 1; /* * Reset the autoindent line value. 0^D keeps the autoindent * line from changing, ^D changes the level, even if there were * no characters in the old line. Note, if using the current * tp structure, use the cursor as the length, the autoindent * characters may have been erased. */ if (LF_ISSET(TXT_AUTOINDENT)) { if (nochange) { nochange = 0; if (v_txt_auto(sp, OOBLNO, &ait, ait.ai, ntp)) goto err; FREE_SPACEW(sp, ait.lb, ait.lb_len); } else if (v_txt_auto(sp, OOBLNO, tp, tp->cno, ntp)) goto err; carat = C_NOTSET; } /* Reset the cursor. */ ntp->cno = ntp->ai; /* * If we're here because wrapmargin was set and we've broken a * line, there may be additional information (i.e. the start of * a line) in the wmt structure. */ if (wm_set) { if (wmt.offset != 0 || wmt.owrite != 0 || wmt.insert != 0) { #define WMTSPACE wmt.offset + wmt.owrite + wmt.insert BINC_GOTOW(sp, ntp->lb, ntp->lb_len, ntp->len + WMTSPACE + 32); MEMMOVE(ntp->lb + ntp->cno, wmt.lb, WMTSPACE); ntp->len += WMTSPACE; ntp->cno += wmt.offset; ntp->owrite = wmt.owrite; ntp->insert = wmt.insert; } wm_set = 0; } /* New lines are TXT_APPENDEOL. */ if (ntp->owrite == 0 && ntp->insert == 0) { BINC_GOTOW(sp, ntp->lb, ntp->lb_len, ntp->len + 1); LF_SET(TXT_APPENDEOL); ntp->lb[ntp->cno] = CH_CURSOR; ++ntp->insert; ++ntp->len; } /* Swap old and new TEXT's, and update the new line. */ tp = ntp; if (vs_change(sp, tp->lno, LINE_INSERT)) goto err; goto resolve; case K_ESCAPE: /* Escape. */ if (!LF_ISSET(TXT_ESCAPE)) goto ins_ch; /* If we have a count, start replaying the input. */ if (rcount > 1) { --rcount; vip->rep_cnt = rcol; rcol = 0; abb = AB_NOTSET; LF_CLR(TXT_RECORD); LF_SET(TXT_REPLAY); /* * Some commands (e.g. 'o') need a for each * repetition. */ if (LF_ISSET(TXT_ADDNEWLINE)) goto k_cr; /* * The R command turns into the 'a' command after the * first repetition. */ if (LF_ISSET(TXT_REPLACE)) { tp->insert = tp->owrite; tp->owrite = 0; LF_CLR(TXT_REPLACE); } goto replay; } /* Set term condition: if empty. */ if (tp->cno <= tp->offset) tp->term = TERM_ESC; /* * Set term condition: if searching incrementally and the user * entered a pattern, return a completed search, regardless if * the entire pattern was found. */ if (FL_ISSET(is_flags, IS_RUNNING) && tp->cno >= tp->offset + 1) tp->term = TERM_SEARCH; k_escape: LINE_RESOLVE; /* * Clean up for the 'R' command, restoring overwrite * characters, and making them into insert characters. */ if (LF_ISSET(TXT_REPLACE)) txt_Rresolve(sp, sp->tiq, tp, len); /* * If there are any overwrite characters, copy down * any insert characters, and decrement the length. */ if (tp->owrite) { if (tp->insert) MEMMOVE(tp->lb + tp->cno, tp->lb + tp->cno + tp->owrite, tp->insert); tp->len -= tp->owrite; } /* * Optionally resolve the lines into the file. If not * resolving the lines into the file, end the line with * a nul. If the line is empty, then set the length to * 0, the termination condition has already been set. * * XXX * This is wrong, should pass back a length. */ if (LF_ISSET(TXT_RESOLVE)) { if (txt_resolve(sp, sp->tiq, flags)) goto err; } else { BINC_GOTOW(sp, tp->lb, tp->lb_len, tp->len + 1); tp->lb[tp->len] = '\0'; } /* * Set the return cursor position to rest on the last * inserted character. */ if (tp->cno != 0) --tp->cno; /* Update the last line. */ if (vs_change(sp, tp->lno, LINE_RESET)) return (1); goto done; case K_CARAT: /* Delete autoindent chars. */ if (tp->cno <= tp->ai && LF_ISSET(TXT_AUTOINDENT)) carat = C_CARATSET; goto ins_ch; case K_ZERO: /* Delete autoindent chars. */ if (tp->cno <= tp->ai && LF_ISSET(TXT_AUTOINDENT)) carat = C_ZEROSET; goto ins_ch; case K_CNTRLD: /* Delete autoindent char. */ /* * If in the first column or no characters to erase, ignore * the ^D (this matches historic practice). If not doing * autoindent or already inserted non-ai characters, it's a * literal. The latter test is done in the switch, as the * CARAT forms are N + 1, not N. */ if (!LF_ISSET(TXT_AUTOINDENT)) goto ins_ch; if (tp->cno == 0) goto resolve; switch (carat) { case C_CARATSET: /* ^^D */ if (tp->ai == 0 || tp->cno > tp->ai + tp->offset + 1) goto ins_ch; /* Save the ai string for later. */ ait.lb = NULL; ait.lb_len = 0; BINC_GOTOW(sp, ait.lb, ait.lb_len, tp->ai); MEMMOVE(ait.lb, tp->lb, tp->ai); ait.ai = ait.len = tp->ai; carat = C_NOTSET; nochange = 1; goto leftmargin; case C_ZEROSET: /* 0^D */ if (tp->ai == 0 || tp->cno > tp->ai + tp->offset + 1) goto ins_ch; carat = C_NOTSET; leftmargin: tp->lb[tp->cno - 1] = ' '; tp->owrite += tp->cno - tp->offset; tp->ai = 0; tp->cno = tp->offset; break; case C_NOTSET: /* ^D */ if (tp->ai == 0 || tp->cno > tp->ai + tp->offset) goto ins_ch; - (void)txt_dent(sp, tp, 0); + (void)txt_dent(sp, tp, O_SHIFTWIDTH, 0); break; default: abort(); } break; case K_VERASE: /* Erase the last character. */ /* If can erase over the prompt, return. */ if (tp->cno <= tp->offset && LF_ISSET(TXT_BS)) { tp->term = TERM_BS; goto done; } /* * If at the beginning of the line, try and drop back to a * previously inserted line. */ if (tp->cno == 0) { if ((ntp = txt_backup(sp, sp->tiq, tp, &flags)) == NULL) goto err; tp = ntp; break; } /* If nothing to erase, bell the user. */ if (tp->cno <= tp->offset) { if (!LF_ISSET(TXT_REPLAY)) txt_nomorech(sp); break; } /* Drop back one character. */ --tp->cno; /* * Historically, vi didn't replace the erased characters with * s, presumably because it's easier to fix a minor * typing mistake and continue on if the previous letters are * already there. This is a problem for incremental searching, * because the user can no longer tell where they are in the * colon command line because the cursor is at the last search * point in the screen. So, if incrementally searching, erase * the erased characters from the screen. */ if (FL_ISSET(is_flags, IS_RUNNING)) tp->lb[tp->cno] = ' '; /* * Increment overwrite, decrement ai if deleted. * * !!! * Historic vi did not permit users to use erase characters * to delete autoindent characters. We do. Eat hot death, * POSIX. */ ++tp->owrite; if (tp->cno < tp->ai) --tp->ai; /* Reset if we deleted an incremental search character. */ if (FL_ISSET(is_flags, IS_RUNNING)) FL_SET(is_flags, IS_RESTART); break; case K_VWERASE: /* Skip back one word. */ /* * If at the beginning of the line, try and drop back to a * previously inserted line. */ if (tp->cno == 0) { if ((ntp = txt_backup(sp, sp->tiq, tp, &flags)) == NULL) goto err; tp = ntp; } /* * If at offset, nothing to erase so bell the user. */ if (tp->cno <= tp->offset) { if (!LF_ISSET(TXT_REPLAY)) txt_nomorech(sp); break; } /* * The first werase goes back to any autoindent column and the * second werase goes back to the offset. * * !!! * Historic vi did not permit users to use erase characters to * delete autoindent characters. */ if (tp->ai && tp->cno > tp->ai) max = tp->ai; else { tp->ai = 0; max = tp->offset; } /* Skip over trailing space characters. */ while (tp->cno > max && ISBLANK(tp->lb[tp->cno - 1])) { --tp->cno; ++tp->owrite; } if (tp->cno == max) break; /* * There are three types of word erase found on UNIX systems. * They can be identified by how the string /a/b/c is treated * -- as 1, 3, or 6 words. Historic vi had two classes of * characters, and strings were delimited by them and * 's, so, 6 words. The historic tty interface used * 's to delimit strings, so, 1 word. The algorithm * offered in the 4.4BSD tty interface (as stty altwerase) * treats it as 3 words -- there are two classes of * characters, and strings are delimited by them and * 's. The difference is that the type of the first * erased character erased is ignored, which is exactly right * when erasing pathname components. The edit options * TXT_ALTWERASE and TXT_TTYWERASE specify the 4.4BSD tty * interface and the historic tty driver behavior, * respectively, and the default is the same as the historic * vi behavior. * * Overwrite erased characters if doing incremental search; * see comment above. */ if (LF_ISSET(TXT_TTYWERASE)) while (tp->cno > max) { if (ISBLANK(tp->lb[tp->cno - 1])) break; --tp->cno; ++tp->owrite; if (FL_ISSET(is_flags, IS_RUNNING)) tp->lb[tp->cno] = ' '; } else { if (LF_ISSET(TXT_ALTWERASE)) { --tp->cno; ++tp->owrite; if (FL_ISSET(is_flags, IS_RUNNING)) tp->lb[tp->cno] = ' '; } if (tp->cno > max) tmp = inword(tp->lb[tp->cno - 1]); while (tp->cno > max) { if (tmp != inword(tp->lb[tp->cno - 1]) || ISBLANK(tp->lb[tp->cno - 1])) break; --tp->cno; ++tp->owrite; if (FL_ISSET(is_flags, IS_RUNNING)) tp->lb[tp->cno] = ' '; } } /* Reset if we deleted an incremental search character. */ if (FL_ISSET(is_flags, IS_RUNNING)) FL_SET(is_flags, IS_RESTART); break; case K_VKILL: /* Restart this line. */ /* * !!! * If at the beginning of the line, try and drop back to a * previously inserted line. Historic vi did not permit * users to go back to previous lines. */ if (tp->cno == 0) { if ((ntp = txt_backup(sp, sp->tiq, tp, &flags)) == NULL) goto err; tp = ntp; } /* If at offset, nothing to erase so bell the user. */ if (tp->cno <= tp->offset) { if (!LF_ISSET(TXT_REPLAY)) txt_nomorech(sp); break; } /* * First kill goes back to any autoindent and second kill goes * back to the offset. * * !!! * Historic vi did not permit users to use erase characters to * delete autoindent characters. */ if (tp->ai && tp->cno > tp->ai) max = tp->ai; else { tp->ai = 0; max = tp->offset; } tp->owrite += tp->cno - max; /* * Overwrite erased characters if doing incremental search; * see comment above. */ if (FL_ISSET(is_flags, IS_RUNNING)) do { tp->lb[--tp->cno] = ' '; } while (tp->cno > max); else tp->cno = max; /* Reset if we deleted an incremental search character. */ if (FL_ISSET(is_flags, IS_RUNNING)) FL_SET(is_flags, IS_RESTART); break; case K_CNTRLT: /* Add autoindent characters. */ if (!LF_ISSET(TXT_CNTRLT)) goto ins_ch; - if (txt_dent(sp, tp, 1)) + if (txt_dent(sp, tp, O_SHIFTWIDTH, 1)) goto err; goto ebuf_chk; - case K_BACKSLASH: /* Quote next erase/kill. */ - /* - * !!! - * Historic vi tried to make abbreviations after a backslash - * escape work. If you did ":ab x y", and inserted "x\^H", - * (assuming the erase character was ^H) you got "x^H", and - * no abbreviation was done. If you inserted "x\z", however, - * it tried to back up and do the abbreviation, i.e. replace - * 'x' with 'y'. The problem was it got it wrong, and you - * ended up with "zy\". - * - * This is really hard to do (you have to remember the - * word/non-word state, for example), and doesn't make any - * sense to me. Both backslash and the characters it - * (usually) escapes will individually trigger the - * abbreviation, so I don't see why the combination of them - * wouldn't. I don't expect to get caught on this one, - * particularly since it never worked right, but I've been - * wrong before. - * - * Do the tests for abbreviations, so ":ab xa XA", - * "ixa\" performs the abbreviation. - */ - quote = Q_BNEXT; - goto insq_ch; case K_VLNEXT: /* Quote next character. */ evp->e_c = '^'; quote = Q_VNEXT; /* * Turn on the quote flag so that the underlying routines * quote the next character where it's possible. Turn off * the input mapbiting flag so that we don't remap the next * character. */ FL_SET(ec_flags, EC_QUOTED); FL_CLR(ec_flags, EC_MAPINPUT); /* * !!! * Skip the tests for abbreviations, so ":ab xa XA", * "ixa^V" doesn't perform the abbreviation. */ goto insl_ch; case K_HEXCHAR: hexcnt = 1; goto insq_ch; + case K_TAB: + if (sp->showmode != SM_COMMAND && quote != Q_VTHIS && + O_ISSET(sp, O_EXPANDTAB)) { + if (txt_dent(sp, tp, O_TABSTOP, 1)) + goto err; + goto ebuf_chk; + } + goto insq_ch; default: /* Insert the character. */ if (LF_ISSET(TXT_SHOWMATCH)) { CHAR_T *match_chars, *cp; match_chars = VIP(sp)->mcs; cp = STRCHR(match_chars, evp->e_c); if (cp != NULL && (cp - match_chars) & 1) showmatch = 1; } ins_ch: /* * Historically, vi eliminated nul's out of hand. If the * beautify option was set, it also deleted any unknown * ASCII value less than space (040) and the del character * (0177), except for tabs. Unknown is a key word here. * Most vi documentation claims that it deleted everything * but , and , as that's what the original * 4BSD documentation said. This is obviously wrong, * however, as would be included in that list. What * we do is eliminate any unquoted, iscntrl() character that * wasn't a replay and wasn't handled specially, except * or . */ if (LF_ISSET(TXT_BEAUTIFY) && ISCNTRL(evp->e_c) && evp->e_value != K_FORMFEED && evp->e_value != K_TAB) { msgq(sp, M_BERR, "192|Illegal character; quote to enter"); if (LF_ISSET(TXT_REPLAY)) goto done; break; } insq_ch: /* * If entering a non-word character after a word, check for * abbreviations. If there was one, discard replay characters. * If entering a blank character, check for unmap commands, * as well. */ if (!inword(evp->e_c)) { if (abb == AB_INWORD && !LF_ISSET(TXT_REPLAY) && F_ISSET(gp, G_ABBREV)) { if (txt_abbrev(sp, tp, &evp->e_c, LF_ISSET(TXT_INFOLINE), &tmp, &ab_turnoff)) goto err; if (tmp) { if (LF_ISSET(TXT_RECORD)) rcol -= tmp + 1; goto resolve; } } if (isblank(evp->e_c) && UNMAP_TST) txt_unmap(sp, tp, &ec_flags); } if (abb != AB_NOTSET) abb = inword(evp->e_c) ? AB_INWORD : AB_NOTWORD; insl_ch: if (txt_insch(sp, tp, &evp->e_c, flags)) goto err; /* * If we're using K_VLNEXT to quote the next character, then * we want the cursor to position itself on the ^ placeholder * we're displaying, to match historic practice. */ if (quote == Q_VNEXT) { --tp->cno; ++tp->owrite; } /* * !!! * Translate "[isxdigit()]*" to a character with * a hex value: this test delimits the value by the max * number of hex bytes. Offset by one, we use 0 to mean * that we've found . */ if (hexcnt != 0 && hexcnt++ == 3) { hexcnt = 0; if (txt_hex(sp, tp)) goto err; } /* * Check to see if we've crossed the margin. * * !!! * In the historic vi, the wrapmargin value was figured out * using the display widths of the characters, i.e. * characters were counted as two characters if the list edit * option is set, but as the tabstop edit option number of * characters otherwise. That's what the vs_column() function * gives us, so we use it. */ if (margin != 0) { if (vs_column(sp, &tcol)) goto err; if (tcol >= margin) { if (txt_margin(sp, tp, &wmt, &tmp, flags)) goto err; if (tmp) { if (isblank(evp->e_c)) wm_skip = 1; wm_set = 1; goto k_cr; } } } /* * If we've reached the end of the buffer, then we need to * switch into insert mode. This happens when there's a * change to a mark and the user puts in more characters than * the length of the motion. */ ebuf_chk: if (tp->cno >= tp->len) { BINC_GOTOW(sp, tp->lb, tp->lb_len, tp->len + 1); LF_SET(TXT_APPENDEOL); tp->lb[tp->cno] = CH_CURSOR; ++tp->insert; ++tp->len; } /* Step the quote state forward. */ - if (quote != Q_NOTSET) { - if (quote == Q_BNEXT) - quote = Q_BTHIS; - if (quote == Q_VNEXT) - quote = Q_VTHIS; - } + if (quote == Q_VNEXT) + quote = Q_VTHIS; break; } #ifdef DEBUG if (tp->cno + tp->insert + tp->owrite != tp->len) { msgq(sp, M_ERR, "len %zu != cno: %zu ai: %zu insert %zu overwrite %zu", tp->len, tp->cno, tp->ai, tp->insert, tp->owrite); if (LF_ISSET(TXT_REPLAY)) goto done; tp->len = tp->cno + tp->insert + tp->owrite; } #endif resolve:/* * 1: If we don't need to know where the cursor really is and we're * replaying text, keep going. */ if (margin == 0 && LF_ISSET(TXT_REPLAY)) goto replay; /* * 2: Reset the line. Don't bother unless we're about to wait on * a character or we need to know where the cursor really is. * We have to do this before showing matching characters so the * user can see what they're matching. */ if ((margin != 0 || !KEYS_WAITING(sp)) && vs_change(sp, tp->lno, LINE_RESET)) return (1); /* * 3: If there aren't keys waiting, display the matching character. * We have to do this before resolving any messages, otherwise * the error message from a missing match won't appear correctly. */ if (showmatch) { if (!KEYS_WAITING(sp) && txt_showmatch(sp, tp)) return (1); showmatch = 0; } /* * 4: If there have been messages and we're not editing on the colon * command line or doing file name completion, resolve them. */ if ((vip->totalcount != 0 || F_ISSET(gp, G_BELLSCHED)) && !F_ISSET(sp, SC_TINPUT_INFO) && !filec_redraw && vs_resolve(sp, NULL, 0)) return (1); /* * 5: Refresh the screen if we're about to wait on a character or we * need to know where the cursor really is. */ if (margin != 0 || !KEYS_WAITING(sp)) { UPDATE_POSITION(sp, tp); if (vs_refresh(sp, margin != 0)) return (1); } /* 6: Proceed with the incremental search. */ if (FL_ISSET(is_flags, IS_RUNNING) && txt_isrch(sp, vp, tp, &is_flags)) return (1); /* 7: Next character... */ if (LF_ISSET(TXT_REPLAY)) goto replay; goto next; done: /* Leave input mode. */ F_CLR(sp, SC_TINPUT); /* If recording for playback, save it. */ if (LF_ISSET(TXT_RECORD)) vip->rep_cnt = rcol; /* * If not working on the colon command line, set the final cursor * position. */ if (!F_ISSET(sp, SC_TINPUT_INFO)) { vp->m_final.lno = tp->lno; vp->m_final.cno = tp->cno; } return (0); err: alloc_err: F_CLR(sp, SC_TINPUT); txt_err(sp, sp->tiq); return (1); } /* * txt_abbrev -- * Handle abbreviations. */ static int txt_abbrev(SCR *sp, TEXT *tp, CHAR_T *pushcp, int isinfoline, int *didsubp, int *turnoffp) { VI_PRIVATE *vip; CHAR_T ch, *p; SEQ *qp; size_t len, off; /* Check to make sure we're not at the start of an append. */ *didsubp = 0; if (tp->cno == tp->offset) return (0); vip = VIP(sp); /* * Find the start of the "word". * * !!! * We match historic practice, which, as far as I can tell, had an * off-by-one error. The way this worked was that when the inserted * text switched from a "word" character to a non-word character, * vi would check for possible abbreviations. It would then take the * type (i.e. word/non-word) of the character entered TWO characters * ago, and move backward in the text until reaching a character that * was not that type, or the beginning of the insert, the line, or * the file. For example, in the string "abc", when the * character triggered the abbreviation check, the type of the 'b' * character was used for moving through the string. Maybe there's a * reason for not using the first (i.e. 'c') character, but I can't * think of one. * * Terminate at the beginning of the insert or the character after the * offset character -- both can be tested for using tp->offset. */ off = tp->cno - 1; /* Previous character. */ p = tp->lb + off; len = 1; /* One character test. */ if (off == tp->offset || isblank(p[-1])) goto search; if (inword(p[-1])) /* Move backward to change. */ for (;;) { --off; --p; ++len; if (off == tp->offset || !inword(p[-1])) break; } else for (;;) { --off; --p; ++len; if (off == tp->offset || inword(p[-1]) || isblank(p[-1])) break; } /* * !!! * Historic vi exploded abbreviations on the command line. This has * obvious problems in that unabbreviating the string can be extremely * tricky, particularly if the string has, say, an embedded escape * character. Personally, I think it's a stunningly bad idea. Other * examples of problems this caused in historic vi are: * :ab foo bar * :ab foo baz * results in "bar" being abbreviated to "baz", which wasn't what the * user had in mind at all. Also, the commands: * :ab foo bar * :unab foo * resulted in an error message that "bar" wasn't mapped. Finally, * since the string was already exploded by the time the unabbreviate * command got it, all it knew was that an abbreviation had occurred. * Cleverly, it checked the replacement string for its unabbreviation * match, which meant that the commands: * :ab foo1 bar * :ab foo2 bar * :unab foo2 * unabbreviate "foo1", and the commands: * :ab foo bar * :ab bar baz * unabbreviate "foo"! * * Anyway, people neglected to first ask my opinion before they wrote * macros that depend on this stuff, so, we make this work as follows. * When checking for an abbreviation on the command line, if we get a * string which is terminated and which starts at the beginning * of the line, we check to see it is the abbreviate or unabbreviate * commands. If it is, turn abbreviations off and return as if no * abbreviation was found. Note also, minor trickiness, so that if * the user erases the line and starts another command, we turn the * abbreviations back on. * * This makes the layering look like a Nachos Supreme. */ search: if (isinfoline) if (off == tp->ai || off == tp->offset) if (ex_is_abbrev(p, len)) { *turnoffp = 1; return (0); } else *turnoffp = 0; else if (*turnoffp) return (0); /* Check for any abbreviations. */ if ((qp = seq_find(sp, NULL, NULL, p, len, SEQ_ABBREV, NULL)) == NULL) return (0); /* * Push the abbreviation onto the tty stack. Historically, characters * resulting from an abbreviation expansion were themselves subject to * map expansions, O_SHOWMATCH matching etc. This means the expanded * characters will be re-tested for abbreviations. It's difficult to * know what historic practice in this case was, since abbreviations * were applied to :colon command lines, so entering abbreviations that * looped was tricky, although possible. In addition, obvious loops * didn't work as expected. (The command ':ab a b|ab b c|ab c a' will * silently only implement and/or display the last abbreviation.) * * This implementation doesn't recover well from such abbreviations. * The main input loop counts abbreviated characters, and, when it * reaches a limit, discards any abbreviated characters on the queue. * It's difficult to back up to the original position, as the replay * queue would have to be adjusted, and the line state when an initial * abbreviated character was received would have to be saved. */ ch = *pushcp; if (v_event_push(sp, NULL, &ch, 1, CH_ABBREVIATED)) return (1); if (v_event_push(sp, NULL, qp->output, qp->olen, CH_ABBREVIATED)) return (1); /* * If the size of the abbreviation is larger than or equal to the size * of the original text, move to the start of the replaced characters, * and add their length to the overwrite count. * * If the abbreviation is smaller than the original text, we have to * delete the additional overwrite characters and copy down any insert * characters. */ tp->cno -= len; if (qp->olen >= len) tp->owrite += len; else { if (tp->insert) MEMMOVE(tp->lb + tp->cno + qp->olen, tp->lb + tp->cno + tp->owrite + len, tp->insert); tp->owrite += qp->olen; tp->len -= len - qp->olen; } /* * We return the length of the abbreviated characters. This is so * the calling routine can replace the replay characters with the * abbreviation. This means that subsequent '.' commands will produce * the same text, regardless of intervening :[un]abbreviate commands. * This is historic practice. */ *didsubp = len; return (0); } /* * txt_unmap -- * Handle the unmap command. */ static void txt_unmap(SCR *sp, TEXT *tp, u_int32_t *ec_flagsp) { size_t len, off; CHAR_T *p; /* Find the beginning of this "word". */ for (off = tp->cno - 1, p = tp->lb + off, len = 0;; --p, --off) { if (isblank(*p)) { ++p; break; } ++len; if (off == tp->ai || off == tp->offset) break; } /* * !!! * Historic vi exploded input mappings on the command line. See the * txt_abbrev() routine for an explanation of the problems inherent * in this. * * We make this work as follows. If we get a string which is * terminated and which starts at the beginning of the line, we check * to see it is the unmap command. If it is, we return that the input * mapping should be turned off. Note also, minor trickiness, so that * if the user erases the line and starts another command, we go ahead * an turn mapping back on. */ if ((off == tp->ai || off == tp->offset) && ex_is_unmap(p, len)) FL_CLR(*ec_flagsp, EC_MAPINPUT); else FL_SET(*ec_flagsp, EC_MAPINPUT); } /* * txt_ai_resolve -- * When a line is resolved by , review autoindent characters. */ static void txt_ai_resolve(SCR *sp, TEXT *tp, int *changedp) { u_long ts; int del; size_t cno, len, new, old, scno, spaces, tab_after_sp, tabs; CHAR_T *p; *changedp = 0; /* * If the line is empty, has an offset, or no autoindent * characters, we're done. */ if (!tp->len || tp->offset || !tp->ai) return; /* * If the length is less than or equal to the autoindent * characters, delete them. */ if (tp->len <= tp->ai) { tp->ai = tp->cno = tp->len = 0; return; } /* * The autoindent characters plus any leading characters * in the line are resolved into the minimum number of characters. * Historic practice. */ ts = O_VAL(sp, O_TABSTOP); /* Figure out the last screen column. */ for (p = tp->lb, scno = 0, len = tp->len, spaces = tab_after_sp = 0; len-- && isblank(*p); ++p) if (*p == '\t') { if (spaces) tab_after_sp = 1; scno += COL_OFF(scno, ts); } else { ++spaces; ++scno; } /* * If there are no spaces, or no tabs after spaces and less than * ts spaces, it's already minimal. + * Keep analysing if expandtab is set. */ - if (!spaces || (!tab_after_sp && spaces < ts)) + if ((!spaces || (!tab_after_sp && spaces < ts)) && + !O_ISSET(sp, O_EXPANDTAB)) return; /* Count up spaces/tabs needed to get to the target. */ - for (cno = 0, tabs = 0; cno + COL_OFF(cno, ts) <= scno; ++tabs) - cno += COL_OFF(cno, ts); + cno = 0; + tabs = 0; + if (!O_ISSET(sp, O_EXPANDTAB)) { + for (; cno + COL_OFF(cno, ts) <= scno; ++tabs) + cno += COL_OFF(cno, ts); + } spaces = scno - cno; /* * Figure out how many characters we're dropping -- if we're not * dropping any, it's already minimal, we're done. */ old = p - tp->lb; new = spaces + tabs; if (old == new) return; /* Shift the rest of the characters down, adjust the counts. */ del = old - new; MEMMOVE(p - del, p, tp->len - old); tp->len -= del; tp->cno -= del; /* Fill in space/tab characters. */ for (p = tp->lb; tabs--;) *p++ = '\t'; while (spaces--) *p++ = ' '; *changedp = 1; } /* * v_txt_auto -- * Handle autoindent. If aitp isn't NULL, use it, otherwise, * retrieve the line. * * PUBLIC: int v_txt_auto(SCR *, recno_t, TEXT *, size_t, TEXT *); */ int v_txt_auto(SCR *sp, recno_t lno, TEXT *aitp, size_t len, TEXT *tp) { size_t nlen; CHAR_T *p, *t; if (aitp == NULL) { /* * If the ex append command is executed with an address of 0, * it's possible to get here with a line number of 0. Return * an indent of 0. */ if (lno == 0) { tp->ai = 0; return (0); } if (db_get(sp, lno, DBG_FATAL, &t, &len)) return (1); } else t = aitp->lb; /* Count whitespace characters. */ for (p = t; len > 0; ++p, --len) if (!isblank(*p)) break; /* Set count, check for no indentation. */ if ((nlen = (p - t)) == 0) return (0); /* Make sure the buffer's big enough. */ BINC_RETW(sp, tp->lb, tp->lb_len, tp->len + nlen); /* Copy the buffer's current contents up. */ if (tp->len != 0) MEMMOVE(tp->lb + nlen, tp->lb, tp->len); tp->len += nlen; /* Copy the indentation into the new buffer. */ MEMMOVE(tp->lb, t, nlen); /* Set the autoindent count. */ tp->ai = nlen; return (0); } /* * txt_backup -- * Back up to the previously edited line. */ static TEXT * txt_backup(SCR *sp, TEXTH *tiqh, TEXT *tp, u_int32_t *flagsp) { VI_PRIVATE *vip; TEXT *ntp; /* Get a handle on the previous TEXT structure. */ if ((ntp = TAILQ_PREV(tp, _texth, q)) == NULL) { if (!FL_ISSET(*flagsp, TXT_REPLAY)) msgq(sp, M_BERR, "193|Already at the beginning of the insert"); return (tp); } /* Bookkeeping. */ ntp->len = ntp->sv_len; /* Handle appending to the line. */ vip = VIP(sp); if (ntp->owrite == 0 && ntp->insert == 0) { ntp->lb[ntp->len] = CH_CURSOR; ++ntp->insert; ++ntp->len; FL_SET(*flagsp, TXT_APPENDEOL); } else FL_CLR(*flagsp, TXT_APPENDEOL); /* Release the current TEXT. */ TAILQ_REMOVE(tiqh, tp, q); text_free(tp); /* Update the old line on the screen. */ if (vs_change(sp, ntp->lno + 1, LINE_DELETE)) return (NULL); /* Return the new/current TEXT. */ return (ntp); } /* * Text indentation is truly strange. ^T and ^D do movements to the next or * previous shiftwidth value, i.e. for a 1-based numbering, with shiftwidth=3, * ^T moves a cursor on the 7th, 8th or 9th column to the 10th column, and ^D * moves it back. * * !!! * The ^T and ^D characters in historical vi had special meaning only when they * were the first characters entered after entering text input mode. As normal * erase characters couldn't erase autoindent characters (^T in this case), it * meant that inserting text into previously existing text was strange -- ^T * only worked if it was the first keystroke(s), and then could only be erased * using ^D. This implementation treats ^T specially anywhere it occurs in the * input, and permits the standard erase characters to erase the characters it * inserts. * * !!! * A fun test is to try: * :se sw=4 ai list * i^Tx^Tx^Tx^Dx^Dx^Dx * Historic vi loses some of the '$' marks on the line ends, but otherwise gets * it right. * * XXX * Technically, txt_dent should be part of the screen interface, as it requires * knowledge of character sizes, including s, on the screen. It's here * because it's a complicated little beast, and I didn't want to shove it down * into the screen. It's probable that KEY_COL will call into the screen once * there are screens with different character representations. * * txt_dent -- * Handle ^T indents, ^D outdents. * * If anything changes here, check the ex version to see if it needs similar * changes. */ static int -txt_dent(SCR *sp, TEXT *tp, int isindent) +txt_dent(SCR *sp, TEXT *tp, int swopt, int isindent) { CHAR_T ch; u_long sw, ts; size_t cno, current, spaces, target, tabs; int ai_reset; ts = O_VAL(sp, O_TABSTOP); - sw = O_VAL(sp, O_SHIFTWIDTH); + sw = O_VAL(sp, swopt); /* * Since we don't know what precedes the character(s) being inserted * (or deleted), the preceding whitespace characters must be resolved. * An example is a , which doesn't need a full shiftwidth number * of columns because it's preceded by s. This is easy to get * if the user sets shiftwidth to a value less than tabstop (or worse, * something for which tabstop isn't a multiple) and then uses ^T to * indent, and ^D to outdent. * * Figure out the current and target screen columns. In the historic * vi, the autoindent column was NOT determined using display widths * of characters as was the wrapmargin column. For that reason, we * can't use the vs_column() function, but have to calculate it here. * This is slow, but it's normally only on the first few characters of * a line. */ for (current = cno = 0; cno < tp->cno; ++cno) current += tp->lb[cno] == '\t' ? COL_OFF(current, ts) : KEY_COL(sp, tp->lb[cno]); target = current; if (isindent) target += COL_OFF(target, sw); else { --target; target -= target % sw; } /* * The AI characters will be turned into overwrite characters if the * cursor immediately follows them. We test both the cursor position * and the indent flag because there's no single test. (^T can only * be detected by the cursor position, and while we know that the test * is always true for ^D, the cursor can be in more than one place, as * "0^D" and "^D" are different.) */ ai_reset = !isindent || tp->cno == tp->ai + tp->offset; /* * Back up over any previous characters, changing them into * overwrite characters (including any ai characters). Then figure * out the current screen column. */ for (; tp->cno > tp->offset && (tp->lb[tp->cno - 1] == ' ' || tp->lb[tp->cno - 1] == '\t'); --tp->cno, ++tp->owrite); for (current = cno = 0; cno < tp->cno; ++cno) current += tp->lb[cno] == '\t' ? COL_OFF(current, ts) : KEY_COL(sp, tp->lb[cno]); /* * If we didn't move up to or past the target, it's because there * weren't enough characters to delete, e.g. the first character * of the line was a tp->offset character, and the user entered * ^D to move to the beginning of a line. An example of this is: * * :set ai sw=4iai^T^D * * Otherwise, count up the total spaces/tabs needed to get from the * beginning of the line (or the last non- character) to the * target. */ if (current >= target) spaces = tabs = 0; else { - for (cno = current, - tabs = 0; cno + COL_OFF(cno, ts) <= target; ++tabs) - cno += COL_OFF(cno, ts); + cno = current; + tabs = 0; + if (!O_ISSET(sp, O_EXPANDTAB)) { + for (; cno + COL_OFF(cno, ts) <= target; ++tabs) + cno += COL_OFF(cno, ts); + } spaces = target - cno; } /* If we overwrote ai characters, reset the ai count. */ if (ai_reset) tp->ai = tabs + spaces; /* * Call txt_insch() to insert each character, so that we get the * correct effect when we add a to replace N . */ for (ch = '\t'; tabs > 0; --tabs) (void)txt_insch(sp, tp, &ch, 0); for (ch = ' '; spaces > 0; --spaces) (void)txt_insch(sp, tp, &ch, 0); return (0); } /* * txt_fc -- * File name and ex command completion. */ static int txt_fc(SCR *sp, TEXT *tp, int *redrawp) { struct stat sb; ARGS **argv; EXCMD cmd; size_t indx, len, nlen, off; int argc; CHAR_T *p, *t, *bp; char *np, *epd = NULL; size_t nplen; int fstwd = 1; *redrawp = 0; ex_cinit(sp, &cmd, 0, 0, OOBLNO, OOBLNO, 0); /* * Find the beginning of this "word" -- if we're at the beginning * of the line, it's a special case. */ if (tp->cno == 1) { len = 0; p = tp->lb; } else { CHAR_T *ap; for (len = 0, off = MAX(tp->ai, tp->offset), ap = tp->lb + off, p = ap; off < tp->cno; ++off, ++ap) { if (IS_ESCAPE(sp, &cmd, *ap)) { if (++off == tp->cno) break; ++ap; len += 2; } else if (cmdskip(*ap)) { p = ap + 1; if (len > 0) fstwd = 0; len = 0; } else ++len; } } /* * If we are at the first word, do ex command completion instead of * file name completion. */ if (fstwd) (void)argv_flt_ex(sp, &cmd, p, len); else { if ((bp = argv_uesc(sp, &cmd, p, len)) == NULL) return (1); if (argv_flt_path(sp, &cmd, bp, STRLEN(bp))) { FREE_SPACEW(sp, bp, 0); return (0); } FREE_SPACEW(sp, bp, 0); } argc = cmd.argc; argv = cmd.argv; switch (argc) { case 0: /* No matches. */ (void)sp->gp->scr_bell(sp); return (0); case 1: /* One match. */ /* Always overwrite the old text. */ nlen = STRLEN(cmd.argv[0]->bp); break; default: /* Multiple matches. */ *redrawp = 1; if (txt_fc_col(sp, argc, argv)) return (1); /* Find the length of the shortest match. */ for (nlen = cmd.argv[0]->len; --argc > 0;) { if (cmd.argv[argc]->len < nlen) nlen = cmd.argv[argc]->len; for (indx = 0; indx < nlen && cmd.argv[argc]->bp[indx] == cmd.argv[0]->bp[indx]; ++indx); nlen = indx; } break; } /* Escape the matched part of the path. */ if (fstwd) bp = cmd.argv[0]->bp; else { if ((bp = argv_esc(sp, &cmd, cmd.argv[0]->bp, nlen)) == NULL) return (1); nlen = STRLEN(bp); } /* Overwrite the expanded text first. */ for (t = bp; len > 0 && nlen > 0; --len, --nlen) *p++ = *t++; /* If lost text, make the remaining old text overwrite characters. */ if (len) { tp->cno -= len; tp->owrite += len; } /* Overwrite any overwrite characters next. */ for (; nlen > 0 && tp->owrite > 0; --nlen, --tp->owrite, ++tp->cno) *p++ = *t++; /* Shift remaining text up, and move the cursor to the end. */ if (nlen) { off = p - tp->lb; BINC_RETW(sp, tp->lb, tp->lb_len, tp->len + nlen); p = tp->lb + off; tp->cno += nlen; tp->len += nlen; if (tp->insert != 0) (void)MEMMOVE(p + nlen, p, tp->insert); while (nlen--) *p++ = *t++; } if (!fstwd) FREE_SPACEW(sp, bp, 0); /* If not a single match of path, we've done. */ if (argc != 1 || fstwd) return (0); /* If a single match and it's a directory, append a '/'. */ INT2CHAR(sp, cmd.argv[0]->bp, cmd.argv[0]->len + 1, np, nplen); if ((epd = expanduser(np)) != NULL) np = epd; if (!stat(np, &sb) && S_ISDIR(sb.st_mode)) { if (tp->owrite == 0) { off = p - tp->lb; BINC_RETW(sp, tp->lb, tp->lb_len, tp->len + 1); p = tp->lb + off; if (tp->insert != 0) (void)MEMMOVE(p + 1, p, tp->insert); ++tp->len; } else --tp->owrite; ++tp->cno; *p++ = '/'; } free(epd); return (0); } /* * txt_fc_col -- * Display file names for file name completion. */ static int txt_fc_col(SCR *sp, int argc, ARGS **argv) { ARGS **av; CHAR_T *p; GS *gp; size_t base, cnt, col, colwidth, numrows, numcols, prefix, row; int ac, nf, reset; char *np, *pp; size_t nlen; gp = sp->gp; /* Trim any directory prefix common to all of the files. */ INT2CHAR(sp, argv[0]->bp, argv[0]->len + 1, np, nlen); if ((pp = strrchr(np, '/')) == NULL) prefix = 0; else { prefix = (pp - np) + 1; for (ac = argc - 1, av = argv + 1; ac > 0; --ac, ++av) if (av[0]->len < prefix || MEMCMP(av[0]->bp, argv[0]->bp, prefix)) { prefix = 0; break; } } /* * Figure out the column width for the longest name. Output is done on * 6 character "tab" boundaries for no particular reason. (Since we * don't output tab characters, we ignore the terminal's tab settings.) * Ignore the user's tab setting because we have no idea how reasonable * it is. */ for (ac = argc, av = argv, colwidth = 0; ac > 0; --ac, ++av) { for (col = 0, p = av[0]->bp + prefix; *p != '\0'; ++p) col += KEY_COL(sp, *p); if (col > colwidth) colwidth = col; } colwidth += COL_OFF(colwidth, 6); /* * Writing to the bottom line of the screen is always turned off when * SC_TINPUT_INFO is set. Turn it back on, we know what we're doing. */ if (F_ISSET(sp, SC_TINPUT_INFO)) { reset = 1; F_CLR(sp, SC_TINPUT_INFO); } else reset = 0; #define CHK_INTR \ if (F_ISSET(gp, G_INTERRUPTED)) \ goto intr; /* If the largest file name is too large, just print them. */ if (colwidth >= sp->cols) { for (ac = argc, av = argv; ac > 0; --ac, ++av) { INT2CHAR(sp, av[0]->bp+prefix, av[0]->len+1-prefix, np, nlen); pp = msg_print(sp, np, &nf); (void)ex_printf(sp, "%s\n", pp); if (nf) FREE_SPACE(sp, pp, 0); if (F_ISSET(gp, G_INTERRUPTED)) break; } CHK_INTR; } else { /* Figure out the number of columns. */ numcols = (sp->cols - 1) / colwidth; if (argc > numcols) { numrows = argc / numcols; if (argc % numcols) ++numrows; } else numrows = 1; /* Display the files in sorted order. */ for (row = 0; row < numrows; ++row) { for (base = row, col = 0; col < numcols; ++col) { INT2CHAR(sp, argv[base]->bp+prefix, argv[base]->len+1-prefix, np, nlen); pp = msg_print(sp, np, &nf); cnt = ex_printf(sp, "%s", pp); if (nf) FREE_SPACE(sp, pp, 0); CHK_INTR; if ((base += numrows) >= argc) break; (void)ex_printf(sp, "%*s", (int)(colwidth - cnt), ""); CHK_INTR; } (void)ex_puts(sp, "\n"); CHK_INTR; } (void)ex_puts(sp, "\n"); CHK_INTR; } (void)ex_fflush(sp); if (0) { intr: F_CLR(gp, G_INTERRUPTED); } if (reset) F_SET(sp, SC_TINPUT_INFO); return (0); } /* * txt_emark -- * Set the end mark on the line. */ static int txt_emark(SCR *sp, TEXT *tp, size_t cno) { CHAR_T ch; u_char *kp; size_t chlen, nlen, olen; CHAR_T *p; ch = CH_ENDMARK; /* * The end mark may not be the same size as the current character. * Don't let the line shift. */ nlen = KEY_COL(sp, ch); if (tp->lb[cno] == '\t') (void)vs_columns(sp, tp->lb, tp->lno, &cno, &olen); else olen = KEY_COL(sp, tp->lb[cno]); /* * If the line got longer, well, it's weird, but it's easy. If * it's the same length, it's easy. If it got shorter, we have * to fix it up. */ if (olen > nlen) { BINC_RETW(sp, tp->lb, tp->lb_len, tp->len + olen); chlen = olen - nlen; if (tp->insert != 0) MEMMOVE(tp->lb + cno + 1 + chlen, tp->lb + cno + 1, tp->insert); tp->len += chlen; tp->owrite += chlen; p = tp->lb + cno; if (tp->lb[cno] == '\t' || KEY_NEEDSWIDE(sp, tp->lb[cno])) for (cno += chlen; chlen--;) *p++ = ' '; else for (kp = (u_char *) KEY_NAME(sp, tp->lb[cno]), cno += chlen; chlen--;) *p++ = *kp++; } tp->lb[cno] = ch; return (vs_change(sp, tp->lno, LINE_RESET)); } /* * txt_err -- * Handle an error during input processing. */ static void txt_err(SCR *sp, TEXTH *tiqh) { recno_t lno; /* * The problem with input processing is that the cursor is at an * indeterminate position since some input may have been lost due * to a malloc error. So, try to go back to the place from which * the cursor started, knowing that it may no longer be available. * * We depend on at least one line number being set in the text * chain. */ for (lno = TAILQ_FIRST(tiqh)->lno; !db_exist(sp, lno) && lno > 0; --lno); sp->lno = lno == 0 ? 1 : lno; sp->cno = 0; /* Redraw the screen, just in case. */ F_SET(sp, SC_SCR_REDRAW); } /* * txt_hex -- * Let the user insert any character value they want. * * !!! * This is an extension. The pattern "^X[0-9a-fA-F]*" is a way * for the user to specify a character value which their keyboard * may not be able to enter. */ static int txt_hex(SCR *sp, TEXT *tp) { CHAR_T savec; size_t len, off; u_long value; CHAR_T *p, *wp; /* * Null-terminate the string. Since nul isn't a legal hex value, * this should be okay, and lets us use a local routine, which * presumably understands the character set, to convert the value. */ savec = tp->lb[tp->cno]; tp->lb[tp->cno] = 0; /* Find the previous CH_HEX character. */ for (off = tp->cno - 1, p = tp->lb + off, len = 0;; --p, --off, ++len) { if (*p == CH_HEX) { wp = p + 1; break; } /* Not on this line? Shouldn't happen. */ if (off == tp->ai || off == tp->offset) goto nothex; } /* If length of 0, then it wasn't a hex value. */ if (len == 0) goto nothex; /* Get the value. */ errno = 0; value = STRTOL(wp, NULL, 16); if (errno || value > UCHAR_MAX) { nothex: tp->lb[tp->cno] = savec; return (0); } /* Restore the original character. */ tp->lb[tp->cno] = savec; /* Adjust the bookkeeping. */ tp->cno -= len; tp->len -= len; tp->lb[tp->cno - 1] = value; /* Copy down any overwrite characters. */ if (tp->owrite) MEMMOVE(tp->lb + tp->cno, tp->lb + tp->cno + len, tp->owrite); /* Copy down any insert characters. */ if (tp->insert) MEMMOVE(tp->lb + tp->cno + tp->owrite, tp->lb + tp->cno + tp->owrite + len, tp->insert); return (0); } /* * txt_insch -- * * !!! * Historic vi did a special screen optimization for tab characters. As an * example, for the keystrokes "iabcd0C", the tab overwrote the * rest of the string when it was displayed. * * Because early versions of this implementation redisplayed the entire line * on each keystroke, the "bcd" was pushed to the right as it ignored that * the user had "promised" to change the rest of the characters. However, * the historic vi implementation had an even worse bug: given the keystrokes * "iabcd0R", the "bcd" disappears, and magically reappears * on the second key. * * POSIX 1003.2 requires (will require) that this be fixed, specifying that * vi overwrite characters the user has committed to changing, on the basis * of the screen space they require, but that it not overwrite other characters. */ static int txt_insch(SCR *sp, TEXT *tp, CHAR_T *chp, u_int flags) { u_char *kp; CHAR_T savech; size_t chlen, cno, copydown, olen, nlen; CHAR_T *p; /* * The 'R' command does one-for-one replacement, because there's * no way to know how many characters the user intends to replace. */ if (LF_ISSET(TXT_REPLACE)) { if (tp->owrite) { --tp->owrite; tp->lb[tp->cno++] = *chp; return (0); } } else if (tp->owrite) { /* Overwrite a character. */ cno = tp->cno; /* * If the old or new characters are tabs, then the length of the * display depends on the character position in the display. We * don't even try to handle this here, just ask the screen. */ if (*chp == '\t') { savech = tp->lb[cno]; tp->lb[cno] = '\t'; (void)vs_columns(sp, tp->lb, tp->lno, &cno, &nlen); tp->lb[cno] = savech; } else nlen = KEY_COL(sp, *chp); /* * Eat overwrite characters until we run out of them or we've * handled the length of the new character. If we only eat * part of an overwrite character, break it into its component * elements and display the remaining components. */ for (copydown = 0; nlen != 0 && tp->owrite != 0;) { --tp->owrite; if (tp->lb[cno] == '\t') (void)vs_columns(sp, tp->lb, tp->lno, &cno, &olen); else olen = KEY_COL(sp, tp->lb[cno]); if (olen == nlen) { nlen = 0; break; } if (olen < nlen) { ++copydown; nlen -= olen; } else { BINC_RETW(sp, tp->lb, tp->lb_len, tp->len + olen); chlen = olen - nlen; MEMMOVE(tp->lb + cno + 1 + chlen, tp->lb + cno + 1, tp->owrite + tp->insert); tp->len += chlen; tp->owrite += chlen; if (tp->lb[cno] == '\t' || KEY_NEEDSWIDE(sp, tp->lb[cno])) for (p = tp->lb + cno + 1; chlen--;) *p++ = ' '; else for (kp = (u_char *) KEY_NAME(sp, tp->lb[cno]) + nlen, p = tp->lb + cno + 1; chlen--;) *p++ = *kp++; nlen = 0; break; } } /* * If had to erase several characters, we adjust the total * count, and if there are any characters left, shift them * into position. */ if (copydown != 0 && (tp->len -= copydown) != 0) MEMMOVE(tp->lb + cno, tp->lb + cno + copydown, tp->owrite + tp->insert + copydown); /* If we had enough overwrite characters, we're done. */ if (nlen == 0) { tp->lb[tp->cno++] = *chp; return (0); } } /* Check to see if the character fits into the input buffer. */ BINC_RETW(sp, tp->lb, tp->lb_len, tp->len + 1); ++tp->len; if (tp->insert) { /* Insert a character. */ if (tp->insert == 1) tp->lb[tp->cno + 1] = tp->lb[tp->cno]; else MEMMOVE(tp->lb + tp->cno + 1, tp->lb + tp->cno, tp->owrite + tp->insert); } tp->lb[tp->cno++] = *chp; return (0); } /* * txt_isrch -- * Do an incremental search. */ static int txt_isrch(SCR *sp, VICMD *vp, TEXT *tp, u_int8_t *is_flagsp) { MARK start; recno_t lno; u_int sf; /* If it's a one-line screen, we don't do incrementals. */ if (IS_ONELINE(sp)) { FL_CLR(*is_flagsp, IS_RUNNING); return (0); } /* * If the user erases back to the beginning of the buffer, there's * nothing to search for. Reset the cursor to the starting point. */ if (tp->cno <= 1) { vp->m_final = vp->m_start; return (0); } /* * If it's an RE quote character, and not quoted, ignore it until * we get another character. */ if (tp->lb[tp->cno - 1] == '\\' && (tp->cno == 2 || tp->lb[tp->cno - 2] != '\\')) return (0); /* * If it's a magic shell character, and not quoted, reset the cursor * to the starting point. */ if (IS_SHELLMETA(sp, tp->lb[tp->cno - 1]) && (tp->cno == 2 || tp->lb[tp->cno - 2] != '\\')) vp->m_final = vp->m_start; /* * If we see the search pattern termination character, then quit doing * an incremental search. There may be more, e.g., ":/foo/;/bar/", * and we can't handle that incrementally. Also, reset the cursor to * the original location, the ex search routines don't know anything * about incremental searches. */ if (tp->lb[0] == tp->lb[tp->cno - 1] && (tp->cno == 2 || tp->lb[tp->cno - 2] != '\\')) { vp->m_final = vp->m_start; FL_CLR(*is_flagsp, IS_RUNNING); return (0); } /* * Remember the input line and discard the special input map, * but don't overwrite the input line on the screen. */ lno = tp->lno; F_SET(VIP(sp), VIP_S_MODELINE); F_CLR(sp, SC_TINPUT | SC_TINPUT_INFO); if (txt_map_end(sp)) return (1); /* * Specify a starting point and search. If we find a match, move to * it and refresh the screen. If we didn't find the match, then we * beep the screen. When searching from the original cursor position, * we have to move the cursor, otherwise, we don't want to move the * cursor in case the text at the current position continues to match. */ if (FL_ISSET(*is_flagsp, IS_RESTART)) { start = vp->m_start; sf = SEARCH_SET; } else { start = vp->m_final; sf = SEARCH_INCR | SEARCH_SET; } if (tp->lb[0] == '/' ? !f_search(sp, &start, &vp->m_final, tp->lb + 1, tp->cno - 1, NULL, sf) : !b_search(sp, &start, &vp->m_final, tp->lb + 1, tp->cno - 1, NULL, sf)) { sp->lno = vp->m_final.lno; sp->cno = vp->m_final.cno; FL_CLR(*is_flagsp, IS_RESTART); if (!KEYS_WAITING(sp) && vs_refresh(sp, 0)) return (1); } else FL_SET(*is_flagsp, IS_RESTART); /* Reinstantiate the special input map. */ if (txt_map_init(sp)) return (1); F_CLR(VIP(sp), VIP_S_MODELINE); F_SET(sp, SC_TINPUT | SC_TINPUT_INFO); /* Reset the line number of the input line. */ tp->lno = TMAP[0].lno; /* * If the colon command-line moved, i.e. the screen scrolled, * refresh the input line. * * XXX * We shouldn't be calling vs_line, here -- we need dirty bits * on entries in the SMAP array. */ if (lno != TMAP[0].lno) { if (vs_line(sp, &TMAP[0], NULL, NULL)) return (1); (void)sp->gp->scr_refresh(sp, 0); } return (0); } /* * txt_resolve -- * Resolve the input text chain into the file. */ static int txt_resolve(SCR *sp, TEXTH *tiqh, u_int32_t flags) { VI_PRIVATE *vip; TEXT *tp; recno_t lno; int changed; /* * The first line replaces a current line, and all subsequent lines * are appended into the file. Resolve autoindented characters for * each line before committing it. If the latter causes the line to * change, we have to redisplay it, otherwise the information cached * about the line will be wrong. */ vip = VIP(sp); tp = TAILQ_FIRST(tiqh); if (LF_ISSET(TXT_AUTOINDENT)) txt_ai_resolve(sp, tp, &changed); else changed = 0; if (db_set(sp, tp->lno, tp->lb, tp->len) || (changed && vs_change(sp, tp->lno, LINE_RESET))) return (1); for (lno = tp->lno; (tp = TAILQ_NEXT(tp, q)) != NULL; ++lno) { if (LF_ISSET(TXT_AUTOINDENT)) txt_ai_resolve(sp, tp, &changed); else changed = 0; if (db_append(sp, 0, lno, tp->lb, tp->len) || (changed && vs_change(sp, tp->lno, LINE_RESET))) return (1); } /* * Clear the input flag, the look-aside buffer is no longer valid. * Has to be done as part of text resolution, or upon return we'll * be looking at incorrect data. */ F_CLR(sp, SC_TINPUT); return (0); } /* * txt_showmatch -- * Show a character match. * * !!! * Historic vi tried to display matches even in the :colon command line. * I think not. */ static int txt_showmatch(SCR *sp, TEXT *tp) { GS *gp; VCS cs; MARK m; int cnt, endc, startc; gp = sp->gp; /* * Do a refresh first, in case we haven't done one in awhile, * so the user can see what we're complaining about. */ UPDATE_POSITION(sp, tp); if (vs_refresh(sp, 1)) return (1); /* * We don't display the match if it's not on the screen. Find * out what the first character on the screen is. */ if (vs_sm_position(sp, &m, 0, P_TOP)) return (1); /* Initialize the getc() interface. */ cs.cs_lno = tp->lno; cs.cs_cno = tp->cno - 1; if (cs_init(sp, &cs)) return (1); startc = STRCHR(VIP(sp)->mcs, endc = cs.cs_ch)[-1]; /* Search for the match. */ for (cnt = 1;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags != 0) { if (cs.cs_flags == CS_EOF || cs.cs_flags == CS_SOF) { msgq(sp, M_BERR, "Unmatched %s", KEY_NAME(sp, endc)); return (0); } continue; } if (cs.cs_ch == endc) ++cnt; else if (cs.cs_ch == startc && --cnt == 0) break; } /* If the match is on the screen, move to it. */ if (cs.cs_lno < m.lno || (cs.cs_lno == m.lno && cs.cs_cno < m.cno)) return (0); sp->lno = cs.cs_lno; sp->cno = cs.cs_cno; if (vs_refresh(sp, 1)) return (1); /* Wait for timeout or character arrival. */ return (v_event_get(sp, NULL, O_VAL(sp, O_MATCHTIME) * 100, EC_TIMEOUT)); } /* * txt_margin -- * Handle margin wrap. */ static int txt_margin(SCR *sp, TEXT *tp, TEXT *wmtp, int *didbreak, u_int32_t flags) { VI_PRIVATE *vip; size_t len, off; CHAR_T *p, *wp; /* Find the nearest previous blank. */ for (off = tp->cno - 1, p = tp->lb + off, len = 0;; --off, --p, ++len) { if (isblank(*p)) { wp = p + 1; break; } /* * If reach the start of the line, there's nowhere to break. * * !!! * Historic vi belled each time a character was entered after * crossing the margin until a space was entered which could * be used to break the line. I don't as it tends to wake the * cats. */ if (off == tp->ai || off == tp->offset) { *didbreak = 0; return (0); } } /* * Store saved information about the rest of the line in the * wrapmargin TEXT structure. * * !!! * The offset field holds the length of the current characters * that the user entered, but which are getting split to the new * line -- it's going to be used to set the cursor value when we * move to the new line. */ vip = VIP(sp); wmtp->lb = p + 1; wmtp->offset = len; wmtp->insert = LF_ISSET(TXT_APPENDEOL) ? tp->insert - 1 : tp->insert; wmtp->owrite = tp->owrite; /* Correct current bookkeeping information. */ tp->cno -= len; if (LF_ISSET(TXT_APPENDEOL)) { tp->len -= len + tp->owrite + (tp->insert - 1); tp->insert = 1; } else { tp->len -= len + tp->owrite + tp->insert; tp->insert = 0; } tp->owrite = 0; /* * !!! * Delete any trailing whitespace from the current line. */ for (;; --p, --off) { if (!isblank(*p)) break; --tp->cno; --tp->len; if (off == tp->ai || off == tp->offset) break; } *didbreak = 1; return (0); } /* * txt_Rresolve -- * Resolve the input line for the 'R' command. */ static void txt_Rresolve(SCR *sp, TEXTH *tiqh, TEXT *tp, const size_t orig_len) { TEXT *ttp; size_t input_len, retain; CHAR_T *p; /* * Check to make sure that the cursor hasn't moved beyond * the end of the line. */ if (tp->owrite == 0) return; /* * Calculate how many characters the user has entered, * plus the blanks erased by /s. */ for (ttp = TAILQ_FIRST(tiqh), input_len = 0;;) { input_len += ttp == tp ? tp->cno : ttp->len + ttp->R_erase; if ((ttp = TAILQ_NEXT(ttp, q)) == NULL) break; } /* * If the user has entered less characters than the original line * was long, restore any overwriteable characters to the original * characters. These characters are entered as "insert characters", * because they're after the cursor and we don't want to lose them. * (This is okay because the R command has no insert characters.) * We set owrite to 0 so that the insert characters don't get copied * to somewhere else, which means that the line and the length have * to be adjusted here as well. * * We have to retrieve the original line because the original pinned * page has long since been discarded. If it doesn't exist, that's * okay, the user just extended the file. */ if (input_len < orig_len) { retain = MIN(tp->owrite, orig_len - input_len); if (db_get(sp, TAILQ_FIRST(tiqh)->lno, DBG_FATAL | DBG_NOCACHE, &p, NULL)) return; MEMCPY(tp->lb + tp->cno, p + input_len, retain); tp->len -= tp->owrite - retain; tp->owrite = 0; tp->insert += retain; } } /* * txt_nomorech -- * No more characters message. */ static void txt_nomorech(SCR *sp) { msgq(sp, M_BERR, "194|No more characters to erase"); } Index: head/contrib/nvi/vi/v_ulcase.c =================================================================== --- head/contrib/nvi/vi/v_ulcase.c (revision 365498) +++ head/contrib/nvi/vi/v_ulcase.c (revision 365499) @@ -1,171 +1,167 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_ulcase.c,v 10.12 2011/12/02 19:58:32 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static int ulcase(SCR *, recno_t, CHAR_T *, size_t, size_t, size_t); /* * v_ulcase -- [count]~ * Toggle upper & lower case letters. * * !!! * Historic vi didn't permit ~ to cross newline boundaries. I can * think of no reason why it shouldn't, which at least lets the user * auto-repeat through a paragraph. * * !!! * In historic vi, the count was ignored. It would have been better * if there had been an associated motion, but it's too late to make * that the default now. * * PUBLIC: int v_ulcase(SCR *, VICMD *); */ int v_ulcase(SCR *sp, VICMD *vp) { recno_t lno; size_t cno, lcnt, len; u_long cnt; CHAR_T *p; lno = vp->m_start.lno; cno = vp->m_start.cno; for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt > 0; cno = 0) { /* SOF is an error, EOF is an infinite count sink. */ if (db_get(sp, lno, 0, &p, &len)) { if (lno == 1) { v_emsg(sp, NULL, VIM_EMPTY); return (1); } --lno; break; } /* Empty lines decrement the count by one. */ if (len == 0) { --cnt; vp->m_final.cno = 0; continue; } if (cno + cnt >= len) { lcnt = len - 1; cnt -= len - cno; vp->m_final.cno = len - 1; } else { lcnt = cno + cnt - 1; cnt = 0; vp->m_final.cno = lcnt + 1; } if (ulcase(sp, lno, p, len, cno, lcnt)) return (1); if (cnt > 0) ++lno; } vp->m_final.lno = lno; return (0); } /* * v_mulcase -- [count]~[count]motion * Toggle upper & lower case letters over a range. * * PUBLIC: int v_mulcase(SCR *, VICMD *); */ int v_mulcase(SCR *sp, VICMD *vp) { CHAR_T *p; size_t len; recno_t lno; for (lno = vp->m_start.lno;;) { if (db_get(sp, lno, DBG_FATAL, &p, &len)) return (1); if (len != 0 && ulcase(sp, lno, p, len, lno == vp->m_start.lno ? vp->m_start.cno : 0, !F_ISSET(vp, VM_LMODE) && lno == vp->m_stop.lno ? vp->m_stop.cno : len)) return (1); if (++lno > vp->m_stop.lno) break; } /* * XXX * I didn't create a new motion command when I added motion semantics * for ~. While that's the correct way to do it, that choice would * have required changes all over the vi directory for little gain. * Instead, we pretend it's a yank command. Note, this means that we * follow the cursor motion rules for yank commands, but that seems * reasonable to me. */ return (0); } /* * ulcase -- * Change part of a line's case. */ static int ulcase(SCR *sp, recno_t lno, CHAR_T *lp, size_t len, size_t scno, size_t ecno) { size_t blen; int change, rval; ARG_CHAR_T ch; CHAR_T *p, *t, *bp; GET_SPACE_RETW(sp, bp, blen, len); MEMMOVE(bp, lp, len); change = rval = 0; for (p = bp + scno, t = bp + ecno + 1; p < t; ++p) { ch = (UCHAR_T)*p; if (ISLOWER(ch)) { *p = TOUPPER(ch); change = 1; } else if (ISUPPER(ch)) { *p = TOLOWER(ch); change = 1; } } if (change && db_set(sp, lno, bp, len)) rval = 1; FREE_SPACEW(sp, bp, blen); return (rval); } Index: head/contrib/nvi/vi/v_undo.c =================================================================== --- head/contrib/nvi/vi/v_undo.c (revision 365498) +++ head/contrib/nvi/vi/v_undo.c (revision 365499) @@ -1,135 +1,131 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_undo.c,v 10.6 2001/06/25 15:19:36 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_Undo -- U * Undo changes to this line. * * PUBLIC: int v_Undo(SCR *, VICMD *); */ int v_Undo(SCR *sp, VICMD *vp) { /* * Historically, U reset the cursor to the first column in the line * (not the first non-blank). This seems a bit non-intuitive, but, * considering that we may have undone multiple changes, anything * else (including the cursor position stored in the logging records) * is going to appear random. */ vp->m_final.cno = 0; /* * !!! * Set up the flags so that an immediately subsequent 'u' will roll * forward, instead of backward. In historic vi, a 'u' following a * 'U' redid all of the changes to the line. Given that the user has * explicitly discarded those changes by entering 'U', it seems likely * that the user wants something between the original and end forms of * the line, so starting to replay the changes seems the best way to * get to there. */ F_SET(sp->ep, F_UNDO); sp->ep->lundo = BACKWARD; return (log_setline(sp)); } /* * v_undo -- u * Undo the last change. * * PUBLIC: int v_undo(SCR *, VICMD *); */ int v_undo(SCR *sp, VICMD *vp) { EXF *ep; /* Set the command count. */ VIP(sp)->u_ccnt = sp->ccnt; /* * !!! * In historic vi, 'u' toggled between "undo" and "redo", i.e. 'u' * undid the last undo. However, if there has been a change since * the last undo/redo, we always do an undo. To make this work when * the user can undo multiple operations, we leave the old semantic * unchanged, but make '.' after a 'u' do another undo/redo operation. * This has two problems. * * The first is that 'u' didn't set '.' in historic vi. So, if a * user made a change, realized it was in the wrong place, does a * 'u' to undo it, moves to the right place and then does '.', the * change was reapplied. To make this work, we only apply the '.' * to the undo command if it's the command immediately following an * undo command. See vi/vi.c:getcmd() for the details. * * The second is that the traditional way to view the numbered cut * buffers in vi was to enter the commands "1pu.u.u.u. which will * no longer work because the '.' immediately follows the 'u' command. * Since we provide a much better method of viewing buffers, and * nobody can think of a better way of adding in multiple undo, this * remains broken. * * !!! * There is change to historic practice for the final cursor position * in this implementation. In historic vi, if an undo was isolated to * a single line, the cursor moved to the start of the change, and * then, subsequent 'u' commands would not move it again. (It has been * pointed out that users used multiple undo commands to get the cursor * to the start of the changed text.) Nvi toggles between the cursor * position before and after the change was made. One final issue is * that historic vi only did this if the user had not moved off of the * line before entering the undo command; otherwise, vi would move the * cursor to the most attractive position on the changed line. * * It would be difficult to match historic practice in this area. You * not only have to know that the changes were isolated to one line, * but whether it was the first or second undo command as well. And, * to completely match historic practice, we'd have to track users line * changes, too. This isn't worth the effort. */ ep = sp->ep; if (!F_ISSET(ep, F_UNDO)) { F_SET(ep, F_UNDO); ep->lundo = BACKWARD; } else if (!F_ISSET(vp, VC_ISDOT)) ep->lundo = ep->lundo == BACKWARD ? FORWARD : BACKWARD; switch (ep->lundo) { case BACKWARD: return (log_backward(sp, &vp->m_final)); case FORWARD: return (log_forward(sp, &vp->m_final)); default: abort(); } /* NOTREACHED */ } Index: head/contrib/nvi/vi/v_util.c =================================================================== --- head/contrib/nvi/vi/v_util.c (revision 365498) +++ head/contrib/nvi/vi/v_util.c (revision 365499) @@ -1,167 +1,163 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_util.c,v 10.14 2001/06/25 15:19:36 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_eof -- * Vi end-of-file error. * * PUBLIC: void v_eof(SCR *, MARK *); */ void v_eof(SCR *sp, MARK *mp) { recno_t lno; if (mp == NULL) v_emsg(sp, NULL, VIM_EOF); else { if (db_last(sp, &lno)) return; if (mp->lno >= lno) v_emsg(sp, NULL, VIM_EOF); else msgq(sp, M_BERR, "195|Movement past the end-of-file"); } } /* * v_eol -- * Vi end-of-line error. * * PUBLIC: void v_eol(SCR *, MARK *); */ void v_eol(SCR *sp, MARK *mp) { size_t len; if (mp == NULL) v_emsg(sp, NULL, VIM_EOL); else { if (db_get(sp, mp->lno, DBG_FATAL, NULL, &len)) return; if (mp->cno == len - 1) v_emsg(sp, NULL, VIM_EOL); else msgq(sp, M_BERR, "196|Movement past the end-of-line"); } } /* * v_nomove -- * Vi no cursor movement error. * * PUBLIC: void v_nomove(SCR *); */ void v_nomove(SCR *sp) { msgq(sp, M_BERR, "197|No cursor movement made"); } /* * v_sof -- * Vi start-of-file error. * * PUBLIC: void v_sof(SCR *, MARK *); */ void v_sof(SCR *sp, MARK *mp) { if (mp == NULL || mp->lno == 1) msgq(sp, M_BERR, "198|Already at the beginning of the file"); else msgq(sp, M_BERR, "199|Movement past the beginning of the file"); } /* * v_sol -- * Vi start-of-line error. * * PUBLIC: void v_sol(SCR *); */ void v_sol(SCR *sp) { msgq(sp, M_BERR, "200|Already in the first column"); } /* * v_isempty -- * Return if the line contains nothing but white-space characters. * * PUBLIC: int v_isempty(CHAR_T *, size_t); */ int v_isempty(CHAR_T *p, size_t len) { for (; len--; ++p) if (!isblank(*p)) return (0); return (1); } /* * v_emsg -- * Display a few common vi messages. * * PUBLIC: void v_emsg(SCR *, char *, vim_t); */ void v_emsg(SCR *sp, char *p, vim_t which) { switch (which) { case VIM_COMBUF: msgq(sp, M_ERR, "201|Buffers should be specified before the command"); break; case VIM_EMPTY: msgq(sp, M_BERR, "209|The file is empty"); break; case VIM_EOF: msgq(sp, M_BERR, "202|Already at end-of-file"); break; case VIM_EOL: msgq(sp, M_BERR, "203|Already at end-of-line"); break; case VIM_NOCOM: case VIM_NOCOM_B: msgq(sp, which == VIM_NOCOM_B ? M_BERR : M_ERR, "204|%s isn't a vi command", p); break; case VIM_WRESIZE: msgq(sp, M_ERR, "Window resize interrupted text input mode"); break; case VIM_USAGE: msgq(sp, M_ERR, "205|Usage: %s", p); break; } } Index: head/contrib/nvi/vi/v_word.c =================================================================== --- head/contrib/nvi/vi/v_word.c (revision 365498) +++ head/contrib/nvi/vi/v_word.c (revision 365499) @@ -1,526 +1,522 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_word.c,v 10.7 2011/12/27 00:49:31 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * There are two types of "words". Bigwords are easy -- groups of anything * delimited by whitespace. Normal words are trickier. They are either a * group of characters, numbers and underscores, or a group of anything but, * delimited by whitespace. When for a word, if you're in whitespace, it's * easy, just remove the whitespace and go to the beginning or end of the * word. Otherwise, figure out if the next character is in a different group. * If it is, go to the beginning or end of that group, otherwise, go to the * beginning or end of the current group. The historic version of vi didn't * get this right, so, for example, there were cases where "4e" was not the * same as "eeee" -- in particular, single character words, and commands that * began in whitespace were almost always handled incorrectly. To get it right * you have to resolve the cursor after each search so that the look-ahead to * figure out what type of "word" the cursor is in will be correct. * * Empty lines, and lines that consist of only white-space characters count * as a single word, and the beginning and end of the file counts as an * infinite number of words. * * Movements associated with commands are different than movement commands. * For example, in "abc def", with the cursor on the 'a', "cw" is from * 'a' to 'c', while "w" is from 'a' to 'd'. In general, trailing white * space is discarded from the change movement. Another example is that, * in the same string, a "cw" on any white space character replaces that * single character, and nothing else. Ain't nothin' in here that's easy. * * One historic note -- in the original vi, the 'w', 'W' and 'B' commands * would treat groups of empty lines as individual words, i.e. the command * would move the cursor to each new empty line. The 'e' and 'E' commands * would treat groups of empty lines as a single word, i.e. the first use * would move past the group of lines. The 'b' command would just beep at * you, or, if you did it from the start of the line as part of a motion * command, go absolutely nuts. If the lines contained only white-space * characters, the 'w' and 'W' commands would just beep at you, and the 'B', * 'b', 'E' and 'e' commands would treat the group as a single word, and * the 'B' and 'b' commands will treat the lines as individual words. This * implementation treats all of these cases as a single white-space word. */ enum which {BIGWORD, LITTLEWORD}; static int bword(SCR *, VICMD *, enum which); static int eword(SCR *, VICMD *, enum which); static int fword(SCR *, VICMD *, enum which); /* * v_wordW -- [count]W * Move forward a bigword at a time. * * PUBLIC: int v_wordW(SCR *, VICMD *); */ int v_wordW(SCR *sp, VICMD *vp) { return (fword(sp, vp, BIGWORD)); } /* * v_wordw -- [count]w * Move forward a word at a time. * * PUBLIC: int v_wordw(SCR *, VICMD *); */ int v_wordw(SCR *sp, VICMD *vp) { return (fword(sp, vp, LITTLEWORD)); } /* * fword -- * Move forward by words. */ static int fword(SCR *sp, VICMD *vp, enum which type) { enum { INWORD, NOTWORD } state; VCS cs; u_long cnt; cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cs.cs_lno = vp->m_start.lno; cs.cs_cno = vp->m_start.cno; if (cs_init(sp, &cs)) return (1); /* * If in white-space: * If the count is 1, and it's a change command, we're done. * Else, move to the first non-white-space character, which * counts as a single word move. If it's a motion command, * don't move off the end of the line. */ if (cs.cs_flags == CS_EMP || (cs.cs_flags == 0 && ISBLANK(cs.cs_ch))) { if (ISMOTION(vp) && cs.cs_flags != CS_EMP && cnt == 1) { if (ISCMD(vp->rkp, 'c')) return (0); if (ISCMD(vp->rkp, 'd') || ISCMD(vp->rkp, 'y')) { if (cs_fspace(sp, &cs)) return (1); goto ret; } } if (cs_fblank(sp, &cs)) return (1); --cnt; } /* * Cyclically move to the next word -- this involves skipping * over word characters and then any trailing non-word characters. * Note, for the 'w' command, the definition of a word keeps * switching. */ if (type == BIGWORD) while (cnt--) { for (;;) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; if (cs.cs_flags != 0 || ISBLANK(cs.cs_ch)) break; } /* * If a motion command and we're at the end of the * last word, we're done. Delete and yank eat any * trailing blanks, but we don't move off the end * of the line regardless. */ if (cnt == 0 && ISMOTION(vp)) { if ((ISCMD(vp->rkp, 'd') || ISCMD(vp->rkp, 'y')) && cs_fspace(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (cs_fblank(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; } else while (cnt--) { state = cs.cs_flags == 0 && inword(cs.cs_ch) ? INWORD : NOTWORD; for (;;) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; if (cs.cs_flags != 0 || ISBLANK(cs.cs_ch)) break; if (state == INWORD) { if (!inword(cs.cs_ch)) break; } else if (inword(cs.cs_ch)) break; } /* See comment above. */ if (cnt == 0 && ISMOTION(vp)) { if ((ISCMD(vp->rkp, 'd') || ISCMD(vp->rkp, 'y')) && cs_fspace(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (cs.cs_flags != 0 || ISBLANK(cs.cs_ch)) if (cs_fblank(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; } /* * If we didn't move, we must be at EOF. * * !!! * That's okay for motion commands, however. */ ret: if (!ISMOTION(vp) && cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) { v_eof(sp, &vp->m_start); return (1); } /* Adjust the end of the range for motion commands. */ vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; if (ISMOTION(vp) && cs.cs_flags == 0) --vp->m_stop.cno; /* * Non-motion commands move to the end of the range. Delete * and yank stay at the start, ignore others. */ vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; return (0); } /* * v_wordE -- [count]E * Move forward to the end of the bigword. * * PUBLIC: int v_wordE(SCR *, VICMD *); */ int v_wordE(SCR *sp, VICMD *vp) { return (eword(sp, vp, BIGWORD)); } /* * v_worde -- [count]e * Move forward to the end of the word. * * PUBLIC: int v_worde(SCR *, VICMD *); */ int v_worde(SCR *sp, VICMD *vp) { return (eword(sp, vp, LITTLEWORD)); } /* * eword -- * Move forward to the end of the word. */ static int eword(SCR *sp, VICMD *vp, enum which type) { enum { INWORD, NOTWORD } state; VCS cs; u_long cnt; cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cs.cs_lno = vp->m_start.lno; cs.cs_cno = vp->m_start.cno; if (cs_init(sp, &cs)) return (1); /* * !!! * If in whitespace, or the next character is whitespace, move past * it. (This doesn't count as a word move.) Stay at the character * past the current one, it sets word "state" for the 'e' command. */ if (cs.cs_flags == 0 && !ISBLANK(cs.cs_ch)) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == 0 && !ISBLANK(cs.cs_ch)) goto start; } if (cs_fblank(sp, &cs)) return (1); /* * Cyclically move to the next word -- this involves skipping * over word characters and then any trailing non-word characters. * Note, for the 'e' command, the definition of a word keeps * switching. */ start: if (type == BIGWORD) while (cnt--) { for (;;) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; if (cs.cs_flags != 0 || ISBLANK(cs.cs_ch)) break; } /* * When we reach the start of the word after the last * word, we're done. If we changed state, back up one * to the end of the previous word. */ if (cnt == 0) { if (cs.cs_flags == 0 && cs_prev(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (cs_fblank(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; } else while (cnt--) { state = cs.cs_flags == 0 && inword(cs.cs_ch) ? INWORD : NOTWORD; for (;;) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; if (cs.cs_flags != 0 || ISBLANK(cs.cs_ch)) break; if (state == INWORD) { if (!inword(cs.cs_ch)) break; } else if (inword(cs.cs_ch)) break; } /* See comment above. */ if (cnt == 0) { if (cs.cs_flags == 0 && cs_prev(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (cs.cs_flags != 0 || ISBLANK(cs.cs_ch)) if (cs_fblank(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; } /* * If we didn't move, we must be at EOF. * * !!! * That's okay for motion commands, however. */ ret: if (!ISMOTION(vp) && cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) { v_eof(sp, &vp->m_start); return (1); } /* Set the end of the range for motion commands. */ vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * Non-motion commands move to the end of the range. * Delete and yank stay at the start, ignore others. */ vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; return (0); } /* * v_WordB -- [count]B * Move backward a bigword at a time. * * PUBLIC: int v_wordB(SCR *, VICMD *); */ int v_wordB(SCR *sp, VICMD *vp) { return (bword(sp, vp, BIGWORD)); } /* * v_wordb -- [count]b * Move backward a word at a time. * * PUBLIC: int v_wordb(SCR *, VICMD *); */ int v_wordb(SCR *sp, VICMD *vp) { return (bword(sp, vp, LITTLEWORD)); } /* * bword -- * Move backward by words. */ static int bword(SCR *sp, VICMD *vp, enum which type) { enum { INWORD, NOTWORD } state; VCS cs; u_long cnt; cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cs.cs_lno = vp->m_start.lno; cs.cs_cno = vp->m_start.cno; if (cs_init(sp, &cs)) return (1); /* * !!! * If in whitespace, or the previous character is whitespace, move * past it. (This doesn't count as a word move.) Stay at the * character before the current one, it sets word "state" for the * 'b' command. */ if (cs.cs_flags == 0 && !ISBLANK(cs.cs_ch)) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags == 0 && !ISBLANK(cs.cs_ch)) goto start; } if (cs_bblank(sp, &cs)) return (1); /* * Cyclically move to the beginning of the previous word -- this * involves skipping over word characters and then any trailing * non-word characters. Note, for the 'b' command, the definition * of a word keeps switching. */ start: if (type == BIGWORD) while (cnt--) { for (;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags == CS_SOF) goto ret; if (cs.cs_flags != 0 || ISBLANK(cs.cs_ch)) break; } /* * When we reach the end of the word before the last * word, we're done. If we changed state, move forward * one to the end of the next word. */ if (cnt == 0) { if (cs.cs_flags == 0 && cs_next(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (cs_bblank(sp, &cs)) return (1); if (cs.cs_flags == CS_SOF) goto ret; } else while (cnt--) { state = cs.cs_flags == 0 && inword(cs.cs_ch) ? INWORD : NOTWORD; for (;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags == CS_SOF) goto ret; if (cs.cs_flags != 0 || ISBLANK(cs.cs_ch)) break; if (state == INWORD) { if (!inword(cs.cs_ch)) break; } else if (inword(cs.cs_ch)) break; } /* See comment above. */ if (cnt == 0) { if (cs.cs_flags == 0 && cs_next(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (cs.cs_flags != 0 || ISBLANK(cs.cs_ch)) if (cs_bblank(sp, &cs)) return (1); if (cs.cs_flags == CS_SOF) goto ret; } /* If we didn't move, we must be at SOF. */ ret: if (cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) { v_sof(sp, &vp->m_start); return (1); } /* Set the end of the range for motion commands. */ vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * All commands move to the end of the range. Motion commands * adjust the starting point to the character before the current * one. * * !!! * The historic vi didn't get this right -- the `yb' command yanked * the right stuff and even updated the cursor value, but the cursor * was not actually updated on the screen. */ vp->m_final = vp->m_stop; if (ISMOTION(vp)) --vp->m_start.cno; return (0); } Index: head/contrib/nvi/vi/v_xchar.c =================================================================== --- head/contrib/nvi/vi/v_xchar.c (revision 365498) +++ head/contrib/nvi/vi/v_xchar.c (revision 365499) @@ -1,103 +1,99 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_xchar.c,v 10.10 2001/06/25 15:19:36 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_xchar -- [buffer] [count]x * Deletes the character(s) on which the cursor sits. * * PUBLIC: int v_xchar(SCR *, VICMD *); */ int v_xchar(SCR *sp, VICMD *vp) { size_t len; int isempty; if (db_eget(sp, vp->m_start.lno, NULL, &len, &isempty)) { if (isempty) goto nodel; return (1); } if (len == 0) { nodel: msgq(sp, M_BERR, "206|No characters to delete"); return (1); } /* * Delete from the cursor toward the end of line, w/o moving the * cursor. * * !!! * Note, "2x" at EOL isn't the same as "xx" because the left movement * of the cursor as part of the 'x' command isn't taken into account. * Historically correct. */ if (F_ISSET(vp, VC_C1SET)) vp->m_stop.cno += vp->count - 1; if (vp->m_stop.cno >= len - 1) { vp->m_stop.cno = len - 1; vp->m_final.cno = vp->m_start.cno ? vp->m_start.cno - 1 : 0; } else vp->m_final.cno = vp->m_start.cno; if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, 0)) return (1); return (del(sp, &vp->m_start, &vp->m_stop, 0)); } /* * v_Xchar -- [buffer] [count]X * Deletes the character(s) immediately before the current cursor * position. * * PUBLIC: int v_Xchar(SCR *, VICMD *); */ int v_Xchar(SCR *sp, VICMD *vp) { u_long cnt; if (vp->m_start.cno == 0) { v_sol(sp); return (1); } cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; if (cnt >= vp->m_start.cno) vp->m_start.cno = 0; else vp->m_start.cno -= cnt; --vp->m_stop.cno; vp->m_final.cno = vp->m_start.cno; if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, 0)) return (1); return (del(sp, &vp->m_start, &vp->m_stop, 0)); } Index: head/contrib/nvi/vi/v_yank.c =================================================================== --- head/contrib/nvi/vi/v_yank.c (revision 365498) +++ head/contrib/nvi/vi/v_yank.c (revision 365499) @@ -1,80 +1,76 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_yank.c,v 10.10 2001/06/25 15:19:36 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_yank -- [buffer][count]y[count][motion] * [buffer][count]Y * Yank text (or lines of text) into a cut buffer. * * !!! * Historic vi moved the cursor to the from MARK if it was before the current * cursor and on a different line, e.g., "yk" moves the cursor but "yj" and * "yl" do not. Unfortunately, it's too late to change this now. Matching * the historic semantics isn't easy. The line number was always changed and * column movement was usually relative. However, "y'a" moved the cursor to * the first non-blank of the line marked by a, while "y`a" moved the cursor * to the line and column marked by a. Hopefully, the motion component code * got it right... Unlike delete, we make no adjustments here. * * PUBLIC: int v_yank(SCR *, VICMD *); */ int v_yank(SCR *sp, VICMD *vp) { size_t len; if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0)) return (1); sp->rptlines[L_YANKED] += (vp->m_stop.lno - vp->m_start.lno) + 1; /* * One special correction, in case we've deleted the current line or * character. We check it here instead of checking in every command * that can be a motion component. */ if (db_get(sp, vp->m_final.lno, DBG_FATAL, NULL, &len)) return (1); /* * !!! * Cursor movements, other than those caused by a line mode command * moving to another line, historically reset the relative position. * * This currently matches the check made in v_delete(), I'm hoping * that they should be consistent... */ if (!F_ISSET(vp, VM_LMODE)) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SET); /* Make sure the set cursor position exists. */ if (vp->m_final.cno >= len) vp->m_final.cno = len ? len - 1 : 0; } return (0); } Index: head/contrib/nvi/vi/v_z.c =================================================================== --- head/contrib/nvi/vi/v_z.c (revision 365498) +++ head/contrib/nvi/vi/v_z.c (revision 365499) @@ -1,145 +1,141 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_z.c,v 10.13 2011/12/02 17:26:59 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_z -- [count]z[count][-.+^] * Move the screen. * * PUBLIC: int v_z(SCR *, VICMD *); */ int v_z(SCR *sp, VICMD *vp) { recno_t lno; e_key_t value; /* * The first count is the line to use. If the value doesn't * exist, use the last line. */ if (F_ISSET(vp, VC_C1SET)) { lno = vp->count; if (!db_exist(sp, lno) && db_last(sp, &lno)) return (1); } else lno = vp->m_start.lno; /* Set default return cursor line. */ vp->m_final.lno = lno; vp->m_final.cno = vp->m_start.cno; /* * The second count is the displayed window size, i.e. the 'z' command * is another way to get artificially small windows. Note, you can't * grow beyond the size of the window. * * !!! * A window size of 0 was historically allowed, and simply ignored. * This could be much more simply done by modifying the value of the * O_WINDOW option, but that's not how it worked historically. */ if (F_ISSET(vp, VC_C2SET) && vp->count2 != 0) { if (vp->count2 > O_VAL(sp, O_WINDOW)) vp->count2 = O_VAL(sp, O_WINDOW); if (vs_crel(sp, vp->count2)) return (1); } switch (vp->character) { case '-': /* Put the line at the bottom. */ if (vs_sm_fill(sp, lno, P_BOTTOM)) return (1); break; case '.': /* Put the line in the middle. */ if (vs_sm_fill(sp, lno, P_MIDDLE)) return (1); break; case '+': /* * If the user specified a line number, put that line at the * top and move the cursor to it. Otherwise, scroll forward * a screen from the current screen. */ if (F_ISSET(vp, VC_C1SET)) { if (vs_sm_fill(sp, lno, P_TOP)) return (1); if (vs_sm_position(sp, &vp->m_final, 0, P_TOP)) return (1); } else if (vs_sm_scroll(sp, &vp->m_final, sp->t_rows, Z_PLUS)) return (1); break; case '^': /* * If the user specified a line number, put that line at the * bottom, move the cursor to it, and then display the screen * before that one. Otherwise, scroll backward a screen from * the current screen. * * !!! * Note, we match the off-by-one characteristics of historic * vi, here. */ if (F_ISSET(vp, VC_C1SET)) { if (vs_sm_fill(sp, lno, P_BOTTOM)) return (1); if (vs_sm_position(sp, &vp->m_final, 0, P_TOP)) return (1); if (vs_sm_fill(sp, vp->m_final.lno, P_BOTTOM)) return (1); } else if (vs_sm_scroll(sp, &vp->m_final, sp->t_rows, Z_CARAT)) return (1); break; default: /* Put the line at the top for . */ value = KEY_VAL(sp, vp->character); if (value != K_CR && value != K_NL) { v_emsg(sp, vp->kp->usage, VIM_USAGE); return (1); } if (vs_sm_fill(sp, lno, P_TOP)) return (1); break; } return (0); } /* * vs_crel -- * Change the relative size of the current screen. * * PUBLIC: int vs_crel(SCR *, long); */ int vs_crel(SCR *sp, long int count) { sp->t_minrows = sp->t_rows = count; if (sp->t_rows > sp->rows - 1) sp->t_minrows = sp->t_rows = sp->rows - 1; TMAP = HMAP + (sp->t_rows - 1); F_SET(sp, SC_SCR_REDRAW); return (0); } Index: head/contrib/nvi/vi/v_zexit.c =================================================================== --- head/contrib/nvi/vi/v_zexit.c (revision 365498) +++ head/contrib/nvi/vi/v_zexit.c (revision 365499) @@ -1,52 +1,48 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: v_zexit.c,v 10.7 2001/06/25 15:19:37 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_zexit -- ZZ * Save the file and exit. * * PUBLIC: int v_zexit(SCR *, VICMD *); */ int v_zexit(SCR *sp, VICMD *vp) { /* Write back any modifications. */ if (F_ISSET(sp->ep, F_MODIFIED) && file_write(sp, NULL, NULL, NULL, FS_ALL)) return (1); /* Check to make sure it's not a temporary file. */ if (file_m3(sp, 0)) return (1); /* Check for more files to edit. */ if (ex_ncheck(sp, 0)) return (1); F_SET(sp, SC_EXIT); return (0); } Index: head/contrib/nvi/vi/vi.c =================================================================== --- head/contrib/nvi/vi/vi.c (revision 365498) +++ head/contrib/nvi/vi/vi.c (revision 365499) @@ -1,1246 +1,1240 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: vi.c,v 10.61 2011/12/21 13:08:30 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" typedef enum { GC_ERR, GC_ERR_NOFLUSH, GC_EVENT, GC_FATAL, GC_INTERRUPT, GC_OK } gcret_t; static VIKEYS const *v_alias(SCR *, VICMD *, VIKEYS const *); static gcret_t v_cmd(SCR *, VICMD *, VICMD *, VICMD *, int *, int *); static int v_count(SCR *, ARG_CHAR_T, u_long *); static void v_dtoh(SCR *); static int v_init(SCR *); static gcret_t v_key(SCR *, int, EVENT *, u_int32_t); static int v_motion(SCR *, VICMD *, VICMD *, int *); #if defined(DEBUG) && defined(COMLOG) static void v_comlog(SCR *, VICMD *); #endif /* * Side-effect: * The dot structure can be set by the underlying vi functions, * see v_Put() and v_put(). */ #define DOT (&VIP(sp)->sdot) #define DOTMOTION (&VIP(sp)->sdotmotion) /* * vi -- * Main vi command loop. * * PUBLIC: int vi(SCR **); */ int vi(SCR **spp) { GS *gp; MARK abs; SCR *next, *sp; VICMD cmd = { 0 }, *vp; VI_PRIVATE *vip; int comcount, mapped, rval; /* Get the first screen. */ sp = *spp; gp = sp->gp; /* Point to the command structure. */ vp = &cmd; /* Reset strange attraction. */ F_SET(vp, VM_RCM_SET); /* Initialize the vi screen. */ if (v_init(sp)) return (1); /* Set the focus. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); for (vip = VIP(sp), rval = 0;;) { /* Resolve messages. */ if (!MAPPED_KEYS_WAITING(sp) && vs_resolve(sp, NULL, 0)) goto ret; /* * If not skipping a refresh, return to command mode and * refresh the screen. */ if (F_ISSET(vip, VIP_S_REFRESH)) F_CLR(vip, VIP_S_REFRESH); else { sp->showmode = SM_COMMAND; if (vs_refresh(sp, 0)) goto ret; } /* Set the new favorite position. */ if (F_ISSET(vp, VM_RCM_SET | VM_RCM_SETFNB | VM_RCM_SETNNB)) { F_CLR(vip, VIP_RCM_LAST); (void)vs_column(sp, &sp->rcm); } /* * If not currently in a map, log the cursor position, * and set a flag so that this command can become the * DOT command. */ if (MAPPED_KEYS_WAITING(sp)) mapped = 1; else { if (log_cursor(sp)) goto err; mapped = 0; } /* * There may be an ex command waiting, and we returned here * only because we exited a screen or file. In this case, * we simply go back into the ex parser. */ if (EXCMD_RUNNING(gp)) { vp->kp = &vikeys[':']; goto ex_continue; } /* Refresh the command structure. */ memset(vp, 0, sizeof(VICMD)); /* * We get a command, which may or may not have an associated * motion. If it does, we get it too, calling its underlying * function to get the resulting mark. We then call the * command setting the cursor to the resulting mark. * * !!! * Vi historically flushed mapped characters on error, but * entering extra characters at the beginning of * a map wasn't considered an error -- in fact, users would * put leading characters in maps to clean up vi * state before the map was interpreted. Beauty! */ switch (v_cmd(sp, DOT, vp, NULL, &comcount, &mapped)) { case GC_ERR: goto err; case GC_ERR_NOFLUSH: goto gc_err_noflush; case GC_EVENT: goto gc_event; case GC_FATAL: goto ret; case GC_INTERRUPT: goto intr; case GC_OK: break; } /* Check for security setting. */ if (F_ISSET(vp->kp, V_SECURE) && O_ISSET(sp, O_SECURE)) { ex_emsg(sp, KEY_NAME(sp, vp->key), EXM_SECURE); goto err; } /* * Historical practice: if a dot command gets a new count, * any motion component goes away, i.e. "d3w2." deletes a * total of 5 words. */ if (F_ISSET(vp, VC_ISDOT) && comcount) DOTMOTION->count = 1; /* Copy the key flags into the local structure. */ F_SET(vp, vp->kp->flags); /* Prepare to set the previous context. */ if (F_ISSET(vp, V_ABS | V_ABS_C | V_ABS_L)) { abs.lno = sp->lno; abs.cno = sp->cno; } /* * Set the three cursor locations to the current cursor. The * underlying routines don't bother if the cursor doesn't move. * This also handles line commands (e.g. Y) defaulting to the * current line. */ vp->m_start.lno = vp->m_stop.lno = vp->m_final.lno = sp->lno; vp->m_start.cno = vp->m_stop.cno = vp->m_final.cno = sp->cno; /* * Do any required motion; v_motion sets the from MARK and the * line mode flag, as well as the VM_RCM flags. */ if (F_ISSET(vp, V_MOTION) && v_motion(sp, DOTMOTION, vp, &mapped)) { if (INTERRUPTED(sp)) goto intr; goto err; } /* * If a count is set and the command is line oriented, set the * to MARK here relative to the cursor/from MARK. This is for * commands that take both counts and motions, i.e. "4yy" and * "y%". As there's no way the command can know which the user * did, we have to do it here. (There are commands that are * line oriented and that take counts ("#G", "#H"), for which * this calculation is either completely meaningless or wrong. * Each command must validate the value for itself. */ if (F_ISSET(vp, VC_C1SET) && F_ISSET(vp, VM_LMODE)) vp->m_stop.lno += vp->count - 1; /* Increment the command count. */ ++sp->ccnt; #if defined(DEBUG) && defined(COMLOG) v_comlog(sp, vp); #endif /* Call the function. */ ex_continue: if (vp->kp->func(sp, vp)) goto err; gc_event: #ifdef DEBUG /* Make sure no function left the temporary space locked. */ if (F_ISSET(gp, G_TMP_INUSE)) { F_CLR(gp, G_TMP_INUSE); msgq(sp, M_ERR, "232|vi: temporary buffer not released"); } #endif /* * If we're exiting this screen, move to the next one, or, if * there aren't any more, return to the main editor loop. The * ordering is careful, don't discard the contents of sp until * the end. */ if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (file_end(sp, NULL, F_ISSET(sp, SC_EXIT_FORCE))) goto ret; if (vs_discard(sp, &next)) goto ret; if (next == NULL && vs_swap(sp, &next, NULL)) goto ret; *spp = next; if (screen_end(sp)) goto ret; if (next == NULL) break; /* Switch screens, change focus. */ sp = next; vip = VIP(sp); (void)sp->gp->scr_rename(sp, sp->frp->name, 1); /* Don't trust the cursor. */ F_SET(vip, VIP_CUR_INVALID); continue; } /* * Set the dot command structure. * * !!! * Historically, commands which used mapped keys did not * set the dot command, with the exception of the text * input commands. */ if (F_ISSET(vp, V_DOT) && !mapped) { *DOT = cmd; F_SET(DOT, VC_ISDOT); /* * If a count was supplied for both the command and * its motion, the count was used only for the motion. * Turn the count back on for the dot structure. */ if (F_ISSET(vp, VC_C1RESET)) F_SET(DOT, VC_C1SET); /* VM flags aren't retained. */ F_CLR(DOT, VM_COMMASK | VM_RCM_MASK); } /* * Some vi row movements are "attracted" to the last position * set, i.e. the VM_RCM commands are moths to the VM_RCM_SET * commands' candle. If the movement is to the EOL the vi * command handles it. If it's to the beginning, we handle it * here. * * Note, some commands (e.g. _, ^) don't set the VM_RCM_SETFNB * flag, but do the work themselves. The reason is that they * have to modify the column in case they're being used as a * motion component. Other similar commands (e.g. +, -) don't * have to modify the column because they are always line mode * operations when used as motions, so the column number isn't * of any interest. * * Does this totally violate the screen and editor layering? * You betcha. As they say, if you think you understand it, * you don't. */ switch (F_ISSET(vp, VM_RCM_MASK)) { case 0: case VM_RCM_SET: break; case VM_RCM: vp->m_final.cno = vs_rcm(sp, vp->m_final.lno, F_ISSET(vip, VIP_RCM_LAST)); break; case VM_RCM_SETLAST: F_SET(vip, VIP_RCM_LAST); break; case VM_RCM_SETFNB: vp->m_final.cno = 0; /* FALLTHROUGH */ case VM_RCM_SETNNB: if (nonblank(sp, vp->m_final.lno, &vp->m_final.cno)) goto err; break; default: abort(); } /* Update the cursor. */ sp->lno = vp->m_final.lno; sp->cno = vp->m_final.cno; /* * Set the absolute mark -- set even if a tags or similar * command, since the tag may be moving to the same file. */ if ((F_ISSET(vp, V_ABS) || (F_ISSET(vp, V_ABS_L) && sp->lno != abs.lno) || (F_ISSET(vp, V_ABS_C) && (sp->lno != abs.lno || sp->cno != abs.cno))) && mark_set(sp, ABSMARK1, &abs, 1)) goto err; if (0) { err: if (v_event_flush(sp, CH_MAPPED)) msgq(sp, M_BERR, "110|Vi command failed: mapped keys discarded"); } /* * Check and clear interrupts. There's an obvious race, but * it's not worth fixing. */ gc_err_noflush: if (INTERRUPTED(sp)) { intr: CLR_INTERRUPT(sp); if (v_event_flush(sp, CH_MAPPED)) msgq(sp, M_ERR, "231|Interrupted: mapped keys discarded"); else msgq(sp, M_ERR, "236|Interrupted"); } /* If the last command switched screens, update. */ if (F_ISSET(sp, SC_SSWITCH)) { F_CLR(sp, SC_SSWITCH); /* * If the current screen is still displayed, it will * need a new status line. */ F_SET(sp, SC_STATUS); /* Switch screens, change focus. */ sp = sp->nextdisp; vip = VIP(sp); (void)sp->gp->scr_rename(sp, sp->frp->name, 1); /* Don't trust the cursor. */ F_SET(vip, VIP_CUR_INVALID); /* Refresh so we can display messages. */ if (vs_refresh(sp, 1)) return (1); } /* If the last command switched files, change focus. */ if (F_ISSET(sp, SC_FSWITCH)) { F_CLR(sp, SC_FSWITCH); (void)sp->gp->scr_rename(sp, sp->frp->name, 1); } /* If leaving vi, return to the main editor loop. */ if (F_ISSET(gp, G_SRESTART) || F_ISSET(sp, SC_EX)) { *spp = sp; v_dtoh(sp); gp->scr_discard(sp, NULL); break; } } if (0) ret: rval = 1; return (rval); } #define KEY(key, ec_flags) { \ if ((gcret = v_key(sp, 0, &ev, ec_flags)) != GC_OK) \ return (gcret); \ if (ev.e_value == K_ESCAPE) \ goto esc; \ if (F_ISSET(&ev.e_ch, CH_MAPPED)) \ *mappedp = 1; \ key = ev.e_c; \ } /* * The O_TILDEOP option makes the ~ command take a motion instead * of a straight count. This is the replacement structure we use * instead of the one currently in the VIKEYS table. * * XXX * This should probably be deleted -- it's not all that useful, and * we get help messages wrong. */ VIKEYS const tmotion = { v_mulcase, V_CNT|V_DOT|V_MOTION|VM_RCM_SET, "[count]~[count]motion", " ~ change case to motion" }; /* * v_cmd -- * * The command structure for vi is less complex than ex (and don't think * I'm not grateful!) The command syntax is: * * [count] [buffer] [count] key [[motion] | [buffer] [character]] * * and there are several special cases. The motion value is itself a vi * command, with the syntax: * * [count] key [character] */ static gcret_t v_cmd( SCR *sp, VICMD *dp, VICMD *vp, VICMD *ismotion, /* Previous key if getting motion component. */ int *comcountp, int *mappedp) { enum { COMMANDMODE, ISPARTIAL, NOTPARTIAL } cpart; EVENT ev; VIKEYS const *kp; gcret_t gcret; u_int flags; CHAR_T key; char *s; /* * Get a key. * * cancels partial commands, i.e. a command where at least * one non-numeric character has been entered. Otherwise, it beeps * the terminal. * * !!! * POSIX 1003.2-1992 explicitly disallows cancelling commands where * all that's been entered is a number, requiring that the terminal * be alerted. */ cpart = ismotion == NULL ? COMMANDMODE : ISPARTIAL; if ((gcret = v_key(sp, ismotion == NULL, &ev, EC_MAPCOMMAND)) != GC_OK) { if (gcret == GC_EVENT) vp->ev = ev; return (gcret); } if (ev.e_value == K_ESCAPE) goto esc; if (F_ISSET(&ev.e_ch, CH_MAPPED)) *mappedp = 1; key = ev.e_c; if (ismotion == NULL) cpart = NOTPARTIAL; /* Pick up an optional buffer. */ if (key == '"') { cpart = ISPARTIAL; if (ismotion != NULL) { v_emsg(sp, NULL, VIM_COMBUF); return (GC_ERR); } KEY(vp->buffer, 0); F_SET(vp, VC_BUFFER); KEY(key, EC_MAPCOMMAND); } /* * Pick up an optional count, where a leading 0 is not a count, * it's a command. */ if (ISDIGIT(key) && key != '0') { if (v_count(sp, key, &vp->count)) return (GC_ERR); F_SET(vp, VC_C1SET); *comcountp = 1; KEY(key, EC_MAPCOMMAND); } else *comcountp = 0; /* Pick up optional buffer. */ if (key == '"') { cpart = ISPARTIAL; if (F_ISSET(vp, VC_BUFFER)) { msgq(sp, M_ERR, "234|Only one buffer may be specified"); return (GC_ERR); } if (ismotion != NULL) { v_emsg(sp, NULL, VIM_COMBUF); return (GC_ERR); } KEY(vp->buffer, 0); F_SET(vp, VC_BUFFER); KEY(key, EC_MAPCOMMAND); } /* Check for an OOB command key. */ cpart = ISPARTIAL; if (key > MAXVIKEY) { v_emsg(sp, KEY_NAME(sp, key), VIM_NOCOM); return (GC_ERR); } kp = &vikeys[vp->key = key]; /* * !!! * Historically, D accepted and then ignored a count. Match it. */ if (vp->key == 'D' && F_ISSET(vp, VC_C1SET)) { *comcountp = 0; vp->count = 0; F_CLR(vp, VC_C1SET); } /* Check for command aliases. */ if (kp->func == NULL && (kp = v_alias(sp, vp, kp)) == NULL) return (GC_ERR); /* The tildeop option makes the ~ command take a motion. */ if (key == '~' && O_ISSET(sp, O_TILDEOP)) kp = &tmotion; vp->kp = kp; /* * Find the command. The only legal command with no underlying * function is dot. It's historic practice that doesn't * just erase the preceding number, it beeps the terminal as well. * It's a common problem, so just beep the terminal unless verbose * was set. */ if (kp->func == NULL) { if (key != '.') { v_emsg(sp, KEY_NAME(sp, key), ev.e_value == K_ESCAPE ? VIM_NOCOM_B : VIM_NOCOM); return (GC_ERR); } /* If called for a motion command, stop now. */ if (dp == NULL) goto usage; /* * !!! * If a '.' is immediately entered after an undo command, we * replay the log instead of redoing the last command. This * is necessary because 'u' can't set the dot command -- see * vi/v_undo.c:v_undo for details. */ if (VIP(sp)->u_ccnt == sp->ccnt) { vp->kp = &vikeys['u']; F_SET(vp, VC_ISDOT); return (GC_OK); } /* Otherwise, a repeatable command must have been executed. */ if (!F_ISSET(dp, VC_ISDOT)) { msgq(sp, M_ERR, "208|No command to repeat"); return (GC_ERR); } /* Set new count/buffer, if any, and return. */ if (F_ISSET(vp, VC_C1SET)) { F_SET(dp, VC_C1SET); dp->count = vp->count; } if (F_ISSET(vp, VC_BUFFER)) dp->buffer = vp->buffer; *vp = *dp; return (GC_OK); } /* Set the flags based on the command flags. */ flags = kp->flags; /* Check for illegal count. */ if (F_ISSET(vp, VC_C1SET) && !LF_ISSET(V_CNT)) goto usage; /* Illegal motion command. */ if (ismotion == NULL) { /* Illegal buffer. */ if (!LF_ISSET(V_OBUF) && F_ISSET(vp, VC_BUFFER)) goto usage; /* Required buffer. */ if (LF_ISSET(V_RBUF)) { KEY(vp->buffer, 0); F_SET(vp, VC_BUFFER); } } /* * Special case: '[', ']' and 'Z' commands. Doesn't the fact that * the *single* characters don't mean anything but the *doubled* * characters do, just frost your shorts? */ if (vp->key == '[' || vp->key == ']' || vp->key == 'Z') { /* * Historically, half entered [[, ]] or Z commands weren't * cancelled by , the terminal was beeped instead. * POSIX.2-1992 probably didn't notice, and requires that * they be cancelled instead of beeping. Seems fine to me. * * Don't set the EC_MAPCOMMAND flag, apparently ] is a popular * vi meta-character, and we don't want the user to wait while * we time out a possible mapping. This *appears* to match * historic vi practice, but with mapping characters, You Just * Never Know. */ KEY(key, 0); if (vp->key != key) { usage: if (ismotion == NULL) s = kp->usage; else if (ismotion->key == '~' && O_ISSET(sp, O_TILDEOP)) s = tmotion.usage; else s = vikeys[ismotion->key].usage; v_emsg(sp, s, VIM_USAGE); return (GC_ERR); } } /* Special case: 'z' command. */ if (vp->key == 'z') { KEY(vp->character, 0); if (ISDIGIT(vp->character)) { if (v_count(sp, vp->character, &vp->count2)) return (GC_ERR); F_SET(vp, VC_C2SET); KEY(vp->character, 0); } } /* * Commands that have motion components can be doubled to imply the * current line. */ if (ismotion != NULL && ismotion->key != key && !LF_ISSET(V_MOVE)) { msgq(sp, M_ERR, "210|%s may not be used as a motion command", KEY_NAME(sp, key)); return (GC_ERR); } /* Pick up required trailing character. */ if (LF_ISSET(V_CHAR)) KEY(vp->character, 0); /* Get any associated cursor word. */ if (F_ISSET(kp, V_KEYW) && v_curword(sp)) return (GC_ERR); return (GC_OK); esc: switch (cpart) { case COMMANDMODE: msgq(sp, M_BERR, "211|Already in command mode"); return (GC_ERR_NOFLUSH); case ISPARTIAL: break; case NOTPARTIAL: (void)sp->gp->scr_bell(sp); break; } return (GC_ERR); } /* * v_motion -- * * Get resulting motion mark. */ static int v_motion( SCR *sp, VICMD *dm, VICMD *vp, int *mappedp) { VICMD motion; size_t len; u_long cnt; u_int flags; int tilde_reset, notused; /* * If '.' command, use the dot motion, else get the motion command. * Clear any line motion flags, the subsequent motion isn't always * the same, i.e. "/aaa" may or may not be a line motion. */ if (F_ISSET(vp, VC_ISDOT)) { motion = *dm; F_SET(&motion, VC_ISDOT); F_CLR(&motion, VM_COMMASK); } else { memset(&motion, 0, sizeof(VICMD)); if (v_cmd(sp, NULL, &motion, vp, ¬used, mappedp) != GC_OK) return (1); } /* * A count may be provided both to the command and to the motion, in * which case the count is multiplicative. For example, "3y4y" is the * same as "12yy". This count is provided to the motion command and * not to the regular function. */ cnt = motion.count = F_ISSET(&motion, VC_C1SET) ? motion.count : 1; if (F_ISSET(vp, VC_C1SET)) { motion.count *= vp->count; F_SET(&motion, VC_C1SET); /* * Set flags to restore the original values of the command * structure so dot commands can change the count values, * e.g. "2dw" "3." deletes a total of five words. */ F_CLR(vp, VC_C1SET); F_SET(vp, VC_C1RESET); } /* * Some commands can be repeated to indicate the current line. In * this case, or if the command is a "line command", set the flags * appropriately. If not a doubled command, run the function to get * the resulting mark. */ if (vp->key == motion.key) { F_SET(vp, VM_LDOUBLE | VM_LMODE); /* Set the origin of the command. */ vp->m_start.lno = sp->lno; vp->m_start.cno = 0; /* * Set the end of the command. * * If the current line is missing, i.e. the file is empty, * historic vi permitted a "cc" or "!!" command to insert * text. */ vp->m_stop.lno = sp->lno + motion.count - 1; if (db_get(sp, vp->m_stop.lno, 0, NULL, &len)) { if (vp->m_stop.lno != 1 || (vp->key != 'c' && vp->key != '!')) { v_emsg(sp, NULL, VIM_EMPTY); return (1); } vp->m_stop.cno = 0; } else vp->m_stop.cno = len ? len - 1 : 0; } else { /* * Motion commands change the underlying movement (*snarl*). * For example, "l" is illegal at the end of a line, but "dl" * is not. Set flags so the function knows the situation. */ motion.rkp = vp->kp; /* * XXX * Use yank instead of creating a new motion command, it's a * lot easier for now. */ if (vp->kp == &tmotion) { tilde_reset = 1; vp->kp = &vikeys['y']; } else tilde_reset = 0; /* * Copy the key flags into the local structure, except for the * RCM flags -- the motion command will set the RCM flags in * the vp structure if necessary. This means that the motion * command is expected to determine where the cursor ends up! * However, we save off the current RCM mask and restore it if * it no RCM flags are set by the motion command, with a small * modification. * * We replace the VM_RCM_SET flag with the VM_RCM flag. This * is so that cursor movement doesn't set the relative position * unless the motion command explicitly specified it. This * appears to match historic practice, but I've never been able * to develop a hard-and-fast rule. */ flags = F_ISSET(vp, VM_RCM_MASK); if (LF_ISSET(VM_RCM_SET)) { LF_SET(VM_RCM); LF_CLR(VM_RCM_SET); } F_CLR(vp, VM_RCM_MASK); F_SET(&motion, motion.kp->flags & ~VM_RCM_MASK); /* * Set the three cursor locations to the current cursor. This * permits commands like 'j' and 'k', that are line oriented * motions and have special cursor suck semantics when they are * used as standalone commands, to ignore column positioning. */ motion.m_final.lno = motion.m_stop.lno = motion.m_start.lno = sp->lno; motion.m_final.cno = motion.m_stop.cno = motion.m_start.cno = sp->cno; /* Run the function. */ if ((motion.kp->func)(sp, &motion)) return (1); /* * If the current line is missing, i.e. the file is empty, * historic vi allowed "c" or "!" to insert * text. Otherwise fail -- most motion commands will have * already failed, but some, e.g. G, succeed in empty files. */ if (!db_exist(sp, vp->m_stop.lno)) { if (vp->m_stop.lno != 1 || (vp->key != 'c' && vp->key != '!')) { v_emsg(sp, NULL, VIM_EMPTY); return (1); } vp->m_stop.cno = 0; } /* * XXX * See above. */ if (tilde_reset) vp->kp = &tmotion; /* * Copy cut buffer, line mode and cursor position information * from the motion command structure, i.e. anything that the * motion command can set for us. The commands can flag the * movement as a line motion (see v_sentence) as well as set * the VM_RCM_* flags explicitly. */ F_SET(vp, F_ISSET(&motion, VM_COMMASK | VM_RCM_MASK)); /* * If the motion command set no relative motion flags, use * the (slightly) modified previous values. */ if (!F_ISSET(vp, VM_RCM_MASK)) F_SET(vp, flags); /* * Commands can change behaviors based on the motion command * used, for example, the ! command repeated the last bang * command if N or n was used as the motion. */ vp->rkp = motion.kp; /* * Motion commands can reset all of the cursor information. * If the motion is in the reverse direction, switch the * from and to MARK's so that it's in a forward direction. * Motions are from the from MARK to the to MARK (inclusive). */ if (motion.m_start.lno > motion.m_stop.lno || (motion.m_start.lno == motion.m_stop.lno && motion.m_start.cno > motion.m_stop.cno)) { vp->m_start = motion.m_stop; vp->m_stop = motion.m_start; } else { vp->m_start = motion.m_start; vp->m_stop = motion.m_stop; } vp->m_final = motion.m_final; } /* * If the command sets dot, save the motion structure. The motion * count was changed above and needs to be reset, that's why this * is done here, and not in the calling routine. */ if (F_ISSET(vp->kp, V_DOT)) { *dm = motion; dm->count = cnt; } return (0); } /* * v_init -- * Initialize the vi screen. */ static int v_init(SCR *sp) { GS *gp; VI_PRIVATE *vip; gp = sp->gp; vip = VIP(sp); /* Switch into vi. */ if (gp->scr_screen(sp, SC_VI)) return (1); (void)gp->scr_attr(sp, SA_ALTERNATE, 1); F_CLR(sp, SC_EX | SC_SCR_EX); F_SET(sp, SC_VI); /* * Initialize screen values. * * Small windows: see vs_refresh(), section 6a. * * Setup: * t_minrows is the minimum rows to display * t_maxrows is the maximum rows to display (rows - 1) * t_rows is the rows currently being displayed */ sp->rows = vip->srows = O_VAL(sp, O_LINES); sp->cols = O_VAL(sp, O_COLUMNS); sp->t_rows = sp->t_minrows = O_VAL(sp, O_WINDOW); if (sp->rows != 1) { if (sp->t_rows > sp->rows - 1) { sp->t_minrows = sp->t_rows = sp->rows - 1; msgq(sp, M_INFO, "214|Windows option value is too large, max is %u", (u_int)sp->t_rows); } sp->t_maxrows = sp->rows - 1; } else sp->t_maxrows = 1; sp->roff = sp->coff = 0; /* Create a screen map. */ - CALLOC_RET(sp, HMAP, SMAP *, SIZE_HMAP(sp), sizeof(SMAP)); + CALLOC_RET(sp, HMAP, SIZE_HMAP(sp), sizeof(SMAP)); TMAP = HMAP + (sp->t_rows - 1); HMAP->lno = sp->lno; HMAP->coff = 0; HMAP->soff = 1; /* * Fill the screen map from scratch -- try and center the line. That * way if we're starting with a file we've seen before, we'll put the * line in the middle, otherwise, it won't work and we'll end up with * the line at the top. */ F_SET(sp, SC_SCR_REFORMAT | SC_SCR_CENTER); /* Invalidate the cursor. */ F_SET(vip, VIP_CUR_INVALID); /* Paint the screen image from scratch. */ F_SET(vip, VIP_N_EX_PAINT); return (0); } /* * v_dtoh -- * Move all but the current screen to the hidden queue. */ static void v_dtoh(SCR *sp) { GS *gp; SCR *tsp; int hidden; /* Move all screens to the hidden queue, tossing screen maps. */ for (hidden = 0, gp = sp->gp; (tsp = TAILQ_FIRST(gp->dq)) != NULL; ++hidden) { - if (_HMAP(tsp) != NULL) { - free(_HMAP(tsp)); - _HMAP(tsp) = NULL; - } + free(_HMAP(tsp)); + _HMAP(tsp) = NULL; TAILQ_REMOVE(gp->dq, tsp, q); TAILQ_INSERT_TAIL(gp->hq, tsp, q); /* XXXX Change if hidden screens per window */ gp->scr_discard(tsp, NULL); } /* Move current screen back to the display queue. */ TAILQ_REMOVE(gp->hq, sp, q); TAILQ_INSERT_TAIL(gp->dq, sp, q); if (hidden > 1) msgq(sp, M_INFO, "319|%d screens backgrounded; use :display to list them", hidden - 1); } /* * v_curword -- * Get the word (tagstring, actually) the cursor is on. * * PUBLIC: int v_curword(SCR *); */ int v_curword(SCR *sp) { VI_PRIVATE *vip; size_t beg, end, len; int moved; CHAR_T *p; if (db_get(sp, sp->lno, DBG_FATAL, &p, &len)) return (1); /* * !!! * Historically, tag commands skipped over any leading whitespace * characters. Make this true in general when using cursor words. * If movement, getting a cursor word implies moving the cursor to * its beginning. Refresh now. * * !!! * Find the beginning/end of the keyword. Keywords are currently * used for cursor-word searching and for tags. Historical vi * only used the word in a tag search from the cursor to the end * of the word, i.e. if the cursor was on the 'b' in " abc ", the * tag was "bc". For consistency, we make cursor word searches * follow the same rule. */ for (moved = 0, beg = sp->cno; beg < len && ISSPACE(p[beg]); moved = 1, ++beg); if (beg >= len) { msgq(sp, M_BERR, "212|Cursor not in a word"); return (1); } if (moved) { sp->cno = beg; (void)vs_refresh(sp, 0); } /* * Find the end of the word. * * !!! * Historically, vi accepted any non-blank as initial character * when building up a tagstring. Required by IEEE 1003.1-2001. */ for (end = beg; ++end < len && inword(p[end]);); vip = VIP(sp); vip->klen = len = (end - beg); BINC_RETW(sp, vip->keyw, vip->keywlen, len+1); MEMMOVE(vip->keyw, p + beg, len); vip->keyw[len] = '\0'; /* XXX */ return (0); } /* * v_alias -- * Check for a command alias. */ static VIKEYS const * v_alias( SCR *sp, VICMD *vp, VIKEYS const *kp) { CHAR_T push; switch (vp->key) { case 'C': /* C -> c$ */ push = '$'; vp->key = 'c'; break; case 'D': /* D -> d$ */ push = '$'; vp->key = 'd'; break; case 'S': /* S -> c_ */ push = '_'; vp->key = 'c'; break; case 'Y': /* Y -> y_ */ push = '_'; vp->key = 'y'; break; default: return (kp); } return (v_event_push(sp, NULL, &push, 1, CH_NOMAP | CH_QUOTED) ? NULL : &vikeys[vp->key]); } /* * v_count -- * Return the next count. */ static int v_count( SCR *sp, ARG_CHAR_T fkey, u_long *countp) { EVENT ev; u_long count, tc; ev.e_c = fkey; count = tc = 0; do { /* * XXX * Assume that overflow results in a smaller number. */ tc = count * 10 + ev.e_c - '0'; if (count > tc) { /* Toss to the next non-digit. */ do { if (v_key(sp, 0, &ev, EC_MAPCOMMAND | EC_MAPNODIGIT) != GC_OK) return (1); } while (ISDIGIT(ev.e_c)); msgq(sp, M_ERR, "235|Number larger than %lu", ULONG_MAX); return (1); } count = tc; if (v_key(sp, 0, &ev, EC_MAPCOMMAND | EC_MAPNODIGIT) != GC_OK) return (1); } while (ISDIGIT(ev.e_c)); *countp = count; return (0); } /* * v_key -- * Return the next event. */ static gcret_t v_key( SCR *sp, int command_events, EVENT *evp, u_int32_t ec_flags) { u_int32_t quote; for (quote = 0;;) { if (v_event_get(sp, evp, 0, ec_flags | quote)) return (GC_FATAL); quote = 0; switch (evp->e_event) { case E_CHARACTER: /* * !!! * Historically, ^V was ignored in the command stream, * although it had a useful side-effect of interrupting * mappings. Adding a quoting bit to the call probably * extends historic practice, but it feels right. */ if (evp->e_value == K_VLNEXT) { quote = EC_QUOTED; break; } return (GC_OK); case E_ERR: case E_EOF: return (GC_FATAL); case E_INTERRUPT: /* * !!! * Historically, vi beeped on command level interrupts. * * Historically, vi exited to ex mode if no file was * named on the command line, and two interrupts were * generated in a row. (Just figured you might want * to know that.) */ (void)sp->gp->scr_bell(sp); return (GC_INTERRUPT); case E_REPAINT: if (vs_repaint(sp, evp)) return (GC_FATAL); break; case E_WRESIZE: return (GC_ERR); default: v_event_err(sp, evp); return (GC_ERR); } } /* NOTREACHED */ } #if defined(DEBUG) && defined(COMLOG) /* * v_comlog -- * Log the contents of the command structure. */ static void v_comlog( SCR *sp, VICMD *vp) { TRACE(sp, "vcmd: "WC, vp->key); if (F_ISSET(vp, VC_BUFFER)) TRACE(sp, " buffer: "WC, vp->buffer); if (F_ISSET(vp, VC_C1SET)) TRACE(sp, " c1: %lu", vp->count); if (F_ISSET(vp, VC_C2SET)) TRACE(sp, " c2: %lu", vp->count2); TRACE(sp, " flags: 0x%x\n", vp->flags); } #endif Index: head/contrib/nvi/vi/vi.h =================================================================== --- head/contrib/nvi/vi/vi.h (revision 365498) +++ head/contrib/nvi/vi/vi.h (revision 365499) @@ -1,386 +1,384 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. - * - * $Id: vi.h,v 10.29 2012/02/11 00:33:46 zy Exp $ */ /* Definition of a vi "word". */ #define inword(ch) ((ch) == '_' || (ISGRAPH(ch) && !ISPUNCT(ch))) typedef struct _vikeys VIKEYS; /* Structure passed around to functions implementing vi commands. */ typedef struct _vicmd { CHAR_T key; /* Command key. */ CHAR_T buffer; /* Buffer. */ CHAR_T character; /* Character. */ u_long count; /* Count. */ u_long count2; /* Second count (only used by z). */ EVENT ev; /* Associated event. */ #define ISCMD(p, key) ((p) == &vikeys[key]) VIKEYS const *kp; /* Command/Motion VIKEYS entry. */ #define ISMOTION(vp) (vp->rkp != NULL && F_ISSET(vp->rkp, V_MOTION)) VIKEYS const *rkp; /* Related C/M VIKEYS entry. */ /* * Historic vi allowed "dl" when the cursor was on the last column, * deleting the last character, and similarly allowed "dw" when * the cursor was on the last column of the file. It didn't allow * "dh" when the cursor was on column 1, although these cases are * not strictly analogous. The point is that some movements would * succeed if they were associated with a motion command, and fail * otherwise. This is part of the off-by-1 schizophrenia that * plagued vi. Other examples are that "dfb" deleted everything * up to and including the next 'b' character, while "d/b" deleted * everything up to the next 'b' character. While this implementation * regularizes the interface to the extent possible, there are many * special cases that can't be fixed. The special cases are handled * by setting flags per command so that the underlying command and * motion routines know what's really going on. * * The VM_* flags are set in the vikeys array and by the underlying * functions (motion component or command) as well. For this reason, * the flags in the VICMD and VIKEYS structures live in the same name * space. */ #define VM_CMDFAILED 0x00000001 /* Command failed. */ #define VM_CUTREQ 0x00000002 /* Always cut into numeric buffers. */ #define VM_LDOUBLE 0x00000004 /* Doubled command for line mode. */ #define VM_LMODE 0x00000008 /* Motion is line oriented. */ #define VM_COMMASK 0x0000000f /* Mask for VM flags. */ /* * The VM_RCM_* flags are single usage, i.e. if you set one, you have * to clear the others. */ #define VM_RCM 0x00000010 /* Use relative cursor movment (RCM). */ #define VM_RCM_SET 0x00000020 /* RCM: set to current position. */ #define VM_RCM_SETFNB 0x00000040 /* RCM: set to first non-blank (FNB). */ #define VM_RCM_SETLAST 0x00000080 /* RCM: set to last character. */ #define VM_RCM_SETNNB 0x00000100 /* RCM: set to next non-blank. */ #define VM_RCM_MASK 0x000001f0 /* Mask for RCM flags. */ /* Flags for the underlying function. */ #define VC_BUFFER 0x00000200 /* The buffer was set. */ #define VC_C1RESET 0x00000400 /* Reset C1SET flag for dot commands. */ #define VC_C1SET 0x00000800 /* Count 1 was set. */ #define VC_C2SET 0x00001000 /* Count 2 was set. */ #define VC_ISDOT 0x00002000 /* Command was the dot command. */ u_int32_t flags; /* * There are four cursor locations that we worry about: the initial * cursor position, the start of the range, the end of the range, * and the final cursor position. The initial cursor position and * the start of the range are both m_start, and are always the same. * All locations are initialized to the starting cursor position by * the main vi routines, and the underlying functions depend on this. * * Commands that can be motion components set the end of the range * cursor position, m_stop. All commands must set the ending cursor * position, m_final. The reason that m_stop isn't the same as m_final * is that there are situations where the final position of the cursor * is outside of the cut/delete range (e.g. 'd[[' from the first column * of a line). The final cursor position often varies based on the * direction of the movement, as well as the command. The only special * case that the delete code handles is that it will make adjustments * if the final cursor position is deleted. * * The reason for all of this is that the historic vi semantics were * defined command-by-command. Every function has to roll its own * starting and stopping positions, and adjust them if it's being used * as a motion component. The general rules are as follows: * * 1: If not a motion component, the final cursor is at the end * of the range. * 2: If moving backward in the file, delete and yank move the * final cursor to the end of the range. * 3: If moving forward in the file, delete and yank leave the * final cursor at the start of the range. * * Usually, if moving backward in the file and it's a motion component, * the starting cursor is decremented by a single character (or, in a * few cases, to the end of the previous line) so that the starting * cursor character isn't cut or deleted. No cursor adjustment is * needed for moving forward, because the cut/delete routines handle * m_stop inclusively, i.e. the last character in the range is cut or * deleted. This makes cutting to the EOF/EOL reasonable. * * The 'c', '<', '>', and '!' commands are special cases. We ignore * the final cursor position for all of them: for 'c', the text input * routines set the cursor to the last character inserted; for '<', * '>' and '!', the underlying ex commands that do the operation will * set the cursor for us, usually to something related to the first * . */ MARK m_start; /* mark: initial cursor, range start. */ MARK m_stop; /* mark: range end. */ MARK m_final; /* mark: final cursor position. */ } VICMD; /* Vi command table structure. */ struct _vikeys { /* Underlying function. */ int (*func)(SCR *, VICMD *); #define V_ABS 0x00004000 /* Absolute movement, set '' mark. */ #define V_ABS_C 0x00008000 /* V_ABS: if the line/column changed. */ #define V_ABS_L 0x00010000 /* V_ABS: if the line changed. */ #define V_CHAR 0x00020000 /* Character (required, trailing). */ #define V_CNT 0x00040000 /* Count (optional, leading). */ #define V_DOT 0x00080000 /* On success, sets dot command. */ #define V_KEYW 0x00100000 /* Cursor referenced word. */ #define V_MOTION 0x00200000 /* Motion (required, trailing). */ #define V_MOVE 0x00400000 /* Command defines movement. */ #define V_OBUF 0x00800000 /* Buffer (optional, leading). */ #define V_RBUF 0x01000000 /* Buffer (required, trailing). */ #define V_SECURE 0x02000000 /* Permission denied if O_SECURE set. */ u_int32_t flags; char *usage; /* Usage line. */ char *help; /* Help line. */ }; #define MAXVIKEY 126 /* List of vi commands. */ extern VIKEYS const vikeys[MAXVIKEY + 1]; extern VIKEYS const tmotion; /* XXX Hacked ~ command. */ /* Character stream structure, prototypes. */ typedef struct _vcs { recno_t cs_lno; /* Line. */ size_t cs_cno; /* Column. */ CHAR_T *cs_bp; /* Buffer. */ size_t cs_len; /* Length. */ CHAR_T cs_ch; /* Character. */ #define CS_EMP 1 /* Empty line. */ #define CS_EOF 2 /* End-of-file. */ #define CS_EOL 3 /* End-of-line. */ #define CS_SOF 4 /* Start-of-file. */ int cs_flags; /* Return flags. */ } VCS; int cs_bblank(SCR *, VCS *); int cs_fblank(SCR *, VCS *); int cs_fspace(SCR *, VCS *); int cs_init(SCR *, VCS *); int cs_next(SCR *, VCS *); int cs_prev(SCR *, VCS *); /* * We use a single "window" for each set of vi screens. The model would be * simpler with two windows (one for the text, and one for the modeline) * because scrolling the text window down would work correctly then, not * affecting the mode line. As it is we have to play games to make it look * right. The reason for this choice is that it would be difficult for * curses to optimize the movement, i.e. detect that the downward scroll * isn't going to change the modeline, set the scrolling region on the * terminal and only scroll the first part of the text window. * * Structure for mapping lines to the screen. An SMAP is an array, with one * structure element per screen line, which holds information describing the * physical line which is displayed in the screen line. The first two fields * (lno and off) are all that are necessary to describe a line. The rest of * the information is useful to keep information from being re-calculated. * * The SMAP always has an entry for each line of the physical screen, plus a * slot for the colon command line, so there is room to add any screen into * another one at screen exit. * * Lno is the line number. If doing the historic vi long line folding, soff * is the screen offset into the line. For example, the pair 2:1 would be * the first screen of line 2, and 2:2 would be the second. In the case of * long lines, the screen map will tend to be staggered, e.g., 1:1, 1:2, 1:3, * 2:1, 3:1, etc. If doing left-right scrolling, the coff field is the screen * column offset into the lines, and can take on any value, as it's adjusted * by the user set value O_SIDESCROLL. */ typedef struct _smap { recno_t lno; /* 1-N: Physical file line number. */ size_t coff; /* 0-N: Column offset in the line. */ size_t soff; /* 1-N: Screen offset in the line. */ /* vs_line() cache information. */ size_t c_sboff; /* 0-N: offset of first character on screen. */ size_t c_eboff; /* 0-N: offset of last character on screen. */ u_int8_t c_scoff; /* 0-N: offset into the first character. */ /* 255: no character of line visible. */ u_int8_t c_eclen; /* 1-N: columns from the last character. */ u_int8_t c_ecsize; /* 1-N: size of the last character. */ } SMAP; /* Macros to flush/test cached information. */ #define SMAP_CACHE(smp) ((smp)->c_ecsize != 0) #define SMAP_FLUSH(smp) ((smp)->c_ecsize = 0) /* Character search information. */ typedef enum { CNOTSET, FSEARCH, fSEARCH, TSEARCH, tSEARCH } cdir_t; typedef enum { AB_NOTSET, AB_NOTWORD, AB_INWORD } abb_t; -typedef enum { Q_NOTSET, Q_BNEXT, Q_BTHIS, Q_VNEXT, Q_VTHIS } quote_t; +typedef enum { Q_NOTSET, Q_VNEXT, Q_VTHIS } quote_t; /* Vi private, per-screen memory. */ typedef struct _vi_private { VICMD cmd; /* Current command, motion. */ VICMD motion; /* * !!! * The saved command structure can be modified by the underlying * vi functions, see v_Put() and v_put(). */ VICMD sdot; /* Saved dot, motion command. */ VICMD sdotmotion; CHAR_T *keyw; /* Keyword buffer. */ size_t klen; /* Keyword length. */ size_t keywlen; /* Keyword buffer length. */ CHAR_T rlast; /* Last 'r' replacement character. */ e_key_t rvalue; /* Value of last replacement character. */ EVENT *rep; /* Input replay buffer. */ size_t rep_len; /* Input replay buffer length. */ size_t rep_cnt; /* Input replay buffer characters. */ mtype_t mtype; /* Last displayed message type. */ size_t linecount; /* 1-N: Output overwrite count. */ size_t lcontinue; /* 1-N: Output line continue value. */ size_t totalcount; /* 1-N: Output overwrite count. */ /* Busy state. */ int busy_ref; /* Busy reference count. */ int busy_ch; /* Busy character. */ size_t busy_fx; /* Busy character x coordinate. */ size_t busy_oldy; /* Saved y coordinate. */ size_t busy_oldx; /* Saved x coordinate. */ struct timespec busy_ts;/* Busy timer. */ MARK sel; /* Select start position. */ CHAR_T *mcs; /* Match character list. */ char *ps; /* Paragraph plus section list. */ u_long u_ccnt; /* Undo command count. */ CHAR_T lastckey; /* Last search character. */ cdir_t csearchdir; /* Character search direction. */ SMAP *h_smap; /* First slot of the line map. */ SMAP *t_smap; /* Last slot of the line map. */ /* * One extra slot is always allocated for the map so that we can use * it to do vi :colon command input; see v_tcmd(). */ recno_t sv_tm_lno; /* tcmd: saved TMAP lno field. */ size_t sv_tm_coff; /* tcmd: saved TMAP coff field. */ size_t sv_tm_soff; /* tcmd: saved TMAP soff field. */ size_t sv_t_maxrows; /* tcmd: saved t_maxrows. */ size_t sv_t_minrows; /* tcmd: saved t_minrows. */ size_t sv_t_rows; /* tcmd: saved t_rows. */ #define SIZE_HMAP(sp) (VIP(sp)->srows + 1) /* * Macros to get to the head/tail of the smap. If the screen only has * one line, HMAP can be equal to TMAP, so the code has to understand * the off-by-one errors that can result. If stepping through an SMAP * and operating on each entry, use sp->t_rows as the count of slots, * don't use a loop that compares <= TMAP. */ #define _HMAP(sp) (VIP(sp)->h_smap) #define HMAP _HMAP(sp) #define _TMAP(sp) (VIP(sp)->t_smap) #define TMAP _TMAP(sp) recno_t ss_lno; /* 1-N: vi_opt_screens cached line number. */ size_t ss_screens; /* vi_opt_screens cached return value. */ #define VI_SCR_CFLUSH(vip) vip->ss_lno = OOBLNO size_t srows; /* 1-N: rows in the terminal/window. */ recno_t olno; /* 1-N: old cursor file line. */ size_t ocno; /* 0-N: old file cursor column. */ size_t sc_col; /* 0-N: LOGICAL screen column. */ SMAP *sc_smap; /* SMAP entry where sc_col occurs. */ #define VIP_CUR_INVALID 0x0001 /* Cursor position is unknown. */ #define VIP_DIVIDER 0x0002 /* Divider line was displayed. */ #define VIP_N_EX_PAINT 0x0004 /* Clear and repaint when ex finishes. */ #define VIP_N_EX_REDRAW 0x0008 /* Schedule SC_SCR_REDRAW when ex finishes. */ #define VIP_N_REFRESH 0x0010 /* Repaint (from SMAP) on the next refresh. */ #define VIP_N_RENUMBER 0x0020 /* Renumber screen on the next refresh. */ #define VIP_RCM_LAST 0x0040 /* Cursor drawn to the last column. */ #define VIP_S_MODELINE 0x0080 /* Skip next modeline refresh. */ #define VIP_S_REFRESH 0x0100 /* Skip next refresh. */ u_int16_t flags; } VI_PRIVATE; /* Vi private area. */ #define VIP(sp) ((VI_PRIVATE *)((sp)->vi_private)) #define O_NUMBER_FMT "%7lu " /* O_NUMBER format, length. */ #define O_NUMBER_LENGTH 8 #define SCREEN_COLS(sp) /* Screen columns. */ \ ((O_ISSET(sp, O_NUMBER) ? (sp)->cols - O_NUMBER_LENGTH : (sp)->cols)) /* * LASTLINE is the zero-based, last line in the screen. Note that it is correct * regardless of the changes in the screen to permit text input on the last line * of the screen, or the existence of small screens. */ #define LASTLINE(sp) \ ((sp)->t_maxrows < (sp)->rows ? (sp)->t_maxrows : (sp)->rows - 1) /* * Small screen (see vs_refresh.c, section 6a) and one-line screen test. * Note, both cannot be true for the same screen. */ #define IS_SMALL(sp) ((sp)->t_minrows != (sp)->t_maxrows) #define IS_ONELINE(sp) ((sp)->rows == 1) #define HALFTEXT(sp) /* Half text. */ \ ((sp)->t_rows == 1 ? 1 : (sp)->t_rows / 2) #define HALFSCREEN(sp) /* Half text screen. */ \ ((sp)->t_maxrows == 1 ? 1 : (sp)->t_maxrows / 2) /* * Next tab offset. * * !!! * There are problems with how the historical vi handled tabs. For example, * by doing "set ts=3" and building lines that fold, you can get it to step * through tabs as if they were spaces and move inserted characters to new * positions when is entered. I believe that nvi does tabs correctly, * but there are some historical incompatibilities. */ #define TAB_OFF(c) COL_OFF((c), O_VAL(sp, O_TABSTOP)) /* If more than one horizontal screen being shown. */ #define IS_HSPLIT(sp) \ ((sp)->rows != O_VAL(sp, O_LINES)) /* If more than one vertical screen being shown. */ #define IS_VSPLIT(sp) \ ((sp)->cols != O_VAL(sp, O_COLUMNS)) /* If more than one screen being shown. */ #define IS_SPLIT(sp) \ (IS_HSPLIT(sp) || IS_VSPLIT(sp)) /* Screen adjustment operations. */ typedef enum { A_DECREASE, A_INCREASE, A_SET } adj_t; /* Screen position operations. */ typedef enum { P_BOTTOM, P_FILL, P_MIDDLE, P_TOP } pos_t; /* Scrolling operations. */ typedef enum { CNTRL_B, CNTRL_D, CNTRL_E, CNTRL_F, CNTRL_U, CNTRL_Y, Z_CARAT, Z_PLUS } scroll_t; /* Vi common error messages. */ typedef enum { VIM_COMBUF, VIM_EMPTY, VIM_EOF, VIM_EOL, VIM_NOCOM, VIM_NOCOM_B, VIM_USAGE, VIM_WRESIZE } vim_t; #include "extern.h" Index: head/contrib/nvi/vi/vs_line.c =================================================================== --- head/contrib/nvi/vi/vs_line.c (revision 365498) +++ head/contrib/nvi/vi/vs_line.c (revision 365499) @@ -1,538 +1,534 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: vs_line.c,v 10.40 2012/02/13 19:22:25 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" #ifdef VISIBLE_TAB_CHARS #define TABCH '-' #else #define TABCH ' ' #endif /* * vs_line -- * Update one line on the screen. * * PUBLIC: int vs_line(SCR *, SMAP *, size_t *, size_t *); */ int vs_line(SCR *sp, SMAP *smp, size_t *yp, size_t *xp) { u_char *kp; GS *gp; SMAP *tsmp; size_t chlen = 0, cno_cnt, cols_per_screen, len, nlen; size_t offset_in_char, offset_in_line, oldx, oldy; size_t scno, skip_cols, skip_screens; int dne, is_cached, is_partial, is_tab, no_draw; int list_tab, list_dollar; CHAR_T *p; CHAR_T *cbp, *ecbp, cbuf[128]; ARG_CHAR_T ch = '\0'; #if defined(DEBUG) && 0 TRACE(sp, "vs_line: row %u: line: %u off: %u\n", smp - HMAP, smp->lno, smp->off); #endif /* * If ex modifies the screen after ex output is already on the screen, * don't touch it -- we'll get scrolling wrong, at best. */ no_draw = 0; if (!F_ISSET(sp, SC_TINPUT_INFO) && VIP(sp)->totalcount > 1) no_draw = 1; if (F_ISSET(sp, SC_SCR_EXWROTE) && smp - HMAP != LASTLINE(sp)) no_draw = 1; /* * Assume that, if the cache entry for the line is filled in, the * line is already on the screen, and all we need to do is return * the cursor position. If the calling routine doesn't need the * cursor position, we can just return. */ is_cached = SMAP_CACHE(smp); if (yp == NULL && (is_cached || no_draw)) return (0); /* * A nasty side effect of this routine is that it returns the screen * position for the "current" character. Not pretty, but this is the * only routine that really knows what's out there. * * Move to the line. This routine can be called by vs_sm_position(), * which uses it to fill in the cache entry so it can figure out what * the real contents of the screen are. Because of this, we have to * return to whereever we started from. */ gp = sp->gp; (void)gp->scr_cursor(sp, &oldy, &oldx); (void)gp->scr_move(sp, smp - HMAP, 0); /* Get the line. */ dne = db_get(sp, smp->lno, 0, &p, &len); /* * Special case if we're printing the info/mode line. Skip printing * the leading number, as well as other minor setup. The only time * this code paints the mode line is when the user is entering text * for a ":" command, so we can put the code here instead of dealing * with the empty line logic below. This is a kludge, but it's pretty * much confined to this module. * * Set the number of columns for this screen. * Set the number of chars or screens to skip until a character is to * be displayed. */ cols_per_screen = sp->cols; if (O_ISSET(sp, O_LEFTRIGHT)) { skip_screens = 0; skip_cols = smp->coff; } else { skip_screens = smp->soff - 1; skip_cols = skip_screens * cols_per_screen; } list_tab = O_ISSET(sp, O_LIST); if (F_ISSET(sp, SC_TINPUT_INFO)) list_dollar = 0; else { list_dollar = list_tab; /* * If O_NUMBER is set, the line doesn't exist and it's line * number 1, i.e., an empty file, display the line number. * * If O_NUMBER is set, the line exists and the first character * on the screen is the first character in the line, display * the line number. * * !!! * If O_NUMBER set, decrement the number of columns in the * first screen. DO NOT CHANGE THIS -- IT'S RIGHT! The * rest of the code expects this to reflect the number of * columns in the first screen, regardless of the number of * columns we're going to skip. */ if (O_ISSET(sp, O_NUMBER)) { cols_per_screen -= O_NUMBER_LENGTH; if ((!dne || smp->lno == 1) && skip_cols == 0) { nlen = snprintf((char*)cbuf, sizeof(cbuf), O_NUMBER_FMT, (u_long)smp->lno); (void)gp->scr_addstr(sp, (char*)cbuf, nlen); } } } /* * Special case non-existent lines and the first line of an empty * file. In both cases, the cursor position is 0, but corrected * as necessary for the O_NUMBER field, if it was displayed. */ if (dne || len == 0) { /* Fill in the cursor. */ if (yp != NULL && smp->lno == sp->lno) { *yp = smp - HMAP; *xp = sp->cols - cols_per_screen; } /* If the line is on the screen, quit. */ if (is_cached || no_draw) goto ret1; /* Set line cache information. */ smp->c_sboff = smp->c_eboff = 0; smp->c_scoff = smp->c_eclen = 0; /* * Lots of special cases for empty lines, but they only apply * if we're displaying the first screen of the line. */ if (skip_cols == 0) if (dne) { if (smp->lno == 1) { if (list_dollar) { ch = '$'; goto empty; } } else { ch = '~'; goto empty; } } else if (list_dollar) { ch = '$'; empty: (void)gp->scr_addstr(sp, KEY_NAME(sp, ch), KEY_LEN(sp, ch)); } (void)gp->scr_clrtoeol(sp); (void)gp->scr_move(sp, oldy, oldx); return (0); } /* If we shortened this line in another screen, the cursor * position may have fallen off. */ if (sp->lno == smp->lno && sp->cno >= len) sp->cno = len - 1; /* * If we just wrote this or a previous line, we cached the starting * and ending positions of that line. The way it works is we keep * information about the lines displayed in the SMAP. If we're * painting the screen in the forward direction, this saves us from * reformatting the physical line for every line on the screen. This * wins big on binary files with 10K lines. * * Test for the first screen of the line, then the current screen line, * then the line behind us, then do the hard work. Note, it doesn't * do us any good to have a line in front of us -- it would be really * hard to try and figure out tabs in the reverse direction, i.e. how * many spaces a tab takes up in the reverse direction depends on * what characters preceded it. * * Test for the first screen of the line. */ if (skip_cols == 0) { smp->c_sboff = offset_in_line = 0; smp->c_scoff = offset_in_char = 0; p = &p[offset_in_line]; goto display; } /* Test to see if we've seen this exact line before. */ if (is_cached) { offset_in_line = smp->c_sboff; offset_in_char = smp->c_scoff; p = &p[offset_in_line]; /* Set cols_per_screen to 2nd and later line length. */ if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen) cols_per_screen = sp->cols; goto display; } /* Test to see if we saw an earlier part of this line before. */ if (smp != HMAP && SMAP_CACHE(tsmp = smp - 1) && tsmp->lno == smp->lno) { if (tsmp->c_eclen != tsmp->c_ecsize) { offset_in_line = tsmp->c_eboff; offset_in_char = tsmp->c_eclen; } else { offset_in_line = tsmp->c_eboff + 1; offset_in_char = 0; } /* Put starting info for this line in the cache. */ smp->c_sboff = offset_in_line; smp->c_scoff = offset_in_char; p = &p[offset_in_line]; /* Set cols_per_screen to 2nd and later line length. */ if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen) cols_per_screen = sp->cols; goto display; } scno = 0; offset_in_line = 0; offset_in_char = 0; /* Do it the hard way, for leftright scrolling screens. */ if (O_ISSET(sp, O_LEFTRIGHT)) { for (; offset_in_line < len; ++offset_in_line) { chlen = (ch = *p++) == '\t' && !list_tab ? TAB_OFF(scno) : KEY_COL(sp, ch); if ((scno += chlen) >= skip_cols) break; } /* Set cols_per_screen to 2nd and later line length. */ cols_per_screen = sp->cols; /* Put starting info for this line in the cache. */ if (offset_in_line >= len) { smp->c_sboff = offset_in_line; smp->c_scoff = 255; } else if (scno != skip_cols) { smp->c_sboff = offset_in_line; smp->c_scoff = offset_in_char = chlen - (scno - skip_cols); --p; } else { smp->c_sboff = ++offset_in_line; smp->c_scoff = 0; } } /* Do it the hard way, for historic line-folding screens. */ else { for (; offset_in_line < len; ++offset_in_line) { chlen = (ch = *p++) == '\t' && !list_tab ? TAB_OFF(scno) : KEY_COL(sp, ch); if ((scno += chlen) < cols_per_screen) continue; scno -= cols_per_screen; /* Set cols_per_screen to 2nd and later line length. */ cols_per_screen = sp->cols; /* * If crossed the last skipped screen boundary, start * displaying the characters. */ if (--skip_screens == 0) break; } /* Put starting info for this line in the cache. */ if (scno != 0) { smp->c_sboff = offset_in_line; smp->c_scoff = offset_in_char = chlen - scno; --p; } else { smp->c_sboff = ++offset_in_line; smp->c_scoff = 0; } } display: /* * Set the number of characters to skip before reaching the cursor * character. Offset by 1 and use 0 as a flag value. Vs_line is * called repeatedly with a valid pointer to a cursor position. * Don't fill anything in unless it's the right line and the right * character, and the right part of the character... */ if (yp == NULL || smp->lno != sp->lno || sp->cno < offset_in_line || offset_in_line + cols_per_screen < sp->cno) { cno_cnt = 0; /* If the line is on the screen, quit. */ if (is_cached || no_draw) goto ret1; } else cno_cnt = (sp->cno - offset_in_line) + 1; /* This is the loop that actually displays characters. */ ecbp = (cbp = cbuf) + SIZE(cbuf) - 1; for (is_partial = 0, scno = 0; offset_in_line < len; ++offset_in_line, offset_in_char = 0) { if ((ch = *p++) == '\t' && !list_tab) { scno += chlen = TAB_OFF(scno) - offset_in_char; is_tab = 1; } else { scno += chlen = KEY_COL(sp, ch) - offset_in_char; is_tab = 0; } /* * Only display up to the right-hand column. Set a flag if * the entire character wasn't displayed for use in setting * the cursor. If reached the end of the line, set the cache * info for the screen. Don't worry about there not being * characters to display on the next screen, its lno/off won't * match up in that case. */ if (scno >= cols_per_screen) { if (is_tab == 1) { chlen -= scno - cols_per_screen; smp->c_ecsize = smp->c_eclen = chlen; scno = cols_per_screen; } else { smp->c_ecsize = chlen; chlen -= scno - cols_per_screen; smp->c_eclen = chlen; if (scno > cols_per_screen) is_partial = 1; } smp->c_eboff = offset_in_line; /* Terminate the loop. */ offset_in_line = len; } /* * If the caller wants the cursor value, and this was the * cursor character, set the value. There are two ways to * put the cursor on a character -- if it's normal display * mode, it goes on the last column of the character. If * it's input mode, it goes on the first. In normal mode, * set the cursor only if the entire character was displayed. */ if (cno_cnt && --cno_cnt == 0 && (F_ISSET(sp, SC_TINPUT) || !is_partial)) { *yp = smp - HMAP; if (F_ISSET(sp, SC_TINPUT)) if (is_partial) *xp = scno - smp->c_ecsize; else *xp = scno - chlen; else *xp = scno - 1; if (O_ISSET(sp, O_NUMBER) && !F_ISSET(sp, SC_TINPUT_INFO) && skip_cols == 0) *xp += O_NUMBER_LENGTH; /* If the line is on the screen, quit. */ if (is_cached || no_draw) goto ret1; } /* If the line is on the screen, don't display anything. */ if (is_cached || no_draw) continue; #define FLUSH { \ *cbp = '\0'; \ (void)gp->scr_waddstr(sp, cbuf, cbp - cbuf); \ cbp = cbuf; \ } /* * Display the character. We do tab expansion here because * the screen interface doesn't have any way to set the tab * length. Note, it's theoretically possible for chlen to * be larger than cbuf, if the user set a impossibly large * tabstop. */ if (is_tab) while (chlen--) { if (cbp >= ecbp) FLUSH; *cbp++ = TABCH; } else { if (cbp + chlen >= ecbp) FLUSH; /* don't display half a wide character */ if (is_partial && CHAR_WIDTH(sp, ch) > 1) { *cbp++ = ' '; break; } if (KEY_NEEDSWIDE(sp, ch)) *cbp++ = ch; else for (kp = (u_char *) KEY_NAME(sp, ch) + offset_in_char; chlen--;) *cbp++ = *kp++; } } if (scno < cols_per_screen) { /* If didn't paint the whole line, update the cache. */ smp->c_ecsize = smp->c_eclen = KEY_COL(sp, ch); smp->c_eboff = len - 1; /* * If not the info/mode line, and O_LIST set, and at the * end of the line, and the line ended on this screen, * add a trailing $. */ if (list_dollar) { ++scno; chlen = KEY_LEN(sp, '$'); if (cbp + chlen >= ecbp) FLUSH; for (kp = (u_char *) KEY_NAME(sp, '$'); chlen--;) *cbp++ = *kp++; } /* If still didn't paint the whole line, clear the rest. */ if (scno < cols_per_screen) (void)gp->scr_clrtoeol(sp); } /* Flush any buffered characters. */ if (cbp > cbuf) FLUSH; ret1: (void)gp->scr_move(sp, oldy, oldx); return (0); } /* * vs_number -- * Repaint the numbers on all the lines. * * PUBLIC: int vs_number(SCR *); */ int vs_number(SCR *sp) { GS *gp; SMAP *smp; VI_PRIVATE *vip; size_t len, oldy, oldx; int exist; char nbuf[10]; gp = sp->gp; vip = VIP(sp); /* No reason to do anything if we're in input mode on the info line. */ if (F_ISSET(sp, SC_TINPUT_INFO)) return (0); /* * Try and avoid getting the last line in the file, by getting the * line after the last line in the screen -- if it exists, we know * we have to to number all the lines in the screen. Get the one * after the last instead of the last, so that the info line doesn't * fool us. (The problem is that file_lline will lie, and tell us * that the info line is the last line in the file.) If that test * fails, we have to check each line for existence. */ exist = db_exist(sp, TMAP->lno + 1); (void)gp->scr_cursor(sp, &oldy, &oldx); for (smp = HMAP; smp <= TMAP; ++smp) { /* Numbers are only displayed for the first screen line. */ if (O_ISSET(sp, O_LEFTRIGHT)) { if (smp->coff != 0) continue; } else if (smp->soff != 1) continue; /* * The first line of an empty file gets numbered, otherwise * number any existing line. */ if (smp->lno != 1 && !exist && !db_exist(sp, smp->lno)) break; (void)gp->scr_move(sp, smp - HMAP, 0); len = snprintf(nbuf, sizeof(nbuf), O_NUMBER_FMT, (u_long)smp->lno); (void)gp->scr_addstr(sp, nbuf, len); } (void)gp->scr_move(sp, oldy, oldx); return (0); } Index: head/contrib/nvi/vi/vs_msg.c =================================================================== --- head/contrib/nvi/vi/vs_msg.c (revision 365498) +++ head/contrib/nvi/vi/vs_msg.c (revision 365499) @@ -1,900 +1,895 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: vs_msg.c,v 10.88 2013/03/19 09:59:03 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" typedef enum { SCROLL_W, /* User wait. */ SCROLL_W_EX, /* User wait, or enter : to continue. */ SCROLL_W_QUIT /* User wait, or enter q to quit. */ /* * SCROLL_W_QUIT has another semantic * -- only wait if the screen is full */ } sw_t; static void vs_divider(SCR *); static void vs_msgsave(SCR *, mtype_t, char *, size_t); static void vs_output(SCR *, mtype_t, const char *, int); static void vs_scroll(SCR *, int *, sw_t); static void vs_wait(SCR *, int *, sw_t); /* * vs_busy -- * Display, update or clear a busy message. * * This routine is the default editor interface for vi busy messages. It * implements a standard strategy of stealing lines from the bottom of the * vi text screen. Screens using an alternate method of displaying busy * messages, e.g. X11 clock icons, should set their scr_busy function to the * correct function before calling the main editor routine. * * PUBLIC: void vs_busy(SCR *, const char *, busy_t); */ void vs_busy(SCR *sp, const char *msg, busy_t btype) { GS *gp; VI_PRIVATE *vip; static const char flagc[] = "|/-\\"; struct timespec ts, ts_diff; const struct timespec ts_min = { 0, 125000000 }; size_t len, notused; const char *p; /* Ex doesn't display busy messages. */ if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) return; gp = sp->gp; vip = VIP(sp); /* * Most of this routine is to deal with the screen sharing real estate * between the normal edit messages and the busy messages. Logically, * all that's needed is something that puts up a message, periodically * updates it, and then goes away. */ switch (btype) { case BUSY_ON: ++vip->busy_ref; if (vip->totalcount != 0 || vip->busy_ref != 1) break; /* Initialize state for updates. */ vip->busy_ch = 0; timepoint_steady(&vip->busy_ts); /* Save the current cursor. */ (void)gp->scr_cursor(sp, &vip->busy_oldy, &vip->busy_oldx); /* Display the busy message. */ p = msg_cat(sp, msg, &len); (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_addstr(sp, p, len); (void)gp->scr_cursor(sp, ¬used, &vip->busy_fx); (void)gp->scr_clrtoeol(sp); (void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx); break; case BUSY_OFF: if (vip->busy_ref == 0) break; --vip->busy_ref; /* * If the line isn't in use for another purpose, clear it. * Always return to the original position. */ if (vip->totalcount == 0 && vip->busy_ref == 0) { (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_clrtoeol(sp); } (void)gp->scr_move(sp, vip->busy_oldy, vip->busy_oldx); break; case BUSY_UPDATE: if (vip->totalcount != 0 || vip->busy_ref == 0) break; /* Update no more than every 1/8 of a second. */ timepoint_steady(&ts); ts_diff = ts; timespecsub(&ts_diff, &vip->busy_ts); if (timespeccmp(&ts_diff, &ts_min, <)) return; vip->busy_ts = ts; /* Display the update. */ if (vip->busy_ch == sizeof(flagc) - 1) vip->busy_ch = 0; (void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx); (void)gp->scr_addstr(sp, flagc + vip->busy_ch++, 1); (void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx); break; } (void)gp->scr_refresh(sp, 0); } /* * vs_home -- * Home the cursor to the bottom row, left-most column. * * PUBLIC: void vs_home(SCR *); */ void vs_home(SCR *sp) { (void)sp->gp->scr_move(sp, LASTLINE(sp), 0); (void)sp->gp->scr_refresh(sp, 0); } /* * vs_update -- * Update a command. * * PUBLIC: void vs_update(SCR *, const char *, const CHAR_T *); */ void vs_update(SCR *sp, const char *m1, const CHAR_T *m2) { GS *gp; size_t len, mlen, oldx, oldy; CONST char *np; size_t nlen; gp = sp->gp; /* * This routine displays a message on the bottom line of the screen, * without updating any of the command structures that would keep it * there for any period of time, i.e. it is overwritten immediately. * * It's used by the ex read and ! commands when the user's command is * expanded, and by the ex substitution confirmation prompt. */ if (F_ISSET(sp, SC_SCR_EXWROTE)) { if (m2 != NULL) INT2CHAR(sp, m2, STRLEN(m2) + 1, np, nlen); (void)ex_printf(sp, "%s\n", m1 == NULL? "" : m1, m2 == NULL ? "" : np); (void)ex_fflush(sp); } /* * Save the cursor position, the substitute-with-confirmation code * will have already set it correctly. */ (void)gp->scr_cursor(sp, &oldy, &oldx); /* Clear the bottom line. */ (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_clrtoeol(sp); /* * XXX * Don't let long file names screw up the screen. */ if (m1 != NULL) { mlen = len = strlen(m1); if (len > sp->cols - 2) mlen = len = sp->cols - 2; (void)gp->scr_addstr(sp, m1, mlen); } else len = 0; if (m2 != NULL) { mlen = STRLEN(m2); if (len + mlen > sp->cols - 2) mlen = (sp->cols - 2) - len; (void)gp->scr_waddstr(sp, m2, mlen); } (void)gp->scr_move(sp, oldy, oldx); (void)gp->scr_refresh(sp, 0); } /* * vs_msg -- * Display ex output or error messages for the screen. * * This routine is the default editor interface for all ex output, and all ex * and vi error/informational messages. It implements the standard strategy * of stealing lines from the bottom of the vi text screen. Screens using an * alternate method of displaying messages, e.g. dialog boxes, should set their * scr_msg function to the correct function before calling the editor. * * PUBLIC: void vs_msg(SCR *, mtype_t, char *, size_t); */ void vs_msg(SCR *sp, mtype_t mtype, char *line, size_t len) { GS *gp; VI_PRIVATE *vip; size_t maxcols, oldx, oldy, padding; const char *e, *s, *t; gp = sp->gp; vip = VIP(sp); /* * Ring the bell if it's scheduled. * * XXX * Shouldn't we save this, too? */ if (F_ISSET(sp, SC_TINPUT_INFO) || F_ISSET(gp, G_BELLSCHED)) if (F_ISSET(sp, SC_SCR_VI)) { F_CLR(gp, G_BELLSCHED); (void)gp->scr_bell(sp); } else F_SET(gp, G_BELLSCHED); /* * If vi is using the error line for text input, there's no screen * real-estate for the error message. Nothing to do without some * information as to how important the error message is. */ if (F_ISSET(sp, SC_TINPUT_INFO)) return; /* * Ex or ex controlled screen output. * * If output happens during startup, e.g., a .exrc file, we may be * in ex mode but haven't initialized the screen. Initialize here, * and in this case, stay in ex mode. * * If the SC_SCR_EXWROTE bit is set, then we're switching back and * forth between ex and vi, but the screen is trashed and we have * to respect that. Switch to ex mode long enough to put out the * message. * * If the SC_EX_WAIT_NO bit is set, turn it off -- we're writing to * the screen, so previous opinions are ignored. */ if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) { if (!F_ISSET(sp, SC_SCR_EX)) if (F_ISSET(sp, SC_SCR_EXWROTE)) { if (sp->gp->scr_screen(sp, SC_EX)) return; } else if (ex_init(sp)) return; if (mtype == M_ERR) (void)gp->scr_attr(sp, SA_INVERSE, 1); (void)printf("%.*s", (int)len, line); if (mtype == M_ERR) (void)gp->scr_attr(sp, SA_INVERSE, 0); (void)fflush(stdout); F_CLR(sp, SC_EX_WAIT_NO); if (!F_ISSET(sp, SC_SCR_EX)) (void)sp->gp->scr_screen(sp, SC_VI); return; } /* If the vi screen isn't ready, save the message. */ if (!F_ISSET(sp, SC_SCR_VI)) { (void)vs_msgsave(sp, mtype, line, len); return; } /* Save the cursor position. */ (void)gp->scr_cursor(sp, &oldy, &oldx); /* If it's an ex output message, just write it out. */ if (mtype == M_NONE) { vs_output(sp, mtype, line, len); goto ret; } /* * If it's a vi message, strip the trailing so we can * try and paste messages together. */ if (line[len - 1] == '\n') --len; /* * If a message won't fit on a single line, try to split on a . * If a subsequent message fits on the same line, write a separator * and output it. Otherwise, put out a newline. * * Need up to two padding characters normally; a semi-colon and a * separating space. If only a single line on the screen, add some * more for the trailing continuation message. * * XXX * Assume that periods and semi-colons take up a single column on the * screen. * * XXX * There are almost certainly pathological cases that will break this * code. */ if (IS_ONELINE(sp)) (void)msg_cmsg(sp, CMSG_CONT_S, &padding); else padding = 0; padding += 2; maxcols = sp->cols - 1; if (vip->lcontinue != 0) if (len + vip->lcontinue + padding > maxcols) vs_output(sp, vip->mtype, ".\n", 2); else { vs_output(sp, vip->mtype, ";", 1); vs_output(sp, M_NONE, " ", 1); } vip->mtype = mtype; for (s = line;; s = t) { - for (; len > 0 && isblank(*s); --len, ++s); + for (; len > 0 && isblank((u_char)*s); --len, ++s); if (len == 0) break; if (len + vip->lcontinue > maxcols) { for (e = s + (maxcols - vip->lcontinue); - e > s && !isblank(*e); --e); + e > s && !isblank((u_char)*e); --e); if (e == s) e = t = s + (maxcols - vip->lcontinue); else - for (t = e; isblank(e[-1]); --e); + for (t = e; isblank((u_char)e[-1]); --e); } else e = t = s + len; /* * If the message ends in a period, discard it, we want to * gang messages where possible. */ len -= t - s; if (len == 0 && (e - s) > 1 && s[(e - s) - 1] == '.') --e; vs_output(sp, mtype, s, e - s); if (len != 0) vs_output(sp, M_NONE, "\n", 1); if (INTERRUPTED(sp)) break; } ret: (void)gp->scr_move(sp, oldy, oldx); (void)gp->scr_refresh(sp, 0); } /* * vs_output -- * Output the text to the screen. */ static void vs_output(SCR *sp, mtype_t mtype, const char *line, int llen) { GS *gp; VI_PRIVATE *vip; size_t notused; int len, rlen, tlen; const char *p, *t; char *cbp, *ecbp, cbuf[128]; gp = sp->gp; vip = VIP(sp); for (p = line, rlen = llen; llen > 0;) { /* Get the next physical line. */ if ((p = memchr(line, '\n', llen)) == NULL) len = llen; else len = p - line; /* * The max is sp->cols characters, and we may have already * written part of the line. */ if (len + vip->lcontinue > sp->cols) len = sp->cols - vip->lcontinue; /* * If the first line output, do nothing. If the second line * output, draw the divider line. If drew a full screen, we * remove the divider line. If it's a continuation line, move * to the continuation point, else, move the screen up. */ if (vip->lcontinue == 0) { if (!IS_ONELINE(sp)) { if (vip->totalcount == 1) { (void)gp->scr_move(sp, LASTLINE(sp) - 1, 0); (void)gp->scr_clrtoeol(sp); (void)vs_divider(sp); F_SET(vip, VIP_DIVIDER); ++vip->totalcount; ++vip->linecount; } if (vip->totalcount == sp->t_maxrows && F_ISSET(vip, VIP_DIVIDER)) { --vip->totalcount; --vip->linecount; F_CLR(vip, VIP_DIVIDER); } } if (vip->totalcount != 0) vs_scroll(sp, NULL, SCROLL_W_QUIT); (void)gp->scr_move(sp, LASTLINE(sp), 0); ++vip->totalcount; ++vip->linecount; if (INTERRUPTED(sp)) break; } else (void)gp->scr_move(sp, LASTLINE(sp), vip->lcontinue); /* Error messages are in inverse video. */ if (mtype == M_ERR) (void)gp->scr_attr(sp, SA_INVERSE, 1); /* Display the line, doing character translation. */ #define FLUSH { \ *cbp = '\0'; \ (void)gp->scr_addstr(sp, cbuf, cbp - cbuf); \ cbp = cbuf; \ } ecbp = (cbp = cbuf) + sizeof(cbuf) - 1; for (t = line, tlen = len; tlen--; ++t) { /* * Replace tabs with spaces, there are places in * ex that do column calculations without looking * at -- and all routines that care about * do their own expansions. This catches * in things like tag search strings. */ if (cbp + 1 >= ecbp) FLUSH; *cbp++ = *t == '\t' ? ' ' : *t; } if (cbp > cbuf) FLUSH; if (mtype == M_ERR) (void)gp->scr_attr(sp, SA_INVERSE, 0); /* Clear the rest of the line. */ (void)gp->scr_clrtoeol(sp); /* If we loop, it's a new line. */ vip->lcontinue = 0; /* Reset for the next line. */ line += len; llen -= len; if (p != NULL) { ++line; --llen; } } /* Set up next continuation line. */ if (p == NULL) gp->scr_cursor(sp, ¬used, &vip->lcontinue); } /* * vs_ex_resolve -- * Deal with ex message output. * * This routine is called when exiting a colon command to resolve any ex * output that may have occurred. * * PUBLIC: int vs_ex_resolve(SCR *, int *); */ int vs_ex_resolve(SCR *sp, int *continuep) { EVENT ev; GS *gp; VI_PRIVATE *vip; sw_t wtype; gp = sp->gp; vip = VIP(sp); *continuep = 0; /* If we ran any ex command, we can't trust the cursor position. */ F_SET(vip, VIP_CUR_INVALID); /* Terminate any partially written message. */ if (vip->lcontinue != 0) { vs_output(sp, vip->mtype, ".", 1); vip->lcontinue = 0; vip->mtype = M_NONE; } /* * If we switched out of the vi screen into ex, switch back while we * figure out what to do with the screen and potentially get another * command to execute. * * If we didn't switch into ex, we're not required to wait, and less * than 2 lines of output, we can continue without waiting for the * wait. * * Note, all other code paths require waiting, so we leave the report * of modified lines until later, so that we won't wait for no other * reason than a threshold number of lines were modified. This means * we display cumulative line modification reports for groups of ex * commands. That seems right to me (well, at least not wrong). */ if (F_ISSET(sp, SC_SCR_EXWROTE)) { if (sp->gp->scr_screen(sp, SC_VI)) return (1); } else if (!F_ISSET(sp, SC_EX_WAIT_YES) && vip->totalcount < 2) { F_CLR(sp, SC_EX_WAIT_NO); return (0); } /* Clear the required wait flag, it's no longer needed. */ F_CLR(sp, SC_EX_WAIT_YES); /* * Wait, unless explicitly told not to wait or the user interrupted * the command. If the user is leaving the screen, for any reason, * they can't continue with further ex commands. */ if (!F_ISSET(sp, SC_EX_WAIT_NO) && !INTERRUPTED(sp)) { wtype = F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE | SC_FSWITCH | SC_SSWITCH) ? SCROLL_W : SCROLL_W_EX; if (F_ISSET(sp, SC_SCR_EXWROTE)) vs_wait(sp, continuep, wtype); else vs_scroll(sp, continuep, wtype); if (*continuep) return (0); } /* If ex wrote on the screen, refresh the screen image. */ if (F_ISSET(sp, SC_SCR_EXWROTE)) F_SET(vip, VIP_N_EX_PAINT); /* * If we're not the bottom of the split screen stack, the screen * image itself is wrong, so redraw everything. */ if (TAILQ_NEXT(sp, q) != NULL) F_SET(sp, SC_SCR_REDRAW); /* If ex changed the underlying file, the map itself is wrong. */ if (F_ISSET(vip, VIP_N_EX_REDRAW)) F_SET(sp, SC_SCR_REFORMAT); /* Ex may have switched out of the alternate screen, return. */ (void)gp->scr_attr(sp, SA_ALTERNATE, 1); /* * Whew. We're finally back home, after what feels like years. * Kiss the ground. */ F_CLR(sp, SC_SCR_EXWROTE | SC_EX_WAIT_NO); /* * We may need to repaint some of the screen, e.g.: * * :set * :!ls * * gives us a combination of some lines that are "wrong", and a need * for a full refresh. */ if (vip->totalcount > 1) { /* Set up the redraw of the overwritten lines. */ ev.e_event = E_REPAINT; ev.e_flno = vip->totalcount >= sp->rows ? 1 : sp->rows - vip->totalcount; ev.e_tlno = sp->rows; /* Reset the count of overwriting lines. */ vip->linecount = vip->lcontinue = vip->totalcount = 0; /* Redraw. */ (void)vs_repaint(sp, &ev); } else /* Reset the count of overwriting lines. */ vip->linecount = vip->lcontinue = vip->totalcount = 0; return (0); } /* * vs_resolve -- * Deal with message output. * * PUBLIC: int vs_resolve(SCR *, SCR *, int); */ int vs_resolve(SCR *sp, SCR *csp, int forcewait) { EVENT ev; GS *gp; MSGS *mp; VI_PRIVATE *vip; size_t oldy, oldx; int redraw; /* * Vs_resolve is called from the main vi loop and the refresh function * to periodically ensure that the user has seen any messages that have * been displayed and that any status lines are correct. The sp screen * is the screen we're checking, usually the current screen. When it's * not, csp is the current screen, used for final cursor positioning. */ gp = sp->gp; vip = VIP(sp); if (csp == NULL) csp = sp; /* Save the cursor position. */ (void)gp->scr_cursor(csp, &oldy, &oldx); /* Ring the bell if it's scheduled. */ if (F_ISSET(gp, G_BELLSCHED)) { F_CLR(gp, G_BELLSCHED); (void)gp->scr_bell(sp); } /* Display new file status line. */ if (F_ISSET(sp, SC_STATUS)) { F_CLR(sp, SC_STATUS); msgq_status(sp, sp->lno, MSTAT_TRUNCATE); } /* Report on line modifications. */ mod_rpt(sp); /* * Flush any saved messages. If the screen isn't ready, refresh * it. (A side-effect of screen refresh is that we can display * messages.) Once this is done, don't trust the cursor. That * extra refresh screwed the pooch. */ if (!SLIST_EMPTY(gp->msgq)) { if (!F_ISSET(sp, SC_SCR_VI) && vs_refresh(sp, 1)) return (1); while ((mp = SLIST_FIRST(gp->msgq)) != NULL) { gp->scr_msg(sp, mp->mtype, mp->buf, mp->len); SLIST_REMOVE_HEAD(gp->msgq, q); free(mp->buf); free(mp); } F_SET(vip, VIP_CUR_INVALID); } switch (vip->totalcount) { case 0: redraw = 0; break; case 1: /* * If we're switching screens, we have to wait for messages, * regardless. If we don't wait, skip updating the modeline. */ if (forcewait) vs_scroll(sp, NULL, SCROLL_W); else F_SET(vip, VIP_S_MODELINE); redraw = 0; break; default: /* * If >1 message line in use, prompt the user to continue and * repaint overwritten lines. */ vs_scroll(sp, NULL, SCROLL_W); ev.e_event = E_REPAINT; ev.e_flno = vip->totalcount >= sp->rows ? 1 : sp->rows - vip->totalcount; ev.e_tlno = sp->rows; redraw = 1; break; } /* Reset the count of overwriting lines. */ vip->linecount = vip->lcontinue = vip->totalcount = 0; /* Redraw. */ if (redraw) (void)vs_repaint(sp, &ev); /* Restore the cursor position. */ (void)gp->scr_move(csp, oldy, oldx); return (0); } /* * vs_scroll -- * Scroll the screen for output. */ static void vs_scroll(SCR *sp, int *continuep, sw_t wtype) { GS *gp; VI_PRIVATE *vip; gp = sp->gp; vip = VIP(sp); if (!IS_ONELINE(sp)) { /* * Scroll the screen. Instead of scrolling the entire screen, * delete the line above the first line output so preserve the * maximum amount of the screen. */ (void)gp->scr_move(sp, vip->totalcount < sp->rows ? LASTLINE(sp) - vip->totalcount : 0, 0); (void)gp->scr_deleteln(sp); /* If there are screens below us, push them back into place. */ if (TAILQ_NEXT(sp, q) != NULL) { (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_insertln(sp); } } if (wtype == SCROLL_W_QUIT && vip->linecount < sp->t_maxrows) return; vs_wait(sp, continuep, wtype); } /* * vs_wait -- * Prompt the user to continue. */ static void vs_wait(SCR *sp, int *continuep, sw_t wtype) { EVENT ev; VI_PRIVATE *vip; const char *p; GS *gp; size_t len; gp = sp->gp; vip = VIP(sp); (void)gp->scr_move(sp, LASTLINE(sp), 0); if (IS_ONELINE(sp)) p = msg_cmsg(sp, CMSG_CONT_S, &len); else switch (wtype) { case SCROLL_W_QUIT: p = msg_cmsg(sp, CMSG_CONT_Q, &len); break; case SCROLL_W_EX: p = msg_cmsg(sp, CMSG_CONT_EX, &len); break; case SCROLL_W: p = msg_cmsg(sp, CMSG_CONT, &len); break; default: abort(); /* NOTREACHED */ } (void)gp->scr_addstr(sp, p, len); ++vip->totalcount; vip->linecount = 0; (void)gp->scr_clrtoeol(sp); (void)gp->scr_refresh(sp, 0); /* Get a single character from the terminal. */ if (continuep != NULL) *continuep = 0; for (;;) { if (v_event_get(sp, &ev, 0, 0)) return; if (ev.e_event == E_CHARACTER) break; if (ev.e_event == E_INTERRUPT) { ev.e_c = CH_QUIT; F_SET(gp, G_INTERRUPTED); break; } (void)gp->scr_bell(sp); } switch (wtype) { case SCROLL_W_QUIT: if (ev.e_c == CH_QUIT) F_SET(gp, G_INTERRUPTED); break; case SCROLL_W_EX: if (ev.e_c == ':' && continuep != NULL) *continuep = 1; break; case SCROLL_W: break; } } /* * vs_divider -- * Draw a dividing line between the screen and the output. */ static void vs_divider(SCR *sp) { GS *gp; size_t len; #define DIVIDESTR "+=+=+=+=+=+=+=+" len = sizeof(DIVIDESTR) - 1 > sp->cols ? sp->cols : sizeof(DIVIDESTR) - 1; gp = sp->gp; (void)gp->scr_attr(sp, SA_INVERSE, 1); (void)gp->scr_addstr(sp, DIVIDESTR, len); (void)gp->scr_attr(sp, SA_INVERSE, 0); } /* * vs_msgsave -- * Save a message for later display. */ static void vs_msgsave(SCR *sp, mtype_t mt, char *p, size_t len) { GS *gp; MSGS *mp_c, *mp_n; /* * We have to handle messages before we have any place to put them. * If there's no screen support yet, allocate a msg structure, copy * in the message, and queue it on the global structure. If we can't * allocate memory here, we're genuinely screwed, dump the message * to stderr in the (probably) vain hope that someone will see it. */ - CALLOC_GOTO(sp, mp_n, MSGS *, 1, sizeof(MSGS)); - MALLOC_GOTO(sp, mp_n->buf, char *, len); + CALLOC_GOTO(sp, mp_n, 1, sizeof(MSGS)); + MALLOC_GOTO(sp, mp_n->buf, len); memmove(mp_n->buf, p, len); mp_n->len = len; mp_n->mtype = mt; gp = sp->gp; if (SLIST_EMPTY(gp->msgq)) { SLIST_INSERT_HEAD(gp->msgq, mp_n, q); } else { SLIST_FOREACH(mp_c, gp->msgq, q) if (SLIST_NEXT(mp_c, q) == NULL) break; SLIST_INSERT_AFTER(mp_c, mp_n, q); } return; alloc_err: - if (mp_n != NULL) - free(mp_n); + free(mp_n); (void)fprintf(stderr, "%.*s\n", (int)len, p); } Index: head/contrib/nvi/vi/vs_refresh.c =================================================================== --- head/contrib/nvi/vi/vs_refresh.c (revision 365498) +++ head/contrib/nvi/vi/vs_refresh.c (revision 365499) @@ -1,886 +1,883 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: vs_refresh.c,v 10.54 2015/04/08 16:32:49 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include +#include #include #include #include #include #include "../common/common.h" #include "vi.h" #define UPDATE_CURSOR 0x01 /* Update the cursor. */ #define UPDATE_SCREEN 0x02 /* Flush to screen. */ static void vs_modeline(SCR *); static int vs_paint(SCR *, u_int); /* * v_repaint -- * Repaint selected lines from the screen. * * PUBLIC: int vs_repaint(SCR *, EVENT *); */ int vs_repaint( SCR *sp, EVENT *evp) { SMAP *smp; for (; evp->e_flno <= evp->e_tlno; ++evp->e_flno) { smp = HMAP + evp->e_flno - 1; SMAP_FLUSH(smp); if (vs_line(sp, smp, NULL, NULL)) return (1); } return (0); } /* * vs_refresh -- * Refresh all screens. * * PUBLIC: int vs_refresh(SCR *, int); */ int vs_refresh( SCR *sp, int forcepaint) { GS *gp; SCR *tsp; int need_refresh = 0; u_int priv_paint, pub_paint; gp = sp->gp; /* * 1: Refresh the screen. * * If SC_SCR_REDRAW is set in the current screen, repaint everything * that we can find, including status lines. */ if (F_ISSET(sp, SC_SCR_REDRAW)) TAILQ_FOREACH(tsp, gp->dq, q) if (tsp != sp) F_SET(tsp, SC_SCR_REDRAW | SC_STATUS); /* * 2: Related or dirtied screens, or screens with messages. * * If related screens share a view into a file, they may have been * modified as well. Refresh any screens that aren't exiting that * have paint or dirty bits set. Always update their screens, we * are not likely to get another chance. Finally, if we refresh any * screens other than the current one, the cursor will be trashed. */ pub_paint = SC_SCR_REFORMAT | SC_SCR_REDRAW; priv_paint = VIP_CUR_INVALID | VIP_N_REFRESH; if (O_ISSET(sp, O_NUMBER)) priv_paint |= VIP_N_RENUMBER; TAILQ_FOREACH(tsp, gp->dq, q) if (tsp != sp && !F_ISSET(tsp, SC_EXIT | SC_EXIT_FORCE) && (F_ISSET(tsp, pub_paint) || F_ISSET(VIP(tsp), priv_paint))) { (void)vs_paint(tsp, (F_ISSET(VIP(tsp), VIP_CUR_INVALID) ? UPDATE_CURSOR : 0) | UPDATE_SCREEN); F_SET(VIP(sp), VIP_CUR_INVALID); } /* * 3: Refresh the current screen. * * Always refresh the current screen, it may be a cursor movement. * Also, always do it last -- that way, SC_SCR_REDRAW can be set * in the current screen only, and the screen won't flash. */ if (vs_paint(sp, UPDATE_CURSOR | (!forcepaint && F_ISSET(sp, SC_SCR_VI) && KEYS_WAITING(sp) ? 0 : UPDATE_SCREEN))) return (1); /* * 4: Paint any missing status lines. * * XXX * This is fairly evil. Status lines are written using the vi message * mechanism, since we have no idea how long they are. Since we may be * painting screens other than the current one, we don't want to make * the user wait. We depend heavily on there not being any other lines * currently waiting to be displayed and the message truncation code in * the msgq_status routine working. * * And, finally, if we updated any status lines, make sure the cursor * gets back to where it belongs. */ TAILQ_FOREACH(tsp, gp->dq, q) if (F_ISSET(tsp, SC_STATUS)) { need_refresh = 1; vs_resolve(tsp, sp, 0); } if (need_refresh) (void)gp->scr_refresh(sp, 0); /* * A side-effect of refreshing the screen is that it's now ready * for everything else, i.e. messages. */ F_SET(sp, SC_SCR_VI); return (0); } /* * vs_paint -- * This is the guts of the vi curses screen code. The idea is that * the SCR structure passed in contains the new coordinates of the * screen. What makes this hard is that we don't know how big * characters are, doing input can put the cursor in illegal places, * and we're frantically trying to avoid repainting unless it's * absolutely necessary. If you change this code, you'd better know * what you're doing. It's subtle and quick to anger. */ static int vs_paint( SCR *sp, u_int flags) { GS *gp; SMAP *smp, tmp; VI_PRIVATE *vip; recno_t lastline, lcnt; size_t cwtotal, cnt, len, notused, off, y; int ch = 0, didpaint, isempty, leftright_warp; CHAR_T *p; #define LNO sp->lno /* Current file line. */ #define OLNO vip->olno /* Remembered file line. */ #define CNO sp->cno /* Current file column. */ #define OCNO vip->ocno /* Remembered file column. */ #define SCNO vip->sc_col /* Current screen column. */ gp = sp->gp; vip = VIP(sp); didpaint = leftright_warp = 0; /* * 5: Reformat the lines. * * If the lines themselves have changed (:set list, for example), * fill in the map from scratch. Adjust the screen that's being * displayed if the leftright flag is set. */ if (F_ISSET(sp, SC_SCR_REFORMAT)) { /* Invalidate the line size cache. */ VI_SCR_CFLUSH(vip); /* Toss vs_line() cached information. */ if (F_ISSET(sp, SC_SCR_TOP)) { if (vs_sm_fill(sp, LNO, P_TOP)) return (1); } else if (F_ISSET(sp, SC_SCR_CENTER)) { if (vs_sm_fill(sp, LNO, P_MIDDLE)) return (1); } else if (vs_sm_fill(sp, OOBLNO, P_TOP)) return (1); F_SET(sp, SC_SCR_REDRAW); } /* * 6: Line movement. * * Line changes can cause the top line to change as well. As * before, if the movement is large, the screen is repainted. * * 6a: Small screens. * * Users can use the window, w300, w1200 and w9600 options to make * the screen artificially small. The behavior of these options * in the historic vi wasn't all that consistent, and, in fact, it * was never documented how various screen movements affected the * screen size. Generally, one of three things would happen: * 1: The screen would expand in size, showing the line * 2: The screen would scroll, showing the line * 3: The screen would compress to its smallest size and * repaint. * In general, scrolling didn't cause compression (200^D was handled * the same as ^D), movement to a specific line would (:N where N * was 1 line below the screen caused a screen compress), and cursor * movement would scroll if it was 11 lines or less, and compress if * it was more than 11 lines. (And, no, I have no idea where the 11 * comes from.) * * What we do is try and figure out if the line is less than half of * a full screen away. If it is, we expand the screen if there's * room, and then scroll as necessary. The alternative is to compress * and repaint. * * !!! * This code is a special case from beginning to end. Unfortunately, * home modems are still slow enough that it's worth having. * * XXX * If the line a really long one, i.e. part of the line is on the * screen but the column offset is not, we'll end up in the adjust * code, when we should probably have compressed the screen. */ if (IS_SMALL(sp)) if (LNO < HMAP->lno) { lcnt = vs_sm_nlines(sp, HMAP, LNO, sp->t_maxrows); if (lcnt <= HALFSCREEN(sp)) for (; lcnt && sp->t_rows != sp->t_maxrows; --lcnt, ++sp->t_rows) { ++TMAP; if (vs_sm_1down(sp)) return (1); } else goto small_fill; } else if (LNO > TMAP->lno) { lcnt = vs_sm_nlines(sp, TMAP, LNO, sp->t_maxrows); if (lcnt <= HALFSCREEN(sp)) for (; lcnt && sp->t_rows != sp->t_maxrows; --lcnt, ++sp->t_rows) { if (vs_sm_next(sp, TMAP, TMAP + 1)) return (1); ++TMAP; if (vs_line(sp, TMAP, NULL, NULL)) return (1); } else { small_fill: (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_clrtoeol(sp); for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) { (void)gp->scr_move(sp, TMAP - HMAP, 0); (void)gp->scr_clrtoeol(sp); } if (vs_sm_fill(sp, LNO, P_FILL)) return (1); F_SET(sp, SC_SCR_REDRAW); goto adjust; } } /* * 6b: Line down, or current screen. */ if (LNO >= HMAP->lno) { /* Current screen. */ if (LNO <= TMAP->lno) goto adjust; if (F_ISSET(sp, SC_SCR_TOP)) goto top; if (F_ISSET(sp, SC_SCR_CENTER)) goto middle; /* * If less than half a screen above the line, scroll down * until the line is on the screen. */ lcnt = vs_sm_nlines(sp, TMAP, LNO, HALFTEXT(sp)); if (lcnt < HALFTEXT(sp)) { while (lcnt--) if (vs_sm_1up(sp)) return (1); goto adjust; } goto bottom; } /* * 6c: If not on the current screen, may request center or top. */ if (F_ISSET(sp, SC_SCR_TOP)) goto top; if (F_ISSET(sp, SC_SCR_CENTER)) goto middle; /* * 6d: Line up. */ lcnt = vs_sm_nlines(sp, HMAP, LNO, HALFTEXT(sp)); if (lcnt < HALFTEXT(sp)) { /* * If less than half a screen below the line, scroll up until * the line is the first line on the screen. Special check so * that if the screen has been emptied, we refill it. */ if (db_exist(sp, HMAP->lno)) { while (lcnt--) if (vs_sm_1down(sp)) return (1); goto adjust; } else goto top; /* XXX No such line. */ /* * If less than a half screen from the bottom of the file, * put the last line of the file on the bottom of the screen. */ bottom: if (db_last(sp, &lastline)) return (1); tmp.lno = LNO; tmp.coff = HMAP->coff; tmp.soff = 1; lcnt = vs_sm_nlines(sp, &tmp, lastline, sp->t_rows); if (lcnt < HALFTEXT(sp)) { if (vs_sm_fill(sp, lastline, P_BOTTOM)) return (1); F_SET(sp, SC_SCR_REDRAW); goto adjust; } /* It's not close, just put the line in the middle. */ goto middle; } /* * If less than half a screen from the top of the file, put the first * line of the file at the top of the screen. Otherwise, put the line * in the middle of the screen. */ tmp.lno = 1; tmp.coff = HMAP->coff; tmp.soff = 1; lcnt = vs_sm_nlines(sp, &tmp, LNO, HALFTEXT(sp)); if (lcnt < HALFTEXT(sp)) { if (vs_sm_fill(sp, 1, P_TOP)) return (1); } else middle: if (vs_sm_fill(sp, LNO, P_MIDDLE)) return (1); if (0) { top: if (vs_sm_fill(sp, LNO, P_TOP)) return (1); } F_SET(sp, SC_SCR_REDRAW); /* * At this point we know part of the line is on the screen. Since * scrolling is done using logical lines, not physical, all of the * line may not be on the screen. While that's not necessarily bad, * if the part the cursor is on isn't there, we're going to lose. * This can be tricky; if the line covers the entire screen, lno * may be the same as both ends of the map, that's why we test BOTH * the top and the bottom of the map. This isn't a problem for * left-right scrolling, the cursor movement code handles the problem. * * There's a performance issue here if editing *really* long lines. * This gets to the right spot by scrolling, and, in a binary, by * scrolling hundreds of lines. If the adjustment looks like it's * going to be a serious problem, refill the screen and repaint. */ adjust: if (!O_ISSET(sp, O_LEFTRIGHT) && (LNO == HMAP->lno || LNO == TMAP->lno)) { cnt = vs_screens(sp, LNO, &CNO); if (LNO == HMAP->lno && cnt < HMAP->soff) if ((HMAP->soff - cnt) > HALFTEXT(sp)) { HMAP->soff = cnt; vs_sm_fill(sp, OOBLNO, P_TOP); F_SET(sp, SC_SCR_REDRAW); } else while (cnt < HMAP->soff) if (vs_sm_1down(sp)) return (1); if (LNO == TMAP->lno && cnt > TMAP->soff) if ((cnt - TMAP->soff) > HALFTEXT(sp)) { TMAP->soff = cnt; vs_sm_fill(sp, OOBLNO, P_BOTTOM); F_SET(sp, SC_SCR_REDRAW); } else while (cnt > TMAP->soff) if (vs_sm_1up(sp)) return (1); } /* * If the screen needs to be repainted, skip cursor optimization. * However, in the code above we skipped leftright scrolling on * the grounds that the cursor code would handle it. Make sure * the right screen is up. */ if (F_ISSET(sp, SC_SCR_REDRAW)) { if (O_ISSET(sp, O_LEFTRIGHT)) goto slow; goto paint; } /* * 7: Cursor movements (current screen only). */ if (!LF_ISSET(UPDATE_CURSOR)) goto number; /* * Decide cursor position. If the line has changed, the cursor has * moved over a tab, or don't know where the cursor was, reparse the * line. Otherwise, we've just moved over fixed-width characters, * and can calculate the left/right scrolling and cursor movement * without reparsing the line. Note that we don't know which (if any) * of the characters between the old and new cursor positions changed. * * XXX * With some work, it should be possible to handle tabs quickly, at * least in obvious situations, like moving right and encountering * a tab, without reparsing the whole line. * * If the line we're working with has changed, reread it.. */ if (F_ISSET(vip, VIP_CUR_INVALID) || LNO != OLNO) goto slow; /* Otherwise, if nothing's changed, ignore the cursor. */ if (CNO == OCNO) goto fast; /* * Get the current line. If this fails, we either have an empty * file and can just repaint, or there's a real problem. This * isn't a performance issue because there aren't any ways to get * here repeatedly. */ if (db_eget(sp, LNO, &p, &len, &isempty)) { if (isempty) goto slow; return (1); } #ifdef DEBUG /* Sanity checking. */ if (CNO >= len && len != 0) { msgq(sp, M_ERR, "Error: %s/%d: cno (%zu) >= len (%zu)", - tail(__FILE__), __LINE__, CNO, len); + basename(__FILE__), __LINE__, CNO, len); return (1); } #endif /* * The basic scheme here is to look at the characters in between * the old and new positions and decide how big they are on the * screen, and therefore, how many screen positions to move. */ if (CNO < OCNO) { /* * 7a: Cursor moved left. * * Point to the old character. The old cursor position can * be past EOL if, for example, we just deleted the rest of * the line. In this case, since we don't know the width of * the characters we traversed, we have to do it slowly. */ p += OCNO; cnt = (OCNO - CNO) + 1; if (OCNO >= len) goto slow; /* * Quick sanity check -- it's hard to figure out exactly when * we cross a screen boundary as we do in the cursor right * movement. If cnt is so large that we're going to cross the * boundary no matter what, stop now. */ if (SCNO + 1 + MAX_CHARACTER_COLUMNS < cnt) goto slow; /* * Count up the widths of the characters. If it's a tab - * character, go do it the the slow way. + * character, go do it the slow way. */ for (cwtotal = 0; cnt--; cwtotal += KEY_COL(sp, ch)) if ((ch = *(UCHAR_T *)p--) == '\t') goto slow; /* * Decrement the screen cursor by the total width of the * characters minus 1. */ cwtotal -= 1; /* * If we're moving left, and there's a wide character in the * current position, go to the end of the character. */ if (KEY_COL(sp, ch) > 1) cwtotal -= KEY_COL(sp, ch) - 1; /* * If the new column moved us off of the current logical line, * calculate a new one. If doing leftright scrolling, we've * moved off of the current screen, as well. */ if (SCNO < cwtotal) goto slow; SCNO -= cwtotal; } else { /* * 7b: Cursor moved right. * * Point to the first character to the right. */ p += OCNO + 1; cnt = CNO - OCNO; /* * Count up the widths of the characters. If it's a tab - * character, go do it the the slow way. If we cross a + * character, go do it the slow way. If we cross a * screen boundary, we can quit. */ for (cwtotal = SCNO; cnt--;) { if ((ch = *(UCHAR_T *)p++) == '\t') goto slow; if ((cwtotal += KEY_COL(sp, ch)) >= SCREEN_COLS(sp)) break; } /* * Increment the screen cursor by the total width of the * characters. */ SCNO = cwtotal; /* See screen change comment in section 6a. */ if (SCNO >= SCREEN_COLS(sp)) goto slow; } /* * 7c: Fast cursor update. * * We have the current column, retrieve the current row. */ fast: (void)gp->scr_cursor(sp, &y, ¬used); goto done_cursor; /* * 7d: Slow cursor update. * * Walk through the map and find the current line. */ slow: for (smp = HMAP; smp->lno != LNO; ++smp); /* * 7e: Leftright scrolling adjustment. * * If doing left-right scrolling and the cursor movement has changed * the displayed screen, scroll the screen left or right, unless we're * updating the info line in which case we just scroll that one line. * We adjust the offset up or down until we have a window that covers * the current column, making sure that we adjust differently for the * first screen as compared to subsequent ones. */ if (O_ISSET(sp, O_LEFTRIGHT)) { /* * Get the screen column for this character, and correct * for the number option offset. */ cnt = vs_columns(sp, NULL, LNO, &CNO, NULL); if (O_ISSET(sp, O_NUMBER)) cnt -= O_NUMBER_LENGTH; /* Adjust the window towards the beginning of the line. */ off = smp->coff; if (off >= cnt) { do { if (off >= O_VAL(sp, O_SIDESCROLL)) off -= O_VAL(sp, O_SIDESCROLL); else { off = 0; break; } } while (off >= cnt); goto shifted; } /* Adjust the window towards the end of the line. */ if ((off == 0 && off + SCREEN_COLS(sp) < cnt) || (off != 0 && off + sp->cols < cnt)) { do { off += O_VAL(sp, O_SIDESCROLL); } while (off + sp->cols < cnt); shifted: /* Fill in screen map with the new offset. */ if (F_ISSET(sp, SC_TINPUT_INFO)) smp->coff = off; else { for (smp = HMAP; smp <= TMAP; ++smp) smp->coff = off; leftright_warp = 1; } goto paint; } /* * We may have jumped here to adjust a leftright screen because * redraw was set. If so, we have to paint the entire screen. */ if (F_ISSET(sp, SC_SCR_REDRAW)) goto paint; } /* * Update the screen lines for this particular file line until we * have a new screen cursor position. */ for (y = -1, vip->sc_smap = NULL; smp <= TMAP && smp->lno == LNO; ++smp) { if (vs_line(sp, smp, &y, &SCNO)) return (1); if (y != -1) { vip->sc_smap = smp; break; } } goto done_cursor; /* * 8: Repaint the entire screen. * * Lost big, do what you have to do. We flush the cache, since * SC_SCR_REDRAW gets set when the screen isn't worth fixing, and * it's simpler to repaint. So, don't trust anything that we * think we know about it. */ paint: for (smp = HMAP; smp <= TMAP; ++smp) SMAP_FLUSH(smp); for (y = -1, vip->sc_smap = NULL, smp = HMAP; smp <= TMAP; ++smp) { if (vs_line(sp, smp, &y, &SCNO)) return (1); if (y != -1 && vip->sc_smap == NULL) vip->sc_smap = smp; } /* * If it's a small screen and we're redrawing, clear the unused lines, * ex may have overwritten them. */ if (F_ISSET(sp, SC_SCR_REDRAW) && IS_SMALL(sp)) for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) { (void)gp->scr_move(sp, cnt, 0); (void)gp->scr_clrtoeol(sp); } didpaint = 1; done_cursor: /* * Sanity checking. When the repainting code messes up, the usual * result is we don't repaint the cursor and so sc_smap will be * NULL. If we're debugging, die, otherwise restart from scratch. */ #ifdef DEBUG if (vip->sc_smap == NULL) abort(); #else if (vip->sc_smap == NULL) { F_SET(sp, SC_SCR_REFORMAT); return (vs_paint(sp, flags)); } #endif /* * 9: Set the remembered cursor values. */ OCNO = CNO; OLNO = LNO; /* * 10: Repaint the line numbers. * * If O_NUMBER is set and the VIP_N_RENUMBER bit is set, and we * didn't repaint the screen, repaint all of the line numbers, * they've changed. */ number: if (O_ISSET(sp, O_NUMBER) && F_ISSET(vip, VIP_N_RENUMBER) && !didpaint && vs_number(sp)) return (1); /* * 11: Update the mode line, position the cursor, and flush changes. * * If we warped the screen, we have to refresh everything. */ if (leftright_warp) LF_SET(UPDATE_CURSOR | UPDATE_SCREEN); if (LF_ISSET(UPDATE_SCREEN) && !IS_ONELINE(sp) && !F_ISSET(vip, VIP_S_MODELINE) && !F_ISSET(sp, SC_TINPUT_INFO)) vs_modeline(sp); if (LF_ISSET(UPDATE_CURSOR)) { (void)gp->scr_move(sp, y, SCNO); /* * XXX * If the screen shifted, we recalculate the "most favorite" * cursor position. Vi won't know that we've warped the * screen, so it's going to have a wrong idea about where the * cursor should be. This is vi's problem, and fixing it here * is a gross layering violation. */ if (leftright_warp) (void)vs_column(sp, &sp->rcm); } if (LF_ISSET(UPDATE_SCREEN)) (void)gp->scr_refresh(sp, F_ISSET(vip, VIP_N_EX_PAINT)); /* 12: Clear the flags that are handled by this routine. */ F_CLR(sp, SC_SCR_CENTER | SC_SCR_REDRAW | SC_SCR_REFORMAT | SC_SCR_TOP); F_CLR(vip, VIP_CUR_INVALID | VIP_N_EX_PAINT | VIP_N_REFRESH | VIP_N_RENUMBER | VIP_S_MODELINE); return (0); #undef LNO #undef OLNO #undef CNO #undef OCNO #undef SCNO } /* * vs_modeline -- * Update the mode line. */ static void vs_modeline(SCR *sp) { static char * const modes[] = { "215|Append", /* SM_APPEND */ "216|Change", /* SM_CHANGE */ "217|Command", /* SM_COMMAND */ "218|Insert", /* SM_INSERT */ "219|Replace", /* SM_REPLACE */ }; GS *gp; size_t cols, curcol, curlen, endpoint, len, midpoint; const char *t = NULL; int ellipsis; char buf[20]; gp = sp->gp; /* * We put down the file name, the ruler, the mode and the dirty flag. * If there's not enough room, there's not enough room, we don't play * any special games. We try to put the ruler in the middle and the * mode and dirty flag at the end. * * !!! * Leave the last character blank, in case it's a really dumb terminal * with hardware scroll. Second, don't paint the last character in the * screen, SunOS 4.1.1 and Ultrix 4.2 curses won't let you. * * Move to the last line on the screen. */ (void)gp->scr_move(sp, LASTLINE(sp), 0); /* If more than one screen in the display, show the file name. */ curlen = 0; if (IS_SPLIT(sp)) { CHAR_T *wp, *p; size_t l; CHAR2INT(sp, sp->frp->name, strlen(sp->frp->name) + 1, wp, l); p = wp + l; for (ellipsis = 0, cols = sp->cols / 2; --p > wp;) { if (*p == '/') { ++p; break; } if ((curlen += KEY_COL(sp, *p)) > cols) { ellipsis = 3; curlen += KEY_LEN(sp, '.') * 3 + KEY_LEN(sp, ' '); while (curlen > cols) { ++p; curlen -= KEY_COL(sp, *p); } break; } } if (ellipsis) { while (ellipsis--) (void)gp->scr_addstr(sp, KEY_NAME(sp, '.'), KEY_LEN(sp, '.')); (void)gp->scr_addstr(sp, KEY_NAME(sp, ' '), KEY_LEN(sp, ' ')); } for (; *p != '\0'; ++p) (void)gp->scr_addstr(sp, KEY_NAME(sp, *p), KEY_COL(sp, *p)); } /* Clear the rest of the line. */ (void)gp->scr_clrtoeol(sp); /* * Display the ruler. If we're not at the midpoint yet, move there. * Otherwise, add in two extra spaces. * * Adjust the current column for the fact that the editor uses it as * a zero-based number. * * XXX * Assume that numbers, commas, and spaces only take up a single * column on the screen. */ cols = sp->cols - 1; if (O_ISSET(sp, O_RULER)) { vs_column(sp, &curcol); len = snprintf(buf, sizeof(buf), "%lu,%lu", (u_long)sp->lno, (u_long)(curcol + 1)); midpoint = (cols - ((len + 1) / 2)) / 2; if (curlen < midpoint) { (void)gp->scr_move(sp, LASTLINE(sp), midpoint); curlen += len; } else if (curlen + 2 + len < cols) { (void)gp->scr_addstr(sp, " ", 2); curlen += 2 + len; } (void)gp->scr_addstr(sp, buf, len); } /* * Display the mode and the modified flag, as close to the end of the * line as possible, but guaranteeing at least two spaces between the * ruler and the modified flag. */ #define MODESIZE 9 endpoint = cols; if (O_ISSET(sp, O_SHOWMODE)) { if (F_ISSET(sp->ep, F_MODIFIED)) --endpoint; t = msg_cat(sp, modes[sp->showmode], &len); endpoint -= len; } if (endpoint > curlen + 2) { (void)gp->scr_move(sp, LASTLINE(sp), endpoint); if (O_ISSET(sp, O_SHOWMODE)) { if (F_ISSET(sp->ep, F_MODIFIED)) (void)gp->scr_addstr(sp, KEY_NAME(sp, '*'), KEY_LEN(sp, '*')); (void)gp->scr_addstr(sp, t, len); } } } Index: head/contrib/nvi/vi/vs_relative.c =================================================================== --- head/contrib/nvi/vi/vs_relative.c (revision 365498) +++ head/contrib/nvi/vi/vs_relative.c (revision 365499) @@ -1,294 +1,290 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: vs_relative.c,v 10.19 2011/12/01 15:22:59 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * vs_column -- * Return the logical column of the cursor in the line. * * PUBLIC: int vs_column(SCR *, size_t *); */ int vs_column(SCR *sp, size_t *colp) { VI_PRIVATE *vip; vip = VIP(sp); *colp = (O_ISSET(sp, O_LEFTRIGHT) ? vip->sc_smap->coff : (vip->sc_smap->soff - 1) * sp->cols) + vip->sc_col - (O_ISSET(sp, O_NUMBER) ? O_NUMBER_LENGTH : 0); return (0); } /* * vs_screens -- * Return the screens necessary to display the line, or if specified, * the physical character column within the line, including space * required for the O_NUMBER and O_LIST options. * * PUBLIC: size_t vs_screens(SCR *, recno_t, size_t *); */ size_t vs_screens(SCR *sp, recno_t lno, size_t *cnop) { size_t cols, screens; /* Left-right screens are simple, it's always 1. */ if (O_ISSET(sp, O_LEFTRIGHT)) return (1); /* * Check for a cached value. We maintain a cache because, if the * line is large, this routine gets called repeatedly. One other * hack, lots of time the cursor is on column one, which is an easy * one. */ if (cnop == NULL) { if (VIP(sp)->ss_lno == lno) return (VIP(sp)->ss_screens); } else if (*cnop == 0) return (1); /* Figure out how many columns the line/column needs. */ cols = vs_columns(sp, NULL, lno, cnop, NULL); screens = (cols / sp->cols + (cols % sp->cols ? 1 : 0)); if (screens == 0) screens = 1; /* Cache the value. */ if (cnop == NULL) { VIP(sp)->ss_lno = lno; VIP(sp)->ss_screens = screens; } return (screens); } /* * vs_columns -- * Return the screen columns necessary to display the line, or, * if specified, the physical character column within the line. * * PUBLIC: size_t vs_columns(SCR *, CHAR_T *, recno_t, size_t *, size_t *); */ size_t vs_columns(SCR *sp, CHAR_T *lp, recno_t lno, size_t *cnop, size_t *diffp) { size_t chlen, cno, curoff, last = 0, len, scno; int ch, leftright, listset; CHAR_T *p; /* * Initialize the screen offset. */ scno = 0; /* Leading number if O_NUMBER option set. */ if (O_ISSET(sp, O_NUMBER)) scno += O_NUMBER_LENGTH; /* Need the line to go any further. */ if (lp == NULL) { (void)db_get(sp, lno, 0, &lp, &len); if (len == 0) goto done; } /* Missing or empty lines are easy. */ if (lp == NULL) { done: if (diffp != NULL) /* XXX */ *diffp = 0; return scno; } /* Store away the values of the list and leftright edit options. */ listset = O_ISSET(sp, O_LIST); leftright = O_ISSET(sp, O_LEFTRIGHT); /* * Initialize the pointer into the buffer and current offset. */ p = lp; curoff = scno; /* Macro to return the display length of any signal character. */ #define CHLEN(val) (ch = *(UCHAR_T *)p++) == '\t' && \ !listset ? TAB_OFF(val) : KEY_COL(sp, ch); /* * If folding screens (the historic vi screen format), past the end * of the current screen, and the character was a tab, reset the * current screen column to 0, and the total screen columns to the * last column of the screen. Otherwise, display the rest of the * character in the next screen. */ #define TAB_RESET { \ curoff += chlen; \ if (!leftright && curoff >= sp->cols) \ if (ch == '\t') { \ curoff = 0; \ scno -= scno % sp->cols; \ } else \ curoff -= sp->cols; \ } if (cnop == NULL) while (len--) { chlen = CHLEN(curoff); last = scno; scno += chlen; TAB_RESET; } else for (cno = *cnop;; --cno) { chlen = CHLEN(curoff); last = scno; scno += chlen; TAB_RESET; if (cno == 0) break; } /* Add the trailing '$' if the O_LIST option set. */ if (listset && cnop == NULL) scno += KEY_LEN(sp, '$'); /* * The text input screen code needs to know how much additional * room the last two characters required, so that it can handle * tab character displays correctly. */ if (diffp != NULL) *diffp = scno - last; return (scno); } /* * vs_rcm -- * Return the physical column from the line that will display a * character closest to the currently most attractive character * position (which is stored as a screen column). * * PUBLIC: size_t vs_rcm(SCR *, recno_t, int); */ size_t vs_rcm(SCR *sp, recno_t lno, int islast) { size_t len; /* Last character is easy, and common. */ if (islast) { if (db_get(sp, lno, 0, NULL, &len) || len == 0) return (0); return (len - 1); } /* First character is easy, and common. */ if (sp->rcm == 0) return (0); return (vs_colpos(sp, lno, sp->rcm)); } /* * vs_colpos -- * Return the physical column from the line that will display a * character closest to the specified screen column. * * PUBLIC: size_t vs_colpos(SCR *, recno_t, size_t); */ size_t vs_colpos(SCR *sp, recno_t lno, size_t cno) { size_t chlen, curoff, len, llen, off, scno; int ch = 0, leftright, listset; CHAR_T *lp, *p; /* Need the line to go any further. */ (void)db_get(sp, lno, 0, &lp, &llen); /* Missing or empty lines are easy. */ if (lp == NULL || llen == 0) return (0); /* Store away the values of the list and leftright edit options. */ listset = O_ISSET(sp, O_LIST); leftright = O_ISSET(sp, O_LEFTRIGHT); /* Discard screen (logical) lines. */ off = cno / sp->cols; cno %= sp->cols; for (scno = 0, p = lp, len = llen; off--;) { for (; len && scno < sp->cols; --len) scno += CHLEN(scno); /* * If reached the end of the physical line, return the last * physical character in the line. */ if (len == 0) return (llen - 1); /* * If folding screens (the historic vi screen format), past * the end of the current screen, and the character was a tab, * reset the current screen column to 0. Otherwise, the rest * of the character is displayed in the next screen. */ if (leftright && ch == '\t') scno = 0; else scno -= sp->cols; } /* Step through the line until reach the right character or EOL. */ for (curoff = scno; len--;) { chlen = CHLEN(curoff); /* * If we've reached the specific character, there are three * cases. * * 1: scno == cno, i.e. the current character ends at the * screen character we care about. * a: off < llen - 1, i.e. not the last character in * the line, return the offset of the next character. * b: else return the offset of the last character. * 2: scno != cno, i.e. this character overruns the character * we care about, return the offset of this character. */ if ((scno += chlen) >= cno) { off = p - lp; return (scno == cno ? (off < llen - 1 ? off : llen - 1) : off - 1); } TAB_RESET; } /* No such character; return the start of the last character. */ return (llen - 1); } Index: head/contrib/nvi/vi/vs_smap.c =================================================================== --- head/contrib/nvi/vi/vs_smap.c (revision 365498) +++ head/contrib/nvi/vi/vs_smap.c (revision 365499) @@ -1,1242 +1,1238 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: vs_smap.c,v 10.31 2011/02/26 13:56:21 skimo Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static int vs_deleteln(SCR *, int); static int vs_insertln(SCR *, int); static int vs_sm_delete(SCR *, recno_t); static int vs_sm_down(SCR *, MARK *, recno_t, scroll_t, SMAP *); static int vs_sm_erase(SCR *); static int vs_sm_insert(SCR *, recno_t); static int vs_sm_reset(SCR *, recno_t); static int vs_sm_up(SCR *, MARK *, recno_t, scroll_t, SMAP *); /* * vs_change -- * Make a change to the screen. * * PUBLIC: int vs_change(SCR *, recno_t, lnop_t); */ int vs_change(SCR *sp, recno_t lno, lnop_t op) { VI_PRIVATE *vip; SMAP *p; size_t cnt, oldy, oldx; vip = VIP(sp); /* * XXX * Very nasty special case. The historic vi code displays a single * space (or a '$' if the list option is set) for the first line in * an "empty" file. If we "insert" a line, that line gets scrolled * down, not repainted, so it's incorrect when we refresh the screen. * The vi text input functions detect it explicitly and don't insert * a new line. * * Check for line #2 before going to the end of the file. */ if (((op == LINE_APPEND && lno == 0) || (op == LINE_INSERT && lno == 1)) && !db_exist(sp, 2)) { lno = 1; op = LINE_RESET; } /* Appending is the same as inserting, if the line is incremented. */ if (op == LINE_APPEND) { ++lno; op = LINE_INSERT; } /* Ignore the change if the line is after the map. */ if (lno > TMAP->lno) return (0); /* * If the line is before the map, and it's a decrement, decrement * the map. If it's an increment, increment the map. Otherwise, * ignore it. */ if (lno < HMAP->lno) { switch (op) { case LINE_APPEND: abort(); /* NOTREACHED */ case LINE_DELETE: for (p = HMAP, cnt = sp->t_rows; cnt--; ++p) --p->lno; if (sp->lno >= lno) --sp->lno; F_SET(vip, VIP_N_RENUMBER); break; case LINE_INSERT: for (p = HMAP, cnt = sp->t_rows; cnt--; ++p) ++p->lno; if (sp->lno >= lno) ++sp->lno; F_SET(vip, VIP_N_RENUMBER); break; case LINE_RESET: break; } return (0); } F_SET(vip, VIP_N_REFRESH); /* * Invalidate the line size cache, and invalidate the cursor if it's * on this line, */ VI_SCR_CFLUSH(vip); if (sp->lno == lno) F_SET(vip, VIP_CUR_INVALID); /* * If ex modifies the screen after ex output is already on the screen * or if we've switched into ex canonical mode, don't touch it -- we'll * get scrolling wrong, at best. */ if (!F_ISSET(sp, SC_TINPUT_INFO) && (F_ISSET(sp, SC_SCR_EXWROTE) || VIP(sp)->totalcount > 1)) { F_SET(vip, VIP_N_EX_REDRAW); return (0); } /* Save and restore the cursor for these routines. */ (void)sp->gp->scr_cursor(sp, &oldy, &oldx); switch (op) { case LINE_DELETE: if (vs_sm_delete(sp, lno)) return (1); if (sp->lno > lno) --sp->lno; F_SET(vip, VIP_N_RENUMBER); break; case LINE_INSERT: if (vs_sm_insert(sp, lno)) return (1); if (sp->lno > lno) ++sp->lno; F_SET(vip, VIP_N_RENUMBER); break; case LINE_RESET: if (vs_sm_reset(sp, lno)) return (1); break; default: abort(); } (void)sp->gp->scr_move(sp, oldy, oldx); return (0); } /* * vs_sm_fill -- * Fill in the screen map, placing the specified line at the * right position. There isn't any way to tell if an SMAP * entry has been filled in, so this routine had better be * called with P_FILL set before anything else is done. * * !!! * Unexported interface: if lno is OOBLNO, P_TOP means that the HMAP * slot is already filled in, P_BOTTOM means that the TMAP slot is * already filled in, and we just finish up the job. * * PUBLIC: int vs_sm_fill(SCR *, recno_t, pos_t); */ int vs_sm_fill(SCR *sp, recno_t lno, pos_t pos) { SMAP *p, tmp; size_t cnt; /* Flush all cached information from the SMAP. */ for (p = HMAP, cnt = sp->t_rows; cnt--; ++p) SMAP_FLUSH(p); /* * If the map is filled, the screen must be redrawn. * * XXX * This is a bug. We should try and figure out if the desired line * is already in the map or close by -- scrolling the screen would * be a lot better than redrawing. */ F_SET(sp, SC_SCR_REDRAW); switch (pos) { case P_FILL: tmp.lno = 1; tmp.coff = 0; tmp.soff = 1; /* See if less than half a screen from the top. */ if (vs_sm_nlines(sp, &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) { lno = 1; goto top; } /* See if less than half a screen from the bottom. */ if (db_last(sp, &tmp.lno)) return (1); tmp.coff = 0; tmp.soff = vs_screens(sp, tmp.lno, NULL); if (vs_sm_nlines(sp, &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) { TMAP->lno = tmp.lno; TMAP->coff = tmp.coff; TMAP->soff = tmp.soff; goto bottom; } goto middle; case P_TOP: if (lno != OOBLNO) { top: HMAP->lno = lno; HMAP->coff = 0; HMAP->soff = 1; } else { /* * If number of lines HMAP->lno (top line) spans * changed due to, say reformatting, and now is * fewer than HMAP->soff, reset so the line is * redrawn at the top of the screen. */ cnt = vs_screens(sp, HMAP->lno, NULL); if (cnt < HMAP->soff) HMAP->soff = 1; } /* If we fail, just punt. */ for (p = HMAP, cnt = sp->t_rows; --cnt; ++p) if (vs_sm_next(sp, p, p + 1)) goto err; break; case P_MIDDLE: /* If we fail, guess that the file is too small. */ middle: p = HMAP + sp->t_rows / 2; p->lno = lno; p->coff = 0; p->soff = 1; for (; p > HMAP; --p) if (vs_sm_prev(sp, p, p - 1)) { lno = 1; goto top; } /* If we fail, just punt. */ p = HMAP + sp->t_rows / 2; for (; p < TMAP; ++p) if (vs_sm_next(sp, p, p + 1)) goto err; break; case P_BOTTOM: if (lno != OOBLNO) { TMAP->lno = lno; TMAP->coff = 0; TMAP->soff = vs_screens(sp, lno, NULL); } /* If we fail, guess that the file is too small. */ bottom: for (p = TMAP; p > HMAP; --p) if (vs_sm_prev(sp, p, p - 1)) { lno = 1; goto top; } break; default: abort(); } return (0); /* * Try and put *something* on the screen. If this fails, we have a * serious hard error. */ err: HMAP->lno = 1; HMAP->coff = 0; HMAP->soff = 1; for (p = HMAP; p < TMAP; ++p) if (vs_sm_next(sp, p, p + 1)) return (1); return (0); } /* * For the routines vs_sm_reset, vs_sm_delete and vs_sm_insert: if the * screen contains only a single line (whether because the screen is small * or the line large), it gets fairly exciting. Skip the fun, set a flag * so the screen map is refilled and the screen redrawn, and return. This * is amazingly slow, but it's not clear that anyone will care. */ #define HANDLE_WEIRDNESS(cnt) { \ if (cnt >= sp->t_rows) { \ F_SET(sp, SC_SCR_REFORMAT); \ return (0); \ } \ } /* * vs_sm_delete -- * Delete a line out of the SMAP. */ static int vs_sm_delete(SCR *sp, recno_t lno) { SMAP *p, *t; size_t cnt_orig; /* * Find the line in the map, and count the number of screen lines * which display any part of the deleted line. */ for (p = HMAP; p->lno != lno; ++p); if (O_ISSET(sp, O_LEFTRIGHT)) cnt_orig = 1; else for (cnt_orig = 1, t = p + 1; t <= TMAP && t->lno == lno; ++cnt_orig, ++t); HANDLE_WEIRDNESS(cnt_orig); /* Delete that many lines from the screen. */ (void)sp->gp->scr_move(sp, p - HMAP, 0); if (vs_deleteln(sp, cnt_orig)) return (1); /* Shift the screen map up. */ memmove(p, p + cnt_orig, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP)); /* Decrement the line numbers for the rest of the map. */ for (t = TMAP - cnt_orig; p <= t; ++p) --p->lno; /* Display the new lines. */ for (p = TMAP - cnt_orig;;) { if (p < TMAP && vs_sm_next(sp, p, p + 1)) return (1); /* vs_sm_next() flushed the cache. */ if (vs_line(sp, ++p, NULL, NULL)) return (1); if (p == TMAP) break; } return (0); } /* * vs_sm_insert -- * Insert a line into the SMAP. */ static int vs_sm_insert(SCR *sp, recno_t lno) { SMAP *p, *t; size_t cnt_orig, cnt, coff; /* Save the offset. */ coff = HMAP->coff; /* * Find the line in the map, find out how many screen lines * needed to display the line. */ for (p = HMAP; p->lno != lno; ++p); cnt_orig = vs_screens(sp, lno, NULL); HANDLE_WEIRDNESS(cnt_orig); /* * The lines left in the screen override the number of screen * lines in the inserted line. */ cnt = (TMAP - p) + 1; if (cnt_orig > cnt) cnt_orig = cnt; /* Push down that many lines. */ (void)sp->gp->scr_move(sp, p - HMAP, 0); if (vs_insertln(sp, cnt_orig)) return (1); /* Shift the screen map down. */ memmove(p + cnt_orig, p, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP)); /* Increment the line numbers for the rest of the map. */ for (t = p + cnt_orig; t <= TMAP; ++t) ++t->lno; /* Fill in the SMAP for the new lines, and display. */ for (cnt = 1, t = p; cnt <= cnt_orig; ++t, ++cnt) { t->lno = lno; t->coff = coff; t->soff = cnt; SMAP_FLUSH(t); if (vs_line(sp, t, NULL, NULL)) return (1); } return (0); } /* * vs_sm_reset -- * Reset a line in the SMAP. */ static int vs_sm_reset(SCR *sp, recno_t lno) { SMAP *p, *t; size_t cnt_orig, cnt_new, cnt, diff; /* * See if the number of on-screen rows taken up by the old display * for the line is the same as the number needed for the new one. * If so, repaint, otherwise do it the hard way. */ for (p = HMAP; p->lno != lno; ++p); if (O_ISSET(sp, O_LEFTRIGHT)) { t = p; cnt_orig = cnt_new = 1; } else { for (cnt_orig = 0, t = p; t <= TMAP && t->lno == lno; ++cnt_orig, ++t); cnt_new = vs_screens(sp, lno, NULL); } HANDLE_WEIRDNESS(cnt_orig); if (cnt_orig == cnt_new) { do { SMAP_FLUSH(p); if (vs_line(sp, p, NULL, NULL)) return (1); } while (++p < t); return (0); } if (cnt_orig < cnt_new) { /* Get the difference. */ diff = cnt_new - cnt_orig; /* * The lines left in the screen override the number of screen * lines in the inserted line. */ cnt = (TMAP - p) + 1; if (diff > cnt) diff = cnt; /* If there are any following lines, push them down. */ if (cnt > 1) { (void)sp->gp->scr_move(sp, p - HMAP, 0); if (vs_insertln(sp, diff)) return (1); /* Shift the screen map down. */ memmove(p + diff, p, (((TMAP - p) - diff) + 1) * sizeof(SMAP)); } /* Fill in the SMAP for the replaced line, and display. */ for (cnt = 1, t = p; cnt_new-- && t <= TMAP; ++t, ++cnt) { t->lno = lno; t->soff = cnt; SMAP_FLUSH(t); if (vs_line(sp, t, NULL, NULL)) return (1); } } else { /* Get the difference. */ diff = cnt_orig - cnt_new; /* Delete that many lines from the screen. */ (void)sp->gp->scr_move(sp, p - HMAP, 0); if (vs_deleteln(sp, diff)) return (1); /* Shift the screen map up. */ memmove(p, p + diff, (((TMAP - p) - diff) + 1) * sizeof(SMAP)); /* Fill in the SMAP for the replaced line, and display. */ for (cnt = 1, t = p; cnt_new--; ++t, ++cnt) { t->lno = lno; t->soff = cnt; SMAP_FLUSH(t); if (vs_line(sp, t, NULL, NULL)) return (1); } /* Display the new lines at the bottom of the screen. */ for (t = TMAP - diff;;) { if (t < TMAP && vs_sm_next(sp, t, t + 1)) return (1); /* vs_sm_next() flushed the cache. */ if (vs_line(sp, ++t, NULL, NULL)) return (1); if (t == TMAP) break; } } return (0); } /* * vs_sm_scroll * Scroll the SMAP up/down count logical lines. Different * semantics based on the vi command, *sigh*. * * PUBLIC: int vs_sm_scroll(SCR *, MARK *, recno_t, scroll_t); */ int vs_sm_scroll(SCR *sp, MARK *rp, recno_t count, scroll_t scmd) { SMAP *smp; /* * Invalidate the cursor. The line is probably going to change, * (although for ^E and ^Y it may not). In any case, the scroll * routines move the cursor to draw things. */ F_SET(VIP(sp), VIP_CUR_INVALID); /* Find the cursor in the screen. */ if (vs_sm_cursor(sp, &smp)) return (1); switch (scmd) { case CNTRL_B: case CNTRL_U: case CNTRL_Y: case Z_CARAT: if (vs_sm_down(sp, rp, count, scmd, smp)) return (1); break; case CNTRL_D: case CNTRL_E: case CNTRL_F: case Z_PLUS: if (vs_sm_up(sp, rp, count, scmd, smp)) return (1); break; default: abort(); } /* * !!! * If we're at the start of a line, go for the first non-blank. * This makes it look like the old vi, even though we're moving * around by logical lines, not physical ones. * * XXX * In the presence of a long line, which has more than a screen * width of leading spaces, this code can cause a cursor warp. * Live with it. */ if (scmd != CNTRL_E && scmd != CNTRL_Y && rp->cno == 0 && nonblank(sp, rp->lno, &rp->cno)) return (1); return (0); } /* * vs_sm_up -- * Scroll the SMAP up count logical lines. */ static int vs_sm_up(SCR *sp, MARK *rp, recno_t count, scroll_t scmd, SMAP *smp) { int cursor_set, echanged, zset; SMAP *ssmp, s1, s2; /* * Check to see if movement is possible. * * Get the line after the map. If that line is a new one (and if * O_LEFTRIGHT option is set, this has to be true), and the next * line doesn't exist, and the cursor doesn't move, or the cursor * isn't even on the screen, or the cursor is already at the last * line in the map, it's an error. If that test succeeded because * the cursor wasn't at the end of the map, test to see if the map * is mostly empty. */ if (vs_sm_next(sp, TMAP, &s1)) return (1); if (s1.lno > TMAP->lno && !db_exist(sp, s1.lno)) { if (scmd == CNTRL_E || scmd == Z_PLUS || smp == TMAP) { v_eof(sp, NULL); return (1); } if (vs_sm_next(sp, smp, &s1)) return (1); if (s1.lno > smp->lno && !db_exist(sp, s1.lno)) { v_eof(sp, NULL); return (1); } } /* * Small screens: see vs_refresh.c section 6a. * * If it's a small screen, and the movement isn't larger than a * screen, i.e some context will remain, open up the screen and * display by scrolling. In this case, the cursor moves down one * line for each line displayed. Otherwise, erase/compress and * repaint, and move the cursor to the first line in the screen. * Note, the ^F command is always in the latter case, for historical * reasons. */ cursor_set = 0; if (IS_SMALL(sp)) { if (count >= sp->t_maxrows || scmd == CNTRL_F) { s1 = TMAP[0]; if (vs_sm_erase(sp)) return (1); for (; count--; s1 = s2) { if (vs_sm_next(sp, &s1, &s2)) return (1); if (s2.lno != s1.lno && !db_exist(sp, s2.lno)) break; } TMAP[0] = s2; if (vs_sm_fill(sp, OOBLNO, P_BOTTOM)) return (1); return (vs_sm_position(sp, rp, 0, P_TOP)); } cursor_set = scmd == CNTRL_E || vs_sm_cursor(sp, &ssmp); for (; count && sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) { if (vs_sm_next(sp, TMAP, &s1)) return (1); if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno)) break; *++TMAP = s1; /* vs_sm_next() flushed the cache. */ if (vs_line(sp, TMAP, NULL, NULL)) return (1); if (!cursor_set) ++ssmp; } if (!cursor_set) { rp->lno = ssmp->lno; rp->cno = ssmp->c_sboff; } if (count == 0) return (0); } for (echanged = zset = 0; count; --count) { /* Decide what would show up on the screen. */ if (vs_sm_next(sp, TMAP, &s1)) return (1); /* If the line doesn't exist, we're done. */ if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno)) break; /* Scroll the screen cursor up one logical line. */ if (vs_sm_1up(sp)) return (1); switch (scmd) { case CNTRL_E: if (smp > HMAP) --smp; else echanged = 1; break; case Z_PLUS: if (zset) { if (smp > HMAP) --smp; } else { smp = TMAP; zset = 1; } /* FALLTHROUGH */ default: break; } } if (cursor_set) return(0); switch (scmd) { case CNTRL_E: /* * On a ^E that was forced to change lines, try and keep the * cursor as close as possible to the last position, but also * set it up so that the next "real" movement will return the * cursor to the closest position to the last real movement. */ if (echanged) { rp->lno = smp->lno; rp->cno = vs_colpos(sp, smp->lno, (O_ISSET(sp, O_LEFTRIGHT) ? smp->coff : (smp->soff - 1) * sp->cols) + sp->rcm % sp->cols); } return (0); case CNTRL_F: /* * If there are more lines, the ^F command is positioned at * the first line of the screen. */ if (!count) { smp = HMAP; break; } /* FALLTHROUGH */ case CNTRL_D: /* * The ^D and ^F commands move the cursor towards EOF * if there are more lines to move. Check to be sure * the lines actually exist. (They may not if the * file is smaller than the screen.) */ for (; count; --count, ++smp) if (smp == TMAP || !db_exist(sp, smp[1].lno)) break; break; case Z_PLUS: /* The z+ command moves the cursor to the first new line. */ break; default: abort(); } if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL)) return (1); rp->lno = smp->lno; rp->cno = smp->c_scoff == 255 ? 0 : smp->c_sboff; return (0); } /* * vs_sm_1up -- * Scroll the SMAP up one. * * PUBLIC: int vs_sm_1up(SCR *); */ int vs_sm_1up(SCR *sp) { /* * Delete the top line of the screen. Shift the screen map * up and display a new line at the bottom of the screen. */ (void)sp->gp->scr_move(sp, 0, 0); if (vs_deleteln(sp, 1)) return (1); /* One-line screens can fail. */ if (IS_ONELINE(sp)) { if (vs_sm_next(sp, TMAP, TMAP)) return (1); } else { memmove(HMAP, HMAP + 1, (sp->rows - 1) * sizeof(SMAP)); if (vs_sm_next(sp, TMAP - 1, TMAP)) return (1); } /* vs_sm_next() flushed the cache. */ return (vs_line(sp, TMAP, NULL, NULL)); } /* * vs_deleteln -- * Delete a line a la curses, make sure to put the information * line and other screens back. */ static int vs_deleteln(SCR *sp, int cnt) { GS *gp; size_t oldy, oldx; gp = sp->gp; /* If the screen is vertically split, we can't scroll it. */ if (IS_VSPLIT(sp)) { F_SET(sp, SC_SCR_REDRAW); return (0); } if (IS_ONELINE(sp)) (void)gp->scr_clrtoeol(sp); else { (void)gp->scr_cursor(sp, &oldy, &oldx); while (cnt--) { (void)gp->scr_deleteln(sp); (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_insertln(sp); (void)gp->scr_move(sp, oldy, oldx); } } return (0); } /* * vs_sm_down -- * Scroll the SMAP down count logical lines. */ static int vs_sm_down(SCR *sp, MARK *rp, recno_t count, scroll_t scmd, SMAP *smp) { SMAP *ssmp, s1, s2; int cursor_set, ychanged, zset; /* Check to see if movement is possible. */ if (HMAP->lno == 1 && (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1) && (scmd == CNTRL_Y || scmd == Z_CARAT || smp == HMAP)) { v_sof(sp, NULL); return (1); } /* * Small screens: see vs_refresh.c section 6a. * * If it's a small screen, and the movement isn't larger than a * screen, i.e some context will remain, open up the screen and * display by scrolling. In this case, the cursor moves up one * line for each line displayed. Otherwise, erase/compress and * repaint, and move the cursor to the first line in the screen. * Note, the ^B command is always in the latter case, for historical * reasons. */ cursor_set = scmd == CNTRL_Y; if (IS_SMALL(sp)) { if (count >= sp->t_maxrows || scmd == CNTRL_B) { s1 = HMAP[0]; if (vs_sm_erase(sp)) return (1); for (; count--; s1 = s2) { if (vs_sm_prev(sp, &s1, &s2)) return (1); if (s2.lno == 1 && (O_ISSET(sp, O_LEFTRIGHT) || s2.soff == 1)) break; } HMAP[0] = s2; if (vs_sm_fill(sp, OOBLNO, P_TOP)) return (1); return (vs_sm_position(sp, rp, 0, P_BOTTOM)); } cursor_set = scmd == CNTRL_Y || vs_sm_cursor(sp, &ssmp); for (; count && sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) { if (HMAP->lno == 1 && (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1)) break; ++TMAP; if (vs_sm_1down(sp)) return (1); } if (!cursor_set) { rp->lno = ssmp->lno; rp->cno = ssmp->c_sboff; } if (count == 0) return (0); } for (ychanged = zset = 0; count; --count) { /* If the line doesn't exist, we're done. */ if (HMAP->lno == 1 && (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1)) break; /* Scroll the screen and cursor down one logical line. */ if (vs_sm_1down(sp)) return (1); switch (scmd) { case CNTRL_Y: if (smp < TMAP) ++smp; else ychanged = 1; break; case Z_CARAT: if (zset) { if (smp < TMAP) ++smp; } else { smp = HMAP; zset = 1; } /* FALLTHROUGH */ default: break; } } if (scmd != CNTRL_Y && cursor_set) return(0); switch (scmd) { case CNTRL_B: /* * If there are more lines, the ^B command is positioned at * the last line of the screen. However, the line may not * exist. */ if (!count) { for (smp = TMAP; smp > HMAP; --smp) if (db_exist(sp, smp->lno)) break; break; } /* FALLTHROUGH */ case CNTRL_U: /* * The ^B and ^U commands move the cursor towards SOF * if there are more lines to move. */ if (count < smp - HMAP) smp -= count; else smp = HMAP; break; case CNTRL_Y: /* * On a ^Y that was forced to change lines, try and keep the * cursor as close as possible to the last position, but also * set it up so that the next "real" movement will return the * cursor to the closest position to the last real movement. */ if (ychanged) { rp->lno = smp->lno; rp->cno = vs_colpos(sp, smp->lno, (O_ISSET(sp, O_LEFTRIGHT) ? smp->coff : (smp->soff - 1) * sp->cols) + sp->rcm % sp->cols); } return (0); case Z_CARAT: /* The z^ command moves the cursor to the first new line. */ break; default: abort(); } if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL)) return (1); rp->lno = smp->lno; rp->cno = smp->c_scoff == 255 ? 0 : smp->c_sboff; return (0); } /* * vs_sm_erase -- * Erase the small screen area for the scrolling functions. */ static int vs_sm_erase(SCR *sp) { GS *gp; gp = sp->gp; (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_clrtoeol(sp); for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) { (void)gp->scr_move(sp, TMAP - HMAP, 0); (void)gp->scr_clrtoeol(sp); } return (0); } /* * vs_sm_1down -- * Scroll the SMAP down one. * * PUBLIC: int vs_sm_1down(SCR *); */ int vs_sm_1down(SCR *sp) { /* * Insert a line at the top of the screen. Shift the screen map * down and display a new line at the top of the screen. */ (void)sp->gp->scr_move(sp, 0, 0); if (vs_insertln(sp, 1)) return (1); /* One-line screens can fail. */ if (IS_ONELINE(sp)) { if (vs_sm_prev(sp, HMAP, HMAP)) return (1); } else { memmove(HMAP + 1, HMAP, (sp->rows - 1) * sizeof(SMAP)); if (vs_sm_prev(sp, HMAP + 1, HMAP)) return (1); } /* vs_sm_prev() flushed the cache. */ return (vs_line(sp, HMAP, NULL, NULL)); } /* * vs_insertln -- * Insert a line a la curses, make sure to put the information * line and other screens back. */ static int vs_insertln(SCR *sp, int cnt) { GS *gp; size_t oldy, oldx; gp = sp->gp; /* If the screen is vertically split, we can't scroll it. */ if (IS_VSPLIT(sp)) { F_SET(sp, SC_SCR_REDRAW); return (0); } if (IS_ONELINE(sp)) { (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_clrtoeol(sp); } else { (void)gp->scr_cursor(sp, &oldy, &oldx); while (cnt--) { (void)gp->scr_move(sp, LASTLINE(sp) - 1, 0); (void)gp->scr_deleteln(sp); (void)gp->scr_move(sp, oldy, oldx); (void)gp->scr_insertln(sp); } } return (0); } /* * vs_sm_next -- * Fill in the next entry in the SMAP. * * PUBLIC: int vs_sm_next(SCR *, SMAP *, SMAP *); */ int vs_sm_next(SCR *sp, SMAP *p, SMAP *t) { size_t lcnt; SMAP_FLUSH(t); if (O_ISSET(sp, O_LEFTRIGHT)) { t->lno = p->lno + 1; t->coff = p->coff; } else { lcnt = vs_screens(sp, p->lno, NULL); if (lcnt == p->soff) { t->lno = p->lno + 1; t->soff = 1; } else { t->lno = p->lno; t->soff = p->soff + 1; } } return (0); } /* * vs_sm_prev -- * Fill in the previous entry in the SMAP. * * PUBLIC: int vs_sm_prev(SCR *, SMAP *, SMAP *); */ int vs_sm_prev(SCR *sp, SMAP *p, SMAP *t) { SMAP_FLUSH(t); if (O_ISSET(sp, O_LEFTRIGHT)) { t->lno = p->lno - 1; t->coff = p->coff; } else { if (p->soff != 1) { t->lno = p->lno; t->soff = p->soff - 1; } else { t->lno = p->lno - 1; t->soff = vs_screens(sp, t->lno, NULL); } } return (t->lno == 0); } /* * vs_sm_cursor -- * Return the SMAP entry referenced by the cursor. * * PUBLIC: int vs_sm_cursor(SCR *, SMAP **); */ int vs_sm_cursor(SCR *sp, SMAP **smpp) { SMAP *p; /* See if the cursor is not in the map. */ if (sp->lno < HMAP->lno || sp->lno > TMAP->lno) return (1); /* Find the first occurence of the line. */ for (p = HMAP; p->lno != sp->lno; ++p); /* Fill in the map information until we find the right line. */ for (; p <= TMAP; ++p) { /* Short lines are common and easy to detect. */ if (p != TMAP && (p + 1)->lno != p->lno) { *smpp = p; return (0); } if (!SMAP_CACHE(p) && vs_line(sp, p, NULL, NULL)) return (1); if (p->c_eboff >= sp->cno) { *smpp = p; return (0); } } /* It was past the end of the map after all. */ return (1); } /* * vs_sm_position -- * Return the line/column of the top, middle or last line on the screen. * (The vi H, M and L commands.) Here because only the screen routines * know what's really out there. * * PUBLIC: int vs_sm_position(SCR *, MARK *, u_long, pos_t); */ int vs_sm_position(SCR *sp, MARK *rp, u_long cnt, pos_t pos) { SMAP *smp; recno_t last; switch (pos) { case P_TOP: /* * !!! * Historically, an invalid count to the H command failed. * We do nothing special here, just making sure that H in * an empty screen works. */ if (cnt > TMAP - HMAP) goto sof; smp = HMAP + cnt; if (cnt && !db_exist(sp, smp->lno)) { sof: msgq(sp, M_BERR, "220|Movement past the end-of-screen"); return (1); } break; case P_MIDDLE: /* * !!! * Historically, a count to the M command was ignored. * If the screen isn't filled, find the middle of what's * real and move there. */ if (!db_exist(sp, TMAP->lno)) { if (db_last(sp, &last)) return (1); for (smp = TMAP; smp->lno > last && smp > HMAP; --smp); if (smp > HMAP) smp -= (smp - HMAP) / 2; } else smp = (HMAP + (TMAP - HMAP) / 2) + cnt; break; case P_BOTTOM: /* * !!! * Historically, an invalid count to the L command failed. * If the screen isn't filled, find the bottom of what's * real and try to offset from there. */ if (cnt > TMAP - HMAP) goto eof; smp = TMAP - cnt; if (!db_exist(sp, smp->lno)) { if (db_last(sp, &last)) return (1); for (; smp->lno > last && smp > HMAP; --smp); if (cnt > smp - HMAP) { eof: msgq(sp, M_BERR, "221|Movement past the beginning-of-screen"); return (1); } smp -= cnt; } break; default: abort(); } /* Make sure that the cached information is valid. */ if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL)) return (1); rp->lno = smp->lno; rp->cno = smp->c_sboff; return (0); } /* * vs_sm_nlines -- * Return the number of screen lines from an SMAP entry to the * start of some file line, less than a maximum value. * * PUBLIC: recno_t vs_sm_nlines(SCR *, SMAP *, recno_t, size_t); */ recno_t vs_sm_nlines(SCR *sp, SMAP *from_sp, recno_t to_lno, size_t max) { recno_t lno, lcnt; if (O_ISSET(sp, O_LEFTRIGHT)) return (from_sp->lno > to_lno ? from_sp->lno - to_lno : to_lno - from_sp->lno); if (from_sp->lno == to_lno) return (from_sp->soff - 1); if (from_sp->lno > to_lno) { lcnt = from_sp->soff - 1; /* Correct for off-by-one. */ for (lno = from_sp->lno; --lno >= to_lno && lcnt <= max;) lcnt += vs_screens(sp, lno, NULL); } else { lno = from_sp->lno; lcnt = (vs_screens(sp, lno, NULL) - from_sp->soff) + 1; for (; ++lno < to_lno && lcnt <= max;) lcnt += vs_screens(sp, lno, NULL); } return (lcnt); } Index: head/contrib/nvi/vi/vs_split.c =================================================================== --- head/contrib/nvi/vi/vs_split.c (revision 365498) +++ head/contrib/nvi/vi/vs_split.c (revision 365499) @@ -1,949 +1,945 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" -#ifndef lint -static const char sccsid[] = "$Id: vs_split.c,v 10.43 2015/04/05 15:21:55 zy Exp $"; -#endif /* not lint */ - #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" typedef enum { HORIZ_FOLLOW, HORIZ_PRECEDE, VERT_FOLLOW, VERT_PRECEDE } jdir_t; static SCR *vs_getbg(SCR *, char *); static void vs_insert(SCR *sp, GS *gp); static int vs_join(SCR *, SCR **, jdir_t *); /* * vs_split -- * Create a new screen, horizontally. * * PUBLIC: int vs_split(SCR *, SCR *, int); */ int vs_split( SCR *sp, SCR *new, int ccl) /* Colon-command line split. */ { GS *gp; SMAP *smp; size_t half; int issmallscreen, splitup; gp = sp->gp; /* Check to see if it's possible. */ /* XXX: The IS_ONELINE fix will change this, too. */ if (sp->rows < 4) { msgq(sp, M_ERR, "222|Screen must be larger than %d lines to split", 4 - 1); return (1); } /* Wait for any messages in the screen. */ vs_resolve(sp, NULL, 1); /* Get a new screen map. */ - CALLOC(sp, _HMAP(new), SMAP *, SIZE_HMAP(sp), sizeof(SMAP)); + CALLOC(sp, _HMAP(new), SIZE_HMAP(sp), sizeof(SMAP)); if (_HMAP(new) == NULL) return (1); _HMAP(new)->lno = sp->lno; _HMAP(new)->coff = 0; _HMAP(new)->soff = 1; /* Split the screen in half. */ half = sp->rows / 2; if (ccl && half > 6) half = 6; /* * Small screens: see vs_refresh.c section 6a. Set a flag so * we know to fix the screen up later. */ issmallscreen = IS_SMALL(sp); /* The columns in the screen don't change. */ new->coff = sp->coff; new->cols = sp->cols; /* * Split the screen, and link the screens together. If creating a * screen to edit the colon command line or the cursor is in the top * half of the current screen, the new screen goes under the current * screen. Else, it goes above the current screen. * * Recalculate current cursor position based on sp->lno, we're called * with the cursor on the colon command line. Then split the screen * in half and update the shared information. */ splitup = !ccl && (vs_sm_cursor(sp, &smp) ? 0 : (smp - HMAP) + 1) >= half; if (splitup) { /* Old is bottom half. */ new->rows = sp->rows - half; /* New. */ new->roff = sp->roff; sp->rows = half; /* Old. */ sp->roff += new->rows; /* * If the parent is the bottom half of the screen, shift * the map down to match on-screen text. */ memcpy(_HMAP(sp), _HMAP(sp) + new->rows, (sp->t_maxrows - new->rows) * sizeof(SMAP)); } else { /* Old is top half. */ new->rows = half; /* New. */ sp->rows -= half; /* Old. */ new->roff = sp->roff + sp->rows; } /* Adjust maximum text count. */ sp->t_maxrows = IS_ONELINE(sp) ? 1 : sp->rows - 1; new->t_maxrows = IS_ONELINE(new) ? 1 : new->rows - 1; /* * Small screens: see vs_refresh.c, section 6a. * * The child may have different screen options sizes than the parent, * so use them. Guarantee that text counts aren't larger than the * new screen sizes. */ if (issmallscreen) { /* Fix the text line count for the parent. */ if (splitup) sp->t_rows -= new->rows; /* Fix the parent screen. */ if (sp->t_rows > sp->t_maxrows) sp->t_rows = sp->t_maxrows; if (sp->t_minrows > sp->t_maxrows) sp->t_minrows = sp->t_maxrows; /* Fix the child screen. */ new->t_minrows = new->t_rows = O_VAL(sp, O_WINDOW); if (new->t_rows > new->t_maxrows) new->t_rows = new->t_maxrows; if (new->t_minrows > new->t_maxrows) new->t_minrows = new->t_maxrows; } else { sp->t_minrows = sp->t_rows = IS_ONELINE(sp) ? 1 : sp->rows - 1; /* * The new screen may be a small screen, even if the parent * was not. Don't complain if O_WINDOW is too large, we're * splitting the screen so the screen is much smaller than * normal. */ new->t_minrows = new->t_rows = O_VAL(sp, O_WINDOW); if (new->t_rows > new->rows - 1) new->t_minrows = new->t_rows = IS_ONELINE(new) ? 1 : new->rows - 1; } /* Adjust the ends of the new and old maps. */ _TMAP(sp) = IS_ONELINE(sp) ? _HMAP(sp) : _HMAP(sp) + (sp->t_rows - 1); _TMAP(new) = IS_ONELINE(new) ? _HMAP(new) : _HMAP(new) + (new->t_rows - 1); /* Reset the length of the default scroll. */ if ((sp->defscroll = sp->t_maxrows / 2) == 0) sp->defscroll = 1; if ((new->defscroll = new->t_maxrows / 2) == 0) new->defscroll = 1; /* Fit the screen into the logical chain. */ vs_insert(new, sp->gp); /* Tell the display that we're splitting. */ (void)gp->scr_split(sp, new); /* * Initialize the screen flags: * * If we're in vi mode in one screen, we don't have to reinitialize. * This isn't just a cosmetic fix. The path goes like this: * * return into vi(), SC_SSWITCH set * call vs_refresh() with SC_STATUS set * call vs_resolve to display the status message * call vs_refresh() because the SC_SCR_VI bit isn't set * * Things go downhill at this point. * * Draw the new screen from scratch, and add a status line. */ F_SET(new, SC_SCR_REFORMAT | SC_STATUS | F_ISSET(sp, SC_EX | SC_VI | SC_SCR_VI | SC_SCR_EX | SC_READONLY)); return (0); } /* * vs_vsplit -- * Create a new screen, vertically. * * PUBLIC: int vs_vsplit(SCR *, SCR *); */ int vs_vsplit(SCR *sp, SCR *new) { GS *gp; size_t cols; gp = sp->gp; /* Check to see if it's possible. */ if (sp->cols / 2 <= MINIMUM_SCREEN_COLS) { msgq(sp, M_ERR, "288|Screen must be larger than %d columns to split", MINIMUM_SCREEN_COLS * 2); return (1); } /* Wait for any messages in the screen. */ vs_resolve(sp, NULL, 1); /* Get a new screen map. */ - CALLOC(sp, _HMAP(new), SMAP *, SIZE_HMAP(sp), sizeof(SMAP)); + CALLOC(sp, _HMAP(new), SIZE_HMAP(sp), sizeof(SMAP)); if (_HMAP(new) == NULL) return (1); _HMAP(new)->lno = sp->lno; _HMAP(new)->coff = 0; _HMAP(new)->soff = 1; /* * Split the screen in half; we have to sacrifice a column to delimit * the screens. * * XXX * We always split to the right... that makes more sense to me, and * I don't want to play the stupid games that I play when splitting * horizontally. * * XXX * We reserve a column for the screen, "knowing" that curses needs * one. This should be worked out with the display interface. */ cols = sp->cols / 2; new->cols = sp->cols - cols - 1; sp->cols = cols; new->coff = sp->coff + cols + 1; sp->cno = 0; /* Nothing else changes. */ new->rows = sp->rows; new->t_rows = sp->t_rows; new->t_maxrows = sp->t_maxrows; new->t_minrows = sp->t_minrows; new->roff = sp->roff; new->defscroll = sp->defscroll; _TMAP(new) = _HMAP(new) + (new->t_rows - 1); /* Fit the screen into the logical chain. */ vs_insert(new, sp->gp); /* Tell the display that we're splitting. */ (void)gp->scr_split(sp, new); /* Redraw the old screen from scratch. */ F_SET(sp, SC_SCR_REFORMAT | SC_STATUS); /* * Initialize the screen flags: * * If we're in vi mode in one screen, we don't have to reinitialize. * This isn't just a cosmetic fix. The path goes like this: * * return into vi(), SC_SSWITCH set * call vs_refresh() with SC_STATUS set * call vs_resolve to display the status message * call vs_refresh() because the SC_SCR_VI bit isn't set * * Things go downhill at this point. * * Draw the new screen from scratch, and add a status line. */ F_SET(new, SC_SCR_REFORMAT | SC_STATUS | F_ISSET(sp, SC_EX | SC_VI | SC_SCR_VI | SC_SCR_EX | SC_READONLY)); return (0); } /* * vs_insert -- * Insert the new screen into the correct place in the logical * chain. */ static void vs_insert(SCR *sp, GS *gp) { SCR *tsp; gp = sp->gp; /* Move past all screens with lower row numbers. */ TAILQ_FOREACH(tsp, gp->dq, q) if (tsp->roff >= sp->roff) break; /* * Move past all screens with the same row number and lower * column numbers. */ for (; tsp != NULL; tsp = TAILQ_NEXT(tsp, q)) if (tsp->roff != sp->roff || tsp->coff > sp->coff) break; /* * If we reached the end, this screen goes there. Otherwise, * put it before or after the screen where we stopped. */ if (tsp == NULL) { TAILQ_INSERT_TAIL(gp->dq, sp, q); } else if (tsp->roff < sp->roff || (tsp->roff == sp->roff && tsp->coff < sp->coff)) { TAILQ_INSERT_AFTER(gp->dq, tsp, sp, q); } else TAILQ_INSERT_BEFORE(tsp, sp, q); } /* * vs_discard -- * Discard the screen, folding the real-estate into a related screen, * if one exists, and return that screen. * * PUBLIC: int vs_discard(SCR *, SCR **); */ int vs_discard(SCR *sp, SCR **spp) { GS *gp; SCR *tsp, **lp, *list[100]; jdir_t jdir; gp = sp->gp; /* * Save the old screen's cursor information. * * XXX * If called after file_end(), and the underlying file was a tmp * file, it may have gone away. */ if (sp->frp != NULL) { sp->frp->lno = sp->lno; sp->frp->cno = sp->cno; F_SET(sp->frp, FR_CURSORSET); } /* If no other screens to join, we're done. */ if (!IS_SPLIT(sp)) { (void)gp->scr_discard(sp, NULL); if (spp != NULL) *spp = NULL; return (0); } /* * Find a set of screens that cover one of the screen's borders. * Check the vertical axis first, for no particular reason. * * XXX * It's possible (I think?), to create a screen that shares no full * border with any other set of screens, so we can't discard it. We * just complain at the user until they clean it up. */ if (vs_join(sp, list, &jdir)) return (1); /* * Modify the affected screens. Redraw the modified screen(s) from * scratch, setting a status line. If this is ever a performance * problem we could play games with the map, but I wrote that code * before and it was never clean or easy. * * Don't clean up the discarded screen's information. If the screen * isn't exiting, we'll do the work when the user redisplays it. */ switch (jdir) { case HORIZ_FOLLOW: case HORIZ_PRECEDE: for (lp = &list[0]; (tsp = *lp) != NULL; ++lp) { /* * Small screens: see vs_refresh.c section 6a. Adjust * text line info, unless it's a small screen. * * Reset the length of the default scroll. * * Reset the map references. */ tsp->rows += sp->rows; if (!IS_SMALL(tsp)) tsp->t_rows = tsp->t_minrows = tsp->rows - 1; tsp->t_maxrows = tsp->rows - 1; tsp->defscroll = tsp->t_maxrows / 2; *(_HMAP(tsp) + (tsp->t_rows - 1)) = *_TMAP(tsp); _TMAP(tsp) = _HMAP(tsp) + (tsp->t_rows - 1); switch (jdir) { case HORIZ_FOLLOW: tsp->roff = sp->roff; vs_sm_fill(tsp, OOBLNO, P_TOP); break; case HORIZ_PRECEDE: vs_sm_fill(tsp, OOBLNO, P_BOTTOM); break; default: abort(); } F_SET(tsp, SC_STATUS); } break; case VERT_FOLLOW: case VERT_PRECEDE: for (lp = &list[0]; (tsp = *lp) != NULL; ++lp) { if (jdir == VERT_FOLLOW) tsp->coff = sp->coff; tsp->cols += sp->cols + 1; /* XXX: DIVIDER */ vs_sm_fill(tsp, OOBLNO, P_TOP); F_SET(tsp, SC_STATUS); } break; default: abort(); } /* Find the closest screen that changed and move to it. */ tsp = list[0]; if (spp != NULL) *spp = tsp; /* Tell the display that we're discarding a screen. */ (void)gp->scr_discard(sp, list); return (0); } /* * vs_join -- * Find a set of screens that covers a screen's border. */ static int vs_join(SCR *sp, SCR **listp, jdir_t *jdirp) { GS *gp; SCR **lp, *tsp; int first; size_t tlen; gp = sp->gp; /* Check preceding vertical. */ for (lp = listp, tlen = sp->rows, tsp = TAILQ_FIRST(gp->dq); tsp != NULL; tsp = TAILQ_NEXT(tsp, q)) { if (sp == tsp) continue; /* Test if precedes the screen vertically. */ if (tsp->coff + tsp->cols + 1 != sp->coff) continue; /* * Test if a subset on the vertical axis. If overlaps the * beginning or end, we can't join on this axis at all. */ if (tsp->roff > sp->roff + sp->rows) continue; if (tsp->roff < sp->roff) { if (tsp->roff + tsp->rows >= sp->roff) break; continue; } if (tsp->roff + tsp->rows > sp->roff + sp->rows) break; #ifdef DEBUG if (tlen < tsp->rows) abort(); #endif tlen -= tsp->rows; *lp++ = tsp; } if (tlen == 0) { *lp = NULL; *jdirp = VERT_PRECEDE; return (0); } /* Check following vertical. */ for (lp = listp, tlen = sp->rows, tsp = TAILQ_FIRST(gp->dq); tsp != NULL; tsp = TAILQ_NEXT(tsp, q)) { if (sp == tsp) continue; /* Test if follows the screen vertically. */ if (tsp->coff != sp->coff + sp->cols + 1) continue; /* * Test if a subset on the vertical axis. If overlaps the * beginning or end, we can't join on this axis at all. */ if (tsp->roff > sp->roff + sp->rows) continue; if (tsp->roff < sp->roff) { if (tsp->roff + tsp->rows >= sp->roff) break; continue; } if (tsp->roff + tsp->rows > sp->roff + sp->rows) break; #ifdef DEBUG if (tlen < tsp->rows) abort(); #endif tlen -= tsp->rows; *lp++ = tsp; } if (tlen == 0) { *lp = NULL; *jdirp = VERT_FOLLOW; return (0); } /* Check preceding horizontal. */ for (first = 0, lp = listp, tlen = sp->cols, tsp = TAILQ_FIRST(gp->dq); tsp != NULL; tsp = TAILQ_NEXT(tsp, q)) { if (sp == tsp) continue; /* Test if precedes the screen horizontally. */ if (tsp->roff + tsp->rows != sp->roff) continue; /* * Test if a subset on the horizontal axis. If overlaps the * beginning or end, we can't join on this axis at all. */ if (tsp->coff > sp->coff + sp->cols) continue; if (tsp->coff < sp->coff) { if (tsp->coff + tsp->cols >= sp->coff) break; continue; } if (tsp->coff + tsp->cols > sp->coff + sp->cols) break; #ifdef DEBUG if (tlen < tsp->cols) abort(); #endif tlen -= tsp->cols + first; first = 1; *lp++ = tsp; } if (tlen == 0) { *lp = NULL; *jdirp = HORIZ_PRECEDE; return (0); } /* Check following horizontal. */ for (first = 0, lp = listp, tlen = sp->cols, tsp = TAILQ_FIRST(gp->dq); tsp != NULL; tsp = TAILQ_NEXT(tsp, q)) { if (sp == tsp) continue; /* Test if precedes the screen horizontally. */ if (tsp->roff != sp->roff + sp->rows) continue; /* * Test if a subset on the horizontal axis. If overlaps the * beginning or end, we can't join on this axis at all. */ if (tsp->coff > sp->coff + sp->cols) continue; if (tsp->coff < sp->coff) { if (tsp->coff + tsp->cols >= sp->coff) break; continue; } if (tsp->coff + tsp->cols > sp->coff + sp->cols) break; #ifdef DEBUG if (tlen < tsp->cols) abort(); #endif tlen -= tsp->cols + first; first = 1; *lp++ = tsp; } if (tlen == 0) { *lp = NULL; *jdirp = HORIZ_FOLLOW; return (0); } return (1); } /* * vs_fg -- * Background the current screen, and foreground a new one. * * PUBLIC: int vs_fg(SCR *, SCR **, CHAR_T *, int); */ int vs_fg(SCR *sp, SCR **nspp, CHAR_T *name, int newscreen) { GS *gp; SCR *nsp; char *np; size_t nlen; gp = sp->gp; if (name) INT2CHAR(sp, name, STRLEN(name) + 1, np, nlen); else np = NULL; if (newscreen) /* Get the specified background screen. */ nsp = vs_getbg(sp, np); else /* Swap screens. */ if (vs_swap(sp, &nsp, np)) return (1); if ((*nspp = nsp) == NULL) { msgq_wstr(sp, M_ERR, name, name == NULL ? "223|There are no background screens" : "224|There's no background screen editing a file named %s"); return (1); } if (newscreen) { /* Remove the new screen from the background queue. */ TAILQ_REMOVE(gp->hq, nsp, q); /* Split the screen; if we fail, hook the screen back in. */ if (vs_split(sp, nsp, 0)) { TAILQ_INSERT_TAIL(gp->hq, nsp, q); return (1); } } else { /* Move the old screen to the background queue. */ TAILQ_REMOVE(gp->dq, sp, q); TAILQ_INSERT_TAIL(gp->hq, sp, q); } return (0); } /* * vs_bg -- * Background the screen, and switch to the next one. * * PUBLIC: int vs_bg(SCR *); */ int vs_bg(SCR *sp) { GS *gp; SCR *nsp; gp = sp->gp; /* Try and join with another screen. */ if (vs_discard(sp, &nsp)) return (1); if (nsp == NULL) { msgq(sp, M_ERR, "225|You may not background your only displayed screen"); return (1); } /* Move the old screen to the background queue. */ TAILQ_REMOVE(gp->dq, sp, q); TAILQ_INSERT_TAIL(gp->hq, sp, q); /* Toss the screen map. */ free(_HMAP(sp)); _HMAP(sp) = NULL; /* Switch screens. */ sp->nextdisp = nsp; F_SET(sp, SC_SSWITCH); return (0); } /* * vs_swap -- * Swap the current screen with a backgrounded one. * * PUBLIC: int vs_swap(SCR *, SCR **, char *); */ int vs_swap(SCR *sp, SCR **nspp, char *name) { GS *gp; SCR *nsp, *list[2]; gp = sp->gp; /* Get the specified background screen. */ if ((*nspp = nsp = vs_getbg(sp, name)) == NULL) return (0); /* * Save the old screen's cursor information. * * XXX * If called after file_end(), and the underlying file was a tmp * file, it may have gone away. */ if (sp->frp != NULL) { sp->frp->lno = sp->lno; sp->frp->cno = sp->cno; F_SET(sp->frp, FR_CURSORSET); } /* Switch screens. */ sp->nextdisp = nsp; F_SET(sp, SC_SSWITCH); /* Initialize terminal information. */ VIP(nsp)->srows = VIP(sp)->srows; /* Initialize screen information. */ nsp->cols = sp->cols; nsp->rows = sp->rows; /* XXX: Only place in vi that sets rows. */ nsp->roff = sp->roff; /* * Small screens: see vs_refresh.c, section 6a. * * The new screens may have different screen options sizes than the * old one, so use them. Make sure that text counts aren't larger * than the new screen sizes. */ if (IS_SMALL(nsp)) { nsp->t_minrows = nsp->t_rows = O_VAL(nsp, O_WINDOW); if (nsp->t_rows > sp->t_maxrows) nsp->t_rows = nsp->t_maxrows; if (nsp->t_minrows > sp->t_maxrows) nsp->t_minrows = nsp->t_maxrows; } else nsp->t_rows = nsp->t_maxrows = nsp->t_minrows = nsp->rows - 1; /* Reset the length of the default scroll. */ nsp->defscroll = nsp->t_maxrows / 2; /* Allocate a new screen map. */ - CALLOC_RET(nsp, _HMAP(nsp), SMAP *, SIZE_HMAP(nsp), sizeof(SMAP)); + CALLOC_RET(nsp, _HMAP(nsp), SIZE_HMAP(nsp), sizeof(SMAP)); _TMAP(nsp) = _HMAP(nsp) + (nsp->t_rows - 1); /* Fill the map. */ nsp->gp = sp->gp; if (vs_sm_fill(nsp, nsp->lno, P_FILL)) return (1); /* * The new screen replaces the old screen in the parent/child list. * We insert the new screen after the old one. If we're exiting, * the exit will delete the old one, if we're foregrounding, the fg * code will move the old one to the background queue. */ TAILQ_REMOVE(gp->hq, nsp, q); TAILQ_INSERT_AFTER(gp->dq, sp, nsp, q); /* * Don't change the screen's cursor information other than to * note that the cursor is wrong. */ F_SET(VIP(nsp), VIP_CUR_INVALID); /* Draw the new screen from scratch, and add a status line. */ F_SET(nsp, SC_SCR_REDRAW | SC_STATUS); list[0] = nsp; list[1] = NULL; (void)gp->scr_discard(sp, list); return (0); } /* * vs_resize -- * Change the absolute size of the current screen. * * PUBLIC: int vs_resize(SCR *, long, adj_t); */ int vs_resize(SCR *sp, long int count, adj_t adj) { GS *gp; SCR *g, *s, *prev, *next, *list[3] = {NULL, NULL, NULL}; size_t g_off, s_off; gp = sp->gp; /* * Figure out which screens will grow, which will shrink, and * make sure it's possible. */ if (count == 0) return (0); if (adj == A_SET) { if (sp->t_maxrows == count) return (0); if (sp->t_maxrows > count) { adj = A_DECREASE; count = sp->t_maxrows - count; } else { adj = A_INCREASE; count = count - sp->t_maxrows; } } /* Find first overlapping screen */ for (next = TAILQ_NEXT(sp, q); next != NULL && (next->coff >= sp->coff + sp->cols || next->coff + next->cols <= sp->coff); next = TAILQ_NEXT(next, q)); /* See if we can use it */ if (next != NULL && (sp->coff != next->coff || sp->cols != next->cols)) next = NULL; for (prev = TAILQ_PREV(sp, _dqh, q); prev != NULL && (prev->coff >= sp->coff + sp->cols || prev->coff + prev->cols <= sp->coff); prev = TAILQ_PREV(prev, _dqh, q)); if (prev != NULL && (sp->coff != prev->coff || sp->cols != prev->cols)) prev = NULL; g_off = s_off = 0; if (adj == A_DECREASE) { if (count < 0) count = -count; s = sp; if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count) goto toosmall; if ((g = prev) == NULL) { if ((g = next) == NULL) goto toobig; g_off = -count; } else s_off = count; } else { g = sp; if ((s = next) != NULL && s->t_maxrows >= MINIMUM_SCREEN_ROWS + count) s_off = count; else s = NULL; if (s == NULL) { if ((s = prev) == NULL) { toobig: msgq(sp, M_BERR, adj == A_DECREASE ? "227|The screen cannot shrink" : "228|The screen cannot grow"); return (1); } if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count) { toosmall: msgq(sp, M_BERR, "226|The screen can only shrink to %d rows", MINIMUM_SCREEN_ROWS); return (1); } g_off = -count; } } /* * Fix up the screens; we could optimize the reformatting of the * screen, but this isn't likely to be a common enough operation * to make it worthwhile. */ s->rows += -count; s->roff += s_off; g->rows += count; g->roff += g_off; g->t_rows += count; if (g->t_minrows == g->t_maxrows) g->t_minrows += count; g->t_maxrows += count; _TMAP(g) += count; F_SET(g, SC_SCR_REFORMAT | SC_STATUS); s->t_rows -= count; s->t_maxrows -= count; if (s->t_minrows > s->t_maxrows) s->t_minrows = s->t_maxrows; _TMAP(s) -= count; F_SET(s, SC_SCR_REFORMAT | SC_STATUS); /* XXXX */ list[0] = g; list[1] = s; gp->scr_discard(0, list); return (0); } /* * vs_getbg -- * Get the specified background screen, or, if name is NULL, the first * background screen. */ static SCR * vs_getbg(SCR *sp, char *name) { GS *gp; SCR *nsp; char *p; gp = sp->gp; /* If name is NULL, return the first background screen on the list. */ if (name == NULL) return (TAILQ_FIRST(gp->hq)); /* Search for a full match. */ TAILQ_FOREACH(nsp, gp->hq, q) if (!strcmp(nsp->frp->name, name)) break; if (nsp != NULL) return (nsp); /* Search for a last-component match. */ TAILQ_FOREACH(nsp, gp->hq, q) { if ((p = strrchr(nsp->frp->name, '/')) == NULL) p = nsp->frp->name; else ++p; if (!strcmp(p, name)) break; } if (nsp != NULL) return (nsp); return (NULL); } Index: head/contrib/nvi =================================================================== --- head/contrib/nvi (revision 365498) +++ head/contrib/nvi (revision 365499) Property changes on: head/contrib/nvi ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /vendor/nvi/dist:r365495,365497 Index: head/usr.bin/vi/Makefile =================================================================== --- head/usr.bin/vi/Makefile (revision 365498) +++ head/usr.bin/vi/Makefile (revision 365499) @@ -1,77 +1,76 @@ # # $FreeBSD$ # .include PACKAGE= vi SRCDIR= ${SRCTOP}/contrib/nvi SUBDIR+= catalog WARNS?= 0 # some warn issues on 32 bit machines VI= nvi EX= nex VIEW= nview PROG= nvi CFLAGS+= -D__REGEX_PRIVATE LINKS= ${BINDIR}/${VI} ${BINDIR}/${EX} ${BINDIR}/${VI} ${BINDIR}/${VIEW} LINKS+= ${BINDIR}/${VI} ${BINDIR}/vi ${BINDIR}/${EX} ${BINDIR}/ex LINKS+= ${BINDIR}/${VI} ${BINDIR}/view -MAN= ${SRCDIR}/docs/man/vi.1 +MAN= ${SRCDIR}/man/vi.1 MLINKS+=vi.1 ex.1 vi.1 view.1 MLINKS+=vi.1 nex.1 vi.1 nview.1 vi.1 nvi.1 .PATH: ${SRCDIR}/common .PATH: ${SRCDIR}/ex .PATH: ${SRCDIR}/cl .PATH: ${SRCDIR}/vi .PATH: ${SRCDIR}/regex CFLAGS+=-I${.CURDIR} -I${SRCDIR} -I${SRCDIR}/regex LIBADD= util ncursesw -CFLAGS+= -DUSE_WIDECHAR .if ${MK_ICONV} == "yes" && !defined(RESCUE) -CFLAGS+= -DUSE_ICONV +CFLAGS+= -DUSE_ICONV -DICONV_TRADITIONAL .endif CLEANFILES+=${EX} # Vi curses sources SRCS+= cl_funcs.c cl_main.c cl_read.c cl_screen.c cl_term.c # General sources. SRCS+= conv.c cut.c delete.c encoding.c exf.c key.c line.c log.c main.c mark.c msg.c options.c \ options_f.c put.c recover.c screen.c search.c seq.c util.c # Ex source. SRCS+= ex.c ex_abbrev.c ex_append.c ex_args.c ex_argv.c ex_at.c ex_bang.c \ ex_cd.c ex_cmd.c ex_cscope.c ex_delete.c ex_display.c \ ex_edit.c ex_equal.c ex_file.c ex_filter.c ex_global.c \ ex_init.c ex_join.c ex_map.c ex_mark.c ex_mkexrc.c ex_move.c \ ex_open.c ex_preserve.c ex_print.c ex_put.c ex_quit.c \ ex_read.c ex_screen.c ex_script.c ex_set.c ex_shell.c \ ex_shift.c ex_source.c ex_stop.c ex_subst.c ex_tag.c \ ex_txt.c ex_undo.c ex_usage.c ex_util.c ex_version.c ex_visual.c \ ex_write.c ex_yank.c ex_z.c # Vi source. SRCS+= getc.c v_at.c v_ch.c v_cmd.c v_delete.c v_ex.c v_increment.c v_init.c \ v_itxt.c v_left.c v_mark.c v_match.c v_paragraph.c v_put.c v_redraw.c \ v_replace.c v_right.c v_screen.c v_scroll.c v_search.c v_section.c \ v_sentence.c v_status.c v_txt.c v_ulcase.c v_undo.c \ v_util.c v_word.c v_xchar.c v_yank.c v_z.c v_zexit.c vi.c # Vi screen source. SRCS+= vs_line.c vs_msg.c vs_refresh.c vs_relative.c vs_smap.c vs_split.c # Wide char regex SRCS+= regcomp.c regerror.c regexec.c regfree.c .include Index: head/usr.bin/vi/catalog/Makefile =================================================================== --- head/usr.bin/vi/catalog/Makefile (revision 365498) +++ head/usr.bin/vi/catalog/Makefile (revision 365499) @@ -1,166 +1,166 @@ # $Id: Makefile,v 9.0 2012/10/19 15:13:11 zy Exp $ # $FreeBSD$ .include PACKAGE=vi V= ${.CURDIR}/../../../contrib/nvi FILESGROUPS+=VICAT CAT= dutch english french german polish ru_RU.KOI8-R spanish swedish \ - uk_UA.KOI8-U zh_CN.GB2312 + uk_UA.KOI8-U zh_CN.GB2312 tr_TR.ISO8859-9 tr_TR.UTF-8 VICAT= ${CAT} VICATDIR= ${FILESDIR} VICATPACKAGE= ${PACKAGE} SCAN= ${V}/cl/*.c ${V}/common/*.c ${V}/ex/*.c ${V}/vi/*.c .PATH: ${V}/catalog all: dump build-tools: dump # Helper since iconv is non trivial to make a build tool utf8convert: .for c in dutch french german spanish swedish iconv -f ISO8859-1 -t UTF-8 $V/catalog/$c.base > $c.UTF-8.base .endfor iconv -f ISO8859-2 -t UTF-8 $V/catalog/polish.base > polish.UTF-8.base iconv -f GB2312 -t UTF-8 $V/catalog/zh_CN.GB2312.base > zh_CN.UTF-8.base iconv -f KOI8-R -t UTF-8 $V/catalog/ru_RU.KOI8-R.base > ru_RU.UTF-8.base iconv -f KOI8-U -t UTF-8 $V/catalog/uk_UA.KOI8-U.base > uk_UA.UTF-8.base .for c in dutch french german polish spanish swedish zh_CN ru_RU uk_UA CAT+= $c.UTF-8 .endfor .for c in ${CAT} ${c}: ${c}.base echo "... $c"; \ rm -f $c; \ env LC_ALL=C sort -u ${.ALLSRC} | \ awk '{ \ if ($$1 == 1) { \ print "\nMESSAGE NUMBER 1 IS NOT LEGAL"; \ exit 1; \ } \ if (++nline > $$1) { \ print "DUPLICATE MESSAGE NUMBER " $$1; \ exit 1; \ } \ print $$0; \ }' | \ sed -e '1s/^/$$set 1~$$quote "~/; 1y/~/\n/' | \ gencat $c /dev/stdin; \ chmod 444 $c; \ if grep DUPLICATE $c > /dev/null; then \ grep DUPLICATE $@; \ fi; \ if grep 'NOT LEGAL' $c > /dev/null; then \ grep 'NOT LEGAL' $@; \ fi .endfor CHK= dutch.check english.check french.check german.check \ polish.check ru_RU.KOI8-R.check spanish.check swedish.check \ uk_UA.KOI8-U.check zh_CN.GB2312.check check: ${CHK} .for c in ${CAT} ${c}.check: ${c}.base @echo "... $c"; \ f=${.ALLSRC:S;.base$;;}; \ (echo "Unused message id's (this is okay):"; \ awk '{ \ while (++nline < $$1) \ printf "%03d\n", nline; \ }' < $$f.base; \ echo =========================; \ echo "MISSING ERROR MESSAGES (Please add!):"; \ awk '{print $$1}' < $$f.base | sort -u > __ck1; \ awk '{print $$1}' < english.base | sort -u > __ck2; \ comm -13 __ck1 __ck2; \ echo =========================; \ echo "Extra error messages (just delete them):"; \ comm -23 __ck1 __ck2; \ echo =========================; \ echo "MESSAGES WITH THE SAME MESSAGE ID's (FIX!):"; \ for j in \ `sed '/^$$/d' < $$f.base | LC_ALL=C sort -u | \ awk '{print $$1}' | uniq -d`; do \ egrep $$j $$f.base; \ done; \ echo =========================; \ echo "Duplicate messages, both id and message (this is okay):"; \ sed '/^$$/d' < $$f.base | LC_ALL=C sort | uniq -c | \ awk '$$1 != 1 { print $$0 }' | sort -n; \ echo =========================) > $c .endfor english.base: dump ${SCAN} #Makefile ${BTOOLSPATH:U.}/dump ${SCAN} |\ sed -e '/|/!d' \ -e 's/|/ "/' \ -e 's/^"//' |\ sort -nu > $@ DEPENDOBJS+= dump dump: ${BUILD_TOOLS_META} CLEANFILES+= dump ${CAT} english.base *.check __ck1 __ck2 CATALOGS= ${CAT} NLLINKS= nl_NL ENLINKS= en_AU en_CA en_GB en_NZ en_US FRLINKS= fr_BE fr_CA fr_CH fr_FR DELINKS= de_AT de_CH de_DE ESLINKS= es_ES SVLINKS= sv_SE PLLINKS= pl_PL FILES= ${CATALOGS} FILESDIR= ${SHAREDIR}/vi/catalog SYMLINKS= .for l in ${NLLINKS} SYMLINKS+= dutch ${FILESDIR}/$l.ISO8859-1 SYMLINKS+= dutch ${FILESDIR}/$l.ISO8859-15 SYMLINKS+= dutch.UTF-8 ${FILESDIR}/$l.UTF-8 .endfor .for l in ${ENLINKS} SYMLINKS+= english ${FILESDIR}/$l.ISO8859-1 SYMLINKS+= english ${FILESDIR}/$l.ISO8859-15 SYMLINKS+= english ${FILESDIR}/$l.US-ASCII SYMLINKS+= english ${FILESDIR}/$l.UTF-8 .endfor SYMLINKS+= english ${FILESDIR}/POSIX SYMLINKS+= english ${FILESDIR}/C .for l in ${FRLINKS} SYMLINKS+= french ${FILESDIR}/$l.ISO8859-1 SYMLINKS+= french ${FILESDIR}/$l.ISO8859-15 SYMLINKS+= french.UTF-8 ${FILESDIR}/$l.UTF-8 .endfor .for l in ${DELINKS} SYMLINKS+= german ${FILESDIR}/$l.ISO8859-1 SYMLINKS+= german ${FILESDIR}/$l.ISO8859-15 SYMLINKS+= german.UTF-8 ${FILESDIR}/$l.UTF-8 .endfor .for l in ${ESLINKS} SYMLINKS+= spanish ${FILESDIR}/$l.ISO8859-1 SYMLINKS+= spanish ${FILESDIR}/$l.ISO8859-15 SYMLINKS+= spanish.UTF-8 ${FILESDIR}/$l.UTF-8 .endfor .for l in ${SVLINKS} SYMLINKS+= swedish ${FILESDIR}/$l.ISO8859-1 SYMLINKS+= swedish ${FILESDIR}/$l.ISO8859-15 SYMLINKS+= swedish.UTF-8 ${FILESDIR}/$l.UTF-8 .endfor .for l in ${PLLINKS} SYMLINKS+= polish ${FILESDIR}/$l.ISO8859-2 SYMLINKS+= polish.UTF-8 ${FILESDIR}/$l.UTF-8 .endfor SYMLINKS+= zh_CN.GB2312 ${FILESDIR}/zh_CN.GB18030 SYMLINKS+= zh_CN.GB2312 ${FILESDIR}/zh_CN.GBK SYMLINKS+= zh_CN.GB2312 ${FILESDIR}/zh_CN.eucCN .include Index: head/usr.bin/vi/config.h =================================================================== --- head/usr.bin/vi/config.h (revision 365498) +++ head/usr.bin/vi/config.h (revision 365499) @@ -1,23 +1,19 @@ -/* $Id: config.h.in,v 9.5 2013/03/11 01:20:53 zy Exp $ */ /* $FreeBSD$ */ -/* Define if you want a debugging version. */ -/* #undef DEBUG */ - /* Define when using wide characters */ -/* #define USE_WIDECHAR set by Makefile */ +#define USE_WIDECHAR /* Define when iconv can be used */ -/* #define USE_ICONV set by Makefile */ +/* #undef USE_ICONV */ /* Define when the 2nd argument of iconv(3) is not const */ /* #undef ICONV_TRADITIONAL */ /* Define if you have */ #define HAVE_LIBUTIL_H /* Define if you have */ #define HAVE_NCURSES_H /* Define if you have */ #define HAVE_TERM_H Index: head/usr.bin/vi/pathnames.h =================================================================== --- head/usr.bin/vi/pathnames.h (revision 365498) +++ head/usr.bin/vi/pathnames.h (revision 365499) @@ -1,29 +1,28 @@ -/* $Id: pathnames.h.in,v 8.7 2012/04/23 08:34:52 zy Exp $ */ /* $FreeBSD$ */ /* Read standard system paths first. */ #include #ifndef _PATH_EXRC #define _PATH_EXRC ".exrc" #endif #ifndef _PATH_MSGCAT #define _PATH_MSGCAT "/usr/share/vi/catalog/" #endif #ifndef _PATH_NEXRC #define _PATH_NEXRC ".nexrc" #endif #ifndef _PATH_PRESERVE #define _PATH_PRESERVE "/var/tmp/vi.recover/" #endif #ifndef _PATH_SYSEXRC #define _PATH_SYSEXRC "/etc/vi.exrc" #endif #ifndef _PATH_TAGS #define _PATH_TAGS "tags" #endif