* 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
* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. 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
static char sccsid
[] = "@(#)v_ntext.c 8.80 (Berkeley) 1/13/94";
static int txt_abbrev
__P((SCR
*, TEXT
*, ARG_CHAR_T
, int, int *, int *));
static void txt_ai_resolve
__P((SCR
*, TEXT
*));
static TEXT
*txt_backup
__P((SCR
*, EXF
*, TEXTH
*, TEXT
*, u_int
));
static void txt_err
__P((SCR
*, EXF
*, TEXTH
*));
static int txt_hex
__P((SCR
*, TEXT
*, int *, ARG_CHAR_T
));
static int txt_indent
__P((SCR
*, TEXT
*));
static int txt_margin
__P((SCR
*, TEXT
*, int *, ARG_CHAR_T
));
static int txt_outdent
__P((SCR
*, TEXT
*));
static void txt_showmatch
__P((SCR
*, EXF
*));
static int txt_resolve
__P((SCR
*, EXF
*, TEXTH
*));
/* Cursor character (space is hard to track on the screen). */
/* Local version of BINC. */
#define TBINC(sp, lp, llen, nlen) { \
if ((nlen) > llen && binc(sp, &(lp), &(llen), nlen)) \
* Read in text from the user.
* Historic vi always used:
* ^D: autoindent deletion
* ^H: last character deletion
* ^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. Ex was not
* completely consistent with this, as it did map the scroll command to the
* This implementation does not use fixed characters, but uses whatever the
* user specified as described by the termios structure. I'm getting away
* with something here, but I think I'm unlikely to get caught.
* Historic vi did a special screen optimization for tab characters. For
* the keystrokes "iabcd<esc>0C<tab>", the tab would overwrite the rest of
* the string when it was displayed. Because this implementation redisplays
* the entire line on each keystroke, the "bcd" gets pushed to the right as
* we ignore that the user has "promised" to change the rest of the characters.
* Users have noticed, but this isn't worth fixing, and, the way that the
* historic vi did it results in an even worse bug. Given the keystrokes
* "iabcd<esc>0R<tab><esc>", the "bcd" disappears, and magically reappears
* on the second <esc> key.
v_ntext(sp
, ep
, tiqh
, tm
, lp
, len
, rp
, prompt
, ai_line
, flags
)
const char *lp
; /* Input line. */
const size_t len
; /* Input line length. */
MARK
*rp
; /* Return MARK. */
int prompt
; /* Prompt to display. */
recno_t ai_line
; /* Line number to use for autoindent count. */
u_int flags
; /* TXT_ flags. */
/* State of abbreviation checks. */
enum { A_NOTSET
, A_SPACE
, A_NOTSPACE
} abb
;
/* State of the "[^0]^D" sequences. */
enum { C_NOTSET
, C_CARATSET
, C_NOCHANGE
, C_ZEROSET
} carat_st
;
/* State of the hex input character. */
enum { H_NOTSET
, H_NEXTCHAR
, H_INHEX
} hex
;
/* State of quotation. */
enum { Q_NOTSET
, Q_NEXTCHAR
, Q_THISCHAR
} quoted
;
CH ikey
; /* Input character structure. */
CHAR_T ch
; /* Input character. */
GS
*gp
; /* Global pointer. */
TEXT
*tp
, *ntp
, ait
; /* Input and autoindent text structures. */
size_t rcol
; /* 0-N: insert offset in the replay buffer. */
size_t col
; /* Current column. */
u_long margin
; /* Wrapmargin value. */
u_int iflags
; /* Input flags. */
int ab_cnt
, ab_turnoff
; /* Abbreviation count, if turned off. */
int eval
; /* Routine return value. */
int replay
; /* If replaying a set of input. */
int showmatch
; /* Showmatch set on this character. */
int testnr
; /* Test first character for nul replay. */
* Set the input flag, so tabs get displayed correctly
* and everyone knows that the text buffer is in use.
/* Local initialization. */
* 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.
if (tiqh
->cqh_first
!= (void *)tiqh
) {
if (tp
->q
.cqe_next
!= (void *)tiqh
|| tp
->lb_len
< len
+ 32) {
tp
->ai
= tp
->insert
= tp
->offset
= tp
->owrite
= 0;
memmove(tp
->lb
, lp
, len
);
newtp
: if ((tp
= text_init(sp
, lp
, len
, len
+ 32)) == NULL
)
CIRCLEQ_INSERT_HEAD(tiqh
, tp
, q
);
/* Set the starting line number. */
* 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
if (LF_ISSET(TXT_OVERWRITE
)) {
tp
->owrite
= tm
->cno
- sp
->cno
;
tp
->insert
= len
- tm
->cno
;
tp
->insert
= len
- sp
->cno
;
tp
->lb
[tm
->cno
- 1] = END_CH
;
* Many of the special cases in this routine 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 wasn't a very
* good idea. Note also that "^^D" resets the next lines' autoindent,
* 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 (txt_auto(sp
, ep
, ai_line
, NULL
, 0, tp
))
* The cc and S commands have a special feature -- leading
* <blank> characters are handled as autoindent characters.
if (LF_ISSET(TXT_AICHARS
)) {
/* If getting a command buffer from the user, there may be a prompt. */
if (LF_ISSET(TXT_PROMPT
)) {
tp
->lb
[sp
->cno
++] = prompt
;
* 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
[sp
->cno
] = CURSOR_CH
;
* Historic practice is that the wrapmargin value was a distance
* from the RIGHT-HAND column, not the left. It's more useful to
* us as a distance from the left-hand column.
* Replay commands are not affected by wrapmargin values. What
* I found surprising was that people actually depend on it, as
* in this gem of a macro which centers lines:
* map #c $mq81a ^V^[81^V|D`qld0:s/ / /g^V^M$p
* 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.
if (LF_ISSET(TXT_REPLAY
) || !LF_ISSET(TXT_WRAPMARGIN
))
else if ((margin
= O_VAL(sp
, O_WRAPMARGIN
)) != 0)
margin
= sp
->cols
- margin
;
/* Initialize abbreviations checks. */
if (F_ISSET(gp
, G_ABBREV
) && LF_ISSET(TXT_MAPINPUT
)) {
* Set up the dot command. Dot commands are done by saving the
* actual characters and replaying the input. We have to push
* the characters onto the key stack and then handle them normally,
* otherwise things like wrapmargin will fail.
* It would be nice if we could swallow backspaces and such, but
* it's not all that easy to do. Another possibility would be to
* recognize full line insertions, which could be performed quickly,
if (replay
= LF_ISSET(TXT_REPLAY
)) {
* Historically, it wasn't an error to replay non-existent
* input. This test is necessary, we get here by the user
* doing an input command followed by a nul.
* Historically, vi did not remap or reabbreviate replayed
* input. It did, however, beep at you if you changed an
* abbreviation and then replayed the input. We're not that
if (VIP(sp
)->rep
== NULL
)
if (term_push(sp
, VIP(sp
)->rep
, VIP(sp
)->rep_cnt
, 0, CH_NOMAP
))
iflags
= LF_ISSET(TXT_MAPCOMMAND
| TXT_MAPINPUT
);
carat_st
= C_NOTSET
, hex
= H_NOTSET
, quoted
= Q_NOTSET
;;) {
* Reset the line and update the screen. (The txt_showmatch()
* code refreshes the screen for us.) Don't refresh unless
* we're about to wait on a character or we need to know where
if (showmatch
|| margin
|| !KEYS_WAITING(sp
)) {
if (sp
->s_change(sp
, ep
, tp
->lno
, LINE_RESET
))
} else if (sp
->s_refresh(sp
, ep
))
/* Get the next character. */
next_ch
: if (term_key(sp
, &ikey
, iflags
) != INP_OK
)
/* Abbreviation check. See comment in txt_abbrev(). */
#define MAX_ABBREVIATION_EXPANSION 256
if (ikey
.flags
& CH_ABBREVIATED
) {
if (++ab_cnt
> MAX_ABBREVIATION_EXPANSION
) {
"Abbreviation exceeded maximum number of characters");
* Historic feature. If the first character of the input is
* a nul, replay the previous input. This isn't documented
* anywhere, and is a great test of vi clones.
if (ch
== '\0' && testnr
) {
* Check to see if the character fits into the input (and
* replay, if necessary) buffers. It isn't necessary to
* have tp->len bytes, since it doesn't consider overwrite
* characters, but not worth fixing.
if (LF_ISSET(TXT_RECORD
)) {
TBINC(sp
, VIP(sp
)->rep
, VIP(sp
)->rep_len
, rcol
+ 1);
VIP(sp
)->rep
[rcol
++] = ch
;
TBINC(sp
, tp
->lb
, tp
->lb_len
, tp
->len
+ 1);
* If the character was quoted, replace the last character
* (the literal mark) with the new character. If quoted
* by someone else, simply insert the character.
* Extension -- if the quoted character is HEX_CH, enter hex
* mode. If the user enters "<HEX_CH>[isxdigit()]*" we will
* try to use the value as a character. Anything else resets
if (ikey
.flags
& CH_QUOTED
)
if (quoted
== Q_THISCHAR
) {
case K_NL
: /* New line. */
* Handle abbreviations. If there was one, \
* discard the replay characters. \
if (abb == A_NOTSPACE && !replay) { \
if (txt_abbrev(sp, tp, ch, \
LF_ISSET(TXT_INFOLINE), &tmp, \
if (LF_ISSET(TXT_RECORD)) \
/* Handle hex numbers. */ \
if (txt_hex(sp, tp, &tmp, ch)) \
* The 'R' command returns any overwriteable \
* characters in the first line to the original \
if (LF_ISSET(TXT_REPLACE) && tp->owrite && \
tp == tiqh->cqh_first) { \
memmove(tp->lb + sp->cno, \
lp + sp->cno, tp->owrite); \
tp->insert += tp->owrite; \
/* Delete any appended cursor. */ \
if (LF_ISSET(TXT_APPENDEOL)) { \
/* CR returns from the vi command line. */
* If a script window and not the colon
* line, push a <cr> so it gets executed.
if (F_ISSET(sp
, S_SCRIPT
) &&
* Historic practice was to delete any <blank>
* characters following the inserted newline.
* This affects the 'R', 'c', and 's' commands.
for (p
= tp
->lb
+ sp
->cno
+ tp
->owrite
;
tp
->insert
&& isblank(*p
);
++p
, ++tp
->owrite
, --tp
->insert
);
* Move any remaining insert characters into
tp
->lb
+ sp
->cno
+ tp
->owrite
,
tp
->insert
, tp
->insert
+ 32)) == NULL
)
/* Set bookkeeping for the new line. */
ntp
->insert
= tp
->insert
;
* Note if the user inserted any characters on this
* line. Done before calling txt_ai_resolve() because
* it changes the value of sp->cno without making the
* corresponding changes to tp->ai.
* Resolve autoindented characters for the old line.
* Reset the autoindent line value. 0^D keeps the ai
* line from changing, ^D changes the level, even if
* there are no characters in the old line. Note,
* if using the current tp structure, use the cursor
* as the length, the user may have erased autoindent
if (LF_ISSET(TXT_AUTOINDENT
)) {
if (carat_st
== C_NOCHANGE
) {
OOBLNO
, &ait
, ait
.ai
, ntp
))
FREE_SPACE(sp
, ait
.lb
, ait
.lb_len
);
OOBLNO
, tp
, sp
->cno
, ntp
))
* If the user hasn't entered any characters, delete
* any autoindent characters.
* Historic vi didn't get the insert test right, if
* there were characters after the cursor, entering
* a <cr> left the autoindent characters on the line.
/* Reset bookkeeping for the old line. */
tp
->ai
= tp
->insert
= tp
->owrite
= 0;
/* New cursor position. */
/* New lines are TXT_APPENDEOL if nothing to insert. */
TBINC(sp
, tp
->lb
, tp
->lb_len
, tp
->len
+ 1);
ntp
->lb
[sp
->cno
] = CURSOR_CH
;
/* Update the old line. */
if (sp
->s_change(sp
, ep
, tp
->lno
, LINE_RESET
))
* Swap old and new TEXT's, 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:file_gline() will be wrong.)
CIRCLEQ_INSERT_TAIL(tiqh
, tp
, q
);
/* Update the new line. */
if (sp
->s_change(sp
, ep
, tp
->lno
, LINE_INSERT
))
/* Set the renumber bit. */
/* Refresh if nothing waiting. */
if ((margin
|| !KEYS_WAITING(sp
)) &&
case K_ESCAPE
: /* Escape. */
if (!LF_ISSET(TXT_ESCAPE
))
* If there aren't any trailing characters in the line
* and the user hasn't entered any characters, delete
* the autoindent characters.
if (!tp
->insert
&& sp
->cno
<= tp
->ai
) {
tp
->len
= tp
->owrite
= 0;
} else if (LF_ISSET(TXT_AUTOINDENT
))
/* If there are insert characters, copy them down. */
k_escape
: if (tp
->insert
&& tp
->owrite
)
memmove(tp
->lb
+ sp
->cno
,
tp
->lb
+ sp
->cno
+ tp
->owrite
, tp
->insert
);
* Delete any lines that were inserted into the text
* structure and then erased.
while (tp
->q
.cqe_next
!= (void *)tiqh
) {
CIRCLEQ_REMOVE(tiqh
, ntp
, q
);
* If not resolving the lines into the file, end
* This is wrong, should pass back a length.
if (LF_ISSET(TXT_RESOLVE
)) {
if (txt_resolve(sp
, ep
, tiqh
))
* Clear input flag -- input buffer no longer
TBINC(sp
, tp
->lb
, tp
->lb_len
, tp
->len
+ 1);
* Set the return cursor position to rest on the last
rp
->cno
= sp
->cno
? sp
->cno
- 1 : 0;
if (sp
->s_change(sp
, ep
, rp
->lno
, LINE_RESET
))
case K_CARAT
: /* Delete autoindent chars. */
if (LF_ISSET(TXT_AUTOINDENT
) && sp
->cno
<= tp
->ai
)
case K_ZERO
: /* Delete autoindent chars. */
if (LF_ISSET(TXT_AUTOINDENT
) && sp
->cno
<= tp
->ai
)
case K_VEOF
: /* 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
))
if (sp
->cno
== 0 || tp
->ai
== 0)
case C_CARATSET
: /* ^^D */
if (sp
->cno
> tp
->ai
+ tp
->offset
+ 1)
/* Save the ai string for later. */
TBINC(sp
, ait
.lb
, ait
.lb_len
, tp
->ai
);
memmove(ait
.lb
, tp
->lb
, tp
->ai
);
ait
.ai
= ait
.len
= tp
->ai
;
case C_ZEROSET
: /* 0^D */
if (sp
->cno
> tp
->ai
+ tp
->offset
+ 1)
leftmargin
: tp
->lb
[sp
->cno
- 1] = ' ';
tp
->owrite
+= sp
->cno
- tp
->offset
;
if (sp
->cno
> tp
->ai
+ tp
->offset
)
(void)txt_outdent(sp
, tp
);
case K_VERASE
: /* Erase the last character. */
* If can erase over the prompt, return. Len is 0
* if backspaced over the prompt, 1 if only CR entered.
if (LF_ISSET(TXT_BS
) && sp
->cno
<= tp
->offset
) {
* If at the beginning of the line, try and drop back
* to a previously inserted line.
if ((ntp
= txt_backup(sp
,
ep
, tiqh
, tp
, flags
)) == NULL
)
/* If nothing to erase, bell the user. */
if (sp
->cno
<= tp
->offset
) {
"No more characters to erase.");
/* Drop back one character. */
* Increment overwrite, decrement ai if deleted.
* Historic vi did not permit users to use erase
* characters to delete autoindent characters.
* Historically, <interrupt> exited the user from
* editing the infoline, and returned to the main
* screen. It also beeped the terminal, but that
if (LF_ISSET(TXT_INFOLINE
)) {
tp
->lb
[tp
->len
= 0] = '\0';
case K_VWERASE
: /* Skip back one word. */
* If at the beginning of the line, try and drop back
* to a previously inserted line.
if ((ntp
= txt_backup(sp
,
ep
, tiqh
, tp
, flags
)) == NULL
)
* If at offset, nothing to erase so bell the user.
if (sp
->cno
<= tp
->offset
) {
"No more characters to erase.");
* First werase goes back to any autoindent
* and second werase goes back to the offset.
* Historic vi did not permit users to use erase
* characters to delete autoindent characters.
if (tp
->ai
&& sp
->cno
> tp
->ai
)
/* Skip over trailing space characters. */
while (sp
->cno
> max
&& isblank(tp
->lb
[sp
->cno
- 1])) {
* 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 <blank>'s, so, 6 words. The
* historic tty interface used <blank>'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 <blank>'s.
* The difference is that the type of the first erased
* character erased is ignored, which is exactly right
* when erasing pathname components. Here, the 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
if (LF_ISSET(TXT_TTYWERASE
))
if (isblank(tp
->lb
[sp
->cno
- 1]))
if (LF_ISSET(TXT_ALTWERASE
)) {
if (isblank(tp
->lb
[sp
->cno
- 1]))
tmp
= inword(tp
->lb
[sp
->cno
- 1]);
if (tmp
!= inword(tp
->lb
[sp
->cno
- 1])
|| isblank(tp
->lb
[sp
->cno
- 1]))
case K_VKILL
: /* Restart this line. */
* If at the beginning of the line, try and drop back
* to a previously inserted line.
if ((ntp
= txt_backup(sp
,
ep
, tiqh
, tp
, flags
)) == NULL
)
/* If at offset, nothing to erase so bell the user. */
if (sp
->cno
<= tp
->offset
) {
"No more characters to erase.");
* 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
&& sp
->cno
> tp
->ai
)
tp
->owrite
+= sp
->cno
- max
;
case K_CNTRLT
: /* Add autoindent char. */
if (!LF_ISSET(TXT_CNTRLT
))
#ifdef HISTORIC_PRACTICE_IS_TO_INSERT_NOT_REPAINT
showmatch
= LF_ISSET(TXT_SHOWMATCH
);
case K_VLNEXT
: /* Quote the next character. */
/* If in hex mode, see if we've entered a hex value. */
if (txt_hex(sp
, tp
, &tmp
, ch
))
default: /* Insert the character. */
* If entering a space character after a word, check
* for abbreviations. If there was one, discard the
if (isblank(ch
) && abb
== A_NOTSPACE
&& !replay
) {
if (txt_abbrev(sp
, tp
, ch
,
LF_ISSET(TXT_INFOLINE
), &tmp
, &ab_turnoff
))
if (LF_ISSET(TXT_RECORD
))
/* If in hex mode, see if we've entered a hex value. */
if (hex
== H_INHEX
&& !isxdigit(ch
)) {
if (txt_hex(sp
, tp
, &tmp
, ch
))
/* Check to see if we've crossed the margin. */
if (sp
->s_column(sp
, ep
, &col
))
if (txt_margin(sp
, tp
, &tmp
, ch
))
abb
= isblank(ch
) ? A_SPACE
: A_NOTSPACE
;
if (tp
->owrite
) /* Overwrite a character. */
else if (tp
->insert
) { /* Insert a character. */
tp
->lb
[sp
->cno
+ 1] = tp
->lb
[sp
->cno
];
memmove(tp
->lb
+ sp
->cno
+ 1,
tp
->lb
+ sp
->cno
, tp
->insert
);
* 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 (sp
->cno
>= tp
->len
) {
TBINC(sp
, tp
->lb
, tp
->lb_len
, tp
->len
+ 1);
tp
->lb
[sp
->cno
] = CURSOR_CH
;
if (quoted
== Q_NEXTCHAR
)
if (sp
->cno
+ tp
->insert
+ tp
->owrite
!= tp
->len
)
"len %u != cno: %u ai: %u insert %u overwrite %u",
tp
->len
, sp
->cno
, tp
->ai
, tp
->insert
, tp
->owrite
);
tp
->len
= sp
->cno
+ tp
->insert
+ tp
->owrite
;
if (LF_ISSET(TXT_RECORD
))
txt_abbrev(sp
, tp
, pushc
, isinfoline
, didsubp
, turnoffp
)
int isinfoline
, *didsubp
, *turnoffp
;
/* Find the beginning of this "word". */
for (off
= sp
->cno
- 1, p
= tp
->lb
+ off
, len
= 0;; --p
, --off
) {
if (off
== tp
->ai
|| off
== tp
->offset
)
* 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:
* results in "bar" being abbreviated to "baz", which wasn't what the
* user had in mind at all. Also, the commands:
* 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:
* unabbreviates "foo1", and the commands:
* 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 <blank> 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. Not also, minor trickiness, so that if the
* user erases the line and starts another command, we go ahead an turn
* This makes the layering look like a Nachos Supreme.
if (off
== tp
->ai
|| off
== tp
->offset
)
if (ex_is_abbrev(p
, len
)) {
/* Check for any abbreviations. */
if ((qp
= seq_find(sp
, NULL
, p
, len
, SEQ_ABBREV
, NULL
)) == NULL
)
* 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.
if (term_push(sp
, &ch
, 1, 0, CH_ABBREVIATED
))
if (term_push(sp
, qp
->output
, qp
->olen
, 0, CH_ABBREVIATED
))
* Move the cursor to the start of the abbreviation,
/* Copy any insert characters back. */
memmove(tp
->lb
+ sp
->cno
+ tp
->owrite
,
tp
->lb
+ sp
->cno
+ tp
->owrite
+ len
, tp
->insert
);
* 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.
/* Offset to next column of stop size. */
#define STOP_OFF(c, stop) (stop - (c) % stop)
* When a line is resolved by <esc> or <cr>, review autoindent
size_t cno
, len
, new, old
, scno
, spaces
, tab_after_sp
, tabs
;
* If the line is empty, has an offset, or no autoindent
* characters, we're done.
if (!tp
->len
|| tp
->offset
|| !tp
->ai
)
* The autoindent characters plus any leading <blank> characters
* in the line are resolved into the minimum number of characters.
ts
= O_VAL(sp
, O_TABSTOP
);
/* Figure out the last <blank> screen column. */
for (p
= tp
->lb
, scno
= 0, len
= tp
->len
,
spaces
= tab_after_sp
= 0; len
-- && isblank(*p
); ++p
)
scno
+= STOP_OFF(scno
, ts
);
* If there are no spaces, or no tabs after spaces and less than
* ts spaces, it's already minimal.
if (!spaces
|| !tab_after_sp
&& spaces
< ts
)
/* Count up spaces/tabs needed to get to the target. */
for (cno
= 0, tabs
= 0; cno
+ STOP_OFF(cno
, ts
) <= scno
; ++tabs
)
cno
+= STOP_OFF(cno
, ts
);
* Figure out how many characters we're dropping -- if we're not
* dropping any, it's already minimal, we're done.
/* Shift the rest of the characters down, adjust the counts. */
memmove(p
- del
, p
, tp
->len
- old
);
/* Fill in space/tab characters. */
for (p
= tp
->lb
; tabs
--;)
* Handle autoindent. If aitp isn't NULL, use it, otherwise,
txt_auto(sp
, ep
, lno
, aitp
, len
, tp
)
if ((p
= t
= file_gline(sp
, ep
, lno
, &len
)) == NULL
)
for (nlen
= 0; len
; ++p
) {
/* If last character is a space, it counts. */
/* Make sure the buffer's big enough. */
BINC_RET(sp
, tp
->lb
, tp
->lb_len
, tp
->len
+ nlen
);
/* Copy the indentation into the new buffer. */
memmove(tp
->lb
+ nlen
, tp
->lb
, tp
->len
);
memmove(tp
->lb
, t
, nlen
);
/* Return the additional length. */
* Back up to the previously edited line.
txt_backup(sp
, ep
, tiqh
, tp
, flags
)
/* Get a handle on the previous TEXT structure. */
if ((ntp
= tp
->q
.cqe_prev
) == (void *)tiqh
) {
msgq(sp
, M_BERR
, "Already at the beginning of the insert");
/* Make sure that we have enough space. */
total
= ntp
->len
+ tp
->insert
;
if (LF_ISSET(TXT_APPENDEOL
))
if (total
> ntp
->lb_len
&&
binc(sp
, &ntp
->lb
, &ntp
->lb_len
, total
))
* Append a cursor or copy inserted bytes to the end of the old line.
* Test for appending a cursor first, because the TEXT insert field
* will be 1 if we're appending a cursor. I don't think there's a
* third case, so abort() if there is.
if (LF_ISSET(TXT_APPENDEOL
)) {
ntp
->lb
[ntp
->len
] = CURSOR_CH
;
memmove(ntp
->lb
+ ntp
->len
, tp
->lb
+ tp
->owrite
, tp
->insert
);
ntp
->insert
= tp
->insert
;
/* Set bookkeeping information. */
/* Release the current TEXT. */
CIRCLEQ_REMOVE(tiqh
, tp
, q
);
/* Update the old line on the screen. */
if (sp
->s_change(sp
, ep
, lno
, LINE_DELETE
))
/* Return the old line. */
* Handle an error during input processing.
* 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
for (lno
= tiqh
->cqh_first
->lno
;
file_gline(sp
, ep
, lno
, &len
) == NULL
&& lno
> 0; --lno
);
sp
->lno
= lno
== 0 ? 1 : lno
;
/* Redraw the screen, just in case. */
* Let the user insert any character value they want.
* This is an extension. The pattern "^Vx[0-9a-fA-F]*" is a way
* for the user to specify a character value which their keyboard
* may not be able to enter.
txt_hex(sp
, tp
, was_hex
, pushc
)
* 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.
/* Find the previous HEX_CH. */
for (off
= sp
->cno
- 1, p
= tp
->lb
+ off
, len
= 0;; --p
, --off
) {
/* If not on this line, there's nothing to do. */
if (off
== tp
->ai
|| off
== tp
->offset
)
/* If no length, then it wasn't a hex value. */
value
= strtol(wp
, NULL
, 16);
if (value
== LONG_MIN
|| value
== LONG_MAX
|| value
> MAX_CHAR_T
) {
nothex
: tp
->lb
[sp
->cno
] = savec
;
if (term_push(sp
, &ch
, 1, 0, CH_NOMAP
| CH_QUOTED
))
if (term_push(sp
, &ch
, 1, 0, CH_NOMAP
| CH_QUOTED
))
/* Move the cursor to the start of the hex value, adjust the length. */
/* Copy any insert characters back. */
memmove(tp
->lb
+ sp
->cno
+ tp
->owrite
,
tp
->lb
+ sp
->cno
+ tp
->owrite
+ len
+ 1, tp
->insert
);
* Txt_indent and txt_outdent are 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 only had special meaning when
* they were the first characters typed after entering text input mode.
* Since normal erase characters couldn't erase autoindent (in this case
* ^T) characters, this meant that inserting text into previously existing
* text was quite strange, ^T only worked if it was the first keystroke,
* and then it could only be erased by using ^D. This implementation treats
* ^T specially anywhere it occurs in the input, and permits the standard
* erase characters to erase characters inserted using it.
* Technically, txt_indent, txt_outdent should part of the screen interface,
* as they require knowledge of the size of a space character on the screen.
* (Not the size of tabs, because tabs are logically composed of spaces.)
* They're left in the text code because they're complicated, not to mention
* the gruesome awareness that if spaces aren't a single column on the screen
* for any language, we're into some serious, ah, for lack of a better word,
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
< sp
->cno
; ++off
)
scno
+= STOP_OFF(scno
, ts
);
/* Count up spaces/tabs needed to get to the target. */
for (cno
= scno
, scno
+= STOP_OFF(scno
, sw
), tabs
= 0;
cno
+ STOP_OFF(cno
, ts
) <= scno
; ++tabs
)
cno
+= STOP_OFF(cno
, ts
);
/* Put space/tab characters in place of any overwrite characters. */
for (; tp
->owrite
&& tabs
; --tp
->owrite
, --tabs
, ++tp
->ai
)
tp
->lb
[sp
->cno
++] = '\t';
for (; tp
->owrite
&& spaces
; --tp
->owrite
, --spaces
, ++tp
->ai
)
/* Make sure there's enough room. */
BINC_RET(sp
, tp
->lb
, tp
->lb_len
, tp
->len
+ spaces
+ tabs
);
/* Move the insert characters out of the way. */
memmove(tp
->lb
+ sp
->cno
+ spaces
+ tabs
,
tp
->lb
+ sp
->cno
, tp
->insert
);
/* Add new space/tab characters. */
for (; tabs
--; ++tp
->len
, ++tp
->ai
)
tp
->lb
[sp
->cno
++] = '\t';
for (; spaces
--; ++tp
->len
, ++tp
->ai
)
size_t cno
, off
, scno
, spaces
;
ts
= O_VAL(sp
, O_TABSTOP
);
sw
= O_VAL(sp
, O_SHIFTWIDTH
);
/* Get the current screen column. */
for (off
= scno
= 0; off
< sp
->cno
; ++off
)
scno
+= STOP_OFF(scno
, ts
);
/* Get the previous shiftwidth column. */
for (cno
= scno
; --scno
% sw
!= 0;);
/* Decrement characters until less than or equal to that slot. */
for (; cno
> scno
; --sp
->cno
, --tp
->ai
, ++tp
->owrite
)
if (tp
->lb
[--off
] == '\t')
cno
-= STOP_OFF(cno
, ts
);
/* Spaces needed to get to the target. */
/* Maybe just a delete. */
/* Make sure there's enough room. */
BINC_RET(sp
, tp
->lb
, tp
->lb_len
, tp
->len
+ spaces
);
/* Use up any overwrite characters. */
for (; tp
->owrite
&& spaces
; --spaces
, ++tp
->ai
, --tp
->owrite
)
/* Maybe that was enough. */
/* Move the insert characters out of the way. */
memmove(tp
->lb
+ sp
->cno
+ spaces
,
tp
->lb
+ sp
->cno
, tp
->insert
);
/* Add new space characters. */
for (; spaces
--; ++tp
->len
, ++tp
->ai
)
* Resolve the input text chain into the file.
txt_resolve(sp
, ep
, tiqh
)
/* The first line replaces a current line. */
if (file_sline(sp
, ep
, tp
->lno
, tp
->lb
, tp
->len
))
/* All subsequent lines are appended into the file. */
for (lno
= tp
->lno
; (tp
= tp
->q
.cqe_next
) != (void *)&sp
->tiq
; ++lno
)
if (file_aline(sp
, ep
, 0, lno
, tp
->lb
, tp
->len
))
* Show a character match.
* Historic vi tried to display matches even in the :colon command line.
* Do a refresh first, in case the v_ntext() code hasn't done
* one in awhile, so the user can see what we're complaining
if (sp
->s_refresh(sp
, ep
))
* We don't display the match if it's not on the screen. Find
* out what the first character on the screen is.
if (sp
->s_position(sp
, ep
, &m
, 0, P_TOP
))
/* Initialize the getc() interface. */
if (cs_init(sp
, ep
, &cs
))
startc
= (endc
= cs
.cs_ch
) == ')' ? '(' : '{';
/* Search for the match. */
if (cs_prev(sp
, ep
, &cs
))
cs
.cs_lno
== m
.lno
&& cs
.cs_cno
< m
.cno
)
if (cs
.cs_flags
== CS_EOF
|| cs
.cs_flags
== CS_SOF
) {
else if (cs
.cs_ch
== startc
&& --cnt
== 0)
(void)sp
->s_refresh(sp
, ep
);
* Sleep(3) is eight system calls. Do it fast -- besides,
* I don't want to wait an entire second.
second
.tv_sec
= O_VAL(sp
, O_MATCHTIME
) / 10;
second
.tv_usec
= (O_VAL(sp
, O_MATCHTIME
) % 10) * 100000L;
(void)select(0, &zero
, &zero
, &zero
, &second
);
/* Return to the current location. */
(void)sp
->s_refresh(sp
, ep
);
* Historic vi belled the user 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, it tends to wake the cats.
txt_margin(sp
, tp
, didbreak
, pushc
)
/* Find the closest previous blank. */
for (off
= sp
->cno
- 1, p
= tp
->lb
+ off
, len
= 0;; --p
, --off
) {
/* If it's the beginning of the line, there's nothing to do. */
if (off
== tp
->ai
|| off
== tp
->offset
) {
* Historic practice is to delete any trailing whitespace
* from the previous line.
for (tlen
= len
;; --p
, --off
) {
if (off
== tp
->ai
|| off
== tp
->offset
)
if (term_push(sp
, &ch
, 1, 0, CH_NOMAP
))
if (len
&& term_push(sp
, wp
, len
, 0, CH_NOMAP
| CH_QUOTED
))
if (term_push(sp
, &ch
, 1, 0, CH_NOMAP
))