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