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 | * mark.c: functions for setting marks and jumping to them |
6 | */ |
7 | |
8 | #include <assert.h> |
9 | #include <inttypes.h> |
10 | #include <string.h> |
11 | #include <limits.h> |
12 | |
13 | #include "nvim/vim.h" |
14 | #include "nvim/ascii.h" |
15 | #include "nvim/mark.h" |
16 | #include "nvim/buffer.h" |
17 | #include "nvim/charset.h" |
18 | #include "nvim/diff.h" |
19 | #include "nvim/eval.h" |
20 | #include "nvim/ex_cmds.h" |
21 | #include "nvim/fileio.h" |
22 | #include "nvim/fold.h" |
23 | #include "nvim/mbyte.h" |
24 | #include "nvim/memline.h" |
25 | #include "nvim/memory.h" |
26 | #include "nvim/message.h" |
27 | #include "nvim/normal.h" |
28 | #include "nvim/option.h" |
29 | #include "nvim/path.h" |
30 | #include "nvim/quickfix.h" |
31 | #include "nvim/search.h" |
32 | #include "nvim/sign.h" |
33 | #include "nvim/strings.h" |
34 | #include "nvim/ui.h" |
35 | #include "nvim/os/os.h" |
36 | #include "nvim/os/time.h" |
37 | #include "nvim/os/input.h" |
38 | |
39 | /* |
40 | * This file contains routines to maintain and manipulate marks. |
41 | */ |
42 | |
43 | /* |
44 | * If a named file mark's lnum is non-zero, it is valid. |
45 | * If a named file mark's fnum is non-zero, it is for an existing buffer, |
46 | * otherwise it is from .shada and namedfm[n].fname is the file name. |
47 | * There are marks 'A - 'Z (set by user) and '0 to '9 (set when writing |
48 | * shada). |
49 | */ |
50 | |
51 | /// Global marks (marks with file number or name) |
52 | static xfmark_T namedfm[NGLOBALMARKS]; |
53 | |
54 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
55 | # include "mark.c.generated.h" |
56 | #endif |
57 | /* |
58 | * Set named mark "c" at current cursor position. |
59 | * Returns OK on success, FAIL if bad name given. |
60 | */ |
61 | int setmark(int c) |
62 | { |
63 | return setmark_pos(c, &curwin->w_cursor, curbuf->b_fnum); |
64 | } |
65 | |
66 | /// Free fmark_T item |
67 | void free_fmark(fmark_T fm) |
68 | { |
69 | tv_dict_unref(fm.additional_data); |
70 | } |
71 | |
72 | /// Free xfmark_T item |
73 | void free_xfmark(xfmark_T fm) |
74 | { |
75 | xfree(fm.fname); |
76 | free_fmark(fm.fmark); |
77 | } |
78 | |
79 | /// Free and clear fmark_T item |
80 | void clear_fmark(fmark_T *fm) |
81 | FUNC_ATTR_NONNULL_ALL |
82 | { |
83 | free_fmark(*fm); |
84 | memset(fm, 0, sizeof(*fm)); |
85 | } |
86 | |
87 | /* |
88 | * Set named mark "c" to position "pos". |
89 | * When "c" is upper case use file "fnum". |
90 | * Returns OK on success, FAIL if bad name given. |
91 | */ |
92 | int setmark_pos(int c, pos_T *pos, int fnum) |
93 | { |
94 | int i; |
95 | |
96 | /* Check for a special key (may cause islower() to crash). */ |
97 | if (c < 0) |
98 | return FAIL; |
99 | |
100 | if (c == '\'' || c == '`') { |
101 | if (pos == &curwin->w_cursor) { |
102 | setpcmark(); |
103 | /* keep it even when the cursor doesn't move */ |
104 | curwin->w_prev_pcmark = curwin->w_pcmark; |
105 | } else |
106 | curwin->w_pcmark = *pos; |
107 | return OK; |
108 | } |
109 | |
110 | // Can't set a mark in a non-existant buffer. |
111 | buf_T *buf = buflist_findnr(fnum); |
112 | if (buf == NULL) { |
113 | return FAIL; |
114 | } |
115 | |
116 | if (c == '"') { |
117 | RESET_FMARK(&buf->b_last_cursor, *pos, buf->b_fnum); |
118 | return OK; |
119 | } |
120 | |
121 | /* Allow setting '[ and '] for an autocommand that simulates reading a |
122 | * file. */ |
123 | if (c == '[') { |
124 | buf->b_op_start = *pos; |
125 | return OK; |
126 | } |
127 | if (c == ']') { |
128 | buf->b_op_end = *pos; |
129 | return OK; |
130 | } |
131 | |
132 | if (c == '<' || c == '>') { |
133 | if (c == '<') { |
134 | buf->b_visual.vi_start = *pos; |
135 | } else { |
136 | buf->b_visual.vi_end = *pos; |
137 | } |
138 | if (buf->b_visual.vi_mode == NUL) { |
139 | // Visual_mode has not yet been set, use a sane default. |
140 | buf->b_visual.vi_mode = 'v'; |
141 | } |
142 | return OK; |
143 | } |
144 | |
145 | if (ASCII_ISLOWER(c)) { |
146 | i = c - 'a'; |
147 | RESET_FMARK(buf->b_namedm + i, *pos, fnum); |
148 | return OK; |
149 | } |
150 | if (ASCII_ISUPPER(c) || ascii_isdigit(c)) { |
151 | if (ascii_isdigit(c)) { |
152 | i = c - '0' + NMARKS; |
153 | } else { |
154 | i = c - 'A'; |
155 | } |
156 | RESET_XFMARK(namedfm + i, *pos, fnum, NULL); |
157 | return OK; |
158 | } |
159 | return FAIL; |
160 | } |
161 | |
162 | /* |
163 | * Set the previous context mark to the current position and add it to the |
164 | * jump list. |
165 | */ |
166 | void setpcmark(void) |
167 | { |
168 | xfmark_T *fm; |
169 | |
170 | /* for :global the mark is set only once */ |
171 | if (global_busy || listcmd_busy || cmdmod.keepjumps) |
172 | return; |
173 | |
174 | curwin->w_prev_pcmark = curwin->w_pcmark; |
175 | curwin->w_pcmark = curwin->w_cursor; |
176 | |
177 | if (curwin->w_pcmark.lnum == 0) { |
178 | curwin->w_pcmark.lnum = 1; |
179 | } |
180 | |
181 | /* If jumplist is full: remove oldest entry */ |
182 | if (++curwin->w_jumplistlen > JUMPLISTSIZE) { |
183 | curwin->w_jumplistlen = JUMPLISTSIZE; |
184 | free_xfmark(curwin->w_jumplist[0]); |
185 | memmove(&curwin->w_jumplist[0], &curwin->w_jumplist[1], |
186 | (JUMPLISTSIZE - 1) * sizeof(curwin->w_jumplist[0])); |
187 | } |
188 | curwin->w_jumplistidx = curwin->w_jumplistlen; |
189 | fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1]; |
190 | |
191 | SET_XFMARK(fm, curwin->w_pcmark, curbuf->b_fnum, NULL); |
192 | } |
193 | |
194 | /* |
195 | * To change context, call setpcmark(), then move the current position to |
196 | * where ever, then call checkpcmark(). This ensures that the previous |
197 | * context will only be changed if the cursor moved to a different line. |
198 | * If pcmark was deleted (with "dG") the previous mark is restored. |
199 | */ |
200 | void checkpcmark(void) |
201 | { |
202 | if (curwin->w_prev_pcmark.lnum != 0 |
203 | && (equalpos(curwin->w_pcmark, curwin->w_cursor) |
204 | || curwin->w_pcmark.lnum == 0)) { |
205 | curwin->w_pcmark = curwin->w_prev_pcmark; |
206 | curwin->w_prev_pcmark.lnum = 0; /* Show it has been checked */ |
207 | } |
208 | } |
209 | |
210 | /* |
211 | * move "count" positions in the jump list (count may be negative) |
212 | */ |
213 | pos_T *movemark(int count) |
214 | { |
215 | pos_T *pos; |
216 | xfmark_T *jmp; |
217 | |
218 | cleanup_jumplist(curwin, true); |
219 | |
220 | if (curwin->w_jumplistlen == 0) /* nothing to jump to */ |
221 | return (pos_T *)NULL; |
222 | |
223 | for (;; ) { |
224 | if (curwin->w_jumplistidx + count < 0 |
225 | || curwin->w_jumplistidx + count >= curwin->w_jumplistlen) |
226 | return (pos_T *)NULL; |
227 | |
228 | /* |
229 | * if first CTRL-O or CTRL-I command after a jump, add cursor position |
230 | * to list. Careful: If there are duplicates (CTRL-O immediately after |
231 | * starting Vim on a file), another entry may have been removed. |
232 | */ |
233 | if (curwin->w_jumplistidx == curwin->w_jumplistlen) { |
234 | setpcmark(); |
235 | --curwin->w_jumplistidx; /* skip the new entry */ |
236 | if (curwin->w_jumplistidx + count < 0) |
237 | return (pos_T *)NULL; |
238 | } |
239 | |
240 | curwin->w_jumplistidx += count; |
241 | |
242 | jmp = curwin->w_jumplist + curwin->w_jumplistidx; |
243 | if (jmp->fmark.fnum == 0) |
244 | fname2fnum(jmp); |
245 | if (jmp->fmark.fnum != curbuf->b_fnum) { |
246 | /* jump to other file */ |
247 | if (buflist_findnr(jmp->fmark.fnum) == NULL) { /* Skip this one .. */ |
248 | count += count < 0 ? -1 : 1; |
249 | continue; |
250 | } |
251 | if (buflist_getfile(jmp->fmark.fnum, jmp->fmark.mark.lnum, |
252 | 0, FALSE) == FAIL) |
253 | return (pos_T *)NULL; |
254 | /* Set lnum again, autocommands my have changed it */ |
255 | curwin->w_cursor = jmp->fmark.mark; |
256 | pos = (pos_T *)-1; |
257 | } else |
258 | pos = &(jmp->fmark.mark); |
259 | return pos; |
260 | } |
261 | } |
262 | |
263 | /* |
264 | * Move "count" positions in the changelist (count may be negative). |
265 | */ |
266 | pos_T *movechangelist(int count) |
267 | { |
268 | int n; |
269 | |
270 | if (curbuf->b_changelistlen == 0) /* nothing to jump to */ |
271 | return (pos_T *)NULL; |
272 | |
273 | n = curwin->w_changelistidx; |
274 | if (n + count < 0) { |
275 | if (n == 0) |
276 | return (pos_T *)NULL; |
277 | n = 0; |
278 | } else if (n + count >= curbuf->b_changelistlen) { |
279 | if (n == curbuf->b_changelistlen - 1) |
280 | return (pos_T *)NULL; |
281 | n = curbuf->b_changelistlen - 1; |
282 | } else |
283 | n += count; |
284 | curwin->w_changelistidx = n; |
285 | return &(curbuf->b_changelist[n].mark); |
286 | } |
287 | |
288 | /* |
289 | * Find mark "c" in buffer pointed to by "buf". |
290 | * If "changefile" is TRUE it's allowed to edit another file for '0, 'A, etc. |
291 | * If "fnum" is not NULL store the fnum there for '0, 'A etc., don't edit |
292 | * another file. |
293 | * Returns: |
294 | * - pointer to pos_T if found. lnum is 0 when mark not set, -1 when mark is |
295 | * in another file which can't be gotten. (caller needs to check lnum!) |
296 | * - NULL if there is no mark called 'c'. |
297 | * - -1 if mark is in other file and jumped there (only if changefile is TRUE) |
298 | */ |
299 | pos_T *getmark_buf(buf_T *buf, int c, int changefile) |
300 | { |
301 | return getmark_buf_fnum(buf, c, changefile, NULL); |
302 | } |
303 | |
304 | pos_T *getmark(int c, int changefile) |
305 | { |
306 | return getmark_buf_fnum(curbuf, c, changefile, NULL); |
307 | } |
308 | |
309 | pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum) |
310 | { |
311 | pos_T *posp; |
312 | pos_T *startp, *endp; |
313 | static pos_T pos_copy; |
314 | |
315 | posp = NULL; |
316 | |
317 | /* Check for special key, can't be a mark name and might cause islower() |
318 | * to crash. */ |
319 | if (c < 0) |
320 | return posp; |
321 | if (c > '~') { // check for islower()/isupper() |
322 | } else if (c == '\'' || c == '`') { // previous context mark |
323 | pos_copy = curwin->w_pcmark; // need to make a copy because |
324 | posp = &pos_copy; // w_pcmark may be changed soon |
325 | } else if (c == '"') { // to pos when leaving buffer |
326 | posp = &(buf->b_last_cursor.mark); |
327 | } else if (c == '^') { // to where Insert mode stopped |
328 | posp = &(buf->b_last_insert.mark); |
329 | } else if (c == '.') { // to where last change was made |
330 | posp = &(buf->b_last_change.mark); |
331 | } else if (c == '[') { // to start of previous operator |
332 | posp = &(buf->b_op_start); |
333 | } else if (c == ']') { // to end of previous operator |
334 | posp = &(buf->b_op_end); |
335 | } else if (c == '{' || c == '}') { // to previous/next paragraph |
336 | pos_T pos; |
337 | oparg_T oa; |
338 | int slcb = listcmd_busy; |
339 | |
340 | pos = curwin->w_cursor; |
341 | listcmd_busy = TRUE; /* avoid that '' is changed */ |
342 | if (findpar(&oa.inclusive, |
343 | c == '}' ? FORWARD : BACKWARD, 1L, NUL, FALSE)) { |
344 | pos_copy = curwin->w_cursor; |
345 | posp = &pos_copy; |
346 | } |
347 | curwin->w_cursor = pos; |
348 | listcmd_busy = slcb; |
349 | } else if (c == '(' || c == ')') { /* to previous/next sentence */ |
350 | pos_T pos; |
351 | int slcb = listcmd_busy; |
352 | |
353 | pos = curwin->w_cursor; |
354 | listcmd_busy = TRUE; /* avoid that '' is changed */ |
355 | if (findsent(c == ')' ? FORWARD : BACKWARD, 1L)) { |
356 | pos_copy = curwin->w_cursor; |
357 | posp = &pos_copy; |
358 | } |
359 | curwin->w_cursor = pos; |
360 | listcmd_busy = slcb; |
361 | } else if (c == '<' || c == '>') { /* start/end of visual area */ |
362 | startp = &buf->b_visual.vi_start; |
363 | endp = &buf->b_visual.vi_end; |
364 | if (((c == '<') == lt(*startp, *endp) || endp->lnum == 0) |
365 | && startp->lnum != 0) { |
366 | posp = startp; |
367 | } else { |
368 | posp = endp; |
369 | } |
370 | |
371 | // For Visual line mode, set mark at begin or end of line |
372 | if (buf->b_visual.vi_mode == 'V') { |
373 | pos_copy = *posp; |
374 | posp = &pos_copy; |
375 | if (c == '<') |
376 | pos_copy.col = 0; |
377 | else |
378 | pos_copy.col = MAXCOL; |
379 | pos_copy.coladd = 0; |
380 | } |
381 | } else if (ASCII_ISLOWER(c)) { /* normal named mark */ |
382 | posp = &(buf->b_namedm[c - 'a'].mark); |
383 | } else if (ASCII_ISUPPER(c) || ascii_isdigit(c)) { /* named file mark */ |
384 | if (ascii_isdigit(c)) |
385 | c = c - '0' + NMARKS; |
386 | else |
387 | c -= 'A'; |
388 | posp = &(namedfm[c].fmark.mark); |
389 | |
390 | if (namedfm[c].fmark.fnum == 0) { |
391 | fname2fnum(&namedfm[c]); |
392 | } |
393 | |
394 | if (fnum != NULL) |
395 | *fnum = namedfm[c].fmark.fnum; |
396 | else if (namedfm[c].fmark.fnum != buf->b_fnum) { |
397 | /* mark is in another file */ |
398 | posp = &pos_copy; |
399 | |
400 | if (namedfm[c].fmark.mark.lnum != 0 |
401 | && changefile && namedfm[c].fmark.fnum) { |
402 | if (buflist_getfile(namedfm[c].fmark.fnum, |
403 | (linenr_T)1, GETF_SETMARK, FALSE) == OK) { |
404 | /* Set the lnum now, autocommands could have changed it */ |
405 | curwin->w_cursor = namedfm[c].fmark.mark; |
406 | return (pos_T *)-1; |
407 | } |
408 | pos_copy.lnum = -1; /* can't get file */ |
409 | } else |
410 | pos_copy.lnum = 0; /* mark exists, but is not valid in |
411 | current buffer */ |
412 | } |
413 | } |
414 | |
415 | return posp; |
416 | } |
417 | |
418 | /* |
419 | * Search for the next named mark in the current file. |
420 | * |
421 | * Returns pointer to pos_T of the next mark or NULL if no mark is found. |
422 | */ |
423 | pos_T * |
424 | getnextmark ( |
425 | pos_T *startpos, /* where to start */ |
426 | int dir, /* direction for search */ |
427 | int begin_line |
428 | ) |
429 | { |
430 | int i; |
431 | pos_T *result = NULL; |
432 | pos_T pos; |
433 | |
434 | pos = *startpos; |
435 | |
436 | /* When searching backward and leaving the cursor on the first non-blank, |
437 | * position must be in a previous line. |
438 | * When searching forward and leaving the cursor on the first non-blank, |
439 | * position must be in a next line. */ |
440 | if (dir == BACKWARD && begin_line) |
441 | pos.col = 0; |
442 | else if (dir == FORWARD && begin_line) |
443 | pos.col = MAXCOL; |
444 | |
445 | for (i = 0; i < NMARKS; i++) { |
446 | if (curbuf->b_namedm[i].mark.lnum > 0) { |
447 | if (dir == FORWARD) { |
448 | if ((result == NULL || lt(curbuf->b_namedm[i].mark, *result)) |
449 | && lt(pos, curbuf->b_namedm[i].mark)) |
450 | result = &curbuf->b_namedm[i].mark; |
451 | } else { |
452 | if ((result == NULL || lt(*result, curbuf->b_namedm[i].mark)) |
453 | && lt(curbuf->b_namedm[i].mark, pos)) |
454 | result = &curbuf->b_namedm[i].mark; |
455 | } |
456 | } |
457 | } |
458 | |
459 | return result; |
460 | } |
461 | |
462 | /* |
463 | * For an xtended filemark: set the fnum from the fname. |
464 | * This is used for marks obtained from the .shada file. It's postponed |
465 | * until the mark is used to avoid a long startup delay. |
466 | */ |
467 | static void fname2fnum(xfmark_T *fm) |
468 | { |
469 | char_u *p; |
470 | |
471 | if (fm->fname != NULL) { |
472 | /* |
473 | * First expand "~/" in the file name to the home directory. |
474 | * Don't expand the whole name, it may contain other '~' chars. |
475 | */ |
476 | if (fm->fname[0] == '~' && (fm->fname[1] == '/' |
477 | #ifdef BACKSLASH_IN_FILENAME |
478 | || fm->fname[1] == '\\' |
479 | #endif |
480 | )) { |
481 | int len; |
482 | |
483 | expand_env((char_u *)"~/" , NameBuff, MAXPATHL); |
484 | len = (int)STRLEN(NameBuff); |
485 | STRLCPY(NameBuff + len, fm->fname + 2, MAXPATHL - len); |
486 | } else |
487 | STRLCPY(NameBuff, fm->fname, MAXPATHL); |
488 | |
489 | /* Try to shorten the file name. */ |
490 | os_dirname(IObuff, IOSIZE); |
491 | p = path_shorten_fname(NameBuff, IObuff); |
492 | |
493 | // buflist_new() will call fmarks_check_names() |
494 | (void)buflist_new(NameBuff, p, (linenr_T)1, 0); |
495 | } |
496 | } |
497 | |
498 | /* |
499 | * Check all file marks for a name that matches the file name in buf. |
500 | * May replace the name with an fnum. |
501 | * Used for marks that come from the .shada file. |
502 | */ |
503 | void fmarks_check_names(buf_T *buf) |
504 | { |
505 | char_u *name = buf->b_ffname; |
506 | int i; |
507 | |
508 | if (buf->b_ffname == NULL) |
509 | return; |
510 | |
511 | for (i = 0; i < NGLOBALMARKS; ++i) |
512 | fmarks_check_one(&namedfm[i], name, buf); |
513 | |
514 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
515 | for (i = 0; i < wp->w_jumplistlen; ++i) { |
516 | fmarks_check_one(&wp->w_jumplist[i], name, buf); |
517 | } |
518 | } |
519 | } |
520 | |
521 | static void fmarks_check_one(xfmark_T *fm, char_u *name, buf_T *buf) |
522 | { |
523 | if (fm->fmark.fnum == 0 |
524 | && fm->fname != NULL |
525 | && fnamecmp(name, fm->fname) == 0) { |
526 | fm->fmark.fnum = buf->b_fnum; |
527 | XFREE_CLEAR(fm->fname); |
528 | } |
529 | } |
530 | |
531 | /* |
532 | * Check a if a position from a mark is valid. |
533 | * Give and error message and return FAIL if not. |
534 | */ |
535 | int check_mark(pos_T *pos) |
536 | { |
537 | if (pos == NULL) { |
538 | EMSG(_(e_umark)); |
539 | return FAIL; |
540 | } |
541 | if (pos->lnum <= 0) { |
542 | /* lnum is negative if mark is in another file can can't get that |
543 | * file, error message already give then. */ |
544 | if (pos->lnum == 0) |
545 | EMSG(_(e_marknotset)); |
546 | return FAIL; |
547 | } |
548 | if (pos->lnum > curbuf->b_ml.ml_line_count) { |
549 | EMSG(_(e_markinval)); |
550 | return FAIL; |
551 | } |
552 | return OK; |
553 | } |
554 | |
555 | /// Clear all marks and change list in the given buffer |
556 | /// |
557 | /// Used mainly when trashing the entire buffer during ":e" type commands. |
558 | /// |
559 | /// @param[out] buf Buffer to clear marks in. |
560 | void clrallmarks(buf_T *const buf) |
561 | FUNC_ATTR_NONNULL_ALL |
562 | { |
563 | for (size_t i = 0; i < NMARKS; i++) { |
564 | clear_fmark(&buf->b_namedm[i]); |
565 | } |
566 | clear_fmark(&buf->b_last_cursor); |
567 | buf->b_last_cursor.mark.lnum = 1; |
568 | clear_fmark(&buf->b_last_insert); |
569 | clear_fmark(&buf->b_last_change); |
570 | buf->b_op_start.lnum = 0; // start/end op mark cleared |
571 | buf->b_op_end.lnum = 0; |
572 | for (int i = 0; i < buf->b_changelistlen; i++) { |
573 | clear_fmark(&buf->b_changelist[i]); |
574 | } |
575 | buf->b_changelistlen = 0; |
576 | } |
577 | |
578 | /* |
579 | * Get name of file from a filemark. |
580 | * When it's in the current buffer, return the text at the mark. |
581 | * Returns an allocated string. |
582 | */ |
583 | char_u *fm_getname(fmark_T *fmark, int lead_len) |
584 | { |
585 | if (fmark->fnum == curbuf->b_fnum) /* current buffer */ |
586 | return mark_line(&(fmark->mark), lead_len); |
587 | return buflist_nr2name(fmark->fnum, FALSE, TRUE); |
588 | } |
589 | |
590 | /* |
591 | * Return the line at mark "mp". Truncate to fit in window. |
592 | * The returned string has been allocated. |
593 | */ |
594 | static char_u *mark_line(pos_T *mp, int lead_len) |
595 | { |
596 | char_u *s, *p; |
597 | int len; |
598 | |
599 | if (mp->lnum == 0 || mp->lnum > curbuf->b_ml.ml_line_count) |
600 | return vim_strsave((char_u *)"-invalid-" ); |
601 | assert(Columns >= 0 && (size_t)Columns <= SIZE_MAX); |
602 | // Allow for up to 5 bytes per character. |
603 | s = vim_strnsave(skipwhite(ml_get(mp->lnum)), (size_t)Columns * 5); |
604 | |
605 | // Truncate the line to fit it in the window |
606 | len = 0; |
607 | for (p = s; *p != NUL; MB_PTR_ADV(p)) { |
608 | len += ptr2cells(p); |
609 | if (len >= Columns - lead_len) |
610 | break; |
611 | } |
612 | *p = NUL; |
613 | return s; |
614 | } |
615 | |
616 | /* |
617 | * print the marks |
618 | */ |
619 | void do_marks(exarg_T *eap) |
620 | { |
621 | char_u *arg = eap->arg; |
622 | int i; |
623 | char_u *name; |
624 | |
625 | if (arg != NULL && *arg == NUL) |
626 | arg = NULL; |
627 | |
628 | show_one_mark('\'', arg, &curwin->w_pcmark, NULL, true); |
629 | for (i = 0; i < NMARKS; ++i) |
630 | show_one_mark(i + 'a', arg, &curbuf->b_namedm[i].mark, NULL, true); |
631 | for (i = 0; i < NGLOBALMARKS; ++i) { |
632 | if (namedfm[i].fmark.fnum != 0) |
633 | name = fm_getname(&namedfm[i].fmark, 15); |
634 | else |
635 | name = namedfm[i].fname; |
636 | if (name != NULL) { |
637 | show_one_mark(i >= NMARKS ? i - NMARKS + '0' : i + 'A', |
638 | arg, &namedfm[i].fmark.mark, name, |
639 | namedfm[i].fmark.fnum == curbuf->b_fnum); |
640 | if (namedfm[i].fmark.fnum != 0) |
641 | xfree(name); |
642 | } |
643 | } |
644 | show_one_mark('"', arg, &curbuf->b_last_cursor.mark, NULL, true); |
645 | show_one_mark('[', arg, &curbuf->b_op_start, NULL, true); |
646 | show_one_mark(']', arg, &curbuf->b_op_end, NULL, true); |
647 | show_one_mark('^', arg, &curbuf->b_last_insert.mark, NULL, true); |
648 | show_one_mark('.', arg, &curbuf->b_last_change.mark, NULL, true); |
649 | show_one_mark('<', arg, &curbuf->b_visual.vi_start, NULL, true); |
650 | show_one_mark('>', arg, &curbuf->b_visual.vi_end, NULL, true); |
651 | show_one_mark(-1, arg, NULL, NULL, false); |
652 | } |
653 | |
654 | static void |
655 | show_one_mark( |
656 | int c, |
657 | char_u *arg, |
658 | pos_T *p, |
659 | char_u *name_arg, |
660 | int current // in current file |
661 | ) |
662 | { |
663 | static bool did_title = false; |
664 | bool mustfree = false; |
665 | char_u *name = name_arg; |
666 | |
667 | if (c == -1) { // finish up |
668 | if (did_title) { |
669 | did_title = false; |
670 | } else { |
671 | if (arg == NULL) { |
672 | MSG(_("No marks set" )); |
673 | } else { |
674 | EMSG2(_("E283: No marks matching \"%s\"" ), arg); |
675 | } |
676 | } |
677 | } else if (!got_int |
678 | && (arg == NULL || vim_strchr(arg, c) != NULL) |
679 | && p->lnum != 0) { |
680 | // don't output anything if 'q' typed at --more-- prompt |
681 | if (name == NULL && current) { |
682 | name = mark_line(p, 15); |
683 | mustfree = true; |
684 | } |
685 | if (!message_filtered(name)) { |
686 | if (!did_title) { |
687 | // Highlight title |
688 | msg_puts_title(_("\nmark line col file/text" )); |
689 | did_title = true; |
690 | } |
691 | msg_putchar('\n'); |
692 | if (!got_int) { |
693 | snprintf((char *)IObuff, IOSIZE, " %c %6ld %4d " , c, p->lnum, p->col); |
694 | msg_outtrans(IObuff); |
695 | if (name != NULL) { |
696 | msg_outtrans_attr(name, current ? HL_ATTR(HLF_D) : 0); |
697 | } |
698 | } |
699 | ui_flush(); // show one line at a time |
700 | } |
701 | if (mustfree) { |
702 | xfree(name); |
703 | } |
704 | } |
705 | } |
706 | |
707 | /* |
708 | * ":delmarks[!] [marks]" |
709 | */ |
710 | void ex_delmarks(exarg_T *eap) |
711 | { |
712 | char_u *p; |
713 | int from, to; |
714 | int i; |
715 | int lower; |
716 | int digit; |
717 | int n; |
718 | |
719 | if (*eap->arg == NUL && eap->forceit) |
720 | /* clear all marks */ |
721 | clrallmarks(curbuf); |
722 | else if (eap->forceit) |
723 | EMSG(_(e_invarg)); |
724 | else if (*eap->arg == NUL) |
725 | EMSG(_(e_argreq)); |
726 | else { |
727 | /* clear specified marks only */ |
728 | for (p = eap->arg; *p != NUL; ++p) { |
729 | lower = ASCII_ISLOWER(*p); |
730 | digit = ascii_isdigit(*p); |
731 | if (lower || digit || ASCII_ISUPPER(*p)) { |
732 | if (p[1] == '-') { |
733 | /* clear range of marks */ |
734 | from = *p; |
735 | to = p[2]; |
736 | if (!(lower ? ASCII_ISLOWER(p[2]) |
737 | : (digit ? ascii_isdigit(p[2]) |
738 | : ASCII_ISUPPER(p[2]))) |
739 | || to < from) { |
740 | EMSG2(_(e_invarg2), p); |
741 | return; |
742 | } |
743 | p += 2; |
744 | } else |
745 | /* clear one lower case mark */ |
746 | from = to = *p; |
747 | |
748 | for (i = from; i <= to; ++i) { |
749 | if (lower) { |
750 | curbuf->b_namedm[i - 'a'].mark.lnum = 0; |
751 | } else { |
752 | if (digit) { |
753 | n = i - '0' + NMARKS; |
754 | } else { |
755 | n = i - 'A'; |
756 | } |
757 | namedfm[n].fmark.mark.lnum = 0; |
758 | XFREE_CLEAR(namedfm[n].fname); |
759 | } |
760 | } |
761 | } else |
762 | switch (*p) { |
763 | case '"': CLEAR_FMARK(&curbuf->b_last_cursor); break; |
764 | case '^': CLEAR_FMARK(&curbuf->b_last_insert); break; |
765 | case '.': CLEAR_FMARK(&curbuf->b_last_change); break; |
766 | case '[': curbuf->b_op_start.lnum = 0; break; |
767 | case ']': curbuf->b_op_end.lnum = 0; break; |
768 | case '<': curbuf->b_visual.vi_start.lnum = 0; break; |
769 | case '>': curbuf->b_visual.vi_end.lnum = 0; break; |
770 | case ' ': break; |
771 | default: EMSG2(_(e_invarg2), p); |
772 | return; |
773 | } |
774 | } |
775 | } |
776 | } |
777 | |
778 | /* |
779 | * print the jumplist |
780 | */ |
781 | void ex_jumps(exarg_T *eap) |
782 | { |
783 | int i; |
784 | char_u *name; |
785 | |
786 | cleanup_jumplist(curwin, true); |
787 | // Highlight title |
788 | MSG_PUTS_TITLE(_("\n jump line col file/text" )); |
789 | for (i = 0; i < curwin->w_jumplistlen && !got_int; ++i) { |
790 | if (curwin->w_jumplist[i].fmark.mark.lnum != 0) { |
791 | name = fm_getname(&curwin->w_jumplist[i].fmark, 16); |
792 | |
793 | // apply :filter /pat/ or file name not available |
794 | if (name == NULL || message_filtered(name)) { |
795 | xfree(name); |
796 | continue; |
797 | } |
798 | |
799 | msg_putchar('\n'); |
800 | if (got_int) { |
801 | xfree(name); |
802 | break; |
803 | } |
804 | sprintf((char *)IObuff, "%c %2d %5ld %4d " , |
805 | i == curwin->w_jumplistidx ? '>' : ' ', |
806 | i > curwin->w_jumplistidx ? i - curwin->w_jumplistidx |
807 | : curwin->w_jumplistidx - i, |
808 | curwin->w_jumplist[i].fmark.mark.lnum, |
809 | curwin->w_jumplist[i].fmark.mark.col); |
810 | msg_outtrans(IObuff); |
811 | msg_outtrans_attr(name, |
812 | curwin->w_jumplist[i].fmark.fnum == curbuf->b_fnum |
813 | ? HL_ATTR(HLF_D) : 0); |
814 | xfree(name); |
815 | os_breakcheck(); |
816 | } |
817 | ui_flush(); |
818 | } |
819 | if (curwin->w_jumplistidx == curwin->w_jumplistlen) |
820 | MSG_PUTS("\n>" ); |
821 | } |
822 | |
823 | void ex_clearjumps(exarg_T *eap) |
824 | { |
825 | free_jumplist(curwin); |
826 | curwin->w_jumplistlen = 0; |
827 | curwin->w_jumplistidx = 0; |
828 | } |
829 | |
830 | /* |
831 | * print the changelist |
832 | */ |
833 | void ex_changes(exarg_T *eap) |
834 | { |
835 | int i; |
836 | char_u *name; |
837 | |
838 | // Highlight title |
839 | MSG_PUTS_TITLE(_("\nchange line col text" )); |
840 | |
841 | for (i = 0; i < curbuf->b_changelistlen && !got_int; ++i) { |
842 | if (curbuf->b_changelist[i].mark.lnum != 0) { |
843 | msg_putchar('\n'); |
844 | if (got_int) |
845 | break; |
846 | sprintf((char *)IObuff, "%c %3d %5ld %4d " , |
847 | i == curwin->w_changelistidx ? '>' : ' ', |
848 | i > curwin->w_changelistidx ? i - curwin->w_changelistidx |
849 | : curwin->w_changelistidx - i, |
850 | (long)curbuf->b_changelist[i].mark.lnum, |
851 | curbuf->b_changelist[i].mark.col); |
852 | msg_outtrans(IObuff); |
853 | name = mark_line(&curbuf->b_changelist[i].mark, 17); |
854 | msg_outtrans_attr(name, HL_ATTR(HLF_D)); |
855 | xfree(name); |
856 | os_breakcheck(); |
857 | } |
858 | ui_flush(); |
859 | } |
860 | if (curwin->w_changelistidx == curbuf->b_changelistlen) |
861 | MSG_PUTS("\n>" ); |
862 | } |
863 | |
864 | #define one_adjust(add) \ |
865 | { \ |
866 | lp = add; \ |
867 | if (*lp >= line1 && *lp <= line2) \ |
868 | { \ |
869 | if (amount == MAXLNUM) \ |
870 | *lp = 0; \ |
871 | else \ |
872 | *lp += amount; \ |
873 | } \ |
874 | else if (amount_after && *lp > line2) \ |
875 | *lp += amount_after; \ |
876 | } |
877 | |
878 | /* don't delete the line, just put at first deleted line */ |
879 | #define one_adjust_nodel(add) \ |
880 | { \ |
881 | lp = add; \ |
882 | if (*lp >= line1 && *lp <= line2) \ |
883 | { \ |
884 | if (amount == MAXLNUM) \ |
885 | *lp = line1; \ |
886 | else \ |
887 | *lp += amount; \ |
888 | } \ |
889 | else if (amount_after && *lp > line2) \ |
890 | *lp += amount_after; \ |
891 | } |
892 | |
893 | /* |
894 | * Adjust marks between line1 and line2 (inclusive) to move 'amount' lines. |
895 | * Must be called before changed_*(), appended_lines() or deleted_lines(). |
896 | * May be called before or after changing the text. |
897 | * When deleting lines line1 to line2, use an 'amount' of MAXLNUM: The marks |
898 | * within this range are made invalid. |
899 | * If 'amount_after' is non-zero adjust marks after line2. |
900 | * Example: Delete lines 34 and 35: mark_adjust(34, 35, MAXLNUM, -2); |
901 | * Example: Insert two lines below 55: mark_adjust(56, MAXLNUM, 2, 0); |
902 | * or: mark_adjust(56, 55, MAXLNUM, 2); |
903 | */ |
904 | void mark_adjust(linenr_T line1, |
905 | linenr_T line2, |
906 | long amount, |
907 | long amount_after, |
908 | bool end_temp) |
909 | { |
910 | mark_adjust_internal(line1, line2, amount, amount_after, true, end_temp); |
911 | } |
912 | |
913 | // mark_adjust_nofold() does the same as mark_adjust() but without adjusting |
914 | // folds in any way. Folds must be adjusted manually by the caller. |
915 | // This is only useful when folds need to be moved in a way different to |
916 | // calling foldMarkAdjust() with arguments line1, line2, amount, amount_after, |
917 | // for an example of why this may be necessary, see do_move(). |
918 | void mark_adjust_nofold(linenr_T line1, linenr_T line2, long amount, |
919 | long amount_after, bool end_temp) |
920 | { |
921 | mark_adjust_internal(line1, line2, amount, amount_after, false, end_temp); |
922 | } |
923 | |
924 | static void mark_adjust_internal(linenr_T line1, linenr_T line2, |
925 | long amount, long amount_after, |
926 | bool adjust_folds, bool end_temp) |
927 | { |
928 | int i; |
929 | int fnum = curbuf->b_fnum; |
930 | linenr_T *lp; |
931 | static pos_T initpos = { 1, 0, 0 }; |
932 | |
933 | if (line2 < line1 && amount_after == 0L) /* nothing to do */ |
934 | return; |
935 | |
936 | if (!cmdmod.lockmarks) { |
937 | /* named marks, lower case and upper case */ |
938 | for (i = 0; i < NMARKS; i++) { |
939 | one_adjust(&(curbuf->b_namedm[i].mark.lnum)); |
940 | if (namedfm[i].fmark.fnum == fnum) |
941 | one_adjust_nodel(&(namedfm[i].fmark.mark.lnum)); |
942 | } |
943 | for (i = NMARKS; i < NGLOBALMARKS; i++) { |
944 | if (namedfm[i].fmark.fnum == fnum) |
945 | one_adjust_nodel(&(namedfm[i].fmark.mark.lnum)); |
946 | } |
947 | |
948 | /* last Insert position */ |
949 | one_adjust(&(curbuf->b_last_insert.mark.lnum)); |
950 | |
951 | /* last change position */ |
952 | one_adjust(&(curbuf->b_last_change.mark.lnum)); |
953 | |
954 | /* last cursor position, if it was set */ |
955 | if (!equalpos(curbuf->b_last_cursor.mark, initpos)) |
956 | one_adjust(&(curbuf->b_last_cursor.mark.lnum)); |
957 | |
958 | |
959 | /* list of change positions */ |
960 | for (i = 0; i < curbuf->b_changelistlen; ++i) |
961 | one_adjust_nodel(&(curbuf->b_changelist[i].mark.lnum)); |
962 | |
963 | /* Visual area */ |
964 | one_adjust_nodel(&(curbuf->b_visual.vi_start.lnum)); |
965 | one_adjust_nodel(&(curbuf->b_visual.vi_end.lnum)); |
966 | |
967 | // quickfix marks |
968 | if (!qf_mark_adjust(NULL, line1, line2, amount, amount_after)) { |
969 | curbuf->b_has_qf_entry &= ~BUF_HAS_QF_ENTRY; |
970 | } |
971 | // location lists |
972 | bool found_one = false; |
973 | FOR_ALL_TAB_WINDOWS(tab, win) { |
974 | found_one |= qf_mark_adjust(win, line1, line2, amount, amount_after); |
975 | } |
976 | if (!found_one) { |
977 | curbuf->b_has_qf_entry &= ~BUF_HAS_LL_ENTRY; |
978 | } |
979 | |
980 | sign_mark_adjust(line1, line2, amount, amount_after); |
981 | bufhl_mark_adjust(curbuf, line1, line2, amount, amount_after, end_temp); |
982 | } |
983 | |
984 | /* previous context mark */ |
985 | one_adjust(&(curwin->w_pcmark.lnum)); |
986 | |
987 | /* previous pcmark */ |
988 | one_adjust(&(curwin->w_prev_pcmark.lnum)); |
989 | |
990 | /* saved cursor for formatting */ |
991 | if (saved_cursor.lnum != 0) |
992 | one_adjust_nodel(&(saved_cursor.lnum)); |
993 | |
994 | /* |
995 | * Adjust items in all windows related to the current buffer. |
996 | */ |
997 | FOR_ALL_TAB_WINDOWS(tab, win) { |
998 | if (!cmdmod.lockmarks) { |
999 | /* Marks in the jumplist. When deleting lines, this may create |
1000 | * duplicate marks in the jumplist, they will be removed later. */ |
1001 | for (i = 0; i < win->w_jumplistlen; ++i) { |
1002 | if (win->w_jumplist[i].fmark.fnum == fnum) { |
1003 | one_adjust_nodel(&(win->w_jumplist[i].fmark.mark.lnum)); |
1004 | } |
1005 | } |
1006 | } |
1007 | |
1008 | if (win->w_buffer == curbuf) { |
1009 | if (!cmdmod.lockmarks) { |
1010 | /* marks in the tag stack */ |
1011 | for (i = 0; i < win->w_tagstacklen; i++) { |
1012 | if (win->w_tagstack[i].fmark.fnum == fnum) { |
1013 | one_adjust_nodel(&(win->w_tagstack[i].fmark.mark.lnum)); |
1014 | } |
1015 | } |
1016 | } |
1017 | |
1018 | /* the displayed Visual area */ |
1019 | if (win->w_old_cursor_lnum != 0) { |
1020 | one_adjust_nodel(&(win->w_old_cursor_lnum)); |
1021 | one_adjust_nodel(&(win->w_old_visual_lnum)); |
1022 | } |
1023 | |
1024 | /* topline and cursor position for windows with the same buffer |
1025 | * other than the current window */ |
1026 | if (win != curwin) { |
1027 | if (win->w_topline >= line1 && win->w_topline <= line2) { |
1028 | if (amount == MAXLNUM) { /* topline is deleted */ |
1029 | if (line1 <= 1) { |
1030 | win->w_topline = 1; |
1031 | } else { |
1032 | win->w_topline = line1 - 1; |
1033 | } |
1034 | } else { /* keep topline on the same line */ |
1035 | win->w_topline += amount; |
1036 | } |
1037 | win->w_topfill = 0; |
1038 | } else if (amount_after && win->w_topline > line2) { |
1039 | win->w_topline += amount_after; |
1040 | win->w_topfill = 0; |
1041 | } |
1042 | if (win->w_cursor.lnum >= line1 && win->w_cursor.lnum <= line2) { |
1043 | if (amount == MAXLNUM) { /* line with cursor is deleted */ |
1044 | if (line1 <= 1) { |
1045 | win->w_cursor.lnum = 1; |
1046 | } else { |
1047 | win->w_cursor.lnum = line1 - 1; |
1048 | } |
1049 | win->w_cursor.col = 0; |
1050 | } else { /* keep cursor on the same line */ |
1051 | win->w_cursor.lnum += amount; |
1052 | } |
1053 | } else if (amount_after && win->w_cursor.lnum > line2) { |
1054 | win->w_cursor.lnum += amount_after; |
1055 | } |
1056 | } |
1057 | |
1058 | if (adjust_folds) { |
1059 | foldMarkAdjust(win, line1, line2, amount, amount_after); |
1060 | } |
1061 | } |
1062 | } |
1063 | |
1064 | /* adjust diffs */ |
1065 | diff_mark_adjust(line1, line2, amount, amount_after); |
1066 | } |
1067 | |
1068 | /* This code is used often, needs to be fast. */ |
1069 | #define col_adjust(pp) \ |
1070 | { \ |
1071 | posp = pp; \ |
1072 | if (posp->lnum == lnum && posp->col >= mincol) \ |
1073 | { \ |
1074 | posp->lnum += lnum_amount; \ |
1075 | assert(col_amount > INT_MIN && col_amount <= INT_MAX); \ |
1076 | if (col_amount < 0 && posp->col <= (colnr_T)-col_amount) { \ |
1077 | posp->col = 0; \ |
1078 | } else if (posp->col < spaces_removed) { \ |
1079 | posp->col = (int)col_amount + spaces_removed; \ |
1080 | } else { \ |
1081 | posp->col += (colnr_T)col_amount; \ |
1082 | } \ |
1083 | } \ |
1084 | } |
1085 | |
1086 | // Adjust marks in line "lnum" at column "mincol" and further: add |
1087 | // "lnum_amount" to the line number and add "col_amount" to the column |
1088 | // position. |
1089 | // "spaces_removed" is the number of spaces that were removed, matters when the |
1090 | // cursor is inside them. |
1091 | void mark_col_adjust( |
1092 | linenr_T lnum, colnr_T mincol, long lnum_amount, long col_amount, |
1093 | int spaces_removed) |
1094 | { |
1095 | int i; |
1096 | int fnum = curbuf->b_fnum; |
1097 | pos_T *posp; |
1098 | |
1099 | if ((col_amount == 0L && lnum_amount == 0L) || cmdmod.lockmarks) |
1100 | return; /* nothing to do */ |
1101 | |
1102 | /* named marks, lower case and upper case */ |
1103 | for (i = 0; i < NMARKS; i++) { |
1104 | col_adjust(&(curbuf->b_namedm[i].mark)); |
1105 | if (namedfm[i].fmark.fnum == fnum) |
1106 | col_adjust(&(namedfm[i].fmark.mark)); |
1107 | } |
1108 | for (i = NMARKS; i < NGLOBALMARKS; i++) { |
1109 | if (namedfm[i].fmark.fnum == fnum) |
1110 | col_adjust(&(namedfm[i].fmark.mark)); |
1111 | } |
1112 | |
1113 | /* last Insert position */ |
1114 | col_adjust(&(curbuf->b_last_insert.mark)); |
1115 | |
1116 | /* last change position */ |
1117 | col_adjust(&(curbuf->b_last_change.mark)); |
1118 | |
1119 | /* list of change positions */ |
1120 | for (i = 0; i < curbuf->b_changelistlen; ++i) |
1121 | col_adjust(&(curbuf->b_changelist[i].mark)); |
1122 | |
1123 | /* Visual area */ |
1124 | col_adjust(&(curbuf->b_visual.vi_start)); |
1125 | col_adjust(&(curbuf->b_visual.vi_end)); |
1126 | |
1127 | /* previous context mark */ |
1128 | col_adjust(&(curwin->w_pcmark)); |
1129 | |
1130 | /* previous pcmark */ |
1131 | col_adjust(&(curwin->w_prev_pcmark)); |
1132 | |
1133 | /* saved cursor for formatting */ |
1134 | col_adjust(&saved_cursor); |
1135 | |
1136 | /* |
1137 | * Adjust items in all windows related to the current buffer. |
1138 | */ |
1139 | FOR_ALL_WINDOWS_IN_TAB(win, curtab) { |
1140 | /* marks in the jumplist */ |
1141 | for (i = 0; i < win->w_jumplistlen; ++i) { |
1142 | if (win->w_jumplist[i].fmark.fnum == fnum) { |
1143 | col_adjust(&(win->w_jumplist[i].fmark.mark)); |
1144 | } |
1145 | } |
1146 | |
1147 | if (win->w_buffer == curbuf) { |
1148 | /* marks in the tag stack */ |
1149 | for (i = 0; i < win->w_tagstacklen; i++) { |
1150 | if (win->w_tagstack[i].fmark.fnum == fnum) { |
1151 | col_adjust(&(win->w_tagstack[i].fmark.mark)); |
1152 | } |
1153 | } |
1154 | |
1155 | /* cursor position for other windows with the same buffer */ |
1156 | if (win != curwin) { |
1157 | col_adjust(&win->w_cursor); |
1158 | } |
1159 | } |
1160 | } |
1161 | } |
1162 | |
1163 | // When deleting lines, this may create duplicate marks in the |
1164 | // jumplist. They will be removed here for the specified window. |
1165 | // When "checktail" is true, removes tail jump if it matches current position. |
1166 | void cleanup_jumplist(win_T *wp, bool checktail) |
1167 | { |
1168 | int i; |
1169 | |
1170 | // Load all the files from the jump list. This is |
1171 | // needed to properly clean up duplicate entries, but will take some |
1172 | // time. |
1173 | for (i = 0; i < wp->w_jumplistlen; i++) { |
1174 | if ((wp->w_jumplist[i].fmark.fnum == 0) |
1175 | && (wp->w_jumplist[i].fmark.mark.lnum != 0)) { |
1176 | fname2fnum(&wp->w_jumplist[i]); |
1177 | } |
1178 | } |
1179 | |
1180 | int to = 0; |
1181 | for (int from = 0; from < wp->w_jumplistlen; from++) { |
1182 | if (wp->w_jumplistidx == from) { |
1183 | wp->w_jumplistidx = to; |
1184 | } |
1185 | for (i = from + 1; i < wp->w_jumplistlen; i++) { |
1186 | if (wp->w_jumplist[i].fmark.fnum |
1187 | == wp->w_jumplist[from].fmark.fnum |
1188 | && wp->w_jumplist[from].fmark.fnum != 0 |
1189 | && wp->w_jumplist[i].fmark.mark.lnum |
1190 | == wp->w_jumplist[from].fmark.mark.lnum) { |
1191 | break; |
1192 | } |
1193 | } |
1194 | if (i >= wp->w_jumplistlen) { // no duplicate |
1195 | if (to != from) { |
1196 | // Not using wp->w_jumplist[to++] = wp->w_jumplist[from] because |
1197 | // this way valgrind complains about overlapping source and destination |
1198 | // in memcpy() call. (clang-3.6.0, debug build with -DEXITFREE). |
1199 | wp->w_jumplist[to] = wp->w_jumplist[from]; |
1200 | } |
1201 | to++; |
1202 | } else { |
1203 | xfree(wp->w_jumplist[from].fname); |
1204 | } |
1205 | } |
1206 | if (wp->w_jumplistidx == wp->w_jumplistlen) { |
1207 | wp->w_jumplistidx = to; |
1208 | } |
1209 | wp->w_jumplistlen = to; |
1210 | |
1211 | // When pointer is below last jump, remove the jump if it matches the current |
1212 | // line. This avoids useless/phantom jumps. #9805 |
1213 | if (checktail && wp->w_jumplistlen |
1214 | && wp->w_jumplistidx == wp->w_jumplistlen) { |
1215 | const xfmark_T *fm_last = &wp->w_jumplist[wp->w_jumplistlen - 1]; |
1216 | if (fm_last->fmark.fnum == curbuf->b_fnum |
1217 | && fm_last->fmark.mark.lnum == wp->w_cursor.lnum) { |
1218 | xfree(fm_last->fname); |
1219 | wp->w_jumplistlen--; |
1220 | wp->w_jumplistidx--; |
1221 | } |
1222 | } |
1223 | } |
1224 | |
1225 | /* |
1226 | * Copy the jumplist from window "from" to window "to". |
1227 | */ |
1228 | void copy_jumplist(win_T *from, win_T *to) |
1229 | { |
1230 | int i; |
1231 | |
1232 | for (i = 0; i < from->w_jumplistlen; ++i) { |
1233 | to->w_jumplist[i] = from->w_jumplist[i]; |
1234 | if (from->w_jumplist[i].fname != NULL) |
1235 | to->w_jumplist[i].fname = vim_strsave(from->w_jumplist[i].fname); |
1236 | } |
1237 | to->w_jumplistlen = from->w_jumplistlen; |
1238 | to->w_jumplistidx = from->w_jumplistidx; |
1239 | } |
1240 | |
1241 | /// Iterate over jumplist items |
1242 | /// |
1243 | /// @warning No jumplist-editing functions must be run while iteration is in |
1244 | /// progress. |
1245 | /// |
1246 | /// @param[in] iter Iterator. Pass NULL to start iteration. |
1247 | /// @param[in] win Window for which jump list is processed. |
1248 | /// @param[out] fm Item definition. |
1249 | /// |
1250 | /// @return Pointer that needs to be passed to next `mark_jumplist_iter` call or |
1251 | /// NULL if iteration is over. |
1252 | const void *mark_jumplist_iter(const void *const iter, const win_T *const win, |
1253 | xfmark_T *const fm) |
1254 | FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT |
1255 | { |
1256 | if (iter == NULL && win->w_jumplistlen == 0) { |
1257 | *fm = (xfmark_T) {{{0, 0, 0}, 0, 0, NULL}, NULL}; |
1258 | return NULL; |
1259 | } |
1260 | const xfmark_T *const iter_mark = |
1261 | (iter == NULL |
1262 | ? &(win->w_jumplist[0]) |
1263 | : (const xfmark_T *const) iter); |
1264 | *fm = *iter_mark; |
1265 | if (iter_mark == &(win->w_jumplist[win->w_jumplistlen - 1])) { |
1266 | return NULL; |
1267 | } else { |
1268 | return iter_mark + 1; |
1269 | } |
1270 | } |
1271 | |
1272 | /// Iterate over global marks |
1273 | /// |
1274 | /// @warning No mark-editing functions must be run while iteration is in |
1275 | /// progress. |
1276 | /// |
1277 | /// @param[in] iter Iterator. Pass NULL to start iteration. |
1278 | /// @param[out] name Mark name. |
1279 | /// @param[out] fm Mark definition. |
1280 | /// |
1281 | /// @return Pointer that needs to be passed to next `mark_global_iter` call or |
1282 | /// NULL if iteration is over. |
1283 | const void *mark_global_iter(const void *const iter, char *const name, |
1284 | xfmark_T *const fm) |
1285 | FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT |
1286 | { |
1287 | *name = NUL; |
1288 | const xfmark_T *iter_mark = (iter == NULL |
1289 | ? &(namedfm[0]) |
1290 | : (const xfmark_T *const) iter); |
1291 | while ((size_t) (iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm) |
1292 | && !iter_mark->fmark.mark.lnum) { |
1293 | iter_mark++; |
1294 | } |
1295 | if ((size_t) (iter_mark - &(namedfm[0])) == ARRAY_SIZE(namedfm) |
1296 | || !iter_mark->fmark.mark.lnum) { |
1297 | return NULL; |
1298 | } |
1299 | size_t iter_off = (size_t) (iter_mark - &(namedfm[0])); |
1300 | *name = (char) (iter_off < NMARKS |
1301 | ? 'A' + (char) iter_off |
1302 | : '0' + (char) (iter_off - NMARKS)); |
1303 | *fm = *iter_mark; |
1304 | while ((size_t) (++iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm)) { |
1305 | if (iter_mark->fmark.mark.lnum) { |
1306 | return (const void *) iter_mark; |
1307 | } |
1308 | } |
1309 | return NULL; |
1310 | } |
1311 | |
1312 | /// Get next mark and its name |
1313 | /// |
1314 | /// @param[in] buf Buffer for which next mark is taken. |
1315 | /// @param[in,out] mark_name Pointer to the current mark name. Next mark name |
1316 | /// will be saved at this address as well. |
1317 | /// |
1318 | /// Current mark name must either be NUL, '"', '^', |
1319 | /// '.' or 'a' .. 'z'. If it is neither of these |
1320 | /// behaviour is undefined. |
1321 | /// |
1322 | /// @return Pointer to the next mark or NULL. |
1323 | static inline const fmark_T *next_buffer_mark(const buf_T *const buf, |
1324 | char *const mark_name) |
1325 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT |
1326 | { |
1327 | switch (*mark_name) { |
1328 | case NUL: { |
1329 | *mark_name = '"'; |
1330 | return &(buf->b_last_cursor); |
1331 | } |
1332 | case '"': { |
1333 | *mark_name = '^'; |
1334 | return &(buf->b_last_insert); |
1335 | } |
1336 | case '^': { |
1337 | *mark_name = '.'; |
1338 | return &(buf->b_last_change); |
1339 | } |
1340 | case '.': { |
1341 | *mark_name = 'a'; |
1342 | return &(buf->b_namedm[0]); |
1343 | } |
1344 | case 'z': { |
1345 | return NULL; |
1346 | } |
1347 | default: { |
1348 | (*mark_name)++; |
1349 | return &(buf->b_namedm[*mark_name - 'a']); |
1350 | } |
1351 | } |
1352 | } |
1353 | |
1354 | /// Iterate over buffer marks |
1355 | /// |
1356 | /// @warning No mark-editing functions must be run while iteration is in |
1357 | /// progress. |
1358 | /// |
1359 | /// @param[in] iter Iterator. Pass NULL to start iteration. |
1360 | /// @param[in] buf Buffer. |
1361 | /// @param[out] name Mark name. |
1362 | /// @param[out] fm Mark definition. |
1363 | /// |
1364 | /// @return Pointer that needs to be passed to next `mark_buffer_iter` call or |
1365 | /// NULL if iteration is over. |
1366 | const void *mark_buffer_iter(const void *const iter, const buf_T *const buf, |
1367 | char *const name, fmark_T *const fm) |
1368 | FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT |
1369 | { |
1370 | *name = NUL; |
1371 | char mark_name = (char) (iter == NULL |
1372 | ? NUL |
1373 | : (iter == &(buf->b_last_cursor) |
1374 | ? '"' |
1375 | : (iter == &(buf->b_last_insert) |
1376 | ? '^' |
1377 | : (iter == &(buf->b_last_change) |
1378 | ? '.' |
1379 | : 'a' + (char) ((const fmark_T *)iter |
1380 | - &(buf->b_namedm[0])))))); |
1381 | const fmark_T *iter_mark = next_buffer_mark(buf, &mark_name); |
1382 | while (iter_mark != NULL && iter_mark->mark.lnum == 0) { |
1383 | iter_mark = next_buffer_mark(buf, &mark_name); |
1384 | } |
1385 | if (iter_mark == NULL) { |
1386 | return NULL; |
1387 | } |
1388 | size_t iter_off = (size_t) (iter_mark - &(buf->b_namedm[0])); |
1389 | if (mark_name) { |
1390 | *name = mark_name; |
1391 | } else { |
1392 | *name = (char) ('a' + (char) iter_off); |
1393 | } |
1394 | *fm = *iter_mark; |
1395 | return (const void *) iter_mark; |
1396 | } |
1397 | |
1398 | /// Set global mark |
1399 | /// |
1400 | /// @param[in] name Mark name. |
1401 | /// @param[in] fm Mark to be set. |
1402 | /// @param[in] update If true then only set global mark if it was created |
1403 | /// later then existing one. |
1404 | /// |
1405 | /// @return true on success, false on failure. |
1406 | bool mark_set_global(const char name, const xfmark_T fm, const bool update) |
1407 | { |
1408 | const int idx = mark_global_index(name); |
1409 | if (idx == -1) { |
1410 | return false; |
1411 | } |
1412 | xfmark_T *const fm_tgt = &(namedfm[idx]); |
1413 | if (update && fm.fmark.timestamp <= fm_tgt->fmark.timestamp) { |
1414 | return false; |
1415 | } |
1416 | if (fm_tgt->fmark.mark.lnum != 0) { |
1417 | free_xfmark(*fm_tgt); |
1418 | } |
1419 | *fm_tgt = fm; |
1420 | return true; |
1421 | } |
1422 | |
1423 | /// Set local mark |
1424 | /// |
1425 | /// @param[in] name Mark name. |
1426 | /// @param[in] buf Pointer to the buffer to set mark in. |
1427 | /// @param[in] fm Mark to be set. |
1428 | /// @param[in] update If true then only set global mark if it was created |
1429 | /// later then existing one. |
1430 | /// |
1431 | /// @return true on success, false on failure. |
1432 | bool mark_set_local(const char name, buf_T *const buf, |
1433 | const fmark_T fm, const bool update) |
1434 | FUNC_ATTR_NONNULL_ALL |
1435 | { |
1436 | fmark_T *fm_tgt = NULL; |
1437 | if (ASCII_ISLOWER(name)) { |
1438 | fm_tgt = &(buf->b_namedm[name - 'a']); |
1439 | } else if (name == '"') { |
1440 | fm_tgt = &(buf->b_last_cursor); |
1441 | } else if (name == '^') { |
1442 | fm_tgt = &(buf->b_last_insert); |
1443 | } else if (name == '.') { |
1444 | fm_tgt = &(buf->b_last_change); |
1445 | } else { |
1446 | return false; |
1447 | } |
1448 | if (update && fm.timestamp <= fm_tgt->timestamp) { |
1449 | return false; |
1450 | } |
1451 | if (fm_tgt->mark.lnum != 0) { |
1452 | free_fmark(*fm_tgt); |
1453 | } |
1454 | *fm_tgt = fm; |
1455 | return true; |
1456 | } |
1457 | |
1458 | /* |
1459 | * Free items in the jumplist of window "wp". |
1460 | */ |
1461 | void free_jumplist(win_T *wp) |
1462 | { |
1463 | int i; |
1464 | |
1465 | for (i = 0; i < wp->w_jumplistlen; ++i) { |
1466 | free_xfmark(wp->w_jumplist[i]); |
1467 | } |
1468 | wp->w_jumplistlen = 0; |
1469 | } |
1470 | |
1471 | void set_last_cursor(win_T *win) |
1472 | { |
1473 | if (win->w_buffer != NULL) { |
1474 | RESET_FMARK(&win->w_buffer->b_last_cursor, win->w_cursor, 0); |
1475 | } |
1476 | } |
1477 | |
1478 | #if defined(EXITFREE) |
1479 | void free_all_marks(void) |
1480 | { |
1481 | int i; |
1482 | |
1483 | for (i = 0; i < NGLOBALMARKS; i++) { |
1484 | if (namedfm[i].fmark.mark.lnum != 0) { |
1485 | free_xfmark(namedfm[i]); |
1486 | } |
1487 | } |
1488 | memset(&namedfm[0], 0, sizeof(namedfm)); |
1489 | } |
1490 | #endif |
1491 | |
1492 | /// Adjust position to point to the first byte of a multi-byte character |
1493 | /// |
1494 | /// If it points to a tail byte it is move backwards to the head byte. |
1495 | /// |
1496 | /// @param[in] buf Buffer to adjust position in. |
1497 | /// @param[out] lp Position to adjust. |
1498 | void mark_mb_adjustpos(buf_T *buf, pos_T *lp) |
1499 | FUNC_ATTR_NONNULL_ALL |
1500 | { |
1501 | if (lp->col > 0 || lp->coladd > 1) { |
1502 | const char_u *const p = ml_get_buf(buf, lp->lnum, false); |
1503 | if (*p == NUL || (int)STRLEN(p) < lp->col) { |
1504 | lp->col = 0; |
1505 | } else { |
1506 | lp->col -= utf_head_off(p, p + lp->col); |
1507 | } |
1508 | // Reset "coladd" when the cursor would be on the right half of a |
1509 | // double-wide character. |
1510 | if (lp->coladd == 1 |
1511 | && p[lp->col] != TAB |
1512 | && vim_isprintc(utf_ptr2char(p + lp->col)) |
1513 | && ptr2cells(p + lp->col) > 1) { |
1514 | lp->coladd = 0; |
1515 | } |
1516 | } |
1517 | } |
1518 | |