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