1 /*
2 * Copyright (C) 1984-2026 Mark Nudelman
3 *
4 * You may distribute under the terms of either the GNU General Public
5 * License or the Less License, as specified in the README file.
6 *
7 * For more information, see the README file.
8 */
9
10
11 #include "less.h"
12 #include "position.h"
13
14 extern IFILE curr_ifile;
15 extern int sc_height;
16 extern int jump_sline;
17 extern int perma_marks;
18
19 /*
20 * A mark is an ifile (input file) plus a position within the file.
21 */
22 struct mark
23 {
24 /*
25 * Normally m_ifile != IFILE_NULL and m_filename == NULL.
26 * For restored marks we set m_filename instead of m_ifile
27 * because we don't want to create an ifile until the
28 * user explicitly requests the file (by name or mark).
29 */
30 IFILE m_ifile; /* Input file being marked */
31 char *m_filename; /* Name of the input file */
32 struct scrpos m_scrpos; /* Position of the mark */
33 };
34
35 /*
36 * The table of marks.
37 * Each mark is identified by a lowercase or uppercase letter.
38 * The final one is lmark, for the "last mark"; addressed by the apostrophe.
39 */
40 #define NMARKS ((2*26)+2) /* a-z, A-Z, mousemark, lastmark */
41 #define NUMARKS (NMARKS-1) /* user marks (not lastmark) */
42 #define LASTMARK (NMARKS-1)
43 #define MOUSEMARK (LASTMARK-1)
44 static struct mark marks[NMARKS];
45 public lbool marks_modified = FALSE;
46 #if CMD_HISTORY
47 static struct mark file_marks[NMARKS];
48 #endif
49
50 /*
51 * Initialize a mark struct.
52 */
cmark(struct mark * m,IFILE ifile,POSITION pos,int ln)53 static void cmark(struct mark *m, IFILE ifile, POSITION pos, int ln)
54 {
55 m->m_ifile = ifile;
56 m->m_scrpos.pos = pos;
57 m->m_scrpos.ln = ln;
58 if (m->m_filename != NULL)
59 /* Normally should not happen but a corrupt lesshst file can do it. */
60 free(m->m_filename);
61 m->m_filename = NULL;
62 }
63
64 /*
65 * Initialize the mark table to show no marks are set.
66 */
init_mark(void)67 public void init_mark(void)
68 {
69 int i;
70
71 for (i = 0; i < NMARKS; i++)
72 {
73 cmark(&marks[i], NULL_IFILE, NULL_POSITION, -1);
74 #if CMD_HISTORY
75 cmark(&file_marks[i], NULL_IFILE, NULL_POSITION, -1);
76 #endif
77 }
78 }
79
mark_clear(struct mark * m)80 static void mark_clear(struct mark *m)
81 {
82 m->m_scrpos.pos = NULL_POSITION;
83 }
84
mark_is_set(struct mark * m)85 static lbool mark_is_set(struct mark *m)
86 {
87 return m->m_scrpos.pos != NULL_POSITION;
88 }
89
90 /*
91 * Set m_ifile and clear m_filename.
92 */
mark_set_ifile(struct mark * m,IFILE ifile)93 static void mark_set_ifile(struct mark *m, IFILE ifile)
94 {
95 m->m_ifile = ifile;
96 /* With m_ifile set, m_filename is no longer needed. */
97 free(m->m_filename);
98 m->m_filename = NULL;
99 }
100
101 /*
102 * Populate the m_ifile member of a mark struct from m_filename.
103 */
mark_get_ifile(struct mark * m)104 static void mark_get_ifile(struct mark *m)
105 {
106 if (m->m_ifile != NULL_IFILE)
107 return; /* m_ifile is already set */
108 mark_set_ifile(m, get_ifile(m->m_filename, prev_ifile(NULL_IFILE)));
109 }
110
111 /*
112 * Return the letter associated with a mark index.
113 */
mark_letter(int index)114 static char mark_letter(int index)
115 {
116 switch (index) {
117 case LASTMARK: return '\'';
118 case MOUSEMARK: return '#';
119 default: return (char) ((index < 26) ? 'a'+index : 'A'+index-26);
120 }
121 }
122
123 /*
124 * Return the mark table index identified by a character.
125 */
mark_index(char c)126 static int mark_index(char c)
127 {
128 if (c >= 'a' && c <= 'z')
129 return c-'a';
130 if (c >= 'A' && c <= 'Z')
131 return c-'A'+26;
132 if (c == '\'')
133 return LASTMARK;
134 if (c == '#')
135 return MOUSEMARK;
136 return -1;
137 }
138
139 /*
140 * Return the user mark struct identified by a character.
141 */
getumark(char c)142 static struct mark * getumark(char c)
143 {
144 int index = mark_index(c);
145 if (index < 0 || index >= NUMARKS)
146 {
147 PARG parg;
148 parg.p_char = (char) c;
149 error("Invalid mark letter %c", &parg);
150 return NULL;
151 }
152 return &marks[index];
153 }
154
155 /*
156 * Get the mark structure identified by a character.
157 * The mark struct may either be in the mark table (user mark)
158 * or may be constructed on the fly for certain characters like ^, $.
159 */
getmark(char c)160 static struct mark * getmark(char c)
161 {
162 struct mark *m;
163 static struct mark sm;
164 POSITION pos;
165
166 switch (c)
167 {
168 case '^':
169 /*
170 * Beginning of the current file.
171 */
172 m = &sm;
173 cmark(m, curr_ifile, ch_zero(), 0);
174 break;
175 case '$':
176 /*
177 * End of the current file.
178 */
179 if (ch_end_seek())
180 {
181 error("Cannot seek to end of file", NULL_PARG);
182 return (NULL);
183 }
184 m = &sm;
185 pos = ch_tell();
186 if (pos == NULL_POSITION)
187 return (NULL);
188 pos = back_line(pos, NULL);
189 if (pos == NULL_POSITION)
190 return (NULL);
191 cmark(m, curr_ifile, pos, sc_height-1);
192 break;
193 case '.':
194 /*
195 * Current position in the current file.
196 */
197 m = &sm;
198 get_scrpos(&m->m_scrpos, TOP);
199 cmark(m, curr_ifile, m->m_scrpos.pos, m->m_scrpos.ln);
200 break;
201 case '\'':
202 /*
203 * The "last mark".
204 */
205 m = &marks[LASTMARK];
206 break;
207 default:
208 /*
209 * Must be a user-defined mark.
210 */
211 m = getumark(c);
212 if (m == NULL)
213 break;
214 if (!mark_is_set(m))
215 {
216 error("Mark not set", NULL_PARG);
217 return (NULL);
218 }
219 break;
220 }
221 return (m);
222 }
223
224 /*
225 * Is a mark letter invalid?
226 */
badmark(char c)227 public lbool badmark(char c)
228 {
229 return (getmark(c) == NULL);
230 }
231
232 /*
233 * Set a user-defined mark.
234 */
setmark(char c,int where,LINENUM linenum)235 public void setmark(char c, int where, LINENUM linenum)
236 {
237 struct mark *m;
238 struct scrpos scrpos;
239 POSITION pos;
240
241 m = getumark(c);
242 if (m == NULL)
243 return;
244
245 if (linenum == 0)
246 {
247 get_scrpos(&scrpos, where);
248 if (scrpos.pos == NULL_POSITION)
249 {
250 lbell();
251 return;
252 }
253 } else
254 {
255 pos = find_pos(linenum);
256 if (pos == NULL_POSITION)
257 {
258 PARG parg;
259 parg.p_linenum = linenum;
260 error("Cannot find line number %n", &parg);
261 return;
262 }
263 get_scrpos_pos(&scrpos, where, pos);
264 }
265 cmark(m, curr_ifile, scrpos.pos, scrpos.ln);
266 marks_modified = TRUE;
267 if (perma_marks && autosave_action('m'))
268 save_cmdhist();
269 }
270
271 /*
272 * Clear a user-defined mark.
273 */
clrmark(char c)274 public void clrmark(char c)
275 {
276 struct mark *m;
277
278 m = getumark(c);
279 if (m == NULL)
280 return;
281 if (m->m_scrpos.pos == NULL_POSITION)
282 {
283 lbell();
284 return;
285 }
286 mark_clear(m);
287 #if CMD_HISTORY
288 /* Also clear in file_marks, so save_marks doesn't save it to history file. */
289 m = &file_marks[mark_index(c)];
290 mark_clear(m);
291 #endif
292 marks_modified = TRUE;
293 if (perma_marks && autosave_action('m'))
294 save_cmdhist();
295 }
296
297 /*
298 * Set lmark (the mark named by the apostrophe).
299 */
lastmark(void)300 public void lastmark(void)
301 {
302 struct scrpos scrpos;
303
304 if (ch_getflags() & CH_HELPFILE)
305 return;
306 get_scrpos(&scrpos, TOP);
307 if (scrpos.pos == NULL_POSITION)
308 return;
309 cmark(&marks[LASTMARK], curr_ifile, scrpos.pos, scrpos.ln);
310 marks_modified = TRUE;
311 }
312
313 /*
314 * Go to a mark.
315 */
gomark(char c,int sline)316 public void gomark(char c, int sline)
317 {
318 struct mark *m;
319 struct scrpos scrpos;
320
321 m = getmark(c);
322 if (m == NULL)
323 return;
324 if (sline != 0)
325 m->m_scrpos.ln = sline;
326
327 /*
328 * If we're trying to go to the lastmark and
329 * it has not been set to anything yet,
330 * set it to the beginning of the current file.
331 * {{ Couldn't we instead set marks[LASTMARK] in edit()? }}
332 */
333 if (m == &marks[LASTMARK] && !mark_is_set(m))
334 cmark(m, curr_ifile, ch_zero(), jump_sline);
335
336 mark_get_ifile(m);
337
338 /* Save scrpos; if it's LASTMARK it could change in edit_ifile. */
339 scrpos = m->m_scrpos;
340 if (m->m_ifile != curr_ifile)
341 {
342 /*
343 * Not in the current file; edit the correct file.
344 */
345 if (edit_ifile(m->m_ifile))
346 return;
347 }
348
349 jump_loc(scrpos.pos, scrpos.ln);
350 }
351
352 /*
353 * Return the position associated with a given mark letter.
354 *
355 * We don't return which screen line the position
356 * is associated with, but this doesn't matter much,
357 * because it's always the first non-blank line on the screen.
358 */
markpos(char c)359 public POSITION markpos(char c)
360 {
361 struct mark *m;
362
363 m = getmark(c);
364 if (m == NULL)
365 return (NULL_POSITION);
366
367 if (m->m_ifile != curr_ifile)
368 {
369 error("Mark not in current file", NULL_PARG);
370 return (NULL_POSITION);
371 }
372 return (m->m_scrpos.pos);
373 }
374
375 /*
376 * Return the mark associated with a given position, if any.
377 */
posmark(POSITION pos)378 public char posmark(POSITION pos)
379 {
380 unsigned char i;
381
382 /* Only user marks */
383 for (i = 0; i < NUMARKS; i++)
384 {
385 struct mark *m = &marks[i];
386 if (m->m_ifile == curr_ifile && m->m_scrpos.pos == pos)
387 return mark_letter(i);
388 }
389 return '\0';
390 }
391
392 /*
393 * Clear the marks associated with a specified ifile.
394 */
unmark(IFILE ifile)395 public void unmark(IFILE ifile)
396 {
397 int i;
398
399 for (i = 0; i < NMARKS; i++)
400 if (marks[i].m_ifile == ifile)
401 mark_clear(&marks[i]);
402 }
403
404 /*
405 * Check if any marks refer to a specified ifile vi m_filename
406 * rather than m_ifile.
407 */
mark_check_ifile(IFILE ifile)408 public void mark_check_ifile(IFILE ifile)
409 {
410 int i;
411 constant char *filename = get_real_filename(ifile);
412
413 for (i = 0; i < NMARKS; i++)
414 {
415 struct mark *m = &marks[i];
416 char *mark_filename = m->m_filename;
417 if (mark_filename != NULL)
418 {
419 mark_filename = lrealpath(mark_filename);
420 if (strcmp(filename, mark_filename) == 0)
421 mark_set_ifile(m, ifile);
422 free(mark_filename);
423 }
424 }
425 }
426
427 #if CMD_HISTORY
428
429 /*
430 * Initialize a mark struct, including the filename.
431 */
cmarkf(struct mark * m,IFILE ifile,POSITION pos,int ln,constant char * filename)432 static void cmarkf(struct mark *m, IFILE ifile, POSITION pos, int ln, constant char *filename)
433 {
434 cmark(m, ifile, pos, ln);
435 m->m_filename = (filename == NULL) ? NULL : save(filename);
436 }
437
438 /*
439 * Save marks to history file.
440 */
save_marks(FILE * fout,constant char * hdr)441 public void save_marks(FILE *fout, constant char *hdr)
442 {
443 int i;
444
445 if (perma_marks)
446 {
447 /* Copy active marks to file_marks. */
448 for (i = 0; i < NMARKS; i++)
449 {
450 struct mark *m = &marks[i];
451 if (mark_is_set(m))
452 cmarkf(&file_marks[i], m->m_ifile, m->m_scrpos.pos, m->m_scrpos.ln, m->m_filename);
453 }
454 }
455
456 /*
457 * Save file_marks to the history file.
458 * These may have been read from the history file at startup
459 * (in restore_mark), or copied from the active marks above.
460 */
461 fprintf(fout, "%s\n", hdr);
462 for (i = 0; i < NMARKS; i++)
463 {
464 constant char *filename;
465 struct mark *m = &file_marks[i];
466 char pos_str[INT_STRLEN_BOUND(m->m_scrpos.pos) + 2];
467 if (!mark_is_set(m))
468 continue;
469 postoa(m->m_scrpos.pos, pos_str, 10);
470 filename = m->m_filename;
471 if (filename == NULL)
472 filename = get_real_filename(m->m_ifile);
473 if (strcmp(filename, "-") != 0)
474 fprintf(fout, "m %c %d %s %s\n",
475 mark_letter(i), m->m_scrpos.ln, pos_str, filename);
476 }
477 }
478
479 /*
480 * Restore one mark from the history file.
481 */
restore_mark(constant char * line)482 public void restore_mark(constant char *line)
483 {
484 int index;
485 int ln;
486 POSITION pos;
487
488 #define skip_whitespace while (*line == ' ') line++
489 if (*line++ != 'm')
490 return;
491 skip_whitespace;
492 index = mark_index(*line++);
493 if (index < 0)
494 return;
495 skip_whitespace;
496 ln = lstrtoic(line, &line, 10);
497 if (ln < 0)
498 return;
499 skip_whitespace;
500 pos = lstrtoposc(line, &line, 10);
501 if (pos < 0)
502 return;
503 skip_whitespace;
504 /* Save in both active marks table and file_marks table. */
505 cmarkf(&marks[index], NULL_IFILE, pos, ln, line);
506 cmarkf(&file_marks[index], NULL_IFILE, pos, ln, line);
507 }
508
509 #endif /* CMD_HISTORY */
510