xref: /vim-8.2.3635/src/mark.c (revision a0122dcd)
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.cmod_flags & CMOD_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)), 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     pos_T	*posp, *startp, *endp;
708 
709     if (arg != NULL && *arg == NUL)
710 	arg = NULL;
711 
712     show_one_mark('\'', arg, &curwin->w_pcmark, NULL, TRUE);
713     for (i = 0; i < NMARKS; ++i)
714 	show_one_mark(i + 'a', arg, &curbuf->b_namedm[i], NULL, TRUE);
715     for (i = 0; i < NMARKS + EXTRA_MARKS; ++i)
716     {
717 	if (namedfm[i].fmark.fnum != 0)
718 	    name = fm_getname(&namedfm[i].fmark, 15);
719 	else
720 	    name = namedfm[i].fname;
721 	if (name != NULL)
722 	{
723 	    show_one_mark(i >= NMARKS ? i - NMARKS + '0' : i + 'A',
724 		    arg, &namedfm[i].fmark.mark, name,
725 		    namedfm[i].fmark.fnum == curbuf->b_fnum);
726 	    if (namedfm[i].fmark.fnum != 0)
727 		vim_free(name);
728 	}
729     }
730     show_one_mark('"', arg, &curbuf->b_last_cursor, NULL, TRUE);
731     show_one_mark('[', arg, &curbuf->b_op_start, NULL, TRUE);
732     show_one_mark(']', arg, &curbuf->b_op_end, NULL, TRUE);
733     show_one_mark('^', arg, &curbuf->b_last_insert, NULL, TRUE);
734     show_one_mark('.', arg, &curbuf->b_last_change, NULL, TRUE);
735 
736     // Show the marks as where they will jump to.
737     startp = &curbuf->b_visual.vi_start;
738     endp = &curbuf->b_visual.vi_end;
739     if ((LT_POS(*startp, *endp) || endp->lnum == 0) && startp->lnum != 0)
740 	posp = startp;
741     else
742 	posp = endp;
743     show_one_mark('<', arg, posp, NULL, TRUE);
744     show_one_mark('>', arg, posp == startp ? endp : startp, NULL, TRUE);
745 
746     show_one_mark(-1, arg, NULL, NULL, FALSE);
747 }
748 
749     static void
750 show_one_mark(
751     int		c,
752     char_u	*arg,
753     pos_T	*p,
754     char_u	*name_arg,
755     int		current)	// in current file
756 {
757     static int	did_title = FALSE;
758     int		mustfree = FALSE;
759     char_u	*name = name_arg;
760 
761     if (c == -1)			    // finish up
762     {
763 	if (did_title)
764 	    did_title = FALSE;
765 	else
766 	{
767 	    if (arg == NULL)
768 		msg(_("No marks set"));
769 	    else
770 		semsg(_("E283: No marks matching \"%s\""), arg);
771 	}
772     }
773     // don't output anything if 'q' typed at --more-- prompt
774     else if (!got_int
775 	    && (arg == NULL || vim_strchr(arg, c) != NULL)
776 	    && p->lnum != 0)
777     {
778 	if (name == NULL && current)
779 	{
780 	    name = mark_line(p, 15);
781 	    mustfree = TRUE;
782 	}
783 	if (!message_filtered(name))
784 	{
785 	    if (!did_title)
786 	    {
787 		// Highlight title
788 		msg_puts_title(_("\nmark line  col file/text"));
789 		did_title = TRUE;
790 	    }
791 	    msg_putchar('\n');
792 	    if (!got_int)
793 	    {
794 		sprintf((char *)IObuff, " %c %6ld %4d ", c, p->lnum, p->col);
795 		msg_outtrans(IObuff);
796 		if (name != NULL)
797 		{
798 		    msg_outtrans_attr(name, current ? HL_ATTR(HLF_D) : 0);
799 		}
800 	    }
801 	    out_flush();		    // show one line at a time
802 	}
803 	if (mustfree)
804 	    vim_free(name);
805     }
806 }
807 
808 /*
809  * ":delmarks[!] [marks]"
810  */
811     void
812 ex_delmarks(exarg_T *eap)
813 {
814     char_u	*p;
815     int		from, to;
816     int		i;
817     int		lower;
818     int		digit;
819     int		n;
820 
821     if (*eap->arg == NUL && eap->forceit)
822 	// clear all marks
823 	clrallmarks(curbuf);
824     else if (eap->forceit)
825 	emsg(_(e_invarg));
826     else if (*eap->arg == NUL)
827 	emsg(_(e_argreq));
828     else
829     {
830 	// clear specified marks only
831 	for (p = eap->arg; *p != NUL; ++p)
832 	{
833 	    lower = ASCII_ISLOWER(*p);
834 	    digit = VIM_ISDIGIT(*p);
835 	    if (lower || digit || ASCII_ISUPPER(*p))
836 	    {
837 		if (p[1] == '-')
838 		{
839 		    // clear range of marks
840 		    from = *p;
841 		    to = p[2];
842 		    if (!(lower ? ASCII_ISLOWER(p[2])
843 				: (digit ? VIM_ISDIGIT(p[2])
844 				    : ASCII_ISUPPER(p[2])))
845 			    || to < from)
846 		    {
847 			semsg(_(e_invarg2), p);
848 			return;
849 		    }
850 		    p += 2;
851 		}
852 		else
853 		    // clear one lower case mark
854 		    from = to = *p;
855 
856 		for (i = from; i <= to; ++i)
857 		{
858 		    if (lower)
859 			curbuf->b_namedm[i - 'a'].lnum = 0;
860 		    else
861 		    {
862 			if (digit)
863 			    n = i - '0' + NMARKS;
864 			else
865 			    n = i - 'A';
866 			namedfm[n].fmark.mark.lnum = 0;
867 			namedfm[n].fmark.fnum = 0;
868 			VIM_CLEAR(namedfm[n].fname);
869 #ifdef FEAT_VIMINFO
870 			namedfm[n].time_set = digit ? 0 : vim_time();
871 #endif
872 		    }
873 		}
874 	    }
875 	    else
876 		switch (*p)
877 		{
878 		    case '"': curbuf->b_last_cursor.lnum = 0; break;
879 		    case '^': curbuf->b_last_insert.lnum = 0; break;
880 		    case '.': curbuf->b_last_change.lnum = 0; break;
881 		    case '[': curbuf->b_op_start.lnum    = 0; break;
882 		    case ']': curbuf->b_op_end.lnum      = 0; break;
883 		    case '<': curbuf->b_visual.vi_start.lnum = 0; break;
884 		    case '>': curbuf->b_visual.vi_end.lnum   = 0; break;
885 		    case ' ': break;
886 		    default:  semsg(_(e_invarg2), p);
887 			      return;
888 		}
889 	}
890     }
891 }
892 
893 #if defined(FEAT_JUMPLIST) || defined(PROTO)
894 /*
895  * print the jumplist
896  */
897     void
898 ex_jumps(exarg_T *eap UNUSED)
899 {
900     int		i;
901     char_u	*name;
902 
903     cleanup_jumplist(curwin, TRUE);
904 
905     // Highlight title
906     msg_puts_title(_("\n jump line  col file/text"));
907     for (i = 0; i < curwin->w_jumplistlen && !got_int; ++i)
908     {
909 	if (curwin->w_jumplist[i].fmark.mark.lnum != 0)
910 	{
911 	    name = fm_getname(&curwin->w_jumplist[i].fmark, 16);
912 
913 	    // apply :filter /pat/ or file name not available
914 	    if (name == NULL || message_filtered(name))
915 	    {
916 		vim_free(name);
917 		continue;
918 	    }
919 
920 	    msg_putchar('\n');
921 	    if (got_int)
922 	    {
923 		vim_free(name);
924 		break;
925 	    }
926 	    sprintf((char *)IObuff, "%c %2d %5ld %4d ",
927 		i == curwin->w_jumplistidx ? '>' : ' ',
928 		i > curwin->w_jumplistidx ? i - curwin->w_jumplistidx
929 					  : curwin->w_jumplistidx - i,
930 		curwin->w_jumplist[i].fmark.mark.lnum,
931 		curwin->w_jumplist[i].fmark.mark.col);
932 	    msg_outtrans(IObuff);
933 	    msg_outtrans_attr(name,
934 			    curwin->w_jumplist[i].fmark.fnum == curbuf->b_fnum
935 							? HL_ATTR(HLF_D) : 0);
936 	    vim_free(name);
937 	    ui_breakcheck();
938 	}
939 	out_flush();
940     }
941     if (curwin->w_jumplistidx == curwin->w_jumplistlen)
942 	msg_puts("\n>");
943 }
944 
945     void
946 ex_clearjumps(exarg_T *eap UNUSED)
947 {
948     free_jumplist(curwin);
949     curwin->w_jumplistlen = 0;
950     curwin->w_jumplistidx = 0;
951 }
952 
953 /*
954  * print the changelist
955  */
956     void
957 ex_changes(exarg_T *eap UNUSED)
958 {
959     int		i;
960     char_u	*name;
961 
962     // Highlight title
963     msg_puts_title(_("\nchange line  col text"));
964 
965     for (i = 0; i < curbuf->b_changelistlen && !got_int; ++i)
966     {
967 	if (curbuf->b_changelist[i].lnum != 0)
968 	{
969 	    msg_putchar('\n');
970 	    if (got_int)
971 		break;
972 	    sprintf((char *)IObuff, "%c %3d %5ld %4d ",
973 		    i == curwin->w_changelistidx ? '>' : ' ',
974 		    i > curwin->w_changelistidx ? i - curwin->w_changelistidx
975 						: curwin->w_changelistidx - i,
976 		    (long)curbuf->b_changelist[i].lnum,
977 		    curbuf->b_changelist[i].col);
978 	    msg_outtrans(IObuff);
979 	    name = mark_line(&curbuf->b_changelist[i], 17);
980 	    if (name == NULL)
981 		break;
982 	    msg_outtrans_attr(name, HL_ATTR(HLF_D));
983 	    vim_free(name);
984 	    ui_breakcheck();
985 	}
986 	out_flush();
987     }
988     if (curwin->w_changelistidx == curbuf->b_changelistlen)
989 	msg_puts("\n>");
990 }
991 #endif
992 
993 #define one_adjust(add) \
994     { \
995 	lp = add; \
996 	if (*lp >= line1 && *lp <= line2) \
997 	{ \
998 	    if (amount == MAXLNUM) \
999 		*lp = 0; \
1000 	    else \
1001 		*lp += amount; \
1002 	} \
1003 	else if (amount_after && *lp > line2) \
1004 	    *lp += amount_after; \
1005     }
1006 
1007 // don't delete the line, just put at first deleted line
1008 #define one_adjust_nodel(add) \
1009     { \
1010 	lp = add; \
1011 	if (*lp >= line1 && *lp <= line2) \
1012 	{ \
1013 	    if (amount == MAXLNUM) \
1014 		*lp = line1; \
1015 	    else \
1016 		*lp += amount; \
1017 	} \
1018 	else if (amount_after && *lp > line2) \
1019 	    *lp += amount_after; \
1020     }
1021 
1022 /*
1023  * Adjust marks between line1 and line2 (inclusive) to move 'amount' lines.
1024  * Must be called before changed_*(), appended_lines() or deleted_lines().
1025  * May be called before or after changing the text.
1026  * When deleting lines line1 to line2, use an 'amount' of MAXLNUM: The marks
1027  * within this range are made invalid.
1028  * If 'amount_after' is non-zero adjust marks after line2.
1029  * Example: Delete lines 34 and 35: mark_adjust(34, 35, MAXLNUM, -2);
1030  * Example: Insert two lines below 55: mark_adjust(56, MAXLNUM, 2, 0);
1031  *				   or: mark_adjust(56, 55, MAXLNUM, 2);
1032  */
1033     void
1034 mark_adjust(
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, TRUE);
1041 }
1042 
1043     void
1044 mark_adjust_nofold(
1045     linenr_T line1,
1046     linenr_T line2,
1047     long amount,
1048     long amount_after)
1049 {
1050     mark_adjust_internal(line1, line2, amount, amount_after, FALSE);
1051 }
1052 
1053     static void
1054 mark_adjust_internal(
1055     linenr_T line1,
1056     linenr_T line2,
1057     long amount,
1058     long amount_after,
1059     int adjust_folds UNUSED)
1060 {
1061     int		i;
1062     int		fnum = curbuf->b_fnum;
1063     linenr_T	*lp;
1064     win_T	*win;
1065     tabpage_T	*tab;
1066     static pos_T initpos = {1, 0, 0};
1067 
1068     if (line2 < line1 && amount_after == 0L)	    // nothing to do
1069 	return;
1070 
1071     if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0)
1072     {
1073 	// named marks, lower case and upper case
1074 	for (i = 0; i < NMARKS; i++)
1075 	{
1076 	    one_adjust(&(curbuf->b_namedm[i].lnum));
1077 	    if (namedfm[i].fmark.fnum == fnum)
1078 		one_adjust_nodel(&(namedfm[i].fmark.mark.lnum));
1079 	}
1080 	for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++)
1081 	{
1082 	    if (namedfm[i].fmark.fnum == fnum)
1083 		one_adjust_nodel(&(namedfm[i].fmark.mark.lnum));
1084 	}
1085 
1086 	// last Insert position
1087 	one_adjust(&(curbuf->b_last_insert.lnum));
1088 
1089 	// last change position
1090 	one_adjust(&(curbuf->b_last_change.lnum));
1091 
1092 	// last cursor position, if it was set
1093 	if (!EQUAL_POS(curbuf->b_last_cursor, initpos))
1094 	    one_adjust(&(curbuf->b_last_cursor.lnum));
1095 
1096 
1097 #ifdef FEAT_JUMPLIST
1098 	// list of change positions
1099 	for (i = 0; i < curbuf->b_changelistlen; ++i)
1100 	    one_adjust_nodel(&(curbuf->b_changelist[i].lnum));
1101 #endif
1102 
1103 	// Visual area
1104 	one_adjust_nodel(&(curbuf->b_visual.vi_start.lnum));
1105 	one_adjust_nodel(&(curbuf->b_visual.vi_end.lnum));
1106 
1107 #ifdef FEAT_QUICKFIX
1108 	// quickfix marks
1109 	qf_mark_adjust(NULL, line1, line2, amount, amount_after);
1110 	// location lists
1111 	FOR_ALL_TAB_WINDOWS(tab, win)
1112 	    qf_mark_adjust(win, line1, line2, amount, amount_after);
1113 #endif
1114 
1115 #ifdef FEAT_SIGNS
1116 	sign_mark_adjust(line1, line2, amount, amount_after);
1117 #endif
1118     }
1119 
1120     // previous context mark
1121     one_adjust(&(curwin->w_pcmark.lnum));
1122 
1123     // previous pcmark
1124     one_adjust(&(curwin->w_prev_pcmark.lnum));
1125 
1126     // saved cursor for formatting
1127     if (saved_cursor.lnum != 0)
1128 	one_adjust_nodel(&(saved_cursor.lnum));
1129 
1130     /*
1131      * Adjust items in all windows related to the current buffer.
1132      */
1133     FOR_ALL_TAB_WINDOWS(tab, win)
1134     {
1135 #ifdef FEAT_JUMPLIST
1136 	if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0)
1137 	    // Marks in the jumplist.  When deleting lines, this may create
1138 	    // duplicate marks in the jumplist, they will be removed later.
1139 	    for (i = 0; i < win->w_jumplistlen; ++i)
1140 		if (win->w_jumplist[i].fmark.fnum == fnum)
1141 		    one_adjust_nodel(&(win->w_jumplist[i].fmark.mark.lnum));
1142 #endif
1143 
1144 	if (win->w_buffer == curbuf)
1145 	{
1146 	    if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0)
1147 		// marks in the tag stack
1148 		for (i = 0; i < win->w_tagstacklen; i++)
1149 		    if (win->w_tagstack[i].fmark.fnum == fnum)
1150 			one_adjust_nodel(&(win->w_tagstack[i].fmark.mark.lnum));
1151 
1152 	    // the displayed Visual area
1153 	    if (win->w_old_cursor_lnum != 0)
1154 	    {
1155 		one_adjust_nodel(&(win->w_old_cursor_lnum));
1156 		one_adjust_nodel(&(win->w_old_visual_lnum));
1157 	    }
1158 
1159 	    // topline and cursor position for windows with the same buffer
1160 	    // other than the current window
1161 	    if (win != curwin)
1162 	    {
1163 		if (win->w_topline >= line1 && win->w_topline <= line2)
1164 		{
1165 		    if (amount == MAXLNUM)	    // topline is deleted
1166 		    {
1167 			if (line1 <= 1)
1168 			    win->w_topline = 1;
1169 			else
1170 			    win->w_topline = line1 - 1;
1171 		    }
1172 		    else		// keep topline on the same line
1173 			win->w_topline += amount;
1174 #ifdef FEAT_DIFF
1175 		    win->w_topfill = 0;
1176 #endif
1177 		}
1178 		else if (amount_after && win->w_topline > line2)
1179 		{
1180 		    win->w_topline += amount_after;
1181 #ifdef FEAT_DIFF
1182 		    win->w_topfill = 0;
1183 #endif
1184 		}
1185 		if (win->w_cursor.lnum >= line1 && win->w_cursor.lnum <= line2)
1186 		{
1187 		    if (amount == MAXLNUM) // line with cursor is deleted
1188 		    {
1189 			if (line1 <= 1)
1190 			    win->w_cursor.lnum = 1;
1191 			else
1192 			    win->w_cursor.lnum = line1 - 1;
1193 			win->w_cursor.col = 0;
1194 		    }
1195 		    else		// keep cursor on the same line
1196 			win->w_cursor.lnum += amount;
1197 		}
1198 		else if (amount_after && win->w_cursor.lnum > line2)
1199 		    win->w_cursor.lnum += amount_after;
1200 	    }
1201 
1202 #ifdef FEAT_FOLDING
1203 	    // adjust folds
1204 	    if (adjust_folds)
1205 		foldMarkAdjust(win, line1, line2, amount, amount_after);
1206 #endif
1207 	}
1208     }
1209 
1210 #ifdef FEAT_DIFF
1211     // adjust diffs
1212     diff_mark_adjust(line1, line2, amount, amount_after);
1213 #endif
1214 }
1215 
1216 // This code is used often, needs to be fast.
1217 #define col_adjust(pp) \
1218     { \
1219 	posp = pp; \
1220 	if (posp->lnum == lnum && posp->col >= mincol) \
1221 	{ \
1222 	    posp->lnum += lnum_amount; \
1223 	    if (col_amount < 0 && posp->col <= (colnr_T)-col_amount) \
1224 		posp->col = 0; \
1225 	    else if (posp->col < spaces_removed) \
1226 		posp->col = col_amount + spaces_removed; \
1227 	    else \
1228 		posp->col += col_amount; \
1229 	} \
1230     }
1231 
1232 /*
1233  * Adjust marks in line "lnum" at column "mincol" and further: add
1234  * "lnum_amount" to the line number and add "col_amount" to the column
1235  * position.
1236  * "spaces_removed" is the number of spaces that were removed, matters when the
1237  * cursor is inside them.
1238  */
1239     void
1240 mark_col_adjust(
1241     linenr_T	lnum,
1242     colnr_T	mincol,
1243     long	lnum_amount,
1244     long	col_amount,
1245     int		spaces_removed)
1246 {
1247     int		i;
1248     int		fnum = curbuf->b_fnum;
1249     win_T	*win;
1250     pos_T	*posp;
1251 
1252     if ((col_amount == 0L && lnum_amount == 0L)
1253 				       || (cmdmod.cmod_flags & CMOD_LOCKMARKS))
1254 	return; // nothing to do
1255 
1256     // named marks, lower case and upper case
1257     for (i = 0; i < NMARKS; i++)
1258     {
1259 	col_adjust(&(curbuf->b_namedm[i]));
1260 	if (namedfm[i].fmark.fnum == fnum)
1261 	    col_adjust(&(namedfm[i].fmark.mark));
1262     }
1263     for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++)
1264     {
1265 	if (namedfm[i].fmark.fnum == fnum)
1266 	    col_adjust(&(namedfm[i].fmark.mark));
1267     }
1268 
1269     // last Insert position
1270     col_adjust(&(curbuf->b_last_insert));
1271 
1272     // last change position
1273     col_adjust(&(curbuf->b_last_change));
1274 
1275 #ifdef FEAT_JUMPLIST
1276     // list of change positions
1277     for (i = 0; i < curbuf->b_changelistlen; ++i)
1278 	col_adjust(&(curbuf->b_changelist[i]));
1279 #endif
1280 
1281     // Visual area
1282     col_adjust(&(curbuf->b_visual.vi_start));
1283     col_adjust(&(curbuf->b_visual.vi_end));
1284 
1285     // previous context mark
1286     col_adjust(&(curwin->w_pcmark));
1287 
1288     // previous pcmark
1289     col_adjust(&(curwin->w_prev_pcmark));
1290 
1291     // saved cursor for formatting
1292     col_adjust(&saved_cursor);
1293 
1294     /*
1295      * Adjust items in all windows related to the current buffer.
1296      */
1297     FOR_ALL_WINDOWS(win)
1298     {
1299 #ifdef FEAT_JUMPLIST
1300 	// marks in the jumplist
1301 	for (i = 0; i < win->w_jumplistlen; ++i)
1302 	    if (win->w_jumplist[i].fmark.fnum == fnum)
1303 		col_adjust(&(win->w_jumplist[i].fmark.mark));
1304 #endif
1305 
1306 	if (win->w_buffer == curbuf)
1307 	{
1308 	    // marks in the tag stack
1309 	    for (i = 0; i < win->w_tagstacklen; i++)
1310 		if (win->w_tagstack[i].fmark.fnum == fnum)
1311 		    col_adjust(&(win->w_tagstack[i].fmark.mark));
1312 
1313 	    // cursor position for other windows with the same buffer
1314 	    if (win != curwin)
1315 		col_adjust(&win->w_cursor);
1316 	}
1317     }
1318 }
1319 
1320 #ifdef FEAT_JUMPLIST
1321 /*
1322  * When deleting lines, this may create duplicate marks in the
1323  * jumplist. They will be removed here for the specified window.
1324  * When "loadfiles" is TRUE first ensure entries have the "fnum" field set
1325  * (this may be a bit slow).
1326  */
1327     void
1328 cleanup_jumplist(win_T *wp, int loadfiles)
1329 {
1330     int	    i;
1331     int	    from, to;
1332 
1333     if (loadfiles)
1334     {
1335 	// If specified, load all the files from the jump list. This is
1336 	// needed to properly clean up duplicate entries, but will take some
1337 	// time.
1338 	for (i = 0; i < wp->w_jumplistlen; ++i)
1339 	{
1340 	    if ((wp->w_jumplist[i].fmark.fnum == 0) &&
1341 		    (wp->w_jumplist[i].fmark.mark.lnum != 0))
1342 		fname2fnum(&wp->w_jumplist[i]);
1343 	}
1344     }
1345 
1346     to = 0;
1347     for (from = 0; from < wp->w_jumplistlen; ++from)
1348     {
1349 	if (wp->w_jumplistidx == from)
1350 	    wp->w_jumplistidx = to;
1351 	for (i = from + 1; i < wp->w_jumplistlen; ++i)
1352 	    if (wp->w_jumplist[i].fmark.fnum
1353 					== wp->w_jumplist[from].fmark.fnum
1354 		    && wp->w_jumplist[from].fmark.fnum != 0
1355 		    && wp->w_jumplist[i].fmark.mark.lnum
1356 				  == wp->w_jumplist[from].fmark.mark.lnum)
1357 		break;
1358 	if (i >= wp->w_jumplistlen)	    // no duplicate
1359 	    wp->w_jumplist[to++] = wp->w_jumplist[from];
1360 	else
1361 	    vim_free(wp->w_jumplist[from].fname);
1362     }
1363     if (wp->w_jumplistidx == wp->w_jumplistlen)
1364 	wp->w_jumplistidx = to;
1365     wp->w_jumplistlen = to;
1366 }
1367 
1368 /*
1369  * Copy the jumplist from window "from" to window "to".
1370  */
1371     void
1372 copy_jumplist(win_T *from, win_T *to)
1373 {
1374     int		i;
1375 
1376     for (i = 0; i < from->w_jumplistlen; ++i)
1377     {
1378 	to->w_jumplist[i] = from->w_jumplist[i];
1379 	if (from->w_jumplist[i].fname != NULL)
1380 	    to->w_jumplist[i].fname = vim_strsave(from->w_jumplist[i].fname);
1381     }
1382     to->w_jumplistlen = from->w_jumplistlen;
1383     to->w_jumplistidx = from->w_jumplistidx;
1384 }
1385 
1386 /*
1387  * Free items in the jumplist of window "wp".
1388  */
1389     void
1390 free_jumplist(win_T *wp)
1391 {
1392     int		i;
1393 
1394     for (i = 0; i < wp->w_jumplistlen; ++i)
1395 	vim_free(wp->w_jumplist[i].fname);
1396 }
1397 #endif // FEAT_JUMPLIST
1398 
1399     void
1400 set_last_cursor(win_T *win)
1401 {
1402     if (win->w_buffer != NULL)
1403 	win->w_buffer->b_last_cursor = win->w_cursor;
1404 }
1405 
1406 #if defined(EXITFREE) || defined(PROTO)
1407     void
1408 free_all_marks(void)
1409 {
1410     int		i;
1411 
1412     for (i = 0; i < NMARKS + EXTRA_MARKS; i++)
1413 	if (namedfm[i].fmark.mark.lnum != 0)
1414 	    vim_free(namedfm[i].fname);
1415 }
1416 #endif
1417 
1418 /*
1419  * Return a pointer to the named file marks.
1420  */
1421     xfmark_T *
1422 get_namedfm(void)
1423 {
1424     return namedfm;
1425 }
1426 
1427 #if defined(FEAT_EVAL) || defined(PROTO)
1428 /*
1429  * Add information about mark 'mname' to list 'l'
1430  */
1431     static int
1432 add_mark(list_T *l, char_u *mname, pos_T *pos, int bufnr, char_u *fname)
1433 {
1434     dict_T	*d;
1435     list_T	*lpos;
1436 
1437     if (pos->lnum <= 0)
1438 	return OK;
1439 
1440     d = dict_alloc();
1441     if (d == NULL)
1442 	return FAIL;
1443 
1444     if (list_append_dict(l, d) == FAIL)
1445     {
1446 	dict_unref(d);
1447 	return FAIL;
1448     }
1449 
1450     lpos = list_alloc();
1451     if (lpos == NULL)
1452 	return FAIL;
1453 
1454     list_append_number(lpos, bufnr);
1455     list_append_number(lpos, pos->lnum);
1456     list_append_number(lpos, pos->col + 1);
1457     list_append_number(lpos, pos->coladd);
1458 
1459     if (dict_add_string(d, "mark", mname) == FAIL
1460 	    || dict_add_list(d, "pos", lpos) == FAIL
1461 	    || (fname != NULL && dict_add_string(d, "file", fname) == FAIL))
1462 	return FAIL;
1463 
1464     return OK;
1465 }
1466 
1467 /*
1468  * Get information about marks local to a buffer.
1469  */
1470     static void
1471 get_buf_local_marks(buf_T *buf, list_T *l)
1472 {
1473     char_u	mname[3] = "' ";
1474     int		i;
1475 
1476     // Marks 'a' to 'z'
1477     for (i = 0; i < NMARKS; ++i)
1478     {
1479 	mname[1] = 'a' + i;
1480 	add_mark(l, mname, &buf->b_namedm[i], buf->b_fnum, NULL);
1481     }
1482 
1483     // Mark '' is a window local mark and not a buffer local mark
1484     add_mark(l, (char_u *)"''", &curwin->w_pcmark, curbuf->b_fnum, NULL);
1485 
1486     add_mark(l, (char_u *)"'\"", &buf->b_last_cursor, buf->b_fnum, NULL);
1487     add_mark(l, (char_u *)"'[", &buf->b_op_start, buf->b_fnum, NULL);
1488     add_mark(l, (char_u *)"']", &buf->b_op_end, buf->b_fnum, NULL);
1489     add_mark(l, (char_u *)"'^", &buf->b_last_insert, buf->b_fnum, NULL);
1490     add_mark(l, (char_u *)"'.", &buf->b_last_change, buf->b_fnum, NULL);
1491     add_mark(l, (char_u *)"'<", &buf->b_visual.vi_start, buf->b_fnum, NULL);
1492     add_mark(l, (char_u *)"'>", &buf->b_visual.vi_end, buf->b_fnum, NULL);
1493 }
1494 
1495 /*
1496  * Get information about global marks ('A' to 'Z' and '0' to '9')
1497  */
1498     static void
1499 get_global_marks(list_T *l)
1500 {
1501     char_u	mname[3] = "' ";
1502     int		i;
1503     char_u	*name;
1504 
1505     // Marks 'A' to 'Z' and '0' to '9'
1506     for (i = 0; i < NMARKS + EXTRA_MARKS; ++i)
1507     {
1508 	if (namedfm[i].fmark.fnum != 0)
1509 	    name = buflist_nr2name(namedfm[i].fmark.fnum, TRUE, TRUE);
1510 	else
1511 	    name = namedfm[i].fname;
1512 	if (name != NULL)
1513 	{
1514 	    mname[1] = i >= NMARKS ? i - NMARKS + '0' : i + 'A';
1515 	    add_mark(l, mname, &namedfm[i].fmark.mark,
1516 		    namedfm[i].fmark.fnum, name);
1517 	    if (namedfm[i].fmark.fnum != 0)
1518 		vim_free(name);
1519 	}
1520     }
1521 }
1522 
1523 /*
1524  * getmarklist() function
1525  */
1526     void
1527 f_getmarklist(typval_T *argvars, typval_T *rettv)
1528 {
1529     buf_T	*buf = NULL;
1530 
1531     if (rettv_list_alloc(rettv) != OK)
1532 	return;
1533 
1534     if (argvars[0].v_type == VAR_UNKNOWN)
1535     {
1536 	get_global_marks(rettv->vval.v_list);
1537 	return;
1538     }
1539 
1540     buf = tv_get_buf(&argvars[0], FALSE);
1541     if (buf == NULL)
1542 	return;
1543 
1544     get_buf_local_marks(buf, rettv->vval.v_list);
1545 }
1546 #endif
1547