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