1 1.6 simonb /* $NetBSD: linenum.c,v 1.6 2023/10/06 06:25:22 simonb Exp $ */ 2 1.1 tron 3 1.1 tron /* 4 1.5 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.4 tron * For more information, see the README file. 10 1.1 tron */ 11 1.1 tron 12 1.1 tron 13 1.1 tron /* 14 1.1 tron * Code to handle displaying line numbers. 15 1.1 tron * 16 1.1 tron * Finding the line number of a given file position is rather tricky. 17 1.1 tron * We don't want to just start at the beginning of the file and 18 1.1 tron * count newlines, because that is slow for large files (and also 19 1.1 tron * wouldn't work if we couldn't get to the start of the file; e.g. 20 1.1 tron * if input is a long pipe). 21 1.1 tron * 22 1.1 tron * So we use the function add_lnum to cache line numbers. 23 1.1 tron * We try to be very clever and keep only the more interesting 24 1.1 tron * line numbers when we run out of space in our table. A line 25 1.1 tron * number is more interesting than another when it is far from 26 1.1 tron * other line numbers. For example, we'd rather keep lines 27 1.1 tron * 100,200,300 than 100,101,300. 200 is more interesting than 28 1.1 tron * 101 because 101 can be derived very cheaply from 100, while 29 1.1 tron * 200 is more expensive to derive from 100. 30 1.1 tron * 31 1.1 tron * The function currline() returns the line number of a given 32 1.1 tron * position in the file. As a side effect, it calls add_lnum 33 1.1 tron * to cache the line number. Therefore currline is occasionally 34 1.1 tron * called to make sure we cache line numbers often enough. 35 1.1 tron */ 36 1.1 tron 37 1.1 tron #include "less.h" 38 1.1 tron 39 1.1 tron /* 40 1.1 tron * Structure to keep track of a line number and the associated file position. 41 1.1 tron * A doubly-linked circular list of line numbers is kept ordered by line number. 42 1.1 tron */ 43 1.1 tron struct linenum_info 44 1.1 tron { 45 1.5 simonb struct linenum_info *next; /* Link to next in the list */ 46 1.5 simonb struct linenum_info *prev; /* Line to previous in the list */ 47 1.5 simonb POSITION pos; /* File position */ 48 1.5 simonb POSITION gap; /* Gap between prev and next */ 49 1.5 simonb LINENUM line; /* Line number */ 50 1.1 tron }; 51 1.1 tron /* 52 1.1 tron * "gap" needs some explanation: the gap of any particular line number 53 1.1 tron * is the distance between the previous one and the next one in the list. 54 1.1 tron * ("Distance" means difference in file position.) In other words, the 55 1.1 tron * gap of a line number is the gap which would be introduced if this 56 1.1 tron * line number were deleted. It is used to decide which one to replace 57 1.1 tron * when we have a new one to insert and the table is full. 58 1.1 tron */ 59 1.1 tron 60 1.5 simonb #define NPOOL 200 /* Size of line number pool */ 61 1.1 tron 62 1.5 simonb #define LONGTIME (2) /* In seconds */ 63 1.1 tron 64 1.5 simonb static struct linenum_info anchor; /* Anchor of the list */ 65 1.5 simonb static struct linenum_info *freelist; /* Anchor of the unused entries */ 66 1.5 simonb static struct linenum_info pool[NPOOL]; /* The pool itself */ 67 1.5 simonb static struct linenum_info *spare; /* We always keep one spare entry */ 68 1.5 simonb public int scanning_eof = FALSE; 69 1.1 tron 70 1.1 tron extern int linenums; 71 1.1 tron extern int sigs; 72 1.1 tron extern int sc_height; 73 1.1 tron extern int screen_trashed; 74 1.5 simonb extern int header_lines; 75 1.5 simonb extern int nonum_headers; 76 1.1 tron 77 1.1 tron /* 78 1.1 tron * Initialize the line number structures. 79 1.1 tron */ 80 1.5 simonb public void clr_linenum(void) 81 1.1 tron { 82 1.5 simonb struct linenum_info *p; 83 1.1 tron 84 1.1 tron /* 85 1.1 tron * Put all the entries on the free list. 86 1.1 tron * Leave one for the "spare". 87 1.1 tron */ 88 1.1 tron for (p = pool; p < &pool[NPOOL-2]; p++) 89 1.1 tron p->next = p+1; 90 1.1 tron pool[NPOOL-2].next = NULL; 91 1.1 tron freelist = pool; 92 1.1 tron 93 1.1 tron spare = &pool[NPOOL-1]; 94 1.1 tron 95 1.1 tron /* 96 1.1 tron * Initialize the anchor. 97 1.1 tron */ 98 1.1 tron anchor.next = anchor.prev = &anchor; 99 1.1 tron anchor.gap = 0; 100 1.1 tron anchor.pos = (POSITION)0; 101 1.1 tron anchor.line = 1; 102 1.1 tron } 103 1.1 tron 104 1.1 tron /* 105 1.1 tron * Calculate the gap for an entry. 106 1.1 tron */ 107 1.5 simonb static void calcgap(struct linenum_info *p) 108 1.1 tron { 109 1.1 tron /* 110 1.1 tron * Don't bother to compute a gap for the anchor. 111 1.1 tron * Also don't compute a gap for the last one in the list. 112 1.1 tron * The gap for that last one should be considered infinite, 113 1.1 tron * but we never look at it anyway. 114 1.1 tron */ 115 1.1 tron if (p == &anchor || p->next == &anchor) 116 1.1 tron return; 117 1.1 tron p->gap = p->next->pos - p->prev->pos; 118 1.1 tron } 119 1.1 tron 120 1.1 tron /* 121 1.1 tron * Add a new line number to the cache. 122 1.1 tron * The specified position (pos) should be the file position of the 123 1.1 tron * FIRST character in the specified line. 124 1.1 tron */ 125 1.5 simonb public void add_lnum(LINENUM linenum, POSITION pos) 126 1.1 tron { 127 1.5 simonb struct linenum_info *p; 128 1.5 simonb struct linenum_info *new; 129 1.5 simonb struct linenum_info *nextp; 130 1.5 simonb struct linenum_info *prevp; 131 1.5 simonb POSITION mingap; 132 1.1 tron 133 1.1 tron /* 134 1.1 tron * Find the proper place in the list for the new one. 135 1.1 tron * The entries are sorted by position. 136 1.1 tron */ 137 1.1 tron for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next) 138 1.1 tron if (p->line == linenum) 139 1.1 tron /* We already have this one. */ 140 1.1 tron return; 141 1.1 tron nextp = p; 142 1.1 tron prevp = p->prev; 143 1.1 tron 144 1.1 tron if (freelist != NULL) 145 1.1 tron { 146 1.1 tron /* 147 1.1 tron * We still have free (unused) entries. 148 1.1 tron * Use one of them. 149 1.1 tron */ 150 1.1 tron new = freelist; 151 1.1 tron freelist = freelist->next; 152 1.1 tron } else 153 1.1 tron { 154 1.1 tron /* 155 1.1 tron * No free entries. 156 1.1 tron * Use the "spare" entry. 157 1.1 tron */ 158 1.1 tron new = spare; 159 1.1 tron spare = NULL; 160 1.1 tron } 161 1.1 tron 162 1.1 tron /* 163 1.1 tron * Fill in the fields of the new entry, 164 1.1 tron * and insert it into the proper place in the list. 165 1.1 tron */ 166 1.1 tron new->next = nextp; 167 1.1 tron new->prev = prevp; 168 1.1 tron new->pos = pos; 169 1.1 tron new->line = linenum; 170 1.1 tron 171 1.1 tron nextp->prev = new; 172 1.1 tron prevp->next = new; 173 1.1 tron 174 1.1 tron /* 175 1.1 tron * Recalculate gaps for the new entry and the neighboring entries. 176 1.1 tron */ 177 1.1 tron calcgap(new); 178 1.1 tron calcgap(nextp); 179 1.1 tron calcgap(prevp); 180 1.1 tron 181 1.1 tron if (spare == NULL) 182 1.1 tron { 183 1.1 tron /* 184 1.1 tron * We have used the spare entry. 185 1.1 tron * Scan the list to find the one with the smallest 186 1.1 tron * gap, take it out and make it the spare. 187 1.1 tron * We should never remove the last one, so stop when 188 1.1 tron * we get to p->next == &anchor. This also avoids 189 1.1 tron * looking at the gap of the last one, which is 190 1.1 tron * not computed by calcgap. 191 1.1 tron */ 192 1.1 tron mingap = anchor.next->gap; 193 1.1 tron for (p = anchor.next; p->next != &anchor; p = p->next) 194 1.1 tron { 195 1.1 tron if (p->gap <= mingap) 196 1.1 tron { 197 1.1 tron spare = p; 198 1.1 tron mingap = p->gap; 199 1.1 tron } 200 1.1 tron } 201 1.1 tron spare->next->prev = spare->prev; 202 1.1 tron spare->prev->next = spare->next; 203 1.1 tron } 204 1.1 tron } 205 1.1 tron 206 1.1 tron /* 207 1.1 tron * If we get stuck in a long loop trying to figure out the 208 1.1 tron * line number, print a message to tell the user what we're doing. 209 1.1 tron */ 210 1.5 simonb static void longloopmessage(void) 211 1.1 tron { 212 1.1 tron ierror("Calculating line numbers", NULL_PARG); 213 1.1 tron } 214 1.1 tron 215 1.1 tron static int loopcount; 216 1.1 tron #if HAVE_TIME 217 1.5 simonb static time_type startime; 218 1.1 tron #endif 219 1.1 tron 220 1.5 simonb static void longish(void) 221 1.1 tron { 222 1.1 tron #if HAVE_TIME 223 1.1 tron if (loopcount >= 0 && ++loopcount > 100) 224 1.1 tron { 225 1.1 tron loopcount = 0; 226 1.1 tron if (get_time() >= startime + LONGTIME) 227 1.1 tron { 228 1.1 tron longloopmessage(); 229 1.1 tron loopcount = -1; 230 1.1 tron } 231 1.1 tron } 232 1.1 tron #else 233 1.1 tron if (loopcount >= 0 && ++loopcount > LONGLOOP) 234 1.1 tron { 235 1.1 tron longloopmessage(); 236 1.1 tron loopcount = -1; 237 1.1 tron } 238 1.1 tron #endif 239 1.1 tron } 240 1.1 tron 241 1.1 tron /* 242 1.1 tron * Turn off line numbers because the user has interrupted 243 1.1 tron * a lengthy line number calculation. 244 1.1 tron */ 245 1.5 simonb static void abort_long(void) 246 1.1 tron { 247 1.5 simonb if (loopcount >= 0) 248 1.5 simonb return; 249 1.1 tron if (linenums == OPT_ONPLUS) 250 1.1 tron /* 251 1.1 tron * We were displaying line numbers, so need to repaint. 252 1.1 tron */ 253 1.1 tron screen_trashed = 1; 254 1.1 tron linenums = 0; 255 1.1 tron error("Line numbers turned off", NULL_PARG); 256 1.1 tron } 257 1.1 tron 258 1.1 tron /* 259 1.1 tron * Find the line number associated with a given position. 260 1.1 tron * Return 0 if we can't figure it out. 261 1.1 tron */ 262 1.5 simonb public LINENUM find_linenum(POSITION pos) 263 1.1 tron { 264 1.5 simonb struct linenum_info *p; 265 1.5 simonb LINENUM linenum; 266 1.1 tron POSITION cpos; 267 1.1 tron 268 1.1 tron if (!linenums) 269 1.1 tron /* 270 1.1 tron * We're not using line numbers. 271 1.1 tron */ 272 1.1 tron return (0); 273 1.1 tron if (pos == NULL_POSITION) 274 1.1 tron /* 275 1.1 tron * Caller doesn't know what he's talking about. 276 1.1 tron */ 277 1.1 tron return (0); 278 1.1 tron if (pos <= ch_zero()) 279 1.1 tron /* 280 1.1 tron * Beginning of file is always line number 1. 281 1.1 tron */ 282 1.1 tron return (1); 283 1.1 tron 284 1.1 tron /* 285 1.1 tron * Find the entry nearest to the position we want. 286 1.1 tron */ 287 1.1 tron for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next) 288 1.1 tron continue; 289 1.1 tron if (p->pos == pos) 290 1.1 tron /* Found it exactly. */ 291 1.1 tron return (p->line); 292 1.1 tron 293 1.1 tron /* 294 1.1 tron * This is the (possibly) time-consuming part. 295 1.1 tron * We start at the line we just found and start 296 1.1 tron * reading the file forward or backward till we 297 1.1 tron * get to the place we want. 298 1.1 tron * 299 1.1 tron * First decide whether we should go forward from the 300 1.1 tron * previous one or backwards from the next one. 301 1.1 tron * The decision is based on which way involves 302 1.1 tron * traversing fewer bytes in the file. 303 1.1 tron */ 304 1.1 tron #if HAVE_TIME 305 1.1 tron startime = get_time(); 306 1.1 tron #endif 307 1.5 simonb loopcount = 0; 308 1.1 tron if (p == &anchor || pos - p->prev->pos < p->pos - pos) 309 1.1 tron { 310 1.1 tron /* 311 1.1 tron * Go forward. 312 1.1 tron */ 313 1.1 tron p = p->prev; 314 1.1 tron if (ch_seek(p->pos)) 315 1.1 tron return (0); 316 1.1 tron for (linenum = p->line, cpos = p->pos; cpos < pos; linenum++) 317 1.1 tron { 318 1.1 tron /* 319 1.1 tron * Allow a signal to abort this loop. 320 1.1 tron */ 321 1.1 tron cpos = forw_raw_line(cpos, (char **)NULL, (int *)NULL); 322 1.1 tron if (ABORT_SIGS()) { 323 1.1 tron abort_long(); 324 1.1 tron return (0); 325 1.1 tron } 326 1.1 tron if (cpos == NULL_POSITION) 327 1.1 tron return (0); 328 1.1 tron longish(); 329 1.1 tron } 330 1.1 tron /* 331 1.1 tron * We might as well cache it. 332 1.1 tron */ 333 1.1 tron add_lnum(linenum, cpos); 334 1.1 tron /* 335 1.1 tron * If the given position is not at the start of a line, 336 1.1 tron * make sure we return the correct line number. 337 1.1 tron */ 338 1.1 tron if (cpos > pos) 339 1.1 tron linenum--; 340 1.1 tron } else 341 1.1 tron { 342 1.1 tron /* 343 1.1 tron * Go backward. 344 1.1 tron */ 345 1.1 tron if (ch_seek(p->pos)) 346 1.1 tron return (0); 347 1.1 tron for (linenum = p->line, cpos = p->pos; cpos > pos; linenum--) 348 1.1 tron { 349 1.1 tron /* 350 1.1 tron * Allow a signal to abort this loop. 351 1.1 tron */ 352 1.1 tron cpos = back_raw_line(cpos, (char **)NULL, (int *)NULL); 353 1.1 tron if (ABORT_SIGS()) { 354 1.1 tron abort_long(); 355 1.1 tron return (0); 356 1.1 tron } 357 1.1 tron if (cpos == NULL_POSITION) 358 1.1 tron return (0); 359 1.1 tron longish(); 360 1.1 tron } 361 1.1 tron /* 362 1.1 tron * We might as well cache it. 363 1.1 tron */ 364 1.1 tron add_lnum(linenum, cpos); 365 1.1 tron } 366 1.5 simonb loopcount = 0; 367 1.1 tron return (linenum); 368 1.1 tron } 369 1.1 tron 370 1.1 tron /* 371 1.1 tron * Find the position of a given line number. 372 1.1 tron * Return NULL_POSITION if we can't figure it out. 373 1.1 tron */ 374 1.5 simonb public POSITION find_pos(LINENUM linenum) 375 1.1 tron { 376 1.5 simonb struct linenum_info *p; 377 1.1 tron POSITION cpos; 378 1.1 tron LINENUM clinenum; 379 1.1 tron 380 1.1 tron if (linenum <= 1) 381 1.1 tron /* 382 1.1 tron * Line number 1 is beginning of file. 383 1.1 tron */ 384 1.1 tron return (ch_zero()); 385 1.1 tron 386 1.1 tron /* 387 1.1 tron * Find the entry nearest to the line number we want. 388 1.1 tron */ 389 1.1 tron for (p = anchor.next; p != &anchor && p->line < linenum; p = p->next) 390 1.1 tron continue; 391 1.1 tron if (p->line == linenum) 392 1.1 tron /* Found it exactly. */ 393 1.1 tron return (p->pos); 394 1.1 tron 395 1.1 tron if (p == &anchor || linenum - p->prev->line < p->line - linenum) 396 1.1 tron { 397 1.1 tron /* 398 1.1 tron * Go forward. 399 1.1 tron */ 400 1.1 tron p = p->prev; 401 1.1 tron if (ch_seek(p->pos)) 402 1.1 tron return (NULL_POSITION); 403 1.1 tron for (clinenum = p->line, cpos = p->pos; clinenum < linenum; clinenum++) 404 1.1 tron { 405 1.1 tron /* 406 1.1 tron * Allow a signal to abort this loop. 407 1.1 tron */ 408 1.1 tron cpos = forw_raw_line(cpos, (char **)NULL, (int *)NULL); 409 1.1 tron if (ABORT_SIGS()) 410 1.1 tron return (NULL_POSITION); 411 1.1 tron if (cpos == NULL_POSITION) 412 1.1 tron return (NULL_POSITION); 413 1.1 tron } 414 1.1 tron } else 415 1.1 tron { 416 1.1 tron /* 417 1.1 tron * Go backward. 418 1.1 tron */ 419 1.1 tron if (ch_seek(p->pos)) 420 1.1 tron return (NULL_POSITION); 421 1.1 tron for (clinenum = p->line, cpos = p->pos; clinenum > linenum; clinenum--) 422 1.1 tron { 423 1.1 tron /* 424 1.1 tron * Allow a signal to abort this loop. 425 1.1 tron */ 426 1.1 tron cpos = back_raw_line(cpos, (char **)NULL, (int *)NULL); 427 1.1 tron if (ABORT_SIGS()) 428 1.1 tron return (NULL_POSITION); 429 1.1 tron if (cpos == NULL_POSITION) 430 1.1 tron return (NULL_POSITION); 431 1.1 tron } 432 1.1 tron } 433 1.1 tron /* 434 1.1 tron * We might as well cache it. 435 1.1 tron */ 436 1.1 tron add_lnum(clinenum, cpos); 437 1.1 tron return (cpos); 438 1.1 tron } 439 1.1 tron 440 1.1 tron /* 441 1.1 tron * Return the line number of the "current" line. 442 1.1 tron * The argument "where" tells which line is to be considered 443 1.1 tron * the "current" line (e.g. TOP, BOTTOM, MIDDLE, etc). 444 1.1 tron */ 445 1.5 simonb public LINENUM currline(int where) 446 1.1 tron { 447 1.1 tron POSITION pos; 448 1.1 tron POSITION len; 449 1.1 tron LINENUM linenum; 450 1.1 tron 451 1.1 tron pos = position(where); 452 1.1 tron len = ch_length(); 453 1.1 tron while (pos == NULL_POSITION && where >= 0 && where < sc_height) 454 1.1 tron pos = position(++where); 455 1.1 tron if (pos == NULL_POSITION) 456 1.1 tron pos = len; 457 1.1 tron linenum = find_linenum(pos); 458 1.1 tron if (pos == len) 459 1.1 tron linenum--; 460 1.1 tron return (linenum); 461 1.1 tron } 462 1.5 simonb 463 1.5 simonb /* 464 1.5 simonb * Scan entire file, counting line numbers. 465 1.5 simonb */ 466 1.5 simonb public void scan_eof(void) 467 1.5 simonb { 468 1.5 simonb POSITION pos = ch_zero(); 469 1.5 simonb LINENUM linenum = 0; 470 1.5 simonb 471 1.5 simonb if (ch_seek(0)) 472 1.5 simonb return; 473 1.5 simonb ierror("Determining length of file", NULL_PARG); 474 1.5 simonb /* 475 1.5 simonb * scanning_eof prevents the "Waiting for data" message from 476 1.5 simonb * overwriting "Determining length of file". 477 1.5 simonb */ 478 1.5 simonb scanning_eof = TRUE; 479 1.5 simonb while (pos != NULL_POSITION) 480 1.5 simonb { 481 1.5 simonb /* For efficiency, only add one every 256 line numbers. */ 482 1.5 simonb if ((linenum++ % 256) == 0) 483 1.5 simonb add_lnum(linenum, pos); 484 1.5 simonb pos = forw_raw_line(pos, (char **)NULL, (int *)NULL); 485 1.5 simonb if (ABORT_SIGS()) 486 1.5 simonb break; 487 1.5 simonb } 488 1.5 simonb scanning_eof = FALSE; 489 1.5 simonb } 490 1.5 simonb 491 1.5 simonb /* 492 1.5 simonb * Return a line number adjusted for display 493 1.5 simonb * (handles the --no-number-headers option). 494 1.5 simonb */ 495 1.5 simonb public LINENUM vlinenum(LINENUM linenum) 496 1.5 simonb { 497 1.5 simonb if (nonum_headers) 498 1.5 simonb linenum = (linenum < header_lines) ? 0 : linenum - header_lines; 499 1.5 simonb return linenum; 500 1.5 simonb } 501