1/* vi: set sw=4 ts=4: */ 2/* 3 * Copyright (c) 2002 by David I. Bell 4 * Permission is granted to use, distribute, or modify this source, 5 * provided that this copyright notice remains intact. 6 * 7 * The "ed" built-in command (much simplified) 8 */ 9 10#include "libbb.h" 11 12typedef struct LINE { 13 struct LINE *next; 14 struct LINE *prev; 15 int len; 16 char data[1]; 17} LINE; 18 19 20#define searchString bb_common_bufsiz1 21 22enum { 23 USERSIZE = sizeof(searchString) > 1024 ? 1024 24 : sizeof(searchString) - 1, /* max line length typed in by user */ 25 INITBUF_SIZE = 1024, /* initial buffer size */ 26}; 27 28struct globals { 29 int curNum; 30 int lastNum; 31 int bufUsed; 32 int bufSize; 33 LINE *curLine; 34 char *bufBase; 35 char *bufPtr; 36 char *fileName; 37 LINE lines; 38 smallint dirty; 39 int marks[26]; 40}; 41#define G (*ptr_to_globals) 42#define curLine (G.curLine ) 43#define bufBase (G.bufBase ) 44#define bufPtr (G.bufPtr ) 45#define fileName (G.fileName ) 46#define curNum (G.curNum ) 47#define lastNum (G.lastNum ) 48#define bufUsed (G.bufUsed ) 49#define bufSize (G.bufSize ) 50#define dirty (G.dirty ) 51#define lines (G.lines ) 52#define marks (G.marks ) 53#define INIT_G() do { \ 54 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ 55} while (0) 56 57 58static void doCommands(void); 59static void subCommand(const char *cmd, int num1, int num2); 60static int getNum(const char **retcp, smallint *retHaveNum, int *retNum); 61static int setCurNum(int num); 62static void addLines(int num); 63static int insertLine(int num, const char *data, int len); 64static void deleteLines(int num1, int num2); 65static int printLines(int num1, int num2, int expandFlag); 66static int writeLines(const char *file, int num1, int num2); 67static int readLines(const char *file, int num); 68static int searchLines(const char *str, int num1, int num2); 69static LINE *findLine(int num); 70static int findString(const LINE *lp, const char * str, int len, int offset); 71 72 73static int bad_nums(int num1, int num2, const char *for_what) 74{ 75 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) { 76 bb_error_msg("bad line range for %s", for_what); 77 return 1; 78 } 79 return 0; 80} 81 82 83static char *skip_blank(const char *cp) 84{ 85 while (isblank(*cp)) 86 cp++; 87 return (char *)cp; 88} 89 90 91int ed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 92int ed_main(int argc UNUSED_PARAM, char **argv) 93{ 94 INIT_G(); 95 96 bufSize = INITBUF_SIZE; 97 bufBase = xmalloc(bufSize); 98 bufPtr = bufBase; 99 lines.next = &lines; 100 lines.prev = &lines; 101 102 if (argv[1]) { 103 fileName = xstrdup(argv[1]); 104 if (!readLines(fileName, 1)) { 105 return EXIT_SUCCESS; 106 } 107 if (lastNum) 108 setCurNum(1); 109 dirty = FALSE; 110 } 111 112 doCommands(); 113 return EXIT_SUCCESS; 114} 115 116/* 117 * Read commands until we are told to stop. 118 */ 119static void doCommands(void) 120{ 121 const char *cp; 122 char *endbuf, buf[USERSIZE]; 123 int len, num1, num2; 124 smallint have1, have2; 125 126 while (TRUE) { 127 /* Returns: 128 * -1 on read errors or EOF, or on bare Ctrl-D. 129 * 0 on ctrl-C, 130 * >0 length of input string, including terminating '\n' 131 */ 132 len = read_line_input(": ", buf, sizeof(buf), NULL); 133 if (len <= 0) 134 return; 135 endbuf = &buf[len - 1]; 136 while ((endbuf > buf) && isblank(endbuf[-1])) 137 endbuf--; 138 *endbuf = '\0'; 139 140 cp = skip_blank(buf); 141 have1 = FALSE; 142 have2 = FALSE; 143 144 if ((curNum == 0) && (lastNum > 0)) { 145 curNum = 1; 146 curLine = lines.next; 147 } 148 149 if (!getNum(&cp, &have1, &num1)) 150 continue; 151 152 cp = skip_blank(cp); 153 154 if (*cp == ',') { 155 cp++; 156 if (!getNum(&cp, &have2, &num2)) 157 continue; 158 if (!have1) 159 num1 = 1; 160 if (!have2) 161 num2 = lastNum; 162 have1 = TRUE; 163 have2 = TRUE; 164 } 165 if (!have1) 166 num1 = curNum; 167 if (!have2) 168 num2 = num1; 169 170 switch (*cp++) { 171 case 'a': 172 addLines(num1 + 1); 173 break; 174 175 case 'c': 176 deleteLines(num1, num2); 177 addLines(num1); 178 break; 179 180 case 'd': 181 deleteLines(num1, num2); 182 break; 183 184 case 'f': 185 if (*cp && !isblank(*cp)) { 186 bb_error_msg("bad file command"); 187 break; 188 } 189 cp = skip_blank(cp); 190 if (*cp == '\0') { 191 if (fileName) 192 printf("\"%s\"\n", fileName); 193 else 194 printf("No file name\n"); 195 break; 196 } 197 free(fileName); 198 fileName = xstrdup(cp); 199 break; 200 201 case 'i': 202 addLines(num1); 203 break; 204 205 case 'k': 206 cp = skip_blank(cp); 207 if ((*cp < 'a') || (*cp > 'z') || cp[1]) { 208 bb_error_msg("bad mark name"); 209 break; 210 } 211 marks[*cp - 'a'] = num2; 212 break; 213 214 case 'l': 215 printLines(num1, num2, TRUE); 216 break; 217 218 case 'p': 219 printLines(num1, num2, FALSE); 220 break; 221 222 case 'q': 223 cp = skip_blank(cp); 224 if (have1 || *cp) { 225 bb_error_msg("bad quit command"); 226 break; 227 } 228 if (!dirty) 229 return; 230 len = read_line_input("Really quit? ", buf, 16, NULL); 231 /* read error/EOF - no way to continue */ 232 if (len < 0) 233 return; 234 cp = skip_blank(buf); 235 if ((*cp | 0x20) == 'y') /* Y or y */ 236 return; 237 break; 238 239 case 'r': 240 if (*cp && !isblank(*cp)) { 241 bb_error_msg("bad read command"); 242 break; 243 } 244 cp = skip_blank(cp); 245 if (*cp == '\0') { 246 bb_error_msg("no file name"); 247 break; 248 } 249 if (!have1) 250 num1 = lastNum; 251 if (readLines(cp, num1 + 1)) 252 break; 253 if (fileName == NULL) 254 fileName = xstrdup(cp); 255 break; 256 257 case 's': 258 subCommand(cp, num1, num2); 259 break; 260 261 case 'w': 262 if (*cp && !isblank(*cp)) { 263 bb_error_msg("bad write command"); 264 break; 265 } 266 cp = skip_blank(cp); 267 if (!have1) { 268 num1 = 1; 269 num2 = lastNum; 270 } 271 if (*cp == '\0') 272 cp = fileName; 273 if (cp == NULL) { 274 bb_error_msg("no file name specified"); 275 break; 276 } 277 writeLines(cp, num1, num2); 278 break; 279 280 case 'z': 281 switch (*cp) { 282 case '-': 283 printLines(curNum - 21, curNum, FALSE); 284 break; 285 case '.': 286 printLines(curNum - 11, curNum + 10, FALSE); 287 break; 288 default: 289 printLines(curNum, curNum + 21, FALSE); 290 break; 291 } 292 break; 293 294 case '.': 295 if (have1) { 296 bb_error_msg("no arguments allowed"); 297 break; 298 } 299 printLines(curNum, curNum, FALSE); 300 break; 301 302 case '-': 303 if (setCurNum(curNum - 1)) 304 printLines(curNum, curNum, FALSE); 305 break; 306 307 case '=': 308 printf("%d\n", num1); 309 break; 310 case '\0': 311 if (have1) { 312 printLines(num2, num2, FALSE); 313 break; 314 } 315 if (setCurNum(curNum + 1)) 316 printLines(curNum, curNum, FALSE); 317 break; 318 319 default: 320 bb_error_msg("unimplemented command"); 321 break; 322 } 323 } 324} 325 326 327/* 328 * Do the substitute command. 329 * The current line is set to the last substitution done. 330 */ 331static void subCommand(const char *cmd, int num1, int num2) 332{ 333 char *cp, *oldStr, *newStr, buf[USERSIZE]; 334 int delim, oldLen, newLen, deltaLen, offset; 335 LINE *lp, *nlp; 336 int globalFlag, printFlag, didSub, needPrint; 337 338 if (bad_nums(num1, num2, "substitute")) 339 return; 340 341 globalFlag = FALSE; 342 printFlag = FALSE; 343 didSub = FALSE; 344 needPrint = FALSE; 345 346 /* 347 * Copy the command so we can modify it. 348 */ 349 strcpy(buf, cmd); 350 cp = buf; 351 352 if (isblank(*cp) || (*cp == '\0')) { 353 bb_error_msg("bad delimiter for substitute"); 354 return; 355 } 356 357 delim = *cp++; 358 oldStr = cp; 359 360 cp = strchr(cp, delim); 361 if (cp == NULL) { 362 bb_error_msg("missing 2nd delimiter for substitute"); 363 return; 364 } 365 366 *cp++ = '\0'; 367 368 newStr = cp; 369 cp = strchr(cp, delim); 370 371 if (cp) 372 *cp++ = '\0'; 373 else 374 cp = (char*)""; 375 376 while (*cp) switch (*cp++) { 377 case 'g': 378 globalFlag = TRUE; 379 break; 380 case 'p': 381 printFlag = TRUE; 382 break; 383 default: 384 bb_error_msg("unknown option for substitute"); 385 return; 386 } 387 388 if (*oldStr == '\0') { 389 if (searchString[0] == '\0') { 390 bb_error_msg("no previous search string"); 391 return; 392 } 393 oldStr = searchString; 394 } 395 396 if (oldStr != searchString) 397 strcpy(searchString, oldStr); 398 399 lp = findLine(num1); 400 if (lp == NULL) 401 return; 402 403 oldLen = strlen(oldStr); 404 newLen = strlen(newStr); 405 deltaLen = newLen - oldLen; 406 offset = 0; 407 nlp = NULL; 408 409 while (num1 <= num2) { 410 offset = findString(lp, oldStr, oldLen, offset); 411 412 if (offset < 0) { 413 if (needPrint) { 414 printLines(num1, num1, FALSE); 415 needPrint = FALSE; 416 } 417 offset = 0; 418 lp = lp->next; 419 num1++; 420 continue; 421 } 422 423 needPrint = printFlag; 424 didSub = TRUE; 425 dirty = TRUE; 426 427 /* 428 * If the replacement string is the same size or shorter 429 * than the old string, then the substitution is easy. 430 */ 431 if (deltaLen <= 0) { 432 memcpy(&lp->data[offset], newStr, newLen); 433 if (deltaLen) { 434 memcpy(&lp->data[offset + newLen], 435 &lp->data[offset + oldLen], 436 lp->len - offset - oldLen); 437 438 lp->len += deltaLen; 439 } 440 offset += newLen; 441 if (globalFlag) 442 continue; 443 if (needPrint) { 444 printLines(num1, num1, FALSE); 445 needPrint = FALSE; 446 } 447 lp = lp->next; 448 num1++; 449 continue; 450 } 451 452 /* 453 * The new string is larger, so allocate a new line 454 * structure and use that. Link it in in place of 455 * the old line structure. 456 */ 457 nlp = xmalloc(sizeof(LINE) + lp->len + deltaLen); 458 459 nlp->len = lp->len + deltaLen; 460 461 memcpy(nlp->data, lp->data, offset); 462 memcpy(&nlp->data[offset], newStr, newLen); 463 memcpy(&nlp->data[offset + newLen], 464 &lp->data[offset + oldLen], 465 lp->len - offset - oldLen); 466 467 nlp->next = lp->next; 468 nlp->prev = lp->prev; 469 nlp->prev->next = nlp; 470 nlp->next->prev = nlp; 471 472 if (curLine == lp) 473 curLine = nlp; 474 475 free(lp); 476 lp = nlp; 477 478 offset += newLen; 479 480 if (globalFlag) 481 continue; 482 483 if (needPrint) { 484 printLines(num1, num1, FALSE); 485 needPrint = FALSE; 486 } 487 488 lp = lp->next; 489 num1++; 490 } 491 492 if (!didSub) 493 bb_error_msg("no substitutions found for \"%s\"", oldStr); 494} 495 496 497/* 498 * Search a line for the specified string starting at the specified 499 * offset in the line. Returns the offset of the found string, or -1. 500 */ 501static int findString(const LINE *lp, const char *str, int len, int offset) 502{ 503 int left; 504 const char *cp, *ncp; 505 506 cp = &lp->data[offset]; 507 left = lp->len - offset; 508 509 while (left >= len) { 510 ncp = memchr(cp, *str, left); 511 if (ncp == NULL) 512 return -1; 513 left -= (ncp - cp); 514 if (left < len) 515 return -1; 516 cp = ncp; 517 if (memcmp(cp, str, len) == 0) 518 return (cp - lp->data); 519 cp++; 520 left--; 521 } 522 523 return -1; 524} 525 526 527/* 528 * Add lines which are typed in by the user. 529 * The lines are inserted just before the specified line number. 530 * The lines are terminated by a line containing a single dot (ugly!), 531 * or by an end of file. 532 */ 533static void addLines(int num) 534{ 535 int len; 536 char buf[USERSIZE + 1]; 537 538 while (1) { 539 /* Returns: 540 * -1 on read errors or EOF, or on bare Ctrl-D. 541 * 0 on ctrl-C, 542 * >0 length of input string, including terminating '\n' 543 */ 544 len = read_line_input("", buf, sizeof(buf), NULL); 545 if (len <= 0) { 546 /* Previously, ctrl-C was exiting to shell. 547 * Now we exit to ed prompt. Is in important? */ 548 return; 549 } 550 if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0')) 551 return; 552 if (!insertLine(num++, buf, len)) 553 return; 554 } 555} 556 557 558/* 559 * Parse a line number argument if it is present. This is a sum 560 * or difference of numbers, '.', '$', 'x, or a search string. 561 * Returns TRUE if successful (whether or not there was a number). 562 * Returns FALSE if there was a parsing error, with a message output. 563 * Whether there was a number is returned indirectly, as is the number. 564 * The character pointer which stopped the scan is also returned. 565 */ 566static int getNum(const char **retcp, smallint *retHaveNum, int *retNum) 567{ 568 const char *cp; 569 char *endStr, str[USERSIZE]; 570 int value, num; 571 smallint haveNum, minus; 572 573 cp = *retcp; 574 value = 0; 575 haveNum = FALSE; 576 minus = 0; 577 578 while (TRUE) { 579 cp = skip_blank(cp); 580 581 switch (*cp) { 582 case '.': 583 haveNum = TRUE; 584 num = curNum; 585 cp++; 586 break; 587 588 case '$': 589 haveNum = TRUE; 590 num = lastNum; 591 cp++; 592 break; 593 594 case '\'': 595 cp++; 596 if ((*cp < 'a') || (*cp > 'z')) { 597 bb_error_msg("bad mark name"); 598 return FALSE; 599 } 600 haveNum = TRUE; 601 num = marks[*cp++ - 'a']; 602 break; 603 604 case '/': 605 strcpy(str, ++cp); 606 endStr = strchr(str, '/'); 607 if (endStr) { 608 *endStr++ = '\0'; 609 cp += (endStr - str); 610 } else 611 cp = ""; 612 num = searchLines(str, curNum, lastNum); 613 if (num == 0) 614 return FALSE; 615 haveNum = TRUE; 616 break; 617 618 default: 619 if (!isdigit(*cp)) { 620 *retcp = cp; 621 *retHaveNum = haveNum; 622 *retNum = value; 623 return TRUE; 624 } 625 num = 0; 626 while (isdigit(*cp)) 627 num = num * 10 + *cp++ - '0'; 628 haveNum = TRUE; 629 break; 630 } 631 632 value += (minus ? -num : num); 633 634 cp = skip_blank(cp); 635 636 switch (*cp) { 637 case '-': 638 minus = 1; 639 cp++; 640 break; 641 642 case '+': 643 minus = 0; 644 cp++; 645 break; 646 647 default: 648 *retcp = cp; 649 *retHaveNum = haveNum; 650 *retNum = value; 651 return TRUE; 652 } 653 } 654} 655 656 657/* 658 * Read lines from a file at the specified line number. 659 * Returns TRUE if the file was successfully read. 660 */ 661static int readLines(const char *file, int num) 662{ 663 int fd, cc; 664 int len, lineCount, charCount; 665 char *cp; 666 667 if ((num < 1) || (num > lastNum + 1)) { 668 bb_error_msg("bad line for read"); 669 return FALSE; 670 } 671 672 fd = open(file, 0); 673 if (fd < 0) { 674 perror(file); 675 return FALSE; 676 } 677 678 bufPtr = bufBase; 679 bufUsed = 0; 680 lineCount = 0; 681 charCount = 0; 682 cc = 0; 683 684 printf("\"%s\", ", file); 685 fflush_all(); 686 687 do { 688 cp = memchr(bufPtr, '\n', bufUsed); 689 690 if (cp) { 691 len = (cp - bufPtr) + 1; 692 if (!insertLine(num, bufPtr, len)) { 693 close(fd); 694 return FALSE; 695 } 696 bufPtr += len; 697 bufUsed -= len; 698 charCount += len; 699 lineCount++; 700 num++; 701 continue; 702 } 703 704 if (bufPtr != bufBase) { 705 memcpy(bufBase, bufPtr, bufUsed); 706 bufPtr = bufBase + bufUsed; 707 } 708 709 if (bufUsed >= bufSize) { 710 len = (bufSize * 3) / 2; 711 cp = xrealloc(bufBase, len); 712 bufBase = cp; 713 bufPtr = bufBase + bufUsed; 714 bufSize = len; 715 } 716 717 cc = safe_read(fd, bufPtr, bufSize - bufUsed); 718 bufUsed += cc; 719 bufPtr = bufBase; 720 721 } while (cc > 0); 722 723 if (cc < 0) { 724 perror(file); 725 close(fd); 726 return FALSE; 727 } 728 729 if (bufUsed) { 730 if (!insertLine(num, bufPtr, bufUsed)) { 731 close(fd); 732 return -1; 733 } 734 lineCount++; 735 charCount += bufUsed; 736 } 737 738 close(fd); 739 740 printf("%d lines%s, %d chars\n", lineCount, 741 (bufUsed ? " (incomplete)" : ""), charCount); 742 743 return TRUE; 744} 745 746 747/* 748 * Write the specified lines out to the specified file. 749 * Returns TRUE if successful, or FALSE on an error with a message output. 750 */ 751static int writeLines(const char *file, int num1, int num2) 752{ 753 LINE *lp; 754 int fd, lineCount, charCount; 755 756 if (bad_nums(num1, num2, "write")) 757 return FALSE; 758 759 lineCount = 0; 760 charCount = 0; 761 762 fd = creat(file, 0666); 763 if (fd < 0) { 764 perror(file); 765 return FALSE; 766 } 767 768 printf("\"%s\", ", file); 769 fflush_all(); 770 771 lp = findLine(num1); 772 if (lp == NULL) { 773 close(fd); 774 return FALSE; 775 } 776 777 while (num1++ <= num2) { 778 if (full_write(fd, lp->data, lp->len) != lp->len) { 779 perror(file); 780 close(fd); 781 return FALSE; 782 } 783 charCount += lp->len; 784 lineCount++; 785 lp = lp->next; 786 } 787 788 if (close(fd) < 0) { 789 perror(file); 790 return FALSE; 791 } 792 793 printf("%d lines, %d chars\n", lineCount, charCount); 794 return TRUE; 795} 796 797 798/* 799 * Print lines in a specified range. 800 * The last line printed becomes the current line. 801 * If expandFlag is TRUE, then the line is printed specially to 802 * show magic characters. 803 */ 804static int printLines(int num1, int num2, int expandFlag) 805{ 806 const LINE *lp; 807 const char *cp; 808 int ch, count; 809 810 if (bad_nums(num1, num2, "print")) 811 return FALSE; 812 813 lp = findLine(num1); 814 if (lp == NULL) 815 return FALSE; 816 817 while (num1 <= num2) { 818 if (!expandFlag) { 819 write(STDOUT_FILENO, lp->data, lp->len); 820 setCurNum(num1++); 821 lp = lp->next; 822 continue; 823 } 824 825 /* 826 * Show control characters and characters with the 827 * high bit set specially. 828 */ 829 cp = lp->data; 830 count = lp->len; 831 832 if ((count > 0) && (cp[count - 1] == '\n')) 833 count--; 834 835 while (count-- > 0) { 836 ch = (unsigned char) *cp++; 837 fputc_printable(ch | PRINTABLE_META, stdout); 838 } 839 840 fputs("$\n", stdout); 841 842 setCurNum(num1++); 843 lp = lp->next; 844 } 845 846 return TRUE; 847} 848 849 850/* 851 * Insert a new line with the specified text. 852 * The line is inserted so as to become the specified line, 853 * thus pushing any existing and further lines down one. 854 * The inserted line is also set to become the current line. 855 * Returns TRUE if successful. 856 */ 857static int insertLine(int num, const char *data, int len) 858{ 859 LINE *newLp, *lp; 860 861 if ((num < 1) || (num > lastNum + 1)) { 862 bb_error_msg("inserting at bad line number"); 863 return FALSE; 864 } 865 866 newLp = xmalloc(sizeof(LINE) + len - 1); 867 868 memcpy(newLp->data, data, len); 869 newLp->len = len; 870 871 if (num > lastNum) 872 lp = &lines; 873 else { 874 lp = findLine(num); 875 if (lp == NULL) { 876 free((char *) newLp); 877 return FALSE; 878 } 879 } 880 881 newLp->next = lp; 882 newLp->prev = lp->prev; 883 lp->prev->next = newLp; 884 lp->prev = newLp; 885 886 lastNum++; 887 dirty = TRUE; 888 return setCurNum(num); 889} 890 891 892/* 893 * Delete lines from the given range. 894 */ 895static void deleteLines(int num1, int num2) 896{ 897 LINE *lp, *nlp, *plp; 898 int count; 899 900 if (bad_nums(num1, num2, "delete")) 901 return; 902 903 lp = findLine(num1); 904 if (lp == NULL) 905 return; 906 907 if ((curNum >= num1) && (curNum <= num2)) { 908 if (num2 < lastNum) 909 setCurNum(num2 + 1); 910 else if (num1 > 1) 911 setCurNum(num1 - 1); 912 else 913 curNum = 0; 914 } 915 916 count = num2 - num1 + 1; 917 if (curNum > num2) 918 curNum -= count; 919 lastNum -= count; 920 921 while (count-- > 0) { 922 nlp = lp->next; 923 plp = lp->prev; 924 plp->next = nlp; 925 nlp->prev = plp; 926 free(lp); 927 lp = nlp; 928 } 929 930 dirty = TRUE; 931} 932 933 934/* 935 * Search for a line which contains the specified string. 936 * If the string is "", then the previously searched for string 937 * is used. The currently searched for string is saved for future use. 938 * Returns the line number which matches, or 0 if there was no match 939 * with an error printed. 940 */ 941static NOINLINE int searchLines(const char *str, int num1, int num2) 942{ 943 const LINE *lp; 944 int len; 945 946 if (bad_nums(num1, num2, "search")) 947 return 0; 948 949 if (*str == '\0') { 950 if (searchString[0] == '\0') { 951 bb_error_msg("no previous search string"); 952 return 0; 953 } 954 str = searchString; 955 } 956 957 if (str != searchString) 958 strcpy(searchString, str); 959 960 len = strlen(str); 961 962 lp = findLine(num1); 963 if (lp == NULL) 964 return 0; 965 966 while (num1 <= num2) { 967 if (findString(lp, str, len, 0) >= 0) 968 return num1; 969 num1++; 970 lp = lp->next; 971 } 972 973 bb_error_msg("can't find string \"%s\"", str); 974 return 0; 975} 976 977 978/* 979 * Return a pointer to the specified line number. 980 */ 981static LINE *findLine(int num) 982{ 983 LINE *lp; 984 int lnum; 985 986 if ((num < 1) || (num > lastNum)) { 987 bb_error_msg("line number %d does not exist", num); 988 return NULL; 989 } 990 991 if (curNum <= 0) { 992 curNum = 1; 993 curLine = lines.next; 994 } 995 996 if (num == curNum) 997 return curLine; 998 999 lp = curLine; 1000 lnum = curNum; 1001 if (num < (curNum / 2)) { 1002 lp = lines.next; 1003 lnum = 1; 1004 } else if (num > ((curNum + lastNum) / 2)) { 1005 lp = lines.prev; 1006 lnum = lastNum; 1007 } 1008 1009 while (lnum < num) { 1010 lp = lp->next; 1011 lnum++; 1012 } 1013 1014 while (lnum > num) { 1015 lp = lp->prev; 1016 lnum--; 1017 } 1018 return lp; 1019} 1020 1021 1022/* 1023 * Set the current line number. 1024 * Returns TRUE if successful. 1025 */ 1026static int setCurNum(int num) 1027{ 1028 LINE *lp; 1029 1030 lp = findLine(num); 1031 if (lp == NULL) 1032 return FALSE; 1033 curNum = num; 1034 curLine = lp; 1035 return TRUE; 1036} 1037