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