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