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