xref: /netbsd-src/external/bsd/less/dist/mark.c (revision 838f5788460f0f133b15d706e644d692a9d4d6ec)
1*838f5788Ssimonb /*	$NetBSD: mark.c,v 1.4 2023/10/06 05:49:49 simonb Exp $	*/
220006a0bStron 
320006a0bStron /*
4*838f5788Ssimonb  * Copyright (C) 1984-2023  Mark Nudelman
520006a0bStron  *
620006a0bStron  * You may distribute under the terms of either the GNU General Public
720006a0bStron  * License or the Less License, as specified in the README file.
820006a0bStron  *
9ec18bca0Stron  * For more information, see the README file.
1020006a0bStron  */
1120006a0bStron 
1220006a0bStron 
1320006a0bStron #include "less.h"
14*838f5788Ssimonb #include "position.h"
1520006a0bStron 
1620006a0bStron extern IFILE curr_ifile;
1720006a0bStron extern int sc_height;
1820006a0bStron extern int jump_sline;
19*838f5788Ssimonb extern int perma_marks;
2020006a0bStron 
2120006a0bStron /*
2220006a0bStron  * A mark is an ifile (input file) plus a position within the file.
2320006a0bStron  */
24*838f5788Ssimonb struct mark
25*838f5788Ssimonb {
26*838f5788Ssimonb 	/*
27*838f5788Ssimonb 	 * Normally m_ifile != IFILE_NULL and m_filename == NULL.
28*838f5788Ssimonb 	 * For restored marks we set m_filename instead of m_ifile
29*838f5788Ssimonb 	 * because we don't want to create an ifile until the
30*838f5788Ssimonb 	 * user explicitly requests the file (by name or mark).
31*838f5788Ssimonb 	 */
32*838f5788Ssimonb 	char m_letter;           /* Associated character */
33*838f5788Ssimonb 	IFILE m_ifile;           /* Input file being marked */
34*838f5788Ssimonb 	char *m_filename;        /* Name of the input file */
35*838f5788Ssimonb 	struct scrpos m_scrpos;  /* Position of the mark */
3620006a0bStron };
3720006a0bStron 
3820006a0bStron /*
3920006a0bStron  * The table of marks.
4020006a0bStron  * Each mark is identified by a lowercase or uppercase letter.
4120006a0bStron  * The final one is lmark, for the "last mark"; addressed by the apostrophe.
4220006a0bStron  */
43*838f5788Ssimonb #define NMARKS          ((2*26)+2)      /* a-z, A-Z, mousemark, lastmark */
44*838f5788Ssimonb #define NUMARKS         ((2*26)+1)      /* user marks (not lastmark) */
45*838f5788Ssimonb #define MOUSEMARK       (NMARKS-2)
4620006a0bStron #define LASTMARK        (NMARKS-1)
4720006a0bStron static struct mark marks[NMARKS];
48*838f5788Ssimonb public int marks_modified = 0;
49*838f5788Ssimonb 
50*838f5788Ssimonb 
51*838f5788Ssimonb /*
52*838f5788Ssimonb  * Initialize a mark struct.
53*838f5788Ssimonb  */
cmark(struct mark * m,IFILE ifile,POSITION pos,int ln)54*838f5788Ssimonb static void cmark(struct mark *m, IFILE ifile, POSITION pos, int ln)
55*838f5788Ssimonb {
56*838f5788Ssimonb 	m->m_ifile = ifile;
57*838f5788Ssimonb 	m->m_scrpos.pos = pos;
58*838f5788Ssimonb 	m->m_scrpos.ln = ln;
59*838f5788Ssimonb 	if (m->m_filename != NULL)
60*838f5788Ssimonb 		/* Normally should not happen but a corrupt lesshst file can do it. */
61*838f5788Ssimonb 		free(m->m_filename);
62*838f5788Ssimonb 	m->m_filename = NULL;
63*838f5788Ssimonb }
6420006a0bStron 
6520006a0bStron /*
6620006a0bStron  * Initialize the mark table to show no marks are set.
6720006a0bStron  */
init_mark(void)68*838f5788Ssimonb public void init_mark(void)
6920006a0bStron {
7020006a0bStron 	int i;
7120006a0bStron 
7220006a0bStron 	for (i = 0;  i < NMARKS;  i++)
73*838f5788Ssimonb 	{
74*838f5788Ssimonb 		char letter;
75*838f5788Ssimonb 		switch (i) {
76*838f5788Ssimonb 		case MOUSEMARK: letter = '#'; break;
77*838f5788Ssimonb 		case LASTMARK: letter = '\''; break;
78*838f5788Ssimonb 		default: letter = (i < 26) ? 'a'+i : 'A'+i-26; break;
79*838f5788Ssimonb 		}
80*838f5788Ssimonb 		marks[i].m_letter = letter;
81*838f5788Ssimonb 		cmark(&marks[i], NULL_IFILE, NULL_POSITION, -1);
82*838f5788Ssimonb 	}
8320006a0bStron }
8420006a0bStron 
8520006a0bStron /*
86*838f5788Ssimonb  * Set m_ifile and clear m_filename.
8720006a0bStron  */
mark_set_ifile(struct mark * m,IFILE ifile)88*838f5788Ssimonb static void mark_set_ifile(struct mark *m, IFILE ifile)
8920006a0bStron {
90*838f5788Ssimonb 	m->m_ifile = ifile;
91*838f5788Ssimonb 	/* With m_ifile set, m_filename is no longer needed. */
92*838f5788Ssimonb 	free(m->m_filename);
93*838f5788Ssimonb 	m->m_filename = NULL;
94*838f5788Ssimonb }
95*838f5788Ssimonb 
96*838f5788Ssimonb /*
97*838f5788Ssimonb  * Populate the m_ifile member of a mark struct from m_filename.
98*838f5788Ssimonb  */
mark_get_ifile(struct mark * m)99*838f5788Ssimonb static void mark_get_ifile(struct mark *m)
100*838f5788Ssimonb {
101*838f5788Ssimonb 	if (m->m_ifile != NULL_IFILE)
102*838f5788Ssimonb 		return; /* m_ifile is already set */
103*838f5788Ssimonb 	mark_set_ifile(m, get_ifile(m->m_filename, prev_ifile(NULL_IFILE)));
104*838f5788Ssimonb }
105*838f5788Ssimonb 
106*838f5788Ssimonb /*
107*838f5788Ssimonb  * Return the user mark struct identified by a character.
108*838f5788Ssimonb  */
getumark(LWCHAR c)109*838f5788Ssimonb static struct mark * getumark(LWCHAR c)
110*838f5788Ssimonb {
111*838f5788Ssimonb 	PARG parg;
11220006a0bStron 	if (c >= 'a' && c <= 'z')
11320006a0bStron 		return (&marks[c-'a']);
11420006a0bStron 	if (c >= 'A' && c <= 'Z')
11520006a0bStron 		return (&marks[c-'A'+26]);
116*838f5788Ssimonb 	if (c == '\'')
117*838f5788Ssimonb 		return (&marks[LASTMARK]);
118*838f5788Ssimonb 	if (c == '#')
119*838f5788Ssimonb 		return (&marks[MOUSEMARK]);
120*838f5788Ssimonb 	parg.p_char = (char) c;
121*838f5788Ssimonb 	error("Invalid mark letter %c", &parg);
12220006a0bStron 	return (NULL);
12320006a0bStron }
12420006a0bStron 
12520006a0bStron /*
12620006a0bStron  * Get the mark structure identified by a character.
127*838f5788Ssimonb  * The mark struct may either be in the mark table (user mark)
12820006a0bStron  * or may be constructed on the fly for certain characters like ^, $.
12920006a0bStron  */
getmark(LWCHAR c)130*838f5788Ssimonb static struct mark * getmark(LWCHAR c)
13120006a0bStron {
132*838f5788Ssimonb 	struct mark *m;
13320006a0bStron 	static struct mark sm;
13420006a0bStron 
13520006a0bStron 	switch (c)
13620006a0bStron 	{
13720006a0bStron 	case '^':
13820006a0bStron 		/*
13920006a0bStron 		 * Beginning of the current file.
14020006a0bStron 		 */
14120006a0bStron 		m = &sm;
142*838f5788Ssimonb 		cmark(m, curr_ifile, ch_zero(), 0);
14320006a0bStron 		break;
14420006a0bStron 	case '$':
14520006a0bStron 		/*
14620006a0bStron 		 * End of the current file.
14720006a0bStron 		 */
14820006a0bStron 		if (ch_end_seek())
14920006a0bStron 		{
15020006a0bStron 			error("Cannot seek to end of file", NULL_PARG);
15120006a0bStron 			return (NULL);
15220006a0bStron 		}
15320006a0bStron 		m = &sm;
154*838f5788Ssimonb 		cmark(m, curr_ifile, ch_tell(), sc_height);
15520006a0bStron 		break;
15620006a0bStron 	case '.':
15720006a0bStron 		/*
15820006a0bStron 		 * Current position in the current file.
15920006a0bStron 		 */
16020006a0bStron 		m = &sm;
161*838f5788Ssimonb 		get_scrpos(&m->m_scrpos, TOP);
162*838f5788Ssimonb 		cmark(m, curr_ifile, m->m_scrpos.pos, m->m_scrpos.ln);
16320006a0bStron 		break;
16420006a0bStron 	case '\'':
16520006a0bStron 		/*
16620006a0bStron 		 * The "last mark".
16720006a0bStron 		 */
16820006a0bStron 		m = &marks[LASTMARK];
16920006a0bStron 		break;
17020006a0bStron 	default:
17120006a0bStron 		/*
17220006a0bStron 		 * Must be a user-defined mark.
17320006a0bStron 		 */
17420006a0bStron 		m = getumark(c);
17520006a0bStron 		if (m == NULL)
17620006a0bStron 			break;
17720006a0bStron 		if (m->m_scrpos.pos == NULL_POSITION)
17820006a0bStron 		{
17920006a0bStron 			error("Mark not set", NULL_PARG);
18020006a0bStron 			return (NULL);
18120006a0bStron 		}
18220006a0bStron 		break;
18320006a0bStron 	}
18420006a0bStron 	return (m);
18520006a0bStron }
18620006a0bStron 
18720006a0bStron /*
188*838f5788Ssimonb  * Is a mark letter invalid?
18920006a0bStron  */
badmark(LWCHAR c)190*838f5788Ssimonb public int badmark(LWCHAR c)
19120006a0bStron {
19220006a0bStron 	return (getmark(c) == NULL);
19320006a0bStron }
19420006a0bStron 
19520006a0bStron /*
19620006a0bStron  * Set a user-defined mark.
19720006a0bStron  */
setmark(LWCHAR c,int where)198*838f5788Ssimonb public void setmark(LWCHAR c, int where)
19920006a0bStron {
200*838f5788Ssimonb 	struct mark *m;
20120006a0bStron 	struct scrpos scrpos;
20220006a0bStron 
20320006a0bStron 	m = getumark(c);
20420006a0bStron 	if (m == NULL)
20520006a0bStron 		return;
206*838f5788Ssimonb 	get_scrpos(&scrpos, where);
207*838f5788Ssimonb 	if (scrpos.pos == NULL_POSITION)
208*838f5788Ssimonb 	{
209*838f5788Ssimonb 		bell();
210*838f5788Ssimonb 		return;
211*838f5788Ssimonb 	}
212*838f5788Ssimonb 	cmark(m, curr_ifile, scrpos.pos, scrpos.ln);
213*838f5788Ssimonb 	marks_modified = 1;
214*838f5788Ssimonb }
215*838f5788Ssimonb 
216*838f5788Ssimonb /*
217*838f5788Ssimonb  * Clear a user-defined mark.
218*838f5788Ssimonb  */
clrmark(LWCHAR c)219*838f5788Ssimonb public void clrmark(LWCHAR c)
220*838f5788Ssimonb {
221*838f5788Ssimonb 	struct mark *m;
222*838f5788Ssimonb 
223*838f5788Ssimonb 	m = getumark(c);
224*838f5788Ssimonb 	if (m == NULL)
225*838f5788Ssimonb 		return;
226*838f5788Ssimonb 	if (m->m_scrpos.pos == NULL_POSITION)
227*838f5788Ssimonb 	{
228*838f5788Ssimonb 		bell();
229*838f5788Ssimonb 		return;
230*838f5788Ssimonb 	}
231*838f5788Ssimonb 	m->m_scrpos.pos = NULL_POSITION;
232*838f5788Ssimonb 	marks_modified = 1;
23320006a0bStron }
23420006a0bStron 
23520006a0bStron /*
23620006a0bStron  * Set lmark (the mark named by the apostrophe).
23720006a0bStron  */
lastmark(void)238*838f5788Ssimonb public void lastmark(void)
23920006a0bStron {
24020006a0bStron 	struct scrpos scrpos;
24120006a0bStron 
24220006a0bStron 	if (ch_getflags() & CH_HELPFILE)
24320006a0bStron 		return;
244*838f5788Ssimonb 	get_scrpos(&scrpos, TOP);
24520006a0bStron 	if (scrpos.pos == NULL_POSITION)
24620006a0bStron 		return;
247*838f5788Ssimonb 	cmark(&marks[LASTMARK], curr_ifile, scrpos.pos, scrpos.ln);
248*838f5788Ssimonb 	marks_modified = 1;
24920006a0bStron }
25020006a0bStron 
25120006a0bStron /*
25220006a0bStron  * Go to a mark.
25320006a0bStron  */
gomark(LWCHAR c)254*838f5788Ssimonb public void gomark(LWCHAR c)
25520006a0bStron {
256*838f5788Ssimonb 	struct mark *m;
25720006a0bStron 	struct scrpos scrpos;
25820006a0bStron 
25920006a0bStron 	m = getmark(c);
26020006a0bStron 	if (m == NULL)
26120006a0bStron 		return;
26220006a0bStron 
26320006a0bStron 	/*
26420006a0bStron 	 * If we're trying to go to the lastmark and
26520006a0bStron 	 * it has not been set to anything yet,
26620006a0bStron 	 * set it to the beginning of the current file.
267*838f5788Ssimonb 	 * {{ Couldn't we instead set marks[LASTMARK] in edit()? }}
26820006a0bStron 	 */
26920006a0bStron 	if (m == &marks[LASTMARK] && m->m_scrpos.pos == NULL_POSITION)
270*838f5788Ssimonb 		cmark(m, curr_ifile, ch_zero(), jump_sline);
27120006a0bStron 
272*838f5788Ssimonb 	mark_get_ifile(m);
273*838f5788Ssimonb 
274*838f5788Ssimonb 	/* Save scrpos; if it's LASTMARK it could change in edit_ifile. */
27520006a0bStron 	scrpos = m->m_scrpos;
27620006a0bStron 	if (m->m_ifile != curr_ifile)
27720006a0bStron 	{
27820006a0bStron 		/*
27920006a0bStron 		 * Not in the current file; edit the correct file.
28020006a0bStron 		 */
28120006a0bStron 		if (edit_ifile(m->m_ifile))
28220006a0bStron 			return;
28320006a0bStron 	}
28420006a0bStron 
28520006a0bStron 	jump_loc(scrpos.pos, scrpos.ln);
28620006a0bStron }
28720006a0bStron 
28820006a0bStron /*
28920006a0bStron  * Return the position associated with a given mark letter.
29020006a0bStron  *
29120006a0bStron  * We don't return which screen line the position
29220006a0bStron  * is associated with, but this doesn't matter much,
29320006a0bStron  * because it's always the first non-blank line on the screen.
29420006a0bStron  */
markpos(LWCHAR c)295*838f5788Ssimonb public POSITION markpos(LWCHAR c)
29620006a0bStron {
297*838f5788Ssimonb 	struct mark *m;
29820006a0bStron 
29920006a0bStron 	m = getmark(c);
30020006a0bStron 	if (m == NULL)
30120006a0bStron 		return (NULL_POSITION);
30220006a0bStron 
30320006a0bStron 	if (m->m_ifile != curr_ifile)
30420006a0bStron 	{
30520006a0bStron 		error("Mark not in current file", NULL_PARG);
30620006a0bStron 		return (NULL_POSITION);
30720006a0bStron 	}
30820006a0bStron 	return (m->m_scrpos.pos);
30920006a0bStron }
31020006a0bStron 
31120006a0bStron /*
312*838f5788Ssimonb  * Return the mark associated with a given position, if any.
313*838f5788Ssimonb  */
posmark(POSITION pos)314*838f5788Ssimonb public char posmark(POSITION pos)
315*838f5788Ssimonb {
316*838f5788Ssimonb 	int i;
317*838f5788Ssimonb 
318*838f5788Ssimonb 	/* Only user marks */
319*838f5788Ssimonb 	for (i = 0;  i < NUMARKS;  i++)
320*838f5788Ssimonb 	{
321*838f5788Ssimonb 		if (marks[i].m_ifile == curr_ifile && marks[i].m_scrpos.pos == pos)
322*838f5788Ssimonb 		{
323*838f5788Ssimonb 			if (i < 26) return 'a' + i;
324*838f5788Ssimonb 			if (i < 26*2) return 'A' + (i - 26);
325*838f5788Ssimonb 			return '#';
326*838f5788Ssimonb 		}
327*838f5788Ssimonb 	}
328*838f5788Ssimonb 	return 0;
329*838f5788Ssimonb }
330*838f5788Ssimonb 
331*838f5788Ssimonb /*
33220006a0bStron  * Clear the marks associated with a specified ifile.
33320006a0bStron  */
unmark(IFILE ifile)334*838f5788Ssimonb public void unmark(IFILE ifile)
33520006a0bStron {
33620006a0bStron 	int i;
33720006a0bStron 
33820006a0bStron 	for (i = 0;  i < NMARKS;  i++)
33920006a0bStron 		if (marks[i].m_ifile == ifile)
34020006a0bStron 			marks[i].m_scrpos.pos = NULL_POSITION;
34120006a0bStron }
342*838f5788Ssimonb 
343*838f5788Ssimonb /*
344*838f5788Ssimonb  * Check if any marks refer to a specified ifile vi m_filename
345*838f5788Ssimonb  * rather than m_ifile.
346*838f5788Ssimonb  */
mark_check_ifile(IFILE ifile)347*838f5788Ssimonb public void mark_check_ifile(IFILE ifile)
348*838f5788Ssimonb {
349*838f5788Ssimonb 	int i;
350*838f5788Ssimonb 	char *filename = get_real_filename(ifile);
351*838f5788Ssimonb 
352*838f5788Ssimonb 	for (i = 0;  i < NMARKS;  i++)
353*838f5788Ssimonb 	{
354*838f5788Ssimonb 		struct mark *m = &marks[i];
355*838f5788Ssimonb 		char *mark_filename = m->m_filename;
356*838f5788Ssimonb 		if (mark_filename != NULL)
357*838f5788Ssimonb 		{
358*838f5788Ssimonb 			mark_filename = lrealpath(mark_filename);
359*838f5788Ssimonb 			if (strcmp(filename, mark_filename) == 0)
360*838f5788Ssimonb 				mark_set_ifile(m, ifile);
361*838f5788Ssimonb 			free(mark_filename);
362*838f5788Ssimonb 		}
363*838f5788Ssimonb 	}
364*838f5788Ssimonb }
365*838f5788Ssimonb 
366*838f5788Ssimonb #if CMD_HISTORY
367*838f5788Ssimonb 
368*838f5788Ssimonb /*
369*838f5788Ssimonb  * Save marks to history file.
370*838f5788Ssimonb  */
save_marks(FILE * fout,char * hdr)371*838f5788Ssimonb public void save_marks(FILE *fout, char *hdr)
372*838f5788Ssimonb {
373*838f5788Ssimonb 	int i;
374*838f5788Ssimonb 
375*838f5788Ssimonb 	if (!perma_marks)
376*838f5788Ssimonb 		return;
377*838f5788Ssimonb 
378*838f5788Ssimonb 	fprintf(fout, "%s\n", hdr);
379*838f5788Ssimonb 	for (i = 0;  i < NMARKS;  i++)
380*838f5788Ssimonb 	{
381*838f5788Ssimonb 		char *filename;
382*838f5788Ssimonb 		struct mark *m = &marks[i];
383*838f5788Ssimonb 		char pos_str[INT_STRLEN_BOUND(m->m_scrpos.pos) + 2];
384*838f5788Ssimonb 		if (m->m_scrpos.pos == NULL_POSITION)
385*838f5788Ssimonb 			continue;
386*838f5788Ssimonb 		postoa(m->m_scrpos.pos, pos_str, 10);
387*838f5788Ssimonb 		filename = m->m_filename;
388*838f5788Ssimonb 		if (filename == NULL)
389*838f5788Ssimonb 			filename = get_real_filename(m->m_ifile);
390*838f5788Ssimonb 		if (strcmp(filename, "-") != 0)
391*838f5788Ssimonb 			fprintf(fout, "m %c %d %s %s\n",
392*838f5788Ssimonb 				m->m_letter, m->m_scrpos.ln, pos_str, filename);
393*838f5788Ssimonb 	}
394*838f5788Ssimonb }
395*838f5788Ssimonb 
396*838f5788Ssimonb /*
397*838f5788Ssimonb  * Restore one mark from the history file.
398*838f5788Ssimonb  */
restore_mark(char * line)399*838f5788Ssimonb public void restore_mark(char *line)
400*838f5788Ssimonb {
401*838f5788Ssimonb 	struct mark *m;
402*838f5788Ssimonb 	int ln;
403*838f5788Ssimonb 	POSITION pos;
404*838f5788Ssimonb 
405*838f5788Ssimonb #define skip_whitespace while (*line == ' ') line++
406*838f5788Ssimonb 	if (*line++ != 'm')
407*838f5788Ssimonb 		return;
408*838f5788Ssimonb 	skip_whitespace;
409*838f5788Ssimonb 	m = getumark(*line++);
410*838f5788Ssimonb 	if (m == NULL)
411*838f5788Ssimonb 		return;
412*838f5788Ssimonb 	skip_whitespace;
413*838f5788Ssimonb 	ln = lstrtoi(line, &line, 10);
414*838f5788Ssimonb 	if (ln < 0)
415*838f5788Ssimonb 		return;
416*838f5788Ssimonb 	if (ln < 1)
417*838f5788Ssimonb 		ln = 1;
418*838f5788Ssimonb 	if (ln > sc_height)
419*838f5788Ssimonb 		ln = sc_height;
420*838f5788Ssimonb 	skip_whitespace;
421*838f5788Ssimonb 	pos = lstrtopos(line, &line, 10);
422*838f5788Ssimonb 	if (pos < 0)
423*838f5788Ssimonb 		return;
424*838f5788Ssimonb 	skip_whitespace;
425*838f5788Ssimonb 	cmark(m, NULL_IFILE, pos, ln);
426*838f5788Ssimonb 	m->m_filename = save(line);
427*838f5788Ssimonb }
428*838f5788Ssimonb 
429*838f5788Ssimonb #endif /* CMD_HISTORY */
430