This is cmdline.c in view mode; [Download] [Up]
/* vi:ts=4:sw=4 * * VIM - Vi IMproved by Bram Moolenaar * * Read the file "credits.txt" for a list of people who contributed. * Read the file "uganda.txt" for copying and usage conditions. */ /* * cmdline.c: functions for reading in the command line and executing it */ #include "vim.h" #include "globals.h" #include "proto.h" #include "param.h" #include "cmdtab.h" #include "ops.h" /* included because we call functions in ops.c */ #include "fcntl.h" /* for chdir() */ /* * variables shared between getcmdline() and redrawcmdline() */ static int cmdlen; /* number of chars on command line */ static int cmdpos; /* current cursor position */ static int cmdspos; /* cursor column on screen */ static int cmdfirstc; /* ':', '/' or '?' */ static char_u *cmdbuff; /* pointer to command line buffer */ /* * The next two variables contain the bounds of any range given in a command. * They are set by docmdline(). */ static linenr_t line1, line2; static int forceit; static int regname; static int quitmore = 0; static int cmd_numfiles = -1; /* number of files found by filename completion */ static void putcmdline __ARGS((int, char_u *)); static void cursorcmd __ARGS((void)); static int ccheck_abbr __ARGS((int)); static char_u *DoOneCmd __ARGS((char_u *)); static int buf_write_all __ARGS((BUF *)); static int dowrite __ARGS((char_u *, int)); static char_u *getargcmd __ARGS((char_u **)); static char_u *checknextcomm __ARGS((char_u *)); static void domake __ARGS((char_u *)); static int doarglist __ARGS((char_u *)); static int check_readonly __ARGS((void)); static int check_changed __ARGS((BUF *, int, int)); static int check_changed_any __ARGS((int)); static int check_more __ARGS((int)); #ifdef WEBB_COMPLETE static void vim_strncpy __ARGS((char_u *, char_u *, int)); static int nextwild __ARGS((char_u *, int)); static int showmatches __ARGS((char_u *)); static void set_expand_context __ARGS((int, char_u *)); static char_u *set_one_cmd_context __ARGS((int, char_u *)); static int ExpandFromContext __ARGS((char_u *, int *, char_u ***, int, int)); #else static void nextwild __ARGS((char_u *, int)); static void showmatches __ARGS((char_u *, int)); #endif /* WEBB_COMPLETE */ static char_u *addstar __ARGS((char_u *, int)); static linenr_t get_address __ARGS((char_u **)); /* * getcmdline() - accept a command line starting with ':', '!', '/', or '?' * * For searches the optional matching '?' or '/' is removed. * * Return OK if there is a commandline, FAIL if not */ int getcmdline(firstc, buff) int firstc; /* either ':', '/', or '?' */ char_u *buff; /* buffer for command string */ { register char_u c; int cc; int nextc = 0; register int i; int retval; int hiscnt; /* current history line in use */ static char_u **history = NULL; /* history table */ static int hislen = 0; /* actual lengt of history table */ int newlen; /* new length of history table */ static int hisidx = -1; /* last entered entry */ char_u **temp; char_u *lookfor = NULL; /* string to match */ int j = -1; int gotesc = FALSE; /* TRUE when last char typed was <ESC> */ int do_abbr; /* when TRUE check for abbr. */ /* * set some variables for redrawcmd() */ cmdfirstc = firstc; cmdbuff = buff; cmdlen = cmdpos = 0; cmdspos = 1; State = CMDLINE; gotocmdline(TRUE, firstc); /* * if size of history table changed, reallocate it */ newlen = (int)p_hi; if (newlen != hislen) /* history length changed */ { if (newlen) temp = (char_u **)lalloc((long_u)(newlen * sizeof(char_u *)), TRUE); else temp = NULL; if (newlen == 0 || temp != NULL) { if (newlen > hislen) /* array becomes bigger */ { for (i = 0; i <= hisidx; ++i) temp[i] = history[i]; j = i; for ( ; i <= newlen - (hislen - hisidx); ++i) temp[i] = NULL; for ( ; j < hislen; ++i, ++j) temp[i] = history[j]; } else /* array becomes smaller */ { j = hisidx; for (i = newlen - 1; ; --i) { if (i >= 0) temp[i] = history[j]; /* copy newest entries */ else free(history[j]); /* remove older entries */ if (--j < 0) j = hislen - 1; if (j == hisidx) break; } hisidx = newlen - 1; } free(history); history = temp; hislen = newlen; } } hiscnt = hislen; /* set hiscnt to impossible history value */ #ifdef DIGRAPHS dodigraph(-1); /* init digraph typahead */ #endif /* collect the command string, handling '\b', @ and much more */ for (;;) { cursorcmd(); /* set the cursor on the right spot */ if (nextc) /* character remaining from CTRL-V */ { c = nextc; nextc = 0; } else { c = vgetc(); if (c == Ctrl('C')) got_int = FALSE; } if (lookfor && c != K_SDARROW && c != K_SUARROW) { free(lookfor); lookfor = NULL; } if (cmd_numfiles != -1 && !(c == p_wc && KeyTyped) && c != Ctrl('N') && c != Ctrl('P') && c != Ctrl('A') && c != Ctrl('L')) (void)ExpandOne(NULL, FALSE, -2); /* may free expanded file names */ #ifdef DIGRAPHS c = dodigraph(c); #endif if (c == '\n' || c == '\r' || (c == ESC && !KeyTyped)) { if (ccheck_abbr(c + 0x100)) continue; outchar('\r'); /* show that we got the return */ flushbuf(); break; } /* hitting <ESC> twice means: abandon command line */ /* wildcard expansion is only done when the key is really typed, not when it comes from a macro */ if (c == p_wc && !gotesc && KeyTyped) { #ifdef WEBB_COMPLETE if (cmd_numfiles > 0) /* typed p_wc twice */ i = nextwild(buff, 3); else /* typed p_wc first time */ i = nextwild(buff, 0); if (c == ESC) gotesc = TRUE; if (i) continue; #else if (cmd_numfiles > 0) /* typed p_wc twice */ nextwild(buff, 3); else /* typed p_wc first time */ nextwild(buff, 0); if (c == ESC) gotesc = TRUE; continue; #endif /* WEBB_COMPLETE */ } gotesc = FALSE; if (c == K_ZERO) /* NUL is stored as NL */ c = '\n'; do_abbr = TRUE; /* default: check for abbreviation */ switch (c) { case BS: case DEL: case Ctrl('W'): /* * delete current character is the same as backspace on next * character, except at end of line */ if (c == DEL && cmdpos != cmdlen) ++cmdpos; if (cmdpos > 0) { j = cmdpos; if (c == Ctrl('W')) { while (cmdpos && isspace(buff[cmdpos - 1])) --cmdpos; i = isidchar(buff[cmdpos - 1]); while (cmdpos && !isspace(buff[cmdpos - 1]) && isidchar(buff[cmdpos - 1]) == i) --cmdpos; } else --cmdpos; cmdlen -= j - cmdpos; i = cmdpos; while (i < cmdlen) buff[i++] = buff[j++]; redrawcmd(); } else if (cmdlen == 0 && c != Ctrl('W')) { retval = FAIL; msg_pos(-1, 0); msg_outchar(' '); /* delete ':' */ goto returncmd; /* back to cmd mode */ } continue; /* case '@': only in very old vi */ case Ctrl('U'): clearline: cmdpos = 0; cmdlen = 0; cmdspos = 1; redrawcmd(); continue; case ESC: /* get here if p_wc != ESC or when ESC typed twice */ case Ctrl('C'): do_esc: retval = FAIL; MSG(""); goto returncmd; /* back to cmd mode */ case Ctrl('D'): { #ifdef WEBB_COMPLETE /* set_expand_context() now finds start of the pattern, so * don't do it here -- webb */ if (showmatches(buff) == FAIL) break; /* Use ^D as normal char instead */ #else for (i = cmdpos; i > 0 && buff[i - 1] != ' '; --i) ; showmatches(&buff[i], cmdpos - i); #endif /* WEBB_COMPLETE */ redrawcmd(); continue; } case K_RARROW: case K_SRARROW: do { if (cmdpos >= cmdlen) break; cmdspos += charsize(buff[cmdpos]); ++cmdpos; } while (c == K_SRARROW && buff[cmdpos] != ' '); continue; case K_LARROW: case K_SLARROW: do { if (cmdpos <= 0) break; --cmdpos; cmdspos -= charsize(buff[cmdpos]); } while (c == K_SLARROW && buff[cmdpos - 1] != ' '); continue; case Ctrl('B'): /* begin of command line */ cmdpos = 0; cmdspos = 1; continue; case Ctrl('E'): /* end of command line */ cmdpos = cmdlen; buff[cmdlen] = NUL; cmdspos = strsize(buff) + 1; continue; case Ctrl('A'): /* all matches */ #ifdef WEBB_COMPLETE if (!nextwild(buff, 4)) break; #else nextwild(buff, 4); #endif /* WEBB_COMPLETE */ continue; case Ctrl('L'): /* longest common part */ #ifdef WEBB_COMPLETE if (!nextwild(buff, 5)) break; #else nextwild(buff, 5); #endif /* WEBB_COMPLETE */ continue; case Ctrl('N'): /* next match */ case Ctrl('P'): /* previous match */ if (cmd_numfiles > 0) { #ifdef WEBB_COMPLETE if (!nextwild(buff, (c == Ctrl('P')) ? 2 : 1)) break; #else nextwild(buff, (c == Ctrl('P')) ? 2 : 1); #endif /* WEBB_COMPLETE */ continue; } case K_UARROW: case K_DARROW: case K_SUARROW: case K_SDARROW: if (hislen == 0) /* no history */ continue; i = hiscnt; /* save current command string */ if (c == K_SUARROW || c == K_SDARROW) { buff[cmdpos] = NUL; if (lookfor == NULL && (lookfor = strsave(buff)) == NULL) continue; j = STRLEN(lookfor); } for (;;) { /* one step backwards */ if (c == K_UARROW || c == K_SUARROW || c == Ctrl('P')) { if (hiscnt == hislen) /* first time */ hiscnt = hisidx; else if (hiscnt == 0 && hisidx != hislen - 1) hiscnt = hislen - 1; else if (hiscnt != hisidx + 1) --hiscnt; else /* at top of list */ break; } else /* one step forwards */ { if (hiscnt == hisidx) /* on last entry, clear the line */ { hiscnt = hislen; goto clearline; } if (hiscnt == hislen) /* not on a history line, nothing to do */ break; if (hiscnt == hislen - 1) /* wrap around */ hiscnt = 0; else ++hiscnt; } if (hiscnt < 0 || history[hiscnt] == NULL) { hiscnt = i; break; } if ((c != K_SUARROW && c != K_SDARROW) || hiscnt == i || STRNCMP(history[hiscnt], lookfor, (size_t)j) == 0) break; } if (hiscnt != i) /* jumped to other entry */ { STRCPY(buff, history[hiscnt]); cmdpos = cmdlen = STRLEN(buff); redrawcmd(); } continue; case Ctrl('V'): putcmdline('^', buff); c = get_literal(&nextc); /* get next (two) character(s) */ do_abbr = FALSE; /* don't do abbreviation now */ break; #ifdef DIGRAPHS case Ctrl('K'): putcmdline('?', buff); c = vgetc(); if (c == ESC) goto do_esc; if (charsize(c) == 1) putcmdline(c, buff); cc = vgetc(); if (cc == ESC) goto do_esc; c = getdigraph(c, cc, TRUE); break; #endif /* DIGRAPHS */ } /* we come here if we have a normal character */ if (do_abbr && !isidchar(c) && ccheck_abbr(c)) continue; if (cmdlen < CMDBUFFSIZE - 2) { for (i = cmdlen++; i > cmdpos; --i) buff[i] = buff[i - 1]; buff[cmdpos] = c; msg_outtrans(buff + cmdpos, cmdlen - cmdpos); ++cmdpos; i = charsize(c); cmdspos += i; } msg_check(); } retval = OK; /* when we get here we have a valid command line */ returncmd: buff[cmdlen] = NUL; /* * put line in history buffer */ if (cmdlen != 0) { if (hislen != 0) { if (++hisidx == hislen) hisidx = 0; free(history[hisidx]); history[hisidx] = strsave(buff); } if (firstc == ':') { free(new_last_cmdline); new_last_cmdline = strsave(buff); } } /* * If the screen was shifted up, redraw the whole screen (later). * If the line is too long, clear it, so ruler and shown command do * not get printed in the middle of it. */ msg_check(); State = NORMAL; return retval; } /* * put a character on the command line. * Used for CTRL-V and CTRL-K */ static void putcmdline(c, buff) int c; char_u *buff; { char_u buf[2]; buf[0] = c; buf[1] = 0; msg_outtrans(buf, 1); msg_outtrans(buff + cmdpos, cmdlen - cmdpos); cursorcmd(); } /* * this fuction is called when the screen size changes */ void redrawcmdline() { msg_scrolled = 0; compute_cmdrow(); redrawcmd(); cursorcmd(); } void compute_cmdrow() { cmdline_row = lastwin->w_winpos + lastwin->w_height + lastwin->w_status_height; } /* * Redraw what is currently on the command line. */ void redrawcmd() { register int i; msg_start(); msg_outchar(cmdfirstc); msg_outtrans(cmdbuff, cmdlen); msg_ceol(); cmdspos = 1; for (i = 0; i < cmdlen && i < cmdpos; ++i) cmdspos += charsize(cmdbuff[i]); } static void cursorcmd() { msg_pos(cmdline_row + (cmdspos / (int)Columns), cmdspos % (int)Columns); windgoto(msg_row, msg_col); } /* * Check the word in front of the cursor for an abbreviation. * Called when the non-id character "c" has been entered. * When an abbreviation is recognized it is removed from the text with * backspaces and the replacement string is inserted, followed by "c". */ static int ccheck_abbr(c) int c; { if (p_paste || no_abbr) /* no abbreviations or in paste mode */ return FALSE; return check_abbr(c, cmdbuff, cmdpos, 0); } /* * docmdline(): execute an Ex command line * * 1. If no line given, get one. * 2. Split up in parts separated with '|'. * * This function may be called recursively! * * return FAIL if commandline could not be executed, OK otherwise */ int docmdline(cmdline) char_u *cmdline; { char_u buff[CMDBUFFSIZE]; /* command line */ char_u *nextcomm; /* * 1. If no line given: get one. */ if (cmdline == NULL) { if (getcmdline(':', buff) == FAIL) return FAIL; } else { if (STRLEN(cmdline) > (size_t)(CMDBUFFSIZE - 2)) { emsg(e_toolong); return FAIL; } /* Make a copy of the command so we can mess with it. */ STRCPY(buff, cmdline); } /* * 2. Loop for each '|' separated command. * DoOneCmd will set nextcommand to NULL if there is no trailing '|'. */ for (;;) { nextcomm = DoOneCmd(buff); if (nextcomm == NULL) break; STRCPY(buff, nextcomm); } /* * If the command was typed, remember it for register : * Do this AFTER executing the command to make :@: work. */ if (cmdline == NULL && new_last_cmdline != NULL) { free(last_cmdline); last_cmdline = new_last_cmdline; new_last_cmdline = NULL; } return OK; } /* * Execute one Ex command. * * 2. skip comment lines and leading space * 3. parse range * 4. parse command * 5. parse arguments * 6. switch on command name * * This function may be called recursively! */ static char_u * DoOneCmd(buff) char_u *buff; { char_u cmdbuf[CMDBUFFSIZE]; /* for '%' and '#' expansion */ char_u c; register char_u *p; char_u *q; char_u *cmd, *arg; char_u *editcmd = NULL; /* +command arg. for doecmd() */ linenr_t doecmdlnum = 0; /* lnum in new file for doecmd() */ int i = 0; /* init to shut up gcc */ int cmdidx; int argt; register linenr_t lnum; long n; int addr_count; /* number of address specifications */ FPOS pos; int append = FALSE; /* write with append */ int usefilter = FALSE; /* filter instead of file name */ char_u *nextcomm = NULL; /* no next command yet */ int amount = 0; /* for ":>" and ":<"; init for gcc */ if (quitmore) --quitmore; /* when not editing the last file :q has to be typed twice */ /* * 2. skip comment lines and leading space, colons or bars */ for (cmd = buff; *cmd && strchr(" \t:|", *cmd) != NULL; cmd++) ; if (*cmd == '"' || *cmd == NUL) /* ignore comment and empty lines */ goto doend; /* * 3. parse a range specifier of the form: addr [,addr] [;addr] .. * * where 'addr' is: * * % (entire file) * $ [+-NUM] * 'x [+-NUM] (where x denotes a currently defined mark) * . [+-NUM] * [+-NUM].. * NUM * * The cmd pointer is updated to point to the first character following the * range spec. If an initial address is found, but no second, the upper bound * is equal to the lower. */ addr_count = 0; --cmd; do { ++cmd; /* skip ',' or ';' */ line1 = line2; line2 = curwin->w_cursor.lnum; /* default is current line number */ skipspace(&cmd); lnum = get_address(&cmd); if (lnum == MAXLNUM) { if (*cmd == '%') /* '%' - all lines */ { ++cmd; line1 = 1; line2 = curbuf->b_ml.ml_line_count; ++addr_count; } } else line2 = lnum; addr_count++; if (*cmd == ';') { if (line2 == 0) curwin->w_cursor.lnum = 1; else curwin->w_cursor.lnum = line2; } } while (*cmd == ',' || *cmd == ';'); /* One address given: set start and end lines */ if (addr_count == 1) { line1 = line2; /* ... but only implicit: really no address given */ if (lnum == MAXLNUM) addr_count = 0; } /* * 4. parse command */ skipspace(&cmd); /* * If we got a line, but no command, then go to the line. * If we find a '|' or '\n' we set nextcomm. */ if (*cmd == NUL || *cmd == '"' || ((*cmd == '|' || *cmd == '\n') && (nextcomm = cmd + 1) != NULL)) /* just an assignment */ { if (addr_count != 0) { /* * strange vi behaviour: ":3" jumps to line 3 * ":3|..." prints line 3 */ if (*cmd == '|') { cmdidx = CMD_print; goto cmdswitch; /* UGLY goto */ } if (line2 == 0) curwin->w_cursor.lnum = 1; else if (line2 > curbuf->b_ml.ml_line_count) curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; else curwin->w_cursor.lnum = line2; curwin->w_cursor.col = 0; cursupdate(); } goto doend; } /* * Isolate the command and search for it in the command table. * Exeptions: * - the 'k' command can directly be followed by any character. * - the 's' command can be followed directly by 'c', 'g' or 'r' * but :sre[wind] is another command. */ if (*cmd == 'k') { cmdidx = CMD_k; p = cmd + 1; } else if (*cmd == 's' && strchr("cgr", cmd[1]) != NULL && STRNCMP("sre", cmd, (size_t)3) != 0) { cmdidx = CMD_substitute; p = cmd + 1; } else { p = cmd; while (isalpha(*p)) ++p; if (p == cmd && strchr("@!=><&~#", *p) != NULL) /* non-alpha command */ ++p; i = (int)(p - cmd); for (cmdidx = 0; cmdidx < CMD_SIZE; ++cmdidx) if (STRNCMP(cmdnames[cmdidx].cmd_name, (char *)cmd, (size_t)i) == 0) break; if (i == 0 || cmdidx == CMD_SIZE) { emsg(e_invcmd); goto doend; } } if (*p == '!') /* forced commands */ { ++p; forceit = TRUE; } else forceit = FALSE; /* * 5. parse arguments */ argt = cmdnames[cmdidx].cmd_argt; if (!(argt & RANGE) && addr_count) { emsg(e_norange); goto doend; } /* * If the range is backwards, ask for confirmation and, if given, swap * line1 & line2 so it's forwards again. * When global command is busy, don't ask, will fail below. */ if (!global_busy && line1 > line2) { if (ask_yesno((char_u *)"Backwards range given, OK to swap") != 'y') goto doend; lnum = line1; line1 = line2; line2 = lnum; } /* * don't complain about the range if it is not used * (could happen if line_count is accidently set to 0) */ if (line1 < 0 || line2 < 0 || line1 > line2 || ((argt & RANGE) && !(argt & NOTADR) && line2 > curbuf->b_ml.ml_line_count)) { emsg(e_invrange); goto doend; } if ((argt & NOTADR) && addr_count == 0) /* default is 1, not cursor */ line2 = 1; if (!(argt & ZEROR)) /* zero in range not allowed */ { if (line1 == 0) line1 = 1; if (line2 == 0) line2 = 1; } /* * for the :make command we insert the 'makeprg' option here, * so things like % get expanded */ if (cmdidx == CMD_make) { if (STRLEN(p_mp) + STRLEN(p) + 2 >= (unsigned)CMDBUFFSIZE) { emsg(e_toolong); goto doend; } STRCPY(cmdbuf, p_mp); STRCAT(cmdbuf, " "); STRCAT(cmdbuf, p); STRCPY(buff, cmdbuf); p = buff; } arg = p; /* remember start of argument */ skipspace(&arg); if ((argt & NEEDARG) && *arg == NUL) { emsg(e_argreq); goto doend; } if (cmdidx == CMD_write) { if (*arg == '>') /* append */ { if (*++arg != '>') /* typed wrong */ { EMSG("Use w or w>>"); goto doend; } ++arg; skipspace(&arg); append = TRUE; } else if (*arg == '!') /* :w !filter */ { ++arg; usefilter = TRUE; } } if (cmdidx == CMD_read) { usefilter = forceit; /* :r! filter if forceit */ if (*arg == '!') /* :r !filter */ { ++arg; usefilter = TRUE; } } if (cmdidx == CMD_lshift || cmdidx == CMD_rshift) { amount = 1; while (*arg == *cmd) /* count number of '>' or '<' */ { ++arg; ++amount; } skipspace(&arg); } /* * Check for '|' to separate commands and '"' to start comments. * Don't do this for ":read !cmd" and ":write !cmd". */ if ((argt & TRLBAR) && !usefilter) { p = arg; while (*p) { if (*p == Ctrl('V')) { if ((argt & USECTRLV) && p[1] != NUL) /* skip CTRL-V and next char */ ++p; else /* remove CTRL-V and skip next char */ STRCPY(p, p + 1); } else if ((*p == '"' && !(argt & NOTRLCOM)) || *p == '|' || *p == '\n') { if (*(p - 1) == '\\') /* remove the backslash */ { STRCPY(p - 1, p); --p; } else { if (*p == '|' || *p == '\n') nextcomm = p + 1; *p = NUL; break; } } ++p; } if (!(argt & NOTRLCOM)) /* remove trailing spaces */ del_spaces(arg); } if ((argt & DFLALL) && addr_count == 0) { line1 = 1; line2 = curbuf->b_ml.ml_line_count; } regname = 0; /* accept numbered register only when no count allowed (:put) */ if ((argt & REGSTR) && *arg != NUL && is_yank_buffer(*arg, FALSE) && !((argt & COUNT) && isdigit(*arg))) { regname = *arg; ++arg; skipspace(&arg); } if ((argt & COUNT) && isdigit(*arg)) { n = getdigits(&arg); skipspace(&arg); if (n <= 0) { emsg(e_zerocount); goto doend; } if (argt & NOTADR) /* e.g. :buffer 2, :sleep 3 */ { line2 = n; if (addr_count == 0) addr_count = 1; } else { line1 = line2; line2 += n - 1; ++addr_count; } } if (!(argt & EXTRA) && strchr("|\"", *arg) == NULL) /* no arguments allowed */ { emsg(e_trailing); goto doend; } /* * change '%' to curbuf->b_filename, '#' to curwin->w_altfile */ if (argt & XFILE) { for (p = arg; *p; ++p) { c = *p; if (c != '%' && c != '#') /* nothing to expand */ continue; if (*(p - 1) == '\\') /* remove escaped char */ { STRCPY(p - 1, p); --p; continue; } if (c == '%') /* current file */ { if (check_fname() == FAIL) goto doend; q = curbuf->b_xfilename; n = 1; /* length of what we expand */ } else /* '#': alternate file */ { q = p + 1; i = (int)getdigits(&q); n = q - p; /* length of what we expand */ if (buflist_name_nr(i, &q, &doecmdlnum) == FAIL) { emsg(e_noalt); goto doend; } } i = STRLEN(arg) + STRLEN(q) + 3; if (nextcomm) i += STRLEN(nextcomm); if (i > CMDBUFFSIZE) { emsg(e_toolong); goto doend; } /* * we built the new argument in cmdbuf[], then copy it back to buff[] */ *p = NUL; /* truncate at the '#' or '%' */ STRCPY(cmdbuf, arg); /* copy up to there */ i = p - arg; /* remember the lenght */ STRCAT(cmdbuf, q); /* append the file name */ if (*(p + n) == '<') /* may remove extension */ { ++n; if ((arg = (char_u *)strrchr((char *)q, '.')) != NULL && arg >= gettail(q)) *(cmdbuf + (arg - q) + i) = NUL; } i = STRLEN(cmdbuf); /* remember the end of the filename */ STRCAT(cmdbuf, p+n); /* append what is after '#' or '%' */ p = buff + i - 1; /* remember where to continue */ if (nextcomm) /* append next command */ { i = STRLEN(cmdbuf) + 1; STRCPY(cmdbuf + i, nextcomm); nextcomm = buff + i; } STRCPY(buff, cmdbuf); /* copy back to buff[] */ arg = buff; } /* * One file argument: expand wildcards. * Don't do this with ":r !command" or ":w !command". */ if (argt & NOSPC) { if (has_wildcard(arg) && !usefilter) { if ((p = ExpandOne(arg, TRUE, -1)) == NULL) goto doend; if (STRLEN(p) + arg - buff < CMDBUFFSIZE - 2) STRCPY(arg, p); else emsg(e_toolong); free(p); } } } /* * 6. switch on command name */ cmdswitch: switch (cmdidx) { /* * quit current window, quit Vim if closed the last window */ case CMD_quit: /* if more files or windows we won't exit */ if (check_more(FALSE) == OK && firstwin == lastwin) exiting = TRUE; if (check_changed(curbuf, FALSE, FALSE) || check_more(TRUE) == FAIL || (firstwin == lastwin && check_changed_any(FALSE))) { exiting = FALSE; settmode(1); break; } if (firstwin == lastwin) /* quit last window */ getout(0); close_window(TRUE); /* may free buffer */ break; /* * try to quit all windows */ case CMD_qall: exiting = TRUE; if (!check_changed_any(FALSE)) getout(0); exiting = FALSE; settmode(1); break; /* * close current window, unless it is the last one */ case CMD_close: close_window(FALSE); /* don't free buffer */ break; /* * close all but current window, unless it is the last one */ case CMD_only: close_others(TRUE); break; case CMD_stop: case CMD_suspend: if (!forceit) autowrite_all(); gotocmdend(); flushbuf(); stoptermcap(); mch_restore_title(3); /* restore window titles */ mch_suspend(); /* call machine specific function */ maketitle(); starttermcap(); if (T_CVV != NULL && *T_CVV) { /* Scroll screen down before drawing over it */ outstr(T_CVV); outstr(T_CV); } must_redraw = CLEAR; break; case CMD_exit: case CMD_xit: case CMD_wq: /* if more files or windows we won't exit */ if (check_more(FALSE) == OK && firstwin == lastwin) exiting = TRUE; if (((cmdidx == CMD_wq || (curbuf->b_nwindows == 1 && curbuf->b_changed)) && (check_readonly() || dowrite(arg, FALSE) == FAIL)) || check_more(TRUE) == FAIL || (firstwin == lastwin && check_changed_any(FALSE))) { exiting = FALSE; settmode(1); break; } if (firstwin == lastwin) /* quit last window, exit Vim */ getout(0); close_window(TRUE); /* quit current window, may free buffer */ break; case CMD_xall: /* write all changed files and exit */ case CMD_wqall: /* write all changed files and quit */ exiting = TRUE; /* FALLTHROUGH */ case CMD_wall: /* write all changed files */ { BUF *buf; int error = 0; for (buf = firstbuf; buf != NULL; buf = buf->b_next) { if (buf->b_changed) { if (buf->b_filename == NULL) { emsg(e_noname); ++error; } else if (!forceit && buf->b_p_ro) { EMSG2("\"%s\" is readonly, use ! to write anyway", buf->b_xfilename); ++error; } else if (buf_write_all(buf) == FAIL) ++error; } } if (exiting) { if (!error) getout(0); /* exit Vim */ exiting = FALSE; settmode(1); } } break; case CMD_preserve: /* put everything in .swp file */ ml_preserve(curbuf, TRUE); break; case CMD_args: /* * ":args file": handle like :next */ if (*arg != NUL && *arg != '|' && *arg != '\n') goto do_next; nextcomm = checknextcomm(arg); /* check for trailing command */ if (arg_count == 0) /* no file name list */ { if (check_fname() == OK) /* check for no file name at all */ smsg((char_u *)"[%s]", curbuf->b_filename); break; } gotocmdline(TRUE, NUL); for (i = 0; i < arg_count; ++i) { if (i == curwin->w_arg_idx) msg_outchar('['); msg_outstr(arg_files[i]); if (i == curwin->w_arg_idx) msg_outchar(']'); msg_outchar(' '); } if (msg_check()) /* if message too long */ { msg_outchar('\n'); wait_return(FALSE); } break; case CMD_wnext: case CMD_wNext: case CMD_wprevious: if (cmd[1] == 'n') i = curwin->w_arg_idx + (int)line2; else i = curwin->w_arg_idx - (int)line2; line1 = 1; line2 = curbuf->b_ml.ml_line_count; if (dowrite(arg, FALSE) == FAIL) break; goto donextfile; case CMD_next: case CMD_snext: do_next: /* * check for changed buffer now, if this fails the * argument list is not redefined. */ if (!(p_hid || cmdidx == CMD_snext) && check_changed(curbuf, TRUE, FALSE)) break; editcmd = getargcmd(&arg); /* get +command argument */ nextcomm = checknextcomm(arg); /* check for trailing command */ if (*arg != NUL) /* redefine file list */ { if (doarglist(arg) == FAIL) break; i = 0; } else i = curwin->w_arg_idx + (int)line2; donextfile: if (i < 0 || i >= arg_count) { if (arg_count == 1) EMSG("There is only one file to edit"); else if (i < 0) EMSG("Cannot go before first file"); else EMSG2("No more than %ld files to edit", (char_u *)(long)arg_count); break; } if (*cmd == 's') /* split window first */ { if (win_split(0L, FALSE) == FAIL) break; } else { register int other = FALSE; /* * if 'hidden' set, only check for changed file when re-editing * the same buffer */ other = TRUE; if (p_hid) other = otherfile(fix_fname(arg_files[i])); if ((!p_hid || !other) && check_changed(curbuf, TRUE, !other)) break; } curwin->w_arg_idx = i; (void)doecmd(arg_files[curwin->w_arg_idx], NULL, editcmd, p_hid, (linenr_t)0); break; case CMD_previous: case CMD_sprevious: case CMD_Next: case CMD_sNext: i = curwin->w_arg_idx - (int)line2; goto doargument; case CMD_rewind: case CMD_srewind: i = 0; goto doargument; case CMD_last: case CMD_slast: i = arg_count - 1; goto doargument; case CMD_argument: case CMD_sargument: if (addr_count) i = line2 - 1; else i = curwin->w_arg_idx; doargument: editcmd = getargcmd(&arg); /* get +command argument */ nextcomm = checknextcomm(arg); /* check for trailing command */ goto donextfile; case CMD_all: case CMD_sall: do_arg_all(); /* open a window for each argument */ break; case CMD_buffer: /* :[N]buffer [N] to buffer N */ case CMD_sbuffer: /* :[N]sbuffer [N] to buffer N */ if (addr_count == 0) /* default is current buffer */ (void)do_buffer(*cmd == 's', 0, FORWARD, 0, 0); else (void)do_buffer(*cmd == 's', 1, FORWARD, (int)line2, 0); break; case CMD_bmodified: /* :[N]bmod [N] to next modified buffer */ case CMD_sbmodified: /* :[N]sbmod [N] to next modified buffer */ (void)do_buffer(*cmd == 's', 3, FORWARD, (int)line2, 0); break; case CMD_bnext: /* :[N]bnext [N] to next buffer */ case CMD_sbnext: /* :[N]sbnext [N] to next buffer */ (void)do_buffer(*cmd == 's', 0, FORWARD, (int)line2, 0); break; case CMD_bNext: /* :[N]bNext [N] to previous buffer */ case CMD_bprevious: /* :[N]bprevious [N] to previous buffer */ case CMD_sbNext: /* :[N]sbNext [N] to previous buffer */ case CMD_sbprevious: /* :[N]sbprevious [N] to previous buffer */ (void)do_buffer(*cmd == 's', 0, BACKWARD, (int)line2, 0); break; case CMD_brewind: /* :brewind to first buffer */ case CMD_sbrewind: /* :sbrewind to first buffer */ (void)do_buffer(*cmd == 's', 1, FORWARD, 0, 0); break; case CMD_blast: /* :blast to last buffer */ case CMD_sblast: /* :sblast to last buffer */ (void)do_buffer(*cmd == 's', 2, FORWARD, 0, 0); break; case CMD_bunload: /* :[N]bunload[!] [N] unload buffer */ i = 2; case CMD_bdelete: /* :[N]bdelete[!] [N] delete buffer */ if (cmdidx == CMD_bdelete) i = 3; /* * addr_count == 0: ":bdel" - delete current buffer * addr_count == 1: ":N bdel" or ":bdel N [N ..] - first delete * buffer 'line2', then any other arguments. * addr_count == 2: ":N,N bdel" - delete buffers in range */ if (addr_count == 0) (void)do_buffer(i, 0, FORWARD, 0, forceit); else { int do_current = FALSE; /* delete current buffer? */ if (addr_count == 2) n = line1; else n = line2; for ( ;!got_int; breakcheck()) { /* * delete the current buffer last, otherwise when the * current buffer is deleted, the next buffer becomes * the current one and will be loaded, which may then * also be deleted, etc. */ if (n == curbuf->b_fnum) do_current = TRUE; else (void)do_buffer(i, 1, FORWARD, (int)n, forceit); if (addr_count == 2) { if (++n > line2) break; } else { skipspace(&arg); if (*arg == NUL) break; if (!isdigit(*arg)) { emsg(e_trailing); break; } n = getdigits(&arg); } } if (!got_int && do_current) (void)do_buffer(i, 1, FORWARD, (int)curbuf->b_fnum, forceit); } break; case CMD_unhide: case CMD_sunhide: (void)do_buffer_all(FALSE); /* open a window for loaded buffers */ break; case CMD_ball: case CMD_sball: (void)do_buffer_all(TRUE); /* open a window for every buffer */ break; case CMD_buffers: case CMD_files: buflist_list(); break; case CMD_write: if (usefilter) /* input lines to shell command */ dofilter(line1, line2, arg, TRUE, FALSE); else (void)dowrite(arg, append); break; /* * set screen mode * if no argument given, just get the screen size and redraw */ case CMD_mode: if (*arg == NUL || mch_screenmode(arg) != FAIL) set_winsize(0, 0, FALSE); break; /* * set, increment or decrement current window height */ case CMD_resize: n = atol((char *)arg); if (*arg == '-' || *arg == '+') win_setheight(curwin->w_height + (int)n); else { if (n == 0) /* default is very high */ n = 9999; win_setheight((int)n); } break; /* * :split [[+command] file] split window with current or new file * :new [[+command] file] split window with no or new file */ case CMD_split: case CMD_new: if (win_split(addr_count ? line2 : 0L, FALSE) == FAIL) break; /*FALLTHROUGH*/ case CMD_edit: case CMD_ex: case CMD_visual: editcmd = getargcmd(&arg); /* get +command argument */ nextcomm = checknextcomm(arg); /* check for trailing command */ if ((cmdidx == CMD_new) && *arg == NUL) (void)doecmd(NULL, NULL, editcmd, TRUE, (linenr_t)1); else if (cmdidx != CMD_split || *arg != NUL) (void)doecmd(arg, NULL, editcmd, p_hid, doecmdlnum); else updateScreen(NOT_VALID); break; case CMD_file: if (*arg != NUL) { if (setfname(arg, NULL, TRUE) == FAIL) break; curbuf->b_notedited = TRUE; maketitle(); } fileinfo(did_cd); /* print full filename if :cd used */ break; case CMD_swapname: p = curbuf->b_ml.ml_mfp->mf_fname; if (p == NULL) MSG("No swap file"); else msg(p); break; case CMD_mfstat: /* print memfile statistics, for debugging */ mf_statistics(); break; case CMD_read: if (usefilter) { dofilter(line1, line2, arg, FALSE, TRUE); /* :r!cmd */ break; } if (!u_save(line2, (linenr_t)(line2 + 1))) break; if (*arg == NUL) { if (check_fname() == FAIL) /* check for no file name at all */ break; i = readfile(curbuf->b_filename, curbuf->b_sfilename, line2, FALSE, (linenr_t)0, MAXLNUM); } else i = readfile(arg, NULL, line2, FALSE, (linenr_t)0, MAXLNUM); if (i == FAIL) { emsg2(e_notopen, arg); break; } updateScreen(NOT_VALID); break; case CMD_cd: case CMD_chdir: #ifdef UNIX /* * for UNIX ":cd" means: go to home directory */ if (*arg == NUL) /* use cmdbuf for home directory name */ { expand_env("$HOME", cmdbuf, CMDBUFFSIZE); arg = cmdbuf; } #endif if (*arg != NUL) { if (!did_cd) { BUF *buf; /* use full path from now on for names of files * being edited and swap files */ for (buf = firstbuf; buf != NULL; buf = buf->b_next) { buf->b_xfilename = buf->b_filename; mf_fullname(buf->b_ml.ml_mfp); } status_redraw_all(); } did_cd = TRUE; if (chdir((char *)arg)) emsg(e_failed); break; } /*FALLTHROUGH*/ case CMD_pwd: if (vim_dirname(NameBuff, MAXPATHL) == OK) msg(NameBuff); else emsg(e_unknown); break; case CMD_equal: smsg((char_u *)"line %ld", (long)line2); break; case CMD_list: i = curwin->w_p_list; curwin->w_p_list = 1; case CMD_number: /* :nu */ case CMD_pound: /* :# */ case CMD_print: /* :p */ gotocmdline(TRUE, NUL); cursor_off(); for ( ;!got_int; breakcheck()) { if (curwin->w_p_nu || cmdidx == CMD_number || cmdidx == CMD_pound) { sprintf((char *)IObuff, "%7ld ", (long)line1); msg_outstr(IObuff); } msg_prt_line(ml_get(line1)); if (++line1 > line2) break; msg_outchar('\n'); flushbuf(); /* show one line at a time */ } if (cmdidx == CMD_list) curwin->w_p_list = i; /* * if we have one line that runs into the shown command, * or more than one line, call wait_return() * also do this when global_busy, so we remember to call * wait_return at the end of the global command. */ if (msg_check() || global_busy) { msg_outchar('\n'); wait_return(FALSE); } break; case CMD_shell: doshell(NULL); break; case CMD_sleep: sleep((int)line2); break; case CMD_tag: dotag(arg, 0, addr_count ? (int)line2 : 1); break; case CMD_pop: dotag((char_u *)"", 1, addr_count ? (int)line2 : 1); break; case CMD_tags: dotags(); break; case CMD_marks: domarks(); break; case CMD_jumps: dojumps(); break; case CMD_digraphs: #ifdef DIGRAPHS if (*arg) putdigraph(arg); else listdigraphs(); #else EMSG("No digraphs in this version"); #endif /* DIGRAPHS */ break; case CMD_set: (void)doset(arg); break; case CMD_abbreviate: case CMD_cabbrev: case CMD_iabbrev: case CMD_cnoreabbrev: case CMD_inoreabbrev: case CMD_noreabbrev: case CMD_unabbreviate: case CMD_cunabbrev: case CMD_iunabbrev: i = ABBREV; goto doabbr; /* almost the same as mapping */ case CMD_cmap: case CMD_imap: case CMD_map: case CMD_cnoremap: case CMD_inoremap: case CMD_noremap: /* * If we are sourcing .exrc or .vimrc in current directory we * print the mappings for security reasons. */ if (secure) { secure = 2; msg_outtrans(cmd, -1); msg_outchar('\n'); } case CMD_cunmap: case CMD_iunmap: case CMD_unmap: i = 0; doabbr: if (*cmd == 'c') /* cmap, cunmap, cnoremap, etc. */ { i += CMDLINE; ++cmd; } else if (*cmd == 'i') /* imap, iunmap, inoremap, etc. */ { i += INSERT; ++cmd; } else if (forceit || i) /* map!, unmap!, noremap!, abbrev */ i += INSERT + CMDLINE; else i += NORMAL; /* map, unmap, noremap */ switch (domap((*cmd == 'n') ? 2 : (*cmd == 'u'), arg, i)) { case 1: emsg(e_invarg); break; case 2: emsg(e_nomap); break; case 3: emsg(e_ambmap); break; } break; case CMD_display: dodis(); /* display buffer contents */ break; case CMD_help: help(); break; case CMD_version: msg(longVersion); break; case CMD_winsize: /* obsolete command */ line1 = getdigits(&arg); skipspace(&arg); line2 = getdigits(&arg); set_winsize((int)line1, (int)line2, TRUE); break; case CMD_delete: case CMD_yank: case CMD_rshift: case CMD_lshift: yankbuffer = regname; curbuf->b_startop.lnum = line1; curbuf->b_endop.lnum = line2; nlines = line2 - line1 + 1; mtype = MLINE; curwin->w_cursor.lnum = line1; switch (cmdidx) { case CMD_delete: dodelete(); break; case CMD_yank: (void)doyank(FALSE); curwin->w_cursor.lnum = line2; /* put cursor on last line */ break; case CMD_rshift: doshift(RSHIFT, FALSE, amount); break; case CMD_lshift: doshift(LSHIFT, FALSE, amount); break; } break; case CMD_put: yankbuffer = regname; curwin->w_cursor.lnum = line2; doput(forceit ? BACKWARD : FORWARD, -1L, FALSE); break; case CMD_t: case CMD_copy: case CMD_move: n = get_address(&arg); /* * move or copy lines from 'line1'-'line2' to below line 'n' */ if (n == MAXLNUM || n < 0 || n > curbuf->b_ml.ml_line_count) { emsg(e_invaddr); break; } if (cmdidx == CMD_move) { if (do_move(line1, line2, n) == FAIL) break; } else do_copy(line1, line2, n); u_clearline(); curwin->w_cursor.col = 0; updateScreen(NOT_VALID); break; case CMD_and: /* :& */ case CMD_tilde: /* :~ */ case CMD_substitute: /* :s */ dosub(line1, line2, arg, &nextcomm, cmdidx == CMD_substitute ? 0 : cmdidx == CMD_and ? 1 : 2); break; case CMD_join: curwin->w_cursor.lnum = line1; if (line1 == line2) { if (addr_count >= 2) /* :2,2join does nothing */ break; if (line2 == curbuf->b_ml.ml_line_count) { beep(); break; } ++line2; } dodojoin(line2 - line1 + 1, !forceit, TRUE); break; case CMD_global: if (forceit) *cmd = 'v'; case CMD_vglobal: doglob(*cmd, line1, line2, arg); break; case CMD_at: /* :[addr]@r */ curwin->w_cursor.lnum = line2; if (doexecbuf(*arg) == FAIL) /* put the register in mapbuf */ beep(); else (void)docmdline((char_u *)NULL); /* execute from the mapbuf */ break; case CMD_bang: dobang(addr_count, line1, line2, forceit, arg); break; case CMD_undo: u_undo(1); break; case CMD_redo: u_redo(1); break; case CMD_source: if (forceit) /* :so! read vi commands */ (void)openscript(arg); else { ++no_wait_return; if (dosource(arg) == FAIL) /* :so read ex commands */ emsg2(e_notopen, arg); --no_wait_return; if (need_wait_return) wait_return(FALSE); } break; case CMD_mkvimrc: if (*arg == NUL) arg = (char_u *)VIMRC_FILE; /*FALLTHROUGH*/ case CMD_mkexrc: { FILE *fd; if (*arg == NUL) arg = (char_u *)EXRC_FILE; #ifdef UNIX /* with Unix it is possible to open a directory */ if (isdir(arg) == TRUE) { EMSG2("\"%s\" is a directory", arg); break; } #endif if (!forceit && (fd = fopen((char *)arg, "r")) != NULL) { fclose(fd); EMSG2("\"%s\" exists (use ! to override)", arg); break; } if ((fd = fopen((char *)arg, "w")) == NULL) { EMSG2("Cannot open \"%s\" for writing", arg); break; } if (makemap(fd) == FAIL || makeset(fd) == FAIL || fclose(fd)) emsg(e_write); break; } case CMD_cc: qf_jump(0, atoi((char *)arg)); break; case CMD_cf: if (*arg != NUL) { /* * Great trick: Insert 'ef=' before arg. * Always ok, because "cf " must be there. */ arg -= 3; arg[0] = 'e'; arg[1] = 'f'; arg[2] = '='; (void)doset(arg); } (void)qf_init(); break; case CMD_cl: qf_list(); break; case CMD_cn: qf_jump(FORWARD, *arg == NUL ? 1 : atoi((char *)arg)); break; case CMD_cp: qf_jump(BACKWARD, *arg == NUL ? 1 : atoi((char *)arg)); break; case CMD_cq: getout(1); /* this does not always work. why? */ case CMD_mark: case CMD_k: pos = curwin->w_cursor; /* save curwin->w_cursor */ curwin->w_cursor.lnum = line2; curwin->w_cursor.col = 0; (void)setmark(*arg); /* set mark */ curwin->w_cursor = pos; /* restore curwin->w_cursor */ break; #ifdef SETKEYMAP case CMD_setkeymap: set_keymap(arg); break; #endif case CMD_center: case CMD_right: case CMD_left: do_align(line1, line2, atoi((char *)arg), cmdidx == CMD_center ? 0 : cmdidx == CMD_right ? 1 : -1); break; case CMD_make: domake(arg); break; default: emsg(e_invcmd); } doend: forceit = FALSE; /* reset now so it can be used in getfile() */ if (nextcomm && *nextcomm == NUL) /* not really a next command */ nextcomm = NULL; return nextcomm; } /* * if 'autowrite' option set, try to write the file * * return FAIL for failure, OK otherwise */ int autowrite(buf) BUF *buf; { if (!p_aw || (!forceit && buf->b_p_ro) || buf->b_filename == NULL) return FAIL; return buf_write_all(buf); } /* * flush all buffers, except the ones that are readonly */ void autowrite_all() { BUF *buf; if (!p_aw) return; for (buf = firstbuf; buf; buf = buf->b_next) if (buf->b_changed && !buf->b_p_ro) (void)buf_write_all(buf); } /* * flush the contents of a buffer, unless it has no file name * * return FAIL for failure, OK otherwise */ static int buf_write_all(buf) BUF *buf; { return (buf_write(buf, buf->b_filename, buf->b_sfilename, (linenr_t)1, buf->b_ml.ml_line_count, 0, 0, TRUE)); } /* * write current buffer to file 'fname' * if 'append' is TRUE, append to the file * * if *fname == NUL write to current file * if b_notedited is TRUE, check for overwriting current file * * return FAIL for failure, OK otherwise */ static int dowrite(fname, append) char_u *fname; int append; { FILE *fd; int other; char_u *sfname = NULL; /* init to shut up gcc */ if (*fname == NUL) other = FALSE; else { sfname = fname; fname = fix_fname(fname); other = otherfile(fname); } /* * if we have a new file name put it in the list of alternate file names */ if (other) setaltfname(fname, sfname, (linenr_t)1); /* * writing to the current file is not allowed in readonly mode * and need a file name */ if (!other && (check_readonly() || check_fname() == FAIL)) return FAIL; if (!other) { fname = curbuf->b_filename; sfname = curbuf->b_sfilename; } /* * write to other file or b_notedited set: overwriting only allowed with '!' */ if ((other || curbuf->b_notedited) && !forceit && !append && !p_wa && (fd = fopen((char *)fname, "r")) != NULL) { /* don't overwrite existing file */ fclose(fd); #ifdef UNIX /* with UNIX it is possible to open a directory */ if (isdir(fname) == TRUE) EMSG2("\"%s\" is a directory", fname); else #endif emsg(e_exists); return FAIL; } return (buf_write(curbuf, fname, sfname, line1, line2, append, forceit, TRUE)); } /* * start editing a new file * * fname: the file name * - full path if sfname used, * - any file name if sfname is NULL * - empty string to re-edit with the same file name (but may be * in a different directory) * - NULL to start an empty buffer * sfname: the short file name (or NULL) * command: the command to be executed after loading the file * hide: if TRUE don't free the current buffer * newlnum: put cursor on this line number (if possible) * * return FAIL for failure, OK otherwise */ int doecmd(fname, sfname, command, hide, newlnum) char_u *fname; char_u *sfname; char_u *command; int hide; linenr_t newlnum; { int other_file; /* TRUE if editing another file */ int oldbuf = FALSE; /* TRUE if using existing buffer */ BUF *buf; /* if no short name given, use fname for short name */ if (sfname == NULL) sfname = fname; if (fname == NULL) other_file = TRUE; else if (*fname == NUL && curbuf->b_filename == NULL) /* there is no file name */ other_file = FALSE; else { if (*fname == NUL) /* re-edit with same file name */ { fname = curbuf->b_filename; sfname = curbuf->b_sfilename; } fname = fix_fname(fname); /* may expand to full path name */ other_file = otherfile(fname); } /* * if the file was changed we may not be allowed to abandon it * - if we are going to re-edit the same file * - or if we are the only window on this file and if hide is FALSE */ if ((!other_file || (curbuf->b_nwindows == 1 && !hide)) && check_changed(curbuf, FALSE, !other_file)) { if (other_file && fname != NULL) setaltfname(fname, sfname, (linenr_t)1); return FAIL; } /* * If we are starting to edit another file, open a (new) buffer. * Otherwise we re-use the current buffer. */ if (other_file) { curwin->w_alt_fnum = curbuf->b_fnum; buflist_altlnum(); buf = buflist_new(fname, sfname, 1L, TRUE); if (buf == NULL) return FAIL; if (buf->b_ml.ml_mfp == NULL) /* no memfile yet */ { oldbuf = FALSE; buf->b_nwindows = 1; } else /* existing memfile */ { oldbuf = TRUE; ++buf->b_nwindows; } /* * make the (new) buffer the one used by the current window * if the old buffer becomes unused, free it if hide is FALSE * If the current buffer was empty and has no file name, curbuf * is returned by buflist_new(). */ if (buf != curbuf) { close_buffer(curbuf, !hide, FALSE); curwin->w_buffer = buf; curbuf = buf; } curwin->w_pcmark.lnum = 1; curwin->w_pcmark.col = 0; } else if (check_fname() == FAIL) return FAIL; /* * If we get here we are sure to start editing */ /* don't redraw until the cursor is in the right line */ ++RedrawingDisabled; /* * other_file oldbuf * FALSE FALSE re-edit same file, buffer is re-used * FALSE TRUE not posible * TRUE FALSE start editing new file, new buffer * TRUE TRUE start editing in existing buffer (nothing to do) */ if (!other_file) /* re-use the buffer */ { if (newlnum == 0) newlnum = curwin->w_cursor.lnum; buf_freeall(curbuf); /* free all things for buffer */ buf_clear(curbuf); curbuf->b_startop.lnum = 0; /* clear '[ and '] marks */ curbuf->b_endop.lnum = 0; } if (!oldbuf) /* need to read the file */ (void)open_buffer(); win_init(curwin); maketitle(); if (newlnum && command == NULL) { curwin->w_cursor.lnum = newlnum; curwin->w_cursor.col = 0; } check_cursor(); /* * Did not read the file, need to show some info about the file. * Do this after setting the cursor. */ if (oldbuf) fileinfo(did_cd); if (command != NULL) docmdline(command); --RedrawingDisabled; if (!skip_redraw) updateScreen(CURSUPD); /* redraw now */ if (p_im) stuffReadbuff((char_u *)"i"); /* start editing in insert mode */ return OK; } /* * get + command from ex argument */ static char_u * getargcmd(argp) char_u **argp; { char_u *arg = *argp; char_u *command = NULL; if (*arg == '+') /* +[command] */ { ++arg; if (isspace(*arg)) command = (char_u *)"$"; else { command = arg; /* * should check for "\ " (but vi has a bug that prevents it to work) */ skiptospace(&arg); } if (*arg) *arg++ = NUL; /* terminate command with NUL */ skipspace(&arg); /* skip over spaces */ *argp = arg; } return command; } /* * look for command separator '|' or '\n' */ static char_u * checknextcomm(arg) char_u *arg; { char_u *p; char_u *nextcomm = NULL; for (p = arg; *p; ++p) { if (*p == '\\' && p[1]) ++p; else if (*p == '|' || *p == '\n') { nextcomm = p + 1; /* remember start of next command */ *p = NUL; /* delete '|' or '\n' */ del_spaces(arg); /* delete spaces in front of '|' or '\n' */ break; } } return nextcomm; } static void domake(arg) char_u *arg; { if (*p_ef == NUL) { EMSG("errorfile option not set"); return; } if (curbuf->b_changed) (void)autowrite(curbuf); remove((char *)p_ef); outchar(':'); outstr(arg); /* show what we are doing */ sprintf((char *)IObuff, "%s %s %s", arg, p_sp, p_ef); doshell(IObuff); #ifdef AMIGA flushbuf(); vpeekc(); /* read window status report and redraw before message */ #endif (void)qf_init(); remove((char *)p_ef); } /* * Redefine the argument list to 'str'. * * Return FAIL for failure, OK otherwise. */ static int doarglist(str) char_u *str; { int new_count = 0; char_u **new_files = NULL; int exp_count; char_u **exp_files; char_u **t; char_u *p; int inquote; int i; while (*str) { /* * create a new entry in new_files[] */ t = (char_u **)lalloc((long_u)(sizeof(char_u *) * (new_count + 1)), TRUE); if (t != NULL) for (i = new_count; --i >= 0; ) t[i] = new_files[i]; free(new_files); if (t == NULL) return FAIL; new_files = t; new_files[new_count++] = str; /* * isolate one argument, taking quotes */ inquote = FALSE; for (p = str; *str; ++str) { /* * for MSDOS a backslash is part of a file name. * Only skip ", space and tab. */ #ifdef MSDOS if (*str == '\\' && (str[1] == '"' || str[1] == ' ' || str[1] == '\t')) #else if (*str == '\\' && str[1] != NUL) #endif *p++ = *++str; else { if (!inquote && isspace(*str)) break; if (*str == '"') inquote ^= TRUE; else *p++ = *str; } } skipspace(&str); *p = NUL; } i = ExpandWildCards(new_count, new_files, &exp_count, &exp_files, FALSE, TRUE); free(new_files); if (i == FAIL) return FAIL; if (exp_count == 0) { emsg(e_nomatch); return FAIL; } if (arg_exp) /* arg_files[] has been allocated, free it */ FreeWild(arg_count, arg_files); else arg_exp = TRUE; arg_files = exp_files; arg_count = exp_count; /* * put all file names in the buffer list */ for (i = 0; i < arg_count; ++i) (void)buflist_add(arg_files[i]); return OK; } void gotocmdline(clr, firstc) int clr; int firstc; { msg_start(); if (clr) /* clear the bottom line(s) */ msg_ceol(); /* will reset clear_cmdline */ windgoto(cmdline_row, 0); if (firstc) msg_outchar(firstc); } void gotocmdend() { windgoto((int)Rows - 1, 0); outchar('\n'); } static int check_readonly() { if (!forceit && curbuf->b_p_ro) { emsg(e_readonly); return TRUE; } return FALSE; } /* * return TRUE if buffer was changed and cannot be abandoned. */ static int check_changed(buf, checkaw, mult_win) BUF *buf; int checkaw; /* do autowrite if buffer was changed */ int mult_win; /* check also when several windows for this buffer */ { if ( !forceit && buf->b_changed && (mult_win || buf->b_nwindows <= 1) && (!checkaw || autowrite(buf) == FAIL)) { emsg(e_nowrtmsg); return TRUE; } return FALSE; } /* * return TRUE if any buffer was changed and cannot be abandoned. */ static int check_changed_any(checkaw) int checkaw; /* do autowrite if buffer was changed */ { BUF *buf; if (!forceit) { for (buf = firstbuf; buf != NULL; buf = buf->b_next) { if (buf->b_changed && (!checkaw || autowrite(buf) == FAIL)) { EMSG2("No write since last change for buffer \"%s\"", buf->b_xfilename == NULL ? (char_u *)"No File" : buf->b_xfilename); return TRUE; } } } return FALSE; } /* * return FAIL if there is no filename, OK if there is one * give error message for FAIL */ int check_fname() { if (curbuf->b_filename == NULL) { emsg(e_noname); return FAIL; } return OK; } /* * - if there are more files to edit * - and this is the last window * - and forceit not used * - and not repeated twice on a row * return FAIL and give error message if 'message' TRUE * return OK otherwise */ static int check_more(message) int message; /* when FALSE check only, no messages */ { if (!forceit && firstwin == lastwin && curwin->w_arg_idx + 1 < arg_count && quitmore == 0) { if (message) { emsg2((char_u *)"%ld more files to edit", (char_u *)(long)(arg_count - curwin->w_arg_idx - 1)); quitmore = 2; /* next try to quit is allowed */ } return FAIL; } return OK; } /* * try to abandon current file and edit "fname" * return 1 for "normal" error, 2 for "not written" error, 0 for success * -1 for succesfully opening another file * 'lnum' is the line number for the cursor in the new file (if non-zero). */ int getfile(fname, sfname, setpm, lnum) char_u *fname; char_u *sfname; int setpm; linenr_t lnum; { int other; fname_expand(&fname, &sfname); /* make fname full path and set sfname */ other = otherfile(fname); if (other && !forceit && curbuf->b_nwindows == 1 && !p_hid && curbuf->b_changed && autowrite(curbuf) == FAIL) { emsg(e_nowrtmsg); return 2; /* file has been changed */ } if (setpm) setpcmark(); if (!other) { if (lnum != 0) curwin->w_cursor.lnum = lnum; check_cursor(); curwin->w_cursor.col = 0; return 0; /* it's in the same file */ } if (doecmd(fname, sfname, NULL, p_hid, lnum) == OK) return -1; /* opened another file */ return 1; /* error encountered */ } #ifdef WEBB_COMPLETE /* * vim_strncpy() * * This is here because strncpy() does not guarantee successful results when * the to and from strings overlap. It is only currently called from nextwild() * which copies part of the command line to another part of the command line. * This produced garbage when expanding files etc in the middle of the command * line (on my terminal, anyway) -- webb. */ static void vim_strncpy(to, from, len) char_u *to; char_u *from; int len; { int i; if (to <= from) { while (len-- && *from) *to++ = *from++; if (len >= 0) *to = *from; /* Copy NUL */ } else { for (i = 0; i < len; i++) { to++; if (*from++ == NUL) { i++; break; } } for (; i > 0; i--) *--to = *--from; } } /* Return FALSE if this is not an appropriate context in which to do * completion of anything, & TRUE if it is (even if there are no matches). For * the caller, this means that the character is just passed through like a * normal character (instead of being expanded). This allows :s/^I^D etc. */ static int #else static void #endif /* WEBB_COMPLETE */ nextwild(buff, type) char_u *buff; int type; { int i; char_u *p1; char_u *p2 = NULL; int oldlen; int difflen; #ifdef WEBB_COMPLETE if (cmd_numfiles == -1) set_expand_context(cmdfirstc, cmdbuff); if (expand_context == EXPAND_UNSUCCESSFUL) { beep(); return OK; /* Something illegal on command line */ } if (expand_context == EXPAND_NOTHING) { /* Caller can use the character as a normal char instead */ return FAIL; } expand_interactively = TRUE; #endif /* WEBB_COMPLETE */ msg_outstr((char_u *)"..."); /* show that we are busy */ flushbuf(); #ifdef WEBB_COMPLETE i = expand_pattern - buff; #else for (i = cmdpos; i > 0 && buff[i - 1] != ' '; --i) ; #endif /* WEBB_COMPLETE */ oldlen = cmdpos - i; /* add a "*" to the file name and expand it */ if ((p1 = addstar(&buff[i], oldlen)) != NULL) { if ((p2 = ExpandOne(p1, FALSE, type)) != NULL) { if (cmdlen + (difflen = STRLEN(p2) - oldlen) > CMDBUFFSIZE - 4) emsg(e_toolong); else { #ifdef WEBB_COMPLETE vim_strncpy(&buff[cmdpos + difflen], &buff[cmdpos], cmdlen - cmdpos); #else STRNCPY(&buff[cmdpos + difflen], &buff[cmdpos], (size_t)(cmdlen - cmdpos)); #endif /* WEBB_COMPLETE */ STRNCPY(&buff[i], p2, STRLEN(p2)); cmdlen += difflen; cmdpos += difflen; } free(p2); } free(p1); } redrawcmd(); #ifdef WEBB_COMPLETE if (cmd_numfiles <= 0 && p2 == NULL) beep(); else if (cmd_numfiles == 1) { (void)ExpandOne(NULL, FALSE, -2); /* free expanded "file" names */ cmd_numfiles = -1; } expand_interactively = FALSE; return OK; #endif /* WEBB_COMPLETE */ } /* * Do wildcard expansion on the string 'str'. * Return a pointer to alloced memory containing the new string. * Return NULL for failure. * * mode = -2: only release file names * mode = -1: normal expansion, do not keep file names * mode = 0: normal expansion, keep file names * mode = 1: use next match in multiple match * mode = 2: use previous match in multiple match * mode = 3: use next match in multiple match and wrap to first * mode = 4: return all matches concatenated * mode = 5: return longest matched part */ char_u * ExpandOne(str, list_notfound, mode) char_u *str; int list_notfound; int mode; { char_u *ss = NULL; static char_u **cmd_files = NULL; /* list of input files */ static int findex; int i, found = 0; int multmatch = FALSE; long_u len; char_u *filesuf, *setsuf, *nextsetsuf; int filesuflen, setsuflen; /* * first handle the case of using an old match */ if (mode >= 1 && mode < 4) { if (cmd_numfiles > 0) { if (mode == 2) --findex; else /* mode == 1 || mode == 3 */ ++findex; if (findex < 0) findex = 0; if (findex > cmd_numfiles - 1) { if (mode == 3) findex = 0; else findex = cmd_numfiles - 1; } return strsave(cmd_files[findex]); } else return NULL; } /* free old names */ if (cmd_numfiles != -1 && mode < 4) { FreeWild(cmd_numfiles, cmd_files); cmd_numfiles = -1; } findex = 0; if (mode == -2) /* only release file name */ return NULL; if (cmd_numfiles == -1) { #ifdef WEBB_COMPLETE if (ExpandFromContext((char_u *)str, &cmd_numfiles, &cmd_files, FALSE, list_notfound) == FAIL) /* error: do nothing */; else if (cmd_numfiles == 0) { if (!expand_interactively) emsg(e_nomatch); } #else if (ExpandWildCards(1, (char_u **)&str, &cmd_numfiles, &cmd_files, FALSE, list_notfound) == FAIL) /* error: do nothing */; else if (cmd_numfiles == 0) emsg(e_nomatch); #endif /* WEBB_COMPLETE */ else if (mode < 4) { if (cmd_numfiles > 1) /* more than one match; check suffixes */ { found = -2; for (i = 0; i < cmd_numfiles; ++i) { if ((filesuf = STRRCHR(cmd_files[i], '.')) != NULL) { filesuflen = STRLEN(filesuf); for (setsuf = p_su; *setsuf; setsuf = nextsetsuf) { if ((nextsetsuf = STRCHR(setsuf + 1, '.')) == NULL) nextsetsuf = setsuf + STRLEN(setsuf); setsuflen = (int)(nextsetsuf - setsuf); if (filesuflen == setsuflen && STRNCMP(setsuf, filesuf, (size_t)setsuflen) == 0) break; } if (*setsuf) /* suffix matched: ignore file */ continue; } if (found >= 0) { multmatch = TRUE; break; } found = i; } } if (multmatch || found < 0) { #ifdef WEBB_COMPLETE /* Can we ever get here unless it's while expanding * interactively? If not, we can get rid of this all together. * Don't really want to wait for this message (and possibly * have to hit return to continue!). */ if (!expand_interactively) #endif /* WEBB_COMPLETE */ emsg(e_toomany); #ifdef WEBB_COMPLETE else beep(); #endif /* WEBB_COMPLETE */ found = 0; /* return first one */ multmatch = TRUE; /* for found < 0 */ } if (found >= 0 && !(multmatch && mode == -1)) ss = strsave(cmd_files[found]); } } if (mode == 5 && cmd_numfiles > 0) /* find longest common part */ { for (len = 0; cmd_files[0][len]; ++len) { for (i = 0; i < cmd_numfiles; ++i) { #ifdef AMIGA if (toupper(cmd_files[i][len]) != toupper(cmd_files[0][len])) #else if (cmd_files[i][len] != cmd_files[0][len]) #endif break; } if (i < cmd_numfiles) break; } ss = alloc((unsigned)len + 1); if (ss) { STRNCPY(ss, cmd_files[0], (size_t)len); ss[len] = NUL; } multmatch = TRUE; /* don't free the names */ findex = -1; /* next p_wc gets first one */ } if (mode == 4 && cmd_numfiles > 0) /* concatenate all file names */ { len = 0; for (i = 0; i < cmd_numfiles; ++i) len += STRLEN(cmd_files[i]) + 1; ss = lalloc(len, TRUE); if (ss) { *ss = NUL; for (i = 0; i < cmd_numfiles; ++i) { STRCAT(ss, cmd_files[i]); if (i != cmd_numfiles - 1) STRCAT(ss, " "); } } } #ifdef WEBB_COMPLETE if (mode == -1 || mode == 4) #else if (!multmatch || mode == -1 || mode == 4) #endif /* WEBB_COMPLETE */ { FreeWild(cmd_numfiles, cmd_files); cmd_numfiles = -1; } return ss; } /* * show all filenames that match the string "file" with length "len" */ #ifdef WEBB_COMPLETE static int showmatches(buff) char_u *buff; #else static void showmatches(file, len) char_u *file; int len; #endif /* WEBB_COMPLETE */ { char_u *file_str; int num_files; char_u **files_found; int i, j, k; int maxlen; int lines; int columns; #ifdef WEBB_COMPLETE set_expand_context(cmdfirstc, cmdbuff); if (expand_context == EXPAND_UNSUCCESSFUL) { beep(); return OK; /* Something illegal on command line */ } if (expand_context == EXPAND_NOTHING) { /* Caller can use the character as a normal char instead */ return FAIL; } expand_interactively = TRUE; /* add star to file name, or convert to regexp if not expanding files! */ file_str = addstar(expand_pattern, (int)(buff + cmdpos - expand_pattern)); if (file_str == NULL) { expand_interactively = FALSE; return OK; } #else file_str = addstar(file, len); /* add star to file name */ if (file_str == NULL) return; #endif /* WEBB_COMPLETE */ msg_outchar('\n'); flushbuf(); /* find all files that match the description */ #ifdef WEBB_COMPLETE if (ExpandFromContext(file_str, &num_files, &files_found, FALSE, FALSE) == FAIL) { num_files = 0; files_found = (char_u **)""; } #else if (ExpandWildCards(1, &file_str, &num_files, &files_found, FALSE, FALSE) == FAIL) return; #endif /* WEBB_COMPLETE */ /* find the maximum length of the file names */ maxlen = 0; for (i = 0; i < num_files; ++i) { j = STRLEN(files_found[i]); if (j > maxlen) maxlen = j; } /* compute the number of columns and lines for the listing */ maxlen += 2; /* two spaces between file names */ columns = ((int)Columns + 2) / maxlen; if (columns < 1) columns = 1; lines = (num_files + columns - 1) / columns; (void)set_highlight('d'); /* find out highlight mode for directories */ /* list the files line by line */ for (i = 0; i < lines; ++i) { for (k = i; k < num_files; k += lines) { if (k > i) for (j = maxlen - STRLEN(files_found[k - lines]); --j >= 0; ) msg_outchar(' '); #ifdef WEBB_COMPLETE if (expand_context == EXPAND_FILES) j = isdir(files_found[k]); /* highlight directories */ else j = FALSE; #else j = isdir(files_found[k]); /* highlight directories */ #endif /* WEBB_COMPLETE */ if (j) { start_highlight(); screen_start(); /* don't output spaces to position cursor */ } msg_outstr(files_found[k]); if (j) stop_highlight(); } msg_outchar('\n'); flushbuf(); /* show one line at a time */ } free(file_str); FreeWild(num_files, files_found); /* * we redraw the command below the lines that we have just listed * This is a bit tricky, but it saves a lot of screen updating. */ cmdline_row = msg_row; /* will put it back later */ #ifdef WEBB_COMPLETE expand_interactively = FALSE; return OK; #endif /* WEBB_COMPLETE */ } /* * copy the file name into allocated memory and add a '*' at the end */ static char_u * addstar(fname, len) char_u *fname; int len; { char_u *retval; #ifdef WEBB_COMPLETE int i, j; int new_len; char_u save_char; if (expand_interactively && expand_context != EXPAND_FILES && expand_context != EXPAND_DIRECTORIES) { /* Matching will be done internally (on something other than files). * So we convert the file-matching-type wildcards into our kind for * use with regcomp(). First work out how long it will be: */ new_len = len + 2; /* +2 for '^' at start, NUL at end */ for (i = 0; i < len; i++) if (fname[i] == '*') new_len++; /* '*' needs to be replaced by '.*' */ retval = alloc(new_len); if (retval != NULL) { retval[0] = '^'; for (i = 0, j = 1; i < len; i++, j++) if (fname[i] == '*') { retval[j++] = '.'; retval[j] = '*'; } else if (fname[i] == '?') retval[j] = '.'; else retval[j] = fname[i]; retval[j] = NUL; } } else { retval = alloc(len + 4); if (retval != NULL) { STRNCPY(retval, fname, (size_t)len); /* * Don't add a star to ~ or ~user */ save_char = fname[j = len]; fname[j] = NUL; if (gettail(fname)[0] != '~') { #ifdef MSDOS /* * if there is no dot in the file name, add "*.*" instead of "*". */ for (i = len - 1; i >= 0; --i) if (strchr(".\\/:", retval[i])) break; if (i < 0 || retval[i] != '.') { retval[len++] = '*'; retval[len++] = '.'; } #endif retval[len++] = '*'; } retval[len] = NUL; fname[j] = save_char; } } #else /* WEBB_COMPLETE */ #ifdef MSDOS int i; #endif retval = alloc(len + 4); if (retval != NULL) { STRNCPY(retval, fname, (size_t)len); #ifdef MSDOS /* * if there is no dot in the file name, add "*.*" instead of "*". */ for (i = len - 1; i >= 0; --i) if (strchr(".\\/:", retval[i])) break; if (i < 0 || retval[i] != '.') { retval[len++] = '*'; retval[len++] = '.'; } #endif retval[len] = '*'; retval[len + 1] = 0; } #endif /* WEBB_COMPLETE */ return retval; } /* * dosource: read the file "fname" and execute its lines as EX commands * * This function may be called recursively! * * return FAIL if file could not be opened, OK otherwise */ int dosource(fname) register char_u *fname; { register FILE *fp; register int len; #ifdef MSDOS int error = FALSE; #endif expand_env(fname, NameBuff, MAXPATHL); /* use NameBuff for expanded name */ if ((fp = fopen((char *)NameBuff, READBIN)) == NULL) return FAIL; ++dont_sleep; /* don't call sleep() in emsg() */ len = 0; while (fgets((char *)IObuff + len, IOSIZE - len, fp) != NULL && !got_int) { len = STRLEN(IObuff) - 1; if (len >= 0 && IObuff[len] == '\n') /* remove trailing newline */ { #ifdef MSDOS if (len > 0 && IObuff[len - 1] == '\r') /* trailing CR-LF */ --len; else { if (!error) EMSG("Warning: Wrong line separator, ^M may be missing"); error = TRUE; /* lines like ":map xx yy^M" will fail */ } #endif /* escaped newline, read more */ if (len > 0 && len < IOSIZE && IObuff[len - 1] == Ctrl('V')) { IObuff[len - 1] = '\n'; /* remove CTRL-V */ continue; } IObuff[len] = NUL; } breakcheck(); /* check for ^C here, so recursive :so will be broken */ docmdline(IObuff); len = 0; } fclose(fp); if (got_int) emsg(e_interr); --dont_sleep; return OK; } /* * get single EX address */ static linenr_t get_address(ptr) char_u **ptr; { linenr_t cursor_lnum = curwin->w_cursor.lnum; int c; int i; long n; char_u *cmd; FPOS pos; FPOS *fp; linenr_t lnum; cmd = *ptr; skipspace(&cmd); lnum = MAXLNUM; do { switch (*cmd) { case '.': /* '.' - Cursor position */ ++cmd; lnum = cursor_lnum; break; case '$': /* '$' - last line */ ++cmd; lnum = curbuf->b_ml.ml_line_count; break; case '\'': /* ''' - mark */ if (*++cmd == NUL || (fp = getmark(*cmd++, FALSE)) == NULL) { emsg(e_umark); goto error; } lnum = fp->lnum; break; case '/': case '?': /* '/' or '?' - search */ c = *cmd++; pos = curwin->w_cursor; /* save curwin->w_cursor */ curwin->w_cursor.col = -1; /* searchit() will increment the col */ if (c == '/') { if (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) /* :/pat on last line */ curwin->w_cursor.lnum = 1; else ++curwin->w_cursor.lnum; } searchcmdlen = 0; if (dosearch(c, cmd, FALSE, (long)1, FALSE, TRUE)) lnum = curwin->w_cursor.lnum; curwin->w_cursor = pos; cmd += searchcmdlen; /* adjust command string pointer */ break; default: if (isdigit(*cmd)) /* absolute line number */ lnum = getdigits(&cmd); } while (*cmd == '-' || *cmd == '+') { if (lnum == MAXLNUM) lnum = cursor_lnum; i = *cmd++; if (!isdigit(*cmd)) /* '+' is '+1', but '+0' is not '+1' */ n = 1; else n = getdigits(&cmd); if (i == '-') lnum -= n; else lnum += n; } cursor_lnum = lnum; } while (*cmd == '/' || *cmd == '?'); error: *ptr = cmd; return lnum; } #ifdef WEBB_COMPLETE /* * Must parse the command line so far to work out what context we are in. * Completion can then be done based on that context. * This routine sets two global variables: * char_u *expand_pattern --- The start of the pattern to be expanded within * the command line (ends at the cursor). * int expand_context --- The type of thing to expand. Will be one of: * EXPAND_UNSUCCESSFUL --- Used somtimes when there is something illegal on * the command line, like an unknown command. Caller should beep. * EXPAND_NOTHING --- Unrecognised context for completion, use char like a * normal char, rather than for completion. eg :s/^I/ * EXPAND_COMMANDS --- Cursor is still touching the command, so complete it. * EXPAND_FILES --- After command with XFILE set, or after setting with * P_EXPAND set. eg :e ^I, :w>>^I * EXPAND_DIRECTORIES --- In some cases this is used instead of the latter * when we know only directories are of interest. eg :set dir=^I * EXPAND_SETTINGS --- Complete variable names. eg :set d^I * EXPAND_BOOL_SETTINGS --- Complete bollean variables only, eg :set no^I * EXPAND_TAGS --- Complete tags from the files in p_tags. eg :ta a^I * * -- webb. */ static void set_expand_context(firstc, buff) int firstc; /* either ':', '/', or '?' */ char_u *buff; /* buffer for command string */ { char_u *nextcomm; char_u old_char; old_char = cmdbuff[cmdpos]; cmdbuff[cmdpos] = NUL; nextcomm = buff; while (nextcomm != NULL) nextcomm = set_one_cmd_context(firstc, nextcomm); cmdbuff[cmdpos] = old_char; } /* * This is all pretty much copied from DoOneCmd(), with all the extra stuff we * don't need/want deleted. Maybe this could be done better if we didn't * repeat all this stuff. The only problem is that they may not stay perfectly * compatible with each other, but then the command line syntax probably won't * change that much -- webb. */ static char_u * set_one_cmd_context(firstc, buff) int firstc; /* either ':', '/', or '?' */ char_u *buff; /* buffer for command string */ { register char_u *p; char_u *cmd, *arg; int i; int cmdidx; int argt; char_u delim; int forced = FALSE; int usefilter = FALSE; /* filter instead of file name */ expand_pattern = buff; if (firstc != ':') { expand_context = EXPAND_NOTHING; return NULL; } expand_context = EXPAND_COMMANDS; /* Default until we get past command */ /* * 2. skip comment lines and leading space, colons or bars */ for (cmd = buff; *cmd && strchr(" \t:|", *cmd) != NULL; cmd++) ; expand_pattern = cmd; if (*cmd == NUL) return NULL; if (*cmd == '"') /* ignore comment lines */ { expand_context = EXPAND_NOTHING; return NULL; } /* * 3. parse a range specifier of the form: addr [,addr] [;addr] .. */ --cmd; do { ++cmd; /* skip ',' or ';' */ skipspace(&cmd); do { switch (*cmd) { case '.': /* '.' - Cursor position */ case '$': /* '$' - last line */ case '%': /* '%' - all lines */ ++cmd; break; case '\'': /* ''' - mark */ if (*++cmd != NUL) ++cmd; break; case '/': case '?': /* '/' or '?' - search */ delim = *cmd++; while (*cmd != NUL && *cmd != delim) cmd++; if (*cmd == delim) cmd++; break; default: while (isdigit((char)*cmd)) ++cmd; break; } while (*cmd == '-' || *cmd == '+') { cmd++; while (isdigit(*cmd)) cmd++; } } while (*cmd == '/' || *cmd == '?'); } while (*cmd == ',' || *cmd == ';'); /* * 4. parse command */ skipspace(&cmd); expand_pattern = cmd; if (*cmd == NUL) return NULL; if (*cmd == '"') { expand_context = EXPAND_NOTHING; return NULL; } if (*cmd == '|' || *cmd == '\n') return cmd + 1; /* There's another command */ /* * Isolate the command and search for it in the command table. * Exeptions: * - the 'k' command can directly be followed by any character. * - the 's' command can be followed directly by 'c', 'g' or 'r' */ if (*cmd == 'k') { cmdidx = CMD_k; p = cmd + 1; } else { p = cmd; while (isalpha(*p) || *p == '*') /* Allow * wild card */ ++p; if (p == cmd && strchr("@!=><&~#", *p) != NULL) /* non-alpha command */ ++p; i = (int)(p - cmd); if (i == 0) { expand_context = EXPAND_UNSUCCESSFUL; return NULL; } for (cmdidx = 0; cmdidx < CMD_SIZE; ++cmdidx) if (STRNCMP(cmdnames[cmdidx].cmd_name, cmd, (size_t)i) == 0) break; } if (p == cmdbuff + cmdpos) /* We are still touching the command */ return NULL; /* So complete it */ if (cmdidx == CMD_SIZE) { if (*cmd == 's' && strchr("cgr", cmd[1]) != NULL) { cmdidx = CMD_substitute; p = cmd + 1; } else { /* Not still touching the command and it was an illegal command */ expand_context = EXPAND_UNSUCCESSFUL; return NULL; } } expand_context = EXPAND_NOTHING; /* Default now that we're past command */ if (*p == '!') /* forced commands */ { forced = TRUE; ++p; } /* * 5. parse arguments */ argt = cmdnames[cmdidx].cmd_argt; arg = p; /* remember start of argument */ skipspace(&arg); if (cmdidx == CMD_write) { if (*arg == '>') /* append */ { if (*++arg == '>') /* It should be */ ++arg; skipspace(&arg); } else if (*arg == '!') /* :w !filter */ { ++arg; usefilter = TRUE; } } if (cmdidx == CMD_read) { usefilter = forced; /* :r! filter if forced */ if (*arg == '!') /* :r !filter */ { ++arg; usefilter = TRUE; } } if (cmdidx == CMD_lshift || cmdidx == CMD_rshift) { while (*arg == *cmd) /* allow any number of '>' or '<' */ ++arg; skipspace(&arg); } /* * Check for '|' to separate commands and '"' to start comments. * Don't do this for ":read !cmd" and ":write !cmd". */ if ((argt & TRLBAR) && !usefilter) { p = arg; while (*p) { if (*p == Ctrl('V')) { if (p[1] != NUL) ++p; } else if ((*p == '"' && !(argt & NOTRLCOM)) || *p == '|' || *p == '\n') { if (*(p - 1) != '\\') { if (*p == '|' || *p == '\n') return p + 1; return NULL; /* It's a comment */ } } ++p; } } if (!(argt & EXTRA) && strchr("|\"", *arg) == NULL) /* no arguments allowed */ return NULL; /* Find start of last argument (argument just before cursor): */ p = cmdbuff + cmdpos; while (p != arg && *p != ' ' && *p != TAB) p--; if (*p == ' ' || *p == TAB) p++; expand_pattern = p; if (argt & XFILE) expand_context = EXPAND_FILES; /* * 6. switch on command name */ switch (cmdidx) { case CMD_cd: case CMD_chdir: expand_context = EXPAND_DIRECTORIES; break; case CMD_buffer: case CMD_wnext: case CMD_args: /* args now takes arguments like :next */ case CMD_next: case CMD_snext: case CMD_split: case CMD_new: case CMD_edit: case CMD_ex: case CMD_visual: for (p = arg; *p; ++p) { if (*p == '\\' && p[1]) ++p; else if (*p == '|' || *p == '\n') return p + 1; } break; case CMD_global: case CMD_vglobal: delim = *arg; /* get the delimiter */ if (delim) ++arg; /* skip delimiter if there is one */ while (arg[0] != NUL && arg[0] != delim) { if (arg[0] == '\\' && arg[1] != NUL) ++arg; ++arg; } if (arg[0] != NUL) return arg + 1; break; case CMD_and: case CMD_substitute: delim = *arg; if (delim) ++arg; for (i = 0; i < 2; i++, arg++) while (arg[0] != NUL && arg[0] != delim) { if (arg[0] == '\\' && arg[1] != NUL) ++arg; ++arg; } while (arg[0] != NUL && strchr("|\"#", arg[0]) == NULL) ++arg; if (arg[0] != NUL) return arg; break; case CMD_set: set_context_in_set_cmd(arg); break; case CMD_tag: expand_context = EXPAND_TAGS; expand_pattern = arg; break; default: break; } return NULL; } /* * Do the expansion based on the global variables expand_context and * expand_pattern -- webb. */ static int ExpandFromContext(pat, num_file, file, files_only, list_notfound) char_u *pat; int *num_file; char_u ***file; int files_only; int list_notfound; { regexp *prog; int cmdidx; int count; int ret; int i; if (!expand_interactively || expand_context == EXPAND_FILES) return ExpandWildCards(1, &pat, num_file, file, files_only, list_notfound); else if (expand_context == EXPAND_DIRECTORIES) { if (ExpandWildCards(1, &pat, num_file, file, files_only, list_notfound) == FAIL) return FAIL; count = 0; for (i = 0; i < *num_file; i++) if (isdir((*file)[i])) (*file)[count++] = (*file)[i]; else free((*file)[i]); if (count == 0) { free(*file); *file = (char_u **)""; *num_file = -1; return FAIL; } *num_file = count; return OK; } *file = (char_u **)""; *num_file = 0; ret = OK; reg_ic = FALSE; reg_magic = p_magic; prog = regcomp(pat); if (prog == NULL) return FAIL; if (expand_context == EXPAND_COMMANDS) { /* Count the matches: */ count = 0; for (cmdidx = 0; cmdidx < CMD_SIZE; cmdidx++) if (regexec(prog, cmdnames[cmdidx].cmd_name, TRUE)) count++; if (count == 0 || (*file = (char_u **) alloc((int)(count * sizeof(char_u *)))) == NULL) ret = FAIL; else { *num_file = count; count = 0; for (cmdidx = 0; cmdidx < CMD_SIZE; cmdidx++) if (regexec(prog, cmdnames[cmdidx].cmd_name, TRUE)) (*file)[count++] = strsave(cmdnames[cmdidx].cmd_name); } } else if (expand_context == EXPAND_SETTINGS || expand_context == EXPAND_BOOL_SETTINGS) ret = ExpandSettings(prog, num_file, file); else if (expand_context == EXPAND_TAGS) ret = ExpandTags(prog, num_file, file); else ret = FAIL; free(prog); return ret; } #endif /* WEBB_COMPLETE */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.