/* * v_cfirst -- [count]_ * Move to the first non-blank character in a line. * * PUBLIC: int v_cfirst __P((SCR *, VICMD *)); */ int v_cfirst(SCR *sp, VICMD *vp) { db_recno_t cnt, lno; /* * !!! * If the _ is a motion component, it makes the command a line motion * e.g. "d_" deletes the line. It also means that the cursor doesn't * move. * * The _ command never failed in the first column. */ if (ISMOTION(vp)) F_SET(vp, VM_LMODE); /* * !!! * Historically a specified count makes _ move down count - 1 * rows, so, "3_" is the same as "2j_". */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; if (cnt != 1) { --vp->count; return (v_down(sp, vp)); } /* * Move to the first non-blank. * * Can't just use RCM_SET_FNB, in case _ is used as the motion * component of another command. */ vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); /* * !!! * The _ command has to fail if the file is empty and we're doing * a delete. If deleting line 1, and 0 is the first nonblank, * make the check. */ if (vp->m_stop.lno == 1 && vp->m_stop.cno == 0 && ISCMD(vp->rkp, 'd')) { if (db_last(sp, &lno)) return (1); if (lno == 0) { v_sol(sp); return (1); } } /* * Delete and non-motion commands move to the end of the range, * yank stays at the start. Ignore others. */ vp->m_final = ISMOTION(vp) && ISCMD(vp->rkp, 'y') ? vp->m_start : vp->m_stop; return (0); }
/***************************************************************** * readCliValueList: *****************************************************************/ static int readCliValueList( /* Return: status */ char **pp /* <m> current location in cmd line */ ,struct cduValue *val /* <m> the value struct */ ) { int sts; char *p; if (!*pp || **pp != '(') return(cli_error(CLI_STS_ERROR,"called under false pretenses")); p = *pp + 1; /* start of multi-valued token */ str_free1_dx(&val->val_dsc); for ( ; ; ) { sts = cliToken(&p,&dsc_token,val->valL_flags); if (~sts & 1) break; str_append(&val->val_dsc,&dsc_token); if (p = nonblank(p)) { if (*p == ')') break; else if (*p == ',') p++, str_append(&val->val_dsc,"\01"); else if (*p == '+') p++, str_append(&val->val_dsc,"\02"); } } if (!p || (*p != ')')) return(cli_error(CLI_STS_ERROR,"Error in value list")); /* return */ *pp = cliNonblank(p+1); return(1); /*---------------------> return */ }
/* * v_dollar -- [count]$ * Move to the last column. * * PUBLIC: int v_dollar __P((SCR *, VICMD *)); */ int v_dollar(SCR *sp, VICMD *vp) { size_t len; int isempty; /* * !!! * A count moves down count - 1 rows, so, "3$" is the same as "2j$". */ if ((F_ISSET(vp, VC_C1SET) ? vp->count : 1) != 1) { /* * !!! * Historically, if the $ is a motion, and deleting from * at or before the first non-blank of the line, it's a * line motion, and the line motion flag is set. */ vp->m_stop.cno = 0; if (nonblank(sp, vp->m_start.lno, &vp->m_stop.cno)) return (1); if (ISMOTION(vp) && vp->m_start.cno <= vp->m_stop.cno) F_SET(vp, VM_LMODE); --vp->count; if (v_down(sp, vp)) return (1); } /* * !!! * Historically, it was illegal to use $ as a motion command on * an empty line. Unfortunately, even though C was historically * aliased to c$, it (and not c$) was special cased to work on * empty lines. Since we alias C to c$ too, we have a problem. * To fix it, we let c$ go through, on the assumption that it's * not a problem for it to work. */ if (db_eget(sp, vp->m_stop.lno, NULL, &len, &isempty)) { if (!isempty) return (1); len = 0; } if (len == 0) { if (ISMOTION(vp) && !ISCMD(vp->rkp, 'c')) { v_eol(sp, NULL); return (1); } return (0); } /* * Non-motion commands move to the end of the range. Delete * and yank stay at the start. Ignore others. */ vp->m_stop.cno = len ? len - 1 : 0; vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; return (0); }
/**************************************************************** * cliNonblank: * Return next non-blank char. ****************************************************************/ char *cliNonblank( /* Return: addr in cmd line */ char *p /* <r> current position in cmd line */ ) { p = nonblank(p); cliNonblank_save = p; return(p); /*----------------------------> return */ }
/* * v_iI -- [count]I * Insert text at the first nonblank. * * PUBLIC: int v_iI __P((SCR *, VICMD *)); */ int v_iI(SCR *sp, VICMD *vp) { sp->cno = 0; if (nonblank(sp, vp->m_start.lno, &sp->cno)) return (1); LOG_CORRECT; return (v_ii(sp, vp)); }
/* * v_first -- ^ * Move to the first non-blank character in this line. * * PUBLIC: int v_first __P((SCR *, VICMD *)); */ int v_first(SCR *sp, VICMD *vp) { /* * !!! * Yielding to none in our quest for compatibility with every * historical blemish of vi, no matter how strange it might be, * we permit the user to enter a count and then ignore it. */ /* * Move to the first non-blank. * * Can't just use RCM_SET_FNB, in case ^ is used as the motion * component of another command. */ vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); /* * !!! * The ^ command succeeded if used as a command when the cursor was * on the first non-blank in the line, but failed if used as a motion * component in the same situation. */ if (ISMOTION(vp) && vp->m_start.cno == vp->m_stop.cno) { v_sol(sp); return (1); } /* * If moving right, non-motion commands move to the end of the range. * Delete and yank stay at the start. Motion commands adjust the * ending point to the character before the current ending charcter. * * If moving left, all commands move to the end of the range. Motion * commands adjust the starting point to the character before the * current starting character. */ if (vp->m_start.cno < vp->m_stop.cno) if (ISMOTION(vp)) { --vp->m_stop.cno; vp->m_final = vp->m_start; } else vp->m_final = vp->m_stop; else { if (ISMOTION(vp)) --vp->m_start.cno; vp->m_final = vp->m_stop; } return (0); }
/**************************************************************** * mdsdcl_define: * Define an mds macro ... ****************************************************************/ int mdsdcl_define() /* Return: status */ { int i,k; int sts; char name[32]; char *p; struct _mdsdcl_macro *m; static DYNAMIC_DESCRIPTOR(dsc_name); static DYNAMIC_DESCRIPTOR(dsc_cmd); static char fmt1[] = "Macro '%s' is open -- cannot be edited"; sts = cli_get_value("P1",&dsc_name); if (~sts & 1) return(sts); k = dsc_name.dscW_length; if (k >= sizeof(name)) k = sizeof(name) - 1; l2un(name,dsc_name.dscA_pointer,k); name[k] = '\0'; /*======================================================= * Get addr of macro struct. Delete any existing lines ... *======================================================*/ m = get_macro(name); if (m->isOpen) return(MdsMsg(MDSDCL_STS_ERROR,fmt1,m->name)); for (i=0 ; i<m->numLines ; i++) free(m->lines[i]); m->numLines = 0; /*====================================================== * Read new macro from input ... *=====================================================*/ for ( ; (sts = mdsdcl_get_input_nosymbols("DEFMAC> ",&dsc_cmd)) & 1 ; ) { if ((dsc_cmd.dscW_length > 0) && dsc_cmd.dscA_pointer) p = nonblank(dsc_cmd.dscA_pointer); else p = 0; if (!p || end_macro(p)) { break; } if (m->numLines >= m->maxLines) extend_macro(m); m->lines[m->numLines++] = STRING_ALLOC(dsc_cmd.dscA_pointer); } return(1); }
/**************************************************************** * displayCmdline: ****************************************************************/ static void displayCmdline( char *cmdline /* <r> the command line */ ) { int i; struct _mdsdcl_ctrl *ctrl = &MdsdclGetThreadStatic()->ctrl; if (!nonblank(cmdline)) return; for (i=0 ; i<ctrl->depth ; i++) printf("=="); printf("> %s\n\r",cmdline); return; }
/*************************************************************** * end_macro: * Return TRUE if line consists of words "END MACRO" ***************************************************************/ static int end_macro( /* Return: TRUE if "END MACRO" */ char originalLine[] /* <r> line to check */ ) { char *p; char line[40]; p = nonblank(originalLine); if (p) { int len = (strlen(p) > (sizeof(line)-1)) ? sizeof(line) - 1 : strlen(p); l2un(line,p,len); line[len]=0; strntrim(line,0,len); return(!strcmp(line,"END MACRO") ? 1 : 0); } else return(0); }
/* * vi -- * Main vi command loop. * * PUBLIC: int vi __P((SCR **)); */ int vi(SCR **spp) { GS *gp; MARK abs; SCR *next, *sp; VICMD cmd = { 0 }, *vp; VI_PRIVATE *vip; int comcount, mapped, rval; /* Get the first screen. */ sp = *spp; gp = sp->gp; /* Point to the command structure. */ vp = &cmd; /* Reset strange attraction. */ F_SET(vp, VM_RCM_SET); /* Initialize the vi screen. */ if (v_init(sp)) return (1); /* Set the focus. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); for (vip = VIP(sp), rval = 0;;) { /* Resolve messages. */ if (!MAPPED_KEYS_WAITING(sp) && vs_resolve(sp, NULL, 0)) goto ret; /* * If not skipping a refresh, return to command mode and * refresh the screen. */ if (F_ISSET(vip, VIP_S_REFRESH)) F_CLR(vip, VIP_S_REFRESH); else { sp->showmode = SM_COMMAND; if (vs_refresh(sp, 0)) goto ret; } /* Set the new favorite position. */ if (F_ISSET(vp, VM_RCM_SET | VM_RCM_SETFNB | VM_RCM_SETNNB)) { F_CLR(vip, VIP_RCM_LAST); (void)vs_column(sp, &sp->rcm); } /* * If not currently in a map, log the cursor position, * and set a flag so that this command can become the * DOT command. */ if (MAPPED_KEYS_WAITING(sp)) mapped = 1; else { if (log_cursor(sp)) goto err; mapped = 0; } /* * There may be an ex command waiting, and we returned here * only because we exited a screen or file. In this case, * we simply go back into the ex parser. */ if (EXCMD_RUNNING(gp)) { vp->kp = &vikeys[':']; goto ex_continue; } /* Refresh the command structure. */ memset(vp, 0, sizeof(VICMD)); /* * We get a command, which may or may not have an associated * motion. If it does, we get it too, calling its underlying * function to get the resulting mark. We then call the * command setting the cursor to the resulting mark. * * !!! * Vi historically flushed mapped characters on error, but * entering extra <escape> characters at the beginning of * a map wasn't considered an error -- in fact, users would * put leading <escape> characters in maps to clean up vi * state before the map was interpreted. Beauty! */ switch (v_cmd(sp, DOT, vp, NULL, &comcount, &mapped)) { case GC_ERR: goto err; case GC_ERR_NOFLUSH: goto gc_err_noflush; case GC_EVENT: goto gc_event; case GC_FATAL: goto ret; case GC_INTERRUPT: goto intr; case GC_OK: break; } /* Check for security setting. */ if (F_ISSET(vp->kp, V_SECURE) && O_ISSET(sp, O_SECURE)) { ex_emsg(sp, KEY_NAME(sp, vp->key), EXM_SECURE); goto err; } /* * Historical practice: if a dot command gets a new count, * any motion component goes away, i.e. "d3w2." deletes a * total of 5 words. */ if (F_ISSET(vp, VC_ISDOT) && comcount) DOTMOTION->count = 1; /* Copy the key flags into the local structure. */ F_SET(vp, vp->kp->flags); /* Prepare to set the previous context. */ if (F_ISSET(vp, V_ABS | V_ABS_C | V_ABS_L)) { abs.lno = sp->lno; abs.cno = sp->cno; } /* * Set the three cursor locations to the current cursor. The * underlying routines don't bother if the cursor doesn't move. * This also handles line commands (e.g. Y) defaulting to the * current line. */ vp->m_start.lno = vp->m_stop.lno = vp->m_final.lno = sp->lno; vp->m_start.cno = vp->m_stop.cno = vp->m_final.cno = sp->cno; /* * Do any required motion; v_motion sets the from MARK and the * line mode flag, as well as the VM_RCM flags. */ if (F_ISSET(vp, V_MOTION) && v_motion(sp, DOTMOTION, vp, &mapped)) { if (INTERRUPTED(sp)) goto intr; goto err; } /* * If a count is set and the command is line oriented, set the * to MARK here relative to the cursor/from MARK. This is for * commands that take both counts and motions, i.e. "4yy" and * "y%". As there's no way the command can know which the user * did, we have to do it here. (There are commands that are * line oriented and that take counts ("#G", "#H"), for which * this calculation is either completely meaningless or wrong. * Each command must validate the value for itself. */ if (F_ISSET(vp, VC_C1SET) && F_ISSET(vp, VM_LMODE)) vp->m_stop.lno += vp->count - 1; /* Increment the command count. */ ++sp->ccnt; #if defined(DEBUG) && defined(COMLOG) v_comlog(sp, vp); #endif /* Call the function. */ ex_continue: if (vp->kp->func(sp, vp)) goto err; gc_event: #ifdef DEBUG /* Make sure no function left the temporary space locked. */ if (F_ISSET(gp, G_TMP_INUSE)) { F_CLR(gp, G_TMP_INUSE); msgq(sp, M_ERR, "232|vi: temporary buffer not released"); } #endif /* * If we're exiting this screen, move to the next one, or, if * there aren't any more, return to the main editor loop. The * ordering is careful, don't discard the contents of sp until * the end. */ if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (file_end(sp, NULL, F_ISSET(sp, SC_EXIT_FORCE))) goto ret; if (vs_discard(sp, &next)) goto ret; if (next == NULL && vs_swap(sp, &next, NULL)) goto ret; *spp = next; if (screen_end(sp)) goto ret; if (next == NULL) break; /* Switch screens, change focus. */ sp = next; vip = VIP(sp); (void)sp->gp->scr_rename(sp, sp->frp->name, 1); /* Don't trust the cursor. */ F_SET(vip, VIP_CUR_INVALID); continue; } /* * Set the dot command structure. * * !!! * Historically, commands which used mapped keys did not * set the dot command, with the exception of the text * input commands. */ if (F_ISSET(vp, V_DOT) && !mapped) { *DOT = cmd; F_SET(DOT, VC_ISDOT); /* * If a count was supplied for both the command and * its motion, the count was used only for the motion. * Turn the count back on for the dot structure. */ if (F_ISSET(vp, VC_C1RESET)) F_SET(DOT, VC_C1SET); /* VM flags aren't retained. */ F_CLR(DOT, VM_COMMASK | VM_RCM_MASK); } /* * Some vi row movements are "attracted" to the last position * set, i.e. the VM_RCM commands are moths to the VM_RCM_SET * commands' candle. If the movement is to the EOL the vi * command handles it. If it's to the beginning, we handle it * here. * * Note, some commands (e.g. _, ^) don't set the VM_RCM_SETFNB * flag, but do the work themselves. The reason is that they * have to modify the column in case they're being used as a * motion component. Other similar commands (e.g. +, -) don't * have to modify the column because they are always line mode * operations when used as motions, so the column number isn't * of any interest. * * Does this totally violate the screen and editor layering? * You betcha. As they say, if you think you understand it, * you don't. */ switch (F_ISSET(vp, VM_RCM_MASK)) { case 0: case VM_RCM_SET: break; case VM_RCM: vp->m_final.cno = vs_rcm(sp, vp->m_final.lno, F_ISSET(vip, VIP_RCM_LAST)); break; case VM_RCM_SETLAST: F_SET(vip, VIP_RCM_LAST); break; case VM_RCM_SETFNB: vp->m_final.cno = 0; /* FALLTHROUGH */ case VM_RCM_SETNNB: if (nonblank(sp, vp->m_final.lno, &vp->m_final.cno)) goto err; break; default: abort(); } /* Update the cursor. */ sp->lno = vp->m_final.lno; sp->cno = vp->m_final.cno; /* * Set the absolute mark -- set even if a tags or similar * command, since the tag may be moving to the same file. */ if ((F_ISSET(vp, V_ABS) || (F_ISSET(vp, V_ABS_L) && sp->lno != abs.lno) || (F_ISSET(vp, V_ABS_C) && (sp->lno != abs.lno || sp->cno != abs.cno))) && mark_set(sp, ABSMARK1, &abs, 1)) goto err; if (0) { err: if (v_event_flush(sp, CH_MAPPED)) msgq(sp, M_BERR, "110|Vi command failed: mapped keys discarded"); } /* * Check and clear interrupts. There's an obvious race, but * it's not worth fixing. */ gc_err_noflush: if (INTERRUPTED(sp)) { intr: CLR_INTERRUPT(sp); if (v_event_flush(sp, CH_MAPPED)) msgq(sp, M_ERR, "231|Interrupted: mapped keys discarded"); else msgq(sp, M_ERR, "236|Interrupted"); } /* If the last command switched screens, update. */ if (F_ISSET(sp, SC_SSWITCH)) { F_CLR(sp, SC_SSWITCH); /* * If the current screen is still displayed, it will * need a new status line. */ F_SET(sp, SC_STATUS); /* Switch screens, change focus. */ sp = sp->nextdisp; vip = VIP(sp); (void)sp->gp->scr_rename(sp, sp->frp->name, 1); /* Don't trust the cursor. */ F_SET(vip, VIP_CUR_INVALID); /* Refresh so we can display messages. */ if (vs_refresh(sp, 1)) return (1); } /* If the last command switched files, change focus. */ if (F_ISSET(sp, SC_FSWITCH)) { F_CLR(sp, SC_FSWITCH); (void)sp->gp->scr_rename(sp, sp->frp->name, 1); } /* If leaving vi, return to the main editor loop. */ if (F_ISSET(gp, G_SRESTART) || F_ISSET(sp, SC_EX)) { *spp = sp; v_dtoh(sp); gp->scr_discard(sp, NULL); break; } } if (0) ret: rval = 1; return (rval); }
/*************************************************************** * fgets_with_edit: ***************************************************************/ char *fgets_with_edit( /* Returns: addr of usrline, or NULL */ char usrline[] /* <w> user's input buffer */ ,int maxlen /* <r> length of input buffer */ ,FILE *fp /* <r> file pointer */ ,char *prompt /* <r:opt> prompt string */ ) { int i,k; int fd; int sts; char *p; static char clrEol[12]; static char cursorRight[12]; #ifdef vms static int pasteboard_id; static int keyboard_id; static int key_table_id; int flags; short wlen; struct descriptor *dscPtr; static DYNAMIC_DESCRIPTOR(dsc_cmdline); static struct descriptor dsc_prompt = {0,DSC_K_DTYPE_T,DSC_K_CLASS_S,0}; #else int idx; int ierr; int kchar; int num; char c; char line[256]; char displayLine[256]; #if !defined(_WIN32) struct termios tt,ttsave; #endif #endif fd = fileno(fp); if (!isatty(fd)) return(fgets(usrline,maxlen,fp)); /*======================================================= * Note: vt100 "terminfo" seems to have extra chars in * its "cursor_right" escape sequence. We'll use * local string "cursorRight" instead ... * Likewise for "clr_eol" escape sequence. *======================================================*/ if (!cursorRight[0]) { /* first time only ... */ irecall = HISTORY_NOT_ACTIVE; #ifdef vms flags = 1; smg$create_pasteboard(&pasteboard_id,0,0,0,&flags); /* */ smg$create_virtual_keyboard(&keyboard_id); /* smg$create_key_table(&key_table_id); /* */ cursorRight[0] = 1; /* indicate no longer first time */ #else sts = setupterm(0,1,&ierr); if (sts || ierr!=1) { printf("after setupterm: sts=%d ierr=%d\n"); usrline[0] = '\0'; return(0); } if (!strncmp(cursor_right,"\033[C",3)) strncpy(cursorRight,cursor_right,3); else { if (strlen(cursor_right) >= sizeof(cursorRight)) { fprintf(stderr,"fgets_with_edit: cursorRight too short\n"); exit(0); } strcpy(cursorRight,cursor_right); } if (!strncmp(clr_eol,"\033[K",3)) strncpy(clrEol,clr_eol,3); else { if (strlen(clr_eol) >= sizeof(clrEol)) { fprintf(stderr,"fgets_with_edit: clrEol[] is too short\n"); exit(0); } strcpy(clrEol,clr_eol); } #endif } #ifdef vms /*======================================================= * VMS only: read using smg routine ... *======================================================*/ dscPtr = prompt ? &dsc_prompt : 0; if (dscPtr) { dsc_prompt.dscA_pointer = prompt ? prompt : "Command> "; dsc_prompt.dscW_length = strlen(dsc_prompt.dscA_pointer); } sts = smg$read_composed_line(&keyboard_id,0, &dsc_cmdline,dscPtr,&wlen); if (~sts & 1) { dasmsg(sts,"Error from smg$read_composed_string"); return(0); } p = dsc_cmdline.dscA_pointer ? dsc_cmdline.dscA_pointer : ""; k = (wlen<maxlen) ? wlen : maxlen; strncpy(usrline,p,k); if (wlen < maxlen) usrline[wlen] = '\0'; #else /*======================================================= * Save tty settings; reset tty for command-line editing. *======================================================*/ #if !defined(_WIN32) sts = tcgetattr(fd,&tt); memcpy(&ttsave,&tt,sizeof(ttsave)); /* save copy of tt */ tt.c_lflag &= ~ICANON; tt.c_lflag &= ~ECHO; tt.c_cc[VTIME] = 0; tt.c_cc[VMIN] = 1; sts = tcsetattr(fd,TCSANOW,&tt); if (sts) { perror("Error from tcseattr"); usrline[0] = '\0'; return(0); } #endif /*======================================================= * Edit input line from tty ... *======================================================*/ line[0] = '\0'; if (prompt) printf("%s",prompt); fflush(stdout); for (p=line ; ; ) { k = read(fd,&c,1); if (k<=0 || c=='\n') break; if (isprint(c) || isspace(c)) { write(1,&c,1); /* Write c at current position ... */ if (*p) { /* if not at end of line */ if (insert_mode) { k = strlen(p); p[k+1] = '\0'; for ( ; k ; k--) p[k] = p[k-1]; k = sprintf(displayLine,"%s%s%s", save_cursor,p+1,restore_cursor); i = write(1,displayLine,k); } } else *(p+1) = '\0'; /* else, set new EOL */ *p++ = c; continue; } if (!iscntrl(c)) continue; if (c == DEL) { if (p <= line) continue; for (i=0,p-- ; p[i]=p[i+1] ; i++) ; k = sprintf(displayLine,"%s%s%s %s", cursor_left,save_cursor,(i?p:""),restore_cursor); write(1,displayLine,k); continue; } switch(c) { default: sprintf(displayLine," ^%c=0x%02X",c+'@',c); break; case ESC: kchar = decode_escape_sequence(fd); if (kchar == KEY_LEFT) { if (p > line) { p--; write(1,cursor_left,strlen(cursor_left)); } } else if (kchar == KEY_RIGHT) { if (*p) { p++; k = strlen(cursorRight); write(1,cursorRight,k); } } else if (kchar==KEY_UP || kchar==KEY_DOWN) { p = start_of_line(line,p); write(1,clrEol,strlen(clrEol)); strcpy(line,get_history_line(kchar)); k = strlen(line); write(1,line,k); p = line + k; } break; case CTRL_E: /* Jump to end of line ... */ num = strlen(p); if (num) { k = 0; for (i=0 ; i<num ; i++) k += sprintf(displayLine+k,cursorRight); write(1,displayLine,k); p += num; } break; case CTRL_H: /* Jump to beginning of line ... */ p = start_of_line(line,p); break; case CTRL_U: /* Delete to beginning of line ... */ num = p - line; /* num chars preceding p */ if (num) { for (i=0,k=0 ; i<num ; i++) k += sprintf(displayLine+k,cursor_left); k += sprintf(displayLine+k,"%s%s",p,clrEol); write(1,displayLine,k); for (i=0 ; line[i]=p[i] ; i++) ; /* shift chars within line[] */ p = start_of_line(line,line+i); } break; } } if (!icnt && (k=strlen(line))) history[icnt++] = strcpy(malloc(k+1),line); else { idx = (icnt-1) % MAX_HISTORY; if (nonblank(line) && strcmp(line,history[idx])) { idx = icnt++ % MAX_HISTORY; if (history[idx]) free(history[idx]); history[idx] = strcpy(malloc(strlen(line)+1),line); } } irecall = HISTORY_NOT_ACTIVE; #if !defined(_WIN32) sts = tcsetattr(fd,TCSANOW,&ttsave); if (sts) perror("Error from tcseattr"); #endif strncpy(usrline,line,maxlen); printf("\n"); #endif return(usrline); }
/* * v_sectionf -- [count]]] * Move forward count sections/functions. * * !!! * Using ]] as a motion command was a bit special, historically. It could * match } as well as the usual { and section values. If it matched a { or * a section, it did NOT include the matched line. If it matched a }, it * did include the line. No clue why. * * PUBLIC: int v_sectionf(SCR *, VICMD *); */ int v_sectionf(SCR *sp, VICMD *vp) { recno_t cnt, lno; size_t len; CHAR_T *p; char *list, *lp; /* Get the macro list. */ if ((list = O_STR(sp, O_SECTIONS)) == NULL) return (1); /* * !!! * If the starting cursor position is at or before any non-blank * characters in the line, i.e. the movement is cutting all of the * line's text, the buffer is in line mode. It's a lot easier to * check here, because we know that the end is going to be the start * or end of a line. */ if (ISMOTION(vp)) if (vp->m_start.cno == 0) F_SET(vp, VM_LMODE); else { vp->m_stop = vp->m_start; vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); if (vp->m_start.cno <= vp->m_stop.cno) F_SET(vp, VM_LMODE); } cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; for (lno = vp->m_start.lno; !db_get(sp, ++lno, 0, &p, &len);) { if (len == 0) continue; if (p[0] == '{' || (ISMOTION(vp) && p[0] == '}')) { if (!--cnt) { if (p[0] == '{') goto adjust1; goto adjust2; } continue; } /* * !!! * Historic documentation (USD:15-11, 4.2) said that formfeed * characters (^L) in the first column delimited sections. * The historic code mentions formfeed characters, but never * implements them. Seems reasonable, do it. */ if (p[0] == '\014') { if (!--cnt) goto adjust1; continue; } if (p[0] != '.' || len < 2) continue; for (lp = list; *lp != '\0'; lp += 2 * sizeof(*lp)) if (lp[0] == p[1] && ((lp[1] == ' ' && len == 2) || lp[1] == p[2]) && !--cnt) { /* * !!! * If not cutting this line, adjust to the end * of the previous one. Otherwise, position to * column 0. */ adjust1: if (ISMOTION(vp)) goto ret1; adjust2: vp->m_stop.lno = lno; vp->m_stop.cno = 0; goto ret2; } } /* If moving forward, reached EOF, check to see if we started there. */ if (vp->m_start.lno == lno - 1) { v_eof(sp, NULL); return (1); } ret1: if (db_get(sp, --lno, DBG_FATAL, NULL, &len)) return (1); vp->m_stop.lno = lno; vp->m_stop.cno = len ? len - 1 : 0; /* * Non-motion commands go to the end of the range. Delete and * yank stay at the start of the range. Ignore others. */ ret2: if (ISMOTION(vp)) { vp->m_final = vp->m_start; if (F_ISSET(vp, VM_LMODE)) vp->m_final.cno = 0; } else vp->m_final = vp->m_stop; return (0); }
/*************************************************************** * cliToken: * Get token, as described by CLI flags ***************************************************************/ int cliToken( char **pp /* <m> addr of ptr to char string */ ,struct descriptor *dsc_ret /* <w> return token here */ ,int flags /* <r> cli flags, describing data type */ ) { int k; int sts; int typeflag; char c; char *p,*p2; char util[1024]; static char legalMdsChars[] = "0123456789._-:;*?%\\"; static struct descriptor dscUtil = { sizeof(util)-1,DSC_K_DTYPE_T,DSC_K_CLASS_S,0}; typeflag = flags & 0x0FFF; p = *pp = nonblank(*pp); if (!p) return(0); dscUtil.dscA_pointer = util; /* set addr in dscUtil descrip */ if (!typeflag || typeflag==VAL_M_QUOTED_STRING || typeflag==VAL_M_USER_DEFINED_TYPE) sts = ascToken(&p,&dscUtil,0,legalMdsChars); else if (typeflag == VAL_M_FILE) sts = ascFilename(&p,&dscUtil,0); else if (typeflag == VAL_M_NUMBER) { if (isdigit(*p) || (*p=='-' && isdigit(*(p+1)))) { sts = longToken(&p,&dscUtil,0,0); } else if (*p == '%') { c = toupper(*(p+1)); p2 = p + 2; /* first char of putative number */ if (c == 'X') { for ( ; isxdigit(*p2) ; p2++) ; } else if (c == 'O') { /* octal number ... */ for ( ; isdigit(*p2) && *p2<='7' ; p2++) ; } k = p2 - p; /* number of chars scanned */ if (k == 2) sts = 0; /*..invalid number */ else { if (k > dscUtil.dscW_length) k = dscUtil.dscW_length; strncpy(dscUtil.dscA_pointer,p,k); if (k < dscUtil.dscW_length) dscUtil.dscA_pointer[k] = '\0'; l2un(dscUtil.dscA_pointer,0,k); p = nonblank(p2); sts = CLI_STS_NORMAL; } } } else if (typeflag == VAL_M_DELTATIME) { sts = deltatimeToken(&p,&dscUtil,0,0); } else if (typeflag == VAL_M_REST_OF_LINE) { k = strlen(p); if (k > dscUtil.dscW_length) k = dscUtil.dscW_length; strncpy(dscUtil.dscA_pointer,p,k); if (k < dscUtil.dscW_length) dscUtil.dscA_pointer[k] = '\0'; p = 0; sts = CLI_STS_NORMAL; } else { printf("\n*ERR* cliToken can't handle typeflag %04X\n\n", typeflag); exit(0); } /*================================================ * Return to caller ... *===============================================*/ str_copy_dx(dsc_ret,&dscUtil); if (sts & 1) *pp = nonblank(p); return(sts); }
/* * shift -- * Ex shift support. */ static int shift(SCR *sp, EXCMD *cmdp, enum which rl) { db_recno_t from, to; size_t blen, len, newcol, newidx, oldcol, oldidx, sw; int curset; CHAR_T *p; CHAR_T *bp, *tbp; NEEDFILE(sp, cmdp); if (O_VAL(sp, O_SHIFTWIDTH) == 0) { msgq(sp, M_INFO, "152|shiftwidth option set to 0"); return (0); } /* Copy the lines being shifted into the unnamed buffer. */ if (cut(sp, NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE)) return (1); /* * The historic version of vi permitted the user to string any number * of '>' or '<' characters together, resulting in an indent of the * appropriate levels. There's a special hack in ex_cmd() so that * cmdp->argv[0] points to the string of '>' or '<' characters. * * Q: What's the difference between the people adding features * to vi and the Girl Scouts? * A: The Girl Scouts have mint cookies and adult supervision. */ for (p = cmdp->argv[0]->bp, sw = 0; *p == '>' || *p == '<'; ++p) sw += O_VAL(sp, O_SHIFTWIDTH); GET_SPACE_RETW(sp, bp, blen, 256); curset = 0; for (from = cmdp->addr1.lno, to = cmdp->addr2.lno; from <= to; ++from) { if (db_get(sp, from, DBG_FATAL, &p, &len)) goto err; if (!len) { if (sp->lno == from) curset = 1; continue; } /* * Calculate the old indent amount and the number of * characters it used. */ for (oldidx = 0, oldcol = 0; oldidx < len; ++oldidx) if (p[oldidx] == ' ') ++oldcol; else if (p[oldidx] == '\t') oldcol += O_VAL(sp, O_TABSTOP) - oldcol % O_VAL(sp, O_TABSTOP); else break; /* Calculate the new indent amount. */ if (rl == RIGHT) newcol = oldcol + sw; else { newcol = oldcol < sw ? 0 : oldcol - sw; if (newcol == oldcol) { if (sp->lno == from) curset = 1; continue; } } /* Get a buffer that will hold the new line. */ ADD_SPACE_RETW(sp, bp, blen, newcol + len); /* * Build a new indent string and count the number of * characters it uses. */ tbp = bp; newidx = 0; if (!O_ISSET(sp, O_EXPANDTAB)) { for (; newcol >= O_VAL(sp, O_TABSTOP); ++newidx) { *tbp++ = '\t'; newcol -= O_VAL(sp, O_TABSTOP); } } for (; newcol > 0; --newcol, ++newidx) *tbp++ = ' '; /* Add the original line. */ MEMCPYW(tbp, p + oldidx, len - oldidx); /* Set the replacement line. */ if (db_set(sp, from, bp, (tbp + (len - oldidx)) - bp)) { err: FREE_SPACEW(sp, bp, blen); return (1); } /* * !!! * The shift command in historic vi had the usual bizarre * collection of cursor semantics. If called from vi, the * cursor was repositioned to the first non-blank character * of the lowest numbered line shifted. If called from ex, * the cursor was repositioned to the first non-blank of the * highest numbered line shifted. Here, if the cursor isn't * part of the set of lines that are moved, move it to the * first non-blank of the last line shifted. (This makes * ":3>>" in vi work reasonably.) If the cursor is part of * the shifted lines, it doesn't get moved at all. This * permits shifting of marked areas, i.e. ">'a." shifts the * marked area twice, something that couldn't be done with * historic vi. */ if (sp->lno == from) { curset = 1; if (newidx > oldidx) sp->cno += newidx - oldidx; else if (sp->cno >= oldidx - newidx) sp->cno -= oldidx - newidx; } } if (!curset) { sp->lno = to; sp->cno = 0; (void)nonblank(sp, to, &sp->cno); } FREE_SPACEW(sp, bp, blen); sp->rptlines[L_SHIFT] += cmdp->addr2.lno - cmdp->addr1.lno + 1; return (0); }
/* * ex_bang -- :[line [,line]] ! command * * Pass the rest of the line after the ! character to the program named by * the O_SHELL option. * * Historical vi did NOT do shell expansion on the arguments before passing * them, only file name expansion. This means that the O_SHELL program got * "$t" as an argument if that is what the user entered. Also, there's a * special expansion done for the bang command. Any exclamation points in * the user's argument are replaced by the last, expanded ! command. * * There's some fairly amazing slop in this routine to make the different * ways of getting here display the right things. It took a long time to * get it right (wrong?), so be careful. * * PUBLIC: int ex_bang(SCR *, EXCMD *); */ int ex_bang(SCR *sp, EXCMD *cmdp) { enum filtertype ftype; ARGS *ap; EX_PRIVATE *exp; MARK rm; recno_t lno; int rval; const char *msg; char *np; size_t nlen; ap = cmdp->argv[0]; if (ap->len == 0) { ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } /* Set the "last bang command" remembered value. */ exp = EXP(sp); free(exp->lastbcomm); if ((exp->lastbcomm = v_wstrdup(sp, ap->bp, ap->len)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* * If the command was modified by the expansion, it was historically * redisplayed. */ if (F_ISSET(cmdp, E_MODIFY) && !F_ISSET(sp, SC_EX_SILENT)) { /* * Display the command if modified. Historic ex/vi displayed * the command if it was modified due to file name and/or bang * expansion. If piping lines in vi, it would be immediately * overwritten by any error or line change reporting. */ if (F_ISSET(sp, SC_VI)) vs_update(sp, "!", ap->bp); else { (void)ex_printf(sp, "!"WS"\n", ap->bp); (void)ex_fflush(sp); } } /* * If no addresses were specified, run the command. If there's an * underlying file, it's been modified and autowrite is set, write * the file back. If the file has been modified, autowrite is not * set and the warn option is set, tell the user about the file. */ if (cmdp->addrcnt == 0) { msg = NULL; if (sp->ep != NULL && F_ISSET(sp->ep, F_MODIFIED)) if (O_ISSET(sp, O_AUTOWRITE)) { if (file_aw(sp, FS_ALL)) return (0); } else if (O_ISSET(sp, O_WARN) && !F_ISSET(sp, SC_EX_SILENT)) msg = msg_cat(sp, "303|File modified since last write.", NULL); /* If we're still in a vi screen, move out explicitly. */ INT2CHAR(sp, ap->bp, ap->len+1, np, nlen); (void)ex_exec_proc(sp, cmdp, np, msg, !F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)); } /* * If addresses were specified, pipe lines from the file through the * command. * * Historically, vi lines were replaced by both the stdout and stderr * lines of the command, but ex lines by only the stdout lines. This * makes no sense to me, so nvi makes it consistent for both, and * matches vi's historic behavior. */ else { NEEDFILE(sp, cmdp); /* Autoprint is set historically, even if the command fails. */ F_SET(cmdp, E_AUTOPRINT); /* * !!! * Historical vi permitted "!!" in an empty file. When this * happens, we arrive here with two addresses of 1,1 and a * bad attitude. The simple solution is to turn it into a * FILTER_READ operation, with the exception that stdin isn't * opened for the utility, and the cursor position isn't the * same. The only historic glitch (I think) is that we don't * put an empty line into the default cut buffer, as historic * vi did. Imagine, if you can, my disappointment. */ ftype = FILTER_BANG; if (cmdp->addr1.lno == 1 && cmdp->addr2.lno == 1) { if (db_last(sp, &lno)) return (1); if (lno == 0) { cmdp->addr1.lno = cmdp->addr2.lno = 0; ftype = FILTER_RBANG; } } rval = ex_filter(sp, cmdp, &cmdp->addr1, &cmdp->addr2, &rm, ap->bp, ftype); /* * If in vi mode, move to the first nonblank. * * !!! * Historic vi wasn't consistent in this area -- if you used * a forward motion it moved to the first nonblank, but if you * did a backward motion it didn't. And, if you followed a * backward motion with a forward motion, it wouldn't move to * the nonblank for either. Going to the nonblank generally * seems more useful and consistent, so we do it. */ sp->lno = rm.lno; if (F_ISSET(sp, SC_VI)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } else sp->cno = rm.cno; } /* Ex terminates with a bang, even if the command fails. */ if (!F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_EX_SILENT)) (void)ex_puts(sp, "!\n"); /* * XXX * The ! commands never return an error, so that autoprint always * happens in the ex parser. */ return (0); }
/* * mark -- * Mark commands. */ static int mark(SCR *sp, VICMD *vp, int getmark, enum which cmd) { dir_t dir; MARK m; size_t len; if (getmark && mark_get(sp, vp->character, &vp->m_stop, M_BERR)) return (1); /* * !!! * Historically, BQMARKS for character positions that no longer * existed acted as FQMARKS. * * FQMARKS move to the first non-blank. */ switch (cmd) { case BQMARK: if (db_get(sp, vp->m_stop.lno, DBG_FATAL, NULL, &len)) return (1); if (vp->m_stop.cno < len || (vp->m_stop.cno == len && len == 0)) break; if (ISMOTION(vp)) F_SET(vp, VM_LMODE); cmd = FQMARK; /* FALLTHROUGH */ case FQMARK: vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); break; default: abort(); } /* Non-motion commands move to the end of the range. */ if (!ISMOTION(vp)) { vp->m_final = vp->m_stop; return (0); } /* * !!! * If a motion component to a BQMARK, the cursor has to move. */ if (cmd == BQMARK && vp->m_stop.lno == vp->m_start.lno && vp->m_stop.cno == vp->m_start.cno) { v_nomove(sp); return (1); } /* * If the motion is in the reverse direction, switch the start and * stop MARK's so that it's in a forward direction. (There's no * reason for this other than to make the tests below easier. The * code in vi.c:vi() would have done the switch.) Both forward * and backward motions can happen for any kind of search command. */ if (vp->m_start.lno > vp->m_stop.lno || (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno > vp->m_stop.cno)) { m = vp->m_start; vp->m_start = vp->m_stop; vp->m_stop = m; dir = BACKWARD; } else dir = FORWARD; /* * Yank cursor motion, when associated with marks as motion commands, * historically behaved as follows: * * ` motion ' motion * Line change? Line change? * Y N Y N * -------------- --------------- * FORWARD: | NM NM | NM NM * | | * BACKWARD: | M M | M NM(1) * * where NM means the cursor didn't move, and M means the cursor * moved to the mark. * * As the cursor was usually moved for yank commands associated * with backward motions, this implementation regularizes it by * changing the NM at position (1) to be an M. This makes mark * motions match search motions, which is probably A Good Thing. * * Delete cursor motion was always to the start of the text region, * regardless. Ignore other motion commands. */ #ifdef HISTORICAL_PRACTICE if (ISCMD(vp->rkp, 'y')) { if ((cmd == BQMARK || (cmd == FQMARK && vp->m_start.lno != vp->m_stop.lno)) && (vp->m_start.lno > vp->m_stop.lno || (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno > vp->m_stop.cno))) vp->m_final = vp->m_stop; } else if (ISCMD(vp->rkp, 'd')) if (vp->m_start.lno > vp->m_stop.lno || (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno > vp->m_stop.cno)) vp->m_final = vp->m_stop; #else vp->m_final = vp->m_start; #endif /* * Forward marks are always line oriented, and it's set in the * vcmd.c table. */ if (cmd == FQMARK) return (0); /* * BQMARK'S moving backward and starting at column 0, and ones moving * forward and ending at column 0 are corrected to the last column of * the previous line. Otherwise, adjust the starting/ending point to * the character before the current one (this is safe because we know * the search had to move to succeed). * * Mark motions become line mode opertions if they start at the first * nonblank and end at column 0 of another line. */ if (vp->m_start.lno < vp->m_stop.lno && vp->m_stop.cno == 0) { if (db_get(sp, --vp->m_stop.lno, DBG_FATAL, NULL, &len)) return (1); vp->m_stop.cno = len ? len - 1 : 0; len = 0; if (nonblank(sp, vp->m_start.lno, &len)) return (1); if (vp->m_start.cno <= len) F_SET(vp, VM_LMODE); } else --vp->m_stop.cno; return (0); }
/**************************************************************** * mdsdcl_dcl_parse: *****************************************************************/ int mdsdcl_dcl_parse( /* Returns CLI_STS_xxx status */ void *command /* <r:opt> command string */ ,struct _mdsdcl_ctrl *ctrl /* <m> control structure */ ,int tabidx /* <r> cmd table idx, 1-based */ ) { int indirect_flag; int nbytes; int sts; char *p; struct _mdsdcl_io *io; struct descriptor *dsc; static char *cmd; /* addr of private cmd string */ static int maxcmd; /* current length of cmd[] */ static char doIndirect[16]; static DYNAMIC_DESCRIPTOR(dsc_filename); #ifdef vms extern mdsdcl$dcl_parse_handler(); #endif #ifdef vms if (tabidx > 1) lib$establish(lib$sig_to_ret); else lib$establish(mdsdcl$dcl_parse_handler); #endif if (!doIndirect[0]) { /* first time ... */ sprintf(doIndirect,"DO %cINDIRECT",QUALIFIER_CHARACTER); } /*------------------------------------------------------- * Copy optional "command" to cmd, checking for '@' ... *------------------------------------------------------*/ if (command && (is_cdescr(command) || is_ddescr(command))) { dsc = command; p = nonblank(dsc->dscA_pointer); } else p = nonblank(command); if (!p) { /* command string is null ... */ sts = cli_dcl_parse(0,ctrl->tbladr[tabidx-1], mdsdcl_get_input,mdsdcl_get_input,&ctrl->prompt); dsc = cli_addr_cmdline_dsc(); io = ctrl->ioLevel + ctrl->depth; str_copy_dx(&io->last_command,dsc); } else { /* else, p is start of command string ... */ nbytes = 0; if (indirect_flag = (*p == '@')) { p = nonblank(p+1); /* skip the '@' */ nbytes = sizeof(doIndirect) - 1; if (!ascFilename(&p,&dsc_filename,0)) return(MdsMsg(MDSDCL_STS_INDIRECT_ERROR, "Illegal filename: %s",p?p:"")); nbytes += strlen(dsc_filename.dscA_pointer) + 3; /* 2 quotes + blank */ } nbytes += p ? strlen(p) + 1 : 0; if (nbytes > maxcmd) { /*----- allocate enough space for cmd[] ------*/ maxcmd = (nbytes>79) ? nbytes : 79; if (cmd) free(cmd); cmd = malloc(maxcmd); if (!cmd) { fprintf(stderr,"mdsdcl_dcl_parse: Out of space!\n"); exit(0); } } if (indirect_flag) sprintf(cmd,"%s \"%s\" %s", doIndirect,dsc_filename.dscA_pointer,p?p:""); else strcpy(cmd,p?p:""); sts = cli_dcl_parse(cmd,ctrl->tbladr[tabidx-1], mdsdcl_get_input,mdsdcl_get_input,&ctrl->prompt); } return(sts); }
/* * ex_read -- :read [file] * :read [!cmd] * Read from a file or utility. * * !!! * Historical vi wouldn't undo a filter read, for no apparent reason. * * PUBLIC: int ex_read __P((SCR *, EXCMD *)); */ int ex_read(SCR *sp, EXCMD *cmdp) { enum { R_ARG, R_EXPANDARG, R_FILTER } which; struct stat sb; CHAR_T *arg; char *name; size_t nlen; EX_PRIVATE *exp; FILE *fp; FREF *frp; GS *gp; MARK rm; db_recno_t nlines; size_t arglen; int argc, rval; char *p; char *np; gp = sp->gp; /* * 0 args: read the current pathname. * 1 args: check for "read !arg". */ switch (cmdp->argc) { case 0: which = R_ARG; break; case 1: arg = cmdp->argv[0]->bp; arglen = cmdp->argv[0]->len; if (*arg == '!') { ++arg; --arglen; which = R_FILTER; /* Secure means no shell access. */ if (O_ISSET(sp, O_SECURE)) { ex_wemsg(sp, cmdp->cmd->name, EXM_SECURE_F); return (1); } } else which = R_EXPANDARG; break; default: abort(); /* NOTREACHED */ } /* Load a temporary file if no file being edited. */ if (sp->ep == NULL) { if ((frp = file_add(sp, NULL)) == NULL) return (1); if (file_init(sp, frp, NULL, 0)) return (1); } switch (which) { case R_FILTER: /* * File name and bang expand the user's argument. If * we don't get an additional argument, it's illegal. */ argc = cmdp->argc; if (argv_exp1(sp, cmdp, arg, arglen, 1)) return (1); if (argc == cmdp->argc) { ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } argc = cmdp->argc - 1; /* Set the last bang command. */ exp = EXP(sp); if (exp->lastbcomm != NULL) free(exp->lastbcomm); if ((exp->lastbcomm = v_wstrdup(sp, cmdp->argv[argc]->bp, cmdp->argv[argc]->len)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* * Vi redisplayed the user's argument if it changed, ex * always displayed a !, plus the user's argument if it * changed. */ if (F_ISSET(sp, SC_VI)) { if (F_ISSET(cmdp, E_MODIFY)) (void)vs_update(sp, "!", cmdp->argv[argc]->bp); } else { if (F_ISSET(cmdp, E_MODIFY)) (void)ex_printf(sp, "!%s\n", cmdp->argv[argc]->bp); else (void)ex_puts(sp, "!\n"); (void)ex_fflush(sp); } /* * Historically, filter reads as the first ex command didn't * wait for the user. If SC_SCR_EXWROTE not already set, set * the don't-wait flag. */ if (!F_ISSET(sp, SC_SCR_EXWROTE)) F_SET(sp, SC_EX_WAIT_NO); /* * Switch into ex canonical mode. The reason to restore the * original terminal modes for read filters is so that users * can do things like ":r! cat /dev/tty". * * !!! * We do not output an extra <newline>, so that we don't touch * the screen on a normal read. */ if (F_ISSET(sp, SC_VI)) { if (gp->scr_screen(sp, SC_EX)) { ex_wemsg(sp, cmdp->cmd->name, EXM_NOCANON_F); return (1); } /* * !!! * Historically, the read command doesn't switch to * the alternate X11 xterm screen, if doing a filter * read -- don't set SA_ALTERNATE. */ F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE); } if (ex_filter(sp, cmdp, &cmdp->addr1, NULL, &rm, cmdp->argv[argc]->bp, FILTER_READ)) return (1); /* The filter version of read set the autoprint flag. */ F_SET(cmdp, E_AUTOPRINT); /* * If in vi mode, move to the first nonblank. Might have * switched into ex mode, so saved the original SC_VI value. */ sp->lno = rm.lno; if (F_ISSET(sp, SC_VI)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } return (0); case R_ARG: name = sp->frp->name; break; case R_EXPANDARG: if (argv_exp2(sp, cmdp, arg, arglen)) return (1); /* * 0 args: impossible. * 1 args: impossible (I hope). * 2 args: read it. * >2 args: object, too many args. * * The 1 args case depends on the argv_sexp() function refusing * to return success without at least one non-blank character. */ switch (cmdp->argc) { case 0: case 1: abort(); /* NOTREACHED */ case 2: INT2CHAR(sp, cmdp->argv[1]->bp, cmdp->argv[1]->len + 1, name, nlen); /* * !!! * Historically, the read and write commands renamed * "unnamed" files, or, if the file had a name, set * the alternate file name. */ if (F_ISSET(sp->frp, FR_TMPFILE) && !F_ISSET(sp->frp, FR_EXNAMED)) { if ((p = strdup(name)) != NULL) { free(sp->frp->name); sp->frp->name = p; } /* * The file has a real name, it's no longer a * temporary, clear the temporary file flags. */ F_CLR(sp->frp, FR_TMPEXIT | FR_TMPFILE); F_SET(sp->frp, FR_NAMECHANGE | FR_EXNAMED); /* Notify the screen. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); name = sp->frp->name; } else { set_alt_name(sp, name); name = sp->alt_name; } break; default: ex_wemsg(sp, cmdp->argv[0]->bp, EXM_FILECOUNT); return (1); } break; } /* * !!! * Historically, vi did not permit reads from non-regular files, nor * did it distinguish between "read !" and "read!", so there was no * way to "force" it. We permit reading from named pipes too, since * they didn't exist when the original implementation of vi was done * and they seem a reasonable addition. */ if ((fp = fopen(name, "r")) == NULL || fstat(fileno(fp), &sb)) { msgq_str(sp, M_SYSERR, name, "%s"); return (1); } if (!S_ISFIFO(sb.st_mode) && !S_ISREG(sb.st_mode)) { (void)fclose(fp); msgq(sp, M_ERR, "145|Only regular files and named pipes may be read"); return (1); } /* Try and get a lock. */ if (file_lock(sp, NULL, NULL, fileno(fp), 0) == LOCK_UNAVAIL) msgq(sp, M_ERR, "146|%s: read lock was unavailable", name); rval = ex_readfp(sp, name, fp, &cmdp->addr1, &nlines, 0); /* * In vi, set the cursor to the first line read in, if anything read * in, otherwise, the address. (Historic vi set it to the line after * the address regardless, but since that line may not exist we don't * bother.) * * In ex, set the cursor to the last line read in, if anything read in, * otherwise, the address. */ if (F_ISSET(sp, SC_VI)) { sp->lno = cmdp->addr1.lno; if (nlines) ++sp->lno; } else sp->lno = cmdp->addr1.lno + nlines; return (rval); }
/***************************************************************** * cli_dcl_parse: *****************************************************************/ int cli_dcl_parse( /* Returns: status */ void *command_string /* <r:opt> descr or c-string */ ,struct cduVerb *table /* <r> addr of command table */ ,int (*param_routine)() /* <r:opt> routine to read req'd params */ ,int (*prompt_routine)() /* <r:opt> routine to read command string*/ ,void *uprompt /* <r:opt> descr or c-string */ ) { int k; int opt; int sts; char *p; char prompt[48]; struct cduParam *prm; struct cduValue *val; struct descriptor *dsc; static DYNAMIC_DESCRIPTOR(dsc_parameter); if (USE_HYPHEN_CONTINUATION == -1) USE_HYPHEN_CONTINUATION = getenv("VMS_SEMANTICS") ? 0 : 1; clearCurrentSyntax(); init_table(table); if (!uprompt) strcpy(prompt,"Command> "); else { if (is_cdescr(uprompt)) { dsc = uprompt; p = dsc->dscA_pointer; k = dsc->dscW_length; } else if (is_ddescr(uprompt)) { dsc = uprompt; p = dsc->dscA_pointer; k = dsc->dscW_length; if (!p && !k) { /* null descriptor */ p = "Command> "; k = strlen(p); } } else { p = uprompt; k = strlen(uprompt); } if (k >= sizeof(prompt)) k = sizeof(prompt) - 1; strncpy(prompt,p,k); prompt[k] = '\0'; } /*====================================================== * Set 'p' to start of command line ... *=====================================================*/ if (p = command_string) { if (is_cdescr(command_string) || is_ddescr(command_string)) { dsc = command_string; p = dsc->dscA_pointer; } } if (!p) { sts = prompt_routine(prompt,&dsc_cmdline); if (~sts & 1) return(sts); /*--------------------> return */ p = nonblank(dsc_cmdline.dscA_pointer); if (!p) return(CLI_STS_NOCOMD); /*---------> return: blank line */ } /*===================================================== * Get the command verb ... *====================================================*/ opt = cmd_lookup(&p,cmdVerb,0,NOMSG|NO_BEMORESPECIFIC,0); if (!opt) sts = CLI_STS_IVVERB; else { paramId = 0; setCurrentSyntax(TRUE,currentTable+(opt-1)); sts = cli_process_verb(&p,prompt); } if (~sts & 1) return(sts); /*---------------> return: err */ /*====================================================== * "paramId" indicates number of parameters on line. * Check that all required parameters are present ... *=====================================================*/ for ( ; (prm=currentParameters+paramId) && prm->prmA_value ; ) { val = prm->prmA_value; if (!(val->valL_flags & VAL_M_REQUIRED)) break; /* done: required params come first */ sprintf(prompt,"_%s: ", prm->prmA_prompt ? prm->prmA_prompt : (prm->prmA_label ? prm->prmA_label : prm->prmA_name)); for (p=0 ; !p ; ) { sts = param_routine(prompt,&dsc_parameter); if (~sts & 1) return(sts); /*--------------------> return */ p = nonblank(dsc_parameter.dscA_pointer); } str_append(&dsc_cmdline," "); str_append(&dsc_cmdline,&dsc_parameter); sts = cli_process_verb(&p,"..."); } return(sts); }
/* * v_correct -- * Handle command with a search as the motion. * * !!! * Historically, commands didn't affect the line searched to/from if the * motion command was a search and the final position was the start/end * of the line. There were some special cases and vi was not consistent; * it was fairly easy to confuse it. For example, given the two lines: * * abcdefghi * ABCDEFGHI * * placing the cursor on the 'A' and doing y?$ would so confuse it that 'h' * 'k' and put would no longer work correctly. In any case, we try to do * the right thing, but it's not going to exactly match historic practice. * * PUBLIC: int v_correct __P((SCR *, VICMD *, int)); */ int v_correct(SCR *sp, VICMD *vp, int isdelta) { dir_t dir; MARK m; size_t len; /* * !!! * We may have wrapped if wrapscan was set, and we may have returned * to the position where the cursor started. Historic vi didn't cope * with this well. Yank wouldn't beep, but the first put after the * yank would move the cursor right one column (without adding any * text) and the second would put a copy of the current line. The * change and delete commands would beep, but would leave the cursor * on the colon command line. I believe that there are macros that * depend on delete, at least, failing. For now, commands that use * search as a motion component fail when the search returns to the * original cursor position. */ if (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno == vp->m_stop.cno) { msgq(sp, M_BERR, "190|Search wrapped to original position"); return (1); } /* * !!! * Searches become line mode operations if there was a delta specified * to the search pattern. */ if (isdelta) F_SET(vp, VM_LMODE); /* * If the motion is in the reverse direction, switch the start and * stop MARK's so that it's in a forward direction. (There's no * reason for this other than to make the tests below easier. The * code in vi.c:vi() would have done the switch.) Both forward * and backward motions can happen for any kind of search command * because of the wrapscan option. */ if (vp->m_start.lno > vp->m_stop.lno || (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno > vp->m_stop.cno)) { m = vp->m_start; vp->m_start = vp->m_stop; vp->m_stop = m; dir = BACKWARD; } else dir = FORWARD; /* * BACKWARD: * Delete and yank commands move to the end of the range. * Ignore others. * * FORWARD: * Delete and yank commands don't move. Ignore others. */ vp->m_final = vp->m_start; /* * !!! * Delta'd searches don't correct based on column positions. */ if (isdelta) return (0); /* * !!! * Backward searches starting at column 0, and forward searches ending * at column 0 are corrected to the last column of the previous line. * Otherwise, adjust the starting/ending point to the character before * the current one (this is safe because we know the search had to move * to succeed). * * Searches become line mode operations if they start at the first * nonblank and end at column 0 of another line. */ if (vp->m_start.lno < vp->m_stop.lno && vp->m_stop.cno == 0) { if (db_get(sp, --vp->m_stop.lno, DBG_FATAL, NULL, &len)) return (1); vp->m_stop.cno = len ? len - 1 : 0; len = 0; if (nonblank(sp, vp->m_start.lno, &len)) return (1); if (vp->m_start.cno <= len) F_SET(vp, VM_LMODE); } else --vp->m_stop.cno; return (0); }
/* * v_match -- % * Search to matching character. * * PUBLIC: int v_match __P((SCR *, VICMD *)); */ int v_match(SCR *sp, VICMD *vp) { VCS cs; MARK *mp; size_t cno, len, off; int cnt, isempty, matchc, startc, (*gc)__P((SCR *, VCS *)); CHAR_T *p; char *cp; const char *match_chars; static MARK match = { 0, 0 }; static int match_dir; /* * Historically vi would match (), {} and [] however * an update included <>. This is ok for editing HTML * but a pain in the butt for C source. * Making it an option lets the user decide what is 'right'. * Also fixed to do something sensible with "". */ match_chars = O_STR(sp, O_MATCHCHARS); /* * !!! * Historic practice; ignore the count. * * !!! * Historical practice was to search for the initial character in the * forward direction only. */ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto nomatch; return (1); } for (off = vp->m_start.cno;; ++off) { if (off >= len) { nomatch: msgq(sp, M_BERR, "184|No match character on this line"); return (1); } startc = p[off]; cp = strchr(match_chars, startc); if (cp != NULL) break; } cnt = cp - match_chars; matchc = match_chars[cnt ^ 1]; /* Alternate back-forward search if startc and matchc the same */ if (startc == matchc) { /* are we continuing from where last match finished? */ if (match.lno == vp->m_start.lno && match.cno ==vp->m_start.cno) /* yes - continue in sequence */ match_dir++; else /* no - go forward, back, back, forward */ match_dir = 1; if (match_dir & 2) cnt++; } gc = cnt & 1 ? cs_prev : cs_next; cs.cs_lno = vp->m_start.lno; cs.cs_cno = off; if (cs_init(sp, &cs)) return (1); for (cnt = 1;;) { if (gc(sp, &cs)) return (1); if (cs.cs_flags != 0) { if (cs.cs_flags == CS_EOF || cs.cs_flags == CS_SOF) break; continue; } if (cs.cs_ch == matchc && --cnt == 0) break; if (cs.cs_ch == startc) ++cnt; } if (cnt) { msgq(sp, M_BERR, "185|Matching character not found"); return (1); } vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * If moving right, non-motion commands move to the end of the range. * Delete and yank stay at the start. * * If moving left, all commands move to the end of the range. * * !!! * Don't correct for leftward movement -- historic vi deleted the * starting cursor position when deleting to a match. */ if (vp->m_start.lno < vp->m_stop.lno || (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno < vp->m_stop.cno)) vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; else vp->m_final = vp->m_stop; match.lno = vp->m_final.lno; match.cno = vp->m_final.cno; /* * !!! * If the motion is across lines, and the earliest cursor position * is at or before any non-blank characters in the line, i.e. the * movement is cutting all of the line's text, and the later cursor * position has nothing other than whitespace characters between it * and the end of its line, the buffer is in line mode. */ if (!ISMOTION(vp) || vp->m_start.lno == vp->m_stop.lno) return (0); mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_start : &vp->m_stop; if (mp->cno != 0) { cno = 0; if (nonblank(sp, mp->lno, &cno)) return (1); if (cno < mp->cno) return (0); } mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_stop : &vp->m_start; if (db_get(sp, mp->lno, DBG_FATAL, &p, &len)) return (1); for (p += mp->cno + 1, len -= mp->cno; --len; ++p) if (!ISBLANK((UCHAR_T)*p)) return (0); F_SET(vp, VM_LMODE); return (0); }
/* * v_change -- [buffer][count]c[count]motion * [buffer][count]C * [buffer][count]S * Change command. * * PUBLIC: int v_change __P((SCR *, VICMD *)); */ int v_change(SCR *sp, VICMD *vp) { size_t blen, len; u_int32_t flags; int isempty, lmode, rval; CHAR_T *bp; CHAR_T *p; /* * 'c' can be combined with motion commands that set the resulting * cursor position, i.e. "cG". Clear the VM_RCM flags and make the * resulting cursor position stick, inserting text has its own rules * for cursor positioning. */ F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SET); /* * Find out if the file is empty, it's easier to handle it as a * special case. */ if (vp->m_start.lno == vp->m_stop.lno && db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); return (v_ia(sp, vp)); } flags = set_txt_std(sp, vp, 0); sp->showmode = SM_CHANGE; /* * Move the cursor to the start of the change. Note, if autoindent * is turned on, the cc command in line mode changes from the first * *non-blank* character of the line, not the first character. And, * to make it just a bit more exciting, the initial space is handled * as auto-indent characters. */ lmode = F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0; if (lmode) { vp->m_start.cno = 0; if (O_ISSET(sp, O_AUTOINDENT)) { if (nonblank(sp, vp->m_start.lno, &vp->m_start.cno)) return (1); LF_SET(TXT_AICHARS); } } sp->lno = vp->m_start.lno; sp->cno = vp->m_start.cno; LOG_CORRECT; /* * If not in line mode and changing within a single line, copy the * text and overwrite it. */ if (!lmode && vp->m_start.lno == vp->m_stop.lno) { /* * !!! * Historic practice, c did not cut into the numeric buffers, * only the unnamed one. */ if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, lmode)) return (1); if (len == 0) LF_SET(TXT_APPENDEOL); LF_SET(TXT_EMARK | TXT_OVERWRITE); return (v_txt(sp, vp, &vp->m_stop, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); } /* * It's trickier if in line mode or changing over multiple lines. If * we're in line mode delete all of the lines and insert a replacement * line which the user edits. If there was leading whitespace in the * first line being changed, we copy it and use it as the replacement. * If we're not in line mode, we delete the text and start inserting. * * !!! * Copy the text. Historic practice, c did not cut into the numeric * buffers, only the unnamed one. */ if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, lmode)) return (1); /* If replacing entire lines and there's leading text. */ if (lmode && vp->m_start.cno) { /* * Get a copy of the first line changed, and copy out the * leading text. */ if (db_get(sp, vp->m_start.lno, DBG_FATAL, &p, &len)) return (1); GET_SPACE_RETW(sp, bp, blen, vp->m_start.cno); MEMMOVEW(bp, p, vp->m_start.cno); } else bp = NULL; /* Delete the text. */ if (del(sp, &vp->m_start, &vp->m_stop, lmode)) return (1); /* If replacing entire lines, insert a replacement line. */ if (lmode) { if (db_insert(sp, vp->m_start.lno, bp, vp->m_start.cno)) return (1); sp->lno = vp->m_start.lno; len = sp->cno = vp->m_start.cno; } /* Get the line we're editing. */ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; } /* Check to see if we're appending to the line. */ if (vp->m_start.cno >= len) LF_SET(TXT_APPENDEOL); rval = v_txt(sp, vp, NULL, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags); if (bp != NULL) FREE_SPACEW(sp, bp, blen); return (rval); }
/* * file_cinit -- * Set up the initial cursor position. */ static void file_cinit(SCR *sp) { GS *gp; MARK m; size_t len; int nb; CHAR_T *wp; size_t wlen; /* Set some basic defaults. */ sp->lno = 1; sp->cno = 0; /* * Historically, initial commands (the -c option) weren't executed * until a file was loaded, e.g. "vi +10 nofile", followed by an * :edit or :tag command, would execute the +10 on the file loaded * by the subsequent command, (assuming that it existed). This * applied as well to files loaded using the tag commands, and we * follow that historic practice. Also, all initial commands were * ex commands and were always executed on the last line of the file. * * Otherwise, if no initial command for this file: * If in ex mode, move to the last line, first nonblank character. * If the file has previously been edited, move to the last known * position, and check it for validity. * Otherwise, move to the first line, first nonblank. * * This gets called by the file init code, because we may be in a * file of ex commands and we want to execute them from the right * location in the file. */ nb = 0; gp = sp->gp; if (gp->c_option != NULL && !F_ISSET(sp->frp, FR_NEWFILE)) { if (db_last(sp, &sp->lno)) return; if (sp->lno == 0) { sp->lno = 1; sp->cno = 0; } CHAR2INT(sp, gp->c_option, strlen(gp->c_option) + 1, wp, wlen); if (ex_run_str(sp, "-c option", wp, wlen - 1, 1, 1)) return; gp->c_option = NULL; } else if (F_ISSET(sp, SC_EX)) { if (db_last(sp, &sp->lno)) return; if (sp->lno == 0) { sp->lno = 1; sp->cno = 0; return; } nb = 1; } else { if (F_ISSET(sp->frp, FR_CURSORSET)) { sp->lno = sp->frp->lno; sp->cno = sp->frp->cno; /* If returning to a file in vi, center the line. */ F_SET(sp, SC_SCR_CENTER); } else { if (O_ISSET(sp, O_COMMENT)) file_comment(sp); else sp->lno = 1; nb = 1; } if (db_get(sp, sp->lno, 0, NULL, &len)) { sp->lno = 1; sp->cno = 0; return; } if (!nb && sp->cno > len) nb = 1; } if (nb) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } /* * !!! * The initial column is also the most attractive column. */ sp->rcm = sp->cno; /* * !!! * Historically, vi initialized the absolute mark, but ex did not. * Which meant, that if the first command in ex mode was "visual", * or if an ex command was executed first (e.g. vi +10 file) vi was * entered without the mark being initialized. For consistency, if * the file isn't empty, we initialize it for everyone, believing * that it can't hurt, and is generally useful. Not initializing it * if the file is empty is historic practice, although it has always * been possible to set (and use) marks in empty vi files. */ m.lno = sp->lno; m.cno = sp->cno; (void)mark_set(sp, ABSMARK1, &m, 0); }
/* * put -- * Put text buffer contents into the file. * * PUBLIC: int put __P((SCR *, CB *, CHAR_T *, MARK *, MARK *, int)); */ int put(SCR *sp, CB *cbp, ARG_CHAR_T *namep, MARK *cp, MARK *rp, int append) { ARG_CHAR_T name; TEXT *ltp, *tp; db_recno_t lno; size_t blen, clen, len; int rval; CHAR_T *bp, *t; CHAR_T *p; if (cbp == NULL) { if (namep == NULL) { cbp = sp->wp->dcbp; if (cbp == NULL) { msgq(sp, M_ERR, "053|The default buffer is empty"); return (1); } } else { name = *namep; CBNAME(sp, cbp, name); if (cbp == NULL) { msgq(sp, M_ERR, "054|Buffer %s is empty", KEY_NAME(sp, name)); return (1); } } } tp = cbp->textq.cqh_first; /* * It's possible to do a put into an empty file, meaning that the cut * buffer simply becomes the file. It's a special case so that we can * ignore it in general. * * !!! * Historically, pasting into a file with no lines in vi would preserve * the single blank line. This is surely a result of the fact that the * historic vi couldn't deal with a file that had no lines in it. This * implementation treats that as a bug, and does not retain the blank * line. * * Historical practice is that the cursor ends at the first character * in the file. */ if (cp->lno == 1) { if (db_last(sp, &lno)) return (1); if (lno == 0) { for (; tp != (void *)&cbp->textq; ++lno, ++sp->rptlines[L_ADDED], tp = tp->q.cqe_next) if (db_append(sp, 1, lno, tp->lb, tp->len)) return (1); rp->lno = 1; rp->cno = 0; return (0); } } /* If a line mode buffer, append each new line into the file. */ if (F_ISSET(cbp, CB_LMODE)) { lno = append ? cp->lno : cp->lno - 1; rp->lno = lno + 1; for (; tp != (void *)&cbp->textq; ++lno, ++sp->rptlines[L_ADDED], tp = tp->q.cqe_next) if (db_append(sp, 1, lno, tp->lb, tp->len)) return (1); rp->cno = 0; (void)nonblank(sp, rp->lno, &rp->cno); return (0); } /* * If buffer was cut in character mode, replace the current line with * one built from the portion of the first line to the left of the * split plus the first line in the CB. Append each intermediate line * in the CB. Append a line built from the portion of the first line * to the right of the split plus the last line in the CB. * * Get the first line. */ lno = cp->lno; if (db_get(sp, lno, DBG_FATAL, &p, &len)) return (1); GET_SPACE_RETW(sp, bp, blen, tp->len + len + 1); t = bp; /* Original line, left of the split. */ if (len > 0 && (clen = cp->cno + (append ? 1 : 0)) > 0) { MEMCPYW(bp, p, clen); p += clen; t += clen; } /* First line from the CB. */ if (tp->len != 0) { MEMCPYW(t, tp->lb, tp->len); t += tp->len; } /* Calculate length left in the original line. */ clen = len == 0 ? 0 : len - (cp->cno + (append ? 1 : 0)); /* * !!! * In the historical 4BSD version of vi, character mode puts within * a single line have two cursor behaviors: if the put is from the * unnamed buffer, the cursor moves to the character inserted which * appears last in the file. If the put is from a named buffer, * the cursor moves to the character inserted which appears first * in the file. In System III/V, it was changed at some point and * the cursor always moves to the first character. In both versions * of vi, character mode puts that cross line boundaries leave the * cursor on the first character. Nvi implements the System III/V * behavior, and expect POSIX.2 to do so as well. */ rp->lno = lno; rp->cno = len == 0 ? 0 : sp->cno + (append && tp->len ? 1 : 0); /* * If no more lines in the CB, append the rest of the original * line and quit. Otherwise, build the last line before doing * the intermediate lines, because the line changes will lose * the cached line. */ if (tp->q.cqe_next == (void *)&cbp->textq) { if (clen > 0) { MEMCPYW(t, p, clen); t += clen; } if (db_set(sp, lno, bp, t - bp)) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } } else { /* * Have to build both the first and last lines of the * put before doing any sets or we'll lose the cached * line. Build both the first and last lines in the * same buffer, so we don't have to have another buffer * floating around. * * Last part of original line; check for space, reset * the pointer into the buffer. */ ltp = cbp->textq.cqh_last; len = t - bp; ADD_SPACE_RETW(sp, bp, blen, ltp->len + clen); t = bp + len; /* Add in last part of the CB. */ MEMCPYW(t, ltp->lb, ltp->len); if (clen) MEMCPYW(t + ltp->len, p, clen); clen += ltp->len; /* * Now: bp points to the first character of the first * line, t points to the last character of the last * line, t - bp is the length of the first line, and * clen is the length of the last. Just figured you'd * want to know. * * Output the line replacing the original line. */ if (db_set(sp, lno, bp, t - bp)) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } /* Output any intermediate lines in the CB. */ for (tp = tp->q.cqe_next; tp->q.cqe_next != (void *)&cbp->textq; ++lno, ++sp->rptlines[L_ADDED], tp = tp->q.cqe_next) if (db_append(sp, 1, lno, tp->lb, tp->len)) goto err; if (db_append(sp, 1, lno, t, clen)) goto err; ++sp->rptlines[L_ADDED]; } rval = 0; if (0) err: rval = 1; FREE_SPACEW(sp, bp, blen); return (rval); }
static int s(SCR *sp, EXCMD *cmdp, char *s, regex_t *re, u_int flags) { EVENT ev; MARK from, to; TEXTH tiq; recno_t elno, lno, slno; regmatch_t match[10]; size_t blen, cnt, last, lbclen, lblen, len, llen; size_t offset, saved_offset, scno; int lflag, nflag, pflag, rflag; int didsub, do_eol_match, eflags, empty_ok, eval; int linechanged, matched, quit, rval; unsigned long ul; char *bp, *lb; NEEDFILE(sp, cmdp); slno = sp->lno; scno = sp->cno; /* * !!! * Historically, the 'g' and 'c' suffices were always toggled as flags, * so ":s/A/B/" was the same as ":s/A/B/ccgg". If O_EDCOMPATIBLE was * not set, they were initialized to 0 for all substitute commands. If * O_EDCOMPATIBLE was set, they were initialized to 0 only if the user * specified substitute/replacement patterns (see ex_s()). */ if (!O_ISSET(sp, O_EDCOMPATIBLE)) sp->c_suffix = sp->g_suffix = 0; /* * Historic vi permitted the '#', 'l' and 'p' options in vi mode, but * it only displayed the last change. I'd disallow them, but they are * useful in combination with the [v]global commands. In the current * model the problem is combining them with the 'c' flag -- the screen * would have to flip back and forth between the confirm screen and the * ex print screen, which would be pretty awful. We do display all * changes, though, for what that's worth. * * !!! * Historic vi was fairly strict about the order of "options", the * count, and "flags". I'm somewhat fuzzy on the difference between * options and flags, anyway, so this is a simpler approach, and we * just take it them in whatever order the user gives them. (The ex * usage statement doesn't reflect this.) */ lflag = nflag = pflag = rflag = 0; if (s == NULL) goto noargs; for (lno = OOBLNO; *s != '\0'; ++s) switch (*s) { case ' ': case '\t': continue; case '+': ++cmdp->flagoff; break; case '-': --cmdp->flagoff; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (lno != OOBLNO) goto usage; errno = 0; if ((ul = strtoul(s, &s, 10)) >= UINT_MAX) errno = ERANGE; if (*s == '\0') /* Loop increment correction. */ --s; if (errno == ERANGE) { if (ul >= UINT_MAX) msgq(sp, M_ERR, "Count overflow"); else msgq(sp, M_SYSERR, NULL); return (1); } lno = (recno_t)ul; /* * In historic vi, the count was inclusive from the * second address. */ cmdp->addr1.lno = cmdp->addr2.lno; cmdp->addr2.lno += lno - 1; if (!db_exist(sp, cmdp->addr2.lno) && db_last(sp, &cmdp->addr2.lno)) return (1); break; case '#': nflag = 1; break; case 'c': sp->c_suffix = !sp->c_suffix; /* Ex text structure initialization. */ if (F_ISSET(sp, SC_EX)) { memset(&tiq, 0, sizeof(TEXTH)); TAILQ_INIT(&tiq); } break; case 'g': sp->g_suffix = !sp->g_suffix; break; case 'l': lflag = 1; break; case 'p': pflag = 1; break; case 'r': if (LF_ISSET(SUB_FIRST)) { msgq(sp, M_ERR, "Regular expression specified; r flag meaningless"); return (1); } if (!F_ISSET(sp, SC_RE_SEARCH)) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } rflag = 1; re = &sp->re_c; break; default: goto usage; } if (*s != '\0' || (!rflag && LF_ISSET(SUB_MUSTSETR))) { usage: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } noargs: if (F_ISSET(sp, SC_VI) && sp->c_suffix && (lflag || nflag || pflag)) { msgq(sp, M_ERR, "The #, l and p flags may not be combined with the c flag in vi mode"); return (1); } /* * bp: if interactive, line cache * blen: if interactive, line cache length * lb: build buffer pointer. * lbclen: current length of built buffer. * lblen; length of build buffer. */ bp = lb = NULL; blen = lbclen = lblen = 0; /* For each line... */ for (matched = quit = 0, lno = cmdp->addr1.lno, elno = cmdp->addr2.lno; !quit && lno <= elno; ++lno) { /* Someone's unhappy, time to stop. */ if (INTERRUPTED(sp)) break; /* Get the line. */ if (db_get(sp, lno, DBG_FATAL, &s, &llen)) goto err; /* * Make a local copy if doing confirmation -- when calling * the confirm routine we're likely to lose the cached copy. */ if (sp->c_suffix) { if (bp == NULL) { GET_SPACE_RET(sp, bp, blen, llen); } else ADD_SPACE_RET(sp, bp, blen, llen); memcpy(bp, s, llen); s = bp; } /* Start searching from the beginning. */ offset = 0; len = llen; /* Reset the build buffer offset. */ lbclen = 0; /* Reset empty match flag. */ empty_ok = 1; /* * We don't want to have to do a setline if the line didn't * change -- keep track of whether or not this line changed. * If doing confirmations, don't want to keep setting the * line if change is refused -- keep track of substitutions. */ didsub = linechanged = 0; /* New line, do an EOL match. */ do_eol_match = 1; /* It's not nul terminated, but we pretend it is. */ eflags = REG_STARTEND; /* * The search area is from s + offset to the EOL. * * Generally, match[0].rm_so is the offset of the start * of the match from the start of the search, and offset * is the offset of the start of the last search. */ nextmatch: match[0].rm_so = 0; match[0].rm_eo = len; /* Get the next match. */ eval = regexec(re, (char *)s + offset, 10, match, eflags); /* * There wasn't a match or if there was an error, deal with * it. If there was a previous match in this line, resolve * the changes into the database. Otherwise, just move on. */ if (eval == REG_NOMATCH) goto endmatch; if (eval != 0) { re_error(sp, eval, re); goto err; } matched = 1; /* Only the first search can match an anchored expression. */ eflags |= REG_NOTBOL; /* * !!! * It's possible to match 0-length strings -- for example, the * command s;a*;X;, when matched against the string "aabb" will * result in "XbXbX", i.e. the matches are "aa", the space * between the b's and the space between the b's and the end of * the string. There is a similar space between the beginning * of the string and the a's. The rule that we use (because vi * historically used it) is that any 0-length match, occurring * immediately after a match, is ignored. Otherwise, the above * example would have resulted in "XXbXbX". Another example is * incorrectly using " *" to replace groups of spaces with one * space. * * The way we do this is that if we just had a successful match, * the starting offset does not skip characters, and the match * is empty, ignore the match and move forward. If there's no * more characters in the string, we were attempting to match * after the last character, so quit. */ if (!empty_ok && match[0].rm_so == 0 && match[0].rm_eo == 0) { empty_ok = 1; if (len == 0) goto endmatch; BUILD(sp, s + offset, 1) ++offset; --len; goto nextmatch; } /* Confirm change. */ if (sp->c_suffix) { /* * Set the cursor position for confirmation. Note, * if we matched on a '$', the cursor may be past * the end of line. */ from.lno = to.lno = lno; from.cno = match[0].rm_so + offset; to.cno = match[0].rm_eo + offset; /* * Both ex and vi have to correct for a change before * the first character in the line. */ if (llen == 0) from.cno = to.cno = 0; if (F_ISSET(sp, SC_VI)) { /* * Only vi has to correct for a change after * the last character in the line. * * XXX * It would be nice to change the vi code so * that we could display a cursor past EOL. */ if (to.cno >= llen) to.cno = llen - 1; if (from.cno >= llen) from.cno = llen - 1; sp->lno = from.lno; sp->cno = from.cno; if (vs_refresh(sp, 1)) goto err; vs_update(sp, "Confirm change? [n]", NULL); if (v_event_get(sp, &ev, 0, 0)) goto err; switch (ev.e_event) { case E_CHARACTER: break; case E_EOF: case E_ERR: case E_INTERRUPT: goto lquit; default: v_event_err(sp, &ev); goto lquit; } } else { if (ex_print(sp, cmdp, &from, &to, 0) || ex_scprint(sp, &from, &to)) goto lquit; if (ex_txt(sp, &tiq, 0, TXT_CR)) goto err; ev.e_c = TAILQ_FIRST(&tiq)->lb[0]; } switch (ev.e_c) { case CH_YES: break; default: case CH_NO: didsub = 0; BUILD(sp, s +offset, match[0].rm_eo); goto skip; case CH_QUIT: /* Set the quit/interrupted flags. */ lquit: quit = 1; F_SET(sp->gp, G_INTERRUPTED); /* * Resolve any changes, then return to (and * exit from) the main loop. */ goto endmatch; } } /* * Set the cursor to the last position changed, converting * from 1-based to 0-based. */ sp->lno = lno; sp->cno = match[0].rm_so; /* Copy the bytes before the match into the build buffer. */ BUILD(sp, s + offset, match[0].rm_so); /* Substitute the matching bytes. */ didsub = 1; if (re_sub(sp, s + offset, &lb, &lbclen, &lblen, match)) goto err; /* Set the change flag so we know this line was modified. */ linechanged = 1; /* Move past the matched bytes. */ skip: offset += match[0].rm_eo; len -= match[0].rm_eo; /* A match cannot be followed by an empty pattern. */ empty_ok = 0; /* * If doing a global change with confirmation, we have to * update the screen. The basic idea is to store the line * so the screen update routines can find it, and restart. */ if (didsub && sp->c_suffix && sp->g_suffix) { /* * The new search offset will be the end of the * modified line. */ saved_offset = lbclen; /* Copy the rest of the line. */ if (len) BUILD(sp, s + offset, len) /* Set the new offset. */ offset = saved_offset; /* Store inserted lines, adjusting the build buffer. */ last = 0; if (sp->newl_cnt) { for (cnt = 0; cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) { if (db_insert(sp, lno, lb + last, sp->newl[cnt] - last)) goto err; last = sp->newl[cnt] + 1; ++sp->rptlines[L_ADDED]; } lbclen -= last; offset -= last; sp->newl_cnt = 0; } /* Store and retrieve the line. */ if (db_set(sp, lno, lb + last, lbclen)) goto err; if (db_get(sp, lno, DBG_FATAL, &s, &llen)) goto err; ADD_SPACE_RET(sp, bp, blen, llen) memcpy(bp, s, llen); s = bp; len = llen - offset; /* Restart the build. */ lbclen = 0; BUILD(sp, s, offset); /* * If we haven't already done the after-the-string * match, do one. Set REG_NOTEOL so the '$' pattern * only matches once. */ if (!do_eol_match) goto endmatch; if (offset == len) { do_eol_match = 0; eflags |= REG_NOTEOL; } goto nextmatch; } /* * If it's a global: * * If at the end of the string, do a test for the after * the string match. Set REG_NOTEOL so the '$' pattern * only matches once. */ if (sp->g_suffix && do_eol_match) { if (len == 0) { do_eol_match = 0; eflags |= REG_NOTEOL; } goto nextmatch; } endmatch: if (!linechanged) continue; /* Copy any remaining bytes into the build buffer. */ if (len) BUILD(sp, s + offset, len) /* Store inserted lines, adjusting the build buffer. */ last = 0; if (sp->newl_cnt) { for (cnt = 0; cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) { if (db_insert(sp, lno, lb + last, sp->newl[cnt] - last)) goto err; last = sp->newl[cnt] + 1; ++sp->rptlines[L_ADDED]; } lbclen -= last; sp->newl_cnt = 0; } /* Store the changed line. */ if (db_set(sp, lno, lb + last, lbclen)) goto err; /* Update changed line counter. */ if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } /* * !!! * Display as necessary. Historic practice is to only * display the last line of a line split into multiple * lines. */ if (lflag || nflag || pflag) { from.lno = to.lno = lno; from.cno = to.cno = 0; if (lflag) (void)ex_print(sp, cmdp, &from, &to, E_C_LIST); if (nflag) (void)ex_print(sp, cmdp, &from, &to, E_C_HASH); if (pflag) (void)ex_print(sp, cmdp, &from, &to, E_C_PRINT); } } /* * !!! * Historically, vi attempted to leave the cursor at the same place if * the substitution was done at the current cursor position. Otherwise * it moved it to the first non-blank of the last line changed. There * were some problems: for example, :s/$/foo/ with the cursor on the * last character of the line left the cursor on the last character, or * the & command with multiple occurrences of the matching string in the * line usually left the cursor in a fairly random position. * * We try to do the same thing, with the exception that if the user is * doing substitution with confirmation, we move to the last line about * which the user was consulted, as opposed to the last line that they * actually changed. This prevents a screen flash if the user doesn't * change many of the possible lines. */ if (!sp->c_suffix && (sp->lno != slno || sp->cno != scno)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } /* * If not in a global command, and nothing matched, say so. * Else, if none of the lines displayed, put something up. */ rval = 0; if (!matched) { if (!F_ISSET(sp, SC_EX_GLOBAL)) { msgq(sp, M_ERR, "No match found"); goto err; } } else if (!lflag && !nflag && !pflag) F_SET(cmdp, E_AUTOPRINT); if (0) { err: rval = 1; } if (bp != NULL) FREE_SPACE(sp, bp, blen); if (lb != NULL) free(lb); return (rval); }
/* * v_match -- % * Search to matching character. * * PUBLIC: int v_match(SCR *, VICMD *); */ int v_match(SCR *sp, VICMD *vp) { VCS cs; MARK *mp; size_t cno, len, off; int cnt, isempty, matchc, startc, (*gc)(SCR *, VCS *); CHAR_T *p; CHAR_T *cp; const CHAR_T *match_chars; /* * Historically vi would match (), {} and [] however * an update included <>. This is ok for editing HTML * but a pain in the butt for C source. * Making it an option lets the user decide what is 'right'. */ match_chars = VIP(sp)->mcs; /* * !!! * Historic practice; ignore the count. * * !!! * Historical practice was to search for the initial character in the * forward direction only. */ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto nomatch; return (1); } for (off = vp->m_start.cno;; ++off) { if (off >= len) { nomatch: msgq(sp, M_BERR, "184|No match character on this line"); return (1); } startc = p[off]; cp = STRCHR(match_chars, startc); if (cp != NULL) { cnt = cp - match_chars; matchc = match_chars[cnt ^ 1]; gc = cnt & 1 ? cs_prev : cs_next; break; } } cs.cs_lno = vp->m_start.lno; cs.cs_cno = off; if (cs_init(sp, &cs)) return (1); for (cnt = 1;;) { if (gc(sp, &cs)) return (1); if (cs.cs_flags != 0) { if (cs.cs_flags == CS_EOF || cs.cs_flags == CS_SOF) break; continue; } if (cs.cs_ch == startc) ++cnt; else if (cs.cs_ch == matchc && --cnt == 0) break; } if (cnt) { msgq(sp, M_BERR, "185|Matching character not found"); return (1); } vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * If moving right, non-motion commands move to the end of the range. * Delete and yank stay at the start. * * If moving left, all commands move to the end of the range. * * !!! * Don't correct for leftward movement -- historic vi deleted the * starting cursor position when deleting to a match. */ if (vp->m_start.lno < vp->m_stop.lno || (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno < vp->m_stop.cno)) vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; else vp->m_final = vp->m_stop; /* * !!! * If the motion is across lines, and the earliest cursor position * is at or before any non-blank characters in the line, i.e. the * movement is cutting all of the line's text, and the later cursor * position has nothing other than whitespace characters between it * and the end of its line, the buffer is in line mode. */ if (!ISMOTION(vp) || vp->m_start.lno == vp->m_stop.lno) return (0); mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_start : &vp->m_stop; if (mp->cno != 0) { cno = 0; if (nonblank(sp, mp->lno, &cno)) return (1); if (cno < mp->cno) return (0); } mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_stop : &vp->m_start; if (db_get(sp, mp->lno, DBG_FATAL, &p, &len)) return (1); for (p += mp->cno + 1, len -= mp->cno; --len; ++p) if (!isblank(*p)) return (0); F_SET(vp, VM_LMODE); return (0); }
/* * ex_visual -- :[line] vi[sual] [^-.+] [window_size] [flags] * Switch to visual mode. * * PUBLIC: int ex_visual(SCR *, EXCMD *); */ int ex_visual(SCR *sp, EXCMD *cmdp) { SCR *tsp; size_t len; int pos; char buf[256]; /* If open option off, disallow visual command. */ if (!O_ISSET(sp, O_OPEN)) { msgq(sp, M_ERR, "The visual command requires that the open option be set"); return (1); } /* Move to the address. */ sp->lno = cmdp->addr1.lno == 0 ? 1 : cmdp->addr1.lno; /* * Push a command based on the line position flags. If no * flag specified, the line goes at the top of the screen. */ switch (FL_ISSET(cmdp->iflags, E_C_CARAT | E_C_DASH | E_C_DOT | E_C_PLUS)) { case E_C_CARAT: pos = '^'; break; case E_C_DASH: pos = '-'; break; case E_C_DOT: pos = '.'; break; case E_C_PLUS: pos = '+'; break; default: sp->frp->lno = sp->lno; sp->frp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); F_SET(sp->frp, FR_CURSORSET); goto nopush; } if (FL_ISSET(cmdp->iflags, E_C_COUNT)) len = snprintf(buf, sizeof(buf), "%luz%c%lu", (ulong)sp->lno, pos, cmdp->count); else len = snprintf(buf, sizeof(buf), "%luz%c", (ulong)sp->lno, pos); if (len >= sizeof(buf)) len = sizeof(buf) - 1; (void)v_event_push(sp, NULL, buf, len, CH_NOMAP | CH_QUOTED); /* * !!! * Historically, if no line address was specified, the [p#l] flags * caused the cursor to be moved to the last line of the file, which * was then positioned as described above. This seems useless, so * I haven't implemented it. */ switch (FL_ISSET(cmdp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT)) { case E_C_HASH: O_SET(sp, O_NUMBER); break; case E_C_LIST: O_SET(sp, O_LIST); break; case E_C_PRINT: break; } nopush: /* * !!! * You can call the visual part of the editor from within an ex * global command. * * XXX * Historically, undoing a visual session was a single undo command, * i.e. you could undo all of the changes you made in visual mode. * We don't get this right; I'm waiting for the new logging code to * be available. * * It's explicit, don't have to wait for the user, unless there's * already a reason to wait. */ if (!F_ISSET(sp, SC_SCR_EXWROTE)) F_SET(sp, SC_EX_WAIT_NO); if (F_ISSET(sp, SC_EX_GLOBAL)) { /* * When the vi screen(s) exit, we don't want to lose our hold * on this screen or this file, otherwise we're going to fail * fairly spectacularly. */ ++sp->refcnt; ++sp->ep->refcnt; /* * Fake up a screen pointer -- vi doesn't get to change our * underlying file, regardless. */ tsp = sp; if (vi(&tsp)) return (1); /* * !!! * Historically, if the user exited the vi screen(s) using an * ex quit command (e.g. :wq, :q) ex/vi exited, it was only if * they exited vi using the Q command that ex continued. Some * early versions of nvi continued in ex regardless, but users * didn't like the semantic. * * Reset the screen. */ if (ex_init(sp)) return (1); /* Move out of the vi screen. */ (void)ex_puts(sp, "\n"); } else { F_CLR(sp, SC_EX | SC_SCR_EX); F_SET(sp, SC_VI); } return (0); }