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