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