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