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 POSITION pos; 165 166 switch (c) 167 { 168 case '^': 169 /* 170 * Beginning of the current file. 171 */ 172 m = &sm; 173 cmark(m, curr_ifile, ch_zero(), 0); 174 break; 175 case '$': 176 /* 177 * End of the current file. 178 */ 179 if (ch_end_seek()) 180 { 181 error("Cannot seek to end of file", NULL_PARG); 182 return (NULL); 183 } 184 m = &sm; 185 pos = ch_tell(); 186 if (pos == NULL_POSITION) 187 return (NULL); 188 pos = back_line(pos, NULL); 189 if (pos == NULL_POSITION) 190 return (NULL); 191 cmark(m, curr_ifile, pos, sc_height-1); 192 break; 193 case '.': 194 /* 195 * Current position in the current file. 196 */ 197 m = &sm; 198 get_scrpos(&m->m_scrpos, TOP); 199 cmark(m, curr_ifile, m->m_scrpos.pos, m->m_scrpos.ln); 200 break; 201 case '\'': 202 /* 203 * The "last mark". 204 */ 205 m = &marks[LASTMARK]; 206 break; 207 default: 208 /* 209 * Must be a user-defined mark. 210 */ 211 m = getumark(c); 212 if (m == NULL) 213 break; 214 if (!mark_is_set(m)) 215 { 216 error("Mark not set", NULL_PARG); 217 return (NULL); 218 } 219 break; 220 } 221 return (m); 222 } 223 224 /* 225 * Is a mark letter invalid? 226 */ 227 public lbool badmark(char c) 228 { 229 return (getmark(c) == NULL); 230 } 231 232 /* 233 * Set a user-defined mark. 234 */ 235 public void setmark(char c, int where, LINENUM linenum) 236 { 237 struct mark *m; 238 struct scrpos scrpos; 239 POSITION pos; 240 241 m = getumark(c); 242 if (m == NULL) 243 return; 244 245 if (linenum == 0) 246 { 247 get_scrpos(&scrpos, where); 248 if (scrpos.pos == NULL_POSITION) 249 { 250 lbell(); 251 return; 252 } 253 } else 254 { 255 pos = find_pos(linenum); 256 if (pos == NULL_POSITION) 257 { 258 PARG parg; 259 parg.p_linenum = linenum; 260 error("Cannot find line number %n", &parg); 261 return; 262 } 263 get_scrpos_pos(&scrpos, where, pos); 264 } 265 cmark(m, curr_ifile, scrpos.pos, scrpos.ln); 266 marks_modified = TRUE; 267 if (perma_marks && autosave_action('m')) 268 save_cmdhist(); 269 } 270 271 /* 272 * Clear a user-defined mark. 273 */ 274 public void clrmark(char c) 275 { 276 struct mark *m; 277 278 m = getumark(c); 279 if (m == NULL) 280 return; 281 if (m->m_scrpos.pos == NULL_POSITION) 282 { 283 lbell(); 284 return; 285 } 286 mark_clear(m); 287 #if CMD_HISTORY 288 /* Also clear in file_marks, so save_marks doesn't save it to history file. */ 289 m = &file_marks[mark_index(c)]; 290 mark_clear(m); 291 #endif 292 marks_modified = TRUE; 293 if (perma_marks && autosave_action('m')) 294 save_cmdhist(); 295 } 296 297 /* 298 * Set lmark (the mark named by the apostrophe). 299 */ 300 public void lastmark(void) 301 { 302 struct scrpos scrpos; 303 304 if (ch_getflags() & CH_HELPFILE) 305 return; 306 get_scrpos(&scrpos, TOP); 307 if (scrpos.pos == NULL_POSITION) 308 return; 309 cmark(&marks[LASTMARK], curr_ifile, scrpos.pos, scrpos.ln); 310 marks_modified = TRUE; 311 } 312 313 /* 314 * Go to a mark. 315 */ 316 public void gomark(char c, int sline) 317 { 318 struct mark *m; 319 struct scrpos scrpos; 320 321 m = getmark(c); 322 if (m == NULL) 323 return; 324 if (sline != 0) 325 m->m_scrpos.ln = sline; 326 327 /* 328 * If we're trying to go to the lastmark and 329 * it has not been set to anything yet, 330 * set it to the beginning of the current file. 331 * {{ Couldn't we instead set marks[LASTMARK] in edit()? }} 332 */ 333 if (m == &marks[LASTMARK] && !mark_is_set(m)) 334 cmark(m, curr_ifile, ch_zero(), jump_sline); 335 336 mark_get_ifile(m); 337 338 /* Save scrpos; if it's LASTMARK it could change in edit_ifile. */ 339 scrpos = m->m_scrpos; 340 if (m->m_ifile != curr_ifile) 341 { 342 /* 343 * Not in the current file; edit the correct file. 344 */ 345 if (edit_ifile(m->m_ifile)) 346 return; 347 } 348 349 jump_loc(scrpos.pos, scrpos.ln); 350 } 351 352 /* 353 * Return the position associated with a given mark letter. 354 * 355 * We don't return which screen line the position 356 * is associated with, but this doesn't matter much, 357 * because it's always the first non-blank line on the screen. 358 */ 359 public POSITION markpos(char c) 360 { 361 struct mark *m; 362 363 m = getmark(c); 364 if (m == NULL) 365 return (NULL_POSITION); 366 367 if (m->m_ifile != curr_ifile) 368 { 369 error("Mark not in current file", NULL_PARG); 370 return (NULL_POSITION); 371 } 372 return (m->m_scrpos.pos); 373 } 374 375 /* 376 * Return the mark associated with a given position, if any. 377 */ 378 public char posmark(POSITION pos) 379 { 380 unsigned char i; 381 382 /* Only user marks */ 383 for (i = 0; i < NUMARKS; i++) 384 { 385 struct mark *m = &marks[i]; 386 if (m->m_ifile == curr_ifile && m->m_scrpos.pos == pos) 387 return mark_letter(i); 388 } 389 return '\0'; 390 } 391 392 /* 393 * Clear the marks associated with a specified ifile. 394 */ 395 public void unmark(IFILE ifile) 396 { 397 int i; 398 399 for (i = 0; i < NMARKS; i++) 400 if (marks[i].m_ifile == ifile) 401 mark_clear(&marks[i]); 402 } 403 404 /* 405 * Check if any marks refer to a specified ifile vi m_filename 406 * rather than m_ifile. 407 */ 408 public void mark_check_ifile(IFILE ifile) 409 { 410 int i; 411 constant char *filename = get_real_filename(ifile); 412 413 for (i = 0; i < NMARKS; i++) 414 { 415 struct mark *m = &marks[i]; 416 char *mark_filename = m->m_filename; 417 if (mark_filename != NULL) 418 { 419 mark_filename = lrealpath(mark_filename); 420 if (strcmp(filename, mark_filename) == 0) 421 mark_set_ifile(m, ifile); 422 free(mark_filename); 423 } 424 } 425 } 426 427 #if CMD_HISTORY 428 429 /* 430 * Initialize a mark struct, including the filename. 431 */ 432 static void cmarkf(struct mark *m, IFILE ifile, POSITION pos, int ln, constant char *filename) 433 { 434 cmark(m, ifile, pos, ln); 435 m->m_filename = (filename == NULL) ? NULL : save(filename); 436 } 437 438 /* 439 * Save marks to history file. 440 */ 441 public void save_marks(FILE *fout, constant char *hdr) 442 { 443 int i; 444 445 if (perma_marks) 446 { 447 /* Copy active marks to file_marks. */ 448 for (i = 0; i < NMARKS; i++) 449 { 450 struct mark *m = &marks[i]; 451 if (mark_is_set(m)) 452 cmarkf(&file_marks[i], m->m_ifile, m->m_scrpos.pos, m->m_scrpos.ln, m->m_filename); 453 } 454 } 455 456 /* 457 * Save file_marks to the history file. 458 * These may have been read from the history file at startup 459 * (in restore_mark), or copied from the active marks above. 460 */ 461 fprintf(fout, "%s\n", hdr); 462 for (i = 0; i < NMARKS; i++) 463 { 464 constant char *filename; 465 struct mark *m = &file_marks[i]; 466 char pos_str[INT_STRLEN_BOUND(m->m_scrpos.pos) + 2]; 467 if (!mark_is_set(m)) 468 continue; 469 postoa(m->m_scrpos.pos, pos_str, 10); 470 filename = m->m_filename; 471 if (filename == NULL) 472 filename = get_real_filename(m->m_ifile); 473 if (strcmp(filename, "-") != 0) 474 fprintf(fout, "m %c %d %s %s\n", 475 mark_letter(i), m->m_scrpos.ln, pos_str, filename); 476 } 477 } 478 479 /* 480 * Restore one mark from the history file. 481 */ 482 public void restore_mark(constant char *line) 483 { 484 int index; 485 int ln; 486 POSITION pos; 487 488 #define skip_whitespace while (*line == ' ') line++ 489 if (*line++ != 'm') 490 return; 491 skip_whitespace; 492 index = mark_index(*line++); 493 if (index < 0) 494 return; 495 skip_whitespace; 496 ln = lstrtoic(line, &line, 10); 497 if (ln < 0) 498 return; 499 skip_whitespace; 500 pos = lstrtoposc(line, &line, 10); 501 if (pos < 0) 502 return; 503 skip_whitespace; 504 /* Save in both active marks table and file_marks table. */ 505 cmarkf(&marks[index], NULL_IFILE, pos, ln, line); 506 cmarkf(&file_marks[index], NULL_IFILE, pos, ln, line); 507 } 508 509 #endif /* CMD_HISTORY */ 510