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