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