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_cmds.c: some functions for command line commands |
6 | */ |
7 | |
8 | #include <assert.h> |
9 | #include <float.h> |
10 | #include <stdbool.h> |
11 | #include <string.h> |
12 | #include <stdlib.h> |
13 | #include <inttypes.h> |
14 | #include <math.h> |
15 | |
16 | #include "nvim/api/private/defs.h" |
17 | #include "nvim/api/buffer.h" |
18 | #include "nvim/log.h" |
19 | #include "nvim/vim.h" |
20 | #include "nvim/ascii.h" |
21 | #include "nvim/ex_cmds.h" |
22 | #include "nvim/buffer.h" |
23 | #include "nvim/change.h" |
24 | #include "nvim/charset.h" |
25 | #include "nvim/cursor.h" |
26 | #include "nvim/diff.h" |
27 | #include "nvim/digraph.h" |
28 | #include "nvim/edit.h" |
29 | #include "nvim/eval.h" |
30 | #include "nvim/ex_cmds2.h" |
31 | #include "nvim/ex_docmd.h" |
32 | #include "nvim/ex_eval.h" |
33 | #include "nvim/ex_getln.h" |
34 | #include "nvim/fileio.h" |
35 | #include "nvim/fold.h" |
36 | #include "nvim/getchar.h" |
37 | #include "nvim/highlight.h" |
38 | #include "nvim/indent.h" |
39 | #include "nvim/buffer_updates.h" |
40 | #include "nvim/main.h" |
41 | #include "nvim/mark.h" |
42 | #include "nvim/mbyte.h" |
43 | #include "nvim/memline.h" |
44 | #include "nvim/message.h" |
45 | #include "nvim/misc1.h" |
46 | #include "nvim/garray.h" |
47 | #include "nvim/memory.h" |
48 | #include "nvim/move.h" |
49 | #include "nvim/mouse.h" |
50 | #include "nvim/normal.h" |
51 | #include "nvim/ops.h" |
52 | #include "nvim/option.h" |
53 | #include "nvim/os_unix.h" |
54 | #include "nvim/path.h" |
55 | #include "nvim/quickfix.h" |
56 | #include "nvim/regexp.h" |
57 | #include "nvim/screen.h" |
58 | #include "nvim/search.h" |
59 | #include "nvim/spell.h" |
60 | #include "nvim/strings.h" |
61 | #include "nvim/syntax.h" |
62 | #include "nvim/tag.h" |
63 | #include "nvim/ui.h" |
64 | #include "nvim/undo.h" |
65 | #include "nvim/window.h" |
66 | #include "nvim/os/os.h" |
67 | #include "nvim/os/shell.h" |
68 | #include "nvim/os/input.h" |
69 | #include "nvim/os/time.h" |
70 | |
71 | |
72 | /// Case matching style to use for :substitute |
73 | typedef enum { |
74 | kSubHonorOptions = 0, ///< Honor the user's 'ignorecase'/'smartcase' options |
75 | kSubIgnoreCase, ///< Ignore case of the search |
76 | kSubMatchCase, ///< Match case of the search |
77 | } SubIgnoreType; |
78 | |
79 | /// Flags kept between calls to :substitute. |
80 | typedef struct { |
81 | bool do_all; ///< do multiple substitutions per line |
82 | bool do_ask; ///< ask for confirmation |
83 | bool do_count; ///< count only |
84 | bool do_error; ///< if false, ignore errors |
85 | bool do_print; ///< print last line with subs |
86 | bool do_list; ///< list last line with subs |
87 | bool do_number; ///< list last line with line nr |
88 | SubIgnoreType do_ic; ///< ignore case flag |
89 | } subflags_T; |
90 | |
91 | /// Partial result of a substitution during :substitute. |
92 | /// Numbers refer to the buffer _after_ substitution |
93 | typedef struct { |
94 | lpos_T start; // start of the match |
95 | lpos_T end; // end of the match |
96 | linenr_T pre_match; // where to begin showing lines before the match |
97 | } SubResult; |
98 | |
99 | // Collected results of a substitution for showing them in |
100 | // the preview window |
101 | typedef struct { |
102 | kvec_t(SubResult) subresults; |
103 | linenr_T lines_needed; // lines neede in the preview window |
104 | } PreviewLines; |
105 | |
106 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
107 | # include "ex_cmds.c.generated.h" |
108 | #endif |
109 | |
110 | /// ":ascii" and "ga" implementation |
111 | void do_ascii(const exarg_T *const eap) |
112 | { |
113 | char_u *dig; |
114 | int cc[MAX_MCO]; |
115 | int c = utfc_ptr2char(get_cursor_pos_ptr(), cc); |
116 | if (c == NUL) { |
117 | MSG("NUL" ); |
118 | return; |
119 | } |
120 | |
121 | size_t iobuff_len = 0; |
122 | |
123 | int ci = 0; |
124 | if (c < 0x80) { |
125 | if (c == NL) { // NUL is stored as NL. |
126 | c = NUL; |
127 | } |
128 | const int cval = (c == CAR && get_fileformat(curbuf) == EOL_MAC |
129 | ? NL // NL is stored as CR. |
130 | : c); |
131 | char buf1[20]; |
132 | if (vim_isprintc_strict(c) && (c < ' ' || c > '~')) { |
133 | char_u buf3[7]; |
134 | transchar_nonprint(buf3, c); |
135 | vim_snprintf(buf1, sizeof(buf1), " <%s>" , (char *)buf3); |
136 | } else { |
137 | buf1[0] = NUL; |
138 | } |
139 | char buf2[20]; |
140 | buf2[0] = NUL; |
141 | |
142 | dig = get_digraph_for_char(cval); |
143 | if (dig != NULL) { |
144 | iobuff_len += ( |
145 | vim_snprintf((char *)IObuff + iobuff_len, |
146 | sizeof(IObuff) - iobuff_len, |
147 | _("<%s>%s%s %d, Hex %02x, Oct %03o, Digr %s" ), |
148 | transchar(c), buf1, buf2, cval, cval, cval, dig)); |
149 | } else { |
150 | iobuff_len += ( |
151 | vim_snprintf((char *)IObuff + iobuff_len, |
152 | sizeof(IObuff) - iobuff_len, |
153 | _("<%s>%s%s %d, Hex %02x, Octal %03o" ), |
154 | transchar(c), buf1, buf2, cval, cval, cval)); |
155 | } |
156 | |
157 | c = cc[ci++]; |
158 | } |
159 | |
160 | #define SPACE_FOR_DESC (1 + 1 + 1 + MB_MAXBYTES + 16 + 4 + 3 + 3 + 1) |
161 | // Space for description: |
162 | // - 1 byte for separator (starting from second entry) |
163 | // - 1 byte for "<" |
164 | // - 1 byte for space to draw composing character on (optional, but really |
165 | // mostly required) |
166 | // - up to MB_MAXBYTES bytes for character itself |
167 | // - 16 bytes for raw text ("> , Hex , Octal "). |
168 | // - at least 4 bytes for hexadecimal representation |
169 | // - at least 3 bytes for decimal representation |
170 | // - at least 3 bytes for octal representation |
171 | // - 1 byte for NUL |
172 | // |
173 | // Taking into account MAX_MCO and characters which need 8 bytes for |
174 | // hexadecimal representation, but not taking translation into account: |
175 | // resulting string will occupy less then 400 bytes (conservative estimate). |
176 | // |
177 | // Less then 1000 bytes if translation multiplies number of bytes needed for |
178 | // raw text by 6, so it should always fit into 1025 bytes reserved for IObuff. |
179 | |
180 | // Repeat for combining characters, also handle multiby here. |
181 | while (c >= 0x80 && iobuff_len < sizeof(IObuff) - SPACE_FOR_DESC) { |
182 | // This assumes every multi-byte char is printable... |
183 | if (iobuff_len > 0) { |
184 | IObuff[iobuff_len++] = ' '; |
185 | } |
186 | IObuff[iobuff_len++] = '<'; |
187 | if (utf_iscomposing(c)) { |
188 | IObuff[iobuff_len++] = ' '; // Draw composing char on top of a space. |
189 | } |
190 | iobuff_len += utf_char2bytes(c, IObuff + iobuff_len); |
191 | |
192 | dig = get_digraph_for_char(c); |
193 | if (dig != NULL) { |
194 | iobuff_len += ( |
195 | vim_snprintf((char *)IObuff + iobuff_len, |
196 | sizeof(IObuff) - iobuff_len, |
197 | (c < 0x10000 |
198 | ? _("> %d, Hex %04x, Oct %o, Digr %s" ) |
199 | : _("> %d, Hex %08x, Oct %o, Digr %s" )), |
200 | c, c, c, dig)); |
201 | } else { |
202 | iobuff_len += ( |
203 | vim_snprintf((char *)IObuff + iobuff_len, |
204 | sizeof(IObuff) - iobuff_len, |
205 | (c < 0x10000 |
206 | ? _("> %d, Hex %04x, Octal %o" ) |
207 | : _("> %d, Hex %08x, Octal %o" )), |
208 | c, c, c)); |
209 | } |
210 | if (ci == MAX_MCO) { |
211 | break; |
212 | } |
213 | c = cc[ci++]; |
214 | } |
215 | if (ci != MAX_MCO && c != 0) { |
216 | xstrlcpy((char *)IObuff + iobuff_len, " ..." , sizeof(IObuff) - iobuff_len); |
217 | } |
218 | |
219 | msg(IObuff); |
220 | } |
221 | |
222 | /* |
223 | * ":left", ":center" and ":right": align text. |
224 | */ |
225 | void ex_align(exarg_T *eap) |
226 | { |
227 | pos_T save_curpos; |
228 | int len; |
229 | int indent = 0; |
230 | int new_indent; |
231 | int has_tab; |
232 | int width; |
233 | |
234 | if (curwin->w_p_rl) { |
235 | /* switch left and right aligning */ |
236 | if (eap->cmdidx == CMD_right) |
237 | eap->cmdidx = CMD_left; |
238 | else if (eap->cmdidx == CMD_left) |
239 | eap->cmdidx = CMD_right; |
240 | } |
241 | |
242 | width = atoi((char *)eap->arg); |
243 | save_curpos = curwin->w_cursor; |
244 | if (eap->cmdidx == CMD_left) { /* width is used for new indent */ |
245 | if (width >= 0) |
246 | indent = width; |
247 | } else { |
248 | /* |
249 | * if 'textwidth' set, use it |
250 | * else if 'wrapmargin' set, use it |
251 | * if invalid value, use 80 |
252 | */ |
253 | if (width <= 0) |
254 | width = curbuf->b_p_tw; |
255 | if (width == 0 && curbuf->b_p_wm > 0) { |
256 | width = curwin->w_width_inner - curbuf->b_p_wm; |
257 | } |
258 | if (width <= 0) { |
259 | width = 80; |
260 | } |
261 | } |
262 | |
263 | if (u_save((linenr_T)(eap->line1 - 1), (linenr_T)(eap->line2 + 1)) == FAIL) |
264 | return; |
265 | |
266 | for (curwin->w_cursor.lnum = eap->line1; |
267 | curwin->w_cursor.lnum <= eap->line2; ++curwin->w_cursor.lnum) { |
268 | if (eap->cmdidx == CMD_left) /* left align */ |
269 | new_indent = indent; |
270 | else { |
271 | has_tab = FALSE; /* avoid uninit warnings */ |
272 | len = linelen(eap->cmdidx == CMD_right ? &has_tab |
273 | : NULL) - get_indent(); |
274 | |
275 | if (len <= 0) /* skip blank lines */ |
276 | continue; |
277 | |
278 | if (eap->cmdidx == CMD_center) |
279 | new_indent = (width - len) / 2; |
280 | else { |
281 | new_indent = width - len; /* right align */ |
282 | |
283 | /* |
284 | * Make sure that embedded TABs don't make the text go too far |
285 | * to the right. |
286 | */ |
287 | if (has_tab) |
288 | while (new_indent > 0) { |
289 | (void)set_indent(new_indent, 0); |
290 | if (linelen(NULL) <= width) { |
291 | /* |
292 | * Now try to move the line as much as possible to |
293 | * the right. Stop when it moves too far. |
294 | */ |
295 | do |
296 | (void)set_indent(++new_indent, 0); |
297 | while (linelen(NULL) <= width); |
298 | --new_indent; |
299 | break; |
300 | } |
301 | --new_indent; |
302 | } |
303 | } |
304 | } |
305 | if (new_indent < 0) |
306 | new_indent = 0; |
307 | (void)set_indent(new_indent, 0); /* set indent */ |
308 | } |
309 | changed_lines(eap->line1, 0, eap->line2 + 1, 0L, true); |
310 | curwin->w_cursor = save_curpos; |
311 | beginline(BL_WHITE | BL_FIX); |
312 | } |
313 | |
314 | /* |
315 | * Get the length of the current line, excluding trailing white space. |
316 | */ |
317 | static int linelen(int *has_tab) |
318 | { |
319 | char_u *line; |
320 | char_u *first; |
321 | char_u *last; |
322 | int save; |
323 | int len; |
324 | |
325 | /* find the first non-blank character */ |
326 | line = get_cursor_line_ptr(); |
327 | first = skipwhite(line); |
328 | |
329 | /* find the character after the last non-blank character */ |
330 | for (last = first + STRLEN(first); |
331 | last > first && ascii_iswhite(last[-1]); --last) |
332 | ; |
333 | save = *last; |
334 | *last = NUL; |
335 | // Get line length. |
336 | len = linetabsize(line); |
337 | // Check for embedded TAB. |
338 | if (has_tab != NULL) { |
339 | *has_tab = vim_strchr(first, TAB) != NULL; |
340 | } |
341 | *last = save; |
342 | |
343 | return len; |
344 | } |
345 | |
346 | /* Buffer for two lines used during sorting. They are allocated to |
347 | * contain the longest line being sorted. */ |
348 | static char_u *sortbuf1; |
349 | static char_u *sortbuf2; |
350 | |
351 | static int sort_ic; ///< ignore case |
352 | static int sort_nr; ///< sort on number |
353 | static int sort_rx; ///< sort on regex instead of skipping it |
354 | static int sort_flt; ///< sort on floating number |
355 | |
356 | static int sort_abort; ///< flag to indicate if sorting has been interrupted |
357 | |
358 | /// Struct to store info to be sorted. |
359 | typedef struct { |
360 | linenr_T lnum; ///< line number |
361 | union { |
362 | struct { |
363 | varnumber_T start_col_nr; ///< starting column number |
364 | varnumber_T end_col_nr; ///< ending column number |
365 | } line; |
366 | struct { |
367 | varnumber_T value; ///< value if sorting by integer |
368 | bool is_number; ///< true when line contains a number |
369 | } num; |
370 | float_T value_flt; ///< value if sorting by float |
371 | } st_u; |
372 | } sorti_T; |
373 | |
374 | |
375 | static int sort_compare(const void *s1, const void *s2) |
376 | { |
377 | sorti_T l1 = *(sorti_T *)s1; |
378 | sorti_T l2 = *(sorti_T *)s2; |
379 | int result = 0; |
380 | |
381 | /* If the user interrupts, there's no way to stop qsort() immediately, but |
382 | * if we return 0 every time, qsort will assume it's done sorting and |
383 | * exit. */ |
384 | if (sort_abort) |
385 | return 0; |
386 | fast_breakcheck(); |
387 | if (got_int) |
388 | sort_abort = TRUE; |
389 | |
390 | // When sorting numbers "start_col_nr" is the number, not the column |
391 | // number. |
392 | if (sort_nr) { |
393 | if (l1.st_u.num.is_number != l2.st_u.num.is_number) { |
394 | result = l1.st_u.num.is_number - l2.st_u.num.is_number; |
395 | } else { |
396 | result = l1.st_u.num.value == l2.st_u.num.value |
397 | ? 0 |
398 | : l1.st_u.num.value > l2.st_u.num.value |
399 | ? 1 |
400 | : -1; |
401 | } |
402 | } else if (sort_flt) { |
403 | result = l1.st_u.value_flt == l2.st_u.value_flt |
404 | ? 0 : l1.st_u.value_flt > l2.st_u.value_flt |
405 | ? 1 : -1; |
406 | } else { |
407 | // We need to copy one line into "sortbuf1", because there is no |
408 | // guarantee that the first pointer becomes invalid when obtaining the |
409 | // second one. |
410 | memcpy(sortbuf1, ml_get(l1.lnum) + l1.st_u.line.start_col_nr, |
411 | l1.st_u.line.end_col_nr - l1.st_u.line.start_col_nr + 1); |
412 | sortbuf1[l1.st_u.line.end_col_nr - l1.st_u.line.start_col_nr] = NUL; |
413 | memcpy(sortbuf2, ml_get(l2.lnum) + l2.st_u.line.start_col_nr, |
414 | l2.st_u.line.end_col_nr - l2.st_u.line.start_col_nr + 1); |
415 | sortbuf2[l2.st_u.line.end_col_nr - l2.st_u.line.start_col_nr] = NUL; |
416 | |
417 | result = sort_ic ? STRICMP(sortbuf1, sortbuf2) |
418 | : STRCMP(sortbuf1, sortbuf2); |
419 | } |
420 | |
421 | /* If two lines have the same value, preserve the original line order. */ |
422 | if (result == 0) |
423 | return (int)(l1.lnum - l2.lnum); |
424 | return result; |
425 | } |
426 | |
427 | // ":sort". |
428 | void ex_sort(exarg_T *eap) |
429 | { |
430 | regmatch_T regmatch; |
431 | int len; |
432 | linenr_T lnum; |
433 | long maxlen = 0; |
434 | size_t count = (size_t)(eap->line2 - eap->line1 + 1); |
435 | size_t i; |
436 | char_u *p; |
437 | char_u *s; |
438 | char_u *s2; |
439 | char_u c; // temporary character storage |
440 | bool unique = false; |
441 | long deleted; |
442 | colnr_T start_col; |
443 | colnr_T end_col; |
444 | int sort_what = 0; |
445 | |
446 | // Sorting one line is really quick! |
447 | if (count <= 1) { |
448 | return; |
449 | } |
450 | |
451 | if (u_save((linenr_T)(eap->line1 - 1), (linenr_T)(eap->line2 + 1)) == FAIL) { |
452 | return; |
453 | } |
454 | sortbuf1 = NULL; |
455 | sortbuf2 = NULL; |
456 | regmatch.regprog = NULL; |
457 | sorti_T *nrs = xmalloc(count * sizeof(sorti_T)); |
458 | |
459 | sort_abort = sort_ic = sort_rx = sort_nr = sort_flt = 0; |
460 | size_t format_found = 0; |
461 | bool change_occurred = false; // Buffer contents changed. |
462 | |
463 | for (p = eap->arg; *p != NUL; ++p) { |
464 | if (ascii_iswhite(*p)) { |
465 | } else if (*p == 'i') { |
466 | sort_ic = true; |
467 | } else if (*p == 'r') { |
468 | sort_rx = true; |
469 | } else if (*p == 'n') { |
470 | sort_nr = 1; |
471 | format_found++; |
472 | } else if (*p == 'f') { |
473 | sort_flt = 1; |
474 | format_found++; |
475 | } else if (*p == 'b') { |
476 | sort_what = STR2NR_BIN + STR2NR_FORCE; |
477 | format_found++; |
478 | } else if (*p == 'o') { |
479 | sort_what = STR2NR_OCT + STR2NR_FORCE; |
480 | format_found++; |
481 | } else if (*p == 'x') { |
482 | sort_what = STR2NR_HEX + STR2NR_FORCE; |
483 | format_found++; |
484 | } else if (*p == 'u') { |
485 | unique = true; |
486 | } else if (*p == '"') { |
487 | // comment start |
488 | break; |
489 | } else if (check_nextcmd(p) != NULL) { |
490 | eap->nextcmd = check_nextcmd(p); |
491 | break; |
492 | } else if (!ASCII_ISALPHA(*p) && regmatch.regprog == NULL) { |
493 | s = skip_regexp(p + 1, *p, true, NULL); |
494 | if (*s != *p) { |
495 | EMSG(_(e_invalpat)); |
496 | goto sortend; |
497 | } |
498 | *s = NUL; |
499 | // Use last search pattern if sort pattern is empty. |
500 | if (s == p + 1) { |
501 | if (last_search_pat() == NULL) { |
502 | EMSG(_(e_noprevre)); |
503 | goto sortend; |
504 | } |
505 | regmatch.regprog = vim_regcomp(last_search_pat(), RE_MAGIC); |
506 | } else { |
507 | regmatch.regprog = vim_regcomp(p + 1, RE_MAGIC); |
508 | } |
509 | if (regmatch.regprog == NULL) { |
510 | goto sortend; |
511 | } |
512 | p = s; // continue after the regexp |
513 | regmatch.rm_ic = p_ic; |
514 | } else { |
515 | EMSG2(_(e_invarg2), p); |
516 | goto sortend; |
517 | } |
518 | } |
519 | |
520 | // Can only have one of 'n', 'b', 'o' and 'x'. |
521 | if (format_found > 1) { |
522 | EMSG(_(e_invarg)); |
523 | goto sortend; |
524 | } |
525 | |
526 | // From here on "sort_nr" is used as a flag for any integer number |
527 | // sorting. |
528 | sort_nr += sort_what; |
529 | |
530 | // Make an array with all line numbers. This avoids having to copy all |
531 | // the lines into allocated memory. |
532 | // When sorting on strings "start_col_nr" is the offset in the line, for |
533 | // numbers sorting it's the number to sort on. This means the pattern |
534 | // matching and number conversion only has to be done once per line. |
535 | // Also get the longest line length for allocating "sortbuf". |
536 | for (lnum = eap->line1; lnum <= eap->line2; ++lnum) { |
537 | s = ml_get(lnum); |
538 | len = (int)STRLEN(s); |
539 | if (maxlen < len) { |
540 | maxlen = len; |
541 | } |
542 | |
543 | start_col = 0; |
544 | end_col = len; |
545 | if (regmatch.regprog != NULL && vim_regexec(®match, s, 0)) { |
546 | if (sort_rx) { |
547 | start_col = (colnr_T)(regmatch.startp[0] - s); |
548 | end_col = (colnr_T)(regmatch.endp[0] - s); |
549 | } else { |
550 | start_col = (colnr_T)(regmatch.endp[0] - s); |
551 | } |
552 | } else if (regmatch.regprog != NULL) { |
553 | end_col = 0; |
554 | } |
555 | |
556 | if (sort_nr || sort_flt) { |
557 | // Make sure vim_str2nr doesn't read any digits past the end |
558 | // of the match, by temporarily terminating the string there |
559 | s2 = s + end_col; |
560 | c = *s2; |
561 | *s2 = NUL; |
562 | // Sorting on number: Store the number itself. |
563 | p = s + start_col; |
564 | if (sort_nr) { |
565 | if (sort_what & STR2NR_HEX) { |
566 | s = skiptohex(p); |
567 | } else if (sort_what & STR2NR_BIN) { |
568 | s = (char_u *)skiptobin((char *)p); |
569 | } else { |
570 | s = skiptodigit(p); |
571 | } |
572 | if (s > p && s[-1] == '-') { |
573 | s--; // include preceding negative sign |
574 | } |
575 | if (*s == NUL) { |
576 | // line without number should sort before any number |
577 | nrs[lnum - eap->line1].st_u.num.is_number = false; |
578 | nrs[lnum - eap->line1].st_u.num.value = 0; |
579 | } else { |
580 | nrs[lnum - eap->line1].st_u.num.is_number = true; |
581 | vim_str2nr(s, NULL, NULL, sort_what, |
582 | &nrs[lnum - eap->line1].st_u.num.value, NULL, 0); |
583 | } |
584 | } else { |
585 | s = skipwhite(p); |
586 | if (*s == '+') { |
587 | s = skipwhite(s + 1); |
588 | } |
589 | |
590 | if (*s == NUL) { |
591 | // empty line should sort before any number |
592 | nrs[lnum - eap->line1].st_u.value_flt = -DBL_MAX; |
593 | } else { |
594 | nrs[lnum - eap->line1].st_u.value_flt = strtod((char *)s, NULL); |
595 | } |
596 | } |
597 | *s2 = c; |
598 | } else { |
599 | // Store the column to sort at. |
600 | nrs[lnum - eap->line1].st_u.line.start_col_nr = start_col; |
601 | nrs[lnum - eap->line1].st_u.line.end_col_nr = end_col; |
602 | } |
603 | |
604 | nrs[lnum - eap->line1].lnum = lnum; |
605 | |
606 | if (regmatch.regprog != NULL) |
607 | fast_breakcheck(); |
608 | if (got_int) |
609 | goto sortend; |
610 | } |
611 | |
612 | // Allocate a buffer that can hold the longest line. |
613 | sortbuf1 = xmalloc(maxlen + 1); |
614 | sortbuf2 = xmalloc(maxlen + 1); |
615 | |
616 | // Sort the array of line numbers. Note: can't be interrupted! |
617 | qsort((void *)nrs, count, sizeof(sorti_T), sort_compare); |
618 | |
619 | if (sort_abort) |
620 | goto sortend; |
621 | |
622 | // Insert the lines in the sorted order below the last one. |
623 | lnum = eap->line2; |
624 | for (i = 0; i < count; i++) { |
625 | const linenr_T get_lnum = nrs[eap->forceit ? count - i - 1 : i].lnum; |
626 | |
627 | // If the original line number of the line being placed is not the same |
628 | // as "lnum" (accounting for offset), we know that the buffer changed. |
629 | if (get_lnum + ((linenr_T)count - 1) != lnum) { |
630 | change_occurred = true; |
631 | } |
632 | |
633 | s = ml_get(get_lnum); |
634 | if (!unique || i == 0 |
635 | || (sort_ic ? STRICMP(s, sortbuf1) : STRCMP(s, sortbuf1)) != 0) { |
636 | // Copy the line into a buffer, it may become invalid in |
637 | // ml_append(). And it's needed for "unique". |
638 | STRCPY(sortbuf1, s); |
639 | if (ml_append(lnum++, sortbuf1, (colnr_T)0, false) == FAIL) { |
640 | break; |
641 | } |
642 | } |
643 | fast_breakcheck(); |
644 | if (got_int) |
645 | goto sortend; |
646 | } |
647 | |
648 | // delete the original lines if appending worked |
649 | if (i == count) { |
650 | for (i = 0; i < count; ++i) { |
651 | ml_delete(eap->line1, false); |
652 | } |
653 | } else { |
654 | count = 0; |
655 | } |
656 | |
657 | // Adjust marks for deleted (or added) lines and prepare for displaying. |
658 | deleted = (long)(count - (lnum - eap->line2)); |
659 | if (deleted > 0) { |
660 | mark_adjust(eap->line2 - deleted, eap->line2, (long)MAXLNUM, -deleted, |
661 | false); |
662 | msgmore(-deleted); |
663 | } else if (deleted < 0) { |
664 | mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false); |
665 | } |
666 | if (change_occurred || deleted != 0) { |
667 | changed_lines(eap->line1, 0, eap->line2 + 1, -deleted, true); |
668 | } |
669 | |
670 | curwin->w_cursor.lnum = eap->line1; |
671 | beginline(BL_WHITE | BL_FIX); |
672 | |
673 | sortend: |
674 | xfree(nrs); |
675 | xfree(sortbuf1); |
676 | xfree(sortbuf2); |
677 | vim_regfree(regmatch.regprog); |
678 | if (got_int) { |
679 | EMSG(_(e_interr)); |
680 | } |
681 | } |
682 | |
683 | /* |
684 | * ":retab". |
685 | */ |
686 | void ex_retab(exarg_T *eap) |
687 | { |
688 | linenr_T lnum; |
689 | int got_tab = FALSE; |
690 | long num_spaces = 0; |
691 | long num_tabs; |
692 | long len; |
693 | long col; |
694 | long vcol; |
695 | long start_col = 0; /* For start of white-space string */ |
696 | long start_vcol = 0; /* For start of white-space string */ |
697 | int temp; |
698 | long old_len; |
699 | char_u *ptr; |
700 | char_u *new_line = (char_u *)1; /* init to non-NULL */ |
701 | int did_undo; /* called u_save for current line */ |
702 | int new_ts; |
703 | int save_list; |
704 | linenr_T first_line = 0; /* first changed line */ |
705 | linenr_T last_line = 0; /* last changed line */ |
706 | |
707 | save_list = curwin->w_p_list; |
708 | curwin->w_p_list = 0; /* don't want list mode here */ |
709 | |
710 | new_ts = getdigits_int(&(eap->arg), false, -1); |
711 | if (new_ts < 0) { |
712 | EMSG(_(e_positive)); |
713 | return; |
714 | } |
715 | if (new_ts == 0) |
716 | new_ts = curbuf->b_p_ts; |
717 | for (lnum = eap->line1; !got_int && lnum <= eap->line2; ++lnum) { |
718 | ptr = ml_get(lnum); |
719 | col = 0; |
720 | vcol = 0; |
721 | did_undo = FALSE; |
722 | for (;; ) { |
723 | if (ascii_iswhite(ptr[col])) { |
724 | if (!got_tab && num_spaces == 0) { |
725 | /* First consecutive white-space */ |
726 | start_vcol = vcol; |
727 | start_col = col; |
728 | } |
729 | if (ptr[col] == ' ') |
730 | num_spaces++; |
731 | else |
732 | got_tab = TRUE; |
733 | } else { |
734 | if (got_tab || (eap->forceit && num_spaces > 1)) { |
735 | /* Retabulate this string of white-space */ |
736 | |
737 | /* len is virtual length of white string */ |
738 | len = num_spaces = vcol - start_vcol; |
739 | num_tabs = 0; |
740 | if (!curbuf->b_p_et) { |
741 | temp = new_ts - (start_vcol % new_ts); |
742 | if (num_spaces >= temp) { |
743 | num_spaces -= temp; |
744 | num_tabs++; |
745 | } |
746 | num_tabs += num_spaces / new_ts; |
747 | num_spaces -= (num_spaces / new_ts) * new_ts; |
748 | } |
749 | if (curbuf->b_p_et || got_tab |
750 | || (num_spaces + num_tabs < len)) { |
751 | if (did_undo == false) { |
752 | did_undo = true; |
753 | if (u_save((linenr_T)(lnum - 1), |
754 | (linenr_T)(lnum + 1)) == FAIL) { |
755 | new_line = NULL; // flag out-of-memory |
756 | break; |
757 | } |
758 | } |
759 | |
760 | /* len is actual number of white characters used */ |
761 | len = num_spaces + num_tabs; |
762 | old_len = (long)STRLEN(ptr); |
763 | new_line = xmalloc(old_len - col + start_col + len + 1); |
764 | |
765 | if (start_col > 0) |
766 | memmove(new_line, ptr, (size_t)start_col); |
767 | memmove(new_line + start_col + len, |
768 | ptr + col, (size_t)(old_len - col + 1)); |
769 | ptr = new_line + start_col; |
770 | for (col = 0; col < len; col++) { |
771 | ptr[col] = (col < num_tabs) ? '\t' : ' '; |
772 | } |
773 | ml_replace(lnum, new_line, false); |
774 | if (first_line == 0) { |
775 | first_line = lnum; |
776 | } |
777 | last_line = lnum; |
778 | ptr = new_line; |
779 | col = start_col + len; |
780 | } |
781 | } |
782 | got_tab = FALSE; |
783 | num_spaces = 0; |
784 | } |
785 | if (ptr[col] == NUL) |
786 | break; |
787 | vcol += chartabsize(ptr + col, (colnr_T)vcol); |
788 | if (has_mbyte) |
789 | col += (*mb_ptr2len)(ptr + col); |
790 | else |
791 | ++col; |
792 | } |
793 | if (new_line == NULL) /* out of memory */ |
794 | break; |
795 | line_breakcheck(); |
796 | } |
797 | if (got_int) |
798 | EMSG(_(e_interr)); |
799 | |
800 | if (curbuf->b_p_ts != new_ts) |
801 | redraw_curbuf_later(NOT_VALID); |
802 | if (first_line != 0) { |
803 | changed_lines(first_line, 0, last_line + 1, 0L, true); |
804 | } |
805 | |
806 | curwin->w_p_list = save_list; /* restore 'list' */ |
807 | |
808 | curbuf->b_p_ts = new_ts; |
809 | coladvance(curwin->w_curswant); |
810 | |
811 | u_clearline(); |
812 | } |
813 | |
814 | /* |
815 | * :move command - move lines line1-line2 to line dest |
816 | * |
817 | * return FAIL for failure, OK otherwise |
818 | */ |
819 | int do_move(linenr_T line1, linenr_T line2, linenr_T dest) |
820 | { |
821 | char_u *str; |
822 | linenr_T l; |
823 | linenr_T ; // Num lines added before line1 |
824 | linenr_T num_lines; // Num lines moved |
825 | linenr_T last_line; // Last line in file after adding new text |
826 | |
827 | if (dest >= line1 && dest < line2) { |
828 | EMSG(_("E134: Cannot move a range of lines into itself" )); |
829 | return FAIL; |
830 | } |
831 | |
832 | // Do nothing if we are not actually moving any lines. This will prevent |
833 | // the 'modified' flag from being set without cause. |
834 | if (dest == line1 - 1 || dest == line2) { |
835 | // Move the cursor as if lines were moved (see below) to be backwards |
836 | // compatible. |
837 | if (dest >= line1) { |
838 | curwin->w_cursor.lnum = dest; |
839 | } else { |
840 | curwin->w_cursor.lnum = dest + (line2 - line1) + 1; |
841 | } |
842 | return OK; |
843 | } |
844 | |
845 | num_lines = line2 - line1 + 1; |
846 | |
847 | /* |
848 | * First we copy the old text to its new location -- webb |
849 | * Also copy the flag that ":global" command uses. |
850 | */ |
851 | if (u_save(dest, dest + 1) == FAIL) |
852 | return FAIL; |
853 | for (extra = 0, l = line1; l <= line2; l++) { |
854 | str = vim_strsave(ml_get(l + extra)); |
855 | ml_append(dest + l - line1, str, (colnr_T)0, false); |
856 | xfree(str); |
857 | if (dest < line1) |
858 | extra++; |
859 | } |
860 | |
861 | /* |
862 | * Now we must be careful adjusting our marks so that we don't overlap our |
863 | * mark_adjust() calls. |
864 | * |
865 | * We adjust the marks within the old text so that they refer to the |
866 | * last lines of the file (temporarily), because we know no other marks |
867 | * will be set there since these line numbers did not exist until we added |
868 | * our new lines. |
869 | * |
870 | * Then we adjust the marks on lines between the old and new text positions |
871 | * (either forwards or backwards). |
872 | * |
873 | * And Finally we adjust the marks we put at the end of the file back to |
874 | * their final destination at the new text position -- webb |
875 | */ |
876 | last_line = curbuf->b_ml.ml_line_count; |
877 | mark_adjust_nofold(line1, line2, last_line - line2, 0L, true); |
878 | changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false); |
879 | if (dest >= line2) { |
880 | mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, false); |
881 | FOR_ALL_TAB_WINDOWS(tab, win) { |
882 | if (win->w_buffer == curbuf) { |
883 | foldMoveRange(&win->w_folds, line1, line2, dest); |
884 | } |
885 | } |
886 | curbuf->b_op_start.lnum = dest - num_lines + 1; |
887 | curbuf->b_op_end.lnum = dest; |
888 | } else { |
889 | mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, false); |
890 | FOR_ALL_TAB_WINDOWS(tab, win) { |
891 | if (win->w_buffer == curbuf) { |
892 | foldMoveRange(&win->w_folds, dest + 1, line1 - 1, line2); |
893 | } |
894 | } |
895 | curbuf->b_op_start.lnum = dest + 1; |
896 | curbuf->b_op_end.lnum = dest + num_lines; |
897 | } |
898 | curbuf->b_op_start.col = curbuf->b_op_end.col = 0; |
899 | mark_adjust_nofold(last_line - num_lines + 1, last_line, |
900 | -(last_line - dest - extra), 0L, true); |
901 | changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false); |
902 | |
903 | // send update regarding the new lines that were added |
904 | buf_updates_send_changes(curbuf, dest + 1, num_lines, 0, true); |
905 | |
906 | /* |
907 | * Now we delete the original text -- webb |
908 | */ |
909 | if (u_save(line1 + extra - 1, line2 + extra + 1) == FAIL) |
910 | return FAIL; |
911 | |
912 | for (l = line1; l <= line2; l++) { |
913 | ml_delete(line1 + extra, true); |
914 | } |
915 | if (!global_busy && num_lines > p_report) { |
916 | if (num_lines == 1) |
917 | MSG(_("1 line moved" )); |
918 | else |
919 | smsg(_("%" PRId64 " lines moved" ), (int64_t)num_lines); |
920 | } |
921 | |
922 | /* |
923 | * Leave the cursor on the last of the moved lines. |
924 | */ |
925 | if (dest >= line1) |
926 | curwin->w_cursor.lnum = dest; |
927 | else |
928 | curwin->w_cursor.lnum = dest + (line2 - line1) + 1; |
929 | |
930 | if (line1 < dest) { |
931 | dest += num_lines + 1; |
932 | last_line = curbuf->b_ml.ml_line_count; |
933 | if (dest > last_line + 1) |
934 | dest = last_line + 1; |
935 | changed_lines(line1, 0, dest, 0L, false); |
936 | } else { |
937 | changed_lines(dest + 1, 0, line1 + num_lines, 0L, false); |
938 | } |
939 | |
940 | // send nvim_buf_lines_event regarding lines that were deleted |
941 | buf_updates_send_changes(curbuf, line1 + extra, 0, num_lines, true); |
942 | |
943 | return OK; |
944 | } |
945 | |
946 | /* |
947 | * ":copy" |
948 | */ |
949 | void ex_copy(linenr_T line1, linenr_T line2, linenr_T n) |
950 | { |
951 | linenr_T count; |
952 | char_u *p; |
953 | |
954 | count = line2 - line1 + 1; |
955 | curbuf->b_op_start.lnum = n + 1; |
956 | curbuf->b_op_end.lnum = n + count; |
957 | curbuf->b_op_start.col = curbuf->b_op_end.col = 0; |
958 | |
959 | /* |
960 | * there are three situations: |
961 | * 1. destination is above line1 |
962 | * 2. destination is between line1 and line2 |
963 | * 3. destination is below line2 |
964 | * |
965 | * n = destination (when starting) |
966 | * curwin->w_cursor.lnum = destination (while copying) |
967 | * line1 = start of source (while copying) |
968 | * line2 = end of source (while copying) |
969 | */ |
970 | if (u_save(n, n + 1) == FAIL) |
971 | return; |
972 | |
973 | curwin->w_cursor.lnum = n; |
974 | while (line1 <= line2) { |
975 | /* need to use vim_strsave() because the line will be unlocked within |
976 | * ml_append() */ |
977 | p = vim_strsave(ml_get(line1)); |
978 | ml_append(curwin->w_cursor.lnum, p, (colnr_T)0, false); |
979 | xfree(p); |
980 | |
981 | /* situation 2: skip already copied lines */ |
982 | if (line1 == n) |
983 | line1 = curwin->w_cursor.lnum; |
984 | ++line1; |
985 | if (curwin->w_cursor.lnum < line1) |
986 | ++line1; |
987 | if (curwin->w_cursor.lnum < line2) |
988 | ++line2; |
989 | ++curwin->w_cursor.lnum; |
990 | } |
991 | |
992 | appended_lines_mark(n, count); |
993 | |
994 | msgmore((long)count); |
995 | } |
996 | |
997 | static char_u *prevcmd = NULL; /* the previous command */ |
998 | |
999 | #if defined(EXITFREE) |
1000 | void free_prev_shellcmd(void) |
1001 | { |
1002 | xfree(prevcmd); |
1003 | } |
1004 | |
1005 | #endif |
1006 | |
1007 | /* |
1008 | * Handle the ":!cmd" command. Also for ":r !cmd" and ":w !cmd" |
1009 | * Bangs in the argument are replaced with the previously entered command. |
1010 | * Remember the argument. |
1011 | */ |
1012 | void do_bang(int addr_count, exarg_T *eap, int forceit, int do_in, int do_out) |
1013 | { |
1014 | char_u *arg = eap->arg; /* command */ |
1015 | linenr_T line1 = eap->line1; /* start of range */ |
1016 | linenr_T line2 = eap->line2; /* end of range */ |
1017 | char_u *newcmd = NULL; /* the new command */ |
1018 | int free_newcmd = FALSE; /* need to free() newcmd */ |
1019 | int ins_prevcmd; |
1020 | char_u *t; |
1021 | char_u *p; |
1022 | char_u *trailarg; |
1023 | int len; |
1024 | int scroll_save = msg_scroll; |
1025 | |
1026 | /* |
1027 | * Disallow shell commands in restricted mode (-Z) |
1028 | * Disallow shell commands from .exrc and .vimrc in current directory for |
1029 | * security reasons. |
1030 | */ |
1031 | if (check_restricted() || check_secure()) |
1032 | return; |
1033 | |
1034 | if (addr_count == 0) { /* :! */ |
1035 | msg_scroll = FALSE; /* don't scroll here */ |
1036 | autowrite_all(); |
1037 | msg_scroll = scroll_save; |
1038 | } |
1039 | |
1040 | /* |
1041 | * Try to find an embedded bang, like in :!<cmd> ! [args] |
1042 | * (:!! is indicated by the 'forceit' variable) |
1043 | */ |
1044 | ins_prevcmd = forceit; |
1045 | trailarg = arg; |
1046 | do { |
1047 | len = (int)STRLEN(trailarg) + 1; |
1048 | if (newcmd != NULL) |
1049 | len += (int)STRLEN(newcmd); |
1050 | if (ins_prevcmd) { |
1051 | if (prevcmd == NULL) { |
1052 | EMSG(_(e_noprev)); |
1053 | xfree(newcmd); |
1054 | return; |
1055 | } |
1056 | len += (int)STRLEN(prevcmd); |
1057 | } |
1058 | t = xmalloc(len); |
1059 | *t = NUL; |
1060 | if (newcmd != NULL) |
1061 | STRCAT(t, newcmd); |
1062 | if (ins_prevcmd) |
1063 | STRCAT(t, prevcmd); |
1064 | p = t + STRLEN(t); |
1065 | STRCAT(t, trailarg); |
1066 | xfree(newcmd); |
1067 | newcmd = t; |
1068 | |
1069 | /* |
1070 | * Scan the rest of the argument for '!', which is replaced by the |
1071 | * previous command. "\!" is replaced by "!" (this is vi compatible). |
1072 | */ |
1073 | trailarg = NULL; |
1074 | while (*p) { |
1075 | if (*p == '!') { |
1076 | if (p > newcmd && p[-1] == '\\') |
1077 | STRMOVE(p - 1, p); |
1078 | else { |
1079 | trailarg = p; |
1080 | *trailarg++ = NUL; |
1081 | ins_prevcmd = TRUE; |
1082 | break; |
1083 | } |
1084 | } |
1085 | ++p; |
1086 | } |
1087 | } while (trailarg != NULL); |
1088 | |
1089 | xfree(prevcmd); |
1090 | prevcmd = newcmd; |
1091 | |
1092 | if (bangredo) { /* put cmd in redo buffer for ! command */ |
1093 | /* If % or # appears in the command, it must have been escaped. |
1094 | * Reescape them, so that redoing them does not substitute them by the |
1095 | * buffername. */ |
1096 | char_u *cmd = vim_strsave_escaped(prevcmd, (char_u *)"%#" ); |
1097 | |
1098 | AppendToRedobuffLit(cmd, -1); |
1099 | xfree(cmd); |
1100 | AppendToRedobuff("\n" ); |
1101 | bangredo = false; |
1102 | } |
1103 | /* |
1104 | * Add quotes around the command, for shells that need them. |
1105 | */ |
1106 | if (*p_shq != NUL) { |
1107 | newcmd = xmalloc(STRLEN(prevcmd) + 2 * STRLEN(p_shq) + 1); |
1108 | STRCPY(newcmd, p_shq); |
1109 | STRCAT(newcmd, prevcmd); |
1110 | STRCAT(newcmd, p_shq); |
1111 | free_newcmd = TRUE; |
1112 | } |
1113 | if (addr_count == 0) { /* :! */ |
1114 | /* echo the command */ |
1115 | msg_start(); |
1116 | msg_putchar(':'); |
1117 | msg_putchar('!'); |
1118 | msg_outtrans(newcmd); |
1119 | msg_clr_eos(); |
1120 | ui_cursor_goto(msg_row, msg_col); |
1121 | |
1122 | do_shell(newcmd, 0); |
1123 | } else { /* :range! */ |
1124 | /* Careful: This may recursively call do_bang() again! (because of |
1125 | * autocommands) */ |
1126 | do_filter(line1, line2, eap, newcmd, do_in, do_out); |
1127 | apply_autocmds(EVENT_SHELLFILTERPOST, NULL, NULL, FALSE, curbuf); |
1128 | } |
1129 | if (free_newcmd) |
1130 | xfree(newcmd); |
1131 | } |
1132 | |
1133 | // do_filter: filter lines through a command given by the user |
1134 | // |
1135 | // We mostly use temp files and the call_shell() routine here. This would |
1136 | // normally be done using pipes on a Unix system, but this is more portable |
1137 | // to non-Unix systems. The call_shell() routine needs to be able |
1138 | // to deal with redirection somehow, and should handle things like looking |
1139 | // at the PATH env. variable, and adding reasonable extensions to the |
1140 | // command name given by the user. All reasonable versions of call_shell() |
1141 | // do this. |
1142 | // Alternatively, if on Unix and redirecting input or output, but not both, |
1143 | // and the 'shelltemp' option isn't set, use pipes. |
1144 | // We use input redirection if do_in is TRUE. |
1145 | // We use output redirection if do_out is TRUE. |
1146 | static void do_filter( |
1147 | linenr_T line1, |
1148 | linenr_T line2, |
1149 | exarg_T *eap, /* for forced 'ff' and 'fenc' */ |
1150 | char_u *cmd, |
1151 | int do_in, |
1152 | int do_out) |
1153 | { |
1154 | char_u *itmp = NULL; |
1155 | char_u *otmp = NULL; |
1156 | linenr_T linecount; |
1157 | linenr_T read_linecount; |
1158 | pos_T cursor_save; |
1159 | char_u *cmd_buf; |
1160 | buf_T *old_curbuf = curbuf; |
1161 | int shell_flags = 0; |
1162 | |
1163 | if (*cmd == NUL) /* no filter command */ |
1164 | return; |
1165 | |
1166 | |
1167 | cursor_save = curwin->w_cursor; |
1168 | linecount = line2 - line1 + 1; |
1169 | curwin->w_cursor.lnum = line1; |
1170 | curwin->w_cursor.col = 0; |
1171 | changed_line_abv_curs(); |
1172 | invalidate_botline(); |
1173 | |
1174 | /* |
1175 | * When using temp files: |
1176 | * 1. * Form temp file names |
1177 | * 2. * Write the lines to a temp file |
1178 | * 3. Run the filter command on the temp file |
1179 | * 4. * Read the output of the command into the buffer |
1180 | * 5. * Delete the original lines to be filtered |
1181 | * 6. * Remove the temp files |
1182 | * |
1183 | * When writing the input with a pipe or when catching the output with a |
1184 | * pipe only need to do 3. |
1185 | */ |
1186 | |
1187 | if (do_out) |
1188 | shell_flags |= kShellOptDoOut; |
1189 | |
1190 | if (!do_in && do_out && !p_stmp) { |
1191 | // Use a pipe to fetch stdout of the command, do not use a temp file. |
1192 | shell_flags |= kShellOptRead; |
1193 | curwin->w_cursor.lnum = line2; |
1194 | } else if (do_in && !do_out && !p_stmp) { |
1195 | // Use a pipe to write stdin of the command, do not use a temp file. |
1196 | shell_flags |= kShellOptWrite; |
1197 | curbuf->b_op_start.lnum = line1; |
1198 | curbuf->b_op_end.lnum = line2; |
1199 | } else if (do_in && do_out && !p_stmp) { |
1200 | // Use a pipe to write stdin and fetch stdout of the command, do not |
1201 | // use a temp file. |
1202 | shell_flags |= kShellOptRead | kShellOptWrite; |
1203 | curbuf->b_op_start.lnum = line1; |
1204 | curbuf->b_op_end.lnum = line2; |
1205 | curwin->w_cursor.lnum = line2; |
1206 | } else if ((do_in && (itmp = vim_tempname()) == NULL) |
1207 | || (do_out && (otmp = vim_tempname()) == NULL)) { |
1208 | EMSG(_(e_notmp)); |
1209 | goto filterend; |
1210 | } |
1211 | |
1212 | /* |
1213 | * The writing and reading of temp files will not be shown. |
1214 | * Vi also doesn't do this and the messages are not very informative. |
1215 | */ |
1216 | ++no_wait_return; /* don't call wait_return() while busy */ |
1217 | if (itmp != NULL && buf_write(curbuf, itmp, NULL, line1, line2, eap, |
1218 | false, false, false, true) == FAIL) { |
1219 | msg_putchar('\n'); // Keep message from buf_write(). |
1220 | no_wait_return--; |
1221 | if (!aborting()) { |
1222 | EMSG2(_("E482: Can't create file %s" ), itmp); // Will call wait_return. |
1223 | } |
1224 | goto filterend; |
1225 | } |
1226 | if (curbuf != old_curbuf) |
1227 | goto filterend; |
1228 | |
1229 | if (!do_out) |
1230 | msg_putchar('\n'); |
1231 | |
1232 | /* Create the shell command in allocated memory. */ |
1233 | cmd_buf = make_filter_cmd(cmd, itmp, otmp); |
1234 | ui_cursor_goto(Rows - 1, 0); |
1235 | |
1236 | if (do_out) { |
1237 | if (u_save((linenr_T)(line2), (linenr_T)(line2 + 1)) == FAIL) { |
1238 | xfree(cmd_buf); |
1239 | goto error; |
1240 | } |
1241 | redraw_curbuf_later(VALID); |
1242 | } |
1243 | read_linecount = curbuf->b_ml.ml_line_count; |
1244 | |
1245 | // Pass on the kShellOptDoOut flag when the output is being redirected. |
1246 | call_shell(cmd_buf, kShellOptFilter | shell_flags, NULL); |
1247 | xfree(cmd_buf); |
1248 | |
1249 | did_check_timestamps = FALSE; |
1250 | need_check_timestamps = TRUE; |
1251 | |
1252 | /* When interrupting the shell command, it may still have produced some |
1253 | * useful output. Reset got_int here, so that readfile() won't cancel |
1254 | * reading. */ |
1255 | os_breakcheck(); |
1256 | got_int = FALSE; |
1257 | |
1258 | if (do_out) { |
1259 | if (otmp != NULL) { |
1260 | if (readfile(otmp, NULL, line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, |
1261 | READ_FILTER) != OK) { |
1262 | if (!aborting()) { |
1263 | msg_putchar('\n'); |
1264 | EMSG2(_(e_notread), otmp); |
1265 | } |
1266 | goto error; |
1267 | } |
1268 | if (curbuf != old_curbuf) |
1269 | goto filterend; |
1270 | } |
1271 | |
1272 | read_linecount = curbuf->b_ml.ml_line_count - read_linecount; |
1273 | |
1274 | if (shell_flags & kShellOptRead) { |
1275 | curbuf->b_op_start.lnum = line2 + 1; |
1276 | curbuf->b_op_end.lnum = curwin->w_cursor.lnum; |
1277 | appended_lines_mark(line2, read_linecount); |
1278 | } |
1279 | |
1280 | if (do_in) { |
1281 | if (cmdmod.keepmarks || vim_strchr(p_cpo, CPO_REMMARK) == NULL) { |
1282 | if (read_linecount >= linecount) { |
1283 | // move all marks from old lines to new lines |
1284 | mark_adjust(line1, line2, linecount, 0L, false); |
1285 | } else { |
1286 | // move marks from old lines to new lines, delete marks |
1287 | // that are in deleted lines |
1288 | mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L, false); |
1289 | mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, false); |
1290 | } |
1291 | } |
1292 | |
1293 | /* |
1294 | * Put cursor on first filtered line for ":range!cmd". |
1295 | * Adjust '[ and '] (set by buf_write()). |
1296 | */ |
1297 | curwin->w_cursor.lnum = line1; |
1298 | del_lines(linecount, TRUE); |
1299 | curbuf->b_op_start.lnum -= linecount; /* adjust '[ */ |
1300 | curbuf->b_op_end.lnum -= linecount; /* adjust '] */ |
1301 | write_lnum_adjust(-linecount); /* adjust last line |
1302 | for next write */ |
1303 | foldUpdate(curwin, curbuf->b_op_start.lnum, curbuf->b_op_end.lnum); |
1304 | } else { |
1305 | /* |
1306 | * Put cursor on last new line for ":r !cmd". |
1307 | */ |
1308 | linecount = curbuf->b_op_end.lnum - curbuf->b_op_start.lnum + 1; |
1309 | curwin->w_cursor.lnum = curbuf->b_op_end.lnum; |
1310 | } |
1311 | |
1312 | beginline(BL_WHITE | BL_FIX); /* cursor on first non-blank */ |
1313 | --no_wait_return; |
1314 | |
1315 | if (linecount > p_report) { |
1316 | if (do_in) { |
1317 | vim_snprintf((char *)msg_buf, sizeof(msg_buf), |
1318 | _("%" PRId64 " lines filtered" ), (int64_t)linecount); |
1319 | if (msg(msg_buf) && !msg_scroll) |
1320 | /* save message to display it after redraw */ |
1321 | set_keep_msg(msg_buf, 0); |
1322 | } else |
1323 | msgmore((long)linecount); |
1324 | } |
1325 | } else { |
1326 | error: |
1327 | /* put cursor back in same position for ":w !cmd" */ |
1328 | curwin->w_cursor = cursor_save; |
1329 | --no_wait_return; |
1330 | wait_return(FALSE); |
1331 | } |
1332 | |
1333 | filterend: |
1334 | |
1335 | if (curbuf != old_curbuf) { |
1336 | --no_wait_return; |
1337 | EMSG(_("E135: *Filter* Autocommands must not change current buffer" )); |
1338 | } |
1339 | if (itmp != NULL) |
1340 | os_remove((char *)itmp); |
1341 | if (otmp != NULL) |
1342 | os_remove((char *)otmp); |
1343 | xfree(itmp); |
1344 | xfree(otmp); |
1345 | } |
1346 | |
1347 | // Call a shell to execute a command. |
1348 | // When "cmd" is NULL start an interactive shell. |
1349 | void |
1350 | do_shell( |
1351 | char_u *cmd, |
1352 | int flags // may be SHELL_DOOUT when output is redirected |
1353 | ) |
1354 | { |
1355 | // Disallow shell commands in restricted mode (-Z) |
1356 | // Disallow shell commands from .exrc and .vimrc in current directory for |
1357 | // security reasons. |
1358 | if (check_restricted() || check_secure()) { |
1359 | msg_end(); |
1360 | return; |
1361 | } |
1362 | |
1363 | |
1364 | /* |
1365 | * For autocommands we want to get the output on the current screen, to |
1366 | * avoid having to type return below. |
1367 | */ |
1368 | msg_putchar('\r'); /* put cursor at start of line */ |
1369 | msg_putchar('\n'); /* may shift screen one line up */ |
1370 | |
1371 | /* warning message before calling the shell */ |
1372 | if (p_warn |
1373 | && !autocmd_busy |
1374 | && msg_silent == 0) |
1375 | FOR_ALL_BUFFERS(buf) { |
1376 | if (bufIsChanged(buf)) { |
1377 | MSG_PUTS(_("[No write since last change]\n" )); |
1378 | break; |
1379 | } |
1380 | } |
1381 | |
1382 | // This ui_cursor_goto is required for when the '\n' resulted in a "delete line |
1383 | // 1" command to the terminal. |
1384 | ui_cursor_goto(msg_row, msg_col); |
1385 | (void)call_shell(cmd, flags, NULL); |
1386 | msg_didout = true; |
1387 | did_check_timestamps = false; |
1388 | need_check_timestamps = true; |
1389 | |
1390 | // put the message cursor at the end of the screen, avoids wait_return() |
1391 | // to overwrite the text that the external command showed |
1392 | msg_row = Rows - 1; |
1393 | msg_col = 0; |
1394 | |
1395 | apply_autocmds(EVENT_SHELLCMDPOST, NULL, NULL, FALSE, curbuf); |
1396 | } |
1397 | |
1398 | #if !defined(UNIX) |
1399 | static char *find_pipe(const char *cmd) |
1400 | { |
1401 | bool inquote = false; |
1402 | |
1403 | for (const char *p = cmd; *p != NUL; p++) { |
1404 | if (!inquote && *p == '|') { |
1405 | return p; |
1406 | } |
1407 | if (*p == '"') { |
1408 | inquote = !inquote; |
1409 | } else if (rem_backslash((const char_u *)p)) { |
1410 | p++; |
1411 | } |
1412 | } |
1413 | return NULL; |
1414 | } |
1415 | #endif |
1416 | |
1417 | /// Create a shell command from a command string, input redirection file and |
1418 | /// output redirection file. |
1419 | /// |
1420 | /// @param cmd Command to execute. |
1421 | /// @param itmp NULL or the input file. |
1422 | /// @param otmp NULL or the output file. |
1423 | /// @returns an allocated string with the shell command. |
1424 | char_u *make_filter_cmd(char_u *cmd, char_u *itmp, char_u *otmp) |
1425 | { |
1426 | bool is_fish_shell = |
1427 | #if defined(UNIX) |
1428 | STRNCMP(invocation_path_tail(p_sh, NULL), "fish" , 4) == 0; |
1429 | #else |
1430 | false; |
1431 | #endif |
1432 | |
1433 | size_t len = STRLEN(cmd) + 1; // At least enough space for cmd + NULL. |
1434 | |
1435 | len += is_fish_shell ? sizeof("begin; " "; end" ) - 1 |
1436 | : sizeof("(" ")" ) - 1; |
1437 | |
1438 | if (itmp != NULL) { |
1439 | len += STRLEN(itmp) + sizeof(" { " " < " " } " ) - 1; |
1440 | } |
1441 | if (otmp != NULL) { |
1442 | len += STRLEN(otmp) + STRLEN(p_srr) + 2; // two extra spaces (" "), |
1443 | } |
1444 | char *const buf = xmalloc(len); |
1445 | |
1446 | #if defined(UNIX) |
1447 | // Put delimiters around the command (for concatenated commands) when |
1448 | // redirecting input and/or output. |
1449 | if (itmp != NULL || otmp != NULL) { |
1450 | char *fmt = is_fish_shell ? "begin; %s; end" |
1451 | : "(%s)" ; |
1452 | vim_snprintf(buf, len, fmt, (char *)cmd); |
1453 | } else { |
1454 | xstrlcpy(buf, (char *)cmd, len); |
1455 | } |
1456 | |
1457 | if (itmp != NULL) { |
1458 | xstrlcat(buf, " < " , len - 1); |
1459 | xstrlcat(buf, (const char *)itmp, len - 1); |
1460 | } |
1461 | #else |
1462 | // For shells that don't understand braces around commands, at least allow |
1463 | // the use of commands in a pipe. |
1464 | xstrlcpy(buf, (char *)cmd, len); |
1465 | if (itmp != NULL) { |
1466 | // If there is a pipe, we have to put the '<' in front of it. |
1467 | // Don't do this when 'shellquote' is not empty, otherwise the |
1468 | // redirection would be inside the quotes. |
1469 | if (*p_shq == NUL) { |
1470 | char *const p = find_pipe(buf); |
1471 | if (p != NULL) { |
1472 | *p = NUL; |
1473 | } |
1474 | } |
1475 | xstrlcat(buf, " < " , len); |
1476 | xstrlcat(buf, (const char *)itmp, len); |
1477 | if (*p_shq == NUL) { |
1478 | const char *const p = find_pipe((const char *)cmd); |
1479 | if (p != NULL) { |
1480 | xstrlcat(buf, " " , len - 1); // Insert a space before the '|' for DOS |
1481 | xstrlcat(buf, p, len - 1); |
1482 | } |
1483 | } |
1484 | } |
1485 | #endif |
1486 | if (otmp != NULL) { |
1487 | append_redir(buf, len, (char *) p_srr, (char *) otmp); |
1488 | } |
1489 | return (char_u *) buf; |
1490 | } |
1491 | |
1492 | /// Append output redirection for the given file to the end of the buffer |
1493 | /// |
1494 | /// @param[out] buf Buffer to append to. |
1495 | /// @param[in] buflen Buffer length. |
1496 | /// @param[in] opt Separator or format string to append: will append |
1497 | /// `printf(' ' . opt, fname)` if `%s` is found in `opt` or |
1498 | /// a space, opt, a space and then fname if `%s` is not found |
1499 | /// there. |
1500 | /// @param[in] fname File name to append. |
1501 | void append_redir(char *const buf, const size_t buflen, |
1502 | const char *const opt, const char *const fname) |
1503 | { |
1504 | char *const end = buf + strlen(buf); |
1505 | // find "%s" |
1506 | const char *p = opt; |
1507 | for (; (p = strchr(p, '%')) != NULL; p++) { |
1508 | if (p[1] == 's') { // found %s |
1509 | break; |
1510 | } else if (p[1] == '%') { // skip %% |
1511 | p++; |
1512 | } |
1513 | } |
1514 | if (p != NULL) { |
1515 | *end = ' '; // not really needed? Not with sh, ksh or bash |
1516 | vim_snprintf(end + 1, (size_t) (buflen - (end + 1 - buf)), opt, fname); |
1517 | } else { |
1518 | vim_snprintf(end, (size_t) (buflen - (end - buf)), " %s %s" , opt, fname); |
1519 | } |
1520 | } |
1521 | |
1522 | void print_line_no_prefix(linenr_T lnum, int use_number, int list) |
1523 | { |
1524 | char numbuf[30]; |
1525 | |
1526 | if (curwin->w_p_nu || use_number) { |
1527 | vim_snprintf(numbuf, sizeof(numbuf), "%*" PRIdLINENR " " , |
1528 | number_width(curwin), lnum); |
1529 | msg_puts_attr(numbuf, HL_ATTR(HLF_N)); // Highlight line nrs. |
1530 | } |
1531 | msg_prt_line(ml_get(lnum), list); |
1532 | } |
1533 | |
1534 | /* |
1535 | * Print a text line. Also in silent mode ("ex -s"). |
1536 | */ |
1537 | void print_line(linenr_T lnum, int use_number, int list) |
1538 | { |
1539 | int save_silent = silent_mode; |
1540 | |
1541 | // apply :filter /pat/ |
1542 | if (message_filtered(ml_get(lnum))) { |
1543 | return; |
1544 | } |
1545 | |
1546 | msg_start(); |
1547 | silent_mode = FALSE; |
1548 | info_message = TRUE; /* use mch_msg(), not mch_errmsg() */ |
1549 | print_line_no_prefix(lnum, use_number, list); |
1550 | if (save_silent) { |
1551 | msg_putchar('\n'); |
1552 | ui_flush(); |
1553 | silent_mode = save_silent; |
1554 | } |
1555 | info_message = FALSE; |
1556 | } |
1557 | |
1558 | int rename_buffer(char_u *new_fname) |
1559 | { |
1560 | char_u *fname, *sfname, *xfname; |
1561 | buf_T *buf; |
1562 | |
1563 | buf = curbuf; |
1564 | apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, FALSE, curbuf); |
1565 | /* buffer changed, don't change name now */ |
1566 | if (buf != curbuf) |
1567 | return FAIL; |
1568 | if (aborting()) /* autocmds may abort script processing */ |
1569 | return FAIL; |
1570 | /* |
1571 | * The name of the current buffer will be changed. |
1572 | * A new (unlisted) buffer entry needs to be made to hold the old file |
1573 | * name, which will become the alternate file name. |
1574 | * But don't set the alternate file name if the buffer didn't have a |
1575 | * name. |
1576 | */ |
1577 | fname = curbuf->b_ffname; |
1578 | sfname = curbuf->b_sfname; |
1579 | xfname = curbuf->b_fname; |
1580 | curbuf->b_ffname = NULL; |
1581 | curbuf->b_sfname = NULL; |
1582 | if (setfname(curbuf, new_fname, NULL, TRUE) == FAIL) { |
1583 | curbuf->b_ffname = fname; |
1584 | curbuf->b_sfname = sfname; |
1585 | return FAIL; |
1586 | } |
1587 | curbuf->b_flags |= BF_NOTEDITED; |
1588 | if (xfname != NULL && *xfname != NUL) { |
1589 | buf = buflist_new(fname, xfname, curwin->w_cursor.lnum, 0); |
1590 | if (buf != NULL && !cmdmod.keepalt) { |
1591 | curwin->w_alt_fnum = buf->b_fnum; |
1592 | } |
1593 | } |
1594 | xfree(fname); |
1595 | xfree(sfname); |
1596 | apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, FALSE, curbuf); |
1597 | /* Change directories when the 'acd' option is set. */ |
1598 | do_autochdir(); |
1599 | return OK; |
1600 | } |
1601 | |
1602 | /* |
1603 | * ":file[!] [fname]". |
1604 | */ |
1605 | void ex_file(exarg_T *eap) |
1606 | { |
1607 | /* ":0file" removes the file name. Check for illegal uses ":3file", |
1608 | * "0file name", etc. */ |
1609 | if (eap->addr_count > 0 |
1610 | && (*eap->arg != NUL |
1611 | || eap->line2 > 0 |
1612 | || eap->addr_count > 1)) { |
1613 | EMSG(_(e_invarg)); |
1614 | return; |
1615 | } |
1616 | |
1617 | if (*eap->arg != NUL || eap->addr_count == 1) { |
1618 | if (rename_buffer(eap->arg) == FAIL) { |
1619 | return; |
1620 | } |
1621 | redraw_tabline = true; |
1622 | } |
1623 | |
1624 | // print file name if no argument or 'F' is not in 'shortmess' |
1625 | if (*eap->arg == NUL || !shortmess(SHM_FILEINFO)) { |
1626 | fileinfo(false, false, eap->forceit); |
1627 | } |
1628 | } |
1629 | |
1630 | /* |
1631 | * ":update". |
1632 | */ |
1633 | void ex_update(exarg_T *eap) |
1634 | { |
1635 | if (curbufIsChanged()) |
1636 | (void)do_write(eap); |
1637 | } |
1638 | |
1639 | /* |
1640 | * ":write" and ":saveas". |
1641 | */ |
1642 | void ex_write(exarg_T *eap) |
1643 | { |
1644 | if (eap->usefilter) /* input lines to shell command */ |
1645 | do_bang(1, eap, FALSE, TRUE, FALSE); |
1646 | else |
1647 | (void)do_write(eap); |
1648 | } |
1649 | |
1650 | /* |
1651 | * write current buffer to file 'eap->arg' |
1652 | * if 'eap->append' is TRUE, append to the file |
1653 | * |
1654 | * if *eap->arg == NUL write to current file |
1655 | * |
1656 | * return FAIL for failure, OK otherwise |
1657 | */ |
1658 | int do_write(exarg_T *eap) |
1659 | { |
1660 | int other; |
1661 | char_u *fname = NULL; /* init to shut up gcc */ |
1662 | char_u *ffname; |
1663 | int retval = FAIL; |
1664 | char_u *free_fname = NULL; |
1665 | buf_T *alt_buf = NULL; |
1666 | int name_was_missing; |
1667 | |
1668 | if (not_writing()) /* check 'write' option */ |
1669 | return FAIL; |
1670 | |
1671 | ffname = eap->arg; |
1672 | if (*ffname == NUL) { |
1673 | if (eap->cmdidx == CMD_saveas) { |
1674 | EMSG(_(e_argreq)); |
1675 | goto theend; |
1676 | } |
1677 | other = FALSE; |
1678 | } else { |
1679 | fname = ffname; |
1680 | free_fname = (char_u *)fix_fname((char *)ffname); |
1681 | /* |
1682 | * When out-of-memory, keep unexpanded file name, because we MUST be |
1683 | * able to write the file in this situation. |
1684 | */ |
1685 | if (free_fname != NULL) |
1686 | ffname = free_fname; |
1687 | other = otherfile(ffname); |
1688 | } |
1689 | |
1690 | /* |
1691 | * If we have a new file, put its name in the list of alternate file names. |
1692 | */ |
1693 | if (other) { |
1694 | if (vim_strchr(p_cpo, CPO_ALTWRITE) != NULL |
1695 | || eap->cmdidx == CMD_saveas) |
1696 | alt_buf = setaltfname(ffname, fname, (linenr_T)1); |
1697 | else |
1698 | alt_buf = buflist_findname(ffname); |
1699 | if (alt_buf != NULL && alt_buf->b_ml.ml_mfp != NULL) { |
1700 | /* Overwriting a file that is loaded in another buffer is not a |
1701 | * good idea. */ |
1702 | EMSG(_(e_bufloaded)); |
1703 | goto theend; |
1704 | } |
1705 | } |
1706 | |
1707 | // Writing to the current file is not allowed in readonly mode |
1708 | // and a file name is required. |
1709 | // "nofile" and "nowrite" buffers cannot be written implicitly either. |
1710 | if (!other && (bt_dontwrite_msg(curbuf) |
1711 | || check_fname() == FAIL |
1712 | || check_readonly(&eap->forceit, curbuf))) { |
1713 | goto theend; |
1714 | } |
1715 | |
1716 | if (!other) { |
1717 | ffname = curbuf->b_ffname; |
1718 | fname = curbuf->b_fname; |
1719 | /* |
1720 | * Not writing the whole file is only allowed with '!'. |
1721 | */ |
1722 | if ( (eap->line1 != 1 |
1723 | || eap->line2 != curbuf->b_ml.ml_line_count) |
1724 | && !eap->forceit |
1725 | && !eap->append |
1726 | && !p_wa) { |
1727 | if (p_confirm || cmdmod.confirm) { |
1728 | if (vim_dialog_yesno(VIM_QUESTION, NULL, |
1729 | (char_u *)_("Write partial file?" ), 2) != VIM_YES) |
1730 | goto theend; |
1731 | eap->forceit = TRUE; |
1732 | } else { |
1733 | EMSG(_("E140: Use ! to write partial buffer" )); |
1734 | goto theend; |
1735 | } |
1736 | } |
1737 | } |
1738 | |
1739 | if (check_overwrite(eap, curbuf, fname, ffname, other) == OK) { |
1740 | if (eap->cmdidx == CMD_saveas && alt_buf != NULL) { |
1741 | buf_T *was_curbuf = curbuf; |
1742 | |
1743 | apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, FALSE, curbuf); |
1744 | apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, FALSE, alt_buf); |
1745 | if (curbuf != was_curbuf || aborting()) { |
1746 | /* buffer changed, don't change name now */ |
1747 | retval = FAIL; |
1748 | goto theend; |
1749 | } |
1750 | /* Exchange the file names for the current and the alternate |
1751 | * buffer. This makes it look like we are now editing the buffer |
1752 | * under the new name. Must be done before buf_write(), because |
1753 | * if there is no file name and 'cpo' contains 'F', it will set |
1754 | * the file name. */ |
1755 | fname = alt_buf->b_fname; |
1756 | alt_buf->b_fname = curbuf->b_fname; |
1757 | curbuf->b_fname = fname; |
1758 | fname = alt_buf->b_ffname; |
1759 | alt_buf->b_ffname = curbuf->b_ffname; |
1760 | curbuf->b_ffname = fname; |
1761 | fname = alt_buf->b_sfname; |
1762 | alt_buf->b_sfname = curbuf->b_sfname; |
1763 | curbuf->b_sfname = fname; |
1764 | buf_name_changed(curbuf); |
1765 | apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, FALSE, curbuf); |
1766 | apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, FALSE, alt_buf); |
1767 | if (!alt_buf->b_p_bl) { |
1768 | alt_buf->b_p_bl = TRUE; |
1769 | apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, alt_buf); |
1770 | } |
1771 | if (curbuf != was_curbuf || aborting()) { |
1772 | /* buffer changed, don't write the file */ |
1773 | retval = FAIL; |
1774 | goto theend; |
1775 | } |
1776 | |
1777 | // If 'filetype' was empty try detecting it now. |
1778 | if (*curbuf->b_p_ft == NUL) { |
1779 | if (au_has_group((char_u *)"filetypedetect" )) { |
1780 | (void)do_doautocmd((char_u *)"filetypedetect BufRead" , true, NULL); |
1781 | } |
1782 | do_modelines(0); |
1783 | } |
1784 | |
1785 | /* Autocommands may have changed buffer names, esp. when |
1786 | * 'autochdir' is set. */ |
1787 | fname = curbuf->b_sfname; |
1788 | } |
1789 | |
1790 | name_was_missing = curbuf->b_ffname == NULL; |
1791 | retval = buf_write(curbuf, ffname, fname, eap->line1, eap->line2, |
1792 | eap, eap->append, eap->forceit, TRUE, FALSE); |
1793 | |
1794 | /* After ":saveas fname" reset 'readonly'. */ |
1795 | if (eap->cmdidx == CMD_saveas) { |
1796 | if (retval == OK) { |
1797 | curbuf->b_p_ro = FALSE; |
1798 | redraw_tabline = TRUE; |
1799 | } |
1800 | } |
1801 | |
1802 | // Change directories when the 'acd' option is set and the file name |
1803 | // got changed or set. |
1804 | if (eap->cmdidx == CMD_saveas || name_was_missing) { |
1805 | do_autochdir(); |
1806 | } |
1807 | } |
1808 | |
1809 | theend: |
1810 | xfree(free_fname); |
1811 | return retval; |
1812 | } |
1813 | |
1814 | /* |
1815 | * Check if it is allowed to overwrite a file. If b_flags has BF_NOTEDITED, |
1816 | * BF_NEW or BF_READERR, check for overwriting current file. |
1817 | * May set eap->forceit if a dialog says it's OK to overwrite. |
1818 | * Return OK if it's OK, FAIL if it is not. |
1819 | */ |
1820 | int |
1821 | check_overwrite( |
1822 | exarg_T *eap, |
1823 | buf_T *buf, |
1824 | char_u *fname, // file name to be used (can differ from |
1825 | // buf->ffname) |
1826 | char_u *ffname, // full path version of fname |
1827 | int other // writing under other name |
1828 | ) |
1829 | { |
1830 | /* |
1831 | * write to other file or b_flags set or not writing the whole file: |
1832 | * overwriting only allowed with '!' |
1833 | */ |
1834 | if ((other |
1835 | || (buf->b_flags & BF_NOTEDITED) |
1836 | || ((buf->b_flags & BF_NEW) |
1837 | && vim_strchr(p_cpo, CPO_OVERNEW) == NULL) |
1838 | || (buf->b_flags & BF_READERR)) |
1839 | && !p_wa |
1840 | && !bt_nofile(buf) |
1841 | && os_path_exists(ffname)) { |
1842 | if (!eap->forceit && !eap->append) { |
1843 | #ifdef UNIX |
1844 | // It is possible to open a directory on Unix. |
1845 | if (os_isdir(ffname)) { |
1846 | EMSG2(_(e_isadir2), ffname); |
1847 | return FAIL; |
1848 | } |
1849 | #endif |
1850 | if (p_confirm || cmdmod.confirm) { |
1851 | char_u buff[DIALOG_MSG_SIZE]; |
1852 | |
1853 | dialog_msg(buff, _("Overwrite existing file \"%s\"?" ), fname); |
1854 | if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2) != VIM_YES) |
1855 | return FAIL; |
1856 | eap->forceit = TRUE; |
1857 | } else { |
1858 | EMSG(_(e_exists)); |
1859 | return FAIL; |
1860 | } |
1861 | } |
1862 | |
1863 | /* For ":w! filename" check that no swap file exists for "filename". */ |
1864 | if (other && !emsg_silent) { |
1865 | char_u *dir; |
1866 | char_u *p; |
1867 | char_u *swapname; |
1868 | |
1869 | /* We only try the first entry in 'directory', without checking if |
1870 | * it's writable. If the "." directory is not writable the write |
1871 | * will probably fail anyway. |
1872 | * Use 'shortname' of the current buffer, since there is no buffer |
1873 | * for the written file. */ |
1874 | if (*p_dir == NUL) { |
1875 | dir = xmalloc(5); |
1876 | STRCPY(dir, "." ); |
1877 | } else { |
1878 | dir = xmalloc(MAXPATHL); |
1879 | p = p_dir; |
1880 | copy_option_part(&p, dir, MAXPATHL, "," ); |
1881 | } |
1882 | swapname = makeswapname(fname, ffname, curbuf, dir); |
1883 | xfree(dir); |
1884 | if (os_path_exists(swapname)) { |
1885 | if (p_confirm || cmdmod.confirm) { |
1886 | char_u buff[DIALOG_MSG_SIZE]; |
1887 | |
1888 | dialog_msg(buff, |
1889 | _("Swap file \"%s\" exists, overwrite anyway?" ), |
1890 | swapname); |
1891 | if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2) |
1892 | != VIM_YES) { |
1893 | xfree(swapname); |
1894 | return FAIL; |
1895 | } |
1896 | eap->forceit = TRUE; |
1897 | } else { |
1898 | EMSG2(_("E768: Swap file exists: %s (:silent! overrides)" ), |
1899 | swapname); |
1900 | xfree(swapname); |
1901 | return FAIL; |
1902 | } |
1903 | } |
1904 | xfree(swapname); |
1905 | } |
1906 | } |
1907 | return OK; |
1908 | } |
1909 | |
1910 | /* |
1911 | * Handle ":wnext", ":wNext" and ":wprevious" commands. |
1912 | */ |
1913 | void ex_wnext(exarg_T *eap) |
1914 | { |
1915 | int i; |
1916 | |
1917 | if (eap->cmd[1] == 'n') |
1918 | i = curwin->w_arg_idx + (int)eap->line2; |
1919 | else |
1920 | i = curwin->w_arg_idx - (int)eap->line2; |
1921 | eap->line1 = 1; |
1922 | eap->line2 = curbuf->b_ml.ml_line_count; |
1923 | if (do_write(eap) != FAIL) |
1924 | do_argfile(eap, i); |
1925 | } |
1926 | |
1927 | /* |
1928 | * ":wall", ":wqall" and ":xall": Write all changed files (and exit). |
1929 | */ |
1930 | void do_wqall(exarg_T *eap) |
1931 | { |
1932 | int error = 0; |
1933 | int save_forceit = eap->forceit; |
1934 | |
1935 | if (eap->cmdidx == CMD_xall || eap->cmdidx == CMD_wqall) { |
1936 | exiting = true; |
1937 | } |
1938 | |
1939 | FOR_ALL_BUFFERS(buf) { |
1940 | if (!bufIsChanged(buf) || bt_dontwrite(buf)) { |
1941 | continue; |
1942 | } |
1943 | /* |
1944 | * Check if there is a reason the buffer cannot be written: |
1945 | * 1. if the 'write' option is set |
1946 | * 2. if there is no file name (even after browsing) |
1947 | * 3. if the 'readonly' is set (even after a dialog) |
1948 | * 4. if overwriting is allowed (even after a dialog) |
1949 | */ |
1950 | if (not_writing()) { |
1951 | ++error; |
1952 | break; |
1953 | } |
1954 | if (buf->b_ffname == NULL) { |
1955 | EMSGN(_("E141: No file name for buffer %" PRId64), buf->b_fnum); |
1956 | ++error; |
1957 | } else if (check_readonly(&eap->forceit, buf) |
1958 | || check_overwrite(eap, buf, buf->b_fname, buf->b_ffname, |
1959 | FALSE) == FAIL) { |
1960 | ++error; |
1961 | } else { |
1962 | bufref_T bufref; |
1963 | set_bufref(&bufref, buf); |
1964 | if (buf_write_all(buf, eap->forceit) == FAIL) { |
1965 | error++; |
1966 | } |
1967 | // An autocommand may have deleted the buffer. |
1968 | if (!bufref_valid(&bufref)) { |
1969 | buf = firstbuf; |
1970 | } |
1971 | } |
1972 | eap->forceit = save_forceit; /* check_overwrite() may set it */ |
1973 | } |
1974 | if (exiting) { |
1975 | if (!error) |
1976 | getout(0); /* exit Vim */ |
1977 | not_exiting(); |
1978 | } |
1979 | } |
1980 | |
1981 | /* |
1982 | * Check the 'write' option. |
1983 | * Return TRUE and give a message when it's not st. |
1984 | */ |
1985 | int not_writing(void) |
1986 | { |
1987 | if (p_write) |
1988 | return FALSE; |
1989 | EMSG(_("E142: File not written: Writing is disabled by 'write' option" )); |
1990 | return TRUE; |
1991 | } |
1992 | |
1993 | /* |
1994 | * Check if a buffer is read-only (either 'readonly' option is set or file is |
1995 | * read-only). Ask for overruling in a dialog. Return TRUE and give an error |
1996 | * message when the buffer is readonly. |
1997 | */ |
1998 | static int check_readonly(int *forceit, buf_T *buf) |
1999 | { |
2000 | /* Handle a file being readonly when the 'readonly' option is set or when |
2001 | * the file exists and permissions are read-only. */ |
2002 | if (!*forceit && (buf->b_p_ro |
2003 | || (os_path_exists(buf->b_ffname) |
2004 | && !os_file_is_writable((char *)buf->b_ffname)))) { |
2005 | if ((p_confirm || cmdmod.confirm) && buf->b_fname != NULL) { |
2006 | char_u buff[DIALOG_MSG_SIZE]; |
2007 | |
2008 | if (buf->b_p_ro) |
2009 | dialog_msg(buff, |
2010 | _( |
2011 | "'readonly' option is set for \"%s\".\nDo you wish to write anyway?" ), |
2012 | buf->b_fname); |
2013 | else |
2014 | dialog_msg(buff, |
2015 | _( |
2016 | "File permissions of \"%s\" are read-only.\nIt may still be possible to write it.\nDo you wish to try?" ), |
2017 | buf->b_fname); |
2018 | |
2019 | if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2) == VIM_YES) { |
2020 | /* Set forceit, to force the writing of a readonly file */ |
2021 | *forceit = TRUE; |
2022 | return FALSE; |
2023 | } else |
2024 | return TRUE; |
2025 | } else if (buf->b_p_ro) |
2026 | EMSG(_(e_readonly)); |
2027 | else |
2028 | EMSG2(_("E505: \"%s\" is read-only (add ! to override)" ), |
2029 | buf->b_fname); |
2030 | return TRUE; |
2031 | } |
2032 | |
2033 | return FALSE; |
2034 | } |
2035 | |
2036 | /* |
2037 | * Try to abandon current file and edit a new or existing file. |
2038 | * "fnum" is the number of the file, if zero use ffname/sfname. |
2039 | * "lnum" is the line number for the cursor in the new file (if non-zero). |
2040 | * |
2041 | * Return: |
2042 | * GETFILE_ERROR for "normal" error, |
2043 | * GETFILE_NOT_WRITTEN for "not written" error, |
2044 | * GETFILE_SAME_FILE for success |
2045 | * GETFILE_OPEN_OTHER for successfully opening another file. |
2046 | */ |
2047 | int getfile(int fnum, char_u *ffname, char_u *sfname, int setpm, linenr_T lnum, int forceit) |
2048 | { |
2049 | int other; |
2050 | int retval; |
2051 | char_u *free_me = NULL; |
2052 | |
2053 | if (text_locked()) { |
2054 | return GETFILE_ERROR; |
2055 | } |
2056 | if (curbuf_locked()) { |
2057 | return GETFILE_ERROR; |
2058 | } |
2059 | |
2060 | if (fnum == 0) { |
2061 | /* make ffname full path, set sfname */ |
2062 | fname_expand(curbuf, &ffname, &sfname); |
2063 | other = otherfile(ffname); |
2064 | free_me = ffname; /* has been allocated, free() later */ |
2065 | } else |
2066 | other = (fnum != curbuf->b_fnum); |
2067 | |
2068 | if (other) { |
2069 | no_wait_return++; // don't wait for autowrite message |
2070 | } |
2071 | if (other && !forceit && curbuf->b_nwindows == 1 && !buf_hide(curbuf) |
2072 | && curbufIsChanged() && autowrite(curbuf, forceit) == FAIL) { |
2073 | if (p_confirm && p_write) { |
2074 | dialog_changed(curbuf, false); |
2075 | } |
2076 | if (curbufIsChanged()) { |
2077 | no_wait_return--; |
2078 | no_write_message(); |
2079 | retval = GETFILE_NOT_WRITTEN; // File has been changed. |
2080 | goto theend; |
2081 | } |
2082 | } |
2083 | if (other) |
2084 | --no_wait_return; |
2085 | if (setpm) |
2086 | setpcmark(); |
2087 | if (!other) { |
2088 | if (lnum != 0) { |
2089 | curwin->w_cursor.lnum = lnum; |
2090 | } |
2091 | check_cursor_lnum(); |
2092 | beginline(BL_SOL | BL_FIX); |
2093 | retval = GETFILE_SAME_FILE; // it's in the same file |
2094 | } else if (do_ecmd(fnum, ffname, sfname, NULL, lnum, |
2095 | (buf_hide(curbuf) ? ECMD_HIDE : 0) |
2096 | + (forceit ? ECMD_FORCEIT : 0), curwin) == OK) { |
2097 | retval = GETFILE_OPEN_OTHER; // opened another file |
2098 | } else { |
2099 | retval = GETFILE_ERROR; // error encountered |
2100 | } |
2101 | |
2102 | theend: |
2103 | xfree(free_me); |
2104 | return retval; |
2105 | } |
2106 | |
2107 | /// start editing a new file |
2108 | /// |
2109 | /// @param fnum file number; if zero use ffname/sfname |
2110 | /// @param ffname the file name |
2111 | /// - full path if sfname used, |
2112 | /// - any file name if sfname is NULL |
2113 | /// - empty string to re-edit with the same file name (but may |
2114 | /// be in a different directory) |
2115 | /// - NULL to start an empty buffer |
2116 | /// @param sfname the short file name (or NULL) |
2117 | /// @param eap contains the command to be executed after loading the file |
2118 | /// and forced 'ff' and 'fenc' |
2119 | /// @param newlnum if > 0: put cursor on this line number (if possible) |
2120 | /// ECMD_LASTL: use last position in loaded file |
2121 | /// ECMD_LAST: use last position in all files |
2122 | /// ECMD_ONE: use first line |
2123 | /// @param flags ECMD_HIDE: if TRUE don't free the current buffer |
2124 | /// ECMD_SET_HELP: set b_help flag of (new) buffer before |
2125 | /// opening file |
2126 | /// ECMD_OLDBUF: use existing buffer if it exists |
2127 | /// ECMD_FORCEIT: ! used for Ex command |
2128 | /// ECMD_ADDBUF: don't edit, just add to buffer list |
2129 | /// @param oldwin Should be "curwin" when editing a new buffer in the current |
2130 | /// window, NULL when splitting the window first. When not NULL |
2131 | /// info of the previous buffer for "oldwin" is stored. |
2132 | /// |
2133 | /// @return FAIL for failure, OK otherwise |
2134 | int do_ecmd( |
2135 | int fnum, |
2136 | char_u *ffname, |
2137 | char_u *sfname, |
2138 | exarg_T *eap, /* can be NULL! */ |
2139 | linenr_T newlnum, |
2140 | int flags, |
2141 | win_T *oldwin |
2142 | ) |
2143 | { |
2144 | int other_file; /* TRUE if editing another file */ |
2145 | int oldbuf; /* TRUE if using existing buffer */ |
2146 | int auto_buf = FALSE; /* TRUE if autocommands brought us |
2147 | into the buffer unexpectedly */ |
2148 | char_u *new_name = NULL; |
2149 | int did_set_swapcommand = FALSE; |
2150 | buf_T *buf; |
2151 | bufref_T bufref; |
2152 | bufref_T old_curbuf; |
2153 | char_u *free_fname = NULL; |
2154 | int retval = FAIL; |
2155 | long n; |
2156 | pos_T orig_pos; |
2157 | linenr_T topline = 0; |
2158 | int newcol = -1; |
2159 | int solcol = -1; |
2160 | pos_T *pos; |
2161 | char_u *command = NULL; |
2162 | int did_get_winopts = FALSE; |
2163 | int readfile_flags = 0; |
2164 | bool did_inc_redrawing_disabled = false; |
2165 | |
2166 | if (eap != NULL) |
2167 | command = eap->do_ecmd_cmd; |
2168 | |
2169 | set_bufref(&old_curbuf, curbuf); |
2170 | |
2171 | if (fnum != 0) { |
2172 | if (fnum == curbuf->b_fnum) /* file is already being edited */ |
2173 | return OK; /* nothing to do */ |
2174 | other_file = TRUE; |
2175 | } else { |
2176 | /* if no short name given, use ffname for short name */ |
2177 | if (sfname == NULL) |
2178 | sfname = ffname; |
2179 | #ifdef USE_FNAME_CASE |
2180 | if (sfname != NULL) |
2181 | path_fix_case(sfname); // set correct case for sfname |
2182 | #endif |
2183 | |
2184 | if ((flags & ECMD_ADDBUF) && (ffname == NULL || *ffname == NUL)) |
2185 | goto theend; |
2186 | |
2187 | if (ffname == NULL) |
2188 | other_file = TRUE; |
2189 | /* there is no file name */ |
2190 | else if (*ffname == NUL && curbuf->b_ffname == NULL) |
2191 | other_file = FALSE; |
2192 | else { |
2193 | if (*ffname == NUL) { /* re-edit with same file name */ |
2194 | ffname = curbuf->b_ffname; |
2195 | sfname = curbuf->b_fname; |
2196 | } |
2197 | free_fname = (char_u *)fix_fname((char *)ffname); /* may expand to full path name */ |
2198 | if (free_fname != NULL) |
2199 | ffname = free_fname; |
2200 | other_file = otherfile(ffname); |
2201 | } |
2202 | } |
2203 | |
2204 | // Re-editing a terminal buffer: skip most buffer re-initialization. |
2205 | if (!other_file && curbuf->terminal) { |
2206 | check_arg_idx(curwin); // Needed when called from do_argfile(). |
2207 | maketitle(); // Title may show the arg index, e.g. "(2 of 5)". |
2208 | retval = OK; |
2209 | goto theend; |
2210 | } |
2211 | |
2212 | /* |
2213 | * if the file was changed we may not be allowed to abandon it |
2214 | * - if we are going to re-edit the same file |
2215 | * - or if we are the only window on this file and if ECMD_HIDE is FALSE |
2216 | */ |
2217 | if ( ((!other_file && !(flags & ECMD_OLDBUF)) |
2218 | || (curbuf->b_nwindows == 1 |
2219 | && !(flags & (ECMD_HIDE | ECMD_ADDBUF)))) |
2220 | && check_changed(curbuf, (p_awa ? CCGD_AW : 0) |
2221 | | (other_file ? 0 : CCGD_MULTWIN) |
2222 | | ((flags & ECMD_FORCEIT) ? CCGD_FORCEIT : 0) |
2223 | | (eap == NULL ? 0 : CCGD_EXCMD))) { |
2224 | if (fnum == 0 && other_file && ffname != NULL) |
2225 | (void)setaltfname(ffname, sfname, newlnum < 0 ? 0 : newlnum); |
2226 | goto theend; |
2227 | } |
2228 | |
2229 | /* |
2230 | * End Visual mode before switching to another buffer, so the text can be |
2231 | * copied into the GUI selection buffer. |
2232 | */ |
2233 | reset_VIsual(); |
2234 | |
2235 | if ((command != NULL || newlnum > (linenr_T)0) |
2236 | && *get_vim_var_str(VV_SWAPCOMMAND) == NUL) { |
2237 | // Set v:swapcommand for the SwapExists autocommands. |
2238 | const size_t len = (command != NULL) ? STRLEN(command) + 3 : 30; |
2239 | char *const p = xmalloc(len); |
2240 | if (command != NULL) { |
2241 | vim_snprintf(p, len, ":%s\r" , command); |
2242 | } else { |
2243 | vim_snprintf(p, len, "%" PRId64 "G" , (int64_t)newlnum); |
2244 | } |
2245 | set_vim_var_string(VV_SWAPCOMMAND, p, -1); |
2246 | did_set_swapcommand = TRUE; |
2247 | xfree(p); |
2248 | } |
2249 | |
2250 | /* |
2251 | * If we are starting to edit another file, open a (new) buffer. |
2252 | * Otherwise we re-use the current buffer. |
2253 | */ |
2254 | if (other_file) { |
2255 | if (!(flags & ECMD_ADDBUF)) { |
2256 | if (!cmdmod.keepalt) |
2257 | curwin->w_alt_fnum = curbuf->b_fnum; |
2258 | if (oldwin != NULL) |
2259 | buflist_altfpos(oldwin); |
2260 | } |
2261 | |
2262 | if (fnum) { |
2263 | buf = buflist_findnr(fnum); |
2264 | } else { |
2265 | if (flags & ECMD_ADDBUF) { |
2266 | linenr_T tlnum = 1L; |
2267 | |
2268 | if (command != NULL) { |
2269 | tlnum = atol((char *)command); |
2270 | if (tlnum <= 0) |
2271 | tlnum = 1L; |
2272 | } |
2273 | (void)buflist_new(ffname, sfname, tlnum, BLN_LISTED); |
2274 | goto theend; |
2275 | } |
2276 | buf = buflist_new(ffname, sfname, 0L, |
2277 | BLN_CURBUF | (flags & ECMD_SET_HELP ? 0 : BLN_LISTED)); |
2278 | // Autocmds may change curwin and curbuf. |
2279 | if (oldwin != NULL) { |
2280 | oldwin = curwin; |
2281 | } |
2282 | set_bufref(&old_curbuf, curbuf); |
2283 | } |
2284 | if (buf == NULL) |
2285 | goto theend; |
2286 | if (buf->b_ml.ml_mfp == NULL) { |
2287 | // No memfile yet. |
2288 | oldbuf = false; |
2289 | } else { |
2290 | // Existing memfile. |
2291 | oldbuf = true; |
2292 | set_bufref(&bufref, buf); |
2293 | (void)buf_check_timestamp(buf, false); |
2294 | // Check if autocommands made buffer invalid or changed the current |
2295 | // buffer. |
2296 | if (!bufref_valid(&bufref) || curbuf != old_curbuf.br_buf) { |
2297 | goto theend; |
2298 | } |
2299 | if (aborting()) { |
2300 | // Autocmds may abort script processing. |
2301 | goto theend; |
2302 | } |
2303 | } |
2304 | |
2305 | /* May jump to last used line number for a loaded buffer or when asked |
2306 | * for explicitly */ |
2307 | if ((oldbuf && newlnum == ECMD_LASTL) || newlnum == ECMD_LAST) { |
2308 | pos = buflist_findfpos(buf); |
2309 | newlnum = pos->lnum; |
2310 | solcol = pos->col; |
2311 | } |
2312 | |
2313 | /* |
2314 | * Make the (new) buffer the one used by the current window. |
2315 | * If the old buffer becomes unused, free it if ECMD_HIDE is FALSE. |
2316 | * If the current buffer was empty and has no file name, curbuf |
2317 | * is returned by buflist_new(), nothing to do here. |
2318 | */ |
2319 | if (buf != curbuf) { |
2320 | /* |
2321 | * Be careful: The autocommands may delete any buffer and change |
2322 | * the current buffer. |
2323 | * - If the buffer we are going to edit is deleted, give up. |
2324 | * - If the current buffer is deleted, prefer to load the new |
2325 | * buffer when loading a buffer is required. This avoids |
2326 | * loading another buffer which then must be closed again. |
2327 | * - If we ended up in the new buffer already, need to skip a few |
2328 | * things, set auto_buf. |
2329 | */ |
2330 | if (buf->b_fname != NULL) { |
2331 | new_name = vim_strsave(buf->b_fname); |
2332 | } |
2333 | set_bufref(&au_new_curbuf, buf); |
2334 | apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf); |
2335 | if (!bufref_valid(&au_new_curbuf)) { |
2336 | // New buffer has been deleted. |
2337 | delbuf_msg(new_name); // Frees new_name. |
2338 | goto theend; |
2339 | } |
2340 | if (aborting()) { /* autocmds may abort script processing */ |
2341 | xfree(new_name); |
2342 | goto theend; |
2343 | } |
2344 | if (buf == curbuf) { // already in new buffer |
2345 | auto_buf = true; |
2346 | } else { |
2347 | win_T *the_curwin = curwin; |
2348 | |
2349 | // Set w_closing to avoid that autocommands close the window. |
2350 | // Set b_locked for the same reason. |
2351 | the_curwin->w_closing = true; |
2352 | buf->b_locked++; |
2353 | |
2354 | if (curbuf == old_curbuf.br_buf) { |
2355 | buf_copy_options(buf, BCO_ENTER); |
2356 | } |
2357 | |
2358 | // Close the link to the current buffer. This will set |
2359 | // oldwin->w_buffer to NULL. |
2360 | u_sync(false); |
2361 | close_buffer(oldwin, curbuf, |
2362 | (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD, |
2363 | false); |
2364 | |
2365 | the_curwin->w_closing = false; |
2366 | buf->b_locked--; |
2367 | |
2368 | // autocmds may abort script processing |
2369 | if (aborting() && curwin->w_buffer != NULL) { |
2370 | xfree(new_name); |
2371 | goto theend; |
2372 | } |
2373 | // Be careful again, like above. |
2374 | if (!bufref_valid(&au_new_curbuf)) { |
2375 | // New buffer has been deleted. |
2376 | delbuf_msg(new_name); // Frees new_name. |
2377 | goto theend; |
2378 | } |
2379 | if (buf == curbuf) { // already in new buffer |
2380 | auto_buf = true; |
2381 | } else { |
2382 | // <VN> We could instead free the synblock |
2383 | // and re-attach to buffer, perhaps. |
2384 | if (curwin->w_buffer == NULL |
2385 | || curwin->w_s == &(curwin->w_buffer->b_s)) { |
2386 | curwin->w_s = &(buf->b_s); |
2387 | } |
2388 | |
2389 | curwin->w_buffer = buf; |
2390 | curbuf = buf; |
2391 | ++curbuf->b_nwindows; |
2392 | |
2393 | /* Set 'fileformat', 'binary' and 'fenc' when forced. */ |
2394 | if (!oldbuf && eap != NULL) { |
2395 | set_file_options(TRUE, eap); |
2396 | set_forced_fenc(eap); |
2397 | } |
2398 | } |
2399 | |
2400 | /* May get the window options from the last time this buffer |
2401 | * was in this window (or another window). If not used |
2402 | * before, reset the local window options to the global |
2403 | * values. Also restores old folding stuff. */ |
2404 | get_winopts(curbuf); |
2405 | did_get_winopts = TRUE; |
2406 | |
2407 | } |
2408 | xfree(new_name); |
2409 | au_new_curbuf.br_buf = NULL; |
2410 | au_new_curbuf.br_buf_free_count = 0; |
2411 | } |
2412 | |
2413 | curwin->w_pcmark.lnum = 1; |
2414 | curwin->w_pcmark.col = 0; |
2415 | } else { // !other_file |
2416 | if ((flags & ECMD_ADDBUF) |
2417 | || check_fname() == FAIL) { |
2418 | goto theend; |
2419 | } |
2420 | oldbuf = (flags & ECMD_OLDBUF); |
2421 | } |
2422 | |
2423 | // Don't redraw until the cursor is in the right line, otherwise |
2424 | // autocommands may cause ml_get errors. |
2425 | RedrawingDisabled++; |
2426 | did_inc_redrawing_disabled = true; |
2427 | |
2428 | buf = curbuf; |
2429 | if ((flags & ECMD_SET_HELP) || keep_help_flag) { |
2430 | prepare_help_buffer(); |
2431 | } else if (!curbuf->b_help) { |
2432 | // Don't make a buffer listed if it's a help buffer. Useful when using |
2433 | // CTRL-O to go back to a help file. |
2434 | set_buflisted(TRUE); |
2435 | } |
2436 | |
2437 | /* If autocommands change buffers under our fingers, forget about |
2438 | * editing the file. */ |
2439 | if (buf != curbuf) |
2440 | goto theend; |
2441 | if (aborting()) /* autocmds may abort script processing */ |
2442 | goto theend; |
2443 | |
2444 | /* Since we are starting to edit a file, consider the filetype to be |
2445 | * unset. Helps for when an autocommand changes files and expects syntax |
2446 | * highlighting to work in the other file. */ |
2447 | did_filetype = FALSE; |
2448 | |
2449 | /* |
2450 | * other_file oldbuf |
2451 | * FALSE FALSE re-edit same file, buffer is re-used |
2452 | * FALSE TRUE re-edit same file, nothing changes |
2453 | * TRUE FALSE start editing new file, new buffer |
2454 | * TRUE TRUE start editing in existing buffer (nothing to do) |
2455 | */ |
2456 | if (!other_file && !oldbuf) { /* re-use the buffer */ |
2457 | set_last_cursor(curwin); /* may set b_last_cursor */ |
2458 | if (newlnum == ECMD_LAST || newlnum == ECMD_LASTL) { |
2459 | newlnum = curwin->w_cursor.lnum; |
2460 | solcol = curwin->w_cursor.col; |
2461 | } |
2462 | buf = curbuf; |
2463 | if (buf->b_fname != NULL) { |
2464 | new_name = vim_strsave(buf->b_fname); |
2465 | } else { |
2466 | new_name = NULL; |
2467 | } |
2468 | set_bufref(&bufref, buf); |
2469 | if (p_ur < 0 || curbuf->b_ml.ml_line_count <= p_ur) { |
2470 | // Save all the text, so that the reload can be undone. |
2471 | // Sync first so that this is a separate undo-able action. |
2472 | u_sync(false); |
2473 | if (u_savecommon(0, curbuf->b_ml.ml_line_count + 1, 0, true) |
2474 | == FAIL) { |
2475 | xfree(new_name); |
2476 | goto theend; |
2477 | } |
2478 | u_unchanged(curbuf); |
2479 | buf_updates_unregister_all(curbuf); |
2480 | buf_freeall(curbuf, BFA_KEEP_UNDO); |
2481 | |
2482 | // Tell readfile() not to clear or reload undo info. |
2483 | readfile_flags = READ_KEEP_UNDO; |
2484 | } else { |
2485 | buf_updates_unregister_all(curbuf); |
2486 | buf_freeall(curbuf, 0); // Free all things for buffer. |
2487 | } |
2488 | // If autocommands deleted the buffer we were going to re-edit, give |
2489 | // up and jump to the end. |
2490 | if (!bufref_valid(&bufref)) { |
2491 | delbuf_msg(new_name); // Frees new_name. |
2492 | goto theend; |
2493 | } |
2494 | xfree(new_name); |
2495 | |
2496 | /* If autocommands change buffers under our fingers, forget about |
2497 | * re-editing the file. Should do the buf_clear_file(), but perhaps |
2498 | * the autocommands changed the buffer... */ |
2499 | if (buf != curbuf) |
2500 | goto theend; |
2501 | if (aborting()) /* autocmds may abort script processing */ |
2502 | goto theend; |
2503 | buf_clear_file(curbuf); |
2504 | curbuf->b_op_start.lnum = 0; /* clear '[ and '] marks */ |
2505 | curbuf->b_op_end.lnum = 0; |
2506 | } |
2507 | |
2508 | /* |
2509 | * If we get here we are sure to start editing |
2510 | */ |
2511 | |
2512 | /* Assume success now */ |
2513 | retval = OK; |
2514 | |
2515 | /* |
2516 | * Check if we are editing the w_arg_idx file in the argument list. |
2517 | */ |
2518 | check_arg_idx(curwin); |
2519 | |
2520 | if (!auto_buf) { |
2521 | /* |
2522 | * Set cursor and init window before reading the file and executing |
2523 | * autocommands. This allows for the autocommands to position the |
2524 | * cursor. |
2525 | */ |
2526 | curwin_init(); |
2527 | |
2528 | /* It's possible that all lines in the buffer changed. Need to update |
2529 | * automatic folding for all windows where it's used. */ |
2530 | FOR_ALL_TAB_WINDOWS(tp, win) { |
2531 | if (win->w_buffer == curbuf) { |
2532 | foldUpdateAll(win); |
2533 | } |
2534 | } |
2535 | |
2536 | /* Change directories when the 'acd' option is set. */ |
2537 | do_autochdir(); |
2538 | |
2539 | /* |
2540 | * Careful: open_buffer() and apply_autocmds() may change the current |
2541 | * buffer and window. |
2542 | */ |
2543 | orig_pos = curwin->w_cursor; |
2544 | topline = curwin->w_topline; |
2545 | if (!oldbuf) { /* need to read the file */ |
2546 | swap_exists_action = SEA_DIALOG; |
2547 | curbuf->b_flags |= BF_CHECK_RO; /* set/reset 'ro' flag */ |
2548 | |
2549 | /* |
2550 | * Open the buffer and read the file. |
2551 | */ |
2552 | if (should_abort(open_buffer(FALSE, eap, readfile_flags))) |
2553 | retval = FAIL; |
2554 | |
2555 | if (swap_exists_action == SEA_QUIT) |
2556 | retval = FAIL; |
2557 | handle_swap_exists(&old_curbuf); |
2558 | } else { |
2559 | /* Read the modelines, but only to set window-local options. Any |
2560 | * buffer-local options have already been set and may have been |
2561 | * changed by the user. */ |
2562 | do_modelines(OPT_WINONLY); |
2563 | |
2564 | apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf, |
2565 | &retval); |
2566 | apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf, |
2567 | &retval); |
2568 | } |
2569 | check_arg_idx(curwin); |
2570 | |
2571 | // If autocommands change the cursor position or topline, we should |
2572 | // keep it. Also when it moves within a line. But not when it moves |
2573 | // to the first non-blank. |
2574 | if (!equalpos(curwin->w_cursor, orig_pos)) { |
2575 | const char_u *text = get_cursor_line_ptr(); |
2576 | |
2577 | if (curwin->w_cursor.lnum != orig_pos.lnum |
2578 | || curwin->w_cursor.col != (int)(skipwhite(text) - text)) { |
2579 | newlnum = curwin->w_cursor.lnum; |
2580 | newcol = curwin->w_cursor.col; |
2581 | } |
2582 | } |
2583 | if (curwin->w_topline == topline) |
2584 | topline = 0; |
2585 | |
2586 | /* Even when cursor didn't move we need to recompute topline. */ |
2587 | changed_line_abv_curs(); |
2588 | |
2589 | maketitle(); |
2590 | } |
2591 | |
2592 | /* Tell the diff stuff that this buffer is new and/or needs updating. |
2593 | * Also needed when re-editing the same buffer, because unloading will |
2594 | * have removed it as a diff buffer. */ |
2595 | if (curwin->w_p_diff) { |
2596 | diff_buf_add(curbuf); |
2597 | diff_invalidate(curbuf); |
2598 | } |
2599 | |
2600 | /* If the window options were changed may need to set the spell language. |
2601 | * Can only do this after the buffer has been properly setup. */ |
2602 | if (did_get_winopts && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) |
2603 | (void)did_set_spelllang(curwin); |
2604 | |
2605 | if (command == NULL) { |
2606 | if (newcol >= 0) { /* position set by autocommands */ |
2607 | curwin->w_cursor.lnum = newlnum; |
2608 | curwin->w_cursor.col = newcol; |
2609 | check_cursor(); |
2610 | } else if (newlnum > 0) { /* line number from caller or old position */ |
2611 | curwin->w_cursor.lnum = newlnum; |
2612 | check_cursor_lnum(); |
2613 | if (solcol >= 0 && !p_sol) { |
2614 | /* 'sol' is off: Use last known column. */ |
2615 | curwin->w_cursor.col = solcol; |
2616 | check_cursor_col(); |
2617 | curwin->w_cursor.coladd = 0; |
2618 | curwin->w_set_curswant = TRUE; |
2619 | } else |
2620 | beginline(BL_SOL | BL_FIX); |
2621 | } else { /* no line number, go to last line in Ex mode */ |
2622 | if (exmode_active) |
2623 | curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; |
2624 | beginline(BL_WHITE | BL_FIX); |
2625 | } |
2626 | } |
2627 | |
2628 | /* Check if cursors in other windows on the same buffer are still valid */ |
2629 | check_lnums(FALSE); |
2630 | |
2631 | /* |
2632 | * Did not read the file, need to show some info about the file. |
2633 | * Do this after setting the cursor. |
2634 | */ |
2635 | if (oldbuf |
2636 | && !auto_buf |
2637 | ) { |
2638 | int msg_scroll_save = msg_scroll; |
2639 | |
2640 | /* Obey the 'O' flag in 'cpoptions': overwrite any previous file |
2641 | * message. */ |
2642 | if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) |
2643 | msg_scroll = FALSE; |
2644 | if (!msg_scroll) /* wait a bit when overwriting an error msg */ |
2645 | check_for_delay(FALSE); |
2646 | msg_start(); |
2647 | msg_scroll = msg_scroll_save; |
2648 | msg_scrolled_ign = TRUE; |
2649 | |
2650 | if (!shortmess(SHM_FILEINFO)) { |
2651 | fileinfo(false, true, false); |
2652 | } |
2653 | |
2654 | msg_scrolled_ign = FALSE; |
2655 | } |
2656 | |
2657 | if (command != NULL) |
2658 | do_cmdline(command, NULL, NULL, DOCMD_VERBOSE); |
2659 | |
2660 | if (curbuf->b_kmap_state & KEYMAP_INIT) |
2661 | (void)keymap_init(); |
2662 | |
2663 | RedrawingDisabled--; |
2664 | did_inc_redrawing_disabled = false; |
2665 | if (!skip_redraw) { |
2666 | n = p_so; |
2667 | if (topline == 0 && command == NULL) |
2668 | p_so = 999; // force cursor to be vertically centered in the window |
2669 | update_topline(); |
2670 | curwin->w_scbind_pos = curwin->w_topline; |
2671 | p_so = n; |
2672 | redraw_curbuf_later(NOT_VALID); /* redraw this buffer later */ |
2673 | } |
2674 | |
2675 | if (p_im) |
2676 | need_start_insertmode = TRUE; |
2677 | |
2678 | /* Change directories when the 'acd' option is set. */ |
2679 | do_autochdir(); |
2680 | |
2681 | |
2682 | theend: |
2683 | if (bufref_valid(&old_curbuf) && old_curbuf.br_buf->terminal != NULL) { |
2684 | terminal_check_size(old_curbuf.br_buf->terminal); |
2685 | } |
2686 | |
2687 | if (did_inc_redrawing_disabled) { |
2688 | RedrawingDisabled--; |
2689 | } |
2690 | if (did_set_swapcommand) { |
2691 | set_vim_var_string(VV_SWAPCOMMAND, NULL, -1); |
2692 | } |
2693 | xfree(free_fname); |
2694 | return retval; |
2695 | } |
2696 | |
2697 | static void delbuf_msg(char_u *name) |
2698 | { |
2699 | EMSG2(_("E143: Autocommands unexpectedly deleted new buffer %s" ), |
2700 | name == NULL ? (char_u *)"" : name); |
2701 | xfree(name); |
2702 | au_new_curbuf.br_buf = NULL; |
2703 | au_new_curbuf.br_buf_free_count = 0; |
2704 | } |
2705 | |
2706 | static int append_indent = 0; /* autoindent for first line */ |
2707 | |
2708 | /* |
2709 | * ":insert" and ":append", also used by ":change" |
2710 | */ |
2711 | void ex_append(exarg_T *eap) |
2712 | { |
2713 | char_u *theline; |
2714 | bool did_undo = false; |
2715 | linenr_T lnum = eap->line2; |
2716 | int indent = 0; |
2717 | char_u *p; |
2718 | int vcol; |
2719 | int empty = (curbuf->b_ml.ml_flags & ML_EMPTY); |
2720 | |
2721 | /* the ! flag toggles autoindent */ |
2722 | if (eap->forceit) |
2723 | curbuf->b_p_ai = !curbuf->b_p_ai; |
2724 | |
2725 | /* First autoindent comes from the line we start on */ |
2726 | if (eap->cmdidx != CMD_change && curbuf->b_p_ai && lnum > 0) |
2727 | append_indent = get_indent_lnum(lnum); |
2728 | |
2729 | if (eap->cmdidx != CMD_append) |
2730 | --lnum; |
2731 | |
2732 | // when the buffer is empty need to delete the dummy line |
2733 | if (empty && lnum == 1) |
2734 | lnum = 0; |
2735 | |
2736 | State = INSERT; /* behave like in Insert mode */ |
2737 | if (curbuf->b_p_iminsert == B_IMODE_LMAP) |
2738 | State |= LANGMAP; |
2739 | |
2740 | for (;; ) { |
2741 | msg_scroll = TRUE; |
2742 | need_wait_return = FALSE; |
2743 | if (curbuf->b_p_ai) { |
2744 | if (append_indent >= 0) { |
2745 | indent = append_indent; |
2746 | append_indent = -1; |
2747 | } else if (lnum > 0) |
2748 | indent = get_indent_lnum(lnum); |
2749 | } |
2750 | ex_keep_indent = FALSE; |
2751 | if (eap->getline == NULL) { |
2752 | /* No getline() function, use the lines that follow. This ends |
2753 | * when there is no more. */ |
2754 | if (eap->nextcmd == NULL || *eap->nextcmd == NUL) |
2755 | break; |
2756 | p = vim_strchr(eap->nextcmd, NL); |
2757 | if (p == NULL) |
2758 | p = eap->nextcmd + STRLEN(eap->nextcmd); |
2759 | theline = vim_strnsave(eap->nextcmd, (int)(p - eap->nextcmd)); |
2760 | if (*p != NUL) |
2761 | ++p; |
2762 | eap->nextcmd = p; |
2763 | } else { |
2764 | // Set State to avoid the cursor shape to be set to INSERT mode |
2765 | // when getline() returns. |
2766 | int save_State = State; |
2767 | State = CMDLINE; |
2768 | theline = eap->getline( |
2769 | eap->cstack->cs_looplevel > 0 ? -1 : |
2770 | NUL, eap->cookie, indent); |
2771 | State = save_State; |
2772 | } |
2773 | lines_left = Rows - 1; |
2774 | if (theline == NULL) |
2775 | break; |
2776 | |
2777 | /* Using ^ CTRL-D in getexmodeline() makes us repeat the indent. */ |
2778 | if (ex_keep_indent) |
2779 | append_indent = indent; |
2780 | |
2781 | /* Look for the "." after automatic indent. */ |
2782 | vcol = 0; |
2783 | for (p = theline; indent > vcol; ++p) { |
2784 | if (*p == ' ') |
2785 | ++vcol; |
2786 | else if (*p == TAB) |
2787 | vcol += 8 - vcol % 8; |
2788 | else |
2789 | break; |
2790 | } |
2791 | if ((p[0] == '.' && p[1] == NUL) |
2792 | || (!did_undo && u_save(lnum, lnum + 1 + (empty ? 1 : 0)) |
2793 | == FAIL)) { |
2794 | xfree(theline); |
2795 | break; |
2796 | } |
2797 | |
2798 | /* don't use autoindent if nothing was typed. */ |
2799 | if (p[0] == NUL) |
2800 | theline[0] = NUL; |
2801 | |
2802 | did_undo = true; |
2803 | ml_append(lnum, theline, (colnr_T)0, false); |
2804 | appended_lines_mark(lnum + (empty ? 1 : 0), 1L); |
2805 | |
2806 | xfree(theline); |
2807 | ++lnum; |
2808 | |
2809 | if (empty) { |
2810 | ml_delete(2L, false); |
2811 | empty = 0; |
2812 | } |
2813 | } |
2814 | State = NORMAL; |
2815 | |
2816 | if (eap->forceit) |
2817 | curbuf->b_p_ai = !curbuf->b_p_ai; |
2818 | |
2819 | /* "start" is set to eap->line2+1 unless that position is invalid (when |
2820 | * eap->line2 pointed to the end of the buffer and nothing was appended) |
2821 | * "end" is set to lnum when something has been appended, otherwise |
2822 | * it is the same than "start" -- Acevedo */ |
2823 | curbuf->b_op_start.lnum = (eap->line2 < curbuf->b_ml.ml_line_count) ? |
2824 | eap->line2 + 1 : curbuf->b_ml.ml_line_count; |
2825 | if (eap->cmdidx != CMD_append) |
2826 | --curbuf->b_op_start.lnum; |
2827 | curbuf->b_op_end.lnum = (eap->line2 < lnum) |
2828 | ? lnum : curbuf->b_op_start.lnum; |
2829 | curbuf->b_op_start.col = curbuf->b_op_end.col = 0; |
2830 | curwin->w_cursor.lnum = lnum; |
2831 | check_cursor_lnum(); |
2832 | beginline(BL_SOL | BL_FIX); |
2833 | |
2834 | need_wait_return = FALSE; /* don't use wait_return() now */ |
2835 | ex_no_reprint = TRUE; |
2836 | } |
2837 | |
2838 | /* |
2839 | * ":change" |
2840 | */ |
2841 | void ex_change(exarg_T *eap) |
2842 | { |
2843 | linenr_T lnum; |
2844 | |
2845 | if (eap->line2 >= eap->line1 |
2846 | && u_save(eap->line1 - 1, eap->line2 + 1) == FAIL) |
2847 | return; |
2848 | |
2849 | /* the ! flag toggles autoindent */ |
2850 | if (eap->forceit ? !curbuf->b_p_ai : curbuf->b_p_ai) |
2851 | append_indent = get_indent_lnum(eap->line1); |
2852 | |
2853 | for (lnum = eap->line2; lnum >= eap->line1; --lnum) { |
2854 | if (curbuf->b_ml.ml_flags & ML_EMPTY) /* nothing to delete */ |
2855 | break; |
2856 | ml_delete(eap->line1, false); |
2857 | } |
2858 | |
2859 | /* make sure the cursor is not beyond the end of the file now */ |
2860 | check_cursor_lnum(); |
2861 | deleted_lines_mark(eap->line1, (long)(eap->line2 - lnum)); |
2862 | |
2863 | /* ":append" on the line above the deleted lines. */ |
2864 | eap->line2 = eap->line1; |
2865 | ex_append(eap); |
2866 | } |
2867 | |
2868 | void ex_z(exarg_T *eap) |
2869 | { |
2870 | char_u *x; |
2871 | int64_t bigness; |
2872 | char_u *kind; |
2873 | int minus = 0; |
2874 | linenr_T start, end, curs, i; |
2875 | int j; |
2876 | linenr_T lnum = eap->line2; |
2877 | |
2878 | // Vi compatible: ":z!" uses display height, without a count uses |
2879 | // 'scroll' |
2880 | if (eap->forceit) { |
2881 | bigness = curwin->w_height_inner; |
2882 | } else if (ONE_WINDOW) { |
2883 | bigness = curwin->w_p_scr * 2; |
2884 | } else { |
2885 | bigness = curwin->w_height_inner - 3; |
2886 | } |
2887 | if (bigness < 1) { |
2888 | bigness = 1; |
2889 | } |
2890 | |
2891 | x = eap->arg; |
2892 | kind = x; |
2893 | if (*kind == '-' || *kind == '+' || *kind == '=' |
2894 | || *kind == '^' || *kind == '.') |
2895 | ++x; |
2896 | while (*x == '-' || *x == '+') |
2897 | ++x; |
2898 | |
2899 | if (*x != 0) { |
2900 | if (!ascii_isdigit(*x)) { |
2901 | EMSG(_("E144: non-numeric argument to :z" )); |
2902 | return; |
2903 | } |
2904 | bigness = atol((char *)x); |
2905 | |
2906 | // bigness could be < 0 if atol(x) overflows. |
2907 | if (bigness > 2 * curbuf->b_ml.ml_line_count || bigness < 0) { |
2908 | bigness = 2 * curbuf->b_ml.ml_line_count; |
2909 | } |
2910 | |
2911 | p_window = bigness; |
2912 | if (*kind == '=') { |
2913 | bigness += 2; |
2914 | } |
2915 | } |
2916 | |
2917 | /* the number of '-' and '+' multiplies the distance */ |
2918 | if (*kind == '-' || *kind == '+') |
2919 | for (x = kind + 1; *x == *kind; ++x) |
2920 | ; |
2921 | |
2922 | switch (*kind) { |
2923 | case '-': |
2924 | start = lnum - bigness * (linenr_T)(x - kind) + 1; |
2925 | end = start + bigness - 1; |
2926 | curs = end; |
2927 | break; |
2928 | |
2929 | case '=': |
2930 | start = lnum - (bigness + 1) / 2 + 1; |
2931 | end = lnum + (bigness + 1) / 2 - 1; |
2932 | curs = lnum; |
2933 | minus = 1; |
2934 | break; |
2935 | |
2936 | case '^': |
2937 | start = lnum - bigness * 2; |
2938 | end = lnum - bigness; |
2939 | curs = lnum - bigness; |
2940 | break; |
2941 | |
2942 | case '.': |
2943 | start = lnum - (bigness + 1) / 2 + 1; |
2944 | end = lnum + (bigness + 1) / 2 - 1; |
2945 | curs = end; |
2946 | break; |
2947 | |
2948 | default: /* '+' */ |
2949 | start = lnum; |
2950 | if (*kind == '+') |
2951 | start += bigness * (linenr_T)(x - kind - 1) + 1; |
2952 | else if (eap->addr_count == 0) |
2953 | ++start; |
2954 | end = start + bigness - 1; |
2955 | curs = end; |
2956 | break; |
2957 | } |
2958 | |
2959 | if (start < 1) |
2960 | start = 1; |
2961 | |
2962 | if (end > curbuf->b_ml.ml_line_count) |
2963 | end = curbuf->b_ml.ml_line_count; |
2964 | |
2965 | if (curs > curbuf->b_ml.ml_line_count) { |
2966 | curs = curbuf->b_ml.ml_line_count; |
2967 | } else if (curs < 1) { |
2968 | curs = 1; |
2969 | } |
2970 | |
2971 | for (i = start; i <= end; i++) { |
2972 | if (minus && i == lnum) { |
2973 | msg_putchar('\n'); |
2974 | |
2975 | for (j = 1; j < Columns; j++) |
2976 | msg_putchar('-'); |
2977 | } |
2978 | |
2979 | print_line(i, eap->flags & EXFLAG_NR, eap->flags & EXFLAG_LIST); |
2980 | |
2981 | if (minus && i == lnum) { |
2982 | msg_putchar('\n'); |
2983 | |
2984 | for (j = 1; j < Columns; j++) |
2985 | msg_putchar('-'); |
2986 | } |
2987 | } |
2988 | |
2989 | if (curwin->w_cursor.lnum != curs) { |
2990 | curwin->w_cursor.lnum = curs; |
2991 | curwin->w_cursor.col = 0; |
2992 | } |
2993 | ex_no_reprint = true; |
2994 | } |
2995 | |
2996 | /* |
2997 | * Check if the restricted flag is set. |
2998 | * If so, give an error message and return TRUE. |
2999 | * Otherwise, return FALSE. |
3000 | */ |
3001 | int check_restricted(void) |
3002 | { |
3003 | if (restricted) { |
3004 | EMSG(_("E145: Shell commands not allowed in restricted mode" )); |
3005 | return TRUE; |
3006 | } |
3007 | return FALSE; |
3008 | } |
3009 | |
3010 | /* |
3011 | * Check if the secure flag is set (.exrc or .vimrc in current directory). |
3012 | * If so, give an error message and return TRUE. |
3013 | * Otherwise, return FALSE. |
3014 | */ |
3015 | int check_secure(void) |
3016 | { |
3017 | if (secure) { |
3018 | secure = 2; |
3019 | EMSG(_(e_curdir)); |
3020 | return TRUE; |
3021 | } |
3022 | |
3023 | // In the sandbox more things are not allowed, including the things |
3024 | // disallowed in secure mode. |
3025 | if (sandbox != 0) { |
3026 | EMSG(_(e_sandbox)); |
3027 | return TRUE; |
3028 | } |
3029 | return FALSE; |
3030 | } |
3031 | |
3032 | /// Previous substitute replacement string |
3033 | static SubReplacementString old_sub = {NULL, 0, NULL}; |
3034 | |
3035 | static int global_need_beginline; // call beginline() after ":g" |
3036 | |
3037 | /// Get old substitute replacement string |
3038 | /// |
3039 | /// @param[out] ret_sub Location where old string will be saved. |
3040 | void sub_get_replacement(SubReplacementString *const ret_sub) |
3041 | FUNC_ATTR_NONNULL_ALL |
3042 | { |
3043 | *ret_sub = old_sub; |
3044 | } |
3045 | |
3046 | /// Set substitute string and timestamp |
3047 | /// |
3048 | /// @warning `sub` must be in allocated memory. It is not copied. |
3049 | /// |
3050 | /// @param[in] sub New replacement string. |
3051 | void sub_set_replacement(SubReplacementString sub) |
3052 | { |
3053 | xfree(old_sub.sub); |
3054 | if (sub.additional_elements != old_sub.additional_elements) { |
3055 | tv_list_unref(old_sub.additional_elements); |
3056 | } |
3057 | old_sub = sub; |
3058 | } |
3059 | |
3060 | /// Recognize ":%s/\n//" and turn it into a join command, which is much |
3061 | /// more efficient. |
3062 | /// |
3063 | /// @param[in] eap Ex arguments |
3064 | /// @param[in] pat Search pattern |
3065 | /// @param[in] sub Replacement string |
3066 | /// @param[in] cmd Command from :s_flags |
3067 | /// @param[in] save Save pattern to options, history |
3068 | /// |
3069 | /// @returns true if :substitute can be replaced with a join command |
3070 | static bool sub_joining_lines(exarg_T *eap, char_u *pat, char_u *sub, |
3071 | char_u *cmd, bool save) |
3072 | FUNC_ATTR_NONNULL_ARG(1, 3, 4) |
3073 | { |
3074 | // TODO(vim): find a generic solution to make line-joining operations more |
3075 | // efficient, avoid allocating a string that grows in size. |
3076 | if (pat != NULL |
3077 | && strcmp((const char *)pat, "\\n" ) == 0 |
3078 | && *sub == NUL |
3079 | && (*cmd == NUL || (cmd[1] == NUL |
3080 | && (*cmd == 'g' |
3081 | || *cmd == 'l' |
3082 | || *cmd == 'p' |
3083 | || *cmd == '#')))) { |
3084 | curwin->w_cursor.lnum = eap->line1; |
3085 | if (*cmd == 'l') { |
3086 | eap->flags = EXFLAG_LIST; |
3087 | } else if (*cmd == '#') { |
3088 | eap->flags = EXFLAG_NR; |
3089 | } else if (*cmd == 'p') { |
3090 | eap->flags = EXFLAG_PRINT; |
3091 | } |
3092 | |
3093 | // The number of lines joined is the number of lines in the range |
3094 | linenr_T joined_lines_count = eap->line2 - eap->line1 + 1 |
3095 | // plus one extra line if not at the end of file. |
3096 | + (eap->line2 < curbuf->b_ml.ml_line_count ? 1 : 0); |
3097 | if (joined_lines_count > 1) { |
3098 | do_join(joined_lines_count, FALSE, TRUE, FALSE, true); |
3099 | sub_nsubs = joined_lines_count - 1; |
3100 | sub_nlines = 1; |
3101 | do_sub_msg(false); |
3102 | ex_may_print(eap); |
3103 | } |
3104 | |
3105 | if (save) { |
3106 | if (!cmdmod.keeppatterns) { |
3107 | save_re_pat(RE_SUBST, pat, p_magic); |
3108 | } |
3109 | add_to_history(HIST_SEARCH, pat, true, NUL); |
3110 | } |
3111 | |
3112 | return true; |
3113 | } |
3114 | |
3115 | return false; |
3116 | } |
3117 | |
3118 | /// Allocate memory to store the replacement text for :substitute. |
3119 | /// |
3120 | /// Slightly more memory that is strictly necessary is allocated to reduce the |
3121 | /// frequency of memory (re)allocation. |
3122 | /// |
3123 | /// @param[in,out] new_start pointer to the memory for the replacement text |
3124 | /// @param[in] needed_len amount of memory needed |
3125 | /// |
3126 | /// @returns pointer to the end of the allocated memory |
3127 | static char_u *sub_grow_buf(char_u **new_start, int needed_len) |
3128 | FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_RET |
3129 | { |
3130 | int new_start_len = 0; |
3131 | char_u *new_end; |
3132 | if (*new_start == NULL) { |
3133 | // Get some space for a temporary buffer to do the |
3134 | // substitution into (and some extra space to avoid |
3135 | // too many calls to xmalloc()/free()). |
3136 | new_start_len = needed_len + 50; |
3137 | *new_start = xmalloc(new_start_len); |
3138 | **new_start = NUL; |
3139 | new_end = *new_start; |
3140 | } else { |
3141 | // Check if the temporary buffer is long enough to do the |
3142 | // substitution into. If not, make it larger (with a bit |
3143 | // extra to avoid too many calls to xmalloc()/free()). |
3144 | size_t len = STRLEN(*new_start); |
3145 | needed_len += len; |
3146 | if (needed_len > new_start_len) { |
3147 | new_start_len = needed_len + 50; |
3148 | *new_start = xrealloc(*new_start, new_start_len); |
3149 | } |
3150 | new_end = *new_start + len; |
3151 | } |
3152 | |
3153 | return new_end; |
3154 | } |
3155 | |
3156 | /// Parse cmd string for :substitute's {flags} and update subflags accordingly |
3157 | /// |
3158 | /// @param[in] cmd command string |
3159 | /// @param[in,out] subflags current flags defined for the :substitute command |
3160 | /// @param[in,out] which_pat pattern type from which to get default search |
3161 | /// |
3162 | /// @returns pointer to the end of the flags, which may be the end of the string |
3163 | static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags, |
3164 | int *which_pat) |
3165 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET |
3166 | { |
3167 | // Find trailing options. When '&' is used, keep old options. |
3168 | if (*cmd == '&') { |
3169 | cmd++; |
3170 | } else { |
3171 | subflags->do_all = p_gd; |
3172 | subflags->do_ask = false; |
3173 | subflags->do_error = true; |
3174 | subflags->do_print = false; |
3175 | subflags->do_list = false; |
3176 | subflags->do_count = false; |
3177 | subflags->do_number = false; |
3178 | subflags->do_ic = kSubHonorOptions; |
3179 | } |
3180 | while (*cmd) { |
3181 | // Note that 'g' and 'c' are always inverted. |
3182 | // 'r' is never inverted. |
3183 | if (*cmd == 'g') { |
3184 | subflags->do_all = !subflags->do_all; |
3185 | } else if (*cmd == 'c') { |
3186 | subflags->do_ask = !subflags->do_ask; |
3187 | } else if (*cmd == 'n') { |
3188 | subflags->do_count = true; |
3189 | } else if (*cmd == 'e') { |
3190 | subflags->do_error = !subflags->do_error; |
3191 | } else if (*cmd == 'r') { // use last used regexp |
3192 | *which_pat = RE_LAST; |
3193 | } else if (*cmd == 'p') { |
3194 | subflags->do_print = true; |
3195 | } else if (*cmd == '#') { |
3196 | subflags->do_print = true; |
3197 | subflags->do_number = true; |
3198 | } else if (*cmd == 'l') { |
3199 | subflags->do_print = true; |
3200 | subflags->do_list = true; |
3201 | } else if (*cmd == 'i') { // ignore case |
3202 | subflags->do_ic = kSubIgnoreCase; |
3203 | } else if (*cmd == 'I') { // don't ignore case |
3204 | subflags->do_ic = kSubMatchCase; |
3205 | } else { |
3206 | break; |
3207 | } |
3208 | cmd++; |
3209 | } |
3210 | if (subflags->do_count) { |
3211 | subflags->do_ask = false; |
3212 | } |
3213 | |
3214 | return cmd; |
3215 | } |
3216 | |
3217 | /// Perform a substitution from line eap->line1 to line eap->line2 using the |
3218 | /// command pointed to by eap->arg which should be of the form: |
3219 | /// |
3220 | /// /pattern/substitution/{flags} |
3221 | /// |
3222 | /// The usual escapes are supported as described in the regexp docs. |
3223 | /// |
3224 | /// @param do_buf_event If `true`, send buffer updates. |
3225 | /// @return buffer used for 'inccommand' preview |
3226 | static buf_T *do_sub(exarg_T *eap, proftime_T timeout, |
3227 | bool do_buf_event) |
3228 | { |
3229 | long i = 0; |
3230 | regmmatch_T regmatch; |
3231 | static subflags_T subflags = { |
3232 | .do_all = false, |
3233 | .do_ask = false, |
3234 | .do_count = false, |
3235 | .do_error = true, |
3236 | .do_print = false, |
3237 | .do_list = false, |
3238 | .do_number = false, |
3239 | .do_ic = kSubHonorOptions |
3240 | }; |
3241 | char_u *pat = NULL, *sub = NULL; // init for GCC |
3242 | int delimiter; |
3243 | bool has_second_delim = false; |
3244 | int sublen; |
3245 | int got_quit = false; |
3246 | int got_match = false; |
3247 | int which_pat; |
3248 | char_u *cmd = eap->arg; |
3249 | linenr_T first_line = 0; // first changed line |
3250 | linenr_T last_line= 0; // below last changed line AFTER the change |
3251 | linenr_T old_line_count = curbuf->b_ml.ml_line_count; |
3252 | char_u *sub_firstline; // allocated copy of first sub line |
3253 | bool endcolumn = false; // cursor in last column when done |
3254 | PreviewLines preview_lines = { KV_INITIAL_VALUE, 0 }; |
3255 | static int pre_src_id = 0; // Source id for the preview highlight |
3256 | static int pre_hl_id = 0; |
3257 | buf_T *orig_buf = curbuf; // save to reset highlighting |
3258 | pos_T old_cursor = curwin->w_cursor; |
3259 | int start_nsubs; |
3260 | int save_ma = 0; |
3261 | int save_b_changed = curbuf->b_changed; |
3262 | bool preview = (State & CMDPREVIEW); |
3263 | |
3264 | if (!global_busy) { |
3265 | sub_nsubs = 0; |
3266 | sub_nlines = 0; |
3267 | } |
3268 | start_nsubs = sub_nsubs; |
3269 | |
3270 | if (eap->cmdidx == CMD_tilde) |
3271 | which_pat = RE_LAST; /* use last used regexp */ |
3272 | else |
3273 | which_pat = RE_SUBST; /* use last substitute regexp */ |
3274 | |
3275 | /* new pattern and substitution */ |
3276 | if (eap->cmd[0] == 's' && *cmd != NUL && !ascii_iswhite(*cmd) |
3277 | && vim_strchr((char_u *)"0123456789cegriIp|\"" , *cmd) == NULL) { |
3278 | /* don't accept alphanumeric for separator */ |
3279 | if (isalpha(*cmd)) { |
3280 | EMSG(_("E146: Regular expressions can't be delimited by letters" )); |
3281 | return NULL; |
3282 | } |
3283 | /* |
3284 | * undocumented vi feature: |
3285 | * "\/sub/" and "\?sub?" use last used search pattern (almost like |
3286 | * //sub/r). "\&sub&" use last substitute pattern (like //sub/). |
3287 | */ |
3288 | if (*cmd == '\\') { |
3289 | ++cmd; |
3290 | if (vim_strchr((char_u *)"/?&" , *cmd) == NULL) { |
3291 | EMSG(_(e_backslash)); |
3292 | return NULL; |
3293 | } |
3294 | if (*cmd != '&') { |
3295 | which_pat = RE_SEARCH; // use last '/' pattern |
3296 | } |
3297 | pat = (char_u *)"" ; // empty search pattern |
3298 | delimiter = *cmd++; // remember delimiter character |
3299 | has_second_delim = true; |
3300 | } else { // find the end of the regexp |
3301 | which_pat = RE_LAST; // use last used regexp |
3302 | delimiter = *cmd++; // remember delimiter character |
3303 | pat = cmd; // remember start of search pat |
3304 | cmd = skip_regexp(cmd, delimiter, p_magic, &eap->arg); |
3305 | if (cmd[0] == delimiter) { // end delimiter found |
3306 | *cmd++ = NUL; // replace it with a NUL |
3307 | has_second_delim = true; |
3308 | } |
3309 | } |
3310 | |
3311 | /* |
3312 | * Small incompatibility: vi sees '\n' as end of the command, but in |
3313 | * Vim we want to use '\n' to find/substitute a NUL. |
3314 | */ |
3315 | sub = cmd; /* remember the start of the substitution */ |
3316 | |
3317 | while (cmd[0]) { |
3318 | if (cmd[0] == delimiter) { /* end delimiter found */ |
3319 | *cmd++ = NUL; /* replace it with a NUL */ |
3320 | break; |
3321 | } |
3322 | if (cmd[0] == '\\' && cmd[1] != 0) { // skip escaped characters |
3323 | cmd++; |
3324 | } |
3325 | MB_PTR_ADV(cmd); |
3326 | } |
3327 | |
3328 | if (!eap->skip && !preview) { |
3329 | sub_set_replacement((SubReplacementString) { |
3330 | .sub = xstrdup((char *) sub), |
3331 | .timestamp = os_time(), |
3332 | .additional_elements = NULL, |
3333 | }); |
3334 | } |
3335 | } else if (!eap->skip) { /* use previous pattern and substitution */ |
3336 | if (old_sub.sub == NULL) { /* there is no previous command */ |
3337 | EMSG(_(e_nopresub)); |
3338 | return NULL; |
3339 | } |
3340 | pat = NULL; /* search_regcomp() will use previous pattern */ |
3341 | sub = (char_u *) old_sub.sub; |
3342 | |
3343 | /* Vi compatibility quirk: repeating with ":s" keeps the cursor in the |
3344 | * last column after using "$". */ |
3345 | endcolumn = (curwin->w_curswant == MAXCOL); |
3346 | } |
3347 | |
3348 | if (sub != NULL && sub_joining_lines(eap, pat, sub, cmd, !preview)) { |
3349 | return NULL; |
3350 | } |
3351 | |
3352 | cmd = sub_parse_flags(cmd, &subflags, &which_pat); |
3353 | |
3354 | bool save_do_all = subflags.do_all; // remember user specified 'g' flag |
3355 | bool save_do_ask = subflags.do_ask; // remember user specified 'c' flag |
3356 | |
3357 | // check for a trailing count |
3358 | cmd = skipwhite(cmd); |
3359 | if (ascii_isdigit(*cmd)) { |
3360 | i = getdigits_long(&cmd, true, 0); |
3361 | if (i <= 0 && !eap->skip && subflags.do_error) { |
3362 | EMSG(_(e_zerocount)); |
3363 | return NULL; |
3364 | } |
3365 | eap->line1 = eap->line2; |
3366 | eap->line2 += i - 1; |
3367 | if (eap->line2 > curbuf->b_ml.ml_line_count) |
3368 | eap->line2 = curbuf->b_ml.ml_line_count; |
3369 | } |
3370 | |
3371 | /* |
3372 | * check for trailing command or garbage |
3373 | */ |
3374 | cmd = skipwhite(cmd); |
3375 | if (*cmd && *cmd != '"') { /* if not end-of-line or comment */ |
3376 | eap->nextcmd = check_nextcmd(cmd); |
3377 | if (eap->nextcmd == NULL) { |
3378 | EMSG(_(e_trailing)); |
3379 | return NULL; |
3380 | } |
3381 | } |
3382 | |
3383 | if (eap->skip) { // not executing commands, only parsing |
3384 | return NULL; |
3385 | } |
3386 | |
3387 | if (!subflags.do_count && !MODIFIABLE(curbuf)) { |
3388 | // Substitution is not allowed in non-'modifiable' buffer |
3389 | EMSG(_(e_modifiable)); |
3390 | return NULL; |
3391 | } |
3392 | |
3393 | if (search_regcomp(pat, RE_SUBST, which_pat, (preview ? 0 : SEARCH_HIS), |
3394 | ®match) == FAIL) { |
3395 | if (subflags.do_error) { |
3396 | EMSG(_(e_invcmd)); |
3397 | } |
3398 | return NULL; |
3399 | } |
3400 | |
3401 | // the 'i' or 'I' flag overrules 'ignorecase' and 'smartcase' |
3402 | if (subflags.do_ic == kSubIgnoreCase) { |
3403 | regmatch.rmm_ic = true; |
3404 | } else if (subflags.do_ic == kSubMatchCase) { |
3405 | regmatch.rmm_ic = false; |
3406 | } |
3407 | |
3408 | sub_firstline = NULL; |
3409 | |
3410 | /* |
3411 | * ~ in the substitute pattern is replaced with the old pattern. |
3412 | * We do it here once to avoid it to be replaced over and over again. |
3413 | * But don't do it when it starts with "\=", then it's an expression. |
3414 | */ |
3415 | if (!(sub[0] == '\\' && sub[1] == '=')) |
3416 | sub = regtilde(sub, p_magic); |
3417 | |
3418 | // Check for a match on each line. |
3419 | // If preview: limit to max('cmdwinheight', viewport). |
3420 | linenr_T line2 = eap->line2; |
3421 | for (linenr_T lnum = eap->line1; |
3422 | lnum <= line2 && !got_quit && !aborting() |
3423 | && (!preview || preview_lines.lines_needed <= (linenr_T)p_cwh |
3424 | || lnum <= curwin->w_botline); |
3425 | lnum++) { |
3426 | long nmatch = vim_regexec_multi(®match, curwin, curbuf, lnum, |
3427 | (colnr_T)0, NULL, NULL); |
3428 | if (nmatch) { |
3429 | colnr_T copycol; |
3430 | colnr_T matchcol; |
3431 | colnr_T prev_matchcol = MAXCOL; |
3432 | char_u *new_end, *new_start = NULL; |
3433 | char_u *p1; |
3434 | int did_sub = FALSE; |
3435 | int lastone; |
3436 | long nmatch_tl = 0; // nr of lines matched below lnum |
3437 | int do_again; // do it again after joining lines |
3438 | int skip_match = false; |
3439 | linenr_T sub_firstlnum; // nr of first sub line |
3440 | |
3441 | /* |
3442 | * The new text is build up step by step, to avoid too much |
3443 | * copying. There are these pieces: |
3444 | * sub_firstline The old text, unmodified. |
3445 | * copycol Column in the old text where we started |
3446 | * looking for a match; from here old text still |
3447 | * needs to be copied to the new text. |
3448 | * matchcol Column number of the old text where to look |
3449 | * for the next match. It's just after the |
3450 | * previous match or one further. |
3451 | * prev_matchcol Column just after the previous match (if any). |
3452 | * Mostly equal to matchcol, except for the first |
3453 | * match and after skipping an empty match. |
3454 | * regmatch.*pos Where the pattern matched in the old text. |
3455 | * new_start The new text, all that has been produced so |
3456 | * far. |
3457 | * new_end The new text, where to append new text. |
3458 | * |
3459 | * lnum The line number where we found the start of |
3460 | * the match. Can be below the line we searched |
3461 | * when there is a \n before a \zs in the |
3462 | * pattern. |
3463 | * sub_firstlnum The line number in the buffer where to look |
3464 | * for a match. Can be different from "lnum" |
3465 | * when the pattern or substitute string contains |
3466 | * line breaks. |
3467 | * |
3468 | * Special situations: |
3469 | * - When the substitute string contains a line break, the part up |
3470 | * to the line break is inserted in the text, but the copy of |
3471 | * the original line is kept. "sub_firstlnum" is adjusted for |
3472 | * the inserted lines. |
3473 | * - When the matched pattern contains a line break, the old line |
3474 | * is taken from the line at the end of the pattern. The lines |
3475 | * in the match are deleted later, "sub_firstlnum" is adjusted |
3476 | * accordingly. |
3477 | * |
3478 | * The new text is built up in new_start[]. It has some extra |
3479 | * room to avoid using xmalloc()/free() too often. |
3480 | * |
3481 | * Make a copy of the old line, so it won't be taken away when |
3482 | * updating the screen or handling a multi-line match. The "old_" |
3483 | * pointers point into this copy. |
3484 | */ |
3485 | sub_firstlnum = lnum; |
3486 | copycol = 0; |
3487 | matchcol = 0; |
3488 | |
3489 | /* At first match, remember current cursor position. */ |
3490 | if (!got_match) { |
3491 | setpcmark(); |
3492 | got_match = TRUE; |
3493 | } |
3494 | |
3495 | /* |
3496 | * Loop until nothing more to replace in this line. |
3497 | * 1. Handle match with empty string. |
3498 | * 2. If subflags.do_ask is set, ask for confirmation. |
3499 | * 3. substitute the string. |
3500 | * 4. if subflags.do_all is set, find next match |
3501 | * 5. break if there isn't another match in this line |
3502 | */ |
3503 | for (;; ) { |
3504 | SubResult current_match = { |
3505 | .start = { 0, 0 }, |
3506 | .end = { 0, 0 }, |
3507 | .pre_match = 0, |
3508 | }; |
3509 | // lnum is where the match start, but maybe not the pattern match, |
3510 | // since we can have \n before \zs in the pattern |
3511 | |
3512 | // Advance "lnum" to the line where the match starts. The |
3513 | // match does not start in the first line when there is a line |
3514 | // break before \zs. |
3515 | if (regmatch.startpos[0].lnum > 0) { |
3516 | current_match.pre_match = lnum; |
3517 | lnum += regmatch.startpos[0].lnum; |
3518 | sub_firstlnum += regmatch.startpos[0].lnum; |
3519 | nmatch -= regmatch.startpos[0].lnum; |
3520 | XFREE_CLEAR(sub_firstline); |
3521 | } |
3522 | |
3523 | // Now we're at the line where the pattern match starts |
3524 | // Note: If not first match on a line, column can't be known here |
3525 | current_match.start.lnum = sub_firstlnum; |
3526 | |
3527 | if (sub_firstline == NULL) { |
3528 | sub_firstline = vim_strsave(ml_get(sub_firstlnum)); |
3529 | } |
3530 | |
3531 | /* Save the line number of the last change for the final |
3532 | * cursor position (just like Vi). */ |
3533 | curwin->w_cursor.lnum = lnum; |
3534 | do_again = FALSE; |
3535 | |
3536 | /* |
3537 | * 1. Match empty string does not count, except for first |
3538 | * match. This reproduces the strange vi behaviour. |
3539 | * This also catches endless loops. |
3540 | */ |
3541 | if (matchcol == prev_matchcol |
3542 | && regmatch.endpos[0].lnum == 0 |
3543 | && matchcol == regmatch.endpos[0].col) { |
3544 | if (sub_firstline[matchcol] == NUL) |
3545 | /* We already were at the end of the line. Don't look |
3546 | * for a match in this line again. */ |
3547 | skip_match = TRUE; |
3548 | else { |
3549 | /* search for a match at next column */ |
3550 | if (has_mbyte) |
3551 | matchcol += mb_ptr2len(sub_firstline + matchcol); |
3552 | else |
3553 | ++matchcol; |
3554 | } |
3555 | // match will be pushed to preview_lines, bring it into a proper state |
3556 | current_match.start.col = matchcol; |
3557 | current_match.end.lnum = sub_firstlnum; |
3558 | current_match.end.col = matchcol; |
3559 | goto skip; |
3560 | } |
3561 | |
3562 | /* Normally we continue searching for a match just after the |
3563 | * previous match. */ |
3564 | matchcol = regmatch.endpos[0].col; |
3565 | prev_matchcol = matchcol; |
3566 | |
3567 | // 2. If subflags.do_count is set only increase the counter. |
3568 | // If do_ask is set, ask for confirmation. |
3569 | if (subflags.do_count) { |
3570 | // For a multi-line match, put matchcol at the NUL at |
3571 | // the end of the line and set nmatch to one, so that |
3572 | // we continue looking for a match on the next line. |
3573 | // Avoids that ":s/\nB\@=//gc" get stuck. |
3574 | if (nmatch > 1) { |
3575 | matchcol = (colnr_T)STRLEN(sub_firstline); |
3576 | nmatch = 1; |
3577 | skip_match = TRUE; |
3578 | } |
3579 | sub_nsubs++; |
3580 | did_sub = TRUE; |
3581 | /* Skip the substitution, unless an expression is used, |
3582 | * then it is evaluated in the sandbox. */ |
3583 | if (!(sub[0] == '\\' && sub[1] == '=')) |
3584 | goto skip; |
3585 | } |
3586 | |
3587 | if (subflags.do_ask && !preview) { |
3588 | int typed = 0; |
3589 | |
3590 | /* change State to CONFIRM, so that the mouse works |
3591 | * properly */ |
3592 | int save_State = State; |
3593 | State = CONFIRM; |
3594 | setmouse(); /* disable mouse in xterm */ |
3595 | curwin->w_cursor.col = regmatch.startpos[0].col; |
3596 | |
3597 | if (curwin->w_p_crb) { |
3598 | do_check_cursorbind(); |
3599 | } |
3600 | |
3601 | /* When 'cpoptions' contains "u" don't sync undo when |
3602 | * asking for confirmation. */ |
3603 | if (vim_strchr(p_cpo, CPO_UNDO) != NULL) |
3604 | ++no_u_sync; |
3605 | |
3606 | /* |
3607 | * Loop until 'y', 'n', 'q', CTRL-E or CTRL-Y typed. |
3608 | */ |
3609 | while (subflags.do_ask) { |
3610 | if (exmode_active) { |
3611 | char_u *resp; |
3612 | colnr_T sc, ec; |
3613 | |
3614 | print_line_no_prefix(lnum, subflags.do_number, subflags.do_list); |
3615 | |
3616 | getvcol(curwin, &curwin->w_cursor, &sc, NULL, NULL); |
3617 | curwin->w_cursor.col = regmatch.endpos[0].col - 1; |
3618 | if (curwin->w_cursor.col < 0) { |
3619 | curwin->w_cursor.col = 0; |
3620 | } |
3621 | getvcol(curwin, &curwin->w_cursor, NULL, NULL, &ec); |
3622 | if (subflags.do_number || curwin->w_p_nu) { |
3623 | int numw = number_width(curwin) + 1; |
3624 | sc += numw; |
3625 | ec += numw; |
3626 | } |
3627 | msg_start(); |
3628 | for (i = 0; i < (long)sc; ++i) |
3629 | msg_putchar(' '); |
3630 | for (; i <= (long)ec; ++i) |
3631 | msg_putchar('^'); |
3632 | |
3633 | resp = getexmodeline('?', NULL, 0); |
3634 | if (resp != NULL) { |
3635 | typed = *resp; |
3636 | xfree(resp); |
3637 | } |
3638 | } else { |
3639 | char_u *orig_line = NULL; |
3640 | int len_change = 0; |
3641 | int save_p_fen = curwin->w_p_fen; |
3642 | |
3643 | curwin->w_p_fen = FALSE; |
3644 | /* Invert the matched string. |
3645 | * Remove the inversion afterwards. */ |
3646 | int temp = RedrawingDisabled; |
3647 | RedrawingDisabled = 0; |
3648 | |
3649 | if (new_start != NULL) { |
3650 | /* There already was a substitution, we would |
3651 | * like to show this to the user. We cannot |
3652 | * really update the line, it would change |
3653 | * what matches. Temporarily replace the line |
3654 | * and change it back afterwards. */ |
3655 | orig_line = vim_strsave(ml_get(lnum)); |
3656 | char_u *new_line = concat_str(new_start, sub_firstline + copycol); |
3657 | |
3658 | // Position the cursor relative to the end of the line, the |
3659 | // previous substitute may have inserted or deleted characters |
3660 | // before the cursor. |
3661 | len_change = (int)STRLEN(new_line) - (int)STRLEN(orig_line); |
3662 | curwin->w_cursor.col += len_change; |
3663 | ml_replace(lnum, new_line, false); |
3664 | } |
3665 | |
3666 | search_match_lines = regmatch.endpos[0].lnum |
3667 | - regmatch.startpos[0].lnum; |
3668 | search_match_endcol = regmatch.endpos[0].col |
3669 | + len_change; |
3670 | highlight_match = TRUE; |
3671 | |
3672 | update_topline(); |
3673 | validate_cursor(); |
3674 | update_screen(SOME_VALID); |
3675 | highlight_match = FALSE; |
3676 | redraw_later(SOME_VALID); |
3677 | |
3678 | curwin->w_p_fen = save_p_fen; |
3679 | if (msg_row == Rows - 1) |
3680 | msg_didout = FALSE; /* avoid a scroll-up */ |
3681 | msg_starthere(); |
3682 | i = msg_scroll; |
3683 | msg_scroll = 0; /* truncate msg when |
3684 | needed */ |
3685 | msg_no_more = true; |
3686 | msg_ext_set_kind("confirm_sub" ); |
3687 | smsg_attr(HL_ATTR(HLF_R), // Same highlight as wait_return(). |
3688 | _("replace with %s (y/n/a/q/l/^E/^Y)?" ), sub); |
3689 | msg_no_more = FALSE; |
3690 | msg_scroll = i; |
3691 | showruler(TRUE); |
3692 | ui_cursor_goto(msg_row, msg_col); |
3693 | RedrawingDisabled = temp; |
3694 | |
3695 | no_mapping++; // don't map this key |
3696 | typed = plain_vgetc(); |
3697 | no_mapping--; |
3698 | |
3699 | /* clear the question */ |
3700 | msg_didout = FALSE; /* don't scroll up */ |
3701 | msg_col = 0; |
3702 | gotocmdline(TRUE); |
3703 | |
3704 | // restore the line |
3705 | if (orig_line != NULL) { |
3706 | ml_replace(lnum, orig_line, false); |
3707 | } |
3708 | } |
3709 | |
3710 | need_wait_return = false; // no hit-return prompt |
3711 | if (typed == 'q' || typed == ESC || typed == Ctrl_C) { |
3712 | got_quit = true; |
3713 | break; |
3714 | } |
3715 | if (typed == 'n') |
3716 | break; |
3717 | if (typed == 'y') |
3718 | break; |
3719 | if (typed == 'l') { |
3720 | // last: replace and then stop |
3721 | subflags.do_all = false; |
3722 | line2 = lnum; |
3723 | break; |
3724 | } |
3725 | if (typed == 'a') { |
3726 | subflags.do_ask = false; |
3727 | break; |
3728 | } |
3729 | if (typed == Ctrl_E) |
3730 | scrollup_clamp(); |
3731 | else if (typed == Ctrl_Y) |
3732 | scrolldown_clamp(); |
3733 | } |
3734 | State = save_State; |
3735 | setmouse(); |
3736 | if (vim_strchr(p_cpo, CPO_UNDO) != NULL) |
3737 | --no_u_sync; |
3738 | |
3739 | if (typed == 'n') { |
3740 | /* For a multi-line match, put matchcol at the NUL at |
3741 | * the end of the line and set nmatch to one, so that |
3742 | * we continue looking for a match on the next line. |
3743 | * Avoids that ":%s/\nB\@=//gc" and ":%s/\n/,\r/gc" |
3744 | * get stuck when pressing 'n'. */ |
3745 | if (nmatch > 1) { |
3746 | matchcol = (colnr_T)STRLEN(sub_firstline); |
3747 | skip_match = TRUE; |
3748 | } |
3749 | goto skip; |
3750 | } |
3751 | if (got_quit) |
3752 | goto skip; |
3753 | } |
3754 | |
3755 | /* Move the cursor to the start of the match, so that we can |
3756 | * use "\=col("."). */ |
3757 | curwin->w_cursor.col = regmatch.startpos[0].col; |
3758 | |
3759 | // When the match included the "$" of the last line it may |
3760 | // go beyond the last line of the buffer. |
3761 | if (nmatch > curbuf->b_ml.ml_line_count - sub_firstlnum + 1) { |
3762 | nmatch = curbuf->b_ml.ml_line_count - sub_firstlnum + 1; |
3763 | current_match.end.lnum = sub_firstlnum + nmatch; |
3764 | skip_match = true; |
3765 | } |
3766 | |
3767 | #define ADJUST_SUB_FIRSTLNUM() \ |
3768 | do { \ |
3769 | /* For a multi-line match, make a copy of the last matched */ \ |
3770 | /* line and continue in that one. */ \ |
3771 | if (nmatch > 1) { \ |
3772 | sub_firstlnum += nmatch - 1; \ |
3773 | xfree(sub_firstline); \ |
3774 | sub_firstline = vim_strsave(ml_get(sub_firstlnum)); \ |
3775 | /* When going beyond the last line, stop substituting. */ \ |
3776 | if (sub_firstlnum <= line2) { \ |
3777 | do_again = true; \ |
3778 | } else { \ |
3779 | subflags.do_all = false; \ |
3780 | } \ |
3781 | } \ |
3782 | if (skip_match) { \ |
3783 | /* Already hit end of the buffer, sub_firstlnum is one */ \ |
3784 | /* less than what it ought to be. */ \ |
3785 | xfree(sub_firstline); \ |
3786 | sub_firstline = vim_strsave((char_u *)""); \ |
3787 | copycol = 0; \ |
3788 | } \ |
3789 | } while (0) |
3790 | |
3791 | // Save the line numbers for the preview buffer |
3792 | // NOTE: If the pattern matches a final newline, the next line will |
3793 | // be shown also, but should not be highlighted. Intentional for now. |
3794 | if (preview && !has_second_delim) { |
3795 | current_match.start.col = regmatch.startpos[0].col; |
3796 | if (current_match.end.lnum == 0) { |
3797 | current_match.end.lnum = sub_firstlnum + nmatch - 1; |
3798 | } |
3799 | current_match.end.col = regmatch.endpos[0].col; |
3800 | |
3801 | ADJUST_SUB_FIRSTLNUM(); |
3802 | lnum += nmatch - 1; |
3803 | |
3804 | goto skip; |
3805 | } |
3806 | |
3807 | // 3. Substitute the string. During 'inccommand' preview only do this if |
3808 | // there is a replace pattern. |
3809 | if (!preview || has_second_delim) { |
3810 | save_ma = curbuf->b_p_ma; |
3811 | if (subflags.do_count) { |
3812 | // prevent accidentally changing the buffer by a function |
3813 | curbuf->b_p_ma = false; |
3814 | sandbox++; |
3815 | } |
3816 | // Save flags for recursion. They can change for e.g. |
3817 | // :s/^/\=execute("s#^##gn") |
3818 | subflags_T subflags_save = subflags; |
3819 | // get length of substitution part |
3820 | sublen = vim_regsub_multi(®match, |
3821 | sub_firstlnum - regmatch.startpos[0].lnum, |
3822 | sub, sub_firstline, false, p_magic, true); |
3823 | // If getting the substitute string caused an error, don't do |
3824 | // the replacement. |
3825 | // Don't keep flags set by a recursive call |
3826 | subflags = subflags_save; |
3827 | if (aborting() || subflags.do_count) { |
3828 | curbuf->b_p_ma = save_ma; |
3829 | if (sandbox > 0) { |
3830 | sandbox--; |
3831 | } |
3832 | goto skip; |
3833 | } |
3834 | |
3835 | // Need room for: |
3836 | // - result so far in new_start (not for first sub in line) |
3837 | // - original text up to match |
3838 | // - length of substituted part |
3839 | // - original text after match |
3840 | if (nmatch == 1) { |
3841 | p1 = sub_firstline; |
3842 | } else { |
3843 | p1 = ml_get(sub_firstlnum + nmatch - 1); |
3844 | nmatch_tl += nmatch - 1; |
3845 | } |
3846 | size_t copy_len = regmatch.startpos[0].col - copycol; |
3847 | new_end = sub_grow_buf(&new_start, |
3848 | (STRLEN(p1) - regmatch.endpos[0].col) |
3849 | + copy_len + sublen + 1); |
3850 | |
3851 | // copy the text up to the part that matched |
3852 | memmove(new_end, sub_firstline + copycol, (size_t)copy_len); |
3853 | new_end += copy_len; |
3854 | |
3855 | // Finally, at this point we can know where the match actually will |
3856 | // start in the new text |
3857 | current_match.start.col = new_end - new_start; |
3858 | |
3859 | (void)vim_regsub_multi(®match, |
3860 | sub_firstlnum - regmatch.startpos[0].lnum, |
3861 | sub, new_end, true, p_magic, true); |
3862 | sub_nsubs++; |
3863 | did_sub = true; |
3864 | |
3865 | // Move the cursor to the start of the line, to avoid that it |
3866 | // is beyond the end of the line after the substitution. |
3867 | curwin->w_cursor.col = 0; |
3868 | |
3869 | // Remember next character to be copied. |
3870 | copycol = regmatch.endpos[0].col; |
3871 | |
3872 | ADJUST_SUB_FIRSTLNUM(); |
3873 | |
3874 | // Now the trick is to replace CTRL-M chars with a real line |
3875 | // break. This would make it impossible to insert a CTRL-M in |
3876 | // the text. The line break can be avoided by preceding the |
3877 | // CTRL-M with a backslash. To be able to insert a backslash, |
3878 | // they must be doubled in the string and are halved here. |
3879 | // That is Vi compatible. |
3880 | for (p1 = new_end; *p1; p1++) { |
3881 | if (p1[0] == '\\' && p1[1] != NUL) { // remove backslash |
3882 | STRMOVE(p1, p1 + 1); |
3883 | } else if (*p1 == CAR) { |
3884 | if (u_inssub(lnum) == OK) { // prepare for undo |
3885 | *p1 = NUL; // truncate up to the CR |
3886 | ml_append(lnum - 1, new_start, |
3887 | (colnr_T)(p1 - new_start + 1), false); |
3888 | mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false); |
3889 | if (subflags.do_ask) { |
3890 | appended_lines(lnum - 1, 1L); |
3891 | } else { |
3892 | if (first_line == 0) { |
3893 | first_line = lnum; |
3894 | } |
3895 | last_line = lnum + 1; |
3896 | } |
3897 | // All line numbers increase. |
3898 | sub_firstlnum++; |
3899 | lnum++; |
3900 | line2++; |
3901 | // move the cursor to the new line, like Vi |
3902 | curwin->w_cursor.lnum++; |
3903 | // copy the rest |
3904 | STRMOVE(new_start, p1 + 1); |
3905 | p1 = new_start - 1; |
3906 | } |
3907 | } else if (has_mbyte) { |
3908 | p1 += (*mb_ptr2len)(p1) - 1; |
3909 | } |
3910 | } |
3911 | current_match.end.col = STRLEN(new_start); |
3912 | current_match.end.lnum = lnum; |
3913 | } |
3914 | |
3915 | // 4. If subflags.do_all is set, find next match. |
3916 | // Prevent endless loop with patterns that match empty |
3917 | // strings, e.g. :s/$/pat/g or :s/[a-z]* /(&)/g. |
3918 | // But ":s/\n/#/" is OK. |
3919 | skip: |
3920 | /* We already know that we did the last subst when we are at |
3921 | * the end of the line, except that a pattern like |
3922 | * "bar\|\nfoo" may match at the NUL. "lnum" can be below |
3923 | * "line2" when there is a \zs in the pattern after a line |
3924 | * break. */ |
3925 | lastone = (skip_match |
3926 | || got_int |
3927 | || got_quit |
3928 | || lnum > line2 |
3929 | || !(subflags.do_all || do_again) |
3930 | || (sub_firstline[matchcol] == NUL && nmatch <= 1 |
3931 | && !re_multiline(regmatch.regprog))); |
3932 | nmatch = -1; |
3933 | |
3934 | /* |
3935 | * Replace the line in the buffer when needed. This is |
3936 | * skipped when there are more matches. |
3937 | * The check for nmatch_tl is needed for when multi-line |
3938 | * matching must replace the lines before trying to do another |
3939 | * match, otherwise "\@<=" won't work. |
3940 | * When the match starts below where we start searching also |
3941 | * need to replace the line first (using \zs after \n). |
3942 | */ |
3943 | if (lastone |
3944 | || nmatch_tl > 0 |
3945 | || (nmatch = vim_regexec_multi(®match, curwin, |
3946 | curbuf, sub_firstlnum, |
3947 | matchcol, NULL, NULL)) == 0 |
3948 | || regmatch.startpos[0].lnum > 0) { |
3949 | if (new_start != NULL) { |
3950 | /* |
3951 | * Copy the rest of the line, that didn't match. |
3952 | * "matchcol" has to be adjusted, we use the end of |
3953 | * the line as reference, because the substitute may |
3954 | * have changed the number of characters. Same for |
3955 | * "prev_matchcol". |
3956 | */ |
3957 | STRCAT(new_start, sub_firstline + copycol); |
3958 | matchcol = (colnr_T)STRLEN(sub_firstline) - matchcol; |
3959 | prev_matchcol = (colnr_T)STRLEN(sub_firstline) |
3960 | - prev_matchcol; |
3961 | |
3962 | if (u_savesub(lnum) != OK) { |
3963 | break; |
3964 | } |
3965 | ml_replace(lnum, new_start, true); |
3966 | |
3967 | if (nmatch_tl > 0) { |
3968 | /* |
3969 | * Matched lines have now been substituted and are |
3970 | * useless, delete them. The part after the match |
3971 | * has been appended to new_start, we don't need |
3972 | * it in the buffer. |
3973 | */ |
3974 | ++lnum; |
3975 | if (u_savedel(lnum, nmatch_tl) != OK) |
3976 | break; |
3977 | for (i = 0; i < nmatch_tl; i++) { |
3978 | ml_delete(lnum, false); |
3979 | } |
3980 | mark_adjust(lnum, lnum + nmatch_tl - 1, |
3981 | (long)MAXLNUM, -nmatch_tl, false); |
3982 | if (subflags.do_ask) { |
3983 | deleted_lines(lnum, nmatch_tl); |
3984 | } |
3985 | lnum--; |
3986 | line2 -= nmatch_tl; // nr of lines decreases |
3987 | nmatch_tl = 0; |
3988 | } |
3989 | |
3990 | /* When asking, undo is saved each time, must also set |
3991 | * changed flag each time. */ |
3992 | if (subflags.do_ask) { |
3993 | changed_bytes(lnum, 0); |
3994 | } else { |
3995 | if (first_line == 0) { |
3996 | first_line = lnum; |
3997 | } |
3998 | last_line = lnum + 1; |
3999 | } |
4000 | |
4001 | sub_firstlnum = lnum; |
4002 | xfree(sub_firstline); /* free the temp buffer */ |
4003 | sub_firstline = new_start; |
4004 | new_start = NULL; |
4005 | matchcol = (colnr_T)STRLEN(sub_firstline) - matchcol; |
4006 | prev_matchcol = (colnr_T)STRLEN(sub_firstline) |
4007 | - prev_matchcol; |
4008 | copycol = 0; |
4009 | } |
4010 | if (nmatch == -1 && !lastone) |
4011 | nmatch = vim_regexec_multi(®match, curwin, curbuf, |
4012 | sub_firstlnum, matchcol, NULL, NULL); |
4013 | |
4014 | /* |
4015 | * 5. break if there isn't another match in this line |
4016 | */ |
4017 | if (nmatch <= 0) { |
4018 | /* If the match found didn't start where we were |
4019 | * searching, do the next search in the line where we |
4020 | * found the match. */ |
4021 | if (nmatch == -1) |
4022 | lnum -= regmatch.startpos[0].lnum; |
4023 | |
4024 | #define PUSH_PREVIEW_LINES() \ |
4025 | do { \ |
4026 | linenr_T match_lines = current_match.end.lnum \ |
4027 | - current_match.start.lnum +1; \ |
4028 | if (preview_lines.subresults.size > 0) { \ |
4029 | linenr_T last = kv_last(preview_lines.subresults).end.lnum; \ |
4030 | if (last == current_match.start.lnum) { \ |
4031 | preview_lines.lines_needed += match_lines - 1; \ |
4032 | } \ |
4033 | } else { \ |
4034 | preview_lines.lines_needed += match_lines; \ |
4035 | } \ |
4036 | kv_push(preview_lines.subresults, current_match); \ |
4037 | } while (0) |
4038 | |
4039 | // Push the match to preview_lines. |
4040 | PUSH_PREVIEW_LINES(); |
4041 | |
4042 | break; |
4043 | } |
4044 | } |
4045 | // Push the match to preview_lines. |
4046 | PUSH_PREVIEW_LINES(); |
4047 | |
4048 | line_breakcheck(); |
4049 | } |
4050 | |
4051 | if (did_sub) { |
4052 | sub_nlines++; |
4053 | } |
4054 | xfree(new_start); // for when substitute was cancelled |
4055 | XFREE_CLEAR(sub_firstline); // free the copy of the original line |
4056 | } |
4057 | |
4058 | line_breakcheck(); |
4059 | |
4060 | if (profile_passed_limit(timeout)) { |
4061 | got_quit = true; |
4062 | } |
4063 | } |
4064 | |
4065 | if (first_line != 0) { |
4066 | /* Need to subtract the number of added lines from "last_line" to get |
4067 | * the line number before the change (same as adding the number of |
4068 | * deleted lines). */ |
4069 | i = curbuf->b_ml.ml_line_count - old_line_count; |
4070 | changed_lines(first_line, 0, last_line - i, i, false); |
4071 | |
4072 | int64_t num_added = last_line - first_line; |
4073 | int64_t num_removed = num_added - i; |
4074 | buf_updates_send_changes(curbuf, first_line, num_added, num_removed, |
4075 | do_buf_event); |
4076 | } |
4077 | |
4078 | xfree(sub_firstline); /* may have to free allocated copy of the line */ |
4079 | |
4080 | // ":s/pat//n" doesn't move the cursor |
4081 | if (subflags.do_count) { |
4082 | curwin->w_cursor = old_cursor; |
4083 | } |
4084 | |
4085 | if (sub_nsubs > start_nsubs) { |
4086 | /* Set the '[ and '] marks. */ |
4087 | curbuf->b_op_start.lnum = eap->line1; |
4088 | curbuf->b_op_end.lnum = line2; |
4089 | curbuf->b_op_start.col = curbuf->b_op_end.col = 0; |
4090 | |
4091 | if (!global_busy) { |
4092 | // when interactive leave cursor on the match |
4093 | if (!subflags.do_ask) { |
4094 | if (endcolumn) { |
4095 | coladvance((colnr_T)MAXCOL); |
4096 | } else { |
4097 | beginline(BL_WHITE | BL_FIX); |
4098 | } |
4099 | } |
4100 | if (!preview && !do_sub_msg(subflags.do_count) && subflags.do_ask) { |
4101 | MSG("" ); |
4102 | } |
4103 | } else { |
4104 | global_need_beginline = true; |
4105 | } |
4106 | if (subflags.do_print) { |
4107 | print_line(curwin->w_cursor.lnum, subflags.do_number, subflags.do_list); |
4108 | } |
4109 | } else if (!global_busy) { |
4110 | if (got_int) { |
4111 | // interrupted |
4112 | EMSG(_(e_interr)); |
4113 | } else if (got_match) { |
4114 | // did find something but nothing substituted |
4115 | MSG("" ); |
4116 | } else if (subflags.do_error) { |
4117 | // nothing found |
4118 | EMSG2(_(e_patnotf2), get_search_pat()); |
4119 | } |
4120 | } |
4121 | |
4122 | if (subflags.do_ask && hasAnyFolding(curwin)) { |
4123 | // Cursor position may require updating |
4124 | changed_window_setting(); |
4125 | } |
4126 | |
4127 | vim_regfree(regmatch.regprog); |
4128 | |
4129 | // Restore the flag values, they can be used for ":&&". |
4130 | subflags.do_all = save_do_all; |
4131 | subflags.do_ask = save_do_ask; |
4132 | |
4133 | // Show 'inccommand' preview if there are matched lines. |
4134 | buf_T *preview_buf = NULL; |
4135 | size_t subsize = preview_lines.subresults.size; |
4136 | if (preview && !aborting()) { |
4137 | if (got_quit || profile_passed_limit(timeout)) { // Too slow, disable. |
4138 | set_string_option_direct((char_u *)"icm" , -1, (char_u *)"" , OPT_FREE, |
4139 | SID_NONE); |
4140 | } else if (*p_icm != NUL && pat != NULL) { |
4141 | if (pre_src_id == 0) { |
4142 | // Get a unique new src_id, saved in a static |
4143 | pre_src_id = bufhl_add_hl(NULL, 0, -1, 0, 0, 0); |
4144 | } |
4145 | if (pre_hl_id == 0) { |
4146 | pre_hl_id = syn_check_group((char_u *)S_LEN("Substitute" )); |
4147 | } |
4148 | curbuf->b_changed = save_b_changed; // preserve 'modified' during preview |
4149 | preview_buf = show_sub(eap, old_cursor, &preview_lines, |
4150 | pre_hl_id, pre_src_id); |
4151 | if (subsize > 0) { |
4152 | bufhl_clear_line_range(orig_buf, pre_src_id, eap->line1, |
4153 | kv_last(preview_lines.subresults).end.lnum); |
4154 | } |
4155 | } |
4156 | } |
4157 | |
4158 | kv_destroy(preview_lines.subresults); |
4159 | |
4160 | return preview_buf; |
4161 | #undef ADJUST_SUB_FIRSTLNUM |
4162 | #undef PUSH_PREVIEW_LINES |
4163 | } // NOLINT(readability/fn_size) |
4164 | |
4165 | /* |
4166 | * Give message for number of substitutions. |
4167 | * Can also be used after a ":global" command. |
4168 | * Return TRUE if a message was given. |
4169 | */ |
4170 | bool |
4171 | do_sub_msg ( |
4172 | bool count_only /* used 'n' flag for ":s" */ |
4173 | ) |
4174 | { |
4175 | /* |
4176 | * Only report substitutions when: |
4177 | * - more than 'report' substitutions |
4178 | * - command was typed by user, or number of changed lines > 'report' |
4179 | * - giving messages is not disabled by 'lazyredraw' |
4180 | */ |
4181 | if (((sub_nsubs > p_report && (KeyTyped || sub_nlines > 1 || p_report < 1)) |
4182 | || count_only) |
4183 | && messaging()) { |
4184 | if (got_int) |
4185 | STRCPY(msg_buf, _("(Interrupted) " )); |
4186 | else |
4187 | *msg_buf = NUL; |
4188 | if (sub_nsubs == 1) |
4189 | vim_snprintf_add((char *)msg_buf, sizeof(msg_buf), |
4190 | "%s" , count_only ? _("1 match" ) : _("1 substitution" )); |
4191 | else |
4192 | vim_snprintf_add((char *)msg_buf, sizeof(msg_buf), |
4193 | count_only ? _("%" PRId64 " matches" ) |
4194 | : _("%" PRId64 " substitutions" ), |
4195 | (int64_t)sub_nsubs); |
4196 | if (sub_nlines == 1) |
4197 | vim_snprintf_add((char *)msg_buf, sizeof(msg_buf), |
4198 | "%s" , _(" on 1 line" )); |
4199 | else |
4200 | vim_snprintf_add((char *)msg_buf, sizeof(msg_buf), |
4201 | _(" on %" PRId64 " lines" ), (int64_t)sub_nlines); |
4202 | if (msg(msg_buf)) |
4203 | /* save message to display it after redraw */ |
4204 | set_keep_msg(msg_buf, 0); |
4205 | return true; |
4206 | } |
4207 | if (got_int) { |
4208 | EMSG(_(e_interr)); |
4209 | return true; |
4210 | } |
4211 | return false; |
4212 | } |
4213 | |
4214 | static void global_exe_one(char_u *const cmd, const linenr_T lnum) |
4215 | { |
4216 | curwin->w_cursor.lnum = lnum; |
4217 | curwin->w_cursor.col = 0; |
4218 | if (*cmd == NUL || *cmd == '\n') { |
4219 | do_cmdline((char_u *)"p" , NULL, NULL, DOCMD_NOWAIT); |
4220 | } else { |
4221 | do_cmdline(cmd, NULL, NULL, DOCMD_NOWAIT); |
4222 | } |
4223 | } |
4224 | |
4225 | /* |
4226 | * Execute a global command of the form: |
4227 | * |
4228 | * g/pattern/X : execute X on all lines where pattern matches |
4229 | * v/pattern/X : execute X on all lines where pattern does not match |
4230 | * |
4231 | * where 'X' is an EX command |
4232 | * |
4233 | * The command character (as well as the trailing slash) is optional, and |
4234 | * is assumed to be 'p' if missing. |
4235 | * |
4236 | * This is implemented in two passes: first we scan the file for the pattern and |
4237 | * set a mark for each line that (not) matches. Secondly we execute the command |
4238 | * for each line that has a mark. This is required because after deleting |
4239 | * lines we do not know where to search for the next match. |
4240 | */ |
4241 | void ex_global(exarg_T *eap) |
4242 | { |
4243 | linenr_T lnum; /* line number according to old situation */ |
4244 | int ndone = 0; |
4245 | int type; /* first char of cmd: 'v' or 'g' */ |
4246 | char_u *cmd; /* command argument */ |
4247 | |
4248 | char_u delim; /* delimiter, normally '/' */ |
4249 | char_u *pat; |
4250 | regmmatch_T regmatch; |
4251 | int match; |
4252 | int which_pat; |
4253 | |
4254 | // When nesting the command works on one line. This allows for |
4255 | // ":g/found/v/notfound/command". |
4256 | if (global_busy && (eap->line1 != 1 |
4257 | || eap->line2 != curbuf->b_ml.ml_line_count)) { |
4258 | // will increment global_busy to break out of the loop |
4259 | EMSG(_("E147: Cannot do :global recursive with a range" )); |
4260 | return; |
4261 | } |
4262 | |
4263 | if (eap->forceit) /* ":global!" is like ":vglobal" */ |
4264 | type = 'v'; |
4265 | else |
4266 | type = *eap->cmd; |
4267 | cmd = eap->arg; |
4268 | which_pat = RE_LAST; /* default: use last used regexp */ |
4269 | |
4270 | /* |
4271 | * undocumented vi feature: |
4272 | * "\/" and "\?": use previous search pattern. |
4273 | * "\&": use previous substitute pattern. |
4274 | */ |
4275 | if (*cmd == '\\') { |
4276 | ++cmd; |
4277 | if (vim_strchr((char_u *)"/?&" , *cmd) == NULL) { |
4278 | EMSG(_(e_backslash)); |
4279 | return; |
4280 | } |
4281 | if (*cmd == '&') |
4282 | which_pat = RE_SUBST; /* use previous substitute pattern */ |
4283 | else |
4284 | which_pat = RE_SEARCH; /* use previous search pattern */ |
4285 | ++cmd; |
4286 | pat = (char_u *)"" ; |
4287 | } else if (*cmd == NUL) { |
4288 | EMSG(_("E148: Regular expression missing from global" )); |
4289 | return; |
4290 | } else { |
4291 | delim = *cmd; /* get the delimiter */ |
4292 | if (delim) |
4293 | ++cmd; /* skip delimiter if there is one */ |
4294 | pat = cmd; /* remember start of pattern */ |
4295 | cmd = skip_regexp(cmd, delim, p_magic, &eap->arg); |
4296 | if (cmd[0] == delim) /* end delimiter found */ |
4297 | *cmd++ = NUL; /* replace it with a NUL */ |
4298 | } |
4299 | |
4300 | if (search_regcomp(pat, RE_BOTH, which_pat, SEARCH_HIS, ®match) == FAIL) { |
4301 | EMSG(_(e_invcmd)); |
4302 | return; |
4303 | } |
4304 | |
4305 | if (global_busy) { |
4306 | lnum = curwin->w_cursor.lnum; |
4307 | match = vim_regexec_multi(®match, curwin, curbuf, lnum, |
4308 | (colnr_T)0, NULL, NULL); |
4309 | if ((type == 'g' && match) || (type == 'v' && !match)) { |
4310 | global_exe_one(cmd, lnum); |
4311 | } |
4312 | } else { |
4313 | // pass 1: set marks for each (not) matching line |
4314 | for (lnum = eap->line1; lnum <= eap->line2 && !got_int; lnum++) { |
4315 | // a match on this line? |
4316 | match = vim_regexec_multi(®match, curwin, curbuf, lnum, |
4317 | (colnr_T)0, NULL, NULL); |
4318 | if ((type == 'g' && match) || (type == 'v' && !match)) { |
4319 | ml_setmarked(lnum); |
4320 | ndone++; |
4321 | } |
4322 | line_breakcheck(); |
4323 | } |
4324 | |
4325 | // pass 2: execute the command for each line that has been marked |
4326 | if (got_int) { |
4327 | MSG(_(e_interr)); |
4328 | } else if (ndone == 0) { |
4329 | if (type == 'v') { |
4330 | smsg(_("Pattern found in every line: %s" ), pat); |
4331 | } else { |
4332 | smsg(_("Pattern not found: %s" ), pat); |
4333 | } |
4334 | } else { |
4335 | global_exe(cmd); |
4336 | } |
4337 | ml_clearmarked(); // clear rest of the marks |
4338 | } |
4339 | vim_regfree(regmatch.regprog); |
4340 | } |
4341 | |
4342 | /// Execute `cmd` on lines marked with ml_setmarked(). |
4343 | void global_exe(char_u *cmd) |
4344 | { |
4345 | linenr_T old_lcount; // b_ml.ml_line_count before the command |
4346 | buf_T *old_buf = curbuf; // remember what buffer we started in |
4347 | linenr_T lnum; // line number according to old situation |
4348 | int save_mapped_ctrl_c = mapped_ctrl_c; |
4349 | |
4350 | // Set current position only once for a global command. |
4351 | // If global_busy is set, setpcmark() will not do anything. |
4352 | // If there is an error, global_busy will be incremented. |
4353 | setpcmark(); |
4354 | |
4355 | // When the command writes a message, don't overwrite the command. |
4356 | msg_didout = true; |
4357 | // Disable CTRL-C mapping, let it interrupt (potentially long output). |
4358 | mapped_ctrl_c = 0; |
4359 | |
4360 | sub_nsubs = 0; |
4361 | sub_nlines = 0; |
4362 | global_need_beginline = false; |
4363 | global_busy = 1; |
4364 | old_lcount = curbuf->b_ml.ml_line_count; |
4365 | |
4366 | while (!got_int && (lnum = ml_firstmarked()) != 0 && global_busy == 1) { |
4367 | global_exe_one(cmd, lnum); |
4368 | os_breakcheck(); |
4369 | } |
4370 | |
4371 | mapped_ctrl_c = save_mapped_ctrl_c; |
4372 | global_busy = 0; |
4373 | if (global_need_beginline) { |
4374 | beginline(BL_WHITE | BL_FIX); |
4375 | } else { |
4376 | check_cursor(); // cursor may be beyond the end of the line |
4377 | } |
4378 | |
4379 | // the cursor may not have moved in the text but a change in a previous |
4380 | // line may move it on the screen |
4381 | changed_line_abv_curs(); |
4382 | |
4383 | // If it looks like no message was written, allow overwriting the |
4384 | // command with the report for number of changes. |
4385 | if (msg_col == 0 && msg_scrolled == 0) { |
4386 | msg_didout = false; |
4387 | } |
4388 | |
4389 | // If substitutes done, report number of substitutes, otherwise report |
4390 | // number of extra or deleted lines. |
4391 | // Don't report extra or deleted lines in the edge case where the buffer |
4392 | // we are in after execution is different from the buffer we started in. |
4393 | if (!do_sub_msg(false) && curbuf == old_buf) { |
4394 | msgmore(curbuf->b_ml.ml_line_count - old_lcount); |
4395 | } |
4396 | } |
4397 | |
4398 | #if defined(EXITFREE) |
4399 | void free_old_sub(void) |
4400 | { |
4401 | sub_set_replacement((SubReplacementString) {NULL, 0, NULL}); |
4402 | } |
4403 | |
4404 | #endif |
4405 | |
4406 | /* |
4407 | * Set up for a tagpreview. |
4408 | * Return TRUE when it was created. |
4409 | */ |
4410 | bool |
4411 | prepare_tagpreview ( |
4412 | bool undo_sync /* sync undo when leaving the window */ |
4413 | ) |
4414 | { |
4415 | /* |
4416 | * If there is already a preview window open, use that one. |
4417 | */ |
4418 | if (!curwin->w_p_pvw) { |
4419 | bool found_win = false; |
4420 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
4421 | if (wp->w_p_pvw) { |
4422 | win_enter(wp, undo_sync); |
4423 | found_win = true; |
4424 | break; |
4425 | } |
4426 | } |
4427 | if (!found_win) { |
4428 | /* |
4429 | * There is no preview window open yet. Create one. |
4430 | */ |
4431 | if (win_split(g_do_tagpreview > 0 ? g_do_tagpreview : 0, 0) |
4432 | == FAIL) |
4433 | return false; |
4434 | curwin->w_p_pvw = TRUE; |
4435 | curwin->w_p_wfh = TRUE; |
4436 | RESET_BINDING(curwin); /* don't take over 'scrollbind' |
4437 | and 'cursorbind' */ |
4438 | curwin->w_p_diff = FALSE; /* no 'diff' */ |
4439 | curwin->w_p_fdc = 0; /* no 'foldcolumn' */ |
4440 | return true; |
4441 | } |
4442 | } |
4443 | return false; |
4444 | } |
4445 | |
4446 | |
4447 | |
4448 | /* |
4449 | * ":help": open a read-only window on a help file |
4450 | */ |
4451 | void ex_help(exarg_T *eap) |
4452 | { |
4453 | char_u *arg; |
4454 | char_u *tag; |
4455 | FILE *helpfd; /* file descriptor of help file */ |
4456 | int n; |
4457 | int i; |
4458 | win_T *wp; |
4459 | int num_matches; |
4460 | char_u **matches; |
4461 | char_u *p; |
4462 | int empty_fnum = 0; |
4463 | int alt_fnum = 0; |
4464 | buf_T *buf; |
4465 | int len; |
4466 | char_u *lang; |
4467 | const bool old_KeyTyped = KeyTyped; |
4468 | |
4469 | if (eap != NULL) { |
4470 | /* |
4471 | * A ":help" command ends at the first LF, or at a '|' that is |
4472 | * followed by some text. Set nextcmd to the following command. |
4473 | */ |
4474 | for (arg = eap->arg; *arg; ++arg) { |
4475 | if (*arg == '\n' || *arg == '\r' |
4476 | || (*arg == '|' && arg[1] != NUL && arg[1] != '|')) { |
4477 | *arg++ = NUL; |
4478 | eap->nextcmd = arg; |
4479 | break; |
4480 | } |
4481 | } |
4482 | arg = eap->arg; |
4483 | |
4484 | if (eap->forceit && *arg == NUL && !curbuf->b_help) { |
4485 | EMSG(_("E478: Don't panic!" )); |
4486 | return; |
4487 | } |
4488 | |
4489 | if (eap->skip) /* not executing commands */ |
4490 | return; |
4491 | } else |
4492 | arg = (char_u *)"" ; |
4493 | |
4494 | /* remove trailing blanks */ |
4495 | p = arg + STRLEN(arg) - 1; |
4496 | while (p > arg && ascii_iswhite(*p) && p[-1] != '\\') |
4497 | *p-- = NUL; |
4498 | |
4499 | /* Check for a specified language */ |
4500 | lang = check_help_lang(arg); |
4501 | |
4502 | /* When no argument given go to the index. */ |
4503 | if (*arg == NUL) |
4504 | arg = (char_u *)"help.txt" ; |
4505 | |
4506 | /* |
4507 | * Check if there is a match for the argument. |
4508 | */ |
4509 | n = find_help_tags(arg, &num_matches, &matches, |
4510 | eap != NULL && eap->forceit); |
4511 | |
4512 | i = 0; |
4513 | if (n != FAIL && lang != NULL) |
4514 | /* Find first item with the requested language. */ |
4515 | for (i = 0; i < num_matches; ++i) { |
4516 | len = (int)STRLEN(matches[i]); |
4517 | if (len > 3 && matches[i][len - 3] == '@' |
4518 | && STRICMP(matches[i] + len - 2, lang) == 0) |
4519 | break; |
4520 | } |
4521 | if (i >= num_matches || n == FAIL) { |
4522 | if (lang != NULL) |
4523 | EMSG3(_("E661: Sorry, no '%s' help for %s" ), lang, arg); |
4524 | else |
4525 | EMSG2(_("E149: Sorry, no help for %s" ), arg); |
4526 | if (n != FAIL) |
4527 | FreeWild(num_matches, matches); |
4528 | return; |
4529 | } |
4530 | |
4531 | /* The first match (in the requested language) is the best match. */ |
4532 | tag = vim_strsave(matches[i]); |
4533 | FreeWild(num_matches, matches); |
4534 | |
4535 | /* |
4536 | * Re-use an existing help window or open a new one. |
4537 | * Always open a new one for ":tab help". |
4538 | */ |
4539 | if (!bt_help(curwin->w_buffer) |
4540 | || cmdmod.tab != 0 |
4541 | ) { |
4542 | if (cmdmod.tab != 0) { |
4543 | wp = NULL; |
4544 | } else { |
4545 | wp = NULL; |
4546 | FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) { |
4547 | if (bt_help(wp2->w_buffer)) { |
4548 | wp = wp2; |
4549 | break; |
4550 | } |
4551 | } |
4552 | } |
4553 | if (wp != NULL && wp->w_buffer->b_nwindows > 0) { |
4554 | win_enter(wp, true); |
4555 | } else { |
4556 | // There is no help window yet. |
4557 | // Try to open the file specified by the "helpfile" option. |
4558 | if ((helpfd = os_fopen((char *)p_hf, READBIN)) == NULL) { |
4559 | smsg(_("Sorry, help file \"%s\" not found" ), p_hf); |
4560 | goto erret; |
4561 | } |
4562 | fclose(helpfd); |
4563 | |
4564 | /* Split off help window; put it at far top if no position |
4565 | * specified, the current window is vertically split and |
4566 | * narrow. */ |
4567 | n = WSP_HELP; |
4568 | if (cmdmod.split == 0 && curwin->w_width != Columns |
4569 | && curwin->w_width < 80) |
4570 | n |= WSP_TOP; |
4571 | if (win_split(0, n) == FAIL) |
4572 | goto erret; |
4573 | |
4574 | if (curwin->w_height < p_hh) |
4575 | win_setheight((int)p_hh); |
4576 | |
4577 | /* |
4578 | * Open help file (do_ecmd() will set b_help flag, readfile() will |
4579 | * set b_p_ro flag). |
4580 | * Set the alternate file to the previously edited file. |
4581 | */ |
4582 | alt_fnum = curbuf->b_fnum; |
4583 | (void)do_ecmd(0, NULL, NULL, NULL, ECMD_LASTL, |
4584 | ECMD_HIDE + ECMD_SET_HELP, |
4585 | NULL /* buffer is still open, don't store info */ |
4586 | ); |
4587 | if (!cmdmod.keepalt) |
4588 | curwin->w_alt_fnum = alt_fnum; |
4589 | empty_fnum = curbuf->b_fnum; |
4590 | } |
4591 | } |
4592 | |
4593 | if (!p_im) |
4594 | restart_edit = 0; /* don't want insert mode in help file */ |
4595 | |
4596 | /* Restore KeyTyped, setting 'filetype=help' may reset it. |
4597 | * It is needed for do_tag top open folds under the cursor. */ |
4598 | KeyTyped = old_KeyTyped; |
4599 | |
4600 | do_tag(tag, DT_HELP, 1, FALSE, TRUE); |
4601 | |
4602 | /* Delete the empty buffer if we're not using it. Careful: autocommands |
4603 | * may have jumped to another window, check that the buffer is not in a |
4604 | * window. */ |
4605 | if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum) { |
4606 | buf = buflist_findnr(empty_fnum); |
4607 | if (buf != NULL && buf->b_nwindows == 0) |
4608 | wipe_buffer(buf, TRUE); |
4609 | } |
4610 | |
4611 | /* keep the previous alternate file */ |
4612 | if (alt_fnum != 0 && curwin->w_alt_fnum == empty_fnum && !cmdmod.keepalt) |
4613 | curwin->w_alt_fnum = alt_fnum; |
4614 | |
4615 | erret: |
4616 | xfree(tag); |
4617 | } |
4618 | |
4619 | |
4620 | /* |
4621 | * In an argument search for a language specifiers in the form "@xx". |
4622 | * Changes the "@" to NUL if found, and returns a pointer to "xx". |
4623 | * Returns NULL if not found. |
4624 | */ |
4625 | char_u *check_help_lang(char_u *arg) |
4626 | { |
4627 | int len = (int)STRLEN(arg); |
4628 | |
4629 | if (len >= 3 && arg[len - 3] == '@' && ASCII_ISALPHA(arg[len - 2]) |
4630 | && ASCII_ISALPHA(arg[len - 1])) { |
4631 | arg[len - 3] = NUL; /* remove the '@' */ |
4632 | return arg + len - 2; |
4633 | } |
4634 | return NULL; |
4635 | } |
4636 | |
4637 | /* |
4638 | * Return a heuristic indicating how well the given string matches. The |
4639 | * smaller the number, the better the match. This is the order of priorities, |
4640 | * from best match to worst match: |
4641 | * - Match with least alpha-numeric characters is better. |
4642 | * - Match with least total characters is better. |
4643 | * - Match towards the start is better. |
4644 | * - Match starting with "+" is worse (feature instead of command) |
4645 | * Assumption is made that the matched_string passed has already been found to |
4646 | * match some string for which help is requested. webb. |
4647 | */ |
4648 | int |
4649 | help_heuristic( |
4650 | char_u *matched_string, |
4651 | int offset, // offset for match |
4652 | int wrong_case // no matching case |
4653 | ) |
4654 | { |
4655 | int num_letters; |
4656 | char_u *p; |
4657 | |
4658 | num_letters = 0; |
4659 | for (p = matched_string; *p; p++) |
4660 | if (ASCII_ISALNUM(*p)) |
4661 | num_letters++; |
4662 | |
4663 | /* |
4664 | * Multiply the number of letters by 100 to give it a much bigger |
4665 | * weighting than the number of characters. |
4666 | * If there only is a match while ignoring case, add 5000. |
4667 | * If the match starts in the middle of a word, add 10000 to put it |
4668 | * somewhere in the last half. |
4669 | * If the match is more than 2 chars from the start, multiply by 200 to |
4670 | * put it after matches at the start. |
4671 | */ |
4672 | if (ASCII_ISALNUM(matched_string[offset]) && offset > 0 |
4673 | && ASCII_ISALNUM(matched_string[offset - 1])) |
4674 | offset += 10000; |
4675 | else if (offset > 2) |
4676 | offset *= 200; |
4677 | if (wrong_case) |
4678 | offset += 5000; |
4679 | /* Features are less interesting than the subjects themselves, but "+" |
4680 | * alone is not a feature. */ |
4681 | if (matched_string[0] == '+' && matched_string[1] != NUL) |
4682 | offset += 100; |
4683 | return (int)(100 * num_letters + STRLEN(matched_string) + offset); |
4684 | } |
4685 | |
4686 | /* |
4687 | * Compare functions for qsort() below, that checks the help heuristics number |
4688 | * that has been put after the tagname by find_tags(). |
4689 | */ |
4690 | static int help_compare(const void *s1, const void *s2) |
4691 | { |
4692 | char *p1; |
4693 | char *p2; |
4694 | |
4695 | p1 = *(char **)s1 + strlen(*(char **)s1) + 1; |
4696 | p2 = *(char **)s2 + strlen(*(char **)s2) + 1; |
4697 | return strcmp(p1, p2); |
4698 | } |
4699 | |
4700 | // Find all help tags matching "arg", sort them and return in matches[], with |
4701 | // the number of matches in num_matches. |
4702 | // The matches will be sorted with a "best" match algorithm. |
4703 | // When "keep_lang" is true try keeping the language of the current buffer. |
4704 | int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches, |
4705 | bool keep_lang) |
4706 | { |
4707 | int i; |
4708 | static const char *(mtable[]) = { |
4709 | "*" , "g*" , "[*" , "]*" , |
4710 | "/*" , "/\\*" , "\"*" , "**" , |
4711 | "/\\(\\)" , "/\\%(\\)" , |
4712 | "?" , ":?" , "?<CR>" , "g?" , "g?g?" , "g??" , |
4713 | "-?" , "q?" , "v_g?" , |
4714 | "/\\?" , "/\\z(\\)" , "\\=" , ":s\\=" , |
4715 | "[count]" , "[quotex]" , |
4716 | "[range]" , ":[range]" , |
4717 | "[pattern]" , "\\|" , "\\%$" , |
4718 | "s/\\~" , "s/\\U" , "s/\\L" , |
4719 | "s/\\1" , "s/\\2" , "s/\\3" , "s/\\9" |
4720 | }; |
4721 | static const char *(rtable[]) = { |
4722 | "star" , "gstar" , "[star" , "]star" , |
4723 | "/star" , "/\\\\star" , "quotestar" , "starstar" , |
4724 | "/\\\\(\\\\)" , "/\\\\%(\\\\)" , |
4725 | "?" , ":?" , "?<CR>" , "g?" , "g?g?" , "g??" , |
4726 | "-?" , "q?" , "v_g?" , |
4727 | "/\\\\?" , "/\\\\z(\\\\)" , "\\\\=" , ":s\\\\=" , |
4728 | "\\[count]" , "\\[quotex]" , |
4729 | "\\[range]" , ":\\[range]" , |
4730 | "\\[pattern]" , "\\\\bar" , "/\\\\%\\$" , |
4731 | "s/\\\\\\~" , "s/\\\\U" , "s/\\\\L" , |
4732 | "s/\\\\1" , "s/\\\\2" , "s/\\\\3" , "s/\\\\9" |
4733 | }; |
4734 | static const char *(expr_table[]) = { |
4735 | "!=?" , "!~?" , "<=?" , "<?" , "==?" , "=~?" , |
4736 | ">=?" , ">?" , "is?" , "isnot?" |
4737 | }; |
4738 | char_u *d = IObuff; // assume IObuff is long enough! |
4739 | |
4740 | if (STRNICMP(arg, "expr-" , 5) == 0) { |
4741 | // When the string starting with "expr-" and containing '?' and matches |
4742 | // the table, it is taken literally. Otherwise '?' is recognized as a |
4743 | // wildcard. |
4744 | for (i = (int)ARRAY_SIZE(expr_table); --i >= 0; ) { |
4745 | if (STRCMP(arg + 5, expr_table[i]) == 0) { |
4746 | STRCPY(d, arg); |
4747 | break; |
4748 | } |
4749 | } |
4750 | } else { |
4751 | // Recognize a few exceptions to the rule. Some strings that contain |
4752 | // '*' with "star". Otherwise '*' is recognized as a wildcard. |
4753 | for (i = (int)ARRAY_SIZE(mtable); --i >= 0; ) { |
4754 | if (STRCMP(arg, mtable[i]) == 0) { |
4755 | STRCPY(d, rtable[i]); |
4756 | break; |
4757 | } |
4758 | } |
4759 | } |
4760 | |
4761 | if (i < 0) { /* no match in table */ |
4762 | /* Replace "\S" with "/\\S", etc. Otherwise every tag is matched. |
4763 | * Also replace "\%^" and "\%(", they match every tag too. |
4764 | * Also "\zs", "\z1", etc. |
4765 | * Also "\@<", "\@=", "\@<=", etc. |
4766 | * And also "\_$" and "\_^". */ |
4767 | if (arg[0] == '\\' |
4768 | && ((arg[1] != NUL && arg[2] == NUL) |
4769 | || (vim_strchr((char_u *)"%_z@" , arg[1]) != NULL |
4770 | && arg[2] != NUL))) { |
4771 | STRCPY(d, "/\\\\" ); |
4772 | STRCPY(d + 3, arg + 1); |
4773 | /* Check for "/\\_$", should be "/\\_\$" */ |
4774 | if (d[3] == '_' && d[4] == '$') |
4775 | STRCPY(d + 4, "\\$" ); |
4776 | } else { |
4777 | /* Replace: |
4778 | * "[:...:]" with "\[:...:]" |
4779 | * "[++...]" with "\[++...]" |
4780 | * "\{" with "\\{" -- matching "} \}" |
4781 | */ |
4782 | if ((arg[0] == '[' && (arg[1] == ':' |
4783 | || (arg[1] == '+' && arg[2] == '+'))) |
4784 | || (arg[0] == '\\' && arg[1] == '{')) |
4785 | *d++ = '\\'; |
4786 | |
4787 | // If tag starts with "('", skip the "(". Fixes CTRL-] on ('option'. |
4788 | if (*arg == '(' && arg[1] == '\'') { |
4789 | arg++; |
4790 | } |
4791 | for (const char_u *s = arg; *s; s++) { |
4792 | // Replace "|" with "bar" and '"' with "quote" to match the name of |
4793 | // the tags for these commands. |
4794 | // Replace "*" with ".*" and "?" with "." to match command line |
4795 | // completion. |
4796 | // Insert a backslash before '~', '$' and '.' to avoid their |
4797 | // special meaning. |
4798 | if (d - IObuff > IOSIZE - 10) { // getting too long!? |
4799 | break; |
4800 | } |
4801 | switch (*s) { |
4802 | case '|': STRCPY(d, "bar" ); |
4803 | d += 3; |
4804 | continue; |
4805 | case '"': STRCPY(d, "quote" ); |
4806 | d += 5; |
4807 | continue; |
4808 | case '*': *d++ = '.'; |
4809 | break; |
4810 | case '?': *d++ = '.'; |
4811 | continue; |
4812 | case '$': |
4813 | case '.': |
4814 | case '~': *d++ = '\\'; |
4815 | break; |
4816 | } |
4817 | |
4818 | /* |
4819 | * Replace "^x" by "CTRL-X". Don't do this for "^_" to make |
4820 | * ":help i_^_CTRL-D" work. |
4821 | * Insert '-' before and after "CTRL-X" when applicable. |
4822 | */ |
4823 | if (*s < ' ' || (*s == '^' && s[1] && (ASCII_ISALPHA(s[1]) |
4824 | || vim_strchr((char_u *) |
4825 | "?@[\\]^" , |
4826 | s[1]) != NULL))) { |
4827 | if (d > IObuff && d[-1] != '_' && d[-1] != '\\') |
4828 | *d++ = '_'; /* prepend a '_' to make x_CTRL-x */ |
4829 | STRCPY(d, "CTRL-" ); |
4830 | d += 5; |
4831 | if (*s < ' ') { |
4832 | *d++ = *s + '@'; |
4833 | if (d[-1] == '\\') |
4834 | *d++ = '\\'; /* double a backslash */ |
4835 | } else |
4836 | *d++ = *++s; |
4837 | if (s[1] != NUL && s[1] != '_') |
4838 | *d++ = '_'; /* append a '_' */ |
4839 | continue; |
4840 | } else if (*s == '^') /* "^" or "CTRL-^" or "^_" */ |
4841 | *d++ = '\\'; |
4842 | |
4843 | /* |
4844 | * Insert a backslash before a backslash after a slash, for search |
4845 | * pattern tags: "/\|" --> "/\\|". |
4846 | */ |
4847 | else if (s[0] == '\\' && s[1] != '\\' |
4848 | && *arg == '/' && s == arg + 1) |
4849 | *d++ = '\\'; |
4850 | |
4851 | /* "CTRL-\_" -> "CTRL-\\_" to avoid the special meaning of "\_" in |
4852 | * "CTRL-\_CTRL-N" */ |
4853 | if (STRNICMP(s, "CTRL-\\_" , 7) == 0) { |
4854 | STRCPY(d, "CTRL-\\\\" ); |
4855 | d += 7; |
4856 | s += 6; |
4857 | } |
4858 | |
4859 | *d++ = *s; |
4860 | |
4861 | // If tag contains "({" or "([", tag terminates at the "(". |
4862 | // This is for help on functions, e.g.: abs({expr}). |
4863 | if (*s == '(' && (s[1] == '{' || s[1] =='[')) { |
4864 | break; |
4865 | } |
4866 | |
4867 | // If tag starts with ', toss everything after a second '. Fixes |
4868 | // CTRL-] on 'option'. (would include the trailing '.'). |
4869 | if (*s == '\'' && s > arg && *arg == '\'') { |
4870 | break; |
4871 | } |
4872 | // Also '{' and '}'. Fixes CTRL-] on '{address}'. |
4873 | if (*s == '}' && s > arg && *arg == '{') { |
4874 | break; |
4875 | } |
4876 | } |
4877 | *d = NUL; |
4878 | |
4879 | if (*IObuff == '`') { |
4880 | if (d > IObuff + 2 && d[-1] == '`') { |
4881 | /* remove the backticks from `command` */ |
4882 | memmove(IObuff, IObuff + 1, STRLEN(IObuff)); |
4883 | d[-2] = NUL; |
4884 | } else if (d > IObuff + 3 && d[-2] == '`' && d[-1] == ',') { |
4885 | /* remove the backticks and comma from `command`, */ |
4886 | memmove(IObuff, IObuff + 1, STRLEN(IObuff)); |
4887 | d[-3] = NUL; |
4888 | } else if (d > IObuff + 4 && d[-3] == '`' |
4889 | && d[-2] == '\\' && d[-1] == '.') { |
4890 | /* remove the backticks and dot from `command`\. */ |
4891 | memmove(IObuff, IObuff + 1, STRLEN(IObuff)); |
4892 | d[-4] = NUL; |
4893 | } |
4894 | } |
4895 | } |
4896 | } |
4897 | |
4898 | *matches = NULL; |
4899 | *num_matches = 0; |
4900 | int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE; |
4901 | if (keep_lang) { |
4902 | flags |= TAG_KEEP_LANG; |
4903 | } |
4904 | if (find_tags(IObuff, num_matches, matches, flags, (int)MAXCOL, NULL) == OK |
4905 | && *num_matches > 0) { |
4906 | /* Sort the matches found on the heuristic number that is after the |
4907 | * tag name. */ |
4908 | qsort((void *)*matches, (size_t)*num_matches, |
4909 | sizeof(char_u *), help_compare); |
4910 | /* Delete more than TAG_MANY to reduce the size of the listing. */ |
4911 | while (*num_matches > TAG_MANY) |
4912 | xfree((*matches)[--*num_matches]); |
4913 | } |
4914 | return OK; |
4915 | } |
4916 | |
4917 | /// Called when starting to edit a buffer for a help file. |
4918 | static void prepare_help_buffer(void) |
4919 | { |
4920 | curbuf->b_help = true; |
4921 | set_string_option_direct((char_u *)"buftype" , -1, (char_u *)"help" , |
4922 | OPT_FREE|OPT_LOCAL, 0); |
4923 | |
4924 | // Always set these options after jumping to a help tag, because the |
4925 | // user may have an autocommand that gets in the way. |
4926 | // Accept all ASCII chars for keywords, except ' ', '*', '"', '|', and |
4927 | // latin1 word characters (for translated help files). |
4928 | // Only set it when needed, buf_init_chartab() is some work. |
4929 | char_u *p = (char_u *)"!-~,^*,^|,^\",192-255" ; |
4930 | if (STRCMP(curbuf->b_p_isk, p) != 0) { |
4931 | set_string_option_direct((char_u *)"isk" , -1, p, OPT_FREE|OPT_LOCAL, 0); |
4932 | check_buf_options(curbuf); |
4933 | (void)buf_init_chartab(curbuf, FALSE); |
4934 | } |
4935 | |
4936 | // Don't use the global foldmethod. |
4937 | set_string_option_direct((char_u *)"fdm" , -1, (char_u *)"manual" , |
4938 | OPT_FREE|OPT_LOCAL, 0); |
4939 | |
4940 | curbuf->b_p_ts = 8; // 'tabstop' is 8. |
4941 | curwin->w_p_list = FALSE; // No list mode. |
4942 | |
4943 | curbuf->b_p_ma = FALSE; // Not modifiable. |
4944 | curbuf->b_p_bin = FALSE; // Reset 'bin' before reading file. |
4945 | curwin->w_p_nu = 0; // No line numbers. |
4946 | curwin->w_p_rnu = 0; // No relative line numbers. |
4947 | RESET_BINDING(curwin); // No scroll or cursor binding. |
4948 | curwin->w_p_arab = FALSE; // No arabic mode. |
4949 | curwin->w_p_rl = FALSE; // Help window is left-to-right. |
4950 | curwin->w_p_fen = FALSE; // No folding in the help window. |
4951 | curwin->w_p_diff = FALSE; // No 'diff'. |
4952 | curwin->w_p_spell = FALSE; // No spell checking. |
4953 | |
4954 | set_buflisted(FALSE); |
4955 | } |
4956 | |
4957 | /* |
4958 | * After reading a help file: May cleanup a help buffer when syntax |
4959 | * highlighting is not used. |
4960 | */ |
4961 | void fix_help_buffer(void) |
4962 | { |
4963 | linenr_T lnum; |
4964 | char_u *line; |
4965 | bool in_example = false; |
4966 | |
4967 | // Set filetype to "help". |
4968 | if (STRCMP(curbuf->b_p_ft, "help" ) != 0) { |
4969 | curbuf_lock++; |
4970 | set_option_value("ft" , 0L, "help" , OPT_LOCAL); |
4971 | curbuf_lock--; |
4972 | } |
4973 | |
4974 | if (!syntax_present(curwin)) { |
4975 | for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) { |
4976 | line = ml_get_buf(curbuf, lnum, false); |
4977 | const size_t len = STRLEN(line); |
4978 | if (in_example && len > 0 && !ascii_iswhite(line[0])) { |
4979 | /* End of example: non-white or '<' in first column. */ |
4980 | if (line[0] == '<') { |
4981 | /* blank-out a '<' in the first column */ |
4982 | line = ml_get_buf(curbuf, lnum, TRUE); |
4983 | line[0] = ' '; |
4984 | } |
4985 | in_example = false; |
4986 | } |
4987 | if (!in_example && len > 0) { |
4988 | if (line[len - 1] == '>' && (len == 1 || line[len - 2] == ' ')) { |
4989 | /* blank-out a '>' in the last column (start of example) */ |
4990 | line = ml_get_buf(curbuf, lnum, TRUE); |
4991 | line[len - 1] = ' '; |
4992 | in_example = true; |
4993 | } else if (line[len - 1] == '~') { |
4994 | /* blank-out a '~' at the end of line (header marker) */ |
4995 | line = ml_get_buf(curbuf, lnum, TRUE); |
4996 | line[len - 1] = ' '; |
4997 | } |
4998 | } |
4999 | } |
5000 | } |
5001 | |
5002 | /* |
5003 | * In the "help.txt" and "help.abx" file, add the locally added help |
5004 | * files. This uses the very first line in the help file. |
5005 | */ |
5006 | char_u *const fname = path_tail(curbuf->b_fname); |
5007 | if (fnamecmp(fname, "help.txt" ) == 0 |
5008 | || (fnamencmp(fname, "help." , 5) == 0 |
5009 | && ASCII_ISALPHA(fname[5]) |
5010 | && ASCII_ISALPHA(fname[6]) |
5011 | && TOLOWER_ASC(fname[7]) == 'x' |
5012 | && fname[8] == NUL) |
5013 | ) { |
5014 | for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; ++lnum) { |
5015 | line = ml_get_buf(curbuf, lnum, FALSE); |
5016 | if (strstr((char *)line, "*local-additions*" ) == NULL) |
5017 | continue; |
5018 | |
5019 | /* Go through all directories in 'runtimepath', skipping |
5020 | * $VIMRUNTIME. */ |
5021 | char_u *p = p_rtp; |
5022 | while (*p != NUL) { |
5023 | copy_option_part(&p, NameBuff, MAXPATHL, "," ); |
5024 | char_u *const rt = (char_u *)vim_getenv("VIMRUNTIME" ); |
5025 | if (rt != NULL |
5026 | && path_full_compare(rt, NameBuff, false) != kEqualFiles) { |
5027 | int fcount; |
5028 | char_u **fnames; |
5029 | char_u *s; |
5030 | vimconv_T vc; |
5031 | char_u *cp; |
5032 | |
5033 | // Find all "doc/ *.txt" files in this directory. |
5034 | if (!add_pathsep((char *)NameBuff) |
5035 | || STRLCAT(NameBuff, "doc/*.??[tx]" , |
5036 | sizeof(NameBuff)) >= MAXPATHL) { |
5037 | EMSG(_(e_fnametoolong)); |
5038 | continue; |
5039 | } |
5040 | |
5041 | // Note: We cannot just do `&NameBuff` because it is a statically sized array |
5042 | // so `NameBuff == &NameBuff` according to C semantics. |
5043 | char_u *buff_list[1] = {NameBuff}; |
5044 | if (gen_expand_wildcards(1, buff_list, &fcount, |
5045 | &fnames, EW_FILE|EW_SILENT) == OK |
5046 | && fcount > 0) { |
5047 | // If foo.abx is found use it instead of foo.txt in |
5048 | // the same directory. |
5049 | for (int i1 = 0; i1 < fcount; i1++) { |
5050 | for (int i2 = 0; i2 < fcount; i2++) { |
5051 | if (i1 == i2) { |
5052 | continue; |
5053 | } |
5054 | if (fnames[i1] == NULL || fnames[i2] == NULL) { |
5055 | continue; |
5056 | } |
5057 | const char_u *const f1 = fnames[i1]; |
5058 | const char_u *const f2 = fnames[i2]; |
5059 | const char_u *const t1 = path_tail(f1); |
5060 | const char_u *const t2 = path_tail(f2); |
5061 | const char_u *const e1 = STRRCHR(t1, '.'); |
5062 | const char_u *const e2 = STRRCHR(t2, '.'); |
5063 | if (e1 == NULL || e2 == NULL) { |
5064 | continue; |
5065 | } |
5066 | if (fnamecmp(e1, ".txt" ) != 0 |
5067 | && fnamecmp(e1, fname + 4) != 0) { |
5068 | // Not .txt and not .abx, remove it. |
5069 | XFREE_CLEAR(fnames[i1]); |
5070 | continue; |
5071 | } |
5072 | if (e1 - f1 != e2 - f2 |
5073 | || fnamencmp(f1, f2, e1 - f1) != 0) { |
5074 | continue; |
5075 | } |
5076 | if (fnamecmp(e1, ".txt" ) == 0 |
5077 | && fnamecmp(e2, fname + 4) == 0) { |
5078 | // use .abx instead of .txt |
5079 | XFREE_CLEAR(fnames[i1]); |
5080 | } |
5081 | } |
5082 | } |
5083 | for (int fi = 0; fi < fcount; fi++) { |
5084 | if (fnames[fi] == NULL) { |
5085 | continue; |
5086 | } |
5087 | |
5088 | FILE *const fd = os_fopen((char *)fnames[fi], "r" ); |
5089 | if (fd == NULL) { |
5090 | continue; |
5091 | } |
5092 | vim_fgets(IObuff, IOSIZE, fd); |
5093 | if (IObuff[0] == '*' |
5094 | && (s = vim_strchr(IObuff + 1, '*')) |
5095 | != NULL) { |
5096 | TriState this_utf = kNone; |
5097 | // Change tag definition to a |
5098 | // reference and remove <CR>/<NL>. |
5099 | IObuff[0] = '|'; |
5100 | *s = '|'; |
5101 | while (*s != NUL) { |
5102 | if (*s == '\r' || *s == '\n') |
5103 | *s = NUL; |
5104 | /* The text is utf-8 when a byte |
5105 | * above 127 is found and no |
5106 | * illegal byte sequence is found. |
5107 | */ |
5108 | if (*s >= 0x80 && this_utf != kFalse) { |
5109 | this_utf = kTrue; |
5110 | const int l = utf_ptr2len(s); |
5111 | if (l == 1) { |
5112 | this_utf = kFalse; |
5113 | } |
5114 | s += l - 1; |
5115 | } |
5116 | ++s; |
5117 | } |
5118 | /* The help file is latin1 or utf-8; |
5119 | * conversion to the current |
5120 | * 'encoding' may be required. */ |
5121 | vc.vc_type = CONV_NONE; |
5122 | convert_setup( |
5123 | &vc, |
5124 | (char_u *)(this_utf == kTrue ? "utf-8" : "latin1" ), |
5125 | p_enc); |
5126 | if (vc.vc_type == CONV_NONE) { |
5127 | // No conversion needed. |
5128 | cp = IObuff; |
5129 | } else { |
5130 | // Do the conversion. If it fails |
5131 | // use the unconverted text. |
5132 | cp = string_convert(&vc, IObuff, NULL); |
5133 | if (cp == NULL) { |
5134 | cp = IObuff; |
5135 | } |
5136 | } |
5137 | convert_setup(&vc, NULL, NULL); |
5138 | |
5139 | ml_append(lnum, cp, (colnr_T)0, false); |
5140 | if (cp != IObuff) { |
5141 | xfree(cp); |
5142 | } |
5143 | lnum++; |
5144 | } |
5145 | fclose(fd); |
5146 | } |
5147 | FreeWild(fcount, fnames); |
5148 | } |
5149 | } |
5150 | xfree(rt); |
5151 | } |
5152 | break; |
5153 | } |
5154 | } |
5155 | } |
5156 | |
5157 | /* |
5158 | * ":exusage" |
5159 | */ |
5160 | void ex_exusage(exarg_T *eap) |
5161 | { |
5162 | do_cmdline_cmd("help ex-cmd-index" ); |
5163 | } |
5164 | |
5165 | /* |
5166 | * ":viusage" |
5167 | */ |
5168 | void ex_viusage(exarg_T *eap) |
5169 | { |
5170 | do_cmdline_cmd("help normal-index" ); |
5171 | } |
5172 | |
5173 | |
5174 | /// Generate tags in one help directory |
5175 | /// |
5176 | /// @param dir Path to the doc directory |
5177 | /// @param ext Suffix of the help files (".txt", ".itx", ".frx", etc.) |
5178 | /// @param tagname Name of the tags file ("tags" for English, "tags-fr" for |
5179 | /// French) |
5180 | /// @param add_help_tags Whether to add the "help-tags" tag |
5181 | static void helptags_one(char_u *const dir, const char_u *const ext, |
5182 | const char_u *const tagfname, const bool add_help_tags) |
5183 | { |
5184 | garray_T ga; |
5185 | int filecount; |
5186 | char_u **files; |
5187 | char_u *p1, *p2; |
5188 | char_u *s; |
5189 | TriState utf8 = kNone; |
5190 | bool mix = false; // detected mixed encodings |
5191 | |
5192 | // Find all *.txt files. |
5193 | size_t dirlen = STRLCPY(NameBuff, dir, sizeof(NameBuff)); |
5194 | if (dirlen >= MAXPATHL |
5195 | || STRLCAT(NameBuff, "/**/*" , sizeof(NameBuff)) >= MAXPATHL // NOLINT |
5196 | || STRLCAT(NameBuff, ext, sizeof(NameBuff)) >= MAXPATHL) { |
5197 | EMSG(_(e_fnametoolong)); |
5198 | return; |
5199 | } |
5200 | |
5201 | // Note: We cannot just do `&NameBuff` because it is a statically sized array |
5202 | // so `NameBuff == &NameBuff` according to C semantics. |
5203 | char_u *buff_list[1] = {NameBuff}; |
5204 | if (gen_expand_wildcards(1, buff_list, &filecount, &files, |
5205 | EW_FILE|EW_SILENT) == FAIL |
5206 | || filecount == 0) { |
5207 | if (!got_int) { |
5208 | EMSG2(_("E151: No match: %s" ), NameBuff); |
5209 | } |
5210 | return; |
5211 | } |
5212 | |
5213 | // |
5214 | // Open the tags file for writing. |
5215 | // Do this before scanning through all the files. |
5216 | // |
5217 | memcpy(NameBuff, dir, dirlen + 1); |
5218 | if (!add_pathsep((char *)NameBuff) |
5219 | || STRLCAT(NameBuff, tagfname, sizeof(NameBuff)) >= MAXPATHL) { |
5220 | EMSG(_(e_fnametoolong)); |
5221 | return; |
5222 | } |
5223 | |
5224 | FILE *const fd_tags = os_fopen((char *)NameBuff, "w" ); |
5225 | if (fd_tags == NULL) { |
5226 | EMSG2(_("E152: Cannot open %s for writing" ), NameBuff); |
5227 | FreeWild(filecount, files); |
5228 | return; |
5229 | } |
5230 | |
5231 | // If using the "++t" argument or generating tags for "$VIMRUNTIME/doc" |
5232 | // add the "help-tags" tag. |
5233 | ga_init(&ga, (int)sizeof(char_u *), 100); |
5234 | if (add_help_tags |
5235 | || path_full_compare((char_u *)"$VIMRUNTIME/doc" , |
5236 | dir, false) == kEqualFiles) { |
5237 | s = xmalloc(18 + STRLEN(tagfname)); |
5238 | sprintf((char *)s, "help-tags\t%s\t1\n" , tagfname); |
5239 | GA_APPEND(char_u *, &ga, s); |
5240 | } |
5241 | |
5242 | // Go over all the files and extract the tags. |
5243 | for (int fi = 0; fi < filecount && !got_int; fi++) { |
5244 | FILE *const fd = os_fopen((char *)files[fi], "r" ); |
5245 | if (fd == NULL) { |
5246 | EMSG2(_("E153: Unable to open %s for reading" ), files[fi]); |
5247 | continue; |
5248 | } |
5249 | const char_u *const fname = files[fi] + dirlen + 1; |
5250 | |
5251 | bool firstline = true; |
5252 | while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) { |
5253 | if (firstline) { |
5254 | // Detect utf-8 file by a non-ASCII char in the first line. |
5255 | TriState this_utf8 = kNone; |
5256 | for (s = IObuff; *s != NUL; s++) { |
5257 | if (*s >= 0x80) { |
5258 | this_utf8 = kTrue; |
5259 | const int l = utf_ptr2len(s); |
5260 | if (l == 1) { |
5261 | // Illegal UTF-8 byte sequence. |
5262 | this_utf8 = kFalse; |
5263 | break; |
5264 | } |
5265 | s += l - 1; |
5266 | } |
5267 | } |
5268 | if (this_utf8 == kNone) { // only ASCII characters found |
5269 | this_utf8 = kFalse; |
5270 | } |
5271 | if (utf8 == kNone) { // first file |
5272 | utf8 = this_utf8; |
5273 | } else if (utf8 != this_utf8) { |
5274 | EMSG2(_( |
5275 | "E670: Mix of help file encodings within a language: %s" ), |
5276 | files[fi]); |
5277 | mix = !got_int; |
5278 | got_int = TRUE; |
5279 | } |
5280 | firstline = false; |
5281 | } |
5282 | p1 = vim_strchr(IObuff, '*'); // find first '*' |
5283 | while (p1 != NULL) { |
5284 | p2 = (char_u *)strchr((const char *)p1 + 1, '*'); // Find second '*'. |
5285 | if (p2 != NULL && p2 > p1 + 1) { // Skip "*" and "**". |
5286 | for (s = p1 + 1; s < p2; s++) { |
5287 | if (*s == ' ' || *s == '\t' || *s == '|') { |
5288 | break; |
5289 | } |
5290 | } |
5291 | |
5292 | // Only accept a *tag* when it consists of valid |
5293 | // characters, there is white space before it and is |
5294 | // followed by a white character or end-of-line. |
5295 | if (s == p2 |
5296 | && (p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t') |
5297 | && (vim_strchr((char_u *)" \t\n\r" , s[1]) != NULL |
5298 | || s[1] == '\0')) { |
5299 | *p2 = '\0'; |
5300 | ++p1; |
5301 | s = xmalloc((p2 - p1) + STRLEN(fname) + 2); |
5302 | GA_APPEND(char_u *, &ga, s); |
5303 | sprintf((char *)s, "%s\t%s" , p1, fname); |
5304 | |
5305 | // find next '*' |
5306 | p2 = vim_strchr(p2 + 1, '*'); |
5307 | } |
5308 | } |
5309 | p1 = p2; |
5310 | } |
5311 | line_breakcheck(); |
5312 | } |
5313 | |
5314 | fclose(fd); |
5315 | } |
5316 | |
5317 | FreeWild(filecount, files); |
5318 | |
5319 | if (!got_int && ga.ga_data != NULL) { |
5320 | // Sort the tags. |
5321 | sort_strings((char_u **)ga.ga_data, ga.ga_len); |
5322 | |
5323 | // Check for duplicates. |
5324 | for (int i = 1; i < ga.ga_len; i++) { |
5325 | p1 = ((char_u **)ga.ga_data)[i - 1]; |
5326 | p2 = ((char_u **)ga.ga_data)[i]; |
5327 | while (*p1 == *p2) { |
5328 | if (*p2 == '\t') { |
5329 | *p2 = NUL; |
5330 | vim_snprintf((char *)NameBuff, MAXPATHL, |
5331 | _("E154: Duplicate tag \"%s\" in file %s/%s" ), |
5332 | ((char_u **)ga.ga_data)[i], dir, p2 + 1); |
5333 | EMSG(NameBuff); |
5334 | *p2 = '\t'; |
5335 | break; |
5336 | } |
5337 | ++p1; |
5338 | ++p2; |
5339 | } |
5340 | } |
5341 | |
5342 | if (utf8 == kTrue) { |
5343 | fprintf(fd_tags, "!_TAG_FILE_ENCODING\tutf-8\t//\n" ); |
5344 | } |
5345 | |
5346 | // Write the tags into the file. |
5347 | for (int i = 0; i < ga.ga_len; i++) { |
5348 | s = ((char_u **)ga.ga_data)[i]; |
5349 | if (STRNCMP(s, "help-tags\t" , 10) == 0) { |
5350 | // help-tags entry was added in formatted form |
5351 | fputs((char *)s, fd_tags); |
5352 | } else { |
5353 | fprintf(fd_tags, "%s\t/" "*" , s); |
5354 | for (p1 = s; *p1 != '\t'; p1++) { |
5355 | // insert backslash before '\\' and '/' |
5356 | if (*p1 == '\\' || *p1 == '/') { |
5357 | putc('\\', fd_tags); |
5358 | } |
5359 | putc(*p1, fd_tags); |
5360 | } |
5361 | fprintf(fd_tags, "*\n" ); |
5362 | } |
5363 | } |
5364 | } |
5365 | if (mix) { |
5366 | got_int = false; // continue with other languages |
5367 | } |
5368 | |
5369 | GA_DEEP_CLEAR_PTR(&ga); |
5370 | fclose(fd_tags); // there is no check for an error... |
5371 | } |
5372 | |
5373 | /// Generate tags in one help directory, taking care of translations. |
5374 | static void do_helptags(char_u *dirname, bool add_help_tags) |
5375 | { |
5376 | int len; |
5377 | garray_T ga; |
5378 | char_u lang[2]; |
5379 | char_u ext[5]; |
5380 | char_u fname[8]; |
5381 | int filecount; |
5382 | char_u **files; |
5383 | |
5384 | // Get a list of all files in the help directory and in subdirectories. |
5385 | STRLCPY(NameBuff, dirname, sizeof(NameBuff)); |
5386 | if (!add_pathsep((char *)NameBuff) |
5387 | || STRLCAT(NameBuff, "**" , sizeof(NameBuff)) >= MAXPATHL) { |
5388 | EMSG(_(e_fnametoolong)); |
5389 | return; |
5390 | } |
5391 | |
5392 | // Note: We cannot just do `&NameBuff` because it is a statically sized array |
5393 | // so `NameBuff == &NameBuff` according to C semantics. |
5394 | char_u *buff_list[1] = {NameBuff}; |
5395 | if (gen_expand_wildcards(1, buff_list, &filecount, &files, |
5396 | EW_FILE|EW_SILENT) == FAIL |
5397 | || filecount == 0) { |
5398 | EMSG2(_("E151: No match: %s" ), NameBuff); |
5399 | return; |
5400 | } |
5401 | |
5402 | /* Go over all files in the directory to find out what languages are |
5403 | * present. */ |
5404 | int j; |
5405 | ga_init(&ga, 1, 10); |
5406 | for (int i = 0; i < filecount; i++) { |
5407 | len = (int)STRLEN(files[i]); |
5408 | if (len <= 4) { |
5409 | continue; |
5410 | } |
5411 | if (STRICMP(files[i] + len - 4, ".txt" ) == 0) { |
5412 | /* ".txt" -> language "en" */ |
5413 | lang[0] = 'e'; |
5414 | lang[1] = 'n'; |
5415 | } else if (files[i][len - 4] == '.' |
5416 | && ASCII_ISALPHA(files[i][len - 3]) |
5417 | && ASCII_ISALPHA(files[i][len - 2]) |
5418 | && TOLOWER_ASC(files[i][len - 1]) == 'x') { |
5419 | /* ".abx" -> language "ab" */ |
5420 | lang[0] = TOLOWER_ASC(files[i][len - 3]); |
5421 | lang[1] = TOLOWER_ASC(files[i][len - 2]); |
5422 | } else |
5423 | continue; |
5424 | |
5425 | // Did we find this language already? |
5426 | for (j = 0; j < ga.ga_len; j += 2) { |
5427 | if (STRNCMP(lang, ((char_u *)ga.ga_data) + j, 2) == 0) { |
5428 | break; |
5429 | } |
5430 | } |
5431 | if (j == ga.ga_len) { |
5432 | // New language, add it. |
5433 | ga_grow(&ga, 2); |
5434 | ((char_u *)ga.ga_data)[ga.ga_len++] = lang[0]; |
5435 | ((char_u *)ga.ga_data)[ga.ga_len++] = lang[1]; |
5436 | } |
5437 | } |
5438 | |
5439 | /* |
5440 | * Loop over the found languages to generate a tags file for each one. |
5441 | */ |
5442 | for (j = 0; j < ga.ga_len; j += 2) { |
5443 | STRCPY(fname, "tags-xx" ); |
5444 | fname[5] = ((char_u *)ga.ga_data)[j]; |
5445 | fname[6] = ((char_u *)ga.ga_data)[j + 1]; |
5446 | if (fname[5] == 'e' && fname[6] == 'n') { |
5447 | /* English is an exception: use ".txt" and "tags". */ |
5448 | fname[4] = NUL; |
5449 | STRCPY(ext, ".txt" ); |
5450 | } else { |
5451 | /* Language "ab" uses ".abx" and "tags-ab". */ |
5452 | STRCPY(ext, ".xxx" ); |
5453 | ext[1] = fname[5]; |
5454 | ext[2] = fname[6]; |
5455 | } |
5456 | helptags_one(dirname, ext, fname, add_help_tags); |
5457 | } |
5458 | |
5459 | ga_clear(&ga); |
5460 | FreeWild(filecount, files); |
5461 | } |
5462 | |
5463 | static void |
5464 | helptags_cb(char_u *fname, void *cookie) |
5465 | { |
5466 | do_helptags(fname, *(bool *)cookie); |
5467 | } |
5468 | |
5469 | /* |
5470 | * ":helptags" |
5471 | */ |
5472 | void ex_helptags(exarg_T *eap) |
5473 | { |
5474 | expand_T xpc; |
5475 | char_u *dirname; |
5476 | bool add_help_tags = false; |
5477 | |
5478 | /* Check for ":helptags ++t {dir}". */ |
5479 | if (STRNCMP(eap->arg, "++t" , 3) == 0 && ascii_iswhite(eap->arg[3])) { |
5480 | add_help_tags = true; |
5481 | eap->arg = skipwhite(eap->arg + 3); |
5482 | } |
5483 | |
5484 | if (STRCMP(eap->arg, "ALL" ) == 0) { |
5485 | do_in_path(p_rtp, (char_u *)"doc" , DIP_ALL + DIP_DIR, |
5486 | helptags_cb, &add_help_tags); |
5487 | } else { |
5488 | ExpandInit(&xpc); |
5489 | xpc.xp_context = EXPAND_DIRECTORIES; |
5490 | dirname = ExpandOne(&xpc, eap->arg, NULL, |
5491 | WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE); |
5492 | if (dirname == NULL || !os_isdir(dirname)) { |
5493 | EMSG2(_("E150: Not a directory: %s" ), eap->arg); |
5494 | } else { |
5495 | do_helptags(dirname, add_help_tags); |
5496 | } |
5497 | xfree(dirname); |
5498 | } |
5499 | } |
5500 | |
5501 | /* |
5502 | * ":helpclose": Close one help window |
5503 | */ |
5504 | void ex_helpclose(exarg_T *eap) |
5505 | { |
5506 | FOR_ALL_WINDOWS_IN_TAB(win, curtab) { |
5507 | if (bt_help(win->w_buffer)) { |
5508 | win_close(win, false); |
5509 | return; |
5510 | } |
5511 | } |
5512 | } |
5513 | |
5514 | /// Shows the effects of the :substitute command being typed ('inccommand'). |
5515 | /// If inccommand=split, shows a preview window and later restores the layout. |
5516 | static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, |
5517 | PreviewLines *preview_lines, int hl_id, int src_id) |
5518 | FUNC_ATTR_NONNULL_ALL |
5519 | { |
5520 | static handle_T bufnr = 0; // special buffer, re-used on each visit |
5521 | |
5522 | win_T *save_curwin = curwin; |
5523 | cmdmod_T save_cmdmod = cmdmod; |
5524 | char_u *save_shm_p = vim_strsave(p_shm); |
5525 | PreviewLines lines = *preview_lines; |
5526 | buf_T *orig_buf = curbuf; |
5527 | |
5528 | // We keep a special-purpose buffer around, but don't assume it exists. |
5529 | buf_T *preview_buf = bufnr ? buflist_findnr(bufnr) : 0; |
5530 | cmdmod.tab = 0; // disable :tab modifier |
5531 | cmdmod.noswapfile = true; // disable swap for preview buffer |
5532 | // disable file info message |
5533 | set_string_option_direct((char_u *)"shm" , -1, (char_u *)"F" , OPT_FREE, |
5534 | SID_NONE); |
5535 | |
5536 | bool outside_curline = (eap->line1 != old_cusr.lnum |
5537 | || eap->line2 != old_cusr.lnum); |
5538 | bool split = outside_curline && (*p_icm != 'n'); |
5539 | if (preview_buf == curbuf) { // Preview buffer cannot preview itself! |
5540 | split = false; |
5541 | preview_buf = NULL; |
5542 | } |
5543 | |
5544 | // Place cursor on nearest matching line, to undo do_sub() cursor placement. |
5545 | for (size_t i = 0; i < lines.subresults.size; i++) { |
5546 | SubResult curres = lines.subresults.items[i]; |
5547 | if (curres.start.lnum >= old_cusr.lnum) { |
5548 | curwin->w_cursor.lnum = curres.start.lnum; |
5549 | curwin->w_cursor.col = curres.start.col; |
5550 | break; |
5551 | } // Else: All matches are above, do_sub() already placed cursor. |
5552 | } |
5553 | |
5554 | // Width of the "| lnum|..." column which displays the line numbers. |
5555 | linenr_T highest_num_line = 0; |
5556 | int col_width = 0; |
5557 | |
5558 | if (split && win_split((int)p_cwh, WSP_BOT) != FAIL) { |
5559 | buf_open_scratch(preview_buf ? bufnr : 0, "[Preview]" ); |
5560 | buf_clear(); |
5561 | preview_buf = curbuf; |
5562 | bufnr = preview_buf->handle; |
5563 | curbuf->b_p_bl = false; |
5564 | curbuf->b_p_ma = true; |
5565 | curbuf->b_p_ul = -1; |
5566 | curbuf->b_p_tw = 0; // Reset 'textwidth' (was set by ftplugin) |
5567 | curwin->w_p_cul = false; |
5568 | curwin->w_p_cuc = false; |
5569 | curwin->w_p_spell = false; |
5570 | curwin->w_p_fen = false; |
5571 | |
5572 | if (lines.subresults.size > 0) { |
5573 | highest_num_line = kv_last(lines.subresults).end.lnum; |
5574 | col_width = log10(highest_num_line) + 1 + 3; |
5575 | } |
5576 | } |
5577 | |
5578 | char *str = NULL; // construct the line to show in here |
5579 | size_t old_line_size = 0; |
5580 | size_t line_size = 0; |
5581 | linenr_T linenr_preview = 0; // last line added to preview buffer |
5582 | linenr_T linenr_origbuf = 0; // last line added to original buffer |
5583 | linenr_T next_linenr = 0; // next line to show for the match |
5584 | |
5585 | for (size_t matchidx = 0; matchidx < lines.subresults.size; matchidx++) { |
5586 | SubResult match = lines.subresults.items[matchidx]; |
5587 | |
5588 | if (split && preview_buf) { |
5589 | lpos_T p_start = { 0, match.start.col }; // match starts here in preview |
5590 | lpos_T p_end = { 0, match.end.col }; // ... and ends here |
5591 | |
5592 | if (match.pre_match == 0) { |
5593 | next_linenr = match.start.lnum; |
5594 | } else { |
5595 | next_linenr = match.pre_match; |
5596 | } |
5597 | // Don't add a line twice |
5598 | if (next_linenr == linenr_origbuf) { |
5599 | next_linenr++; |
5600 | p_start.lnum = linenr_preview; // might be redefined below |
5601 | p_end.lnum = linenr_preview; // might be redefined below |
5602 | } |
5603 | |
5604 | for (; next_linenr <= match.end.lnum; next_linenr++) { |
5605 | if (next_linenr == match.start.lnum) { |
5606 | p_start.lnum = linenr_preview + 1; |
5607 | } |
5608 | if (next_linenr == match.end.lnum) { |
5609 | p_end.lnum = linenr_preview + 1; |
5610 | } |
5611 | char *line; |
5612 | if (next_linenr == orig_buf->b_ml.ml_line_count + 1) { |
5613 | line = "" ; |
5614 | } else { |
5615 | line = (char *)ml_get_buf(orig_buf, next_linenr, false); |
5616 | line_size = strlen(line) + col_width + 1; |
5617 | |
5618 | // Reallocate if line not long enough |
5619 | if (line_size > old_line_size) { |
5620 | str = xrealloc(str, line_size * sizeof(char)); |
5621 | old_line_size = line_size; |
5622 | } |
5623 | } |
5624 | // Put "|lnum| line" into `str` and append it to the preview buffer. |
5625 | snprintf(str, line_size, "|%*ld| %s" , col_width - 3, |
5626 | next_linenr, line); |
5627 | if (linenr_preview == 0) { |
5628 | ml_replace(1, (char_u *)str, true); |
5629 | } else { |
5630 | ml_append(linenr_preview, (char_u *)str, (colnr_T)line_size, false); |
5631 | } |
5632 | linenr_preview += 1; |
5633 | } |
5634 | linenr_origbuf = match.end.lnum; |
5635 | |
5636 | bufhl_add_hl_pos_offset(preview_buf, src_id, hl_id, p_start, |
5637 | p_end, col_width); |
5638 | } |
5639 | bufhl_add_hl_pos_offset(orig_buf, src_id, hl_id, match.start, |
5640 | match.end, 0); |
5641 | } |
5642 | xfree(str); |
5643 | |
5644 | redraw_later(SOME_VALID); |
5645 | win_enter(save_curwin, false); // Return to original window |
5646 | update_topline(); |
5647 | |
5648 | // Update screen now. Must do this _before_ close_windows(). |
5649 | int save_rd = RedrawingDisabled; |
5650 | RedrawingDisabled = 0; |
5651 | update_screen(SOME_VALID); |
5652 | RedrawingDisabled = save_rd; |
5653 | |
5654 | set_string_option_direct((char_u *)"shm" , -1, save_shm_p, OPT_FREE, SID_NONE); |
5655 | xfree(save_shm_p); |
5656 | |
5657 | cmdmod = save_cmdmod; |
5658 | |
5659 | return preview_buf; |
5660 | } |
5661 | |
5662 | /// :substitute command |
5663 | /// |
5664 | /// If 'inccommand' is empty: calls do_sub(). |
5665 | /// If 'inccommand' is set: shows a "live" preview then removes the changes. |
5666 | /// from undo history. |
5667 | void ex_substitute(exarg_T *eap) |
5668 | { |
5669 | bool preview = (State & CMDPREVIEW); |
5670 | if (*p_icm == NUL || !preview) { // 'inccommand' is disabled |
5671 | (void)do_sub(eap, profile_zero(), true); |
5672 | return; |
5673 | } |
5674 | |
5675 | block_autocmds(); // Disable events during command preview. |
5676 | |
5677 | char_u *save_eap = eap->arg; |
5678 | garray_T save_view; |
5679 | win_size_save(&save_view); // Save current window sizes. |
5680 | save_search_patterns(); |
5681 | int save_changedtick = buf_get_changedtick(curbuf); |
5682 | time_t save_b_u_time_cur = curbuf->b_u_time_cur; |
5683 | u_header_T *save_b_u_newhead = curbuf->b_u_newhead; |
5684 | long save_b_p_ul = curbuf->b_p_ul; |
5685 | int save_w_p_cul = curwin->w_p_cul; |
5686 | int save_w_p_cuc = curwin->w_p_cuc; |
5687 | |
5688 | curbuf->b_p_ul = LONG_MAX; // make sure we can undo all changes |
5689 | curwin->w_p_cul = false; // Disable 'cursorline' |
5690 | curwin->w_p_cuc = false; // Disable 'cursorcolumn' |
5691 | |
5692 | // Don't show search highlighting during live substitution |
5693 | bool save_hls = p_hls; |
5694 | p_hls = false; |
5695 | buf_T *preview_buf = do_sub(eap, profile_setlimit(p_rdt), false); |
5696 | p_hls = save_hls; |
5697 | |
5698 | if (save_changedtick != buf_get_changedtick(curbuf)) { |
5699 | // Undo invisibly. This also moves the cursor! |
5700 | if (!u_undo_and_forget(1)) { abort(); } |
5701 | // Restore newhead. It is meaningless when curhead is valid, but we must |
5702 | // restore it so that undotree() is identical before/after the preview. |
5703 | curbuf->b_u_newhead = save_b_u_newhead; |
5704 | curbuf->b_u_time_cur = save_b_u_time_cur; |
5705 | buf_set_changedtick(curbuf, save_changedtick); |
5706 | } |
5707 | if (buf_valid(preview_buf)) { |
5708 | // XXX: Must do this *after* u_undo_and_forget(), why? |
5709 | close_windows(preview_buf, false); |
5710 | } |
5711 | curbuf->b_p_ul = save_b_p_ul; |
5712 | curwin->w_p_cul = save_w_p_cul; // Restore 'cursorline' |
5713 | curwin->w_p_cuc = save_w_p_cuc; // Restore 'cursorcolumn' |
5714 | eap->arg = save_eap; |
5715 | restore_search_patterns(); |
5716 | win_size_restore(&save_view); |
5717 | ga_clear(&save_view); |
5718 | unblock_autocmds(); |
5719 | } |
5720 | |
5721 | /// Skip over the pattern argument of ":vimgrep /pat/[g][j]". |
5722 | /// Put the start of the pattern in "*s", unless "s" is NULL. |
5723 | /// If "flags" is not NULL put the flags in it: VGR_GLOBAL, VGR_NOJUMP. |
5724 | /// If "s" is not NULL terminate the pattern with a NUL. |
5725 | /// Return a pointer to the char just past the pattern plus flags. |
5726 | char_u *skip_vimgrep_pat(char_u *p, char_u **s, int *flags) |
5727 | { |
5728 | int c; |
5729 | |
5730 | if (vim_isIDc(*p)) { |
5731 | // ":vimgrep pattern fname" |
5732 | if (s != NULL) { |
5733 | *s = p; |
5734 | } |
5735 | p = skiptowhite(p); |
5736 | if (s != NULL && *p != NUL) { |
5737 | *p++ = NUL; |
5738 | } |
5739 | } else { |
5740 | // ":vimgrep /pattern/[g][j] fname" |
5741 | if (s != NULL) { |
5742 | *s = p + 1; |
5743 | } |
5744 | c = *p; |
5745 | p = skip_regexp(p + 1, c, true, NULL); |
5746 | if (*p != c) { |
5747 | return NULL; |
5748 | } |
5749 | |
5750 | // Truncate the pattern. |
5751 | if (s != NULL) { |
5752 | *p = NUL; |
5753 | } |
5754 | p++; |
5755 | |
5756 | // Find the flags |
5757 | while (*p == 'g' || *p == 'j') { |
5758 | if (flags != NULL) { |
5759 | if (*p == 'g') { |
5760 | *flags |= VGR_GLOBAL; |
5761 | } else { |
5762 | *flags |= VGR_NOJUMP; |
5763 | } |
5764 | } |
5765 | p++; |
5766 | } |
5767 | } |
5768 | return p; |
5769 | } |
5770 | |
5771 | /// List v:oldfiles in a nice way. |
5772 | void ex_oldfiles(exarg_T *eap) |
5773 | { |
5774 | list_T *l = get_vim_var_list(VV_OLDFILES); |
5775 | long nr = 0; |
5776 | |
5777 | if (l == NULL) { |
5778 | msg((char_u *)_("No old files" )); |
5779 | } else { |
5780 | msg_start(); |
5781 | msg_scroll = true; |
5782 | TV_LIST_ITER(l, li, { |
5783 | if (got_int) { |
5784 | break; |
5785 | } |
5786 | nr++; |
5787 | const char *fname = tv_get_string(TV_LIST_ITEM_TV(li)); |
5788 | if (!message_filtered((char_u *)fname)) { |
5789 | msg_outnum(nr); |
5790 | MSG_PUTS(": " ); |
5791 | msg_outtrans((char_u *)tv_get_string(TV_LIST_ITEM_TV(li))); |
5792 | msg_clr_eos(); |
5793 | msg_putchar('\n'); |
5794 | ui_flush(); // output one line at a time |
5795 | os_breakcheck(); |
5796 | } |
5797 | }); |
5798 | |
5799 | // Assume "got_int" was set to truncate the listing. |
5800 | got_int = false; |
5801 | |
5802 | // File selection prompt on ":browse oldfiles" |
5803 | if (cmdmod.browse) { |
5804 | quit_more = false; |
5805 | nr = prompt_for_number(false); |
5806 | msg_starthere(); |
5807 | if (nr > 0 && nr <= tv_list_len(l)) { |
5808 | const char *const p = tv_list_find_str(l, nr - 1); |
5809 | if (p == NULL) { |
5810 | return; |
5811 | } |
5812 | char *const s = (char *)expand_env_save((char_u *)p); |
5813 | eap->arg = (char_u *)s; |
5814 | eap->cmdidx = CMD_edit; |
5815 | cmdmod.browse = false; |
5816 | do_exedit(eap, NULL); |
5817 | xfree(s); |
5818 | } |
5819 | } |
5820 | } |
5821 | } |
5822 | |