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