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