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