├── link.sub ├── s.com ├── compile.sub ├── s.lnk ├── CONTRIBUTING.md ├── Makefile ├── s.h ├── README.md ├── LICENSE ├── adjust.c ├── lib.c ├── yank.c ├── buffer.c ├── s.c ├── keyboard.c ├── screen.c ├── operator.c ├── Bman.c ├── commands.c ├── address.c └── Sman.c /link.sub: -------------------------------------------------------------------------------- 1 | j:link 5 | #include 6 | 7 | #define CR '\r' /* sent by key */ 8 | #define ctrl(x) (x & 037) /* control character 'x' */ 9 | #define ESCAPE 27 /* end-of-insertion character */ 10 | #define MAXTEXT 1000 /* maximum length of a line */ 11 | #define SCROLL_SIZE 12 /* number of rows to scroll */ 12 | #define TAB_WIDTH 8 /* columns per tab character */ 13 | 14 | #define abs(x) ((x > 0) ? x : -(x)) 15 | #define max(x,y) (x > y) ? x : y 16 | #define min(x,y) (x < y) ? x : y 17 | 18 | /* for an unknown command, ring the bell */ 19 | #define UNKNOWN putc(7, stderr) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # s 2 | A tiny vi like screen editor 3 | 4 | Original sources were published in this book: 5 | 6 | 7 | Author: Webb Miller 8 | 9 | Title: A software tools sampler 10 | 11 | Publisher: Prentice-Hall, Inc. Upper Saddle River, NJ, USA ©1987 12 | 13 | ISBN: 0-13-822305-X 14 | 15 | 16 | Martin, a guy from one of my hangouts named , located 17 | the sources from this book. The repository starts from these 18 | original sources. Martin also provided the initial CP/M patches 19 | for compiling with HI-TECH C. Then the sources were overworked, 20 | to get it compiled without warnings on old systems with K&R 21 | C compiler, as well as modern systems with ANSI C compiler. 22 | 23 | This version of s is known to compile on: 24 | 25 | HI-TECH C for the Z80 under CP/M 26 | 27 | clang under OSX 28 | 29 | clang and gcc under Linux 30 | 31 | Mark Williams K&R C compiler under COHERENT 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /adjust.c: -------------------------------------------------------------------------------- 1 | /* adjust.c - handle the # command */ 2 | 3 | #include 4 | 5 | #include "s.h" 6 | 7 | #define LENGTH 78 8 | 9 | extern void b_getcur(), b_getmark(), b_gets(), b_delete(), b_setcur(); 10 | extern int b_insert(); 11 | 12 | void adjust(n) 13 | int n; /* line length */ 14 | { 15 | char *b, *p; 16 | int start, end, temp, remaining, put_next, get_next, pos, len; 17 | char buf[1000]; 18 | 19 | if (n == 0) 20 | n = LENGTH; 21 | b_getcur(&start, &pos); 22 | end = 0; 23 | b_getmark(&end, &pos); 24 | if (end == 0) { 25 | UNKNOWN; 26 | return; 27 | } 28 | if (start > end) { 29 | temp = start; 30 | start = end; 31 | end = temp; 32 | } 33 | *buf = '\0'; 34 | put_next = get_next = start; 35 | for (remaining = end - start + 1; remaining > 0; ) { 36 | while (remaining > 0 && (len = strlen(buf)) < n) { 37 | if (len > 0 && buf[len-1] != ' ') { 38 | /* add spacer */ 39 | buf[len++] = ' '; 40 | buf[len] = '\0'; 41 | } 42 | b_gets(get_next, buf + len); 43 | b_delete(get_next, get_next); 44 | --remaining; 45 | } 46 | while ((int)strlen(buf) >= n) { 47 | for (b = buf + n; b > buf && *b != ' '; --b) 48 | ; 49 | if (b == buf) { 50 | b_insert(put_next++, buf); 51 | ++get_next; 52 | break; 53 | } 54 | *b = '\0'; 55 | b_insert(put_next++, buf); 56 | ++get_next; 57 | for (p = buf, ++b; (*p++ = *b++) != '\0'; ) 58 | ; 59 | } 60 | } 61 | if (*buf != '\0') 62 | b_insert(put_next, buf); 63 | b_setcur(start, 0); 64 | } 65 | -------------------------------------------------------------------------------- /lib.c: -------------------------------------------------------------------------------- 1 | /* lib.c - library of C procedures. */ 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define MAX_NAME 50 /* maximum length of program or file name */ 8 | 9 | static char prog_name[MAX_NAME+1]; /* used in error messages */ 10 | 11 | /* savename - record a program name for error messages */ 12 | void savename(name) 13 | char *name; 14 | { 15 | if (strlen(name) <= MAX_NAME) 16 | strcpy(prog_name, name); 17 | } 18 | 19 | /* fatal - print message and die */ 20 | void fatal(msg) 21 | char *msg; 22 | { 23 | if (prog_name[0] != '\0') 24 | fprintf(stderr, "%s: ", prog_name); 25 | fprintf(stderr, "%s\n", msg); 26 | exit(1); 27 | } 28 | 29 | /* fatalf - format message, print it, and die */ 30 | void fatalf(msg, val) 31 | char *msg, *val; 32 | { 33 | if (prog_name[0] != '\0') 34 | fprintf(stderr, "%s: ", prog_name); 35 | fprintf(stderr, msg, val); 36 | putc('\n', stderr); 37 | exit(1); 38 | } 39 | 40 | /* ckopen - open file; check for success */ 41 | FILE *ckopen(name, mode) 42 | char *name, *mode; 43 | { 44 | FILE *fp; 45 | 46 | if ((fp = fopen(name, mode)) == NULL) 47 | fatalf("Cannot open %s.", name); 48 | return(fp); 49 | } 50 | 51 | /* ckalloc - allocate space; check for success */ 52 | char *ckalloc(amount) 53 | int amount; 54 | { 55 | char *p; 56 | 57 | if ((p = malloc( (unsigned) amount)) == NULL) 58 | fatal("Ran out of memory."); 59 | return(p); 60 | } 61 | 62 | /* strsame - tell whether two strings are identical */ 63 | int strsame(s, t) 64 | char *s, *t; 65 | { 66 | return(strcmp(s, t) == 0); 67 | } 68 | 69 | /* strsave - save string s somewhere; return address */ 70 | char *strsave(s) 71 | char *s; 72 | { 73 | char *p; 74 | 75 | p = ckalloc(strlen(s)+1); /* +1 to hold '\0' */ 76 | return(strcpy(p, s)); 77 | } 78 | -------------------------------------------------------------------------------- /yank.c: -------------------------------------------------------------------------------- 1 | /* 2 | * yank.c - yank buffer 3 | * 4 | * 5 | * Entry points: 6 | * 7 | * do_put(way) 8 | * int way; 9 | * Copy the yank buffer to the main buffer. If way = 1, the lines 10 | * go after the current line; otherwise, they go before it. 11 | * 12 | * int do_yank(line1, line2) 13 | * int line1, line2; 14 | * Copy the block of lines from line1 to line2, inclusive, to the 15 | * yank buffer; tell if successful. Line1 cannot exceed line2. 16 | * 17 | * 18 | * External procedure calls: 19 | * 20 | * b_getcur(line_ptr, pos_ptr) .. file Bman.c 21 | * int *line_ptr, *pos_ptr; 22 | * Return the line and position of the cursor. 23 | * 24 | * b_gets(k, s) .. file Bman.c 25 | * int k; 26 | * char s[]; 27 | * Copy the k-th buffer line to s. 28 | * 29 | * b_insert(k, s) .. file Bman.c 30 | * int k; 31 | * char s[]; 32 | * Insert s into the buffer as line k. 33 | * 34 | * b_setline(line) .. file Bman.c 35 | * int line; 36 | * Set the cursor to line's first nonwhite character. 37 | * 38 | * s_savemsg(msg, count) .. file Sman.c 39 | * char *msg; 40 | * int count; 41 | * Format msg and save it for the next screen refreshing. 42 | * 43 | * 44 | * 45 | * Implementation: 46 | * 47 | * Simple linked list. Storage is allocated dynamically with malloc() 48 | * (not ckalloc()) so execution can continue if storage is exhausted. 49 | */ 50 | 51 | #include 52 | #include 53 | 54 | #include "s.h" 55 | 56 | extern void b_getcur(), b_setline(), s_savemsg(), b_gets(); 57 | extern int b_insert(); 58 | void free_ybuf(); 59 | 60 | struct y_line { 61 | char *y_text; 62 | struct y_line *next; 63 | }; 64 | 65 | static struct y_line *start = NULL; 66 | 67 | 68 | /* do_put - copy the yank buffer to the file buffer */ 69 | void do_put(way) 70 | int way; 71 | { 72 | struct y_line *p; 73 | int cur_line, cur_pos, line, size; 74 | 75 | if (start == NULL) { 76 | UNKNOWN; 77 | return; 78 | } 79 | b_getcur(&cur_line, &cur_pos); 80 | if (way == 1) 81 | ++cur_line; 82 | for (line = cur_line, p = start; p != NULL; p = p->next) 83 | b_insert(line++, p->y_text); 84 | 85 | /* move to first nonwhite character */ 86 | b_setline(cur_line); 87 | if ((size = line - cur_line) >= 5) 88 | s_savemsg("%d lines added", size); 89 | } 90 | 91 | /* do_yank - copy lines from main buffer to yank buffer; tell if successful */ 92 | int do_yank(line1, line2) 93 | int line1, line2; 94 | { 95 | struct y_line *p, *q; 96 | char *r, text[MAXTEXT-1]; 97 | 98 | p = NULL; 99 | 100 | free_ybuf(); 101 | 102 | for ( ; line1 <= line2; ++line1) { 103 | b_gets(line1, text); 104 | q = (struct y_line *) malloc(sizeof(struct y_line)); 105 | r = malloc((unsigned)strlen(text) +1); 106 | if (q == NULL || r == NULL) { 107 | free_ybuf(); 108 | return(0); 109 | } 110 | q->y_text = strcpy(r, text); 111 | q->next = NULL; 112 | /* link the line in at the end of the list */ 113 | if (start == NULL) 114 | start = q; 115 | else 116 | p->next = q; 117 | p = q; /* p points to the end of the list */ 118 | } 119 | return(1); 120 | } 121 | 122 | /* free_ybuf - free the storage for the yank buffer */ 123 | void free_ybuf() 124 | { 125 | struct y_line *p, *q; 126 | 127 | for (p = start; p != NULL; p = q) { 128 | free(p->y_text); 129 | q = p->next; 130 | free((char *)p); 131 | } 132 | start = NULL; 133 | } 134 | -------------------------------------------------------------------------------- /buffer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * buffer.c - data structure for the buffer 3 | * 4 | * 5 | * Only procedures in Bman.c should access buffer.c. Entry points: 6 | * 7 | * buf_delete(from, to) 8 | * int from, to; 9 | * Delete a range of buffer lines. 10 | * 11 | * buf_free() 12 | * Free temporary buffer storage. 13 | * 14 | * buf_gets(k, s) 15 | * int k; 16 | * char *s; 17 | * Copy the k-th buffer line to s. 18 | * 19 | * int buf_id(k) 20 | * int k; 21 | * Return the ID of the k-th buffer line. 22 | * 23 | * buf_init() 24 | * Initialize the buffer. 25 | * 26 | * int buf_insert(k, s) 27 | * int k; 28 | * char *s; 29 | * Insert s as the k-th buffer line; tell if successful. 30 | * 31 | * int buf_replace(k, s) 32 | * int k; 33 | * char *s; 34 | * Replace the k-th buffer line by s; tell if successful. 35 | * 36 | * 37 | * Implementation: 38 | * 39 | * Doubly-linked list with a pointer to a recently referenced line. 40 | */ 41 | 42 | #include 43 | #include 44 | 45 | #include "s.h" 46 | 47 | static void reference(); 48 | 49 | struct b_line { 50 | char *b_text; /* text of the line */ 51 | int b_id; /* ID of the line */ 52 | struct b_line *next; /* pointer to next line */ 53 | struct b_line *prev; /* pointer to previous line */ 54 | }; 55 | 56 | static struct b_line 57 | line0, /* points to first and last buffer lines */ 58 | *ref_line; /* recently referenced line */ 59 | 60 | static int 61 | last_id = 0, /* last ID assigned to a buffer line */ 62 | ref_nbr; /* number of recently referenced line */ 63 | 64 | /* buf_delete - delete buffer lines */ 65 | void buf_delete(from, to) 66 | int from, to; 67 | { 68 | struct b_line *b; 69 | int count = to - from + 1; 70 | 71 | reference(from); 72 | while (count-- > 0) { 73 | b = ref_line->next; 74 | b->prev = ref_line->prev; 75 | b->prev->next = b; 76 | free(ref_line->b_text); 77 | free((char *)ref_line); 78 | ref_line = b; 79 | } 80 | } 81 | 82 | /* buf_free - free temporary buffer storage */ 83 | void buf_free() 84 | { 85 | /* 86 | * This implementation does nothing. Implementations using a temporary file 87 | * can unlink it here. 88 | */ 89 | } 90 | 91 | /* buf_gets - get a line from the buffer */ 92 | void buf_gets(k, s) 93 | int k; 94 | char *s; 95 | { 96 | reference(k); 97 | strcpy(s, ref_line->b_text); 98 | } 99 | 100 | /* buf_id - return the ID of a line */ 101 | int buf_id(k) 102 | int k; 103 | { 104 | reference(k); 105 | return(ref_line->b_id); 106 | } 107 | 108 | /* buf_init - initialize the buffer */ 109 | void buf_init() 110 | { 111 | line0.b_text = NULL; 112 | line0.b_id = 0; 113 | ref_line = line0.next = line0.prev = &line0; 114 | ref_nbr = 0; 115 | } 116 | 117 | /* buf_insert - insert s as the k-th buffer line; tell if successful */ 118 | int buf_insert(k, s) 119 | int k; 120 | char *s; 121 | { 122 | struct b_line *p; 123 | char *q; 124 | 125 | p = (struct b_line *) malloc(sizeof(struct b_line)); 126 | q = malloc((unsigned)strlen(s)+1); 127 | if (p == NULL || q == NULL) 128 | return(0); 129 | reference(k-1); 130 | p->b_text = strcpy(q, s); 131 | p->b_id = ++last_id; 132 | /* link node p in after node ref_line */ 133 | p->next = ref_line->next; 134 | p->prev = ref_line; 135 | ref_line->next->prev = p; 136 | ref_line->next = p; 137 | return(1); 138 | } 139 | 140 | /* buf_replace - replace a buffer line; tell if successful */ 141 | int buf_replace(k, s) 142 | int k; 143 | char *s; 144 | { 145 | char *p; 146 | 147 | if ((p = malloc((unsigned)strlen(s)+1)) != NULL) { 148 | reference(k); 149 | free(ref_line->b_text); 150 | ref_line->b_text = strcpy(p, s); 151 | ref_line->b_id = ++last_id; 152 | return(1); 153 | } 154 | return(0); 155 | } 156 | 157 | /* reference - point ref_line to the n-th buffer line; update ref_nbr */ 158 | static void reference(n) 159 | int n; 160 | { 161 | /* search forward from a recently referenced line ... */ 162 | for ( ; ref_nbr < n; ++ref_nbr) 163 | ref_line = ref_line->next; 164 | /* ... or backward */ 165 | for ( ; ref_nbr > n; --ref_nbr) 166 | ref_line = ref_line->prev; 167 | } 168 | -------------------------------------------------------------------------------- /s.c: -------------------------------------------------------------------------------- 1 | /* 2 | * s - a screen editor 3 | * 4 | * 5 | * Source files: 6 | * 7 | * command handler: 8 | * address.c - process addresses 9 | * commands.c - commands without an address 10 | * keyboard.c - read commands 11 | * lib.c - library of C procedures 12 | * operator.c - operators c, d and y 13 | * s.c - this file; contains the main program 14 | * s.h - macro definitions 15 | * yank.c - the yank buffer 16 | * 17 | * buffer module: 18 | * Bman.c - buffer manager 19 | * buffer.c - data structure for the buffer 20 | * 21 | * screen module: 22 | * Sman.c - screen manager 23 | * screen.c - terminal-specific procedures 24 | * 25 | * 26 | * External procedure calls: 27 | * 28 | * address(n, c, op) .. file address.c 29 | * int n; 30 | * char c, op; 31 | * Set the buffer's cursor according to the count n, 32 | * the cursor movement command c and the operation op. 33 | * 34 | * b_getcur(line_ptr, pos_ptr) .. file Bman.c 35 | * int *line_ptr, *pos_ptr; 36 | * Return the line and position of the cursor. 37 | * 38 | * b_init() .. file Bman.c 39 | * Initialize the buffer module. 40 | * 41 | * k_donext(command) .. file keyboard.c 42 | * char *command; 43 | * Arrange for the given edit command to be executed next. 44 | * 45 | * int k_getch() .. file keyboard.c 46 | * Return the next character of the command. 47 | * 48 | * k_init() .. file keyboard.c 49 | * Initialize the keyboard manager. 50 | * 51 | * k_newcmd() .. file keyboard.c 52 | * Prepare for reading a new command. 53 | * 54 | * operator(op, line, pos) .. file operator.c 55 | * char op; 56 | * int line, pos; 57 | * Apply op = 'c', 'd' or 'y' using the indicated buffer 58 | * location and the cursor location. 59 | * 60 | * s_init() .. file Sman.c 61 | * Initialize the screen module. 62 | * 63 | * s_refresh() .. file Sman.c 64 | * Bring the screen up to date with the buffer. 65 | * 66 | * int simp_cmd(n, c) .. file commands.c 67 | * int n; 68 | * char c; 69 | * Apply commands without addresses; tell if successful. 70 | * 71 | * 72 | * System Dependencies: 73 | * 74 | * To move this editor to a non-UNIX operating system, the function 75 | * k_flip() in file keyboard.c must be changed. (This functions flips the 76 | * terminal driver to and from noecho-raw mode.) 77 | * 78 | * To operate this editor on a non-ANSI standard video terminal, or one 79 | * without "autowrap", requires modification of the file screen.c. 80 | */ 81 | 82 | #include "s.h" 83 | 84 | extern void b_init(), k_init(), s_init(), k_donext(), s_refresh(); 85 | extern void k_newcmd(), b_getcur(), address(), operator(); 86 | extern int fatal(), k_getch(), simp_cmd(); 87 | static int get_count(); 88 | 89 | int main(argc, argv) 90 | int argc; 91 | char *argv[]; 92 | { 93 | int count, count2, cur_line, cur_pos, new_line, new_pos; 94 | char c, op, cmd[MAXTEXT]; 95 | 96 | if (argc != 2) 97 | fatal("usage: s file"); 98 | b_init(); 99 | k_init(); 100 | s_init(); 101 | /* do the command: :e */ 102 | sprintf(cmd, ":e %s%c", argv[1], CR); 103 | k_donext(cmd); 104 | for ( ; ; ) { /* loop over commands */ 105 | /* prepare to get a new command */ 106 | s_refresh(); 107 | k_newcmd(); 108 | c = k_getch(); 109 | count = get_count(&c); 110 | /* for simple commands, move on to the next command */ 111 | if (simp_cmd(count, c)) 112 | continue; 113 | /* for c, d or y operators, get the second count */ 114 | if (c == 'c' || c == 'd' || c == 'y') { 115 | op = c; 116 | c = k_getch(); 117 | count2 = get_count(&c); 118 | if (count > 0 && count2 > 0) 119 | count *= count2; 120 | else 121 | count = max(count, count2); 122 | } else 123 | op = ' '; 124 | /* set the buffer's idea of the cursor to the new address */ 125 | b_getcur(&cur_line, &cur_pos); 126 | address(count, c, op); 127 | /* check that cursor actually moved */ 128 | b_getcur(&new_line, &new_pos); 129 | if (cur_line == new_line && cur_pos == new_pos) 130 | UNKNOWN; 131 | else if (op != ' ') 132 | operator(op, cur_line, cur_pos); 133 | } 134 | return(0); 135 | } 136 | 137 | /* get_count - determine a count in an edit command */ 138 | static int get_count(ch_ptr) 139 | char *ch_ptr; 140 | { 141 | int ch = *ch_ptr, count; 142 | 143 | if (isdigit(ch) && ch != '0') { /* a count cannot start with zero */ 144 | count = ch - '0'; 145 | while (isdigit(ch = k_getch())) 146 | count = 10*count + ch - '0'; 147 | } else 148 | count = 0; 149 | *ch_ptr = ch; 150 | return(count); 151 | } 152 | -------------------------------------------------------------------------------- /keyboard.c: -------------------------------------------------------------------------------- 1 | /* 2 | * keyboard.c - read commands 3 | * 4 | * 5 | * Entry points: 6 | * 7 | * k_donext(cmd) 8 | * char *cmd; 9 | * Arrange that cmd will be done next. 10 | * 11 | * k_finish() 12 | * Close down the keyboard manager. 13 | * 14 | * int k_getch() 15 | * Return the next character of the current command. 16 | * 17 | * k_init() 18 | * Initialize the keyboard manager. 19 | * 20 | * char k_lastcmd() 21 | * Return the first letter in the last command. 22 | * 23 | * k_newcmd() 24 | * Prepare for reading a new command. 25 | * 26 | * k_redo() 27 | * Redo the last buffer-change command. 28 | * 29 | * int k_keyin() 30 | * Get a character from the keyboard. 31 | * 32 | * 33 | * External procedure calls: 34 | * 35 | * int b_changed() .. file Bman.c 36 | * Tell if the buffer was changed by the last command. 37 | * 38 | * b_newcmd(bit) .. file Bman.c 39 | * int bit; 40 | * Inform the buffer module that a new command is starting and tell 41 | * whether it is from the keyboard (bit = 1) or not (bit = 0). 42 | * 43 | * s_keyboard(bit) .. file Sman.c 44 | * int bit; 45 | * Inform the screen module whether the next input character is 46 | * from the keyboard (bit = 1) or not (bit = 0). 47 | * 48 | * s_savemsg(msg, count) .. file Sman.c 49 | * char *msg; 50 | * int count; 51 | * Format msg and save it for the next screen refreshing. 52 | */ 53 | 54 | #ifdef TERMIOS 55 | #include 56 | #include 57 | #include 58 | static struct termios oldt; 59 | #else 60 | #ifdef CONIO 61 | #include 62 | #include 63 | #else 64 | #include 65 | static struct sgttyb oldt; 66 | #endif 67 | #endif 68 | #include 69 | 70 | #include "s.h" 71 | 72 | #define CMD_MAX 500 /* longest command that can be redone */ 73 | 74 | extern void s_savemsg(), s_keyboard(), b_newcmd(); 75 | extern int b_changed(); 76 | int k_keyin(); 77 | void k_flip(); 78 | 79 | static char 80 | change[CMD_MAX+2], /* most recent buffer-change command */ 81 | cmd_last, /* first letter in the last command */ 82 | command[CMD_MAX+2], /* accumulates the current command */ 83 | *cmd_ptr = command, /* next location in command */ 84 | pushed[CMD_MAX], /* pushed-back command */ 85 | *push_ptr = pushed; /* next location in pushed */ 86 | 87 | /* k_donext - push a command back on the input stream */ 88 | void k_donext(cmd) 89 | char *cmd; 90 | { 91 | int cmd_size; 92 | char *s; 93 | 94 | cmd_size = strlen(cmd); 95 | if (push_ptr - pushed + cmd_size > CMD_MAX) { 96 | s_savemsg("Pushed commands are too long.", 0); 97 | UNKNOWN; 98 | } else if (cmd_size > 0) { 99 | /* copy cmd to pushed[] in reverse order */ 100 | for (s = cmd + cmd_size - 1; s >= cmd; --s) 101 | *push_ptr++ = *s; 102 | s_keyboard(0); 103 | } 104 | } 105 | 106 | /* k_finish - close down the keyboard manager */ 107 | void k_finish() 108 | { 109 | k_flip(); 110 | } 111 | 112 | /* k_getch - get a character of the command */ 113 | int k_getch() 114 | { 115 | int ch; 116 | 117 | /* get pushed character (preferably) or read keyboard */ 118 | /* use logical AND operation with octal 0177 to strip the parity bit */ 119 | ch = (push_ptr > pushed) ? *(--push_ptr) : k_keyin() & 0177; 120 | /* remember character if there is room */ 121 | if (cmd_ptr <= command + CMD_MAX) 122 | *cmd_ptr++ = ch; 123 | s_keyboard(push_ptr == pushed); 124 | return(ch); 125 | } 126 | 127 | /* k_init - initialize the keyboard manager */ 128 | void k_init() 129 | { 130 | k_flip(); 131 | } 132 | 133 | /* k_lastcmd - get first letter of the last command */ 134 | char k_lastcmd() 135 | { 136 | return(cmd_last); 137 | } 138 | 139 | /* k_newcmd - start a new command */ 140 | void k_newcmd() 141 | { 142 | char *s; 143 | 144 | *cmd_ptr = '\0'; 145 | /* remember first letter of the old command */ 146 | for (s = command; *s != '\0' && !isalpha(*s); ++s) 147 | ; 148 | cmd_last = *s; 149 | /* if the old command changed the buffer, remember it */ 150 | if (b_changed()) 151 | strcpy(change, command); 152 | cmd_ptr = command; /* prepare to collect the new command */ 153 | b_newcmd(push_ptr == pushed); /* mark buffer "unchanged" */ 154 | } 155 | 156 | /* k_redo - redo the last buffer-change command */ 157 | void k_redo() 158 | { 159 | if (strlen(change) > CMD_MAX) { 160 | s_savemsg("Cannot redo commands longer than %d characters.", 161 | CMD_MAX); 162 | change[0] = '\0'; 163 | } 164 | if (change[0] == '\0') 165 | UNKNOWN; 166 | else 167 | k_donext(change); 168 | } 169 | 170 | /* keyboard input mode */ 171 | static int k_raw = 0; 172 | 173 | /* 174 | * k_keyin - get a character from the keyboard 175 | * Hide system dependent differences in keyboard input 176 | */ 177 | 178 | int k_keyin() 179 | { 180 | #ifdef CONIO 181 | if (k_raw) { 182 | return getch(); 183 | } else { 184 | return getchar(); 185 | } 186 | #else 187 | return getchar(); 188 | #endif 189 | } 190 | 191 | /* 192 | * k_flip - toggle keyboard input to and from noecho-raw mode (UNIX) 193 | * Normally: 194 | * 1. typed characters are echoed back to the terminal and 195 | * 2. input characters are buffered until a complete line 196 | * has been received. 197 | * Flipping to noecho-raw mode suspends all such input processing. 198 | */ 199 | 200 | void k_flip() 201 | { 202 | #ifdef TERMIOS 203 | struct termios newt; 204 | #else 205 | #ifndef CONIO 206 | struct sgttyb newt; 207 | #endif 208 | #endif 209 | 210 | if (!k_raw) { 211 | k_raw = 1; 212 | #ifdef CONIO 213 | /* Stop SIGINT () detection */ 214 | /* Keyboard reads during screen redraw kills raw input */ 215 | signal(SIGINT, SIG_IGN); 216 | #else 217 | #ifdef TERMIOS 218 | ioctl(0, TCGETS, &oldt); 219 | ioctl(0, TCGETS, &newt); 220 | newt.c_lflag &= ~(ISIG|ICANON|ECHO); 221 | newt.c_iflag &= ~(INLCR|IGNCR|ICRNL|IUCLC|IXON|IXOFF); 222 | newt.c_oflag &= ~OPOST; 223 | newt.c_cc[VMIN] = 1; 224 | newt.c_cc[VTIME] = 0; 225 | ioctl(0, TCSETSW, &newt); 226 | #else 227 | ioctl(0, TIOCGETP, &oldt); 228 | ioctl(0, TIOCGETP, &newt); 229 | newt.sg_flags |= RAW; 230 | newt.sg_flags &= ~ECHO; 231 | ioctl(0, TIOCSETP, &newt); 232 | #endif 233 | #endif 234 | } else { 235 | k_raw = 0; 236 | #ifdef CONIO 237 | /* normal SIGINT handling */ 238 | signal(SIGINT, SIG_DFL); 239 | #else 240 | #ifdef TERMIOS 241 | ioctl(0, TCSETSW, &oldt); 242 | #else 243 | ioctl(0, TIOCSETP, &oldt); 244 | #endif 245 | #endif 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /screen.c: -------------------------------------------------------------------------------- 1 | /* 2 | * screen.c - terminal-specific procedures 3 | * 4 | * 5 | * Only procedures in Sman.c should access screen.c. Entry points: 6 | * 7 | * scr_clr() 8 | * Clear the remainder of the row, i.e., delete the characters 9 | * under, and to the right of, the cursor. 10 | * 11 | * scr_cls() 12 | * Clear all characters from the screen. 13 | * 14 | * scr_delc(i) 15 | * int i; 16 | * Delete i characters. Characters that follow on the same row are 17 | * shifted left i positions and i blank characters are placed at 18 | * the right end of the row. 19 | * 20 | * scr_delr() 21 | * Delete the row under the cursor. Later screen rows are shifted 22 | * up and a blank row is placed at the bottom of the screen. 23 | * 24 | * scr_inr() 25 | * Insert a blank row at the cursor location. Rows at and below 26 | * the current row are shifted down and the last row is lost. 27 | * 28 | * scr_instr(s) 29 | * char *s; 30 | * Insert the string s. Characters under, and to the right of, the 31 | * cursor are shifted right. Characters shifted beyond the right 32 | * margin of the screen are lost. The calling procedure must not 33 | * allow s to contain tab or newline characters and is responsible 34 | * for resetting the cursor after a character is inserted at the 35 | * right margin of the screen. No attempt should be made to insert 36 | * a character at the extreme lower right corner of the screen. 37 | * 38 | * scr_move(row, col) 39 | * int row, col; 40 | * Move the cursor to the given row and column of the screen. The 41 | * upper left corner of the screen is considered row 1, column 1. 42 | * 43 | * scr_puts(s) 44 | * char *s; 45 | * Print the string s, overwriting existing characters. The 46 | * calling procedure must not allow s to contain tab or newline 47 | * characters and is responsible for resetting the cursor after a 48 | * character is printed at the right margin of the screen. A 49 | * character may be printed at the extreme lower right corner of 50 | * the screen. 51 | * 52 | * scr_scrl() 53 | * Scroll screen rows up and place a blank row on the bottom. 54 | * The top screen row is lost. 55 | * 56 | * scr_shape(nrow_ptr, ncol_ptr) 57 | * int *nrow_ptr, *col_ptr; 58 | * Return the number of rows and columns on the screen. 59 | */ 60 | 61 | #include 62 | 63 | #include "s.h" 64 | 65 | /* screen control commands for ANSI terminals */ 66 | #define AUTOWRAP 1 67 | #define CLEAR_ROW "K" 68 | #define CLEAR_SCREEN "2J" 69 | #define DELETE_CHAR "P" 70 | #define DELETE_ROW "M" 71 | #define INSERT_BEGIN "4h" 72 | #define INSERT_END "4l" 73 | #define INSERT_ROW "L" 74 | #define LONG_COUNT 10 75 | #define MOVE(row,col) printf("\033[%d;%dH",row,col) 76 | #define NCOL 80 /* columns per screen row */ 77 | #define NROW 24 /* rows per screen */ 78 | #define PAD_CHAR '\0' 79 | #define SCREEN(x) printf("\033[%s",x) 80 | #define SHORT_COUNT 4 81 | 82 | extern void s_errmsg(); 83 | static void errmsg(); 84 | static void wait(); 85 | 86 | static int cur_row = 0, cur_col; /* cursor location */ 87 | static char save = '\0'; /* character in location (NROW, NCOL-1) */ 88 | 89 | /* scr_clr - clear the current row */ 90 | void scr_clr() 91 | { 92 | SCREEN(CLEAR_ROW); 93 | wait(LONG_COUNT); 94 | } 95 | 96 | /* scr_cls - clear the screen */ 97 | void scr_cls() 98 | { 99 | SCREEN(CLEAR_SCREEN); 100 | wait(LONG_COUNT); 101 | } 102 | 103 | /* scr_delc - delete characters */ 104 | void scr_delc(i) 105 | int i; 106 | { 107 | while (i-- > 0) { 108 | SCREEN(DELETE_CHAR); 109 | wait(SHORT_COUNT); 110 | } 111 | if (cur_row == NROW) 112 | save = '\0'; 113 | } 114 | 115 | /* scr_delr - delete the current row */ 116 | void scr_delr() 117 | { 118 | SCREEN(DELETE_ROW); 119 | } 120 | 121 | /* scr_inr - insert a row */ 122 | void scr_inr() 123 | { 124 | SCREEN(INSERT_ROW); 125 | save = '\0'; 126 | } 127 | 128 | /* scr_instr - insert a string */ 129 | void scr_instr(s) 130 | char *s; 131 | { 132 | int s_len = strlen(s); 133 | 134 | if (cur_col + s_len > NCOL + 1) { 135 | errmsg("scr_instr(): line extends past column %d", NCOL); 136 | s_len = NCOL + 1 - cur_col; 137 | s[s_len] = '\0'; 138 | } 139 | cur_col += s_len; 140 | if (cur_row == NROW && cur_col == NCOL + 1) { 141 | errmsg("scr_instr(): cannot insert in lower, right corner", 0); 142 | return; 143 | } 144 | SCREEN(INSERT_BEGIN); 145 | while (*s != '\0') { 146 | putchar(*s++); 147 | wait(SHORT_COUNT); 148 | } 149 | SCREEN(INSERT_END); 150 | if (AUTOWRAP && cur_col == NCOL + 1) { 151 | cur_col = 1; 152 | ++cur_row; 153 | } 154 | } 155 | 156 | /* scr_move - move the cursor */ 157 | void scr_move(row, col) 158 | int row, col; 159 | { 160 | if (row < 1 || row > NROW) 161 | s_errmsg("scr_move(): illegal row %d", row); 162 | else if (col < 1 || col > NCOL) 163 | s_errmsg("scr_move(): illegal col %d", col); 164 | else if (col == 1 && cur_row > 0 165 | && cur_row <= row && row <= cur_row + 5) { 166 | putchar('\r'); /* move to the start of the current row ... */ 167 | for ( ; cur_row < row; ++cur_row) 168 | putchar('\n'); /* ... and down to the desired row */ 169 | cur_col = col; 170 | } else if (col != cur_col || row != cur_row ) { 171 | MOVE(row, col); 172 | wait(LONG_COUNT); 173 | cur_row = row; 174 | cur_col = col; 175 | } 176 | } 177 | 178 | /* scr_puts - overwrite characters on the screen */ 179 | void scr_puts(s) 180 | char *s; 181 | { 182 | static int bad_bytes = 0; 183 | int s_len = strlen(s); 184 | char buf[NCOL+1], *t; 185 | 186 | for (t = s; *t != '\0'; ++t) 187 | if (!isprint(*t)) { 188 | if (++bad_bytes < 5) 189 | errmsg("scr_puts(): replacing byte with value %d by '#'", 190 | (int)*t); 191 | *t = '#'; 192 | } 193 | if (cur_col + s_len > NCOL + 1) { 194 | errmsg("scr_puts(): line extends past column %d", NCOL); 195 | s_len = NCOL + 1 - cur_col; 196 | s[s_len] = '\0'; 197 | } 198 | cur_col += s_len; 199 | if (cur_row == NROW && cur_col == NCOL) 200 | save = s[s_len-1]; 201 | else if (cur_row == NROW && cur_col == NCOL + 1 && s_len > 1) 202 | save = s[s_len-2]; 203 | if (AUTOWRAP && cur_row == NROW && cur_col == NCOL + 1) { 204 | if (save == '\0') { 205 | scr_move(NROW, 1); 206 | scr_puts("Improbable display error; refresh this line."); 207 | scr_clr(); 208 | return; 209 | } 210 | if (s_len > 1) { 211 | strcpy(buf, s); 212 | buf[s_len-2] = buf[s_len-1]; 213 | buf[s_len-1] = '\0'; 214 | fputs(buf, stdout); 215 | } else { 216 | scr_move(NROW, NCOL-1); 217 | putchar(*s); 218 | } 219 | cur_col = NCOL; 220 | scr_move(NROW, NCOL-1); 221 | SCREEN(INSERT_BEGIN); 222 | putchar(save); 223 | SCREEN(INSERT_END); 224 | cur_col = NCOL; 225 | } else { 226 | fputs(s, stdout); 227 | if (AUTOWRAP && cur_col == NCOL + 1) { 228 | cur_col = 1; 229 | ++cur_row; 230 | } 231 | } 232 | } 233 | 234 | /* scr_scrl - scroll screen rows up */ 235 | void scr_scrl() 236 | { 237 | scr_move(NROW, 1); 238 | putchar('\n'); 239 | } 240 | 241 | /* scr_shape - return the number of rows and columns on the screen */ 242 | void scr_shape(nrow_ptr, ncol_ptr) 243 | int *nrow_ptr, *ncol_ptr; 244 | { 245 | *nrow_ptr = NROW; 246 | *ncol_ptr = NCOL; 247 | } 248 | 249 | /* errmsg - print an error message and return cursor to current location */ 250 | static void errmsg(msg, val) 251 | char *msg; 252 | int val; 253 | { 254 | int col = cur_col, row = cur_row; 255 | 256 | s_errmsg(msg, val); 257 | scr_move(row, col); 258 | } 259 | 260 | /* wait - pause, allowing the screen to catch up */ 261 | static void wait(count) 262 | int count; 263 | { 264 | while (count-- > 0) 265 | putchar(PAD_CHAR); 266 | } 267 | -------------------------------------------------------------------------------- /operator.c: -------------------------------------------------------------------------------- 1 | /* 2 | * operator.c - operators c, d and y 3 | * 4 | * 5 | * Entry points: 6 | * 7 | * do_insert() 8 | * Read characters from the keyboard and place them in the buffer 9 | * at the cursor location. Characters at and right of the cursor 10 | * are shifted right to make room for the new characters. Input is 11 | * ended by . 12 | * 13 | * operator(op, line1, pos1) 14 | * char op; 15 | * int line1, pos1; 16 | * Apply the operator op. Let the location addressed in the 17 | * operator command be (line2, pos2), where, pos2 < 0 for a line 18 | * address. With a line address, lines from line1 to line2, 19 | * inclusive, are affected. With a character address, the affected 20 | * buffer segment extends from the earlier (in the buffer) location 21 | * to one position before later location. 22 | * 23 | * The commands implemented by operator() are: 24 | * 25 | * c - change a buffer segment 26 | * d - delete a buffer segment 27 | * y - copy a buffer segment to the yank buffer 28 | * 29 | * 30 | * External procedure calls: 31 | * 32 | * b_delete(first, last) .. file Bman.c 33 | * int first, last; 34 | * Delete a range of buffer lines. 35 | * 36 | * b_getcur(line_ptr, pos_ptr) .. file Bman.c 37 | * int *line_ptr, *pos_ptr; 38 | * Return the location of the cursor. 39 | * 40 | * b_gets(k, s) .. file Bman.c 41 | * int k; 42 | * char s[]; 43 | * Copy the k-th buffer line to s. 44 | * 45 | * int b_insert(k, s) .. file Bman.c 46 | * int k; 47 | * char s[]; 48 | * Insert s into the buffer as line k; tell if successful. 49 | * 50 | * b_replace(k, s) .. file Bman.c 51 | * int k; 52 | * char s[]; 53 | * Replace the k-th buffer line by s. 54 | * 55 | * b_setcur(line, pos) .. file Bman.c 56 | * int line, pos; 57 | * Set the cursor location. 58 | * 59 | * int b_size() .. file Bman.c 60 | * Return the number of lines in the buffer. 61 | * 62 | * int do_yank(line1, line2) .. file yank.c 63 | * int line1, line2; 64 | * Copy a range of lines to the yank buffer; tell if successful. 65 | * 66 | * int k_getch() .. file keyboard.c 67 | * Return the next character from the keyboard. 68 | * 69 | * s_refresh() .. file Sman.c 70 | * Bring the screen up to date after a buffer change. 71 | * 72 | * s_savemsg(msg, count) .. file Sman.c 73 | * char *msg; 74 | * int count; 75 | * Format msg and save it for the next screen refreshing. 76 | */ 77 | 78 | #include 79 | 80 | #include "s.h" 81 | 82 | extern void b_getcur(), s_putmsg(), s_savemsg(), b_setcur(), b_delete(); 83 | extern void b_replace(), s_refresh(), b_gets(), b_setline(); 84 | extern int do_yank(), k_getch(), b_size(), b_insert(); 85 | static void do_delete(), in_chars(); 86 | 87 | /* do_insert - insert text */ 88 | void do_insert() 89 | { 90 | in_chars(0, 0); 91 | } 92 | 93 | /* operator - apply operators */ 94 | void operator(op, line1, pos1) 95 | char op; 96 | int line1, pos1; 97 | { 98 | int keep_going, line_addr, line2, pos2, size, swap, temp; 99 | char text[MAXTEXT-1]; 100 | 101 | b_getcur(&line2, &pos2); 102 | line_addr = (pos2 < 0); 103 | swap = ((line2 < line1) || (line2 == line1 && pos2 < pos1)); 104 | if (swap) { 105 | /* swap so that Location 1 precedes Location 2 */ 106 | temp = line1; 107 | line1 = line2; 108 | line2 = temp; 109 | temp = pos1; 110 | pos1 = pos2; 111 | pos2 = temp; 112 | } 113 | size = line2 - line1 + 1; /* number of affected lines */ 114 | 115 | if (!line_addr) 116 | keep_going = 1; 117 | else if ((keep_going = do_yank(line1, line2)) == 0 && op != 'y') { 118 | s_putmsg("Cannot yank lines; should operation be completed? "); 119 | keep_going = (k_getch() == 'y'); 120 | } 121 | 122 | if (op == 'y') { 123 | if (!keep_going) 124 | s_savemsg("Cannot yank lines.", 0); 125 | else if (!line_addr) 126 | UNKNOWN; 127 | else if (size >= 5) 128 | s_savemsg("%d lines yanked", size); 129 | } 130 | 131 | if (op == 'y' || !keep_going) 132 | /* return the cursor to its initial location */ 133 | if (swap) 134 | b_setcur(line2, pos2); 135 | else 136 | b_setcur(line1, pos1); 137 | else if (op == 'd') { 138 | if (size >= 5) 139 | s_savemsg("%d lines deleted", size); 140 | do_delete(line1, pos1, line2, pos2); 141 | } else { /* op == 'c' */ 142 | if (size >= 5) 143 | s_savemsg("%d lines changed", size); 144 | if (line_addr) { 145 | if (line1 < line2) 146 | b_delete(line1, line2-1); /* replace the lines.. */ 147 | b_replace(line1, ""); /* ..by an empty line */ 148 | b_setcur(line1, 0); /* start on that line */ 149 | s_refresh(); /* display all this */ 150 | in_chars(0, 0); /* accept input */ 151 | } else { 152 | /* mark the last overwrite location */ 153 | if (--pos2 >= 0) 154 | b_gets(line2, text); 155 | else { 156 | b_gets(--line2, text); 157 | pos2 = strlen(text) - 1; 158 | } 159 | text[pos2] = '$'; 160 | b_replace(line2, text); 161 | b_setcur(line1, pos1); /* start at the left */ 162 | s_refresh(); /* display all this */ 163 | in_chars(line2, pos2); /* accept input */ 164 | } 165 | } 166 | } 167 | 168 | /* 169 | * do_delete - delete text from the buffer 170 | * 171 | * If pos1 < 0 or pos2 < 0, then complete lines from line1 to line2, inclusive, 172 | * are deleted. Otherwise, the deleted text starts at the first address and 173 | * extends up to, but not including, the second address. The first address must 174 | * precede the second. 175 | */ 176 | static void do_delete(line1, pos1, line2, pos2) 177 | int line1, pos1, line2, pos2; 178 | { 179 | char text1[MAXTEXT-1], text2[MAXTEXT-1]; 180 | 181 | if (pos1 < 0 || pos2 < 0) { /* line address */ 182 | b_delete(line1, line2); 183 | if (b_size() == 0) { 184 | s_savemsg("No lines in buffer.", 0); 185 | b_insert(1, ""); 186 | } 187 | b_setline( min(line1, b_size()) ); 188 | } else { 189 | /* glue the head of line1 to the tail of line2 */ 190 | b_gets(line1, text1); 191 | b_gets(line2, text2); 192 | if (pos1 + strlen(text2 + pos2) > MAXTEXT-1) { 193 | UNKNOWN; 194 | s_savemsg("Line length exceeds %d.", MAXTEXT); 195 | return; 196 | } 197 | strcpy(text1 + pos1, text2 + pos2); 198 | if (line1 < line2) 199 | b_delete(line1, line2-1); 200 | b_replace(line1, text1); 201 | if (pos1 > 0 && text1[pos1] == '\0') 202 | --pos1; 203 | b_setcur(line1, pos1); 204 | } 205 | } 206 | 207 | /* 208 | * in_chars - insert characters into the buffer 209 | * 210 | * Read characters from the keyboard and place them in the buffer at the cursor 211 | * location. Existing buffer characters are overwritten until the cursor passes 212 | * the location indicated by in_chars()'s arguments. Thereafter, characters in 213 | * the current line are shifted right to accommodate the new ones. If characters 214 | * marked for overwriting remain when input is ended (with ), then they are 215 | * deleted from the buffer. 216 | */ 217 | static void in_chars(end_line, end_pos) 218 | int end_line, end_pos; 219 | { 220 | int c, cur_line, cur_pos, i, length, start_line, start_pos; 221 | char text[MAXTEXT-1]; 222 | 223 | b_getcur(&cur_line, &cur_pos); 224 | start_line = cur_line; 225 | start_pos = cur_pos; 226 | b_gets(cur_line, text); 227 | length = strlen(text); 228 | while ((c = k_getch()) != ESCAPE) { 229 | switch (c) { 230 | case '\b': 231 | /* don't back up past beginning of a line ... */ 232 | if ((cur_pos == 0) || 233 | /* ... or where the insertion started */ 234 | ((cur_line == start_line) && 235 | (cur_pos == start_pos))) { 236 | UNKNOWN; 237 | continue; 238 | } 239 | --cur_pos; 240 | /* doom the backed-over character for removal */ 241 | if (end_line == 0) { 242 | end_line = cur_line; 243 | end_pos = cur_pos; 244 | } 245 | break; 246 | case CR: 247 | /* break the current line into two lines */ 248 | c = text[cur_pos]; 249 | text[cur_pos] = '\0'; 250 | b_replace(cur_line, text); 251 | text[cur_pos] = c; 252 | if (end_line == cur_line) { 253 | /* good chance to delete text */ 254 | cur_pos = end_pos + 1; 255 | end_line = 0; 256 | } 257 | if (end_line > cur_line) { 258 | b_gets(++cur_line, text); 259 | length = strlen(text); 260 | cur_pos = 0; 261 | break; 262 | } 263 | /* shift text to create current line (strcpy() 264 | is nonportable when its arguments overlap) */ 265 | for (i = 0; (text[i] = text[cur_pos+i]) != '\0'; ++i) 266 | ; 267 | length -= cur_pos; 268 | if (b_insert(++cur_line, text) == 0) /* failed */ 269 | break; 270 | if (end_line > 0) 271 | ++end_line; 272 | cur_pos = 0; 273 | break; 274 | default: 275 | if (!isprint(c) && c != '\t') { 276 | /* unprintable character */ 277 | UNKNOWN; 278 | continue; 279 | } 280 | /* insert the character */ 281 | if (end_line == 0) { /* not overwriting */ 282 | if (length >= MAXTEXT-1) { 283 | UNKNOWN; 284 | s_savemsg("Line length exceeds %d.", 285 | MAXTEXT); 286 | break; 287 | } 288 | for (i = ++length; i > cur_pos; --i) 289 | text[i] = text[i-1]; 290 | } 291 | if (text[cur_pos] == '\0') 292 | text[cur_pos+1] = '\0'; 293 | text[cur_pos] = c; 294 | b_replace(cur_line, text); 295 | /* if this is the last doomed location */ 296 | if (end_line == cur_line && end_pos == cur_pos) 297 | end_line = 0; 298 | ++cur_pos; 299 | break; 300 | } 301 | b_setcur(cur_line, cur_pos); 302 | s_refresh(); 303 | } 304 | if (end_line > 0) 305 | /* delete the doomed characters */ 306 | do_delete(cur_line, cur_pos, end_line, end_pos + 1); 307 | 308 | /* position the cursor at the last inserted character */ 309 | b_setcur(cur_line, max(cur_pos-1, 0)); 310 | } 311 | -------------------------------------------------------------------------------- /Bman.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Bman.c - buffer manager 3 | * 4 | * 5 | * Entry points: 6 | * 7 | * int b_changed() 8 | * Tell if the buffer has been modified since the last call to 9 | * b_newcmd(). 10 | * 11 | * b_delete(from, to) 12 | * int from, to; 13 | * Manage deletion of a range of buffer lines. 14 | * 15 | * b_free() 16 | * Free the temporary buffer storage. 17 | * 18 | * b_getcur(line_ptr, pos_ptr) 19 | * int *line_ptr, *pos_ptr; 20 | * Return the line and position of the cursor. 21 | * 22 | * b_getmark(line_ptr, pos_ptr) 23 | * int *line_ptr, *pos_ptr; 24 | * Return the line and position of the mark. 25 | * 26 | * b_gets(k, s) 27 | * int k; 28 | * char *s; 29 | * Manage copying of the k-th buffer line to s. 30 | * 31 | * b_init() 32 | * Initialize the buffer module. 33 | * 34 | * b_insert(k, s) 35 | * int k; 36 | * char *s; 37 | * Manage insertion of s as the k-th buffer line; tell if 38 | * successful. 39 | * 40 | * int b_lineid(k) 41 | * int k; 42 | * Return the ID of the k-th buffer line. 43 | * 44 | * int b_modified() 45 | * Tell if the buffer has been modified since the last call to 46 | * b_unmod(). 47 | * 48 | * b_newcmd(keyboard) 49 | * int keyboard; 50 | * Record the start of a new edit command. The argument tells 51 | * whether the command will be read from the keyboard. 52 | * 53 | * b_replace(k, s) 54 | * int k; 55 | * char *s; 56 | * Manage replacement of the k-th buffer line with s. 57 | * 58 | * b_setcur(line, pos) 59 | * int line, pos; 60 | * Set the cursor location. 61 | * 62 | * b_setline(line) 63 | * int line; 64 | * Set the cursor to line's first nonwhite character. 65 | * 66 | * b_setmark() 67 | * Set the mark to the cursor location. 68 | * 69 | * int b_size() 70 | * Return the number of lines currently in the buffer. 71 | * 72 | * b_unmod() 73 | * Record that the buffer contents match the external file. 74 | * 75 | * undo() 76 | * Undo the most recent user command that changed the buffer. 77 | * 78 | * 79 | * External procedure calls: 80 | * 81 | * buf_delete(from, to) .. file buffer.c 82 | * int from, to; 83 | * Delete a range of buffer lines. 84 | * 85 | * buf_free() .. file buffer.c 86 | * Free the temporary buffer storage. 87 | * 88 | * buf_gets(k, s) .. file buffer.c 89 | * int k; 90 | * char *s; 91 | * Copy the k-th buffer line to s. 92 | * 93 | * int buf_id(k) .. file buffer.c 94 | * int k; 95 | * Return the ID of the k-the buffer line. 96 | * 97 | * buf_init() .. file buffer.c 98 | * Initialize the buffer. 99 | * 100 | * int buf_insert(k, s) .. file buffer.c 101 | * int k; 102 | * char *s; 103 | * Insert s as the k-th buffer line; tell if successful. 104 | * 105 | * int buf_replace(k, s) .. file buffer.c 106 | * int k; 107 | * char *s; 108 | * Replace the k-th buffer line by s; tell if successful. 109 | * 110 | * s_errmsg(msg, val) .. file Sman.c 111 | * char *msg; 112 | * int val; 113 | * Format and print msg; wait for a key to be pressed. 114 | * 115 | * s_savemsg(msg, val) .. file Sman.c 116 | * char *msg; 117 | * int val; 118 | * Format msg and save it for the next screen refreshing. 119 | */ 120 | 121 | #include 122 | #include 123 | 124 | #include "s.h" 125 | 126 | /* buffer operations */ 127 | #define DELETE 1 128 | #define INSERT 2 129 | #define REPLACE 3 130 | 131 | extern void s_errmsg(), buf_gets(), buf_delete(), buf_free(), buf_init(); 132 | extern void s_savemsg(); 133 | extern int b_lineid(), buf_insert(), buf_id(), buf_replace(); 134 | static void add_rec(), free_recs(); 135 | 136 | static int 137 | b_count = 0, /* number of lines in the buffer */ 138 | changed, /* did last command change the buffer? */ 139 | cur_line, cur_pos, /* cursor location */ 140 | line_prev, pos_prev, /* origin of previous user change */ 141 | line_start, pos_start, /* origin of this user command */ 142 | mark_id, mark_pos, /* ID of marked line; position of mark in line */ 143 | modified; /* does buffer differ from external file? */ 144 | 145 | /* definition of a modification record */ 146 | struct mod_rec { 147 | int type; /* DELETE, INSERT or REPLACE */ 148 | int line; /* line number in the buffer */ 149 | char *del_text; /* deleted text (NULL for INSERT) */ 150 | struct mod_rec *next; /* link to next modification record */ 151 | }; 152 | 153 | static struct mod_rec 154 | *curr_recs, /* mod recs for current user command */ 155 | *prev_recs; /* mod recs for previous user change */ 156 | 157 | /* b_changed - tell if last command changed the buffer */ 158 | int b_changed() 159 | { 160 | return(changed); 161 | } 162 | 163 | /* b_delete - manage deletion of buffer lines */ 164 | void b_delete(from, to) 165 | int from, to; 166 | { 167 | int count, line; 168 | char text[MAXTEXT-1]; 169 | 170 | if ((count = to - from + 1) < 0) 171 | s_errmsg("b_delete(): cannot delete %d lines", count); 172 | else if (from < 1 || to > b_count) 173 | s_errmsg("b_delete(): improper line number %d", 174 | (from < 1) ? from : to ); 175 | else { 176 | for (line = from; line <= to; ++line) { 177 | buf_gets(line, text); 178 | add_rec(DELETE, from, text); 179 | } 180 | buf_delete(from, to); 181 | b_count -= count; 182 | } 183 | } 184 | 185 | /* b_free - manage freeing of temporary buffer storage */ 186 | void b_free() 187 | { 188 | buf_free(); 189 | } 190 | 191 | /* b_getcur - get the cursor location */ 192 | void b_getcur(line_ptr, pos_ptr) 193 | int *line_ptr, *pos_ptr; 194 | { 195 | *line_ptr = cur_line; 196 | *pos_ptr = cur_pos; 197 | } 198 | 199 | /* b_getmark - get the mark's location */ 200 | void b_getmark(line_ptr, pos_ptr) 201 | int *line_ptr, *pos_ptr; 202 | { 203 | int line; 204 | 205 | for (line = 1; line <= b_count; ++line) 206 | if (b_lineid(line) == mark_id) { 207 | *line_ptr = line; 208 | *pos_ptr = mark_pos; 209 | return; 210 | } 211 | *line_ptr = *pos_ptr = 0; 212 | } 213 | 214 | /* b_gets - manage retrieval of a buffer line */ 215 | void b_gets(k, s) 216 | int k; 217 | char *s; 218 | { 219 | if (k < 1 || k > b_count) { 220 | s_errmsg("b_gets(): improper line number %d", k); 221 | strcpy(s, ""); 222 | } else 223 | buf_gets(k, s); 224 | } 225 | 226 | /* b_init - manage buffer initialization */ 227 | void b_init() 228 | { 229 | buf_init(); 230 | } 231 | 232 | /* b_insert - manage insertion of s as k-th buffer line; tell if successful */ 233 | int b_insert(k, s) 234 | int k; 235 | char *s; 236 | { 237 | if (k < 1 || k > b_count + 1) 238 | s_errmsg("b_insert(): improper line number %d", k); 239 | else if (buf_insert(k, s)) { 240 | add_rec(INSERT, k, (char *)NULL); 241 | ++b_count; 242 | return(1); 243 | } 244 | return(0); 245 | } 246 | 247 | /* b_lineid - return ID of buffer line k */ 248 | int b_lineid(k) 249 | int k; 250 | { 251 | if (k < 1 || k > b_count) { 252 | s_errmsg("b_lineid(): improper line number %d", k); 253 | return(0); 254 | } 255 | return(buf_id(k)); 256 | } 257 | 258 | /* b_modified - tell if buffer differs from external file */ 259 | int b_modified() 260 | { 261 | return(modified); 262 | } 263 | 264 | /* b_newcmd - record the start of a command */ 265 | void b_newcmd(keyboard) 266 | int keyboard; 267 | { 268 | changed = 0; /* even if command was pushed back on input */ 269 | 270 | if (!keyboard) 271 | return; 272 | 273 | /* 274 | * It is a user command. If the last user command changed the buffer, 275 | * move its modification records to the prev_recs list. 276 | */ 277 | if (curr_recs != NULL) { 278 | free_recs(prev_recs); 279 | prev_recs = curr_recs; 280 | curr_recs = NULL; 281 | 282 | /* remember where the user change started */ 283 | line_prev = line_start; 284 | pos_prev = pos_start; 285 | } 286 | 287 | /* remember where the current user command started */ 288 | line_start = cur_line; 289 | pos_start = cur_pos; 290 | } 291 | 292 | /* b_replace - manage replacement of a buffer line */ 293 | void b_replace(k, s) 294 | int k; 295 | char *s; 296 | { 297 | char text[MAXTEXT-1]; 298 | 299 | buf_gets(k, text); 300 | if (k < 1 || k > b_count) 301 | s_errmsg("b_replace(): improper line number %d", k); 302 | else if (buf_replace(k, s)) 303 | add_rec(REPLACE, k, text); 304 | } 305 | 306 | /* b_setcur - set buffer's record of the cursor location */ 307 | void b_setcur(line, pos) 308 | int line, pos; 309 | { 310 | if (line < 1 || line > b_count) 311 | s_errmsg("b_setcur(): improper line %d", line); 312 | else if (pos < -1) 313 | /* address() uses pos == -1 to signal a line address */ 314 | s_errmsg("b_setcur(): improper position %d", pos); 315 | else { 316 | cur_line = line; 317 | cur_pos = pos; 318 | } 319 | } 320 | 321 | /* b_setline - set cursor to first nonwhite character of line */ 322 | void b_setline(line) 323 | int line; 324 | { 325 | int pos; 326 | char text[MAXTEXT-1]; 327 | 328 | b_gets(line, text); 329 | for (pos = 0; isspace(text[pos]); ++pos) 330 | ; 331 | if (text[pos] == '\0') 332 | pos = max(pos-1, 0); 333 | b_setcur(line, pos); 334 | } 335 | 336 | /* b_setmark - set buffer's mark to the cursor location */ 337 | void b_setmark() 338 | { 339 | mark_id = b_lineid(cur_line); 340 | mark_pos = cur_pos; 341 | } 342 | 343 | /* b_size - return the number of lines currently in the buffer */ 344 | int b_size() 345 | { 346 | return(b_count); 347 | } 348 | 349 | /* b_unmod - record that the buffer matches the external file */ 350 | void b_unmod() 351 | { 352 | modified = 0; 353 | } 354 | 355 | /* undo - undo the last user command that changed the buffer */ 356 | void undo() 357 | { 358 | struct mod_rec *m; 359 | 360 | if (curr_recs != NULL) { 361 | /* happens if star operation tries to redo an undo */ 362 | s_savemsg("Improper undo operation."); 363 | return; 364 | } 365 | 366 | /* 367 | * Undo() marches down the list of modification records generated by 368 | * the last user change (the list starts with the most recent change). 369 | * A delete is undone by an insert, and vice versa. A replace is 370 | * undone by another replace. 371 | */ 372 | 373 | for (m = prev_recs; m != NULL; m = m->next) 374 | switch (m->type) { 375 | case DELETE: 376 | b_insert(m->line, m->del_text); 377 | break; 378 | case INSERT: 379 | b_delete(m->line, m->line); 380 | break; 381 | case REPLACE: 382 | b_replace(m->line, m->del_text); 383 | break; 384 | default: 385 | s_errmsg("Undo(): cannot happen", 0); 386 | break; 387 | } 388 | 389 | /* change starting location so this undo command can be undone */ 390 | line_start = cur_line; 391 | pos_start = cur_pos; 392 | 393 | if (b_size() > 0) 394 | b_setcur(line_prev, pos_prev); 395 | else { 396 | s_savemsg("No lines in buffer.", 0); 397 | b_insert(1, ""); 398 | b_setcur(1, 0); 399 | } 400 | } 401 | 402 | /* add_rec - add to the list of current modification records */ 403 | static void add_rec(type, line, del_text) 404 | int type, line; 405 | char *del_text; 406 | { 407 | struct mod_rec *new; 408 | static int nospace = 0; /* are we out of memory? */ 409 | char *p; 410 | 411 | changed = modified = 1; 412 | 413 | /* look for the possibility of collapsing modification records */ 414 | if ((curr_recs != NULL) && (curr_recs->line == line) 415 | && (type == REPLACE) && (curr_recs->type != DELETE)) 416 | return; 417 | 418 | /* do nothing if space has been exhausted */ 419 | if (nospace) 420 | return; 421 | 422 | new = (struct mod_rec *) malloc(sizeof(struct mod_rec)); 423 | if ((new == NULL) || ((del_text != NULL) && 424 | ((p = malloc((unsigned)strlen(del_text)+1)) == NULL))) { 425 | nospace = 1; 426 | free_recs(curr_recs); 427 | curr_recs = NULL; 428 | s_errmsg("Ran out of memory!", 0); 429 | return; 430 | } 431 | new->type = type; 432 | new->line = line; 433 | new->del_text = (del_text != (char *)0) ? strcpy(p, del_text) : (char *)0; 434 | new->next = curr_recs; 435 | curr_recs = new; 436 | } 437 | 438 | /* free_recs - free storage for modification records */ 439 | static void free_recs(m) 440 | struct mod_rec *m; 441 | { 442 | struct mod_rec *a; 443 | 444 | for ( ; m != NULL; m = a) { 445 | a = m->next; 446 | if (m->del_text != NULL) 447 | free(m->del_text); 448 | free((char *)m); 449 | } 450 | } 451 | -------------------------------------------------------------------------------- /commands.c: -------------------------------------------------------------------------------- 1 | /* 2 | * commands.c - commands without an address 3 | * ... also handles the :w command, which can contain an address 4 | * 5 | * 6 | * Entry point: 7 | * 8 | * int simp_cmd(n, c) 9 | * int n; 10 | * char c; 11 | * If the count n, character c and, in some cases, characters that 12 | * follow c constitute one of the commands listed below, then the 13 | * command is performed and 1 is returned. Otherwise, 0 is returned. 14 | * 15 | * The commands recognized by simp_cmd() are: 16 | * 17 | * a - append characters after the cursor location 18 | * A - append characters at the end of the current line 19 | * C - change the remainder of the line (same as c$) 20 | * D - delete the remainder of the line (same as d$) 21 | * i - insert characters at the cursor location 22 | * J - join two lines together 23 | * m - mark the current position 24 | * o - insert lines below the current line 25 | * O - insert lines above the current line 26 | * p - put the contents of the yank buffer below the current line 27 | * P - put the contents of the yank buffer above the current line 28 | * q - quit; don't save the modifications 29 | * r - replace the current character by 30 | * s - substitute for the current character (same as c) 31 | * u - undo the most recent buffer-change command 32 | * x - delete n characters (same as d) 33 | * ZZ - save the modifications and quit 34 | * ? - tell the current position in the file 35 | * . - redo the most recent buffer-change command 36 | * * - iterate the last search-change pair 37 | * :e - edit another file 38 | * :r - read a file; place its contents below the current line 39 | * :R - read a file; place its contents above the current line 40 | * :w - write a range of lines to a file 41 | * 42 | * 43 | * External procedures calls: 44 | * 45 | * address(n, c, op) .. file address.c 46 | * int n; 47 | * char c, op; 48 | * Set the buffer's cursor according to the count n, 49 | * the cursor movement command c and the operation op. 50 | * 51 | * b_delete(first, last) .. file Bman.c 52 | * int first, last; 53 | * Delete a range of buffer lines. 54 | * 55 | * b_free() .. file Bman.c 56 | * Free all temporary buffer storage. 57 | * 58 | * b_getcur(line_ptr, pos_ptr) .. file Bman.c 59 | * int *line_ptr, *pos_ptr; 60 | * Return the line and position of the cursor. 61 | * 62 | * b_getmark(line_ptr, pos_ptr) .. file Bman.c 63 | * int *line_ptr, *pos_ptr; 64 | * Return the line and position of the mark. 65 | * 66 | * b_gets(k, s) .. file Bman.c 67 | * int k; 68 | * char s[]; 69 | * Copy the k-th buffer line to s. 70 | * 71 | * int b_insert(k, s) .. file Bman.c 72 | * int k; 73 | * char s[]; 74 | * Insert s into the buffer as line k; tell if successful. 75 | * 76 | * int b_modified() .. file Bman.c 77 | * Tell if buffer contents differ from the external file. 78 | * 79 | * b_setcur(line, pos) .. file Bman.c 80 | * int line, pos; 81 | * Set the cursor to the indicated location. 82 | * 83 | * b_setline(line) .. file Bman.c 84 | * int line; 85 | * Set the cursor to line's first nonwhite character. 86 | * 87 | * b_setmark(line, pos) .. file Bman.c 88 | * int line, pos; 89 | * Mark the indicated location. 90 | * 91 | * int b_size() .. file Bman.c 92 | * Return the number of lines currently in the buffer. 93 | * 94 | * b_unmod() .. file Bman.c 95 | * Record that buffer contents match the external file. 96 | * 97 | * do_insert() .. file operator.c 98 | * Read characters from keyboard; insert them in buffer. 99 | * 100 | * do_put(way) .. file operator.c 101 | * int way; 102 | * Put the yank buffer on the indicated side of the current line. 103 | * 104 | * k_donext(command) .. file keyboard.c 105 | * char *command; 106 | * Arrange for the given editor command to be executed next. 107 | * 108 | * k_finish() .. file keyboard.c 109 | * Close down the keyboard manager. 110 | * 111 | * int k_getch() .. file keyboard.c 112 | * Return the next character of the command. 113 | * 114 | * k_redo() .. file keyboard.c 115 | * Redo the most recent buffer-change command. 116 | * 117 | * s_finish() .. file Sman.c 118 | * Shut down the display module. 119 | * 120 | * char *s_getmsg(msg) .. file Sman.c 121 | * char *msg; 122 | * Print msg; return the user's reply, 123 | * 124 | * int s_ismsg() .. file Sman.c 125 | * Tell if an error message is pending. 126 | * 127 | * s_putmsg(msg) .. file Sman.c 128 | * char *msg; 129 | * Print the message on the last row. 130 | * 131 | * s_refresh() .. file Sman.c 132 | * Bring the display up to date with the buffer. 133 | * 134 | * s_savemsg(msg, val) .. file Sman.c 135 | * char *msg; 136 | * int val; 137 | * Format msg and save it for the next screen refreshing. 138 | * 139 | * undo() .. file Bman.c 140 | * Undo the most recent buffer-change command. 141 | */ 142 | 143 | #include 144 | #include 145 | 146 | #include "s.h" 147 | 148 | extern void b_getcur(), b_gets(), b_setcur(), s_refresh(), do_insert(); 149 | extern void k_donext(), s_savemsg(), b_delete(), b_setmark(); 150 | extern void do_put(), s_putmsg(), b_free(), k_finish(), s_finish(); 151 | extern void undo(), k_redo(), adjust(), b_getmark(), address(); 152 | extern void b_newcmd(), b_unmod(), b_setline(), s_errmsg(); 153 | extern int b_size(), b_modified(), k_getch(), s_ismsg(), strsame(), b_insert(); 154 | static void do_star(), do_io(), do_write(), write_lines(); 155 | static int do_read(); 156 | 157 | static char cur_file[MAXTEXT]; /* remembers name of the current file */ 158 | 159 | int simp_cmd(n, c) 160 | int n; 161 | char c; 162 | { 163 | int cur_line, cur_pos, i; 164 | char *t, text1[MAXTEXT], text2[MAXTEXT]; 165 | 166 | b_getcur(&cur_line, &cur_pos); 167 | switch (c) { 168 | case 'a': 169 | b_gets(cur_line, text1); 170 | if (text1[0] != '\0') { /* unless the line is empty */ 171 | b_setcur(cur_line, cur_pos+1); 172 | s_refresh(); 173 | } 174 | do_insert(); 175 | break; 176 | case 'A': 177 | b_gets(cur_line, text1); 178 | b_setcur(cur_line, strlen(text1)); 179 | s_refresh(); 180 | do_insert(); 181 | break; 182 | case 'C': 183 | k_donext("c$"); 184 | break; 185 | case 'D': 186 | k_donext("d$"); 187 | break; 188 | case 'i': 189 | do_insert(); 190 | break; 191 | case 'J': 192 | if (cur_line >= b_size()) { 193 | UNKNOWN; 194 | break; 195 | } 196 | b_gets(cur_line, text1); 197 | i = strlen(text1); 198 | /* put a space between the two lines */ 199 | text1[i] = ' '; 200 | b_gets(cur_line + 1, text2); 201 | /* strip leading white characters from second line */ 202 | for (t = text2; isspace(*t); ++t) 203 | ; 204 | if (i + 1 + strlen(t) >= MAXTEXT-1) { 205 | s_savemsg("Line length exceeds %d.", MAXTEXT); 206 | break; 207 | } 208 | strcpy(text1 + i + 1, t); 209 | b_delete(cur_line, cur_line + 1); 210 | b_insert(cur_line, text1); 211 | b_setcur(cur_line, i); 212 | break; 213 | case 'm': 214 | b_setmark(); 215 | break; 216 | case 'o': 217 | ++cur_line; 218 | /* no break statement; fall into ... */ 219 | case 'O': 220 | b_insert(cur_line, ""); 221 | b_setcur(cur_line, 0); 222 | s_refresh(); 223 | do_insert(); 224 | break; 225 | case 'p': 226 | do_put(1); 227 | break; 228 | case 'P': 229 | do_put(-1); 230 | break; 231 | case 'q': 232 | if (b_modified()) { 233 | s_putmsg("Discard? "); 234 | if (k_getch() != 'y') { 235 | UNKNOWN; 236 | break; 237 | } 238 | } 239 | if (s_ismsg()) 240 | s_refresh(); 241 | b_free(); 242 | k_finish(); 243 | s_finish(); 244 | exit(0); 245 | case 'r': 246 | sprintf(text1, "c %c%c", k_getch(), ESCAPE); 247 | k_donext(text1); /* c */ 248 | break; 249 | case 's': 250 | k_donext("c "); /* c */ 251 | break; 252 | case 'u': 253 | undo(); 254 | break; 255 | case 'x': 256 | if (n == 0) /* set default */ 257 | n = 1; 258 | sprintf(text1, "%dd ", n); 259 | k_donext(text1); /* d */ 260 | break; 261 | case 'Z': 262 | if (k_getch() != 'Z') { 263 | UNKNOWN; 264 | break; 265 | } 266 | if (b_modified()) { 267 | sprintf(text1, ":w%cq", CR); 268 | k_donext(text1); /* :wq */ 269 | } else 270 | k_donext("q"); 271 | break; 272 | case '?': 273 | sprintf(text1, "%s: %sline %d of %d", cur_file, 274 | (b_modified()) ? "[Modified] " : "", 275 | cur_line, b_size()); 276 | s_savemsg(text1, 0); 277 | break; 278 | case '.': 279 | k_redo(); 280 | break; 281 | case '*': 282 | do_star(); 283 | break; 284 | case ':': 285 | do_io(); 286 | break; 287 | case '#': 288 | adjust(n); 289 | break; 290 | default: 291 | return(0); 292 | } 293 | return(1); 294 | } 295 | 296 | /* 297 | -------------------- the star command -------------------- 298 | * Entry point: 299 | * 300 | * do_star() 301 | * From the minimum of the current and marked lines, alternate 302 | * between n and redo commands until the maximum of the two lines 303 | * is reached. The marked line defaults to the last buffer line. 304 | */ 305 | 306 | #define STAR_MAX 50 /* limits the number of n and redo commands */ 307 | 308 | static void do_star() 309 | { 310 | static int all_lines, doing_star = 0, iterations, start_line = 0, 311 | start_pos; 312 | int cur_line, cur_pos, done, mark_line, mark_pos, old_line, old_pos; 313 | 314 | if (!b_modified()) { 315 | UNKNOWN; 316 | return; 317 | } 318 | if (s_ismsg()) { /* an error message is waiting; don't continue */ 319 | doing_star = 0; 320 | if (start_line == 0) 321 | UNKNOWN; 322 | else 323 | b_setcur(start_line, start_pos); 324 | return; 325 | } 326 | if (!doing_star) { /* initialize for this star command */ 327 | doing_star = 1; 328 | iterations = 0; 329 | b_getcur(&start_line, &start_pos); 330 | b_getmark(&mark_line, &mark_pos); 331 | if (mark_line > 0 && mark_line < start_line) { 332 | /* guarantee that cursor precedes mark */ 333 | b_setmark(); 334 | b_setcur(mark_line, mark_pos); 335 | b_getcur(&start_line, &start_pos); 336 | b_getmark(&mark_line, &mark_pos); 337 | } 338 | if (mark_line == 0 || mark_line == b_size() || start_line == b_size()) 339 | all_lines = 1; 340 | else { 341 | all_lines = 0; 342 | /* move mark to the following line */ 343 | b_setcur(mark_line+1, 0); 344 | b_setmark(); 345 | b_setcur(start_line, start_pos); 346 | } 347 | } 348 | /* execute an n command */ 349 | b_getcur(&old_line, &old_pos); 350 | address(0, 'n', ' '); 351 | b_getcur(&cur_line, &cur_pos); 352 | b_getmark(&mark_line, &mark_pos); 353 | /* done if past mark or if the search fails */ 354 | done = (((!all_lines && cur_line >= mark_line) || 355 | (cur_line < old_line) || 356 | (cur_line == old_line)) && (cur_pos <= old_pos)); 357 | if (!done && ++iterations <= STAR_MAX) 358 | /* execute a redo command then return to this procedure */ 359 | k_donext(".*"); 360 | else { 361 | if (done) 362 | if (iterations == 1) 363 | s_savemsg("1 change", 0); 364 | else 365 | s_savemsg("%d changes", iterations); 366 | else 367 | s_savemsg("%d changes; type '*' to continue.", STAR_MAX); 368 | doing_star = 0; 369 | b_setcur(start_line, start_pos); 370 | } 371 | } 372 | 373 | /* 374 | -------------------- I/O commands -------------------- 375 | * 376 | * Entry point: 377 | * 378 | * do_io(); 379 | * Read the part of an edit command after ':', then execute it. 380 | * 381 | */ 382 | 383 | static void do_io() 384 | { 385 | int cur_line, cur_pos, size; 386 | char *file, *s_getmsg(), msg[80], *reply; 387 | 388 | /* get the remainder of the command */ 389 | reply = s_getmsg(":"); 390 | b_getcur(&cur_line, &cur_pos); 391 | 392 | /* write commands contain an address; treat them as a special case */ 393 | if (*reply == 'w') { 394 | do_write(reply+1); 395 | b_setcur(cur_line, cur_pos); 396 | return; 397 | } 398 | 399 | /* find the start of the file name */ 400 | for (file = reply+1; *file == ' '; ++file) 401 | ; 402 | if (*file == '\0') 403 | file = cur_file; /* default: use the current file name */ 404 | switch (*reply) { 405 | case 'e': 406 | if (b_modified()) { 407 | s_putmsg("Discard? "); 408 | if (k_getch() != 'y') 409 | break; 410 | } 411 | if (file != cur_file) 412 | strcpy(cur_file, file); /* remember name */ 413 | b_delete(1, b_size()); 414 | b_newcmd(); /* so the :e command cannot be undone */ 415 | if ((size = do_read(file, 1)) > 0) { 416 | sprintf(msg, "%s: %d lines", file, size); 417 | s_savemsg(msg, 0); 418 | } else { 419 | if (size == -1) { 420 | sprintf(msg, "%s is a new file", file); 421 | s_savemsg(msg, 0); 422 | } 423 | b_insert(1, ""); 424 | b_setcur(1, 0); 425 | } 426 | /* record that buffer contents match external file */ 427 | b_unmod(); 428 | break; 429 | case 'r': 430 | ++cur_line; 431 | /* no break statement; fall into ... */ 432 | case 'R': 433 | if ((size = do_read(file, cur_line)) > 5) 434 | s_savemsg("%d lines read", size); 435 | else if (size == -1) { 436 | sprintf(msg, "Cannot read %s.", file); 437 | s_savemsg(msg, 0); 438 | } 439 | break; 440 | default: 441 | UNKNOWN; 442 | break; 443 | } 444 | } 445 | 446 | /* 447 | * do_read - read a file to buffer at the indicated line; 448 | * a returned value >= 0 tells number of lines that were read from the file 449 | * a returned value -1 means that the file could not be opened for reading 450 | * a returned value -2 means that the file contains nonprintable characters 451 | */ 452 | static int do_read(file, line) 453 | char *file; 454 | int line; 455 | { 456 | FILE *fp, *fopen(); 457 | int i, c; 458 | char text[MAXTEXT]; 459 | 460 | if ((fp = fopen(file, "r")) == NULL) 461 | return(-1); 462 | /* 463 | * Read the first ten characters and check that they are printable. 464 | * Some C I/O packages read '\r' () characters. Also, 465 | * nonprintable characters in files might be used for, e.g., 466 | * printer control characters. 467 | */ 468 | for (i = 0; (c = getc(fp)) != EOF && i < 10; ++i) 469 | if (!isprint(c) && c != '\n' && c != '\t' ) { 470 | sprintf(text, "%s is not a text file", file); 471 | s_savemsg(text, 0); 472 | fclose(fp); 473 | return(-2); 474 | } 475 | if (i == 0) { 476 | sprintf(text, "%s is empty", file); 477 | s_savemsg(text, 0); 478 | fclose(fp); 479 | return(0); 480 | } 481 | 482 | rewind(fp); 483 | /* copy the file to the buffer */ 484 | for (i = line; fgets(text, MAXTEXT, fp) != NULL; ++i) { 485 | text[strlen(text)-1] = '\0'; /* trim off the newline */ 486 | if (b_insert(i, text) == 0) { 487 | s_errmsg("Out of memory on line %d", i); 488 | break; 489 | } 490 | } 491 | fclose(fp); 492 | /* move to the first nonwhite character in the first line read */ 493 | b_setline(line); 494 | return(i - line); 495 | } 496 | 497 | /* do_write - write a file according to given specifications */ 498 | static void do_write(specs) 499 | char *specs; 500 | { 501 | int cur_line, cur_pos, n, new_line, new_pos; 502 | char *addr, *file; 503 | 504 | 505 | /* special case: :w writes buffer to current file */ 506 | if (*specs == '\0') { 507 | write_lines(1, b_size(), cur_file); 508 | return; 509 | } 510 | /* special case: :wfile writes buffer to named file */ 511 | if (*specs == ' ') { 512 | for (file = specs + 1; *file == ' '; ++file) 513 | ; 514 | write_lines(1, b_size(), file); 515 | return; 516 | } 517 | 518 | /* get the count that follows ":w" */ 519 | n = 0; 520 | for (addr = specs; isdigit(*addr); ++addr) 521 | n = 10*n + *addr - '0'; 522 | if (*addr == '\0') { 523 | UNKNOWN; 524 | return; 525 | } 526 | 527 | /* find and mark the end of the address */ 528 | for (file = addr; *file != '\0' && *file != ' '; ++file) 529 | ; 530 | if (*file == ' ') 531 | *file++ = '\0'; 532 | 533 | b_getcur(&cur_line, &cur_pos); 534 | /* push the address, minus the first character, back on the input */ 535 | k_donext(addr + 1); 536 | address(n, *addr, ':'); 537 | b_getcur(&new_line, &new_pos); 538 | if (new_line == cur_line && new_pos == cur_pos) { 539 | /* the address did not make sense */ 540 | UNKNOWN; 541 | return; 542 | } 543 | 544 | /* special case: :w writes to the current file */ 545 | if (*file == '\0') 546 | file = cur_file; 547 | 548 | if (cur_line <= new_line) 549 | write_lines(cur_line, new_line, file); 550 | else 551 | write_lines(new_line, cur_line, file); 552 | } 553 | 554 | /* write_lines - write a range of lines to a file */ 555 | static void write_lines(from, to, file) 556 | int from, to; 557 | char *file; 558 | { 559 | FILE *fopen(), *fp; 560 | int count = to - from + 1; 561 | char text[MAXTEXT-1]; 562 | 563 | /* be cautious about overwriting files */ 564 | if (!strsame(file, cur_file)) { 565 | if ((fp = fopen(file, "r")) != NULL) { 566 | s_putmsg("Overwrite? "); 567 | if (k_getch() == 'y') 568 | fclose(fp); 569 | else 570 | return; 571 | } 572 | } else if (from > 1 || to < b_size()) { 573 | s_putmsg("Write partial buffer to current file? "); 574 | if (k_getch() != 'y') 575 | return; 576 | } 577 | 578 | if ((fp = fopen(file, "w")) == NULL) { 579 | sprintf(text, "Cannot write %s.", file); 580 | s_savemsg(text, 0); 581 | return; 582 | } 583 | /* if entire buffer is saved, record that the user is free to quit */ 584 | if (strsame(file, cur_file) && from == 1 && to == b_size()) 585 | b_unmod(); 586 | /* write the lines */ 587 | while (from <= to) { 588 | b_gets(from++, text); 589 | fprintf(fp, "%s\n", text); 590 | } 591 | fclose(fp); 592 | sprintf(text, "%s: %d line%s", file, count, (count == 1) ? "" : "s"); 593 | s_savemsg(text, 0); 594 | } 595 | -------------------------------------------------------------------------------- /address.c: -------------------------------------------------------------------------------- 1 | /* 2 | * address.c - process addresses 3 | * 4 | * 5 | * Entry point: 6 | * 7 | * address(n, c, op) 8 | * int n; 9 | * char c, op; 10 | * Reposition the cursor as specified by the count n, the motion 11 | * command c and the operator op. For pure cursor movement, 12 | * op = ' ' (the blank character). For line addresses used with an 13 | * operator (op != ' '), the cursor position in the addressed line 14 | * is set to -1. If the addressing operation fails, then the 15 | * cursor's location is unchanged. 16 | * 17 | * A list of the addressing commands follows. The default value of the 18 | * count is 1, except for commands g (where it is the number of lines 19 | * in the buffer) and ctrl('d') and ctrl('u') (where it is half of a screen). 20 | * Commands M, 0 (zero), ' (apostrophe), and ` (backquote) ignore the count. 21 | * 22 | * Line Addresses: 23 | * g - line n of the buffer 24 | * H - down n lines from the top of the screen 25 | * L - up n lines from the bottom of the screen 26 | * M - the middle line of the screen 27 | * - down n lines from the current line 28 | * - - up n lines from the current line 29 | * ctrl(d) - down n lines from the bottom of the screen 30 | * ctrl(u) - up n lines from the top of the screen 31 | * c - down n-1 lines (only with the c operator) 32 | * d - down n-1 lines (only with the d operator) 33 | * y - down n-1 lines (only with the y operator) 34 | * 35 | * Character Addresses: 36 | * b - back n words 37 | * f - right n occurrences of 38 | * F - left n occurrences of 39 | * h - left n characters (same as ) 40 | * j - down n lines in the same column 41 | * k - up n lines in the same column 42 | * l - right n characters (same as ) 43 | * n - n repetetions of the previous pattern search 44 | * w - forward n words 45 | * 0 (zero) - the start of the current line 46 | * $ - the end of the (n-1)th following line 47 | * - right n characters 48 | * - left n characters 49 | * ' (apostrophe) - return to the marked location 50 | * ` (backquote) - return to the previous location 51 | * ; - n repetetions of the previous f or F command 52 | * / - forward n occurrences of string 53 | * \ - back n occurrences of string 54 | * 55 | * 56 | * External procedure calls: 57 | * 58 | * b_getcur(line_ptr, pos_ptr) .. file Bman.c 59 | * int *line_ptr, *pos_ptr; 60 | * Return the line and position of the cursor. 61 | * 62 | * b_getmark(line_ptr, pos_ptr) .. file Bman.c 63 | * int *line_ptr, *pos_ptr; 64 | * Return the line and position of the mark. 65 | * 66 | * b_gets(k, s) .. file Bman.c 67 | * int k; 68 | * char s[]; 69 | * Copy the k-th buffer line to s. 70 | * 71 | * b_setcur(line, pos) .. file Bman.c 72 | * int line, pos; 73 | * Set the cursor location. 74 | * 75 | * b_setline(line) .. file Bman.c 76 | * int line; 77 | * Set the cursor to line's first nonwhite character. 78 | * 79 | * int b_size() .. file Bman.c 80 | * Return the number of lines in the buffer. 81 | * 82 | * int k_getch() .. file keyboard.c 83 | * Return the next character from the keyboard. 84 | * 85 | * char k_lastcmd() .. file keyboard.c 86 | * Return the first letter in the last command. 87 | * 88 | * int s_firstline() .. file Sman.c 89 | * Return the number of the first line visible on the screen. 90 | * 91 | * char *s_getmsg(msg) .. file Sman.c 92 | * char *msg; 93 | * Print msg; return the user's reply. 94 | * 95 | * int s_lastline() .. file Sman.c 96 | * Return the number of the last line visible on the screen. 97 | */ 98 | 99 | #include 100 | 101 | #include "s.h" 102 | 103 | /* define "identifier character" and "special character" */ 104 | #define ident_char(x) (isalnum(x) || x == '_') 105 | #define special_char(x) (!ident_char(x) && !isspace(x)) 106 | 107 | extern void b_getcur(), b_setcur(), b_gets(), b_getmark(), b_setline(); 108 | extern char k_lastcmd(), *s_getmsg(); 109 | extern int b_size(), s_firstline(), s_lastline(), k_getch(); 110 | static void do_up_down(), loc_char(), loc_word(); 111 | static void loc_string(); 112 | static int col_to_pos(), pos_to_col(), word_start(), locate(); 113 | 114 | void address(n, c, op) 115 | int n; 116 | char c, op; 117 | { 118 | static int prev_line = 0, prev_pos, scroll_size = SCROLL_SIZE; 119 | int cur_line, cur_pos, direction, limit, line_addr, mark_line, mark_pos, 120 | new_line, new_pos; 121 | char ch, text[MAXTEXT-1]; 122 | 123 | /* set default count to 1, except for three special cases */ 124 | if (n == 0 && c != 'g' && c != ctrl('d') && c != ctrl('u')) 125 | n = 1; 126 | b_getcur(&cur_line, &cur_pos); /* cursor location */ 127 | line_addr = 0; /* reset by commands that address lines */ 128 | 129 | switch (c) { 130 | 131 | /* ---------- Line Addresses: ---------- */ 132 | case 'g': 133 | /* ad hoc default value for the count */ 134 | if (n == 0) 135 | n = b_size(); 136 | line_addr = n; 137 | break; 138 | case 'H': 139 | line_addr = s_firstline() + n - 1; 140 | break; 141 | case 'L': 142 | line_addr = max (s_lastline() - n + 1, 1); 143 | break; 144 | case 'M': 145 | line_addr = (s_firstline() + s_lastline())/2; 146 | break; 147 | case CR: /* */ 148 | line_addr = cur_line + n; 149 | break; 150 | case '-': 151 | line_addr = max (cur_line - n, 1); 152 | break; 153 | case ctrl('d'): 154 | /* ad hoc interpretation of the count */ 155 | if (n != 0) 156 | scroll_size = n; 157 | line_addr = s_lastline() + scroll_size; 158 | break; 159 | case ctrl('u'): 160 | if (n != 0) 161 | scroll_size = n; 162 | line_addr = max (s_firstline() - scroll_size, 1); 163 | break; 164 | case 'c': 165 | case 'd': 166 | case 'y': 167 | if (op == c) 168 | line_addr = cur_line + n - 1; 169 | break; 170 | 171 | /* ---------- Character Addresses: ---------- */ 172 | case 'b': 173 | while (n-- > 0) 174 | loc_word(-1); 175 | /* 176 | * ad hoc rule: 177 | * operators affect only the current line 178 | */ 179 | if (op != ' ') { 180 | b_getcur(&new_line, &new_pos); 181 | if (new_line != cur_line) 182 | b_setcur(cur_line, 0); 183 | } 184 | break; 185 | case 'f': 186 | case 'F': 187 | direction = (c == 'f') ? 1 : -1; 188 | ch = k_getch(); 189 | while (n-- > 0) 190 | loc_char(ch, direction); 191 | break; 192 | case 'h': 193 | case '\b': /* */ 194 | b_setcur(cur_line, max(cur_pos - n, 0)); 195 | break; 196 | case 'j': 197 | do_up_down(n); 198 | break; 199 | case 'k': 200 | do_up_down(-n); 201 | break; 202 | case 'l': 203 | case ' ': 204 | b_gets(cur_line, text); 205 | limit = strlen(text) - 1; 206 | /* 207 | * ad hoc rule: 208 | * operators affect the line's last character 209 | */ 210 | if (op != ' ') 211 | ++limit; 212 | 213 | b_setcur(cur_line, min(cur_pos + n, limit)); 214 | break; 215 | case 'n': 216 | while (n-- > 0) 217 | loc_string('\0'); 218 | break; 219 | case 'w': 220 | while (n-- > 0) 221 | loc_word(1); 222 | /* 223 | * ad hoc rule: 224 | * operators affect only the current line 225 | */ 226 | if (op != ' ') { 227 | b_getcur(&new_line, &new_pos); 228 | if (new_line != cur_line || 229 | new_pos == cur_pos) { /* last word in buffer */ 230 | /* set cursor past the end of line */ 231 | b_gets(cur_line, text); 232 | b_setcur(cur_line, strlen(text)); 233 | } 234 | } 235 | /* 236 | * ad hoc rule: 237 | * c does not affect the whitespace at the end of a word 238 | */ 239 | if (op == 'c') { 240 | b_getcur(&new_line, &new_pos); 241 | b_gets(new_line, text); 242 | while (new_pos > 0 && isspace(text[new_pos-1])) 243 | --new_pos; 244 | b_setcur(new_line, new_pos); 245 | } 246 | break; 247 | case '0': /* zero */ 248 | b_setcur(cur_line, 0); 249 | break; 250 | case '$': 251 | new_line = cur_line + n - 1; 252 | b_gets(new_line, text); 253 | new_pos = strlen(text) - 1; 254 | /* 255 | * ad hoc rule: 256 | * operators affect the line's last character 257 | */ 258 | if (op != ' ') 259 | ++new_pos; 260 | 261 | b_setcur(new_line, new_pos); 262 | break; 263 | case '\'': /* apostrophe */ 264 | b_getmark(&mark_line, &mark_pos); 265 | if (mark_line == 0) 266 | break; 267 | b_setcur(mark_line, mark_pos); 268 | /* 269 | * ad hoc rule: 270 | * operators treat the marked location as a line address 271 | */ 272 | if (op != ' ') 273 | line_addr = mark_line; 274 | 275 | break; 276 | case '`': /* backquote */ 277 | if (prev_line == 0) 278 | break; 279 | b_setcur(prev_line, prev_pos); 280 | /* 281 | * ad hoc rule: 282 | * operators treat the previous location as a line address 283 | */ 284 | if (op != ' ') 285 | line_addr = prev_line; 286 | 287 | break; 288 | case ';': 289 | while (n-- > 0) 290 | loc_char('\0', 0); 291 | break; 292 | case '\\': 293 | case '/': /* pattern matching */ 294 | loc_string(c); 295 | while (n-- > 1) 296 | loc_string('\0'); 297 | break; 298 | default: 299 | break; 300 | } 301 | 302 | /* set the cursor for line addresses */ 303 | if (line_addr > 0 ) { 304 | line_addr = min (line_addr, b_size()); 305 | if (op == ' ') /* no operator */ 306 | /* move to the first nonwhite character of line */ 307 | b_setline(line_addr); 308 | else 309 | /* use position -1 to signify a line address */ 310 | b_setcur(line_addr, -1); 311 | } 312 | 313 | /* handle the previous location for the ` (backquote) command */ 314 | if (op == ' ') { 315 | b_getcur(&new_line, &new_pos); 316 | if (new_line != cur_line || new_pos != cur_pos) { 317 | prev_line = cur_line; 318 | prev_pos = cur_pos; 319 | } 320 | } else if (op == 'c' || op == 'd') 321 | /* buffer change; the previous location becomes undefined */ 322 | prev_line = 0; 323 | /* else op is yank or write; do nothing */ 324 | } 325 | 326 | /* 327 | -------------------- the j and k commands -------------------- 328 | * 329 | * Entry point: 330 | * 331 | * do_up_down(i) 332 | * int i; 333 | * Move the cursor i lines, staying in the same column. Throughout 334 | * an uninterrupted sequence of j and k commands, the cursor stays 335 | * in the same column subject to the constraint that it always lie 336 | * on a buffer character. 337 | */ 338 | 339 | static void do_up_down(i) 340 | int i; 341 | { 342 | static int col; /* remembered column */ 343 | int cur_line, cur_pos, new_line, new_pos; 344 | 345 | b_getcur(&cur_line, &cur_pos); 346 | if (i > 0) 347 | new_line = min (cur_line + i, b_size()); 348 | else 349 | new_line = max (cur_line + i, 1); 350 | /* if the last command was neither j nor k, compute a new column */ 351 | if (k_lastcmd() != 'j' && k_lastcmd() != 'k') 352 | col = pos_to_col(cur_line, cur_pos); 353 | /* translate the screen column to a position in the new line */ 354 | new_pos = col_to_pos(new_line, col); 355 | b_setcur(new_line, new_pos); 356 | } 357 | 358 | /* col_to_pos - convert a screen column to a line position */ 359 | static int col_to_pos(line, col) 360 | int line, col; 361 | { 362 | int c, p; 363 | char text[MAXTEXT-1]; 364 | 365 | b_gets(line, text); 366 | for (c = 1, p = 0; c < col && text[p] != '\0'; ++c, ++p) 367 | /* keep column c corresponding to position p */ 368 | if (text[p] == '\t') 369 | while (c%TAB_WIDTH != 0) 370 | if (++c >= col) 371 | return(p); 372 | if (p > 0 && text[p] == '\0') 373 | --p; 374 | return(p); 375 | } 376 | 377 | /* pos_to_col - convert a line position to a screen column */ 378 | static int pos_to_col(line, pos) 379 | int line, pos; 380 | { 381 | int c, p; 382 | char text[MAXTEXT-1]; 383 | 384 | b_gets(line, text); 385 | for (c = 1, p = 0; p < pos && text[p] != '\0'; ++c, ++p) 386 | /* keep column c corresponding to position p */ 387 | if (text[p] == '\t') 388 | while (c%TAB_WIDTH != 0) 389 | ++c; 390 | return(c); 391 | } 392 | 393 | /* 394 | --------------------- the f, F and ; commands -------------------- 395 | * 396 | * Entry point: 397 | * 398 | * loc_char(ch, way) 399 | * char ch; 400 | * int way; 401 | * Set cursor to the next position of ch in the current line. 402 | * If way = 1, then search to the right; otherwise, way = -1 and 403 | * the search moves left. If ch = '\0', then ch and way are taken 404 | * from the previous call to loc_char. 405 | */ 406 | 407 | static void loc_char(ch, way) 408 | char ch; 409 | int way; 410 | { 411 | static int w; /* remembered way */ 412 | int cur_line, cur_pos; 413 | static char c = 0; /* remembered ch */ 414 | char *b, buf[MAXTEXT-1]; 415 | 416 | 417 | if (ch != 0) { 418 | c = ch; 419 | w = way; 420 | } else if (c == 0) 421 | return; /* no character specified or remembered */ 422 | b_getcur(&cur_line, &cur_pos); 423 | b_gets(cur_line, buf); 424 | for (b = buf + cur_pos + w; b >= buf && *b != '\0'; b += w) 425 | if (*b == c) { 426 | b_setcur(cur_line, b - buf); 427 | break; 428 | } 429 | } 430 | 431 | /* 432 | -------------------- the b and w commands -------------------- 433 | * 434 | * Entry point: 435 | * 436 | * loc_word(way) 437 | * int way; 438 | * Set cursor to the start of the next word. If way = 1, then 439 | * search toward file's end; otherwise, search toward file's 440 | * beginning. Do not "wrap around" at the ends of the file. 441 | */ 442 | 443 | static void loc_word(way) 444 | int way; 445 | { 446 | int cur_line, cur_pos; 447 | char *b, buf[MAXTEXT-1]; 448 | 449 | b_getcur(&cur_line, &cur_pos); 450 | b_gets(cur_line, buf); 451 | /* try the current line */ 452 | for (b = buf + cur_pos + way; b >= buf && *b != '\0'; b += way) 453 | if (word_start(b, buf)) 454 | break; 455 | /* try other lines */ 456 | while (!word_start(b,buf)) { 457 | cur_line += way; 458 | if (cur_line > b_size() || cur_line < 1) 459 | break; 460 | b_gets(cur_line, buf); 461 | b = (way == 1) ? buf : buf + strlen(buf) - 1; 462 | while (b >= buf && *b != '\0' && !word_start(b,buf)) 463 | b += way; 464 | } 465 | if (word_start(b, buf)) 466 | b_setcur(cur_line, b - buf); 467 | } 468 | 469 | /* word_start - tell if s points to the start of a word in text */ 470 | static int word_start(s, text) 471 | char *s, *text; 472 | { 473 | if (s < text || *s == '\0') 474 | return(0); 475 | if (s == text) 476 | return(!isspace(*s)); 477 | if ((ident_char(*s) && !ident_char(s[-1])) 478 | || (special_char(*s) && !special_char(s[-1]))) 479 | return(1); 480 | return(0); 481 | } 482 | 483 | /* 484 | -------------------- the /, \ and n commands -------------------- 485 | * 486 | * Entry point: 487 | * 488 | * loc_string(ch) 489 | * char ch; 490 | * Set cursor to the next instance of a user-supplied string. 491 | * (Loc_string prompts the user to provide the string.) 492 | * If ch = '/', then the search is forward in the file and 493 | * wraps around from the end of the file to the start. If 494 | * ch = '\', then the search is backward (toward the start of 495 | * the file) and wraps around from the first line to the last line. 496 | * For commands / and \ the string is taken 497 | * from the previous call to loc_string. If ch = '\0', then the 498 | * string and way are taken from the previous call to loc_string. 499 | */ 500 | 501 | static void loc_string(ch) 502 | char ch; 503 | { 504 | static int way; /* remembered direction */ 505 | static char string[MAXTEXT-1]; /* remembered pattern */ 506 | 507 | int cur_line, cur_pos, first, last, len, line, pos; 508 | char *pat, out[2], cur_text[MAXTEXT+1], text[MAXTEXT+1]; 509 | 510 | if (ch != '\0') { /* get new pattern and direction */ 511 | way = (ch == '/') ? 1 : -1; 512 | out[0] = ch; 513 | out[1] = '\0'; 514 | pat = s_getmsg(out); 515 | if (*pat == '\b') 516 | /* user backspaced off left margin */ 517 | return; 518 | if (*pat != '\0') { 519 | if (pat[0] == '\\' && pat[1] == 'n') 520 | *++pat = '\n'; 521 | len = strlen(pat); 522 | if (len > 1 && pat[len-2] == '\\' && pat[len-1] == 'n') { 523 | pat[len-2] = '\n'; 524 | pat[len-1] = '\0'; 525 | } 526 | strcpy(string, pat); 527 | } 528 | } 529 | if (string[0] == '\0') 530 | /* want to use the old string, but none exists */ 531 | return; 532 | 533 | b_getcur(&cur_line, &cur_pos); 534 | line = cur_line; 535 | text[0] = cur_text[0] = '\n'; 536 | /* split the current line at the current position */ 537 | b_gets(cur_line, cur_text+1); 538 | len = strlen(cur_text); 539 | cur_text[len] = '\n'; /* newlines were removed when file was read */ 540 | cur_text[len+1] = '\0'; 541 | cur_text[cur_pos+1] = '\0'; /* +1 for leading '\n' */ 542 | if (way > 0) { 543 | first = cur_pos + 2; 544 | last = 0; 545 | } else { 546 | first = 0; 547 | last = cur_pos + 2; 548 | } 549 | 550 | /* search the first section of the current line */ 551 | pos = locate(&cur_text[first], string, way); 552 | if (pos >= 0) 553 | pos += first; 554 | 555 | /* if that fails, search the other lines */ 556 | while (pos < 0) { 557 | if (way > 0) 558 | line = (line < b_size()) ? line + 1 : 1; 559 | else 560 | line = (line > 1) ? line - 1 : b_size(); 561 | if (line == cur_line) 562 | break; 563 | b_gets(line, text+1); 564 | len = strlen(text); 565 | text[len] = '\n'; 566 | text[len+1] = '\0'; 567 | pos = locate(text, string, way); 568 | } 569 | 570 | /* if that fails, search the other section of the current line */ 571 | if (pos < 0) { 572 | line = cur_line; 573 | pos = locate(&cur_text[last], string, way); 574 | if (pos >= 0) 575 | pos += last; 576 | } 577 | 578 | if (pos >= 0) { /* found a match */ 579 | --pos; /* compensate for leading '\n' in text buffer */ 580 | pos = max(pos, 0); /* if leading '\n' in pattern */ 581 | b_gets(line, text); 582 | pos = min(pos, (int)strlen(text)-1); /* if matched '\n' after line */ 583 | b_setcur(line, pos); 584 | } 585 | } 586 | 587 | /* locate - return the position of a pattern in a text line */ 588 | static int locate(text, pat, way) 589 | char *text, *pat; 590 | int way; 591 | { 592 | int i, lim; 593 | char *p, *t; 594 | 595 | if (way > 0) { 596 | i = 0; 597 | lim = strlen(text); 598 | } else { 599 | i = strlen(text) - 1; 600 | lim = -1; 601 | } 602 | for ( ; i != lim; i += way) 603 | for (p = pat, t = &text[i]; *p == *t; ++p, ++t) 604 | if (p[1] == '\0') 605 | return(i); 606 | return(-1); /* no match */ 607 | } 608 | -------------------------------------------------------------------------------- /Sman.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Sman.c - screen manager 3 | * 4 | * 5 | * Entry points: 6 | * 7 | * s_errmsg(msg, val) 8 | * char *msg; 9 | * int val; 10 | * Format and print msg; wait for a key to be pressed. 11 | * 12 | * s_finish() 13 | * Adjust the screen at the end of an edit session. 14 | * 15 | * int s_firstline() 16 | * Return the number of the first line on the screen. 17 | * 18 | * char *s_getmsg(msg) 19 | * char *msg; 20 | * Print msg on the last row; return the user's reply. 21 | * 22 | * s_init() 23 | * Initialize the screen module. 24 | * 25 | * int s_ismsg() 26 | * Tell whether a message is pending. 27 | * 28 | * s_keyboard(bit) 29 | * int bit; 30 | * Record if input is from the keyboard. 31 | * 32 | * int s_lastline() 33 | * Return the number of the last line on the screen. 34 | * 35 | * s_putmsg(msg) 36 | * char *msg; 37 | * Print msg on the last row. 38 | * 39 | * s_refresh() 40 | * Bring the screen up to date after a buffer change. 41 | * 42 | * s_savemsg(msg, val) 43 | * char *msg; 44 | * int val; 45 | * Format msg and save it for the next screen refreshing. 46 | * 47 | * 48 | * External procedure calls: 49 | * 50 | * b_getcur(line_ptr, pos_ptr) .. file Bman.c 51 | * int *line_ptr, *pos_ptr; 52 | * Return the line and position of the cursor. 53 | * 54 | * b_gets(k, s) .. file Bman.c 55 | * int k; 56 | * char s[]; 57 | * Copy the k-th buffer line to s. 58 | * 59 | * int b_lineid(k) .. file Bman.c 60 | * int k; 61 | * Return the ID of the k-th buffer line. 62 | * 63 | * int b_size() .. file Bman.c 64 | * Return the number of lines in the buffer. 65 | * 66 | * int k_getch() .. file keyboard.c 67 | * Return the next character of the current command. 68 | * 69 | * int k_keyin() .. file keyboard.c 70 | * Get a character from the keyboard. 71 | * 72 | * scr_clr() .. file screen.c 73 | * Clear the remainder of the row, i.e., delete the characters 74 | * under, and to the right of, the cursor. Characters to the 75 | * left and in other rows remain. 76 | * 77 | * scr_cls() .. file screen.c 78 | * Remove all characters from the screen. 79 | * 80 | * scr_delc(i) .. file screen.c 81 | * int i; 82 | * Delete i characters. All characters that follow on the same 83 | * row are shifted left i positions and i blank characters are 84 | * placed at the right end of the row. 85 | * 86 | * scr_delr() .. file screen.c 87 | * Delete the row under the cursor. Later rows on the screen are 88 | * shifted up, and a blank row is placed at the bottom of the 89 | * screen. 90 | * 91 | * scr_inr() .. file screen.c 92 | * Insert a blank row at the cursor location. Rows at and below 93 | * the current row are shifted down and the last row is lost. 94 | * 95 | * scr_instr(s) .. file screen.c 96 | * char *s; 97 | * Insert the string s. Characters under, and to the right of, the 98 | * cursor are shifted right. Characters shifted beyond the right 99 | * margin of the screen are lost. No assumption is made about 100 | * what happens if the line contains tabs or newline characters 101 | * or if the cursor reaches the right margin of the screen. 102 | * 103 | * scr_move(row, col) .. file screen.c 104 | * int row, col; 105 | * Move the cursor to the given row and column of the screen. The 106 | * upper left corner of the screen is considered row 1, column 1. 107 | * 108 | * scr_puts(s) .. file screen.c 109 | * char *s; 110 | * Print the line s. No assumption is made about what happens 111 | * if the line contains tabs or newline characters or if the cursor 112 | * reaches the right margin of the screen. 113 | * 114 | * scr_scrl() .. file screen.c 115 | * Scroll screen rows up and place a blank row on the bottom. 116 | * The top screen row is lost. 117 | * 118 | * scr_shape(nrow_ptr, ncol_ptr) .. file screen.c 119 | * int *nrow_ptr, *col_ptr; 120 | * Return the number of rows and columns on the screen. 121 | */ 122 | 123 | #include 124 | #include "s.h" 125 | 126 | #define CLEAR -1 /* "ID" for cleared row */ 127 | #define D_OR_I_COST 3 /* delete/insert cost per character */ 128 | #define MESSAGE -2 /* "ID" for message row */ 129 | #define MAXCOLS 80 /* maximum possible screen width */ 130 | /* size of buffers for expanded text */ 131 | #define MAXEXPAND MAXTEXT+100 132 | #define MAXROWS 24 /* maximum possible screen height */ 133 | /* text at given segment and column */ 134 | #define TEXT text[row+seg] + col - 1 135 | #define TILDE -3 /* "ID" for "~" row */ 136 | #define USEFUL 8 /* repaint screen if fewer rows can be reused */ 137 | 138 | extern void s_putmsg(), k_keyin(), scr_scrl(), scr_move(), scr_shape(); 139 | extern void scr_clr(), scr_puts(), b_getcur(), bgets(), scr_instr(); 140 | extern void scr_delr(), scr_inr(), b_gets(), scr_clc(), scr_cls(), scr_delc(); 141 | extern int k_getch(), b_lineid(), b_size(); 142 | static int after_line(), can_scroll(), expand(), good_first(), row_of_id(); 143 | static void bottom(), changes(), chop_arg(), chop_cpy(), delete(); 144 | static void display(), displ_text(), insert(), ins_text(); 145 | static void pos_to_seg(), repaint(), replace(), repl_text(), scroll(); 146 | 147 | static int 148 | first_line = 0, /* line number of first screen row */ 149 | id[MAXROWS+1], /* ID of line at row i (subscript 0 unused) */ 150 | keyboard, /* is command coming from keyboard? */ 151 | last_row, /* last row displaying buffer contents */ 152 | ncols, /* number of columns on the screen */ 153 | nrows; /* number of rows on the screen */ 154 | 155 | static char 156 | msg_save[MAXCOLS+1], /* message saved for next screen refreshing */ 157 | *text[MAXROWS+1]; /* text of line at row i (subscript 0 unused) */ 158 | 159 | /* s_errmsg - format and print msg; wait for the user to read it */ 160 | void s_errmsg(msg, val) 161 | char *msg; 162 | int val; 163 | { 164 | char message[MAXCOLS+1]; 165 | 166 | sprintf(message, msg, val); 167 | s_putmsg(message); 168 | k_keyin(); 169 | } 170 | 171 | /* s_finish - terminate the edit session */ 172 | void s_finish() 173 | { 174 | scr_scrl(); 175 | scr_move(nrows, 1); 176 | } 177 | 178 | /* s_firstline - return the number of the first line on the screen */ 179 | int s_firstline() 180 | { 181 | return(first_line); 182 | } 183 | 184 | /* s_getmsg - write a message; return the reply */ 185 | char *s_getmsg(msg) 186 | char *msg; 187 | { 188 | static char last_text[MAXCOLS+1]; 189 | char expanded[MAXCOLS+2], *reply, *s; 190 | 191 | strcpy(last_text, msg); 192 | if (keyboard) 193 | s_putmsg(last_text); 194 | s = reply = last_text + strlen(last_text); 195 | for ( ; s - last_text < MAXCOLS && (*s = k_getch()) != CR; ++s) { 196 | if (*s == '\b') 197 | if (s == reply) { 198 | s[1] = '\0'; /* return the '\b' */ 199 | return(reply); 200 | } else 201 | s -= 2; 202 | else if (!isprint(*s) && *s != '\t') { 203 | UNKNOWN; 204 | --s; 205 | continue; 206 | } 207 | if (keyboard) { 208 | s[1] = '\0'; 209 | if (expand(expanded, last_text, sizeof(expanded)) > 1) 210 | /* exceeds one row; don't display */ 211 | return(reply); 212 | repl_text(nrows, MESSAGE, 1, 0, expanded); 213 | } 214 | } 215 | *s = '\0'; /* trim off the final CR */ 216 | return(reply); 217 | } 218 | 219 | /* s_init - initialize for an edit session */ 220 | void s_init() 221 | { 222 | int row; 223 | char *ckalloc(); 224 | 225 | /* save constants giving terminal characteristics */ 226 | scr_shape(&nrows, &ncols); 227 | if (ncols > MAXCOLS) 228 | s_errmsg("The screen has too many columns.", 0); 229 | else if (nrows > MAXROWS) 230 | s_errmsg("The screen has too many rows.", 0); 231 | else /* allocate storage for remembering screen contents */ 232 | for (row = 1; row <= nrows; ++row) { 233 | text[row] = ckalloc((unsigned)(ncols+1)); 234 | strcpy(text[row], ""); 235 | } 236 | } 237 | 238 | /* s_ismsg - tell if a message is waiting to be displayed */ 239 | int s_ismsg() 240 | { 241 | return(msg_save[0] != '\0'); 242 | } 243 | 244 | /* s_keyboard - record if command is from the keyboard */ 245 | void s_keyboard(bit) 246 | int bit; 247 | { 248 | keyboard = bit; 249 | } 250 | 251 | /* s_lastline - return the number of the last line on the screen */ 252 | int s_lastline() 253 | { 254 | int last_line = first_line, row; 255 | 256 | for (row = 2; row <= nrows; ++row) 257 | if (id[row] > 0 && id[row] != id[row-1]) 258 | ++last_line; 259 | return(last_line); 260 | } 261 | 262 | /* s_putmsg - print a message on the last screen row */ 263 | void s_putmsg(msg) 264 | char *msg; 265 | { 266 | scr_move(nrows, 1); 267 | if (id[nrows] != CLEAR) 268 | scr_clr(); 269 | id[nrows] = MESSAGE; 270 | expand(text[nrows], msg, ncols+1); 271 | scr_puts(text[nrows]); 272 | } 273 | 274 | /* s_refresh - refresh the screen */ 275 | void s_refresh() 276 | { 277 | if (keyboard) { 278 | last_row = nrows - s_ismsg(); 279 | if (first_line == 0) /* initial refreshing */ 280 | repaint(); 281 | else 282 | changes(); /* economical refreshing */ 283 | } 284 | } 285 | 286 | /* s_savemsg - save msg for the next screen refreshing */ 287 | void s_savemsg(msg, val) 288 | char *msg; 289 | int val; 290 | { 291 | sprintf(msg_save, msg, val); 292 | } 293 | 294 | /* --------- static procedures for refreshing the screen ---------- */ 295 | 296 | /* after_line - return the first screen row of the next buffer line */ 297 | static int after_line(row) 298 | int row; 299 | { 300 | while (row < nrows && id[row+1] == id[row]) 301 | ++row; 302 | return(row+1); 303 | } 304 | 305 | /* bottom - make current location visible; handle TILDE lines and messages */ 306 | static void bottom() 307 | { 308 | int cur_col, cur_id, cur_line, cur_pos, cur_row, cur_seg, idr, junk, 309 | last_seg, n, r, tilde_row; 310 | char cur_text[MAXTEXT-1]; 311 | 312 | /* guarantee that current line is completely visible */ 313 | b_getcur(&cur_line, &cur_pos); 314 | b_gets(cur_line, cur_text); 315 | pos_to_seg(cur_text, strlen(cur_text)-1, &last_seg, &junk); 316 | cur_id = b_lineid(cur_line); 317 | n = 0; 318 | while ((cur_row = row_of_id(cur_id, 1)) == 0 319 | || cur_row + last_seg > last_row) 320 | if (++n < 20) 321 | scroll(1, s_lastline()); 322 | else { 323 | s_savemsg("Screen repainted because of display error.", 0); 324 | repaint(); 325 | return; 326 | } 327 | 328 | /* fill in TILDE rows below last line of buffer */ 329 | /* (first condition avoids long buffer search) */ 330 | if (b_size() < first_line + nrows && 331 | (r = row_of_id(b_lineid(b_size()), cur_row)) > 0) 332 | for (tilde_row = after_line(r); tilde_row <= last_row; ++tilde_row) 333 | if ((idr = id[tilde_row]) != TILDE) { 334 | scr_move(tilde_row, 1); 335 | if (idr != CLEAR) 336 | scr_clr(); 337 | scr_puts("~"); 338 | id[tilde_row] = TILDE; 339 | strcpy(text[tilde_row], "~"); 340 | } 341 | 342 | /* if a message is waiting, print it */ 343 | if (s_ismsg()) { 344 | s_putmsg(msg_save); 345 | strcpy(msg_save, ""); 346 | } 347 | 348 | /* move the cursor into position */ 349 | pos_to_seg(cur_text, cur_pos, &cur_seg, &cur_col); 350 | if (cur_row + cur_seg <= nrows) 351 | scr_move(cur_row + cur_seg, cur_col); 352 | } 353 | 354 | /* can_scroll - try to scroll the window down; tell if successful */ 355 | static int can_scroll(new_row1, new_first) 356 | int new_row1, /* row to be moved to the top of the screen */ 357 | new_first; /* number of the line currently at new_row1 */ 358 | { 359 | int count, line, row; 360 | 361 | /* don't scroll if the bottom part of the screen requires updating */ 362 | for (row = new_row1, line = new_first; ++row <= nrows && id[row] > 0; ) 363 | if (id[row] != id[row-1]) 364 | if (++line > b_size() || id[row] != b_lineid(line)) 365 | return(0); 366 | 367 | /* count lines to be removed from the top of the screen */ 368 | for (count = row = 1; row < new_row1 - 1; ++row) 369 | if (id[row+1] != id[row]) 370 | ++count; 371 | 372 | scroll(count, line); 373 | return(1); 374 | } 375 | 376 | /* changes - economically update the screen */ 377 | static void changes() 378 | { 379 | int line, /* buffer line being displayed */ 380 | row, /* row where line will begin */ 381 | visible, /* next buffer line already on the screen */ 382 | useful_row, /* row where the visible line begins */ 383 | cur_line, cur_pos, n, new_first, last_line, partial; 384 | 385 | b_getcur(&cur_line, &cur_pos); 386 | 387 | /* determine the first buffer line that will be on the screen */ 388 | if (cur_line >= s_firstline() && cur_line <= s_lastline() + 1) 389 | /* the old first line will probably display the current line */ 390 | new_first = first_line; 391 | /* else compute the first line that optimally reuses existing rows */ 392 | else if ((new_first = good_first()) == 0) { 393 | /* there is no good choice; repaint the screen */ 394 | repaint(); 395 | return; 396 | } 397 | 398 | /* determine the last displayed line, assuming one row per line */ 399 | last_line = min(new_first + nrows - 1, b_size()); 400 | 401 | /* record ID of line that may have segments falling below the screen */ 402 | if ((partial = id[nrows]) <= 0) 403 | partial = id[nrows-1]; 404 | 405 | for (row = 1, line = new_first; 406 | row <= nrows && (row <= last_row || line == cur_line) && line <= last_line; 407 | row = after_line(row), ++line) { 408 | 409 | /* determine the next buffer line that is already visible */ 410 | for (visible = line; visible <= last_line; ++visible) 411 | if ((useful_row = row_of_id(b_lineid(visible), row)) > 0) 412 | break; 413 | 414 | if (row < useful_row && line == visible) { 415 | /* if screen update can be performed by scrolling .. */ 416 | if (row == 1 && can_scroll(useful_row, new_first)) 417 | break; 418 | delete(row, useful_row - 1); 419 | } else if (row < useful_row 420 | || useful_row == 0 /* no more useful rows */ 421 | || id[row] == partial) /* may need additional segments */ 422 | replace(row, line, useful_row); 423 | else if (line < visible) 424 | /* insert in reverse order so scrolling up looks OK */ 425 | for (n = visible - 1; n >= line; --n) 426 | insert(row, n); 427 | /* else line is already displayed at row */ 428 | } 429 | 430 | first_line = new_first; 431 | bottom(); /* handle TILDE rows, message, etc */ 432 | } 433 | 434 | /* chop_arg - chop a function's argument to a maximum length */ 435 | static void chop_arg(fcn, arg, maxlen) 436 | void (*fcn)(); 437 | int maxlen; 438 | char *arg; 439 | { 440 | char save; 441 | 442 | save = arg[maxlen]; 443 | arg[maxlen] = '\0'; 444 | (*fcn)(arg); 445 | arg[maxlen] = save; 446 | } 447 | 448 | /* chop_cpy - copy at most maxlen characters from s to t; add '\0' */ 449 | static void chop_cpy(s, t, maxlen) 450 | char *s, *t; 451 | int maxlen; 452 | { 453 | while (maxlen-- > 0 && (*s++ = *t++) != '\0') 454 | ; 455 | *s = '\0'; 456 | } 457 | 458 | /* delete - delete rows from the screen */ 459 | static void delete(from, to) 460 | int from, to; 461 | { 462 | int k, nbr_rows = to - from + 1; 463 | 464 | /* don't let message move up */ 465 | if (id[nrows] == MESSAGE) { 466 | id[nrows] = CLEAR; 467 | strcpy(text[nrows], ""); 468 | scr_move(nrows, 1); 469 | scr_delr(); 470 | } 471 | 472 | /* remember the rows that are shifted up */ 473 | for (k = from; k <= nrows - nbr_rows; ++k) { 474 | id[k] = id[k+nbr_rows]; 475 | strcpy(text[k], text[k+nbr_rows]); 476 | } 477 | /* remember the bottom rows that are cleared */ 478 | for (k = nrows - nbr_rows + 1; k <= nrows; ++k) { 479 | id[k] = CLEAR; 480 | strcpy(text[k], ""); 481 | } 482 | /* delete rows from the screen */ 483 | scr_move(from, 1); 484 | while (nbr_rows-- > 0) 485 | scr_delr(); 486 | } 487 | 488 | /* display - display a line */ 489 | static void display(row, line) 490 | int row, line; 491 | { 492 | int nsegs; 493 | char buf[MAXTEXT-1], expanded[MAXEXPAND]; 494 | 495 | b_gets(line, buf); 496 | nsegs = expand(expanded, buf, sizeof(expanded)); 497 | displ_text(row, b_lineid(line), nsegs, expanded); 498 | } 499 | 500 | /* displ_text - print the text of a line */ 501 | static void displ_text(row, line_id, nsegs, s) 502 | int row, line_id, nsegs; 503 | char *s; 504 | { 505 | int do_clear; 506 | 507 | for ( ; nsegs-- > 0 && row <= last_row; ++row, s += ncols) { 508 | scr_move(row, 1); 509 | do_clear = (id[row] != CLEAR && strlen(text[row]) > strlen(s)); 510 | id[row] = line_id; 511 | chop_cpy(text[row], s, ncols); 512 | scr_puts(text[row]); 513 | if (do_clear) 514 | scr_clr(); 515 | } 516 | } 517 | 518 | /* expand - expand t to s; return the number of segments */ 519 | static int expand(s, t, maxchars) 520 | char *s, *t; 521 | int maxchars; 522 | { 523 | char *start = s; 524 | 525 | for ( ; s - start < maxchars - 1 && *t != '\0' ; ++s, ++t) 526 | if ((*s = *t) == '\t') { 527 | *s = ' '; /* overwrite the tab */ 528 | while (s - start < maxchars - 2 529 | && (s - start + 1)%TAB_WIDTH != 0) 530 | *++s = ' '; 531 | } 532 | *s = '\0'; 533 | return(1 + (s - start - 1)/ncols); 534 | } 535 | 536 | /* good_first - return a good first line for window; 0 = no good choice */ 537 | static int good_first() 538 | { 539 | int best_first, cur_line, cur_pos, first, last, line, max_overlap, overlap; 540 | 541 | /* 542 | * find the window containing the current line and as many of the 543 | * currently visible lines as possible 544 | */ 545 | 546 | b_getcur(&cur_line, &cur_pos); 547 | 548 | /* start with the highest window that (probably) contains cur_line */ 549 | first = max(cur_line - nrows + 1 + s_ismsg(), 1); 550 | 551 | /* determine the last possible line in the highest window */ 552 | last = min(first + nrows - 1 - s_ismsg(), b_size()); 553 | 554 | /* compute overlap between current screen and highest window */ 555 | overlap = 0; 556 | for (line = first ; line <= last; ++line) 557 | if (row_of_id(b_lineid(line), 1) > 0) 558 | ++overlap; 559 | 560 | /* try other possible windows */ 561 | max_overlap = overlap; 562 | best_first = first; 563 | while (++first <= cur_line) { /* next window */ 564 | if (row_of_id(b_lineid(first-1), 1) > 0) 565 | --overlap; 566 | if (++last <= b_size() && row_of_id(b_lineid(last), 1) > 0) 567 | ++overlap; 568 | /* in case of a tie, pick the lower window */ 569 | if (overlap >= max_overlap) { 570 | max_overlap = overlap; 571 | best_first = first; 572 | } 573 | } 574 | return((max_overlap >= USEFUL) ? best_first : 0); 575 | } 576 | 577 | /* insert - insert a line */ 578 | static void insert(row, line) 579 | int row, line; 580 | { 581 | int nsegs; 582 | char buf[MAXTEXT-1], expanded[MAXEXPAND]; 583 | 584 | b_gets(line, buf); 585 | nsegs = expand(expanded, buf, sizeof(expanded)); 586 | ins_text(row, b_lineid(line), nsegs, expanded); 587 | } 588 | 589 | /* ins_text - insert the text of a line */ 590 | static void ins_text(row, lineid, nsegs, t) 591 | int row, lineid, nsegs; 592 | char *t; 593 | { 594 | int r; 595 | 596 | nsegs = min(nsegs, nrows - row + 1); 597 | /* remember the rows that are shifted down */ 598 | for (r = nrows; r >= row + nsegs; --r) { 599 | id[r] = id[r-nsegs]; 600 | strcpy(text[r], text[r-nsegs]); 601 | } 602 | 603 | /* insert blank rows on the screen */ 604 | scr_move(row, 1); 605 | for (r = 1; r <= nsegs; ++r) 606 | scr_inr(); 607 | displ_text(row, lineid, nsegs, t); 608 | } 609 | 610 | /* pos_to_seg - convert a line position to a screen segment and column */ 611 | static void pos_to_seg(t, pos, seg_ptr, col_ptr) 612 | char *t; 613 | int pos, *seg_ptr, *col_ptr; 614 | { 615 | int c, p; 616 | 617 | for (c = 1, p = 0; p < pos && t[p] != '\0'; ++c, ++p) 618 | /* keep column c corresponding to position p */ 619 | if (t[p] == '\t') 620 | while (c%TAB_WIDTH != 0) 621 | ++c; 622 | 623 | *seg_ptr = (c - 1)/ncols; 624 | *col_ptr = 1 + (c-1) % ncols; 625 | } 626 | 627 | /* repaint - completely repaint the screen */ 628 | static void repaint() 629 | { 630 | int cur_line, cur_pos, line, row; 631 | 632 | /* clear the screen */ 633 | scr_cls(); 634 | for (row = 1; row <= nrows; ++row) { 635 | id[row] = CLEAR; 636 | strcpy(text[row], ""); 637 | } 638 | 639 | b_getcur(&cur_line, &cur_pos); 640 | for (row = 1, line = first_line = max (cur_line - 8, 1); 641 | row <= last_row && line <= b_size(); 642 | row = after_line(row), ++line) 643 | display(row, line); 644 | bottom(); 645 | } 646 | 647 | /* replace - replace a line */ 648 | static void replace(row, line, useful_row) 649 | int row, line, useful_row; 650 | { 651 | int nsegs; 652 | char buf[MAXTEXT-1], expanded[MAXEXPAND]; 653 | 654 | b_gets(line, buf); 655 | nsegs = expand(expanded, buf, sizeof(expanded)); 656 | repl_text(row, b_lineid(line), nsegs, useful_row, expanded); 657 | } 658 | 659 | /* repl_text - economically replace the text of a line */ 660 | static void repl_text(row, line_id, new_segs, useful_row, new_text) 661 | int row, /* row containing 0-th segment of the line */ 662 | line_id, /* ID of the new line */ 663 | new_segs, /* number of segments in the new line */ 664 | useful_row; /* next row that should not be overwritten */ 665 | char *new_text; /* text of the new line */ 666 | { 667 | int add_segs, /* number of segments to be added */ 668 | col, /* column of current interest */ 669 | count, /* character count for current segment */ 670 | d_count, /* number of characters to be deleted */ 671 | do_clear, /* is overwrite/clear strategy used? */ 672 | i, /* generic loop index */ 673 | i_count, /* number of characters to be inserted */ 674 | o_count, /* number of characters to be overwritten */ 675 | old_segs, /* number of segments in the old line */ 676 | repl_segs, /* number of segments to be replaced */ 677 | seg, /* segment of current interest */ 678 | tail_len; /* length of new text after mismatch */ 679 | char *p1, /* first mismatching character in old text */ 680 | *p2, /* first mismatching character in new text */ 681 | *s1, /* start of matching suffix in old text */ 682 | *s2, /* start of matching suffix in new text */ 683 | *t, /* generic character pointer */ 684 | old_text[MAXEXPAND]; /* current displayed line */ 685 | 686 | /* build the old line from screen segments */ 687 | strcpy(old_text, text[row]); 688 | for (i = row + 1; i <= nrows && id[i] == id[row] && id[i] > 0; ++i) 689 | strcat(old_text, text[i]); 690 | old_segs = i - row; 691 | /* don't consider segments below the screen */ 692 | new_segs = min(new_segs, nrows - row + 1); 693 | /* don't replace segments that should be inserted or deleted */ 694 | repl_segs = min(old_segs, new_segs); 695 | 696 | /* update id[] and text[] */ 697 | for (seg = 0, t = new_text; seg < repl_segs; ++seg, t += ncols) { 698 | chop_cpy(text[row+seg], t, ncols); 699 | id[row+seg] = line_id; 700 | } 701 | 702 | /* point p1 and p2 to first differing characters */ 703 | for (p1 = old_text, p2 = new_text; 704 | *p1 != '\0' && *p1 == *p2; ++p1, ++p2) 705 | ; 706 | if (*p1 == '\0' && *p2 == '\0') /* identical lines */ 707 | return; 708 | 709 | /* point s1 and s2 to the starts of longest common suffix */ 710 | tail_len = strlen(p2); /* length of remainder of new line */ 711 | for (s1 = p1 + strlen(p1), s2 = p2 + tail_len; 712 | s1 > p1 && s2 > p2 && s1[-1] == s2[-1]; --s1, --s2) 713 | ; 714 | 715 | /* compare overwrite-clear cost against overwrite-(delete/insert) */ 716 | d_count = s1 - p1 - (s2 - p2); /* counts deleted chars (<0 for insert) */ 717 | o_count = min(s1 - p1, s2 - p2); /* counts overwritten chars */ 718 | do_clear = (tail_len < o_count + D_OR_I_COST*abs(d_count)); 719 | if (do_clear) 720 | o_count = tail_len; /* overwrite with entire tail */ 721 | 722 | /* move cursor to first improper character */ 723 | seg = (p2 - new_text)/ncols; 724 | col = 1 + (p2 - new_text) % ncols; 725 | if (seg < repl_segs) 726 | scr_move(row + seg, col); 727 | 728 | /* overwrite if appropriate */ 729 | for ( ; o_count > 0 && seg < repl_segs; o_count -= count) 730 | /* if overwrite operation reaches the segment's end ... */ 731 | if ((count = ncols - col + 1) <= o_count) { 732 | /* overwrite with remainder of desired row */ 733 | scr_puts(TEXT); 734 | /* move to start of next segment */ 735 | if (++seg < repl_segs) 736 | scr_move(row + seg, col = 1); 737 | } else { 738 | /* overwrite internal substring */ 739 | chop_arg(scr_puts, TEXT, count = o_count); 740 | col += count; 741 | } 742 | 743 | /* clear remainder of row if appropriate */ 744 | if (do_clear) { 745 | if (d_count > 0 && seg < repl_segs) 746 | /* old text is longer than new text */ 747 | scr_clr(); 748 | /* else delete text if appropriate */ 749 | } else if (d_count > 0) 750 | while (seg < repl_segs) { 751 | /* don't delete past the segment's end */ 752 | count = min(d_count, ncols - col + 1); 753 | scr_delc(count); 754 | /* if there are later segments in old text ... */ 755 | if (seg < old_segs - 1) { 756 | /* append characters from the next segment */ 757 | scr_move(row + seg, col = ncols - count + 1); 758 | scr_puts(TEXT); 759 | } 760 | if (++seg < repl_segs) 761 | scr_move(row + seg, col = 1); 762 | } 763 | /* else insert text if appropriate */ 764 | else if ((i_count = -d_count) > 0) 765 | while (seg < repl_segs) { 766 | /* if inserted text reaches the segment's end ... */ 767 | if (i_count > ncols - col) 768 | scr_puts(TEXT); /* just overwrite with it */ 769 | else 770 | chop_arg(scr_instr, TEXT, i_count); 771 | if (++seg < repl_segs) 772 | scr_move(row + seg, col = 1); 773 | } 774 | /* else d_count = 0; do nothing */ 775 | 776 | /* overwrite or insert any additional segments */ 777 | if ((add_segs = new_segs - old_segs) > 0) { 778 | t = new_text + old_segs*ncols; /* points to remaining text */ 779 | if (useful_row == 0 || row + new_segs <= useful_row) 780 | displ_text(row+old_segs, line_id, add_segs, t); 781 | else 782 | ins_text(row+old_segs, line_id, add_segs, t); 783 | } 784 | } 785 | 786 | /* row_of_id - return the screen row having a given id */ 787 | static int row_of_id(i, row) 788 | int i, /* ID being sought */ 789 | row; /* first row to be searched */ 790 | { 791 | for ( ; row <= nrows; ++row) 792 | if (id[row] == i) 793 | return(row); 794 | return(0); 795 | } 796 | 797 | /* scroll - scroll the window down */ 798 | static void scroll(k, line) 799 | int k, /* number of lines to be pushed off the top of the screen */ 800 | line; /* last visible line */ 801 | { 802 | int desired, /* desired top line */ 803 | i, /* generic loop index */ 804 | nsegs, /* number of segments in last visible line */ 805 | row, /* row holding last visible segment */ 806 | seg; /* number of last visible segment */ 807 | char *s, /* points to last visible segment */ 808 | buf[MAXTEXT-1], expanded[MAXEXPAND]; 809 | 810 | /* determine desired first line; initialize nsegs, row, seg and s */ 811 | desired = first_line + k; 812 | b_gets(line, buf); 813 | nsegs = expand(expanded, buf, sizeof(expanded)); 814 | for (row = nrows; row > 1 && id[row] < 0; --row) 815 | ; 816 | for (i = row; i > 1 && id[i-1] == id[row]; --i) 817 | ; 818 | seg = row - i; 819 | s = expanded + seg*ncols; 820 | 821 | /* keep adding segments or TILDE rows to produce desired first line */ 822 | while (first_line != desired) { 823 | /* get next values of nsegs, seg and s */ 824 | if (line <= b_size() && ++seg < nsegs) 825 | s += ncols; 826 | else if (++line <= b_size()) { 827 | b_gets(line, buf); 828 | nsegs = expand(expanded, buf, sizeof(expanded)); 829 | seg = 0; 830 | s = expanded; 831 | } 832 | /* get next value of row; make a space for the next segment */ 833 | if (row < nrows) 834 | ++row; 835 | else { 836 | scr_scrl(); 837 | if (id[1] != id[2] && id[2] > 0) 838 | ++first_line; 839 | for (i = 0; i < nrows; ++i) { 840 | id[i] = id[i+1]; 841 | text[i] = text[i+1]; 842 | } 843 | id[nrows] = CLEAR; 844 | text[nrows] = text[0]; 845 | strcpy(text[nrows], ""); 846 | /* don't write segment then cover by a message */ 847 | if (first_line == desired && s_ismsg()) 848 | break; 849 | } 850 | /* write the segment or "~" */ 851 | if (line <= b_size()) { 852 | scr_move(row, 1); 853 | if (id[row] != CLEAR) 854 | scr_clr(); 855 | id[row] = b_lineid(line); 856 | chop_cpy(text[row], s, ncols); 857 | scr_puts(text[row]); 858 | } else if (id[row] != TILDE) { 859 | scr_move(row, 1); 860 | if (id[row] != CLEAR) 861 | scr_clr(); 862 | id[row] = TILDE; 863 | strcpy(text[row], "~"); 864 | scr_puts("~"); 865 | } 866 | } 867 | } 868 | --------------------------------------------------------------------------------