xref: /dragonfly/contrib/less/mark.c (revision e0f238eda64c20d98364903e0c003825fd0b66dd)
1 /*
2  * Copyright (C) 1984-2024  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  */
cmark(struct mark * m,IFILE ifile,POSITION pos,int ln)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  */
init_mark(void)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 = (char) ((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  */
mark_set_ifile(struct mark * m,IFILE ifile)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  */
mark_get_ifile(struct mark * m)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  */
getumark(char c)107 static struct mark * getumark(char 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  */
getmark(char c)128 static struct mark * getmark(char 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  */
badmark(char c)188 public int badmark(char c)
189 {
190           return (getmark(c) == NULL);
191 }
192 
193 /*
194  * Set a user-defined mark.
195  */
setmark(char c,int where)196 public void setmark(char 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  */
clrmark(char c)217 public void clrmark(char 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  */
lastmark(void)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  */
gomark(char c)252 public void gomark(char 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  */
markpos(char c)293 public POSITION markpos(char 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  */
posmark(POSITION pos)312 public char posmark(POSITION pos)
313 {
314           unsigned char 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 (char) ('a' + i);
322                               if (i < 26*2) return (char) ('A' + (i - 26));
323                               return '#';
324                     }
325           }
326           return 0;
327 }
328 
329 /*
330  * Clear the marks associated with a specified ifile.
331  */
unmark(IFILE ifile)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  */
mark_check_ifile(IFILE ifile)345 public void mark_check_ifile(IFILE ifile)
346 {
347           int i;
348           constant 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  */
save_marks(FILE * fout,constant char * hdr)369 public void save_marks(FILE *fout, constant 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                     constant 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  */
restore_mark(constant char * line)397 public void restore_mark(constant 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 = lstrtoic(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 = lstrtoposc(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