#ident "@(#)vi.c 1.8 93/07/09 SMI" /* From AT&T Toolchest */ /* 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 -*/ #include #ifdef KSHELL # include "defs.h" #else # include "io.h" #endif /* KSHELL */ #include "history.h" #include "edit.h" #include "terminal.h" # ifdef FIONREAD # include /* XXX - need _IOR definition */ # endif /* FIONREAD */ #ifdef OLDTERMIO # undef ECHOCTL extern char echoctl; #else # ifdef ECHOCTL # define echoctl ECHOCTL # else # define echoctl 0 # endif /* ECHOCTL */ #endif /*OLDTERMIO */ #ifndef FIORDCHK # define NTICKS 5 /* number of ticks for typeahead */ # ifndef KSHELL # ifdef _sys_times_ # include # else struct tms { time_t tms_utime; time_t tms_stime; time_t tms_cutime; time_t tms_cstime; }; # endif /* _sys_times */ # endif /* KSHELL */ #endif /* FIORDCHK */ #define MAXCHAR MAXLINE-2 /* max char per line */ #define WINDOW MAXWINDOW /* max char in window of which */ /* WINDOW-2 are available to user */ /* actual window size may be smaller */ #ifndef KSHELL extern char *malloc(); extern char *strcpy(); extern char *strchr(); extern char *strncpy(); #endif /* KSHELL */ #undef isblank #ifdef MULTIBYTE static int bigvi; # 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)) #else # 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) isalnum(virtual[v]) # define isblank(v) isspace(virtual[v]) # define ismetach(v) ismeta(virtual[v]) # define digit(c) isdigit(c) # define is_print(c) isprint(c) #endif /* MULTIBYTE */ #define fold(c) ((c)&~040) /* lower and uppercase equivalent */ #ifdef INT16 /* save space by defining functions for these */ # undef isalph # undef isblank # undef ismetach #endif /* INT16 */ #undef putchar #undef getchar #define getchar() ed_getchar() #define putchar(c) ed_putchar(c) #define bell ed_ringbell() /* ring terminal's bell */ #define crlf ed_crlf() /* return and linefeed */ #define in_raw editb.e_addnl /* next char input is raw */ #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 env editb.e_env #define fildes editb.e_fd #define findchar editb.e_fchar /* last find char */ #define first_virt editb.e_fcol /* first allowable column */ #define first_wind editb.e_globals[0] /* first column of window */ #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 last_wind editb.e_globals[1] /* last column in window */ #define lastmotion editb.e_globals[2] /* last motion */ #define lastrepeat editb.e_mode /* last repeat count for motion cmds */ #define long_char editb.e_globals[3] /* line bigger than window */ #define long_line editb.e_globals[4] /* line bigger than window */ #define lsearch editb.e_search /* last search string */ #define lookahead editb.e_index /* characters in buffer */ #define previous editb.e_lbuf /* lookahead buffer */ #define max_col editb.e_llimit /* maximum column */ #define ocur_phys editb.e_globals[5] /* old current physical position */ #define ocur_virt editb.e_globals[6] /* old last virtual position */ #define ofirst_wind editb.e_globals[7] /* old window first col */ #define o_v_char editb.e_globals[8] /* prev virtual[ocur_virt] */ #define Prompt editb.e_prompt /* pointer to prompt */ #define plen editb.e_plen /* length of prompt */ #define physical editb.e_physbuf /* physical image */ #define repeat editb.e_repeat /* repeat count for motion cmds */ #define ttyspeed editb.e_ttyspeed /* tty speed */ #define u_column editb.e_ucol /* undo current column */ #define U_saved editb.e_saved /* original virtual saved */ #define U_space editb.e_Ubuf /* used for U command */ #define u_space editb.e_ubuf /* used for u command */ #define usreof editb.e_eof /* user defined eof char */ #define usrerase editb.e_erase /* user defined erase char */ #define usrintr editb.e_intr /* user defined intr char */ #define usrkill editb.e_kill /* user defined kill char */ #define usrquit editb.e_quit /* user defined quit 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 */ #ifndef KSHELL extern clock_t times(); #endif /* KSHELL */ #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 DEL '\177' /* interrupt char */ #define TRUE 1 #define FALSE 0 #define INVALID (-1) /* invalid column */ #define QUIT_C '\34' /* quit char */ #define SYSERR (-1) /* system error */ static char addnl; /* boolean - add newline flag */ static char last_cmd = '\0'; /* last command */ static char repeat_set; static char nonewline; static genchar *lastline; static char paren_chars[] = "([{)]}"; /* for % command */ #ifdef FIORDCHK static clock_t typeahead; /* typeahead occurred */ #else static int typeahead; /* typeahead occurred */ #endif /* FIORDCHK */ static void del_line(); static int getcount(); static void getline(); static int getrchar(); static int mvcursor(); static void pr_prompt(); static void pr_string(); static void putstring(); static void refresh(); static void replace(); static void restore_v(); static void save_last(); static void save_v(); static int search(); static void sync_cursor(); static int textmod(); /*+ VI_READ( fd, shbuf, nchar ) * * This routine implements a one line version of vi and is * called by _filbuf.c * -*/ vi_read(fd, shbuf, nchar) int fd; /* input file descriptor */ register char *shbuf; /* shell line buffer */ unsigned nchar; /* number of chars to read */ { register int c; /* general variable */ register int i; /* general variable */ register int term_char; /* read() termination character */ 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[WINDOW+10]; /* window image */ char cntl_char; /* TRUE if control character present */ int Globals[9]; /* local global variables */ int esc_or_hang = 0; /* or hangup */ #ifndef FIORDCHK clock_t oldtime, newtime; struct tms dummy; #endif /* FIORDCHK */ /*** setup prompt ***/ Prompt = prompt; ed_setup(fd); #ifndef RAWONLY if( !is_option(VIRAW) ) { /*** Change the eol characters to '\r' and eof ***/ /* in addition to '\n' and make eof an ESC */ if( tty_alt(ERRIO) == BAD ) { return(read(fd, shbuf, nchar)); } # ifdef FIORDCHK # ifdef FIONREAD ioctl(fd,FIONREAD,&typeahead); # else typeahead = ioctl(fd,FIORDCHK,0); # endif /* FIONREAD */ # else /* time the current line to determine typeahead */ oldtime = times(&dummy); # endif /* FIORDCHK */ # ifdef KSHELL /* abort of interrupt has occurred */ if(sh.trapnote&SIGSET) i = -1; else # endif /* KSHELL */ /*** Read the line ***/ i = read(fd, shbuf, nchar); # ifndef FIORDCHK newtime = times(&dummy); 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 { 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( 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 /* RAWONLY */ { /*** Set raw mode ***/ #ifndef RAWONLY if( ttyspeed == 0 ) { /*** never did TCGETA, so do it ***/ /* avoids problem if user does 'sh -o viraw' */ tty_alt(ERRIO); } #endif /* RAWONLY */ if( tty_raw(ERRIO) == BAD ) { return(read(fd, shbuf, nchar)); } i = INVALID; } /*** Initialize some things ***/ virtual = (genchar*)shbuf; #undef virtual #define virtual ((genchar*)shbuf) #ifdef MULTIBYTE shbuf[i+1] = 0; i = ed_internal(shbuf,virtual)-1; #endif /* MULTIBYTE */ globals = Globals; cur_phys = i + 1; cur_virt = i; fildes = fd; first_virt = 0; first_wind = 0; last_virt = i; last_phys = i; last_wind = i; long_line = ' '; long_char = ' '; o_v_char = '\0'; ocur_phys = 0; in_raw = 0; ocur_virt = MAXCHAR; ofirst_wind = 0; physical = Physical; u_column = INVALID - 1; U_space = Ubuf; u_space = ubuf; window = Window; window[0] = '\0'; #if KSHELL && (2*CHARSIZE*MAXLINE) MAXCHAR ) nchar = MAXCHAR - plen; max_col = nchar - 2; #ifndef RAWONLY if( !is_option(VIRAW) ) { int kill_erase = 0; # ifndef ECHOCTL cntl_char = FALSE; # endif /* !ECHOCTL */ 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) { c = virtual[i]; if( c==usrerase || c==usrkill ) { /*** user typed escaped erase or kill char ***/ cntl_char = TRUE; if(is_print(c)) kill_erase++; } else if( !is_print(c) ) { cntl_char = TRUE; if( c == cntl('V') ) { 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(virtual,physical,last_virt,0,0); if( last_phys >= w_size ) { /*** line longer than window ***/ last_wind = w_size - 1; } else last_wind = last_phys; genncpy(window, virtual, 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( ttyspeed==FAST && last_virt!=INVALID # ifdef ECHOCTL && typeahead) # else && (typeahead || cntl_char==TRUE) ) # endif /*ECHOCTL */ { refresh(TRANSLATE); pr_string(Prompt); putstring(0, last_phys+1); if(echoctl) crlf; else while(kill_erase-- > 0) putchar(' '); } if( term_char=='\n' ) { if(!echoctl) crlf; virtual[++last_virt] = '\n'; } last_cmd = 'i'; save_last(); #ifdef MULTIBYTE virtual[last_virt+1] = 0; last_virt = ed_external(virtual,shbuf); return(last_virt); #else return(++last_virt); #endif /* MULTIBYTE */ } /*** Line terminated with escape, or escaped eol/eof, ***/ /* so set raw mode */ if( tty_raw(ERRIO) == BAD ) { tty_cooked(ERRIO); /* * The following prevents drivers that return 0 on * reads after disconnect (rather than -1), from * causing an infinite loop */ if(esc_or_hang) return(-1); virtual[++last_virt] = '\n'; #ifdef MULTIBYTE virtual[last_virt+1] = 0; last_virt = ed_external(virtual,shbuf); return(last_virt); #else return(++last_virt); #endif /* MULTIBYTE */ } if(echoctl) /*** for cntl-echo erase the ^[ ***/ pr_string("\b\b \b\b"); if( crallowed == YES ) { /*** 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(Prompt); if( term_char==ESC && virtual[last_virt]!=ESC ) refresh(CONTROL); else refresh(INPUT); } else { /*** just update everything internally ***/ refresh(TRANSLATE); } } else #endif /* RAWONLY */ virtual[0] = '\0'; /*** Handle usrintr, usrquit, or EOF ***/ i = SETJMP(env); if(i !=0) { virtual[0] = '\0'; tty_cooked(ERRIO); switch(i) { case UEOF: /*** EOF ***/ return(0); case UINTR: /** interrupt **/ return(SYSERR); } return(SYSERR); } /*** Get a line from the terminal ***/ U_saved = FALSE; #ifdef RAWONLY getline(APPEND); #else if( is_option(VIRAW) || (last_virt>=0 && virtual[last_virt]==term_char)) getline(APPEND); else getline(ESC); #endif /* RAWONLY */ /*** add a new line if user typed unescaped \n ***/ /* to cause the shell to process the line */ tty_cooked(ERRIO); if( addnl ) { virtual[++last_virt] = '\n'; crlf; } if( ++last_virt >= 0 ) { #ifdef MULTIBYTE if(bigvi) { bigvi = 0; shbuf[last_virt-1] = '\n'; } else { virtual[last_virt] = 0; last_virt = ed_external(virtual,shbuf); } #endif /* MULTIBYTE */ return(last_virt); } else return(SYSERR); } #undef virtual #define virtual editb.e_inbuf /* pointer to virtual image buffer */ /*{ 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(c, mode) int c; int mode; { register int i; if( last_virt cur_virt; --i) { virtual[i] = virtual[i-1]; } } virtual[++cur_virt] = c; } else bell; return; } /*{ BACKWORD( nwords, cmd ) * * This routine will position cur_virt at the nth previous word. * }*/ static void backword(nwords, cmd) 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); if((!isalph(tcur_virt) && last) || (isalph(tcur_virt) && !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() { 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( U_saved == FALSE ) { /*** save virtual image if never done before ***/ virtual[last_virt+1] = '\0'; gencpy(U_space, virtual); U_saved = TRUE; } save_last(); real_u_space = u_space; curhline = histmax; first_virt = 0; repeat = 1; if( cur_virt > INVALID ) { /*** make sure cursor is at the last char ***/ sync_cursor(); } /*** Read control char until something happens to cause a ***/ /* return to APPEND/REPLACE mode */ while( c=getchar() ) { repeat_set = 0; was_inmacro = inmacro; if( c == '0' ) { /*** move to leftmost column ***/ cur_virt = 0; sync_cursor(); continue; } if( digit(c) ) { lastrepeat = repeat; c = getcount(c); if( c == '.' ) lastrepeat = repeat; } /*** see if it's a move cursor command ***/ if( mvcursor(c) == GOOD ) { sync_cursor(); repeat = 1; continue; } /*** see if it's a repeat of the last command ***/ if( c == '.' ) { c = last_cmd; repeat = lastrepeat; i = textmod(c, c); } else { i = textmod(c, 0); } /*** see if it's a text modification command ***/ switch(i) { case BAD: break; default: /** input mode **/ if(!was_inmacro) { last_cmd = c; lastrepeat = repeat; } 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(nonewline==0) putchar('\n'); nonewline = 0; pr_string(Prompt); window[0] = '\0'; cur_phys = first_wind; ofirst_wind = INVALID; long_line = ' '; break; case cntl('V'): { register const char *p = &e_version[5]; save_v(); del_line(BAD); while(c = *p++) append(c,APPEND); refresh(CONTROL); ed_getchar(); restore_v(); break; } case '/': /** Search **/ case '?': case 'N': case 'n': save_v(); switch( search(c) ) { case GOOD: /*** force a total refresh ***/ window[0] = '\0'; goto newhist; case BAD: /*** no match ***/ bell; default: if( u_column == INVALID ) del_line(BAD); else restore_v(); break; } break; case 'j': /** get next command **/ case '+': /** get next command **/ curhline += repeat; if( curhline > histmax ) { curhline = histmax; goto ringbell; } else if(curhline==histmax && tmp_u_column!=INVALID ) { u_space = tmp_u_space; u_column = tmp_u_column; restore_v(); u_space = real_u_space; break; } save_v(); goto newhist; case 'k': /** get previous command **/ case '-': /** get previous command **/ if( curhline == histmax ) { u_space = tmp_u_space; i = u_column; save_v(); u_space = real_u_space; tmp_u_column = u_column; u_column = i; } curhline -= repeat; if( curhline <= histmin ) { curhline = histmin + 1; goto ringbell; } save_v(); newhist: hist_copy((char*)virtual, curhline, -1); #ifdef MULTIBYTE ed_internal((char*)virtual,virtual); #endif /* MULTIBYTE */ if( (last_virt = genlen((char*)virtual) - 1) >= 0 ) cur_virt = 0; else cur_virt = INVALID; break; case 'u': /** undo the last thing done **/ restore_v(); break; case 'U': /** Undo everything **/ save_v(); if( virtual[0] == '\0' ) goto ringbell; else { gencpy(virtual, U_space); last_virt = genlen(U_space) - 1; cur_virt = 0; } break; #ifdef KSHELL case 'v': if(repeat_set==0) goto vcommand; #endif /* KSHELL */ case 'G': /** goto command repeat **/ if(repeat_set==0) repeat = histmin+1; if( repeat <= histmin || repeat > histmax ) { goto ringbell; } curhline = repeat; save_v(); if(c == 'G') goto newhist; #ifdef KSHELL vcommand: if(ed_fulledit()==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 0 ) { /*** move to right ***/ putstring(cur_phys, delta); } else { /*** move to left ***/ delta = -delta; /*** attempt to optimize cursor movement ***/ if( crallowed==NO || (delta <= ((cur_phys-first_wind)+plen)>>1) ) { while( delta-- ) putchar('\b'); } else { pr_string(Prompt); putstring(first_wind, x - first_wind); } } cur_phys = x; return; } /*{ 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 delete(nchars, mode) register int nchars; char mode; { register int i; register genchar *vp; if( cur_virt < first_virt ) { bell; return; } if( nchars > 0 ) { vp = virtual+cur_virt; o_v_char = vp[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 = vp[nchars]; vp[nchars] = 0; gencpy(yankbuf,vp); vp[nchars] = i; } /*** now delete these characters ***/ if( mode != 'y' ) { gencpy(vp,vp+nchars); last_virt -= nchars; } } return; } /*{ DEL_LINE( mode ) * * This routine will delete the line. * mode = GOOD, do a save_v() * }*/ static void del_line(mode) int mode; { if( last_virt == INVALID ) return; if( mode == GOOD ) save_v(); cur_virt = 0; first_virt = 0; delete(last_virt+1, BAD); refresh(CONTROL); cur_virt = INVALID; cur_phys = 0; findchar = INVALID; last_phys = INVALID; last_virt = INVALID; last_wind = INVALID; first_wind = 0; o_v_char = '\0'; ocur_phys = 0; ocur_virt = MAXCHAR; 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 GOOD if operation successful; else BAD. * }*/ static int delmotion(motion, mode) int motion; char mode; { register int begin; register int end; /* the following saves a register */ # define delta end if( cur_virt == INVALID ) return(BAD); if( mode != 'y' ) save_v(); begin = cur_virt; /*** fake out the motion routines by appending a blank ***/ virtual[++last_virt] = ' '; end = mvcursor(motion); virtual[last_virt--] = 0; if(end==BAD) return(BAD); 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; } delete(delta, mode); if( mode == 'y' ) cur_virt = begin; # undef delta return(GOOD); } /*{ ENDWORD( nwords, cmd ) * * This routine will move cur_virt to the end of the nth word. * }*/ static void endword(nwords, cmd) 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(nwords, cmd) register int nwords; char 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 0 ) 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(mode) register int mode; { register int c; register int tmp; addnl = TRUE; if( mode == ESC ) { /*** go directly to control mode ***/ goto escape; } for(;;) { if( (c = getchar()) == cntl('V') ) { /*** implement ^V to escape next char ***/ in_raw++; c = getchar(); in_raw = 0; append(c, mode); refresh(INPUT); continue; } if( c == usreof ) c = UEOF; else if( c == usrerase ) c = UERASE; else if( c == usrkill ) c = UKILL; switch( c ) { case ESC: /** enter control mode **/ if( mode == SEARCH ) { bell; continue; } else { escape: if( mode == REPLACE ) --cur_virt; tmp = cntlmode(); if( tmp == ENTER || tmp == BIGVI ) { #ifdef MULTIBYTE bigvi = (tmp==BIGVI); #endif /* MULTIBYTE */ return; } if( tmp == INSERT ) { mode = APPEND; continue; } mode = tmp; } break; case UERASE: /** user erase char **/ /*** treat as backspace ***/ case '\b': /** backspace **/ if( virtual[cur_virt] == '\\' ) { delete(1, BAD); append(usrerase, mode); } else { if( mode==SEARCH && cur_virt==0 ) { first_virt = 0; delete(1, BAD); return; } delete(1, BAD); } break; case cntl('W'): /** delete back word **/ if( cur_virt > first_virt && isblank(cur_virt-1) ) { delete(1, BAD); } else { tmp = cur_virt; backword(1, 'b'); delete(tmp - cur_virt + 1, BAD); } break; case UKILL: /** user kill line char **/ if( virtual[cur_virt] == '\\' ) { delete(1, BAD); append(usrkill, mode); } else { if( mode == SEARCH ) { cur_virt = 1; delmotion('$', BAD); } else if(first_virt) { tmp = cur_virt; cur_virt = first_virt; delete(tmp - cur_virt + 1, BAD); } else del_line(GOOD); } break; case UEOF: /** eof char **/ if( cur_virt != INVALID ) continue; addnl = FALSE; case '\n': /** newline or return **/ if( mode != SEARCH ) save_last(); return; default: if( mode == REPLACE ) { if( cur_virt < last_virt ) { replace(c, TRUE); continue; } delete(1, BAD); mode = APPEND; } append(c, mode); break; } refresh(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(motion) register int motion; { register int count; register int tcur_virt; register int incr = -1; register int bound = 0; static int last_find = 0; /* last find command */ 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 = repeat-1; if(tcur_virt <= last_virt) break; /* fall through */ case '$': /** End of line **/ tcur_virt = last_virt; 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 += repeat*incr; if( incr*tcur_virt > motion) tcur_virt = motion; } else { return(BAD); } break; case 'B': case 'b': /** back word **/ tcur_virt = cur_virt; backword(repeat, motion); if( cur_virt == tcur_virt ) return(BAD); return(GOOD); case 'E': case 'e': /** end of word **/ tcur_virt = cur_virt; if(tcur_virt >=0) endword(repeat, motion); if( cur_virt == tcur_virt ) return(BAD); return(GOOD); case ',': /** reverse find old char **/ case ';': /** find old char **/ switch(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(BAD); } 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 **/ last_find = motion; if((findchar=getrchar())==ESC) return(GOOD); find_b: tcur_virt = cur_virt; count = repeat; while( count-- ) { while( incr*(tcur_virt+=incr) <= bound && virtual[tcur_virt] != findchar ); if( incr*tcur_virt > bound ) { return(BAD); } } if( fold(last_find) == 'T' ) tcur_virt -= incr; break; /* new, undocumented feature */ 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(BAD); 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(BAD); break; } case 'W': case 'w': /** forward word **/ tcur_virt = cur_virt; forward(repeat, motion); if( tcur_virt == cur_virt ) return(BAD); return(GOOD); default: return(BAD); } cur_virt = tcur_virt; return(GOOD); } /* * print a string */ static void pr_string(s) register char *s; { /*** copy string s ***/ register char *ptr = editb.e_outptr; while(*s) *ptr++ = *s++; editb.e_outptr = ptr; return; } /*{ PUTSTRING( column, nchars ) * * Put nchars starting at column of physical into the workspace * to be printed. * }*/ static void putstring(col, nchars) 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(mode) int mode; { register int p; register int regb; register int first_w = first_wind; int p_differ; int new_lw; int ncur_phys; int opflag; /* search optimize flag */ # define w regb # define v regb /*** 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( v=(first_w + w_size) ) { cursor(first_w); first_w = ncur_phys - (w_size>>1); if( first_w < 0 ) first_w = 0; first_wind = cur_phys = first_w; } /*** attempt to optimize search somewhat to find ***/ /*** out where physical and window images differ ***/ if( first_w==ofirst_wind && ncur_phys>=ocur_phys && opflag==TRUE ) { p = ocur_phys; w = p - first_w; } else { p = first_w; w = 0; } for(; (p<=last_phys && w<=last_wind); ++p, ++w) { if( window[w] != physical[p] ) break; } p_differ = p; if( (p>last_phys || p>=first_w+w_size) && w>last_wind && cur_virt==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 <= last_wind ) window[w++] = ' '; last_wind = --w; p = p_differ; /*** move cursor to start of difference ***/ cursor(p); /*** and output difference ***/ w = p - first_w; while( w <= last_wind ) putchar(window[w++]); cur_phys = w + first_w; last_wind = --new_lw; if( last_phys >= w_size ) { if( first_w == 0 ) long_char = '>'; else if( last_phys < (first_w+w_size) ) long_char = '<'; else long_char = '*'; } else long_char = ' '; if( long_line != long_char ) { /*** indicate lines longer than window ***/ while( w++ < w_size ) { putchar(' '); ++cur_phys; } putchar(long_char); ++cur_phys; long_line = long_char; } ocur_phys = ncur_phys; ocur_virt = cur_virt; ofirst_wind = first_w; if( mode==INPUT && cur_virt>INVALID ) ++ncur_phys; cursor(ncur_phys); ed_flush(); return; } /*{ REPLACE( char, increment ) * * Replace the cur_virt character with char. This routine attempts * to avoid using refresh(). * * increment = TRUE, increment cur_virt after replacement. * = FALSE, leave cur_virt where it is. * }*/ static void replace(c, increment) register int c; register int increment; { register int cur_window; if( cur_virt == INVALID ) { /*** can't replace invalid cursor ***/ bell; return; } cur_window = cur_phys - first_wind; if( ocur_virt == INVALID || !is_print(c) || !is_print(virtual[cur_virt]) || !is_print(o_v_char) #ifdef MULTIBYTE || icharset(c) || out_csize(icharset(o_v_char))>1 #endif /* MULTIBYTE */ || (increment==TRUE && (cur_window==w_size-1) || !is_print(virtual[cur_virt+1])) ) { /*** must use standard refresh routine ***/ delete(1, BAD); append(c, APPEND); if( increment==TRUE && cur_virt 0 ) { /*** save last thing user typed ***/ genncpy(lastline, (&virtual[first_virt]), i); lastline[i] = '\0'; } return; } /*{ SAVE_V() * * This routine will save the contents of virtual in u_space. * }*/ static void save_v() { if(!inmacro) { virtual[last_virt + 1] = '\0'; gencpy(u_space, virtual); 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 * }*/ static int search(mode) register char mode; { register int new_direction; register int oldcurhline; static int direction = -1; histloc location; if( mode == '/' || mode == '?') { /*** new search expression ***/ del_line(BAD); append(mode, APPEND); refresh(INPUT); first_virt = 1; getline(SEARCH); first_virt = 0; virtual[last_virt + 1] = '\0'; /*** make null terminated ***/ 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(BAD); strcpy( ((char*)virtual)+1, lsearch); #ifdef MULTIBYTE *((char*)virtual) = '/'; ed_internal((char*)virtual,virtual); #endif /* MULTIBYTE */ } if( mode == 'N' ) new_direction = -direction; else new_direction = direction; if( new_direction==1 && curhline >= histmax ) curhline = histmin + 1; /*** now search ***/ oldcurhline = curhline; #ifdef MULTIBYTE ed_external(virtual,(char*)virtual); #endif /* MULTIBYTE */ location = hist_find(((char*)virtual)+1, curhline, 1, new_direction); strncpy(lsearch, ((char*)virtual)+1, SEARCHSIZE); if( (curhline=location.his_command) >=0 ) { 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 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(first_wind==ofirst_wind && cur_virt>ocur_virt && ocur_virt!=INVALID) { /*** try to optimize search a little ***/ p = ocur_phys + 1; #ifdef MULTIBYTE while(physical[p]==MARKER) p++; #endif /* MULTIBYTE */ v = ocur_virt + 1; } else { p = 0; v = 0; } for(; v <= last_virt; ++p, ++v) { #ifdef MULTIBYTE int d; c = virtual[v]; if(d = icharset(c)) { if( v != cur_virt ) p += (out_csize(d)-1); } else #else c = virtual[v]; #endif /* MULTIBYTE */ if( !isprint(c) ) { 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 < first_wind || new_phys >= first_wind + w_size ) { /*** asked to move outside of window ***/ window[0] = '\0'; refresh(CONTROL); return; } cursor(new_phys); ed_flush(); ocur_phys = cur_phys; ocur_virt = cur_virt; o_v_char = virtual[ocur_virt]; return; } /*{ TEXTMOD( command, mode ) * * Modify text operations. * * mode != 0, repeat previous operation * }*/ static int textmod(c, mode) register int c; int mode; { register int i; register genchar *p = lastline; register int trepeat = repeat; static int lastmacro; genchar *savep; if(mode && (fold(lastmotion)=='F' || fold(lastmotion)=='T')) lastmotion = ';'; if( fold(c) == 'P' ) { /*** change p from lastline to yankbuf ***/ p = yankbuf; } addin: switch( c ) { /***** Input commands *****/ #ifdef KSHELL 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(); i = last_virt; ++last_virt; virtual[last_virt] = 0; if( ed_expand((char*)virtual, &cur_virt, &last_virt, c) ) { last_virt = i; bell; } else if(c == '=') { last_virt = i; nonewline++; ed_ungetchar(cntl('L')); return(GOOD); } else { --cur_virt; --last_virt; ocur_virt = MAXCHAR; return(APPEND); } break; case '@': /** macro expansion **/ if( mode ) c = lastmacro; else if((c=getrchar())==ESC) return(GOOD); if(!inmacro) lastmacro = c; if(ed_macro(c)) { save_v(); inmacro++; return(GOOD); } bell; return(BAD); #endif /* KSHELL */ case '_': /** append last argument of prev command **/ save_v(); { char tmpbuf[MAXLINE]; genchar gtmpbuf[MAXLINE]; if(repeat_set==0) repeat = -1; p = (genchar*)hist_word(tmpbuf,repeat); #ifndef KSHELL if(p==0) { bell; break; } #endif /* KSHELL */ #ifdef MULTIBYTE ed_internal((char*)p,gtmpbuf); p = gtmpbuf; #endif /* MULTIBYTE */ i = ' '; do { append(i,APPEND); } while(i = *p++); return(APPEND); } case 'A': /** append to end of line **/ cur_virt = last_virt; sync_cursor(); case 'a': /** append **/ if( fold(mode) == 'A' ) { c = 'p'; goto addin; } save_v(); if( cur_virt != INVALID ) { first_virt = cur_virt + 1; cursor(cur_phys + 1); ed_flush(); } return(APPEND); case 'I': /** insert at beginning of line **/ cur_virt = first_virt; sync_cursor(); case 'i': /** insert **/ if( fold(mode) == 'I' ) { c = 'P'; goto addin; } save_v(); if( cur_virt != INVALID ) { 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 = lastmotion; else c = getcount(getchar()); chgeol: lastmotion = c; if( c == 'c' ) { del_line(GOOD); return(APPEND); } if( delmotion(c, 'c') == BAD ) 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 = lastmotion; else c = getcount(getchar()); deleol: lastmotion = c; if( c == 'd' ) { del_line(GOOD); break; } if( delmotion(c, 'd') == BAD ) 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)) ocur_virt = INVALID; --cur_virt; } case 'p': /** print **/ if( p[0] == '\0' ) return(BAD); if( mode != 's' && mode != 'c' ) { save_v(); if( c == 'P' ) { /*** fix stored cur_virt ***/ ++u_column; } } if( mode == 'R' ) mode = REPLACE; else mode = APPEND; savep = p; for(i=0; i0 && i!=cur_virt) { i = cur_virt; c = virtual[cur_virt]; #ifdef MULTIBYTE if((c&~STRIP)==0) #endif /* MULTIBYTE */ if( isupper(c) ) c = tolower(c); else if( islower(c) ) c = toupper(c); replace(c, TRUE); } return(GOOD); } else return(BAD); default: return(BAD); } refresh(CONTROL); return(GOOD); } #ifdef INT16 /* making these functions reduces the size of the text region */ int isalph(c) register int c; { register int v = virtual[c]; return(isalnum(v)); } int isblank(c) register int c; { register int v = virtual[c]; return(isspace(v)); } int ismetach(c) register int c; { register int v = virtual[c]; return(ismeta(v)); } #endif /* INT16 */ #ifdef MULTIBYTE int isalph(c) register int c; { register int v = virtual[c]; return((v&~STRIP) || isalnum(v)); } int isblank(c) register int c; { register int v = virtual[c]; return((v&~STRIP)==0 && isspace(v)); } int ismetach(c) register int c; { register int v = virtual[c]; return((v&~STRIP)==0 && ismeta(v)); } #endif /* MULTIBYTE */ /* * get a character, after ^V processing */ static int getrchar() { register int c; if((c=getchar())== cntl('V')) { in_raw++; c = getchar(); in_raw = 0; } return(c); }