blob: 346594773015ed3288fdcc1659f824bfdaf226f0 [file] [log] [blame]
Bram Moolenaar071d4272004-06-13 20:20:40 +00001/* vi:set ts=8 sts=4 sw=4:
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#define EXTRA_MARKS 10 /* marks 0-9 */
28static xfmark_T namedfm[NMARKS + EXTRA_MARKS]; /* marks with file nr */
29
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010030static void fname2fnum(xfmark_T *fm);
31static void fmarks_check_one(xfmark_T *fm, char_u *name, buf_T *buf);
32static char_u *mark_line(pos_T *mp, int lead_len);
33static void show_one_mark(int, char_u *, pos_T *, char_u *, int current);
Bram Moolenaar071d4272004-06-13 20:20:40 +000034#ifdef FEAT_JUMPLIST
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010035static void cleanup_jumplist(void);
Bram Moolenaar071d4272004-06-13 20:20:40 +000036#endif
37#ifdef FEAT_VIMINFO
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010038static void write_one_filemark(FILE *fp, xfmark_T *fm, int c1, int c2);
Bram Moolenaar071d4272004-06-13 20:20:40 +000039#endif
40
41/*
Bram Moolenaarbfb2d402006-03-03 22:50:42 +000042 * Set named mark "c" at current cursor position.
Bram Moolenaar071d4272004-06-13 20:20:40 +000043 * Returns OK on success, FAIL if bad name given.
44 */
45 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +010046setmark(int c)
Bram Moolenaar071d4272004-06-13 20:20:40 +000047{
Bram Moolenaarbfb2d402006-03-03 22:50:42 +000048 return setmark_pos(c, &curwin->w_cursor, curbuf->b_fnum);
49}
50
51/*
52 * Set named mark "c" to position "pos".
53 * When "c" is upper case use file "fnum".
54 * Returns OK on success, FAIL if bad name given.
55 */
56 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +010057setmark_pos(int c, pos_T *pos, int fnum)
Bram Moolenaarbfb2d402006-03-03 22:50:42 +000058{
Bram Moolenaar071d4272004-06-13 20:20:40 +000059 int i;
60
61 /* Check for a special key (may cause islower() to crash). */
62 if (c < 0)
63 return FAIL;
64
65 if (c == '\'' || c == '`')
66 {
Bram Moolenaarbfb2d402006-03-03 22:50:42 +000067 if (pos == &curwin->w_cursor)
68 {
69 setpcmark();
70 /* keep it even when the cursor doesn't move */
71 curwin->w_prev_pcmark = curwin->w_pcmark;
72 }
73 else
74 curwin->w_pcmark = *pos;
Bram Moolenaar071d4272004-06-13 20:20:40 +000075 return OK;
76 }
77
Bram Moolenaar08250432008-02-13 11:42:46 +000078 if (c == '"')
79 {
80 curbuf->b_last_cursor = *pos;
81 return OK;
82 }
83
Bram Moolenaar071d4272004-06-13 20:20:40 +000084 /* Allow setting '[ and '] for an autocommand that simulates reading a
85 * file. */
86 if (c == '[')
87 {
Bram Moolenaarbfb2d402006-03-03 22:50:42 +000088 curbuf->b_op_start = *pos;
Bram Moolenaar071d4272004-06-13 20:20:40 +000089 return OK;
90 }
91 if (c == ']')
92 {
Bram Moolenaarbfb2d402006-03-03 22:50:42 +000093 curbuf->b_op_end = *pos;
Bram Moolenaar071d4272004-06-13 20:20:40 +000094 return OK;
95 }
96
Bram Moolenaarbc88a272013-08-02 17:22:23 +020097 if (c == '<' || c == '>')
Bram Moolenaar0306ac32012-07-06 17:51:28 +020098 {
Bram Moolenaarbc88a272013-08-02 17:22:23 +020099 if (c == '<')
100 curbuf->b_visual.vi_start = *pos;
101 else
102 curbuf->b_visual.vi_end = *pos;
103 if (curbuf->b_visual.vi_mode == NUL)
104 /* Visual_mode has not yet been set, use a sane default. */
105 curbuf->b_visual.vi_mode = 'v';
Bram Moolenaar0306ac32012-07-06 17:51:28 +0200106 return OK;
107 }
Bram Moolenaar0306ac32012-07-06 17:51:28 +0200108
Bram Moolenaar071d4272004-06-13 20:20:40 +0000109#ifndef EBCDIC
110 if (c > 'z') /* some islower() and isupper() cannot handle
111 characters above 127 */
112 return FAIL;
113#endif
114 if (islower(c))
115 {
116 i = c - 'a';
Bram Moolenaarbfb2d402006-03-03 22:50:42 +0000117 curbuf->b_namedm[i] = *pos;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000118 return OK;
119 }
120 if (isupper(c))
121 {
122 i = c - 'A';
Bram Moolenaarbfb2d402006-03-03 22:50:42 +0000123 namedfm[i].fmark.mark = *pos;
124 namedfm[i].fmark.fnum = fnum;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000125 vim_free(namedfm[i].fname);
126 namedfm[i].fname = NULL;
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
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100137setpcmark(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000138{
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#endif
188}
189
190/*
191 * To change context, call setpcmark(), then move the current position to
192 * where ever, then call checkpcmark(). This ensures that the previous
193 * context will only be changed if the cursor moved to a different line.
194 * If pcmark was deleted (with "dG") the previous mark is restored.
195 */
196 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100197checkpcmark(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000198{
199 if (curwin->w_prev_pcmark.lnum != 0
200 && (equalpos(curwin->w_pcmark, curwin->w_cursor)
201 || curwin->w_pcmark.lnum == 0))
202 {
203 curwin->w_pcmark = curwin->w_prev_pcmark;
204 curwin->w_prev_pcmark.lnum = 0; /* Show it has been checked */
205 }
206}
207
208#if defined(FEAT_JUMPLIST) || defined(PROTO)
209/*
210 * move "count" positions in the jump list (count may be negative)
211 */
212 pos_T *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100213movemark(int count)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000214{
215 pos_T *pos;
216 xfmark_T *jmp;
217
218 cleanup_jumplist();
219
220 if (curwin->w_jumplistlen == 0) /* nothing to jump to */
221 return (pos_T *)NULL;
222
223 for (;;)
224 {
225 if (curwin->w_jumplistidx + count < 0
226 || curwin->w_jumplistidx + count >= curwin->w_jumplistlen)
227 return (pos_T *)NULL;
228
229 /*
230 * if first CTRL-O or CTRL-I command after a jump, add cursor position
Bram Moolenaarf711faf2007-05-10 16:48:19 +0000231 * to list. Careful: If there are duplicates (CTRL-O immediately after
Bram Moolenaar071d4272004-06-13 20:20:40 +0000232 * starting Vim on a file), another entry may have been removed.
233 */
234 if (curwin->w_jumplistidx == curwin->w_jumplistlen)
235 {
236 setpcmark();
237 --curwin->w_jumplistidx; /* skip the new entry */
238 if (curwin->w_jumplistidx + count < 0)
239 return (pos_T *)NULL;
240 }
241
242 curwin->w_jumplistidx += count;
243
244 jmp = curwin->w_jumplist + curwin->w_jumplistidx;
245 if (jmp->fmark.fnum == 0)
246 fname2fnum(jmp);
247 if (jmp->fmark.fnum != curbuf->b_fnum)
248 {
249 /* jump to other file */
250 if (buflist_findnr(jmp->fmark.fnum) == NULL)
251 { /* Skip this one .. */
252 count += count < 0 ? -1 : 1;
253 continue;
254 }
255 if (buflist_getfile(jmp->fmark.fnum, jmp->fmark.mark.lnum,
256 0, FALSE) == FAIL)
257 return (pos_T *)NULL;
258 /* Set lnum again, autocommands my have changed it */
259 curwin->w_cursor = jmp->fmark.mark;
260 pos = (pos_T *)-1;
261 }
262 else
263 pos = &(jmp->fmark.mark);
264 return pos;
265 }
266}
267
268/*
269 * Move "count" positions in the changelist (count may be negative).
270 */
271 pos_T *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100272movechangelist(int count)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000273{
274 int n;
275
276 if (curbuf->b_changelistlen == 0) /* nothing to jump to */
277 return (pos_T *)NULL;
278
279 n = curwin->w_changelistidx;
280 if (n + count < 0)
281 {
282 if (n == 0)
283 return (pos_T *)NULL;
284 n = 0;
285 }
286 else if (n + count >= curbuf->b_changelistlen)
287 {
288 if (n == curbuf->b_changelistlen - 1)
289 return (pos_T *)NULL;
290 n = curbuf->b_changelistlen - 1;
291 }
292 else
293 n += count;
294 curwin->w_changelistidx = n;
295 return curbuf->b_changelist + n;
296}
297#endif
298
299/*
Bram Moolenaar9d182dd2013-01-23 15:53:15 +0100300 * Find mark "c" in buffer pointed to by "buf".
Bram Moolenaarbfb2d402006-03-03 22:50:42 +0000301 * If "changefile" is TRUE it's allowed to edit another file for '0, 'A, etc.
302 * If "fnum" is not NULL store the fnum there for '0, 'A etc., don't edit
303 * another file.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000304 * Returns:
305 * - pointer to pos_T if found. lnum is 0 when mark not set, -1 when mark is
306 * in another file which can't be gotten. (caller needs to check lnum!)
307 * - NULL if there is no mark called 'c'.
308 * - -1 if mark is in other file and jumped there (only if changefile is TRUE)
309 */
310 pos_T *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100311getmark_buf(buf_T *buf, int c, int changefile)
Bram Moolenaar9d182dd2013-01-23 15:53:15 +0100312{
313 return getmark_buf_fnum(buf, c, changefile, NULL);
314}
315
316 pos_T *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100317getmark(int c, int changefile)
Bram Moolenaarbfb2d402006-03-03 22:50:42 +0000318{
Bram Moolenaar9d182dd2013-01-23 15:53:15 +0100319 return getmark_buf_fnum(curbuf, c, changefile, NULL);
Bram Moolenaarbfb2d402006-03-03 22:50:42 +0000320}
321
322 pos_T *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100323getmark_buf_fnum(
324 buf_T *buf,
325 int c,
326 int changefile,
327 int *fnum)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000328{
329 pos_T *posp;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000330 pos_T *startp, *endp;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000331 static pos_T pos_copy;
332
333 posp = NULL;
334
335 /* Check for special key, can't be a mark name and might cause islower()
336 * to crash. */
337 if (c < 0)
338 return posp;
339#ifndef EBCDIC
340 if (c > '~') /* check for islower()/isupper() */
341 ;
342 else
343#endif
344 if (c == '\'' || c == '`') /* previous context mark */
345 {
346 pos_copy = curwin->w_pcmark; /* need to make a copy because */
347 posp = &pos_copy; /* w_pcmark may be changed soon */
348 }
349 else if (c == '"') /* to pos when leaving buffer */
Bram Moolenaar9d182dd2013-01-23 15:53:15 +0100350 posp = &(buf->b_last_cursor);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000351 else if (c == '^') /* to where Insert mode stopped */
Bram Moolenaar9d182dd2013-01-23 15:53:15 +0100352 posp = &(buf->b_last_insert);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000353 else if (c == '.') /* to where last change was made */
Bram Moolenaar9d182dd2013-01-23 15:53:15 +0100354 posp = &(buf->b_last_change);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000355 else if (c == '[') /* to start of previous operator */
Bram Moolenaar9d182dd2013-01-23 15:53:15 +0100356 posp = &(buf->b_op_start);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000357 else if (c == ']') /* to end of previous operator */
Bram Moolenaar9d182dd2013-01-23 15:53:15 +0100358 posp = &(buf->b_op_end);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000359 else if (c == '{' || c == '}') /* to previous/next paragraph */
360 {
361 pos_T pos;
362 oparg_T oa;
363 int slcb = listcmd_busy;
364
365 pos = curwin->w_cursor;
366 listcmd_busy = TRUE; /* avoid that '' is changed */
Bram Moolenaar8b96d642005-09-05 22:05:30 +0000367 if (findpar(&oa.inclusive,
368 c == '}' ? FORWARD : BACKWARD, 1L, NUL, FALSE))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000369 {
370 pos_copy = curwin->w_cursor;
371 posp = &pos_copy;
372 }
373 curwin->w_cursor = pos;
374 listcmd_busy = slcb;
375 }
376 else if (c == '(' || c == ')') /* to previous/next sentence */
377 {
378 pos_T pos;
379 int slcb = listcmd_busy;
380
381 pos = curwin->w_cursor;
382 listcmd_busy = TRUE; /* avoid that '' is changed */
383 if (findsent(c == ')' ? FORWARD : BACKWARD, 1L))
384 {
385 pos_copy = curwin->w_cursor;
386 posp = &pos_copy;
387 }
388 curwin->w_cursor = pos;
389 listcmd_busy = slcb;
390 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000391 else if (c == '<' || c == '>') /* start/end of visual area */
392 {
Bram Moolenaar9d182dd2013-01-23 15:53:15 +0100393 startp = &buf->b_visual.vi_start;
394 endp = &buf->b_visual.vi_end;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000395 if ((c == '<') == lt(*startp, *endp))
396 posp = startp;
397 else
398 posp = endp;
399 /*
400 * For Visual line mode, set mark at begin or end of line
401 */
Bram Moolenaar9d182dd2013-01-23 15:53:15 +0100402 if (buf->b_visual.vi_mode == 'V')
Bram Moolenaar071d4272004-06-13 20:20:40 +0000403 {
404 pos_copy = *posp;
405 posp = &pos_copy;
406 if (c == '<')
407 pos_copy.col = 0;
408 else
409 pos_copy.col = MAXCOL;
410#ifdef FEAT_VIRTUALEDIT
411 pos_copy.coladd = 0;
412#endif
413 }
414 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000415 else if (ASCII_ISLOWER(c)) /* normal named mark */
416 {
Bram Moolenaar9d182dd2013-01-23 15:53:15 +0100417 posp = &(buf->b_namedm[c - 'a']);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000418 }
419 else if (ASCII_ISUPPER(c) || VIM_ISDIGIT(c)) /* named file mark */
420 {
421 if (VIM_ISDIGIT(c))
422 c = c - '0' + NMARKS;
423 else
424 c -= 'A';
425 posp = &(namedfm[c].fmark.mark);
426
427 if (namedfm[c].fmark.fnum == 0)
428 fname2fnum(&namedfm[c]);
Bram Moolenaarbfb2d402006-03-03 22:50:42 +0000429
430 if (fnum != NULL)
431 *fnum = namedfm[c].fmark.fnum;
Bram Moolenaar9d182dd2013-01-23 15:53:15 +0100432 else if (namedfm[c].fmark.fnum != buf->b_fnum)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000433 {
Bram Moolenaarbfb2d402006-03-03 22:50:42 +0000434 /* mark is in another file */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000435 posp = &pos_copy;
436
Bram Moolenaar071d4272004-06-13 20:20:40 +0000437 if (namedfm[c].fmark.mark.lnum != 0
438 && changefile && namedfm[c].fmark.fnum)
439 {
440 if (buflist_getfile(namedfm[c].fmark.fnum,
441 (linenr_T)1, GETF_SETMARK, FALSE) == OK)
442 {
443 /* Set the lnum now, autocommands could have changed it */
444 curwin->w_cursor = namedfm[c].fmark.mark;
445 return (pos_T *)-1;
446 }
447 pos_copy.lnum = -1; /* can't get file */
448 }
449 else
450 pos_copy.lnum = 0; /* mark exists, but is not valid in
451 current buffer */
452 }
453 }
454
455 return posp;
456}
457
458/*
459 * Search for the next named mark in the current file.
460 *
461 * Returns pointer to pos_T of the next mark or NULL if no mark is found.
462 */
463 pos_T *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100464getnextmark(
465 pos_T *startpos, /* where to start */
466 int dir, /* direction for search */
467 int begin_line)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000468{
469 int i;
470 pos_T *result = NULL;
471 pos_T pos;
472
473 pos = *startpos;
474
475 /* When searching backward and leaving the cursor on the first non-blank,
476 * position must be in a previous line.
477 * When searching forward and leaving the cursor on the first non-blank,
478 * position must be in a next line. */
479 if (dir == BACKWARD && begin_line)
480 pos.col = 0;
481 else if (dir == FORWARD && begin_line)
482 pos.col = MAXCOL;
483
484 for (i = 0; i < NMARKS; i++)
485 {
486 if (curbuf->b_namedm[i].lnum > 0)
487 {
488 if (dir == FORWARD)
489 {
490 if ((result == NULL || lt(curbuf->b_namedm[i], *result))
491 && lt(pos, curbuf->b_namedm[i]))
492 result = &curbuf->b_namedm[i];
493 }
494 else
495 {
496 if ((result == NULL || lt(*result, curbuf->b_namedm[i]))
497 && lt(curbuf->b_namedm[i], pos))
498 result = &curbuf->b_namedm[i];
499 }
500 }
501 }
502
503 return result;
504}
505
506/*
507 * For an xtended filemark: set the fnum from the fname.
508 * This is used for marks obtained from the .viminfo file. It's postponed
509 * until the mark is used to avoid a long startup delay.
510 */
511 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100512fname2fnum(xfmark_T *fm)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000513{
514 char_u *p;
515
516 if (fm->fname != NULL)
517 {
518 /*
519 * First expand "~/" in the file name to the home directory.
Bram Moolenaar525ad4d2008-01-03 19:22:13 +0000520 * Don't expand the whole name, it may contain other '~' chars.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000521 */
Bram Moolenaar525ad4d2008-01-03 19:22:13 +0000522 if (fm->fname[0] == '~' && (fm->fname[1] == '/'
523#ifdef BACKSLASH_IN_FILENAME
524 || fm->fname[1] == '\\'
525#endif
526 ))
527 {
528 int len;
529
530 expand_env((char_u *)"~/", NameBuff, MAXPATHL);
Bram Moolenaarcb4cef22008-03-16 15:04:34 +0000531 len = (int)STRLEN(NameBuff);
Bram Moolenaar525ad4d2008-01-03 19:22:13 +0000532 vim_strncpy(NameBuff + len, fm->fname + 2, MAXPATHL - len - 1);
533 }
534 else
535 vim_strncpy(NameBuff, fm->fname, MAXPATHL - 1);
536
537 /* Try to shorten the file name. */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000538 mch_dirname(IObuff, IOSIZE);
539 p = shorten_fname(NameBuff, IObuff);
540
541 /* buflist_new() will call fmarks_check_names() */
542 (void)buflist_new(NameBuff, p, (linenr_T)1, 0);
543 }
544}
545
546/*
547 * Check all file marks for a name that matches the file name in buf.
548 * May replace the name with an fnum.
549 * Used for marks that come from the .viminfo file.
550 */
551 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100552fmarks_check_names(buf_T *buf)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000553{
554 char_u *name;
555 int i;
556#ifdef FEAT_JUMPLIST
557 win_T *wp;
558#endif
559
560 if (buf->b_ffname == NULL)
561 return;
562
563 name = home_replace_save(buf, buf->b_ffname);
564 if (name == NULL)
565 return;
566
567 for (i = 0; i < NMARKS + EXTRA_MARKS; ++i)
568 fmarks_check_one(&namedfm[i], name, buf);
569
570#ifdef FEAT_JUMPLIST
571 FOR_ALL_WINDOWS(wp)
572 {
573 for (i = 0; i < wp->w_jumplistlen; ++i)
574 fmarks_check_one(&wp->w_jumplist[i], name, buf);
575 }
576#endif
577
578 vim_free(name);
579}
580
581 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100582fmarks_check_one(xfmark_T *fm, char_u *name, buf_T *buf)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000583{
584 if (fm->fmark.fnum == 0
585 && fm->fname != NULL
586 && fnamecmp(name, fm->fname) == 0)
587 {
588 fm->fmark.fnum = buf->b_fnum;
589 vim_free(fm->fname);
590 fm->fname = NULL;
591 }
592}
593
594/*
595 * Check a if a position from a mark is valid.
596 * Give and error message and return FAIL if not.
597 */
598 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100599check_mark(pos_T *pos)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000600{
601 if (pos == NULL)
602 {
603 EMSG(_(e_umark));
604 return FAIL;
605 }
606 if (pos->lnum <= 0)
607 {
608 /* lnum is negative if mark is in another file can can't get that
609 * file, error message already give then. */
610 if (pos->lnum == 0)
611 EMSG(_(e_marknotset));
612 return FAIL;
613 }
614 if (pos->lnum > curbuf->b_ml.ml_line_count)
615 {
616 EMSG(_(e_markinval));
617 return FAIL;
618 }
619 return OK;
620}
621
622/*
623 * clrallmarks() - clear all marks in the buffer 'buf'
624 *
625 * Used mainly when trashing the entire buffer during ":e" type commands
626 */
627 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100628clrallmarks(buf_T *buf)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000629{
630 static int i = -1;
631
632 if (i == -1) /* first call ever: initialize */
633 for (i = 0; i < NMARKS + 1; i++)
634 {
635 namedfm[i].fmark.mark.lnum = 0;
636 namedfm[i].fname = NULL;
637 }
638
639 for (i = 0; i < NMARKS; i++)
640 buf->b_namedm[i].lnum = 0;
641 buf->b_op_start.lnum = 0; /* start/end op mark cleared */
642 buf->b_op_end.lnum = 0;
643 buf->b_last_cursor.lnum = 1; /* '" mark cleared */
644 buf->b_last_cursor.col = 0;
645#ifdef FEAT_VIRTUALEDIT
646 buf->b_last_cursor.coladd = 0;
647#endif
648 buf->b_last_insert.lnum = 0; /* '^ mark cleared */
649 buf->b_last_change.lnum = 0; /* '. mark cleared */
650#ifdef FEAT_JUMPLIST
651 buf->b_changelistlen = 0;
652#endif
653}
654
655/*
656 * Get name of file from a filemark.
657 * When it's in the current buffer, return the text at the mark.
658 * Returns an allocated string.
659 */
660 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100661fm_getname(fmark_T *fmark, int lead_len)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000662{
663 if (fmark->fnum == curbuf->b_fnum) /* current buffer */
664 return mark_line(&(fmark->mark), lead_len);
665 return buflist_nr2name(fmark->fnum, FALSE, TRUE);
666}
667
668/*
669 * Return the line at mark "mp". Truncate to fit in window.
670 * The returned string has been allocated.
671 */
672 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100673mark_line(pos_T *mp, int lead_len)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000674{
675 char_u *s, *p;
676 int len;
677
678 if (mp->lnum == 0 || mp->lnum > curbuf->b_ml.ml_line_count)
679 return vim_strsave((char_u *)"-invalid-");
680 s = vim_strnsave(skipwhite(ml_get(mp->lnum)), (int)Columns);
681 if (s == NULL)
682 return NULL;
683 /* Truncate the line to fit it in the window */
684 len = 0;
Bram Moolenaar1cd871b2004-12-19 22:46:22 +0000685 for (p = s; *p != NUL; mb_ptr_adv(p))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000686 {
687 len += ptr2cells(p);
688 if (len >= Columns - lead_len)
689 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000690 }
691 *p = NUL;
692 return s;
693}
694
695/*
696 * print the marks
697 */
698 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100699do_marks(exarg_T *eap)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000700{
701 char_u *arg = eap->arg;
702 int i;
703 char_u *name;
704
705 if (arg != NULL && *arg == NUL)
706 arg = NULL;
707
708 show_one_mark('\'', arg, &curwin->w_pcmark, NULL, TRUE);
709 for (i = 0; i < NMARKS; ++i)
710 show_one_mark(i + 'a', arg, &curbuf->b_namedm[i], NULL, TRUE);
711 for (i = 0; i < NMARKS + EXTRA_MARKS; ++i)
712 {
713 if (namedfm[i].fmark.fnum != 0)
714 name = fm_getname(&namedfm[i].fmark, 15);
715 else
716 name = namedfm[i].fname;
717 if (name != NULL)
718 {
719 show_one_mark(i >= NMARKS ? i - NMARKS + '0' : i + 'A',
720 arg, &namedfm[i].fmark.mark, name,
721 namedfm[i].fmark.fnum == curbuf->b_fnum);
722 if (namedfm[i].fmark.fnum != 0)
723 vim_free(name);
724 }
725 }
726 show_one_mark('"', arg, &curbuf->b_last_cursor, NULL, TRUE);
727 show_one_mark('[', arg, &curbuf->b_op_start, NULL, TRUE);
728 show_one_mark(']', arg, &curbuf->b_op_end, NULL, TRUE);
729 show_one_mark('^', arg, &curbuf->b_last_insert, NULL, TRUE);
730 show_one_mark('.', arg, &curbuf->b_last_change, NULL, TRUE);
Bram Moolenaara226a6d2006-02-26 23:59:20 +0000731 show_one_mark('<', arg, &curbuf->b_visual.vi_start, NULL, TRUE);
732 show_one_mark('>', arg, &curbuf->b_visual.vi_end, NULL, TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000733 show_one_mark(-1, arg, NULL, NULL, FALSE);
734}
735
736 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100737show_one_mark(
738 int c,
739 char_u *arg,
740 pos_T *p,
741 char_u *name,
742 int current) /* in current file */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000743{
744 static int did_title = FALSE;
745 int mustfree = FALSE;
746
747 if (c == -1) /* finish up */
748 {
749 if (did_title)
750 did_title = FALSE;
751 else
752 {
753 if (arg == NULL)
754 MSG(_("No marks set"));
755 else
756 EMSG2(_("E283: No marks matching \"%s\""), arg);
757 }
758 }
759 /* don't output anything if 'q' typed at --more-- prompt */
760 else if (!got_int
761 && (arg == NULL || vim_strchr(arg, c) != NULL)
762 && p->lnum != 0)
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 && current)
776 {
777 name = mark_line(p, 15);
778 mustfree = TRUE;
779 }
780 if (name != NULL)
781 {
782 msg_outtrans_attr(name, current ? hl_attr(HLF_D) : 0);
783 if (mustfree)
784 vim_free(name);
785 }
786 }
787 out_flush(); /* show one line at a time */
788 }
789}
790
Bram Moolenaarc0197e22004-09-13 20:26:32 +0000791/*
792 * ":delmarks[!] [marks]"
793 */
794 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100795ex_delmarks(exarg_T *eap)
Bram Moolenaarc0197e22004-09-13 20:26:32 +0000796{
797 char_u *p;
798 int from, to;
799 int i;
800 int lower;
801 int digit;
802 int n;
803
804 if (*eap->arg == NUL && eap->forceit)
805 /* clear all marks */
806 clrallmarks(curbuf);
807 else if (eap->forceit)
808 EMSG(_(e_invarg));
809 else if (*eap->arg == NUL)
810 EMSG(_(e_argreq));
811 else
812 {
813 /* clear specified marks only */
814 for (p = eap->arg; *p != NUL; ++p)
815 {
816 lower = ASCII_ISLOWER(*p);
817 digit = VIM_ISDIGIT(*p);
818 if (lower || digit || ASCII_ISUPPER(*p))
819 {
820 if (p[1] == '-')
821 {
822 /* clear range of marks */
823 from = *p;
824 to = p[2];
825 if (!(lower ? ASCII_ISLOWER(p[2])
826 : (digit ? VIM_ISDIGIT(p[2])
827 : ASCII_ISUPPER(p[2])))
828 || to < from)
829 {
830 EMSG2(_(e_invarg2), p);
831 return;
832 }
833 p += 2;
834 }
835 else
836 /* clear one lower case mark */
837 from = to = *p;
838
839 for (i = from; i <= to; ++i)
840 {
841 if (lower)
842 curbuf->b_namedm[i - 'a'].lnum = 0;
843 else
844 {
845 if (digit)
846 n = i - '0' + NMARKS;
847 else
848 n = i - 'A';
849 namedfm[n].fmark.mark.lnum = 0;
850 vim_free(namedfm[n].fname);
851 namedfm[n].fname = NULL;
852 }
853 }
854 }
855 else
856 switch (*p)
857 {
858 case '"': curbuf->b_last_cursor.lnum = 0; break;
859 case '^': curbuf->b_last_insert.lnum = 0; break;
860 case '.': curbuf->b_last_change.lnum = 0; break;
861 case '[': curbuf->b_op_start.lnum = 0; break;
862 case ']': curbuf->b_op_end.lnum = 0; break;
Bram Moolenaara226a6d2006-02-26 23:59:20 +0000863 case '<': curbuf->b_visual.vi_start.lnum = 0; break;
864 case '>': curbuf->b_visual.vi_end.lnum = 0; break;
Bram Moolenaarc0197e22004-09-13 20:26:32 +0000865 case ' ': break;
866 default: EMSG2(_(e_invarg2), p);
867 return;
868 }
869 }
870 }
871}
872
Bram Moolenaar071d4272004-06-13 20:20:40 +0000873#if defined(FEAT_JUMPLIST) || defined(PROTO)
874/*
875 * print the jumplist
876 */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000877 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100878ex_jumps(exarg_T *eap UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000879{
880 int i;
881 char_u *name;
882
883 cleanup_jumplist();
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 if (curwin->w_jumplist[i].fmark.fnum == 0)
891 fname2fnum(&curwin->w_jumplist[i]);
892 name = fm_getname(&curwin->w_jumplist[i].fmark, 16);
893 if (name == NULL) /* file name not available */
894 continue;
895
896 msg_putchar('\n');
897 if (got_int)
Bram Moolenaared39e1d2008-08-09 17:55:22 +0000898 {
899 vim_free(name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000900 break;
Bram Moolenaared39e1d2008-08-09 17:55:22 +0000901 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000902 sprintf((char *)IObuff, "%c %2d %5ld %4d ",
903 i == curwin->w_jumplistidx ? '>' : ' ',
904 i > curwin->w_jumplistidx ? i - curwin->w_jumplistidx
905 : curwin->w_jumplistidx - i,
906 curwin->w_jumplist[i].fmark.mark.lnum,
907 curwin->w_jumplist[i].fmark.mark.col);
908 msg_outtrans(IObuff);
909 msg_outtrans_attr(name,
910 curwin->w_jumplist[i].fmark.fnum == curbuf->b_fnum
911 ? hl_attr(HLF_D) : 0);
912 vim_free(name);
913 ui_breakcheck();
914 }
915 out_flush();
916 }
917 if (curwin->w_jumplistidx == curwin->w_jumplistlen)
918 MSG_PUTS("\n>");
919}
920
921/*
922 * print the changelist
923 */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000924 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100925ex_changes(exarg_T *eap UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000926{
927 int i;
928 char_u *name;
929
930 /* Highlight title */
931 MSG_PUTS_TITLE(_("\nchange line col text"));
932
933 for (i = 0; i < curbuf->b_changelistlen && !got_int; ++i)
934 {
935 if (curbuf->b_changelist[i].lnum != 0)
936 {
937 msg_putchar('\n');
938 if (got_int)
939 break;
940 sprintf((char *)IObuff, "%c %3d %5ld %4d ",
941 i == curwin->w_changelistidx ? '>' : ' ',
942 i > curwin->w_changelistidx ? i - curwin->w_changelistidx
943 : curwin->w_changelistidx - i,
944 (long)curbuf->b_changelist[i].lnum,
945 curbuf->b_changelist[i].col);
946 msg_outtrans(IObuff);
947 name = mark_line(&curbuf->b_changelist[i], 17);
948 if (name == NULL)
949 break;
950 msg_outtrans_attr(name, hl_attr(HLF_D));
951 vim_free(name);
952 ui_breakcheck();
953 }
954 out_flush();
955 }
956 if (curwin->w_changelistidx == curbuf->b_changelistlen)
957 MSG_PUTS("\n>");
958}
959#endif
960
961#define one_adjust(add) \
962 { \
963 lp = add; \
964 if (*lp >= line1 && *lp <= line2) \
965 { \
966 if (amount == MAXLNUM) \
967 *lp = 0; \
968 else \
969 *lp += amount; \
970 } \
971 else if (amount_after && *lp > line2) \
972 *lp += amount_after; \
973 }
974
975/* don't delete the line, just put at first deleted line */
976#define one_adjust_nodel(add) \
977 { \
978 lp = add; \
979 if (*lp >= line1 && *lp <= line2) \
980 { \
981 if (amount == MAXLNUM) \
982 *lp = line1; \
983 else \
984 *lp += amount; \
985 } \
986 else if (amount_after && *lp > line2) \
987 *lp += amount_after; \
988 }
989
990/*
991 * Adjust marks between line1 and line2 (inclusive) to move 'amount' lines.
992 * Must be called before changed_*(), appended_lines() or deleted_lines().
993 * May be called before or after changing the text.
994 * When deleting lines line1 to line2, use an 'amount' of MAXLNUM: The marks
995 * within this range are made invalid.
996 * If 'amount_after' is non-zero adjust marks after line2.
997 * Example: Delete lines 34 and 35: mark_adjust(34, 35, MAXLNUM, -2);
998 * Example: Insert two lines below 55: mark_adjust(56, MAXLNUM, 2, 0);
999 * or: mark_adjust(56, 55, MAXLNUM, 2);
1000 */
1001 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001002mark_adjust(
1003 linenr_T line1,
1004 linenr_T line2,
1005 long amount,
1006 long amount_after)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001007{
1008 int i;
1009 int fnum = curbuf->b_fnum;
1010 linenr_T *lp;
1011 win_T *win;
Bram Moolenaarbd1e5d22009-04-29 09:02:44 +00001012#ifdef FEAT_WINDOWS
1013 tabpage_T *tab;
1014#endif
Bram Moolenaarb6a76ff2013-02-06 12:33:21 +01001015 static pos_T initpos = INIT_POS_T(1, 0, 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001016
1017 if (line2 < line1 && amount_after == 0L) /* nothing to do */
1018 return;
1019
1020 if (!cmdmod.lockmarks)
1021 {
1022 /* named marks, lower case and upper case */
1023 for (i = 0; i < NMARKS; i++)
1024 {
1025 one_adjust(&(curbuf->b_namedm[i].lnum));
1026 if (namedfm[i].fmark.fnum == fnum)
1027 one_adjust_nodel(&(namedfm[i].fmark.mark.lnum));
1028 }
1029 for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++)
1030 {
1031 if (namedfm[i].fmark.fnum == fnum)
1032 one_adjust_nodel(&(namedfm[i].fmark.mark.lnum));
1033 }
1034
1035 /* last Insert position */
1036 one_adjust(&(curbuf->b_last_insert.lnum));
1037
1038 /* last change position */
1039 one_adjust(&(curbuf->b_last_change.lnum));
1040
Bram Moolenaarb6a76ff2013-02-06 12:33:21 +01001041 /* last cursor position, if it was set */
1042 if (!equalpos(curbuf->b_last_cursor, initpos))
1043 one_adjust(&(curbuf->b_last_cursor.lnum));
1044
1045
Bram Moolenaar071d4272004-06-13 20:20:40 +00001046#ifdef FEAT_JUMPLIST
1047 /* list of change positions */
1048 for (i = 0; i < curbuf->b_changelistlen; ++i)
1049 one_adjust_nodel(&(curbuf->b_changelist[i].lnum));
1050#endif
1051
Bram Moolenaar071d4272004-06-13 20:20:40 +00001052 /* Visual area */
Bram Moolenaara226a6d2006-02-26 23:59:20 +00001053 one_adjust_nodel(&(curbuf->b_visual.vi_start.lnum));
1054 one_adjust_nodel(&(curbuf->b_visual.vi_end.lnum));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001055
1056#ifdef FEAT_QUICKFIX
1057 /* quickfix marks */
Bram Moolenaar28c258f2006-01-25 22:02:51 +00001058 qf_mark_adjust(NULL, line1, line2, amount, amount_after);
1059 /* location lists */
Bram Moolenaarbd1e5d22009-04-29 09:02:44 +00001060 FOR_ALL_TAB_WINDOWS(tab, win)
Bram Moolenaar28c258f2006-01-25 22:02:51 +00001061 qf_mark_adjust(win, line1, line2, amount, amount_after);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001062#endif
1063
1064#ifdef FEAT_SIGNS
1065 sign_mark_adjust(line1, line2, amount, amount_after);
1066#endif
1067 }
1068
1069 /* previous context mark */
1070 one_adjust(&(curwin->w_pcmark.lnum));
1071
1072 /* previous pcmark */
1073 one_adjust(&(curwin->w_prev_pcmark.lnum));
1074
1075 /* saved cursor for formatting */
1076 if (saved_cursor.lnum != 0)
1077 one_adjust_nodel(&(saved_cursor.lnum));
1078
1079 /*
1080 * Adjust items in all windows related to the current buffer.
1081 */
Bram Moolenaarbd1e5d22009-04-29 09:02:44 +00001082 FOR_ALL_TAB_WINDOWS(tab, win)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001083 {
1084#ifdef FEAT_JUMPLIST
1085 if (!cmdmod.lockmarks)
1086 /* Marks in the jumplist. When deleting lines, this may create
1087 * duplicate marks in the jumplist, they will be removed later. */
1088 for (i = 0; i < win->w_jumplistlen; ++i)
1089 if (win->w_jumplist[i].fmark.fnum == fnum)
1090 one_adjust_nodel(&(win->w_jumplist[i].fmark.mark.lnum));
1091#endif
1092
1093 if (win->w_buffer == curbuf)
1094 {
1095 if (!cmdmod.lockmarks)
1096 /* marks in the tag stack */
1097 for (i = 0; i < win->w_tagstacklen; i++)
1098 if (win->w_tagstack[i].fmark.fnum == fnum)
1099 one_adjust_nodel(&(win->w_tagstack[i].fmark.mark.lnum));
1100
Bram Moolenaar071d4272004-06-13 20:20:40 +00001101 /* the displayed Visual area */
1102 if (win->w_old_cursor_lnum != 0)
1103 {
1104 one_adjust_nodel(&(win->w_old_cursor_lnum));
1105 one_adjust_nodel(&(win->w_old_visual_lnum));
1106 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001107
1108 /* topline and cursor position for windows with the same buffer
1109 * other than the current window */
1110 if (win != curwin)
1111 {
1112 if (win->w_topline >= line1 && win->w_topline <= line2)
1113 {
1114 if (amount == MAXLNUM) /* topline is deleted */
1115 {
1116 if (line1 <= 1)
1117 win->w_topline = 1;
1118 else
1119 win->w_topline = line1 - 1;
1120 }
1121 else /* keep topline on the same line */
1122 win->w_topline += amount;
1123#ifdef FEAT_DIFF
1124 win->w_topfill = 0;
1125#endif
1126 }
1127 else if (amount_after && win->w_topline > line2)
1128 {
1129 win->w_topline += amount_after;
1130#ifdef FEAT_DIFF
1131 win->w_topfill = 0;
1132#endif
1133 }
1134 if (win->w_cursor.lnum >= line1 && win->w_cursor.lnum <= line2)
1135 {
1136 if (amount == MAXLNUM) /* line with cursor is deleted */
1137 {
1138 if (line1 <= 1)
1139 win->w_cursor.lnum = 1;
1140 else
1141 win->w_cursor.lnum = line1 - 1;
1142 win->w_cursor.col = 0;
1143 }
1144 else /* keep cursor on the same line */
1145 win->w_cursor.lnum += amount;
1146 }
1147 else if (amount_after && win->w_cursor.lnum > line2)
1148 win->w_cursor.lnum += amount_after;
1149 }
1150
1151#ifdef FEAT_FOLDING
1152 /* adjust folds */
1153 foldMarkAdjust(win, line1, line2, amount, amount_after);
1154#endif
1155 }
1156 }
1157
1158#ifdef FEAT_DIFF
1159 /* adjust diffs */
1160 diff_mark_adjust(line1, line2, amount, amount_after);
1161#endif
1162}
1163
1164/* This code is used often, needs to be fast. */
1165#define col_adjust(pp) \
1166 { \
1167 posp = pp; \
1168 if (posp->lnum == lnum && posp->col >= mincol) \
1169 { \
1170 posp->lnum += lnum_amount; \
1171 if (col_amount < 0 && posp->col <= (colnr_T)-col_amount) \
1172 posp->col = 0; \
1173 else \
1174 posp->col += col_amount; \
1175 } \
1176 }
1177
1178/*
1179 * Adjust marks in line "lnum" at column "mincol" and further: add
1180 * "lnum_amount" to the line number and add "col_amount" to the column
1181 * position.
1182 */
1183 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001184mark_col_adjust(
1185 linenr_T lnum,
1186 colnr_T mincol,
1187 long lnum_amount,
1188 long col_amount)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001189{
1190 int i;
1191 int fnum = curbuf->b_fnum;
1192 win_T *win;
1193 pos_T *posp;
1194
1195 if ((col_amount == 0L && lnum_amount == 0L) || cmdmod.lockmarks)
1196 return; /* nothing to do */
1197
1198 /* named marks, lower case and upper case */
1199 for (i = 0; i < NMARKS; i++)
1200 {
1201 col_adjust(&(curbuf->b_namedm[i]));
1202 if (namedfm[i].fmark.fnum == fnum)
1203 col_adjust(&(namedfm[i].fmark.mark));
1204 }
1205 for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++)
1206 {
1207 if (namedfm[i].fmark.fnum == fnum)
1208 col_adjust(&(namedfm[i].fmark.mark));
1209 }
1210
1211 /* last Insert position */
1212 col_adjust(&(curbuf->b_last_insert));
1213
1214 /* last change position */
1215 col_adjust(&(curbuf->b_last_change));
1216
1217#ifdef FEAT_JUMPLIST
1218 /* list of change positions */
1219 for (i = 0; i < curbuf->b_changelistlen; ++i)
1220 col_adjust(&(curbuf->b_changelist[i]));
1221#endif
1222
Bram Moolenaar071d4272004-06-13 20:20:40 +00001223 /* Visual area */
Bram Moolenaara226a6d2006-02-26 23:59:20 +00001224 col_adjust(&(curbuf->b_visual.vi_start));
1225 col_adjust(&(curbuf->b_visual.vi_end));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001226
1227 /* previous context mark */
1228 col_adjust(&(curwin->w_pcmark));
1229
1230 /* previous pcmark */
1231 col_adjust(&(curwin->w_prev_pcmark));
1232
1233 /* saved cursor for formatting */
1234 col_adjust(&saved_cursor);
1235
1236 /*
1237 * Adjust items in all windows related to the current buffer.
1238 */
1239 FOR_ALL_WINDOWS(win)
1240 {
1241#ifdef FEAT_JUMPLIST
1242 /* marks in the jumplist */
1243 for (i = 0; i < win->w_jumplistlen; ++i)
1244 if (win->w_jumplist[i].fmark.fnum == fnum)
1245 col_adjust(&(win->w_jumplist[i].fmark.mark));
1246#endif
1247
1248 if (win->w_buffer == curbuf)
1249 {
1250 /* marks in the tag stack */
1251 for (i = 0; i < win->w_tagstacklen; i++)
1252 if (win->w_tagstack[i].fmark.fnum == fnum)
1253 col_adjust(&(win->w_tagstack[i].fmark.mark));
1254
1255 /* cursor position for other windows with the same buffer */
1256 if (win != curwin)
1257 col_adjust(&win->w_cursor);
1258 }
1259 }
1260}
1261
1262#ifdef FEAT_JUMPLIST
1263/*
1264 * When deleting lines, this may create duplicate marks in the
1265 * jumplist. They will be removed here for the current window.
1266 */
1267 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001268cleanup_jumplist(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001269{
1270 int i;
1271 int from, to;
1272
1273 to = 0;
1274 for (from = 0; from < curwin->w_jumplistlen; ++from)
1275 {
1276 if (curwin->w_jumplistidx == from)
1277 curwin->w_jumplistidx = to;
1278 for (i = from + 1; i < curwin->w_jumplistlen; ++i)
1279 if (curwin->w_jumplist[i].fmark.fnum
1280 == curwin->w_jumplist[from].fmark.fnum
1281 && curwin->w_jumplist[from].fmark.fnum != 0
1282 && curwin->w_jumplist[i].fmark.mark.lnum
1283 == curwin->w_jumplist[from].fmark.mark.lnum)
1284 break;
1285 if (i >= curwin->w_jumplistlen) /* no duplicate */
1286 curwin->w_jumplist[to++] = curwin->w_jumplist[from];
1287 else
1288 vim_free(curwin->w_jumplist[from].fname);
1289 }
1290 if (curwin->w_jumplistidx == curwin->w_jumplistlen)
1291 curwin->w_jumplistidx = to;
1292 curwin->w_jumplistlen = to;
1293}
1294
1295# if defined(FEAT_WINDOWS) || defined(PROTO)
1296/*
1297 * Copy the jumplist from window "from" to window "to".
1298 */
1299 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001300copy_jumplist(win_T *from, win_T *to)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001301{
1302 int i;
1303
1304 for (i = 0; i < from->w_jumplistlen; ++i)
1305 {
1306 to->w_jumplist[i] = from->w_jumplist[i];
1307 if (from->w_jumplist[i].fname != NULL)
1308 to->w_jumplist[i].fname = vim_strsave(from->w_jumplist[i].fname);
1309 }
1310 to->w_jumplistlen = from->w_jumplistlen;
1311 to->w_jumplistidx = from->w_jumplistidx;
1312}
1313
1314/*
1315 * Free items in the jumplist of window "wp".
1316 */
1317 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001318free_jumplist(win_T *wp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001319{
1320 int i;
1321
1322 for (i = 0; i < wp->w_jumplistlen; ++i)
1323 vim_free(wp->w_jumplist[i].fname);
1324}
1325# endif
1326#endif /* FEAT_JUMPLIST */
1327
1328 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001329set_last_cursor(win_T *win)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001330{
Bram Moolenaar9db12932013-11-03 00:20:52 +01001331 if (win->w_buffer != NULL)
1332 win->w_buffer->b_last_cursor = win->w_cursor;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001333}
1334
Bram Moolenaarea408852005-06-25 22:49:46 +00001335#if defined(EXITFREE) || defined(PROTO)
1336 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001337free_all_marks(void)
Bram Moolenaarea408852005-06-25 22:49:46 +00001338{
1339 int i;
1340
1341 for (i = 0; i < NMARKS + EXTRA_MARKS; i++)
1342 if (namedfm[i].fmark.mark.lnum != 0)
1343 vim_free(namedfm[i].fname);
1344}
1345#endif
1346
Bram Moolenaar071d4272004-06-13 20:20:40 +00001347#if defined(FEAT_VIMINFO) || defined(PROTO)
1348 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001349read_viminfo_filemark(vir_T *virp, int force)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001350{
1351 char_u *str;
1352 xfmark_T *fm;
1353 int i;
1354
1355 /* We only get here if line[0] == '\'' or '-'.
1356 * Illegal mark names are ignored (for future expansion). */
1357 str = virp->vir_line + 1;
1358 if (
1359#ifndef EBCDIC
1360 *str <= 127 &&
1361#endif
1362 ((*virp->vir_line == '\'' && (VIM_ISDIGIT(*str) || isupper(*str)))
1363 || (*virp->vir_line == '-' && *str == '\'')))
1364 {
1365 if (*str == '\'')
1366 {
1367#ifdef FEAT_JUMPLIST
1368 /* If the jumplist isn't full insert fmark as oldest entry */
1369 if (curwin->w_jumplistlen == JUMPLISTSIZE)
1370 fm = NULL;
1371 else
1372 {
1373 for (i = curwin->w_jumplistlen; i > 0; --i)
1374 curwin->w_jumplist[i] = curwin->w_jumplist[i - 1];
1375 ++curwin->w_jumplistidx;
1376 ++curwin->w_jumplistlen;
1377 fm = &curwin->w_jumplist[0];
1378 fm->fmark.mark.lnum = 0;
1379 fm->fname = NULL;
1380 }
1381#else
1382 fm = NULL;
1383#endif
1384 }
1385 else if (VIM_ISDIGIT(*str))
1386 fm = &namedfm[*str - '0' + NMARKS];
1387 else
1388 fm = &namedfm[*str - 'A'];
1389 if (fm != NULL && (fm->fmark.mark.lnum == 0 || force))
1390 {
1391 str = skipwhite(str + 1);
1392 fm->fmark.mark.lnum = getdigits(&str);
1393 str = skipwhite(str);
1394 fm->fmark.mark.col = getdigits(&str);
1395#ifdef FEAT_VIRTUALEDIT
1396 fm->fmark.mark.coladd = 0;
1397#endif
1398 fm->fmark.fnum = 0;
1399 str = skipwhite(str);
1400 vim_free(fm->fname);
1401 fm->fname = viminfo_readstring(virp, (int)(str - virp->vir_line),
1402 FALSE);
1403 }
1404 }
1405 return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd);
1406}
1407
1408 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001409write_viminfo_filemarks(FILE *fp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001410{
1411 int i;
1412 char_u *name;
1413 buf_T *buf;
1414 xfmark_T *fm;
1415
1416 if (get_viminfo_parameter('f') == 0)
1417 return;
1418
Bram Moolenaar2f1e0502010-08-13 11:18:02 +02001419 fputs(_("\n# File marks:\n"), fp);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001420
1421 /*
1422 * Find a mark that is the same file and position as the cursor.
1423 * That one, or else the last one is deleted.
1424 * Move '0 to '1, '1 to '2, etc. until the matching one or '9
1425 * Set '0 mark to current cursor position.
1426 */
1427 if (curbuf->b_ffname != NULL && !removable(curbuf->b_ffname))
1428 {
1429 name = buflist_nr2name(curbuf->b_fnum, TRUE, FALSE);
1430 for (i = NMARKS; i < NMARKS + EXTRA_MARKS - 1; ++i)
1431 if (namedfm[i].fmark.mark.lnum == curwin->w_cursor.lnum
1432 && (namedfm[i].fname == NULL
1433 ? namedfm[i].fmark.fnum == curbuf->b_fnum
1434 : (name != NULL
1435 && STRCMP(name, namedfm[i].fname) == 0)))
1436 break;
1437 vim_free(name);
1438
1439 vim_free(namedfm[i].fname);
1440 for ( ; i > NMARKS; --i)
1441 namedfm[i] = namedfm[i - 1];
1442 namedfm[NMARKS].fmark.mark = curwin->w_cursor;
1443 namedfm[NMARKS].fmark.fnum = curbuf->b_fnum;
1444 namedfm[NMARKS].fname = NULL;
1445 }
1446
1447 /* Write the filemarks '0 - '9 and 'A - 'Z */
1448 for (i = 0; i < NMARKS + EXTRA_MARKS; i++)
1449 write_one_filemark(fp, &namedfm[i], '\'',
1450 i < NMARKS ? i + 'A' : i - NMARKS + '0');
1451
1452#ifdef FEAT_JUMPLIST
1453 /* Write the jumplist with -' */
Bram Moolenaar2f1e0502010-08-13 11:18:02 +02001454 fputs(_("\n# Jumplist (newest first):\n"), fp);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001455 setpcmark(); /* add current cursor position */
1456 cleanup_jumplist();
1457 for (fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1];
1458 fm >= &curwin->w_jumplist[0]; --fm)
1459 {
1460 if (fm->fmark.fnum == 0
1461 || ((buf = buflist_findnr(fm->fmark.fnum)) != NULL
1462 && !removable(buf->b_ffname)))
1463 write_one_filemark(fp, fm, '-', '\'');
1464 }
1465#endif
1466}
1467
1468 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001469write_one_filemark(
1470 FILE *fp,
1471 xfmark_T *fm,
1472 int c1,
1473 int c2)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001474{
1475 char_u *name;
1476
1477 if (fm->fmark.mark.lnum == 0) /* not set */
1478 return;
1479
1480 if (fm->fmark.fnum != 0) /* there is a buffer */
1481 name = buflist_nr2name(fm->fmark.fnum, TRUE, FALSE);
1482 else
1483 name = fm->fname; /* use name from .viminfo */
1484 if (name != NULL && *name != NUL)
1485 {
1486 fprintf(fp, "%c%c %ld %ld ", c1, c2, (long)fm->fmark.mark.lnum,
1487 (long)fm->fmark.mark.col);
1488 viminfo_writestring(fp, name);
1489 }
1490
1491 if (fm->fmark.fnum != 0)
1492 vim_free(name);
1493}
1494
1495/*
1496 * Return TRUE if "name" is on removable media (depending on 'viminfo').
1497 */
1498 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001499removable(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001500{
1501 char_u *p;
1502 char_u part[51];
1503 int retval = FALSE;
Bram Moolenaarcfc6c432005-06-06 21:50:35 +00001504 size_t n;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001505
1506 name = home_replace_save(NULL, name);
1507 if (name != NULL)
1508 {
1509 for (p = p_viminfo; *p; )
1510 {
1511 copy_option_part(&p, part, 51, ", ");
Bram Moolenaarcfc6c432005-06-06 21:50:35 +00001512 if (part[0] == 'r')
Bram Moolenaar071d4272004-06-13 20:20:40 +00001513 {
Bram Moolenaarcfc6c432005-06-06 21:50:35 +00001514 n = STRLEN(part + 1);
1515 if (MB_STRNICMP(part + 1, name, n) == 0)
1516 {
1517 retval = TRUE;
1518 break;
1519 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001520 }
1521 }
1522 vim_free(name);
1523 }
1524 return retval;
1525}
1526
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +01001527static void write_one_mark(FILE *fp_out, int c, pos_T *pos);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001528
1529/*
1530 * Write all the named marks for all buffers.
1531 * Return the number of buffers for which marks have been written.
1532 */
1533 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001534write_viminfo_marks(FILE *fp_out)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001535{
1536 int count;
1537 buf_T *buf;
1538 int is_mark_set;
1539 int i;
1540#ifdef FEAT_WINDOWS
1541 win_T *win;
Bram Moolenaarf740b292006-02-16 22:11:02 +00001542 tabpage_T *tp;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001543
1544 /*
1545 * Set b_last_cursor for the all buffers that have a window.
1546 */
Bram Moolenaarf740b292006-02-16 22:11:02 +00001547 FOR_ALL_TAB_WINDOWS(tp, win)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001548 set_last_cursor(win);
1549#else
1550 set_last_cursor(curwin);
1551#endif
1552
Bram Moolenaar2f1e0502010-08-13 11:18:02 +02001553 fputs(_("\n# History of marks within files (newest to oldest):\n"), fp_out);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001554 count = 0;
1555 for (buf = firstbuf; buf != NULL; buf = buf->b_next)
1556 {
1557 /*
1558 * Only write something if buffer has been loaded and at least one
1559 * mark is set.
1560 */
1561 if (buf->b_marks_read)
1562 {
1563 if (buf->b_last_cursor.lnum != 0)
1564 is_mark_set = TRUE;
1565 else
1566 {
1567 is_mark_set = FALSE;
1568 for (i = 0; i < NMARKS; i++)
1569 if (buf->b_namedm[i].lnum != 0)
1570 {
1571 is_mark_set = TRUE;
1572 break;
1573 }
1574 }
1575 if (is_mark_set && buf->b_ffname != NULL
1576 && buf->b_ffname[0] != NUL && !removable(buf->b_ffname))
1577 {
1578 home_replace(NULL, buf->b_ffname, IObuff, IOSIZE, TRUE);
1579 fprintf(fp_out, "\n> ");
1580 viminfo_writestring(fp_out, IObuff);
1581 write_one_mark(fp_out, '"', &buf->b_last_cursor);
1582 write_one_mark(fp_out, '^', &buf->b_last_insert);
1583 write_one_mark(fp_out, '.', &buf->b_last_change);
1584#ifdef FEAT_JUMPLIST
1585 /* changelist positions are stored oldest first */
1586 for (i = 0; i < buf->b_changelistlen; ++i)
1587 write_one_mark(fp_out, '+', &buf->b_changelist[i]);
1588#endif
1589 for (i = 0; i < NMARKS; i++)
1590 write_one_mark(fp_out, 'a' + i, &buf->b_namedm[i]);
1591 count++;
1592 }
1593 }
1594 }
1595
1596 return count;
1597}
1598
1599 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001600write_one_mark(FILE *fp_out, int c, pos_T *pos)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001601{
1602 if (pos->lnum != 0)
1603 fprintf(fp_out, "\t%c\t%ld\t%d\n", c, (long)pos->lnum, (int)pos->col);
1604}
1605
1606/*
1607 * Handle marks in the viminfo file:
Bram Moolenaard812df62008-11-09 12:46:09 +00001608 * fp_out != NULL: copy marks for buffers not in buffer list
1609 * fp_out == NULL && (flags & VIF_WANT_MARKS): read marks for curbuf only
1610 * fp_out == NULL && (flags & VIF_GET_OLDFILES | VIF_FORCEIT): fill v:oldfiles
Bram Moolenaar071d4272004-06-13 20:20:40 +00001611 */
1612 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001613copy_viminfo_marks(
1614 vir_T *virp,
1615 FILE *fp_out,
1616 int count,
1617 int eof,
1618 int flags)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001619{
1620 char_u *line = virp->vir_line;
1621 buf_T *buf;
1622 int num_marked_files;
1623 int load_marks;
1624 int copy_marks_out;
1625 char_u *str;
1626 int i;
1627 char_u *p;
1628 char_u *name_buf;
1629 pos_T pos;
Bram Moolenaard812df62008-11-09 12:46:09 +00001630#ifdef FEAT_EVAL
1631 list_T *list = NULL;
1632#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001633
1634 if ((name_buf = alloc(LSIZE)) == NULL)
1635 return;
1636 *name_buf = NUL;
Bram Moolenaard812df62008-11-09 12:46:09 +00001637
1638#ifdef FEAT_EVAL
1639 if (fp_out == NULL && (flags & (VIF_GET_OLDFILES | VIF_FORCEIT)))
1640 {
1641 list = list_alloc();
1642 if (list != NULL)
1643 set_vim_var_list(VV_OLDFILES, list);
1644 }
1645#endif
1646
Bram Moolenaar071d4272004-06-13 20:20:40 +00001647 num_marked_files = get_viminfo_parameter('\'');
1648 while (!eof && (count < num_marked_files || fp_out == NULL))
1649 {
1650 if (line[0] != '>')
1651 {
1652 if (line[0] != '\n' && line[0] != '\r' && line[0] != '#')
1653 {
1654 if (viminfo_error("E576: ", _("Missing '>'"), line))
1655 break; /* too many errors, return now */
1656 }
1657 eof = vim_fgets(line, LSIZE, virp->vir_fd);
1658 continue; /* Skip this dud line */
1659 }
1660
1661 /*
1662 * Handle long line and translate escaped characters.
1663 * Find file name, set str to start.
1664 * Ignore leading and trailing white space.
1665 */
1666 str = skipwhite(line + 1);
1667 str = viminfo_readstring(virp, (int)(str - virp->vir_line), FALSE);
1668 if (str == NULL)
1669 continue;
1670 p = str + STRLEN(str);
1671 while (p != str && (*p == NUL || vim_isspace(*p)))
1672 p--;
1673 if (*p)
1674 p++;
1675 *p = NUL;
1676
Bram Moolenaard812df62008-11-09 12:46:09 +00001677#ifdef FEAT_EVAL
1678 if (list != NULL)
1679 list_append_string(list, str, -1);
1680#endif
1681
Bram Moolenaar071d4272004-06-13 20:20:40 +00001682 /*
1683 * If fp_out == NULL, load marks for current buffer.
1684 * If fp_out != NULL, copy marks for buffers not in buflist.
1685 */
1686 load_marks = copy_marks_out = FALSE;
1687 if (fp_out == NULL)
1688 {
Bram Moolenaard812df62008-11-09 12:46:09 +00001689 if ((flags & VIF_WANT_MARKS) && curbuf->b_ffname != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001690 {
1691 if (*name_buf == NUL) /* only need to do this once */
1692 home_replace(NULL, curbuf->b_ffname, name_buf, LSIZE, TRUE);
1693 if (fnamecmp(str, name_buf) == 0)
1694 load_marks = TRUE;
1695 }
1696 }
1697 else /* fp_out != NULL */
1698 {
1699 /* This is slow if there are many buffers!! */
1700 for (buf = firstbuf; buf != NULL; buf = buf->b_next)
1701 if (buf->b_ffname != NULL)
1702 {
1703 home_replace(NULL, buf->b_ffname, name_buf, LSIZE, TRUE);
1704 if (fnamecmp(str, name_buf) == 0)
1705 break;
1706 }
1707
1708 /*
1709 * copy marks if the buffer has not been loaded
1710 */
1711 if (buf == NULL || !buf->b_marks_read)
1712 {
1713 copy_marks_out = TRUE;
1714 fputs("\n> ", fp_out);
1715 viminfo_writestring(fp_out, str);
1716 count++;
1717 }
1718 }
1719 vim_free(str);
1720
1721#ifdef FEAT_VIRTUALEDIT
1722 pos.coladd = 0;
1723#endif
1724 while (!(eof = viminfo_readline(virp)) && line[0] == TAB)
1725 {
1726 if (load_marks)
1727 {
1728 if (line[1] != NUL)
1729 {
Bram Moolenaare698add2011-02-25 15:11:22 +01001730 unsigned u;
1731
1732 sscanf((char *)line + 2, "%ld %u", &pos.lnum, &u);
1733 pos.col = u;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001734 switch (line[1])
1735 {
1736 case '"': curbuf->b_last_cursor = pos; break;
1737 case '^': curbuf->b_last_insert = pos; break;
1738 case '.': curbuf->b_last_change = pos; break;
1739 case '+':
1740#ifdef FEAT_JUMPLIST
1741 /* changelist positions are stored oldest
1742 * first */
1743 if (curbuf->b_changelistlen == JUMPLISTSIZE)
1744 /* list is full, remove oldest entry */
1745 mch_memmove(curbuf->b_changelist,
1746 curbuf->b_changelist + 1,
1747 sizeof(pos_T) * (JUMPLISTSIZE - 1));
1748 else
1749 ++curbuf->b_changelistlen;
1750 curbuf->b_changelist[
1751 curbuf->b_changelistlen - 1] = pos;
1752#endif
1753 break;
1754 default: if ((i = line[1] - 'a') >= 0 && i < NMARKS)
1755 curbuf->b_namedm[i] = pos;
1756 }
1757 }
1758 }
1759 else if (copy_marks_out)
1760 fputs((char *)line, fp_out);
1761 }
1762 if (load_marks)
1763 {
1764#ifdef FEAT_JUMPLIST
1765 win_T *wp;
1766
1767 FOR_ALL_WINDOWS(wp)
1768 {
1769 if (wp->w_buffer == curbuf)
1770 wp->w_changelistidx = curbuf->b_changelistlen;
1771 }
1772#endif
1773 break;
1774 }
1775 }
1776 vim_free(name_buf);
1777}
1778#endif /* FEAT_VIMINFO */