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