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