Mercurial > vim
diff src/ex_docmd.c @ 6398:5a76e36f07b1 v7.4.530
updated for version 7.4.530
Problem: Many commands take a count or range that is not using line
numbers.
Solution: For each command specify what kind of count it uses. For windows,
buffers and arguments have "$" and "." have a relevant meaning.
(Marcin Szamotulski)
author | Bram Moolenaar <bram@vim.org> |
---|---|
date | Thu, 27 Nov 2014 16:22:48 +0100 |
parents | 60659773c73b |
children | 2c0cddd0df8c |
line wrap: on
line diff
--- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -60,6 +60,7 @@ static char_u *get_user_command_name __A # define IS_USER_CMDIDX(idx) (FALSE) #endif +static int compute_buffer_local_count __ARGS((int addr_type, int lnum, int local)); #ifdef FEAT_EVAL static char_u *do_one_cmd __ARGS((char_u **, int, struct condstack *, char_u *(*fgetline)(int, void *, int), void *cookie)); #else @@ -133,7 +134,7 @@ static int getargopt __ARGS((exarg_T *ea #endif static int check_more __ARGS((int, int)); -static linenr_T get_address __ARGS((char_u **, int skip, int to_other_file)); +static linenr_T get_address __ARGS((char_u **, int addr_type, int skip, int to_other_file)); static void get_flags __ARGS((exarg_T *eap)); #if !defined(FEAT_PERL) \ || !defined(FEAT_PYTHON) || !defined(FEAT_PYTHON3) \ @@ -1680,6 +1681,39 @@ getline_cookie(fgetline, cookie) } #endif + +/* + * Helper function to apply an offset for buffer commands, i.e. ":bdelete", + * ":bwipeout", etc. + * Returns the buffer number. + */ + static int +compute_buffer_local_count(addr_type, lnum, offset) + int addr_type; + int lnum; + int offset; +{ + buf_T *buf; + int count = offset; + + buf = firstbuf; + while (buf->b_next != NULL && buf->b_fnum < lnum) + buf = buf->b_next; + while (count != 0) + { + count += (count < 0) ? 1 : -1; + if (buf->b_prev == NULL) + break; + buf = (count < 0) ? buf->b_prev : buf->b_next; + if (addr_type == ADDR_LOADED_BUFFERS) + /* skip over unloaded buffers */ + while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) + buf = (count < 0) ? buf->b_prev : buf->b_next; + } + return buf->b_fnum; +} + + /* * Execute one Ex command. * @@ -1687,10 +1721,10 @@ getline_cookie(fgetline, cookie) * * 1. skip comment lines and leading space * 2. handle command modifiers - * 3. parse range - * 4. parse command - * 5. parse arguments - * 6. switch on command name + * 3. parse command + * 4. parse range + * 6. parse arguments + * 7. switch on command name * * Note: "fgetline" can be NULL. * @@ -1730,6 +1764,9 @@ do_one_cmd(cmdlinep, sourcing, #endif cmdmod_T save_cmdmod; int ni; /* set when Not Implemented */ + win_T *wp; + tabpage_T *tp; + char_u *cmd; vim_memset(&ea, 0, sizeof(ea)); ea.line1 = 1; @@ -1769,7 +1806,7 @@ do_one_cmd(cmdlinep, sourcing, for (;;) { /* - * 1. skip comment lines and leading white space and colons + * 1. Skip comment lines and leading white space and colons. */ while (*ea.cmd == ' ' || *ea.cmd == '\t' || *ea.cmd == ':') ++ea.cmd; @@ -1794,7 +1831,7 @@ do_one_cmd(cmdlinep, sourcing, } /* - * 2. handle command modifiers. + * 2. Handle command modifiers. */ p = ea.cmd; if (VIM_ISDIGIT(*ea.cmd)) @@ -2003,7 +2040,18 @@ do_one_cmd(cmdlinep, sourcing, #endif /* - * 3. parse a range specifier of the form: addr [,addr] [;addr] .. + * 3. Skip over the range to find the command. Let "p" point to after it. + * + * We need the command to know what kind of range it uses. + */ + cmd = ea.cmd; + ea.cmd = skip_range(ea.cmd, NULL); + if (*ea.cmd == '*' && vim_strchr(p_cpo, CPO_STAR) == NULL) + ea.cmd = skipwhite(ea.cmd + 1); + p = find_command(&ea, NULL); + +/* + * 4. parse a range specifier of the form: addr [,addr] [;addr] .. * * where 'addr' is: * @@ -2019,13 +2067,52 @@ do_one_cmd(cmdlinep, sourcing, * is equal to the lower. */ + if (ea.cmdidx != CMD_SIZE) + ea.addr_type = cmdnames[(int)ea.cmdidx].cmd_addr_type; + else + ea.addr_type = ADDR_LINES; + ea.cmd = cmd; + /* repeat for all ',' or ';' separated addresses */ for (;;) { ea.line1 = ea.line2; - ea.line2 = curwin->w_cursor.lnum; /* default is current line number */ + switch (ea.addr_type) + { + case ADDR_LINES: + /* default is current line number */ + ea.line2 = curwin->w_cursor.lnum; + break; + case ADDR_WINDOWS: + lnum = 0; + for (wp = firstwin; wp != NULL; wp = wp->w_next) + { + lnum++; + if (wp == curwin) + break; + } + ea.line2 = lnum; + break; + case ADDR_ARGUMENTS: + ea.line2 = curwin->w_arg_idx + 1; + break; + case ADDR_LOADED_BUFFERS: + case ADDR_UNLOADED_BUFFERS: + ea.line2 = curbuf->b_fnum; + break; + case ADDR_TABS: + lnum = 0; + for(tp = first_tabpage; tp != NULL; tp = tp->tp_next) + { + lnum++; + if (tp == curtab) + break; + } + ea.line2 = lnum; + break; + } ea.cmd = skipwhite(ea.cmd); - lnum = get_address(&ea.cmd, ea.skip, ea.addr_count == 0); + lnum = get_address(&ea.cmd, ea.addr_type, ea.skip, ea.addr_count == 0); if (ea.cmd == NULL) /* error detected */ goto doend; if (lnum == MAXLNUM) @@ -2033,8 +2120,24 @@ do_one_cmd(cmdlinep, sourcing, if (*ea.cmd == '%') /* '%' - all lines */ { ++ea.cmd; - ea.line1 = 1; - ea.line2 = curbuf->b_ml.ml_line_count; + switch (ea.addr_type) + { + case ADDR_LINES: + ea.line1 = 1; + ea.line2 = curbuf->b_ml.ml_line_count; + break; + case ADDR_WINDOWS: + case ADDR_LOADED_BUFFERS: + case ADDR_UNLOADED_BUFFERS: + case ADDR_TABS: + errormsg = (char_u *)_(e_invrange); + goto doend; + break; + case ADDR_ARGUMENTS: + ea.line1 = 1; + ea.line2 = ARGCOUNT; + break; + } ++ea.addr_count; } /* '*' - visual area */ @@ -2042,6 +2145,12 @@ do_one_cmd(cmdlinep, sourcing, { pos_T *fp; + if (ea.addr_type != ADDR_LINES) + { + errormsg = (char_u *)_(e_invrange); + goto doend; + } + ++ea.cmd; if (!ea.skip) { @@ -2084,7 +2193,7 @@ do_one_cmd(cmdlinep, sourcing, check_cursor_lnum(); /* - * 4. parse command + * 5. Parse the command. */ /* @@ -2098,8 +2207,8 @@ do_one_cmd(cmdlinep, sourcing, * If we got a line, but no command, then go to the line. * If we find a '|' or '\n' we set ea.nextcmd. */ - if (*ea.cmd == NUL || *ea.cmd == '"' || - (ea.nextcmd = check_nextcmd(ea.cmd)) != NULL) + if (*ea.cmd == NUL || *ea.cmd == '"' + || (ea.nextcmd = check_nextcmd(ea.cmd)) != NULL) { /* * strange vi behaviour: @@ -2145,9 +2254,6 @@ do_one_cmd(cmdlinep, sourcing, goto doend; } - /* Find the command and let "p" point to after it. */ - p = find_command(&ea, NULL); - #ifdef FEAT_AUTOCMD /* If this looks like an undefined user command and there are CmdUndefined * autocommands defined, trigger the matching autocommands. */ @@ -2229,7 +2335,7 @@ do_one_cmd(cmdlinep, sourcing, ea.forceit = FALSE; /* - * 5. parse arguments + * 5. Parse arguments. */ if (!IS_USER_CMDIDX(ea.cmdidx)) ea.argt = (long)cmdnames[(int)ea.cmdidx].cmd_argt; @@ -2676,7 +2782,7 @@ do_one_cmd(cmdlinep, sourcing, #endif /* - * 6. switch on command name + * 6. Switch on command name. * * The "ea" structure holds the arguments that can be used. */ @@ -4082,8 +4188,9 @@ skip_range(cmd, ctx) * Return MAXLNUM when no Ex address was found. */ static linenr_T -get_address(ptr, skip, to_other_file) +get_address(ptr, addr_type, skip, to_other_file) char_u **ptr; + int addr_type; /* flag: one of ADDR_LINES, ... */ int skip; /* only skip the address, don't use it */ int to_other_file; /* flag: may jump to other file */ { @@ -4094,6 +4201,8 @@ get_address(ptr, skip, to_other_file) pos_T pos; pos_T *fp; linenr_T lnum; + win_T *wp; + tabpage_T *tp; cmd = skipwhite(*ptr); lnum = MAXLNUM; @@ -4102,137 +4211,204 @@ get_address(ptr, skip, to_other_file) switch (*cmd) { case '.': /* '.' - Cursor position */ - ++cmd; + ++cmd; + switch (addr_type) + { + case ADDR_LINES: lnum = curwin->w_cursor.lnum; break; + case ADDR_WINDOWS: + lnum = 0; + for (wp = firstwin; wp != NULL; wp = wp->w_next) + { + lnum++; + if (wp == curwin) + break; + } + break; + case ADDR_ARGUMENTS: + lnum = curwin->w_arg_idx + 1; + break; + case ADDR_LOADED_BUFFERS: + case ADDR_UNLOADED_BUFFERS: + lnum = curbuf->b_fnum; + break; + case ADDR_TABS: + lnum = 0; + for(tp = first_tabpage; tp != NULL; tp = tp->tp_next) + { + lnum++; + if (tp == curtab) + break; + } + break; + } + break; case '$': /* '$' - last line */ - ++cmd; + ++cmd; + switch (addr_type) + { + case ADDR_LINES: lnum = curbuf->b_ml.ml_line_count; break; + case ADDR_WINDOWS: + lnum = 0; + for (wp = firstwin; wp != NULL; wp = wp->w_next) + lnum++; + break; + case ADDR_ARGUMENTS: + lnum = ARGCOUNT; + break; + case ADDR_LOADED_BUFFERS: + case ADDR_UNLOADED_BUFFERS: + lnum = lastbuf->b_fnum; + break; + case ADDR_TABS: + lnum = 0; + for(tp = first_tabpage; tp != NULL; tp = tp->tp_next) + lnum++; + break; + } + break; case '\'': /* ''' - mark */ - if (*++cmd == NUL) + if (*++cmd == NUL) + { + cmd = NULL; + goto error; + } + if (addr_type != ADDR_LINES) + { + EMSG(_(e_invaddr)); + goto error; + } + if (skip) + ++cmd; + else + { + /* Only accept a mark in another file when it is + * used by itself: ":'M". */ + fp = getmark(*cmd, to_other_file && cmd[1] == NUL); + ++cmd; + if (fp == (pos_T *)-1) + /* Jumped to another file. */ + lnum = curwin->w_cursor.lnum; + else + { + if (check_mark(fp) == FAIL) { cmd = NULL; goto error; } - if (skip) - ++cmd; - else - { - /* Only accept a mark in another file when it is - * used by itself: ":'M". */ - fp = getmark(*cmd, to_other_file && cmd[1] == NUL); - ++cmd; - if (fp == (pos_T *)-1) - /* Jumped to another file. */ - lnum = curwin->w_cursor.lnum; - else - { - if (check_mark(fp) == FAIL) - { - cmd = NULL; - goto error; - } - lnum = fp->lnum; - } - } - break; + lnum = fp->lnum; + } + } + break; case '/': case '?': /* '/' or '?' - search */ - c = *cmd++; - if (skip) /* skip "/pat/" */ - { - cmd = skip_regexp(cmd, c, (int)p_magic, NULL); - if (*cmd == c) - ++cmd; - } - else - { - pos = curwin->w_cursor; /* save curwin->w_cursor */ - /* - * When '/' or '?' follows another address, start - * from there. - */ - if (lnum != MAXLNUM) - curwin->w_cursor.lnum = lnum; - /* - * Start a forward search at the end of the line. - * Start a backward search at the start of the line. - * This makes sure we never match in the current - * line, and can match anywhere in the - * next/previous line. - */ - if (c == '/') - curwin->w_cursor.col = MAXCOL; - else - curwin->w_cursor.col = 0; - searchcmdlen = 0; - if (!do_search(NULL, c, cmd, 1L, - SEARCH_HIS | SEARCH_MSG, NULL)) - { - curwin->w_cursor = pos; - cmd = NULL; - goto error; - } - lnum = curwin->w_cursor.lnum; - curwin->w_cursor = pos; - /* adjust command string pointer */ - cmd += searchcmdlen; - } - break; + c = *cmd++; + if (addr_type != ADDR_LINES) + { + EMSG(_(e_invaddr)); + goto error; + } + if (skip) /* skip "/pat/" */ + { + cmd = skip_regexp(cmd, c, (int)p_magic, NULL); + if (*cmd == c) + ++cmd; + } + else + { + pos = curwin->w_cursor; /* save curwin->w_cursor */ + /* + * When '/' or '?' follows another address, start + * from there. + */ + if (lnum != MAXLNUM) + curwin->w_cursor.lnum = lnum; + /* + * Start a forward search at the end of the line. + * Start a backward search at the start of the line. + * This makes sure we never match in the current + * line, and can match anywhere in the + * next/previous line. + */ + if (c == '/') + curwin->w_cursor.col = MAXCOL; + else + curwin->w_cursor.col = 0; + searchcmdlen = 0; + if (!do_search(NULL, c, cmd, 1L, + SEARCH_HIS | SEARCH_MSG, NULL)) + { + curwin->w_cursor = pos; + cmd = NULL; + 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; - } - - if (!skip) - { - /* - * When search follows another address, start from - * there. - */ - if (lnum != MAXLNUM) - pos.lnum = lnum; - else - pos.lnum = curwin->w_cursor.lnum; - - /* - * Start the search just like for the above - * do_search(). - */ - if (*cmd != '?') - pos.col = MAXCOL; - else - pos.col = 0; - if (searchit(curwin, curbuf, &pos, - *cmd == '?' ? BACKWARD : FORWARD, - (char_u *)"", 1L, SEARCH_MSG, - i, (linenr_T)0, NULL) != FAIL) - lnum = pos.lnum; - else - { - cmd = NULL; - goto error; - } - } - ++cmd; - break; + ++cmd; + if (addr_type != ADDR_LINES) + { + EMSG(_(e_invaddr)); + goto error; + } + if (*cmd == '&') + i = RE_SUBST; + else if (*cmd == '?' || *cmd == '/') + i = RE_SEARCH; + else + { + EMSG(_(e_backslash)); + cmd = NULL; + goto error; + } + + if (!skip) + { + /* + * When search follows another address, start from + * there. + */ + if (lnum != MAXLNUM) + pos.lnum = lnum; + else + pos.lnum = curwin->w_cursor.lnum; + + /* + * Start the search just like for the above + * do_search(). + */ + if (*cmd != '?') + pos.col = MAXCOL; + else + pos.col = 0; + if (searchit(curwin, curbuf, &pos, + *cmd == '?' ? BACKWARD : FORWARD, + (char_u *)"", 1L, SEARCH_MSG, + i, (linenr_T)0, NULL) != FAIL) + lnum = pos.lnum; + else + { + cmd = NULL; + goto error; + } + } + ++cmd; + break; default: - if (VIM_ISDIGIT(*cmd)) /* absolute line number */ - lnum = getdigits(&cmd); + if (VIM_ISDIGIT(*cmd)) /* absolute line number */ + lnum = getdigits(&cmd); } for (;;) @@ -4242,7 +4418,40 @@ get_address(ptr, skip, to_other_file) break; if (lnum == MAXLNUM) - lnum = curwin->w_cursor.lnum; /* "+1" is same as ".+1" */ + { + switch (addr_type) + { + case ADDR_LINES: + lnum = curwin->w_cursor.lnum; /* "+1" is same as ".+1" */ + break; + case ADDR_WINDOWS: + lnum = 0; + for (wp = firstwin; wp != NULL; wp = wp->w_next) + { + lnum++; + if (wp == curwin) + break; + } + break; + case ADDR_ARGUMENTS: + lnum = curwin->w_arg_idx + 1; + break; + case ADDR_LOADED_BUFFERS: + case ADDR_UNLOADED_BUFFERS: + lnum = curbuf->b_fnum; + break; + case ADDR_TABS: + lnum = 0; + for(tp = first_tabpage; tp != NULL; tp = tp->tp_next) + { + lnum++; + if (tp == curtab) + break; + } + break; + } + } + if (VIM_ISDIGIT(*cmd)) i = '+'; /* "number" is same as "+number" */ else @@ -4251,10 +4460,59 @@ get_address(ptr, skip, to_other_file) n = 1; else n = getdigits(&cmd); - if (i == '-') + if (addr_type == ADDR_LOADED_BUFFERS + || addr_type == ADDR_UNLOADED_BUFFERS) + lnum = compute_buffer_local_count(addr_type, lnum, n); + else if (i == '-') lnum -= n; else lnum += n; + + switch (addr_type) + { + case ADDR_LINES: + break; + case ADDR_ARGUMENTS: + if (lnum < 0) + lnum = 0; + else if (lnum >= ARGCOUNT) + lnum = ARGCOUNT; + break; + case ADDR_TABS: + if (lnum < 0) + { + lnum = 0; + break; + } + c = 0; + for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) + c++; + if (lnum >= c) + lnum = c; + break; + case ADDR_WINDOWS: + if (lnum < 0) + { + lnum = 0; + break; + } + c = 0; + for (wp = firstwin; wp != NULL; wp = wp->w_next) + c++; + if (lnum > c) + lnum = c; + break; + case ADDR_LOADED_BUFFERS: + case ADDR_UNLOADED_BUFFERS: + if (lnum < firstbuf->b_fnum) + { + lnum = firstbuf->b_fnum; + break; + } + if (lnum > lastbuf->b_fnum) + lnum = lastbuf->b_fnum; + break; + } } } while (*cmd == '/' || *cmd == '?'); @@ -6556,6 +6814,10 @@ not_exiting() ex_quit(eap) exarg_T *eap; { + win_T *wp; + buf_T *buf; + int wnr; + #ifdef FEAT_CMDWIN if (cmdwin_type != 0) { @@ -6569,11 +6831,28 @@ ex_quit(eap) text_locked_msg(); return; } + if (eap->addr_count > 0) + { + wnr = eap->line2; + for (wp = firstwin; --wnr > 0; ) + { + if (wp->w_next == NULL) + break; + else + wp = wp->w_next; + } + buf = wp->w_buffer; + } + else + { + wp = curwin; + buf = curbuf; + } #ifdef FEAT_AUTOCMD apply_autocmds(EVENT_QUITPRE, NULL, NULL, FALSE, curbuf); /* Refuse to quit when locked or when the buffer in the last window is * being closed (can only happen in autocommands). */ - if (curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_closing)) + if (curbuf_locked() || (buf->b_nwindows == 1 && buf->b_closing)) return; #endif @@ -6606,7 +6885,7 @@ ex_quit(eap) need_mouse_correct = TRUE; # endif /* close window; may free buffer */ - win_close(curwin, !P_HID(curwin->w_buffer) || eap->forceit); + win_close(wp, !P_HID(wp->w_buffer) || eap->forceit); #endif } } @@ -6668,6 +6947,8 @@ ex_quit_all(eap) ex_close(eap) exarg_T *eap; { + win_T *win; + int winnr = 0; # ifdef FEAT_CMDWIN if (cmdwin_type != 0) cmdwin_result = Ctrl_C; @@ -6678,7 +6959,21 @@ ex_close(eap) && !curbuf_locked() #endif ) - ex_win_close(eap->forceit, curwin, NULL); + { + if (eap->addr_count == 0) + ex_win_close(eap->forceit, curwin, NULL); + else { + for (win = firstwin; win != NULL; win = win->w_next) + { + winnr++; + if (winnr == eap->line2) + break; + } + if (win == NULL) + win = lastwin; + ex_win_close(eap->forceit, win, NULL); + } + } } # ifdef FEAT_QUICKFIX @@ -6804,6 +7099,8 @@ ex_tabonly(eap) MSG(_("Already only one tab page")); else { + if (eap->addr_count > 0) + goto_tabpage(eap->line2); /* Repeat this up to a 1000 times, because autocommands may mess * up the lists. */ for (done = 0; done < 1000; ++done) @@ -6882,9 +7179,23 @@ tabpage_close_other(tp, forceit) ex_only(eap) exarg_T *eap; { + win_T *wp; + int wnr; # ifdef FEAT_GUI need_mouse_correct = TRUE; # endif + if (eap->addr_count > 0) + { + wnr = eap->line2; + for (wp = firstwin; --wnr > 0; ) + { + if (wp->w_next == NULL) + break; + else + wp = wp->w_next; + } + win_goto(wp); + } close_others(TRUE, eap->forceit); } @@ -6906,6 +7217,9 @@ ex_all(eap) ex_hide(eap) exarg_T *eap; { + win_T *win; + int winnr = 0; + if (*eap->arg != NUL && check_nextcmd(eap->arg) == NULL) eap->errmsg = e_invarg; else @@ -6918,7 +7232,19 @@ ex_hide(eap) # ifdef FEAT_GUI need_mouse_correct = TRUE; # endif - win_close(curwin, FALSE); /* don't free buffer */ + if (eap->addr_count == 0) + win_close(curwin, FALSE); /* don't free buffer */ + else { + for (win = firstwin; win != NULL; win = win->w_next) + { + winnr++; + if (winnr == eap->line2) + break; + } + if (win == NULL) + win = lastwin; + win_close(win, FALSE); + } } #endif } @@ -8652,7 +8978,7 @@ ex_copymove(eap) { long n; - n = get_address(&eap->arg, FALSE, FALSE); + n = get_address(&eap->arg, eap->addr_type, FALSE, FALSE); if (eap->arg == NULL) /* error detected */ { eap->nextcmd = NULL;