xref: /netbsd-src/external/bsd/less/dist/mark.c (revision 838f5788460f0f133b15d706e644d692a9d4d6ec)
1 /*	$NetBSD: mark.c,v 1.4 2023/10/06 05:49:49 simonb Exp $	*/
2 
3 /*
4  * Copyright (C) 1984-2023  Mark Nudelman
5  *
6  * You may distribute under the terms of either the GNU General Public
7  * License or the Less License, as specified in the README file.
8  *
9  * For more information, see the README file.
10  */
11 
12 
13 #include "less.h"
14 #include "position.h"
15 
16 extern IFILE curr_ifile;
17 extern int sc_height;
18 extern int jump_sline;
19 extern int perma_marks;
20 
21 /*
22  * A mark is an ifile (input file) plus a position within the file.
23  */
24 struct mark
25 {
26 	/*
27 	 * Normally m_ifile != IFILE_NULL and m_filename == NULL.
28 	 * For restored marks we set m_filename instead of m_ifile
29 	 * because we don't want to create an ifile until the
30 	 * user explicitly requests the file (by name or mark).
31 	 */
32 	char m_letter;           /* Associated character */
33 	IFILE m_ifile;           /* Input file being marked */
34 	char *m_filename;        /* Name of the input file */
35 	struct scrpos m_scrpos;  /* Position of the mark */
36 };
37 
38 /*
39  * The table of marks.
40  * Each mark is identified by a lowercase or uppercase letter.
41  * The final one is lmark, for the "last mark"; addressed by the apostrophe.
42  */
43 #define NMARKS          ((2*26)+2)      /* a-z, A-Z, mousemark, lastmark */
44 #define NUMARKS         ((2*26)+1)      /* user marks (not lastmark) */
45 #define MOUSEMARK       (NMARKS-2)
46 #define LASTMARK        (NMARKS-1)
47 static struct mark marks[NMARKS];
48 public int marks_modified = 0;
49 
50 
51 /*
52  * Initialize a mark struct.
53  */
cmark(struct mark * m,IFILE ifile,POSITION pos,int ln)54 static void cmark(struct mark *m, IFILE ifile, POSITION pos, int ln)
55 {
56 	m->m_ifile = ifile;
57 	m->m_scrpos.pos = pos;
58 	m->m_scrpos.ln = ln;
59 	if (m->m_filename != NULL)
60 		/* Normally should not happen but a corrupt lesshst file can do it. */
61 		free(m->m_filename);
62 	m->m_filename = NULL;
63 }
64 
65 /*
66  * Initialize the mark table to show no marks are set.
67  */
init_mark(void)68 public void init_mark(void)
69 {
70 	int i;
71 
72 	for (i = 0;  i < NMARKS;  i++)
73 	{
74 		char letter;
75 		switch (i) {
76 		case MOUSEMARK: letter = '#'; break;
77 		case LASTMARK: letter = '\''; break;
78 		default: letter = (i < 26) ? 'a'+i : 'A'+i-26; break;
79 		}
80 		marks[i].m_letter = letter;
81 		cmark(&marks[i], NULL_IFILE, NULL_POSITION, -1);
82 	}
83 }
84 
85 /*
86  * Set m_ifile and clear m_filename.
87  */
mark_set_ifile(struct mark * m,IFILE ifile)88 static void mark_set_ifile(struct mark *m, IFILE ifile)
89 {
90 	m->m_ifile = ifile;
91 	/* With m_ifile set, m_filename is no longer needed. */
92 	free(m->m_filename);
93 	m->m_filename = NULL;
94 }
95 
96 /*
97  * Populate the m_ifile member of a mark struct from m_filename.
98  */
mark_get_ifile(struct mark * m)99 static void mark_get_ifile(struct mark *m)
100 {
101 	if (m->m_ifile != NULL_IFILE)
102 		return; /* m_ifile is already set */
103 	mark_set_ifile(m, get_ifile(m->m_filename, prev_ifile(NULL_IFILE)));
104 }
105 
106 /*
107  * Return the user mark struct identified by a character.
108  */
getumark(LWCHAR c)109 static struct mark * getumark(LWCHAR c)
110 {
111 	PARG parg;
112 	if (c >= 'a' && c <= 'z')
113 		return (&marks[c-'a']);
114 	if (c >= 'A' && c <= 'Z')
115 		return (&marks[c-'A'+26]);
116 	if (c == '\'')
117 		return (&marks[LASTMARK]);
118 	if (c == '#')
119 		return (&marks[MOUSEMARK]);
120 	parg.p_char = (char) c;
121 	error("Invalid mark letter %c", &parg);
122 	return (NULL);
123 }
124 
125 /*
126  * Get the mark structure identified by a character.
127  * The mark struct may either be in the mark table (user mark)
128  * or may be constructed on the fly for certain characters like ^, $.
129  */
getmark(LWCHAR c)130 static struct mark * getmark(LWCHAR c)
131 {
132 	struct mark *m;
133 	static struct mark sm;
134 
135 	switch (c)
136 	{
137 	case '^':
138 		/*
139 		 * Beginning of the current file.
140 		 */
141 		m = &sm;
142 		cmark(m, curr_ifile, ch_zero(), 0);
143 		break;
144 	case '$':
145 		/*
146 		 * End of the current file.
147 		 */
148 		if (ch_end_seek())
149 		{
150 			error("Cannot seek to end of file", NULL_PARG);
151 			return (NULL);
152 		}
153 		m = &sm;
154 		cmark(m, curr_ifile, ch_tell(), sc_height);
155 		break;
156 	case '.':
157 		/*
158 		 * Current position in the current file.
159 		 */
160 		m = &sm;
161 		get_scrpos(&m->m_scrpos, TOP);
162 		cmark(m, curr_ifile, m->m_scrpos.pos, m->m_scrpos.ln);
163 		break;
164 	case '\'':
165 		/*
166 		 * The "last mark".
167 		 */
168 		m = &marks[LASTMARK];
169 		break;
170 	default:
171 		/*
172 		 * Must be a user-defined mark.
173 		 */
174 		m = getumark(c);
175 		if (m == NULL)
176 			break;
177 		if (m->m_scrpos.pos == NULL_POSITION)
178 		{
179 			error("Mark not set", NULL_PARG);
180 			return (NULL);
181 		}
182 		break;
183 	}
184 	return (m);
185 }
186 
187 /*
188  * Is a mark letter invalid?
189  */
badmark(LWCHAR c)190 public int badmark(LWCHAR c)
191 {
192 	return (getmark(c) == NULL);
193 }
194 
195 /*
196  * Set a user-defined mark.
197  */
setmark(LWCHAR c,int where)198 public void setmark(LWCHAR c, int where)
199 {
200 	struct mark *m;
201 	struct scrpos scrpos;
202 
203 	m = getumark(c);
204 	if (m == NULL)
205 		return;
206 	get_scrpos(&scrpos, where);
207 	if (scrpos.pos == NULL_POSITION)
208 	{
209 		bell();
210 		return;
211 	}
212 	cmark(m, curr_ifile, scrpos.pos, scrpos.ln);
213 	marks_modified = 1;
214 }
215 
216 /*
217  * Clear a user-defined mark.
218  */
clrmark(LWCHAR c)219 public void clrmark(LWCHAR c)
220 {
221 	struct mark *m;
222 
223 	m = getumark(c);
224 	if (m == NULL)
225 		return;
226 	if (m->m_scrpos.pos == NULL_POSITION)
227 	{
228 		bell();
229 		return;
230 	}
231 	m->m_scrpos.pos = NULL_POSITION;
232 	marks_modified = 1;
233 }
234 
235 /*
236  * Set lmark (the mark named by the apostrophe).
237  */
lastmark(void)238 public void lastmark(void)
239 {
240 	struct scrpos scrpos;
241 
242 	if (ch_getflags() & CH_HELPFILE)
243 		return;
244 	get_scrpos(&scrpos, TOP);
245 	if (scrpos.pos == NULL_POSITION)
246 		return;
247 	cmark(&marks[LASTMARK], curr_ifile, scrpos.pos, scrpos.ln);
248 	marks_modified = 1;
249 }
250 
251 /*
252  * Go to a mark.
253  */
gomark(LWCHAR c)254 public void gomark(LWCHAR c)
255 {
256 	struct mark *m;
257 	struct scrpos scrpos;
258 
259 	m = getmark(c);
260 	if (m == NULL)
261 		return;
262 
263 	/*
264 	 * If we're trying to go to the lastmark and
265 	 * it has not been set to anything yet,
266 	 * set it to the beginning of the current file.
267 	 * {{ Couldn't we instead set marks[LASTMARK] in edit()? }}
268 	 */
269 	if (m == &marks[LASTMARK] && m->m_scrpos.pos == NULL_POSITION)
270 		cmark(m, curr_ifile, ch_zero(), jump_sline);
271 
272 	mark_get_ifile(m);
273 
274 	/* Save scrpos; if it's LASTMARK it could change in edit_ifile. */
275 	scrpos = m->m_scrpos;
276 	if (m->m_ifile != curr_ifile)
277 	{
278 		/*
279 		 * Not in the current file; edit the correct file.
280 		 */
281 		if (edit_ifile(m->m_ifile))
282 			return;
283 	}
284 
285 	jump_loc(scrpos.pos, scrpos.ln);
286 }
287 
288 /*
289  * Return the position associated with a given mark letter.
290  *
291  * We don't return which screen line the position
292  * is associated with, but this doesn't matter much,
293  * because it's always the first non-blank line on the screen.
294  */
markpos(LWCHAR c)295 public POSITION markpos(LWCHAR c)
296 {
297 	struct mark *m;
298 
299 	m = getmark(c);
300 	if (m == NULL)
301 		return (NULL_POSITION);
302 
303 	if (m->m_ifile != curr_ifile)
304 	{
305 		error("Mark not in current file", NULL_PARG);
306 		return (NULL_POSITION);
307 	}
308 	return (m->m_scrpos.pos);
309 }
310 
311 /*
312  * Return the mark associated with a given position, if any.
313  */
posmark(POSITION pos)314 public char posmark(POSITION pos)
315 {
316 	int i;
317 
318 	/* Only user marks */
319 	for (i = 0;  i < NUMARKS;  i++)
320 	{
321 		if (marks[i].m_ifile == curr_ifile && marks[i].m_scrpos.pos == pos)
322 		{
323 			if (i < 26) return 'a' + i;
324 			if (i < 26*2) return 'A' + (i - 26);
325 			return '#';
326 		}
327 	}
328 	return 0;
329 }
330 
331 /*
332  * Clear the marks associated with a specified ifile.
333  */
unmark(IFILE ifile)334 public void unmark(IFILE ifile)
335 {
336 	int i;
337 
338 	for (i = 0;  i < NMARKS;  i++)
339 		if (marks[i].m_ifile == ifile)
340 			marks[i].m_scrpos.pos = NULL_POSITION;
341 }
342 
343 /*
344  * Check if any marks refer to a specified ifile vi m_filename
345  * rather than m_ifile.
346  */
mark_check_ifile(IFILE ifile)347 public void mark_check_ifile(IFILE ifile)
348 {
349 	int i;
350 	char *filename = get_real_filename(ifile);
351 
352 	for (i = 0;  i < NMARKS;  i++)
353 	{
354 		struct mark *m = &marks[i];
355 		char *mark_filename = m->m_filename;
356 		if (mark_filename != NULL)
357 		{
358 			mark_filename = lrealpath(mark_filename);
359 			if (strcmp(filename, mark_filename) == 0)
360 				mark_set_ifile(m, ifile);
361 			free(mark_filename);
362 		}
363 	}
364 }
365 
366 #if CMD_HISTORY
367 
368 /*
369  * Save marks to history file.
370  */
save_marks(FILE * fout,char * hdr)371 public void save_marks(FILE *fout, char *hdr)
372 {
373 	int i;
374 
375 	if (!perma_marks)
376 		return;
377 
378 	fprintf(fout, "%s\n", hdr);
379 	for (i = 0;  i < NMARKS;  i++)
380 	{
381 		char *filename;
382 		struct mark *m = &marks[i];
383 		char pos_str[INT_STRLEN_BOUND(m->m_scrpos.pos) + 2];
384 		if (m->m_scrpos.pos == NULL_POSITION)
385 			continue;
386 		postoa(m->m_scrpos.pos, pos_str, 10);
387 		filename = m->m_filename;
388 		if (filename == NULL)
389 			filename = get_real_filename(m->m_ifile);
390 		if (strcmp(filename, "-") != 0)
391 			fprintf(fout, "m %c %d %s %s\n",
392 				m->m_letter, m->m_scrpos.ln, pos_str, filename);
393 	}
394 }
395 
396 /*
397  * Restore one mark from the history file.
398  */
restore_mark(char * line)399 public void restore_mark(char *line)
400 {
401 	struct mark *m;
402 	int ln;
403 	POSITION pos;
404 
405 #define skip_whitespace while (*line == ' ') line++
406 	if (*line++ != 'm')
407 		return;
408 	skip_whitespace;
409 	m = getumark(*line++);
410 	if (m == NULL)
411 		return;
412 	skip_whitespace;
413 	ln = lstrtoi(line, &line, 10);
414 	if (ln < 0)
415 		return;
416 	if (ln < 1)
417 		ln = 1;
418 	if (ln > sc_height)
419 		ln = sc_height;
420 	skip_whitespace;
421 	pos = lstrtopos(line, &line, 10);
422 	if (pos < 0)
423 		return;
424 	skip_whitespace;
425 	cmark(m, NULL_IFILE, pos, ln);
426 	m->m_filename = save(line);
427 }
428 
429 #endif /* CMD_HISTORY */
430