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