xref: /freebsd/contrib/less/mark.c (revision 4523eebc6c1828d4fd41d7f1ab943cf079043bc8)
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