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