mirror of
git://git.code.sf.net/p/cdesktopenv/code
synced 2025-02-24 23:14:14 +00:00
This commit fixes the following bugs in the 'vi' editing mode backslash escape feature. Ref.: Bolsky & Korn (1995), p. 113, which states for \: "Similar to Control+V [...] except that it escapes only the next Erase or Kill charactrer". 1. The vi mode now only escapes the next character if the last character input was a backslash, fixing the bug demonstrated at: https://asciinema.org/a/E3Rq3et07MMQG5BaF7vkXQTg0 2. Escaping backslashes are now disabled in vi.c if the vi mode is disabled (note that vi.c handles raw editing mode in UTF-8 locales). This makes the behavior of the raw editing mode consistent in C/POSIX and UTF-8 locales. 3. An odd interaction with Backspace when the character prior to a separate buffer entered with Shift-C was a backslash has been fixed. Demonstration at: https://asciinema.org/a/314833 ^? will no longer be output repeatedly when attempting to erase a separate buffer with a Backspace, although, to be consistent with vi(1), you still cannot backspace past it before escaping out of it. Ref.: https://github.com/ksh93/ksh/issues/56#issuecomment-653586994 src/cmd/ksh93/edit/vi.c: - Prevent a backslash from escaping the next input if the previous input wasn't a backslash. This is done by unsetting a variable named backslash if a backslash escaped a character. backslash is set to the result of c == '\\' when the user enters a new character. - Disable escaping backslashes in the raw editing mode because it should not be enabled there. src/cmd/ksh93/tests/pty.sh: - Add some tests for how ksh handles backslashes in each editing mode to test for the bugs fixed by this commit. Fixes #56.
2763 lines
56 KiB
C
2763 lines
56 KiB
C
/***********************************************************************
|
|
* *
|
|
* This software is part of the ast package *
|
|
* Copyright (c) 1982-2012 AT&T Intellectual Property *
|
|
* and is licensed under the *
|
|
* Eclipse Public License, Version 1.0 *
|
|
* by AT&T Intellectual Property *
|
|
* *
|
|
* A copy of the License is available at *
|
|
* http://www.eclipse.org/org/documents/epl-v10.html *
|
|
* (with md5 checksum b35adb5213ca9657e911e9befb180842) *
|
|
* *
|
|
* Information and Software Systems Research *
|
|
* AT&T Research *
|
|
* Florham Park NJ *
|
|
* *
|
|
* David Korn <dgk@research.att.com> *
|
|
* *
|
|
***********************************************************************/
|
|
#pragma prototyped
|
|
/* Adapted for ksh by David Korn */
|
|
/*+ VI.C P.D. Sullivan
|
|
*
|
|
* One line editor for the shell based on the vi editor.
|
|
*
|
|
* Questions to:
|
|
* P.D. Sullivan
|
|
* cbosgd!pds
|
|
-*/
|
|
|
|
|
|
#if KSHELL
|
|
# include "defs.h"
|
|
#else
|
|
# include <ast.h>
|
|
# include "FEATURE/options"
|
|
# include <ctype.h>
|
|
#endif /* KSHELL */
|
|
#include "io.h"
|
|
|
|
#include "history.h"
|
|
#include "edit.h"
|
|
#include "terminal.h"
|
|
#include "FEATURE/time"
|
|
|
|
#ifdef ECHOCTL
|
|
# define echoctl ECHOCTL
|
|
#else
|
|
# define echoctl 0
|
|
#endif /* ECHOCTL */
|
|
|
|
#ifndef FIORDCHK
|
|
# define NTICKS 5 /* number of ticks for typeahead */
|
|
#endif /* FIORDCHK */
|
|
|
|
#define MAXCHAR MAXLINE-2 /* max char per line */
|
|
|
|
#if SHOPT_MULTIBYTE
|
|
# include "lexstates.h"
|
|
# define gencpy(a,b) ed_gencpy(a,b)
|
|
# define genncpy(a,b,n) ed_genncpy(a,b,n)
|
|
# define genlen(str) ed_genlen(str)
|
|
# define digit(c) ((c&~STRIP)==0 && isdigit(c))
|
|
# define is_print(c) ((c&~STRIP) || isprint(c))
|
|
# if !_lib_iswprint && !defined(iswprint)
|
|
# define iswprint(c) ((c&~0177) || isprint(c))
|
|
# endif
|
|
static int _isalph(int);
|
|
static int _ismetach(int);
|
|
static int _isblank(int);
|
|
# undef isblank
|
|
# define isblank(v) _isblank(virtual[v])
|
|
# define isalph(v) _isalph(virtual[v])
|
|
# define ismetach(v) _ismetach(virtual[v])
|
|
#else
|
|
static genchar _c;
|
|
# define gencpy(a,b) strcpy((char*)(a),(char*)(b))
|
|
# define genncpy(a,b,n) strncpy((char*)(a),(char*)(b),n)
|
|
# define genlen(str) strlen(str)
|
|
# define isalph(v) ((_c=virtual[v])=='_'||isalnum(_c))
|
|
# undef isblank
|
|
# define isblank(v) isspace(virtual[v])
|
|
# define ismetach(v) ismeta(virtual[v])
|
|
# define digit(c) isdigit(c)
|
|
# define is_print(c) isprint(c)
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
|
|
#if ( 'a' == 97) /* ASCII? */
|
|
# define fold(c) ((c)&~040) /* lower and uppercase equivalent */
|
|
#else
|
|
# define fold(c) ((c)|0100) /* lower and uppercase equivalent */
|
|
#endif
|
|
|
|
#ifndef iswascii
|
|
#define iswascii(c) (!((c)&(~0177)))
|
|
#endif
|
|
|
|
typedef struct _vi_
|
|
{
|
|
int direction;
|
|
int lastmacro;
|
|
char addnl; /* boolean - add newline flag */
|
|
char last_find; /* last find command */
|
|
char last_cmd; /* last command */
|
|
char repeat_set;
|
|
char nonewline;
|
|
int findchar; /* last find char */
|
|
genchar *lastline;
|
|
int first_wind; /* first column of window */
|
|
int last_wind; /* last column in window */
|
|
int lastmotion; /* last motion */
|
|
int long_char; /* line bigger than window */
|
|
int long_line; /* line bigger than window */
|
|
int ocur_phys; /* old current physical position */
|
|
int ocur_virt; /* old last virtual position */
|
|
int ofirst_wind; /* old window first col */
|
|
int o_v_char; /* prev virtual[ocur_virt] */
|
|
int repeat; /* repeat count for motion cmds */
|
|
int lastrepeat; /* last repeat count for motion cmds */
|
|
int u_column; /* undo current column */
|
|
int U_saved; /* original virtual saved */
|
|
genchar *U_space; /* used for U command */
|
|
genchar *u_space; /* used for u command */
|
|
#ifdef FIORDCHK
|
|
clock_t typeahead; /* typeahead occurred */
|
|
#else
|
|
int typeahead; /* typeahead occurred */
|
|
#endif /* FIORDCHK */
|
|
#if SHOPT_MULTIBYTE
|
|
int bigvi;
|
|
#endif
|
|
Edit_t *ed; /* pointer to edit data */
|
|
} Vi_t;
|
|
|
|
#define editb (*vp->ed)
|
|
|
|
#undef putchar
|
|
#define putchar(c) ed_putchar(vp->ed,c)
|
|
|
|
#define crallowed editb.e_crlf
|
|
#define cur_virt editb.e_cur /* current virtual column */
|
|
#define cur_phys editb.e_pcur /* current phys column cursor is at */
|
|
#define curhline editb.e_hline /* current history line */
|
|
#define first_virt editb.e_fcol /* first allowable column */
|
|
#define globals editb.e_globals /* local global variables */
|
|
#define histmin editb.e_hismin
|
|
#define histmax editb.e_hismax
|
|
#define last_phys editb.e_peol /* last column in physical */
|
|
#define last_virt editb.e_eol /* last column */
|
|
#define lsearch editb.e_search /* last search string */
|
|
#define lookahead editb.e_lookahead /* characters in buffer */
|
|
#define previous editb.e_lbuf /* lookahead buffer */
|
|
#define max_col editb.e_llimit /* maximum column */
|
|
#define Prompt editb.e_prompt /* pointer to prompt */
|
|
#define plen editb.e_plen /* length of prompt */
|
|
#define physical editb.e_physbuf /* physical image */
|
|
#define usreof editb.e_eof /* user defined eof char */
|
|
#define usrerase editb.e_erase /* user defined erase char */
|
|
#define usrlnext editb.e_lnext /* user defined next literal */
|
|
#define usrkill editb.e_kill /* user defined kill char */
|
|
#define virtual editb.e_inbuf /* pointer to virtual image buffer */
|
|
#define window editb.e_window /* window buffer */
|
|
#define w_size editb.e_wsize /* window size */
|
|
#define inmacro editb.e_inmacro /* true when in macro */
|
|
#define yankbuf editb.e_killbuf /* yank/delete buffer */
|
|
|
|
|
|
#define ABORT -2 /* user abort */
|
|
#define APPEND -10 /* append chars */
|
|
#define BAD -1 /* failure flag */
|
|
#define BIGVI -15 /* user wants real vi */
|
|
#define CONTROL -20 /* control mode */
|
|
#define ENTER -25 /* enter flag */
|
|
#define GOOD 0 /* success flag */
|
|
#define INPUT -30 /* input mode */
|
|
#define INSERT -35 /* insert mode */
|
|
#define REPLACE -40 /* replace chars */
|
|
#define SEARCH -45 /* search flag */
|
|
#define TRANSLATE -50 /* translate virt to phys only */
|
|
|
|
#define INVALID (-1) /* invalid column */
|
|
|
|
static const char paren_chars[] = "([{)]}"; /* for % command */
|
|
|
|
static void cursor(Vi_t*, int);
|
|
static void del_line(Vi_t*,int);
|
|
static int getcount(Vi_t*,int);
|
|
static void getline(Vi_t*,int);
|
|
static int getrchar(Vi_t*);
|
|
static int mvcursor(Vi_t*,int);
|
|
static void pr_string(Vi_t*,const char*);
|
|
static void putstring(Vi_t*,int, int);
|
|
static void refresh(Vi_t*,int);
|
|
static void replace(Vi_t*,int, int);
|
|
static void restore_v(Vi_t*);
|
|
static void save_last(Vi_t*);
|
|
static void save_v(Vi_t*);
|
|
static int search(Vi_t*,int);
|
|
static void sync_cursor(Vi_t*);
|
|
static int textmod(Vi_t*,int,int);
|
|
|
|
/*+ VI_READ( fd, shbuf, nchar )
|
|
*
|
|
* This routine implements a one line version of vi and is
|
|
* called by _filbuf.c
|
|
*
|
|
-*/
|
|
|
|
/*
|
|
* if reedit is non-zero, initialize edit buffer with reedit chars
|
|
*/
|
|
int ed_viread(void *context, int fd, register char *shbuf, int nchar, int reedit)
|
|
{
|
|
Edit_t *ed = (Edit_t*)context;
|
|
register int i; /* general variable */
|
|
register int term_char=0; /* read() termination character */
|
|
register Vi_t *vp = ed->e_vi;
|
|
char prompt[PRSIZE+2]; /* prompt */
|
|
genchar Physical[2*MAXLINE]; /* physical image */
|
|
genchar Ubuf[MAXLINE]; /* used for U command */
|
|
genchar ubuf[MAXLINE]; /* used for u command */
|
|
genchar Window[MAXLINE]; /* window image */
|
|
int Globals[9]; /* local global variables */
|
|
int esc_or_hang=0; /* <ESC> or hangup */
|
|
char cntl_char=0; /* TRUE if control character present */
|
|
#if SHOPT_RAWONLY
|
|
# define viraw 1
|
|
#else
|
|
int viraw = (sh_isoption(SH_VIRAW) || ed->sh->st.trap[SH_KEYTRAP]);
|
|
# ifndef FIORDCHK
|
|
clock_t oldtime, newtime;
|
|
struct tms dummy;
|
|
# endif /* FIORDCHK */
|
|
#endif /* SHOPT_RAWONLY */
|
|
if(!vp)
|
|
{
|
|
ed->e_vi = vp = newof(0,Vi_t,1,0);
|
|
vp->lastline = (genchar*)malloc(MAXLINE*CHARSIZE);
|
|
vp->direction = -1;
|
|
vp->ed = ed;
|
|
}
|
|
|
|
/*** setup prompt ***/
|
|
|
|
Prompt = prompt;
|
|
ed_setup(vp->ed,fd, reedit);
|
|
shbuf[reedit] = 0;
|
|
|
|
#if !SHOPT_RAWONLY
|
|
if(!viraw)
|
|
{
|
|
/*** Change the eol characters to '\r' and eof ***/
|
|
/* in addition to '\n' and make eof an ESC */
|
|
if(tty_alt(ERRIO) < 0)
|
|
return(reexit?reedit:ed_read(context, fd, shbuf, nchar,0));
|
|
|
|
#ifdef FIORDCHK
|
|
ioctl(fd,FIORDCHK,&vp->typeahead);
|
|
#else
|
|
/* time the current line to determine typeahead */
|
|
oldtime = times(&dummy);
|
|
#endif /* FIORDCHK */
|
|
#if KSHELL
|
|
/* abort of interrupt has occurred */
|
|
if(ed->sh->trapnote&SH_SIGSET)
|
|
i = -1;
|
|
else
|
|
#endif /* KSHELL */
|
|
/*** Read the line ***/
|
|
i = ed_read(context, fd, shbuf, nchar, 0);
|
|
#ifndef FIORDCHK
|
|
newtime = times(&dummy);
|
|
vp->typeahead = ((newtime-oldtime) < NTICKS);
|
|
#endif /* FIORDCHK */
|
|
if(echoctl)
|
|
{
|
|
if( i <= 0 )
|
|
{
|
|
/*** read error or eof typed ***/
|
|
tty_cooked(ERRIO);
|
|
return(i);
|
|
}
|
|
term_char = shbuf[--i];
|
|
if( term_char == '\r' )
|
|
term_char = '\n';
|
|
if( term_char=='\n' || term_char==ESC )
|
|
shbuf[i--] = '\0';
|
|
else
|
|
shbuf[i+1] = '\0';
|
|
}
|
|
else
|
|
{
|
|
register int c = shbuf[0];
|
|
|
|
/*** Save and remove the last character if its an eol, ***/
|
|
/* changing '\r' to '\n' */
|
|
|
|
if( i == 0 )
|
|
{
|
|
/*** ESC was typed as first char of line ***/
|
|
esc_or_hang = 1;
|
|
term_char = ESC;
|
|
shbuf[i--] = '\0'; /* null terminate line */
|
|
}
|
|
else if( i<0 || c==usreof )
|
|
{
|
|
/*** read error or eof typed ***/
|
|
tty_cooked(ERRIO);
|
|
if( c == usreof )
|
|
i = 0;
|
|
return(i);
|
|
}
|
|
else
|
|
{
|
|
term_char = shbuf[--i];
|
|
if( term_char == '\r' )
|
|
term_char = '\n';
|
|
#if !defined(VEOL2) && !defined(ECHOCTL)
|
|
if(term_char=='\n')
|
|
{
|
|
tty_cooked(ERRIO);
|
|
return(i+1);
|
|
}
|
|
#endif
|
|
if( term_char=='\n' || term_char==usreof )
|
|
{
|
|
/*** remove terminator & null terminate ***/
|
|
shbuf[i--] = '\0';
|
|
}
|
|
else
|
|
{
|
|
/** terminator was ESC, which is not xmitted **/
|
|
term_char = ESC;
|
|
shbuf[i+1] = '\0';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
#endif /* SHOPT_RAWONLY */
|
|
{
|
|
/*** Set raw mode ***/
|
|
|
|
#if !SHOPT_RAWONLY
|
|
if( editb.e_ttyspeed == 0 )
|
|
{
|
|
/*** never did TCGETA, so do it ***/
|
|
/* avoids problem if user does 'sh -o viraw' */
|
|
tty_alt(ERRIO);
|
|
}
|
|
#endif /* SHOPT_RAWONLY */
|
|
if(tty_raw(ERRIO,0) < 0 )
|
|
return(reedit?reedit:ed_read(context, fd, shbuf, nchar,0));
|
|
i = last_virt-1;
|
|
}
|
|
|
|
/*** Initialize some things ***/
|
|
|
|
virtual = (genchar*)shbuf;
|
|
#if SHOPT_MULTIBYTE
|
|
virtual = (genchar*)roundof((char*)virtual-(char*)0,sizeof(genchar));
|
|
shbuf[i+1] = 0;
|
|
i = ed_internal(shbuf,virtual)-1;
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
globals = Globals;
|
|
cur_phys = i + 1;
|
|
cur_virt = i;
|
|
first_virt = 0;
|
|
vp->first_wind = 0;
|
|
last_virt = i;
|
|
last_phys = i;
|
|
vp->last_wind = i;
|
|
vp->long_line = ' ';
|
|
vp->long_char = ' ';
|
|
vp->o_v_char = '\0';
|
|
vp->ocur_phys = 0;
|
|
vp->ocur_virt = MAXCHAR;
|
|
vp->ofirst_wind = 0;
|
|
physical = Physical;
|
|
vp->u_column = INVALID - 1;
|
|
vp->U_space = Ubuf;
|
|
vp->u_space = ubuf;
|
|
window = Window;
|
|
window[0] = '\0';
|
|
|
|
if(!yankbuf)
|
|
yankbuf = (genchar*)malloc(MAXLINE*CHARSIZE);
|
|
if( vp->last_cmd == '\0' )
|
|
{
|
|
/*** first time for this shell ***/
|
|
|
|
vp->last_cmd = 'i';
|
|
vp->findchar = INVALID;
|
|
vp->lastmotion = '\0';
|
|
vp->lastrepeat = 1;
|
|
vp->repeat = 1;
|
|
*yankbuf = 0;
|
|
}
|
|
|
|
/*** fiddle around with prompt length ***/
|
|
if( nchar+plen > MAXCHAR )
|
|
nchar = MAXCHAR - plen;
|
|
max_col = nchar - 2;
|
|
|
|
if( !viraw )
|
|
{
|
|
int kill_erase = 0;
|
|
for(i=(echoctl?last_virt:0); i<last_virt; ++i )
|
|
{
|
|
/*** change \r to \n, check for control characters, ***/
|
|
/* delete appropriate ^Vs, */
|
|
/* and estimate last physical column */
|
|
|
|
if( virtual[i] == '\r' )
|
|
virtual[i] = '\n';
|
|
if(!echoctl)
|
|
{
|
|
register int c = virtual[i];
|
|
if( c<=usrerase)
|
|
{
|
|
/*** user typed escaped erase or kill char ***/
|
|
cntl_char = 1;
|
|
if(is_print(c))
|
|
kill_erase++;
|
|
}
|
|
else if( !is_print(c) )
|
|
{
|
|
cntl_char = 1;
|
|
|
|
if( c == usrlnext )
|
|
{
|
|
if( i == last_virt )
|
|
{
|
|
/*** eol/eof was escaped ***/
|
|
/* so replace ^V with it */
|
|
virtual[i] = term_char;
|
|
break;
|
|
}
|
|
|
|
/*** delete ^V ***/
|
|
gencpy((&virtual[i]), (&virtual[i+1]));
|
|
--cur_virt;
|
|
--last_virt;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*** copy virtual image to window ***/
|
|
if(last_virt > 0)
|
|
last_phys = ed_virt_to_phys(vp->ed,virtual,physical,last_virt,0,0);
|
|
if( last_phys >= w_size )
|
|
{
|
|
/*** line longer than window ***/
|
|
vp->last_wind = w_size - 1;
|
|
}
|
|
else
|
|
vp->last_wind = last_phys;
|
|
genncpy(window, virtual, vp->last_wind+1);
|
|
|
|
if( term_char!=ESC && (last_virt==INVALID
|
|
|| virtual[last_virt]!=term_char) )
|
|
{
|
|
/*** Line not terminated with ESC or escaped (^V) ***/
|
|
/* eol, so return after doing a total update */
|
|
/* if( (speed is greater or equal to 1200 */
|
|
/* and something was typed) and */
|
|
/* (control character present */
|
|
/* or typeahead occurred) ) */
|
|
|
|
tty_cooked(ERRIO);
|
|
if( editb.e_ttyspeed==FAST && last_virt!=INVALID
|
|
&& (vp->typeahead || cntl_char) )
|
|
{
|
|
refresh(vp,TRANSLATE);
|
|
pr_string(vp,Prompt);
|
|
putstring(vp,0, last_phys+1);
|
|
if(echoctl)
|
|
ed_crlf(vp->ed);
|
|
else
|
|
while(kill_erase-- > 0)
|
|
putchar(' ');
|
|
}
|
|
|
|
if( term_char=='\n' )
|
|
{
|
|
if(!echoctl)
|
|
ed_crlf(vp->ed);
|
|
virtual[++last_virt] = '\n';
|
|
}
|
|
vp->last_cmd = 'i';
|
|
save_last(vp);
|
|
#if SHOPT_MULTIBYTE
|
|
virtual[last_virt+1] = 0;
|
|
last_virt = ed_external(virtual,shbuf);
|
|
return(last_virt);
|
|
#else
|
|
return(++last_virt);
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
}
|
|
|
|
/*** Line terminated with escape, or escaped eol/eof, ***/
|
|
/* so set raw mode */
|
|
|
|
if( tty_raw(ERRIO,0) < 0 )
|
|
{
|
|
tty_cooked(ERRIO);
|
|
/*
|
|
* The following prevents drivers that return 0 on
|
|
* causing an infinite loop
|
|
*/
|
|
if(esc_or_hang)
|
|
return(-1);
|
|
virtual[++last_virt] = '\n';
|
|
#if SHOPT_MULTIBYTE
|
|
virtual[last_virt+1] = 0;
|
|
last_virt = ed_external(virtual,shbuf);
|
|
return(last_virt);
|
|
#else
|
|
return(++last_virt);
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
}
|
|
|
|
if(echoctl) /*** for cntl-echo erase the ^[ ***/
|
|
pr_string(vp,"\b\b\b\b \b\b");
|
|
|
|
|
|
if(crallowed)
|
|
{
|
|
/*** start over since there may be ***/
|
|
/*** a control char, or cursor might not ***/
|
|
/*** be at left margin (this lets us know ***/
|
|
/*** where we are ***/
|
|
cur_phys = 0;
|
|
window[0] = '\0';
|
|
pr_string(vp,Prompt);
|
|
if( term_char==ESC && (last_virt<0 || virtual[last_virt]!=ESC))
|
|
refresh(vp,CONTROL);
|
|
else
|
|
refresh(vp,INPUT);
|
|
}
|
|
else
|
|
{
|
|
/*** just update everything internally ***/
|
|
refresh(vp,TRANSLATE);
|
|
}
|
|
}
|
|
|
|
/*** Handle usrintr, usrquit, or EOF ***/
|
|
|
|
i = sigsetjmp(editb.e_env,0);
|
|
if( i != 0 )
|
|
{
|
|
if(vp->ed->e_multiline)
|
|
{
|
|
cur_virt = last_virt;
|
|
sync_cursor(vp);
|
|
}
|
|
virtual[0] = '\0';
|
|
tty_cooked(ERRIO);
|
|
|
|
switch(i)
|
|
{
|
|
case UEOF:
|
|
/*** EOF ***/
|
|
return(0);
|
|
|
|
case UINTR:
|
|
/** interrupt **/
|
|
return(-1);
|
|
}
|
|
return(-1);
|
|
}
|
|
|
|
/*** Get a line from the terminal ***/
|
|
|
|
vp->U_saved = 0;
|
|
if(reedit)
|
|
{
|
|
cur_phys = vp->first_wind;
|
|
vp->ofirst_wind = INVALID;
|
|
refresh(vp,INPUT);
|
|
}
|
|
if(viraw)
|
|
getline(vp,APPEND);
|
|
else if(last_virt>=0 && virtual[last_virt]==term_char)
|
|
getline(vp,APPEND);
|
|
else
|
|
getline(vp,ESC);
|
|
if(vp->ed->e_multiline)
|
|
cursor(vp, last_phys);
|
|
/*** add a new line if user typed unescaped \n ***/
|
|
/* to cause the shell to process the line */
|
|
tty_cooked(ERRIO);
|
|
if(ed->e_nlist)
|
|
{
|
|
ed->e_nlist = 0;
|
|
stakset(ed->e_stkptr,ed->e_stkoff);
|
|
}
|
|
if( vp->addnl )
|
|
{
|
|
virtual[++last_virt] = '\n';
|
|
ed_crlf(vp->ed);
|
|
}
|
|
if( ++last_virt >= 0 )
|
|
{
|
|
#if SHOPT_MULTIBYTE
|
|
if(vp->bigvi)
|
|
{
|
|
vp->bigvi = 0;
|
|
shbuf[last_virt-1] = '\n';
|
|
}
|
|
else
|
|
{
|
|
virtual[last_virt] = 0;
|
|
last_virt = ed_external(virtual,shbuf);
|
|
}
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
#if SHOPT_EDPREDICT
|
|
if(vp->ed->nhlist)
|
|
ed_histlist(vp->ed,0);
|
|
#endif /* SHOPT_EDPREDICT */
|
|
return(last_virt);
|
|
}
|
|
else
|
|
return(-1);
|
|
}
|
|
|
|
|
|
/*{ APPEND( char, mode )
|
|
*
|
|
* This routine will append char after cur_virt in the virtual image.
|
|
* mode = APPEND, shift chars right before appending
|
|
* REPLACE, replace char if possible
|
|
*
|
|
}*/
|
|
|
|
static void append(Vi_t *vp,int c, int mode)
|
|
{
|
|
register int i,j;
|
|
|
|
if( last_virt<max_col && last_phys<max_col )
|
|
{
|
|
if( mode==APPEND || (cur_virt==last_virt && last_virt>=0))
|
|
{
|
|
j = (cur_virt>=0?cur_virt:0);
|
|
for(i = ++last_virt; i > j; --i)
|
|
virtual[i] = virtual[i-1];
|
|
}
|
|
virtual[++cur_virt] = c;
|
|
}
|
|
else
|
|
ed_ringbell();
|
|
return;
|
|
}
|
|
|
|
/*{ BACKWORD( nwords, cmd )
|
|
*
|
|
* This routine will position cur_virt at the nth previous word.
|
|
*
|
|
}*/
|
|
|
|
static void backword(Vi_t *vp,int nwords, register int cmd)
|
|
{
|
|
register int tcur_virt = cur_virt;
|
|
while( nwords-- && tcur_virt > first_virt )
|
|
{
|
|
if( !isblank(tcur_virt) && isblank(tcur_virt-1)
|
|
&& tcur_virt>first_virt )
|
|
--tcur_virt;
|
|
else if(cmd != 'B')
|
|
{
|
|
register int last = isalph(tcur_virt-1);
|
|
register int cur = isalph(tcur_virt);
|
|
if((!cur && last) || (cur && !last))
|
|
--tcur_virt;
|
|
}
|
|
while( isblank(tcur_virt) && tcur_virt>=first_virt )
|
|
--tcur_virt;
|
|
if( cmd == 'B' )
|
|
{
|
|
while( !isblank(tcur_virt) && tcur_virt>=first_virt )
|
|
--tcur_virt;
|
|
}
|
|
else
|
|
{
|
|
if(isalph(tcur_virt))
|
|
while( isalph(tcur_virt) && tcur_virt>=first_virt )
|
|
--tcur_virt;
|
|
else
|
|
while( !isalph(tcur_virt) && !isblank(tcur_virt)
|
|
&& tcur_virt>=first_virt )
|
|
--tcur_virt;
|
|
}
|
|
cur_virt = ++tcur_virt;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*{ CNTLMODE()
|
|
*
|
|
* This routine implements the vi command subset.
|
|
* The cursor will always be positioned at the char of interest.
|
|
*
|
|
}*/
|
|
|
|
static int cntlmode(Vi_t *vp)
|
|
{
|
|
register int c;
|
|
register int i;
|
|
genchar tmp_u_space[MAXLINE]; /* temporary u_space */
|
|
genchar *real_u_space; /* points to real u_space */
|
|
int tmp_u_column = INVALID; /* temporary u_column */
|
|
int was_inmacro;
|
|
|
|
if(!vp->U_saved)
|
|
{
|
|
/*** save virtual image if never done before ***/
|
|
virtual[last_virt+1] = '\0';
|
|
gencpy(vp->U_space, virtual);
|
|
vp->U_saved = 1;
|
|
}
|
|
|
|
save_last(vp);
|
|
|
|
real_u_space = vp->u_space;
|
|
curhline = histmax;
|
|
first_virt = 0;
|
|
vp->repeat = 1;
|
|
if( cur_virt > INVALID )
|
|
{
|
|
/*** make sure cursor is at the last char ***/
|
|
sync_cursor(vp);
|
|
}
|
|
else if(last_virt > INVALID )
|
|
cur_virt++;
|
|
|
|
/*** Read control char until something happens to cause a ***/
|
|
/* return to APPEND/REPLACE mode */
|
|
|
|
while( c=ed_getchar(vp->ed,-1) )
|
|
{
|
|
vp->repeat_set = 0;
|
|
was_inmacro = inmacro;
|
|
if( c == '0' )
|
|
{
|
|
/*** move to leftmost column ***/
|
|
cur_virt = 0;
|
|
sync_cursor(vp);
|
|
continue;
|
|
}
|
|
|
|
if( digit(c) )
|
|
{
|
|
c = getcount(vp,c);
|
|
if( c == '.' )
|
|
vp->lastrepeat = vp->repeat;
|
|
}
|
|
|
|
/*** see if it's a move cursor command ***/
|
|
|
|
if(mvcursor(vp,c))
|
|
{
|
|
sync_cursor(vp);
|
|
vp->repeat = 1;
|
|
continue;
|
|
}
|
|
|
|
/*** see if it's a repeat of the last command ***/
|
|
|
|
if( c == '.' )
|
|
{
|
|
c = vp->last_cmd;
|
|
vp->repeat = vp->lastrepeat;
|
|
i = textmod(vp,c, c);
|
|
}
|
|
else
|
|
{
|
|
i = textmod(vp,c, 0);
|
|
}
|
|
|
|
/*** see if it's a text modification command ***/
|
|
|
|
switch(i)
|
|
{
|
|
case BAD:
|
|
break;
|
|
|
|
default: /** input mode **/
|
|
if(!was_inmacro)
|
|
{
|
|
vp->last_cmd = c;
|
|
vp->lastrepeat = vp->repeat;
|
|
}
|
|
vp->repeat = 1;
|
|
if( i == GOOD )
|
|
continue;
|
|
return(i);
|
|
}
|
|
|
|
switch( c )
|
|
{
|
|
/***** Other stuff *****/
|
|
|
|
case cntl('L'): /** Redraw line **/
|
|
/*** print the prompt and ***/
|
|
/* force a total refresh */
|
|
if(vp->nonewline==0 && !vp->ed->e_nocrnl)
|
|
putchar('\n');
|
|
vp->nonewline = 0;
|
|
pr_string(vp,Prompt);
|
|
window[0] = '\0';
|
|
cur_phys = vp->first_wind;
|
|
vp->ofirst_wind = INVALID;
|
|
vp->long_line = ' ';
|
|
break;
|
|
|
|
case cntl('V'):
|
|
{
|
|
register const char *p = fmtident(e_version);
|
|
save_v(vp);
|
|
del_line(vp,BAD);
|
|
while(c = *p++)
|
|
append(vp,c,APPEND);
|
|
refresh(vp,CONTROL);
|
|
ed_getchar(vp->ed,-1);
|
|
restore_v(vp);
|
|
ed_ungetchar(vp->ed,'a');
|
|
break;
|
|
}
|
|
|
|
case '/': /** Search **/
|
|
case '?':
|
|
case 'N':
|
|
case 'n':
|
|
save_v(vp);
|
|
switch( search(vp,c) )
|
|
{
|
|
case GOOD:
|
|
/*** force a total refresh ***/
|
|
window[0] = '\0';
|
|
goto newhist;
|
|
|
|
case BAD:
|
|
/*** no match ***/
|
|
ed_ringbell();
|
|
|
|
default:
|
|
if( vp->u_column == INVALID )
|
|
del_line(vp,BAD);
|
|
else
|
|
restore_v(vp);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 'j': /** get next command **/
|
|
case '+': /** get next command **/
|
|
#if SHOPT_EDPREDICT
|
|
if(vp->ed->hlist)
|
|
{
|
|
if(vp->ed->hoff >= vp->ed->hmax)
|
|
goto ringbell;
|
|
vp->ed->hoff++;
|
|
goto hupdate;
|
|
}
|
|
#endif /* SHOPT_EDPREDICT */
|
|
curhline += vp->repeat;
|
|
if( curhline > histmax )
|
|
{
|
|
curhline = histmax;
|
|
goto ringbell;
|
|
}
|
|
else if(curhline==histmax && tmp_u_column!=INVALID )
|
|
{
|
|
vp->u_space = tmp_u_space;
|
|
vp->u_column = tmp_u_column;
|
|
restore_v(vp);
|
|
vp->u_space = real_u_space;
|
|
break;
|
|
}
|
|
save_v(vp);
|
|
cur_virt = INVALID;
|
|
goto newhist;
|
|
|
|
case 'k': /** get previous command **/
|
|
case '-': /** get previous command **/
|
|
#if SHOPT_EDPREDICT
|
|
if(vp->ed->hlist)
|
|
{
|
|
if(vp->ed->hoff == 0)
|
|
goto ringbell;
|
|
vp->ed->hoff--;
|
|
hupdate:
|
|
ed_histlist(vp->ed,*vp->ed->hlist!=0);
|
|
vp->nonewline++;
|
|
ed_ungetchar(vp->ed,cntl('L'));
|
|
continue;
|
|
}
|
|
#endif /* SHOPT_EDPREDICT */
|
|
if( curhline == histmax )
|
|
{
|
|
vp->u_space = tmp_u_space;
|
|
i = vp->u_column;
|
|
save_v(vp);
|
|
vp->u_space = real_u_space;
|
|
tmp_u_column = vp->u_column;
|
|
vp->u_column = i;
|
|
}
|
|
|
|
curhline -= vp->repeat;
|
|
if( curhline <= histmin )
|
|
{
|
|
curhline += vp->repeat;
|
|
goto ringbell;
|
|
}
|
|
save_v(vp);
|
|
cur_virt = INVALID;
|
|
newhist:
|
|
if(curhline!=histmax || cur_virt==INVALID)
|
|
hist_copy((char*)virtual, MAXLINE, curhline,-1);
|
|
else
|
|
{
|
|
strcpy((char*)virtual,(char*)vp->u_space);
|
|
#if SHOPT_MULTIBYTE
|
|
ed_internal((char*)vp->u_space,vp->u_space);
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
}
|
|
#if SHOPT_MULTIBYTE
|
|
ed_internal((char*)virtual,virtual);
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
if((last_virt=genlen(virtual)-1) >= 0 && cur_virt == INVALID)
|
|
cur_virt = 0;
|
|
#if SHOPT_EDPREDICT
|
|
if(vp->ed->hlist)
|
|
{
|
|
ed_histlist(vp->ed,0);
|
|
if(c=='\n')
|
|
ed_ungetchar(vp->ed,c);
|
|
ed_ungetchar(vp->ed,cntl('L'));
|
|
vp->nonewline = 1;
|
|
cur_virt = 0;
|
|
}
|
|
#endif /*SHOPT_EDPREDICT */
|
|
break;
|
|
|
|
|
|
case 'u': /** undo the last thing done **/
|
|
restore_v(vp);
|
|
break;
|
|
|
|
case 'U': /** Undo everything **/
|
|
save_v(vp);
|
|
if( virtual[0] == '\0' )
|
|
goto ringbell;
|
|
else
|
|
{
|
|
gencpy(virtual, vp->U_space);
|
|
last_virt = genlen(vp->U_space) - 1;
|
|
cur_virt = 0;
|
|
}
|
|
break;
|
|
|
|
#if KSHELL
|
|
case 'v':
|
|
if(vp->repeat_set==0)
|
|
goto vcommand;
|
|
#endif /* KSHELL */
|
|
|
|
case 'G': /** goto command repeat **/
|
|
if(vp->repeat_set==0)
|
|
vp->repeat = histmin+1;
|
|
if( vp->repeat <= histmin || vp->repeat > histmax )
|
|
{
|
|
goto ringbell;
|
|
}
|
|
curhline = vp->repeat;
|
|
save_v(vp);
|
|
if(c == 'G')
|
|
{
|
|
cur_virt = INVALID;
|
|
goto newhist;
|
|
}
|
|
|
|
#if KSHELL
|
|
vcommand:
|
|
if(ed_fulledit(vp->ed)==GOOD)
|
|
return(BIGVI);
|
|
else
|
|
goto ringbell;
|
|
#endif /* KSHELL */
|
|
|
|
case '#': /** insert(delete) # to (no)comment command **/
|
|
if( cur_virt != INVALID )
|
|
{
|
|
register genchar *p = &virtual[last_virt+1];
|
|
*p = 0;
|
|
/*** see whether first char is comment char ***/
|
|
c = (virtual[0]=='#');
|
|
while(p-- >= virtual)
|
|
{
|
|
if(*p=='\n' || p<virtual)
|
|
{
|
|
if(c) /* delete '#' */
|
|
{
|
|
if(p[1]=='#')
|
|
{
|
|
last_virt--;
|
|
gencpy(p+1,p+2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cur_virt = p-virtual;
|
|
append(vp,'#', APPEND);
|
|
}
|
|
}
|
|
}
|
|
if(c)
|
|
{
|
|
curhline = histmax;
|
|
cur_virt = 0;
|
|
break;
|
|
}
|
|
refresh(vp,INPUT);
|
|
}
|
|
|
|
case '\n': /** send to shell **/
|
|
#if SHOPT_EDPREDICT
|
|
if(!vp->ed->hlist)
|
|
return(ENTER);
|
|
case '\t': /** bring choice to edit **/
|
|
if(vp->ed->hlist)
|
|
{
|
|
if(vp->repeat > vp->ed->nhlist-vp->ed->hoff)
|
|
goto ringbell;
|
|
curhline = vp->ed->hlist[vp->repeat+vp->ed->hoff-1]->index;
|
|
goto newhist;
|
|
}
|
|
goto ringbell;
|
|
#else
|
|
return(ENTER);
|
|
#endif /* SHOPT_EDPREDICT */
|
|
case ESC:
|
|
/* don't ring bell if next char is '[' */
|
|
if(!lookahead)
|
|
{
|
|
char x;
|
|
if(sfpkrd(editb.e_fd,&x,1,'\r',400L,-1)>0)
|
|
ed_ungetchar(vp->ed,x);
|
|
}
|
|
if(lookahead)
|
|
{
|
|
ed_ungetchar(vp->ed,c=ed_getchar(vp->ed,1));
|
|
if(c=='[')
|
|
{
|
|
vp->repeat = 1;
|
|
continue;
|
|
}
|
|
}
|
|
default:
|
|
ringbell:
|
|
ed_ringbell();
|
|
vp->repeat = 1;
|
|
continue;
|
|
}
|
|
|
|
refresh(vp,CONTROL);
|
|
vp->repeat = 1;
|
|
}
|
|
/* NOTREACHED */
|
|
return(0);
|
|
}
|
|
|
|
/*{ CURSOR( new_current_physical )
|
|
*
|
|
* This routine will position the virtual cursor at
|
|
* physical column x in the window.
|
|
*
|
|
}*/
|
|
|
|
static void cursor(Vi_t *vp,register int x)
|
|
{
|
|
#if SHOPT_MULTIBYTE
|
|
while(physical[x]==MARKER)
|
|
x++;
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
cur_phys = ed_setcursor(vp->ed, physical, cur_phys,x,vp->first_wind);
|
|
}
|
|
|
|
/*{ DELETE( nchars, mode )
|
|
*
|
|
* Delete nchars from the virtual space and leave cur_virt positioned
|
|
* at cur_virt-1.
|
|
*
|
|
* If mode = 'c', do not save the characters deleted
|
|
* = 'd', save them in yankbuf and delete.
|
|
* = 'y', save them in yankbuf but do not delete.
|
|
*
|
|
}*/
|
|
|
|
static void cdelete(Vi_t *vp,register int nchars, int mode)
|
|
{
|
|
register int i;
|
|
register genchar *cp;
|
|
|
|
if( cur_virt < first_virt )
|
|
{
|
|
ed_ringbell();
|
|
return;
|
|
}
|
|
if( nchars > 0 )
|
|
{
|
|
cp = virtual+cur_virt;
|
|
vp->o_v_char = cp[0];
|
|
if( (cur_virt-- + nchars) > last_virt )
|
|
{
|
|
/*** set nchars to number actually deleted ***/
|
|
nchars = last_virt - cur_virt;
|
|
}
|
|
|
|
/*** save characters to be deleted ***/
|
|
|
|
if( mode != 'c' )
|
|
{
|
|
i = cp[nchars];
|
|
cp[nchars] = 0;
|
|
gencpy(yankbuf,cp);
|
|
cp[nchars] = i;
|
|
}
|
|
|
|
/*** now delete these characters ***/
|
|
|
|
if( mode != 'y' )
|
|
{
|
|
gencpy(cp,cp+nchars);
|
|
last_virt -= nchars;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*{ DEL_LINE( mode )
|
|
*
|
|
* This routine will delete the line.
|
|
* mode = GOOD, do a save_v()
|
|
*
|
|
}*/
|
|
static void del_line(register Vi_t *vp, int mode)
|
|
{
|
|
if( last_virt == INVALID )
|
|
return;
|
|
|
|
if( mode == GOOD )
|
|
save_v(vp);
|
|
|
|
cur_virt = 0;
|
|
first_virt = 0;
|
|
cdelete(vp,last_virt+1, BAD);
|
|
refresh(vp,CONTROL);
|
|
|
|
cur_virt = INVALID;
|
|
cur_phys = 0;
|
|
vp->findchar = INVALID;
|
|
last_phys = INVALID;
|
|
last_virt = INVALID;
|
|
vp->last_wind = INVALID;
|
|
vp->first_wind = 0;
|
|
vp->o_v_char = '\0';
|
|
vp->ocur_phys = 0;
|
|
vp->ocur_virt = MAXCHAR;
|
|
vp->ofirst_wind = 0;
|
|
window[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
/*{ DELMOTION( motion, mode )
|
|
*
|
|
* Delete thru motion.
|
|
*
|
|
* mode = 'd', save deleted characters, delete
|
|
* = 'c', do not save characters, change
|
|
* = 'y', save characters, yank
|
|
*
|
|
* Returns 1 if operation successful; else 0.
|
|
*
|
|
}*/
|
|
|
|
static int delmotion(Vi_t *vp,int motion, int mode)
|
|
{
|
|
register int begin, end, delta;
|
|
/* the following saves a register */
|
|
|
|
if( cur_virt == INVALID )
|
|
return(0);
|
|
if( mode != 'y' )
|
|
save_v(vp);
|
|
begin = cur_virt;
|
|
|
|
/*** fake out the motion routines by appending a blank ***/
|
|
|
|
virtual[++last_virt] = ' ';
|
|
end = mvcursor(vp,motion);
|
|
virtual[last_virt--] = 0;
|
|
if(!end)
|
|
return(0);
|
|
|
|
end = cur_virt;
|
|
if( mode=='c' && end>begin && strchr("wW", motion) )
|
|
{
|
|
/*** called by change operation, user really expects ***/
|
|
/* the effect of the eE commands, so back up to end of word */
|
|
while( end>begin && isblank(end-1) )
|
|
--end;
|
|
if( end == begin )
|
|
++end;
|
|
}
|
|
|
|
delta = end - begin;
|
|
if( delta >= 0 )
|
|
{
|
|
cur_virt = begin;
|
|
if( strchr("eE;,TtFf%", motion) )
|
|
++delta;
|
|
}
|
|
else
|
|
{
|
|
delta = -delta + (motion=='%');
|
|
}
|
|
|
|
cdelete(vp,delta, mode);
|
|
if( mode == 'y' )
|
|
cur_virt = begin;
|
|
return(1);
|
|
}
|
|
|
|
|
|
/*{ ENDWORD( nwords, cmd )
|
|
*
|
|
* This routine will move cur_virt to the end of the nth word.
|
|
*
|
|
}*/
|
|
|
|
static void endword(Vi_t *vp, int nwords, register int cmd)
|
|
{
|
|
register int tcur_virt = cur_virt;
|
|
while( nwords-- )
|
|
{
|
|
if( !isblank(tcur_virt) && tcur_virt<=last_virt )
|
|
++tcur_virt;
|
|
while( isblank(tcur_virt) && tcur_virt<=last_virt )
|
|
++tcur_virt;
|
|
if( cmd == 'E' )
|
|
{
|
|
while( !isblank(tcur_virt) && tcur_virt<=last_virt )
|
|
++tcur_virt;
|
|
}
|
|
else
|
|
{
|
|
if( isalph(tcur_virt) )
|
|
while( isalph(tcur_virt) && tcur_virt<=last_virt )
|
|
++tcur_virt;
|
|
else
|
|
while( !isalph(tcur_virt) && !isblank(tcur_virt)
|
|
&& tcur_virt<=last_virt )
|
|
++tcur_virt;
|
|
}
|
|
if( tcur_virt > first_virt )
|
|
tcur_virt--;
|
|
}
|
|
cur_virt = tcur_virt;
|
|
return;
|
|
}
|
|
|
|
/*{ FORWARD( nwords, cmd )
|
|
*
|
|
* This routine will move cur_virt forward to the next nth word.
|
|
*
|
|
}*/
|
|
|
|
static void forward(Vi_t *vp,register int nwords, int cmd)
|
|
{
|
|
register int tcur_virt = cur_virt;
|
|
while( nwords-- )
|
|
{
|
|
if( cmd == 'W' )
|
|
{
|
|
while( !isblank(tcur_virt) && tcur_virt < last_virt )
|
|
++tcur_virt;
|
|
}
|
|
else
|
|
{
|
|
if( isalph(tcur_virt) )
|
|
{
|
|
while( isalph(tcur_virt) && tcur_virt<last_virt )
|
|
++tcur_virt;
|
|
}
|
|
else
|
|
{
|
|
while( !isalph(tcur_virt) && !isblank(tcur_virt)
|
|
&& tcur_virt < last_virt )
|
|
++tcur_virt;
|
|
}
|
|
}
|
|
while( isblank(tcur_virt) && tcur_virt < last_virt )
|
|
++tcur_virt;
|
|
}
|
|
cur_virt = tcur_virt;
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/*{ GETCOUNT(c)
|
|
*
|
|
* Set repeat to the user typed number and return the terminating
|
|
* character.
|
|
*
|
|
}*/
|
|
|
|
static int getcount(register Vi_t *vp,register int c)
|
|
{
|
|
register int i;
|
|
|
|
/*** get any repeat count ***/
|
|
|
|
if( c == '0' )
|
|
return(c);
|
|
|
|
vp->repeat_set++;
|
|
i = 0;
|
|
while( digit(c) )
|
|
{
|
|
i = i*10 + c - '0';
|
|
c = ed_getchar(vp->ed,-1);
|
|
}
|
|
|
|
if( i > 0 )
|
|
vp->repeat *= i;
|
|
return(c);
|
|
}
|
|
|
|
|
|
/*{ GETLINE( mode )
|
|
*
|
|
* This routine will fetch a line.
|
|
* mode = APPEND, allow escape to cntlmode subroutine
|
|
* appending characters.
|
|
* = REPLACE, allow escape to cntlmode subroutine
|
|
* replacing characters.
|
|
* = SEARCH, no escape allowed
|
|
* = ESC, enter control mode immediately
|
|
*
|
|
* The cursor will always be positioned after the last
|
|
* char printed.
|
|
*
|
|
* This routine returns when cr, nl, or (eof in column 0) is
|
|
* received (column 0 is the first char position).
|
|
*
|
|
}*/
|
|
|
|
static void getline(register Vi_t* vp,register int mode)
|
|
{
|
|
register int c;
|
|
register int tmp;
|
|
int max_virt=0, last_save=0, backslash=0;
|
|
genchar saveline[MAXLINE];
|
|
vp->addnl = 1;
|
|
|
|
if( mode == ESC )
|
|
{
|
|
/*** go directly to control mode ***/
|
|
goto escape;
|
|
}
|
|
|
|
for(;;)
|
|
{
|
|
if( (c=ed_getchar(vp->ed,mode==SEARCH?1:-2)) == usreof )
|
|
c = UEOF;
|
|
else if( c == usrerase )
|
|
c = UERASE;
|
|
else if( c == usrkill )
|
|
c = UKILL;
|
|
else if( c == editb.e_werase )
|
|
c = UWERASE;
|
|
else if( c == usrlnext )
|
|
c = ULNEXT;
|
|
else if(mode==SEARCH && c==editb.e_intr)
|
|
c = UINTR;
|
|
|
|
if( c == ULNEXT)
|
|
{
|
|
/*** implement ^V to escape next char ***/
|
|
c = ed_getchar(vp->ed,2);
|
|
append(vp,c, mode);
|
|
refresh(vp,INPUT);
|
|
continue;
|
|
}
|
|
if(c!='\t')
|
|
vp->ed->e_tabcount = 0;
|
|
|
|
switch( c )
|
|
{
|
|
case ESC: /** enter control mode **/
|
|
if(!sh_isoption(SH_VI))
|
|
{
|
|
append(vp,c, mode);
|
|
break;
|
|
}
|
|
if( mode == SEARCH )
|
|
{
|
|
ed_ringbell();
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
escape:
|
|
if( mode == REPLACE )
|
|
{
|
|
c = max_virt-cur_virt;
|
|
if(c > 0 && last_save>=cur_virt)
|
|
{
|
|
genncpy((&virtual[cur_virt]),&saveline[cur_virt],c);
|
|
if(last_virt>=last_save)
|
|
last_virt=last_save-1;
|
|
refresh(vp,INPUT);
|
|
}
|
|
--cur_virt;
|
|
}
|
|
tmp = cntlmode(vp);
|
|
if( tmp == ENTER || tmp == BIGVI )
|
|
{
|
|
#if SHOPT_MULTIBYTE
|
|
vp->bigvi = (tmp==BIGVI);
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
return;
|
|
}
|
|
if( tmp == INSERT )
|
|
{
|
|
mode = APPEND;
|
|
continue;
|
|
}
|
|
mode = tmp;
|
|
if(mode==REPLACE)
|
|
{
|
|
c = last_save = last_virt+1;
|
|
if(c >= MAXLINE)
|
|
c = MAXLINE-1;
|
|
genncpy(saveline, virtual, c);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case UINTR:
|
|
first_virt = 0;
|
|
cdelete(vp,cur_virt+1, BAD);
|
|
cur_virt = -1;
|
|
return;
|
|
case UERASE: /** user erase char **/
|
|
/*** treat as backspace ***/
|
|
|
|
case '\b': /** backspace **/
|
|
if( sh_isoption(SH_VI) && backslash && virtual[cur_virt] == '\\' )
|
|
{
|
|
backslash = 0;
|
|
cdelete(vp,1, BAD);
|
|
append(vp,usrerase, mode);
|
|
}
|
|
else
|
|
{
|
|
if( mode==SEARCH && cur_virt==0 )
|
|
{
|
|
first_virt = 0;
|
|
cdelete(vp,1, BAD);
|
|
return;
|
|
}
|
|
if(mode==REPLACE || (last_save>0 && last_virt<=last_save))
|
|
{
|
|
if(cur_virt<=first_virt)
|
|
ed_ringbell();
|
|
else if(mode==REPLACE)
|
|
--cur_virt;
|
|
mode = REPLACE;
|
|
sync_cursor(vp);
|
|
continue;
|
|
}
|
|
else
|
|
cdelete(vp,1, BAD);
|
|
}
|
|
break;
|
|
|
|
case UWERASE: /** delete back word **/
|
|
if( cur_virt > first_virt &&
|
|
!isblank(cur_virt) &&
|
|
!ispunct(virtual[cur_virt]) &&
|
|
isblank(cur_virt-1) )
|
|
{
|
|
cdelete(vp,1, BAD);
|
|
}
|
|
else
|
|
{
|
|
tmp = cur_virt;
|
|
backword(vp,1, 'W');
|
|
cdelete(vp,tmp - cur_virt + 1, BAD);
|
|
}
|
|
break;
|
|
|
|
case UKILL: /** user kill line char **/
|
|
if( sh_isoption(SH_VI) && backslash && virtual[cur_virt] == '\\' )
|
|
{
|
|
backslash = 0;
|
|
cdelete(vp,1, BAD);
|
|
append(vp,usrkill, mode);
|
|
}
|
|
else
|
|
{
|
|
if( mode == SEARCH )
|
|
{
|
|
cur_virt = 1;
|
|
delmotion(vp, '$', BAD);
|
|
}
|
|
else if(first_virt)
|
|
{
|
|
tmp = cur_virt;
|
|
cur_virt = first_virt;
|
|
cdelete(vp,tmp - cur_virt + 1, BAD);
|
|
}
|
|
else
|
|
del_line(vp,GOOD);
|
|
}
|
|
break;
|
|
|
|
case UEOF: /** eof char **/
|
|
if( cur_virt != INVALID )
|
|
continue;
|
|
vp->addnl = 0;
|
|
|
|
case '\n': /** newline or return **/
|
|
if( mode != SEARCH )
|
|
save_last(vp);
|
|
refresh(vp,INPUT);
|
|
physical[++last_phys] = 0;
|
|
return;
|
|
|
|
case '\t': /** command completion **/
|
|
if(sh_isoption(SH_VI) &&
|
|
mode != SEARCH &&
|
|
last_virt >= 0 &&
|
|
(vp->ed->e_tabcount || !isblank(cur_virt)) &&
|
|
vp->ed->sh->nextprompt)
|
|
{
|
|
if(virtual[cur_virt]=='\\')
|
|
{
|
|
virtual[cur_virt] = '\t';
|
|
break;
|
|
}
|
|
if(vp->ed->e_tabcount==0)
|
|
{
|
|
ed_ungetchar(vp->ed,'\\');
|
|
vp->ed->e_tabcount=1;
|
|
goto escape;
|
|
}
|
|
else if(vp->ed->e_tabcount==1)
|
|
{
|
|
ed_ungetchar(vp->ed,'=');
|
|
goto escape;
|
|
}
|
|
vp->ed->e_tabcount = 0;
|
|
}
|
|
/* FALL THRU*/
|
|
default:
|
|
if( mode == REPLACE )
|
|
{
|
|
if( cur_virt < last_virt )
|
|
{
|
|
replace(vp,c, 1);
|
|
if(cur_virt>max_virt)
|
|
max_virt = cur_virt;
|
|
continue;
|
|
}
|
|
cdelete(vp,1, BAD);
|
|
mode = APPEND;
|
|
max_virt = last_virt+3;
|
|
}
|
|
backslash = (c == '\\');
|
|
append(vp,c, mode);
|
|
break;
|
|
}
|
|
refresh(vp,INPUT);
|
|
|
|
}
|
|
}
|
|
|
|
/*{ MVCURSOR( motion )
|
|
*
|
|
* This routine will move the virtual cursor according to motion
|
|
* for repeat times.
|
|
*
|
|
* It returns GOOD if successful; else BAD.
|
|
*
|
|
}*/
|
|
|
|
static int mvcursor(register Vi_t* vp,register int motion)
|
|
{
|
|
register int count;
|
|
register int tcur_virt;
|
|
register int incr = -1;
|
|
register int bound = 0;
|
|
|
|
switch(motion)
|
|
{
|
|
/***** Cursor move commands *****/
|
|
|
|
case '0': /** First column **/
|
|
tcur_virt = 0;
|
|
break;
|
|
|
|
case '^': /** First nonblank character **/
|
|
tcur_virt = first_virt;
|
|
while( isblank(tcur_virt) && tcur_virt < last_virt )
|
|
++tcur_virt;
|
|
break;
|
|
|
|
case '|':
|
|
tcur_virt = vp->repeat-1;
|
|
if(tcur_virt <= last_virt)
|
|
break;
|
|
/* fall through */
|
|
|
|
case '$': /** End of line **/
|
|
tcur_virt = last_virt;
|
|
break;
|
|
|
|
case '[':
|
|
switch(motion=getcount(vp,ed_getchar(vp->ed,-1)))
|
|
{
|
|
case 'A':
|
|
#if SHOPT_EDPREDICT
|
|
if(!vp->ed->hlist && cur_virt>=0 && cur_virt<(SEARCHSIZE-2) && cur_virt == last_virt)
|
|
#else
|
|
if(cur_virt>=0 && cur_virt<(SEARCHSIZE-2) && cur_virt == last_virt)
|
|
#endif /* SHOPT_EDPREDICT */
|
|
{
|
|
virtual[last_virt + 1] = '\0';
|
|
#if SHOPT_MULTIBYTE
|
|
ed_external(virtual,lsearch+1);
|
|
#else
|
|
strcpy(lsearch+1,virtual);
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
*lsearch = '^';
|
|
vp->direction = -2;
|
|
ed_ungetchar(vp->ed,'n');
|
|
}
|
|
else if(cur_virt==0 && vp->direction == -2)
|
|
ed_ungetchar(vp->ed,'n');
|
|
else
|
|
ed_ungetchar(vp->ed,'k');
|
|
return(1);
|
|
case 'B':
|
|
ed_ungetchar(vp->ed,'j');
|
|
return(1);
|
|
case 'C':
|
|
motion = last_virt;
|
|
incr = 1;
|
|
goto walk;
|
|
case 'D':
|
|
motion = first_virt;
|
|
goto walk;
|
|
case 'H':
|
|
tcur_virt = 0;
|
|
break;
|
|
case 'Y':
|
|
tcur_virt = last_virt;
|
|
break;
|
|
default:
|
|
ed_ungetchar(vp->ed,motion);
|
|
return(0);
|
|
}
|
|
break;
|
|
|
|
case 'h': /** Left one **/
|
|
case '\b':
|
|
motion = first_virt;
|
|
goto walk;
|
|
|
|
case ' ':
|
|
case 'l': /** Right one **/
|
|
motion = last_virt;
|
|
incr = 1;
|
|
walk:
|
|
tcur_virt = cur_virt;
|
|
if( incr*tcur_virt < motion)
|
|
{
|
|
tcur_virt += vp->repeat*incr;
|
|
if( incr*tcur_virt > motion)
|
|
tcur_virt = motion;
|
|
}
|
|
else
|
|
return(0);
|
|
break;
|
|
|
|
case 'B':
|
|
case 'b': /** back word **/
|
|
tcur_virt = cur_virt;
|
|
backword(vp,vp->repeat, motion);
|
|
if( cur_virt == tcur_virt )
|
|
return(0);
|
|
return(1);
|
|
|
|
case 'E':
|
|
case 'e': /** end of word **/
|
|
tcur_virt = cur_virt;
|
|
if(tcur_virt >=0)
|
|
endword(vp, vp->repeat, motion);
|
|
if( cur_virt == tcur_virt )
|
|
return(0);
|
|
return(1);
|
|
|
|
case ',': /** reverse find old char **/
|
|
case ';': /** find old char **/
|
|
switch(vp->last_find)
|
|
{
|
|
case 't':
|
|
case 'f':
|
|
if(motion==';')
|
|
{
|
|
bound = last_virt;
|
|
incr = 1;
|
|
}
|
|
goto find_b;
|
|
|
|
case 'T':
|
|
case 'F':
|
|
if(motion==',')
|
|
{
|
|
bound = last_virt;
|
|
incr = 1;
|
|
}
|
|
goto find_b;
|
|
|
|
default:
|
|
return(0);
|
|
}
|
|
|
|
|
|
case 't': /** find up to new char forward **/
|
|
case 'f': /** find new char forward **/
|
|
bound = last_virt;
|
|
incr = 1;
|
|
|
|
case 'T': /** find up to new char backward **/
|
|
case 'F': /** find new char backward **/
|
|
vp->last_find = motion;
|
|
if((vp->findchar=getrchar(vp))==ESC)
|
|
return(1);
|
|
find_b:
|
|
tcur_virt = cur_virt;
|
|
count = vp->repeat;
|
|
while( count-- )
|
|
{
|
|
while( incr*(tcur_virt+=incr) <= bound
|
|
&& virtual[tcur_virt] != vp->findchar );
|
|
if( incr*tcur_virt > bound )
|
|
{
|
|
return(0);
|
|
}
|
|
}
|
|
if( fold(vp->last_find) == 'T' )
|
|
tcur_virt -= incr;
|
|
break;
|
|
|
|
case '%':
|
|
{
|
|
int nextmotion;
|
|
int nextc;
|
|
tcur_virt = cur_virt;
|
|
while( tcur_virt <= last_virt
|
|
&& strchr(paren_chars,virtual[tcur_virt])==(char*)0)
|
|
tcur_virt++;
|
|
if(tcur_virt > last_virt )
|
|
return(0);
|
|
nextc = virtual[tcur_virt];
|
|
count = strchr(paren_chars,nextc)-paren_chars;
|
|
if(count < 3)
|
|
{
|
|
incr = 1;
|
|
bound = last_virt;
|
|
nextmotion = paren_chars[count+3];
|
|
}
|
|
else
|
|
nextmotion = paren_chars[count-3];
|
|
count = 1;
|
|
while(count >0 && incr*(tcur_virt+=incr) <= bound)
|
|
{
|
|
if(virtual[tcur_virt] == nextmotion)
|
|
count--;
|
|
else if(virtual[tcur_virt]==nextc)
|
|
count++;
|
|
}
|
|
if(count)
|
|
return(0);
|
|
break;
|
|
}
|
|
|
|
case 'W':
|
|
case 'w': /** forward word **/
|
|
tcur_virt = cur_virt;
|
|
forward(vp,vp->repeat, motion);
|
|
if( tcur_virt == cur_virt )
|
|
return(0);
|
|
return(1);
|
|
|
|
default:
|
|
return(0);
|
|
}
|
|
cur_virt = tcur_virt;
|
|
|
|
return(1);
|
|
}
|
|
|
|
/*
|
|
* print a string
|
|
*/
|
|
|
|
static void pr_string(register Vi_t *vp, register const char *sp)
|
|
{
|
|
/*** copy string sp ***/
|
|
register char *ptr = editb.e_outptr;
|
|
while(*sp)
|
|
*ptr++ = *sp++;
|
|
editb.e_outptr = ptr;
|
|
return;
|
|
}
|
|
|
|
/*{ PUTSTRING( column, nchars )
|
|
*
|
|
* Put nchars starting at column of physical into the workspace
|
|
* to be printed.
|
|
*
|
|
}*/
|
|
|
|
static void putstring(register Vi_t *vp,register int col, register int nchars)
|
|
{
|
|
while( nchars-- )
|
|
putchar(physical[col++]);
|
|
return;
|
|
}
|
|
|
|
/*{ REFRESH( mode )
|
|
*
|
|
* This routine will refresh the crt so the physical image matches
|
|
* the virtual image and display the proper window.
|
|
*
|
|
* mode = CONTROL, refresh in control mode, ie. leave cursor
|
|
* positioned at last char printed.
|
|
* = INPUT, refresh in input mode; leave cursor positioned
|
|
* after last char printed.
|
|
* = TRANSLATE, perform virtual to physical translation
|
|
* and adjust left margin only.
|
|
*
|
|
* +-------------------------------+
|
|
* | | | virtual | | |
|
|
* +-------------------------------+
|
|
* cur_virt last_virt
|
|
*
|
|
* +-----------------------------------------------+
|
|
* | | | physical | | |
|
|
* +-----------------------------------------------+
|
|
* cur_phys last_phys
|
|
*
|
|
* 0 w_size - 1
|
|
* +-----------------------+
|
|
* | | | window |
|
|
* +-----------------------+
|
|
* cur_window = cur_phys - first_wind
|
|
}*/
|
|
|
|
static void refresh(register Vi_t* vp, int mode)
|
|
{
|
|
register int p;
|
|
register int v;
|
|
register int first_w = vp->first_wind;
|
|
int p_differ;
|
|
int new_lw;
|
|
int ncur_phys;
|
|
int opflag; /* search optimize flag */
|
|
|
|
# define w v
|
|
|
|
/*** find out if it's necessary to start translating at beginning ***/
|
|
|
|
if(lookahead>0)
|
|
{
|
|
p = previous[lookahead-1];
|
|
if(p != ESC && p != '\n' && p != '\r')
|
|
mode = TRANSLATE;
|
|
}
|
|
v = cur_virt;
|
|
#if SHOPT_EDPREDICT
|
|
if(mode==INPUT && v>0 && virtual[0]=='#' && v==last_virt && virtual[v]!='*' && sh_isoption(SH_VI))
|
|
{
|
|
int n;
|
|
virtual[last_virt+1] = 0;
|
|
# if SHOPT_MULTIBYTE
|
|
ed_external(virtual,(char*)virtual);
|
|
# endif /* SHOPT_MULTIBYTE */
|
|
n = ed_histgen(vp->ed,(char*)virtual);
|
|
# if SHOPT_MULTIBYTE
|
|
ed_internal((char*)virtual,virtual);
|
|
# endif /* SHOPT_MULTIBYTE */
|
|
if(vp->ed->hlist)
|
|
{
|
|
ed_histlist(vp->ed,n);
|
|
pr_string(vp,Prompt);
|
|
vp->ocur_virt = INVALID;
|
|
ed_setcursor(vp->ed,physical,0,cur_phys,0);
|
|
}
|
|
else
|
|
ed_ringbell();
|
|
}
|
|
else if(mode==INPUT && v<=1 && vp->ed->hlist)
|
|
ed_histlist(vp->ed,0);
|
|
#endif /* SHOPT_EDPREDICT */
|
|
if( v<vp->ocur_virt || vp->ocur_virt==INVALID
|
|
|| ( v==vp->ocur_virt
|
|
&& (!is_print(virtual[v]) || !is_print(vp->o_v_char))) )
|
|
{
|
|
opflag = 0;
|
|
p = 0;
|
|
v = 0;
|
|
}
|
|
else
|
|
{
|
|
opflag = 1;
|
|
p = vp->ocur_phys;
|
|
v = vp->ocur_virt;
|
|
if( !is_print(virtual[v]) )
|
|
{
|
|
/*** avoid double ^'s ***/
|
|
++p;
|
|
++v;
|
|
}
|
|
}
|
|
virtual[last_virt+1] = 0;
|
|
ncur_phys = ed_virt_to_phys(vp->ed,virtual,physical,cur_virt,v,p);
|
|
p = genlen(physical);
|
|
if( --p < 0 )
|
|
last_phys = 0;
|
|
else
|
|
last_phys = p;
|
|
|
|
/*** see if this was a translate only ***/
|
|
|
|
if( mode == TRANSLATE )
|
|
return;
|
|
|
|
/*** adjust left margin if necessary ***/
|
|
|
|
if( ncur_phys<first_w || ncur_phys>=(first_w + w_size) )
|
|
{
|
|
cursor(vp,first_w);
|
|
first_w = ncur_phys - (w_size>>1);
|
|
if( first_w < 0 )
|
|
first_w = 0;
|
|
vp->first_wind = cur_phys = first_w;
|
|
}
|
|
|
|
/*** attempt to optimize search somewhat to find ***/
|
|
/*** out where physical and window images differ ***/
|
|
|
|
if( first_w==vp->ofirst_wind && ncur_phys>=vp->ocur_phys && opflag==1 )
|
|
{
|
|
p = vp->ocur_phys;
|
|
w = p - first_w;
|
|
}
|
|
else
|
|
{
|
|
p = first_w;
|
|
w = 0;
|
|
}
|
|
|
|
for(; (p<=last_phys && w<=vp->last_wind); ++p, ++w)
|
|
{
|
|
if( window[w] != physical[p] )
|
|
break;
|
|
}
|
|
p_differ = p;
|
|
|
|
if( (p>last_phys || p>=first_w+w_size) && w>vp->last_wind
|
|
&& cur_virt==vp->ocur_virt )
|
|
{
|
|
/*** images are identical ***/
|
|
return;
|
|
}
|
|
|
|
/*** copy the physical image to the window image ***/
|
|
|
|
if( last_virt != INVALID )
|
|
{
|
|
while( p <= last_phys && w < w_size )
|
|
window[w++] = physical[p++];
|
|
}
|
|
new_lw = w;
|
|
|
|
/*** erase trailing characters if needed ***/
|
|
|
|
while( w <= vp->last_wind )
|
|
window[w++] = ' ';
|
|
vp->last_wind = --w;
|
|
|
|
p = p_differ;
|
|
|
|
/*** move cursor to start of difference ***/
|
|
|
|
cursor(vp,p);
|
|
|
|
/*** and output difference ***/
|
|
|
|
w = p - first_w;
|
|
while( w <= vp->last_wind )
|
|
putchar(window[w++]);
|
|
|
|
cur_phys = w + first_w;
|
|
vp->last_wind = --new_lw;
|
|
|
|
if( last_phys >= w_size )
|
|
{
|
|
if( first_w == 0 )
|
|
vp->long_char = '>';
|
|
else if( last_phys < (first_w+w_size) )
|
|
vp->long_char = '<';
|
|
else
|
|
vp->long_char = '*';
|
|
}
|
|
else
|
|
vp->long_char = ' ';
|
|
|
|
if( vp->long_line != vp->long_char )
|
|
{
|
|
/*** indicate lines longer than window ***/
|
|
while( w++ < w_size )
|
|
{
|
|
putchar(' ');
|
|
++cur_phys;
|
|
}
|
|
putchar(vp->long_char);
|
|
++cur_phys;
|
|
vp->long_line = vp->long_char;
|
|
}
|
|
|
|
if(vp->ed->e_multiline && vp->ofirst_wind==INVALID && !vp->ed->e_nocrnl)
|
|
ed_setcursor(vp->ed, physical, last_phys+1, last_phys+1, -1);
|
|
vp->ed->e_nocrnl = 0;
|
|
vp->ocur_phys = ncur_phys;
|
|
vp->ocur_virt = cur_virt;
|
|
vp->ofirst_wind = first_w;
|
|
|
|
if( mode==INPUT && cur_virt>INVALID )
|
|
++ncur_phys;
|
|
|
|
cursor(vp,ncur_phys);
|
|
ed_flush(vp->ed);
|
|
return;
|
|
}
|
|
|
|
/*{ REPLACE( char, increment )
|
|
*
|
|
* Replace the cur_virt character with char. This routine attempts
|
|
* to avoid using refresh().
|
|
*
|
|
* increment = 1, increment cur_virt after replacement.
|
|
* = 0, leave cur_virt where it is.
|
|
*
|
|
}*/
|
|
|
|
static void replace(register Vi_t *vp, register int c, register int increment)
|
|
{
|
|
register int cur_window;
|
|
|
|
if( cur_virt == INVALID )
|
|
{
|
|
/*** can't replace invalid cursor ***/
|
|
ed_ringbell();
|
|
return;
|
|
}
|
|
cur_window = cur_phys - vp->first_wind;
|
|
if( vp->ocur_virt == INVALID || !is_print(c)
|
|
|| !is_print(virtual[cur_virt])
|
|
|| !is_print(vp->o_v_char)
|
|
#if SHOPT_MULTIBYTE
|
|
|| !iswascii(c) || mbwidth(vp->o_v_char)>1
|
|
|| !iswascii(virtual[cur_virt])
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
|| (increment && (cur_window==w_size-1)
|
|
|| !is_print(virtual[cur_virt+1])) )
|
|
{
|
|
/*** must use standard refresh routine ***/
|
|
|
|
cdelete(vp,1, BAD);
|
|
append(vp,c, APPEND);
|
|
if( increment && cur_virt<last_virt )
|
|
++cur_virt;
|
|
refresh(vp,CONTROL);
|
|
}
|
|
else
|
|
{
|
|
virtual[cur_virt] = c;
|
|
physical[cur_phys] = c;
|
|
window[cur_window] = c;
|
|
putchar(c);
|
|
if(increment)
|
|
{
|
|
c = virtual[++cur_virt];
|
|
++cur_phys;
|
|
}
|
|
else
|
|
{
|
|
putchar('\b');
|
|
}
|
|
vp->o_v_char = c;
|
|
ed_flush(vp->ed);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*{ RESTORE_V()
|
|
*
|
|
* Restore the contents of virtual space from u_space.
|
|
*
|
|
}*/
|
|
|
|
static void restore_v(register Vi_t *vp)
|
|
{
|
|
register int tmpcol;
|
|
genchar tmpspace[MAXLINE];
|
|
|
|
if( vp->u_column == INVALID-1 )
|
|
{
|
|
/*** never saved anything ***/
|
|
ed_ringbell();
|
|
return;
|
|
}
|
|
gencpy(tmpspace, vp->u_space);
|
|
tmpcol = vp->u_column;
|
|
save_v(vp);
|
|
gencpy(virtual, tmpspace);
|
|
cur_virt = tmpcol;
|
|
last_virt = genlen(tmpspace) - 1;
|
|
vp->ocur_virt = MAXCHAR; /** invalidate refresh optimization **/
|
|
return;
|
|
}
|
|
|
|
/*{ SAVE_LAST()
|
|
*
|
|
* If the user has typed something, save it in last line.
|
|
*
|
|
}*/
|
|
|
|
static void save_last(register Vi_t* vp)
|
|
{
|
|
register int i;
|
|
|
|
if( (i = cur_virt - first_virt + 1) > 0 )
|
|
{
|
|
/*** save last thing user typed ***/
|
|
if(i >= MAXLINE)
|
|
i = MAXLINE-1;
|
|
genncpy(vp->lastline, (&virtual[first_virt]), i);
|
|
vp->lastline[i] = '\0';
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*{ SAVE_V()
|
|
*
|
|
* This routine will save the contents of virtual in u_space.
|
|
*
|
|
}*/
|
|
|
|
static void save_v(register Vi_t *vp)
|
|
{
|
|
if(!inmacro)
|
|
{
|
|
virtual[last_virt + 1] = '\0';
|
|
gencpy(vp->u_space, virtual);
|
|
vp->u_column = cur_virt;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*{ SEARCH( mode )
|
|
*
|
|
* Search history file for regular expression.
|
|
*
|
|
* mode = '/' require search string and search new to old
|
|
* mode = '?' require search string and search old to new
|
|
* mode = 'N' repeat last search in reverse direction
|
|
* mode = 'n' repeat last search
|
|
*
|
|
}*/
|
|
|
|
/*
|
|
* search for <string> in the current command
|
|
*/
|
|
static int curline_search(Vi_t *vp, const char *string)
|
|
{
|
|
register size_t len=strlen(string);
|
|
register const char *dp,*cp=string, *dpmax;
|
|
#if SHOPT_MULTIBYTE
|
|
ed_external(vp->u_space,(char*)vp->u_space);
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
for(dp=(char*)vp->u_space,dpmax=dp+strlen(dp)-len; dp<=dpmax; dp++)
|
|
{
|
|
if(*dp==*cp && memcmp(cp,dp,len)==0)
|
|
return(dp-(char*)vp->u_space);
|
|
}
|
|
#if SHOPT_MULTIBYTE
|
|
ed_internal((char*)vp->u_space,vp->u_space);
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
return(-1);
|
|
}
|
|
|
|
static int search(register Vi_t* vp,register int mode)
|
|
{
|
|
register int new_direction;
|
|
register int oldcurhline;
|
|
register int i;
|
|
Histloc_t location;
|
|
|
|
if( vp->direction == -2 && mode != 'n')
|
|
vp->direction = -1;
|
|
if( mode == '/' || mode == '?')
|
|
{
|
|
/*** new search expression ***/
|
|
del_line(vp,BAD);
|
|
append(vp,mode, APPEND);
|
|
refresh(vp,INPUT);
|
|
first_virt = 1;
|
|
getline(vp,SEARCH);
|
|
first_virt = 0;
|
|
virtual[last_virt + 1] = '\0'; /*** make null terminated ***/
|
|
vp->direction = mode=='/' ? -1 : 1;
|
|
}
|
|
|
|
if( cur_virt == INVALID )
|
|
{
|
|
/*** no operation ***/
|
|
return(ABORT);
|
|
}
|
|
|
|
if( cur_virt==0 || fold(mode)=='N' )
|
|
{
|
|
/*** user wants repeat of last search ***/
|
|
del_line(vp,BAD);
|
|
strcpy( ((char*)virtual)+1, lsearch);
|
|
#if SHOPT_MULTIBYTE
|
|
*((char*)virtual) = '/';
|
|
ed_internal((char*)virtual,virtual);
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
}
|
|
|
|
if( mode == 'N' )
|
|
new_direction = -vp->direction;
|
|
else
|
|
new_direction = vp->direction;
|
|
|
|
|
|
/*** now search ***/
|
|
|
|
oldcurhline = curhline;
|
|
#if SHOPT_MULTIBYTE
|
|
ed_external(virtual,(char*)virtual);
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
if(mode=='?' && (i=curline_search(vp,((char*)virtual)+1))>=0)
|
|
{
|
|
location.hist_command = curhline;
|
|
location.hist_char = i;
|
|
}
|
|
else
|
|
{
|
|
i = INVALID;
|
|
if( new_direction==1 && curhline >= histmax )
|
|
curhline = histmin + 1;
|
|
location = hist_find(shgd->hist_ptr,((char*)virtual)+1, curhline, 1, new_direction);
|
|
}
|
|
cur_virt = i;
|
|
strncpy(lsearch, ((char*)virtual)+1, SEARCHSIZE);
|
|
lsearch[SEARCHSIZE-1] = 0;
|
|
if( (curhline=location.hist_command) >=0 )
|
|
{
|
|
vp->ocur_virt = INVALID;
|
|
return(GOOD);
|
|
}
|
|
|
|
/*** could not find matching line ***/
|
|
|
|
curhline = oldcurhline;
|
|
return(BAD);
|
|
}
|
|
|
|
/*{ SYNC_CURSOR()
|
|
*
|
|
* This routine will move the physical cursor to the same
|
|
* column as the virtual cursor.
|
|
*
|
|
}*/
|
|
|
|
static void sync_cursor(register Vi_t *vp)
|
|
{
|
|
register int p;
|
|
register int v;
|
|
register int c;
|
|
int new_phys;
|
|
|
|
if( cur_virt == INVALID )
|
|
return;
|
|
|
|
/*** find physical col that corresponds to virtual col ***/
|
|
|
|
new_phys = 0;
|
|
if(vp->first_wind==vp->ofirst_wind && cur_virt>vp->ocur_virt && vp->ocur_virt!=INVALID)
|
|
{
|
|
/*** try to optimize search a little ***/
|
|
p = vp->ocur_phys + 1;
|
|
#if SHOPT_MULTIBYTE
|
|
while(physical[p]==MARKER)
|
|
p++;
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
v = vp->ocur_virt + 1;
|
|
}
|
|
else
|
|
{
|
|
p = 0;
|
|
v = 0;
|
|
}
|
|
for(; v <= last_virt; ++p, ++v)
|
|
{
|
|
#if SHOPT_MULTIBYTE
|
|
int d;
|
|
c = virtual[v];
|
|
if((d = mbwidth(c)) > 1)
|
|
{
|
|
if( v != cur_virt )
|
|
p += (d-1);
|
|
}
|
|
else if(!iswprint(c))
|
|
#else
|
|
c = virtual[v];
|
|
if(!isprint(c))
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
{
|
|
if( c == '\t' )
|
|
{
|
|
p -= ((p+editb.e_plen)%TABSIZE);
|
|
p += (TABSIZE-1);
|
|
}
|
|
else
|
|
{
|
|
++p;
|
|
}
|
|
}
|
|
if( v == cur_virt )
|
|
{
|
|
new_phys = p;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( new_phys < vp->first_wind || new_phys >= vp->first_wind + w_size )
|
|
{
|
|
/*** asked to move outside of window ***/
|
|
|
|
window[0] = '\0';
|
|
refresh(vp,CONTROL);
|
|
return;
|
|
}
|
|
|
|
cursor(vp,new_phys);
|
|
ed_flush(vp->ed);
|
|
vp->ocur_phys = cur_phys;
|
|
vp->ocur_virt = cur_virt;
|
|
vp->o_v_char = virtual[vp->ocur_virt];
|
|
|
|
return;
|
|
}
|
|
|
|
/*{ TEXTMOD( command, mode )
|
|
*
|
|
* Modify text operations.
|
|
*
|
|
* mode != 0, repeat previous operation
|
|
*
|
|
}*/
|
|
|
|
static int textmod(register Vi_t *vp,register int c, int mode)
|
|
{
|
|
register int i;
|
|
register genchar *p = vp->lastline;
|
|
register int trepeat = vp->repeat;
|
|
genchar *savep;
|
|
int ch;
|
|
|
|
if(mode && (fold(vp->lastmotion)=='F' || fold(vp->lastmotion)=='T'))
|
|
vp->lastmotion = ';';
|
|
|
|
if( fold(c) == 'P' )
|
|
{
|
|
/*** change p from lastline to yankbuf ***/
|
|
p = yankbuf;
|
|
}
|
|
|
|
addin:
|
|
switch( c )
|
|
{
|
|
/***** Input commands *****/
|
|
|
|
#if KSHELL
|
|
case '\t':
|
|
if(vp->ed->e_tabcount!=1)
|
|
return(BAD);
|
|
c = '=';
|
|
case '*': /** do file name expansion in place **/
|
|
case '\\': /** do file name completion in place **/
|
|
if( cur_virt == INVALID )
|
|
return(BAD);
|
|
case '=': /** list file name expansions **/
|
|
save_v(vp);
|
|
i = last_virt;
|
|
++last_virt;
|
|
mode = cur_virt-1;
|
|
virtual[last_virt] = 0;
|
|
ch = c;
|
|
if(mode>=0 && c=='\\' && virtual[mode+1]=='/')
|
|
c = '=';
|
|
if(ed_expand(vp->ed,(char*)virtual, &cur_virt, &last_virt, ch, vp->repeat_set?vp->repeat:-1)<0)
|
|
{
|
|
if(vp->ed->e_tabcount)
|
|
{
|
|
vp->ed->e_tabcount=2;
|
|
ed_ungetchar(vp->ed,'\t');
|
|
--last_virt;
|
|
return(APPEND);
|
|
}
|
|
last_virt = i;
|
|
ed_ringbell();
|
|
}
|
|
else if((c=='=' || (c=='\\'&&virtual[last_virt]=='/')) && !vp->repeat_set)
|
|
{
|
|
vp->nonewline++;
|
|
ed_ungetchar(vp->ed,cntl('L'));
|
|
return(GOOD);
|
|
}
|
|
else
|
|
{
|
|
--cur_virt;
|
|
--last_virt;
|
|
vp->ocur_virt = MAXCHAR;
|
|
if(c=='=' || (mode<cur_virt && (virtual[cur_virt]==' ' || virtual[cur_virt]=='/')))
|
|
vp->ed->e_tabcount = 0;
|
|
return(APPEND);
|
|
}
|
|
break;
|
|
|
|
case '@': /** macro expansion **/
|
|
if( mode )
|
|
c = vp->lastmacro;
|
|
else
|
|
if((c=getrchar(vp))==ESC)
|
|
return(GOOD);
|
|
if(!inmacro)
|
|
vp->lastmacro = c;
|
|
if(ed_macro(vp->ed,c))
|
|
{
|
|
save_v(vp);
|
|
inmacro++;
|
|
return(GOOD);
|
|
}
|
|
ed_ringbell();
|
|
return(BAD);
|
|
|
|
#endif /* KSHELL */
|
|
case '_': /** append last argument of prev command **/
|
|
save_v(vp);
|
|
{
|
|
genchar tmpbuf[MAXLINE];
|
|
if(vp->repeat_set==0)
|
|
vp->repeat = -1;
|
|
p = (genchar*)hist_word((char*)tmpbuf,MAXLINE,vp->repeat);
|
|
if(p==0)
|
|
{
|
|
ed_ringbell();
|
|
break;
|
|
}
|
|
#if SHOPT_MULTIBYTE
|
|
ed_internal((char*)p,tmpbuf);
|
|
p = tmpbuf;
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
i = ' ';
|
|
do
|
|
{
|
|
append(vp,i,APPEND);
|
|
}
|
|
while(i = *p++);
|
|
return(APPEND);
|
|
}
|
|
|
|
case 'A': /** append to end of line **/
|
|
cur_virt = last_virt;
|
|
sync_cursor(vp);
|
|
|
|
case 'a': /** append **/
|
|
if( fold(mode) == 'A' )
|
|
{
|
|
c = 'p';
|
|
goto addin;
|
|
}
|
|
save_v(vp);
|
|
if( cur_virt != INVALID )
|
|
{
|
|
first_virt = cur_virt + 1;
|
|
cursor(vp,cur_phys + 1);
|
|
ed_flush(vp->ed);
|
|
}
|
|
return(APPEND);
|
|
|
|
case 'I': /** insert at beginning of line **/
|
|
cur_virt = first_virt;
|
|
sync_cursor(vp);
|
|
|
|
case 'i': /** insert **/
|
|
if( fold(mode) == 'I' )
|
|
{
|
|
c = 'P';
|
|
goto addin;
|
|
}
|
|
save_v(vp);
|
|
if( cur_virt != INVALID )
|
|
{
|
|
vp->o_v_char = virtual[cur_virt];
|
|
first_virt = cur_virt--;
|
|
}
|
|
return(INSERT);
|
|
|
|
case 'C': /** change to eol **/
|
|
c = '$';
|
|
goto chgeol;
|
|
|
|
case 'c': /** change **/
|
|
if( mode )
|
|
c = vp->lastmotion;
|
|
else
|
|
c = getcount(vp,ed_getchar(vp->ed,-1));
|
|
chgeol:
|
|
vp->lastmotion = c;
|
|
if( c == 'c' )
|
|
{
|
|
del_line(vp,GOOD);
|
|
return(APPEND);
|
|
}
|
|
|
|
if(!delmotion(vp, c, 'c'))
|
|
return(BAD);
|
|
|
|
if( mode == 'c' )
|
|
{
|
|
c = 'p';
|
|
trepeat = 1;
|
|
goto addin;
|
|
}
|
|
first_virt = cur_virt + 1;
|
|
return(APPEND);
|
|
|
|
case 'D': /** delete to eol **/
|
|
c = '$';
|
|
goto deleol;
|
|
|
|
case 'd': /** delete **/
|
|
if( mode )
|
|
c = vp->lastmotion;
|
|
else
|
|
c = getcount(vp,ed_getchar(vp->ed,-1));
|
|
deleol:
|
|
vp->lastmotion = c;
|
|
if( c == 'd' )
|
|
{
|
|
del_line(vp,GOOD);
|
|
break;
|
|
}
|
|
if(!delmotion(vp, c, 'd'))
|
|
return(BAD);
|
|
if( cur_virt < last_virt )
|
|
++cur_virt;
|
|
break;
|
|
|
|
case 'P':
|
|
if( p[0] == '\0' )
|
|
return(BAD);
|
|
if( cur_virt != INVALID )
|
|
{
|
|
i = virtual[cur_virt];
|
|
if(!is_print(i))
|
|
vp->ocur_virt = INVALID;
|
|
--cur_virt;
|
|
}
|
|
|
|
case 'p': /** print **/
|
|
if( p[0] == '\0' )
|
|
return(BAD);
|
|
|
|
if( mode != 's' && mode != 'c' )
|
|
{
|
|
save_v(vp);
|
|
if( c == 'P' )
|
|
{
|
|
/*** fix stored cur_virt ***/
|
|
++vp->u_column;
|
|
}
|
|
}
|
|
if( mode == 'R' )
|
|
mode = REPLACE;
|
|
else
|
|
mode = APPEND;
|
|
savep = p;
|
|
for(i=0; i<trepeat; ++i)
|
|
{
|
|
while(c= *p++)
|
|
append(vp,c,mode);
|
|
p = savep;
|
|
}
|
|
break;
|
|
|
|
case 'R': /* Replace many chars **/
|
|
if( mode == 'R' )
|
|
{
|
|
c = 'P';
|
|
goto addin;
|
|
}
|
|
save_v(vp);
|
|
if( cur_virt != INVALID )
|
|
first_virt = cur_virt;
|
|
return(REPLACE);
|
|
|
|
case 'r': /** replace **/
|
|
if( mode )
|
|
c = *p;
|
|
else
|
|
if((c=getrchar(vp))==ESC)
|
|
return(GOOD);
|
|
*p = c;
|
|
save_v(vp);
|
|
while(trepeat--)
|
|
replace(vp,c, trepeat!=0);
|
|
return(GOOD);
|
|
|
|
case 'S': /** Substitute line - cc **/
|
|
c = 'c';
|
|
goto chgeol;
|
|
|
|
case 's': /** substitute **/
|
|
save_v(vp);
|
|
cdelete(vp,vp->repeat, BAD);
|
|
if( mode )
|
|
{
|
|
c = 'p';
|
|
trepeat = 1;
|
|
goto addin;
|
|
}
|
|
first_virt = cur_virt + 1;
|
|
return(APPEND);
|
|
|
|
case 'Y': /** Yank to end of line **/
|
|
c = '$';
|
|
goto yankeol;
|
|
|
|
case 'y': /** yank thru motion **/
|
|
if( mode )
|
|
c = vp->lastmotion;
|
|
else
|
|
c = getcount(vp,ed_getchar(vp->ed,-1));
|
|
yankeol:
|
|
vp->lastmotion = c;
|
|
if( c == 'y' )
|
|
{
|
|
gencpy(yankbuf, virtual);
|
|
}
|
|
else if(!delmotion(vp, c, 'y'))
|
|
{
|
|
return(BAD);
|
|
}
|
|
break;
|
|
|
|
case 'x': /** delete repeat chars forward - dl **/
|
|
c = 'l';
|
|
goto deleol;
|
|
|
|
case 'X': /** delete repeat chars backward - dh **/
|
|
c = 'h';
|
|
goto deleol;
|
|
|
|
case '~': /** invert case and advance **/
|
|
if( cur_virt != INVALID )
|
|
{
|
|
save_v(vp);
|
|
i = INVALID;
|
|
while(trepeat-->0 && i!=cur_virt)
|
|
{
|
|
i = cur_virt;
|
|
c = virtual[cur_virt];
|
|
#if SHOPT_MULTIBYTE
|
|
if((c&~STRIP)==0)
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
if( isupper(c) )
|
|
c = tolower(c);
|
|
else if( islower(c) )
|
|
c = toupper(c);
|
|
replace(vp,c, 1);
|
|
}
|
|
return(GOOD);
|
|
}
|
|
else
|
|
return(BAD);
|
|
|
|
default:
|
|
return(BAD);
|
|
}
|
|
refresh(vp,CONTROL);
|
|
return(GOOD);
|
|
}
|
|
|
|
|
|
#if SHOPT_MULTIBYTE
|
|
static int _isalph(register int v)
|
|
{
|
|
#ifdef _lib_iswalnum
|
|
return(iswalnum(v) || v=='_');
|
|
#else
|
|
return((v&~STRIP) || isalnum(v) || v=='_');
|
|
#endif
|
|
}
|
|
|
|
|
|
static int _isblank(register int v)
|
|
{
|
|
return((v&~STRIP)==0 && isspace(v));
|
|
}
|
|
|
|
static int _ismetach(register int v)
|
|
{
|
|
return((v&~STRIP)==0 && ismeta(v));
|
|
}
|
|
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
|
|
/*
|
|
* get a character, after ^V processing
|
|
*/
|
|
static int getrchar(register Vi_t *vp)
|
|
{
|
|
register int c;
|
|
if((c=ed_getchar(vp->ed,1))== usrlnext)
|
|
c = ed_getchar(vp->ed,2);
|
|
return(c);
|
|
}
|