Home | History | Annotate | Line # | Download | only in dist
mark.c revision 1.4
      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  */
     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  */
     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  */
     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  */
     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  */
    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  */
    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  */
    190 public int badmark(LWCHAR c)
    191 {
    192 	return (getmark(c) == NULL);
    193 }
    194 
    195 /*
    196  * Set a user-defined mark.
    197  */
    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  */
    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  */
    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  */
    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  */
    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  */
    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  */
    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  */
    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  */
    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  */
    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