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