1 /* 2 * Copyright (C) 1984-2026 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