xref: /freebsd/contrib/less/mark.c (revision af23369a6deaaeb612ab266eb88b8bb8d560c322)
1 /*
2  * Copyright (C) 1984-2022  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  */
52 	static void
53 cmark(m, ifile, pos, ln)
54 	struct mark *m;
55 	IFILE ifile;
56 	POSITION pos;
57 	int ln;
58 {
59 	m->m_ifile = ifile;
60 	m->m_scrpos.pos = pos;
61 	m->m_scrpos.ln = ln;
62 	if (m->m_filename != NULL)
63 		/* Normally should not happen but a corrupt lesshst file can do it. */
64 		free(m->m_filename);
65 	m->m_filename = NULL;
66 }
67 
68 /*
69  * Initialize the mark table to show no marks are set.
70  */
71 	public void
72 init_mark(VOID_PARAM)
73 {
74 	int i;
75 
76 	for (i = 0;  i < NMARKS;  i++)
77 	{
78 		char letter;
79 		switch (i) {
80 		case MOUSEMARK: letter = '#'; break;
81 		case LASTMARK: letter = '\''; break;
82 		default: letter = (i < 26) ? 'a'+i : 'A'+i-26; break;
83 		}
84 		marks[i].m_letter = letter;
85 		cmark(&marks[i], NULL_IFILE, NULL_POSITION, -1);
86 	}
87 }
88 
89 /*
90  * Set m_ifile and clear m_filename.
91  */
92 	static void
93 mark_set_ifile(m, ifile)
94 	struct mark *m;
95 	IFILE ifile;
96 {
97 	m->m_ifile = ifile;
98 	/* With m_ifile set, m_filename is no longer needed. */
99 	free(m->m_filename);
100 	m->m_filename = NULL;
101 }
102 
103 /*
104  * Populate the m_ifile member of a mark struct from m_filename.
105  */
106 	static void
107 mark_get_ifile(m)
108 	struct mark *m;
109 {
110 	if (m->m_ifile != NULL_IFILE)
111 		return; /* m_ifile is already set */
112 	mark_set_ifile(m, get_ifile(m->m_filename, prev_ifile(NULL_IFILE)));
113 }
114 
115 /*
116  * Return the user mark struct identified by a character.
117  */
118 	static struct mark *
119 getumark(c)
120 	int c;
121 {
122 	PARG parg;
123 	if (c >= 'a' && c <= 'z')
124 		return (&marks[c-'a']);
125 	if (c >= 'A' && c <= 'Z')
126 		return (&marks[c-'A'+26]);
127 	if (c == '\'')
128 		return (&marks[LASTMARK]);
129 	if (c == '#')
130 		return (&marks[MOUSEMARK]);
131 	parg.p_char = (char) c;
132 	error("Invalid mark letter %c", &parg);
133 	return (NULL);
134 }
135 
136 /*
137  * Get the mark structure identified by a character.
138  * The mark struct may either be in the mark table (user mark)
139  * or may be constructed on the fly for certain characters like ^, $.
140  */
141 	static struct mark *
142 getmark(c)
143 	int c;
144 {
145 	struct mark *m;
146 	static struct mark sm;
147 
148 	switch (c)
149 	{
150 	case '^':
151 		/*
152 		 * Beginning of the current file.
153 		 */
154 		m = &sm;
155 		cmark(m, curr_ifile, ch_zero(), 0);
156 		break;
157 	case '$':
158 		/*
159 		 * End of the current file.
160 		 */
161 		if (ch_end_seek())
162 		{
163 			error("Cannot seek to end of file", NULL_PARG);
164 			return (NULL);
165 		}
166 		m = &sm;
167 		cmark(m, curr_ifile, ch_tell(), sc_height);
168 		break;
169 	case '.':
170 		/*
171 		 * Current position in the current file.
172 		 */
173 		m = &sm;
174 		get_scrpos(&m->m_scrpos, TOP);
175 		cmark(m, curr_ifile, m->m_scrpos.pos, m->m_scrpos.ln);
176 		break;
177 	case '\'':
178 		/*
179 		 * The "last mark".
180 		 */
181 		m = &marks[LASTMARK];
182 		break;
183 	default:
184 		/*
185 		 * Must be a user-defined mark.
186 		 */
187 		m = getumark(c);
188 		if (m == NULL)
189 			break;
190 		if (m->m_scrpos.pos == NULL_POSITION)
191 		{
192 			error("Mark not set", NULL_PARG);
193 			return (NULL);
194 		}
195 		break;
196 	}
197 	return (m);
198 }
199 
200 /*
201  * Is a mark letter invalid?
202  */
203 	public int
204 badmark(c)
205 	int c;
206 {
207 	return (getmark(c) == NULL);
208 }
209 
210 /*
211  * Set a user-defined mark.
212  */
213 	public void
214 setmark(c, where)
215 	int c;
216 	int where;
217 {
218 	struct mark *m;
219 	struct scrpos scrpos;
220 
221 	m = getumark(c);
222 	if (m == NULL)
223 		return;
224 	get_scrpos(&scrpos, where);
225 	if (scrpos.pos == NULL_POSITION)
226 	{
227 		bell();
228 		return;
229 	}
230 	cmark(m, curr_ifile, scrpos.pos, scrpos.ln);
231 	marks_modified = 1;
232 }
233 
234 /*
235  * Clear a user-defined mark.
236  */
237 	public void
238 clrmark(c)
239 	int c;
240 {
241 	struct mark *m;
242 
243 	m = getumark(c);
244 	if (m == NULL)
245 		return;
246 	if (m->m_scrpos.pos == NULL_POSITION)
247 	{
248 		bell();
249 		return;
250 	}
251 	m->m_scrpos.pos = NULL_POSITION;
252 	marks_modified = 1;
253 }
254 
255 /*
256  * Set lmark (the mark named by the apostrophe).
257  */
258 	public void
259 lastmark(VOID_PARAM)
260 {
261 	struct scrpos scrpos;
262 
263 	if (ch_getflags() & CH_HELPFILE)
264 		return;
265 	get_scrpos(&scrpos, TOP);
266 	if (scrpos.pos == NULL_POSITION)
267 		return;
268 	cmark(&marks[LASTMARK], curr_ifile, scrpos.pos, scrpos.ln);
269 	marks_modified = 1;
270 }
271 
272 /*
273  * Go to a mark.
274  */
275 	public void
276 gomark(c)
277 	int c;
278 {
279 	struct mark *m;
280 	struct scrpos scrpos;
281 
282 	m = getmark(c);
283 	if (m == NULL)
284 		return;
285 
286 	/*
287 	 * If we're trying to go to the lastmark and
288 	 * it has not been set to anything yet,
289 	 * set it to the beginning of the current file.
290 	 * {{ Couldn't we instead set marks[LASTMARK] in edit()? }}
291 	 */
292 	if (m == &marks[LASTMARK] && m->m_scrpos.pos == NULL_POSITION)
293 		cmark(m, curr_ifile, ch_zero(), jump_sline);
294 
295 	mark_get_ifile(m);
296 
297 	/* Save scrpos; if it's LASTMARK it could change in edit_ifile. */
298 	scrpos = m->m_scrpos;
299 	if (m->m_ifile != curr_ifile)
300 	{
301 		/*
302 		 * Not in the current file; edit the correct file.
303 		 */
304 		if (edit_ifile(m->m_ifile))
305 			return;
306 	}
307 
308 	jump_loc(scrpos.pos, scrpos.ln);
309 }
310 
311 /*
312  * Return the position associated with a given mark letter.
313  *
314  * We don't return which screen line the position
315  * is associated with, but this doesn't matter much,
316  * because it's always the first non-blank line on the screen.
317  */
318 	public POSITION
319 markpos(c)
320 	int c;
321 {
322 	struct mark *m;
323 
324 	m = getmark(c);
325 	if (m == NULL)
326 		return (NULL_POSITION);
327 
328 	if (m->m_ifile != curr_ifile)
329 	{
330 		error("Mark not in current file", NULL_PARG);
331 		return (NULL_POSITION);
332 	}
333 	return (m->m_scrpos.pos);
334 }
335 
336 /*
337  * Return the mark associated with a given position, if any.
338  */
339 	public char
340 posmark(pos)
341 	POSITION pos;
342 {
343 	int i;
344 
345 	/* Only user marks */
346 	for (i = 0;  i < NUMARKS;  i++)
347 	{
348 		if (marks[i].m_ifile == curr_ifile && marks[i].m_scrpos.pos == pos)
349 		{
350 			if (i < 26) return 'a' + i;
351 			if (i < 26*2) return 'A' + (i - 26);
352 			return '#';
353 		}
354 	}
355 	return 0;
356 }
357 
358 /*
359  * Clear the marks associated with a specified ifile.
360  */
361 	public void
362 unmark(ifile)
363 	IFILE ifile;
364 {
365 	int i;
366 
367 	for (i = 0;  i < NMARKS;  i++)
368 		if (marks[i].m_ifile == ifile)
369 			marks[i].m_scrpos.pos = NULL_POSITION;
370 }
371 
372 /*
373  * Check if any marks refer to a specified ifile vi m_filename
374  * rather than m_ifile.
375  */
376 	public void
377 mark_check_ifile(ifile)
378 	IFILE ifile;
379 {
380 	int i;
381 	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  * Save marks to history file.
401  */
402 	public void
403 save_marks(fout, hdr)
404 	FILE *fout;
405 	char *hdr;
406 {
407 	int i;
408 
409 	if (!perma_marks)
410 		return;
411 
412 	fprintf(fout, "%s\n", hdr);
413 	for (i = 0;  i < NMARKS;  i++)
414 	{
415 		char *filename;
416 		struct mark *m = &marks[i];
417 		char pos_str[INT_STRLEN_BOUND(m->m_scrpos.pos) + 2];
418 		if (m->m_scrpos.pos == NULL_POSITION)
419 			continue;
420 		postoa(m->m_scrpos.pos, pos_str);
421 		filename = m->m_filename;
422 		if (filename == NULL)
423 			filename = get_real_filename(m->m_ifile);
424 		if (strcmp(filename, "-") != 0)
425 			fprintf(fout, "m %c %d %s %s\n",
426 				m->m_letter, m->m_scrpos.ln, pos_str, filename);
427 	}
428 }
429 
430 /*
431  * Restore one mark from the history file.
432  */
433 	public void
434 restore_mark(line)
435 	char *line;
436 {
437 	struct mark *m;
438 	int ln;
439 	POSITION pos;
440 
441 #define skip_whitespace while (*line == ' ') line++
442 	if (*line++ != 'm')
443 		return;
444 	skip_whitespace;
445 	m = getumark(*line++);
446 	if (m == NULL)
447 		return;
448 	skip_whitespace;
449 	ln = lstrtoi(line, &line);
450 	if (ln < 1)
451 		ln = 1;
452 	if (ln > sc_height)
453 		ln = sc_height;
454 	skip_whitespace;
455 	pos = lstrtopos(line, &line);
456 	skip_whitespace;
457 	cmark(m, NULL_IFILE, pos, ln);
458 	m->m_filename = save(line);
459 }
460 
461 #endif /* CMD_HISTORY */
462