1 | // This is an open source non-commercial project. Dear PVS-Studio, please check |
2 | // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com |
3 | |
4 | /* |
5 | * ex_docmd.c: functions for executing an Ex command line. |
6 | */ |
7 | |
8 | #include <assert.h> |
9 | #include <string.h> |
10 | #include <stdbool.h> |
11 | #include <stdlib.h> |
12 | #include <inttypes.h> |
13 | |
14 | #include "nvim/vim.h" |
15 | #include "nvim/ascii.h" |
16 | #include "nvim/ex_docmd.h" |
17 | #include "nvim/buffer.h" |
18 | #include "nvim/change.h" |
19 | #include "nvim/charset.h" |
20 | #include "nvim/cursor.h" |
21 | #include "nvim/diff.h" |
22 | #include "nvim/digraph.h" |
23 | #include "nvim/edit.h" |
24 | #include "nvim/eval.h" |
25 | #include "nvim/ex_cmds.h" |
26 | #include "nvim/ex_cmds2.h" |
27 | #include "nvim/ex_eval.h" |
28 | #include "nvim/ex_getln.h" |
29 | #include "nvim/fileio.h" |
30 | #include "nvim/fold.h" |
31 | #include "nvim/func_attr.h" |
32 | #include "nvim/getchar.h" |
33 | #include "nvim/hardcopy.h" |
34 | #include "nvim/if_cscope.h" |
35 | #include "nvim/main.h" |
36 | #include "nvim/mark.h" |
37 | #include "nvim/mbyte.h" |
38 | #include "nvim/memline.h" |
39 | #include "nvim/memory.h" |
40 | #include "nvim/menu.h" |
41 | #include "nvim/message.h" |
42 | #include "nvim/misc1.h" |
43 | #include "nvim/keymap.h" |
44 | #include "nvim/file_search.h" |
45 | #include "nvim/garray.h" |
46 | #include "nvim/move.h" |
47 | #include "nvim/normal.h" |
48 | #include "nvim/ops.h" |
49 | #include "nvim/option.h" |
50 | #include "nvim/os_unix.h" |
51 | #include "nvim/path.h" |
52 | #include "nvim/quickfix.h" |
53 | #include "nvim/regexp.h" |
54 | #include "nvim/screen.h" |
55 | #include "nvim/search.h" |
56 | #include "nvim/sign.h" |
57 | #include "nvim/spell.h" |
58 | #include "nvim/spellfile.h" |
59 | #include "nvim/strings.h" |
60 | #include "nvim/syntax.h" |
61 | #include "nvim/tag.h" |
62 | #include "nvim/terminal.h" |
63 | #include "nvim/ui.h" |
64 | #include "nvim/undo.h" |
65 | #include "nvim/version.h" |
66 | #include "nvim/window.h" |
67 | #include "nvim/os/os.h" |
68 | #include "nvim/os/input.h" |
69 | #include "nvim/os/time.h" |
70 | #include "nvim/ex_cmds_defs.h" |
71 | #include "nvim/mouse.h" |
72 | #include "nvim/event/rstream.h" |
73 | #include "nvim/event/wstream.h" |
74 | #include "nvim/shada.h" |
75 | #include "nvim/lua/executor.h" |
76 | #include "nvim/globals.h" |
77 | #include "nvim/api/private/helpers.h" |
78 | |
79 | static int quitmore = 0; |
80 | static int ex_pressedreturn = FALSE; |
81 | |
82 | /// Whether ":lcd" or ":tcd" was produced for a session. |
83 | static int did_lcd; |
84 | |
85 | typedef struct ucmd { |
86 | char_u *uc_name; // The command name |
87 | uint32_t uc_argt; // The argument type |
88 | char_u *uc_rep; // The command's replacement string |
89 | long uc_def; // The default value for a range/count |
90 | int uc_compl; // completion type |
91 | int uc_addr_type; // The command's address type |
92 | sctx_T uc_script_ctx; // SCTX where the command was defined |
93 | char_u *uc_compl_arg; // completion argument if any |
94 | } ucmd_T; |
95 | |
96 | #define UC_BUFFER 1 /* -buffer: local to current buffer */ |
97 | |
98 | static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL}; |
99 | |
100 | #define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i]) |
101 | #define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i]) |
102 | |
103 | /* Wether a command index indicates a user command. */ |
104 | # define IS_USER_CMDIDX(idx) ((int)(idx) < 0) |
105 | |
106 | /* Struct for storing a line inside a while/for loop */ |
107 | typedef struct { |
108 | char_u *line; /* command line */ |
109 | linenr_T lnum; /* sourcing_lnum of the line */ |
110 | } wcmd_T; |
111 | |
112 | #define FREE_WCMD(wcmd) xfree((wcmd)->line) |
113 | |
114 | /* |
115 | * Structure used to store info for line position in a while or for loop. |
116 | * This is required, because do_one_cmd() may invoke ex_function(), which |
117 | * reads more lines that may come from the while/for loop. |
118 | */ |
119 | struct loop_cookie { |
120 | garray_T *lines_gap; /* growarray with line info */ |
121 | int current_line; /* last read line from growarray */ |
122 | int repeating; /* TRUE when looping a second time */ |
123 | /* When "repeating" is FALSE use "getline" and "cookie" to get lines */ |
124 | char_u *(*getline)(int, void *, int); |
125 | void *cookie; |
126 | }; |
127 | |
128 | |
129 | /* Struct to save a few things while debugging. Used in do_cmdline() only. */ |
130 | struct dbg_stuff { |
131 | int trylevel; |
132 | int force_abort; |
133 | except_T *caught_stack; |
134 | char_u *vv_exception; |
135 | char_u *vv_throwpoint; |
136 | int did_emsg; |
137 | int got_int; |
138 | int need_rethrow; |
139 | int check_cstack; |
140 | except_T *current_exception; |
141 | }; |
142 | |
143 | |
144 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
145 | # include "ex_docmd.c.generated.h" |
146 | #endif |
147 | |
148 | #ifndef HAVE_WORKING_LIBINTL |
149 | # define ex_language ex_ni |
150 | #endif |
151 | |
152 | /* |
153 | * Declare cmdnames[]. |
154 | */ |
155 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
156 | # include "ex_cmds_defs.generated.h" |
157 | #endif |
158 | |
159 | static char_u dollar_command[2] = {'$', 0}; |
160 | |
161 | static void save_dbg_stuff(struct dbg_stuff *dsp) |
162 | { |
163 | dsp->trylevel = trylevel; trylevel = 0; |
164 | dsp->force_abort = force_abort; force_abort = FALSE; |
165 | dsp->caught_stack = caught_stack; caught_stack = NULL; |
166 | dsp->vv_exception = v_exception(NULL); |
167 | dsp->vv_throwpoint = v_throwpoint(NULL); |
168 | |
169 | // Necessary for debugging an inactive ":catch", ":finally", ":endtry". |
170 | dsp->did_emsg = did_emsg; did_emsg = false; |
171 | dsp->got_int = got_int; got_int = false; |
172 | dsp->need_rethrow = need_rethrow; need_rethrow = false; |
173 | dsp->check_cstack = check_cstack; check_cstack = false; |
174 | dsp->current_exception = current_exception; current_exception = NULL; |
175 | } |
176 | |
177 | static void restore_dbg_stuff(struct dbg_stuff *dsp) |
178 | { |
179 | suppress_errthrow = FALSE; |
180 | trylevel = dsp->trylevel; |
181 | force_abort = dsp->force_abort; |
182 | caught_stack = dsp->caught_stack; |
183 | (void)v_exception(dsp->vv_exception); |
184 | (void)v_throwpoint(dsp->vv_throwpoint); |
185 | did_emsg = dsp->did_emsg; |
186 | got_int = dsp->got_int; |
187 | need_rethrow = dsp->need_rethrow; |
188 | check_cstack = dsp->check_cstack; |
189 | current_exception = dsp->current_exception; |
190 | } |
191 | |
192 | /// Repeatedly get commands for Ex mode, until the ":vi" command is given. |
193 | void do_exmode(int improved) |
194 | { |
195 | int save_msg_scroll; |
196 | int prev_msg_row; |
197 | linenr_T prev_line; |
198 | int changedtick; |
199 | |
200 | if (improved) |
201 | exmode_active = EXMODE_VIM; |
202 | else |
203 | exmode_active = EXMODE_NORMAL; |
204 | State = NORMAL; |
205 | |
206 | /* When using ":global /pat/ visual" and then "Q" we return to continue |
207 | * the :global command. */ |
208 | if (global_busy) |
209 | return; |
210 | |
211 | save_msg_scroll = msg_scroll; |
212 | RedrawingDisabled++; // don't redisplay the window |
213 | no_wait_return++; // don't wait for return |
214 | |
215 | MSG(_("Entering Ex mode. Type \"visual\" to go to Normal mode." )); |
216 | while (exmode_active) { |
217 | /* Check for a ":normal" command and no more characters left. */ |
218 | if (ex_normal_busy > 0 && typebuf.tb_len == 0) { |
219 | exmode_active = FALSE; |
220 | break; |
221 | } |
222 | msg_scroll = true; |
223 | need_wait_return = false; |
224 | ex_pressedreturn = false; |
225 | ex_no_reprint = false; |
226 | changedtick = buf_get_changedtick(curbuf); |
227 | prev_msg_row = msg_row; |
228 | prev_line = curwin->w_cursor.lnum; |
229 | cmdline_row = msg_row; |
230 | do_cmdline(NULL, getexline, NULL, 0); |
231 | lines_left = Rows - 1; |
232 | |
233 | if ((prev_line != curwin->w_cursor.lnum |
234 | || changedtick != buf_get_changedtick(curbuf)) && !ex_no_reprint) { |
235 | if (curbuf->b_ml.ml_flags & ML_EMPTY) { |
236 | EMSG(_(e_emptybuf)); |
237 | } else { |
238 | if (ex_pressedreturn) { |
239 | /* go up one line, to overwrite the ":<CR>" line, so the |
240 | * output doesn't contain empty lines. */ |
241 | msg_row = prev_msg_row; |
242 | if (prev_msg_row == Rows - 1) |
243 | msg_row--; |
244 | } |
245 | msg_col = 0; |
246 | print_line_no_prefix(curwin->w_cursor.lnum, FALSE, FALSE); |
247 | msg_clr_eos(); |
248 | } |
249 | } else if (ex_pressedreturn && !ex_no_reprint) { /* must be at EOF */ |
250 | if (curbuf->b_ml.ml_flags & ML_EMPTY) |
251 | EMSG(_(e_emptybuf)); |
252 | else |
253 | EMSG(_("E501: At end-of-file" )); |
254 | } |
255 | } |
256 | |
257 | RedrawingDisabled--; |
258 | no_wait_return--; |
259 | redraw_all_later(NOT_VALID); |
260 | update_screen(NOT_VALID); |
261 | need_wait_return = false; |
262 | msg_scroll = save_msg_scroll; |
263 | } |
264 | |
265 | /* |
266 | * Execute a simple command line. Used for translated commands like "*". |
267 | */ |
268 | int do_cmdline_cmd(const char *cmd) |
269 | { |
270 | return do_cmdline((char_u *)cmd, NULL, NULL, |
271 | DOCMD_NOWAIT|DOCMD_KEYTYPED); |
272 | } |
273 | |
274 | /* |
275 | * do_cmdline(): execute one Ex command line |
276 | * |
277 | * 1. Execute "cmdline" when it is not NULL. |
278 | * If "cmdline" is NULL, or more lines are needed, fgetline() is used. |
279 | * 2. Split up in parts separated with '|'. |
280 | * |
281 | * This function can be called recursively! |
282 | * |
283 | * flags: |
284 | * DOCMD_VERBOSE - The command will be included in the error message. |
285 | * DOCMD_NOWAIT - Don't call wait_return() and friends. |
286 | * DOCMD_REPEAT - Repeat execution until fgetline() returns NULL. |
287 | * DOCMD_KEYTYPED - Don't reset KeyTyped. |
288 | * DOCMD_EXCRESET - Reset the exception environment (used for debugging). |
289 | * DOCMD_KEEPLINE - Store first typed line (for repeating with "."). |
290 | * |
291 | * return FAIL if cmdline could not be executed, OK otherwise |
292 | */ |
293 | int do_cmdline(char_u *cmdline, LineGetter fgetline, |
294 | void *cookie, /* argument for fgetline() */ |
295 | int flags) |
296 | { |
297 | char_u *next_cmdline; /* next cmd to execute */ |
298 | char_u *cmdline_copy = NULL; /* copy of cmd line */ |
299 | int used_getline = FALSE; /* used "fgetline" to obtain command */ |
300 | static int recursive = 0; /* recursive depth */ |
301 | int msg_didout_before_start = 0; |
302 | int count = 0; /* line number count */ |
303 | int did_inc = FALSE; /* incremented RedrawingDisabled */ |
304 | int retval = OK; |
305 | struct condstack cstack; /* conditional stack */ |
306 | garray_T lines_ga; /* keep lines for ":while"/":for" */ |
307 | int current_line = 0; /* active line in lines_ga */ |
308 | char_u *fname = NULL; /* function or script name */ |
309 | linenr_T *breakpoint = NULL; /* ptr to breakpoint field in cookie */ |
310 | int *dbg_tick = NULL; /* ptr to dbg_tick field in cookie */ |
311 | struct dbg_stuff debug_saved; /* saved things for debug mode */ |
312 | int initial_trylevel; |
313 | struct msglist **saved_msg_list = NULL; |
314 | struct msglist *private_msg_list; |
315 | |
316 | /* "fgetline" and "cookie" passed to do_one_cmd() */ |
317 | char_u *(*cmd_getline)(int, void *, int); |
318 | void *cmd_cookie; |
319 | struct loop_cookie cmd_loop_cookie; |
320 | void *real_cookie; |
321 | int getline_is_func; |
322 | static int call_depth = 0; /* recursiveness */ |
323 | |
324 | /* For every pair of do_cmdline()/do_one_cmd() calls, use an extra memory |
325 | * location for storing error messages to be converted to an exception. |
326 | * This ensures that the do_errthrow() call in do_one_cmd() does not |
327 | * combine the messages stored by an earlier invocation of do_one_cmd() |
328 | * with the command name of the later one. This would happen when |
329 | * BufWritePost autocommands are executed after a write error. */ |
330 | saved_msg_list = msg_list; |
331 | msg_list = &private_msg_list; |
332 | private_msg_list = NULL; |
333 | |
334 | // It's possible to create an endless loop with ":execute", catch that |
335 | // here. The value of 200 allows nested function calls, ":source", etc. |
336 | // Allow 200 or 'maxfuncdepth', whatever is larger. |
337 | if (call_depth >= 200 && call_depth >= p_mfd) { |
338 | EMSG(_("E169: Command too recursive" )); |
339 | // When converting to an exception, we do not include the command name |
340 | // since this is not an error of the specific command. |
341 | do_errthrow((struct condstack *)NULL, (char_u *)NULL); |
342 | msg_list = saved_msg_list; |
343 | return FAIL; |
344 | } |
345 | call_depth++; |
346 | start_batch_changes(); |
347 | |
348 | cstack.cs_idx = -1; |
349 | cstack.cs_looplevel = 0; |
350 | cstack.cs_trylevel = 0; |
351 | cstack.cs_emsg_silent_list = NULL; |
352 | cstack.cs_lflags = 0; |
353 | ga_init(&lines_ga, (int)sizeof(wcmd_T), 10); |
354 | |
355 | real_cookie = getline_cookie(fgetline, cookie); |
356 | |
357 | /* Inside a function use a higher nesting level. */ |
358 | getline_is_func = getline_equal(fgetline, cookie, get_func_line); |
359 | if (getline_is_func && ex_nesting_level == func_level(real_cookie)) |
360 | ++ex_nesting_level; |
361 | |
362 | /* Get the function or script name and the address where the next breakpoint |
363 | * line and the debug tick for a function or script are stored. */ |
364 | if (getline_is_func) { |
365 | fname = func_name(real_cookie); |
366 | breakpoint = func_breakpoint(real_cookie); |
367 | dbg_tick = func_dbg_tick(real_cookie); |
368 | } else if (getline_equal(fgetline, cookie, getsourceline)) { |
369 | fname = sourcing_name; |
370 | breakpoint = source_breakpoint(real_cookie); |
371 | dbg_tick = source_dbg_tick(real_cookie); |
372 | } |
373 | |
374 | /* |
375 | * Initialize "force_abort" and "suppress_errthrow" at the top level. |
376 | */ |
377 | if (!recursive) { |
378 | force_abort = FALSE; |
379 | suppress_errthrow = FALSE; |
380 | } |
381 | |
382 | // If requested, store and reset the global values controlling the |
383 | // exception handling (used when debugging). Otherwise clear it to avoid |
384 | // a bogus compiler warning when the optimizer uses inline functions... |
385 | if (flags & DOCMD_EXCRESET) { |
386 | save_dbg_stuff(&debug_saved); |
387 | } else { |
388 | memset(&debug_saved, 0, sizeof(debug_saved)); |
389 | } |
390 | |
391 | initial_trylevel = trylevel; |
392 | |
393 | current_exception = NULL; |
394 | // "did_emsg" will be set to TRUE when emsg() is used, in which case we |
395 | // cancel the whole command line, and any if/endif or loop. |
396 | // If force_abort is set, we cancel everything. |
397 | did_emsg = false; |
398 | |
399 | /* |
400 | * KeyTyped is only set when calling vgetc(). Reset it here when not |
401 | * calling vgetc() (sourced command lines). |
402 | */ |
403 | if (!(flags & DOCMD_KEYTYPED) |
404 | && !getline_equal(fgetline, cookie, getexline)) |
405 | KeyTyped = false; |
406 | |
407 | /* |
408 | * Continue executing command lines: |
409 | * - when inside an ":if", ":while" or ":for" |
410 | * - for multiple commands on one line, separated with '|' |
411 | * - when repeating until there are no more lines (for ":source") |
412 | */ |
413 | next_cmdline = cmdline; |
414 | do { |
415 | getline_is_func = getline_equal(fgetline, cookie, get_func_line); |
416 | |
417 | /* stop skipping cmds for an error msg after all endif/while/for */ |
418 | if (next_cmdline == NULL |
419 | && !force_abort |
420 | && cstack.cs_idx < 0 |
421 | && !(getline_is_func && func_has_abort(real_cookie)) |
422 | ) |
423 | did_emsg = FALSE; |
424 | |
425 | /* |
426 | * 1. If repeating a line in a loop, get a line from lines_ga. |
427 | * 2. If no line given: Get an allocated line with fgetline(). |
428 | * 3. If a line is given: Make a copy, so we can mess with it. |
429 | */ |
430 | |
431 | /* 1. If repeating, get a previous line from lines_ga. */ |
432 | if (cstack.cs_looplevel > 0 && current_line < lines_ga.ga_len) { |
433 | /* Each '|' separated command is stored separately in lines_ga, to |
434 | * be able to jump to it. Don't use next_cmdline now. */ |
435 | XFREE_CLEAR(cmdline_copy); |
436 | |
437 | /* Check if a function has returned or, unless it has an unclosed |
438 | * try conditional, aborted. */ |
439 | if (getline_is_func) { |
440 | if (do_profiling == PROF_YES) |
441 | func_line_end(real_cookie); |
442 | if (func_has_ended(real_cookie)) { |
443 | retval = FAIL; |
444 | break; |
445 | } |
446 | } else if (do_profiling == PROF_YES |
447 | && getline_equal(fgetline, cookie, getsourceline)) |
448 | script_line_end(); |
449 | |
450 | /* Check if a sourced file hit a ":finish" command. */ |
451 | if (source_finished(fgetline, cookie)) { |
452 | retval = FAIL; |
453 | break; |
454 | } |
455 | |
456 | /* If breakpoints have been added/deleted need to check for it. */ |
457 | if (breakpoint != NULL && dbg_tick != NULL |
458 | && *dbg_tick != debug_tick) { |
459 | *breakpoint = dbg_find_breakpoint( |
460 | getline_equal(fgetline, cookie, getsourceline), |
461 | fname, sourcing_lnum); |
462 | *dbg_tick = debug_tick; |
463 | } |
464 | |
465 | next_cmdline = ((wcmd_T *)(lines_ga.ga_data))[current_line].line; |
466 | sourcing_lnum = ((wcmd_T *)(lines_ga.ga_data))[current_line].lnum; |
467 | |
468 | /* Did we encounter a breakpoint? */ |
469 | if (breakpoint != NULL && *breakpoint != 0 |
470 | && *breakpoint <= sourcing_lnum) { |
471 | dbg_breakpoint(fname, sourcing_lnum); |
472 | /* Find next breakpoint. */ |
473 | *breakpoint = dbg_find_breakpoint( |
474 | getline_equal(fgetline, cookie, getsourceline), |
475 | fname, sourcing_lnum); |
476 | *dbg_tick = debug_tick; |
477 | } |
478 | if (do_profiling == PROF_YES) { |
479 | if (getline_is_func) |
480 | func_line_start(real_cookie); |
481 | else if (getline_equal(fgetline, cookie, getsourceline)) |
482 | script_line_start(); |
483 | } |
484 | } |
485 | |
486 | if (cstack.cs_looplevel > 0) { |
487 | /* Inside a while/for loop we need to store the lines and use them |
488 | * again. Pass a different "fgetline" function to do_one_cmd() |
489 | * below, so that it stores lines in or reads them from |
490 | * "lines_ga". Makes it possible to define a function inside a |
491 | * while/for loop. */ |
492 | cmd_getline = get_loop_line; |
493 | cmd_cookie = (void *)&cmd_loop_cookie; |
494 | cmd_loop_cookie.lines_gap = &lines_ga; |
495 | cmd_loop_cookie.current_line = current_line; |
496 | cmd_loop_cookie.getline = fgetline; |
497 | cmd_loop_cookie.cookie = cookie; |
498 | cmd_loop_cookie.repeating = (current_line < lines_ga.ga_len); |
499 | } else { |
500 | cmd_getline = fgetline; |
501 | cmd_cookie = cookie; |
502 | } |
503 | |
504 | /* 2. If no line given, get an allocated line with fgetline(). */ |
505 | if (next_cmdline == NULL) { |
506 | /* |
507 | * Need to set msg_didout for the first line after an ":if", |
508 | * otherwise the ":if" will be overwritten. |
509 | */ |
510 | if (count == 1 && getline_equal(fgetline, cookie, getexline)) |
511 | msg_didout = TRUE; |
512 | if (fgetline == NULL || (next_cmdline = fgetline(':', cookie, |
513 | cstack.cs_idx < |
514 | 0 ? 0 : (cstack.cs_idx + 1) * 2 |
515 | )) == NULL) { |
516 | /* Don't call wait_return for aborted command line. The NULL |
517 | * returned for the end of a sourced file or executed function |
518 | * doesn't do this. */ |
519 | if (KeyTyped && !(flags & DOCMD_REPEAT)) |
520 | need_wait_return = FALSE; |
521 | retval = FAIL; |
522 | break; |
523 | } |
524 | used_getline = TRUE; |
525 | |
526 | /* |
527 | * Keep the first typed line. Clear it when more lines are typed. |
528 | */ |
529 | if (flags & DOCMD_KEEPLINE) { |
530 | xfree(repeat_cmdline); |
531 | if (count == 0) |
532 | repeat_cmdline = vim_strsave(next_cmdline); |
533 | else |
534 | repeat_cmdline = NULL; |
535 | } |
536 | } |
537 | /* 3. Make a copy of the command so we can mess with it. */ |
538 | else if (cmdline_copy == NULL) { |
539 | next_cmdline = vim_strsave(next_cmdline); |
540 | } |
541 | cmdline_copy = next_cmdline; |
542 | |
543 | /* |
544 | * Save the current line when inside a ":while" or ":for", and when |
545 | * the command looks like a ":while" or ":for", because we may need it |
546 | * later. When there is a '|' and another command, it is stored |
547 | * separately, because we need to be able to jump back to it from an |
548 | * :endwhile/:endfor. |
549 | */ |
550 | if (current_line == lines_ga.ga_len |
551 | && (cstack.cs_looplevel || has_loop_cmd(next_cmdline))) { |
552 | store_loop_line(&lines_ga, next_cmdline); |
553 | } |
554 | did_endif = FALSE; |
555 | |
556 | if (count++ == 0) { |
557 | /* |
558 | * All output from the commands is put below each other, without |
559 | * waiting for a return. Don't do this when executing commands |
560 | * from a script or when being called recursive (e.g. for ":e |
561 | * +command file"). |
562 | */ |
563 | if (!(flags & DOCMD_NOWAIT) && !recursive) { |
564 | msg_didout_before_start = msg_didout; |
565 | msg_didany = FALSE; /* no output yet */ |
566 | msg_start(); |
567 | msg_scroll = TRUE; /* put messages below each other */ |
568 | ++no_wait_return; /* don't wait for return until finished */ |
569 | ++RedrawingDisabled; |
570 | did_inc = TRUE; |
571 | } |
572 | } |
573 | |
574 | if (p_verbose >= 15 && sourcing_name != NULL) { |
575 | ++no_wait_return; |
576 | verbose_enter_scroll(); |
577 | |
578 | smsg(_("line %" PRIdLINENR ": %s" ), sourcing_lnum, cmdline_copy); |
579 | if (msg_silent == 0) { |
580 | msg_puts("\n" ); // don't overwrite this either |
581 | } |
582 | |
583 | verbose_leave_scroll(); |
584 | --no_wait_return; |
585 | } |
586 | |
587 | /* |
588 | * 2. Execute one '|' separated command. |
589 | * do_one_cmd() will return NULL if there is no trailing '|'. |
590 | * "cmdline_copy" can change, e.g. for '%' and '#' expansion. |
591 | */ |
592 | recursive++; |
593 | next_cmdline = do_one_cmd(&cmdline_copy, flags, |
594 | &cstack, |
595 | cmd_getline, cmd_cookie); |
596 | recursive--; |
597 | |
598 | // Ignore trailing '|'-separated commands in preview-mode ('inccommand'). |
599 | if (State & CMDPREVIEW) { |
600 | next_cmdline = NULL; |
601 | } |
602 | |
603 | if (cmd_cookie == (void *)&cmd_loop_cookie) |
604 | /* Use "current_line" from "cmd_loop_cookie", it may have been |
605 | * incremented when defining a function. */ |
606 | current_line = cmd_loop_cookie.current_line; |
607 | |
608 | if (next_cmdline == NULL) { |
609 | XFREE_CLEAR(cmdline_copy); |
610 | // |
611 | // If the command was typed, remember it for the ':' register. |
612 | // Do this AFTER executing the command to make :@: work. |
613 | // |
614 | if (getline_equal(fgetline, cookie, getexline) |
615 | && new_last_cmdline != NULL) { |
616 | xfree(last_cmdline); |
617 | last_cmdline = new_last_cmdline; |
618 | new_last_cmdline = NULL; |
619 | } |
620 | } else { |
621 | /* need to copy the command after the '|' to cmdline_copy, for the |
622 | * next do_one_cmd() */ |
623 | STRMOVE(cmdline_copy, next_cmdline); |
624 | next_cmdline = cmdline_copy; |
625 | } |
626 | |
627 | |
628 | /* reset did_emsg for a function that is not aborted by an error */ |
629 | if (did_emsg && !force_abort |
630 | && getline_equal(fgetline, cookie, get_func_line) |
631 | && !func_has_abort(real_cookie)) |
632 | did_emsg = FALSE; |
633 | |
634 | if (cstack.cs_looplevel > 0) { |
635 | ++current_line; |
636 | |
637 | /* |
638 | * An ":endwhile", ":endfor" and ":continue" is handled here. |
639 | * If we were executing commands, jump back to the ":while" or |
640 | * ":for". |
641 | * If we were not executing commands, decrement cs_looplevel. |
642 | */ |
643 | if (cstack.cs_lflags & (CSL_HAD_CONT | CSL_HAD_ENDLOOP)) { |
644 | cstack.cs_lflags &= ~(CSL_HAD_CONT | CSL_HAD_ENDLOOP); |
645 | |
646 | /* Jump back to the matching ":while" or ":for". Be careful |
647 | * not to use a cs_line[] from an entry that isn't a ":while" |
648 | * or ":for": It would make "current_line" invalid and can |
649 | * cause a crash. */ |
650 | if (!did_emsg && !got_int && !current_exception |
651 | && cstack.cs_idx >= 0 |
652 | && (cstack.cs_flags[cstack.cs_idx] |
653 | & (CSF_WHILE | CSF_FOR)) |
654 | && cstack.cs_line[cstack.cs_idx] >= 0 |
655 | && (cstack.cs_flags[cstack.cs_idx] & CSF_ACTIVE)) { |
656 | current_line = cstack.cs_line[cstack.cs_idx]; |
657 | /* remember we jumped there */ |
658 | cstack.cs_lflags |= CSL_HAD_LOOP; |
659 | line_breakcheck(); /* check if CTRL-C typed */ |
660 | |
661 | /* Check for the next breakpoint at or after the ":while" |
662 | * or ":for". */ |
663 | if (breakpoint != NULL) { |
664 | *breakpoint = dbg_find_breakpoint( |
665 | getline_equal(fgetline, cookie, getsourceline), |
666 | fname, |
667 | ((wcmd_T *)lines_ga.ga_data)[current_line].lnum-1); |
668 | *dbg_tick = debug_tick; |
669 | } |
670 | } else { |
671 | /* can only get here with ":endwhile" or ":endfor" */ |
672 | if (cstack.cs_idx >= 0) |
673 | rewind_conditionals(&cstack, cstack.cs_idx - 1, |
674 | CSF_WHILE | CSF_FOR, &cstack.cs_looplevel); |
675 | } |
676 | } |
677 | /* |
678 | * For a ":while" or ":for" we need to remember the line number. |
679 | */ |
680 | else if (cstack.cs_lflags & CSL_HAD_LOOP) { |
681 | cstack.cs_lflags &= ~CSL_HAD_LOOP; |
682 | cstack.cs_line[cstack.cs_idx] = current_line - 1; |
683 | } |
684 | } |
685 | |
686 | /* |
687 | * When not inside any ":while" loop, clear remembered lines. |
688 | */ |
689 | if (cstack.cs_looplevel == 0) { |
690 | if (!GA_EMPTY(&lines_ga)) { |
691 | sourcing_lnum = ((wcmd_T *)lines_ga.ga_data)[lines_ga.ga_len - 1].lnum; |
692 | GA_DEEP_CLEAR(&lines_ga, wcmd_T, FREE_WCMD); |
693 | } |
694 | current_line = 0; |
695 | } |
696 | |
697 | /* |
698 | * A ":finally" makes did_emsg, got_int and current_exception pending for |
699 | * being restored at the ":endtry". Reset them here and set the |
700 | * ACTIVE and FINALLY flags, so that the finally clause gets executed. |
701 | * This includes the case where a missing ":endif", ":endwhile" or |
702 | * ":endfor" was detected by the ":finally" itself. |
703 | */ |
704 | if (cstack.cs_lflags & CSL_HAD_FINA) { |
705 | cstack.cs_lflags &= ~CSL_HAD_FINA; |
706 | report_make_pending((cstack.cs_pending[cstack.cs_idx] |
707 | & (CSTP_ERROR | CSTP_INTERRUPT | CSTP_THROW)), |
708 | current_exception); |
709 | did_emsg = got_int = false; |
710 | current_exception = NULL; |
711 | cstack.cs_flags[cstack.cs_idx] |= CSF_ACTIVE | CSF_FINALLY; |
712 | } |
713 | |
714 | /* Update global "trylevel" for recursive calls to do_cmdline() from |
715 | * within this loop. */ |
716 | trylevel = initial_trylevel + cstack.cs_trylevel; |
717 | |
718 | // If the outermost try conditional (across function calls and sourced |
719 | // files) is aborted because of an error, an interrupt, or an uncaught |
720 | // exception, cancel everything. If it is left normally, reset |
721 | // force_abort to get the non-EH compatible abortion behavior for |
722 | // the rest of the script. |
723 | if (trylevel == 0 && !did_emsg && !got_int && !current_exception) { |
724 | force_abort = false; |
725 | } |
726 | |
727 | /* Convert an interrupt to an exception if appropriate. */ |
728 | (void)do_intthrow(&cstack); |
729 | |
730 | } |
731 | /* |
732 | * Continue executing command lines when: |
733 | * - no CTRL-C typed, no aborting error, no exception thrown or try |
734 | * conditionals need to be checked for executing finally clauses or |
735 | * catching an interrupt exception |
736 | * - didn't get an error message or lines are not typed |
737 | * - there is a command after '|', inside a :if, :while, :for or :try, or |
738 | * looping for ":source" command or function call. |
739 | */ |
740 | while (!((got_int || (did_emsg && force_abort) || current_exception) |
741 | && cstack.cs_trylevel == 0) |
742 | && !(did_emsg |
743 | /* Keep going when inside try/catch, so that the error can be |
744 | * deal with, except when it is a syntax error, it may cause |
745 | * the :endtry to be missed. */ |
746 | && (cstack.cs_trylevel == 0 || did_emsg_syntax) |
747 | && used_getline |
748 | && (getline_equal(fgetline, cookie, getexmodeline) |
749 | || getline_equal(fgetline, cookie, getexline))) |
750 | && (next_cmdline != NULL |
751 | || cstack.cs_idx >= 0 |
752 | || (flags & DOCMD_REPEAT))); |
753 | |
754 | xfree(cmdline_copy); |
755 | did_emsg_syntax = FALSE; |
756 | GA_DEEP_CLEAR(&lines_ga, wcmd_T, FREE_WCMD); |
757 | |
758 | if (cstack.cs_idx >= 0) { |
759 | /* |
760 | * If a sourced file or executed function ran to its end, report the |
761 | * unclosed conditional. |
762 | */ |
763 | if (!got_int && !current_exception |
764 | && ((getline_equal(fgetline, cookie, getsourceline) |
765 | && !source_finished(fgetline, cookie)) |
766 | || (getline_equal(fgetline, cookie, get_func_line) |
767 | && !func_has_ended(real_cookie)))) { |
768 | if (cstack.cs_flags[cstack.cs_idx] & CSF_TRY) |
769 | EMSG(_(e_endtry)); |
770 | else if (cstack.cs_flags[cstack.cs_idx] & CSF_WHILE) |
771 | EMSG(_(e_endwhile)); |
772 | else if (cstack.cs_flags[cstack.cs_idx] & CSF_FOR) |
773 | EMSG(_(e_endfor)); |
774 | else |
775 | EMSG(_(e_endif)); |
776 | } |
777 | |
778 | /* |
779 | * Reset "trylevel" in case of a ":finish" or ":return" or a missing |
780 | * ":endtry" in a sourced file or executed function. If the try |
781 | * conditional is in its finally clause, ignore anything pending. |
782 | * If it is in a catch clause, finish the caught exception. |
783 | * Also cleanup any "cs_forinfo" structures. |
784 | */ |
785 | do { |
786 | int idx = cleanup_conditionals(&cstack, 0, TRUE); |
787 | |
788 | if (idx >= 0) |
789 | --idx; /* remove try block not in its finally clause */ |
790 | rewind_conditionals(&cstack, idx, CSF_WHILE | CSF_FOR, |
791 | &cstack.cs_looplevel); |
792 | } while (cstack.cs_idx >= 0); |
793 | trylevel = initial_trylevel; |
794 | } |
795 | |
796 | /* If a missing ":endtry", ":endwhile", ":endfor", or ":endif" or a memory |
797 | * lack was reported above and the error message is to be converted to an |
798 | * exception, do this now after rewinding the cstack. */ |
799 | do_errthrow(&cstack, getline_equal(fgetline, cookie, get_func_line) |
800 | ? (char_u *)"endfunction" : (char_u *)NULL); |
801 | |
802 | if (trylevel == 0) { |
803 | // When an exception is being thrown out of the outermost try |
804 | // conditional, discard the uncaught exception, disable the conversion |
805 | // of interrupts or errors to exceptions, and ensure that no more |
806 | // commands are executed. |
807 | if (current_exception) { |
808 | void *p = NULL; |
809 | char_u *saved_sourcing_name; |
810 | int saved_sourcing_lnum; |
811 | struct msglist *messages = NULL; |
812 | struct msglist *next; |
813 | |
814 | /* |
815 | * If the uncaught exception is a user exception, report it as an |
816 | * error. If it is an error exception, display the saved error |
817 | * message now. For an interrupt exception, do nothing; the |
818 | * interrupt message is given elsewhere. |
819 | */ |
820 | switch (current_exception->type) { |
821 | case ET_USER: |
822 | vim_snprintf((char *)IObuff, IOSIZE, |
823 | _("E605: Exception not caught: %s" ), |
824 | current_exception->value); |
825 | p = vim_strsave(IObuff); |
826 | break; |
827 | case ET_ERROR: |
828 | messages = current_exception->messages; |
829 | current_exception->messages = NULL; |
830 | break; |
831 | case ET_INTERRUPT: |
832 | break; |
833 | } |
834 | |
835 | saved_sourcing_name = sourcing_name; |
836 | saved_sourcing_lnum = sourcing_lnum; |
837 | sourcing_name = current_exception->throw_name; |
838 | sourcing_lnum = current_exception->throw_lnum; |
839 | current_exception->throw_name = NULL; |
840 | |
841 | discard_current_exception(); // uses IObuff if 'verbose' |
842 | suppress_errthrow = true; |
843 | force_abort = true; |
844 | msg_ext_set_kind("emsg" ); // kind=emsg for :throw, exceptions. #9993 |
845 | |
846 | if (messages != NULL) { |
847 | do { |
848 | next = messages->next; |
849 | emsg(messages->msg); |
850 | xfree(messages->msg); |
851 | xfree(messages); |
852 | messages = next; |
853 | } while (messages != NULL); |
854 | } else if (p != NULL) { |
855 | emsg(p); |
856 | xfree(p); |
857 | } |
858 | xfree(sourcing_name); |
859 | sourcing_name = saved_sourcing_name; |
860 | sourcing_lnum = saved_sourcing_lnum; |
861 | } |
862 | /* |
863 | * On an interrupt or an aborting error not converted to an exception, |
864 | * disable the conversion of errors to exceptions. (Interrupts are not |
865 | * converted any more, here.) This enables also the interrupt message |
866 | * when force_abort is set and did_emsg unset in case of an interrupt |
867 | * from a finally clause after an error. |
868 | */ |
869 | else if (got_int || (did_emsg && force_abort)) |
870 | suppress_errthrow = TRUE; |
871 | } |
872 | |
873 | // The current cstack will be freed when do_cmdline() returns. An uncaught |
874 | // exception will have to be rethrown in the previous cstack. If a function |
875 | // has just returned or a script file was just finished and the previous |
876 | // cstack belongs to the same function or, respectively, script file, it |
877 | // will have to be checked for finally clauses to be executed due to the |
878 | // ":return" or ":finish". This is done in do_one_cmd(). |
879 | if (current_exception) { |
880 | need_rethrow = true; |
881 | } |
882 | if ((getline_equal(fgetline, cookie, getsourceline) |
883 | && ex_nesting_level > source_level(real_cookie)) |
884 | || (getline_equal(fgetline, cookie, get_func_line) |
885 | && ex_nesting_level > func_level(real_cookie) + 1)) { |
886 | if (!current_exception) { |
887 | check_cstack = true; |
888 | } |
889 | } else { |
890 | /* When leaving a function, reduce nesting level. */ |
891 | if (getline_equal(fgetline, cookie, get_func_line)) |
892 | --ex_nesting_level; |
893 | /* |
894 | * Go to debug mode when returning from a function in which we are |
895 | * single-stepping. |
896 | */ |
897 | if ((getline_equal(fgetline, cookie, getsourceline) |
898 | || getline_equal(fgetline, cookie, get_func_line)) |
899 | && ex_nesting_level + 1 <= debug_break_level) |
900 | do_debug(getline_equal(fgetline, cookie, getsourceline) |
901 | ? (char_u *)_("End of sourced file" ) |
902 | : (char_u *)_("End of function" )); |
903 | } |
904 | |
905 | /* |
906 | * Restore the exception environment (done after returning from the |
907 | * debugger). |
908 | */ |
909 | if (flags & DOCMD_EXCRESET) |
910 | restore_dbg_stuff(&debug_saved); |
911 | |
912 | msg_list = saved_msg_list; |
913 | |
914 | /* |
915 | * If there was too much output to fit on the command line, ask the user to |
916 | * hit return before redrawing the screen. With the ":global" command we do |
917 | * this only once after the command is finished. |
918 | */ |
919 | if (did_inc) { |
920 | --RedrawingDisabled; |
921 | --no_wait_return; |
922 | msg_scroll = FALSE; |
923 | |
924 | /* |
925 | * When just finished an ":if"-":else" which was typed, no need to |
926 | * wait for hit-return. Also for an error situation. |
927 | */ |
928 | if (retval == FAIL |
929 | || (did_endif && KeyTyped && !did_emsg) |
930 | ) { |
931 | need_wait_return = FALSE; |
932 | msg_didany = FALSE; /* don't wait when restarting edit */ |
933 | } else if (need_wait_return) { |
934 | /* |
935 | * The msg_start() above clears msg_didout. The wait_return we do |
936 | * here should not overwrite the command that may be shown before |
937 | * doing that. |
938 | */ |
939 | msg_didout |= msg_didout_before_start; |
940 | wait_return(FALSE); |
941 | } |
942 | } |
943 | |
944 | did_endif = FALSE; /* in case do_cmdline used recursively */ |
945 | |
946 | call_depth--; |
947 | end_batch_changes(); |
948 | return retval; |
949 | } |
950 | |
951 | /* |
952 | * Obtain a line when inside a ":while" or ":for" loop. |
953 | */ |
954 | static char_u *get_loop_line(int c, void *cookie, int indent) |
955 | { |
956 | struct loop_cookie *cp = (struct loop_cookie *)cookie; |
957 | wcmd_T *wp; |
958 | char_u *line; |
959 | |
960 | if (cp->current_line + 1 >= cp->lines_gap->ga_len) { |
961 | if (cp->repeating) |
962 | return NULL; /* trying to read past ":endwhile"/":endfor" */ |
963 | |
964 | /* First time inside the ":while"/":for": get line normally. */ |
965 | if (cp->getline == NULL) |
966 | line = getcmdline(c, 0L, indent); |
967 | else |
968 | line = cp->getline(c, cp->cookie, indent); |
969 | if (line != NULL) { |
970 | store_loop_line(cp->lines_gap, line); |
971 | ++cp->current_line; |
972 | } |
973 | |
974 | return line; |
975 | } |
976 | |
977 | KeyTyped = false; |
978 | cp->current_line++; |
979 | wp = (wcmd_T *)(cp->lines_gap->ga_data) + cp->current_line; |
980 | sourcing_lnum = wp->lnum; |
981 | return vim_strsave(wp->line); |
982 | } |
983 | |
984 | /* |
985 | * Store a line in "gap" so that a ":while" loop can execute it again. |
986 | */ |
987 | static void store_loop_line(garray_T *gap, char_u *line) |
988 | { |
989 | wcmd_T *p = GA_APPEND_VIA_PTR(wcmd_T, gap); |
990 | p->line = vim_strsave(line); |
991 | p->lnum = sourcing_lnum; |
992 | } |
993 | |
994 | /* |
995 | * If "fgetline" is get_loop_line(), return TRUE if the getline it uses equals |
996 | * "func". * Otherwise return TRUE when "fgetline" equals "func". |
997 | */ |
998 | int getline_equal(LineGetter fgetline, |
999 | void *cookie, /* argument for fgetline() */ |
1000 | LineGetter func) |
1001 | { |
1002 | LineGetter gp; |
1003 | struct loop_cookie *cp; |
1004 | |
1005 | /* When "fgetline" is "get_loop_line()" use the "cookie" to find the |
1006 | * function that's originally used to obtain the lines. This may be |
1007 | * nested several levels. */ |
1008 | gp = fgetline; |
1009 | cp = (struct loop_cookie *)cookie; |
1010 | while (gp == get_loop_line) { |
1011 | gp = cp->getline; |
1012 | cp = cp->cookie; |
1013 | } |
1014 | return gp == func; |
1015 | } |
1016 | |
1017 | /* |
1018 | * If "fgetline" is get_loop_line(), return the cookie used by the original |
1019 | * getline function. Otherwise return "cookie". |
1020 | */ |
1021 | void * getline_cookie(LineGetter fgetline, |
1022 | void *cookie /* argument for fgetline() */ |
1023 | ) |
1024 | { |
1025 | LineGetter gp; |
1026 | struct loop_cookie *cp; |
1027 | |
1028 | /* When "fgetline" is "get_loop_line()" use the "cookie" to find the |
1029 | * cookie that's originally used to obtain the lines. This may be nested |
1030 | * several levels. */ |
1031 | gp = fgetline; |
1032 | cp = (struct loop_cookie *)cookie; |
1033 | while (gp == get_loop_line) { |
1034 | gp = cp->getline; |
1035 | cp = cp->cookie; |
1036 | } |
1037 | return cp; |
1038 | } |
1039 | |
1040 | /* |
1041 | * Helper function to apply an offset for buffer commands, i.e. ":bdelete", |
1042 | * ":bwipeout", etc. |
1043 | * Returns the buffer number. |
1044 | */ |
1045 | static int compute_buffer_local_count(int addr_type, int lnum, int offset) |
1046 | { |
1047 | buf_T *buf; |
1048 | buf_T *nextbuf; |
1049 | int count = offset; |
1050 | |
1051 | buf = firstbuf; |
1052 | while (buf->b_next != NULL && buf->b_fnum < lnum) |
1053 | buf = buf->b_next; |
1054 | while (count != 0) { |
1055 | count += (count < 0) ? 1 : -1; |
1056 | nextbuf = (offset < 0) ? buf->b_prev : buf->b_next; |
1057 | if (nextbuf == NULL) |
1058 | break; |
1059 | buf = nextbuf; |
1060 | if (addr_type == ADDR_LOADED_BUFFERS) |
1061 | /* skip over unloaded buffers */ |
1062 | while (buf->b_ml.ml_mfp == NULL) { |
1063 | nextbuf = (offset < 0) ? buf->b_prev : buf->b_next; |
1064 | if (nextbuf == NULL) { |
1065 | break; |
1066 | } |
1067 | buf = nextbuf; |
1068 | } |
1069 | } |
1070 | // we might have gone too far, last buffer is not loaded |
1071 | if (addr_type == ADDR_LOADED_BUFFERS) { |
1072 | while (buf->b_ml.ml_mfp == NULL) { |
1073 | nextbuf = (offset >= 0) ? buf->b_prev : buf->b_next; |
1074 | if (nextbuf == NULL) |
1075 | break; |
1076 | buf = nextbuf; |
1077 | } |
1078 | } |
1079 | return buf->b_fnum; |
1080 | } |
1081 | |
1082 | static int current_win_nr(win_T *win) |
1083 | { |
1084 | int nr = 0; |
1085 | |
1086 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
1087 | ++nr; |
1088 | if (wp == win) |
1089 | break; |
1090 | } |
1091 | return nr; |
1092 | } |
1093 | |
1094 | static int current_tab_nr(tabpage_T *tab) |
1095 | { |
1096 | int nr = 0; |
1097 | |
1098 | FOR_ALL_TABS(tp) { |
1099 | ++nr; |
1100 | if (tp == tab) |
1101 | break; |
1102 | } |
1103 | return nr; |
1104 | } |
1105 | |
1106 | #define CURRENT_WIN_NR current_win_nr(curwin) |
1107 | #define LAST_WIN_NR current_win_nr(NULL) |
1108 | #define CURRENT_TAB_NR current_tab_nr(curtab) |
1109 | #define LAST_TAB_NR current_tab_nr(NULL) |
1110 | |
1111 | |
1112 | /// Figure out the address type for ":wincmd". |
1113 | static void get_wincmd_addr_type(char_u *arg, exarg_T *eap) |
1114 | { |
1115 | switch (*arg) { |
1116 | case 'S': |
1117 | case Ctrl_S: |
1118 | case 's': |
1119 | case Ctrl_N: |
1120 | case 'n': |
1121 | case 'j': |
1122 | case Ctrl_J: |
1123 | case 'k': |
1124 | case Ctrl_K: |
1125 | case 'T': |
1126 | case Ctrl_R: |
1127 | case 'r': |
1128 | case 'R': |
1129 | case 'K': |
1130 | case 'J': |
1131 | case '+': |
1132 | case '-': |
1133 | case Ctrl__: |
1134 | case '_': |
1135 | case '|': |
1136 | case ']': |
1137 | case Ctrl_RSB: |
1138 | case 'g': |
1139 | case Ctrl_G: |
1140 | case Ctrl_V: |
1141 | case 'v': |
1142 | case 'h': |
1143 | case Ctrl_H: |
1144 | case 'l': |
1145 | case Ctrl_L: |
1146 | case 'H': |
1147 | case 'L': |
1148 | case '>': |
1149 | case '<': |
1150 | case '}': |
1151 | case 'f': |
1152 | case 'F': |
1153 | case Ctrl_F: |
1154 | case 'i': |
1155 | case Ctrl_I: |
1156 | case 'd': |
1157 | case Ctrl_D: |
1158 | // window size or any count |
1159 | eap->addr_type = ADDR_LINES; // -V1037 |
1160 | break; |
1161 | |
1162 | case Ctrl_HAT: |
1163 | case '^': |
1164 | // buffer number |
1165 | eap->addr_type = ADDR_BUFFERS; |
1166 | break; |
1167 | |
1168 | case Ctrl_Q: |
1169 | case 'q': |
1170 | case Ctrl_C: |
1171 | case 'c': |
1172 | case Ctrl_O: |
1173 | case 'o': |
1174 | case Ctrl_W: |
1175 | case 'w': |
1176 | case 'W': |
1177 | case 'x': |
1178 | case Ctrl_X: |
1179 | // window number |
1180 | eap->addr_type = ADDR_WINDOWS; |
1181 | break; |
1182 | |
1183 | case Ctrl_Z: |
1184 | case 'z': |
1185 | case 'P': |
1186 | case 't': |
1187 | case Ctrl_T: |
1188 | case 'b': |
1189 | case Ctrl_B: |
1190 | case 'p': |
1191 | case Ctrl_P: |
1192 | case '=': |
1193 | case CAR: |
1194 | // no count |
1195 | eap->addr_type = 0; |
1196 | break; |
1197 | } |
1198 | } |
1199 | |
1200 | /* |
1201 | * Execute one Ex command. |
1202 | * |
1203 | * If 'sourcing' is TRUE, the command will be included in the error message. |
1204 | * |
1205 | * 1. skip comment lines and leading space |
1206 | * 2. handle command modifiers |
1207 | * 3. skip over the range to find the command |
1208 | * 4. parse the range |
1209 | * 5. parse the command |
1210 | * 6. parse arguments |
1211 | * 7. switch on command name |
1212 | * |
1213 | * Note: "fgetline" can be NULL. |
1214 | * |
1215 | * This function may be called recursively! |
1216 | */ |
1217 | static char_u * do_one_cmd(char_u **cmdlinep, |
1218 | int flags, |
1219 | struct condstack *cstack, |
1220 | LineGetter fgetline, |
1221 | void *cookie /* argument for fgetline() */ |
1222 | ) |
1223 | { |
1224 | char_u *p; |
1225 | linenr_T lnum; |
1226 | long n; |
1227 | char_u *errormsg = NULL; /* error message */ |
1228 | exarg_T ea; /* Ex command arguments */ |
1229 | long verbose_save = -1; |
1230 | int save_msg_scroll = msg_scroll; |
1231 | int save_msg_silent = -1; |
1232 | int did_esilent = 0; |
1233 | int did_sandbox = FALSE; |
1234 | cmdmod_T save_cmdmod; |
1235 | const int save_reg_executing = reg_executing; |
1236 | char_u *cmd; |
1237 | int address_count = 1; |
1238 | |
1239 | memset(&ea, 0, sizeof(ea)); |
1240 | ea.line1 = 1; |
1241 | ea.line2 = 1; |
1242 | ex_nesting_level++; |
1243 | |
1244 | /* When the last file has not been edited :q has to be typed twice. */ |
1245 | if (quitmore |
1246 | /* avoid that a function call in 'statusline' does this */ |
1247 | && !getline_equal(fgetline, cookie, get_func_line) |
1248 | /* avoid that an autocommand, e.g. QuitPre, does this */ |
1249 | && !getline_equal(fgetline, cookie, getnextac) |
1250 | ) |
1251 | --quitmore; |
1252 | |
1253 | /* |
1254 | * Reset browse, confirm, etc.. They are restored when returning, for |
1255 | * recursive calls. |
1256 | */ |
1257 | save_cmdmod = cmdmod; |
1258 | memset(&cmdmod, 0, sizeof(cmdmod)); |
1259 | |
1260 | /* "#!anything" is handled like a comment. */ |
1261 | if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') |
1262 | goto doend; |
1263 | |
1264 | /* |
1265 | * Repeat until no more command modifiers are found. |
1266 | */ |
1267 | ea.cmd = *cmdlinep; |
1268 | for (;; ) { |
1269 | /* |
1270 | * 1. Skip comment lines and leading white space and colons. |
1271 | */ |
1272 | while (*ea.cmd == ' ' || *ea.cmd == '\t' || *ea.cmd == ':') |
1273 | ++ea.cmd; |
1274 | |
1275 | /* in ex mode, an empty line works like :+ */ |
1276 | if (*ea.cmd == NUL && exmode_active |
1277 | && (getline_equal(fgetline, cookie, getexmodeline) |
1278 | || getline_equal(fgetline, cookie, getexline)) |
1279 | && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { |
1280 | ea.cmd = (char_u *)"+" ; |
1281 | ex_pressedreturn = TRUE; |
1282 | } |
1283 | |
1284 | /* ignore comment and empty lines */ |
1285 | if (*ea.cmd == '"') |
1286 | goto doend; |
1287 | if (*ea.cmd == NUL) { |
1288 | ex_pressedreturn = TRUE; |
1289 | goto doend; |
1290 | } |
1291 | |
1292 | /* |
1293 | * 2. Handle command modifiers. |
1294 | */ |
1295 | p = skip_range(ea.cmd, NULL); |
1296 | switch (*p) { |
1297 | /* When adding an entry, also modify cmd_exists(). */ |
1298 | case 'a': if (!checkforcmd(&ea.cmd, "aboveleft" , 3)) |
1299 | break; |
1300 | cmdmod.split |= WSP_ABOVE; |
1301 | continue; |
1302 | |
1303 | case 'b': if (checkforcmd(&ea.cmd, "belowright" , 3)) { |
1304 | cmdmod.split |= WSP_BELOW; |
1305 | continue; |
1306 | } |
1307 | if (checkforcmd(&ea.cmd, "browse" , 3)) { |
1308 | cmdmod.browse = true; |
1309 | continue; |
1310 | } |
1311 | if (!checkforcmd(&ea.cmd, "botright" , 2)) |
1312 | break; |
1313 | cmdmod.split |= WSP_BOT; |
1314 | continue; |
1315 | |
1316 | case 'c': if (!checkforcmd(&ea.cmd, "confirm" , 4)) |
1317 | break; |
1318 | cmdmod.confirm = true; |
1319 | continue; |
1320 | |
1321 | case 'k': if (checkforcmd(&ea.cmd, "keepmarks" , 3)) { |
1322 | cmdmod.keepmarks = true; |
1323 | continue; |
1324 | } |
1325 | if (checkforcmd(&ea.cmd, "keepalt" , 5)) { |
1326 | cmdmod.keepalt = true; |
1327 | continue; |
1328 | } |
1329 | if (checkforcmd(&ea.cmd, "keeppatterns" , 5)) { |
1330 | cmdmod.keeppatterns = true; |
1331 | continue; |
1332 | } |
1333 | if (!checkforcmd(&ea.cmd, "keepjumps" , 5)) |
1334 | break; |
1335 | cmdmod.keepjumps = true; |
1336 | continue; |
1337 | |
1338 | case 'f': { // only accept ":filter {pat} cmd" |
1339 | char_u *reg_pat; |
1340 | |
1341 | if (!checkforcmd(&p, "filter" , 4) || *p == NUL || ends_excmd(*p)) { |
1342 | break; |
1343 | } |
1344 | if (*p == '!') { |
1345 | cmdmod.filter_force = true; |
1346 | p = skipwhite(p + 1); |
1347 | if (*p == NUL || ends_excmd(*p)) { |
1348 | break; |
1349 | } |
1350 | } |
1351 | p = skip_vimgrep_pat(p, ®_pat, NULL); |
1352 | if (p == NULL || *p == NUL) { |
1353 | break; |
1354 | } |
1355 | cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC); |
1356 | if (cmdmod.filter_regmatch.regprog == NULL) { |
1357 | break; |
1358 | } |
1359 | ea.cmd = p; |
1360 | continue; |
1361 | } |
1362 | |
1363 | /* ":hide" and ":hide | cmd" are not modifiers */ |
1364 | case 'h': if (p != ea.cmd || !checkforcmd(&p, "hide" , 3) |
1365 | || *p == NUL || ends_excmd(*p)) |
1366 | break; |
1367 | ea.cmd = p; |
1368 | cmdmod.hide = true; |
1369 | continue; |
1370 | |
1371 | case 'l': if (checkforcmd(&ea.cmd, "lockmarks" , 3)) { |
1372 | cmdmod.lockmarks = true; |
1373 | continue; |
1374 | } |
1375 | |
1376 | if (!checkforcmd(&ea.cmd, "leftabove" , 5)) |
1377 | break; |
1378 | cmdmod.split |= WSP_ABOVE; |
1379 | continue; |
1380 | |
1381 | case 'n': |
1382 | if (checkforcmd(&ea.cmd, "noautocmd" , 3)) { |
1383 | if (cmdmod.save_ei == NULL) { |
1384 | /* Set 'eventignore' to "all". Restore the |
1385 | * existing option value later. */ |
1386 | cmdmod.save_ei = vim_strsave(p_ei); |
1387 | set_string_option_direct( |
1388 | (char_u *)"ei" , -1, (char_u *)"all" , OPT_FREE, SID_NONE); |
1389 | } |
1390 | continue; |
1391 | } |
1392 | if (!checkforcmd(&ea.cmd, "noswapfile" , 3)) { |
1393 | break; |
1394 | } |
1395 | cmdmod.noswapfile = true; |
1396 | continue; |
1397 | |
1398 | case 'r': if (!checkforcmd(&ea.cmd, "rightbelow" , 6)) |
1399 | break; |
1400 | cmdmod.split |= WSP_BELOW; |
1401 | continue; |
1402 | |
1403 | case 's': if (checkforcmd(&ea.cmd, "sandbox" , 3)) { |
1404 | if (!did_sandbox) |
1405 | ++sandbox; |
1406 | did_sandbox = TRUE; |
1407 | continue; |
1408 | } |
1409 | if (!checkforcmd(&ea.cmd, "silent" , 3)) |
1410 | break; |
1411 | if (save_msg_silent == -1) |
1412 | save_msg_silent = msg_silent; |
1413 | ++msg_silent; |
1414 | if (*ea.cmd == '!' && !ascii_iswhite(ea.cmd[-1])) { |
1415 | /* ":silent!", but not "silent !cmd" */ |
1416 | ea.cmd = skipwhite(ea.cmd + 1); |
1417 | ++emsg_silent; |
1418 | ++did_esilent; |
1419 | } |
1420 | continue; |
1421 | |
1422 | case 't': if (checkforcmd(&p, "tab" , 3)) { |
1423 | long tabnr = get_address(&ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1); |
1424 | if (tabnr == MAXLNUM) { |
1425 | cmdmod.tab = tabpage_index(curtab) + 1; |
1426 | } else { |
1427 | if (tabnr < 0 || tabnr > LAST_TAB_NR) { |
1428 | errormsg = (char_u *)_(e_invrange); |
1429 | goto doend; |
1430 | } |
1431 | cmdmod.tab = tabnr + 1; |
1432 | } |
1433 | ea.cmd = p; |
1434 | continue; |
1435 | } |
1436 | if (!checkforcmd(&ea.cmd, "topleft" , 2)) |
1437 | break; |
1438 | cmdmod.split |= WSP_TOP; |
1439 | continue; |
1440 | |
1441 | case 'u': if (!checkforcmd(&ea.cmd, "unsilent" , 3)) |
1442 | break; |
1443 | if (save_msg_silent == -1) |
1444 | save_msg_silent = msg_silent; |
1445 | msg_silent = 0; |
1446 | continue; |
1447 | |
1448 | case 'v': if (checkforcmd(&ea.cmd, "vertical" , 4)) { |
1449 | cmdmod.split |= WSP_VERT; |
1450 | continue; |
1451 | } |
1452 | if (!checkforcmd(&p, "verbose" , 4)) |
1453 | break; |
1454 | if (verbose_save < 0) |
1455 | verbose_save = p_verbose; |
1456 | if (ascii_isdigit(*ea.cmd)) |
1457 | p_verbose = atoi((char *)ea.cmd); |
1458 | else |
1459 | p_verbose = 1; |
1460 | ea.cmd = p; |
1461 | continue; |
1462 | } |
1463 | break; |
1464 | } |
1465 | char_u *after_modifier = ea.cmd; |
1466 | |
1467 | ea.skip = (did_emsg |
1468 | || got_int |
1469 | || current_exception |
1470 | || (cstack->cs_idx >= 0 |
1471 | && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); |
1472 | |
1473 | // 3. Skip over the range to find the command. Let "p" point to after it. |
1474 | // |
1475 | // We need the command to know what kind of range it uses. |
1476 | |
1477 | cmd = ea.cmd; |
1478 | ea.cmd = skip_range(ea.cmd, NULL); |
1479 | if (*ea.cmd == '*') { |
1480 | ea.cmd = skipwhite(ea.cmd + 1); |
1481 | } |
1482 | p = find_command(&ea, NULL); |
1483 | |
1484 | // Count this line for profiling if skip is TRUE. |
1485 | if (do_profiling == PROF_YES |
1486 | && (!ea.skip || cstack->cs_idx == 0 |
1487 | || (cstack->cs_idx > 0 |
1488 | && (cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE)))) { |
1489 | int skip = did_emsg || got_int || current_exception; |
1490 | |
1491 | if (ea.cmdidx == CMD_catch) { |
1492 | skip = !skip && !(cstack->cs_idx >= 0 |
1493 | && (cstack->cs_flags[cstack->cs_idx] & CSF_THROWN) |
1494 | && !(cstack->cs_flags[cstack->cs_idx] & CSF_CAUGHT)); |
1495 | } else if (ea.cmdidx == CMD_else || ea.cmdidx == CMD_elseif) { |
1496 | skip = skip || !(cstack->cs_idx >= 0 |
1497 | && !(cstack->cs_flags[cstack->cs_idx] |
1498 | & (CSF_ACTIVE | CSF_TRUE))); |
1499 | } else if (ea.cmdidx == CMD_finally) { |
1500 | skip = false; |
1501 | } else if (ea.cmdidx != CMD_endif |
1502 | && ea.cmdidx != CMD_endfor |
1503 | && ea.cmdidx != CMD_endtry |
1504 | && ea.cmdidx != CMD_endwhile) { |
1505 | skip = ea.skip; |
1506 | } |
1507 | |
1508 | if (!skip) { |
1509 | if (getline_equal(fgetline, cookie, get_func_line)) { |
1510 | func_line_exec(getline_cookie(fgetline, cookie)); |
1511 | } else if (getline_equal(fgetline, cookie, getsourceline)) { |
1512 | script_line_exec(); |
1513 | } |
1514 | } |
1515 | } |
1516 | |
1517 | // May go to debug mode. If this happens and the ">quit" debug command is |
1518 | // used, throw an interrupt exception and skip the next command. |
1519 | dbg_check_breakpoint(&ea); |
1520 | if (!ea.skip && got_int) { |
1521 | ea.skip = TRUE; |
1522 | (void)do_intthrow(cstack); |
1523 | } |
1524 | |
1525 | // 4. Parse a range specifier of the form: addr [,addr] [;addr] .. |
1526 | // |
1527 | // where 'addr' is: |
1528 | // |
1529 | // % (entire file) |
1530 | // $ [+-NUM] |
1531 | // 'x [+-NUM] (where x denotes a currently defined mark) |
1532 | // . [+-NUM] |
1533 | // [+-NUM].. |
1534 | // NUM |
1535 | // |
1536 | // The ea.cmd pointer is updated to point to the first character following the |
1537 | // range spec. If an initial address is found, but no second, the upper bound |
1538 | // is equal to the lower. |
1539 | |
1540 | // ea.addr_type for user commands is set by find_ucmd |
1541 | if (!IS_USER_CMDIDX(ea.cmdidx)) { |
1542 | if (ea.cmdidx != CMD_SIZE) { |
1543 | ea.addr_type = cmdnames[(int)ea.cmdidx].cmd_addr_type; |
1544 | } else { |
1545 | ea.addr_type = ADDR_LINES; |
1546 | } |
1547 | // :wincmd range depends on the argument |
1548 | if (ea.cmdidx == CMD_wincmd && p != NULL) { |
1549 | get_wincmd_addr_type(skipwhite(p), &ea); |
1550 | } |
1551 | } |
1552 | |
1553 | /* repeat for all ',' or ';' separated addresses */ |
1554 | ea.cmd = cmd; |
1555 | for (;; ) { |
1556 | ea.line1 = ea.line2; |
1557 | switch (ea.addr_type) { |
1558 | case ADDR_LINES: |
1559 | // default is current line number |
1560 | ea.line2 = curwin->w_cursor.lnum; |
1561 | break; |
1562 | case ADDR_WINDOWS: |
1563 | ea.line2 = CURRENT_WIN_NR; |
1564 | break; |
1565 | case ADDR_ARGUMENTS: |
1566 | ea.line2 = curwin->w_arg_idx + 1; |
1567 | if (ea.line2 > ARGCOUNT) { |
1568 | ea.line2 = ARGCOUNT; |
1569 | } |
1570 | break; |
1571 | case ADDR_LOADED_BUFFERS: |
1572 | case ADDR_BUFFERS: |
1573 | ea.line2 = curbuf->b_fnum; |
1574 | break; |
1575 | case ADDR_TABS: |
1576 | ea.line2 = CURRENT_TAB_NR; |
1577 | break; |
1578 | case ADDR_TABS_RELATIVE: |
1579 | ea.line2 = 1; |
1580 | break; |
1581 | case ADDR_QUICKFIX: |
1582 | ea.line2 = qf_get_cur_valid_idx(&ea); |
1583 | break; |
1584 | } |
1585 | ea.cmd = skipwhite(ea.cmd); |
1586 | lnum = get_address(&ea, &ea.cmd, ea.addr_type, ea.skip, |
1587 | ea.addr_count == 0, address_count++); |
1588 | if (ea.cmd == NULL) { // error detected |
1589 | goto doend; |
1590 | } |
1591 | if (lnum == MAXLNUM) { |
1592 | if (*ea.cmd == '%') { /* '%' - all lines */ |
1593 | ++ea.cmd; |
1594 | switch (ea.addr_type) { |
1595 | case ADDR_LINES: |
1596 | ea.line1 = 1; |
1597 | ea.line2 = curbuf->b_ml.ml_line_count; |
1598 | break; |
1599 | case ADDR_LOADED_BUFFERS: { |
1600 | buf_T *buf = firstbuf; |
1601 | while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) { |
1602 | buf = buf->b_next; |
1603 | } |
1604 | ea.line1 = buf->b_fnum; |
1605 | buf = lastbuf; |
1606 | while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) { |
1607 | buf = buf->b_prev; |
1608 | } |
1609 | ea.line2 = buf->b_fnum; |
1610 | break; |
1611 | } |
1612 | case ADDR_BUFFERS: |
1613 | ea.line1 = firstbuf->b_fnum; |
1614 | ea.line2 = lastbuf->b_fnum; |
1615 | break; |
1616 | case ADDR_WINDOWS: |
1617 | case ADDR_TABS: |
1618 | if (IS_USER_CMDIDX(ea.cmdidx)) { |
1619 | ea.line1 = 1; |
1620 | ea.line2 = |
1621 | ea.addr_type == ADDR_WINDOWS ? LAST_WIN_NR : LAST_TAB_NR; |
1622 | } else { |
1623 | // there is no Vim command which uses '%' and |
1624 | // ADDR_WINDOWS or ADDR_TABS |
1625 | errormsg = (char_u *)_(e_invrange); |
1626 | goto doend; |
1627 | } |
1628 | break; |
1629 | case ADDR_TABS_RELATIVE: |
1630 | errormsg = (char_u *)_(e_invrange); |
1631 | goto doend; |
1632 | break; |
1633 | case ADDR_ARGUMENTS: |
1634 | if (ARGCOUNT == 0) { |
1635 | ea.line1 = ea.line2 = 0; |
1636 | } else { |
1637 | ea.line1 = 1; |
1638 | ea.line2 = ARGCOUNT; |
1639 | } |
1640 | break; |
1641 | case ADDR_QUICKFIX: |
1642 | ea.line1 = 1; |
1643 | ea.line2 = qf_get_size(&ea); |
1644 | if (ea.line2 == 0) { |
1645 | ea.line2 = 1; |
1646 | } |
1647 | break; |
1648 | } |
1649 | ++ea.addr_count; |
1650 | } |
1651 | /* '*' - visual area */ |
1652 | else if (*ea.cmd == '*') { |
1653 | pos_T *fp; |
1654 | |
1655 | if (ea.addr_type != ADDR_LINES) { |
1656 | errormsg = (char_u *)_(e_invrange); |
1657 | goto doend; |
1658 | } |
1659 | |
1660 | ++ea.cmd; |
1661 | if (!ea.skip) { |
1662 | fp = getmark('<', FALSE); |
1663 | if (check_mark(fp) == FAIL) |
1664 | goto doend; |
1665 | ea.line1 = fp->lnum; |
1666 | fp = getmark('>', FALSE); |
1667 | if (check_mark(fp) == FAIL) |
1668 | goto doend; |
1669 | ea.line2 = fp->lnum; |
1670 | ++ea.addr_count; |
1671 | } |
1672 | } |
1673 | } else |
1674 | ea.line2 = lnum; |
1675 | ea.addr_count++; |
1676 | |
1677 | if (*ea.cmd == ';') { |
1678 | if (!ea.skip) { |
1679 | curwin->w_cursor.lnum = ea.line2; |
1680 | // don't leave the cursor on an illegal line or column |
1681 | check_cursor(); |
1682 | } |
1683 | } else if (*ea.cmd != ',') { |
1684 | break; |
1685 | } |
1686 | ea.cmd++; |
1687 | } |
1688 | |
1689 | /* One address given: set start and end lines */ |
1690 | if (ea.addr_count == 1) { |
1691 | ea.line1 = ea.line2; |
1692 | /* ... but only implicit: really no address given */ |
1693 | if (lnum == MAXLNUM) |
1694 | ea.addr_count = 0; |
1695 | } |
1696 | |
1697 | /* |
1698 | * 5. Parse the command. |
1699 | */ |
1700 | |
1701 | /* |
1702 | * Skip ':' and any white space |
1703 | */ |
1704 | ea.cmd = skipwhite(ea.cmd); |
1705 | while (*ea.cmd == ':') |
1706 | ea.cmd = skipwhite(ea.cmd + 1); |
1707 | |
1708 | /* |
1709 | * If we got a line, but no command, then go to the line. |
1710 | * If we find a '|' or '\n' we set ea.nextcmd. |
1711 | */ |
1712 | if (*ea.cmd == NUL || *ea.cmd == '"' |
1713 | || (ea.nextcmd = check_nextcmd(ea.cmd)) != NULL) { |
1714 | // strange vi behaviour: |
1715 | // ":3" jumps to line 3 |
1716 | // ":3|..." prints line 3 |
1717 | // ":|" prints current line |
1718 | if (ea.skip) { // skip this if inside :if |
1719 | goto doend; |
1720 | } |
1721 | if (*ea.cmd == '|' || (exmode_active && ea.line1 != ea.line2)) { |
1722 | ea.cmdidx = CMD_print; |
1723 | ea.argt = RANGE | COUNT | TRLBAR; |
1724 | if ((errormsg = invalid_range(&ea)) == NULL) { |
1725 | correct_range(&ea); |
1726 | ex_print(&ea); |
1727 | } |
1728 | } else if (ea.addr_count != 0) { |
1729 | if (ea.line2 > curbuf->b_ml.ml_line_count) { |
1730 | ea.line2 = curbuf->b_ml.ml_line_count; |
1731 | } |
1732 | |
1733 | if (ea.line2 < 0) |
1734 | errormsg = (char_u *)_(e_invrange); |
1735 | else { |
1736 | if (ea.line2 == 0) |
1737 | curwin->w_cursor.lnum = 1; |
1738 | else |
1739 | curwin->w_cursor.lnum = ea.line2; |
1740 | beginline(BL_SOL | BL_FIX); |
1741 | } |
1742 | } |
1743 | goto doend; |
1744 | } |
1745 | |
1746 | // If this looks like an undefined user command and there are CmdUndefined |
1747 | // autocommands defined, trigger the matching autocommands. |
1748 | if (p != NULL && ea.cmdidx == CMD_SIZE && !ea.skip |
1749 | && ASCII_ISUPPER(*ea.cmd) |
1750 | && has_event(EVENT_CMDUNDEFINED)) { |
1751 | p = ea.cmd; |
1752 | while (ASCII_ISALNUM(*p)) { |
1753 | ++p; |
1754 | } |
1755 | p = vim_strnsave(ea.cmd, p - ea.cmd); |
1756 | int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, TRUE, NULL); |
1757 | xfree(p); |
1758 | // If the autocommands did something and didn't cause an error, try |
1759 | // finding the command again. |
1760 | p = (ret && !aborting()) ? find_command(&ea, NULL) : ea.cmd; |
1761 | } |
1762 | |
1763 | if (p == NULL) { |
1764 | if (!ea.skip) |
1765 | errormsg = (char_u *)_("E464: Ambiguous use of user-defined command" ); |
1766 | goto doend; |
1767 | } |
1768 | // Check for wrong commands. |
1769 | if (ea.cmdidx == CMD_SIZE) { |
1770 | if (!ea.skip) { |
1771 | STRCPY(IObuff, _("E492: Not an editor command" )); |
1772 | if (!(flags & DOCMD_VERBOSE)) { |
1773 | // If the modifier was parsed OK the error must be in the following |
1774 | // command |
1775 | if (after_modifier != NULL) { |
1776 | append_command(after_modifier); |
1777 | } else { |
1778 | append_command(*cmdlinep); |
1779 | } |
1780 | } |
1781 | errormsg = IObuff; |
1782 | did_emsg_syntax = TRUE; |
1783 | } |
1784 | goto doend; |
1785 | } |
1786 | |
1787 | // set when Not Implemented |
1788 | const int ni = !IS_USER_CMDIDX(ea.cmdidx) |
1789 | && (cmdnames[ea.cmdidx].cmd_func == ex_ni |
1790 | || cmdnames[ea.cmdidx].cmd_func == ex_script_ni); |
1791 | |
1792 | |
1793 | // Forced commands. |
1794 | if (*p == '!' && ea.cmdidx != CMD_substitute |
1795 | && ea.cmdidx != CMD_smagic && ea.cmdidx != CMD_snomagic) { |
1796 | p++; |
1797 | ea.forceit = true; |
1798 | } else { |
1799 | ea.forceit = false; |
1800 | } |
1801 | |
1802 | /* |
1803 | * 6. Parse arguments. |
1804 | */ |
1805 | if (!IS_USER_CMDIDX(ea.cmdidx)) { |
1806 | ea.argt = cmdnames[(int)ea.cmdidx].cmd_argt; |
1807 | } |
1808 | |
1809 | if (!ea.skip) { |
1810 | if (sandbox != 0 && !(ea.argt & SBOXOK)) { |
1811 | /* Command not allowed in sandbox. */ |
1812 | errormsg = (char_u *)_(e_sandbox); |
1813 | goto doend; |
1814 | } |
1815 | if (!MODIFIABLE(curbuf) && (ea.argt & MODIFY) |
1816 | // allow :put in terminals |
1817 | && (!curbuf->terminal || ea.cmdidx != CMD_put)) { |
1818 | /* Command not allowed in non-'modifiable' buffer */ |
1819 | errormsg = (char_u *)_(e_modifiable); |
1820 | goto doend; |
1821 | } |
1822 | |
1823 | if (text_locked() && !(ea.argt & CMDWIN) |
1824 | && !IS_USER_CMDIDX(ea.cmdidx)) { |
1825 | // Command not allowed when editing the command line. |
1826 | errormsg = (char_u *)_(get_text_locked_msg()); |
1827 | goto doend; |
1828 | } |
1829 | |
1830 | // Disallow editing another buffer when "curbuf_lock" is set. |
1831 | // Do allow ":checktime" (it is postponed). |
1832 | // Do allow ":edit" (check for an argument later). |
1833 | // Do allow ":file" with no arguments (check for an argument later). |
1834 | if (!(ea.argt & CMDWIN) |
1835 | && ea.cmdidx != CMD_checktime |
1836 | && ea.cmdidx != CMD_edit |
1837 | && ea.cmdidx != CMD_file |
1838 | && !IS_USER_CMDIDX(ea.cmdidx) |
1839 | && curbuf_locked()) { |
1840 | goto doend; |
1841 | } |
1842 | |
1843 | if (!ni && !(ea.argt & RANGE) && ea.addr_count > 0) { |
1844 | /* no range allowed */ |
1845 | errormsg = (char_u *)_(e_norange); |
1846 | goto doend; |
1847 | } |
1848 | } |
1849 | |
1850 | if (!ni && !(ea.argt & BANG) && ea.forceit) { /* no <!> allowed */ |
1851 | errormsg = (char_u *)_(e_nobang); |
1852 | goto doend; |
1853 | } |
1854 | |
1855 | /* |
1856 | * Don't complain about the range if it is not used |
1857 | * (could happen if line_count is accidentally set to 0). |
1858 | */ |
1859 | if (!ea.skip && !ni) { |
1860 | /* |
1861 | * If the range is backwards, ask for confirmation and, if given, swap |
1862 | * ea.line1 & ea.line2 so it's forwards again. |
1863 | * When global command is busy, don't ask, will fail below. |
1864 | */ |
1865 | if (!global_busy && ea.line1 > ea.line2) { |
1866 | if (msg_silent == 0) { |
1867 | if ((flags & DOCMD_VERBOSE) || exmode_active) { |
1868 | errormsg = (char_u *)_("E493: Backwards range given" ); |
1869 | goto doend; |
1870 | } |
1871 | if (ask_yesno(_("Backwards range given, OK to swap" ), false) != 'y') { |
1872 | goto doend; |
1873 | } |
1874 | } |
1875 | lnum = ea.line1; |
1876 | ea.line1 = ea.line2; |
1877 | ea.line2 = lnum; |
1878 | } |
1879 | if ((errormsg = invalid_range(&ea)) != NULL) |
1880 | goto doend; |
1881 | } |
1882 | |
1883 | if ((ea.argt & NOTADR) && ea.addr_count == 0) /* default is 1, not cursor */ |
1884 | ea.line2 = 1; |
1885 | |
1886 | correct_range(&ea); |
1887 | |
1888 | if (((ea.argt & WHOLEFOLD) || ea.addr_count >= 2) && !global_busy |
1889 | && ea.addr_type == ADDR_LINES) { |
1890 | // Put the first line at the start of a closed fold, put the last line |
1891 | // at the end of a closed fold. |
1892 | (void)hasFolding(ea.line1, &ea.line1, NULL); |
1893 | (void)hasFolding(ea.line2, NULL, &ea.line2); |
1894 | } |
1895 | |
1896 | /* |
1897 | * For the ":make" and ":grep" commands we insert the 'makeprg'/'grepprg' |
1898 | * option here, so things like % get expanded. |
1899 | */ |
1900 | p = replace_makeprg(&ea, p, cmdlinep); |
1901 | if (p == NULL) |
1902 | goto doend; |
1903 | |
1904 | /* |
1905 | * Skip to start of argument. |
1906 | * Don't do this for the ":!" command, because ":!! -l" needs the space. |
1907 | */ |
1908 | if (ea.cmdidx == CMD_bang) |
1909 | ea.arg = p; |
1910 | else |
1911 | ea.arg = skipwhite(p); |
1912 | |
1913 | // ":file" cannot be run with an argument when "curbuf_lock" is set |
1914 | if (ea.cmdidx == CMD_file && *ea.arg != NUL && curbuf_locked()) { |
1915 | goto doend; |
1916 | } |
1917 | |
1918 | /* |
1919 | * Check for "++opt=val" argument. |
1920 | * Must be first, allow ":w ++enc=utf8 !cmd" |
1921 | */ |
1922 | if (ea.argt & ARGOPT) |
1923 | while (ea.arg[0] == '+' && ea.arg[1] == '+') |
1924 | if (getargopt(&ea) == FAIL && !ni) { |
1925 | errormsg = (char_u *)_(e_invarg); |
1926 | goto doend; |
1927 | } |
1928 | |
1929 | if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) { |
1930 | if (*ea.arg == '>') { /* append */ |
1931 | if (*++ea.arg != '>') { /* typed wrong */ |
1932 | errormsg = (char_u *)_("E494: Use w or w>>" ); |
1933 | goto doend; |
1934 | } |
1935 | ea.arg = skipwhite(ea.arg + 1); |
1936 | ea.append = TRUE; |
1937 | } else if (*ea.arg == '!' && ea.cmdidx == CMD_write) { /* :w !filter */ |
1938 | ++ea.arg; |
1939 | ea.usefilter = TRUE; |
1940 | } |
1941 | } |
1942 | |
1943 | if (ea.cmdidx == CMD_read) { |
1944 | if (ea.forceit) { |
1945 | ea.usefilter = TRUE; /* :r! filter if ea.forceit */ |
1946 | ea.forceit = FALSE; |
1947 | } else if (*ea.arg == '!') { /* :r !filter */ |
1948 | ++ea.arg; |
1949 | ea.usefilter = TRUE; |
1950 | } |
1951 | } |
1952 | |
1953 | if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { |
1954 | ea.amount = 1; |
1955 | while (*ea.arg == *ea.cmd) { /* count number of '>' or '<' */ |
1956 | ++ea.arg; |
1957 | ++ea.amount; |
1958 | } |
1959 | ea.arg = skipwhite(ea.arg); |
1960 | } |
1961 | |
1962 | /* |
1963 | * Check for "+command" argument, before checking for next command. |
1964 | * Don't do this for ":read !cmd" and ":write !cmd". |
1965 | */ |
1966 | if ((ea.argt & EDITCMD) && !ea.usefilter) |
1967 | ea.do_ecmd_cmd = getargcmd(&ea.arg); |
1968 | |
1969 | /* |
1970 | * Check for '|' to separate commands and '"' to start comments. |
1971 | * Don't do this for ":read !cmd" and ":write !cmd". |
1972 | */ |
1973 | if ((ea.argt & TRLBAR) && !ea.usefilter) { |
1974 | separate_nextcmd(&ea); |
1975 | } else if (ea.cmdidx == CMD_bang |
1976 | || ea.cmdidx == CMD_terminal |
1977 | || ea.cmdidx == CMD_global |
1978 | || ea.cmdidx == CMD_vglobal |
1979 | || ea.usefilter) { |
1980 | // Check for <newline> to end a shell command. |
1981 | // Also do this for ":read !cmd", ":write !cmd" and ":global". |
1982 | // Any others? |
1983 | for (p = ea.arg; *p; p++) { |
1984 | // Remove one backslash before a newline, so that it's possible to |
1985 | // pass a newline to the shell and also a newline that is preceded |
1986 | // with a backslash. This makes it impossible to end a shell |
1987 | // command in a backslash, but that doesn't appear useful. |
1988 | // Halving the number of backslashes is incompatible with previous |
1989 | // versions. |
1990 | if (*p == '\\' && p[1] == '\n') { |
1991 | STRMOVE(p, p + 1); |
1992 | } else if (*p == '\n') { |
1993 | ea.nextcmd = p + 1; |
1994 | *p = NUL; |
1995 | break; |
1996 | } |
1997 | } |
1998 | } |
1999 | |
2000 | if ((ea.argt & DFLALL) && ea.addr_count == 0) { |
2001 | buf_T *buf; |
2002 | |
2003 | ea.line1 = 1; |
2004 | switch (ea.addr_type) { |
2005 | case ADDR_LINES: |
2006 | ea.line2 = curbuf->b_ml.ml_line_count; |
2007 | break; |
2008 | case ADDR_LOADED_BUFFERS: |
2009 | buf = firstbuf; |
2010 | while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) { |
2011 | buf = buf->b_next; |
2012 | } |
2013 | ea.line1 = buf->b_fnum; |
2014 | buf = lastbuf; |
2015 | while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) { |
2016 | buf = buf->b_prev; |
2017 | } |
2018 | ea.line2 = buf->b_fnum; |
2019 | break; |
2020 | case ADDR_BUFFERS: |
2021 | ea.line1 = firstbuf->b_fnum; |
2022 | ea.line2 = lastbuf->b_fnum; |
2023 | break; |
2024 | case ADDR_WINDOWS: |
2025 | ea.line2 = LAST_WIN_NR; |
2026 | break; |
2027 | case ADDR_TABS: |
2028 | ea.line2 = LAST_TAB_NR; |
2029 | break; |
2030 | case ADDR_TABS_RELATIVE: |
2031 | ea.line2 = 1; |
2032 | break; |
2033 | case ADDR_ARGUMENTS: |
2034 | if (ARGCOUNT == 0) { |
2035 | ea.line1 = ea.line2 = 0; |
2036 | } else { |
2037 | ea.line2 = ARGCOUNT; |
2038 | } |
2039 | break; |
2040 | case ADDR_QUICKFIX: |
2041 | ea.line2 = qf_get_size(&ea); |
2042 | if (ea.line2 == 0) { |
2043 | ea.line2 = 1; |
2044 | } |
2045 | break; |
2046 | } |
2047 | } |
2048 | |
2049 | /* accept numbered register only when no count allowed (:put) */ |
2050 | if ((ea.argt & REGSTR) |
2051 | && *ea.arg != NUL |
2052 | /* Do not allow register = for user commands */ |
2053 | && (!IS_USER_CMDIDX(ea.cmdidx) || *ea.arg != '=') |
2054 | && !((ea.argt & COUNT) && ascii_isdigit(*ea.arg))) { |
2055 | if (valid_yank_reg(*ea.arg, (ea.cmdidx != CMD_put |
2056 | && !IS_USER_CMDIDX(ea.cmdidx)))) { |
2057 | ea.regname = *ea.arg++; |
2058 | /* for '=' register: accept the rest of the line as an expression */ |
2059 | if (ea.arg[-1] == '=' && ea.arg[0] != NUL) { |
2060 | set_expr_line(vim_strsave(ea.arg)); |
2061 | ea.arg += STRLEN(ea.arg); |
2062 | } |
2063 | ea.arg = skipwhite(ea.arg); |
2064 | } |
2065 | } |
2066 | |
2067 | // |
2068 | // Check for a count. When accepting a BUFNAME, don't use "123foo" as a |
2069 | // count, it's a buffer name. |
2070 | /// |
2071 | if ((ea.argt & COUNT) && ascii_isdigit(*ea.arg) |
2072 | && (!(ea.argt & BUFNAME) || *(p = skipdigits(ea.arg)) == NUL |
2073 | || ascii_iswhite(*p))) { |
2074 | n = getdigits_long(&ea.arg, false, -1); |
2075 | ea.arg = skipwhite(ea.arg); |
2076 | if (n <= 0 && !ni && (ea.argt & ZEROR) == 0) { |
2077 | errormsg = (char_u *)_(e_zerocount); |
2078 | goto doend; |
2079 | } |
2080 | if (ea.argt & NOTADR) { /* e.g. :buffer 2, :sleep 3 */ |
2081 | ea.line2 = n; |
2082 | if (ea.addr_count == 0) |
2083 | ea.addr_count = 1; |
2084 | } else { |
2085 | ea.line1 = ea.line2; |
2086 | ea.line2 += n - 1; |
2087 | ++ea.addr_count; |
2088 | // Be vi compatible: no error message for out of range. |
2089 | if (ea.addr_type == ADDR_LINES |
2090 | && ea.line2 > curbuf->b_ml.ml_line_count) { |
2091 | ea.line2 = curbuf->b_ml.ml_line_count; |
2092 | } |
2093 | } |
2094 | } |
2095 | |
2096 | /* |
2097 | * Check for flags: 'l', 'p' and '#'. |
2098 | */ |
2099 | if (ea.argt & EXFLAGS) |
2100 | get_flags(&ea); |
2101 | /* no arguments allowed */ |
2102 | if (!ni && !(ea.argt & EXTRA) && *ea.arg != NUL |
2103 | && *ea.arg != '"' && (*ea.arg != '|' || (ea.argt & TRLBAR) == 0)) { |
2104 | errormsg = (char_u *)_(e_trailing); |
2105 | goto doend; |
2106 | } |
2107 | |
2108 | if (!ni && (ea.argt & NEEDARG) && *ea.arg == NUL) { |
2109 | errormsg = (char_u *)_(e_argreq); |
2110 | goto doend; |
2111 | } |
2112 | |
2113 | /* |
2114 | * Skip the command when it's not going to be executed. |
2115 | * The commands like :if, :endif, etc. always need to be executed. |
2116 | * Also make an exception for commands that handle a trailing command |
2117 | * themselves. |
2118 | */ |
2119 | if (ea.skip) { |
2120 | switch (ea.cmdidx) { |
2121 | /* commands that need evaluation */ |
2122 | case CMD_while: |
2123 | case CMD_endwhile: |
2124 | case CMD_for: |
2125 | case CMD_endfor: |
2126 | case CMD_if: |
2127 | case CMD_elseif: |
2128 | case CMD_else: |
2129 | case CMD_endif: |
2130 | case CMD_try: |
2131 | case CMD_catch: |
2132 | case CMD_finally: |
2133 | case CMD_endtry: |
2134 | case CMD_function: |
2135 | break; |
2136 | |
2137 | /* Commands that handle '|' themselves. Check: A command should |
2138 | * either have the TRLBAR flag, appear in this list or appear in |
2139 | * the list at ":help :bar". */ |
2140 | case CMD_aboveleft: |
2141 | case CMD_and: |
2142 | case CMD_belowright: |
2143 | case CMD_botright: |
2144 | case CMD_browse: |
2145 | case CMD_call: |
2146 | case CMD_confirm: |
2147 | case CMD_delfunction: |
2148 | case CMD_djump: |
2149 | case CMD_dlist: |
2150 | case CMD_dsearch: |
2151 | case CMD_dsplit: |
2152 | case CMD_echo: |
2153 | case CMD_echoerr: |
2154 | case CMD_echomsg: |
2155 | case CMD_echon: |
2156 | case CMD_execute: |
2157 | case CMD_filter: |
2158 | case CMD_help: |
2159 | case CMD_hide: |
2160 | case CMD_ijump: |
2161 | case CMD_ilist: |
2162 | case CMD_isearch: |
2163 | case CMD_isplit: |
2164 | case CMD_keepalt: |
2165 | case CMD_keepjumps: |
2166 | case CMD_keepmarks: |
2167 | case CMD_keeppatterns: |
2168 | case CMD_leftabove: |
2169 | case CMD_let: |
2170 | case CMD_lockmarks: |
2171 | case CMD_lua: |
2172 | case CMD_match: |
2173 | case CMD_mzscheme: |
2174 | case CMD_noautocmd: |
2175 | case CMD_noswapfile: |
2176 | case CMD_perl: |
2177 | case CMD_psearch: |
2178 | case CMD_python: |
2179 | case CMD_py3: |
2180 | case CMD_python3: |
2181 | case CMD_pythonx: |
2182 | case CMD_pyx: |
2183 | case CMD_pyxdo: |
2184 | case CMD_pyxfile: |
2185 | case CMD_return: |
2186 | case CMD_rightbelow: |
2187 | case CMD_ruby: |
2188 | case CMD_silent: |
2189 | case CMD_smagic: |
2190 | case CMD_snomagic: |
2191 | case CMD_substitute: |
2192 | case CMD_syntax: |
2193 | case CMD_tab: |
2194 | case CMD_tcl: |
2195 | case CMD_throw: |
2196 | case CMD_tilde: |
2197 | case CMD_topleft: |
2198 | case CMD_unlet: |
2199 | case CMD_verbose: |
2200 | case CMD_vertical: |
2201 | case CMD_wincmd: |
2202 | break; |
2203 | |
2204 | default: |
2205 | goto doend; |
2206 | } |
2207 | } |
2208 | |
2209 | if (ea.argt & XFILE) { |
2210 | if (expand_filename(&ea, cmdlinep, &errormsg) == FAIL) |
2211 | goto doend; |
2212 | } |
2213 | |
2214 | /* |
2215 | * Accept buffer name. Cannot be used at the same time with a buffer |
2216 | * number. Don't do this for a user command. |
2217 | */ |
2218 | if ((ea.argt & BUFNAME) && *ea.arg != NUL && ea.addr_count == 0 |
2219 | && !IS_USER_CMDIDX(ea.cmdidx) |
2220 | ) { |
2221 | /* |
2222 | * :bdelete, :bwipeout and :bunload take several arguments, separated |
2223 | * by spaces: find next space (skipping over escaped characters). |
2224 | * The others take one argument: ignore trailing spaces. |
2225 | */ |
2226 | if (ea.cmdidx == CMD_bdelete || ea.cmdidx == CMD_bwipeout |
2227 | || ea.cmdidx == CMD_bunload) |
2228 | p = skiptowhite_esc(ea.arg); |
2229 | else { |
2230 | p = ea.arg + STRLEN(ea.arg); |
2231 | while (p > ea.arg && ascii_iswhite(p[-1])) |
2232 | --p; |
2233 | } |
2234 | ea.line2 = buflist_findpat(ea.arg, p, (ea.argt & BUFUNL) != 0, |
2235 | FALSE, FALSE); |
2236 | if (ea.line2 < 0) /* failed */ |
2237 | goto doend; |
2238 | ea.addr_count = 1; |
2239 | ea.arg = skipwhite(p); |
2240 | } |
2241 | |
2242 | // The :try command saves the emsg_silent flag, reset it here when |
2243 | // ":silent! try" was used, it should only apply to :try itself. |
2244 | if (ea.cmdidx == CMD_try && did_esilent > 0) { |
2245 | emsg_silent -= did_esilent; |
2246 | if (emsg_silent < 0) { |
2247 | emsg_silent = 0; |
2248 | } |
2249 | did_esilent = 0; |
2250 | } |
2251 | |
2252 | // 7. Execute the command. |
2253 | // |
2254 | // The "ea" structure holds the arguments that can be used. |
2255 | ea.cmdlinep = cmdlinep; |
2256 | ea.getline = fgetline; |
2257 | ea.cookie = cookie; |
2258 | ea.cstack = cstack; |
2259 | |
2260 | if (IS_USER_CMDIDX(ea.cmdidx)) { |
2261 | /* |
2262 | * Execute a user-defined command. |
2263 | */ |
2264 | do_ucmd(&ea); |
2265 | } else { |
2266 | /* |
2267 | * Call the function to execute the command. |
2268 | */ |
2269 | ea.errmsg = NULL; |
2270 | (cmdnames[ea.cmdidx].cmd_func)(&ea); |
2271 | if (ea.errmsg != NULL) |
2272 | errormsg = (char_u *)_(ea.errmsg); |
2273 | } |
2274 | |
2275 | /* |
2276 | * If the command just executed called do_cmdline(), any throw or ":return" |
2277 | * or ":finish" encountered there must also check the cstack of the still |
2278 | * active do_cmdline() that called this do_one_cmd(). Rethrow an uncaught |
2279 | * exception, or reanimate a returned function or finished script file and |
2280 | * return or finish it again. |
2281 | */ |
2282 | if (need_rethrow) |
2283 | do_throw(cstack); |
2284 | else if (check_cstack) { |
2285 | if (source_finished(fgetline, cookie)) |
2286 | do_finish(&ea, TRUE); |
2287 | else if (getline_equal(fgetline, cookie, get_func_line) |
2288 | && current_func_returned()) |
2289 | do_return(&ea, TRUE, FALSE, NULL); |
2290 | } |
2291 | need_rethrow = check_cstack = FALSE; |
2292 | |
2293 | doend: |
2294 | // can happen with zero line number |
2295 | if (curwin->w_cursor.lnum == 0) { |
2296 | curwin->w_cursor.lnum = 1; |
2297 | curwin->w_cursor.col = 0; |
2298 | } |
2299 | |
2300 | if (errormsg != NULL && *errormsg != NUL && !did_emsg) { |
2301 | if (flags & DOCMD_VERBOSE) { |
2302 | if (errormsg != IObuff) { |
2303 | STRCPY(IObuff, errormsg); |
2304 | errormsg = IObuff; |
2305 | } |
2306 | append_command(*cmdlinep); |
2307 | } |
2308 | emsg(errormsg); |
2309 | } |
2310 | do_errthrow(cstack, |
2311 | (ea.cmdidx != CMD_SIZE && !IS_USER_CMDIDX(ea.cmdidx)) |
2312 | ? cmdnames[(int)ea.cmdidx].cmd_name |
2313 | : (char_u *)NULL); |
2314 | |
2315 | if (verbose_save >= 0) |
2316 | p_verbose = verbose_save; |
2317 | if (cmdmod.save_ei != NULL) { |
2318 | /* Restore 'eventignore' to the value before ":noautocmd". */ |
2319 | set_string_option_direct((char_u *)"ei" , -1, cmdmod.save_ei, |
2320 | OPT_FREE, SID_NONE); |
2321 | free_string_option(cmdmod.save_ei); |
2322 | } |
2323 | |
2324 | if (cmdmod.filter_regmatch.regprog != NULL) { |
2325 | vim_regfree(cmdmod.filter_regmatch.regprog); |
2326 | } |
2327 | |
2328 | cmdmod = save_cmdmod; |
2329 | reg_executing = save_reg_executing; |
2330 | |
2331 | if (save_msg_silent != -1) { |
2332 | /* messages could be enabled for a serious error, need to check if the |
2333 | * counters don't become negative */ |
2334 | if (!did_emsg || msg_silent > save_msg_silent) |
2335 | msg_silent = save_msg_silent; |
2336 | emsg_silent -= did_esilent; |
2337 | if (emsg_silent < 0) |
2338 | emsg_silent = 0; |
2339 | /* Restore msg_scroll, it's set by file I/O commands, even when no |
2340 | * message is actually displayed. */ |
2341 | msg_scroll = save_msg_scroll; |
2342 | |
2343 | /* "silent reg" or "silent echo x" inside "redir" leaves msg_col |
2344 | * somewhere in the line. Put it back in the first column. */ |
2345 | if (redirecting()) |
2346 | msg_col = 0; |
2347 | } |
2348 | |
2349 | if (did_sandbox) |
2350 | --sandbox; |
2351 | |
2352 | if (ea.nextcmd && *ea.nextcmd == NUL) /* not really a next command */ |
2353 | ea.nextcmd = NULL; |
2354 | |
2355 | --ex_nesting_level; |
2356 | |
2357 | return ea.nextcmd; |
2358 | } |
2359 | |
2360 | /* |
2361 | * Check for an Ex command with optional tail. |
2362 | * If there is a match advance "pp" to the argument and return TRUE. |
2363 | */ |
2364 | int |
2365 | checkforcmd( |
2366 | char_u **pp, // start of command |
2367 | char *cmd, // name of command |
2368 | int len // required length |
2369 | ) |
2370 | { |
2371 | int i; |
2372 | |
2373 | for (i = 0; cmd[i] != NUL; ++i) |
2374 | if (((char_u *)cmd)[i] != (*pp)[i]) |
2375 | break; |
2376 | if (i >= len && !isalpha((*pp)[i])) { |
2377 | *pp = skipwhite(*pp + i); |
2378 | return TRUE; |
2379 | } |
2380 | return FALSE; |
2381 | } |
2382 | |
2383 | /* |
2384 | * Append "cmd" to the error message in IObuff. |
2385 | * Takes care of limiting the length and handling 0xa0, which would be |
2386 | * invisible otherwise. |
2387 | */ |
2388 | static void append_command(char_u *cmd) |
2389 | { |
2390 | char_u *s = cmd; |
2391 | char_u *d; |
2392 | |
2393 | STRCAT(IObuff, ": " ); |
2394 | d = IObuff + STRLEN(IObuff); |
2395 | while (*s != NUL && d - IObuff < IOSIZE - 7) { |
2396 | if ( |
2397 | enc_utf8 ? (s[0] == 0xc2 && s[1] == 0xa0) : |
2398 | *s == 0xa0) { |
2399 | s += |
2400 | enc_utf8 ? 2 : |
2401 | 1; |
2402 | STRCPY(d, "<a0>" ); |
2403 | d += 4; |
2404 | } else |
2405 | MB_COPY_CHAR(s, d); |
2406 | } |
2407 | *d = NUL; |
2408 | } |
2409 | |
2410 | /* |
2411 | * Find an Ex command by its name, either built-in or user. |
2412 | * Start of the name can be found at eap->cmd. |
2413 | * Returns pointer to char after the command name. |
2414 | * "full" is set to TRUE if the whole command name matched. |
2415 | * Returns NULL for an ambiguous user command. |
2416 | */ |
2417 | static char_u *find_command(exarg_T *eap, int *full) |
2418 | { |
2419 | int len; |
2420 | char_u *p; |
2421 | int i; |
2422 | |
2423 | /* |
2424 | * Isolate the command and search for it in the command table. |
2425 | * Exceptions: |
2426 | * - the 'k' command can directly be followed by any character. |
2427 | * - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' |
2428 | * but :sre[wind] is another command, as are :scr[iptnames], |
2429 | * :scs[cope], :sim[alt], :sig[ns] and :sil[ent]. |
2430 | * - the "d" command can directly be followed by 'l' or 'p' flag. |
2431 | */ |
2432 | p = eap->cmd; |
2433 | if (*p == 'k') { |
2434 | eap->cmdidx = CMD_k; |
2435 | ++p; |
2436 | } else if (p[0] == 's' |
2437 | && ((p[1] == 'c' |
2438 | && (p[2] == NUL |
2439 | || (p[2] != 's' && p[2] != 'r' |
2440 | && (p[3] == NUL |
2441 | || (p[3] != 'i' && p[4] != 'p'))))) |
2442 | || p[1] == 'g' |
2443 | || (p[1] == 'i' && p[2] != 'm' && p[2] != 'l' && p[2] != 'g') |
2444 | || p[1] == 'I' |
2445 | || (p[1] == 'r' && p[2] != 'e'))) { |
2446 | eap->cmdidx = CMD_substitute; |
2447 | ++p; |
2448 | } else { |
2449 | while (ASCII_ISALPHA(*p)) |
2450 | ++p; |
2451 | /* for python 3.x support ":py3", ":python3", ":py3file", etc. */ |
2452 | if (eap->cmd[0] == 'p' && eap->cmd[1] == 'y') |
2453 | while (ASCII_ISALNUM(*p)) |
2454 | ++p; |
2455 | |
2456 | /* check for non-alpha command */ |
2457 | if (p == eap->cmd && vim_strchr((char_u *)"@!=><&~#" , *p) != NULL) |
2458 | ++p; |
2459 | len = (int)(p - eap->cmd); |
2460 | if (*eap->cmd == 'd' && (p[-1] == 'l' || p[-1] == 'p')) { |
2461 | /* Check for ":dl", ":dell", etc. to ":deletel": that's |
2462 | * :delete with the 'l' flag. Same for 'p'. */ |
2463 | for (i = 0; i < len; ++i) |
2464 | if (eap->cmd[i] != ((char_u *)"delete" )[i]) |
2465 | break; |
2466 | if (i == len - 1) { |
2467 | --len; |
2468 | if (p[-1] == 'l') |
2469 | eap->flags |= EXFLAG_LIST; |
2470 | else |
2471 | eap->flags |= EXFLAG_PRINT; |
2472 | } |
2473 | } |
2474 | |
2475 | if (ASCII_ISLOWER(eap->cmd[0])) { |
2476 | const int c1 = eap->cmd[0]; |
2477 | const int c2 = len == 1 ? NUL : eap->cmd[1]; |
2478 | |
2479 | if (command_count != (int)CMD_SIZE) { |
2480 | iemsg((char *)_("E943: Command table needs to be updated, run 'make'" )); |
2481 | getout(1); |
2482 | } |
2483 | |
2484 | // Use a precomputed index for fast look-up in cmdnames[] |
2485 | // taking into account the first 2 letters of eap->cmd. |
2486 | eap->cmdidx = cmdidxs1[CharOrdLow(c1)]; |
2487 | if (ASCII_ISLOWER(c2)) { |
2488 | eap->cmdidx += cmdidxs2[CharOrdLow(c1)][CharOrdLow(c2)]; |
2489 | } |
2490 | } else { |
2491 | eap->cmdidx = CMD_bang; |
2492 | } |
2493 | |
2494 | for (; (int)eap->cmdidx < (int)CMD_SIZE; |
2495 | eap->cmdidx = (cmdidx_T)((int)eap->cmdidx + 1)) |
2496 | if (STRNCMP(cmdnames[(int)eap->cmdidx].cmd_name, (char *)eap->cmd, |
2497 | (size_t)len) == 0) { |
2498 | if (full != NULL |
2499 | && cmdnames[(int)eap->cmdidx].cmd_name[len] == NUL) |
2500 | *full = TRUE; |
2501 | break; |
2502 | } |
2503 | |
2504 | // Look for a user defined command as a last resort. |
2505 | if ((eap->cmdidx == CMD_SIZE) |
2506 | && *eap->cmd >= 'A' && *eap->cmd <= 'Z') { |
2507 | /* User defined commands may contain digits. */ |
2508 | while (ASCII_ISALNUM(*p)) |
2509 | ++p; |
2510 | p = find_ucmd(eap, p, full, NULL, NULL); |
2511 | } |
2512 | if (p == eap->cmd) |
2513 | eap->cmdidx = CMD_SIZE; |
2514 | } |
2515 | |
2516 | return p; |
2517 | } |
2518 | |
2519 | /* |
2520 | * Search for a user command that matches "eap->cmd". |
2521 | * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx". |
2522 | * Return a pointer to just after the command. |
2523 | * Return NULL if there is no matching command. |
2524 | */ |
2525 | static char_u * |
2526 | find_ucmd ( |
2527 | exarg_T *eap, |
2528 | char_u *p, // end of the command (possibly including count) |
2529 | int *full, // set to TRUE for a full match |
2530 | expand_T *xp, // used for completion, NULL otherwise |
2531 | int *complp // completion flags or NULL |
2532 | ) |
2533 | { |
2534 | int len = (int)(p - eap->cmd); |
2535 | int j, k, matchlen = 0; |
2536 | ucmd_T *uc; |
2537 | int found = FALSE; |
2538 | int possible = FALSE; |
2539 | char_u *cp, *np; /* Point into typed cmd and test name */ |
2540 | garray_T *gap; |
2541 | int amb_local = FALSE; /* Found ambiguous buffer-local command, |
2542 | only full match global is accepted. */ |
2543 | |
2544 | /* |
2545 | * Look for buffer-local user commands first, then global ones. |
2546 | */ |
2547 | gap = &curbuf->b_ucmds; |
2548 | for (;; ) { |
2549 | for (j = 0; j < gap->ga_len; ++j) { |
2550 | uc = USER_CMD_GA(gap, j); |
2551 | cp = eap->cmd; |
2552 | np = uc->uc_name; |
2553 | k = 0; |
2554 | while (k < len && *np != NUL && *cp++ == *np++) |
2555 | k++; |
2556 | if (k == len || (*np == NUL && ascii_isdigit(eap->cmd[k]))) { |
2557 | /* If finding a second match, the command is ambiguous. But |
2558 | * not if a buffer-local command wasn't a full match and a |
2559 | * global command is a full match. */ |
2560 | if (k == len && found && *np != NUL) { |
2561 | if (gap == &ucmds) |
2562 | return NULL; |
2563 | amb_local = TRUE; |
2564 | } |
2565 | |
2566 | if (!found || (k == len && *np == NUL)) { |
2567 | /* If we matched up to a digit, then there could |
2568 | * be another command including the digit that we |
2569 | * should use instead. |
2570 | */ |
2571 | if (k == len) |
2572 | found = TRUE; |
2573 | else |
2574 | possible = TRUE; |
2575 | |
2576 | if (gap == &ucmds) |
2577 | eap->cmdidx = CMD_USER; |
2578 | else |
2579 | eap->cmdidx = CMD_USER_BUF; |
2580 | eap->argt = uc->uc_argt; |
2581 | eap->useridx = j; |
2582 | eap->addr_type = uc->uc_addr_type; |
2583 | |
2584 | if (complp != NULL) { |
2585 | *complp = uc->uc_compl; |
2586 | } |
2587 | if (xp != NULL) { |
2588 | xp->xp_arg = uc->uc_compl_arg; |
2589 | xp->xp_script_ctx = uc->uc_script_ctx; |
2590 | xp->xp_script_ctx.sc_lnum += sourcing_lnum; |
2591 | } |
2592 | /* Do not search for further abbreviations |
2593 | * if this is an exact match. */ |
2594 | matchlen = k; |
2595 | if (k == len && *np == NUL) { |
2596 | if (full != NULL) |
2597 | *full = TRUE; |
2598 | amb_local = FALSE; |
2599 | break; |
2600 | } |
2601 | } |
2602 | } |
2603 | } |
2604 | |
2605 | /* Stop if we found a full match or searched all. */ |
2606 | if (j < gap->ga_len || gap == &ucmds) |
2607 | break; |
2608 | gap = &ucmds; |
2609 | } |
2610 | |
2611 | /* Only found ambiguous matches. */ |
2612 | if (amb_local) { |
2613 | if (xp != NULL) |
2614 | xp->xp_context = EXPAND_UNSUCCESSFUL; |
2615 | return NULL; |
2616 | } |
2617 | |
2618 | /* The match we found may be followed immediately by a number. Move "p" |
2619 | * back to point to it. */ |
2620 | if (found || possible) |
2621 | return p + (matchlen - len); |
2622 | return p; |
2623 | } |
2624 | |
2625 | static struct cmdmod { |
2626 | char *name; |
2627 | int minlen; |
2628 | int has_count; /* :123verbose :3tab */ |
2629 | } cmdmods[] = { |
2630 | { "aboveleft" , 3, false }, |
2631 | { "belowright" , 3, false }, |
2632 | { "botright" , 2, false }, |
2633 | { "browse" , 3, false }, |
2634 | { "confirm" , 4, false }, |
2635 | { "filter" , 4, false }, |
2636 | { "hide" , 3, false }, |
2637 | { "keepalt" , 5, false }, |
2638 | { "keepjumps" , 5, false }, |
2639 | { "keepmarks" , 3, false }, |
2640 | { "keeppatterns" , 5, false }, |
2641 | { "leftabove" , 5, false }, |
2642 | { "lockmarks" , 3, false }, |
2643 | { "noautocmd" , 3, false }, |
2644 | { "noswapfile" , 3, false }, |
2645 | { "rightbelow" , 6, false }, |
2646 | { "sandbox" , 3, false }, |
2647 | { "silent" , 3, false }, |
2648 | { "tab" , 3, true }, |
2649 | { "topleft" , 2, false }, |
2650 | { "unsilent" , 3, false }, |
2651 | { "verbose" , 4, true }, |
2652 | { "vertical" , 4, false }, |
2653 | }; |
2654 | |
2655 | /* |
2656 | * Return length of a command modifier (including optional count). |
2657 | * Return zero when it's not a modifier. |
2658 | */ |
2659 | int modifier_len(char_u *cmd) |
2660 | { |
2661 | int i, j; |
2662 | char_u *p = cmd; |
2663 | |
2664 | if (ascii_isdigit(*cmd)) |
2665 | p = skipwhite(skipdigits(cmd)); |
2666 | for (i = 0; i < (int)ARRAY_SIZE(cmdmods); ++i) { |
2667 | for (j = 0; p[j] != NUL; ++j) |
2668 | if (p[j] != cmdmods[i].name[j]) |
2669 | break; |
2670 | if (!ASCII_ISALPHA(p[j]) && j >= cmdmods[i].minlen |
2671 | && (p == cmd || cmdmods[i].has_count)) |
2672 | return j + (int)(p - cmd); |
2673 | } |
2674 | return 0; |
2675 | } |
2676 | |
2677 | /* |
2678 | * Return > 0 if an Ex command "name" exists. |
2679 | * Return 2 if there is an exact match. |
2680 | * Return 3 if there is an ambiguous match. |
2681 | */ |
2682 | int cmd_exists(const char *const name) |
2683 | { |
2684 | exarg_T ea; |
2685 | char_u *p; |
2686 | |
2687 | // Check command modifiers. |
2688 | for (int i = 0; i < (int)ARRAY_SIZE(cmdmods); i++) { |
2689 | int j; |
2690 | for (j = 0; name[j] != NUL; j++) { |
2691 | if (name[j] != (char)cmdmods[i].name[j]) { |
2692 | break; |
2693 | } |
2694 | } |
2695 | if (name[j] == NUL && j >= cmdmods[i].minlen) { |
2696 | return cmdmods[i].name[j] == NUL ? 2 : 1; |
2697 | } |
2698 | } |
2699 | |
2700 | /* Check built-in commands and user defined commands. |
2701 | * For ":2match" and ":3match" we need to skip the number. */ |
2702 | ea.cmd = (char_u *)((*name == '2' || *name == '3') ? name + 1 : name); |
2703 | ea.cmdidx = (cmdidx_T)0; |
2704 | int full = false; |
2705 | p = find_command(&ea, &full); |
2706 | if (p == NULL) |
2707 | return 3; |
2708 | if (ascii_isdigit(*name) && ea.cmdidx != CMD_match) |
2709 | return 0; |
2710 | if (*skipwhite(p) != NUL) |
2711 | return 0; /* trailing garbage */ |
2712 | return ea.cmdidx == CMD_SIZE ? 0 : (full ? 2 : 1); |
2713 | } |
2714 | |
2715 | /* |
2716 | * This is all pretty much copied from do_one_cmd(), with all the extra stuff |
2717 | * we don't need/want deleted. Maybe this could be done better if we didn't |
2718 | * repeat all this stuff. The only problem is that they may not stay |
2719 | * perfectly compatible with each other, but then the command line syntax |
2720 | * probably won't change that much -- webb. |
2721 | */ |
2722 | const char * set_one_cmd_context( |
2723 | expand_T *xp, |
2724 | const char *buff // buffer for command string |
2725 | ) |
2726 | { |
2727 | size_t len = 0; |
2728 | exarg_T ea; |
2729 | int context = EXPAND_NOTHING; |
2730 | bool forceit = false; |
2731 | bool usefilter = false; // Filter instead of file name. |
2732 | |
2733 | ExpandInit(xp); |
2734 | xp->xp_pattern = (char_u *)buff; |
2735 | xp->xp_context = EXPAND_COMMANDS; // Default until we get past command |
2736 | ea.argt = 0; |
2737 | |
2738 | // 2. skip comment lines and leading space, colons or bars |
2739 | const char *cmd; |
2740 | for (cmd = buff; vim_strchr((const char_u *)" \t:|" , *cmd) != NULL; cmd++) { |
2741 | } |
2742 | xp->xp_pattern = (char_u *)cmd; |
2743 | |
2744 | if (*cmd == NUL) |
2745 | return NULL; |
2746 | if (*cmd == '"') { /* ignore comment lines */ |
2747 | xp->xp_context = EXPAND_NOTHING; |
2748 | return NULL; |
2749 | } |
2750 | |
2751 | /* |
2752 | * 3. parse a range specifier of the form: addr [,addr] [;addr] .. |
2753 | */ |
2754 | cmd = (const char *)skip_range((const char_u *)cmd, &xp->xp_context); |
2755 | |
2756 | /* |
2757 | * 4. parse command |
2758 | */ |
2759 | xp->xp_pattern = (char_u *)cmd; |
2760 | if (*cmd == NUL) { |
2761 | return NULL; |
2762 | } |
2763 | if (*cmd == '"') { |
2764 | xp->xp_context = EXPAND_NOTHING; |
2765 | return NULL; |
2766 | } |
2767 | |
2768 | if (*cmd == '|' || *cmd == '\n') |
2769 | return cmd + 1; /* There's another command */ |
2770 | |
2771 | /* |
2772 | * Isolate the command and search for it in the command table. |
2773 | * Exceptions: |
2774 | * - the 'k' command can directly be followed by any character, but |
2775 | * do accept "keepmarks", "keepalt" and "keepjumps". |
2776 | * - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' |
2777 | */ |
2778 | const char *p; |
2779 | if (*cmd == 'k' && cmd[1] != 'e') { |
2780 | ea.cmdidx = CMD_k; |
2781 | p = cmd + 1; |
2782 | } else { |
2783 | p = cmd; |
2784 | while (ASCII_ISALPHA(*p) || *p == '*') { // Allow * wild card |
2785 | p++; |
2786 | } |
2787 | // a user command may contain digits |
2788 | if (ASCII_ISUPPER(cmd[0])) { |
2789 | while (ASCII_ISALNUM(*p) || *p == '*') { |
2790 | p++; |
2791 | } |
2792 | } |
2793 | // for python 3.x: ":py3*" commands completion |
2794 | if (cmd[0] == 'p' && cmd[1] == 'y' && p == cmd + 2 && *p == '3') { |
2795 | p++; |
2796 | while (ASCII_ISALPHA(*p) || *p == '*') { |
2797 | p++; |
2798 | } |
2799 | } |
2800 | // check for non-alpha command |
2801 | if (p == cmd && vim_strchr((const char_u *)"@*!=><&~#" , *p) != NULL) { |
2802 | p++; |
2803 | } |
2804 | len = (size_t)(p - cmd); |
2805 | |
2806 | if (len == 0) { |
2807 | xp->xp_context = EXPAND_UNSUCCESSFUL; |
2808 | return NULL; |
2809 | } |
2810 | for (ea.cmdidx = (cmdidx_T)0; (int)ea.cmdidx < (int)CMD_SIZE; |
2811 | ea.cmdidx = (cmdidx_T)((int)ea.cmdidx + 1)) { |
2812 | if (STRNCMP(cmdnames[(int)ea.cmdidx].cmd_name, cmd, len) == 0) { |
2813 | break; |
2814 | } |
2815 | } |
2816 | |
2817 | if (cmd[0] >= 'A' && cmd[0] <= 'Z') { |
2818 | while (ASCII_ISALNUM(*p) || *p == '*') { // Allow * wild card |
2819 | p++; |
2820 | } |
2821 | } |
2822 | } |
2823 | |
2824 | /* |
2825 | * If the cursor is touching the command, and it ends in an alpha-numeric |
2826 | * character, complete the command name. |
2827 | */ |
2828 | if (*p == NUL && ASCII_ISALNUM(p[-1])) |
2829 | return NULL; |
2830 | |
2831 | if (ea.cmdidx == CMD_SIZE) { |
2832 | if (*cmd == 's' && vim_strchr((const char_u *)"cgriI" , cmd[1]) != NULL) { |
2833 | ea.cmdidx = CMD_substitute; |
2834 | p = cmd + 1; |
2835 | } else if (cmd[0] >= 'A' && cmd[0] <= 'Z') { |
2836 | ea.cmd = (char_u *)cmd; |
2837 | p = (const char *)find_ucmd(&ea, (char_u *)p, NULL, xp, &context); |
2838 | if (p == NULL) { |
2839 | ea.cmdidx = CMD_SIZE; // Ambiguous user command. |
2840 | } |
2841 | } |
2842 | } |
2843 | if (ea.cmdidx == CMD_SIZE) { |
2844 | /* Not still touching the command and it was an illegal one */ |
2845 | xp->xp_context = EXPAND_UNSUCCESSFUL; |
2846 | return NULL; |
2847 | } |
2848 | |
2849 | xp->xp_context = EXPAND_NOTHING; /* Default now that we're past command */ |
2850 | |
2851 | if (*p == '!') { // forced commands |
2852 | forceit = true; |
2853 | p++; |
2854 | } |
2855 | |
2856 | /* |
2857 | * 5. parse arguments |
2858 | */ |
2859 | if (!IS_USER_CMDIDX(ea.cmdidx)) { |
2860 | ea.argt = cmdnames[(int)ea.cmdidx].cmd_argt; |
2861 | } |
2862 | |
2863 | const char *arg = (const char *)skipwhite((const char_u *)p); |
2864 | |
2865 | if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) { |
2866 | if (*arg == '>') { // Append. |
2867 | if (*++arg == '>') { |
2868 | arg++; |
2869 | } |
2870 | arg = (const char *)skipwhite((const char_u *)arg); |
2871 | } else if (*arg == '!' && ea.cmdidx == CMD_write) { // :w !filter |
2872 | arg++; |
2873 | usefilter = true; |
2874 | } |
2875 | } |
2876 | |
2877 | if (ea.cmdidx == CMD_read) { |
2878 | usefilter = forceit; // :r! filter if forced |
2879 | if (*arg == '!') { // :r !filter |
2880 | arg++; |
2881 | usefilter = true; |
2882 | } |
2883 | } |
2884 | |
2885 | if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { |
2886 | while (*arg == *cmd) { // allow any number of '>' or '<' |
2887 | arg++; |
2888 | } |
2889 | arg = (const char *)skipwhite((const char_u *)arg); |
2890 | } |
2891 | |
2892 | /* Does command allow "+command"? */ |
2893 | if ((ea.argt & EDITCMD) && !usefilter && *arg == '+') { |
2894 | /* Check if we're in the +command */ |
2895 | p = arg + 1; |
2896 | arg = (const char *)skip_cmd_arg((char_u *)arg, false); |
2897 | |
2898 | /* Still touching the command after '+'? */ |
2899 | if (*arg == NUL) |
2900 | return p; |
2901 | |
2902 | // Skip space(s) after +command to get to the real argument. |
2903 | arg = (const char *)skipwhite((const char_u *)arg); |
2904 | } |
2905 | |
2906 | /* |
2907 | * Check for '|' to separate commands and '"' to start comments. |
2908 | * Don't do this for ":read !cmd" and ":write !cmd". |
2909 | */ |
2910 | if ((ea.argt & TRLBAR) && !usefilter) { |
2911 | p = arg; |
2912 | /* ":redir @" is not the start of a comment */ |
2913 | if (ea.cmdidx == CMD_redir && p[0] == '@' && p[1] == '"') |
2914 | p += 2; |
2915 | while (*p) { |
2916 | if (*p == Ctrl_V) { |
2917 | if (p[1] != NUL) |
2918 | ++p; |
2919 | } else if ( (*p == '"' && !(ea.argt & NOTRLCOM)) |
2920 | || *p == '|' || *p == '\n') { |
2921 | if (*(p - 1) != '\\') { |
2922 | if (*p == '|' || *p == '\n') |
2923 | return p + 1; |
2924 | return NULL; /* It's a comment */ |
2925 | } |
2926 | } |
2927 | MB_PTR_ADV(p); |
2928 | } |
2929 | } |
2930 | |
2931 | // no arguments allowed |
2932 | if (!(ea.argt & EXTRA) && *arg != NUL && strchr("|\"" , *arg) == NULL) { |
2933 | return NULL; |
2934 | } |
2935 | |
2936 | /* Find start of last argument (argument just before cursor): */ |
2937 | p = buff; |
2938 | xp->xp_pattern = (char_u *)p; |
2939 | len = strlen(buff); |
2940 | while (*p && p < buff + len) { |
2941 | if (*p == ' ' || *p == TAB) { |
2942 | // Argument starts after a space. |
2943 | xp->xp_pattern = (char_u *)++p; |
2944 | } else { |
2945 | if (*p == '\\' && *(p + 1) != NUL) { |
2946 | p++; // skip over escaped character |
2947 | } |
2948 | MB_PTR_ADV(p); |
2949 | } |
2950 | } |
2951 | |
2952 | if (ea.argt & XFILE) { |
2953 | int c; |
2954 | int in_quote = false; |
2955 | const char *bow = NULL; // Beginning of word. |
2956 | |
2957 | /* |
2958 | * Allow spaces within back-quotes to count as part of the argument |
2959 | * being expanded. |
2960 | */ |
2961 | xp->xp_pattern = skipwhite((const char_u *)arg); |
2962 | p = (const char *)xp->xp_pattern; |
2963 | while (*p != NUL) { |
2964 | c = utf_ptr2char((const char_u *)p); |
2965 | if (c == '\\' && p[1] != NUL) { |
2966 | p++; |
2967 | } else if (c == '`') { |
2968 | if (!in_quote) { |
2969 | xp->xp_pattern = (char_u *)p; |
2970 | bow = p + 1; |
2971 | } |
2972 | in_quote = !in_quote; |
2973 | } |
2974 | /* An argument can contain just about everything, except |
2975 | * characters that end the command and white space. */ |
2976 | else if (c == '|' |
2977 | || c == '\n' |
2978 | || c == '"' |
2979 | || ascii_iswhite(c)) { |
2980 | len = 0; /* avoid getting stuck when space is in 'isfname' */ |
2981 | while (*p != NUL) { |
2982 | c = utf_ptr2char((const char_u *)p); |
2983 | if (c == '`' || vim_isfilec_or_wc(c)) { |
2984 | break; |
2985 | } |
2986 | len = (size_t)utfc_ptr2len((const char_u *)p); |
2987 | MB_PTR_ADV(p); |
2988 | } |
2989 | if (in_quote) { |
2990 | bow = p; |
2991 | } else { |
2992 | xp->xp_pattern = (char_u *)p; |
2993 | } |
2994 | p -= len; |
2995 | } |
2996 | MB_PTR_ADV(p); |
2997 | } |
2998 | |
2999 | /* |
3000 | * If we are still inside the quotes, and we passed a space, just |
3001 | * expand from there. |
3002 | */ |
3003 | if (bow != NULL && in_quote) { |
3004 | xp->xp_pattern = (char_u *)bow; |
3005 | } |
3006 | xp->xp_context = EXPAND_FILES; |
3007 | |
3008 | /* For a shell command more chars need to be escaped. */ |
3009 | if (usefilter || ea.cmdidx == CMD_bang || ea.cmdidx == CMD_terminal) { |
3010 | #ifndef BACKSLASH_IN_FILENAME |
3011 | xp->xp_shell = TRUE; |
3012 | #endif |
3013 | // When still after the command name expand executables. |
3014 | if (xp->xp_pattern == skipwhite((const char_u *)arg)) { |
3015 | xp->xp_context = EXPAND_SHELLCMD; |
3016 | } |
3017 | } |
3018 | |
3019 | // Check for environment variable. |
3020 | if (*xp->xp_pattern == '$') { |
3021 | for (p = (const char *)xp->xp_pattern + 1; *p != NUL; p++) { |
3022 | if (!vim_isIDc((uint8_t)(*p))) { |
3023 | break; |
3024 | } |
3025 | } |
3026 | if (*p == NUL) { |
3027 | xp->xp_context = EXPAND_ENV_VARS; |
3028 | xp->xp_pattern++; |
3029 | // Avoid that the assignment uses EXPAND_FILES again. |
3030 | if (context != EXPAND_USER_DEFINED && context != EXPAND_USER_LIST) { |
3031 | context = EXPAND_ENV_VARS; |
3032 | } |
3033 | } |
3034 | } |
3035 | /* Check for user names */ |
3036 | if (*xp->xp_pattern == '~') { |
3037 | for (p = (const char *)xp->xp_pattern + 1; *p != NUL && *p != '/'; p++) { |
3038 | } |
3039 | // Complete ~user only if it partially matches a user name. |
3040 | // A full match ~user<Tab> will be replaced by user's home |
3041 | // directory i.e. something like ~user<Tab> -> /home/user/ |
3042 | if (*p == NUL && p > (const char *)xp->xp_pattern + 1 |
3043 | && match_user(xp->xp_pattern + 1) >= 1) { |
3044 | xp->xp_context = EXPAND_USER; |
3045 | ++xp->xp_pattern; |
3046 | } |
3047 | } |
3048 | } |
3049 | |
3050 | /* |
3051 | * 6. switch on command name |
3052 | */ |
3053 | switch (ea.cmdidx) { |
3054 | case CMD_find: |
3055 | case CMD_sfind: |
3056 | case CMD_tabfind: |
3057 | if (xp->xp_context == EXPAND_FILES) |
3058 | xp->xp_context = EXPAND_FILES_IN_PATH; |
3059 | break; |
3060 | case CMD_cd: |
3061 | case CMD_chdir: |
3062 | case CMD_lcd: |
3063 | case CMD_lchdir: |
3064 | case CMD_tcd: |
3065 | case CMD_tchdir: |
3066 | if (xp->xp_context == EXPAND_FILES) { |
3067 | xp->xp_context = EXPAND_DIRECTORIES; |
3068 | } |
3069 | break; |
3070 | case CMD_help: |
3071 | xp->xp_context = EXPAND_HELP; |
3072 | xp->xp_pattern = (char_u *)arg; |
3073 | break; |
3074 | |
3075 | /* Command modifiers: return the argument. |
3076 | * Also for commands with an argument that is a command. */ |
3077 | case CMD_aboveleft: |
3078 | case CMD_argdo: |
3079 | case CMD_belowright: |
3080 | case CMD_botright: |
3081 | case CMD_browse: |
3082 | case CMD_bufdo: |
3083 | case CMD_cdo: |
3084 | case CMD_cfdo: |
3085 | case CMD_confirm: |
3086 | case CMD_debug: |
3087 | case CMD_folddoclosed: |
3088 | case CMD_folddoopen: |
3089 | case CMD_hide: |
3090 | case CMD_keepalt: |
3091 | case CMD_keepjumps: |
3092 | case CMD_keepmarks: |
3093 | case CMD_keeppatterns: |
3094 | case CMD_ldo: |
3095 | case CMD_leftabove: |
3096 | case CMD_lfdo: |
3097 | case CMD_lockmarks: |
3098 | case CMD_noautocmd: |
3099 | case CMD_noswapfile: |
3100 | case CMD_rightbelow: |
3101 | case CMD_sandbox: |
3102 | case CMD_silent: |
3103 | case CMD_tab: |
3104 | case CMD_tabdo: |
3105 | case CMD_topleft: |
3106 | case CMD_verbose: |
3107 | case CMD_vertical: |
3108 | case CMD_windo: |
3109 | return arg; |
3110 | |
3111 | case CMD_filter: |
3112 | if (*arg != NUL) { |
3113 | arg = (const char *)skip_vimgrep_pat((char_u *)arg, NULL, NULL); |
3114 | } |
3115 | if (arg == NULL || *arg == NUL) { |
3116 | xp->xp_context = EXPAND_NOTHING; |
3117 | return NULL; |
3118 | } |
3119 | return (const char *)skipwhite((const char_u *)arg); |
3120 | |
3121 | case CMD_match: |
3122 | if (*arg == NUL || !ends_excmd(*arg)) { |
3123 | /* also complete "None" */ |
3124 | set_context_in_echohl_cmd(xp, arg); |
3125 | arg = (const char *)skipwhite(skiptowhite((const char_u *)arg)); |
3126 | if (*arg != NUL) { |
3127 | xp->xp_context = EXPAND_NOTHING; |
3128 | arg = (const char *)skip_regexp((char_u *)arg + 1, (uint8_t)(*arg), |
3129 | p_magic, NULL); |
3130 | } |
3131 | } |
3132 | return (const char *)find_nextcmd((char_u *)arg); |
3133 | |
3134 | /* |
3135 | * All completion for the +cmdline_compl feature goes here. |
3136 | */ |
3137 | |
3138 | case CMD_command: |
3139 | /* Check for attributes */ |
3140 | while (*arg == '-') { |
3141 | arg++; // Skip "-". |
3142 | p = (const char *)skiptowhite((const char_u *)arg); |
3143 | if (*p == NUL) { |
3144 | // Cursor is still in the attribute. |
3145 | p = strchr(arg, '='); |
3146 | if (p == NULL) { |
3147 | // No "=", so complete attribute names. |
3148 | xp->xp_context = EXPAND_USER_CMD_FLAGS; |
3149 | xp->xp_pattern = (char_u *)arg; |
3150 | return NULL; |
3151 | } |
3152 | |
3153 | // For the -complete, -nargs and -addr attributes, we complete |
3154 | // their arguments as well. |
3155 | if (STRNICMP(arg, "complete" , p - arg) == 0) { |
3156 | xp->xp_context = EXPAND_USER_COMPLETE; |
3157 | xp->xp_pattern = (char_u *)p + 1; |
3158 | return NULL; |
3159 | } else if (STRNICMP(arg, "nargs" , p - arg) == 0) { |
3160 | xp->xp_context = EXPAND_USER_NARGS; |
3161 | xp->xp_pattern = (char_u *)p + 1; |
3162 | return NULL; |
3163 | } else if (STRNICMP(arg, "addr" , p - arg) == 0) { |
3164 | xp->xp_context = EXPAND_USER_ADDR_TYPE; |
3165 | xp->xp_pattern = (char_u *)p + 1; |
3166 | return NULL; |
3167 | } |
3168 | return NULL; |
3169 | } |
3170 | arg = (const char *)skipwhite((char_u *)p); |
3171 | } |
3172 | |
3173 | // After the attributes comes the new command name. |
3174 | p = (const char *)skiptowhite((const char_u *)arg); |
3175 | if (*p == NUL) { |
3176 | xp->xp_context = EXPAND_USER_COMMANDS; |
3177 | xp->xp_pattern = (char_u *)arg; |
3178 | break; |
3179 | } |
3180 | |
3181 | // And finally comes a normal command. |
3182 | return (const char *)skipwhite((const char_u *)p); |
3183 | |
3184 | case CMD_delcommand: |
3185 | xp->xp_context = EXPAND_USER_COMMANDS; |
3186 | xp->xp_pattern = (char_u *)arg; |
3187 | break; |
3188 | |
3189 | case CMD_global: |
3190 | case CMD_vglobal: { |
3191 | const int delim = (uint8_t)(*arg); // Get the delimiter. |
3192 | if (delim) { |
3193 | arg++; // Skip delimiter if there is one. |
3194 | } |
3195 | |
3196 | while (arg[0] != NUL && (uint8_t)arg[0] != delim) { |
3197 | if (arg[0] == '\\' && arg[1] != NUL) { |
3198 | arg++; |
3199 | } |
3200 | arg++; |
3201 | } |
3202 | if (arg[0] != NUL) |
3203 | return arg + 1; |
3204 | break; |
3205 | } |
3206 | case CMD_and: |
3207 | case CMD_substitute: { |
3208 | const int delim = (uint8_t)(*arg); |
3209 | if (delim) { |
3210 | // Skip "from" part. |
3211 | arg++; |
3212 | arg = (const char *)skip_regexp((char_u *)arg, delim, p_magic, NULL); |
3213 | } |
3214 | // Skip "to" part. |
3215 | while (arg[0] != NUL && (uint8_t)arg[0] != delim) { |
3216 | if (arg[0] == '\\' && arg[1] != NUL) { |
3217 | arg++; |
3218 | } |
3219 | arg++; |
3220 | } |
3221 | if (arg[0] != NUL) { // Skip delimiter. |
3222 | arg++; |
3223 | } |
3224 | while (arg[0] && strchr("|\"#" , arg[0]) == NULL) { |
3225 | arg++; |
3226 | } |
3227 | if (arg[0] != NUL) { |
3228 | return arg; |
3229 | } |
3230 | break; |
3231 | } |
3232 | case CMD_isearch: |
3233 | case CMD_dsearch: |
3234 | case CMD_ilist: |
3235 | case CMD_dlist: |
3236 | case CMD_ijump: |
3237 | case CMD_psearch: |
3238 | case CMD_djump: |
3239 | case CMD_isplit: |
3240 | case CMD_dsplit: |
3241 | // Skip count. |
3242 | arg = (const char *)skipwhite(skipdigits((const char_u *)arg)); |
3243 | if (*arg == '/') { // Match regexp, not just whole words. |
3244 | for (++arg; *arg && *arg != '/'; arg++) { |
3245 | if (*arg == '\\' && arg[1] != NUL) { |
3246 | arg++; |
3247 | } |
3248 | } |
3249 | if (*arg) { |
3250 | arg = (const char *)skipwhite((const char_u *)arg + 1); |
3251 | |
3252 | // Check for trailing illegal characters. |
3253 | if (*arg && strchr("|\"\n" , *arg) == NULL) { |
3254 | xp->xp_context = EXPAND_NOTHING; |
3255 | } else { |
3256 | return arg; |
3257 | } |
3258 | } |
3259 | } |
3260 | break; |
3261 | case CMD_autocmd: |
3262 | return (const char *)set_context_in_autocmd(xp, (char_u *)arg, false); |
3263 | |
3264 | case CMD_doautocmd: |
3265 | case CMD_doautoall: |
3266 | return (const char *)set_context_in_autocmd(xp, (char_u *)arg, true); |
3267 | case CMD_set: |
3268 | set_context_in_set_cmd(xp, (char_u *)arg, 0); |
3269 | break; |
3270 | case CMD_setglobal: |
3271 | set_context_in_set_cmd(xp, (char_u *)arg, OPT_GLOBAL); |
3272 | break; |
3273 | case CMD_setlocal: |
3274 | set_context_in_set_cmd(xp, (char_u *)arg, OPT_LOCAL); |
3275 | break; |
3276 | case CMD_tag: |
3277 | case CMD_stag: |
3278 | case CMD_ptag: |
3279 | case CMD_ltag: |
3280 | case CMD_tselect: |
3281 | case CMD_stselect: |
3282 | case CMD_ptselect: |
3283 | case CMD_tjump: |
3284 | case CMD_stjump: |
3285 | case CMD_ptjump: |
3286 | if (wop_flags & WOP_TAGFILE) { |
3287 | xp->xp_context = EXPAND_TAGS_LISTFILES; |
3288 | } else { |
3289 | xp->xp_context = EXPAND_TAGS; |
3290 | } |
3291 | xp->xp_pattern = (char_u *)arg; |
3292 | break; |
3293 | case CMD_augroup: |
3294 | xp->xp_context = EXPAND_AUGROUP; |
3295 | xp->xp_pattern = (char_u *)arg; |
3296 | break; |
3297 | case CMD_syntax: |
3298 | set_context_in_syntax_cmd(xp, arg); |
3299 | break; |
3300 | case CMD_let: |
3301 | case CMD_if: |
3302 | case CMD_elseif: |
3303 | case CMD_while: |
3304 | case CMD_for: |
3305 | case CMD_echo: |
3306 | case CMD_echon: |
3307 | case CMD_execute: |
3308 | case CMD_echomsg: |
3309 | case CMD_echoerr: |
3310 | case CMD_call: |
3311 | case CMD_return: |
3312 | case CMD_cexpr: |
3313 | case CMD_caddexpr: |
3314 | case CMD_cgetexpr: |
3315 | case CMD_lexpr: |
3316 | case CMD_laddexpr: |
3317 | case CMD_lgetexpr: |
3318 | set_context_for_expression(xp, (char_u *)arg, ea.cmdidx); |
3319 | break; |
3320 | |
3321 | case CMD_unlet: |
3322 | while ((xp->xp_pattern = (char_u *)strchr(arg, ' ')) != NULL) { |
3323 | arg = (const char *)xp->xp_pattern + 1; |
3324 | } |
3325 | |
3326 | xp->xp_context = EXPAND_USER_VARS; |
3327 | xp->xp_pattern = (char_u *)arg; |
3328 | |
3329 | if (*xp->xp_pattern == '$') { |
3330 | xp->xp_context = EXPAND_ENV_VARS; |
3331 | xp->xp_pattern++; |
3332 | } |
3333 | |
3334 | break; |
3335 | |
3336 | case CMD_function: |
3337 | case CMD_delfunction: |
3338 | xp->xp_context = EXPAND_USER_FUNC; |
3339 | xp->xp_pattern = (char_u *)arg; |
3340 | break; |
3341 | |
3342 | case CMD_echohl: |
3343 | set_context_in_echohl_cmd(xp, arg); |
3344 | break; |
3345 | case CMD_highlight: |
3346 | set_context_in_highlight_cmd(xp, arg); |
3347 | break; |
3348 | case CMD_cscope: |
3349 | case CMD_lcscope: |
3350 | case CMD_scscope: |
3351 | set_context_in_cscope_cmd(xp, arg, ea.cmdidx); |
3352 | break; |
3353 | case CMD_sign: |
3354 | set_context_in_sign_cmd(xp, (char_u *)arg); |
3355 | break; |
3356 | case CMD_bdelete: |
3357 | case CMD_bwipeout: |
3358 | case CMD_bunload: |
3359 | while ((xp->xp_pattern = (char_u *)strchr(arg, ' ')) != NULL) { |
3360 | arg = (const char *)xp->xp_pattern + 1; |
3361 | } |
3362 | FALLTHROUGH; |
3363 | case CMD_buffer: |
3364 | case CMD_sbuffer: |
3365 | case CMD_checktime: |
3366 | xp->xp_context = EXPAND_BUFFERS; |
3367 | xp->xp_pattern = (char_u *)arg; |
3368 | break; |
3369 | case CMD_USER: |
3370 | case CMD_USER_BUF: |
3371 | if (context != EXPAND_NOTHING) { |
3372 | // XFILE: file names are handled above. |
3373 | if (!(ea.argt & XFILE)) { |
3374 | if (context == EXPAND_MENUS) { |
3375 | return (const char *)set_context_in_menu_cmd(xp, (char_u *)cmd, |
3376 | (char_u *)arg, forceit); |
3377 | } else if (context == EXPAND_COMMANDS) { |
3378 | return arg; |
3379 | } else if (context == EXPAND_MAPPINGS) { |
3380 | return (const char *)set_context_in_map_cmd( |
3381 | xp, (char_u *)"map" , (char_u *)arg, forceit, false, false, |
3382 | CMD_map); |
3383 | } |
3384 | // Find start of last argument. |
3385 | p = arg; |
3386 | while (*p) { |
3387 | if (*p == ' ') { |
3388 | // argument starts after a space |
3389 | arg = p + 1; |
3390 | } else if (*p == '\\' && *(p + 1) != NUL) { |
3391 | p++; // skip over escaped character |
3392 | } |
3393 | MB_PTR_ADV(p); |
3394 | } |
3395 | xp->xp_pattern = (char_u *)arg; |
3396 | } |
3397 | xp->xp_context = context; |
3398 | } |
3399 | break; |
3400 | case CMD_map: case CMD_noremap: |
3401 | case CMD_nmap: case CMD_nnoremap: |
3402 | case CMD_vmap: case CMD_vnoremap: |
3403 | case CMD_omap: case CMD_onoremap: |
3404 | case CMD_imap: case CMD_inoremap: |
3405 | case CMD_cmap: case CMD_cnoremap: |
3406 | case CMD_lmap: case CMD_lnoremap: |
3407 | case CMD_smap: case CMD_snoremap: |
3408 | case CMD_xmap: case CMD_xnoremap: |
3409 | return (const char *)set_context_in_map_cmd( |
3410 | xp, (char_u *)cmd, (char_u *)arg, forceit, false, false, ea.cmdidx); |
3411 | case CMD_unmap: |
3412 | case CMD_nunmap: |
3413 | case CMD_vunmap: |
3414 | case CMD_ounmap: |
3415 | case CMD_iunmap: |
3416 | case CMD_cunmap: |
3417 | case CMD_lunmap: |
3418 | case CMD_sunmap: |
3419 | case CMD_xunmap: |
3420 | return (const char *)set_context_in_map_cmd( |
3421 | xp, (char_u *)cmd, (char_u *)arg, forceit, false, true, ea.cmdidx); |
3422 | case CMD_mapclear: |
3423 | case CMD_nmapclear: |
3424 | case CMD_vmapclear: |
3425 | case CMD_omapclear: |
3426 | case CMD_imapclear: |
3427 | case CMD_cmapclear: |
3428 | case CMD_lmapclear: |
3429 | case CMD_smapclear: |
3430 | case CMD_xmapclear: |
3431 | xp->xp_context = EXPAND_MAPCLEAR; |
3432 | xp->xp_pattern = (char_u *)arg; |
3433 | break; |
3434 | |
3435 | case CMD_abbreviate: case CMD_noreabbrev: |
3436 | case CMD_cabbrev: case CMD_cnoreabbrev: |
3437 | case CMD_iabbrev: case CMD_inoreabbrev: |
3438 | return (const char *)set_context_in_map_cmd( |
3439 | xp, (char_u *)cmd, (char_u *)arg, forceit, true, false, ea.cmdidx); |
3440 | case CMD_unabbreviate: |
3441 | case CMD_cunabbrev: |
3442 | case CMD_iunabbrev: |
3443 | return (const char *)set_context_in_map_cmd( |
3444 | xp, (char_u *)cmd, (char_u *)arg, forceit, true, true, ea.cmdidx); |
3445 | case CMD_menu: case CMD_noremenu: case CMD_unmenu: |
3446 | case CMD_amenu: case CMD_anoremenu: case CMD_aunmenu: |
3447 | case CMD_nmenu: case CMD_nnoremenu: case CMD_nunmenu: |
3448 | case CMD_vmenu: case CMD_vnoremenu: case CMD_vunmenu: |
3449 | case CMD_omenu: case CMD_onoremenu: case CMD_ounmenu: |
3450 | case CMD_imenu: case CMD_inoremenu: case CMD_iunmenu: |
3451 | case CMD_cmenu: case CMD_cnoremenu: case CMD_cunmenu: |
3452 | case CMD_tmenu: case CMD_tunmenu: |
3453 | case CMD_popup: case CMD_emenu: |
3454 | return (const char *)set_context_in_menu_cmd( |
3455 | xp, (char_u *)cmd, (char_u *)arg, forceit); |
3456 | |
3457 | case CMD_colorscheme: |
3458 | xp->xp_context = EXPAND_COLORS; |
3459 | xp->xp_pattern = (char_u *)arg; |
3460 | break; |
3461 | |
3462 | case CMD_compiler: |
3463 | xp->xp_context = EXPAND_COMPILER; |
3464 | xp->xp_pattern = (char_u *)arg; |
3465 | break; |
3466 | |
3467 | case CMD_ownsyntax: |
3468 | xp->xp_context = EXPAND_OWNSYNTAX; |
3469 | xp->xp_pattern = (char_u *)arg; |
3470 | break; |
3471 | |
3472 | case CMD_setfiletype: |
3473 | xp->xp_context = EXPAND_FILETYPE; |
3474 | xp->xp_pattern = (char_u *)arg; |
3475 | break; |
3476 | |
3477 | case CMD_packadd: |
3478 | xp->xp_context = EXPAND_PACKADD; |
3479 | xp->xp_pattern = (char_u *)arg; |
3480 | break; |
3481 | |
3482 | #ifdef HAVE_WORKING_LIBINTL |
3483 | case CMD_language: |
3484 | p = (const char *)skiptowhite((const char_u *)arg); |
3485 | if (*p == NUL) { |
3486 | xp->xp_context = EXPAND_LANGUAGE; |
3487 | xp->xp_pattern = (char_u *)arg; |
3488 | } else { |
3489 | if (strncmp(arg, "messages" , p - arg) == 0 |
3490 | || strncmp(arg, "ctype" , p - arg) == 0 |
3491 | || strncmp(arg, "time" , p - arg) == 0) { |
3492 | xp->xp_context = EXPAND_LOCALES; |
3493 | xp->xp_pattern = skipwhite((const char_u *)p); |
3494 | } else { |
3495 | xp->xp_context = EXPAND_NOTHING; |
3496 | } |
3497 | } |
3498 | break; |
3499 | #endif |
3500 | case CMD_profile: |
3501 | set_context_in_profile_cmd(xp, arg); |
3502 | break; |
3503 | case CMD_checkhealth: |
3504 | xp->xp_context = EXPAND_CHECKHEALTH; |
3505 | xp->xp_pattern = (char_u *)arg; |
3506 | break; |
3507 | case CMD_behave: |
3508 | xp->xp_context = EXPAND_BEHAVE; |
3509 | xp->xp_pattern = (char_u *)arg; |
3510 | break; |
3511 | |
3512 | case CMD_messages: |
3513 | xp->xp_context = EXPAND_MESSAGES; |
3514 | xp->xp_pattern = (char_u *)arg; |
3515 | break; |
3516 | |
3517 | case CMD_history: |
3518 | xp->xp_context = EXPAND_HISTORY; |
3519 | xp->xp_pattern = (char_u *)arg; |
3520 | break; |
3521 | case CMD_syntime: |
3522 | xp->xp_context = EXPAND_SYNTIME; |
3523 | xp->xp_pattern = (char_u *)arg; |
3524 | break; |
3525 | |
3526 | case CMD_argdelete: |
3527 | while ((xp->xp_pattern = vim_strchr((const char_u *)arg, ' ')) != NULL) { |
3528 | arg = (const char *)(xp->xp_pattern + 1); |
3529 | } |
3530 | xp->xp_context = EXPAND_ARGLIST; |
3531 | xp->xp_pattern = (char_u *)arg; |
3532 | break; |
3533 | |
3534 | default: |
3535 | break; |
3536 | } |
3537 | return NULL; |
3538 | } |
3539 | |
3540 | /* |
3541 | * skip a range specifier of the form: addr [,addr] [;addr] .. |
3542 | * |
3543 | * Backslashed delimiters after / or ? will be skipped, and commands will |
3544 | * not be expanded between /'s and ?'s or after "'". |
3545 | * |
3546 | * Also skip white space and ":" characters. |
3547 | * Returns the "cmd" pointer advanced to beyond the range. |
3548 | */ |
3549 | char_u *skip_range( |
3550 | const char_u *cmd, |
3551 | int *ctx // pointer to xp_context or NULL |
3552 | ) |
3553 | { |
3554 | unsigned delim; |
3555 | |
3556 | while (vim_strchr((char_u *)" \t0123456789.$%'/?-+,;\\" , *cmd) != NULL) { |
3557 | if (*cmd == '\\') { |
3558 | if (cmd[1] == '?' || cmd[1] == '/' || cmd[1] == '&') { |
3559 | cmd++; |
3560 | } else { |
3561 | break; |
3562 | } |
3563 | } else if (*cmd == '\'') { |
3564 | if (*++cmd == NUL && ctx != NULL) { |
3565 | *ctx = EXPAND_NOTHING; |
3566 | } |
3567 | } else if (*cmd == '/' || *cmd == '?') { |
3568 | delim = *cmd++; |
3569 | while (*cmd != NUL && *cmd != delim) |
3570 | if (*cmd++ == '\\' && *cmd != NUL) |
3571 | ++cmd; |
3572 | if (*cmd == NUL && ctx != NULL) |
3573 | *ctx = EXPAND_NOTHING; |
3574 | } |
3575 | if (*cmd != NUL) |
3576 | ++cmd; |
3577 | } |
3578 | |
3579 | /* Skip ":" and white space. */ |
3580 | while (*cmd == ':') |
3581 | cmd = skipwhite(cmd + 1); |
3582 | |
3583 | return (char_u *)cmd; |
3584 | } |
3585 | |
3586 | /* |
3587 | * get a single EX address |
3588 | * |
3589 | * Set ptr to the next character after the part that was interpreted. |
3590 | * Set ptr to NULL when an error is encountered. |
3591 | * |
3592 | * Return MAXLNUM when no Ex address was found. |
3593 | */ |
3594 | static linenr_T get_address(exarg_T *eap, |
3595 | char_u **ptr, |
3596 | int addr_type, // flag: one of ADDR_LINES, ... |
3597 | int skip, // only skip the address, don't use it |
3598 | int to_other_file, // flag: may jump to other file |
3599 | int address_count) // 1 for first, >1 after comma |
3600 | { |
3601 | int c; |
3602 | int i; |
3603 | long n; |
3604 | char_u *cmd; |
3605 | pos_T pos; |
3606 | pos_T *fp; |
3607 | linenr_T lnum; |
3608 | buf_T *buf; |
3609 | |
3610 | cmd = skipwhite(*ptr); |
3611 | lnum = MAXLNUM; |
3612 | do { |
3613 | switch (*cmd) { |
3614 | case '.': /* '.' - Cursor position */ |
3615 | ++cmd; |
3616 | switch (addr_type) { |
3617 | case ADDR_LINES: |
3618 | lnum = curwin->w_cursor.lnum; |
3619 | break; |
3620 | case ADDR_WINDOWS: |
3621 | lnum = CURRENT_WIN_NR; |
3622 | break; |
3623 | case ADDR_ARGUMENTS: |
3624 | lnum = curwin->w_arg_idx + 1; |
3625 | break; |
3626 | case ADDR_LOADED_BUFFERS: |
3627 | case ADDR_BUFFERS: |
3628 | lnum = curbuf->b_fnum; |
3629 | break; |
3630 | case ADDR_TABS: |
3631 | lnum = CURRENT_TAB_NR; |
3632 | break; |
3633 | case ADDR_TABS_RELATIVE: |
3634 | EMSG(_(e_invrange)); |
3635 | cmd = NULL; |
3636 | goto error; |
3637 | break; |
3638 | case ADDR_QUICKFIX: |
3639 | lnum = qf_get_cur_valid_idx(eap); |
3640 | break; |
3641 | } |
3642 | break; |
3643 | |
3644 | case '$': /* '$' - last line */ |
3645 | ++cmd; |
3646 | switch (addr_type) { |
3647 | case ADDR_LINES: |
3648 | lnum = curbuf->b_ml.ml_line_count; |
3649 | break; |
3650 | case ADDR_WINDOWS: |
3651 | lnum = LAST_WIN_NR; |
3652 | break; |
3653 | case ADDR_ARGUMENTS: |
3654 | lnum = ARGCOUNT; |
3655 | break; |
3656 | case ADDR_LOADED_BUFFERS: |
3657 | buf = lastbuf; |
3658 | while (buf->b_ml.ml_mfp == NULL) { |
3659 | if (buf->b_prev == NULL) { |
3660 | break; |
3661 | } |
3662 | buf = buf->b_prev; |
3663 | } |
3664 | lnum = buf->b_fnum; |
3665 | break; |
3666 | case ADDR_BUFFERS: |
3667 | lnum = lastbuf->b_fnum; |
3668 | break; |
3669 | case ADDR_TABS: |
3670 | lnum = LAST_TAB_NR; |
3671 | break; |
3672 | case ADDR_TABS_RELATIVE: |
3673 | EMSG(_(e_invrange)); |
3674 | cmd = NULL; |
3675 | goto error; |
3676 | break; |
3677 | case ADDR_QUICKFIX: |
3678 | lnum = qf_get_size(eap); |
3679 | if (lnum == 0) { |
3680 | lnum = 1; |
3681 | } |
3682 | break; |
3683 | } |
3684 | break; |
3685 | |
3686 | case '\'': /* ''' - mark */ |
3687 | if (*++cmd == NUL) { |
3688 | cmd = NULL; |
3689 | goto error; |
3690 | } |
3691 | if (addr_type != ADDR_LINES) { |
3692 | EMSG(_(e_invaddr)); |
3693 | cmd = NULL; |
3694 | goto error; |
3695 | } |
3696 | if (skip) |
3697 | ++cmd; |
3698 | else { |
3699 | /* Only accept a mark in another file when it is |
3700 | * used by itself: ":'M". */ |
3701 | fp = getmark(*cmd, to_other_file && cmd[1] == NUL); |
3702 | ++cmd; |
3703 | if (fp == (pos_T *)-1) |
3704 | /* Jumped to another file. */ |
3705 | lnum = curwin->w_cursor.lnum; |
3706 | else { |
3707 | if (check_mark(fp) == FAIL) { |
3708 | cmd = NULL; |
3709 | goto error; |
3710 | } |
3711 | lnum = fp->lnum; |
3712 | } |
3713 | } |
3714 | break; |
3715 | |
3716 | case '/': |
3717 | case '?': /* '/' or '?' - search */ |
3718 | c = *cmd++; |
3719 | if (addr_type != ADDR_LINES) { |
3720 | EMSG(_(e_invaddr)); |
3721 | cmd = NULL; |
3722 | goto error; |
3723 | } |
3724 | if (skip) { /* skip "/pat/" */ |
3725 | cmd = skip_regexp(cmd, c, p_magic, NULL); |
3726 | if (*cmd == c) |
3727 | ++cmd; |
3728 | } else { |
3729 | pos = curwin->w_cursor; /* save curwin->w_cursor */ |
3730 | /* |
3731 | * When '/' or '?' follows another address, start |
3732 | * from there. |
3733 | */ |
3734 | if (lnum != MAXLNUM) |
3735 | curwin->w_cursor.lnum = lnum; |
3736 | |
3737 | // Start a forward search at the end of the line (unless |
3738 | // before the first line). |
3739 | // Start a backward search at the start of the line. |
3740 | // This makes sure we never match in the current |
3741 | // line, and can match anywhere in the |
3742 | // next/previous line. |
3743 | if (c == '/' && curwin->w_cursor.lnum > 0) { |
3744 | curwin->w_cursor.col = MAXCOL; |
3745 | } else { |
3746 | curwin->w_cursor.col = 0; |
3747 | } |
3748 | searchcmdlen = 0; |
3749 | if (!do_search(NULL, c, cmd, 1L, |
3750 | SEARCH_HIS | SEARCH_MSG, NULL, NULL)) { |
3751 | curwin->w_cursor = pos; |
3752 | cmd = NULL; |
3753 | goto error; |
3754 | } |
3755 | lnum = curwin->w_cursor.lnum; |
3756 | curwin->w_cursor = pos; |
3757 | /* adjust command string pointer */ |
3758 | cmd += searchcmdlen; |
3759 | } |
3760 | break; |
3761 | |
3762 | case '\\': /* "\?", "\/" or "\&", repeat search */ |
3763 | ++cmd; |
3764 | if (addr_type != ADDR_LINES) { |
3765 | EMSG(_(e_invaddr)); |
3766 | cmd = NULL; |
3767 | goto error; |
3768 | } |
3769 | if (*cmd == '&') |
3770 | i = RE_SUBST; |
3771 | else if (*cmd == '?' || *cmd == '/') |
3772 | i = RE_SEARCH; |
3773 | else { |
3774 | EMSG(_(e_backslash)); |
3775 | cmd = NULL; |
3776 | goto error; |
3777 | } |
3778 | |
3779 | if (!skip) { |
3780 | // When search follows another address, start from there. |
3781 | pos.lnum = (lnum != MAXLNUM) ? lnum : curwin->w_cursor.lnum; |
3782 | // Start the search just like for the above do_search(). |
3783 | pos.col = (*cmd != '?') ? MAXCOL : 0; |
3784 | pos.coladd = 0; |
3785 | if (searchit(curwin, curbuf, &pos, NULL, |
3786 | *cmd == '?' ? BACKWARD : FORWARD, |
3787 | (char_u *)"" , 1L, SEARCH_MSG, |
3788 | i, (linenr_T)0, NULL, NULL) != FAIL) { |
3789 | lnum = pos.lnum; |
3790 | } else { |
3791 | cmd = NULL; |
3792 | goto error; |
3793 | } |
3794 | } |
3795 | ++cmd; |
3796 | break; |
3797 | |
3798 | default: |
3799 | if (ascii_isdigit(*cmd)) { // absolute line number |
3800 | lnum = getdigits_long(&cmd, false, 0); |
3801 | } |
3802 | } |
3803 | |
3804 | for (;; ) { |
3805 | cmd = skipwhite(cmd); |
3806 | if (*cmd != '-' && *cmd != '+' && !ascii_isdigit(*cmd)) { |
3807 | break; |
3808 | } |
3809 | |
3810 | if (lnum == MAXLNUM) { |
3811 | switch (addr_type) { |
3812 | case ADDR_LINES: |
3813 | lnum = curwin->w_cursor.lnum; /* "+1" is same as ".+1" */ |
3814 | break; |
3815 | case ADDR_WINDOWS: |
3816 | lnum = CURRENT_WIN_NR; |
3817 | break; |
3818 | case ADDR_ARGUMENTS: |
3819 | lnum = curwin->w_arg_idx + 1; |
3820 | break; |
3821 | case ADDR_LOADED_BUFFERS: |
3822 | case ADDR_BUFFERS: |
3823 | lnum = curbuf->b_fnum; |
3824 | break; |
3825 | case ADDR_TABS: |
3826 | lnum = CURRENT_TAB_NR; |
3827 | break; |
3828 | case ADDR_TABS_RELATIVE: |
3829 | lnum = 1; |
3830 | break; |
3831 | case ADDR_QUICKFIX: |
3832 | lnum = qf_get_cur_valid_idx(eap); |
3833 | break; |
3834 | } |
3835 | } |
3836 | |
3837 | if (ascii_isdigit(*cmd)) { |
3838 | i = '+'; // "number" is same as "+number" |
3839 | } else { |
3840 | i = *cmd++; |
3841 | } |
3842 | if (!ascii_isdigit(*cmd)) { // '+' is '+1', but '+0' is not '+1' |
3843 | n = 1; |
3844 | } else { |
3845 | n = getdigits(&cmd, true, 0); |
3846 | } |
3847 | |
3848 | if (addr_type == ADDR_TABS_RELATIVE) { |
3849 | EMSG(_(e_invrange)); |
3850 | cmd = NULL; |
3851 | goto error; |
3852 | } else if (addr_type == ADDR_LOADED_BUFFERS || addr_type == ADDR_BUFFERS) { |
3853 | lnum = compute_buffer_local_count( |
3854 | addr_type, lnum, (i == '-') ? -1 * n : n); |
3855 | } else { |
3856 | // Relative line addressing, need to adjust for folded lines |
3857 | // now, but only do it after the first address. |
3858 | if (addr_type == ADDR_LINES && (i == '-' || i == '+') |
3859 | && address_count >= 2) { |
3860 | (void)hasFolding(lnum, NULL, &lnum); |
3861 | } |
3862 | if (i == '-') { |
3863 | lnum -= n; |
3864 | } else { |
3865 | lnum += n; |
3866 | } |
3867 | } |
3868 | } |
3869 | } while (*cmd == '/' || *cmd == '?'); |
3870 | |
3871 | error: |
3872 | *ptr = cmd; |
3873 | return lnum; |
3874 | } |
3875 | |
3876 | /* |
3877 | * Get flags from an Ex command argument. |
3878 | */ |
3879 | static void get_flags(exarg_T *eap) |
3880 | { |
3881 | while (vim_strchr((char_u *)"lp#" , *eap->arg) != NULL) { |
3882 | if (*eap->arg == 'l') |
3883 | eap->flags |= EXFLAG_LIST; |
3884 | else if (*eap->arg == 'p') |
3885 | eap->flags |= EXFLAG_PRINT; |
3886 | else |
3887 | eap->flags |= EXFLAG_NR; |
3888 | eap->arg = skipwhite(eap->arg + 1); |
3889 | } |
3890 | } |
3891 | |
3892 | /// Stub function for command which is Not Implemented. NI! |
3893 | void ex_ni(exarg_T *eap) |
3894 | { |
3895 | if (!eap->skip) |
3896 | eap->errmsg = (char_u *)N_( |
3897 | "E319: The command is not available in this version" ); |
3898 | } |
3899 | |
3900 | /// Stub function for script command which is Not Implemented. NI! |
3901 | /// Skips over ":perl <<EOF" constructs. |
3902 | static void ex_script_ni(exarg_T *eap) |
3903 | { |
3904 | if (!eap->skip) { |
3905 | ex_ni(eap); |
3906 | } else { |
3907 | size_t len; |
3908 | xfree(script_get(eap, &len)); |
3909 | } |
3910 | } |
3911 | |
3912 | /* |
3913 | * Check range in Ex command for validity. |
3914 | * Return NULL when valid, error message when invalid. |
3915 | */ |
3916 | static char_u *invalid_range(exarg_T *eap) |
3917 | { |
3918 | buf_T *buf; |
3919 | if (eap->line1 < 0 || eap->line2 < 0 || eap->line1 > eap->line2) { |
3920 | return (char_u *)_(e_invrange); |
3921 | } |
3922 | |
3923 | if (eap->argt & RANGE) { |
3924 | switch(eap->addr_type) { |
3925 | case ADDR_LINES: |
3926 | if (!(eap->argt & NOTADR) |
3927 | && eap->line2 > curbuf->b_ml.ml_line_count |
3928 | + (eap->cmdidx == CMD_diffget)) { |
3929 | return (char_u *)_(e_invrange); |
3930 | } |
3931 | break; |
3932 | case ADDR_ARGUMENTS: |
3933 | // add 1 if ARGCOUNT is 0 |
3934 | if (eap->line2 > ARGCOUNT + (!ARGCOUNT)) { |
3935 | return (char_u *)_(e_invrange); |
3936 | } |
3937 | break; |
3938 | case ADDR_BUFFERS: |
3939 | if (eap->line1 < firstbuf->b_fnum |
3940 | || eap->line2 > lastbuf->b_fnum) { |
3941 | return (char_u *)_(e_invrange); |
3942 | } |
3943 | break; |
3944 | case ADDR_LOADED_BUFFERS: |
3945 | buf = firstbuf; |
3946 | while (buf->b_ml.ml_mfp == NULL) { |
3947 | if (buf->b_next == NULL) { |
3948 | return (char_u *)_(e_invrange); |
3949 | } |
3950 | buf = buf->b_next; |
3951 | } |
3952 | if (eap->line1 < buf->b_fnum) { |
3953 | return (char_u *)_(e_invrange); |
3954 | } |
3955 | buf = lastbuf; |
3956 | while (buf->b_ml.ml_mfp == NULL) { |
3957 | if (buf->b_prev == NULL) { |
3958 | return (char_u *)_(e_invrange); |
3959 | } |
3960 | buf = buf->b_prev; |
3961 | } |
3962 | if (eap->line2 > buf->b_fnum) { |
3963 | return (char_u *)_(e_invrange); |
3964 | } |
3965 | break; |
3966 | case ADDR_WINDOWS: |
3967 | if (eap->line2 > LAST_WIN_NR) { |
3968 | return (char_u *)_(e_invrange); |
3969 | } |
3970 | break; |
3971 | case ADDR_TABS: |
3972 | if (eap->line2 > LAST_TAB_NR) { |
3973 | return (char_u *)_(e_invrange); |
3974 | } |
3975 | break; |
3976 | case ADDR_TABS_RELATIVE: |
3977 | // Do nothing |
3978 | break; |
3979 | case ADDR_QUICKFIX: |
3980 | assert(eap->line2 >= 0); |
3981 | if (eap->line2 != 1 && (size_t)eap->line2 > qf_get_size(eap)) { |
3982 | return (char_u *)_(e_invrange); |
3983 | } |
3984 | break; |
3985 | } |
3986 | } |
3987 | return NULL; |
3988 | } |
3989 | |
3990 | /* |
3991 | * Correct the range for zero line number, if required. |
3992 | */ |
3993 | static void correct_range(exarg_T *eap) |
3994 | { |
3995 | if (!(eap->argt & ZEROR)) { /* zero in range not allowed */ |
3996 | if (eap->line1 == 0) |
3997 | eap->line1 = 1; |
3998 | if (eap->line2 == 0) |
3999 | eap->line2 = 1; |
4000 | } |
4001 | } |
4002 | |
4003 | |
4004 | /* |
4005 | * For a ":vimgrep" or ":vimgrepadd" command return a pointer past the |
4006 | * pattern. Otherwise return eap->arg. |
4007 | */ |
4008 | static char_u *skip_grep_pat(exarg_T *eap) |
4009 | { |
4010 | char_u *p = eap->arg; |
4011 | |
4012 | if (*p != NUL && (eap->cmdidx == CMD_vimgrep || eap->cmdidx == CMD_lvimgrep |
4013 | || eap->cmdidx == CMD_vimgrepadd |
4014 | || eap->cmdidx == CMD_lvimgrepadd |
4015 | || grep_internal(eap->cmdidx))) { |
4016 | p = skip_vimgrep_pat(p, NULL, NULL); |
4017 | if (p == NULL) |
4018 | p = eap->arg; |
4019 | } |
4020 | return p; |
4021 | } |
4022 | |
4023 | /* |
4024 | * For the ":make" and ":grep" commands insert the 'makeprg'/'grepprg' option |
4025 | * in the command line, so that things like % get expanded. |
4026 | */ |
4027 | static char_u *replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep) |
4028 | { |
4029 | char_u *new_cmdline; |
4030 | char_u *program; |
4031 | char_u *pos; |
4032 | char_u *ptr; |
4033 | int len; |
4034 | int i; |
4035 | |
4036 | /* |
4037 | * Don't do it when ":vimgrep" is used for ":grep". |
4038 | */ |
4039 | if ((eap->cmdidx == CMD_make || eap->cmdidx == CMD_lmake |
4040 | || eap->cmdidx == CMD_grep || eap->cmdidx == CMD_lgrep |
4041 | || eap->cmdidx == CMD_grepadd |
4042 | || eap->cmdidx == CMD_lgrepadd) |
4043 | && !grep_internal(eap->cmdidx)) { |
4044 | if (eap->cmdidx == CMD_grep || eap->cmdidx == CMD_lgrep |
4045 | || eap->cmdidx == CMD_grepadd || eap->cmdidx == CMD_lgrepadd) { |
4046 | if (*curbuf->b_p_gp == NUL) |
4047 | program = p_gp; |
4048 | else |
4049 | program = curbuf->b_p_gp; |
4050 | } else { |
4051 | if (*curbuf->b_p_mp == NUL) |
4052 | program = p_mp; |
4053 | else |
4054 | program = curbuf->b_p_mp; |
4055 | } |
4056 | |
4057 | p = skipwhite(p); |
4058 | |
4059 | if ((pos = (char_u *)strstr((char *)program, "$*" )) != NULL) { |
4060 | /* replace $* by given arguments */ |
4061 | i = 1; |
4062 | while ((pos = (char_u *)strstr((char *)pos + 2, "$*" )) != NULL) |
4063 | ++i; |
4064 | len = (int)STRLEN(p); |
4065 | new_cmdline = xmalloc(STRLEN(program) + i * (len - 2) + 1); |
4066 | ptr = new_cmdline; |
4067 | while ((pos = (char_u *)strstr((char *)program, "$*" )) != NULL) { |
4068 | i = (int)(pos - program); |
4069 | memcpy(ptr, program, i); |
4070 | STRCPY(ptr += i, p); |
4071 | ptr += len; |
4072 | program = pos + 2; |
4073 | } |
4074 | STRCPY(ptr, program); |
4075 | } else { |
4076 | new_cmdline = xmalloc(STRLEN(program) + STRLEN(p) + 2); |
4077 | STRCPY(new_cmdline, program); |
4078 | STRCAT(new_cmdline, " " ); |
4079 | STRCAT(new_cmdline, p); |
4080 | } |
4081 | msg_make(p); |
4082 | |
4083 | /* 'eap->cmd' is not set here, because it is not used at CMD_make */ |
4084 | xfree(*cmdlinep); |
4085 | *cmdlinep = new_cmdline; |
4086 | p = new_cmdline; |
4087 | } |
4088 | return p; |
4089 | } |
4090 | |
4091 | // Expand file name in Ex command argument. |
4092 | // When an error is detected, "errormsgp" is set to a non-NULL pointer. |
4093 | // Return FAIL for failure, OK otherwise. |
4094 | int expand_filename(exarg_T *eap, char_u **cmdlinep, char_u **errormsgp) |
4095 | { |
4096 | int has_wildcards; /* need to expand wildcards */ |
4097 | char_u *repl; |
4098 | size_t srclen; |
4099 | char_u *p; |
4100 | int escaped; |
4101 | |
4102 | /* Skip a regexp pattern for ":vimgrep[add] pat file..." */ |
4103 | p = skip_grep_pat(eap); |
4104 | |
4105 | /* |
4106 | * Decide to expand wildcards *before* replacing '%', '#', etc. If |
4107 | * the file name contains a wildcard it should not cause expanding. |
4108 | * (it will be expanded anyway if there is a wildcard before replacing). |
4109 | */ |
4110 | has_wildcards = path_has_wildcard(p); |
4111 | while (*p != NUL) { |
4112 | /* Skip over `=expr`, wildcards in it are not expanded. */ |
4113 | if (p[0] == '`' && p[1] == '=') { |
4114 | p += 2; |
4115 | (void)skip_expr(&p); |
4116 | if (*p == '`') |
4117 | ++p; |
4118 | continue; |
4119 | } |
4120 | /* |
4121 | * Quick check if this cannot be the start of a special string. |
4122 | * Also removes backslash before '%', '#' and '<'. |
4123 | */ |
4124 | if (vim_strchr((char_u *)"%#<" , *p) == NULL) { |
4125 | ++p; |
4126 | continue; |
4127 | } |
4128 | |
4129 | /* |
4130 | * Try to find a match at this position. |
4131 | */ |
4132 | repl = eval_vars(p, eap->arg, &srclen, &(eap->do_ecmd_lnum), |
4133 | errormsgp, &escaped); |
4134 | if (*errormsgp != NULL) /* error detected */ |
4135 | return FAIL; |
4136 | if (repl == NULL) { /* no match found */ |
4137 | p += srclen; |
4138 | continue; |
4139 | } |
4140 | |
4141 | /* Wildcards won't be expanded below, the replacement is taken |
4142 | * literally. But do expand "~/file", "~user/file" and "$HOME/file". */ |
4143 | if (vim_strchr(repl, '$') != NULL || vim_strchr(repl, '~') != NULL) { |
4144 | char_u *l = repl; |
4145 | |
4146 | repl = expand_env_save(repl); |
4147 | xfree(l); |
4148 | } |
4149 | |
4150 | /* Need to escape white space et al. with a backslash. |
4151 | * Don't do this for: |
4152 | * - replacement that already has been escaped: "##" |
4153 | * - shell commands (may have to use quotes instead). |
4154 | */ |
4155 | if (!eap->usefilter |
4156 | && !escaped |
4157 | && eap->cmdidx != CMD_bang |
4158 | && eap->cmdidx != CMD_grep |
4159 | && eap->cmdidx != CMD_grepadd |
4160 | && eap->cmdidx != CMD_hardcopy |
4161 | && eap->cmdidx != CMD_lgrep |
4162 | && eap->cmdidx != CMD_lgrepadd |
4163 | && eap->cmdidx != CMD_lmake |
4164 | && eap->cmdidx != CMD_make |
4165 | && eap->cmdidx != CMD_terminal |
4166 | && !(eap->argt & NOSPC) |
4167 | ) { |
4168 | char_u *l; |
4169 | #ifdef BACKSLASH_IN_FILENAME |
4170 | /* Don't escape a backslash here, because rem_backslash() doesn't |
4171 | * remove it later. */ |
4172 | static char_u *nobslash = (char_u *)" \t\"|" ; |
4173 | # define ESCAPE_CHARS nobslash |
4174 | #else |
4175 | # define ESCAPE_CHARS escape_chars |
4176 | #endif |
4177 | |
4178 | for (l = repl; *l; ++l) |
4179 | if (vim_strchr(ESCAPE_CHARS, *l) != NULL) { |
4180 | l = vim_strsave_escaped(repl, ESCAPE_CHARS); |
4181 | xfree(repl); |
4182 | repl = l; |
4183 | break; |
4184 | } |
4185 | } |
4186 | |
4187 | // For a shell command a '!' must be escaped. |
4188 | if ((eap->usefilter |
4189 | || eap->cmdidx == CMD_bang |
4190 | || eap->cmdidx == CMD_terminal) |
4191 | && vim_strpbrk(repl, (char_u *)"!" ) != NULL) { |
4192 | char_u *l; |
4193 | |
4194 | l = vim_strsave_escaped(repl, (char_u *)"!" ); |
4195 | xfree(repl); |
4196 | repl = l; |
4197 | } |
4198 | |
4199 | p = repl_cmdline(eap, p, srclen, repl, cmdlinep); |
4200 | xfree(repl); |
4201 | } |
4202 | |
4203 | /* |
4204 | * One file argument: Expand wildcards. |
4205 | * Don't do this with ":r !command" or ":w !command". |
4206 | */ |
4207 | if ((eap->argt & NOSPC) && !eap->usefilter) { |
4208 | // Replace environment variables. |
4209 | if (has_wildcards) { |
4210 | /* |
4211 | * May expand environment variables. This |
4212 | * can be done much faster with expand_env() than with |
4213 | * something else (e.g., calling a shell). |
4214 | * After expanding environment variables, check again |
4215 | * if there are still wildcards present. |
4216 | */ |
4217 | if (vim_strchr(eap->arg, '$') != NULL |
4218 | || vim_strchr(eap->arg, '~') != NULL) { |
4219 | expand_env_esc(eap->arg, NameBuff, MAXPATHL, |
4220 | TRUE, TRUE, NULL); |
4221 | has_wildcards = path_has_wildcard(NameBuff); |
4222 | p = NameBuff; |
4223 | } else |
4224 | p = NULL; |
4225 | if (p != NULL) { |
4226 | (void)repl_cmdline(eap, eap->arg, STRLEN(eap->arg), p, cmdlinep); |
4227 | } |
4228 | } |
4229 | |
4230 | /* |
4231 | * Halve the number of backslashes (this is Vi compatible). |
4232 | * For Unix, when wildcards are expanded, this is |
4233 | * done by ExpandOne() below. |
4234 | */ |
4235 | #ifdef UNIX |
4236 | if (!has_wildcards) |
4237 | #endif |
4238 | backslash_halve(eap->arg); |
4239 | |
4240 | if (has_wildcards) { |
4241 | expand_T xpc; |
4242 | int options = WILD_LIST_NOTFOUND|WILD_ADD_SLASH; |
4243 | |
4244 | ExpandInit(&xpc); |
4245 | xpc.xp_context = EXPAND_FILES; |
4246 | if (p_wic) |
4247 | options += WILD_ICASE; |
4248 | p = ExpandOne(&xpc, eap->arg, NULL, options, WILD_EXPAND_FREE); |
4249 | if (p == NULL) { |
4250 | return FAIL; |
4251 | } |
4252 | (void)repl_cmdline(eap, eap->arg, STRLEN(eap->arg), p, cmdlinep); |
4253 | xfree(p); |
4254 | } |
4255 | } |
4256 | return OK; |
4257 | } |
4258 | |
4259 | /* |
4260 | * Replace part of the command line, keeping eap->cmd, eap->arg and |
4261 | * eap->nextcmd correct. |
4262 | * "src" points to the part that is to be replaced, of length "srclen". |
4263 | * "repl" is the replacement string. |
4264 | * Returns a pointer to the character after the replaced string. |
4265 | */ |
4266 | static char_u *repl_cmdline(exarg_T *eap, char_u *src, size_t srclen, |
4267 | char_u *repl, char_u **cmdlinep) |
4268 | { |
4269 | /* |
4270 | * The new command line is build in new_cmdline[]. |
4271 | * First allocate it. |
4272 | * Careful: a "+cmd" argument may have been NUL terminated. |
4273 | */ |
4274 | size_t len = STRLEN(repl); |
4275 | size_t i = (size_t)(src - *cmdlinep) + STRLEN(src + srclen) + len + 3; |
4276 | if (eap->nextcmd != NULL) |
4277 | i += STRLEN(eap->nextcmd); /* add space for next command */ |
4278 | char_u *new_cmdline = xmalloc(i); |
4279 | |
4280 | /* |
4281 | * Copy the stuff before the expanded part. |
4282 | * Copy the expanded stuff. |
4283 | * Copy what came after the expanded part. |
4284 | * Copy the next commands, if there are any. |
4285 | */ |
4286 | i = (size_t)(src - *cmdlinep); /* length of part before match */ |
4287 | memmove(new_cmdline, *cmdlinep, i); |
4288 | |
4289 | memmove(new_cmdline + i, repl, len); |
4290 | i += len; /* remember the end of the string */ |
4291 | STRCPY(new_cmdline + i, src + srclen); |
4292 | src = new_cmdline + i; /* remember where to continue */ |
4293 | |
4294 | if (eap->nextcmd != NULL) { /* append next command */ |
4295 | i = STRLEN(new_cmdline) + 1; |
4296 | STRCPY(new_cmdline + i, eap->nextcmd); |
4297 | eap->nextcmd = new_cmdline + i; |
4298 | } |
4299 | eap->cmd = new_cmdline + (eap->cmd - *cmdlinep); |
4300 | eap->arg = new_cmdline + (eap->arg - *cmdlinep); |
4301 | if (eap->do_ecmd_cmd != NULL && eap->do_ecmd_cmd != dollar_command) |
4302 | eap->do_ecmd_cmd = new_cmdline + (eap->do_ecmd_cmd - *cmdlinep); |
4303 | xfree(*cmdlinep); |
4304 | *cmdlinep = new_cmdline; |
4305 | |
4306 | return src; |
4307 | } |
4308 | |
4309 | /* |
4310 | * Check for '|' to separate commands and '"' to start comments. |
4311 | */ |
4312 | void separate_nextcmd(exarg_T *eap) |
4313 | { |
4314 | char_u *p; |
4315 | |
4316 | p = skip_grep_pat(eap); |
4317 | |
4318 | for (; *p; MB_PTR_ADV(p)) { |
4319 | if (*p == Ctrl_V) { |
4320 | if (eap->argt & (USECTRLV | XFILE)) |
4321 | ++p; /* skip CTRL-V and next char */ |
4322 | else |
4323 | /* remove CTRL-V and skip next char */ |
4324 | STRMOVE(p, p + 1); |
4325 | if (*p == NUL) /* stop at NUL after CTRL-V */ |
4326 | break; |
4327 | } |
4328 | /* Skip over `=expr` when wildcards are expanded. */ |
4329 | else if (p[0] == '`' && p[1] == '=' && (eap->argt & XFILE)) { |
4330 | p += 2; |
4331 | (void)skip_expr(&p); |
4332 | } |
4333 | /* Check for '"': start of comment or '|': next command */ |
4334 | /* :@" does not start a comment! |
4335 | * :redir @" doesn't either. */ |
4336 | else if ((*p == '"' && !(eap->argt & NOTRLCOM) |
4337 | && (eap->cmdidx != CMD_at || p != eap->arg) |
4338 | && (eap->cmdidx != CMD_redir |
4339 | || p != eap->arg + 1 || p[-1] != '@')) |
4340 | || *p == '|' || *p == '\n') { |
4341 | /* |
4342 | * We remove the '\' before the '|', unless USECTRLV is used |
4343 | * AND 'b' is present in 'cpoptions'. |
4344 | */ |
4345 | if ((vim_strchr(p_cpo, CPO_BAR) == NULL |
4346 | || !(eap->argt & USECTRLV)) && *(p - 1) == '\\') { |
4347 | STRMOVE(p - 1, p); /* remove the '\' */ |
4348 | --p; |
4349 | } else { |
4350 | eap->nextcmd = check_nextcmd(p); |
4351 | *p = NUL; |
4352 | break; |
4353 | } |
4354 | } |
4355 | } |
4356 | |
4357 | if (!(eap->argt & NOTRLCOM)) /* remove trailing spaces */ |
4358 | del_trailing_spaces(eap->arg); |
4359 | } |
4360 | |
4361 | /* |
4362 | * get + command from ex argument |
4363 | */ |
4364 | static char_u *getargcmd(char_u **argp) |
4365 | { |
4366 | char_u *arg = *argp; |
4367 | char_u *command = NULL; |
4368 | |
4369 | if (*arg == '+') { /* +[command] */ |
4370 | ++arg; |
4371 | if (ascii_isspace(*arg) || *arg == '\0') |
4372 | command = dollar_command; |
4373 | else { |
4374 | command = arg; |
4375 | arg = skip_cmd_arg(command, TRUE); |
4376 | if (*arg != NUL) |
4377 | *arg++ = NUL; /* terminate command with NUL */ |
4378 | } |
4379 | |
4380 | arg = skipwhite(arg); /* skip over spaces */ |
4381 | *argp = arg; |
4382 | } |
4383 | return command; |
4384 | } |
4385 | |
4386 | /* |
4387 | * Find end of "+command" argument. Skip over "\ " and "\\". |
4388 | */ |
4389 | static char_u * |
4390 | skip_cmd_arg ( |
4391 | char_u *p, |
4392 | int rembs /* TRUE to halve the number of backslashes */ |
4393 | ) |
4394 | { |
4395 | while (*p && !ascii_isspace(*p)) { |
4396 | if (*p == '\\' && p[1] != NUL) { |
4397 | if (rembs) |
4398 | STRMOVE(p, p + 1); |
4399 | else |
4400 | ++p; |
4401 | } |
4402 | MB_PTR_ADV(p); |
4403 | } |
4404 | return p; |
4405 | } |
4406 | |
4407 | /* |
4408 | * Get "++opt=arg" argument. |
4409 | * Return FAIL or OK. |
4410 | */ |
4411 | static int getargopt(exarg_T *eap) |
4412 | { |
4413 | char_u *arg = eap->arg + 2; |
4414 | int *pp = NULL; |
4415 | int bad_char_idx; |
4416 | char_u *p; |
4417 | |
4418 | /* ":edit ++[no]bin[ary] file" */ |
4419 | if (STRNCMP(arg, "bin" , 3) == 0 || STRNCMP(arg, "nobin" , 5) == 0) { |
4420 | if (*arg == 'n') { |
4421 | arg += 2; |
4422 | eap->force_bin = FORCE_NOBIN; |
4423 | } else |
4424 | eap->force_bin = FORCE_BIN; |
4425 | if (!checkforcmd(&arg, "binary" , 3)) |
4426 | return FAIL; |
4427 | eap->arg = skipwhite(arg); |
4428 | return OK; |
4429 | } |
4430 | |
4431 | /* ":read ++edit file" */ |
4432 | if (STRNCMP(arg, "edit" , 4) == 0) { |
4433 | eap->read_edit = TRUE; |
4434 | eap->arg = skipwhite(arg + 4); |
4435 | return OK; |
4436 | } |
4437 | |
4438 | if (STRNCMP(arg, "ff" , 2) == 0) { |
4439 | arg += 2; |
4440 | pp = &eap->force_ff; |
4441 | } else if (STRNCMP(arg, "fileformat" , 10) == 0) { |
4442 | arg += 10; |
4443 | pp = &eap->force_ff; |
4444 | } else if (STRNCMP(arg, "enc" , 3) == 0) { |
4445 | if (STRNCMP(arg, "encoding" , 8) == 0) |
4446 | arg += 8; |
4447 | else |
4448 | arg += 3; |
4449 | pp = &eap->force_enc; |
4450 | } else if (STRNCMP(arg, "bad" , 3) == 0) { |
4451 | arg += 3; |
4452 | pp = &bad_char_idx; |
4453 | } |
4454 | |
4455 | if (pp == NULL || *arg != '=') |
4456 | return FAIL; |
4457 | |
4458 | ++arg; |
4459 | *pp = (int)(arg - eap->cmd); |
4460 | arg = skip_cmd_arg(arg, FALSE); |
4461 | eap->arg = skipwhite(arg); |
4462 | *arg = NUL; |
4463 | |
4464 | if (pp == &eap->force_ff) { |
4465 | if (check_ff_value(eap->cmd + eap->force_ff) == FAIL) |
4466 | return FAIL; |
4467 | } else if (pp == &eap->force_enc) { |
4468 | /* Make 'fileencoding' lower case. */ |
4469 | for (p = eap->cmd + eap->force_enc; *p != NUL; ++p) |
4470 | *p = TOLOWER_ASC(*p); |
4471 | } else { |
4472 | /* Check ++bad= argument. Must be a single-byte character, "keep" or |
4473 | * "drop". */ |
4474 | p = eap->cmd + bad_char_idx; |
4475 | if (STRICMP(p, "keep" ) == 0) |
4476 | eap->bad_char = BAD_KEEP; |
4477 | else if (STRICMP(p, "drop" ) == 0) |
4478 | eap->bad_char = BAD_DROP; |
4479 | else if (MB_BYTE2LEN(*p) == 1 && p[1] == NUL) |
4480 | eap->bad_char = *p; |
4481 | else |
4482 | return FAIL; |
4483 | } |
4484 | |
4485 | return OK; |
4486 | } |
4487 | |
4488 | /// Handle the argument for a tabpage related ex command. |
4489 | /// Returns a tabpage number. |
4490 | /// When an error is encountered then eap->errmsg is set. |
4491 | static int get_tabpage_arg(exarg_T *eap) |
4492 | { |
4493 | int tab_number = 0; |
4494 | int unaccept_arg0 = (eap->cmdidx == CMD_tabmove) ? 0 : 1; |
4495 | |
4496 | if (eap->arg && *eap->arg != NUL) { |
4497 | char_u *p = eap->arg; |
4498 | char_u *p_save; |
4499 | int relative = 0; // argument +N/-N means: go to N places to the |
4500 | // right/left relative to the current position. |
4501 | |
4502 | if (*p == '-') { |
4503 | relative = -1; |
4504 | p++; |
4505 | } else if (*p == '+') { |
4506 | relative = 1; |
4507 | p++; |
4508 | } |
4509 | |
4510 | p_save = p; |
4511 | tab_number = getdigits(&p, false, tab_number); |
4512 | |
4513 | if (relative == 0) { |
4514 | if (STRCMP(p, "$" ) == 0) { |
4515 | tab_number = LAST_TAB_NR; |
4516 | } else if (p == p_save || *p_save == '-' || *p != NUL |
4517 | || tab_number > LAST_TAB_NR) { |
4518 | // No numbers as argument. |
4519 | eap->errmsg = e_invarg; |
4520 | goto theend; |
4521 | } |
4522 | } else { |
4523 | if (*p_save == NUL) { |
4524 | tab_number = 1; |
4525 | } |
4526 | else if (p == p_save || *p_save == '-' || *p != NUL || tab_number == 0) { |
4527 | // No numbers as argument. |
4528 | eap->errmsg = e_invarg; |
4529 | goto theend; |
4530 | } |
4531 | tab_number = tab_number * relative + tabpage_index(curtab); |
4532 | if (!unaccept_arg0 && relative == -1) { |
4533 | --tab_number; |
4534 | } |
4535 | } |
4536 | if (tab_number < unaccept_arg0 || tab_number > LAST_TAB_NR) { |
4537 | eap->errmsg = e_invarg; |
4538 | } |
4539 | } else if (eap->addr_count > 0) { |
4540 | if (unaccept_arg0 && eap->line2 == 0) { |
4541 | eap->errmsg = e_invrange; |
4542 | tab_number = 0; |
4543 | } else { |
4544 | tab_number = eap->line2; |
4545 | if (!unaccept_arg0 && *skipwhite(*eap->cmdlinep) == '-') { |
4546 | tab_number--; |
4547 | if (tab_number < unaccept_arg0) { |
4548 | eap->errmsg = e_invarg; |
4549 | } |
4550 | } |
4551 | } |
4552 | } else { |
4553 | switch (eap->cmdidx) { |
4554 | case CMD_tabnext: |
4555 | tab_number = tabpage_index(curtab) + 1; |
4556 | if (tab_number > LAST_TAB_NR) |
4557 | tab_number = 1; |
4558 | break; |
4559 | case CMD_tabmove: |
4560 | tab_number = LAST_TAB_NR; |
4561 | break; |
4562 | default: |
4563 | tab_number = tabpage_index(curtab); |
4564 | } |
4565 | } |
4566 | |
4567 | theend: |
4568 | return tab_number; |
4569 | } |
4570 | |
4571 | /* |
4572 | * ":abbreviate" and friends. |
4573 | */ |
4574 | static void ex_abbreviate(exarg_T *eap) |
4575 | { |
4576 | do_exmap(eap, TRUE); /* almost the same as mapping */ |
4577 | } |
4578 | |
4579 | /* |
4580 | * ":map" and friends. |
4581 | */ |
4582 | static void ex_map(exarg_T *eap) |
4583 | { |
4584 | /* |
4585 | * If we are sourcing .exrc or .vimrc in current directory we |
4586 | * print the mappings for security reasons. |
4587 | */ |
4588 | if (secure) { |
4589 | secure = 2; |
4590 | msg_outtrans(eap->cmd); |
4591 | msg_putchar('\n'); |
4592 | } |
4593 | do_exmap(eap, FALSE); |
4594 | } |
4595 | |
4596 | /* |
4597 | * ":unmap" and friends. |
4598 | */ |
4599 | static void ex_unmap(exarg_T *eap) |
4600 | { |
4601 | do_exmap(eap, FALSE); |
4602 | } |
4603 | |
4604 | /* |
4605 | * ":mapclear" and friends. |
4606 | */ |
4607 | static void ex_mapclear(exarg_T *eap) |
4608 | { |
4609 | map_clear_mode(eap->cmd, eap->arg, eap->forceit, false); |
4610 | } |
4611 | |
4612 | /* |
4613 | * ":abclear" and friends. |
4614 | */ |
4615 | static void ex_abclear(exarg_T *eap) |
4616 | { |
4617 | map_clear_mode(eap->cmd, eap->arg, true, true); |
4618 | } |
4619 | |
4620 | static void ex_autocmd(exarg_T *eap) |
4621 | { |
4622 | /* |
4623 | * Disallow auto commands from .exrc and .vimrc in current |
4624 | * directory for security reasons. |
4625 | */ |
4626 | if (secure) { |
4627 | secure = 2; |
4628 | eap->errmsg = e_curdir; |
4629 | } else if (eap->cmdidx == CMD_autocmd) |
4630 | do_autocmd(eap->arg, eap->forceit); |
4631 | else |
4632 | do_augroup(eap->arg, eap->forceit); |
4633 | } |
4634 | |
4635 | /* |
4636 | * ":doautocmd": Apply the automatic commands to the current buffer. |
4637 | */ |
4638 | static void ex_doautocmd(exarg_T *eap) |
4639 | { |
4640 | char_u *arg = eap->arg; |
4641 | int call_do_modelines = check_nomodeline(&arg); |
4642 | bool did_aucmd; |
4643 | |
4644 | (void)do_doautocmd(arg, false, &did_aucmd); |
4645 | // Only when there is no <nomodeline>. |
4646 | if (call_do_modelines && did_aucmd) { |
4647 | do_modelines(0); |
4648 | } |
4649 | } |
4650 | |
4651 | /* |
4652 | * :[N]bunload[!] [N] [bufname] unload buffer |
4653 | * :[N]bdelete[!] [N] [bufname] delete buffer from buffer list |
4654 | * :[N]bwipeout[!] [N] [bufname] delete buffer really |
4655 | */ |
4656 | static void ex_bunload(exarg_T *eap) |
4657 | { |
4658 | eap->errmsg = do_bufdel( |
4659 | eap->cmdidx == CMD_bdelete ? DOBUF_DEL |
4660 | : eap->cmdidx == CMD_bwipeout ? DOBUF_WIPE |
4661 | : DOBUF_UNLOAD, eap->arg, |
4662 | eap->addr_count, (int)eap->line1, (int)eap->line2, eap->forceit); |
4663 | } |
4664 | |
4665 | /* |
4666 | * :[N]buffer [N] to buffer N |
4667 | * :[N]sbuffer [N] to buffer N |
4668 | */ |
4669 | static void ex_buffer(exarg_T *eap) |
4670 | { |
4671 | if (*eap->arg) { |
4672 | eap->errmsg = e_trailing; |
4673 | } else { |
4674 | if (eap->addr_count == 0) { // default is current buffer |
4675 | goto_buffer(eap, DOBUF_CURRENT, FORWARD, 0); |
4676 | } else { |
4677 | goto_buffer(eap, DOBUF_FIRST, FORWARD, (int)eap->line2); |
4678 | } |
4679 | if (eap->do_ecmd_cmd != NULL) { |
4680 | do_cmdline_cmd((char *)eap->do_ecmd_cmd); |
4681 | } |
4682 | } |
4683 | } |
4684 | |
4685 | /* |
4686 | * :[N]bmodified [N] to next mod. buffer |
4687 | * :[N]sbmodified [N] to next mod. buffer |
4688 | */ |
4689 | static void ex_bmodified(exarg_T *eap) |
4690 | { |
4691 | goto_buffer(eap, DOBUF_MOD, FORWARD, (int)eap->line2); |
4692 | if (eap->do_ecmd_cmd != NULL) { |
4693 | do_cmdline_cmd((char *)eap->do_ecmd_cmd); |
4694 | } |
4695 | } |
4696 | |
4697 | /* |
4698 | * :[N]bnext [N] to next buffer |
4699 | * :[N]sbnext [N] split and to next buffer |
4700 | */ |
4701 | static void ex_bnext(exarg_T *eap) |
4702 | { |
4703 | goto_buffer(eap, DOBUF_CURRENT, FORWARD, (int)eap->line2); |
4704 | if (eap->do_ecmd_cmd != NULL) { |
4705 | do_cmdline_cmd((char *)eap->do_ecmd_cmd); |
4706 | } |
4707 | } |
4708 | |
4709 | /* |
4710 | * :[N]bNext [N] to previous buffer |
4711 | * :[N]bprevious [N] to previous buffer |
4712 | * :[N]sbNext [N] split and to previous buffer |
4713 | * :[N]sbprevious [N] split and to previous buffer |
4714 | */ |
4715 | static void ex_bprevious(exarg_T *eap) |
4716 | { |
4717 | goto_buffer(eap, DOBUF_CURRENT, BACKWARD, (int)eap->line2); |
4718 | if (eap->do_ecmd_cmd != NULL) { |
4719 | do_cmdline_cmd((char *)eap->do_ecmd_cmd); |
4720 | } |
4721 | } |
4722 | |
4723 | /* |
4724 | * :brewind to first buffer |
4725 | * :bfirst to first buffer |
4726 | * :sbrewind split and to first buffer |
4727 | * :sbfirst split and to first buffer |
4728 | */ |
4729 | static void ex_brewind(exarg_T *eap) |
4730 | { |
4731 | goto_buffer(eap, DOBUF_FIRST, FORWARD, 0); |
4732 | if (eap->do_ecmd_cmd != NULL) { |
4733 | do_cmdline_cmd((char *)eap->do_ecmd_cmd); |
4734 | } |
4735 | } |
4736 | |
4737 | /* |
4738 | * :blast to last buffer |
4739 | * :sblast split and to last buffer |
4740 | */ |
4741 | static void ex_blast(exarg_T *eap) |
4742 | { |
4743 | goto_buffer(eap, DOBUF_LAST, BACKWARD, 0); |
4744 | if (eap->do_ecmd_cmd != NULL) { |
4745 | do_cmdline_cmd((char *)eap->do_ecmd_cmd); |
4746 | } |
4747 | } |
4748 | |
4749 | int ends_excmd(int c) FUNC_ATTR_CONST |
4750 | { |
4751 | return c == NUL || c == '|' || c == '"' || c == '\n'; |
4752 | } |
4753 | |
4754 | /* |
4755 | * Return the next command, after the first '|' or '\n'. |
4756 | * Return NULL if not found. |
4757 | */ |
4758 | char_u *find_nextcmd(const char_u *p) |
4759 | { |
4760 | while (*p != '|' && *p != '\n') { |
4761 | if (*p == NUL) { |
4762 | return NULL; |
4763 | } |
4764 | p++; |
4765 | } |
4766 | return (char_u *)p + 1; |
4767 | } |
4768 | |
4769 | /// Check if *p is a separator between Ex commands, skipping over white space. |
4770 | /// Return NULL if it isn't, the following character if it is. |
4771 | char_u *check_nextcmd(char_u *p) |
4772 | { |
4773 | char_u *s = skipwhite(p); |
4774 | |
4775 | if (*s == '|' || *s == '\n') { |
4776 | return (s + 1); |
4777 | } else { |
4778 | return NULL; |
4779 | } |
4780 | } |
4781 | |
4782 | /* |
4783 | * - if there are more files to edit |
4784 | * - and this is the last window |
4785 | * - and forceit not used |
4786 | * - and not repeated twice on a row |
4787 | * return FAIL and give error message if 'message' TRUE |
4788 | * return OK otherwise |
4789 | */ |
4790 | static int |
4791 | check_more( |
4792 | int message, // when FALSE check only, no messages |
4793 | int forceit |
4794 | ) |
4795 | { |
4796 | int n = ARGCOUNT - curwin->w_arg_idx - 1; |
4797 | |
4798 | if (!forceit && only_one_window() |
4799 | && ARGCOUNT > 1 && !arg_had_last && n >= 0 && quitmore == 0) { |
4800 | if (message) { |
4801 | if ((p_confirm || cmdmod.confirm) && curbuf->b_fname != NULL) { |
4802 | char_u buff[DIALOG_MSG_SIZE]; |
4803 | |
4804 | if (n == 1) |
4805 | STRLCPY(buff, _("1 more file to edit. Quit anyway?" ), |
4806 | DIALOG_MSG_SIZE); |
4807 | else |
4808 | vim_snprintf((char *)buff, DIALOG_MSG_SIZE, |
4809 | _("%d more files to edit. Quit anyway?" ), n); |
4810 | if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 1) == VIM_YES) |
4811 | return OK; |
4812 | return FAIL; |
4813 | } |
4814 | if (n == 1) |
4815 | EMSG(_("E173: 1 more file to edit" )); |
4816 | else |
4817 | EMSGN(_("E173: %" PRId64 " more files to edit" ), n); |
4818 | quitmore = 2; /* next try to quit is allowed */ |
4819 | } |
4820 | return FAIL; |
4821 | } |
4822 | return OK; |
4823 | } |
4824 | |
4825 | /* |
4826 | * Function given to ExpandGeneric() to obtain the list of command names. |
4827 | */ |
4828 | char_u *get_command_name(expand_T *xp, int idx) |
4829 | { |
4830 | if (idx >= (int)CMD_SIZE) |
4831 | return get_user_command_name(idx); |
4832 | return cmdnames[idx].cmd_name; |
4833 | } |
4834 | |
4835 | static int uc_add_command(char_u *name, size_t name_len, char_u *rep, |
4836 | uint32_t argt, long def, int flags, int compl, |
4837 | char_u *compl_arg, int addr_type, int force) |
4838 | { |
4839 | ucmd_T *cmd = NULL; |
4840 | char_u *p; |
4841 | int i; |
4842 | int cmp = 1; |
4843 | char_u *rep_buf = NULL; |
4844 | garray_T *gap; |
4845 | |
4846 | replace_termcodes(rep, STRLEN(rep), &rep_buf, false, false, true, |
4847 | CPO_TO_CPO_FLAGS); |
4848 | if (rep_buf == NULL) { |
4849 | /* Can't replace termcodes - try using the string as is */ |
4850 | rep_buf = vim_strsave(rep); |
4851 | } |
4852 | |
4853 | /* get address of growarray: global or in curbuf */ |
4854 | if (flags & UC_BUFFER) { |
4855 | gap = &curbuf->b_ucmds; |
4856 | if (gap->ga_itemsize == 0) |
4857 | ga_init(gap, (int)sizeof(ucmd_T), 4); |
4858 | } else |
4859 | gap = &ucmds; |
4860 | |
4861 | /* Search for the command in the already defined commands. */ |
4862 | for (i = 0; i < gap->ga_len; ++i) { |
4863 | size_t len; |
4864 | |
4865 | cmd = USER_CMD_GA(gap, i); |
4866 | len = STRLEN(cmd->uc_name); |
4867 | cmp = STRNCMP(name, cmd->uc_name, name_len); |
4868 | if (cmp == 0) { |
4869 | if (name_len < len) |
4870 | cmp = -1; |
4871 | else if (name_len > len) |
4872 | cmp = 1; |
4873 | } |
4874 | |
4875 | if (cmp == 0) { |
4876 | if (!force) { |
4877 | EMSG(_("E174: Command already exists: add ! to replace it" )); |
4878 | goto fail; |
4879 | } |
4880 | |
4881 | XFREE_CLEAR(cmd->uc_rep); |
4882 | XFREE_CLEAR(cmd->uc_compl_arg); |
4883 | break; |
4884 | } |
4885 | |
4886 | /* Stop as soon as we pass the name to add */ |
4887 | if (cmp < 0) |
4888 | break; |
4889 | } |
4890 | |
4891 | /* Extend the array unless we're replacing an existing command */ |
4892 | if (cmp != 0) { |
4893 | ga_grow(gap, 1); |
4894 | |
4895 | p = vim_strnsave(name, (int)name_len); |
4896 | |
4897 | cmd = USER_CMD_GA(gap, i); |
4898 | memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T)); |
4899 | |
4900 | ++gap->ga_len; |
4901 | |
4902 | cmd->uc_name = p; |
4903 | } |
4904 | |
4905 | cmd->uc_rep = rep_buf; |
4906 | cmd->uc_argt = argt; |
4907 | cmd->uc_def = def; |
4908 | cmd->uc_compl = compl; |
4909 | cmd->uc_script_ctx = current_sctx; |
4910 | cmd->uc_script_ctx.sc_lnum += sourcing_lnum; |
4911 | cmd->uc_compl_arg = compl_arg; |
4912 | cmd->uc_addr_type = addr_type; |
4913 | |
4914 | return OK; |
4915 | |
4916 | fail: |
4917 | xfree(rep_buf); |
4918 | xfree(compl_arg); |
4919 | return FAIL; |
4920 | } |
4921 | |
4922 | |
4923 | static struct { |
4924 | int expand; |
4925 | char *name; |
4926 | } addr_type_complete[] = |
4927 | { |
4928 | { ADDR_ARGUMENTS, "arguments" }, |
4929 | { ADDR_LINES, "lines" }, |
4930 | { ADDR_LOADED_BUFFERS, "loaded_buffers" }, |
4931 | { ADDR_TABS, "tabs" }, |
4932 | { ADDR_BUFFERS, "buffers" }, |
4933 | { ADDR_WINDOWS, "windows" }, |
4934 | { ADDR_QUICKFIX, "quickfix" }, |
4935 | { -1, NULL } |
4936 | }; |
4937 | |
4938 | /* |
4939 | * List of names for completion for ":command" with the EXPAND_ flag. |
4940 | * Must be alphabetical for completion. |
4941 | */ |
4942 | static const char *command_complete[] = |
4943 | { |
4944 | [EXPAND_ARGLIST] = "arglist" , |
4945 | [EXPAND_AUGROUP] = "augroup" , |
4946 | [EXPAND_BEHAVE] = "behave" , |
4947 | [EXPAND_BUFFERS] = "buffer" , |
4948 | [EXPAND_CHECKHEALTH] = "checkhealth" , |
4949 | [EXPAND_COLORS] = "color" , |
4950 | [EXPAND_COMMANDS] = "command" , |
4951 | [EXPAND_COMPILER] = "compiler" , |
4952 | [EXPAND_CSCOPE] = "cscope" , |
4953 | [EXPAND_USER_DEFINED] = "custom" , |
4954 | [EXPAND_USER_LIST] = "customlist" , |
4955 | [EXPAND_DIRECTORIES] = "dir" , |
4956 | [EXPAND_ENV_VARS] = "environment" , |
4957 | [EXPAND_EVENTS] = "event" , |
4958 | [EXPAND_EXPRESSION] = "expression" , |
4959 | [EXPAND_FILES] = "file" , |
4960 | [EXPAND_FILES_IN_PATH] = "file_in_path" , |
4961 | [EXPAND_FILETYPE] = "filetype" , |
4962 | [EXPAND_FUNCTIONS] = "function" , |
4963 | [EXPAND_HELP] = "help" , |
4964 | [EXPAND_HIGHLIGHT] = "highlight" , |
4965 | [EXPAND_HISTORY] = "history" , |
4966 | #ifdef HAVE_WORKING_LIBINTL |
4967 | [EXPAND_LOCALES] = "locale" , |
4968 | #endif |
4969 | [EXPAND_MAPCLEAR] = "mapclear" , |
4970 | [EXPAND_MAPPINGS] = "mapping" , |
4971 | [EXPAND_MENUS] = "menu" , |
4972 | [EXPAND_MESSAGES] = "messages" , |
4973 | [EXPAND_OWNSYNTAX] = "syntax" , |
4974 | [EXPAND_SYNTIME] = "syntime" , |
4975 | [EXPAND_SETTINGS] = "option" , |
4976 | [EXPAND_PACKADD] = "packadd" , |
4977 | [EXPAND_SHELLCMD] = "shellcmd" , |
4978 | [EXPAND_SIGN] = "sign" , |
4979 | [EXPAND_TAGS] = "tag" , |
4980 | [EXPAND_TAGS_LISTFILES] = "tag_listfiles" , |
4981 | [EXPAND_USER] = "user" , |
4982 | [EXPAND_USER_VARS] = "var" , |
4983 | }; |
4984 | |
4985 | static char *get_command_complete(int arg) |
4986 | { |
4987 | if (arg >= (int)(ARRAY_SIZE(command_complete))) { |
4988 | return NULL; |
4989 | } else { |
4990 | return (char *)command_complete[arg]; |
4991 | } |
4992 | } |
4993 | |
4994 | static void uc_list(char_u *name, size_t name_len) |
4995 | { |
4996 | int i, j; |
4997 | int found = FALSE; |
4998 | ucmd_T *cmd; |
4999 | int len; |
5000 | uint32_t a; |
5001 | garray_T *gap; |
5002 | |
5003 | gap = &curbuf->b_ucmds; |
5004 | for (;; ) { |
5005 | for (i = 0; i < gap->ga_len; ++i) { |
5006 | cmd = USER_CMD_GA(gap, i); |
5007 | a = cmd->uc_argt; |
5008 | |
5009 | // Skip commands which don't match the requested prefix and |
5010 | // commands filtered out. |
5011 | if (STRNCMP(name, cmd->uc_name, name_len) != 0 |
5012 | || message_filtered(cmd->uc_name)) { |
5013 | continue; |
5014 | } |
5015 | |
5016 | /* Put out the title first time */ |
5017 | if (!found) |
5018 | MSG_PUTS_TITLE(_("\n Name Args Address Complete Definition" )); |
5019 | found = TRUE; |
5020 | msg_putchar('\n'); |
5021 | if (got_int) |
5022 | break; |
5023 | |
5024 | /* Special cases */ |
5025 | msg_putchar(a & BANG ? '!' : ' '); |
5026 | msg_putchar(a & REGSTR ? '"' : ' '); |
5027 | msg_putchar(gap != &ucmds ? 'b' : ' '); |
5028 | msg_putchar(' '); |
5029 | |
5030 | msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D)); |
5031 | len = (int)STRLEN(cmd->uc_name) + 4; |
5032 | |
5033 | do { |
5034 | msg_putchar(' '); |
5035 | ++len; |
5036 | } while (len < 16); |
5037 | |
5038 | len = 0; |
5039 | |
5040 | /* Arguments */ |
5041 | switch (a & (EXTRA|NOSPC|NEEDARG)) { |
5042 | case 0: IObuff[len++] = '0'; break; |
5043 | case (EXTRA): IObuff[len++] = '*'; break; |
5044 | case (EXTRA|NOSPC): IObuff[len++] = '?'; break; |
5045 | case (EXTRA|NEEDARG): IObuff[len++] = '+'; break; |
5046 | case (EXTRA|NOSPC|NEEDARG): IObuff[len++] = '1'; break; |
5047 | } |
5048 | |
5049 | do { |
5050 | IObuff[len++] = ' '; |
5051 | } while (len < 5); |
5052 | |
5053 | /* Range */ |
5054 | if (a & (RANGE|COUNT)) { |
5055 | if (a & COUNT) { |
5056 | /* -count=N */ |
5057 | sprintf((char *)IObuff + len, "%" PRId64 "c" , (int64_t)cmd->uc_def); |
5058 | len += (int)STRLEN(IObuff + len); |
5059 | } else if (a & DFLALL) |
5060 | IObuff[len++] = '%'; |
5061 | else if (cmd->uc_def >= 0) { |
5062 | /* -range=N */ |
5063 | sprintf((char *)IObuff + len, "%" PRId64 "" , (int64_t)cmd->uc_def); |
5064 | len += (int)STRLEN(IObuff + len); |
5065 | } else |
5066 | IObuff[len++] = '.'; |
5067 | } |
5068 | |
5069 | do { |
5070 | IObuff[len++] = ' '; |
5071 | } while (len < 11); |
5072 | |
5073 | // Address Type |
5074 | for (j = 0; addr_type_complete[j].expand != -1; j++) { |
5075 | if (addr_type_complete[j].expand != ADDR_LINES |
5076 | && addr_type_complete[j].expand == cmd->uc_addr_type) { |
5077 | STRCPY(IObuff + len, addr_type_complete[j].name); |
5078 | len += (int)STRLEN(IObuff + len); |
5079 | break; |
5080 | } |
5081 | } |
5082 | |
5083 | do { |
5084 | IObuff[len++] = ' '; |
5085 | } while (len < 21); |
5086 | |
5087 | // Completion |
5088 | char *cmd_compl = get_command_complete(cmd->uc_compl); |
5089 | if (cmd_compl != NULL) { |
5090 | STRCPY(IObuff + len, get_command_complete(cmd->uc_compl)); |
5091 | len += (int)STRLEN(IObuff + len); |
5092 | } |
5093 | |
5094 | do { |
5095 | IObuff[len++] = ' '; |
5096 | } while (len < 35); |
5097 | |
5098 | IObuff[len] = '\0'; |
5099 | msg_outtrans(IObuff); |
5100 | |
5101 | msg_outtrans_special(cmd->uc_rep, false); |
5102 | if (p_verbose > 0) { |
5103 | last_set_msg(cmd->uc_script_ctx); |
5104 | } |
5105 | line_breakcheck(); |
5106 | if (got_int) { |
5107 | break; |
5108 | } |
5109 | } |
5110 | if (gap == &ucmds || i < gap->ga_len) |
5111 | break; |
5112 | gap = &ucmds; |
5113 | } |
5114 | |
5115 | if (!found) |
5116 | MSG(_("No user-defined commands found" )); |
5117 | } |
5118 | |
5119 | static int uc_scan_attr(char_u *attr, size_t len, uint32_t *argt, long *def, |
5120 | int *flags, int *complp, char_u **compl_arg, |
5121 | int *addr_type_arg) |
5122 | { |
5123 | char_u *p; |
5124 | |
5125 | if (len == 0) { |
5126 | EMSG(_("E175: No attribute specified" )); |
5127 | return FAIL; |
5128 | } |
5129 | |
5130 | /* First, try the simple attributes (no arguments) */ |
5131 | if (STRNICMP(attr, "bang" , len) == 0) |
5132 | *argt |= BANG; |
5133 | else if (STRNICMP(attr, "buffer" , len) == 0) |
5134 | *flags |= UC_BUFFER; |
5135 | else if (STRNICMP(attr, "register" , len) == 0) |
5136 | *argt |= REGSTR; |
5137 | else if (STRNICMP(attr, "bar" , len) == 0) |
5138 | *argt |= TRLBAR; |
5139 | else { |
5140 | int i; |
5141 | char_u *val = NULL; |
5142 | size_t vallen = 0; |
5143 | size_t attrlen = len; |
5144 | |
5145 | /* Look for the attribute name - which is the part before any '=' */ |
5146 | for (i = 0; i < (int)len; ++i) { |
5147 | if (attr[i] == '=') { |
5148 | val = &attr[i + 1]; |
5149 | vallen = len - i - 1; |
5150 | attrlen = i; |
5151 | break; |
5152 | } |
5153 | } |
5154 | |
5155 | if (STRNICMP(attr, "nargs" , attrlen) == 0) { |
5156 | if (vallen == 1) { |
5157 | if (*val == '0') |
5158 | /* Do nothing - this is the default */; |
5159 | else if (*val == '1') |
5160 | *argt |= (EXTRA | NOSPC | NEEDARG); |
5161 | else if (*val == '*') |
5162 | *argt |= EXTRA; |
5163 | else if (*val == '?') |
5164 | *argt |= (EXTRA | NOSPC); |
5165 | else if (*val == '+') |
5166 | *argt |= (EXTRA | NEEDARG); |
5167 | else |
5168 | goto wrong_nargs; |
5169 | } else { |
5170 | wrong_nargs: |
5171 | EMSG(_("E176: Invalid number of arguments" )); |
5172 | return FAIL; |
5173 | } |
5174 | } else if (STRNICMP(attr, "range" , attrlen) == 0) { |
5175 | *argt |= RANGE; |
5176 | if (vallen == 1 && *val == '%') |
5177 | *argt |= DFLALL; |
5178 | else if (val != NULL) { |
5179 | p = val; |
5180 | if (*def >= 0) { |
5181 | two_count: |
5182 | EMSG(_("E177: Count cannot be specified twice" )); |
5183 | return FAIL; |
5184 | } |
5185 | |
5186 | *def = getdigits_long(&p, true, 0); |
5187 | *argt |= (ZEROR | NOTADR); |
5188 | |
5189 | if (p != val + vallen || vallen == 0) { |
5190 | invalid_count: |
5191 | EMSG(_("E178: Invalid default value for count" )); |
5192 | return FAIL; |
5193 | } |
5194 | } |
5195 | } else if (STRNICMP(attr, "count" , attrlen) == 0) { |
5196 | *argt |= (COUNT | ZEROR | RANGE | NOTADR); |
5197 | |
5198 | if (val != NULL) { |
5199 | p = val; |
5200 | if (*def >= 0) |
5201 | goto two_count; |
5202 | |
5203 | *def = getdigits_long(&p, true, 0); |
5204 | |
5205 | if (p != val + vallen) |
5206 | goto invalid_count; |
5207 | } |
5208 | |
5209 | if (*def < 0) |
5210 | *def = 0; |
5211 | } else if (STRNICMP(attr, "complete" , attrlen) == 0) { |
5212 | if (val == NULL) { |
5213 | EMSG(_("E179: argument required for -complete" )); |
5214 | return FAIL; |
5215 | } |
5216 | |
5217 | if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg) |
5218 | == FAIL) { |
5219 | return FAIL; |
5220 | } |
5221 | } else if (STRNICMP(attr, "addr" , attrlen) == 0) { |
5222 | *argt |= RANGE; |
5223 | if (val == NULL) { |
5224 | EMSG(_("E179: argument required for -addr" )); |
5225 | return FAIL; |
5226 | } |
5227 | if (parse_addr_type_arg(val, (int)vallen, argt, addr_type_arg) == FAIL) { |
5228 | return FAIL; |
5229 | } |
5230 | if (addr_type_arg != ADDR_LINES) { |
5231 | *argt |= (ZEROR | NOTADR); |
5232 | } |
5233 | } else { |
5234 | char_u ch = attr[len]; |
5235 | attr[len] = '\0'; |
5236 | EMSG2(_("E181: Invalid attribute: %s" ), attr); |
5237 | attr[len] = ch; |
5238 | return FAIL; |
5239 | } |
5240 | } |
5241 | |
5242 | return OK; |
5243 | } |
5244 | |
5245 | /* |
5246 | * ":command ..." |
5247 | */ |
5248 | static void ex_command(exarg_T *eap) |
5249 | { |
5250 | char_u *name; |
5251 | char_u *end; |
5252 | char_u *p; |
5253 | uint32_t argt = 0; |
5254 | long def = -1; |
5255 | int flags = 0; |
5256 | int compl = EXPAND_NOTHING; |
5257 | char_u *compl_arg = NULL; |
5258 | int addr_type_arg = ADDR_LINES; |
5259 | int has_attr = (eap->arg[0] == '-'); |
5260 | int name_len; |
5261 | |
5262 | p = eap->arg; |
5263 | |
5264 | /* Check for attributes */ |
5265 | while (*p == '-') { |
5266 | ++p; |
5267 | end = skiptowhite(p); |
5268 | if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl, &compl_arg, |
5269 | &addr_type_arg) == FAIL) { |
5270 | return; |
5271 | } |
5272 | p = skipwhite(end); |
5273 | } |
5274 | |
5275 | // Get the name (if any) and skip to the following argument. |
5276 | name = p; |
5277 | if (ASCII_ISALPHA(*p)) { |
5278 | while (ASCII_ISALNUM(*p)) { |
5279 | p++; |
5280 | } |
5281 | } |
5282 | if (!ends_excmd(*p) && !ascii_iswhite(*p)) { |
5283 | EMSG(_("E182: Invalid command name" )); |
5284 | return; |
5285 | } |
5286 | end = p; |
5287 | name_len = (int)(end - name); |
5288 | |
5289 | /* If there is nothing after the name, and no attributes were specified, |
5290 | * we are listing commands |
5291 | */ |
5292 | p = skipwhite(end); |
5293 | if (!has_attr && ends_excmd(*p)) { |
5294 | uc_list(name, end - name); |
5295 | } else if (!ASCII_ISUPPER(*name)) { |
5296 | EMSG(_("E183: User defined commands must start with an uppercase letter" )); |
5297 | return; |
5298 | } else if (name_len <= 4 && STRNCMP(name, "Next" , name_len) == 0) { |
5299 | EMSG(_("E841: Reserved name, cannot be used for user defined command" )); |
5300 | return; |
5301 | } else { |
5302 | uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg, |
5303 | addr_type_arg, eap->forceit); |
5304 | } |
5305 | } |
5306 | |
5307 | /* |
5308 | * ":comclear" |
5309 | * Clear all user commands, global and for current buffer. |
5310 | */ |
5311 | void ex_comclear(exarg_T *eap) |
5312 | { |
5313 | uc_clear(&ucmds); |
5314 | uc_clear(&curbuf->b_ucmds); |
5315 | } |
5316 | |
5317 | static void free_ucmd(ucmd_T* cmd) { |
5318 | xfree(cmd->uc_name); |
5319 | xfree(cmd->uc_rep); |
5320 | xfree(cmd->uc_compl_arg); |
5321 | } |
5322 | |
5323 | /* |
5324 | * Clear all user commands for "gap". |
5325 | */ |
5326 | void uc_clear(garray_T *gap) |
5327 | { |
5328 | GA_DEEP_CLEAR(gap, ucmd_T, free_ucmd); |
5329 | } |
5330 | |
5331 | static void ex_delcommand(exarg_T *eap) |
5332 | { |
5333 | int i = 0; |
5334 | ucmd_T *cmd = NULL; |
5335 | int cmp = -1; |
5336 | garray_T *gap; |
5337 | |
5338 | gap = &curbuf->b_ucmds; |
5339 | for (;; ) { |
5340 | for (i = 0; i < gap->ga_len; ++i) { |
5341 | cmd = USER_CMD_GA(gap, i); |
5342 | cmp = STRCMP(eap->arg, cmd->uc_name); |
5343 | if (cmp <= 0) |
5344 | break; |
5345 | } |
5346 | if (gap == &ucmds || cmp == 0) |
5347 | break; |
5348 | gap = &ucmds; |
5349 | } |
5350 | |
5351 | if (cmp != 0) { |
5352 | EMSG2(_("E184: No such user-defined command: %s" ), eap->arg); |
5353 | return; |
5354 | } |
5355 | |
5356 | xfree(cmd->uc_name); |
5357 | xfree(cmd->uc_rep); |
5358 | xfree(cmd->uc_compl_arg); |
5359 | |
5360 | --gap->ga_len; |
5361 | |
5362 | if (i < gap->ga_len) |
5363 | memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T)); |
5364 | } |
5365 | |
5366 | /* |
5367 | * split and quote args for <f-args> |
5368 | */ |
5369 | static char_u *uc_split_args(char_u *arg, size_t *lenp) |
5370 | { |
5371 | char_u *buf; |
5372 | char_u *p; |
5373 | char_u *q; |
5374 | int len; |
5375 | |
5376 | /* Precalculate length */ |
5377 | p = arg; |
5378 | len = 2; /* Initial and final quotes */ |
5379 | |
5380 | while (*p) { |
5381 | if (p[0] == '\\' && p[1] == '\\') { |
5382 | len += 2; |
5383 | p += 2; |
5384 | } else if (p[0] == '\\' && ascii_iswhite(p[1])) { |
5385 | len += 1; |
5386 | p += 2; |
5387 | } else if (*p == '\\' || *p == '"') { |
5388 | len += 2; |
5389 | p += 1; |
5390 | } else if (ascii_iswhite(*p)) { |
5391 | p = skipwhite(p); |
5392 | if (*p == NUL) |
5393 | break; |
5394 | len += 3; /* "," */ |
5395 | } else { |
5396 | int charlen = (*mb_ptr2len)(p); |
5397 | len += charlen; |
5398 | p += charlen; |
5399 | } |
5400 | } |
5401 | |
5402 | buf = xmalloc(len + 1); |
5403 | |
5404 | p = arg; |
5405 | q = buf; |
5406 | *q++ = '"'; |
5407 | while (*p) { |
5408 | if (p[0] == '\\' && p[1] == '\\') { |
5409 | *q++ = '\\'; |
5410 | *q++ = '\\'; |
5411 | p += 2; |
5412 | } else if (p[0] == '\\' && ascii_iswhite(p[1])) { |
5413 | *q++ = p[1]; |
5414 | p += 2; |
5415 | } else if (*p == '\\' || *p == '"') { |
5416 | *q++ = '\\'; |
5417 | *q++ = *p++; |
5418 | } else if (ascii_iswhite(*p)) { |
5419 | p = skipwhite(p); |
5420 | if (*p == NUL) |
5421 | break; |
5422 | *q++ = '"'; |
5423 | *q++ = ','; |
5424 | *q++ = '"'; |
5425 | } else { |
5426 | MB_COPY_CHAR(p, q); |
5427 | } |
5428 | } |
5429 | *q++ = '"'; |
5430 | *q = 0; |
5431 | |
5432 | *lenp = len; |
5433 | return buf; |
5434 | } |
5435 | |
5436 | static size_t add_cmd_modifier(char_u *buf, char *mod_str, bool *multi_mods) |
5437 | { |
5438 | size_t result = STRLEN(mod_str); |
5439 | if (*multi_mods) { |
5440 | result++; |
5441 | } |
5442 | |
5443 | if (buf != NULL) { |
5444 | if (*multi_mods) { |
5445 | STRCAT(buf, " " ); |
5446 | } |
5447 | STRCAT(buf, mod_str); |
5448 | } |
5449 | |
5450 | *multi_mods = true; |
5451 | return result; |
5452 | } |
5453 | |
5454 | /* |
5455 | * Check for a <> code in a user command. |
5456 | * "code" points to the '<'. "len" the length of the <> (inclusive). |
5457 | * "buf" is where the result is to be added. |
5458 | * "split_buf" points to a buffer used for splitting, caller should free it. |
5459 | * "split_len" is the length of what "split_buf" contains. |
5460 | * Returns the length of the replacement, which has been added to "buf". |
5461 | * Returns -1 if there was no match, and only the "<" has been copied. |
5462 | */ |
5463 | static size_t |
5464 | uc_check_code( |
5465 | char_u *code, |
5466 | size_t len, |
5467 | char_u *buf, |
5468 | ucmd_T *cmd, /* the user command we're expanding */ |
5469 | exarg_T *eap, /* ex arguments */ |
5470 | char_u **split_buf, |
5471 | size_t *split_len |
5472 | ) |
5473 | { |
5474 | size_t result = 0; |
5475 | char_u *p = code + 1; |
5476 | size_t l = len - 2; |
5477 | int quote = 0; |
5478 | enum { |
5479 | ct_ARGS, |
5480 | ct_BANG, |
5481 | ct_COUNT, |
5482 | ct_LINE1, |
5483 | ct_LINE2, |
5484 | ct_RANGE, |
5485 | ct_MODS, |
5486 | ct_REGISTER, |
5487 | ct_LT, |
5488 | ct_NONE |
5489 | } type = ct_NONE; |
5490 | |
5491 | if ((vim_strchr((char_u *)"qQfF" , *p) != NULL) && p[1] == '-') { |
5492 | quote = (*p == 'q' || *p == 'Q') ? 1 : 2; |
5493 | p += 2; |
5494 | l -= 2; |
5495 | } |
5496 | |
5497 | l++; |
5498 | if (l <= 1) { |
5499 | type = ct_NONE; |
5500 | } else if (STRNICMP(p, "args>" , l) == 0) { |
5501 | type = ct_ARGS; |
5502 | } else if (STRNICMP(p, "bang>" , l) == 0) { |
5503 | type = ct_BANG; |
5504 | } else if (STRNICMP(p, "count>" , l) == 0) { |
5505 | type = ct_COUNT; |
5506 | } else if (STRNICMP(p, "line1>" , l) == 0) { |
5507 | type = ct_LINE1; |
5508 | } else if (STRNICMP(p, "line2>" , l) == 0) { |
5509 | type = ct_LINE2; |
5510 | } else if (STRNICMP(p, "range>" , l) == 0) { |
5511 | type = ct_RANGE; |
5512 | } else if (STRNICMP(p, "lt>" , l) == 0) { |
5513 | type = ct_LT; |
5514 | } else if (STRNICMP(p, "reg>" , l) == 0 || STRNICMP(p, "register>" , l) == 0) { |
5515 | type = ct_REGISTER; |
5516 | } else if (STRNICMP(p, "mods>" , l) == 0) { |
5517 | type = ct_MODS; |
5518 | } |
5519 | |
5520 | switch (type) { |
5521 | case ct_ARGS: |
5522 | /* Simple case first */ |
5523 | if (*eap->arg == NUL) { |
5524 | if (quote == 1) { |
5525 | result = 2; |
5526 | if (buf != NULL) |
5527 | STRCPY(buf, "''" ); |
5528 | } else |
5529 | result = 0; |
5530 | break; |
5531 | } |
5532 | |
5533 | /* When specified there is a single argument don't split it. |
5534 | * Works for ":Cmd %" when % is "a b c". */ |
5535 | if ((eap->argt & NOSPC) && quote == 2) |
5536 | quote = 1; |
5537 | |
5538 | switch (quote) { |
5539 | case 0: /* No quoting, no splitting */ |
5540 | result = STRLEN(eap->arg); |
5541 | if (buf != NULL) |
5542 | STRCPY(buf, eap->arg); |
5543 | break; |
5544 | case 1: /* Quote, but don't split */ |
5545 | result = STRLEN(eap->arg) + 2; |
5546 | for (p = eap->arg; *p; p++) { |
5547 | if (*p == '\\' || *p == '"') { |
5548 | result++; |
5549 | } |
5550 | } |
5551 | |
5552 | if (buf != NULL) { |
5553 | *buf++ = '"'; |
5554 | for (p = eap->arg; *p; p++) { |
5555 | if (*p == '\\' || *p == '"') { |
5556 | *buf++ = '\\'; |
5557 | } |
5558 | *buf++ = *p; |
5559 | } |
5560 | *buf = '"'; |
5561 | } |
5562 | |
5563 | break; |
5564 | case 2: /* Quote and split (<f-args>) */ |
5565 | /* This is hard, so only do it once, and cache the result */ |
5566 | if (*split_buf == NULL) |
5567 | *split_buf = uc_split_args(eap->arg, split_len); |
5568 | |
5569 | result = *split_len; |
5570 | if (buf != NULL && result != 0) |
5571 | STRCPY(buf, *split_buf); |
5572 | |
5573 | break; |
5574 | } |
5575 | break; |
5576 | |
5577 | case ct_BANG: |
5578 | result = eap->forceit ? 1 : 0; |
5579 | if (quote) |
5580 | result += 2; |
5581 | if (buf != NULL) { |
5582 | if (quote) |
5583 | *buf++ = '"'; |
5584 | if (eap->forceit) |
5585 | *buf++ = '!'; |
5586 | if (quote) |
5587 | *buf = '"'; |
5588 | } |
5589 | break; |
5590 | |
5591 | case ct_LINE1: |
5592 | case ct_LINE2: |
5593 | case ct_RANGE: |
5594 | case ct_COUNT: |
5595 | { |
5596 | char num_buf[20]; |
5597 | long num = (type == ct_LINE1) ? eap->line1 : |
5598 | (type == ct_LINE2) ? eap->line2 : |
5599 | (type == ct_RANGE) ? eap->addr_count : |
5600 | (eap->addr_count > 0) ? eap->line2 : cmd->uc_def; |
5601 | size_t num_len; |
5602 | |
5603 | sprintf(num_buf, "%" PRId64, (int64_t)num); |
5604 | num_len = STRLEN(num_buf); |
5605 | result = num_len; |
5606 | |
5607 | if (quote) |
5608 | result += 2; |
5609 | |
5610 | if (buf != NULL) { |
5611 | if (quote) |
5612 | *buf++ = '"'; |
5613 | STRCPY(buf, num_buf); |
5614 | buf += num_len; |
5615 | if (quote) |
5616 | *buf = '"'; |
5617 | } |
5618 | |
5619 | break; |
5620 | } |
5621 | |
5622 | case ct_MODS: |
5623 | { |
5624 | result = quote ? 2 : 0; |
5625 | if (buf != NULL) { |
5626 | if (quote) { |
5627 | *buf++ = '"'; |
5628 | } |
5629 | *buf = '\0'; |
5630 | } |
5631 | |
5632 | bool multi_mods = false; |
5633 | |
5634 | // :aboveleft and :leftabove |
5635 | if (cmdmod.split & WSP_ABOVE) { |
5636 | result += add_cmd_modifier(buf, "aboveleft" , &multi_mods); |
5637 | } |
5638 | // :belowright and :rightbelow |
5639 | if (cmdmod.split & WSP_BELOW) { |
5640 | result += add_cmd_modifier(buf, "belowright" , &multi_mods); |
5641 | } |
5642 | // :botright |
5643 | if (cmdmod.split & WSP_BOT) { |
5644 | result += add_cmd_modifier(buf, "botright" , &multi_mods); |
5645 | } |
5646 | |
5647 | typedef struct { |
5648 | bool *set; |
5649 | char *name; |
5650 | } mod_entry_T; |
5651 | static mod_entry_T mod_entries[] = { |
5652 | { &cmdmod.browse, "browse" }, |
5653 | { &cmdmod.confirm, "confirm" }, |
5654 | { &cmdmod.hide, "hide" }, |
5655 | { &cmdmod.keepalt, "keepalt" }, |
5656 | { &cmdmod.keepjumps, "keepjumps" }, |
5657 | { &cmdmod.keepmarks, "keepmarks" }, |
5658 | { &cmdmod.keeppatterns, "keeppatterns" }, |
5659 | { &cmdmod.lockmarks, "lockmarks" }, |
5660 | { &cmdmod.noswapfile, "noswapfile" } |
5661 | }; |
5662 | // the modifiers that are simple flags |
5663 | for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) { |
5664 | if (*mod_entries[i].set) { |
5665 | result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods); |
5666 | } |
5667 | } |
5668 | |
5669 | // TODO(vim): How to support :noautocmd? |
5670 | // TODO(vim): How to support :sandbox? |
5671 | |
5672 | // :silent |
5673 | if (msg_silent > 0) { |
5674 | result += add_cmd_modifier(buf, emsg_silent > 0 ? "silent!" : "silent" , |
5675 | &multi_mods); |
5676 | } |
5677 | // :tab |
5678 | if (cmdmod.tab > 0) { |
5679 | result += add_cmd_modifier(buf, "tab" , &multi_mods); |
5680 | } |
5681 | // :topleft |
5682 | if (cmdmod.split & WSP_TOP) { |
5683 | result += add_cmd_modifier(buf, "topleft" , &multi_mods); |
5684 | } |
5685 | |
5686 | // TODO(vim): How to support :unsilent? |
5687 | |
5688 | // :verbose |
5689 | if (p_verbose > 0) { |
5690 | result += add_cmd_modifier(buf, "verbose" , &multi_mods); |
5691 | } |
5692 | // :vertical |
5693 | if (cmdmod.split & WSP_VERT) { |
5694 | result += add_cmd_modifier(buf, "vertical" , &multi_mods); |
5695 | } |
5696 | if (quote && buf != NULL) { |
5697 | buf += result - 2; |
5698 | *buf = '"'; |
5699 | } |
5700 | break; |
5701 | } |
5702 | |
5703 | case ct_REGISTER: |
5704 | result = eap->regname ? 1 : 0; |
5705 | if (quote) |
5706 | result += 2; |
5707 | if (buf != NULL) { |
5708 | if (quote) |
5709 | *buf++ = '\''; |
5710 | if (eap->regname) |
5711 | *buf++ = eap->regname; |
5712 | if (quote) |
5713 | *buf = '\''; |
5714 | } |
5715 | break; |
5716 | |
5717 | case ct_LT: |
5718 | result = 1; |
5719 | if (buf != NULL) |
5720 | *buf = '<'; |
5721 | break; |
5722 | |
5723 | default: |
5724 | /* Not recognized: just copy the '<' and return -1. */ |
5725 | result = (size_t)-1; |
5726 | if (buf != NULL) |
5727 | *buf = '<'; |
5728 | break; |
5729 | } |
5730 | |
5731 | return result; |
5732 | } |
5733 | |
5734 | static void do_ucmd(exarg_T *eap) |
5735 | { |
5736 | char_u *buf; |
5737 | char_u *p; |
5738 | char_u *q; |
5739 | |
5740 | char_u *start; |
5741 | char_u *end = NULL; |
5742 | char_u *ksp; |
5743 | size_t len, totlen; |
5744 | |
5745 | size_t split_len = 0; |
5746 | char_u *split_buf = NULL; |
5747 | ucmd_T *cmd; |
5748 | const sctx_T save_current_sctx = current_sctx; |
5749 | |
5750 | if (eap->cmdidx == CMD_USER) |
5751 | cmd = USER_CMD(eap->useridx); |
5752 | else |
5753 | cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx); |
5754 | |
5755 | /* |
5756 | * Replace <> in the command by the arguments. |
5757 | * First round: "buf" is NULL, compute length, allocate "buf". |
5758 | * Second round: copy result into "buf". |
5759 | */ |
5760 | buf = NULL; |
5761 | for (;; ) { |
5762 | p = cmd->uc_rep; /* source */ |
5763 | q = buf; /* destination */ |
5764 | totlen = 0; |
5765 | |
5766 | for (;; ) { |
5767 | start = vim_strchr(p, '<'); |
5768 | if (start != NULL) |
5769 | end = vim_strchr(start + 1, '>'); |
5770 | if (buf != NULL) { |
5771 | for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ksp++) { |
5772 | } |
5773 | if (*ksp == K_SPECIAL |
5774 | && (start == NULL || ksp < start || end == NULL) |
5775 | && (ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)) { |
5776 | // K_SPECIAL has been put in the buffer as K_SPECIAL |
5777 | // KS_SPECIAL KE_FILLER, like for mappings, but |
5778 | // do_cmdline() doesn't handle that, so convert it back. |
5779 | // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI. |
5780 | len = ksp - p; |
5781 | if (len > 0) { |
5782 | memmove(q, p, len); |
5783 | q += len; |
5784 | } |
5785 | *q++ = K_SPECIAL; |
5786 | p = ksp + 3; |
5787 | continue; |
5788 | } |
5789 | } |
5790 | |
5791 | /* break if there no <item> is found */ |
5792 | if (start == NULL || end == NULL) |
5793 | break; |
5794 | |
5795 | /* Include the '>' */ |
5796 | ++end; |
5797 | |
5798 | /* Take everything up to the '<' */ |
5799 | len = start - p; |
5800 | if (buf == NULL) |
5801 | totlen += len; |
5802 | else { |
5803 | memmove(q, p, len); |
5804 | q += len; |
5805 | } |
5806 | |
5807 | len = uc_check_code(start, end - start, q, cmd, eap, |
5808 | &split_buf, &split_len); |
5809 | if (len == (size_t)-1) { |
5810 | /* no match, continue after '<' */ |
5811 | p = start + 1; |
5812 | len = 1; |
5813 | } else |
5814 | p = end; |
5815 | if (buf == NULL) |
5816 | totlen += len; |
5817 | else |
5818 | q += len; |
5819 | } |
5820 | if (buf != NULL) { /* second time here, finished */ |
5821 | STRCPY(q, p); |
5822 | break; |
5823 | } |
5824 | |
5825 | totlen += STRLEN(p); /* Add on the trailing characters */ |
5826 | buf = xmalloc(totlen + 1); |
5827 | } |
5828 | |
5829 | current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid; |
5830 | (void)do_cmdline(buf, eap->getline, eap->cookie, |
5831 | DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED); |
5832 | current_sctx = save_current_sctx; |
5833 | xfree(buf); |
5834 | xfree(split_buf); |
5835 | } |
5836 | |
5837 | static char_u *get_user_command_name(int idx) |
5838 | { |
5839 | return get_user_commands(NULL, idx - (int)CMD_SIZE); |
5840 | } |
5841 | /* |
5842 | * Function given to ExpandGeneric() to obtain the list of user address type names. |
5843 | */ |
5844 | char_u *get_user_cmd_addr_type(expand_T *xp, int idx) |
5845 | { |
5846 | return (char_u *)addr_type_complete[idx].name; |
5847 | } |
5848 | |
5849 | /* |
5850 | * Function given to ExpandGeneric() to obtain the list of user command names. |
5851 | */ |
5852 | char_u *get_user_commands(expand_T *xp, int idx) |
5853 | { |
5854 | if (idx < curbuf->b_ucmds.ga_len) |
5855 | return USER_CMD_GA(&curbuf->b_ucmds, idx)->uc_name; |
5856 | idx -= curbuf->b_ucmds.ga_len; |
5857 | if (idx < ucmds.ga_len) |
5858 | return USER_CMD(idx)->uc_name; |
5859 | return NULL; |
5860 | } |
5861 | |
5862 | /* |
5863 | * Function given to ExpandGeneric() to obtain the list of user command |
5864 | * attributes. |
5865 | */ |
5866 | char_u *get_user_cmd_flags(expand_T *xp, int idx) |
5867 | { |
5868 | static char *user_cmd_flags[] = {"addr" , "bang" , "bar" , |
5869 | "buffer" , "complete" , "count" , |
5870 | "nargs" , "range" , "register" }; |
5871 | |
5872 | if (idx >= (int)ARRAY_SIZE(user_cmd_flags)) |
5873 | return NULL; |
5874 | return (char_u *)user_cmd_flags[idx]; |
5875 | } |
5876 | |
5877 | /* |
5878 | * Function given to ExpandGeneric() to obtain the list of values for -nargs. |
5879 | */ |
5880 | char_u *get_user_cmd_nargs(expand_T *xp, int idx) |
5881 | { |
5882 | static char *user_cmd_nargs[] = {"0" , "1" , "*" , "?" , "+" }; |
5883 | |
5884 | if (idx >= (int)ARRAY_SIZE(user_cmd_nargs)) |
5885 | return NULL; |
5886 | return (char_u *)user_cmd_nargs[idx]; |
5887 | } |
5888 | |
5889 | /* |
5890 | * Function given to ExpandGeneric() to obtain the list of values for -complete. |
5891 | */ |
5892 | char_u *get_user_cmd_complete(expand_T *xp, int idx) |
5893 | { |
5894 | if (idx >= (int)ARRAY_SIZE(command_complete)) { |
5895 | return NULL; |
5896 | } |
5897 | char *cmd_compl = get_command_complete(idx); |
5898 | if (cmd_compl == NULL) { |
5899 | return (char_u *)"" ; |
5900 | } else { |
5901 | return (char_u *)cmd_compl; |
5902 | } |
5903 | } |
5904 | |
5905 | /* |
5906 | * Parse address type argument |
5907 | */ |
5908 | int parse_addr_type_arg(char_u *value, int vallen, uint32_t *argt, |
5909 | int *addr_type_arg) |
5910 | { |
5911 | int i, a, b; |
5912 | |
5913 | for (i = 0; addr_type_complete[i].expand != -1; i++) { |
5914 | a = (int)STRLEN(addr_type_complete[i].name) == vallen; |
5915 | b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0; |
5916 | if (a && b) { |
5917 | *addr_type_arg = addr_type_complete[i].expand; |
5918 | break; |
5919 | } |
5920 | } |
5921 | |
5922 | if (addr_type_complete[i].expand == -1) { |
5923 | char_u *err = value; |
5924 | |
5925 | for (i = 0; err[i] != NUL && !ascii_iswhite(err[i]); i++) {} |
5926 | err[i] = NUL; |
5927 | EMSG2(_("E180: Invalid address type value: %s" ), err); |
5928 | return FAIL; |
5929 | } |
5930 | |
5931 | if (*addr_type_arg != ADDR_LINES) |
5932 | *argt |= NOTADR; |
5933 | |
5934 | return OK; |
5935 | } |
5936 | |
5937 | /* |
5938 | * Parse a completion argument "value[vallen]". |
5939 | * The detected completion goes in "*complp", argument type in "*argt". |
5940 | * When there is an argument, for function and user defined completion, it's |
5941 | * copied to allocated memory and stored in "*compl_arg". |
5942 | * Returns FAIL if something is wrong. |
5943 | */ |
5944 | int parse_compl_arg(const char_u *value, int vallen, int *complp, |
5945 | uint32_t *argt, char_u **compl_arg) |
5946 | { |
5947 | const char_u *arg = NULL; |
5948 | size_t arglen = 0; |
5949 | int i; |
5950 | int valend = vallen; |
5951 | |
5952 | /* Look for any argument part - which is the part after any ',' */ |
5953 | for (i = 0; i < vallen; ++i) { |
5954 | if (value[i] == ',') { |
5955 | arg = &value[i + 1]; |
5956 | arglen = vallen - i - 1; |
5957 | valend = i; |
5958 | break; |
5959 | } |
5960 | } |
5961 | |
5962 | for (i = 0; i < (int)ARRAY_SIZE(command_complete); i++) { |
5963 | if (get_command_complete(i) == NULL) { |
5964 | continue; |
5965 | } |
5966 | if ((int)STRLEN(command_complete[i]) == valend |
5967 | && STRNCMP(value, command_complete[i], valend) == 0) { |
5968 | *complp = i; |
5969 | if (i == EXPAND_BUFFERS) { |
5970 | *argt |= BUFNAME; |
5971 | } else if (i == EXPAND_DIRECTORIES || i == EXPAND_FILES) { |
5972 | *argt |= XFILE; |
5973 | } |
5974 | break; |
5975 | } |
5976 | } |
5977 | |
5978 | if (i == (int)ARRAY_SIZE(command_complete)) { |
5979 | EMSG2(_("E180: Invalid complete value: %s" ), value); |
5980 | return FAIL; |
5981 | } |
5982 | |
5983 | if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST |
5984 | && arg != NULL) { |
5985 | EMSG(_("E468: Completion argument only allowed for custom completion" )); |
5986 | return FAIL; |
5987 | } |
5988 | |
5989 | if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST) |
5990 | && arg == NULL) { |
5991 | EMSG(_("E467: Custom completion requires a function argument" )); |
5992 | return FAIL; |
5993 | } |
5994 | |
5995 | if (arg != NULL) |
5996 | *compl_arg = vim_strnsave(arg, (int)arglen); |
5997 | return OK; |
5998 | } |
5999 | |
6000 | int cmdcomplete_str_to_type(char_u *complete_str) |
6001 | { |
6002 | for (int i = 0; i < (int)(ARRAY_SIZE(command_complete)); i++) { |
6003 | char *cmd_compl = get_command_complete(i); |
6004 | if (cmd_compl == NULL) { |
6005 | continue; |
6006 | } |
6007 | if (STRCMP(complete_str, command_complete[i]) == 0) { |
6008 | return i; |
6009 | } |
6010 | } |
6011 | |
6012 | return EXPAND_NOTHING; |
6013 | } |
6014 | |
6015 | static void ex_colorscheme(exarg_T *eap) |
6016 | { |
6017 | if (*eap->arg == NUL) { |
6018 | char_u *expr = vim_strsave((char_u *)"g:colors_name" ); |
6019 | char_u *p = NULL; |
6020 | |
6021 | ++emsg_off; |
6022 | p = eval_to_string(expr, NULL, FALSE); |
6023 | --emsg_off; |
6024 | xfree(expr); |
6025 | |
6026 | if (p != NULL) { |
6027 | MSG(p); |
6028 | xfree(p); |
6029 | } else |
6030 | MSG("default" ); |
6031 | } else if (load_colors(eap->arg) == FAIL) |
6032 | EMSG2(_("E185: Cannot find color scheme '%s'" ), eap->arg); |
6033 | } |
6034 | |
6035 | static void ex_highlight(exarg_T *eap) |
6036 | { |
6037 | if (*eap->arg == NUL && eap->cmd[2] == '!') { |
6038 | MSG(_("Greetings, Vim user!" )); |
6039 | } |
6040 | do_highlight((const char *)eap->arg, eap->forceit, false); |
6041 | } |
6042 | |
6043 | |
6044 | /* |
6045 | * Call this function if we thought we were going to exit, but we won't |
6046 | * (because of an error). May need to restore the terminal mode. |
6047 | */ |
6048 | void not_exiting(void) |
6049 | { |
6050 | exiting = false; |
6051 | } |
6052 | |
6053 | static bool before_quit_autocmds(win_T *wp, bool quit_all, int forceit) |
6054 | { |
6055 | apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, wp->w_buffer); |
6056 | |
6057 | // Bail out when autocommands closed the window. |
6058 | // Refuse to quit when the buffer in the last window is being closed (can |
6059 | // only happen in autocommands). |
6060 | if (!win_valid(wp) |
6061 | || curbuf_locked() |
6062 | || (wp->w_buffer->b_nwindows == 1 && wp->w_buffer->b_locked > 0)) { |
6063 | return true; |
6064 | } |
6065 | |
6066 | if (quit_all |
6067 | || (check_more(false, forceit) == OK && only_one_window())) { |
6068 | apply_autocmds(EVENT_EXITPRE, NULL, NULL, false, curbuf); |
6069 | // Refuse to quit when locked or when the buffer in the last window is |
6070 | // being closed (can only happen in autocommands). |
6071 | if (curbuf_locked() |
6072 | || (curbuf->b_nwindows == 1 && curbuf->b_locked > 0)) { |
6073 | return true; |
6074 | } |
6075 | } |
6076 | |
6077 | return false; |
6078 | } |
6079 | |
6080 | // ":quit": quit current window, quit Vim if the last window is closed. |
6081 | // ":{nr}quit": quit window {nr} |
6082 | static void ex_quit(exarg_T *eap) |
6083 | { |
6084 | if (cmdwin_type != 0) { |
6085 | cmdwin_result = Ctrl_C; |
6086 | return; |
6087 | } |
6088 | /* Don't quit while editing the command line. */ |
6089 | if (text_locked()) { |
6090 | text_locked_msg(); |
6091 | return; |
6092 | } |
6093 | |
6094 | win_T *wp; |
6095 | |
6096 | if (eap->addr_count > 0) { |
6097 | int wnr = eap->line2; |
6098 | |
6099 | for (wp = firstwin; wp->w_next != NULL; wp = wp->w_next) { |
6100 | if (--wnr <= 0) |
6101 | break; |
6102 | } |
6103 | } else { |
6104 | wp = curwin; |
6105 | } |
6106 | |
6107 | // Refuse to quit when locked. |
6108 | if (curbuf_locked()) { |
6109 | return; |
6110 | } |
6111 | |
6112 | // Trigger QuitPre and maybe ExitPre |
6113 | if (before_quit_autocmds(wp, false, eap->forceit)) { |
6114 | return; |
6115 | } |
6116 | |
6117 | // If there are more files or windows we won't exit. |
6118 | if (check_more(false, eap->forceit) == OK && only_one_window()) { |
6119 | exiting = true; |
6120 | } |
6121 | if ((!buf_hide(wp->w_buffer) |
6122 | && check_changed(wp->w_buffer, (p_awa ? CCGD_AW : 0) |
6123 | | (eap->forceit ? CCGD_FORCEIT : 0) |
6124 | | CCGD_EXCMD)) |
6125 | || check_more(true, eap->forceit) == FAIL |
6126 | || (only_one_window() && check_changed_any(eap->forceit, true))) { |
6127 | not_exiting(); |
6128 | } else { |
6129 | // quit last window |
6130 | // Note: only_one_window() returns true, even so a help window is |
6131 | // still open. In that case only quit, if no address has been |
6132 | // specified. Example: |
6133 | // :h|wincmd w|1q - don't quit |
6134 | // :h|wincmd w|q - quit |
6135 | if (only_one_window() && (ONE_WINDOW || eap->addr_count == 0)) { |
6136 | getout(0); |
6137 | } |
6138 | not_exiting(); |
6139 | // close window; may free buffer |
6140 | win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit); |
6141 | } |
6142 | } |
6143 | |
6144 | /* |
6145 | * ":cquit". |
6146 | */ |
6147 | static void ex_cquit(exarg_T *eap) |
6148 | { |
6149 | getout(eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE); |
6150 | } |
6151 | |
6152 | /* |
6153 | * ":qall": try to quit all windows |
6154 | */ |
6155 | static void ex_quit_all(exarg_T *eap) |
6156 | { |
6157 | if (cmdwin_type != 0) { |
6158 | if (eap->forceit) { |
6159 | cmdwin_result = K_XF1; // open_cmdwin() takes care of this |
6160 | } else { |
6161 | cmdwin_result = K_XF2; |
6162 | } |
6163 | return; |
6164 | } |
6165 | |
6166 | /* Don't quit while editing the command line. */ |
6167 | if (text_locked()) { |
6168 | text_locked_msg(); |
6169 | return; |
6170 | } |
6171 | |
6172 | if (before_quit_autocmds(curwin, true, eap->forceit)) { |
6173 | return; |
6174 | } |
6175 | |
6176 | exiting = true; |
6177 | if (eap->forceit || !check_changed_any(false, false)) { |
6178 | getout(0); |
6179 | } |
6180 | not_exiting(); |
6181 | } |
6182 | |
6183 | /* |
6184 | * ":close": close current window, unless it is the last one |
6185 | */ |
6186 | static void ex_close(exarg_T *eap) |
6187 | { |
6188 | win_T *win = NULL; |
6189 | int winnr = 0; |
6190 | if (cmdwin_type != 0) { |
6191 | cmdwin_result = Ctrl_C; |
6192 | } else if (!text_locked() && !curbuf_locked()) { |
6193 | if (eap->addr_count == 0) { |
6194 | ex_win_close(eap->forceit, curwin, NULL); |
6195 | } else { |
6196 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
6197 | winnr++; |
6198 | if (winnr == eap->line2) { |
6199 | win = wp; |
6200 | break; |
6201 | } |
6202 | } |
6203 | if (win == NULL) |
6204 | win = lastwin; |
6205 | ex_win_close(eap->forceit, win, NULL); |
6206 | } |
6207 | } |
6208 | } |
6209 | |
6210 | /* |
6211 | * ":pclose": Close any preview window. |
6212 | */ |
6213 | static void ex_pclose(exarg_T *eap) |
6214 | { |
6215 | FOR_ALL_WINDOWS_IN_TAB(win, curtab) { |
6216 | if (win->w_p_pvw) { |
6217 | ex_win_close(eap->forceit, win, NULL); |
6218 | break; |
6219 | } |
6220 | } |
6221 | } |
6222 | |
6223 | /* |
6224 | * Close window "win" and take care of handling closing the last window for a |
6225 | * modified buffer. |
6226 | */ |
6227 | void |
6228 | ex_win_close( |
6229 | int forceit, |
6230 | win_T *win, |
6231 | tabpage_T *tp /* NULL or the tab page "win" is in */ |
6232 | ) |
6233 | { |
6234 | int need_hide; |
6235 | buf_T *buf = win->w_buffer; |
6236 | |
6237 | need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1); |
6238 | if (need_hide && !buf_hide(buf) && !forceit) { |
6239 | if ((p_confirm || cmdmod.confirm) && p_write) { |
6240 | bufref_T bufref; |
6241 | set_bufref(&bufref, buf); |
6242 | dialog_changed(buf, false); |
6243 | if (bufref_valid(&bufref) && bufIsChanged(buf)) { |
6244 | return; |
6245 | } |
6246 | need_hide = false; |
6247 | } else { |
6248 | no_write_message(); |
6249 | return; |
6250 | } |
6251 | } |
6252 | |
6253 | |
6254 | // free buffer when not hiding it or when it's a scratch buffer |
6255 | if (tp == NULL) { |
6256 | win_close(win, !need_hide && !buf_hide(buf)); |
6257 | } else { |
6258 | win_close_othertab(win, !need_hide && !buf_hide(buf), tp); |
6259 | } |
6260 | } |
6261 | |
6262 | /* |
6263 | * ":tabclose": close current tab page, unless it is the last one. |
6264 | * ":tabclose N": close tab page N. |
6265 | */ |
6266 | static void ex_tabclose(exarg_T *eap) |
6267 | { |
6268 | tabpage_T *tp; |
6269 | |
6270 | if (cmdwin_type != 0) |
6271 | cmdwin_result = K_IGNORE; |
6272 | else if (first_tabpage->tp_next == NULL) |
6273 | EMSG(_("E784: Cannot close last tab page" )); |
6274 | else { |
6275 | int tab_number = get_tabpage_arg(eap); |
6276 | if (eap->errmsg == NULL) { |
6277 | tp = find_tabpage(tab_number); |
6278 | if (tp == NULL) { |
6279 | beep_flush(); |
6280 | return; |
6281 | } |
6282 | if (tp != curtab) { |
6283 | tabpage_close_other(tp, eap->forceit); |
6284 | return; |
6285 | } else if (!text_locked() && !curbuf_locked()) { |
6286 | tabpage_close(eap->forceit); |
6287 | } |
6288 | } |
6289 | } |
6290 | } |
6291 | |
6292 | /// ":tabonly": close all tab pages except the current one |
6293 | static void ex_tabonly(exarg_T *eap) |
6294 | { |
6295 | if (cmdwin_type != 0) { |
6296 | cmdwin_result = K_IGNORE; |
6297 | } else if (first_tabpage->tp_next == NULL) { |
6298 | MSG(_("Already only one tab page" )); |
6299 | } else { |
6300 | int tab_number = get_tabpage_arg(eap); |
6301 | if (eap->errmsg == NULL) { |
6302 | goto_tabpage(tab_number); |
6303 | // Repeat this up to a 1000 times, because autocommands may |
6304 | // mess up the lists. |
6305 | for (int done = 0; done < 1000; done++) { |
6306 | FOR_ALL_TAB_WINDOWS(tp, wp) { |
6307 | assert(wp != aucmd_win); |
6308 | } |
6309 | FOR_ALL_TABS(tp) { |
6310 | if (tp->tp_topframe != topframe) { |
6311 | tabpage_close_other(tp, eap->forceit); |
6312 | // if we failed to close it quit |
6313 | if (valid_tabpage(tp)) { |
6314 | done = 1000; |
6315 | } |
6316 | // start over, "tp" is now invalid |
6317 | break; |
6318 | } |
6319 | } |
6320 | assert(first_tabpage); |
6321 | if (first_tabpage->tp_next == NULL) { |
6322 | break; |
6323 | } |
6324 | } |
6325 | } |
6326 | } |
6327 | } |
6328 | |
6329 | /* |
6330 | * Close the current tab page. |
6331 | */ |
6332 | void tabpage_close(int forceit) |
6333 | { |
6334 | // First close all the windows but the current one. If that worked then |
6335 | // close the last window in this tab, that will close it. |
6336 | while (curwin->w_floating) { |
6337 | ex_win_close(forceit, curwin, NULL); |
6338 | } |
6339 | if (!ONE_WINDOW) { |
6340 | close_others(true, forceit); |
6341 | } |
6342 | if (ONE_WINDOW) { |
6343 | ex_win_close(forceit, curwin, NULL); |
6344 | } |
6345 | } |
6346 | |
6347 | /* |
6348 | * Close tab page "tp", which is not the current tab page. |
6349 | * Note that autocommands may make "tp" invalid. |
6350 | * Also takes care of the tab pages line disappearing when closing the |
6351 | * last-but-one tab page. |
6352 | */ |
6353 | void tabpage_close_other(tabpage_T *tp, int forceit) |
6354 | { |
6355 | int done = 0; |
6356 | win_T *wp; |
6357 | int h = tabline_height(); |
6358 | char_u prev_idx[NUMBUFLEN]; |
6359 | |
6360 | /* Limit to 1000 windows, autocommands may add a window while we close |
6361 | * one. OK, so I'm paranoid... */ |
6362 | while (++done < 1000) { |
6363 | snprintf((char *)prev_idx, sizeof(prev_idx), "%i" , tabpage_index(tp)); |
6364 | wp = tp->tp_lastwin; |
6365 | ex_win_close(forceit, wp, tp); |
6366 | |
6367 | /* Autocommands may delete the tab page under our fingers and we may |
6368 | * fail to close a window with a modified buffer. */ |
6369 | if (!valid_tabpage(tp) || tp->tp_firstwin == wp) |
6370 | break; |
6371 | } |
6372 | |
6373 | redraw_tabline = TRUE; |
6374 | if (h != tabline_height()) |
6375 | shell_new_rows(); |
6376 | } |
6377 | |
6378 | /* |
6379 | * ":only". |
6380 | */ |
6381 | static void ex_only(exarg_T *eap) |
6382 | { |
6383 | win_T *wp; |
6384 | int wnr; |
6385 | |
6386 | if (eap->addr_count > 0) { |
6387 | wnr = eap->line2; |
6388 | for (wp = firstwin; --wnr > 0;) { |
6389 | if (wp->w_next == NULL) |
6390 | break; |
6391 | else |
6392 | wp = wp->w_next; |
6393 | } |
6394 | } else { |
6395 | wp = curwin; |
6396 | } |
6397 | if (wp != curwin) { |
6398 | win_goto(wp); |
6399 | } |
6400 | close_others(TRUE, eap->forceit); |
6401 | } |
6402 | |
6403 | /* |
6404 | * ":all" and ":sall". |
6405 | * Also used for ":tab drop file ..." after setting the argument list. |
6406 | */ |
6407 | void ex_all(exarg_T *eap) |
6408 | { |
6409 | if (eap->addr_count == 0) |
6410 | eap->line2 = 9999; |
6411 | do_arg_all((int)eap->line2, eap->forceit, eap->cmdidx == CMD_drop); |
6412 | } |
6413 | |
6414 | static void ex_hide(exarg_T *eap) |
6415 | { |
6416 | // ":hide" or ":hide | cmd": hide current window |
6417 | if (!eap->skip) { |
6418 | if (eap->addr_count == 0) { |
6419 | win_close(curwin, false); // don't free buffer |
6420 | } else { |
6421 | int winnr = 0; |
6422 | win_T *win = NULL; |
6423 | |
6424 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
6425 | winnr++; |
6426 | if (winnr == eap->line2) { |
6427 | win = wp; |
6428 | break; |
6429 | } |
6430 | } |
6431 | if (win == NULL) { |
6432 | win = lastwin; |
6433 | } |
6434 | win_close(win, false); |
6435 | } |
6436 | } |
6437 | } |
6438 | |
6439 | /// ":stop" and ":suspend": Suspend Vim. |
6440 | static void ex_stop(exarg_T *eap) |
6441 | { |
6442 | // Disallow suspending in restricted mode (-Z) |
6443 | if (!check_restricted()) { |
6444 | if (!eap->forceit) { |
6445 | autowrite_all(); |
6446 | } |
6447 | apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL); |
6448 | |
6449 | // TODO(bfredl): the TUI should do this on suspend |
6450 | ui_cursor_goto(Rows - 1, 0); |
6451 | ui_call_grid_scroll(1, 0, Rows, 0, Columns, 1, 0); |
6452 | ui_flush(); |
6453 | ui_call_suspend(); // call machine specific function |
6454 | |
6455 | ui_flush(); |
6456 | maketitle(); |
6457 | resettitle(); // force updating the title |
6458 | ui_refresh(); // may have resized window |
6459 | apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL); |
6460 | } |
6461 | } |
6462 | |
6463 | // ":exit", ":xit" and ":wq": Write file and quite the current window. |
6464 | static void ex_exit(exarg_T *eap) |
6465 | { |
6466 | if (cmdwin_type != 0) { |
6467 | cmdwin_result = Ctrl_C; |
6468 | return; |
6469 | } |
6470 | /* Don't quit while editing the command line. */ |
6471 | if (text_locked()) { |
6472 | text_locked_msg(); |
6473 | return; |
6474 | } |
6475 | |
6476 | if (before_quit_autocmds(curwin, false, eap->forceit)) { |
6477 | return; |
6478 | } |
6479 | |
6480 | // if more files or windows we won't exit |
6481 | if (check_more(false, eap->forceit) == OK && only_one_window()) { |
6482 | exiting = true; |
6483 | } |
6484 | if (((eap->cmdidx == CMD_wq |
6485 | || curbufIsChanged()) |
6486 | && do_write(eap) == FAIL) |
6487 | || check_more(true, eap->forceit) == FAIL |
6488 | || (only_one_window() && check_changed_any(eap->forceit, false))) { |
6489 | not_exiting(); |
6490 | } else { |
6491 | if (only_one_window()) { |
6492 | // quit last window, exit Vim |
6493 | getout(0); |
6494 | } |
6495 | not_exiting(); |
6496 | // Quit current window, may free the buffer. |
6497 | win_close(curwin, !buf_hide(curwin->w_buffer)); |
6498 | } |
6499 | } |
6500 | |
6501 | /* |
6502 | * ":print", ":list", ":number". |
6503 | */ |
6504 | static void ex_print(exarg_T *eap) |
6505 | { |
6506 | if (curbuf->b_ml.ml_flags & ML_EMPTY) |
6507 | EMSG(_(e_emptybuf)); |
6508 | else { |
6509 | for (; !got_int; os_breakcheck()) { |
6510 | print_line(eap->line1, |
6511 | (eap->cmdidx == CMD_number || eap->cmdidx == CMD_pound |
6512 | || (eap->flags & EXFLAG_NR)), |
6513 | eap->cmdidx == CMD_list || (eap->flags & EXFLAG_LIST)); |
6514 | if (++eap->line1 > eap->line2) |
6515 | break; |
6516 | ui_flush(); /* show one line at a time */ |
6517 | } |
6518 | setpcmark(); |
6519 | /* put cursor at last line */ |
6520 | curwin->w_cursor.lnum = eap->line2; |
6521 | beginline(BL_SOL | BL_FIX); |
6522 | } |
6523 | |
6524 | ex_no_reprint = TRUE; |
6525 | } |
6526 | |
6527 | static void ex_goto(exarg_T *eap) |
6528 | { |
6529 | goto_byte(eap->line2); |
6530 | } |
6531 | |
6532 | /* |
6533 | * Clear an argument list: free all file names and reset it to zero entries. |
6534 | */ |
6535 | void alist_clear(alist_T *al) |
6536 | { |
6537 | # define FREE_AENTRY_FNAME(arg) xfree(arg->ae_fname) |
6538 | GA_DEEP_CLEAR(&al->al_ga, aentry_T, FREE_AENTRY_FNAME); |
6539 | } |
6540 | |
6541 | /* |
6542 | * Init an argument list. |
6543 | */ |
6544 | void alist_init(alist_T *al) |
6545 | { |
6546 | ga_init(&al->al_ga, (int)sizeof(aentry_T), 5); |
6547 | } |
6548 | |
6549 | |
6550 | /* |
6551 | * Remove a reference from an argument list. |
6552 | * Ignored when the argument list is the global one. |
6553 | * If the argument list is no longer used by any window, free it. |
6554 | */ |
6555 | void alist_unlink(alist_T *al) |
6556 | { |
6557 | if (al != &global_alist && --al->al_refcount <= 0) { |
6558 | alist_clear(al); |
6559 | xfree(al); |
6560 | } |
6561 | } |
6562 | |
6563 | /* |
6564 | * Create a new argument list and use it for the current window. |
6565 | */ |
6566 | void alist_new(void) |
6567 | { |
6568 | curwin->w_alist = xmalloc(sizeof(*curwin->w_alist)); |
6569 | curwin->w_alist->al_refcount = 1; |
6570 | curwin->w_alist->id = ++max_alist_id; |
6571 | alist_init(curwin->w_alist); |
6572 | } |
6573 | |
6574 | #if !defined(UNIX) |
6575 | /* |
6576 | * Expand the file names in the global argument list. |
6577 | * If "fnum_list" is not NULL, use "fnum_list[fnum_len]" as a list of buffer |
6578 | * numbers to be re-used. |
6579 | */ |
6580 | void alist_expand(int *fnum_list, int fnum_len) |
6581 | { |
6582 | char_u **old_arg_files; |
6583 | int old_arg_count; |
6584 | char_u **new_arg_files; |
6585 | int new_arg_file_count; |
6586 | char_u *save_p_su = p_su; |
6587 | int i; |
6588 | |
6589 | /* Don't use 'suffixes' here. This should work like the shell did the |
6590 | * expansion. Also, the vimrc file isn't read yet, thus the user |
6591 | * can't set the options. */ |
6592 | p_su = empty_option; |
6593 | old_arg_files = xmalloc(sizeof(*old_arg_files) * GARGCOUNT); |
6594 | for (i = 0; i < GARGCOUNT; ++i) |
6595 | old_arg_files[i] = vim_strsave(GARGLIST[i].ae_fname); |
6596 | old_arg_count = GARGCOUNT; |
6597 | if (expand_wildcards(old_arg_count, old_arg_files, |
6598 | &new_arg_file_count, &new_arg_files, |
6599 | EW_FILE|EW_NOTFOUND|EW_ADDSLASH|EW_NOERROR) == OK |
6600 | && new_arg_file_count > 0) { |
6601 | alist_set(&global_alist, new_arg_file_count, new_arg_files, |
6602 | TRUE, fnum_list, fnum_len); |
6603 | FreeWild(old_arg_count, old_arg_files); |
6604 | } |
6605 | p_su = save_p_su; |
6606 | } |
6607 | #endif |
6608 | |
6609 | /* |
6610 | * Set the argument list for the current window. |
6611 | * Takes over the allocated files[] and the allocated fnames in it. |
6612 | */ |
6613 | void alist_set(alist_T *al, int count, char_u **files, int use_curbuf, int *fnum_list, int fnum_len) |
6614 | { |
6615 | int i; |
6616 | static int recursive = 0; |
6617 | |
6618 | if (recursive) { |
6619 | EMSG(_(e_au_recursive)); |
6620 | return; |
6621 | } |
6622 | recursive++; |
6623 | |
6624 | alist_clear(al); |
6625 | ga_grow(&al->al_ga, count); |
6626 | { |
6627 | for (i = 0; i < count; ++i) { |
6628 | if (got_int) { |
6629 | /* When adding many buffers this can take a long time. Allow |
6630 | * interrupting here. */ |
6631 | while (i < count) |
6632 | xfree(files[i++]); |
6633 | break; |
6634 | } |
6635 | |
6636 | /* May set buffer name of a buffer previously used for the |
6637 | * argument list, so that it's re-used by alist_add. */ |
6638 | if (fnum_list != NULL && i < fnum_len) |
6639 | buf_set_name(fnum_list[i], files[i]); |
6640 | |
6641 | alist_add(al, files[i], use_curbuf ? 2 : 1); |
6642 | os_breakcheck(); |
6643 | } |
6644 | xfree(files); |
6645 | } |
6646 | |
6647 | if (al == &global_alist) { |
6648 | arg_had_last = false; |
6649 | } |
6650 | recursive--; |
6651 | } |
6652 | |
6653 | /* |
6654 | * Add file "fname" to argument list "al". |
6655 | * "fname" must have been allocated and "al" must have been checked for room. |
6656 | */ |
6657 | void |
6658 | alist_add( |
6659 | alist_T *al, |
6660 | char_u *fname, |
6661 | int set_fnum /* 1: set buffer number; 2: re-use curbuf */ |
6662 | ) |
6663 | { |
6664 | if (fname == NULL) /* don't add NULL file names */ |
6665 | return; |
6666 | #ifdef BACKSLASH_IN_FILENAME |
6667 | slash_adjust(fname); |
6668 | #endif |
6669 | AARGLIST(al)[al->al_ga.ga_len].ae_fname = fname; |
6670 | if (set_fnum > 0) |
6671 | AARGLIST(al)[al->al_ga.ga_len].ae_fnum = |
6672 | buflist_add(fname, BLN_LISTED | (set_fnum == 2 ? BLN_CURBUF : 0)); |
6673 | ++al->al_ga.ga_len; |
6674 | } |
6675 | |
6676 | #if defined(BACKSLASH_IN_FILENAME) |
6677 | /* |
6678 | * Adjust slashes in file names. Called after 'shellslash' was set. |
6679 | */ |
6680 | void alist_slash_adjust(void) |
6681 | { |
6682 | for (int i = 0; i < GARGCOUNT; ++i) { |
6683 | if (GARGLIST[i].ae_fname != NULL) { |
6684 | slash_adjust(GARGLIST[i].ae_fname); |
6685 | } |
6686 | } |
6687 | |
6688 | FOR_ALL_TAB_WINDOWS(tp, wp) { |
6689 | if (wp->w_alist != &global_alist) { |
6690 | for (int i = 0; i < WARGCOUNT(wp); ++i) { |
6691 | if (WARGLIST(wp)[i].ae_fname != NULL) { |
6692 | slash_adjust(WARGLIST(wp)[i].ae_fname); |
6693 | } |
6694 | } |
6695 | } |
6696 | } |
6697 | } |
6698 | |
6699 | #endif |
6700 | |
6701 | /// ":preserve". |
6702 | static void ex_preserve(exarg_T *eap) |
6703 | { |
6704 | curbuf->b_flags |= BF_PRESERVED; |
6705 | ml_preserve(curbuf, true, true); |
6706 | } |
6707 | |
6708 | /// ":recover". |
6709 | static void ex_recover(exarg_T *eap) |
6710 | { |
6711 | /* Set recoverymode right away to avoid the ATTENTION prompt. */ |
6712 | recoverymode = TRUE; |
6713 | if (!check_changed(curbuf, (p_awa ? CCGD_AW : 0) |
6714 | | CCGD_MULTWIN |
6715 | | (eap->forceit ? CCGD_FORCEIT : 0) |
6716 | | CCGD_EXCMD) |
6717 | |
6718 | && (*eap->arg == NUL |
6719 | || setfname(curbuf, eap->arg, NULL, TRUE) == OK)) |
6720 | ml_recover(); |
6721 | recoverymode = FALSE; |
6722 | } |
6723 | |
6724 | /* |
6725 | * Command modifier used in a wrong way. |
6726 | */ |
6727 | static void ex_wrongmodifier(exarg_T *eap) |
6728 | { |
6729 | eap->errmsg = e_invcmd; |
6730 | } |
6731 | |
6732 | /* |
6733 | * :sview [+command] file split window with new file, read-only |
6734 | * :split [[+command] file] split window with current or new file |
6735 | * :vsplit [[+command] file] split window vertically with current or new file |
6736 | * :new [[+command] file] split window with no or new file |
6737 | * :vnew [[+command] file] split vertically window with no or new file |
6738 | * :sfind [+command] file split window with file in 'path' |
6739 | * |
6740 | * :tabedit open new Tab page with empty window |
6741 | * :tabedit [+command] file open new Tab page and edit "file" |
6742 | * :tabnew [[+command] file] just like :tabedit |
6743 | * :tabfind [+command] file open new Tab page and find "file" |
6744 | */ |
6745 | void ex_splitview(exarg_T *eap) |
6746 | { |
6747 | win_T *old_curwin = curwin; |
6748 | char_u *fname = NULL; |
6749 | |
6750 | |
6751 | |
6752 | /* A ":split" in the quickfix window works like ":new". Don't want two |
6753 | * quickfix windows. But it's OK when doing ":tab split". */ |
6754 | if (bt_quickfix(curbuf) && cmdmod.tab == 0) { |
6755 | if (eap->cmdidx == CMD_split) |
6756 | eap->cmdidx = CMD_new; |
6757 | if (eap->cmdidx == CMD_vsplit) |
6758 | eap->cmdidx = CMD_vnew; |
6759 | } |
6760 | |
6761 | if (eap->cmdidx == CMD_sfind || eap->cmdidx == CMD_tabfind) { |
6762 | fname = find_file_in_path(eap->arg, STRLEN(eap->arg), |
6763 | FNAME_MESS, TRUE, curbuf->b_ffname); |
6764 | if (fname == NULL) |
6765 | goto theend; |
6766 | eap->arg = fname; |
6767 | } |
6768 | |
6769 | /* |
6770 | * Either open new tab page or split the window. |
6771 | */ |
6772 | if (eap->cmdidx == CMD_tabedit |
6773 | || eap->cmdidx == CMD_tabfind |
6774 | || eap->cmdidx == CMD_tabnew) { |
6775 | if (win_new_tabpage(cmdmod.tab != 0 ? cmdmod.tab : eap->addr_count == 0 |
6776 | ? 0 : (int)eap->line2 + 1, eap->arg) != FAIL) { |
6777 | do_exedit(eap, old_curwin); |
6778 | apply_autocmds(EVENT_TABNEWENTERED, NULL, NULL, FALSE, curbuf); |
6779 | |
6780 | /* set the alternate buffer for the window we came from */ |
6781 | if (curwin != old_curwin |
6782 | && win_valid(old_curwin) |
6783 | && old_curwin->w_buffer != curbuf |
6784 | && !cmdmod.keepalt) |
6785 | old_curwin->w_alt_fnum = curbuf->b_fnum; |
6786 | } |
6787 | } else if (win_split(eap->addr_count > 0 ? (int)eap->line2 : 0, |
6788 | *eap->cmd == 'v' ? WSP_VERT : 0) != FAIL) { |
6789 | /* Reset 'scrollbind' when editing another file, but keep it when |
6790 | * doing ":split" without arguments. */ |
6791 | if (*eap->arg != NUL |
6792 | ) { |
6793 | RESET_BINDING(curwin); |
6794 | } else { |
6795 | do_check_scrollbind(false); |
6796 | } |
6797 | do_exedit(eap, old_curwin); |
6798 | } |
6799 | |
6800 | |
6801 | theend: |
6802 | xfree(fname); |
6803 | } |
6804 | |
6805 | /* |
6806 | * Open a new tab page. |
6807 | */ |
6808 | void tabpage_new(void) |
6809 | { |
6810 | exarg_T ea; |
6811 | |
6812 | memset(&ea, 0, sizeof(ea)); |
6813 | ea.cmdidx = CMD_tabnew; |
6814 | ea.cmd = (char_u *)"tabn" ; |
6815 | ea.arg = (char_u *)"" ; |
6816 | ex_splitview(&ea); |
6817 | } |
6818 | |
6819 | /* |
6820 | * :tabnext command |
6821 | */ |
6822 | static void ex_tabnext(exarg_T *eap) |
6823 | { |
6824 | int tab_number; |
6825 | |
6826 | switch (eap->cmdidx) { |
6827 | case CMD_tabfirst: |
6828 | case CMD_tabrewind: |
6829 | goto_tabpage(1); |
6830 | break; |
6831 | case CMD_tablast: |
6832 | goto_tabpage(9999); |
6833 | break; |
6834 | case CMD_tabprevious: |
6835 | case CMD_tabNext: |
6836 | if (eap->arg && *eap->arg != NUL) { |
6837 | char_u *p = eap->arg; |
6838 | char_u *p_save = p; |
6839 | tab_number = getdigits(&p, false, 0); |
6840 | if (p == p_save || *p_save == '-' || *p_save == '+' || *p != NUL |
6841 | || tab_number == 0) { |
6842 | // No numbers as argument. |
6843 | eap->errmsg = e_invarg; |
6844 | return; |
6845 | } |
6846 | } else { |
6847 | if (eap->addr_count == 0) { |
6848 | tab_number = 1; |
6849 | } else { |
6850 | tab_number = eap->line2; |
6851 | if (tab_number < 1) { |
6852 | eap->errmsg = e_invrange; |
6853 | return; |
6854 | } |
6855 | } |
6856 | } |
6857 | goto_tabpage(-tab_number); |
6858 | break; |
6859 | default: // CMD_tabnext |
6860 | tab_number = get_tabpage_arg(eap); |
6861 | if (eap->errmsg == NULL) { |
6862 | goto_tabpage(tab_number); |
6863 | } |
6864 | break; |
6865 | } |
6866 | } |
6867 | |
6868 | /* |
6869 | * :tabmove command |
6870 | */ |
6871 | static void ex_tabmove(exarg_T *eap) |
6872 | { |
6873 | int tab_number = get_tabpage_arg(eap); |
6874 | if (eap->errmsg == NULL) { |
6875 | tabpage_move(tab_number); |
6876 | } |
6877 | } |
6878 | |
6879 | /* |
6880 | * :tabs command: List tabs and their contents. |
6881 | */ |
6882 | static void ex_tabs(exarg_T *eap) |
6883 | { |
6884 | int tabcount = 1; |
6885 | |
6886 | msg_start(); |
6887 | msg_scroll = TRUE; |
6888 | |
6889 | FOR_ALL_TABS(tp) { |
6890 | if (got_int) { |
6891 | break; |
6892 | } |
6893 | |
6894 | msg_putchar('\n'); |
6895 | vim_snprintf((char *)IObuff, IOSIZE, _("Tab page %d" ), tabcount++); |
6896 | msg_outtrans_attr(IObuff, HL_ATTR(HLF_T)); |
6897 | ui_flush(); // output one line at a time |
6898 | os_breakcheck(); |
6899 | |
6900 | FOR_ALL_WINDOWS_IN_TAB(wp, tp) { |
6901 | if (got_int) { |
6902 | break; |
6903 | } |
6904 | |
6905 | msg_putchar('\n'); |
6906 | msg_putchar(wp == curwin ? '>' : ' '); |
6907 | msg_putchar(' '); |
6908 | msg_putchar(bufIsChanged(wp->w_buffer) ? '+' : ' '); |
6909 | msg_putchar(' '); |
6910 | if (buf_spname(wp->w_buffer) != NULL) |
6911 | STRLCPY(IObuff, buf_spname(wp->w_buffer), IOSIZE); |
6912 | else |
6913 | home_replace(wp->w_buffer, wp->w_buffer->b_fname, |
6914 | IObuff, IOSIZE, TRUE); |
6915 | msg_outtrans(IObuff); |
6916 | ui_flush(); /* output one line at a time */ |
6917 | os_breakcheck(); |
6918 | } |
6919 | } |
6920 | } |
6921 | |
6922 | |
6923 | /* |
6924 | * ":mode": |
6925 | * If no argument given, get the screen size and redraw. |
6926 | */ |
6927 | static void ex_mode(exarg_T *eap) |
6928 | { |
6929 | if (*eap->arg == NUL) { |
6930 | must_redraw = CLEAR; |
6931 | ex_redraw(eap); |
6932 | } else { |
6933 | EMSG(_(e_screenmode)); |
6934 | } |
6935 | } |
6936 | |
6937 | /* |
6938 | * ":resize". |
6939 | * set, increment or decrement current window height |
6940 | */ |
6941 | static void ex_resize(exarg_T *eap) |
6942 | { |
6943 | int n; |
6944 | win_T *wp = curwin; |
6945 | |
6946 | if (eap->addr_count > 0) { |
6947 | n = eap->line2; |
6948 | for (wp = firstwin; wp->w_next != NULL && --n > 0; wp = wp->w_next) |
6949 | ; |
6950 | } |
6951 | |
6952 | n = atol((char *)eap->arg); |
6953 | if (cmdmod.split & WSP_VERT) { |
6954 | if (*eap->arg == '-' || *eap->arg == '+') { |
6955 | n += curwin->w_width; |
6956 | } else if (n == 0 && eap->arg[0] == NUL) { // default is very wide |
6957 | n = Columns; |
6958 | } |
6959 | win_setwidth_win(n, wp); |
6960 | } else { |
6961 | if (*eap->arg == '-' || *eap->arg == '+') { |
6962 | n += curwin->w_height; |
6963 | } else if (n == 0 && eap->arg[0] == NUL) { // default is very high |
6964 | n = Rows-1; |
6965 | } |
6966 | win_setheight_win(n, wp); |
6967 | } |
6968 | } |
6969 | |
6970 | /* |
6971 | * ":find [+command] <file>" command. |
6972 | */ |
6973 | static void ex_find(exarg_T *eap) |
6974 | { |
6975 | char_u *fname; |
6976 | int count; |
6977 | |
6978 | fname = find_file_in_path(eap->arg, STRLEN(eap->arg), |
6979 | FNAME_MESS, TRUE, curbuf->b_ffname); |
6980 | if (eap->addr_count > 0) { |
6981 | /* Repeat finding the file "count" times. This matters when it |
6982 | * appears several times in the path. */ |
6983 | count = eap->line2; |
6984 | while (fname != NULL && --count > 0) { |
6985 | xfree(fname); |
6986 | fname = find_file_in_path(NULL, 0, FNAME_MESS, FALSE, curbuf->b_ffname); |
6987 | } |
6988 | } |
6989 | |
6990 | if (fname != NULL) { |
6991 | eap->arg = fname; |
6992 | do_exedit(eap, NULL); |
6993 | xfree(fname); |
6994 | } |
6995 | } |
6996 | |
6997 | /* |
6998 | * ":edit", ":badd", ":visual". |
6999 | */ |
7000 | static void ex_edit(exarg_T *eap) |
7001 | { |
7002 | do_exedit(eap, NULL); |
7003 | } |
7004 | |
7005 | /* |
7006 | * ":edit <file>" command and alikes. |
7007 | */ |
7008 | void |
7009 | do_exedit( |
7010 | exarg_T *eap, |
7011 | win_T *old_curwin /* curwin before doing a split or NULL */ |
7012 | ) |
7013 | { |
7014 | int n; |
7015 | int need_hide; |
7016 | |
7017 | /* |
7018 | * ":vi" command ends Ex mode. |
7019 | */ |
7020 | if (exmode_active && (eap->cmdidx == CMD_visual |
7021 | || eap->cmdidx == CMD_view)) { |
7022 | exmode_active = FALSE; |
7023 | if (*eap->arg == NUL) { |
7024 | /* Special case: ":global/pat/visual\NLvi-commands" */ |
7025 | if (global_busy) { |
7026 | int rd = RedrawingDisabled; |
7027 | int nwr = no_wait_return; |
7028 | int ms = msg_scroll; |
7029 | |
7030 | if (eap->nextcmd != NULL) { |
7031 | stuffReadbuff((const char *)eap->nextcmd); |
7032 | eap->nextcmd = NULL; |
7033 | } |
7034 | |
7035 | RedrawingDisabled = 0; |
7036 | no_wait_return = 0; |
7037 | need_wait_return = FALSE; |
7038 | msg_scroll = 0; |
7039 | redraw_all_later(NOT_VALID); |
7040 | |
7041 | normal_enter(false, true); |
7042 | |
7043 | RedrawingDisabled = rd; |
7044 | no_wait_return = nwr; |
7045 | msg_scroll = ms; |
7046 | } |
7047 | return; |
7048 | } |
7049 | } |
7050 | |
7051 | if ((eap->cmdidx == CMD_new |
7052 | || eap->cmdidx == CMD_tabnew |
7053 | || eap->cmdidx == CMD_tabedit |
7054 | || eap->cmdidx == CMD_vnew |
7055 | ) && *eap->arg == NUL) { |
7056 | /* ":new" or ":tabnew" without argument: edit an new empty buffer */ |
7057 | setpcmark(); |
7058 | (void)do_ecmd(0, NULL, NULL, eap, ECMD_ONE, |
7059 | ECMD_HIDE + (eap->forceit ? ECMD_FORCEIT : 0), |
7060 | old_curwin == NULL ? curwin : NULL); |
7061 | } else if ((eap->cmdidx != CMD_split && eap->cmdidx != CMD_vsplit) |
7062 | || *eap->arg != NUL) { |
7063 | /* Can't edit another file when "curbuf_lock" is set. Only ":edit" |
7064 | * can bring us here, others are stopped earlier. */ |
7065 | if (*eap->arg != NUL && curbuf_locked()) |
7066 | return; |
7067 | n = readonlymode; |
7068 | if (eap->cmdidx == CMD_view || eap->cmdidx == CMD_sview) |
7069 | readonlymode = TRUE; |
7070 | else if (eap->cmdidx == CMD_enew) |
7071 | readonlymode = FALSE; /* 'readonly' doesn't make sense in an |
7072 | empty buffer */ |
7073 | setpcmark(); |
7074 | if (do_ecmd(0, (eap->cmdidx == CMD_enew ? NULL : eap->arg), |
7075 | NULL, eap, eap->do_ecmd_lnum, |
7076 | (buf_hide(curbuf) ? ECMD_HIDE : 0) |
7077 | + (eap->forceit ? ECMD_FORCEIT : 0) |
7078 | // After a split we can use an existing buffer. |
7079 | + (old_curwin != NULL ? ECMD_OLDBUF : 0) |
7080 | + (eap->cmdidx == CMD_badd ? ECMD_ADDBUF : 0) |
7081 | , old_curwin == NULL ? curwin : NULL) == FAIL) { |
7082 | // Editing the file failed. If the window was split, close it. |
7083 | if (old_curwin != NULL) { |
7084 | need_hide = (curbufIsChanged() && curbuf->b_nwindows <= 1); |
7085 | if (!need_hide || buf_hide(curbuf)) { |
7086 | cleanup_T cs; |
7087 | |
7088 | /* Reset the error/interrupt/exception state here so that |
7089 | * aborting() returns FALSE when closing a window. */ |
7090 | enter_cleanup(&cs); |
7091 | win_close(curwin, !need_hide && !buf_hide(curbuf)); |
7092 | |
7093 | /* Restore the error/interrupt/exception state if not |
7094 | * discarded by a new aborting error, interrupt, or |
7095 | * uncaught exception. */ |
7096 | leave_cleanup(&cs); |
7097 | } |
7098 | } |
7099 | } else if (readonlymode && curbuf->b_nwindows == 1) { |
7100 | /* When editing an already visited buffer, 'readonly' won't be set |
7101 | * but the previous value is kept. With ":view" and ":sview" we |
7102 | * want the file to be readonly, except when another window is |
7103 | * editing the same buffer. */ |
7104 | curbuf->b_p_ro = TRUE; |
7105 | } |
7106 | readonlymode = n; |
7107 | } else { |
7108 | if (eap->do_ecmd_cmd != NULL) |
7109 | do_cmdline_cmd((char *)eap->do_ecmd_cmd); |
7110 | n = curwin->w_arg_idx_invalid; |
7111 | check_arg_idx(curwin); |
7112 | if (n != curwin->w_arg_idx_invalid) |
7113 | maketitle(); |
7114 | } |
7115 | |
7116 | /* |
7117 | * if ":split file" worked, set alternate file name in old window to new |
7118 | * file |
7119 | */ |
7120 | if (old_curwin != NULL |
7121 | && *eap->arg != NUL |
7122 | && curwin != old_curwin |
7123 | && win_valid(old_curwin) |
7124 | && old_curwin->w_buffer != curbuf |
7125 | && !cmdmod.keepalt) |
7126 | old_curwin->w_alt_fnum = curbuf->b_fnum; |
7127 | |
7128 | ex_no_reprint = TRUE; |
7129 | } |
7130 | |
7131 | /// ":gui" and ":gvim" when there is no GUI. |
7132 | static void ex_nogui(exarg_T *eap) |
7133 | { |
7134 | eap->errmsg = (char_u *)N_("E25: Nvim does not have a built-in GUI" ); |
7135 | } |
7136 | |
7137 | |
7138 | |
7139 | static void ex_swapname(exarg_T *eap) |
7140 | { |
7141 | if (curbuf->b_ml.ml_mfp == NULL || curbuf->b_ml.ml_mfp->mf_fname == NULL) |
7142 | MSG(_("No swap file" )); |
7143 | else |
7144 | msg(curbuf->b_ml.ml_mfp->mf_fname); |
7145 | } |
7146 | |
7147 | /* |
7148 | * ":syncbind" forces all 'scrollbind' windows to have the same relative |
7149 | * offset. |
7150 | * (1998-11-02 16:21:01 R. Edward Ralston <eralston@computer.org>) |
7151 | */ |
7152 | static void ex_syncbind(exarg_T *eap) |
7153 | { |
7154 | win_T *save_curwin = curwin; |
7155 | buf_T *save_curbuf = curbuf; |
7156 | long topline; |
7157 | long y; |
7158 | linenr_T old_linenr = curwin->w_cursor.lnum; |
7159 | |
7160 | setpcmark(); |
7161 | |
7162 | /* |
7163 | * determine max topline |
7164 | */ |
7165 | if (curwin->w_p_scb) { |
7166 | topline = curwin->w_topline; |
7167 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
7168 | if (wp->w_p_scb && wp->w_buffer) { |
7169 | y = wp->w_buffer->b_ml.ml_line_count - p_so; |
7170 | if (topline > y) { |
7171 | topline = y; |
7172 | } |
7173 | } |
7174 | } |
7175 | if (topline < 1) { |
7176 | topline = 1; |
7177 | } |
7178 | } else { |
7179 | topline = 1; |
7180 | } |
7181 | |
7182 | |
7183 | /* |
7184 | * Set all scrollbind windows to the same topline. |
7185 | */ |
7186 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
7187 | curwin = wp; |
7188 | if (curwin->w_p_scb) { |
7189 | curbuf = curwin->w_buffer; |
7190 | y = topline - curwin->w_topline; |
7191 | if (y > 0) |
7192 | scrollup(y, TRUE); |
7193 | else |
7194 | scrolldown(-y, TRUE); |
7195 | curwin->w_scbind_pos = topline; |
7196 | redraw_later(VALID); |
7197 | cursor_correct(); |
7198 | curwin->w_redr_status = TRUE; |
7199 | } |
7200 | } |
7201 | curwin = save_curwin; |
7202 | curbuf = save_curbuf; |
7203 | if (curwin->w_p_scb) { |
7204 | did_syncbind = TRUE; |
7205 | checkpcmark(); |
7206 | if (old_linenr != curwin->w_cursor.lnum) { |
7207 | char_u ctrl_o[2]; |
7208 | |
7209 | ctrl_o[0] = Ctrl_O; |
7210 | ctrl_o[1] = 0; |
7211 | ins_typebuf(ctrl_o, REMAP_NONE, 0, TRUE, FALSE); |
7212 | } |
7213 | } |
7214 | } |
7215 | |
7216 | |
7217 | static void ex_read(exarg_T *eap) |
7218 | { |
7219 | int i; |
7220 | int empty = (curbuf->b_ml.ml_flags & ML_EMPTY); |
7221 | linenr_T lnum; |
7222 | |
7223 | if (eap->usefilter) /* :r!cmd */ |
7224 | do_bang(1, eap, FALSE, FALSE, TRUE); |
7225 | else { |
7226 | if (u_save(eap->line2, (linenr_T)(eap->line2 + 1)) == FAIL) |
7227 | return; |
7228 | |
7229 | if (*eap->arg == NUL) { |
7230 | if (check_fname() == FAIL) /* check for no file name */ |
7231 | return; |
7232 | i = readfile(curbuf->b_ffname, curbuf->b_fname, |
7233 | eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0); |
7234 | } else { |
7235 | if (vim_strchr(p_cpo, CPO_ALTREAD) != NULL) |
7236 | (void)setaltfname(eap->arg, eap->arg, (linenr_T)1); |
7237 | i = readfile(eap->arg, NULL, |
7238 | eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0); |
7239 | |
7240 | } |
7241 | if (i != OK) { |
7242 | if (!aborting()) { |
7243 | EMSG2(_(e_notopen), eap->arg); |
7244 | } |
7245 | } else { |
7246 | if (empty && exmode_active) { |
7247 | /* Delete the empty line that remains. Historically ex does |
7248 | * this but vi doesn't. */ |
7249 | if (eap->line2 == 0) |
7250 | lnum = curbuf->b_ml.ml_line_count; |
7251 | else |
7252 | lnum = 1; |
7253 | if (*ml_get(lnum) == NUL && u_savedel(lnum, 1L) == OK) { |
7254 | ml_delete(lnum, false); |
7255 | if (curwin->w_cursor.lnum > 1 |
7256 | && curwin->w_cursor.lnum >= lnum) { |
7257 | curwin->w_cursor.lnum--; |
7258 | } |
7259 | deleted_lines_mark(lnum, 1L); |
7260 | } |
7261 | } |
7262 | redraw_curbuf_later(VALID); |
7263 | } |
7264 | } |
7265 | } |
7266 | |
7267 | static char_u *prev_dir = NULL; |
7268 | |
7269 | #if defined(EXITFREE) |
7270 | void free_cd_dir(void) |
7271 | { |
7272 | XFREE_CLEAR(prev_dir); |
7273 | XFREE_CLEAR(globaldir); |
7274 | } |
7275 | |
7276 | #endif |
7277 | |
7278 | /// Deal with the side effects of changing the current directory. |
7279 | /// |
7280 | /// @param scope Scope of the function call (global, tab or window). |
7281 | void post_chdir(CdScope scope, bool trigger_dirchanged) |
7282 | { |
7283 | // Always overwrite the window-local CWD. |
7284 | XFREE_CLEAR(curwin->w_localdir); |
7285 | |
7286 | // Overwrite the tab-local CWD for :cd, :tcd. |
7287 | if (scope >= kCdScopeTab) { |
7288 | XFREE_CLEAR(curtab->tp_localdir); |
7289 | } |
7290 | |
7291 | if (scope < kCdScopeGlobal) { |
7292 | // If still in global directory, set CWD as the global directory. |
7293 | if (globaldir == NULL && prev_dir != NULL) { |
7294 | globaldir = vim_strsave(prev_dir); |
7295 | } |
7296 | } |
7297 | |
7298 | char cwd[MAXPATHL]; |
7299 | if (os_dirname((char_u *)cwd, MAXPATHL) != OK) { |
7300 | return; |
7301 | } |
7302 | switch (scope) { |
7303 | case kCdScopeGlobal: |
7304 | // We are now in the global directory, no need to remember its name. |
7305 | XFREE_CLEAR(globaldir); |
7306 | break; |
7307 | case kCdScopeTab: |
7308 | curtab->tp_localdir = (char_u *)xstrdup(cwd); |
7309 | break; |
7310 | case kCdScopeWindow: |
7311 | curwin->w_localdir = (char_u *)xstrdup(cwd); |
7312 | break; |
7313 | case kCdScopeInvalid: |
7314 | assert(false); |
7315 | } |
7316 | |
7317 | shorten_fnames(true); |
7318 | |
7319 | if (trigger_dirchanged) { |
7320 | do_autocmd_dirchanged(cwd, scope); |
7321 | } |
7322 | } |
7323 | |
7324 | /// `:cd`, `:tcd`, `:lcd`, `:chdir`, `:tchdir` and `:lchdir`. |
7325 | void ex_cd(exarg_T *eap) |
7326 | { |
7327 | char_u *new_dir; |
7328 | char_u *tofree; |
7329 | |
7330 | new_dir = eap->arg; |
7331 | #if !defined(UNIX) |
7332 | /* for non-UNIX ":cd" means: print current directory */ |
7333 | if (*new_dir == NUL) |
7334 | ex_pwd(NULL); |
7335 | else |
7336 | #endif |
7337 | { |
7338 | if (allbuf_locked()) |
7339 | return; |
7340 | |
7341 | /* ":cd -": Change to previous directory */ |
7342 | if (STRCMP(new_dir, "-" ) == 0) { |
7343 | if (prev_dir == NULL) { |
7344 | EMSG(_("E186: No previous directory" )); |
7345 | return; |
7346 | } |
7347 | new_dir = prev_dir; |
7348 | } |
7349 | |
7350 | /* Save current directory for next ":cd -" */ |
7351 | tofree = prev_dir; |
7352 | if (os_dirname(NameBuff, MAXPATHL) == OK) |
7353 | prev_dir = vim_strsave(NameBuff); |
7354 | else |
7355 | prev_dir = NULL; |
7356 | |
7357 | #if defined(UNIX) |
7358 | // On Unix ":cd" means: go to home directory. |
7359 | if (*new_dir == NUL) { |
7360 | // Use NameBuff for home directory name. |
7361 | expand_env((char_u *)"$HOME" , NameBuff, MAXPATHL); |
7362 | new_dir = NameBuff; |
7363 | } |
7364 | #endif |
7365 | CdScope scope = kCdScopeGlobal; // Depends on command invoked |
7366 | |
7367 | switch (eap->cmdidx) { |
7368 | case CMD_tcd: |
7369 | case CMD_tchdir: |
7370 | scope = kCdScopeTab; |
7371 | break; |
7372 | case CMD_lcd: |
7373 | case CMD_lchdir: |
7374 | scope = kCdScopeWindow; |
7375 | break; |
7376 | default: |
7377 | break; |
7378 | } |
7379 | |
7380 | if (vim_chdir(new_dir)) { |
7381 | EMSG(_(e_failed)); |
7382 | } else { |
7383 | post_chdir(scope, true); |
7384 | // Echo the new current directory if the command was typed. |
7385 | if (KeyTyped || p_verbose >= 5) { |
7386 | ex_pwd(eap); |
7387 | } |
7388 | } |
7389 | |
7390 | xfree(tofree); |
7391 | } |
7392 | } |
7393 | |
7394 | /* |
7395 | * ":pwd". |
7396 | */ |
7397 | static void ex_pwd(exarg_T *eap) |
7398 | { |
7399 | if (os_dirname(NameBuff, MAXPATHL) == OK) { |
7400 | #ifdef BACKSLASH_IN_FILENAME |
7401 | slash_adjust(NameBuff); |
7402 | #endif |
7403 | msg(NameBuff); |
7404 | } else |
7405 | EMSG(_("E187: Unknown" )); |
7406 | } |
7407 | |
7408 | /* |
7409 | * ":=". |
7410 | */ |
7411 | static void ex_equal(exarg_T *eap) |
7412 | { |
7413 | smsg("%" PRId64, (int64_t)eap->line2); |
7414 | ex_may_print(eap); |
7415 | } |
7416 | |
7417 | static void ex_sleep(exarg_T *eap) |
7418 | { |
7419 | int n; |
7420 | long len; |
7421 | |
7422 | if (cursor_valid()) { |
7423 | n = curwin->w_winrow + curwin->w_wrow - msg_scrolled; |
7424 | if (n >= 0) |
7425 | ui_cursor_goto(n, curwin->w_wincol + curwin->w_wcol); |
7426 | } |
7427 | |
7428 | len = eap->line2; |
7429 | switch (*eap->arg) { |
7430 | case 'm': break; |
7431 | case NUL: len *= 1000L; break; |
7432 | default: EMSG2(_(e_invarg2), eap->arg); return; |
7433 | } |
7434 | do_sleep(len); |
7435 | } |
7436 | |
7437 | /* |
7438 | * Sleep for "msec" milliseconds, but keep checking for a CTRL-C every second. |
7439 | */ |
7440 | void do_sleep(long msec) |
7441 | { |
7442 | ui_flush(); // flush before waiting |
7443 | for (long left = msec; !got_int && left > 0; left -= 1000L) { |
7444 | int next = left > 1000l ? 1000 : (int)left; |
7445 | LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, (int)next, got_int); |
7446 | os_breakcheck(); |
7447 | } |
7448 | |
7449 | // If CTRL-C was typed to interrupt the sleep, drop the CTRL-C from the |
7450 | // input buffer, otherwise a following call to input() fails. |
7451 | if (got_int) { |
7452 | (void)vpeekc(); |
7453 | } |
7454 | } |
7455 | |
7456 | static void do_exmap(exarg_T *eap, int isabbrev) |
7457 | { |
7458 | int mode; |
7459 | char_u *cmdp; |
7460 | |
7461 | cmdp = eap->cmd; |
7462 | mode = get_map_mode(&cmdp, eap->forceit || isabbrev); |
7463 | |
7464 | switch (do_map((*cmdp == 'n') ? 2 : (*cmdp == 'u'), |
7465 | eap->arg, mode, isabbrev)) { |
7466 | case 1: EMSG(_(e_invarg)); |
7467 | break; |
7468 | case 2: EMSG(isabbrev ? _(e_noabbr) : _(e_nomap)); |
7469 | break; |
7470 | } |
7471 | } |
7472 | |
7473 | /* |
7474 | * ":winsize" command (obsolete). |
7475 | */ |
7476 | static void ex_winsize(exarg_T *eap) |
7477 | { |
7478 | char_u *arg = eap->arg; |
7479 | int w = getdigits_int(&arg, false, 10); |
7480 | arg = skipwhite(arg); |
7481 | char_u *p = arg; |
7482 | int h = getdigits_int(&arg, false, 10); |
7483 | if (*p != NUL && *arg == NUL) { |
7484 | screen_resize(w, h); |
7485 | } else { |
7486 | EMSG(_("E465: :winsize requires two number arguments" )); |
7487 | } |
7488 | } |
7489 | |
7490 | static void ex_wincmd(exarg_T *eap) |
7491 | { |
7492 | int xchar = NUL; |
7493 | char_u *p; |
7494 | |
7495 | if (*eap->arg == 'g' || *eap->arg == Ctrl_G) { |
7496 | /* CTRL-W g and CTRL-W CTRL-G have an extra command character */ |
7497 | if (eap->arg[1] == NUL) { |
7498 | EMSG(_(e_invarg)); |
7499 | return; |
7500 | } |
7501 | xchar = eap->arg[1]; |
7502 | p = eap->arg + 2; |
7503 | } else |
7504 | p = eap->arg + 1; |
7505 | |
7506 | eap->nextcmd = check_nextcmd(p); |
7507 | p = skipwhite(p); |
7508 | if (*p != NUL && *p != '"' && eap->nextcmd == NULL) |
7509 | EMSG(_(e_invarg)); |
7510 | else if (!eap->skip) { |
7511 | /* Pass flags on for ":vertical wincmd ]". */ |
7512 | postponed_split_flags = cmdmod.split; |
7513 | postponed_split_tab = cmdmod.tab; |
7514 | do_window(*eap->arg, eap->addr_count > 0 ? eap->line2 : 0L, xchar); |
7515 | postponed_split_flags = 0; |
7516 | postponed_split_tab = 0; |
7517 | } |
7518 | } |
7519 | |
7520 | /* |
7521 | * Handle command that work like operators: ":delete", ":yank", ":>" and ":<". |
7522 | */ |
7523 | static void ex_operators(exarg_T *eap) |
7524 | { |
7525 | oparg_T oa; |
7526 | |
7527 | clear_oparg(&oa); |
7528 | oa.regname = eap->regname; |
7529 | oa.start.lnum = eap->line1; |
7530 | oa.end.lnum = eap->line2; |
7531 | oa.line_count = eap->line2 - eap->line1 + 1; |
7532 | oa.motion_type = kMTLineWise; |
7533 | virtual_op = kFalse; |
7534 | if (eap->cmdidx != CMD_yank) { // position cursor for undo |
7535 | setpcmark(); |
7536 | curwin->w_cursor.lnum = eap->line1; |
7537 | beginline(BL_SOL | BL_FIX); |
7538 | } |
7539 | |
7540 | if (VIsual_active) |
7541 | end_visual_mode(); |
7542 | |
7543 | switch (eap->cmdidx) { |
7544 | case CMD_delete: |
7545 | oa.op_type = OP_DELETE; |
7546 | op_delete(&oa); |
7547 | break; |
7548 | |
7549 | case CMD_yank: |
7550 | oa.op_type = OP_YANK; |
7551 | (void)op_yank(&oa, true, false); |
7552 | break; |
7553 | |
7554 | default: /* CMD_rshift or CMD_lshift */ |
7555 | if ( |
7556 | (eap->cmdidx == CMD_rshift) ^ curwin->w_p_rl |
7557 | ) |
7558 | oa.op_type = OP_RSHIFT; |
7559 | else |
7560 | oa.op_type = OP_LSHIFT; |
7561 | op_shift(&oa, FALSE, eap->amount); |
7562 | break; |
7563 | } |
7564 | virtual_op = kNone; |
7565 | ex_may_print(eap); |
7566 | } |
7567 | |
7568 | /* |
7569 | * ":put". |
7570 | */ |
7571 | static void ex_put(exarg_T *eap) |
7572 | { |
7573 | /* ":0put" works like ":1put!". */ |
7574 | if (eap->line2 == 0) { |
7575 | eap->line2 = 1; |
7576 | eap->forceit = TRUE; |
7577 | } |
7578 | curwin->w_cursor.lnum = eap->line2; |
7579 | do_put(eap->regname, NULL, eap->forceit ? BACKWARD : FORWARD, 1, |
7580 | PUT_LINE|PUT_CURSLINE); |
7581 | } |
7582 | |
7583 | /* |
7584 | * Handle ":copy" and ":move". |
7585 | */ |
7586 | static void ex_copymove(exarg_T *eap) |
7587 | { |
7588 | long n = get_address(eap, &eap->arg, eap->addr_type, false, false, 1); |
7589 | if (eap->arg == NULL) { // error detected |
7590 | eap->nextcmd = NULL; |
7591 | return; |
7592 | } |
7593 | get_flags(eap); |
7594 | |
7595 | /* |
7596 | * move or copy lines from 'eap->line1'-'eap->line2' to below line 'n' |
7597 | */ |
7598 | if (n == MAXLNUM || n < 0 || n > curbuf->b_ml.ml_line_count) { |
7599 | EMSG(_(e_invaddr)); |
7600 | return; |
7601 | } |
7602 | |
7603 | if (eap->cmdidx == CMD_move) { |
7604 | if (do_move(eap->line1, eap->line2, n) == FAIL) |
7605 | return; |
7606 | } else |
7607 | ex_copy(eap->line1, eap->line2, n); |
7608 | u_clearline(); |
7609 | beginline(BL_SOL | BL_FIX); |
7610 | ex_may_print(eap); |
7611 | } |
7612 | |
7613 | /* |
7614 | * Print the current line if flags were given to the Ex command. |
7615 | */ |
7616 | void ex_may_print(exarg_T *eap) |
7617 | { |
7618 | if (eap->flags != 0) { |
7619 | print_line(curwin->w_cursor.lnum, (eap->flags & EXFLAG_NR), |
7620 | (eap->flags & EXFLAG_LIST)); |
7621 | ex_no_reprint = TRUE; |
7622 | } |
7623 | } |
7624 | |
7625 | /// ":smagic" and ":snomagic". |
7626 | static void ex_submagic(exarg_T *eap) |
7627 | { |
7628 | int magic_save = p_magic; |
7629 | |
7630 | p_magic = (eap->cmdidx == CMD_smagic); |
7631 | ex_substitute(eap); |
7632 | p_magic = magic_save; |
7633 | } |
7634 | |
7635 | /* |
7636 | * ":join". |
7637 | */ |
7638 | static void ex_join(exarg_T *eap) |
7639 | { |
7640 | curwin->w_cursor.lnum = eap->line1; |
7641 | if (eap->line1 == eap->line2) { |
7642 | if (eap->addr_count >= 2) /* :2,2join does nothing */ |
7643 | return; |
7644 | if (eap->line2 == curbuf->b_ml.ml_line_count) { |
7645 | beep_flush(); |
7646 | return; |
7647 | } |
7648 | ++eap->line2; |
7649 | } |
7650 | do_join(eap->line2 - eap->line1 + 1, !eap->forceit, TRUE, TRUE, true); |
7651 | beginline(BL_WHITE | BL_FIX); |
7652 | ex_may_print(eap); |
7653 | } |
7654 | |
7655 | /* |
7656 | * ":[addr]@r": execute register |
7657 | */ |
7658 | static void ex_at(exarg_T *eap) |
7659 | { |
7660 | int prev_len = typebuf.tb_len; |
7661 | |
7662 | curwin->w_cursor.lnum = eap->line2; |
7663 | check_cursor_col(); |
7664 | |
7665 | // Get the register name. No name means use the previous one. |
7666 | int c = *eap->arg; |
7667 | if (c == NUL) { |
7668 | c = '@'; |
7669 | } |
7670 | |
7671 | /* Put the register in the typeahead buffer with the "silent" flag. */ |
7672 | if (do_execreg(c, TRUE, vim_strchr(p_cpo, CPO_EXECBUF) != NULL, TRUE) |
7673 | == FAIL) { |
7674 | beep_flush(); |
7675 | } else { |
7676 | int save_efr = exec_from_reg; |
7677 | |
7678 | exec_from_reg = TRUE; |
7679 | |
7680 | /* |
7681 | * Execute from the typeahead buffer. |
7682 | * Continue until the stuff buffer is empty and all added characters |
7683 | * have been consumed. |
7684 | */ |
7685 | while (!stuff_empty() || typebuf.tb_len > prev_len) |
7686 | (void)do_cmdline(NULL, getexline, NULL, DOCMD_NOWAIT|DOCMD_VERBOSE); |
7687 | |
7688 | exec_from_reg = save_efr; |
7689 | } |
7690 | } |
7691 | |
7692 | /* |
7693 | * ":!". |
7694 | */ |
7695 | static void ex_bang(exarg_T *eap) |
7696 | { |
7697 | do_bang(eap->addr_count, eap, eap->forceit, TRUE, TRUE); |
7698 | } |
7699 | |
7700 | /* |
7701 | * ":undo". |
7702 | */ |
7703 | static void ex_undo(exarg_T *eap) |
7704 | { |
7705 | if (eap->addr_count == 1) { // :undo 123 |
7706 | undo_time(eap->line2, false, false, true); |
7707 | } else { |
7708 | u_undo(1); |
7709 | } |
7710 | } |
7711 | |
7712 | static void ex_wundo(exarg_T *eap) |
7713 | { |
7714 | char_u hash[UNDO_HASH_SIZE]; |
7715 | |
7716 | u_compute_hash(hash); |
7717 | u_write_undo((char *) eap->arg, eap->forceit, curbuf, hash); |
7718 | } |
7719 | |
7720 | static void ex_rundo(exarg_T *eap) |
7721 | { |
7722 | char_u hash[UNDO_HASH_SIZE]; |
7723 | |
7724 | u_compute_hash(hash); |
7725 | u_read_undo((char *) eap->arg, hash, NULL); |
7726 | } |
7727 | |
7728 | /// ":redo". |
7729 | static void ex_redo(exarg_T *eap) |
7730 | { |
7731 | u_redo(1); |
7732 | } |
7733 | |
7734 | /// ":earlier" and ":later". |
7735 | static void ex_later(exarg_T *eap) |
7736 | { |
7737 | long count = 0; |
7738 | bool sec = false; |
7739 | bool file = false; |
7740 | char_u *p = eap->arg; |
7741 | |
7742 | if (*p == NUL) { |
7743 | count = 1; |
7744 | } else if (isdigit(*p)) { |
7745 | count = getdigits_long(&p, false, 0); |
7746 | switch (*p) { |
7747 | case 's': ++p; sec = true; break; |
7748 | case 'm': ++p; sec = true; count *= 60; break; |
7749 | case 'h': ++p; sec = true; count *= 60 * 60; break; |
7750 | case 'd': ++p; sec = true; count *= 24 * 60 * 60; break; |
7751 | case 'f': ++p; file = true; break; |
7752 | } |
7753 | } |
7754 | |
7755 | if (*p != NUL) { |
7756 | EMSG2(_(e_invarg2), eap->arg); |
7757 | } else { |
7758 | undo_time(eap->cmdidx == CMD_earlier ? -count : count, |
7759 | sec, file, false); |
7760 | } |
7761 | } |
7762 | |
7763 | /* |
7764 | * ":redir": start/stop redirection. |
7765 | */ |
7766 | static void ex_redir(exarg_T *eap) |
7767 | { |
7768 | char *mode; |
7769 | char_u *fname; |
7770 | char_u *arg = eap->arg; |
7771 | |
7772 | if (STRICMP(eap->arg, "END" ) == 0) |
7773 | close_redir(); |
7774 | else { |
7775 | if (*arg == '>') { |
7776 | ++arg; |
7777 | if (*arg == '>') { |
7778 | ++arg; |
7779 | mode = "a" ; |
7780 | } else |
7781 | mode = "w" ; |
7782 | arg = skipwhite(arg); |
7783 | |
7784 | close_redir(); |
7785 | |
7786 | /* Expand environment variables and "~/". */ |
7787 | fname = expand_env_save(arg); |
7788 | if (fname == NULL) |
7789 | return; |
7790 | |
7791 | redir_fd = open_exfile(fname, eap->forceit, mode); |
7792 | xfree(fname); |
7793 | } else if (*arg == '@') { |
7794 | /* redirect to a register a-z (resp. A-Z for appending) */ |
7795 | close_redir(); |
7796 | ++arg; |
7797 | if (valid_yank_reg(*arg, true) && *arg != '_') { |
7798 | redir_reg = *arg++; |
7799 | if (*arg == '>' && arg[1] == '>') /* append */ |
7800 | arg += 2; |
7801 | else { |
7802 | /* Can use both "@a" and "@a>". */ |
7803 | if (*arg == '>') |
7804 | arg++; |
7805 | // Make register empty when not using @A-@Z and the |
7806 | // command is valid. |
7807 | if (*arg == NUL && !isupper(redir_reg)) { |
7808 | write_reg_contents(redir_reg, (char_u *)"" , 0, false); |
7809 | } |
7810 | } |
7811 | } |
7812 | if (*arg != NUL) { |
7813 | redir_reg = 0; |
7814 | EMSG2(_(e_invarg2), eap->arg); |
7815 | } |
7816 | } else if (*arg == '=' && arg[1] == '>') { |
7817 | int append; |
7818 | |
7819 | /* redirect to a variable */ |
7820 | close_redir(); |
7821 | arg += 2; |
7822 | |
7823 | if (*arg == '>') { |
7824 | ++arg; |
7825 | append = TRUE; |
7826 | } else |
7827 | append = FALSE; |
7828 | |
7829 | if (var_redir_start(skipwhite(arg), append) == OK) |
7830 | redir_vname = 1; |
7831 | } |
7832 | /* TODO: redirect to a buffer */ |
7833 | else |
7834 | EMSG2(_(e_invarg2), eap->arg); |
7835 | } |
7836 | |
7837 | /* Make sure redirection is not off. Can happen for cmdline completion |
7838 | * that indirectly invokes a command to catch its output. */ |
7839 | if (redir_fd != NULL |
7840 | || redir_reg || redir_vname |
7841 | ) |
7842 | redir_off = FALSE; |
7843 | } |
7844 | |
7845 | /// ":redraw": force redraw |
7846 | static void ex_redraw(exarg_T *eap) |
7847 | { |
7848 | if (State & CMDPREVIEW) { |
7849 | return; // Ignore :redraw during 'inccommand' preview. #9777 |
7850 | } |
7851 | int r = RedrawingDisabled; |
7852 | int p = p_lz; |
7853 | |
7854 | RedrawingDisabled = 0; |
7855 | p_lz = FALSE; |
7856 | validate_cursor(); |
7857 | update_topline(); |
7858 | if (eap->forceit) { |
7859 | redraw_all_later(NOT_VALID); |
7860 | } |
7861 | update_screen(eap->forceit ? NOT_VALID |
7862 | : VIsual_active ? INVERTED : 0); |
7863 | if (need_maketitle) { |
7864 | maketitle(); |
7865 | } |
7866 | RedrawingDisabled = r; |
7867 | p_lz = p; |
7868 | |
7869 | /* Reset msg_didout, so that a message that's there is overwritten. */ |
7870 | msg_didout = FALSE; |
7871 | msg_col = 0; |
7872 | |
7873 | /* No need to wait after an intentional redraw. */ |
7874 | need_wait_return = FALSE; |
7875 | |
7876 | ui_flush(); |
7877 | } |
7878 | |
7879 | /// ":redrawstatus": force redraw of status line(s) |
7880 | static void ex_redrawstatus(exarg_T *eap) |
7881 | { |
7882 | if (State & CMDPREVIEW) { |
7883 | return; // Ignore :redrawstatus during 'inccommand' preview. #9777 |
7884 | } |
7885 | int r = RedrawingDisabled; |
7886 | int p = p_lz; |
7887 | |
7888 | RedrawingDisabled = 0; |
7889 | p_lz = FALSE; |
7890 | if (eap->forceit) |
7891 | status_redraw_all(); |
7892 | else |
7893 | status_redraw_curbuf(); |
7894 | update_screen( |
7895 | VIsual_active ? INVERTED : |
7896 | 0); |
7897 | RedrawingDisabled = r; |
7898 | p_lz = p; |
7899 | ui_flush(); |
7900 | } |
7901 | |
7902 | // ":redrawtabline": force redraw of the tabline |
7903 | static void ex_redrawtabline(exarg_T *eap FUNC_ATTR_UNUSED) |
7904 | { |
7905 | const int r = RedrawingDisabled; |
7906 | const int p = p_lz; |
7907 | |
7908 | RedrawingDisabled = 0; |
7909 | p_lz = false; |
7910 | |
7911 | draw_tabline(); |
7912 | |
7913 | RedrawingDisabled = r; |
7914 | p_lz = p; |
7915 | ui_flush(); |
7916 | } |
7917 | |
7918 | static void close_redir(void) |
7919 | { |
7920 | if (redir_fd != NULL) { |
7921 | fclose(redir_fd); |
7922 | redir_fd = NULL; |
7923 | } |
7924 | redir_reg = 0; |
7925 | if (redir_vname) { |
7926 | var_redir_stop(); |
7927 | redir_vname = 0; |
7928 | } |
7929 | } |
7930 | |
7931 | #ifdef USE_CRNL |
7932 | # define MKSESSION_NL |
7933 | static int mksession_nl = FALSE; /* use NL only in put_eol() */ |
7934 | #endif |
7935 | |
7936 | /* |
7937 | * ":mkexrc", ":mkvimrc", ":mkview" and ":mksession". |
7938 | */ |
7939 | static void ex_mkrc(exarg_T *eap) |
7940 | { |
7941 | FILE *fd; |
7942 | int failed = false; |
7943 | int view_session = false; |
7944 | int using_vdir = false; // using 'viewdir'? |
7945 | char *viewFile = NULL; |
7946 | unsigned *flagp; |
7947 | |
7948 | if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) { |
7949 | view_session = TRUE; |
7950 | } |
7951 | |
7952 | /* Use the short file name until ":lcd" is used. We also don't use the |
7953 | * short file name when 'acd' is set, that is checked later. */ |
7954 | did_lcd = FALSE; |
7955 | |
7956 | char *fname; |
7957 | // ":mkview" or ":mkview 9": generate file name with 'viewdir' |
7958 | if (eap->cmdidx == CMD_mkview |
7959 | && (*eap->arg == NUL |
7960 | || (ascii_isdigit(*eap->arg) && eap->arg[1] == NUL))) { |
7961 | eap->forceit = true; |
7962 | fname = get_view_file(*eap->arg); |
7963 | if (fname == NULL) { |
7964 | return; |
7965 | } |
7966 | viewFile = fname; |
7967 | using_vdir = true; |
7968 | } else if (*eap->arg != NUL) { |
7969 | fname = (char *) eap->arg; |
7970 | } else if (eap->cmdidx == CMD_mkvimrc) { |
7971 | fname = VIMRC_FILE; |
7972 | } else if (eap->cmdidx == CMD_mksession) { |
7973 | fname = SESSION_FILE; |
7974 | } else { |
7975 | fname = EXRC_FILE; |
7976 | } |
7977 | |
7978 | /* When using 'viewdir' may have to create the directory. */ |
7979 | if (using_vdir && !os_isdir(p_vdir)) { |
7980 | vim_mkdir_emsg((const char *)p_vdir, 0755); |
7981 | } |
7982 | |
7983 | fd = open_exfile((char_u *) fname, eap->forceit, WRITEBIN); |
7984 | if (fd != NULL) { |
7985 | if (eap->cmdidx == CMD_mkview) |
7986 | flagp = &vop_flags; |
7987 | else |
7988 | flagp = &ssop_flags; |
7989 | |
7990 | #ifdef MKSESSION_NL |
7991 | /* "unix" in 'sessionoptions': use NL line separator */ |
7992 | if (view_session && (*flagp & SSOP_UNIX)) |
7993 | mksession_nl = TRUE; |
7994 | #endif |
7995 | |
7996 | /* Write the version command for :mkvimrc */ |
7997 | if (eap->cmdidx == CMD_mkvimrc) |
7998 | (void)put_line(fd, "version 6.0" ); |
7999 | |
8000 | if (eap->cmdidx == CMD_mksession) { |
8001 | if (put_line(fd, "let SessionLoad = 1" ) == FAIL) |
8002 | failed = TRUE; |
8003 | } |
8004 | |
8005 | if (!view_session |
8006 | || (eap->cmdidx == CMD_mksession |
8007 | && (*flagp & SSOP_OPTIONS))) |
8008 | failed |= (makemap(fd, NULL) == FAIL |
8009 | || makeset(fd, OPT_GLOBAL, FALSE) == FAIL); |
8010 | |
8011 | if (!failed && view_session) { |
8012 | if (put_line(fd, |
8013 | "let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0" ) |
8014 | == FAIL) |
8015 | failed = TRUE; |
8016 | if (eap->cmdidx == CMD_mksession) { |
8017 | char_u *dirnow; /* current directory */ |
8018 | |
8019 | dirnow = xmalloc(MAXPATHL); |
8020 | /* |
8021 | * Change to session file's dir. |
8022 | */ |
8023 | if (os_dirname(dirnow, MAXPATHL) == FAIL |
8024 | || os_chdir((char *)dirnow) != 0) |
8025 | *dirnow = NUL; |
8026 | if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) { |
8027 | if (vim_chdirfile((char_u *) fname) == OK) { |
8028 | shorten_fnames(true); |
8029 | } |
8030 | } else if (*dirnow != NUL |
8031 | && (ssop_flags & SSOP_CURDIR) && globaldir != NULL) { |
8032 | if (os_chdir((char *)globaldir) == 0) |
8033 | shorten_fnames(TRUE); |
8034 | } |
8035 | |
8036 | failed |= (makeopens(fd, dirnow) == FAIL); |
8037 | |
8038 | /* restore original dir */ |
8039 | if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) |
8040 | || ((ssop_flags & SSOP_CURDIR) && globaldir != |
8041 | NULL))) { |
8042 | if (os_chdir((char *)dirnow) != 0) |
8043 | EMSG(_(e_prev_dir)); |
8044 | shorten_fnames(TRUE); |
8045 | /* restore original dir */ |
8046 | if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) |
8047 | || ((ssop_flags & SSOP_CURDIR) && globaldir != |
8048 | NULL))) { |
8049 | if (os_chdir((char *)dirnow) != 0) |
8050 | EMSG(_(e_prev_dir)); |
8051 | shorten_fnames(TRUE); |
8052 | } |
8053 | } |
8054 | xfree(dirnow); |
8055 | } else { |
8056 | failed |= (put_view(fd, curwin, !using_vdir, flagp, |
8057 | -1) == FAIL); |
8058 | } |
8059 | if (put_line(fd, "let &so = s:so_save | let &siso = s:siso_save" ) |
8060 | == FAIL) |
8061 | failed = TRUE; |
8062 | if (put_line(fd, "doautoall SessionLoadPost" ) == FAIL) |
8063 | failed = TRUE; |
8064 | if (eap->cmdidx == CMD_mksession) { |
8065 | if (put_line(fd, "unlet SessionLoad" ) == FAIL) |
8066 | failed = TRUE; |
8067 | } |
8068 | } |
8069 | if (put_line(fd, "\" vim: set ft=vim :" ) == FAIL) |
8070 | failed = TRUE; |
8071 | |
8072 | failed |= fclose(fd); |
8073 | |
8074 | if (failed) { |
8075 | EMSG(_(e_write)); |
8076 | } else if (eap->cmdidx == CMD_mksession) { |
8077 | // successful session write - set v:this_session |
8078 | char *const tbuf = xmalloc(MAXPATHL); |
8079 | if (vim_FullName(fname, tbuf, MAXPATHL, false) == OK) { |
8080 | set_vim_var_string(VV_THIS_SESSION, tbuf, -1); |
8081 | } |
8082 | xfree(tbuf); |
8083 | } |
8084 | #ifdef MKSESSION_NL |
8085 | mksession_nl = FALSE; |
8086 | #endif |
8087 | } |
8088 | |
8089 | xfree(viewFile); |
8090 | } |
8091 | |
8092 | /// Try creating a directory, give error message on failure |
8093 | /// |
8094 | /// @param[in] name Directory to create. |
8095 | /// @param[in] prot Directory permissions. |
8096 | /// |
8097 | /// @return OK in case of success, FAIL otherwise. |
8098 | int vim_mkdir_emsg(const char *const name, const int prot) |
8099 | FUNC_ATTR_NONNULL_ALL |
8100 | { |
8101 | int ret; |
8102 | if ((ret = os_mkdir(name, prot)) != 0) { |
8103 | EMSG3(_(e_mkdir), name, os_strerror(ret)); |
8104 | return FAIL; |
8105 | } |
8106 | return OK; |
8107 | } |
8108 | |
8109 | /* |
8110 | * Open a file for writing for an Ex command, with some checks. |
8111 | * Return file descriptor, or NULL on failure. |
8112 | */ |
8113 | FILE * |
8114 | open_exfile ( |
8115 | char_u *fname, |
8116 | int forceit, |
8117 | char *mode /* "w" for create new file or "a" for append */ |
8118 | ) |
8119 | { |
8120 | FILE *fd; |
8121 | |
8122 | #ifdef UNIX |
8123 | /* with Unix it is possible to open a directory */ |
8124 | if (os_isdir(fname)) { |
8125 | EMSG2(_(e_isadir2), fname); |
8126 | return NULL; |
8127 | } |
8128 | #endif |
8129 | if (!forceit && *mode != 'a' && os_path_exists(fname)) { |
8130 | EMSG2(_("E189: \"%s\" exists (add ! to override)" ), fname); |
8131 | return NULL; |
8132 | } |
8133 | |
8134 | if ((fd = os_fopen((char *)fname, mode)) == NULL) { |
8135 | EMSG2(_("E190: Cannot open \"%s\" for writing" ), fname); |
8136 | } |
8137 | |
8138 | return fd; |
8139 | } |
8140 | |
8141 | /* |
8142 | * ":mark" and ":k". |
8143 | */ |
8144 | static void ex_mark(exarg_T *eap) |
8145 | { |
8146 | pos_T pos; |
8147 | |
8148 | if (*eap->arg == NUL) /* No argument? */ |
8149 | EMSG(_(e_argreq)); |
8150 | else if (eap->arg[1] != NUL) /* more than one character? */ |
8151 | EMSG(_(e_trailing)); |
8152 | else { |
8153 | pos = curwin->w_cursor; /* save curwin->w_cursor */ |
8154 | curwin->w_cursor.lnum = eap->line2; |
8155 | beginline(BL_WHITE | BL_FIX); |
8156 | if (setmark(*eap->arg) == FAIL) /* set mark */ |
8157 | EMSG(_("E191: Argument must be a letter or forward/backward quote" )); |
8158 | curwin->w_cursor = pos; /* restore curwin->w_cursor */ |
8159 | } |
8160 | } |
8161 | |
8162 | /* |
8163 | * Update w_topline, w_leftcol and the cursor position. |
8164 | */ |
8165 | void update_topline_cursor(void) |
8166 | { |
8167 | check_cursor(); /* put cursor on valid line */ |
8168 | update_topline(); |
8169 | if (!curwin->w_p_wrap) |
8170 | validate_cursor(); |
8171 | update_curswant(); |
8172 | } |
8173 | |
8174 | /* |
8175 | * ":normal[!] {commands}": Execute normal mode commands. |
8176 | */ |
8177 | static void ex_normal(exarg_T *eap) |
8178 | { |
8179 | if (curbuf->terminal && State & TERM_FOCUS) { |
8180 | EMSG("Can't re-enter normal mode from terminal mode" ); |
8181 | return; |
8182 | } |
8183 | int save_msg_scroll = msg_scroll; |
8184 | int save_restart_edit = restart_edit; |
8185 | int save_msg_didout = msg_didout; |
8186 | int save_State = State; |
8187 | tasave_T tabuf; |
8188 | int save_insertmode = p_im; |
8189 | int save_finish_op = finish_op; |
8190 | long save_opcount = opcount; |
8191 | char_u *arg = NULL; |
8192 | int l; |
8193 | char_u *p; |
8194 | |
8195 | if (ex_normal_lock > 0) { |
8196 | EMSG(_(e_secure)); |
8197 | return; |
8198 | } |
8199 | if (ex_normal_busy >= p_mmd) { |
8200 | EMSG(_("E192: Recursive use of :normal too deep" )); |
8201 | return; |
8202 | } |
8203 | ++ex_normal_busy; |
8204 | |
8205 | msg_scroll = FALSE; /* no msg scrolling in Normal mode */ |
8206 | restart_edit = 0; /* don't go to Insert mode */ |
8207 | p_im = FALSE; /* don't use 'insertmode' */ |
8208 | |
8209 | /* |
8210 | * vgetc() expects a CSI and K_SPECIAL to have been escaped. Don't do |
8211 | * this for the K_SPECIAL leading byte, otherwise special keys will not |
8212 | * work. |
8213 | */ |
8214 | if (has_mbyte) { |
8215 | int len = 0; |
8216 | |
8217 | /* Count the number of characters to be escaped. */ |
8218 | for (p = eap->arg; *p != NUL; ++p) { |
8219 | for (l = (*mb_ptr2len)(p) - 1; l > 0; --l) |
8220 | if (*++p == K_SPECIAL /* trailbyte K_SPECIAL or CSI */ |
8221 | ) |
8222 | len += 2; |
8223 | } |
8224 | if (len > 0) { |
8225 | arg = xmalloc(STRLEN(eap->arg) + len + 1); |
8226 | len = 0; |
8227 | for (p = eap->arg; *p != NUL; ++p) { |
8228 | arg[len++] = *p; |
8229 | for (l = (*mb_ptr2len)(p) - 1; l > 0; --l) { |
8230 | arg[len++] = *++p; |
8231 | if (*p == K_SPECIAL) { |
8232 | arg[len++] = KS_SPECIAL; |
8233 | arg[len++] = KE_FILLER; |
8234 | } |
8235 | } |
8236 | arg[len] = NUL; |
8237 | } |
8238 | } |
8239 | } |
8240 | |
8241 | /* |
8242 | * Save the current typeahead. This is required to allow using ":normal" |
8243 | * from an event handler and makes sure we don't hang when the argument |
8244 | * ends with half a command. |
8245 | */ |
8246 | save_typeahead(&tabuf); |
8247 | // TODO(philix): after save_typeahead() this is always TRUE |
8248 | if (tabuf.typebuf_valid) { |
8249 | /* |
8250 | * Repeat the :normal command for each line in the range. When no |
8251 | * range given, execute it just once, without positioning the cursor |
8252 | * first. |
8253 | */ |
8254 | do { |
8255 | if (eap->addr_count != 0) { |
8256 | curwin->w_cursor.lnum = eap->line1++; |
8257 | curwin->w_cursor.col = 0; |
8258 | check_cursor_moved(curwin); |
8259 | } |
8260 | |
8261 | exec_normal_cmd( |
8262 | arg != NULL ? arg : |
8263 | eap->arg, eap->forceit ? REMAP_NONE : REMAP_YES, FALSE); |
8264 | } while (eap->addr_count > 0 && eap->line1 <= eap->line2 && !got_int); |
8265 | } |
8266 | |
8267 | /* Might not return to the main loop when in an event handler. */ |
8268 | update_topline_cursor(); |
8269 | |
8270 | /* Restore the previous typeahead. */ |
8271 | restore_typeahead(&tabuf); |
8272 | |
8273 | --ex_normal_busy; |
8274 | msg_scroll = save_msg_scroll; |
8275 | if (force_restart_edit) { |
8276 | force_restart_edit = false; |
8277 | } else { |
8278 | // Some function (terminal_enter()) was aware of ex_normal and decided to |
8279 | // override the value of restart_edit anyway. |
8280 | restart_edit = save_restart_edit; |
8281 | } |
8282 | p_im = save_insertmode; |
8283 | finish_op = save_finish_op; |
8284 | opcount = save_opcount; |
8285 | msg_didout |= save_msg_didout; /* don't reset msg_didout now */ |
8286 | |
8287 | /* Restore the state (needed when called from a function executed for |
8288 | * 'indentexpr'). Update the mouse and cursor, they may have changed. */ |
8289 | State = save_State; |
8290 | setmouse(); |
8291 | ui_cursor_shape(); /* may show different cursor shape */ |
8292 | xfree(arg); |
8293 | } |
8294 | |
8295 | /* |
8296 | * ":startinsert", ":startreplace" and ":startgreplace" |
8297 | */ |
8298 | static void ex_startinsert(exarg_T *eap) |
8299 | { |
8300 | if (eap->forceit) { |
8301 | // cursor line can be zero on startup |
8302 | if (!curwin->w_cursor.lnum) { |
8303 | curwin->w_cursor.lnum = 1; |
8304 | } |
8305 | set_cursor_for_append_to_line(); |
8306 | } |
8307 | |
8308 | // Ignore the command when already in Insert mode. Inserting an |
8309 | // expression register that invokes a function can do this. |
8310 | if (State & INSERT) { |
8311 | return; |
8312 | } |
8313 | |
8314 | if (eap->cmdidx == CMD_startinsert) |
8315 | restart_edit = 'a'; |
8316 | else if (eap->cmdidx == CMD_startreplace) |
8317 | restart_edit = 'R'; |
8318 | else |
8319 | restart_edit = 'V'; |
8320 | |
8321 | if (!eap->forceit) { |
8322 | if (eap->cmdidx == CMD_startinsert) |
8323 | restart_edit = 'i'; |
8324 | curwin->w_curswant = 0; // avoid MAXCOL |
8325 | } |
8326 | |
8327 | if (VIsual_active) { |
8328 | showmode(); |
8329 | } |
8330 | } |
8331 | |
8332 | /* |
8333 | * ":stopinsert" |
8334 | */ |
8335 | static void ex_stopinsert(exarg_T *eap) |
8336 | { |
8337 | restart_edit = 0; |
8338 | stop_insert_mode = true; |
8339 | clearmode(); |
8340 | } |
8341 | |
8342 | /* |
8343 | * Execute normal mode command "cmd". |
8344 | * "remap" can be REMAP_NONE or REMAP_YES. |
8345 | */ |
8346 | void exec_normal_cmd(char_u *cmd, int remap, bool silent) |
8347 | { |
8348 | // Stuff the argument into the typeahead buffer. |
8349 | ins_typebuf(cmd, remap, 0, true, silent); |
8350 | exec_normal(false); |
8351 | } |
8352 | |
8353 | /// Execute normal_cmd() until there is no typeahead left. |
8354 | /// |
8355 | /// @param was_typed whether or not something was typed |
8356 | void exec_normal(bool was_typed) |
8357 | { |
8358 | oparg_T oa; |
8359 | |
8360 | clear_oparg(&oa); |
8361 | finish_op = false; |
8362 | while ((!stuff_empty() |
8363 | || ((was_typed || !typebuf_typed()) |
8364 | && typebuf.tb_len > 0)) |
8365 | && !got_int) { |
8366 | update_topline_cursor(); |
8367 | normal_cmd(&oa, true); // execute a Normal mode cmd |
8368 | } |
8369 | } |
8370 | |
8371 | static void ex_checkpath(exarg_T *eap) |
8372 | { |
8373 | find_pattern_in_path(NULL, 0, 0, FALSE, FALSE, CHECK_PATH, 1L, |
8374 | eap->forceit ? ACTION_SHOW_ALL : ACTION_SHOW, |
8375 | (linenr_T)1, (linenr_T)MAXLNUM); |
8376 | } |
8377 | |
8378 | /* |
8379 | * ":psearch" |
8380 | */ |
8381 | static void ex_psearch(exarg_T *eap) |
8382 | { |
8383 | g_do_tagpreview = p_pvh; |
8384 | ex_findpat(eap); |
8385 | g_do_tagpreview = 0; |
8386 | } |
8387 | |
8388 | static void ex_findpat(exarg_T *eap) |
8389 | { |
8390 | int whole = TRUE; |
8391 | long n; |
8392 | char_u *p; |
8393 | int action; |
8394 | |
8395 | switch (cmdnames[eap->cmdidx].cmd_name[2]) { |
8396 | case 'e': /* ":psearch", ":isearch" and ":dsearch" */ |
8397 | if (cmdnames[eap->cmdidx].cmd_name[0] == 'p') |
8398 | action = ACTION_GOTO; |
8399 | else |
8400 | action = ACTION_SHOW; |
8401 | break; |
8402 | case 'i': /* ":ilist" and ":dlist" */ |
8403 | action = ACTION_SHOW_ALL; |
8404 | break; |
8405 | case 'u': /* ":ijump" and ":djump" */ |
8406 | action = ACTION_GOTO; |
8407 | break; |
8408 | default: /* ":isplit" and ":dsplit" */ |
8409 | action = ACTION_SPLIT; |
8410 | break; |
8411 | } |
8412 | |
8413 | n = 1; |
8414 | if (ascii_isdigit(*eap->arg)) { // get count |
8415 | n = getdigits_long(&eap->arg, false, 0); |
8416 | eap->arg = skipwhite(eap->arg); |
8417 | } |
8418 | if (*eap->arg == '/') { // Match regexp, not just whole words |
8419 | whole = false; |
8420 | eap->arg++; |
8421 | p = skip_regexp(eap->arg, '/', p_magic, NULL); |
8422 | if (*p) { |
8423 | *p++ = NUL; |
8424 | p = skipwhite(p); |
8425 | |
8426 | // Check for trailing illegal characters. |
8427 | if (!ends_excmd(*p)) { |
8428 | eap->errmsg = e_trailing; |
8429 | } else { |
8430 | eap->nextcmd = check_nextcmd(p); |
8431 | } |
8432 | } |
8433 | } |
8434 | if (!eap->skip) |
8435 | find_pattern_in_path(eap->arg, 0, STRLEN(eap->arg), whole, !eap->forceit, |
8436 | *eap->cmd == 'd' ? FIND_DEFINE : FIND_ANY, |
8437 | n, action, eap->line1, eap->line2); |
8438 | } |
8439 | |
8440 | |
8441 | /* |
8442 | * ":ptag", ":ptselect", ":ptjump", ":ptnext", etc. |
8443 | */ |
8444 | static void ex_ptag(exarg_T *eap) |
8445 | { |
8446 | g_do_tagpreview = p_pvh; /* will be reset to 0 in ex_tag_cmd() */ |
8447 | ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name + 1); |
8448 | } |
8449 | |
8450 | /* |
8451 | * ":pedit" |
8452 | */ |
8453 | static void ex_pedit(exarg_T *eap) |
8454 | { |
8455 | win_T *curwin_save = curwin; |
8456 | |
8457 | // Open the preview window or popup and make it the current window. |
8458 | g_do_tagpreview = p_pvh; |
8459 | prepare_tagpreview(true); |
8460 | |
8461 | // Edit the file. |
8462 | do_exedit(eap, NULL); |
8463 | |
8464 | if (curwin != curwin_save && win_valid(curwin_save)) { |
8465 | // Return cursor to where we were |
8466 | validate_cursor(); |
8467 | redraw_later(VALID); |
8468 | win_enter(curwin_save, true); |
8469 | } |
8470 | g_do_tagpreview = 0; |
8471 | } |
8472 | |
8473 | /* |
8474 | * ":stag", ":stselect" and ":stjump". |
8475 | */ |
8476 | static void ex_stag(exarg_T *eap) |
8477 | { |
8478 | postponed_split = -1; |
8479 | postponed_split_flags = cmdmod.split; |
8480 | postponed_split_tab = cmdmod.tab; |
8481 | ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name + 1); |
8482 | postponed_split_flags = 0; |
8483 | postponed_split_tab = 0; |
8484 | } |
8485 | |
8486 | /* |
8487 | * ":tag", ":tselect", ":tjump", ":tnext", etc. |
8488 | */ |
8489 | static void ex_tag(exarg_T *eap) |
8490 | { |
8491 | ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name); |
8492 | } |
8493 | |
8494 | static void ex_tag_cmd(exarg_T *eap, char_u *name) |
8495 | { |
8496 | int cmd; |
8497 | |
8498 | switch (name[1]) { |
8499 | case 'j': cmd = DT_JUMP; // ":tjump" |
8500 | break; |
8501 | case 's': cmd = DT_SELECT; // ":tselect" |
8502 | break; |
8503 | case 'p': // ":tprevious" |
8504 | case 'N': cmd = DT_PREV; // ":tNext" |
8505 | break; |
8506 | case 'n': cmd = DT_NEXT; // ":tnext" |
8507 | break; |
8508 | case 'o': cmd = DT_POP; // ":pop" |
8509 | break; |
8510 | case 'f': // ":tfirst" |
8511 | case 'r': cmd = DT_FIRST; // ":trewind" |
8512 | break; |
8513 | case 'l': cmd = DT_LAST; // ":tlast" |
8514 | break; |
8515 | default: // ":tag" |
8516 | if (p_cst && *eap->arg != NUL) { |
8517 | ex_cstag(eap); |
8518 | return; |
8519 | } |
8520 | cmd = DT_TAG; |
8521 | break; |
8522 | } |
8523 | |
8524 | if (name[0] == 'l') { |
8525 | cmd = DT_LTAG; |
8526 | } |
8527 | |
8528 | do_tag(eap->arg, cmd, eap->addr_count > 0 ? (int)eap->line2 : 1, |
8529 | eap->forceit, TRUE); |
8530 | } |
8531 | |
8532 | /* |
8533 | * Check "str" for starting with a special cmdline variable. |
8534 | * If found return one of the SPEC_ values and set "*usedlen" to the length of |
8535 | * the variable. Otherwise return -1 and "*usedlen" is unchanged. |
8536 | */ |
8537 | ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) |
8538 | FUNC_ATTR_NONNULL_ALL |
8539 | { |
8540 | size_t len; |
8541 | static char *(spec_str[]) = { |
8542 | "%" , |
8543 | #define SPEC_PERC 0 |
8544 | "#" , |
8545 | #define SPEC_HASH (SPEC_PERC + 1) |
8546 | "<cword>" , // cursor word |
8547 | #define SPEC_CWORD (SPEC_HASH + 1) |
8548 | "<cWORD>" , // cursor WORD |
8549 | #define SPEC_CCWORD (SPEC_CWORD + 1) |
8550 | "<cexpr>" , // expr under cursor |
8551 | #define SPEC_CEXPR (SPEC_CCWORD + 1) |
8552 | "<cfile>" , // cursor path name |
8553 | #define SPEC_CFILE (SPEC_CEXPR + 1) |
8554 | "<sfile>" , // ":so" file name |
8555 | #define SPEC_SFILE (SPEC_CFILE + 1) |
8556 | "<slnum>" , // ":so" file line number |
8557 | #define SPEC_SLNUM (SPEC_SFILE + 1) |
8558 | "<afile>" , // autocommand file name |
8559 | #define SPEC_AFILE (SPEC_SLNUM + 1) |
8560 | "<abuf>" , // autocommand buffer number |
8561 | #define SPEC_ABUF (SPEC_AFILE + 1) |
8562 | "<amatch>" , // autocommand match name |
8563 | #define SPEC_AMATCH (SPEC_ABUF + 1) |
8564 | "<sflnum>" , // script file line number |
8565 | #define SPEC_SFLNUM (SPEC_AMATCH + 1) |
8566 | }; |
8567 | |
8568 | for (size_t i = 0; i < ARRAY_SIZE(spec_str); ++i) { |
8569 | len = STRLEN(spec_str[i]); |
8570 | if (STRNCMP(src, spec_str[i], len) == 0) { |
8571 | *usedlen = len; |
8572 | assert(i <= SSIZE_MAX); |
8573 | return (ssize_t)i; |
8574 | } |
8575 | } |
8576 | return -1; |
8577 | } |
8578 | |
8579 | /* |
8580 | * Evaluate cmdline variables. |
8581 | * |
8582 | * change '%' to curbuf->b_ffname |
8583 | * '#' to curwin->w_altfile |
8584 | * '<cword>' to word under the cursor |
8585 | * '<cWORD>' to WORD under the cursor |
8586 | * '<cfile>' to path name under the cursor |
8587 | * '<sfile>' to sourced file name |
8588 | * '<slnum>' to sourced file line number |
8589 | * '<afile>' to file name for autocommand |
8590 | * '<abuf>' to buffer number for autocommand |
8591 | * '<amatch>' to matching name for autocommand |
8592 | * |
8593 | * When an error is detected, "errormsg" is set to a non-NULL pointer (may be |
8594 | * "" for error without a message) and NULL is returned. |
8595 | * Returns an allocated string if a valid match was found. |
8596 | * Returns NULL if no match was found. "usedlen" then still contains the |
8597 | * number of characters to skip. |
8598 | */ |
8599 | char_u * |
8600 | eval_vars ( |
8601 | char_u *src, /* pointer into commandline */ |
8602 | char_u *srcstart, /* beginning of valid memory for src */ |
8603 | size_t *usedlen, /* characters after src that are used */ |
8604 | linenr_T *lnump, /* line number for :e command, or NULL */ |
8605 | char_u **errormsg, /* pointer to error message */ |
8606 | int *escaped /* return value has escaped white space (can |
8607 | * be NULL) */ |
8608 | ) |
8609 | { |
8610 | int i; |
8611 | char_u *s; |
8612 | char_u *result; |
8613 | char_u *resultbuf = NULL; |
8614 | size_t resultlen; |
8615 | buf_T *buf; |
8616 | int valid = VALID_HEAD | VALID_PATH; // Assume valid result. |
8617 | bool tilde_file = false; |
8618 | int skip_mod = false; |
8619 | char strbuf[30]; |
8620 | |
8621 | *errormsg = NULL; |
8622 | if (escaped != NULL) |
8623 | *escaped = FALSE; |
8624 | |
8625 | /* |
8626 | * Check if there is something to do. |
8627 | */ |
8628 | ssize_t spec_idx = find_cmdline_var(src, usedlen); |
8629 | if (spec_idx < 0) { /* no match */ |
8630 | *usedlen = 1; |
8631 | return NULL; |
8632 | } |
8633 | |
8634 | /* |
8635 | * Skip when preceded with a backslash "\%" and "\#". |
8636 | * Note: In "\\%" the % is also not recognized! |
8637 | */ |
8638 | if (src > srcstart && src[-1] == '\\') { |
8639 | *usedlen = 0; |
8640 | STRMOVE(src - 1, src); /* remove backslash */ |
8641 | return NULL; |
8642 | } |
8643 | |
8644 | /* |
8645 | * word or WORD under cursor |
8646 | */ |
8647 | if (spec_idx == SPEC_CWORD |
8648 | || spec_idx == SPEC_CCWORD |
8649 | || spec_idx == SPEC_CEXPR) { |
8650 | resultlen = find_ident_under_cursor( |
8651 | &result, |
8652 | spec_idx == SPEC_CWORD |
8653 | ? (FIND_IDENT | FIND_STRING) |
8654 | : (spec_idx == SPEC_CEXPR |
8655 | ? (FIND_IDENT | FIND_STRING | FIND_EVAL) |
8656 | : FIND_STRING)); |
8657 | if (resultlen == 0) { |
8658 | *errormsg = (char_u *)"" ; |
8659 | return NULL; |
8660 | } |
8661 | // |
8662 | // '#': Alternate file name |
8663 | // '%': Current file name |
8664 | // File name under the cursor |
8665 | // File name for autocommand |
8666 | // and following modifiers |
8667 | // |
8668 | } else { |
8669 | switch (spec_idx) { |
8670 | case SPEC_PERC: // '%': current file |
8671 | if (curbuf->b_fname == NULL) { |
8672 | result = (char_u *)"" ; |
8673 | valid = 0; // Must have ":p:h" to be valid |
8674 | } else { |
8675 | result = curbuf->b_fname; |
8676 | tilde_file = STRCMP(result, "~" ) == 0; |
8677 | } |
8678 | break; |
8679 | |
8680 | case SPEC_HASH: /* '#' or "#99": alternate file */ |
8681 | if (src[1] == '#') { /* "##": the argument list */ |
8682 | result = arg_all(); |
8683 | resultbuf = result; |
8684 | *usedlen = 2; |
8685 | if (escaped != NULL) |
8686 | *escaped = TRUE; |
8687 | skip_mod = TRUE; |
8688 | break; |
8689 | } |
8690 | s = src + 1; |
8691 | if (*s == '<') { // "#<99" uses v:oldfiles. |
8692 | s++; |
8693 | } |
8694 | i = getdigits_int(&s, false, 0); |
8695 | if (s == src + 2 && src[1] == '-') { |
8696 | // just a minus sign, don't skip over it |
8697 | s--; |
8698 | } |
8699 | *usedlen = (size_t)(s - src); // length of what we expand |
8700 | |
8701 | if (src[1] == '<' && i != 0) { |
8702 | if (*usedlen < 2) { |
8703 | /* Should we give an error message for #<text? */ |
8704 | *usedlen = 1; |
8705 | return NULL; |
8706 | } |
8707 | result = (char_u *)tv_list_find_str(get_vim_var_list(VV_OLDFILES), |
8708 | i - 1); |
8709 | if (result == NULL) { |
8710 | *errormsg = (char_u *)"" ; |
8711 | return NULL; |
8712 | } |
8713 | } else { |
8714 | if (i == 0 && src[1] == '<' && *usedlen > 1) { |
8715 | *usedlen = 1; |
8716 | } |
8717 | buf = buflist_findnr(i); |
8718 | if (buf == NULL) { |
8719 | *errormsg = (char_u *)_( |
8720 | "E194: No alternate file name to substitute for '#'" ); |
8721 | return NULL; |
8722 | } |
8723 | if (lnump != NULL) |
8724 | *lnump = ECMD_LAST; |
8725 | if (buf->b_fname == NULL) { |
8726 | result = (char_u *)"" ; |
8727 | valid = 0; // Must have ":p:h" to be valid |
8728 | } else { |
8729 | result = buf->b_fname; |
8730 | tilde_file = STRCMP(result, "~" ) == 0; |
8731 | } |
8732 | } |
8733 | break; |
8734 | |
8735 | case SPEC_CFILE: /* file name under cursor */ |
8736 | result = file_name_at_cursor(FNAME_MESS|FNAME_HYP, 1L, NULL); |
8737 | if (result == NULL) { |
8738 | *errormsg = (char_u *)"" ; |
8739 | return NULL; |
8740 | } |
8741 | resultbuf = result; /* remember allocated string */ |
8742 | break; |
8743 | |
8744 | case SPEC_AFILE: // file name for autocommand |
8745 | if (autocmd_fname != NULL |
8746 | && !path_is_absolute(autocmd_fname) |
8747 | // For CmdlineEnter and related events, <afile> is not a path! #9348 |
8748 | && !strequal("/" , (char *)autocmd_fname)) { |
8749 | // Still need to turn the fname into a full path. It was |
8750 | // postponed to avoid a delay when <afile> is not used. |
8751 | result = (char_u *)FullName_save((char *)autocmd_fname, false); |
8752 | // Copy into `autocmd_fname`, don't reassign it. #8165 |
8753 | xstrlcpy((char *)autocmd_fname, (char *)result, MAXPATHL); |
8754 | xfree(result); |
8755 | } |
8756 | result = autocmd_fname; |
8757 | if (result == NULL) { |
8758 | *errormsg = (char_u *)_( |
8759 | "E495: no autocommand file name to substitute for \"<afile>\"" ); |
8760 | return NULL; |
8761 | } |
8762 | result = path_try_shorten_fname(result); |
8763 | break; |
8764 | |
8765 | case SPEC_ABUF: /* buffer number for autocommand */ |
8766 | if (autocmd_bufnr <= 0) { |
8767 | *errormsg = (char_u *)_( |
8768 | "E496: no autocommand buffer number to substitute for \"<abuf>\"" ); |
8769 | return NULL; |
8770 | } |
8771 | snprintf(strbuf, sizeof(strbuf), "%d" , autocmd_bufnr); |
8772 | result = (char_u *)strbuf; |
8773 | break; |
8774 | |
8775 | case SPEC_AMATCH: /* match name for autocommand */ |
8776 | result = autocmd_match; |
8777 | if (result == NULL) { |
8778 | *errormsg = (char_u *)_( |
8779 | "E497: no autocommand match name to substitute for \"<amatch>\"" ); |
8780 | return NULL; |
8781 | } |
8782 | break; |
8783 | |
8784 | case SPEC_SFILE: /* file name for ":so" command */ |
8785 | result = sourcing_name; |
8786 | if (result == NULL) { |
8787 | *errormsg = (char_u *)_( |
8788 | "E498: no :source file name to substitute for \"<sfile>\"" ); |
8789 | return NULL; |
8790 | } |
8791 | break; |
8792 | |
8793 | case SPEC_SLNUM: // line in file for ":so" command |
8794 | if (sourcing_name == NULL || sourcing_lnum == 0) { |
8795 | *errormsg = (char_u *)_("E842: no line number to use for \"<slnum>\"" ); |
8796 | return NULL; |
8797 | } |
8798 | snprintf(strbuf, sizeof(strbuf), "%" PRIdLINENR, sourcing_lnum); |
8799 | result = (char_u *)strbuf; |
8800 | break; |
8801 | |
8802 | case SPEC_SFLNUM: // line in script file |
8803 | if (current_sctx.sc_lnum + sourcing_lnum == 0) { |
8804 | *errormsg = (char_u *)_("E961: no line number to use for \"<sflnum>\"" ); |
8805 | return NULL; |
8806 | } |
8807 | snprintf((char *)strbuf, sizeof(strbuf), "%" PRIdLINENR, |
8808 | current_sctx.sc_lnum + sourcing_lnum); |
8809 | result = (char_u *)strbuf; |
8810 | break; |
8811 | |
8812 | default: |
8813 | // should not happen |
8814 | *errormsg = (char_u *)"" ; |
8815 | result = (char_u *)"" ; // avoid gcc warning |
8816 | break; |
8817 | } |
8818 | |
8819 | // Length of new string. |
8820 | resultlen = STRLEN(result); |
8821 | // Remove the file name extension. |
8822 | if (src[*usedlen] == '<') { |
8823 | (*usedlen)++; |
8824 | if ((s = STRRCHR(result, '.')) != NULL && s >= path_tail(result)) { |
8825 | resultlen = (size_t)(s - result); |
8826 | } |
8827 | } else if (!skip_mod) { |
8828 | valid |= modify_fname(src, tilde_file, usedlen, &result, |
8829 | &resultbuf, &resultlen); |
8830 | if (result == NULL) { |
8831 | *errormsg = (char_u *)"" ; |
8832 | return NULL; |
8833 | } |
8834 | } |
8835 | } |
8836 | |
8837 | if (resultlen == 0 || valid != VALID_HEAD + VALID_PATH) { |
8838 | if (valid != VALID_HEAD + VALID_PATH) |
8839 | /* xgettext:no-c-format */ |
8840 | *errormsg = (char_u *)_( |
8841 | "E499: Empty file name for '%' or '#', only works with \":p:h\"" ); |
8842 | else |
8843 | *errormsg = (char_u *)_("E500: Evaluates to an empty string" ); |
8844 | result = NULL; |
8845 | } else |
8846 | result = vim_strnsave(result, resultlen); |
8847 | xfree(resultbuf); |
8848 | return result; |
8849 | } |
8850 | |
8851 | /* |
8852 | * Concatenate all files in the argument list, separated by spaces, and return |
8853 | * it in one allocated string. |
8854 | * Spaces and backslashes in the file names are escaped with a backslash. |
8855 | */ |
8856 | static char_u *arg_all(void) |
8857 | { |
8858 | int len; |
8859 | int idx; |
8860 | char_u *retval = NULL; |
8861 | char_u *p; |
8862 | |
8863 | /* |
8864 | * Do this loop two times: |
8865 | * first time: compute the total length |
8866 | * second time: concatenate the names |
8867 | */ |
8868 | for (;; ) { |
8869 | len = 0; |
8870 | for (idx = 0; idx < ARGCOUNT; ++idx) { |
8871 | p = alist_name(&ARGLIST[idx]); |
8872 | if (p == NULL) { |
8873 | continue; |
8874 | } |
8875 | if (len > 0) { |
8876 | /* insert a space in between names */ |
8877 | if (retval != NULL) |
8878 | retval[len] = ' '; |
8879 | ++len; |
8880 | } |
8881 | for (; *p != NUL; p++) { |
8882 | if (*p == ' ' |
8883 | #ifndef BACKSLASH_IN_FILENAME |
8884 | || *p == '\\' |
8885 | #endif |
8886 | || *p == '`') { |
8887 | // insert a backslash |
8888 | if (retval != NULL) { |
8889 | retval[len] = '\\'; |
8890 | } |
8891 | len++; |
8892 | } |
8893 | if (retval != NULL) { |
8894 | retval[len] = *p; |
8895 | } |
8896 | len++; |
8897 | } |
8898 | } |
8899 | |
8900 | /* second time: break here */ |
8901 | if (retval != NULL) { |
8902 | retval[len] = NUL; |
8903 | break; |
8904 | } |
8905 | |
8906 | /* allocate memory */ |
8907 | retval = xmalloc(len + 1); |
8908 | } |
8909 | |
8910 | return retval; |
8911 | } |
8912 | |
8913 | /* |
8914 | * Expand the <sfile> string in "arg". |
8915 | * |
8916 | * Returns an allocated string, or NULL for any error. |
8917 | */ |
8918 | char_u *expand_sfile(char_u *arg) |
8919 | { |
8920 | char_u *errormsg; |
8921 | size_t len; |
8922 | char_u *result; |
8923 | char_u *newres; |
8924 | char_u *repl; |
8925 | size_t srclen; |
8926 | char_u *p; |
8927 | |
8928 | result = vim_strsave(arg); |
8929 | |
8930 | for (p = result; *p; ) { |
8931 | if (STRNCMP(p, "<sfile>" , 7) != 0) |
8932 | ++p; |
8933 | else { |
8934 | /* replace "<sfile>" with the sourced file name, and do ":" stuff */ |
8935 | repl = eval_vars(p, result, &srclen, NULL, &errormsg, NULL); |
8936 | if (errormsg != NULL) { |
8937 | if (*errormsg) |
8938 | emsg(errormsg); |
8939 | xfree(result); |
8940 | return NULL; |
8941 | } |
8942 | if (repl == NULL) { /* no match (cannot happen) */ |
8943 | p += srclen; |
8944 | continue; |
8945 | } |
8946 | len = STRLEN(result) - srclen + STRLEN(repl) + 1; |
8947 | newres = xmalloc(len); |
8948 | memmove(newres, result, (size_t)(p - result)); |
8949 | STRCPY(newres + (p - result), repl); |
8950 | len = STRLEN(newres); |
8951 | STRCAT(newres, p + srclen); |
8952 | xfree(repl); |
8953 | xfree(result); |
8954 | result = newres; |
8955 | p = newres + len; /* continue after the match */ |
8956 | } |
8957 | } |
8958 | |
8959 | return result; |
8960 | } |
8961 | |
8962 | |
8963 | /* |
8964 | * Write openfile commands for the current buffers to an .exrc file. |
8965 | * Return FAIL on error, OK otherwise. |
8966 | */ |
8967 | static int |
8968 | makeopens( |
8969 | FILE *fd, |
8970 | char_u *dirnow /* Current directory name */ |
8971 | ) |
8972 | { |
8973 | int only_save_windows = TRUE; |
8974 | int nr; |
8975 | int restore_size = true; |
8976 | win_T *wp; |
8977 | char_u *sname; |
8978 | win_T *edited_win = NULL; |
8979 | int tabnr; |
8980 | win_T *tab_firstwin; |
8981 | frame_T *tab_topframe; |
8982 | int cur_arg_idx = 0; |
8983 | int next_arg_idx = 0; |
8984 | |
8985 | if (ssop_flags & SSOP_BUFFERS) |
8986 | only_save_windows = FALSE; /* Save ALL buffers */ |
8987 | |
8988 | // Begin by setting v:this_session, and then other sessionable variables. |
8989 | if (put_line(fd, "let v:this_session=expand(\"<sfile>:p\")" ) == FAIL) { |
8990 | return FAIL; |
8991 | } |
8992 | if (ssop_flags & SSOP_GLOBALS) { |
8993 | if (store_session_globals(fd) == FAIL) { |
8994 | return FAIL; |
8995 | } |
8996 | } |
8997 | |
8998 | /* |
8999 | * Close all windows but one. |
9000 | */ |
9001 | if (put_line(fd, "silent only" ) == FAIL) |
9002 | return FAIL; |
9003 | |
9004 | /* |
9005 | * Now a :cd command to the session directory or the current directory |
9006 | */ |
9007 | if (ssop_flags & SSOP_SESDIR) { |
9008 | if (put_line(fd, "exe \"cd \" . escape(expand(\"<sfile>:p:h\"), ' ')" ) |
9009 | == FAIL) |
9010 | return FAIL; |
9011 | } else if (ssop_flags & SSOP_CURDIR) { |
9012 | sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow); |
9013 | if (fputs("cd " , fd) < 0 |
9014 | || ses_put_fname(fd, sname, &ssop_flags) == FAIL |
9015 | || put_eol(fd) == FAIL) { |
9016 | xfree(sname); |
9017 | return FAIL; |
9018 | } |
9019 | xfree(sname); |
9020 | } |
9021 | |
9022 | /* |
9023 | * If there is an empty, unnamed buffer we will wipe it out later. |
9024 | * Remember the buffer number. |
9025 | */ |
9026 | if (put_line(fd, |
9027 | "if expand('%') == '' && !&modified && line('$') <= 1 && getline(1) == ''" ) |
9028 | == |
9029 | FAIL) |
9030 | return FAIL; |
9031 | if (put_line(fd, " let s:wipebuf = bufnr('%')" ) == FAIL) |
9032 | return FAIL; |
9033 | if (put_line(fd, "endif" ) == FAIL) |
9034 | return FAIL; |
9035 | |
9036 | /* |
9037 | * Now save the current files, current buffer first. |
9038 | */ |
9039 | if (put_line(fd, "set shortmess=aoO" ) == FAIL) |
9040 | return FAIL; |
9041 | |
9042 | /* Now put the other buffers into the buffer list */ |
9043 | FOR_ALL_BUFFERS(buf) { |
9044 | if (!(only_save_windows && buf->b_nwindows == 0) |
9045 | && !(buf->b_help && !(ssop_flags & SSOP_HELP)) |
9046 | && buf->b_fname != NULL |
9047 | && buf->b_p_bl) { |
9048 | if (fprintf(fd, "badd +%" PRId64 " " , |
9049 | buf->b_wininfo == NULL |
9050 | ? (int64_t)1L |
9051 | : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0 |
9052 | || ses_fname(fd, buf, &ssop_flags, true) == FAIL) { |
9053 | return FAIL; |
9054 | } |
9055 | } |
9056 | } |
9057 | |
9058 | /* the global argument list */ |
9059 | if (ses_arglist(fd, "argglobal" , &global_alist.al_ga, |
9060 | !(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) { |
9061 | return FAIL; |
9062 | } |
9063 | |
9064 | if (ssop_flags & SSOP_RESIZE) { |
9065 | /* Note: after the restore we still check it worked!*/ |
9066 | if (fprintf(fd, "set lines=%" PRId64 " columns=%" PRId64, |
9067 | (int64_t)Rows, (int64_t)Columns) < 0 |
9068 | || put_eol(fd) == FAIL) |
9069 | return FAIL; |
9070 | } |
9071 | |
9072 | int restore_stal = FALSE; |
9073 | // When there are two or more tabpages and 'showtabline' is 1 the tabline |
9074 | // will be displayed when creating the next tab. That resizes the windows |
9075 | // in the first tab, which may cause problems. Set 'showtabline' to 2 |
9076 | // temporarily to avoid that. |
9077 | if (p_stal == 1 && first_tabpage->tp_next != NULL) { |
9078 | if (put_line(fd, "set stal=2" ) == FAIL) { |
9079 | return FAIL; |
9080 | } |
9081 | restore_stal = TRUE; |
9082 | } |
9083 | |
9084 | /* |
9085 | * May repeat putting Windows for each tab, when "tabpages" is in |
9086 | * 'sessionoptions'. |
9087 | * Don't use goto_tabpage(), it may change directory and trigger |
9088 | * autocommands. |
9089 | */ |
9090 | tab_firstwin = firstwin; /* first window in tab page "tabnr" */ |
9091 | tab_topframe = topframe; |
9092 | for (tabnr = 1;; tabnr++) { |
9093 | tabpage_T *tp = find_tabpage(tabnr); |
9094 | if (tp == NULL) { |
9095 | break; // done all tab pages |
9096 | } |
9097 | |
9098 | int need_tabnew = false; |
9099 | int cnr = 1; |
9100 | |
9101 | if ((ssop_flags & SSOP_TABPAGES)) { |
9102 | if (tp == curtab) { |
9103 | tab_firstwin = firstwin; |
9104 | tab_topframe = topframe; |
9105 | } else { |
9106 | tab_firstwin = tp->tp_firstwin; |
9107 | tab_topframe = tp->tp_topframe; |
9108 | } |
9109 | if (tabnr > 1) |
9110 | need_tabnew = TRUE; |
9111 | } |
9112 | |
9113 | /* |
9114 | * Before creating the window layout, try loading one file. If this |
9115 | * is aborted we don't end up with a number of useless windows. |
9116 | * This may have side effects! (e.g., compressed or network file). |
9117 | */ |
9118 | for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { |
9119 | if (ses_do_win(wp) |
9120 | && wp->w_buffer->b_ffname != NULL |
9121 | && !bt_help(wp->w_buffer) |
9122 | && !bt_nofile(wp->w_buffer) |
9123 | ) { |
9124 | if (fputs(need_tabnew ? "tabedit " : "edit " , fd) < 0 |
9125 | || ses_fname(fd, wp->w_buffer, &ssop_flags, true) == FAIL) { |
9126 | return FAIL; |
9127 | } |
9128 | need_tabnew = false; |
9129 | if (!wp->w_arg_idx_invalid) { |
9130 | edited_win = wp; |
9131 | } |
9132 | break; |
9133 | } |
9134 | } |
9135 | |
9136 | /* If no file got edited create an empty tab page. */ |
9137 | if (need_tabnew && put_line(fd, "tabnew" ) == FAIL) |
9138 | return FAIL; |
9139 | |
9140 | /* |
9141 | * Save current window layout. |
9142 | */ |
9143 | if (put_line(fd, "set splitbelow splitright" ) == FAIL) |
9144 | return FAIL; |
9145 | if (ses_win_rec(fd, tab_topframe) == FAIL) |
9146 | return FAIL; |
9147 | if (!p_sb && put_line(fd, "set nosplitbelow" ) == FAIL) |
9148 | return FAIL; |
9149 | if (!p_spr && put_line(fd, "set nosplitright" ) == FAIL) |
9150 | return FAIL; |
9151 | |
9152 | /* |
9153 | * Check if window sizes can be restored (no windows omitted). |
9154 | * Remember the window number of the current window after restoring. |
9155 | */ |
9156 | nr = 0; |
9157 | for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { |
9158 | if (ses_do_win(wp)) |
9159 | ++nr; |
9160 | else |
9161 | restore_size = FALSE; |
9162 | if (curwin == wp) |
9163 | cnr = nr; |
9164 | } |
9165 | |
9166 | /* Go to the first window. */ |
9167 | if (put_line(fd, "wincmd t" ) == FAIL) |
9168 | return FAIL; |
9169 | |
9170 | // If more than one window, see if sizes can be restored. |
9171 | // First set 'winheight' and 'winwidth' to 1 to avoid the windows being |
9172 | // resized when moving between windows. |
9173 | // Do this before restoring the view, so that the topline and the |
9174 | // cursor can be set. This is done again below. |
9175 | // winminheight and winminwidth need to be set to avoid an error if the |
9176 | // user has set winheight or winwidth. |
9177 | if (put_line(fd, "set winminheight=0" ) == FAIL |
9178 | || put_line(fd, "set winheight=1" ) == FAIL |
9179 | || put_line(fd, "set winminwidth=0" ) == FAIL |
9180 | || put_line(fd, "set winwidth=1" ) == FAIL) { |
9181 | return FAIL; |
9182 | } |
9183 | if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { |
9184 | return FAIL; |
9185 | } |
9186 | |
9187 | /* |
9188 | * Restore the view of the window (options, file, cursor, etc.). |
9189 | */ |
9190 | for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { |
9191 | if (!ses_do_win(wp)) |
9192 | continue; |
9193 | if (put_view(fd, wp, wp != edited_win, &ssop_flags, |
9194 | cur_arg_idx) == FAIL) |
9195 | return FAIL; |
9196 | if (nr > 1 && put_line(fd, "wincmd w" ) == FAIL) |
9197 | return FAIL; |
9198 | next_arg_idx = wp->w_arg_idx; |
9199 | } |
9200 | |
9201 | /* The argument index in the first tab page is zero, need to set it in |
9202 | * each window. For further tab pages it's the window where we do |
9203 | * "tabedit". */ |
9204 | cur_arg_idx = next_arg_idx; |
9205 | |
9206 | /* |
9207 | * Restore cursor to the current window if it's not the first one. |
9208 | */ |
9209 | if (cnr > 1 && (fprintf(fd, "%dwincmd w" , cnr) < 0 |
9210 | || put_eol(fd) == FAIL)) |
9211 | return FAIL; |
9212 | |
9213 | /* |
9214 | * Restore window sizes again after jumping around in windows, because |
9215 | * the current window has a minimum size while others may not. |
9216 | */ |
9217 | if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) |
9218 | return FAIL; |
9219 | |
9220 | // Take care of tab-local working directories if applicable |
9221 | if (tp->tp_localdir) { |
9222 | if (fputs("if exists(':tcd') == 2 | tcd " , fd) < 0 |
9223 | || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL |
9224 | || fputs(" | endif" , fd) < 0 |
9225 | || put_eol(fd) == FAIL) { |
9226 | return FAIL; |
9227 | } |
9228 | did_lcd = true; |
9229 | } |
9230 | |
9231 | /* Don't continue in another tab page when doing only the current one |
9232 | * or when at the last tab page. */ |
9233 | if (!(ssop_flags & SSOP_TABPAGES)) |
9234 | break; |
9235 | } |
9236 | |
9237 | if (ssop_flags & SSOP_TABPAGES) { |
9238 | if (fprintf(fd, "tabnext %d" , tabpage_index(curtab)) < 0 |
9239 | || put_eol(fd) == FAIL) |
9240 | return FAIL; |
9241 | } |
9242 | if (restore_stal && put_line(fd, "set stal=1" ) == FAIL) { |
9243 | return FAIL; |
9244 | } |
9245 | |
9246 | /* |
9247 | * Wipe out an empty unnamed buffer we started in. |
9248 | */ |
9249 | if (put_line(fd, "if exists('s:wipebuf') " |
9250 | "&& getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'" ) |
9251 | == FAIL) |
9252 | return FAIL; |
9253 | if (put_line(fd, " silent exe 'bwipe ' . s:wipebuf" ) == FAIL) |
9254 | return FAIL; |
9255 | if (put_line(fd, "endif" ) == FAIL) |
9256 | return FAIL; |
9257 | if (put_line(fd, "unlet! s:wipebuf" ) == FAIL) |
9258 | return FAIL; |
9259 | |
9260 | // Re-apply options. |
9261 | if (fprintf(fd, "set winheight=%" PRId64 " winwidth=%" PRId64 |
9262 | " winminheight=%" PRId64 " winminwidth=%" PRId64 |
9263 | " shortmess=%s" , |
9264 | (int64_t)p_wh, |
9265 | (int64_t)p_wiw, |
9266 | (int64_t)p_wmh, |
9267 | (int64_t)p_wmw, |
9268 | p_shm) < 0 |
9269 | || put_eol(fd) == FAIL) { |
9270 | return FAIL; |
9271 | } |
9272 | |
9273 | /* |
9274 | * Lastly, execute the x.vim file if it exists. |
9275 | */ |
9276 | if (put_line(fd, "let s:sx = expand(\"<sfile>:p:r\").\"x.vim\"" ) == FAIL |
9277 | || put_line(fd, "if file_readable(s:sx)" ) == FAIL |
9278 | || put_line(fd, " exe \"source \" . fnameescape(s:sx)" ) == FAIL |
9279 | || put_line(fd, "endif" ) == FAIL) |
9280 | return FAIL; |
9281 | |
9282 | return OK; |
9283 | } |
9284 | |
9285 | static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin) |
9286 | { |
9287 | int n = 0; |
9288 | win_T *wp; |
9289 | |
9290 | if (restore_size && (ssop_flags & SSOP_WINSIZE)) { |
9291 | for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { |
9292 | if (!ses_do_win(wp)) { |
9293 | continue; |
9294 | } |
9295 | n++; |
9296 | |
9297 | // restore height when not full height |
9298 | if (wp->w_height + wp->w_status_height < topframe->fr_height |
9299 | && (fprintf(fd, |
9300 | "exe '%dresize ' . ((&lines * %" PRId64 |
9301 | " + %" PRId64 ") / %" PRId64 ")" , |
9302 | n, (int64_t)wp->w_height, |
9303 | (int64_t)Rows / 2, (int64_t)Rows) < 0 |
9304 | || put_eol(fd) == FAIL)) { |
9305 | return FAIL; |
9306 | } |
9307 | |
9308 | // restore width when not full width |
9309 | if (wp->w_width < Columns |
9310 | && (fprintf(fd, |
9311 | "exe 'vert %dresize ' . ((&columns * %" PRId64 |
9312 | " + %" PRId64 ") / %" PRId64 ")" , |
9313 | n, (int64_t)wp->w_width, (int64_t)Columns / 2, |
9314 | (int64_t)Columns) < 0 |
9315 | || put_eol(fd) == FAIL)) { |
9316 | return FAIL; |
9317 | } |
9318 | } |
9319 | } else { |
9320 | // Just equalise window sizes |
9321 | if (put_line(fd, "wincmd =" ) == FAIL) { |
9322 | return FAIL; |
9323 | } |
9324 | } |
9325 | return OK; |
9326 | } |
9327 | |
9328 | /* |
9329 | * Write commands to "fd" to recursively create windows for frame "fr", |
9330 | * horizontally and vertically split. |
9331 | * After the commands the last window in the frame is the current window. |
9332 | * Returns FAIL when writing the commands to "fd" fails. |
9333 | */ |
9334 | static int ses_win_rec(FILE *fd, frame_T *fr) |
9335 | { |
9336 | frame_T *frc; |
9337 | int count = 0; |
9338 | |
9339 | if (fr->fr_layout != FR_LEAF) { |
9340 | /* Find first frame that's not skipped and then create a window for |
9341 | * each following one (first frame is already there). */ |
9342 | frc = ses_skipframe(fr->fr_child); |
9343 | if (frc != NULL) |
9344 | while ((frc = ses_skipframe(frc->fr_next)) != NULL) { |
9345 | /* Make window as big as possible so that we have lots of room |
9346 | * to split. */ |
9347 | if (put_line(fd, "wincmd _ | wincmd |" ) == FAIL |
9348 | || put_line(fd, fr->fr_layout == FR_COL |
9349 | ? "split" : "vsplit" ) == FAIL) |
9350 | return FAIL; |
9351 | ++count; |
9352 | } |
9353 | |
9354 | /* Go back to the first window. */ |
9355 | if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL |
9356 | ? "%dwincmd k" : "%dwincmd h" , count) < 0 |
9357 | || put_eol(fd) == FAIL)) |
9358 | return FAIL; |
9359 | |
9360 | /* Recursively create frames/windows in each window of this column or |
9361 | * row. */ |
9362 | frc = ses_skipframe(fr->fr_child); |
9363 | while (frc != NULL) { |
9364 | ses_win_rec(fd, frc); |
9365 | frc = ses_skipframe(frc->fr_next); |
9366 | /* Go to next window. */ |
9367 | if (frc != NULL && put_line(fd, "wincmd w" ) == FAIL) |
9368 | return FAIL; |
9369 | } |
9370 | } |
9371 | return OK; |
9372 | } |
9373 | |
9374 | /* |
9375 | * Skip frames that don't contain windows we want to save in the Session. |
9376 | * Returns NULL when there none. |
9377 | */ |
9378 | static frame_T *ses_skipframe(frame_T *fr) |
9379 | { |
9380 | frame_T *frc; |
9381 | |
9382 | FOR_ALL_FRAMES(frc, fr) { |
9383 | if (ses_do_frame(frc)) { |
9384 | break; |
9385 | } |
9386 | } |
9387 | return frc; |
9388 | } |
9389 | |
9390 | // Return true if frame "fr" has a window somewhere that we want to save in |
9391 | // the Session. |
9392 | static bool ses_do_frame(const frame_T *fr) |
9393 | FUNC_ATTR_NONNULL_ARG(1) |
9394 | { |
9395 | const frame_T *frc; |
9396 | |
9397 | if (fr->fr_layout == FR_LEAF) { |
9398 | return ses_do_win(fr->fr_win); |
9399 | } |
9400 | FOR_ALL_FRAMES(frc, fr->fr_child) { |
9401 | if (ses_do_frame(frc)) { |
9402 | return true; |
9403 | } |
9404 | } |
9405 | return false; |
9406 | } |
9407 | |
9408 | /// Return non-zero if window "wp" is to be stored in the Session. |
9409 | static int ses_do_win(win_T *wp) |
9410 | { |
9411 | if (wp->w_buffer->b_fname == NULL |
9412 | // When 'buftype' is "nofile" can't restore the window contents. |
9413 | || (!wp->w_buffer->terminal && bt_nofile(wp->w_buffer))) { |
9414 | return ssop_flags & SSOP_BLANK; |
9415 | } |
9416 | if (bt_help(wp->w_buffer)) { |
9417 | return ssop_flags & SSOP_HELP; |
9418 | } |
9419 | return true; |
9420 | } |
9421 | |
9422 | static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces) |
9423 | { |
9424 | int r; |
9425 | |
9426 | if (wp->w_curswant == MAXCOL) { |
9427 | r = fprintf(fd, "%snormal! $" , spaces); |
9428 | } else { |
9429 | r = fprintf(fd, "%snormal! 0%d|" , spaces, wp->w_virtcol + 1); |
9430 | } |
9431 | return r < 0 || put_eol(fd) == FAIL ? FAIL : OK; |
9432 | } |
9433 | |
9434 | /* |
9435 | * Write commands to "fd" to restore the view of a window. |
9436 | * Caller must make sure 'scrolloff' is zero. |
9437 | */ |
9438 | static int |
9439 | put_view( |
9440 | FILE *fd, |
9441 | win_T *wp, |
9442 | int add_edit, /* add ":edit" command to view */ |
9443 | unsigned *flagp, /* vop_flags or ssop_flags */ |
9444 | int current_arg_idx /* current argument index of the window, use |
9445 | * -1 if unknown */ |
9446 | ) |
9447 | { |
9448 | win_T *save_curwin; |
9449 | int f; |
9450 | int do_cursor; |
9451 | int did_next = FALSE; |
9452 | |
9453 | /* Always restore cursor position for ":mksession". For ":mkview" only |
9454 | * when 'viewoptions' contains "cursor". */ |
9455 | do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR); |
9456 | |
9457 | /* |
9458 | * Local argument list. |
9459 | */ |
9460 | if (wp->w_alist == &global_alist) { |
9461 | if (put_line(fd, "argglobal" ) == FAIL) |
9462 | return FAIL; |
9463 | } else { |
9464 | if (ses_arglist(fd, "arglocal" , &wp->w_alist->al_ga, |
9465 | flagp == &vop_flags |
9466 | || !(*flagp & SSOP_CURDIR) |
9467 | || wp->w_localdir != NULL, flagp) == FAIL) |
9468 | return FAIL; |
9469 | } |
9470 | |
9471 | /* Only when part of a session: restore the argument index. Some |
9472 | * arguments may have been deleted, check if the index is valid. */ |
9473 | if (wp->w_arg_idx != current_arg_idx && wp->w_arg_idx < WARGCOUNT(wp) |
9474 | && flagp == &ssop_flags) { |
9475 | if (fprintf(fd, "%" PRId64 "argu" , (int64_t)wp->w_arg_idx + 1) < 0 |
9476 | || put_eol(fd) == FAIL) { |
9477 | return FAIL; |
9478 | } |
9479 | did_next = true; |
9480 | } |
9481 | |
9482 | /* Edit the file. Skip this when ":next" already did it. */ |
9483 | if (add_edit && (!did_next || wp->w_arg_idx_invalid)) { |
9484 | /* |
9485 | * Load the file. |
9486 | */ |
9487 | if (wp->w_buffer->b_ffname != NULL |
9488 | && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal) |
9489 | ) { |
9490 | // Editing a file in this buffer: use ":edit file". |
9491 | // This may have side effects! (e.g., compressed or network file). |
9492 | // |
9493 | // Note, if a buffer for that file already exists, use :badd to |
9494 | // edit that buffer, to not lose folding information (:edit resets |
9495 | // folds in other buffers) |
9496 | if (fputs("if bufexists(\"" , fd) < 0 |
9497 | || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL |
9498 | || fputs("\") | buffer " , fd) < 0 |
9499 | || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL |
9500 | || fputs(" | else | edit " , fd) < 0 |
9501 | || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL |
9502 | || fputs(" | endif" , fd) < 0 |
9503 | || put_eol(fd) == FAIL) { |
9504 | return FAIL; |
9505 | } |
9506 | } else { |
9507 | // No file in this buffer, just make it empty. |
9508 | if (put_line(fd, "enew" ) == FAIL) { |
9509 | return FAIL; |
9510 | } |
9511 | if (wp->w_buffer->b_ffname != NULL) { |
9512 | // The buffer does have a name, but it's not a file name. |
9513 | if (fputs("file " , fd) < 0 |
9514 | || ses_fname(fd, wp->w_buffer, flagp, true) == FAIL) { |
9515 | return FAIL; |
9516 | } |
9517 | } |
9518 | do_cursor = false; |
9519 | } |
9520 | } |
9521 | |
9522 | /* |
9523 | * Local mappings and abbreviations. |
9524 | */ |
9525 | if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) |
9526 | && makemap(fd, wp->w_buffer) == FAIL) |
9527 | return FAIL; |
9528 | |
9529 | /* |
9530 | * Local options. Need to go to the window temporarily. |
9531 | * Store only local values when using ":mkview" and when ":mksession" is |
9532 | * used and 'sessionoptions' doesn't include "nvim/options". |
9533 | * Some folding options are always stored when "folds" is included, |
9534 | * otherwise the folds would not be restored correctly. |
9535 | */ |
9536 | save_curwin = curwin; |
9537 | curwin = wp; |
9538 | curbuf = curwin->w_buffer; |
9539 | if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) |
9540 | f = makeset(fd, OPT_LOCAL, |
9541 | flagp == &vop_flags || !(*flagp & SSOP_OPTIONS)); |
9542 | else if (*flagp & SSOP_FOLDS) |
9543 | f = makefoldset(fd); |
9544 | else |
9545 | f = OK; |
9546 | curwin = save_curwin; |
9547 | curbuf = curwin->w_buffer; |
9548 | if (f == FAIL) |
9549 | return FAIL; |
9550 | |
9551 | /* |
9552 | * Save Folds when 'buftype' is empty and for help files. |
9553 | */ |
9554 | if ((*flagp & SSOP_FOLDS) |
9555 | && wp->w_buffer->b_ffname != NULL |
9556 | && (*wp->w_buffer->b_p_bt == NUL || bt_help(wp->w_buffer)) |
9557 | ) { |
9558 | if (put_folds(fd, wp) == FAIL) |
9559 | return FAIL; |
9560 | } |
9561 | |
9562 | /* |
9563 | * Set the cursor after creating folds, since that moves the cursor. |
9564 | */ |
9565 | if (do_cursor) { |
9566 | |
9567 | /* Restore the cursor line in the file and relatively in the |
9568 | * window. Don't use "G", it changes the jumplist. */ |
9569 | if (fprintf(fd, |
9570 | "let s:l = %" PRId64 " - ((%" PRId64 |
9571 | " * winheight(0) + %" PRId64 ") / %" PRId64 ")" , |
9572 | (int64_t)wp->w_cursor.lnum, |
9573 | (int64_t)(wp->w_cursor.lnum - wp->w_topline), |
9574 | (int64_t)(wp->w_height_inner / 2), |
9575 | (int64_t)wp->w_height_inner) < 0 |
9576 | || put_eol(fd) == FAIL |
9577 | || put_line(fd, "if s:l < 1 | let s:l = 1 | endif" ) == FAIL |
9578 | || put_line(fd, "exe s:l" ) == FAIL |
9579 | || put_line(fd, "normal! zt" ) == FAIL |
9580 | || fprintf(fd, "%" PRId64, (int64_t)wp->w_cursor.lnum) < 0 |
9581 | || put_eol(fd) == FAIL) |
9582 | return FAIL; |
9583 | /* Restore the cursor column and left offset when not wrapping. */ |
9584 | if (wp->w_cursor.col == 0) { |
9585 | if (put_line(fd, "normal! 0" ) == FAIL) |
9586 | return FAIL; |
9587 | } else { |
9588 | if (!wp->w_p_wrap && wp->w_leftcol > 0 && wp->w_width > 0) { |
9589 | if (fprintf(fd, |
9590 | "let s:c = %" PRId64 " - ((%" PRId64 |
9591 | " * winwidth(0) + %" PRId64 ") / %" PRId64 ")" , |
9592 | (int64_t)wp->w_virtcol + 1, |
9593 | (int64_t)(wp->w_virtcol - wp->w_leftcol), |
9594 | (int64_t)(wp->w_width / 2), |
9595 | (int64_t)wp->w_width) < 0 |
9596 | || put_eol(fd) == FAIL |
9597 | || put_line(fd, "if s:c > 0" ) == FAIL |
9598 | || fprintf(fd, " exe 'normal! ' . s:c . '|zs' . %" PRId64 " . '|'" , |
9599 | (int64_t)wp->w_virtcol + 1) < 0 |
9600 | || put_eol(fd) == FAIL |
9601 | || put_line(fd, "else" ) == FAIL |
9602 | || put_view_curpos(fd, wp, " " ) == FAIL |
9603 | || put_line(fd, "endif" ) == FAIL) { |
9604 | return FAIL; |
9605 | } |
9606 | } else if (put_view_curpos(fd, wp, "" ) == FAIL) { |
9607 | return FAIL; |
9608 | } |
9609 | } |
9610 | } |
9611 | |
9612 | // |
9613 | // Local directory, if the current flag is not view options or the "curdir" |
9614 | // option is included. |
9615 | // |
9616 | if (wp->w_localdir != NULL |
9617 | && (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) { |
9618 | if (fputs("lcd " , fd) < 0 |
9619 | || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL |
9620 | || put_eol(fd) == FAIL) { |
9621 | return FAIL; |
9622 | } |
9623 | did_lcd = true; |
9624 | } |
9625 | |
9626 | return OK; |
9627 | } |
9628 | |
9629 | /* |
9630 | * Write an argument list to the session file. |
9631 | * Returns FAIL if writing fails. |
9632 | */ |
9633 | static int |
9634 | ses_arglist( |
9635 | FILE *fd, |
9636 | char *cmd, |
9637 | garray_T *gap, |
9638 | int fullname, /* TRUE: use full path name */ |
9639 | unsigned *flagp |
9640 | ) |
9641 | { |
9642 | char_u *buf = NULL; |
9643 | char_u *s; |
9644 | |
9645 | if (fputs(cmd, fd) < 0 || put_eol(fd) == FAIL) { |
9646 | return FAIL; |
9647 | } |
9648 | if (put_line(fd, "%argdel" ) == FAIL) { |
9649 | return FAIL; |
9650 | } |
9651 | for (int i = 0; i < gap->ga_len; ++i) { |
9652 | /* NULL file names are skipped (only happens when out of memory). */ |
9653 | s = alist_name(&((aentry_T *)gap->ga_data)[i]); |
9654 | if (s != NULL) { |
9655 | if (fullname) { |
9656 | buf = xmalloc(MAXPATHL); |
9657 | (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, FALSE); |
9658 | s = buf; |
9659 | } |
9660 | if (fputs("$argadd " , fd) < 0 || ses_put_fname(fd, s, flagp) == FAIL |
9661 | || put_eol(fd) == FAIL) { |
9662 | xfree(buf); |
9663 | return FAIL; |
9664 | } |
9665 | xfree(buf); |
9666 | } |
9667 | } |
9668 | return OK; |
9669 | } |
9670 | |
9671 | /// Write a buffer name to the session file. |
9672 | /// Also ends the line, if "add_eol" is true. |
9673 | /// Returns FAIL if writing fails. |
9674 | static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) |
9675 | { |
9676 | char_u *name; |
9677 | |
9678 | /* Use the short file name if the current directory is known at the time |
9679 | * the session file will be sourced. |
9680 | * Don't do this for ":mkview", we don't know the current directory. |
9681 | * Don't do this after ":lcd", we don't keep track of what the current |
9682 | * directory is. */ |
9683 | if (buf->b_sfname != NULL |
9684 | && flagp == &ssop_flags |
9685 | && (ssop_flags & (SSOP_CURDIR | SSOP_SESDIR)) |
9686 | && !p_acd |
9687 | && !did_lcd) |
9688 | name = buf->b_sfname; |
9689 | else |
9690 | name = buf->b_ffname; |
9691 | if (ses_put_fname(fd, name, flagp) == FAIL |
9692 | || (add_eol && put_eol(fd) == FAIL)) { |
9693 | return FAIL; |
9694 | } |
9695 | return OK; |
9696 | } |
9697 | |
9698 | /* |
9699 | * Write a file name to the session file. |
9700 | * Takes care of the "slash" option in 'sessionoptions' and escapes special |
9701 | * characters. |
9702 | * Returns FAIL if writing fails. |
9703 | */ |
9704 | static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp) |
9705 | { |
9706 | char_u *p; |
9707 | |
9708 | char_u *sname = home_replace_save(NULL, name); |
9709 | |
9710 | if (*flagp & SSOP_SLASH) { |
9711 | // change all backslashes to forward slashes |
9712 | for (p = sname; *p != NUL; MB_PTR_ADV(p)) { |
9713 | if (*p == '\\') { |
9714 | *p = '/'; |
9715 | } |
9716 | } |
9717 | } |
9718 | |
9719 | // Escape special characters. |
9720 | p = (char_u *)vim_strsave_fnameescape((const char *)sname, false); |
9721 | xfree(sname); |
9722 | |
9723 | /* write the result */ |
9724 | bool retval = fputs((char *)p, fd) < 0 ? FAIL : OK; |
9725 | xfree(p); |
9726 | return retval; |
9727 | } |
9728 | |
9729 | /* |
9730 | * ":loadview [nr]" |
9731 | */ |
9732 | static void ex_loadview(exarg_T *eap) |
9733 | { |
9734 | char *fname = get_view_file(*eap->arg); |
9735 | if (fname != NULL) { |
9736 | if (do_source((char_u *)fname, FALSE, DOSO_NONE) == FAIL) { |
9737 | EMSG2(_(e_notopen), fname); |
9738 | } |
9739 | xfree(fname); |
9740 | } |
9741 | } |
9742 | |
9743 | /// Get the name of the view file for the current buffer. |
9744 | static char *get_view_file(int c) |
9745 | { |
9746 | if (curbuf->b_ffname == NULL) { |
9747 | EMSG(_(e_noname)); |
9748 | return NULL; |
9749 | } |
9750 | char *sname = (char *)home_replace_save(NULL, curbuf->b_ffname); |
9751 | |
9752 | // We want a file name without separators, because we're not going to make |
9753 | // a directory. |
9754 | // "normal" path separator -> "=+" |
9755 | // "=" -> "==" |
9756 | // ":" path separator -> "=-" |
9757 | size_t len = 0; |
9758 | for (char *p = sname; *p; p++) { |
9759 | if (*p == '=' || vim_ispathsep(*p)) { |
9760 | ++len; |
9761 | } |
9762 | } |
9763 | char *retval = xmalloc(strlen(sname) + len + STRLEN(p_vdir) + 9); |
9764 | STRCPY(retval, p_vdir); |
9765 | add_pathsep(retval); |
9766 | char *s = retval + strlen(retval); |
9767 | for (char *p = sname; *p; p++) { |
9768 | if (*p == '=') { |
9769 | *s++ = '='; |
9770 | *s++ = '='; |
9771 | } else if (vim_ispathsep(*p)) { |
9772 | *s++ = '='; |
9773 | #if defined(BACKSLASH_IN_FILENAME) |
9774 | if (*p == ':') |
9775 | *s++ = '-'; |
9776 | else |
9777 | #endif |
9778 | *s++ = '+'; |
9779 | } else |
9780 | *s++ = *p; |
9781 | } |
9782 | *s++ = '='; |
9783 | *s++ = c; |
9784 | strcpy(s, ".vim" ); |
9785 | |
9786 | xfree(sname); |
9787 | return retval; |
9788 | } |
9789 | |
9790 | |
9791 | /* |
9792 | * Write end-of-line character(s) for ":mkexrc", ":mkvimrc" and ":mksession". |
9793 | * Return FAIL for a write error. |
9794 | */ |
9795 | int put_eol(FILE *fd) |
9796 | { |
9797 | #if defined(USE_CRNL) && defined(MKSESSION_NL) |
9798 | if ((!mksession_nl && putc('\r', fd) < 0) || putc('\n', fd) < 0) { |
9799 | #elif defined(USE_CRNL) |
9800 | if (putc('\r', fd) < 0 || putc('\n', fd) < 0) { |
9801 | #else |
9802 | if (putc('\n', fd) < 0) { |
9803 | #endif |
9804 | return FAIL; |
9805 | } |
9806 | return OK; |
9807 | } |
9808 | |
9809 | /* |
9810 | * Write a line to "fd". |
9811 | * Return FAIL for a write error. |
9812 | */ |
9813 | int put_line(FILE *fd, char *s) |
9814 | { |
9815 | if (fputs(s, fd) < 0 || put_eol(fd) == FAIL) |
9816 | return FAIL; |
9817 | return OK; |
9818 | } |
9819 | |
9820 | /* |
9821 | * ":rshada" and ":wshada". |
9822 | */ |
9823 | static void ex_shada(exarg_T *eap) |
9824 | { |
9825 | char_u *save_shada; |
9826 | |
9827 | save_shada = p_shada; |
9828 | if (*p_shada == NUL) |
9829 | p_shada = (char_u *)"'100" ; |
9830 | if (eap->cmdidx == CMD_rviminfo || eap->cmdidx == CMD_rshada) { |
9831 | (void) shada_read_everything((char *) eap->arg, eap->forceit, false); |
9832 | } else { |
9833 | shada_write_file((char *) eap->arg, eap->forceit); |
9834 | } |
9835 | p_shada = save_shada; |
9836 | } |
9837 | |
9838 | /* |
9839 | * Make a dialog message in "buff[DIALOG_MSG_SIZE]". |
9840 | * "format" must contain "%s". |
9841 | */ |
9842 | void dialog_msg(char_u *buff, char *format, char_u *fname) |
9843 | { |
9844 | if (fname == NULL) |
9845 | fname = (char_u *)_("Untitled" ); |
9846 | vim_snprintf((char *)buff, DIALOG_MSG_SIZE, format, fname); |
9847 | } |
9848 | |
9849 | /* |
9850 | * ":behave {mswin,xterm}" |
9851 | */ |
9852 | static void ex_behave(exarg_T *eap) |
9853 | { |
9854 | if (STRCMP(eap->arg, "mswin" ) == 0) { |
9855 | set_option_value("selection" , 0L, "exclusive" , 0); |
9856 | set_option_value("selectmode" , 0L, "mouse,key" , 0); |
9857 | set_option_value("mousemodel" , 0L, "popup" , 0); |
9858 | set_option_value("keymodel" , 0L, "startsel,stopsel" , 0); |
9859 | } else if (STRCMP(eap->arg, "xterm" ) == 0) { |
9860 | set_option_value("selection" , 0L, "inclusive" , 0); |
9861 | set_option_value("selectmode" , 0L, "" , 0); |
9862 | set_option_value("mousemodel" , 0L, "extend" , 0); |
9863 | set_option_value("keymodel" , 0L, "" , 0); |
9864 | } else { |
9865 | EMSG2(_(e_invarg2), eap->arg); |
9866 | } |
9867 | } |
9868 | |
9869 | /* |
9870 | * Function given to ExpandGeneric() to obtain the possible arguments of the |
9871 | * ":behave {mswin,xterm}" command. |
9872 | */ |
9873 | char_u *get_behave_arg(expand_T *xp, int idx) |
9874 | { |
9875 | if (idx == 0) |
9876 | return (char_u *)"mswin" ; |
9877 | if (idx == 1) |
9878 | return (char_u *)"xterm" ; |
9879 | return NULL; |
9880 | } |
9881 | |
9882 | // Function given to ExpandGeneric() to obtain the possible arguments of the |
9883 | // ":messages {clear}" command. |
9884 | char_u *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) |
9885 | { |
9886 | if (idx == 0) { |
9887 | return (char_u *)"clear" ; |
9888 | } |
9889 | return NULL; |
9890 | } |
9891 | |
9892 | char_u *get_mapclear_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) |
9893 | { |
9894 | if (idx == 0) { |
9895 | return (char_u *)"<buffer>" ; |
9896 | } |
9897 | return NULL; |
9898 | } |
9899 | |
9900 | static TriState filetype_detect = kNone; |
9901 | static TriState filetype_plugin = kNone; |
9902 | static TriState filetype_indent = kNone; |
9903 | |
9904 | /* |
9905 | * ":filetype [plugin] [indent] {on,off,detect}" |
9906 | * on: Load the filetype.vim file to install autocommands for file types. |
9907 | * off: Load the ftoff.vim file to remove all autocommands for file types. |
9908 | * plugin on: load filetype.vim and ftplugin.vim |
9909 | * plugin off: load ftplugof.vim |
9910 | * indent on: load filetype.vim and indent.vim |
9911 | * indent off: load indoff.vim |
9912 | */ |
9913 | static void ex_filetype(exarg_T *eap) |
9914 | { |
9915 | char_u *arg = eap->arg; |
9916 | bool plugin = false; |
9917 | bool indent = false; |
9918 | |
9919 | if (*eap->arg == NUL) { |
9920 | /* Print current status. */ |
9921 | smsg("filetype detection:%s plugin:%s indent:%s" , |
9922 | filetype_detect == kTrue ? "ON" : "OFF" , |
9923 | filetype_plugin == kTrue ? (filetype_detect == kTrue ? "ON" : "(on)" ) : "OFF" , // NOLINT(whitespace/line_length) |
9924 | filetype_indent == kTrue ? (filetype_detect == kTrue ? "ON" : "(on)" ) : "OFF" ); // NOLINT(whitespace/line_length) |
9925 | return; |
9926 | } |
9927 | |
9928 | /* Accept "plugin" and "indent" in any order. */ |
9929 | for (;; ) { |
9930 | if (STRNCMP(arg, "plugin" , 6) == 0) { |
9931 | plugin = true; |
9932 | arg = skipwhite(arg + 6); |
9933 | continue; |
9934 | } |
9935 | if (STRNCMP(arg, "indent" , 6) == 0) { |
9936 | indent = true; |
9937 | arg = skipwhite(arg + 6); |
9938 | continue; |
9939 | } |
9940 | break; |
9941 | } |
9942 | if (STRCMP(arg, "on" ) == 0 || STRCMP(arg, "detect" ) == 0) { |
9943 | if (*arg == 'o' || !filetype_detect) { |
9944 | source_runtime((char_u *)FILETYPE_FILE, DIP_ALL); |
9945 | filetype_detect = kTrue; |
9946 | if (plugin) { |
9947 | source_runtime((char_u *)FTPLUGIN_FILE, DIP_ALL); |
9948 | filetype_plugin = kTrue; |
9949 | } |
9950 | if (indent) { |
9951 | source_runtime((char_u *)INDENT_FILE, DIP_ALL); |
9952 | filetype_indent = kTrue; |
9953 | } |
9954 | } |
9955 | if (*arg == 'd') { |
9956 | (void)do_doautocmd((char_u *)"filetypedetect BufRead" , true, NULL); |
9957 | do_modelines(0); |
9958 | } |
9959 | } else if (STRCMP(arg, "off" ) == 0) { |
9960 | if (plugin || indent) { |
9961 | if (plugin) { |
9962 | source_runtime((char_u *)FTPLUGOF_FILE, DIP_ALL); |
9963 | filetype_plugin = kFalse; |
9964 | } |
9965 | if (indent) { |
9966 | source_runtime((char_u *)INDOFF_FILE, DIP_ALL); |
9967 | filetype_indent = kFalse; |
9968 | } |
9969 | } else { |
9970 | source_runtime((char_u *)FTOFF_FILE, DIP_ALL); |
9971 | filetype_detect = kFalse; |
9972 | } |
9973 | } else |
9974 | EMSG2(_(e_invarg2), arg); |
9975 | } |
9976 | |
9977 | /// Set all :filetype options ON if user did not explicitly set any to OFF. |
9978 | void filetype_maybe_enable(void) |
9979 | { |
9980 | if (filetype_detect == kNone) { |
9981 | source_runtime((char_u *)FILETYPE_FILE, true); |
9982 | filetype_detect = kTrue; |
9983 | } |
9984 | if (filetype_plugin == kNone) { |
9985 | source_runtime((char_u *)FTPLUGIN_FILE, true); |
9986 | filetype_plugin = kTrue; |
9987 | } |
9988 | if (filetype_indent == kNone) { |
9989 | source_runtime((char_u *)INDENT_FILE, true); |
9990 | filetype_indent = kTrue; |
9991 | } |
9992 | } |
9993 | |
9994 | /// ":setfiletype [FALLBACK] {name}" |
9995 | static void ex_setfiletype(exarg_T *eap) |
9996 | { |
9997 | if (!did_filetype) { |
9998 | char_u *arg = eap->arg; |
9999 | |
10000 | if (STRNCMP(arg, "FALLBACK " , 9) == 0) { |
10001 | arg += 9; |
10002 | } |
10003 | |
10004 | set_option_value("filetype" , 0L, (char *)arg, OPT_LOCAL); |
10005 | if (arg != eap->arg) { |
10006 | did_filetype = false; |
10007 | } |
10008 | } |
10009 | } |
10010 | |
10011 | static void ex_digraphs(exarg_T *eap) |
10012 | { |
10013 | if (*eap->arg != NUL) |
10014 | putdigraph(eap->arg); |
10015 | else |
10016 | listdigraphs(); |
10017 | } |
10018 | |
10019 | static void ex_set(exarg_T *eap) |
10020 | { |
10021 | int flags = 0; |
10022 | |
10023 | if (eap->cmdidx == CMD_setlocal) |
10024 | flags = OPT_LOCAL; |
10025 | else if (eap->cmdidx == CMD_setglobal) |
10026 | flags = OPT_GLOBAL; |
10027 | (void)do_set(eap->arg, flags); |
10028 | } |
10029 | |
10030 | void set_no_hlsearch(bool flag) |
10031 | { |
10032 | no_hlsearch = flag; |
10033 | set_vim_var_nr(VV_HLSEARCH, !no_hlsearch && p_hls); |
10034 | } |
10035 | |
10036 | /* |
10037 | * ":nohlsearch" |
10038 | */ |
10039 | static void ex_nohlsearch(exarg_T *eap) |
10040 | { |
10041 | set_no_hlsearch(true); |
10042 | redraw_all_later(SOME_VALID); |
10043 | } |
10044 | |
10045 | // ":[N]match {group} {pattern}" |
10046 | // Sets nextcmd to the start of the next command, if any. Also called when |
10047 | // skipping commands to find the next command. |
10048 | static void ex_match(exarg_T *eap) |
10049 | { |
10050 | char_u *p; |
10051 | char_u *g = NULL; |
10052 | char_u *end; |
10053 | int c; |
10054 | int id; |
10055 | |
10056 | if (eap->line2 <= 3) { |
10057 | id = eap->line2; |
10058 | } else { |
10059 | EMSG(e_invcmd); |
10060 | return; |
10061 | } |
10062 | |
10063 | // First clear any old pattern. |
10064 | if (!eap->skip) { |
10065 | match_delete(curwin, id, false); |
10066 | } |
10067 | |
10068 | if (ends_excmd(*eap->arg)) { |
10069 | end = eap->arg; |
10070 | } else if ((STRNICMP(eap->arg, "none" , 4) == 0 |
10071 | && (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) { |
10072 | end = eap->arg + 4; |
10073 | } else { |
10074 | p = skiptowhite(eap->arg); |
10075 | if (!eap->skip) { |
10076 | g = vim_strnsave(eap->arg, (int)(p - eap->arg)); |
10077 | } |
10078 | p = skipwhite(p); |
10079 | if (*p == NUL) { |
10080 | // There must be two arguments. |
10081 | xfree(g); |
10082 | EMSG2(_(e_invarg2), eap->arg); |
10083 | return; |
10084 | } |
10085 | end = skip_regexp(p + 1, *p, true, NULL); |
10086 | if (!eap->skip) { |
10087 | if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) { |
10088 | xfree(g); |
10089 | eap->errmsg = e_trailing; |
10090 | return; |
10091 | } |
10092 | if (*end != *p) { |
10093 | xfree(g); |
10094 | EMSG2(_(e_invarg2), p); |
10095 | return; |
10096 | } |
10097 | |
10098 | c = *end; |
10099 | *end = NUL; |
10100 | match_add(curwin, (const char *)g, (const char *)p + 1, 10, id, |
10101 | NULL, NULL); |
10102 | xfree(g); |
10103 | *end = c; |
10104 | } |
10105 | } |
10106 | eap->nextcmd = find_nextcmd(end); |
10107 | } |
10108 | |
10109 | static void ex_fold(exarg_T *eap) |
10110 | { |
10111 | if (foldManualAllowed(TRUE)) |
10112 | foldCreate(eap->line1, eap->line2); |
10113 | } |
10114 | |
10115 | static void ex_foldopen(exarg_T *eap) |
10116 | { |
10117 | opFoldRange(eap->line1, eap->line2, eap->cmdidx == CMD_foldopen, |
10118 | eap->forceit, FALSE); |
10119 | } |
10120 | |
10121 | static void ex_folddo(exarg_T *eap) |
10122 | { |
10123 | // First set the marks for all lines closed/open. |
10124 | for (linenr_T lnum = eap->line1; lnum <= eap->line2; ++lnum) { |
10125 | if (hasFolding(lnum, NULL, NULL) == (eap->cmdidx == CMD_folddoclosed)) { |
10126 | ml_setmarked(lnum); |
10127 | } |
10128 | } |
10129 | |
10130 | global_exe(eap->arg); // Execute the command on the marked lines. |
10131 | ml_clearmarked(); // clear rest of the marks |
10132 | } |
10133 | |
10134 | static void ex_terminal(exarg_T *eap) |
10135 | { |
10136 | char ex_cmd[1024]; |
10137 | |
10138 | if (*eap->arg != NUL) { // Run {cmd} in 'shell'. |
10139 | char *name = (char *)vim_strsave_escaped(eap->arg, (char_u *)"\"\\" ); |
10140 | snprintf(ex_cmd, sizeof(ex_cmd), |
10141 | ":enew%s | call termopen(\"%s\")" , |
10142 | eap->forceit ? "!" : "" , name); |
10143 | xfree(name); |
10144 | } else { // No {cmd}: run the job with tokenized 'shell'. |
10145 | if (*p_sh == NUL) { |
10146 | EMSG(_(e_shellempty)); |
10147 | return; |
10148 | } |
10149 | |
10150 | char **argv = shell_build_argv(NULL, NULL); |
10151 | char **p = argv; |
10152 | char tempstring[512]; |
10153 | char shell_argv[512] = { 0 }; |
10154 | |
10155 | while (*p != NULL) { |
10156 | snprintf(tempstring, sizeof(tempstring), ",\"%s\"" , *p); |
10157 | xstrlcat(shell_argv, tempstring, sizeof(shell_argv)); |
10158 | p++; |
10159 | } |
10160 | shell_free_argv(argv); |
10161 | |
10162 | snprintf(ex_cmd, sizeof(ex_cmd), |
10163 | ":enew%s | call termopen([%s])" , |
10164 | eap->forceit ? "!" : "" , shell_argv + 1); |
10165 | } |
10166 | |
10167 | do_cmdline_cmd(ex_cmd); |
10168 | } |
10169 | |
10170 | /// Checks if `cmd` is "previewable" (i.e. supported by 'inccommand'). |
10171 | /// |
10172 | /// @param[in] cmd Commandline to check. May start with a range or modifier. |
10173 | /// |
10174 | /// @return true if `cmd` is previewable |
10175 | bool cmd_can_preview(char_u *cmd) |
10176 | { |
10177 | if (cmd == NULL) { |
10178 | return false; |
10179 | } |
10180 | |
10181 | // Ignore any leading modifiers (:keeppatterns, :verbose, etc.) |
10182 | for (int len = modifier_len(cmd); len != 0; len = modifier_len(cmd)) { |
10183 | cmd += len; |
10184 | cmd = skipwhite(cmd); |
10185 | } |
10186 | |
10187 | exarg_T ea; |
10188 | memset(&ea, 0, sizeof(ea)); |
10189 | // parse the command line |
10190 | ea.cmd = skip_range(cmd, NULL); |
10191 | if (*ea.cmd == '*') { |
10192 | ea.cmd = skipwhite(ea.cmd + 1); |
10193 | } |
10194 | char_u *end = find_command(&ea, NULL); |
10195 | |
10196 | switch (ea.cmdidx) { |
10197 | case CMD_substitute: |
10198 | case CMD_smagic: |
10199 | case CMD_snomagic: |
10200 | // Only preview once the pattern delimiter has been typed |
10201 | if (*end && !ASCII_ISALNUM(*end)) { |
10202 | return true; |
10203 | } |
10204 | break; |
10205 | default: |
10206 | break; |
10207 | } |
10208 | |
10209 | return false; |
10210 | } |
10211 | |
10212 | /// Gets a map of maps describing user-commands defined for buffer `buf` or |
10213 | /// defined globally if `buf` is NULL. |
10214 | /// |
10215 | /// @param buf Buffer to inspect, or NULL to get global commands. |
10216 | /// |
10217 | /// @return Map of maps describing commands |
10218 | Dictionary commands_array(buf_T *buf) |
10219 | { |
10220 | Dictionary rv = ARRAY_DICT_INIT; |
10221 | Object obj = NIL; |
10222 | (void)obj; // Avoid "dead assignment" warning. |
10223 | char str[20]; |
10224 | garray_T *gap = (buf == NULL) ? &ucmds : &buf->b_ucmds; |
10225 | |
10226 | for (int i = 0; i < gap->ga_len; i++) { |
10227 | char arg[2] = { 0, 0 }; |
10228 | Dictionary d = ARRAY_DICT_INIT; |
10229 | ucmd_T *cmd = USER_CMD_GA(gap, i); |
10230 | |
10231 | PUT(d, "name" , STRING_OBJ(cstr_to_string((char *)cmd->uc_name))); |
10232 | PUT(d, "definition" , STRING_OBJ(cstr_to_string((char *)cmd->uc_rep))); |
10233 | PUT(d, "script_id" , INTEGER_OBJ(cmd->uc_script_ctx.sc_sid)); |
10234 | PUT(d, "bang" , BOOLEAN_OBJ(!!(cmd->uc_argt & BANG))); |
10235 | PUT(d, "bar" , BOOLEAN_OBJ(!!(cmd->uc_argt & TRLBAR))); |
10236 | PUT(d, "register" , BOOLEAN_OBJ(!!(cmd->uc_argt & REGSTR))); |
10237 | |
10238 | switch (cmd->uc_argt & (EXTRA|NOSPC|NEEDARG)) { |
10239 | case 0: arg[0] = '0'; break; |
10240 | case(EXTRA): arg[0] = '*'; break; |
10241 | case(EXTRA|NOSPC): arg[0] = '?'; break; |
10242 | case(EXTRA|NEEDARG): arg[0] = '+'; break; |
10243 | case(EXTRA|NOSPC|NEEDARG): arg[0] = '1'; break; |
10244 | } |
10245 | PUT(d, "nargs" , STRING_OBJ(cstr_to_string(arg))); |
10246 | |
10247 | char *cmd_compl = get_command_complete(cmd->uc_compl); |
10248 | PUT(d, "complete" , (cmd_compl == NULL |
10249 | ? NIL : STRING_OBJ(cstr_to_string(cmd_compl)))); |
10250 | PUT(d, "complete_arg" , cmd->uc_compl_arg == NULL |
10251 | ? NIL : STRING_OBJ(cstr_to_string((char *)cmd->uc_compl_arg))); |
10252 | |
10253 | obj = NIL; |
10254 | if (cmd->uc_argt & COUNT) { |
10255 | if (cmd->uc_def >= 0) { |
10256 | snprintf(str, sizeof(str), "%" PRId64, (int64_t)cmd->uc_def); |
10257 | obj = STRING_OBJ(cstr_to_string(str)); // -count=N |
10258 | } else { |
10259 | obj = STRING_OBJ(cstr_to_string("0" )); // -count |
10260 | } |
10261 | } |
10262 | PUT(d, "count" , obj); |
10263 | |
10264 | obj = NIL; |
10265 | if (cmd->uc_argt & RANGE) { |
10266 | if (cmd->uc_argt & DFLALL) { |
10267 | obj = STRING_OBJ(cstr_to_string("%" )); // -range=% |
10268 | } else if (cmd->uc_def >= 0) { |
10269 | snprintf(str, sizeof(str), "%" PRId64, (int64_t)cmd->uc_def); |
10270 | obj = STRING_OBJ(cstr_to_string(str)); // -range=N |
10271 | } else { |
10272 | obj = STRING_OBJ(cstr_to_string("." )); // -range |
10273 | } |
10274 | } |
10275 | PUT(d, "range" , obj); |
10276 | |
10277 | obj = NIL; |
10278 | for (int j = 0; addr_type_complete[j].expand != -1; j++) { |
10279 | if (addr_type_complete[j].expand != ADDR_LINES |
10280 | && addr_type_complete[j].expand == cmd->uc_addr_type) { |
10281 | obj = STRING_OBJ(cstr_to_string(addr_type_complete[j].name)); |
10282 | break; |
10283 | } |
10284 | } |
10285 | PUT(d, "addr" , obj); |
10286 | |
10287 | PUT(rv, (char *)cmd->uc_name, DICTIONARY_OBJ(d)); |
10288 | } |
10289 | return rv; |
10290 | } |
10291 | |