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(LM(Invalid_mark_letter_X), &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(LM(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, NULL);
189 if (pos == NULL_POSITION)
190 return (NULL);
191 cmark(m, curr_ifile, pos, sc_height-1);
192 break;
193 case '.': case ':':
194 /*
195 * Top line on screen.
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 * Bottom line on screen.
204 */
205 m = &sm;
206 get_scrpos(&m->m_scrpos, BOTTOM);
207 cmark(m, curr_ifile, m->m_scrpos.pos, m->m_scrpos.ln);
208 break;
209 case '\'':
210 /*
211 * The "last mark".
212 */
213 m = &marks[LASTMARK];
214 break;
215 default:
216 /*
217 * Must be a user-defined mark.
218 */
219 m = getumark(c);
220 if (m == NULL)
221 break;
222 if (!mark_is_set(m))
223 {
224 error(LM(Mark_not_set), NULL_PARG);
225 return (NULL);
226 }
227 break;
228 }
229 return (m);
230 }
231
232 /*
233 * Is a mark letter invalid?
234 */
badmark(char c)235 public lbool badmark(char c)
236 {
237 return (getmark(c) == NULL);
238 }
239
240 /*
241 * Set a user-defined mark.
242 */
setmark(char c,int where,LINENUM linenum)243 public void setmark(char c, int where, LINENUM linenum)
244 {
245 struct mark *m;
246 struct scrpos scrpos;
247 POSITION pos;
248
249 m = getumark(c);
250 if (m == NULL)
251 return;
252
253 if (linenum == 0)
254 {
255 get_scrpos(&scrpos, where);
256 if (scrpos.pos == NULL_POSITION)
257 {
258 lbell();
259 return;
260 }
261 } else
262 {
263 pos = find_pos(linenum);
264 if (pos == NULL_POSITION)
265 {
266 PARG parg;
267 parg.p_linenum = linenum;
268 error(LM(Cannot_find_line_number_X), &parg);
269 return;
270 }
271 get_scrpos_pos(&scrpos, where, pos);
272 }
273 cmark(m, curr_ifile, scrpos.pos, scrpos.ln);
274 marks_modified = TRUE;
275 if (perma_marks && autosave_action('m'))
276 save_cmdhist();
277 }
278
279 /*
280 * Clear a user-defined mark.
281 */
clrmark(char c)282 public void clrmark(char c)
283 {
284 struct mark *m;
285
286 m = getumark(c);
287 if (m == NULL)
288 return;
289 if (m->m_scrpos.pos == NULL_POSITION)
290 {
291 lbell();
292 return;
293 }
294 mark_clear(m);
295 #if CMD_HISTORY
296 /* Also clear in file_marks, so save_marks doesn't save it to history file. */
297 m = &file_marks[mark_index(c)];
298 mark_clear(m);
299 #endif
300 marks_modified = TRUE;
301 if (perma_marks && autosave_action('m'))
302 save_cmdhist();
303 }
304
305 /*
306 * Set lmark (the mark named by the apostrophe).
307 */
lastmark(void)308 public void lastmark(void)
309 {
310 struct scrpos scrpos;
311
312 if (ch_getflags() & CH_HELPFILE)
313 return;
314 get_scrpos(&scrpos, TOP);
315 if (scrpos.pos == NULL_POSITION)
316 return;
317 cmark(&marks[LASTMARK], curr_ifile, scrpos.pos, scrpos.ln);
318 marks_modified = TRUE;
319 }
320
321 /*
322 * Go to a mark.
323 */
gomark(char c,int sline)324 public void gomark(char c, int sline)
325 {
326 struct mark *m;
327 struct scrpos scrpos;
328
329 m = getmark(c);
330 if (m == NULL)
331 return;
332
333 /*
334 * If we're trying to go to the lastmark and
335 * it has not been set to anything yet,
336 * set it to the beginning of the current file.
337 * {{ Couldn't we instead set marks[LASTMARK] in edit()? }}
338 */
339 if (m == &marks[LASTMARK] && !mark_is_set(m))
340 cmark(m, curr_ifile, ch_zero(), jump_sline);
341
342 mark_get_ifile(m);
343
344 /* Save scrpos; if it's LASTMARK it could change in edit_ifile. */
345 scrpos = m->m_scrpos;
346 if (m->m_ifile != curr_ifile)
347 {
348 /*
349 * Not in the current file; edit the correct file.
350 */
351 if (edit_ifile(m->m_ifile))
352 return;
353 }
354 if (sline == 0)
355 sline = m->m_scrpos.ln;
356 jump_loc(scrpos.pos, sline);
357 }
358
359 /*
360 * Return the position associated with a given mark letter.
361 *
362 * We don't return which screen line the position
363 * is associated with, but this doesn't matter much,
364 * because it's always the first non-blank line on the screen.
365 */
markpos(char c)366 public POSITION markpos(char c)
367 {
368 struct mark *m;
369
370 m = getmark(c);
371 if (m == NULL)
372 return (NULL_POSITION);
373
374 if (m->m_ifile != curr_ifile)
375 {
376 error(LM(Mark_not_in_current_file), NULL_PARG);
377 return (NULL_POSITION);
378 }
379 return (m->m_scrpos.pos);
380 }
381
382 /*
383 * Return the mark associated with a given position, if any.
384 */
posmark(POSITION pos)385 public char posmark(POSITION pos)
386 {
387 unsigned char i;
388
389 /* Only user marks */
390 for (i = 0; i < NUMARKS; i++)
391 {
392 struct mark *m = &marks[i];
393 if (m->m_ifile == curr_ifile && m->m_scrpos.pos == pos)
394 return mark_letter(i);
395 }
396 return '\0';
397 }
398
399 /*
400 * Clear the marks associated with a specified ifile.
401 */
unmark(IFILE ifile)402 public void unmark(IFILE ifile)
403 {
404 int i;
405
406 for (i = 0; i < NMARKS; i++)
407 if (marks[i].m_ifile == ifile)
408 mark_clear(&marks[i]);
409 }
410
411 /*
412 * Check if any marks refer to a specified ifile vi m_filename
413 * rather than m_ifile.
414 */
mark_check_ifile(IFILE ifile)415 public void mark_check_ifile(IFILE ifile)
416 {
417 int i;
418 constant char *filename = get_real_filename(ifile);
419
420 for (i = 0; i < NMARKS; i++)
421 {
422 struct mark *m = &marks[i];
423 char *mark_filename = m->m_filename;
424 if (mark_filename != NULL)
425 {
426 mark_filename = lrealpath(mark_filename);
427 if (strcmp(filename, mark_filename) == 0)
428 mark_set_ifile(m, ifile);
429 free(mark_filename);
430 }
431 }
432 }
433
434 #if CMD_HISTORY
435
436 /*
437 * Initialize a mark struct, including the filename.
438 */
cmarkf(struct mark * m,IFILE ifile,POSITION pos,int ln,constant char * filename)439 static void cmarkf(struct mark *m, IFILE ifile, POSITION pos, int ln, constant char *filename)
440 {
441 cmark(m, ifile, pos, ln);
442 m->m_filename = (filename == NULL) ? NULL : save(filename);
443 }
444
445 /*
446 * Save marks to history file.
447 */
save_marks(FILE * fout,constant char * hdr)448 public void save_marks(FILE *fout, constant char *hdr)
449 {
450 int i;
451
452 if (perma_marks)
453 {
454 /* Copy active marks to file_marks. */
455 for (i = 0; i < NMARKS; i++)
456 {
457 struct mark *m = &marks[i];
458 if (mark_is_set(m))
459 cmarkf(&file_marks[i], m->m_ifile, m->m_scrpos.pos, m->m_scrpos.ln, m->m_filename);
460 }
461 }
462
463 /*
464 * Save file_marks to the history file.
465 * These may have been read from the history file at startup
466 * (in restore_mark), or copied from the active marks above.
467 */
468 fprintf(fout, "%s\n", hdr);
469 for (i = 0; i < NMARKS; i++)
470 {
471 constant char *filename;
472 struct mark *m = &file_marks[i];
473 char pos_str[INT_STRLEN_BOUND(m->m_scrpos.pos) + 2];
474 if (!mark_is_set(m))
475 continue;
476 postoa(m->m_scrpos.pos, pos_str, 10);
477 filename = m->m_filename;
478 if (filename == NULL)
479 filename = get_real_filename(m->m_ifile);
480 if (strcmp(filename, "-") != 0)
481 fprintf(fout, "m %c %d %s %s\n",
482 mark_letter(i), m->m_scrpos.ln, pos_str, filename);
483 }
484 }
485
486 /*
487 * Restore one mark from the history file.
488 */
restore_mark(constant char * line)489 public void restore_mark(constant char *line)
490 {
491 int index;
492 int ln;
493 POSITION pos;
494
495 if (*line++ != 'm')
496 return;
497 line = skipspc(line);
498 index = mark_index(*line++);
499 if (index < 0)
500 return;
501 line = skipspc(line);
502 ln = lstrtoic(line, &line, 10);
503 if (ln < 0)
504 return;
505 line = skipspc(line);
506 pos = lstrtoposc(line, &line, 10);
507 if (pos < 0)
508 return;
509 line = skipspc(line);
510 /* Save in both active marks table and file_marks table. */
511 cmarkf(&marks[index], NULL_IFILE, pos, ln, line);
512 cmarkf(&file_marks[index], NULL_IFILE, pos, ln, line);
513 }
514
515 #endif /* CMD_HISTORY */
516