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