| /* vi: set sw=4 ts=4: */ |
| /* |
| * Copyright (c) 2002 by David I. Bell |
| * Permission is granted to use, distribute, or modify this source, |
| * provided that this copyright notice remains intact. |
| * |
| * The "ed" built-in command (much simplified) |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <string.h> |
| #include <time.h> |
| #include <ctype.h> |
| #include <sys/param.h> |
| #include "busybox.h" |
| |
| #define USERSIZE 1024 /* max line length typed in by user */ |
| #define INITBUF_SIZE 1024 /* initial buffer size */ |
| typedef struct LINE { |
| struct LINE *next; |
| struct LINE *prev; |
| int len; |
| char data[1]; |
| } LINE; |
| |
| static LINE lines, *curLine; |
| static int curNum, lastNum, marks[26], dirty; |
| static char *bufBase, *bufPtr, *fileName, searchString[USERSIZE]; |
| static int bufUsed, bufSize; |
| |
| static void doCommands(void); |
| static void subCommand(const char *cmd, int num1, int num2); |
| static int getNum(const char **retcp, int *retHaveNum, int *retNum); |
| static int setCurNum(int num); |
| static int initEdit(void); |
| static void termEdit(void); |
| static void addLines(int num); |
| static int insertLine(int num, const char *data, int len); |
| static int deleteLines(int num1, int num2); |
| static int printLines(int num1, int num2, int expandFlag); |
| static int writeLines(const char *file, int num1, int num2); |
| static int readLines(const char *file, int num); |
| static int searchLines(const char *str, int num1, int num2); |
| static LINE *findLine(int num); |
| |
| static int findString(const LINE *lp, const char * str, int len, int offset); |
| |
| int ed_main(int argc, char **argv) |
| { |
| if (!initEdit()) |
| return EXIT_FAILURE; |
| |
| if (argc > 1) { |
| fileName = strdup(argv[1]); |
| |
| if (fileName == NULL) { |
| bb_error_msg("No memory"); |
| termEdit(); |
| return EXIT_SUCCESS; |
| } |
| |
| if (!readLines(fileName, 1)) { |
| termEdit(); |
| return EXIT_SUCCESS; |
| } |
| |
| if (lastNum) |
| setCurNum(1); |
| |
| dirty = FALSE; |
| } |
| |
| doCommands(); |
| |
| termEdit(); |
| return EXIT_SUCCESS; |
| } |
| |
| /* |
| * Read commands until we are told to stop. |
| */ |
| static void doCommands(void) |
| { |
| const char *cp; |
| char *endbuf, *newname, buf[USERSIZE]; |
| int len, num1, num2, have1, have2; |
| |
| while (TRUE) |
| { |
| printf(": "); |
| fflush(stdout); |
| |
| if (fgets(buf, sizeof(buf), stdin) == NULL) |
| return; |
| |
| len = strlen(buf); |
| |
| if (len == 0) |
| return; |
| |
| endbuf = &buf[len - 1]; |
| |
| if (*endbuf != '\n') |
| { |
| bb_error_msg("Command line too long"); |
| |
| do |
| { |
| len = fgetc(stdin); |
| } |
| while ((len != EOF) && (len != '\n')); |
| |
| continue; |
| } |
| |
| while ((endbuf > buf) && isblank(endbuf[-1])) |
| endbuf--; |
| |
| *endbuf = '\0'; |
| |
| cp = buf; |
| |
| while (isblank(*cp)) |
| cp++; |
| |
| have1 = FALSE; |
| have2 = FALSE; |
| |
| if ((curNum == 0) && (lastNum > 0)) |
| { |
| curNum = 1; |
| curLine = lines.next; |
| } |
| |
| if (!getNum(&cp, &have1, &num1)) |
| continue; |
| |
| while (isblank(*cp)) |
| cp++; |
| |
| if (*cp == ',') |
| { |
| cp++; |
| |
| if (!getNum(&cp, &have2, &num2)) |
| continue; |
| |
| if (!have1) |
| num1 = 1; |
| |
| if (!have2) |
| num2 = lastNum; |
| |
| have1 = TRUE; |
| have2 = TRUE; |
| } |
| |
| if (!have1) |
| num1 = curNum; |
| |
| if (!have2) |
| num2 = num1; |
| |
| switch (*cp++) |
| { |
| case 'a': |
| addLines(num1 + 1); |
| break; |
| |
| case 'c': |
| deleteLines(num1, num2); |
| addLines(num1); |
| break; |
| |
| case 'd': |
| deleteLines(num1, num2); |
| break; |
| |
| case 'f': |
| if (*cp && !isblank(*cp)) |
| { |
| bb_error_msg("Bad file command"); |
| break; |
| } |
| |
| while (isblank(*cp)) |
| cp++; |
| |
| if (*cp == '\0') |
| { |
| if (fileName) |
| printf("\"%s\"\n", fileName); |
| else |
| printf("No file name\n"); |
| |
| break; |
| } |
| |
| newname = strdup(cp); |
| |
| if (newname == NULL) |
| { |
| bb_error_msg("No memory for file name"); |
| break; |
| } |
| |
| if (fileName) |
| free(fileName); |
| |
| fileName = newname; |
| break; |
| |
| case 'i': |
| addLines(num1); |
| break; |
| |
| case 'k': |
| while (isblank(*cp)) |
| cp++; |
| |
| if ((*cp < 'a') || (*cp > 'a') || cp[1]) |
| { |
| bb_error_msg("Bad mark name"); |
| break; |
| } |
| |
| marks[*cp - 'a'] = num2; |
| break; |
| |
| case 'l': |
| printLines(num1, num2, TRUE); |
| break; |
| |
| case 'p': |
| printLines(num1, num2, FALSE); |
| break; |
| |
| case 'q': |
| while (isblank(*cp)) |
| cp++; |
| |
| if (have1 || *cp) |
| { |
| bb_error_msg("Bad quit command"); |
| break; |
| } |
| |
| if (!dirty) |
| return; |
| |
| printf("Really quit? "); |
| fflush(stdout); |
| |
| buf[0] = '\0'; |
| fgets(buf, sizeof(buf), stdin); |
| cp = buf; |
| |
| while (isblank(*cp)) |
| cp++; |
| |
| if ((*cp == 'y') || (*cp == 'Y')) |
| return; |
| |
| break; |
| |
| case 'r': |
| if (*cp && !isblank(*cp)) |
| { |
| bb_error_msg("Bad read command"); |
| break; |
| } |
| |
| while (isblank(*cp)) |
| cp++; |
| |
| if (*cp == '\0') |
| { |
| bb_error_msg("No file name"); |
| break; |
| } |
| |
| if (!have1) |
| num1 = lastNum; |
| |
| if (readLines(cp, num1 + 1)) |
| break; |
| |
| if (fileName == NULL) |
| fileName = strdup(cp); |
| |
| break; |
| |
| case 's': |
| subCommand(cp, num1, num2); |
| break; |
| |
| case 'w': |
| if (*cp && !isblank(*cp)) |
| { |
| bb_error_msg("Bad write command"); |
| break; |
| } |
| |
| while (isblank(*cp)) |
| cp++; |
| |
| if (!have1) { |
| num1 = 1; |
| num2 = lastNum; |
| } |
| |
| if (*cp == '\0') |
| cp = fileName; |
| |
| if (cp == NULL) |
| { |
| bb_error_msg("No file name specified"); |
| break; |
| } |
| |
| writeLines(cp, num1, num2); |
| break; |
| |
| case 'z': |
| switch (*cp) |
| { |
| case '-': |
| printLines(curNum-21, curNum, FALSE); |
| break; |
| case '.': |
| printLines(curNum-11, curNum+10, FALSE); |
| break; |
| default: |
| printLines(curNum, curNum+21, FALSE); |
| break; |
| } |
| break; |
| |
| case '.': |
| if (have1) |
| { |
| bb_error_msg("No arguments allowed"); |
| break; |
| } |
| |
| printLines(curNum, curNum, FALSE); |
| break; |
| |
| case '-': |
| if (setCurNum(curNum - 1)) |
| printLines(curNum, curNum, FALSE); |
| |
| break; |
| |
| case '=': |
| printf("%d\n", num1); |
| break; |
| |
| case '\0': |
| if (have1) |
| { |
| printLines(num2, num2, FALSE); |
| break; |
| } |
| |
| if (setCurNum(curNum + 1)) |
| printLines(curNum, curNum, FALSE); |
| |
| break; |
| |
| default: |
| bb_error_msg("Unimplemented command"); |
| break; |
| } |
| } |
| } |
| |
| |
| /* |
| * Do the substitute command. |
| * The current line is set to the last substitution done. |
| */ |
| static void subCommand(const char * cmd, int num1, int num2) |
| { |
| char *cp, *oldStr, *newStr, buf[USERSIZE]; |
| int delim, oldLen, newLen, deltaLen, offset; |
| LINE *lp, *nlp; |
| int globalFlag, printFlag, didSub, needPrint; |
| |
| if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) |
| { |
| bb_error_msg("Bad line range for substitute"); |
| |
| return; |
| } |
| |
| globalFlag = FALSE; |
| printFlag = FALSE; |
| didSub = FALSE; |
| needPrint = FALSE; |
| |
| /* |
| * Copy the command so we can modify it. |
| */ |
| strcpy(buf, cmd); |
| cp = buf; |
| |
| if (isblank(*cp) || (*cp == '\0')) |
| { |
| bb_error_msg("Bad delimiter for substitute"); |
| |
| return; |
| } |
| |
| delim = *cp++; |
| oldStr = cp; |
| |
| cp = strchr(cp, delim); |
| |
| if (cp == NULL) |
| { |
| bb_error_msg("Missing 2nd delimiter for substitute"); |
| |
| return; |
| } |
| |
| *cp++ = '\0'; |
| |
| newStr = cp; |
| cp = strchr(cp, delim); |
| |
| if (cp) |
| *cp++ = '\0'; |
| else |
| cp = ""; |
| |
| while (*cp) switch (*cp++) |
| { |
| case 'g': |
| globalFlag = TRUE; |
| break; |
| |
| case 'p': |
| printFlag = TRUE; |
| break; |
| |
| default: |
| bb_error_msg("Unknown option for substitute"); |
| |
| return; |
| } |
| |
| if (*oldStr == '\0') |
| { |
| if (searchString[0] == '\0') |
| { |
| bb_error_msg("No previous search string"); |
| |
| return; |
| } |
| |
| oldStr = searchString; |
| } |
| |
| if (oldStr != searchString) |
| strcpy(searchString, oldStr); |
| |
| lp = findLine(num1); |
| |
| if (lp == NULL) |
| return; |
| |
| oldLen = strlen(oldStr); |
| newLen = strlen(newStr); |
| deltaLen = newLen - oldLen; |
| offset = 0; |
| nlp = NULL; |
| |
| while (num1 <= num2) |
| { |
| offset = findString(lp, oldStr, oldLen, offset); |
| |
| if (offset < 0) |
| { |
| if (needPrint) |
| { |
| printLines(num1, num1, FALSE); |
| needPrint = FALSE; |
| } |
| |
| offset = 0; |
| lp = lp->next; |
| num1++; |
| |
| continue; |
| } |
| |
| needPrint = printFlag; |
| didSub = TRUE; |
| dirty = TRUE; |
| |
| /* |
| * If the replacement string is the same size or shorter |
| * than the old string, then the substitution is easy. |
| */ |
| if (deltaLen <= 0) |
| { |
| memcpy(&lp->data[offset], newStr, newLen); |
| |
| if (deltaLen) |
| { |
| memcpy(&lp->data[offset + newLen], |
| &lp->data[offset + oldLen], |
| lp->len - offset - oldLen); |
| |
| lp->len += deltaLen; |
| } |
| |
| offset += newLen; |
| |
| if (globalFlag) |
| continue; |
| |
| if (needPrint) |
| { |
| printLines(num1, num1, FALSE); |
| needPrint = FALSE; |
| } |
| |
| lp = lp->next; |
| num1++; |
| |
| continue; |
| } |
| |
| /* |
| * The new string is larger, so allocate a new line |
| * structure and use that. Link it in in place of |
| * the old line structure. |
| */ |
| nlp = (LINE *) malloc(sizeof(LINE) + lp->len + deltaLen); |
| |
| if (nlp == NULL) |
| { |
| bb_error_msg("Cannot get memory for line"); |
| |
| return; |
| } |
| |
| nlp->len = lp->len + deltaLen; |
| |
| memcpy(nlp->data, lp->data, offset); |
| |
| memcpy(&nlp->data[offset], newStr, newLen); |
| |
| memcpy(&nlp->data[offset + newLen], |
| &lp->data[offset + oldLen], |
| lp->len - offset - oldLen); |
| |
| nlp->next = lp->next; |
| nlp->prev = lp->prev; |
| nlp->prev->next = nlp; |
| nlp->next->prev = nlp; |
| |
| if (curLine == lp) |
| curLine = nlp; |
| |
| free(lp); |
| lp = nlp; |
| |
| offset += newLen; |
| |
| if (globalFlag) |
| continue; |
| |
| if (needPrint) |
| { |
| printLines(num1, num1, FALSE); |
| needPrint = FALSE; |
| } |
| |
| lp = lp->next; |
| num1++; |
| } |
| |
| if (!didSub) |
| bb_error_msg("No substitutions found for \"%s\"", oldStr); |
| } |
| |
| |
| /* |
| * Search a line for the specified string starting at the specified |
| * offset in the line. Returns the offset of the found string, or -1. |
| */ |
| static int findString( const LINE * lp, const char * str, int len, int offset) |
| { |
| int left; |
| const char *cp, *ncp; |
| |
| cp = &lp->data[offset]; |
| left = lp->len - offset; |
| |
| while (left >= len) |
| { |
| ncp = memchr(cp, *str, left); |
| |
| if (ncp == NULL) |
| return -1; |
| |
| left -= (ncp - cp); |
| |
| if (left < len) |
| return -1; |
| |
| cp = ncp; |
| |
| if (memcmp(cp, str, len) == 0) |
| return (cp - lp->data); |
| |
| cp++; |
| left--; |
| } |
| |
| return -1; |
| } |
| |
| |
| /* |
| * Add lines which are typed in by the user. |
| * The lines are inserted just before the specified line number. |
| * The lines are terminated by a line containing a single dot (ugly!), |
| * or by an end of file. |
| */ |
| static void addLines(int num) |
| { |
| int len; |
| char buf[USERSIZE + 1]; |
| |
| while (fgets(buf, sizeof(buf), stdin)) |
| { |
| if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0')) |
| return; |
| |
| len = strlen(buf); |
| |
| if (len == 0) |
| return; |
| |
| if (buf[len - 1] != '\n') |
| { |
| bb_error_msg("Line too long"); |
| |
| do |
| { |
| len = fgetc(stdin); |
| } |
| while ((len != EOF) && (len != '\n')); |
| |
| return; |
| } |
| |
| if (!insertLine(num++, buf, len)) |
| return; |
| } |
| } |
| |
| |
| /* |
| * Parse a line number argument if it is present. This is a sum |
| * or difference of numbers, '.', '$', 'x, or a search string. |
| * Returns TRUE if successful (whether or not there was a number). |
| * Returns FALSE if there was a parsing error, with a message output. |
| * Whether there was a number is returned indirectly, as is the number. |
| * The character pointer which stopped the scan is also returned. |
| */ |
| static int getNum(const char **retcp, int *retHaveNum, int *retNum) |
| { |
| const char *cp; |
| char *endStr, str[USERSIZE]; |
| int haveNum, value, num, sign; |
| |
| cp = *retcp; |
| haveNum = FALSE; |
| value = 0; |
| sign = 1; |
| |
| while (TRUE) |
| { |
| while (isblank(*cp)) |
| cp++; |
| |
| switch (*cp) |
| { |
| case '.': |
| haveNum = TRUE; |
| num = curNum; |
| cp++; |
| break; |
| |
| case '$': |
| haveNum = TRUE; |
| num = lastNum; |
| cp++; |
| break; |
| |
| case '\'': |
| cp++; |
| |
| if ((*cp < 'a') || (*cp > 'z')) |
| { |
| bb_error_msg("Bad mark name"); |
| |
| return FALSE; |
| } |
| |
| haveNum = TRUE; |
| num = marks[*cp++ - 'a']; |
| break; |
| |
| case '/': |
| strcpy(str, ++cp); |
| endStr = strchr(str, '/'); |
| |
| if (endStr) |
| { |
| *endStr++ = '\0'; |
| cp += (endStr - str); |
| } |
| else |
| cp = ""; |
| |
| num = searchLines(str, curNum, lastNum); |
| |
| if (num == 0) |
| return FALSE; |
| |
| haveNum = TRUE; |
| break; |
| |
| default: |
| if (!isdigit(*cp)) |
| { |
| *retcp = cp; |
| *retHaveNum = haveNum; |
| *retNum = value; |
| |
| return TRUE; |
| } |
| |
| num = 0; |
| |
| while (isdigit(*cp)) |
| num = num * 10 + *cp++ - '0'; |
| |
| haveNum = TRUE; |
| break; |
| } |
| |
| value += num * sign; |
| |
| while (isblank(*cp)) |
| cp++; |
| |
| switch (*cp) |
| { |
| case '-': |
| sign = -1; |
| cp++; |
| break; |
| |
| case '+': |
| sign = 1; |
| cp++; |
| break; |
| |
| default: |
| *retcp = cp; |
| *retHaveNum = haveNum; |
| *retNum = value; |
| |
| return TRUE; |
| } |
| } |
| } |
| |
| |
| /* |
| * Initialize everything for editing. |
| */ |
| static int initEdit(void) |
| { |
| int i; |
| |
| bufSize = INITBUF_SIZE; |
| bufBase = malloc(bufSize); |
| |
| if (bufBase == NULL) |
| { |
| bb_error_msg("No memory for buffer"); |
| |
| return FALSE; |
| } |
| |
| bufPtr = bufBase; |
| bufUsed = 0; |
| |
| lines.next = &lines; |
| lines.prev = &lines; |
| |
| curLine = NULL; |
| curNum = 0; |
| lastNum = 0; |
| dirty = FALSE; |
| fileName = NULL; |
| searchString[0] = '\0'; |
| |
| for (i = 0; i < 26; i++) |
| marks[i] = 0; |
| |
| return TRUE; |
| } |
| |
| |
| /* |
| * Finish editing. |
| */ |
| static void termEdit(void) |
| { |
| if (bufBase) |
| free(bufBase); |
| |
| bufBase = NULL; |
| bufPtr = NULL; |
| bufSize = 0; |
| bufUsed = 0; |
| |
| if (fileName) |
| free(fileName); |
| |
| fileName = NULL; |
| |
| searchString[0] = '\0'; |
| |
| if (lastNum) |
| deleteLines(1, lastNum); |
| |
| lastNum = 0; |
| curNum = 0; |
| curLine = NULL; |
| } |
| |
| |
| /* |
| * Read lines from a file at the specified line number. |
| * Returns TRUE if the file was successfully read. |
| */ |
| static int readLines(const char * file, int num) |
| { |
| int fd, cc; |
| int len, lineCount, charCount; |
| char *cp; |
| |
| if ((num < 1) || (num > lastNum + 1)) |
| { |
| bb_error_msg("Bad line for read"); |
| |
| return FALSE; |
| } |
| |
| fd = open(file, 0); |
| |
| if (fd < 0) |
| { |
| perror(file); |
| |
| return FALSE; |
| } |
| |
| bufPtr = bufBase; |
| bufUsed = 0; |
| lineCount = 0; |
| charCount = 0; |
| cc = 0; |
| |
| printf("\"%s\", ", file); |
| fflush(stdout); |
| |
| do |
| { |
| cp = memchr(bufPtr, '\n', bufUsed); |
| |
| if (cp) |
| { |
| len = (cp - bufPtr) + 1; |
| |
| if (!insertLine(num, bufPtr, len)) |
| { |
| close(fd); |
| |
| return FALSE; |
| } |
| |
| bufPtr += len; |
| bufUsed -= len; |
| charCount += len; |
| lineCount++; |
| num++; |
| |
| continue; |
| } |
| |
| if (bufPtr != bufBase) |
| { |
| memcpy(bufBase, bufPtr, bufUsed); |
| bufPtr = bufBase + bufUsed; |
| } |
| |
| if (bufUsed >= bufSize) |
| { |
| len = (bufSize * 3) / 2; |
| cp = realloc(bufBase, len); |
| |
| if (cp == NULL) |
| { |
| bb_error_msg("No memory for buffer"); |
| close(fd); |
| |
| return FALSE; |
| } |
| |
| bufBase = cp; |
| bufPtr = bufBase + bufUsed; |
| bufSize = len; |
| } |
| |
| cc = read(fd, bufPtr, bufSize - bufUsed); |
| bufUsed += cc; |
| bufPtr = bufBase; |
| |
| } |
| while (cc > 0); |
| |
| if (cc < 0) |
| { |
| perror(file); |
| close(fd); |
| |
| return FALSE; |
| } |
| |
| if (bufUsed) |
| { |
| if (!insertLine(num, bufPtr, bufUsed)) |
| { |
| close(fd); |
| |
| return -1; |
| } |
| |
| lineCount++; |
| charCount += bufUsed; |
| } |
| |
| close(fd); |
| |
| printf("%d lines%s, %d chars\n", lineCount, |
| (bufUsed ? " (incomplete)" : ""), charCount); |
| |
| return TRUE; |
| } |
| |
| |
| /* |
| * Write the specified lines out to the specified file. |
| * Returns TRUE if successful, or FALSE on an error with a message output. |
| */ |
| static int writeLines(const char * file, int num1, int num2) |
| { |
| LINE *lp; |
| int fd, lineCount, charCount; |
| |
| if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) |
| { |
| bb_error_msg("Bad line range for write"); |
| |
| return FALSE; |
| } |
| |
| lineCount = 0; |
| charCount = 0; |
| |
| fd = creat(file, 0666); |
| |
| if (fd < 0) { |
| perror(file); |
| |
| return FALSE; |
| } |
| |
| printf("\"%s\", ", file); |
| fflush(stdout); |
| |
| lp = findLine(num1); |
| |
| if (lp == NULL) |
| { |
| close(fd); |
| |
| return FALSE; |
| } |
| |
| while (num1++ <= num2) |
| { |
| if (write(fd, lp->data, lp->len) != lp->len) |
| { |
| perror(file); |
| close(fd); |
| |
| return FALSE; |
| } |
| |
| charCount += lp->len; |
| lineCount++; |
| lp = lp->next; |
| } |
| |
| if (close(fd) < 0) |
| { |
| perror(file); |
| |
| return FALSE; |
| } |
| |
| printf("%d lines, %d chars\n", lineCount, charCount); |
| |
| return TRUE; |
| } |
| |
| |
| /* |
| * Print lines in a specified range. |
| * The last line printed becomes the current line. |
| * If expandFlag is TRUE, then the line is printed specially to |
| * show magic characters. |
| */ |
| static int printLines(int num1, int num2, int expandFlag) |
| { |
| const LINE *lp; |
| const char *cp; |
| int ch, count; |
| |
| if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) |
| { |
| bb_error_msg("Bad line range for print"); |
| |
| return FALSE; |
| } |
| |
| lp = findLine(num1); |
| |
| if (lp == NULL) |
| return FALSE; |
| |
| while (num1 <= num2) |
| { |
| if (!expandFlag) |
| { |
| write(1, lp->data, lp->len); |
| setCurNum(num1++); |
| lp = lp->next; |
| |
| continue; |
| } |
| |
| /* |
| * Show control characters and characters with the |
| * high bit set specially. |
| */ |
| cp = lp->data; |
| count = lp->len; |
| |
| if ((count > 0) && (cp[count - 1] == '\n')) |
| count--; |
| |
| while (count-- > 0) |
| { |
| ch = *cp++; |
| |
| if (ch & 0x80) |
| { |
| fputs("M-", stdout); |
| ch &= 0x7f; |
| } |
| |
| if (ch < ' ') |
| { |
| fputc('^', stdout); |
| ch += '@'; |
| } |
| |
| if (ch == 0x7f) |
| { |
| fputc('^', stdout); |
| ch = '?'; |
| } |
| |
| fputc(ch, stdout); |
| } |
| |
| fputs("$\n", stdout); |
| |
| setCurNum(num1++); |
| lp = lp->next; |
| } |
| |
| return TRUE; |
| } |
| |
| |
| /* |
| * Insert a new line with the specified text. |
| * The line is inserted so as to become the specified line, |
| * thus pushing any existing and further lines down one. |
| * The inserted line is also set to become the current line. |
| * Returns TRUE if successful. |
| */ |
| static int insertLine(int num, const char * data, int len) |
| { |
| LINE *newLp, *lp; |
| |
| if ((num < 1) || (num > lastNum + 1)) |
| { |
| bb_error_msg("Inserting at bad line number"); |
| |
| return FALSE; |
| } |
| |
| newLp = (LINE *) malloc(sizeof(LINE) + len - 1); |
| |
| if (newLp == NULL) |
| { |
| bb_error_msg("Failed to allocate memory for line"); |
| |
| return FALSE; |
| } |
| |
| memcpy(newLp->data, data, len); |
| newLp->len = len; |
| |
| if (num > lastNum) |
| lp = &lines; |
| else |
| { |
| lp = findLine(num); |
| |
| if (lp == NULL) |
| { |
| free((char *) newLp); |
| |
| return FALSE; |
| } |
| } |
| |
| newLp->next = lp; |
| newLp->prev = lp->prev; |
| lp->prev->next = newLp; |
| lp->prev = newLp; |
| |
| lastNum++; |
| dirty = TRUE; |
| |
| return setCurNum(num); |
| } |
| |
| |
| /* |
| * Delete lines from the given range. |
| */ |
| static int deleteLines(int num1, int num2) |
| { |
| LINE *lp, *nlp, *plp; |
| int count; |
| |
| if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) |
| { |
| bb_error_msg("Bad line numbers for delete"); |
| |
| return FALSE; |
| } |
| |
| lp = findLine(num1); |
| |
| if (lp == NULL) |
| return FALSE; |
| |
| if ((curNum >= num1) && (curNum <= num2)) |
| { |
| if (num2 < lastNum) |
| setCurNum(num2 + 1); |
| else if (num1 > 1) |
| setCurNum(num1 - 1); |
| else |
| curNum = 0; |
| } |
| |
| count = num2 - num1 + 1; |
| |
| if (curNum > num2) |
| curNum -= count; |
| |
| lastNum -= count; |
| |
| while (count-- > 0) |
| { |
| nlp = lp->next; |
| plp = lp->prev; |
| plp->next = nlp; |
| nlp->prev = plp; |
| lp->next = NULL; |
| lp->prev = NULL; |
| lp->len = 0; |
| free(lp); |
| lp = nlp; |
| } |
| |
| dirty = TRUE; |
| |
| return TRUE; |
| } |
| |
| |
| /* |
| * Search for a line which contains the specified string. |
| * If the string is NULL, then the previously searched for string |
| * is used. The currently searched for string is saved for future use. |
| * Returns the line number which matches, or 0 if there was no match |
| * with an error printed. |
| */ |
| static int searchLines(const char *str, int num1, int num2) |
| { |
| const LINE *lp; |
| int len; |
| |
| if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) |
| { |
| bb_error_msg("Bad line numbers for search"); |
| |
| return 0; |
| } |
| |
| if (*str == '\0') |
| { |
| if (searchString[0] == '\0') |
| { |
| bb_error_msg("No previous search string"); |
| |
| return 0; |
| } |
| |
| str = searchString; |
| } |
| |
| if (str != searchString) |
| strcpy(searchString, str); |
| |
| len = strlen(str); |
| |
| lp = findLine(num1); |
| |
| if (lp == NULL) |
| return 0; |
| |
| while (num1 <= num2) |
| { |
| if (findString(lp, str, len, 0) >= 0) |
| return num1; |
| |
| num1++; |
| lp = lp->next; |
| } |
| |
| bb_error_msg("Cannot find string \"%s\"", str); |
| |
| return 0; |
| } |
| |
| |
| /* |
| * Return a pointer to the specified line number. |
| */ |
| static LINE *findLine(int num) |
| { |
| LINE *lp; |
| int lnum; |
| |
| if ((num < 1) || (num > lastNum)) |
| { |
| bb_error_msg("Line number %d does not exist", num); |
| |
| return NULL; |
| } |
| |
| if (curNum <= 0) |
| { |
| curNum = 1; |
| curLine = lines.next; |
| } |
| |
| if (num == curNum) |
| return curLine; |
| |
| lp = curLine; |
| lnum = curNum; |
| |
| if (num < (curNum / 2)) |
| { |
| lp = lines.next; |
| lnum = 1; |
| } |
| else if (num > ((curNum + lastNum) / 2)) |
| { |
| lp = lines.prev; |
| lnum = lastNum; |
| } |
| |
| while (lnum < num) |
| { |
| lp = lp->next; |
| lnum++; |
| } |
| |
| while (lnum > num) |
| { |
| lp = lp->prev; |
| lnum--; |
| } |
| |
| return lp; |
| } |
| |
| |
| /* |
| * Set the current line number. |
| * Returns TRUE if successful. |
| */ |
| static int setCurNum(int num) |
| { |
| LINE *lp; |
| |
| lp = findLine(num); |
| |
| if (lp == NULL) |
| return FALSE; |
| |
| curNum = num; |
| curLine = lp; |
| |
| return TRUE; |
| } |