/* $OpenBSD: cmdline.c,v 1.1 1996/09/07 21:40:26 downsj Exp $ */ /* vi:set ts=4 sw=4: * * VIM - Vi IMproved by Bram Moolenaar * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. */ /* * cmdline.c: functions for reading in the command line and executing it */ #include "vim.h" #include "globals.h" #include "proto.h" #include "option.h" #include "cmdtab.h" #include "ops.h" /* included because we call functions in ops.c */ #ifdef HAVE_FCNTL_H # include /* for chdir() */ #endif /* * variables shared between getcmdline() and redrawcmdline() */ static char_u *cmdbuff; /* pointer to command line buffer */ static int cmdbufflen; /* length of cmdbuff */ 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 '?' */ /* * Typing mode on the command line. Shared by getcmdline() and * put_on_cmdline(). */ static int overstrike = FALSE; /* typing mode */ /* * The next two variables contain the bounds of any range given in a command. * They are set by do_cmdline(). */ 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 */ /* * There are two history tables: * 0: colon commands * 1: search commands */ static char_u **(history[2]) = {NULL, NULL}; /* history tables */ static int hisidx[2] = {-1, -1}; /* last entered entry */ static int hislen = 0; /* actual lengt of history tables */ #ifdef RIGHTLEFT static int cmd_hkmap = 0; /* Hebrew mapping during command line */ #endif static void init_history __ARGS((void)); static int is_in_history __ARGS((int, char_u *, int)); static void putcmdline __ARGS((int)); static void redrawcmd __ARGS((void)); static void cursorcmd __ARGS((void)); static int ccheck_abbr __ARGS((int)); static char_u *do_one_cmd __ARGS((char_u **, int *, int)); static int buf_write_all __ARGS((BUF *)); static int do_write __ARGS((char_u *, int)); static char_u *getargcmd __ARGS((char_u **)); static void backslash_halve __ARGS((char_u *p, int expand_wildcards)); static void do_make __ARGS((char_u *)); static int do_arglist __ARGS((char_u *)); static int is_backslash __ARGS((char_u *str)); static int check_readonly __ARGS((void)); static int check_changed __ARGS((BUF *, int, int)); static int check_changed_any __ARGS((void)); static int check_more __ARGS((int)); static void vim_strncpy __ARGS((char_u *, char_u *, int)); static int nextwild __ARGS((int)); static int showmatches __ARGS((char_u *)); static linenr_t get_address __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)); static int ExpandCommands __ARGS((regexp *, int *, char_u ***)); /* * init_history() - initialize the command line history */ static void init_history() { int newlen; /* new length of history table */ char_u **temp; register int i; int j; int type; /* * If size of history table changed, reallocate it */ newlen = (int)p_hi; if (newlen != hislen) /* history length changed */ { for (type = 0; type <= 1; ++type) /* adjust both history tables */ { if (newlen) temp = (char_u **)lalloc((long_u)(newlen * sizeof(char_u *)), TRUE); else temp = NULL; if (newlen == 0 || temp != NULL) { if (hisidx[type] < 0) /* there are no entries yet */ { for (i = 0; i < newlen; ++i) temp[i] = NULL; } else if (newlen > hislen) /* array becomes bigger */ { for (i = 0; i <= hisidx[type]; ++i) temp[i] = history[type][i]; j = i; for ( ; i <= newlen - (hislen - hisidx[type]); ++i) temp[i] = NULL; for ( ; j < hislen; ++i, ++j) temp[i] = history[type][j]; } else /* array becomes smaller or 0 */ { j = hisidx[type]; for (i = newlen - 1; ; --i) { if (i >= 0) /* copy newest entries */ temp[i] = history[type][j]; else /* remove older entries */ vim_free(history[type][j]); if (--j < 0) j = hislen - 1; if (j == hisidx[type]) break; } hisidx[type] = newlen - 1; } vim_free(history[type]); history[type] = temp; } } hislen = newlen; } } /* * check if command line 'str' is already in history * 'type' is 0 for ':' commands, '1' for search commands * if 'move_to_front' is TRUE, matching entry is moved to end of history */ static int is_in_history(type, str, move_to_front) int type; char_u *str; int move_to_front; /* Move the entry to the front if it exists */ { int i; int last_i = -1; if (hisidx[type] < 0) return FALSE; i = hisidx[type]; do { if (history[type][i] == NULL) return FALSE; if (STRCMP(str, history[type][i]) == 0) { if (!move_to_front) return TRUE; last_i = i; break; } if (--i < 0) i = hislen - 1; } while (i != hisidx[type]); if (last_i >= 0) { str = history[type][i]; while (i != hisidx[type]) { if (++i >= hislen) i = 0; history[type][last_i] = history[type][i]; last_i = i; } history[type][i] = str; return TRUE; } return FALSE; } /* * Add the given string to the given history. If the string is already in the * history then it is moved to the front. histype may be 0 for the ':' * history, or 1 for the '/' history. */ void add_to_history(histype, new_entry) int histype; char_u *new_entry; { if (hislen != 0 && !is_in_history(histype, new_entry, TRUE)) { if (++hisidx[histype] == hislen) hisidx[histype] = 0; vim_free(history[histype][hisidx[histype]]); history[histype][hisidx[histype]] = strsave(new_entry); } } /* * getcmdline() - accept a command line starting with ':', '/', or '?' * * The line is collected in cmdbuff, which is reallocated to fit the command * line. * * Return pointer to allocated string if there is a commandline, NULL * otherwise. */ char_u * getcmdline(firstc, count) int firstc; /* either ':', '/', or '?' */ long count; /* only used for incremental search */ { register int c; #ifdef DIGRAPHS int cc; #endif register int i; int j; char_u *p; int hiscnt; /* current history line in use */ char_u *lookfor = NULL; /* string to match */ int gotesc = FALSE; /* TRUE when just typed */ int do_abbr; /* when TRUE check for abbr. */ int histype; /* history type to be used */ FPOS old_cursor; colnr_t old_curswant; int did_incsearch = FALSE; int incsearch_postponed = FALSE; int save_msg_scroll = msg_scroll; int some_key_typed = FALSE; /* one of the keys was typed */ #ifdef USE_MOUSE /* mouse drag and release events are ignored, unless they are * preceded with a mouse down event */ int ignore_drag_release = TRUE; #endif overstrike = FALSE; /* always start in insert mode */ old_cursor = curwin->w_cursor; /* needs to be restored later */ old_curswant = curwin->w_curswant; /* * set some variables for redrawcmd() */ cmdfirstc = firstc; alloc_cmdbuff(0); /* allocate initial cmdbuff */ if (cmdbuff == NULL) return NULL; /* out of memory */ cmdlen = cmdpos = 0; cmdspos = 1; State = CMDLINE; #ifdef USE_MOUSE setmouse(); #endif gotocmdline(TRUE); msg_outchar(firstc); /* * Avoid scrolling when called by a recursive do_cmdline(), e.g. when doing * ":@0" when register 0 doesn't contain a CR. */ msg_scroll = FALSE; init_history(); hiscnt = hislen; /* set hiscnt to impossible history value */ histype = (firstc == ':' ? 0 : 1); #ifdef DIGRAPHS do_digraph(-1); /* init digraph typahead */ #endif /* collect the command string, handling editing keys */ for (;;) { cursorcmd(); /* set the cursor on the right spot */ c = vgetc(); if (KeyTyped) { some_key_typed = TRUE; #ifdef RIGHTLEFT if (cmd_hkmap) c = hkmap(c); #endif } if (c == Ctrl('C')) got_int = FALSE; /* ignore got_int when CTRL-C was typed here */ /* free old command line when finished moving around in the * history list */ if (lookfor && c != K_S_DOWN && c != K_S_UP && c != K_DOWN && c != K_UP && c != K_PAGEDOWN && c != K_PAGEUP && (cmd_numfiles > 0 || (c != Ctrl('P') && c != Ctrl('N')))) { vim_free(lookfor); lookfor = NULL; } /* * works like CTRL-P (unless 'wc' is ). */ if (c != p_wc && c == K_S_TAB) c = Ctrl('P'); /* free expanded names when finished walking through matches */ if (cmd_numfiles != -1 && !(c == p_wc && KeyTyped) && c != Ctrl('N') && c != Ctrl('P') && c != Ctrl('A') && c != Ctrl('L')) (void)ExpandOne(NULL, NULL, 0, WILD_FREE); #ifdef DIGRAPHS c = do_digraph(c); #endif if (c == '\n' || c == '\r' || (c == ESC && (!KeyTyped || vim_strchr(p_cpo, CPO_ESC) != NULL))) { if (ccheck_abbr(c + ABBR_OFF)) goto cmdline_changed; outchar('\r'); /* show that we got the return */ screen_cur_col = 0; flushbuf(); break; } /* hitting 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) { if (cmd_numfiles > 0) /* typed p_wc twice */ i = nextwild(WILD_NEXT); else /* typed p_wc first time */ i = nextwild(WILD_EXPAND_KEEP); if (c == ESC) gotesc = TRUE; if (i) goto cmdline_changed; } gotesc = FALSE; if (c == NUL || c == K_ZERO) /* NUL is stored as NL */ c = NL; do_abbr = TRUE; /* default: check for abbreviation */ switch (c) { case K_BS: case Ctrl('H'): case K_DEL: case Ctrl('W'): /* * delete current character is the same as backspace on next * character, except at end of line */ if (c == K_DEL && cmdpos != cmdlen) ++cmdpos; if (cmdpos > 0) { j = cmdpos; if (c == Ctrl('W')) { while (cmdpos && vim_isspace(cmdbuff[cmdpos - 1])) --cmdpos; i = iswordchar(cmdbuff[cmdpos - 1]); while (cmdpos && !vim_isspace(cmdbuff[cmdpos - 1]) && iswordchar(cmdbuff[cmdpos - 1]) == i) --cmdpos; } else --cmdpos; cmdlen -= j - cmdpos; i = cmdpos; while (i < cmdlen) cmdbuff[i++] = cmdbuff[j++]; redrawcmd(); } else if (cmdlen == 0 && c != Ctrl('W')) { vim_free(cmdbuff); /* no commandline to return */ cmdbuff = NULL; msg_pos(-1, 0); msg_outchar(' '); /* delete ':' */ redraw_cmdline = TRUE; goto returncmd; /* back to cmd mode */ } goto cmdline_changed; case K_INS: overstrike = !overstrike; /* should change shape of cursor */ goto cmdline_not_changed; /* case '@': only in very old vi */ case Ctrl('U'): cmdpos = 0; cmdlen = 0; cmdspos = 1; redrawcmd(); goto cmdline_changed; case ESC: /* get here if p_wc != ESC or when ESC typed twice */ case Ctrl('C'): gotesc = TRUE; /* will free cmdbuff after putting it in history */ goto returncmd; /* back to cmd mode */ case Ctrl('R'): /* insert register */ putcmdline('"'); ++no_mapping; c = vgetc(); --no_mapping; if (c != ESC) /* use ESC to cancel inserting register */ cmdline_paste(c); redrawcmd(); goto cmdline_changed; case Ctrl('D'): { if (showmatches(cmdbuff) == FAIL) break; /* Use ^D as normal char instead */ redrawcmd(); continue; /* don't do incremental search now */ } case K_RIGHT: case K_S_RIGHT: do { if (cmdpos >= cmdlen) break; cmdspos += charsize(cmdbuff[cmdpos]); ++cmdpos; } while (c == K_S_RIGHT && cmdbuff[cmdpos] != ' '); goto cmdline_not_changed; case K_LEFT: case K_S_LEFT: do { if (cmdpos <= 0) break; --cmdpos; cmdspos -= charsize(cmdbuff[cmdpos]); } while (c == K_S_LEFT && cmdbuff[cmdpos - 1] != ' '); goto cmdline_not_changed; #ifdef USE_MOUSE case K_MIDDLEDRAG: case K_MIDDLERELEASE: case K_IGNORE: goto cmdline_not_changed; /* Ignore mouse */ case K_MIDDLEMOUSE: # ifdef USE_GUI /* When GUI is active, also paste when 'mouse' is empty */ if (!gui.in_use) # endif if (!mouse_has(MOUSE_COMMAND)) goto cmdline_not_changed; /* Ignore mouse */ # ifdef USE_GUI if (gui.in_use && yankbuffer == 0) cmdline_paste('*'); else # endif cmdline_paste(yankbuffer); redrawcmd(); goto cmdline_changed; case K_LEFTDRAG: case K_LEFTRELEASE: case K_RIGHTDRAG: case K_RIGHTRELEASE: if (ignore_drag_release) goto cmdline_not_changed; /* FALLTHROUGH */ case K_LEFTMOUSE: case K_RIGHTMOUSE: if (c == K_LEFTRELEASE || c == K_RIGHTRELEASE) ignore_drag_release = TRUE; else ignore_drag_release = FALSE; # ifdef USE_GUI /* When GUI is active, also move when 'mouse' is empty */ if (!gui.in_use) # endif if (!mouse_has(MOUSE_COMMAND)) goto cmdline_not_changed; /* Ignore mouse */ cmdspos = 1; for (cmdpos = 0; cmdpos < cmdlen; ++cmdpos) { i = charsize(cmdbuff[cmdpos]); if (mouse_row <= cmdline_row + cmdspos / Columns && mouse_col < cmdspos % Columns + i) break; cmdspos += i; } goto cmdline_not_changed; #endif /* USE_MOUSE */ #ifdef USE_GUI case K_SCROLLBAR: if (!msg_scrolled) { gui_do_scroll(); redrawcmd(); } goto cmdline_not_changed; case K_HORIZ_SCROLLBAR: if (!msg_scrolled) { gui_do_horiz_scroll(); redrawcmd(); } goto cmdline_not_changed; #endif case Ctrl('B'): /* begin of command line */ case K_HOME: cmdpos = 0; cmdspos = 1; goto cmdline_not_changed; case Ctrl('E'): /* end of command line */ case K_END: cmdpos = cmdlen; cmdbuff[cmdlen] = NUL; cmdspos = strsize(cmdbuff) + 1; goto cmdline_not_changed; case Ctrl('A'): /* all matches */ if (!nextwild(WILD_ALL)) break; goto cmdline_changed; case Ctrl('L'): /* longest common part */ if (!nextwild(WILD_LONGEST)) break; goto cmdline_changed; case Ctrl('N'): /* next match */ case Ctrl('P'): /* previous match */ if (cmd_numfiles > 0) { if (!nextwild((c == Ctrl('P')) ? WILD_PREV : WILD_NEXT)) break; goto cmdline_changed; } case K_UP: case K_DOWN: case K_S_UP: case K_S_DOWN: case K_PAGEUP: case K_PAGEDOWN: if (hislen == 0) /* no history */ goto cmdline_not_changed; i = hiscnt; /* save current command string so it can be restored later */ cmdbuff[cmdpos] = NUL; if (lookfor == NULL && (lookfor = strsave(cmdbuff)) == NULL) goto cmdline_not_changed; j = STRLEN(lookfor); for (;;) { /* one step backwards */ if (c == K_UP || c == K_S_UP || c == Ctrl('P') || c == K_PAGEUP) { if (hiscnt == hislen) /* first time */ hiscnt = hisidx[histype]; else if (hiscnt == 0 && hisidx[histype] != hislen - 1) hiscnt = hislen - 1; else if (hiscnt != hisidx[histype] + 1) --hiscnt; else /* at top of list */ { hiscnt = i; break; } } else /* one step forwards */ { /* on last entry, clear the line */ if (hiscnt == hisidx[histype]) { hiscnt = hislen; break; } /* not on a history line, nothing to do */ if (hiscnt == hislen) break; if (hiscnt == hislen - 1) /* wrap around */ hiscnt = 0; else ++hiscnt; } if (hiscnt < 0 || history[histype][hiscnt] == NULL) { hiscnt = i; break; } if ((c != K_UP && c != K_DOWN) || hiscnt == i || STRNCMP(history[histype][hiscnt], lookfor, (size_t)j) == 0) break; } if (hiscnt != i) /* jumped to other entry */ { vim_free(cmdbuff); if (hiscnt == hislen) p = lookfor; /* back to the old one */ else p = history[histype][hiscnt]; alloc_cmdbuff((int)STRLEN(p)); if (cmdbuff == NULL) goto returncmd; STRCPY(cmdbuff, p); cmdpos = cmdlen = STRLEN(cmdbuff); redrawcmd(); goto cmdline_changed; } beep_flush(); goto cmdline_not_changed; case Ctrl('V'): case Ctrl('Q'): #ifdef USE_MOUSE ignore_drag_release = TRUE; #endif putcmdline('^'); c = get_literal(); /* get next (two) character(s) */ do_abbr = FALSE; /* don't do abbreviation now */ break; #ifdef DIGRAPHS case Ctrl('K'): #ifdef USE_MOUSE ignore_drag_release = TRUE; #endif putcmdline('?'); ++no_mapping; ++allow_keys; c = vgetc(); --no_mapping; --allow_keys; if (c != ESC) /* ESC cancels CTRL-K */ { if (IS_SPECIAL(c)) /* insert special key code */ break; if (charsize(c) == 1) putcmdline(c); ++no_mapping; ++allow_keys; cc = vgetc(); --no_mapping; --allow_keys; if (cc != ESC) /* ESC cancels CTRL-K */ { c = getdigraph(c, cc, TRUE); break; } } redrawcmd(); goto cmdline_not_changed; #endif /* DIGRAPHS */ #ifdef RIGHTLEFT case Ctrl('_'): /* CTRL-_: switch language mode */ cmd_hkmap = !cmd_hkmap; goto cmdline_not_changed; #endif default: /* * Normal character with no special meaning. Just set mod_mask * to 0x0 so that typing Shift-Space in the GUI doesn't enter * the string . This should only happen after ^V. */ if (!IS_SPECIAL(c)) mod_mask = 0x0; break; } /* we come here if we have a normal character */ if (do_abbr && (IS_SPECIAL(c) || !iswordchar(c)) && ccheck_abbr(c)) goto cmdline_changed; /* * put the character in the command line */ if (IS_SPECIAL(c) || mod_mask != 0x0) put_on_cmdline(get_special_key_name(c, mod_mask), -1, TRUE); else { IObuff[0] = c; put_on_cmdline(IObuff, 1, TRUE); } goto cmdline_changed; /* * This part implements incremental searches for "/" and "?" * Jump to cmdline_not_changed when a character has been read but the command * line did not change. Then we only search and redraw if something changed in * the past. * Jump to cmdline_changed when the command line did change. * (Sorry for the goto's, I know it is ugly). */ cmdline_not_changed: if (!incsearch_postponed) continue; cmdline_changed: if (p_is && (firstc == '/' || firstc == '?')) { /* if there is a character waiting, search and redraw later */ if (char_avail()) { incsearch_postponed = TRUE; continue; } incsearch_postponed = FALSE; curwin->w_cursor = old_cursor; /* start at old position */ /* If there is no command line, don't do anything */ if (cmdlen == 0) i = 0; else { cmdbuff[cmdlen] = NUL; emsg_off = TRUE; /* So it doesn't beep if bad expr */ i = do_search(firstc, cmdbuff, count, SEARCH_KEEP + SEARCH_OPT + SEARCH_NOOF); emsg_off = FALSE; } if (i) { highlight_match = TRUE; /* highlight position */ cursupdate(); } else { highlight_match = FALSE; /* don't highlight */ /* vim_beep(); */ /* even beeps when invalid expr, e.g. "[" */ } updateScreen(NOT_VALID); redrawcmdline(); did_incsearch = TRUE; } } returncmd: if (did_incsearch) { curwin->w_cursor = old_cursor; curwin->w_curswant = old_curswant; highlight_match = FALSE; redraw_later(NOT_VALID); } if (cmdbuff != NULL) { /* * Put line in history buffer (":" only when it was typed). */ cmdbuff[cmdlen] = NUL; if (cmdlen != 0 && (some_key_typed || firstc != ':')) { add_to_history(histype, cmdbuff); if (firstc == ':') { vim_free(new_last_cmdline); new_last_cmdline = strsave(cmdbuff); } } if (gotesc) /* abandon command line */ { vim_free(cmdbuff); cmdbuff = NULL; MSG(""); redraw_cmdline = TRUE; } } /* * 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(); msg_scroll = save_msg_scroll; State = NORMAL; #ifdef USE_MOUSE setmouse(); #endif return cmdbuff; } /* * Put the given string, of the given length, onto the command line. * If len is -1, then STRLEN() is used to calculate the length. * If 'redraw' is TRUE then the new part of the command line, and the remaining * part will be redrawn, otherwise it will not. If this function is called * twice in a row, then 'redraw' should be FALSE and redrawcmd() should be * called afterwards. */ int put_on_cmdline(str, len, redraw) char_u *str; int len; int redraw; { int i; if (len < 0) len = STRLEN(str); /* Check if cmdbuff needs to be longer */ if (cmdlen + len + 1 >= cmdbufflen) i = realloc_cmdbuff(cmdlen + len); else i = OK; if (i == OK) { if (!overstrike) { vim_memmove(cmdbuff + cmdpos + len, cmdbuff + cmdpos, (size_t)(cmdlen - cmdpos)); cmdlen += len; } else if (cmdpos + len > cmdlen) cmdlen = cmdpos + len; vim_memmove(cmdbuff + cmdpos, str, (size_t)len); if (redraw) msg_outtrans_len(cmdbuff + cmdpos, cmdlen - cmdpos); cmdpos += len; while (len--) cmdspos += charsize(str[len]); } if (redraw) msg_check(); return i; } void alloc_cmdbuff(len) int len; { /* * give some extra space to avoid having to allocate all the time */ if (len < 80) len = 100; else len += 20; cmdbuff = alloc(len); /* caller should check for out of memory */ cmdbufflen = len; } /* * Re-allocate the command line to length len + something extra. * return FAIL for failure, OK otherwise */ int realloc_cmdbuff(len) int len; { char_u *p; p = cmdbuff; alloc_cmdbuff(len); /* will get some more */ if (cmdbuff == NULL) /* out of memory */ { cmdbuff = p; /* keep the old one */ return FAIL; } vim_memmove(cmdbuff, p, (size_t)cmdlen); vim_free(p); return OK; } /* * put a character on the command line. * Used for CTRL-V and CTRL-K */ static void putcmdline(c) int c; { char_u buf[1]; buf[0] = c; msg_outtrans_len(buf, 1); msg_outtrans_len(cmdbuff + cmdpos, cmdlen - cmdpos); cursorcmd(); } /* * this fuction is called when the screen size changes and with incremental * search */ void redrawcmdline() { msg_scrolled = 0; need_wait_return = FALSE; 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. */ static void redrawcmd() { register int i; msg_start(); msg_outchar(cmdfirstc); msg_outtrans_len(cmdbuff, cmdlen); msg_clr_eos(); cmdspos = 1; for (i = 0; i < cmdlen && i < cmdpos; ++i) cmdspos += charsize(cmdbuff[i]); /* * An emsg() before may have set msg_scroll and need_sleep. These are used * in normal mode, in cmdline mode we can reset them now. */ msg_scroll = FALSE; /* next message overwrites cmdline */ #ifdef SLEEP_IN_EMSG need_sleep = FALSE; /* don't sleep */ #endif } 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); } /* * do_cmdline(): 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! * * If 'sourcing' is TRUE, the command will be included in the error message. * If 'repeating' is TRUE, there is no wait_return() and friends. * * return FAIL if commandline could not be executed, OK otherwise */ int do_cmdline(cmdline, sourcing, repeating) char_u *cmdline; int sourcing; int repeating; { int cmdlinelen; char_u *nextcomm; static int recursive = 0; /* recursive depth */ int got_cmdline = FALSE; /* TRUE when cmdline was typed */ int msg_didout_before_start; /* * 1. If no line given: Get a line in cmdbuff. * If a line is given: Copy it into cmdbuff. * After this we don't use cmdbuff but cmdline, because of recursiveness */ if (cmdline == NULL) { if ((cmdline = getcmdline(':', 1L)) == NULL) { /* don't call wait_return for aborted command line */ need_wait_return = FALSE; return FAIL; } got_cmdline = TRUE; } else { /* Make a copy of the command so we can mess with it. */ alloc_cmdbuff((int)STRLEN(cmdline)); if (cmdbuff == NULL) return FAIL; STRCPY(cmdbuff, cmdline); cmdline = cmdbuff; } cmdlinelen = cmdbufflen; /* we need to copy it for recursiveness */ /* * All output from the commands is put below each other, without waiting for a * return. Don't do this when executing commands from a script or when being * called recursive (e.g. for ":e +command file"). */ msg_didout_before_start = msg_didout; if (!repeating && !recursive) { msg_didany = FALSE; /* no output yet */ msg_start(); msg_scroll = TRUE; /* put messages below each other */ #ifdef SLEEP_IN_EMSG ++dont_sleep; /* don't sleep in emsg() */ #endif ++no_wait_return; /* dont wait for return until finished */ ++RedrawingDisabled; } /* * 2. Loop for each '|' separated command. * do_one_cmd will set nextcomm to NULL if there is no trailing '|'. * cmdline and cmdlinelen may change, e.g. for '%' and '#' expansion. */ ++recursive; for (;;) { nextcomm = do_one_cmd(&cmdline, &cmdlinelen, sourcing); if (nextcomm == NULL) break; STRCPY(cmdline, nextcomm); } --recursive; vim_free(cmdline); /* * If there was too much output to fit on the command line, ask the user to * hit return before redrawing the screen. With the ":global" command we do * this only once after the command is finished. */ if (!repeating && !recursive) { --RedrawingDisabled; #ifdef SLEEP_IN_EMSG --dont_sleep; #endif --no_wait_return; msg_scroll = FALSE; if (need_wait_return || (msg_check() && !dont_wait_return)) { /* * The msg_start() above clears msg_didout. The wait_return we do * here should not overwrite the command that may be shown before * doing that. */ msg_didout = msg_didout_before_start; wait_return(FALSE); } } /* * If the command was typed, remember it for register : * Do this AFTER executing the command to make :@: work. */ if (got_cmdline && new_last_cmdline != NULL) { vim_free(last_cmdline); last_cmdline = new_last_cmdline; new_last_cmdline = NULL; } return OK; } static char *(make_cmd_chars[6]) = { " \164\145a", "\207\171\204\170\060\175\171\174\173\117\032", " c\157\146\146e\145", "\200\174\165\161\203\165\060\171\176\203\165\202\204\060\163\177\171\176\060\204\177\060\202\205\176\060\175\161\173\165\032", " \164o\141\163t", "\136\137\122\137\124\151\060\165\210\200\165\163\204\203\060\204\170\165\060\143\200\161\176\171\203\170\060\171\176\201\205\171\203\171\204\171\177\176\061\032" }; /* * Execute one Ex command. * * If 'sourcing' is TRUE, the command will be included in the error message. * * 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 * do_one_cmd(cmdlinep, cmdlinelenp, sourcing) char_u **cmdlinep; int *cmdlinelenp; int sourcing; { char_u *p; char_u *q; char_u *s; char_u *cmd, *arg; char_u *do_ecmd_cmd = NULL; /* +command for do_ecmd() */ linenr_t do_ecmd_lnum = 0; /* lnum file for do_ecmd() */ int i = 0; /* init to shut up gcc */ int len; int cmdidx; long argt; register linenr_t lnum; long n = 0; /* init to shut up gcc */ int addr_count; /* number of address specs */ FPOS pos; int append = FALSE; /* write with append */ int usefilter = FALSE; /* no read/write but filter */ char_u *nextcomm = NULL; /* no next command yet */ int amount = 0; /* for ":>"; init for gcc */ char_u *errormsg = NULL; /* error message */ WIN *old_curwin = NULL; /* init for GCC */ /* when not editing the last file :q has to be typed twice */ if (quitmore) --quitmore; did_emsg = FALSE; /* will be set to TRUE when emsg() used, in which * case we set nextcomm to NULL to cancel the * whole command line */ /* * 2. skip comment lines and leading space and colons */ for (cmd = *cmdlinep; vim_strchr((char_u *)" \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 { line1 = line2; line2 = curwin->w_cursor.lnum; /* default is current line number */ cmd = skipwhite(cmd + 1); /* skip ',' or ';' and following ' ' */ lnum = get_address(&cmd); if (cmd == NULL) /* error detected */ goto doend; if (lnum == MAXLNUM) { if (*cmd == '%') /* '%' - all lines */ { ++cmd; line1 = 1; line2 = curbuf->b_ml.ml_line_count; ++addr_count; } else if (*cmd == '*') /* '*' - visual area */ { FPOS *fp; ++cmd; fp = getmark('<', FALSE); if (check_mark(fp) == FAIL) goto doend; line1 = fp->lnum; fp = getmark('>', FALSE); if (check_mark(fp) == FAIL) goto doend; line2 = fp->lnum; ++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 */ /* * Skip ':' and any white space */ cmd = skipwhite(cmd); if (*cmd == ':') cmd = skipwhite(cmd + 1); /* * 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 */ { /* * strange vi behaviour: * ":3" jumps to line 3 * ":3|..." prints line 3 * ":|" prints current line */ if (*cmd == '|') { cmdidx = CMD_print; goto cmdswitch; /* UGLY goto */ } if (addr_count != 0) { 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; beginline(MAYBE); /* This causes problems for ":234", since displaying is disabled * here */ /* 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' && vim_strchr((char_u *)"cgr", cmd[1]) != NULL && STRNCMP("sre", cmd, (size_t)3) != 0) { cmdidx = CMD_substitute; p = cmd + 1; } else { p = cmd; while (isalpha(*p)) ++p; /* check for non-alpha command */ if (p == cmd && vim_strchr((char_u *)"@!=><&~#", *p) != NULL) ++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) { STRCPY(IObuff, "Not an editor command"); if (!sourcing) { STRCAT(IObuff, ": "); STRNCAT(IObuff, *cmdlinep, 40); } errormsg = IObuff; 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) /* no range allowed */ { errormsg = e_norange; goto doend; } if (!(argt & BANG) && forceit) /* no allowed */ { errormsg = e_nobang; 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 (sourcing) { errormsg = (char_u *)"Backwards range given"; goto doend; } else if (ask_yesno((char_u *)"Backwards range given, OK to swap", FALSE) != '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)) { errormsg = 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) { alloc_cmdbuff((int)(STRLEN(p_mp) + STRLEN(p) + 2)); if (cmdbuff == NULL) /* out of memory */ goto doend; /* * Check for special command characters and echo them. */ for (i = 0; i < 6; i += 2) if (!STRCMP(make_cmd_chars[i], p)) for (s = (char_u *)(make_cmd_chars[i + 1]); *s; ++s) msg_outchar(*s - 16); STRCPY(cmdbuff, p_mp); STRCAT(cmdbuff, " "); STRCAT(cmdbuff, p); /* 'cmd' is not set here, because it is not used at CMD_make */ vim_free(*cmdlinep); *cmdlinep = cmdbuff; *cmdlinelenp = cmdbufflen; p = cmdbuff; } /* * Skip to start of argument. * Don't do this for the ":!" command, because ":!! -l" needs the space. */ if (cmdidx == CMD_bang) arg = p; else arg = skipwhite(p); if (cmdidx == CMD_write) { if (*arg == '>') /* append */ { if (*++arg != '>') /* typed wrong */ { errormsg = (char_u *)"Use w or w>>"; goto doend; } arg = skipwhite(arg + 1); append = TRUE; } else if (*arg == '!') /* :w !filter */ { ++arg; usefilter = TRUE; } } if (cmdidx == CMD_read) { if (forceit) { usefilter = TRUE; /* :r! filter if forceit */ forceit = FALSE; } else 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; } arg = skipwhite(arg); } /* * Check for "+command" argument, before checking for next command. * Don't do this for ":read !cmd" and ":write !cmd". */ if ((argt & EDITCMD) && !usefilter) do_ecmd_cmd = getargcmd(&arg); /* * Check for '|' to separate commands and '"' to start comments. * Don't do this for ":read !cmd" and ":write !cmd". */ if ((argt & TRLBAR) && !usefilter) { for (p = arg; *p; ++p) { if (*p == Ctrl('V')) { if (argt & (USECTRLV | XFILE)) ++p; /* skip CTRL-V and next char */ else STRCPY(p, p + 1); /* remove CTRL-V and skip next char */ if (*p == NUL) /* stop at NUL after CTRL-V */ break; } else if ((*p == '"' && !(argt & NOTRLCOM)) || *p == '|' || *p == '\n') { /* * We remove the '\' before the '|', unless USECTRLV is used * AND 'b' is present in 'cpoptions'. */ if ((vim_strchr(p_cpo, CPO_BAR) == NULL || !(argt & USECTRLV)) && *(p - 1) == '\\') { STRCPY(p - 1, p); /* remove the backslash */ --p; } else { if (*p == '|' || *p == '\n') nextcomm = p + 1; *p = NUL; break; } } } if (!(argt & NOTRLCOM)) /* remove trailing spaces */ del_trailing_spaces(arg); } /* * Check for to end a shell command. * Also do this for ":read !cmd" and ":write !cmd". */ else if (cmdidx == CMD_bang || usefilter) { for (p = arg; *p; ++p) { if (*p == '\\' && p[1]) ++p; else if (*p == '\n') { nextcomm = p + 1; *p = NUL; break; } } } 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 = skipwhite(arg + 1); } if ((argt & COUNT) && isdigit(*arg)) { n = getdigits(&arg); arg = skipwhite(arg); if (n <= 0) { errormsg = 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; /* * Be vi compatible: no error message for out of range. */ if (line2 > curbuf->b_ml.ml_line_count) line2 = curbuf->b_ml.ml_line_count; } } /* no arguments allowed */ if (!(argt & EXTRA) && *arg != NUL && vim_strchr((char_u *)"|\"", *arg) == NULL) { errormsg = e_trailing; goto doend; } if ((argt & NEEDARG) && *arg == NUL) { errormsg = e_argreq; goto doend; } /* * change '%' to curbuf->b_filename * '#' to curwin->w_altfile * '' to word under the cursor * '' to WORD under the cursor * '' to path name under the cursor * '' to file name for autocommand */ if (argt & XFILE) { char_u *buf = NULL; int expand_wildcards; /* need to expand wildcards */ int spec_idx; static char *(spec_str[]) = { "%", #define SPEC_PERC 0 "#", #define SPEC_HASH 1 "", /* cursor word */ #define SPEC_CWORD 2 "", /* cursor WORD */ #define SPEC_CCWORD 3 "", /* cursor path name */ #define SPEC_CFILE 4 "" /* autocommand file name */ #define SPEC_AFILE 5 }; #define SPEC_COUNT 6 /* * Decide to expand wildcards *before* replacing '%', '#', etc. If * the file name contains a wildcard it should not cause expanding. * (it will be expanded anyway if there is a wildcard before replacing). */ expand_wildcards = mch_has_wildcard(arg); for (p = arg; *p; ++p) { /* * Check if there is something to do. */ for (spec_idx = 0; spec_idx < SPEC_COUNT; ++spec_idx) { n = strlen(spec_str[spec_idx]); if (STRNCMP(p, spec_str[spec_idx], n) == 0) break; } if (spec_idx == SPEC_COUNT) /* no match */ continue; /* * Skip when preceded with a backslash "\%" and "\#". * Note: In "\\%" the % is also not recognized! */ if (*(p - 1) == '\\') { --p; STRCPY(p, p + 1); /* remove escaped char */ continue; } /* * word or WORD under cursor */ if (spec_idx == SPEC_CWORD || spec_idx == SPEC_CCWORD) { len = find_ident_under_cursor(&q, spec_idx == SPEC_CWORD ? (FIND_IDENT|FIND_STRING) : FIND_STRING); if (len == 0) goto doend; } /* * '#': Alternate file name * '%': Current file name * File name under the cursor * File name for autocommand * and following modifiers */ else { switch (spec_idx) { case SPEC_PERC: /* '%': current file */ if (curbuf->b_filename == NULL) { errormsg = (char_u *)"No file name to substitute for '%'"; goto doend; } q = curbuf->b_xfilename; break; case SPEC_HASH: /* '#' or "#99": alternate file */ q = p + 1; i = (int)getdigits(&q); n = q - p; /* length of what we expand */ if (buflist_name_nr(i, &q, &do_ecmd_lnum) == FAIL) { errormsg = (char_u *)"no alternate filename to substitute for '#'"; goto doend; } break; case SPEC_CFILE: /* file name under cursor */ q = file_name_at_cursor(FNAME_MESS|FNAME_HYP); if (q == NULL) goto doend; buf = q; break; case SPEC_AFILE: /* file name for autocommand */ q = autocmd_fname; if (q == NULL) { errormsg = (char_u *)"no autocommand filename to substitute for \"\""; goto doend; } break; } len = STRLEN(q); /* length of new string */ if (p[n] == '<') /* remove the file name extension */ { ++n; if ((s = vim_strrchr(q, '.')) != NULL && s >= gettail(q)) len = s - q; } else { char_u *tail; /* ":p" - full path/filename */ if (p[n] == ':' && p[n + 1] == 'p') { n += 2; s = FullName_save(q); vim_free(buf); /* free any allocated file name */ if (s == NULL) goto doend; q = s; len = STRLEN(q); buf = q; } tail = gettail(q); /* ":h" - head, remove "/filename" */ /* ":h" can be repeated */ while (p[n] == ':' && p[n + 1] == 'h') { n += 2; while (tail > q && ispathsep(tail[-1])) --tail; len = tail - q; while (tail > q && !ispathsep(tail[-1])) --tail; } /* ":t" - tail, just the basename */ if (p[n] == ':' && p[n + 1] == 't') { n += 2; len -= tail - q; q = tail; } /* ":e" - extension */ /* ":e" can be repeated */ /* ":r" - root, without extension */ /* ":r" can be repeated */ while (p[n] == ':' && (p[n + 1] == 'e' || p[n + 1] == 'r')) { /* find a '.' in the tail: * - for second :e: before the current fname * - otherwise: The last '.' */ if (p[n + 1] == 'e' && q > tail) s = q - 2; else s = q + len - 1; for ( ; s > tail; --s) if (s[0] == '.') break; if (p[n + 1] == 'e') /* :e */ { if (s > tail) { len += q - (s + 1); q = s + 1; } else if (q <= tail) len = 0; } else /* :r */ { if (s > tail) /* remove one extension */ len = s - q; } n += 2; } } /* TODO - ":s/pat/foo/" - substitute */ /* if (p[n] == ':' && p[n + 1] == 's') */ } /* * The new command line is build in cmdbuff[]. * First allocate it. */ i = STRLEN(*cmdlinep) + len + 3; if (nextcomm) i += STRLEN(nextcomm); /* add space for next command */ alloc_cmdbuff(i); if (cmdbuff == NULL) /* out of memory! */ goto doend; i = p - *cmdlinep; /* length of part before c */ vim_memmove(cmdbuff, *cmdlinep, (size_t)i); vim_memmove(cmdbuff + i, q, (size_t)len); /* append the string */ i += len; /* remember the end of the string */ STRCPY(cmdbuff + i, p + n); /* append what is after '#' or '%' */ p = cmdbuff + i - 1; /* remember where to continue */ vim_free(buf); /* free any allocated string */ if (nextcomm) /* append next command */ { i = STRLEN(cmdbuff) + 1; STRCPY(cmdbuff + i, nextcomm); nextcomm = cmdbuff + i; } cmd = cmdbuff + (cmd - *cmdlinep); arg = cmdbuff + (arg - *cmdlinep); vim_free(*cmdlinep); *cmdlinep = cmdbuff; *cmdlinelenp = cmdbufflen; } /* * One file argument: expand wildcards. * Don't do this with ":r !command" or ":w !command". */ if ((argt & NOSPC) && !usefilter) { #if defined(UNIX) /* * Only for Unix we check for more than one file name. * For other systems spaces are considered to be part * of the file name. * Only check here if there is no wildcard, otherwise ExpandOne * will check for errors. This allows ":e `ls ve*.c`" on Unix. */ if (!expand_wildcards) for (p = arg; *p; ++p) { /* skip escaped characters */ if (p[1] && (*p == '\\' || *p == Ctrl('V'))) ++p; else if (vim_iswhite(*p)) { errormsg = (char_u *)"Only one file name allowed"; goto doend; } } #endif /* * halve the number of backslashes (this is vi compatible) */ backslash_halve(arg, expand_wildcards); if (expand_wildcards) { if ((p = ExpandOne(arg, NULL, WILD_LIST_NOTFOUND, WILD_EXPAND_FREE)) == NULL) goto doend; n = arg - *cmdlinep; i = STRLEN(p) + n; if (nextcomm) i += STRLEN(nextcomm); alloc_cmdbuff(i); if (cmdbuff != NULL) { STRNCPY(cmdbuff, *cmdlinep, n); STRCPY(cmdbuff + n, p); if (nextcomm) /* append next command */ { i = STRLEN(cmdbuff) + 1; STRCPY(cmdbuff + i, nextcomm); nextcomm = cmdbuff + i; } cmd = cmdbuff + (cmd - *cmdlinep); arg = cmdbuff + n; vim_free(*cmdlinep); *cmdlinep = cmdbuff; *cmdlinelenp = cmdbufflen; } vim_free(p); } } } /* * Accept buffer name. Cannot be used at the same time with a buffer * number. */ if ((argt & BUFNAME) && *arg && addr_count == 0) { /* * :bdelete and :bunload take several arguments, separated by spaces: * find next space (skipping over escaped characters). * The others take one argument: ignore trailing spaces. */ if (cmdidx == CMD_bdelete || cmdidx == CMD_bunload) p = skiptowhite_esc(arg); else { p = arg + STRLEN(arg); while (p > arg && vim_iswhite(p[-1])) --p; } line2 = buflist_findpat(arg, p); if (line2 < 0) /* failed */ goto doend; addr_count = 1; arg = skipwhite(p); } /* * 6. switch on command name * arg points to the argument of the command * nextcomm points to the next command (if any) * cmd points to the name of the command (except for :make) * cmdidx is the index for the command * forceit is TRUE if ! present * addr_count is the number of addresses given * line1 is the first line number * line2 is the second line number or count * do_ecmd_cmd is +command argument to be used in edited file * do_ecmd_lnum is the line number in edited file * append is TRUE with ":w >>file" command * usefilter is TRUE with ":w !command" and ":r!command" * amount is number of '>' or '<' for shift command */ 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 && only_one_window()) exiting = TRUE; if (check_changed(curbuf, FALSE, FALSE) || check_more(TRUE) == FAIL || (only_one_window() && check_changed_any())) { exiting = FALSE; settmode(1); break; } if (only_one_window()) /* quit last window */ getout(0); close_window(curwin, TRUE); /* may free buffer */ break; /* * try to quit all windows */ case CMD_qall: exiting = TRUE; if (!check_changed_any()) getout(0); exiting = FALSE; settmode(1); break; /* * close current window, unless it is the last one */ case CMD_close: close_window(curwin, 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: #ifdef WIN32 /* * Check if external commands are allowed now. */ if (can_end_termcap_mode(TRUE) == FALSE) break; #endif if (!forceit) autowrite_all(); windgoto((int)Rows - 1, 0); outchar('\n'); flushbuf(); stoptermcap(); mch_restore_title(3); /* restore window titles */ mch_suspend(); /* call machine specific function */ maketitle(); starttermcap(); scroll_start(); /* scroll screen before redrawing */ must_redraw = CLEAR; set_winsize(0, 0, FALSE); /* May have resized window -- webb */ break; case CMD_exit: case CMD_xit: case CMD_wq: /* if more files or windows we won't exit */ if (check_more(FALSE) == OK && only_one_window()) exiting = TRUE; if (((cmdidx == CMD_wq || curbuf->b_changed) && do_write(arg, FALSE) == FAIL) || check_more(TRUE) == FAIL || (only_one_window() && check_changed_any())) { exiting = FALSE; settmode(1); break; } if (only_one_window()) /* quit last window, exit Vim */ getout(0); close_window(curwin, 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_recover: /* recover file */ recoverymode = TRUE; if (!check_changed(curbuf, FALSE, TRUE) && (*arg == NUL || setfname(arg, NULL, TRUE) == OK)) ml_recover(); recoverymode = FALSE; break; case CMD_args: /* * ":args file": handle like :next */ if (*arg != NUL && *arg != '|' && *arg != '\n') goto do_next; if (arg_count == 0) /* no file name list */ { if (check_fname() == OK) /* check for no file name */ smsg((char_u *)"[%s]", curbuf->b_filename); break; } /* * Overwrite the command, in most cases there is no scrolling * required and no wait_return(). */ gotocmdline(TRUE); for (i = 0; i < arg_count; ++i) { if (i == curwin->w_arg_idx) msg_outchar('['); msg_outtrans(arg_files[i]); if (i == curwin->w_arg_idx) msg_outchar(']'); msg_outchar(' '); } 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 (do_write(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; if (*arg != NUL) /* redefine file list */ { if (do_arglist(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 EMSG("Cannot go beyond last file"); break; } setpcmark(); if (*cmd == 's') /* split window first */ { if (win_split(0, FALSE) == FAIL) break; } else { register int other; /* * 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; if (i == arg_count - 1) arg_had_last = TRUE; (void)do_ecmd(0, arg_files[curwin->w_arg_idx], NULL, do_ecmd_cmd, p_hid, do_ecmd_lnum, FALSE); break; case CMD_previous: case CMD_sprevious: case CMD_Next: case CMD_sNext: i = curwin->w_arg_idx - (int)line2; goto donextfile; case CMD_rewind: case CMD_srewind: i = 0; goto donextfile; case CMD_last: case CMD_slast: i = arg_count - 1; goto donextfile; case CMD_argument: case CMD_sargument: if (addr_count) i = line2 - 1; else i = curwin->w_arg_idx; goto donextfile; case CMD_all: case CMD_sall: if (addr_count == 0) line2 = 9999; do_arg_all((int)line2); /* 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 (*arg) { errormsg = e_trailing; break; } if (addr_count == 0) /* default is current buffer */ (void)do_buffer(*cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO, DOBUF_CURRENT, FORWARD, 0, 0); else (void)do_buffer(*cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO, DOBUF_FIRST, 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' ? DOBUF_SPLIT : DOBUF_GOTO, DOBUF_MOD, 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' ? DOBUF_SPLIT : DOBUF_GOTO, DOBUF_CURRENT, 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' ? DOBUF_SPLIT : DOBUF_GOTO, DOBUF_CURRENT, BACKWARD, (int)line2, 0); break; case CMD_brewind: /* :brewind to first buffer */ case CMD_sbrewind: /* :sbrewind to first buffer */ (void)do_buffer(*cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO, DOBUF_FIRST, FORWARD, 0, 0); break; case CMD_blast: /* :blast to last buffer */ case CMD_sblast: /* :sblast to last buffer */ (void)do_buffer(*cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO, DOBUF_LAST, FORWARD, 0, 0); break; case CMD_bunload: /* :[N]bunload[!] [N] [bufname] unload buffer */ case CMD_bdelete: /* :[N]bdelete[!] [N] [bufname] delete buffer */ errormsg = do_bufdel( cmdidx == CMD_bdelete ? DOBUF_DEL : DOBUF_UNLOAD, arg, addr_count, (int)line1, (int)line2, forceit); break; case CMD_unhide: case CMD_sunhide: /* open a window for loaded buffers */ if (addr_count == 0) line2 = 9999; (void)do_buffer_all((int)line2, FALSE); break; case CMD_ball: case CMD_sball: /* open a window for every buffer */ if (addr_count == 0) line2 = 9999; (void)do_buffer_all((int)line2, TRUE); break; case CMD_buffers: case CMD_files: case CMD_ls: buflist_list(); break; case CMD_write: if (usefilter) /* input lines to shell command */ do_bang(1, line1, line2, FALSE, arg, TRUE, FALSE); else (void)do_write(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; /* * :sview [+command] file split window with new file, ro * :split [[+command] file] split window with current or new file * :new [[+command] file] split window with no or new file */ case CMD_sview: case CMD_split: case CMD_new: old_curwin = curwin; if (win_split(addr_count ? (int)line2 : 0, FALSE) == FAIL) break; /*FALLTHROUGH*/ case CMD_edit: case CMD_ex: case CMD_visual: case CMD_view: if ((cmdidx == CMD_new) && *arg == NUL) { setpcmark(); (void)do_ecmd(0, NULL, NULL, do_ecmd_cmd, TRUE, (linenr_t)1, FALSE); } else if (cmdidx != CMD_split || *arg != NUL) { n = readonlymode; if (cmdidx == CMD_view || cmdidx == CMD_sview) readonlymode = TRUE; setpcmark(); (void)do_ecmd(0, arg, NULL, do_ecmd_cmd, p_hid, do_ecmd_lnum, FALSE); readonlymode = n; } else updateScreen(NOT_VALID); /* if ":split file" worked, set alternate filename in * old window to new file */ if ((cmdidx == CMD_new || cmdidx == CMD_split) && *arg != NUL && curwin != old_curwin && old_curwin->w_buffer != curbuf) old_curwin->w_alt_fnum = curbuf->b_fnum; break; #ifdef USE_GUI /* * Change from the terminal version to the GUI version. File names may * be given to redefine the args list -- webb */ case CMD_gvim: case CMD_gui: if (arg[0] == '-' && arg[1] == 'f' && (arg[2] == NUL || vim_iswhite(arg[2]))) { gui.dofork = FALSE; arg = skipwhite(arg + 2); } else gui.dofork = TRUE; if (!gui.in_use) gui_start(); if (*arg != NUL && *arg != '|' && *arg != '\n') goto do_next; break; #endif case CMD_file: do_file(arg, forceit); break; case CMD_swapname: if (curbuf->b_ml.ml_mfp == NULL || (p = curbuf->b_ml.ml_mfp->mf_fname) == 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) /* :r!cmd */ { do_bang(1, line1, line2, FALSE, arg, FALSE, TRUE); break; } if (u_save(line2, (linenr_t)(line2 + 1)) == FAIL) break; if (*arg == NUL) { if (check_fname() == FAIL) /* check for no file name */ break; i = readfile(curbuf->b_filename, curbuf->b_sfilename, line2, FALSE, (linenr_t)0, MAXLNUM, FALSE); } else { i = readfile(arg, NULL, line2, FALSE, (linenr_t)0, MAXLNUM, FALSE); } 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 NameBuff for home directory name */ { expand_env((char_u *)"$HOME", NameBuff, MAXPATHL); arg = NameBuff; } #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 (vim_chdir((char *)arg)) emsg(e_failed); break; } /*FALLTHROUGH*/ case CMD_pwd: if (mch_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 */ for ( ;!got_int; mch_breakcheck()) { print_line(line1, (cmdidx == CMD_number || cmdidx == CMD_pound)); if (++line1 > line2) break; flushbuf(); /* show one line at a time */ } setpcmark(); curwin->w_cursor.lnum = line2; /* put cursor at last line */ if (cmdidx == CMD_list) curwin->w_p_list = i; break; case CMD_shell: do_shell(NULL); break; case CMD_sleep: n = curwin->w_winpos + curwin->w_row - msg_scrolled; if (n >= 0) { windgoto((int)n, curwin->w_col); flushbuf(); } mch_delay(line2 * 1000L, TRUE); break; case CMD_stag: postponed_split = TRUE; /*FALLTHROUGH*/ case CMD_tag: do_tag(arg, 0, addr_count ? (int)line2 : 1); break; case CMD_pop: do_tag((char_u *)"", 1, addr_count ? (int)line2 : 1); break; case CMD_tags: do_tags(); break; case CMD_marks: do_marks(arg); break; case CMD_jumps: do_jumps(); break; case CMD_ascii: do_ascii(); break; case CMD_checkpath: find_pattern_in_path(NULL, 0, FALSE, FALSE, CHECK_PATH, 1L, forceit ? ACTION_SHOW_ALL : ACTION_SHOW, (linenr_t)1, (linenr_t)MAXLNUM); 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)do_set(arg); break; case CMD_fixdel: do_fixdel(); break; #ifdef AUTOCMD case CMD_autocmd: /* * Disallow auto commands from .exrc and .vimrc in current * directory for security reasons. */ if (secure) { secure = 2; errormsg = e_curdir; } else do_autocmd(arg, forceit); /* handle the auto commands */ break; case CMD_doautocmd: do_doautocmd(arg); /* apply the automatic commands */ do_modelines(); break; #endif 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_nmap: case CMD_vmap: case CMD_cmap: case CMD_imap: case CMD_map: case CMD_nnoremap: case CMD_vnoremap: 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); msg_outchar('\n'); } case CMD_nunmap: case CMD_vunmap: 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; } /* nmap, nunmap, nnoremap */ else if (*cmd == 'n' && *(cmd + 1) != 'o') { i += NORMAL; ++cmd; } else if (*cmd == 'v') /* vmap, vunmap, vnoremap */ { i += VISUAL; ++cmd; } else if (forceit || i) /* map!, unmap!, noremap!, abbrev */ i += INSERT + CMDLINE; else /* map, unmap, noremap */ i += NORMAL + VISUAL; switch (do_map((*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_mapclear: case CMD_imapclear: case CMD_nmapclear: case CMD_vmapclear: case CMD_cmapclear: map_clear(*cmd, forceit, FALSE); break; case CMD_abclear: case CMD_iabclear: case CMD_cabclear: map_clear(*cmd, FALSE, TRUE); break; #ifdef USE_GUI case CMD_menu: case CMD_noremenu: case CMD_unmenu: case CMD_nmenu: case CMD_nnoremenu: case CMD_nunmenu: case CMD_vmenu: case CMD_vnoremenu: case CMD_vunmenu: case CMD_imenu: case CMD_inoremenu: case CMD_iunmenu: case CMD_cmenu: case CMD_cnoremenu: case CMD_cunmenu: gui_do_menu(cmd, arg, forceit); break; #endif /* USE_GUI */ case CMD_display: case CMD_registers: do_dis(arg); /* display buffer contents */ break; case CMD_help: do_help(arg); break; case CMD_version: do_version(arg); break; case CMD_winsize: /* obsolete command */ line1 = getdigits(&arg); arg = skipwhite(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_op_start.lnum = line1; curbuf->b_op_end.lnum = line2; op_line_count = line2 - line1 + 1; op_motion_type = MLINE; if (cmdidx != CMD_yank) /* set cursor position for undo */ { setpcmark(); curwin->w_cursor.lnum = line1; beginline(MAYBE); } switch (cmdidx) { case CMD_delete: do_delete(); break; case CMD_yank: (void)do_yank(FALSE, TRUE); break; #ifdef RIGHTLEFT case CMD_rshift: do_shift(curwin->w_p_rl ? LSHIFT : RSHIFT, FALSE, amount); break; case CMD_lshift: do_shift(curwin->w_p_rl ? RSHIFT : LSHIFT, FALSE, amount); break; #else case CMD_rshift: do_shift(RSHIFT, FALSE, amount); break; case CMD_lshift: do_shift(LSHIFT, FALSE, amount); break; #endif } break; case CMD_put: yankbuffer = regname; curwin->w_cursor.lnum = line2; do_put(forceit ? BACKWARD : FORWARD, -1L, FALSE); break; case CMD_t: case CMD_copy: case CMD_move: n = get_address(&arg); if (arg == NULL) /* error detected */ { nextcomm = NULL; break; } /* * 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(); beginline(MAYBE); updateScreen(NOT_VALID); break; case CMD_and: /* :& */ case CMD_tilde: /* :~ */ case CMD_substitute: /* :s */ do_sub(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_flush(); break; } ++line2; } do_do_join(line2 - line1 + 1, !forceit, FALSE); beginline(TRUE); break; case CMD_global: if (forceit) *cmd = 'v'; case CMD_vglobal: do_glob(*cmd, line1, line2, arg); break; case CMD_at: /* :[addr]@r */ curwin->w_cursor.lnum = line2; /* put the register in mapbuf */ if (do_execbuf(*arg, TRUE, vim_strchr(p_cpo, CPO_EXECBUF) != NULL) == FAIL) beep_flush(); else /* execute from the mapbuf */ while (vpeekc() == ':') { (void)vgetc(); (void)do_cmdline((char_u *)NULL, TRUE, TRUE); } break; case CMD_bang: do_bang(addr_count, line1, line2, forceit, arg, TRUE, TRUE); 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); /* :so read ex commands */ else if (do_source(arg, FALSE) == FAIL) emsg2(e_notopen, arg); break; #ifdef VIMINFO case CMD_rviminfo: p = p_viminfo; if (*p_viminfo == NUL) p_viminfo = (char_u *)"'100"; if (read_viminfo(arg, TRUE, TRUE, forceit) == FAIL) EMSG("Cannot open viminfo file for reading"); p_viminfo = p; break; case CMD_wviminfo: p = p_viminfo; if (*p_viminfo == NUL) p_viminfo = (char_u *)"'100"; write_viminfo(arg, forceit); p_viminfo = p; break; #endif /* VIMINFO */ 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 (mch_isdir(arg)) { EMSG2("\"%s\" is a directory", arg); break; } #endif if (!forceit && vim_fexists(arg)) { EMSG2("\"%s\" exists (use ! to override)", arg); break; } if ((fd = fopen((char *)arg, WRITEBIN)) == NULL) { EMSG2("Cannot open \"%s\" for writing", arg); break; } /* Write the version command for :mkvimrc */ if (cmdidx == CMD_mkvimrc) { #ifdef USE_CRNL fprintf(fd, "version 4.0\r\n"); #else fprintf(fd, "version 4.0\n"); #endif } if (makemap(fd) == FAIL || makeset(fd) == FAIL || fclose(fd)) emsg(e_write); break; } case CMD_cc: qf_jump(0, addr_count ? (int)line2 : 0); break; case CMD_cfile: 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)do_set(arg); } if (qf_init() == OK) qf_jump(0, 0); /* display first error */ break; case CMD_clist: qf_list(forceit); break; case CMD_cnext: qf_jump(FORWARD, addr_count ? (int)line2 : 1); break; case CMD_cNext: case CMD_cprevious: qf_jump(BACKWARD, addr_count ? (int)line2 : 1); break; case CMD_cquit: 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; beginline(MAYBE); (void)setmark(*arg); /* set mark */ curwin->w_cursor = pos; /* restore curwin->w_cursor */ break; 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_retab: n = getdigits(&arg); do_retab(line1, line2, (int)n, forceit); u_clearline(); updateScreen(NOT_VALID); break; case CMD_make: do_make(arg); break; /* * :normal[!] {commands} - execute normal mode commands * Mostly used for ":autocmd". */ case CMD_normal: /* * Stuff the argument into the typeahead buffer. * Execute normal() until there is no more typeahead than * there was before this command. */ len = typelen; ins_typebuf(arg, forceit ? -1 : 0, 0, TRUE); while ((!stuff_empty() || (!typebuf_typed() && typelen > len)) && !got_int) { adjust_cursor(); /* put cursor on an existing line */ cursupdate(); /* update cursor position */ normal(); /* get and execute a normal mode command */ } break; case CMD_isearch: case CMD_dsearch: i = ACTION_SHOW; goto find_pat; case CMD_ilist: case CMD_dlist: i = ACTION_SHOW_ALL; goto find_pat; case CMD_ijump: case CMD_djump: i = ACTION_GOTO; goto find_pat; case CMD_isplit: case CMD_dsplit: i = ACTION_SPLIT; find_pat: { int whole = TRUE; n = 1; if (isdigit(*arg)) /* get count */ { n = getdigits(&arg); arg = skipwhite(arg); } if (*arg == '/') /* Match regexp, not just whole words */ { whole = FALSE; ++arg; for (p = arg; *p && *p != '/'; p++) if (*p == '\\' && p[1] != NUL) p++; if (*p) { *p++ = NUL; p = skipwhite(p); /* Check for trailing illegal characters */ if (*p && vim_strchr((char_u *)"|\"\n", *p) == NULL) errormsg = e_trailing; else nextcomm = p; } } find_pattern_in_path(arg, (int)STRLEN(arg), whole, !forceit, *cmd == 'd' ? FIND_DEFINE : FIND_ANY, n, i, line1, line2); } break; default: /* Normal illegal commands have already been handled */ errormsg = (char_u *)"Sorry, this command is not implemented"; } doend: if (errormsg != NULL) { emsg(errormsg); if (sourcing) { MSG_OUTSTR(": "); msg_outtrans(*cmdlinep); } } if (did_emsg) nextcomm = NULL; /* cancel nextcomm at an error */ 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, FALSE)); } /* * 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 do_write(fname, append) char_u *fname; int append; { 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; /* * Not writing the whole file is only allowed with '!'. */ if ((line1 != 1 || line2 != curbuf->b_ml.ml_line_count) && !forceit && !append && !p_wa) { EMSG("Use ! to write partial buffer"); return FAIL; } } /* * write to other file or b_notedited set or not writing the whole file: * overwriting only allowed with '!' */ if ((other || curbuf->b_notedited) && !forceit && !append && !p_wa && vim_fexists(fname)) { /* don't overwrite existing file */ #ifdef UNIX /* with UNIX it is possible to open a directory */ if (mch_isdir(fname)) EMSG2("\"%s\" is a directory", fname); else #endif emsg(e_exists); return FAIL; } return (buf_write(curbuf, fname, sfname, line1, line2, append, forceit, TRUE, FALSE)); } /* * start editing a new file * * fnum: file number; if zero use fname/sfname * 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) * set_help: set b_help flag of (new) buffer before opening file * * return FAIL for failure, OK otherwise */ int do_ecmd(fnum, fname, sfname, command, hide, newlnum, set_help) int fnum; char_u *fname; char_u *sfname; char_u *command; int hide; linenr_t newlnum; int set_help; { int other_file; /* TRUE if editing another file */ int oldbuf = FALSE; /* TRUE if using existing buffer */ BUF *buf; if (fnum != 0) { if (fnum == curbuf->b_fnum) /* file is already being edited */ return OK; /* nothing to do */ other_file = TRUE; } else { /* if no short name given, use fname for short name */ if (sfname == NULL) sfname = fname; #ifdef USE_FNAME_CASE # ifdef USE_LONG_FNAME if (USE_LONG_FNAME) # endif fname_case(sfname); /* set correct case for short filename */ #endif if (fname == NULL) other_file = TRUE; /* there is no file name */ else if (*fname == NUL && curbuf->b_filename == NULL) 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 (fnum == 0 && other_file && fname != NULL) setaltfname(fname, sfname, (linenr_t)1); return FAIL; } /* * End Visual mode before switching to another buffer, so the text can be * copied into the GUI selection buffer. */ if (VIsual_active) end_visual_mode(); /* * 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(); if (fnum) buf = buflist_findnr(fnum); else 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; buf_check_timestamp(buf); } /* * 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) { #ifdef AUTOCMD apply_autocmds(EVENT_BUFLEAVE, NULL, NULL); #endif #ifdef VIMINFO curbuf->b_last_cursor = curwin->w_cursor; #endif buf_copy_options(curbuf, buf, TRUE); close_buffer(curwin, 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; if (set_help) curbuf->b_help = TRUE; /* * 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_op_start.lnum = 0; /* clear '[ and '] marks */ curbuf->b_op_end.lnum = 0; } /* * Check if we are editing the w_arg_idx file in the argument list. */ check_arg_idx(); if (!oldbuf) /* need to read the file */ (void)open_buffer(); #ifdef AUTOCMD else apply_autocmds(EVENT_BUFENTER, NULL, NULL); #endif win_init(curwin); maketitle(); if (command == NULL) { if (newlnum) { curwin->w_cursor.lnum = newlnum; check_cursor(); beginline(MAYBE); } else beginline(TRUE); } /* * Did not read the file, need to show some info about the file. * Do this after setting the cursor. */ if (oldbuf) fileinfo(did_cd, TRUE, FALSE); if (command != NULL) do_cmdline(command, TRUE, FALSE); --RedrawingDisabled; if (!skip_redraw) updateScreen(CURSUPD); /* redraw now */ if (p_im) need_start_insertmode = TRUE; 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 (vim_isspace(*arg)) command = (char_u *)"$"; else { /* * should check for "\ " (but vi has a bug that prevents it to work) */ command = arg; arg = skiptowhite(command); if (*arg) *arg++ = NUL; /* terminate command with NUL */ } arg = skipwhite(arg); /* skip over spaces */ *argp = arg; } return command; } /* * Halve the number of backslashes in a file name argument. * For MS-DOS we only do this if the character after the backslash * is not a normal file character. * For Unix, when wildcards are going to be expanded, don't remove * backslashes before special characters. */ static void backslash_halve(p, expand_wildcards) char_u *p; int expand_wildcards; /* going to expand wildcards later */ { for ( ; *p; ++p) if (is_backslash(p) #if defined(MSDOS) || defined(WIN32) && p[1] != '*' && p[1] != '?' #endif #if defined(UNIX) || defined(OS2) && !(expand_wildcards && vim_strchr((char_u *)" *?[{`$\\", p[1])) #endif ) STRCPY(p, p + 1); } static void do_make(arg) char_u *arg; { if (*p_ef == NUL) { EMSG("errorfile option not set"); return; } autowrite_all(); vim_remove(p_ef); sprintf((char *)IObuff, "%s %s %s", arg, p_sp, p_ef); MSG_OUTSTR(":!"); msg_outtrans(IObuff); /* show what we are doing */ do_shell(IObuff); #ifdef AMIGA flushbuf(); /* read window status report and redraw before message */ (void)char_avail(); #endif if (qf_init() == OK) qf_jump(0, 0); /* display first error */ vim_remove(p_ef); } /* * Redefine the argument list to 'str'. * * Return FAIL for failure, OK otherwise. */ static int do_arglist(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]; vim_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 et.al. a backslash is part of a file name. * Only skip ", space and tab. */ if (is_backslash(str)) *p++ = *++str; else { if (!inquote && vim_isspace(*str)) break; if (*str == '"') inquote ^= TRUE; else *p++ = *str; } } str = skipwhite(str); *p = NUL; } i = ExpandWildCards(new_count, new_files, &exp_count, &exp_files, FALSE, TRUE); vim_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; arg_had_last = FALSE; /* * put all file names in the buffer list */ for (i = 0; i < arg_count; ++i) (void)buflist_add(arg_files[i]); return OK; } /* * Return TRUE if "str" starts with a backslash that should be removed. * For MS-DOS, WIN32 and OS/2 this is only done when the character after the * backslash is not a normal file name character. */ static int is_backslash(str) char_u *str; { #ifdef BACKSLASH_IN_FILENAME return (str[0] == '\\' && str[1] != NUL && !(isfilechar(str[1]) && str[1] != '\\')); #else return (str[0] == '\\' && str[1] != NUL); #endif } /* * Check if we are editing the w_arg_idx file in the argument list. */ void check_arg_idx() { int t; if (arg_count > 1 && (curbuf->b_filename == NULL || curwin->w_arg_idx >= arg_count || (t = fullpathcmp(arg_files[curwin->w_arg_idx], curbuf->b_filename)) == FPC_DIFF || t == FPC_DIFFX)) curwin->w_arg_idx_invalid = TRUE; else curwin->w_arg_idx_invalid = FALSE; } void gotocmdline(clr) int clr; { msg_start(); if (clr) /* clear the bottom line(s) */ msg_clr_eos(); /* will reset clear_cmdline */ windgoto(cmdline_row, 0); } 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 the 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. * That changed buffer becomes the current buffer. */ static int check_changed_any() { BUF *buf; int save; if (!forceit) { for (buf = firstbuf; buf != NULL; buf = buf->b_next) { if (buf->b_changed) { /* There must be a wait_return for this message, do_buffer * will cause a redraw */ exiting = FALSE; if (EMSG2("No write since last change for buffer \"%s\"", buf->b_xfilename == NULL ? (char_u *)"No File" : buf->b_xfilename)) { save = no_wait_return; no_wait_return = FALSE; wait_return(FALSE); no_wait_return = save; } (void)do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0); 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 && only_one_window() && arg_count > 1 && !arg_had_last && quitmore == 0) { if (message) { EMSGN("%ld more files to edit", 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 a new or existing file * 'fnum' is the number of the file, if zero use fname/sfname * * 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(fnum, fname, sfname, setpm, lnum) int fnum; char_u *fname; char_u *sfname; int setpm; linenr_t lnum; { int other; if (fnum == 0) { fname_expand(&fname, &sfname); /* make fname full path, set sfname */ other = otherfile(fname); } else other = (fnum != curbuf->b_fnum); if (other) ++no_wait_return; /* don't wait for autowrite message */ if (other && !forceit && curbuf->b_nwindows == 1 && !p_hid && curbuf->b_changed && autowrite(curbuf) == FAIL) { if (other) --no_wait_return; emsg(e_nowrtmsg); return 2; /* file has been changed */ } if (other) --no_wait_return; if (setpm) setpcmark(); if (!other) { if (lnum != 0) curwin->w_cursor.lnum = lnum; check_cursor(); beginline(MAYBE); return 0; /* it's in the same file */ } if (do_ecmd(fnum, fname, sfname, NULL, p_hid, lnum, FALSE) == OK) return -1; /* opened another file */ return 1; /* error encountered */ } /* * 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 nextwild(type) int type; { int i; char_u *p1; char_u *p2; int oldlen; int difflen; int v; if (cmd_numfiles == -1) set_expand_context(cmdfirstc, cmdbuff); if (expand_context == EXPAND_UNSUCCESSFUL) { beep_flush(); 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; MSG_OUTSTR("..."); /* show that we are busy */ flushbuf(); i = expand_pattern - cmdbuff; oldlen = cmdpos - i; if (type == WILD_NEXT || type == WILD_PREV) { /* * Get next/previous match for a previous expanded pattern. */ p2 = ExpandOne(NULL, NULL, 0, type); } else { /* * Translate string into pattern and expand it. */ if ((p1 = addstar(&cmdbuff[i], oldlen)) == NULL) p2 = NULL; else { p2 = ExpandOne(p1, strnsave(&cmdbuff[i], oldlen), WILD_HOME_REPLACE, type); vim_free(p1); } } if (p2 != NULL) { if (cmdlen + (difflen = STRLEN(p2) - oldlen) > cmdbufflen - 4) v = realloc_cmdbuff(cmdlen + difflen); else v = OK; if (v == OK) { vim_strncpy(&cmdbuff[cmdpos + difflen], &cmdbuff[cmdpos], cmdlen - cmdpos); STRNCPY(&cmdbuff[i], p2, STRLEN(p2)); cmdlen += difflen; cmdpos += difflen; } vim_free(p2); } redrawcmd(); if (cmd_numfiles <= 0 && p2 == NULL) beep_flush(); else if (cmd_numfiles == 1) (void)ExpandOne(NULL, NULL, 0, WILD_FREE); /* free expanded pattern */ expand_interactively = FALSE; /* reset for next call */ return OK; } #define MAXSUFLEN 30 /* maximum length of a file suffix */ /* * Do wildcard expansion on the string 'str'. * Return a pointer to alloced memory containing the new string. * Return NULL for failure. * * mode = WILD_FREE: just free previously expanded matches * mode = WILD_EXPAND_FREE: normal expansion, do not keep matches * mode = WILD_EXPAND_KEEP: normal expansion, keep matches * mode = WILD_NEXT: use next match in multiple match, wrap to first * mode = WILD_PREV: use previous match in multiple match, wrap to first * mode = WILD_ALL: return all matches concatenated * mode = WILD_LONGEST: return longest matched part * * options = WILD_LIST_NOTFOUND: list entries without a match * options = WILD_HOME_REPLACE: do home_replace() for buffer names */ char_u * ExpandOne(str, orig, options, mode) char_u *str; char_u *orig; /* original string which is expanded */ int options; int mode; { char_u *ss = NULL; static char_u **cmd_files = NULL; /* list of input files */ static int findex; static char_u *orig_save = NULL; /* kept value of orig */ int i, found = 0; int multmatch = FALSE; long_u len; char_u *setsuf; int fnamelen, setsuflen; char_u suf_buf[MAXSUFLEN]; char_u *p; /* * first handle the case of using an old match */ if (mode == WILD_NEXT || mode == WILD_PREV) { if (cmd_numfiles > 0) { if (mode == WILD_PREV) { if (findex == -1) findex = cmd_numfiles; --findex; } else /* mode == WILD_NEXT */ ++findex; /* * When wrapping around, return the original string, set findex to * -1. */ if (findex < 0) { if (orig_save == NULL) findex = cmd_numfiles - 1; else findex = -1; } if (findex >= cmd_numfiles) { if (orig_save == NULL) findex = 0; else findex = -1; } if (findex == -1) return strsave(orig_save); return strsave(cmd_files[findex]); } else return NULL; } /* free old names */ if (cmd_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) { FreeWild(cmd_numfiles, cmd_files); cmd_numfiles = -1; vim_free(orig_save); orig_save = NULL; } findex = 0; if (mode == WILD_FREE) /* only release file name */ return NULL; if (cmd_numfiles == -1) { vim_free(orig_save); orig_save = orig; if (ExpandFromContext(str, &cmd_numfiles, &cmd_files, FALSE, options) == FAIL) /* error: do nothing */; else if (cmd_numfiles == 0) { if (!expand_interactively) emsg(e_nomatch); } else { /* * If the pattern starts with a '~', replace the home diretory * with '~' again. */ if (*str == '~' && (options & WILD_HOME_REPLACE)) { for (i = 0; i < cmd_numfiles; ++i) { p = home_replace_save(NULL, cmd_files[i]); if (p != NULL) { vim_free(cmd_files[i]); cmd_files[i] = p; } } } /* * Insert backslashes into a file name before a space, \, %, # and * wildmatch characters, except '~'. */ if (expand_interactively && (expand_context == EXPAND_FILES || expand_context == EXPAND_BUFFERS || expand_context == EXPAND_DIRECTORIES)) { for (i = 0; i < cmd_numfiles; ++i) { p = strsave_escaped(cmd_files[i], #ifdef BACKSLASH_IN_FILENAME (char_u *)" *?[{`$%#"); #else (char_u *)" *?[{`$\\%#"); #endif if (p != NULL) { vim_free(cmd_files[i]); cmd_files[i] = p; } } } if (mode != WILD_ALL && mode != WILD_LONGEST) { if (cmd_numfiles > 1) /* more than one match; check suffix */ { found = -2; for (i = 0; i < cmd_numfiles; ++i) { fnamelen = STRLEN(cmd_files[i]); setsuflen = 0; for (setsuf = p_su; *setsuf; ) { setsuflen = copy_option_part(&setsuf, suf_buf, MAXSUFLEN, ".,"); if (fnamelen >= setsuflen && STRNCMP(suf_buf, cmd_files[i] + fnamelen - setsuflen, (size_t)setsuflen) == 0) break; setsuflen = 0; } if (setsuflen) /* suffix matched: ignore file */ continue; if (found >= 0) { multmatch = TRUE; break; } found = i; } } if (multmatch || found < 0) { /* 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) emsg(e_toomany); else beep_flush(); found = 0; /* return first one */ multmatch = TRUE; /* for found < 0 */ } if (found >= 0 && !(multmatch && mode == WILD_EXPAND_FREE)) ss = strsave(cmd_files[found]); } } } /* Find longest common part */ if (mode == WILD_LONGEST && cmd_numfiles > 0) { for (len = 0; cmd_files[0][len]; ++len) { for (i = 0; i < cmd_numfiles; ++i) { #ifdef CASE_INSENSITIVE_FILENAME if ((expand_context == EXPAND_DIRECTORIES || expand_context == EXPAND_FILES || expand_context == EXPAND_BUFFERS) && toupper(cmd_files[i][len]) != toupper(cmd_files[0][len])) break; else #endif if (cmd_files[i][len] != cmd_files[0][len]) break; } if (i < cmd_numfiles) { vim_beep(); break; } } ss = alloc((unsigned)len + 1); if (ss) { STRNCPY(ss, cmd_files[0], len); ss[len] = NUL; } findex = -1; /* next p_wc gets first one */ } /* Concatenate all matching names */ if (mode == WILD_ALL && cmd_numfiles > 0) { 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, " "); } } } if (mode == WILD_EXPAND_FREE || mode == WILD_ALL) { FreeWild(cmd_numfiles, cmd_files); cmd_numfiles = -1; } return ss; } /* * show all matches for completion on the command line */ static int showmatches(buff) char_u *buff; { char_u *file_str; int num_files; char_u **files_found; int i, j, k; int maxlen; int lines; int columns; char_u *p; int lastlen; set_expand_context(cmdfirstc, cmdbuff); if (expand_context == EXPAND_UNSUCCESSFUL) { beep_flush(); 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; } msg_didany = FALSE; /* lines_left will be set */ msg_start(); /* prepare for paging */ msg_outchar('\n'); flushbuf(); cmdline_row = msg_row; msg_didany = FALSE; /* lines_left will be set again */ msg_start(); /* prepare for paging */ /* find all files that match the description */ if (ExpandFromContext(file_str, &num_files, &files_found, FALSE, 0) == FAIL) { num_files = 0; files_found = (char_u **)""; } /* find the length of the longest file name */ maxlen = 0; for (i = 0; i < num_files; ++i) { if (expand_context == EXPAND_FILES || expand_context == EXPAND_BUFFERS) { home_replace(NULL, files_found[i], NameBuff, MAXPATHL); j = strsize(NameBuff); } else j = strsize(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) { lastlen = 999; for (k = i; k < num_files; k += lines) { for (j = maxlen - lastlen; --j >= 0; ) msg_outchar(' '); if (expand_context == EXPAND_FILES || expand_context == EXPAND_BUFFERS) { /* highlight directories */ j = (mch_isdir(files_found[k])); home_replace(NULL, files_found[k], NameBuff, MAXPATHL); p = NameBuff; } else { j = FALSE; p = files_found[k]; } if (j) start_highlight(); lastlen = msg_outtrans(p); if (j) stop_highlight(); } msg_outchar('\n'); flushbuf(); /* show one line at a time */ if (got_int) { got_int = FALSE; break; } } vim_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 */ expand_interactively = FALSE; return OK; } /* * Prepare a string for expansion. * When expanding file names: The string will be used with ExpandWildCards(). * Copy the file name into allocated memory and add a '*' at the end. * When expanding other names: The string will be used with regcomp(). Copy * the name into allocated memory and add ".*" at the end. */ char_u * addstar(fname, len) char_u *fname; int len; { char_u *retval; int i, j; int new_len; char_u *tail; 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 vim_regcomp(). First work out how long it will be: */ /* for help tags the translation is done in find_help_tags() */ if (expand_context == EXPAND_HELP) retval = strnsave(fname, len); else { new_len = len + 2; /* +2 for '^' at start, NUL at end */ for (i = 0; i < len; i++) { if (fname[i] == '*' || fname[i] == '~') new_len++; /* '*' needs to be replaced by ".*" '~' needs to be replaced by "\~" */ /* Buffer names are like file names. "." should be literal */ if (expand_context == EXPAND_BUFFERS && fname[i] == '.') new_len++; /* "." becomes "\." */ } retval = alloc(new_len); if (retval != NULL) { retval[0] = '^'; j = 1; for (i = 0; i < len; i++, j++) { if (fname[i] == '\\' && ++i == len) /* skip backslash */ break; switch (fname[i]) { case '*': retval[j++] = '.'; break; case '~': retval[j++] = '\\'; break; case '?': retval[j] = '.'; continue; case '.': if (expand_context == EXPAND_BUFFERS) retval[j++] = '\\'; break; } retval[j] = fname[i]; } retval[j] = NUL; } } } else { retval = alloc(len + 4); if (retval != NULL) { STRNCPY(retval, fname, len); retval[len] = NUL; backslash_halve(retval, TRUE); /* remove some backslashes */ len = STRLEN(retval); /* * Don't add a star to ~, ~user, $var or `cmd`. * ~ would be at the start of the tail. * $ could be anywhere in the tail. * ` could be anywhere in the file name. */ tail = gettail(retval); if (*tail != '~' && vim_strchr(tail, '$') == NULL && vim_strchr(retval, '`') == NULL) { #ifdef MSDOS /* * if there is no dot in the file name, add "*.*" instead of * "*". */ for (i = len - 1; i >= 0; --i) if (vim_strchr((char_u *)".\\/:", retval[i]) != NULL) break; if (i < 0 || retval[i] != '.') { retval[len++] = '*'; retval[len++] = '.'; } #endif retval[len++] = '*'; } retval[len] = NUL; } } return retval; } /* * do_source: 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 do_source(fname, check_other) register char_u *fname; int check_other; /* check for .vimrc and _vimrc */ { register FILE *fp; register int len; #ifdef USE_CRNL int has_cr; int textmode = -1; /* -1 = unknown, 0 = NL, 1 = CR-NL */ int error = FALSE; #endif /* use NameBuff for expanded name */ expand_env(fname, NameBuff, MAXPATHL); fp = fopen((char *)NameBuff, READBIN); if (fp == NULL && check_other) { /* * Try again, replacing file name ".vimrc" by "_vimrc" or vice versa * (if applicable) */ len = STRLEN(NameBuff); if (((len > 6 && ispathsep(NameBuff[len - 7])) || len == 6) && (NameBuff[len - 6] == '.' || NameBuff[len - 6] == '_') && (STRCMP(&NameBuff[len - 5], "vimrc") == 0)) { if (NameBuff[len - 6] == '_') NameBuff[len - 6] = '.'; else NameBuff[len - 6] = '_'; fp = fopen((char *)NameBuff, READBIN); } } if (fp == NULL) return FAIL; #ifdef USE_CRNL /* no automatic textmode: Set default to CR-NL */ if (!p_ta) textmode = 1; #endif sourcing_name = fname; sourcing_lnum = 1; #ifdef SLEEP_IN_EMSG ++dont_sleep; /* don't call sleep() in emsg() */ #endif 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 USE_CRNL has_cr = (len > 0 && IObuff[len - 1] == '\r'); if (textmode == -1) { if (has_cr) textmode = 1; else textmode = 0; } if (textmode) { if (has_cr) /* remove trailing CR-LF */ --len; else /* lines like ":map xx yy^M" will have failed */ { if (!error) EMSG("Warning: Wrong line separator, ^M may be missing"); error = TRUE; textmode = 0; } } #endif /* escaped newline, read more */ if (len > 0 && len < IOSIZE && IObuff[len - 1] == Ctrl('V')) { IObuff[len - 1] = '\n'; /* remove CTRL-V */ ++sourcing_lnum; continue; } IObuff[len] = NUL; } /* check for ^C here, so recursive :so will be broken */ mch_breakcheck(); do_cmdline(IObuff, TRUE, TRUE); len = 0; ++sourcing_lnum; } fclose(fp); if (got_int) emsg(e_interr); #ifdef SLEEP_IN_EMSG --dont_sleep; #endif sourcing_name = NULL; sourcing_lnum = 0; return OK; } /* * get a single EX address * * Set ptr to the next character after the part that was interpreted. * Set ptr to NULL when an error is encountered. */ 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 = skipwhite(*ptr); 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 || (check_mark( fp = getmark(*cmd++, FALSE)) == FAIL)) goto error; lnum = fp->lnum; break; case '/': case '?': /* '/' or '?' - search */ c = *cmd++; pos = curwin->w_cursor; /* save curwin->w_cursor */ if (c == '/') /* forward search, start on next line */ { ++curwin->w_cursor.lnum; curwin->w_cursor.col = 0; } else /* backward search, start on prev line */ { --curwin->w_cursor.lnum; curwin->w_cursor.col = MAXCOL; } searchcmdlen = 0; if (!do_search(c, cmd, 1L, SEARCH_HIS + SEARCH_MSG + SEARCH_START)) { cmd = NULL; curwin->w_cursor = pos; goto error; } lnum = curwin->w_cursor.lnum; curwin->w_cursor = pos; /* adjust command string pointer */ cmd += searchcmdlen; break; case '\\': /* "\?", "\/" or "\&", repeat search */ ++cmd; if (*cmd == '&') i = RE_SUBST; else if (*cmd == '?' || *cmd == '/') i = RE_SEARCH; else { emsg(e_backslash); cmd = NULL; goto error; } /* forward search, start on next line */ if (*cmd != '?') { pos.lnum = curwin->w_cursor.lnum + 1; pos.col = 0; } /* backward search, start on prev line */ else { pos.lnum = curwin->w_cursor.lnum - 1; pos.col = MAXCOL; } if (searchit(&pos, *cmd == '?' ? BACKWARD : FORWARD, (char_u *)"", 1L, SEARCH_MSG + SEARCH_START, i) == OK) lnum = pos.lnum; else { cmd = NULL; goto error; } ++cmd; break; default: if (isdigit(*cmd)) /* absolute line number */ lnum = getdigits(&cmd); } for (;;) { cmd = skipwhite(cmd); if (*cmd != '-' && *cmd != '+' && !isdigit(*cmd)) break; if (lnum == MAXLNUM) lnum = cursor_lnum; /* "+1" is same as ".+1" */ if (isdigit(*cmd)) i = '+'; /* "number" is same as "+number" */ else 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; } /* * 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 sometimes 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_BUFFERS Complete file names for :buf and :sbuf commands. * 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 boolean variables only, eg :set no^I * EXPAND_TAGS Complete tags from the files in p_tags. eg :ta a^I * EXPAND_HELP Complete tags from the file 'helpfile'/vim_tags * EXPAND_EVENTS Complete event names * * -- 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 do_one_cmd(), 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 */ { char_u *p; char_u *cmd, *arg; int i; int cmdidx; long 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; vim_strchr((char_u *)" \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] .. */ /* * Backslashed delimiters after / or ? will be skipped, and commands will * not be expanded between /'s and ?'s or after "'". -- webb */ while (*cmd != NUL && (vim_isspace(*cmd) || isdigit(*cmd) || vim_strchr((char_u *)".$%'/?-+,;", *cmd) != NULL)) { if (*cmd == '\'') { if (*++cmd == NUL) expand_context = EXPAND_NOTHING; } else if (*cmd == '/' || *cmd == '?') { delim = *cmd++; while (*cmd != NUL && *cmd != delim) if (*cmd++ == '\\' && *cmd != NUL) ++cmd; if (*cmd == NUL) expand_context = EXPAND_NOTHING; } if (*cmd != NUL) ++cmd; } /* * 4. parse command */ cmd = skipwhite(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; /* check for non-alpha command */ if (p == cmd && vim_strchr((char_u *)"@!=><&~#", *p) != NULL) ++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 the cursor is touching the command, and it ends in an alphabetic * character, complete the command name. */ if (p == cmdbuff + cmdpos && isalpha(p[-1])) return NULL; if (cmdidx == CMD_SIZE) { if (*cmd == 's' && vim_strchr((char_u *)"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 = skipwhite(p); if (cmdidx == CMD_write) { if (*arg == '>') /* append */ { if (*++arg == '>') /* It should be */ ++arg; arg = skipwhite(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; arg = skipwhite(arg); } /* Does command allow "+command"? */ if ((argt & EDITCMD) && !usefilter && *arg == '+') { /* Check if we're in the +command */ p = arg + 1; arg = skiptowhite(arg); /* Still touching the command after '+'? */ if (arg >= cmdbuff + cmdpos) return p; /* Skip space after +command to get to the real argument */ arg = skipwhite(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; } } /* no arguments allowed */ if (!(argt & EXTRA) && *arg != NUL && vim_strchr((char_u *)"|\"", *arg) == NULL) 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) { int in_quote = FALSE; char_u *bow = NULL; /* Beginning of word */ /* * Allow spaces within back-quotes to count as part of the argument * being expanded. */ expand_pattern = skipwhite(arg); for (p = expand_pattern; *p; ++p) { if (*p == '\\' && p[1]) ++p; #ifdef SPACE_IN_FILENAME else if (vim_iswhite(*p) && (!(argt & NOSPC) || usefilter)) #else else if (vim_iswhite(*p)) #endif { p = skipwhite(p); if (in_quote) bow = p; else expand_pattern = p; --p; } else if (*p == '`') { if (!in_quote) { expand_pattern = p; bow = p + 1; } in_quote = !in_quote; } } /* * If we are still inside the quotes, and we passed a space, just * expand from there. */ if (bow != NULL && in_quote) expand_pattern = bow; expand_context = EXPAND_FILES; } /* * 6. switch on command name */ switch (cmdidx) { case CMD_cd: case CMD_chdir: expand_context = EXPAND_DIRECTORIES; 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++) { while (arg[0] != NUL && arg[0] != delim) { if (arg[0] == '\\' && arg[1] != NUL) ++arg; ++arg; } if (arg[0] != NUL) /* skip delimiter */ ++arg; } while (arg[0] && vim_strchr((char_u *)"|\"#", arg[0]) == NULL) ++arg; if (arg[0] != NUL) return arg; break; case CMD_isearch: case CMD_dsearch: case CMD_ilist: case CMD_dlist: case CMD_ijump: case CMD_djump: case CMD_isplit: case CMD_dsplit: arg = skipwhite(skipdigits(arg)); /* skip count */ if (*arg == '/') /* Match regexp, not just whole words */ { for (++arg; *arg && *arg != '/'; arg++) if (*arg == '\\' && arg[1] != NUL) arg++; if (*arg) { arg = skipwhite(arg + 1); /* Check for trailing illegal characters */ if (*arg && vim_strchr((char_u *)"|\"\n", *arg) == NULL) expand_context = EXPAND_NOTHING; else return arg; } } break; #ifdef AUTOCMD case CMD_autocmd: return set_context_in_autocmd(arg, FALSE); case CMD_doautocmd: return set_context_in_autocmd(arg, TRUE); #endif case CMD_set: set_context_in_set_cmd(arg); break; case CMD_stag: case CMD_tag: expand_context = EXPAND_TAGS; expand_pattern = arg; break; case CMD_help: expand_context = EXPAND_HELP; expand_pattern = arg; break; case CMD_bdelete: case CMD_bunload: while ((expand_pattern = vim_strchr(arg, ' ')) != NULL) arg = expand_pattern + 1; case CMD_buffer: case CMD_sbuffer: expand_context = EXPAND_BUFFERS; expand_pattern = arg; break; #ifdef USE_GUI case CMD_menu: case CMD_noremenu: case CMD_unmenu: case CMD_nmenu: case CMD_nnoremenu: case CMD_nunmenu: case CMD_vmenu: case CMD_vnoremenu: case CMD_vunmenu: case CMD_imenu: case CMD_inoremenu: case CMD_iunmenu: case CMD_cmenu: case CMD_cnoremenu: case CMD_cunmenu: return gui_set_context_in_menu_cmd(cmd, arg, forced); break; #endif 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, options) char_u *pat; int *num_file; char_u ***file; int files_only; int options; { regexp *prog; int ret; int i; int count; if (!expand_interactively || expand_context == EXPAND_FILES) return ExpandWildCards(1, &pat, num_file, file, files_only, (options & WILD_LIST_NOTFOUND)); else if (expand_context == EXPAND_DIRECTORIES) { if (ExpandWildCards(1, &pat, num_file, file, files_only, (options & WILD_LIST_NOTFOUND)) == FAIL) return FAIL; count = 0; for (i = 0; i < *num_file; i++) if (mch_isdir((*file)[i])) (*file)[count++] = (*file)[i]; else vim_free((*file)[i]); if (count == 0) { vim_free(*file); *file = (char_u **)""; *num_file = -1; return FAIL; } *num_file = count; return OK; } *file = (char_u **)""; *num_file = 0; if (expand_context == EXPAND_OLD_SETTING) return ExpandOldSetting(num_file, file); if (expand_context == EXPAND_HELP) return find_help_tags(pat, num_file, file); set_reg_ic(pat); /* set reg_ic according to p_ic, p_scs and pat */ #ifdef AUTOCMD if (expand_context == EXPAND_EVENTS) reg_ic = TRUE; /* always ignore case for events */ #endif reg_magic = p_magic; if (expand_context == EXPAND_BUFFERS) return ExpandBufnames(pat, num_file, file, options); prog = vim_regcomp(pat); if (prog == NULL) return FAIL; if (expand_context == EXPAND_COMMANDS) ret = ExpandCommands(prog, num_file, file); else if (expand_context == EXPAND_SETTINGS || expand_context == EXPAND_BOOL_SETTINGS) ret = ExpandSettings(prog, num_file, file); else if (expand_context == EXPAND_TAGS) ret = find_tags(NULL, prog, num_file, file, FALSE); #ifdef AUTOCMD else if (expand_context == EXPAND_EVENTS) ret = ExpandEvents(prog, num_file, file); #endif #ifdef USE_GUI else if (expand_context == EXPAND_MENUS) ret = gui_ExpandMenuNames(prog, num_file, file); #endif else ret = FAIL; vim_free(prog); return ret; } static int ExpandCommands(prog, num_file, file) regexp *prog; int *num_file; char_u ***file; { int cmdidx; int count; int round; /* * round == 1: Count the matches. * round == 2: Save the matches into the array. */ for (round = 1; round <= 2; ++round) { count = 0; for (cmdidx = 0; cmdidx < CMD_SIZE; cmdidx++) if (vim_regexec(prog, cmdnames[cmdidx].cmd_name, TRUE)) { if (round == 1) count++; else (*file)[count++] = strsave(cmdnames[cmdidx].cmd_name); } if (round == 1) { *num_file = count; if (count == 0 || (*file = (char_u **) alloc((unsigned)(count * sizeof(char_u *)))) == NULL) return FAIL; } } return OK; } #ifdef VIMINFO static char_u **viminfo_history[2] = {NULL, NULL}; static int viminfo_hisidx[2] = {0, 0}; static int viminfo_hislen = 0; static int viminfo_add_at_front = FALSE; void prepare_viminfo_history(len) int len; { int i; int num; int type; init_history(); viminfo_add_at_front = (len != 0); if (len > hislen) len = hislen; for (type = 0; type <= 1; ++type) { /* If there are more spaces available than we request, then fill them * up */ for (i = 0, num = 0; i < hislen; i++) if (history[type][i] == NULL) num++; if (num > len) len = num; viminfo_hisidx[type] = 0; if (len <= 0) viminfo_history[type] = NULL; else viminfo_history[type] = (char_u **)lalloc(len * sizeof(char_u *), FALSE); } viminfo_hislen = len; if (viminfo_history[0] == NULL || viminfo_history[1] == NULL) viminfo_hislen = 0; } int read_viminfo_history(line, fp) char_u *line; FILE *fp; { int type; type = (line[0] == ':' ? 0 : 1); if (viminfo_hisidx[type] != viminfo_hislen) { viminfo_readstring(line); if (!is_in_history(type, line + 1, viminfo_add_at_front)) viminfo_history[type][viminfo_hisidx[type]++] = strsave(line + 1); } return vim_fgets(line, LSIZE, fp); } void finish_viminfo_history() { int idx; int i; int type; for (type = 0; type <= 1; ++type) { if (history[type] == NULL) return; idx = hisidx[type] + viminfo_hisidx[type]; if (idx >= hislen) idx -= hislen; if (viminfo_add_at_front) hisidx[type] = idx; else { if (hisidx[type] == -1) hisidx[type] = hislen - 1; do { if (history[type][idx] != NULL) break; if (++idx == hislen) idx = 0; } while (idx != hisidx[type]); if (idx != hisidx[type] && --idx < 0) idx = hislen - 1; } for (i = 0; i < viminfo_hisidx[type]; i++) { history[type][idx] = viminfo_history[type][i]; if (--idx < 0) idx = hislen - 1; } vim_free(viminfo_history[type]); viminfo_history[type] = NULL; } } void write_viminfo_history(fp) FILE *fp; { int i; int type; int num_saved; init_history(); if (hislen == 0) return; for (type = 0; type <= 1; ++type) { num_saved = get_viminfo_parameter(type == 0 ? ':' : '/'); if (num_saved == 0) continue; if (num_saved < 0) /* Use default */ num_saved = hislen; fprintf(fp, "\n# %s History (newest to oldest):\n", type == 0 ? "Command Line" : "Search String"); if (num_saved > hislen) num_saved = hislen; i = hisidx[type]; if (i >= 0) while (num_saved--) { if (history[type][i] != NULL) { putc(type == 0 ? ':' : '?', fp); viminfo_writestring(fp, history[type][i]); } if (--i < 0) i = hislen - 1; } } } #endif /* VIMINFO */