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