vi inlining
No binary (build failed)
—
(log yok)
| @@ -19,7 +19,7 @@ | |
| */ | |
| static const char vi_Version[] = | |
| - | "$Id: vi.c,v 1.22 2002/07/31 21:22:21 sandman Exp $"; |
| + | "$Id: vi.c,v 1.23 2002/08/21 13:02:24 aaronl Exp $"; |
| /* | |
| * To compile for standalone use: | |
| @@ -202,7 +202,7 @@ static void do_cmd(Byte); // execute a command | |
| static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot | |
| static Byte *begin_line(Byte *); // return pointer to cur line B-o-l | |
| static Byte *end_line(Byte *); // return pointer to cur line E-o-l | |
| - | static Byte *dollar_line(Byte *); // return pointer to just before NL |
| + | extern inline Byte *dollar_line(Byte *); // return pointer to just before NL |
| static Byte *prev_line(Byte *); // return pointer to prev line B-o-l | |
| static Byte *next_line(Byte *); // return pointer to next line B-o-l | |
| static Byte *end_screen(void); // get pointer to last char on screen | |
| @@ -232,7 +232,7 @@ static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte h | |
| static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole | |
| static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete | |
| static void show_help(void); // display some help info | |
| - | static void print_literal(Byte *, Byte *); // copy s to buf, convert unprintable |
| + | extern inline void print_literal(Byte *, Byte *); // copy s to buf, convert unprintable |
| static void rawmode(void); // set "raw" mode on tty | |
| static void cookmode(void); // return to "cooked" mode on tty | |
| static int mysleep(int); // sleep for 'h' 1/100 seconds | |
| @@ -296,7 +296,7 @@ static Byte *string_insert(Byte *, Byte *); // insert the string at 'p' | |
| static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register | |
| static Byte what_reg(void); // what is letter of current YDreg | |
| static void check_context(Byte); // remember context for '' command | |
| - | static Byte *swap_context(Byte *); // goto new context for '' command |
| + | extern inline Byte *swap_context(Byte *); // goto new context for '' command |
| #endif /* CONFIG_FEATURE_VI_YANKMARK */ | |
| #ifdef CONFIG_FEATURE_VI_CRASHME | |
| static void crash_dummy(); | |
| @@ -758,3207 +758,3207 @@ static void crash_test() | |
| } | |
| #endif /* CONFIG_FEATURE_VI_CRASHME */ | |
| - | //--------------------------------------------------------------------- |
| - | //----- the Ascii Chart ----------------------------------------------- |
| - | // |
| - | // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel |
| - | // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si |
| - | // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb |
| - | // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us |
| - | // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 ' |
| - | // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f / |
| - | // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7 |
| - | // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ? |
| - | // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G |
| - | // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O |
| - | // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W |
| - | // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _ |
| - | // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g |
| - | // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o |
| - | // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w |
| - | // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del |
| - | //--------------------------------------------------------------------- |
| - | |
| - | //----- Execute a Vi Command ----------------------------------- |
| - | static void do_cmd(Byte c) |
| + | //----- The Colon commands ------------------------------------- |
| + | #ifdef CONFIG_FEATURE_VI_COLON |
| + | static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present |
| { | |
| - | Byte c1, *p, *q, *msg, buf[9], *save_dot; |
| - | int cnt, i, j, dir, yf; |
| - | |
| - | c1 = c; // quiet the compiler |
| - | cnt = yf = dir = 0; // quiet the compiler |
| - | p = q = save_dot = msg = buf; // quiet the compiler |
| - | memset(buf, '\0', 9); // clear buf |
| + | int st; |
| + | Byte *q; |
| - | /* if this is a cursor key, skip these checks */ |
| - | switch (c) { |
| - | case VI_K_UP: |
| - | case VI_K_DOWN: |
| - | case VI_K_LEFT: |
| - | case VI_K_RIGHT: |
| - | case VI_K_HOME: |
| - | case VI_K_END: |
| - | case VI_K_PAGEUP: |
| - | case VI_K_PAGEDOWN: |
| - | goto key_cmd_mode; |
| - | } |
| + | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| + | Byte c; |
| + | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| + | #ifdef CONFIG_FEATURE_VI_SEARCH |
| + | Byte *pat, buf[BUFSIZ]; |
| + | #endif /* CONFIG_FEATURE_VI_SEARCH */ |
| - | if (cmd_mode == 2) { |
| - | // we are 'R'eplacing the current *dot with new char |
| - | if (*dot == '\n') { |
| - | // don't Replace past E-o-l |
| - | cmd_mode = 1; // convert to insert |
| - | } else { |
| - | if (1 <= c && c <= 127) { // only ASCII chars |
| - | if (c != 27) |
| - | dot = yank_delete(dot, dot, 0, YANKDEL); // delete char |
| - | dot = char_insert(dot, c); // insert new char |
| + | *addr = -1; // assume no addr |
| + | if (*p == '.') { // the current line |
| + | p++; |
| + | q = begin_line(dot); |
| + | *addr = count_lines(text, q); |
| + | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| + | } else if (*p == '\'') { // is this a mark addr |
| + | p++; |
| + | c = tolower(*p); |
| + | p++; |
| + | if (c >= 'a' && c <= 'z') { |
| + | // we have a mark |
| + | c = c - 'a'; |
| + | q = mark[(int) c]; |
| + | if (q != NULL) { // is mark valid |
| + | *addr = count_lines(text, q); // count lines |
| } | |
| - | goto dc1; |
| } | |
| - | } |
| - | if (cmd_mode == 1) { |
| - | // hitting "Insert" twice means "R" replace mode |
| - | if (c == VI_K_INSERT) goto dc5; |
| - | // insert the char c at "dot" |
| - | if (1 <= c && c <= 127) { |
| - | dot = char_insert(dot, c); // only ASCII chars |
| + | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| + | #ifdef CONFIG_FEATURE_VI_SEARCH |
| + | } else if (*p == '/') { // a search pattern |
| + | q = buf; |
| + | for (p++; *p; p++) { |
| + | if (*p == '/') |
| + | break; |
| + | *q++ = *p; |
| + | *q = '\0'; |
| } | |
| - | goto dc1; |
| + | pat = (Byte *) xstrdup((char *) buf); // save copy of pattern |
| + | if (*p == '/') |
| + | p++; |
| + | q = char_search(dot, pat, FORWARD, FULL); |
| + | if (q != NULL) { |
| + | *addr = count_lines(text, q); |
| + | } |
| + | free(pat); |
| + | #endif /* CONFIG_FEATURE_VI_SEARCH */ |
| + | } else if (*p == '$') { // the last line in file |
| + | p++; |
| + | q = begin_line(end - 1); |
| + | *addr = count_lines(text, q); |
| + | } else if (isdigit(*p)) { // specific line number |
| + | sscanf((char *) p, "%d%n", addr, &st); |
| + | p += st; |
| + | } else { // I don't reconise this |
| + | // unrecognised address- assume -1 |
| + | *addr = -1; |
| } | |
| + | return (p); |
| + | } |
| - | key_cmd_mode: |
| - | switch (c) { |
| - | //case 0x01: // soh |
| - | //case 0x09: // ht |
| - | //case 0x0b: // vt |
| - | //case 0x0e: // so |
| - | //case 0x0f: // si |
| - | //case 0x10: // dle |
| - | //case 0x11: // dc1 |
| - | //case 0x13: // dc3 |
| - | #ifdef CONFIG_FEATURE_VI_CRASHME |
| - | case 0x14: // dc4 ctrl-T |
| - | crashme = (crashme == 0) ? 1 : 0; |
| - | break; |
| - | #endif /* CONFIG_FEATURE_VI_CRASHME */ |
| - | //case 0x16: // syn |
| - | //case 0x17: // etb |
| - | //case 0x18: // can |
| - | //case 0x1c: // fs |
| - | //case 0x1d: // gs |
| - | //case 0x1e: // rs |
| - | //case 0x1f: // us |
| - | //case '!': // !- |
| - | //case '#': // #- |
| - | //case '&': // &- |
| - | //case '(': // (- |
| - | //case ')': // )- |
| - | //case '*': // *- |
| - | //case ',': // ,- |
| - | //case '=': // =- |
| - | //case '@': // @- |
| - | //case 'F': // F- |
| - | //case 'K': // K- |
| - | //case 'Q': // Q- |
| - | //case 'S': // S- |
| - | //case 'T': // T- |
| - | //case 'V': // V- |
| - | //case '[': // [- |
| - | //case '\\': // \- |
| - | //case ']': // ]- |
| - | //case '_': // _- |
| - | //case '`': // `- |
| - | //case 'g': // g- |
| - | //case 'u': // u- FIXME- there is no undo |
| - | //case 'v': // v- |
| - | default: // unrecognised command |
| - | buf[0] = c; |
| - | buf[1] = '\0'; |
| - | if (c <= ' ') { |
| - | buf[0] = '^'; |
| - | buf[1] = c + '@'; |
| - | buf[2] = '\0'; |
| - | } |
| - | ni((Byte *) buf); |
| - | end_cmd_q(); // stop adding to q |
| - | case 0x00: // nul- ignore |
| - | break; |
| - | case 2: // ctrl-B scroll up full screen |
| - | case VI_K_PAGEUP: // Cursor Key Page Up |
| - | dot_scroll(rows - 2, -1); |
| - | break; |
| - | #ifdef CONFIG_FEATURE_VI_USE_SIGNALS |
| - | case 0x03: // ctrl-C interrupt |
| - | longjmp(restart, 1); |
| - | break; |
| - | case 26: // ctrl-Z suspend |
| - | suspend_sig(SIGTSTP); |
| - | break; |
| - | #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ |
| - | case 4: // ctrl-D scroll down half screen |
| - | dot_scroll((rows - 2) / 2, 1); |
| - | break; |
| - | case 5: // ctrl-E scroll down one line |
| - | dot_scroll(1, 1); |
| - | break; |
| - | case 6: // ctrl-F scroll down full screen |
| - | case VI_K_PAGEDOWN: // Cursor Key Page Down |
| - | dot_scroll(rows - 2, 1); |
| - | break; |
| - | case 7: // ctrl-G show current status |
| - | edit_status(); |
| - | break; |
| - | case 'h': // h- move left |
| - | case VI_K_LEFT: // cursor key Left |
| - | case 8: // ctrl-H- move left (This may be ERASE char) |
| - | case 127: // DEL- move left (This may be ERASE char) |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd(c); |
| - | } // repeat cnt |
| - | dot_left(); |
| - | break; |
| - | case 10: // Newline ^J |
| - | case 'j': // j- goto next line, same col |
| - | case VI_K_DOWN: // cursor key Down |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd(c); |
| - | } // repeat cnt |
| - | dot_next(); // go to next B-o-l |
| - | dot = move_to_col(dot, ccol + offset); // try stay in same col |
| - | break; |
| - | case 12: // ctrl-L force redraw whole screen |
| - | case 18: // ctrl-R force redraw |
| - | place_cursor(0, 0, FALSE); // put cursor in correct place |
| - | clear_to_eos(); // tel terminal to erase display |
| - | (void) mysleep(10); |
| - | screen_erase(); // erase the internal screen buffer |
| - | refresh(TRUE); // this will redraw the entire display |
| - | break; |
| - | case 13: // Carriage Return ^M |
| - | case '+': // +- goto next line |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd(c); |
| - | } // repeat cnt |
| - | dot_next(); |
| - | dot_skip_over_ws(); |
| - | break; |
| - | case 21: // ctrl-U scroll up half screen |
| - | dot_scroll((rows - 2) / 2, -1); |
| - | break; |
| - | case 25: // ctrl-Y scroll up one line |
| - | dot_scroll(1, -1); |
| - | break; |
| - | case 27: // esc |
| - | if (cmd_mode == 0) |
| - | indicate_error(c); |
| - | cmd_mode = 0; // stop insrting |
| - | end_cmd_q(); |
| - | *status_buffer = '\0'; // clear status buffer |
| - | break; |
| - | case ' ': // move right |
| - | case 'l': // move right |
| - | case VI_K_RIGHT: // Cursor Key Right |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd(c); |
| - | } // repeat cnt |
| - | dot_right(); |
| - | break; |
| - | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| - | case '"': // "- name a register to use for Delete/Yank |
| - | c1 = get_one_char(); |
| - | c1 = tolower(c1); |
| - | if (islower(c1)) { |
| - | YDreg = c1 - 'a'; |
| - | } else { |
| - | indicate_error(c); |
| - | } |
| - | break; |
| - | case '\'': // '- goto a specific mark |
| - | c1 = get_one_char(); |
| - | c1 = tolower(c1); |
| - | if (islower(c1)) { |
| - | c1 = c1 - 'a'; |
| - | // get the b-o-l |
| - | q = mark[(int) c1]; |
| - | if (text <= q && q < end) { |
| - | dot = q; |
| - | dot_begin(); // go to B-o-l |
| - | dot_skip_over_ws(); |
| - | } |
| - | } else if (c1 == '\'') { // goto previous context |
| - | dot = swap_context(dot); // swap current and previous context |
| - | dot_begin(); // go to B-o-l |
| + | static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present |
| + | { |
| + | //----- get the address' i.e., 1,3 'a,'b ----- |
| + | // get FIRST addr, if present |
| + | while (isblnk(*p)) |
| + | p++; // skip over leading spaces |
| + | if (*p == '%') { // alias for 1,$ |
| + | p++; |
| + | *b = 1; |
| + | *e = count_lines(text, end-1); |
| + | goto ga0; |
| + | } |
| + | p = get_one_address(p, b); |
| + | while (isblnk(*p)) |
| + | p++; |
| + | if (*p == ',') { // is there a address seperator |
| + | p++; |
| + | while (isblnk(*p)) |
| + | p++; |
| + | // get SECOND addr, if present |
| + | p = get_one_address(p, e); |
| + | } |
| + | ga0: |
| + | while (isblnk(*p)) |
| + | p++; // skip over trailing spaces |
| + | return (p); |
| + | } |
| + | |
| + | static void colon(Byte * buf) |
| + | { |
| + | Byte c, *orig_buf, *buf1, *q, *r; |
| + | Byte *fn, cmd[BUFSIZ], args[BUFSIZ]; |
| + | int i, l, li, ch, st, b, e; |
| + | int useforce, forced; |
| + | struct stat st_buf; |
| + | |
| + | // :3154 // if (-e line 3154) goto it else stay put |
| + | // :4,33w! foo // write a portion of buffer to file "foo" |
| + | // :w // write all of buffer to current file |
| + | // :q // quit |
| + | // :q! // quit- dont care about modified file |
| + | // :'a,'z!sort -u // filter block through sort |
| + | // :'f // goto mark "f" |
| + | // :'fl // list literal the mark "f" line |
| + | // :.r bar // read file "bar" into buffer before dot |
| + | // :/123/,/abc/d // delete lines from "123" line to "abc" line |
| + | // :/xyz/ // goto the "xyz" line |
| + | // :s/find/replace/ // substitute pattern "find" with "replace" |
| + | // :!<cmd> // run <cmd> then return |
| + | // |
| + | if (strlen((char *) buf) <= 0) |
| + | goto vc1; |
| + | if (*buf == ':') |
| + | buf++; // move past the ':' |
| + | |
| + | forced = useforce = FALSE; |
| + | li = st = ch = i = 0; |
| + | b = e = -1; |
| + | q = text; // assume 1,$ for the range |
| + | r = end - 1; |
| + | li = count_lines(text, end - 1); |
| + | fn = cfn; // default to current file |
| + | memset(cmd, '\0', BUFSIZ); // clear cmd[] |
| + | memset(args, '\0', BUFSIZ); // clear args[] |
| + | |
| + | // look for optional address(es) :. :1 :1,9 :'q,'a :% |
| + | buf = get_address(buf, &b, &e); |
| + | |
| + | // remember orig command line |
| + | orig_buf = buf; |
| + | |
| + | // get the COMMAND into cmd[] |
| + | buf1 = cmd; |
| + | while (*buf != '\0') { |
| + | if (isspace(*buf)) |
| + | break; |
| + | *buf1++ = *buf++; |
| + | } |
| + | // get any ARGuments |
| + | while (isblnk(*buf)) |
| + | buf++; |
| + | strcpy((char *) args, (char *) buf); |
| + | buf1 = last_char_is((char *)cmd, '!'); |
| + | if (buf1) { |
| + | useforce = TRUE; |
| + | *buf1 = '\0'; // get rid of ! |
| + | } |
| + | if (b >= 0) { |
| + | // if there is only one addr, then the addr |
| + | // is the line number of the single line the |
| + | // user wants. So, reset the end |
| + | // pointer to point at end of the "b" line |
| + | q = find_line(b); // what line is #b |
| + | r = end_line(q); |
| + | li = 1; |
| + | } |
| + | if (e >= 0) { |
| + | // we were given two addrs. change the |
| + | // end pointer to the addr given by user. |
| + | r = find_line(e); // what line is #e |
| + | r = end_line(r); |
| + | li = e - b + 1; |
| + | } |
| + | // ------------ now look for the command ------------ |
| + | i = strlen((char *) cmd); |
| + | if (i == 0) { // :123CR goto line #123 |
| + | if (b >= 0) { |
| + | dot = find_line(b); // what line is #b |
| dot_skip_over_ws(); | |
| - | } else { |
| - | indicate_error(c); |
| } | |
| - | break; |
| - | case 'm': // m- Mark a line |
| - | // this is really stupid. If there are any inserts or deletes |
| - | // between text[0] and dot then this mark will not point to the |
| - | // correct location! It could be off by many lines! |
| - | // Well..., at least its quick and dirty. |
| - | c1 = get_one_char(); |
| - | c1 = tolower(c1); |
| - | if (islower(c1)) { |
| - | c1 = c1 - 'a'; |
| - | // remember the line |
| - | mark[(int) c1] = dot; |
| - | } else { |
| - | indicate_error(c); |
| + | } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd |
| + | // :!ls run the <cmd> |
| + | (void) alarm(0); // wait for input- no alarms |
| + | place_cursor(rows - 1, 0, FALSE); // go to Status line |
| + | clear_to_eol(); // clear the line |
| + | cookmode(); |
| + | system(orig_buf+1); // run the cmd |
| + | rawmode(); |
| + | Hit_Return(); // let user see results |
| + | (void) alarm(3); // done waiting for input |
| + | } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address |
| + | if (b < 0) { // no addr given- use defaults |
| + | b = e = count_lines(text, dot); |
| } | |
| - | break; |
| - | case 'P': // P- Put register before |
| - | case 'p': // p- put register after |
| - | p = reg[YDreg]; |
| - | if (p == 0) { |
| - | psbs("Nothing in register %c", what_reg()); |
| - | break; |
| + | psb("%d", b); |
| + | } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines |
| + | if (b < 0) { // no addr given- use defaults |
| + | q = begin_line(dot); // assume .,. for the range |
| + | r = end_line(dot); |
| } | |
| - | // are we putting whole lines or strings |
| - | if (strchr((char *) p, '\n') != NULL) { |
| - | if (c == 'P') { |
| - | dot_begin(); // putting lines- Put above |
| + | dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines |
| + | dot_skip_over_ws(); |
| + | } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file |
| + | int sr; |
| + | sr= 0; |
| + | // don't edit, if the current file has been modified |
| + | if (file_modified && ! useforce) { |
| + | psbs("No write since last change (:edit! overrides)"); |
| + | goto vc1; |
| + | } |
| + | if (strlen(args) > 0) { |
| + | // the user supplied a file name |
| + | fn= args; |
| + | } else if (cfn != 0 && strlen(cfn) > 0) { |
| + | // no user supplied name- use the current filename |
| + | fn= cfn; |
| + | goto vc5; |
| + | } else { |
| + | // no user file name, no current name- punt |
| + | psbs("No current filename"); |
| + | goto vc1; |
| + | } |
| + | |
| + | // see if file exists- if not, its just a new file request |
| + | if ((sr=stat((char*)fn, &st_buf)) < 0) { |
| + | // This is just a request for a new file creation. |
| + | // The file_insert below will fail but we get |
| + | // an empty buffer with a file name. Then the "write" |
| + | // command can do the create. |
| + | } else { |
| + | if ((st_buf.st_mode & (S_IFREG)) == 0) { |
| + | // This is not a regular file |
| + | psbs("\"%s\" is not a regular file", fn); |
| + | goto vc1; |
| } | |
| - | if (c == 'p') { |
| - | // are we putting after very last line? |
| - | if (end_line(dot) == (end - 1)) { |
| - | dot = end; // force dot to end of text[] |
| - | } else { |
| - | dot_next(); // next line, then put before |
| - | } |
| + | if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) { |
| + | // dont have any read permissions |
| + | psbs("\"%s\" is not readable", fn); |
| + | goto vc1; |
| } | |
| - | } else { |
| - | if (c == 'p') |
| - | dot_right(); // move to right, can move to NL |
| } | |
| - | dot = string_insert(dot, p); // insert the string |
| - | end_cmd_q(); // stop adding to q |
| - | break; |
| - | case 'U': // U- Undo; replace current line with original version |
| - | if (reg[Ureg] != 0) { |
| - | p = begin_line(dot); |
| - | q = end_line(dot); |
| - | p = text_hole_delete(p, q); // delete cur line |
| - | p = string_insert(p, reg[Ureg]); // insert orig line |
| - | dot = p; |
| - | dot_skip_over_ws(); |
| + | |
| + | // There is a read-able regular file |
| + | // make this the current file |
| + | q = (Byte *) xstrdup((char *) fn); // save the cfn |
| + | if (cfn != 0) |
| + | free(cfn); // free the old name |
| + | cfn = q; // remember new cfn |
| + | |
| + | vc5: |
| + | // delete all the contents of text[] |
| + | new_text(2 * file_size(fn)); |
| + | screenbegin = dot = end = text; |
| + | |
| + | // insert new file |
| + | ch = file_insert(fn, text, file_size(fn)); |
| + | |
| + | if (ch < 1) { |
| + | // start empty buf with dummy line |
| + | (void) char_insert(text, '\n'); |
| + | ch= 1; |
| } | |
| - | break; |
| + | file_modified = FALSE; |
| + | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| + | if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) { |
| + | free(reg[Ureg]); // free orig line reg- for 'U' |
| + | reg[Ureg]= 0; |
| + | } |
| + | if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) { |
| + | free(reg[YDreg]); // free default yank/delete register |
| + | reg[YDreg]= 0; |
| + | } |
| + | for (li = 0; li < 28; li++) { |
| + | mark[li] = 0; |
| + | } // init the marks |
| #endif /* CONFIG_FEATURE_VI_YANKMARK */ | |
| - | case '$': // $- goto end of line |
| - | case VI_K_END: // Cursor Key End |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd(c); |
| - | } // repeat cnt |
| - | dot = end_line(dot + 1); |
| - | break; |
| - | case '%': // %- find matching char of pair () [] {} |
| - | for (q = dot; q < end && *q != '\n'; q++) { |
| - | if (strchr("()[]{}", *q) != NULL) { |
| - | // we found half of a pair |
| - | p = find_pair(q, *q); |
| - | if (p == NULL) { |
| - | indicate_error(c); |
| - | } else { |
| - | dot = p; |
| - | } |
| - | break; |
| - | } |
| + | // how many lines in text[]? |
| + | li = count_lines(text, end - 1); |
| + | psb("\"%s\"%s" |
| + | #ifdef CONFIG_FEATURE_VI_READONLY |
| + | "%s" |
| + | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| + | " %dL, %dC", cfn, |
| + | (sr < 0 ? " [New file]" : ""), |
| + | #ifdef CONFIG_FEATURE_VI_READONLY |
| + | ((vi_readonly || readonly) ? " [Read only]" : ""), |
| + | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| + | li, ch); |
| + | } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this |
| + | if (b != -1 || e != -1) { |
| + | ni((Byte *) "No address allowed on this command"); |
| + | goto vc1; |
| } | |
| - | if (*q == '\n') |
| - | indicate_error(c); |
| - | break; |
| - | case 'f': // f- forward to a user specified char |
| - | last_forward_char = get_one_char(); // get the search char |
| - | // |
| - | // dont seperate these two commands. 'f' depends on ';' |
| - | // |
| - | //**** fall thru to ... 'i' |
| - | case ';': // ;- look at rest of line for last forward char |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd(';'); |
| - | } // repeat cnt |
| - | if (last_forward_char == 0) break; |
| - | q = dot + 1; |
| - | while (q < end - 1 && *q != '\n' && *q != last_forward_char) { |
| - | q++; |
| + | if (strlen((char *) args) > 0) { |
| + | // user wants a new filename |
| + | if (cfn != NULL) |
| + | free(cfn); |
| + | cfn = (Byte *) xstrdup((char *) args); |
| + | } else { |
| + | // user wants file status info |
| + | edit_status(); |
| } | |
| - | if (*q == last_forward_char) |
| - | dot = q; |
| - | break; |
| - | case '-': // -- goto prev line |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd(c); |
| - | } // repeat cnt |
| - | dot_prev(); |
| - | dot_skip_over_ws(); |
| - | break; |
| - | #ifdef CONFIG_FEATURE_VI_DOT_CMD |
| - | case '.': // .- repeat the last modifying command |
| - | // Stuff the last_modifying_cmd back into stdin |
| - | // and let it be re-executed. |
| - | if (last_modifying_cmd != 0) { |
| - | ioq = ioq_start = (Byte *) xstrdup((char *) last_modifying_cmd); |
| + | } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available |
| + | // print out values of all features |
| + | place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen |
| + | clear_to_eol(); // clear the line |
| + | cookmode(); |
| + | show_help(); |
| + | rawmode(); |
| + | Hit_Return(); |
| + | } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line |
| + | if (b < 0) { // no addr given- use defaults |
| + | q = begin_line(dot); // assume .,. for the range |
| + | r = end_line(dot); |
| } | |
| - | break; |
| - | #endif /* CONFIG_FEATURE_VI_DOT_CMD */ |
| - | #ifdef CONFIG_FEATURE_VI_SEARCH |
| - | case '?': // /- search for a pattern |
| - | case '/': // /- search for a pattern |
| - | buf[0] = c; |
| - | buf[1] = '\0'; |
| - | q = get_input_line(buf); // get input line- use "status line" |
| - | if (strlen((char *) q) == 1) |
| - | goto dc3; // if no pat re-use old pat |
| - | if (strlen((char *) q) > 1) { // new pat- save it and find |
| - | // there is a new pat |
| - | if (last_search_pattern != 0) { |
| - | free(last_search_pattern); |
| + | place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen |
| + | clear_to_eol(); // clear the line |
| + | write(1, "\r\n", 2); |
| + | for (; q <= r; q++) { |
| + | c = *q; |
| + | if (c > '~') |
| + | standout_start(); |
| + | if (c == '\n') { |
| + | write(1, "$\r", 2); |
| + | } else if (*q < ' ') { |
| + | write(1, "^", 1); |
| + | c += '@'; |
| } | |
| - | last_search_pattern = (Byte *) xstrdup((char *) q); |
| - | goto dc3; // now find the pattern |
| + | write(1, &c, 1); |
| + | if (c > '~') |
| + | standout_end(); |
| } | |
| - | // user changed mind and erased the "/"- do nothing |
| - | break; |
| - | case 'N': // N- backward search for last pattern |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd(c); |
| - | } // repeat cnt |
| - | dir = BACK; // assume BACKWARD search |
| - | p = dot - 1; |
| - | if (last_search_pattern[0] == '?') { |
| - | dir = FORWARD; |
| - | p = dot + 1; |
| + | #ifdef CONFIG_FEATURE_VI_SET |
| + | vc2: |
| + | #endif /* CONFIG_FEATURE_VI_SET */ |
| + | Hit_Return(); |
| + | } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit |
| + | (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file |
| + | if (useforce) { |
| + | // force end of argv list |
| + | if (*cmd == 'q') { |
| + | optind = save_argc; |
| + | } |
| + | editing = 0; |
| + | goto vc1; |
| } | |
| - | goto dc4; // now search for pattern |
| - | break; |
| - | case 'n': // n- repeat search for last pattern |
| - | // search rest of text[] starting at next char |
| - | // if search fails return orignal "p" not the "p+1" address |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd(c); |
| - | } // repeat cnt |
| - | dc3: |
| - | if (last_search_pattern == 0) { |
| - | msg = (Byte *) "No previous regular expression"; |
| - | goto dc2; |
| + | // don't exit if the file been modified |
| + | if (file_modified) { |
| + | psbs("No write since last change (:%s! overrides)", |
| + | (*cmd == 'q' ? "quit" : "next")); |
| + | goto vc1; |
| } | |
| - | if (last_search_pattern[0] == '/') { |
| - | dir = FORWARD; // assume FORWARD search |
| - | p = dot + 1; |
| + | // are there other file to edit |
| + | if (*cmd == 'q' && optind < save_argc - 1) { |
| + | psbs("%d more file to edit", (save_argc - optind - 1)); |
| + | goto vc1; |
| } | |
| - | if (last_search_pattern[0] == '?') { |
| - | dir = BACK; |
| - | p = dot - 1; |
| + | if (*cmd == 'n' && optind >= save_argc - 1) { |
| + | psbs("No more files to edit"); |
| + | goto vc1; |
| } | |
| - | dc4: |
| - | q = char_search(p, last_search_pattern + 1, dir, FULL); |
| - | if (q != NULL) { |
| - | dot = q; // good search, update "dot" |
| - | msg = (Byte *) ""; |
| - | goto dc2; |
| + | editing = 0; |
| + | } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[] |
| + | fn = args; |
| + | if (strlen((char *) fn) <= 0) { |
| + | psbs("No filename given"); |
| + | goto vc1; |
| } | |
| - | // no pattern found between "dot" and "end"- continue at top |
| - | p = text; |
| - | if (dir == BACK) { |
| - | p = end - 1; |
| + | if (b < 0) { // no addr given- use defaults |
| + | q = begin_line(dot); // assume "dot" |
| } | |
| - | q = char_search(p, last_search_pattern + 1, dir, FULL); |
| - | if (q != NULL) { // found something |
| - | dot = q; // found new pattern- goto it |
| - | msg = (Byte *) "search hit BOTTOM, continuing at TOP"; |
| - | if (dir == BACK) { |
| - | msg = (Byte *) "search hit TOP, continuing at BOTTOM"; |
| - | } |
| - | } else { |
| - | msg = (Byte *) "Pattern not found"; |
| + | // read after current line- unless user said ":0r foo" |
| + | if (b != 0) |
| + | q = next_line(q); |
| + | #ifdef CONFIG_FEATURE_VI_READONLY |
| + | l= readonly; // remember current files' status |
| + | #endif |
| + | ch = file_insert(fn, q, file_size(fn)); |
| + | #ifdef CONFIG_FEATURE_VI_READONLY |
| + | readonly= l; |
| + | #endif |
| + | if (ch < 0) |
| + | goto vc1; // nothing was inserted |
| + | // how many lines in text[]? |
| + | li = count_lines(q, q + ch - 1); |
| + | psb("\"%s\"" |
| + | #ifdef CONFIG_FEATURE_VI_READONLY |
| + | "%s" |
| + | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| + | " %dL, %dC", fn, |
| + | #ifdef CONFIG_FEATURE_VI_READONLY |
| + | ((vi_readonly || readonly) ? " [Read only]" : ""), |
| + | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| + | li, ch); |
| + | if (ch > 0) { |
| + | // if the insert is before "dot" then we need to update |
| + | if (q <= dot) |
| + | dot += ch; |
| + | file_modified = TRUE; |
| } | |
| - | dc2: |
| - | psbs("%s", msg); |
| - | break; |
| - | case '{': // {- move backward paragraph |
| - | q = char_search(dot, (Byte *) "\n\n", BACK, FULL); |
| - | if (q != NULL) { // found blank line |
| - | dot = next_line(q); // move to next blank line |
| + | } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args |
| + | if (file_modified && ! useforce) { |
| + | psbs("No write since last change (:rewind! overrides)"); |
| + | } else { |
| + | // reset the filenames to edit |
| + | optind = fn_start - 1; |
| + | editing = 0; |
| } | |
| - | break; |
| - | case '}': // }- move forward paragraph |
| - | q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL); |
| - | if (q != NULL) { // found blank line |
| - | dot = next_line(q); // move to next blank line |
| + | #ifdef CONFIG_FEATURE_VI_SET |
| + | } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features |
| + | i = 0; // offset into args |
| + | if (strlen((char *) args) == 0) { |
| + | // print out values of all options |
| + | place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen |
| + | clear_to_eol(); // clear the line |
| + | printf("----------------------------------------\r\n"); |
| + | #ifdef CONFIG_FEATURE_VI_SETOPTS |
| + | if (!autoindent) |
| + | printf("no"); |
| + | printf("autoindent "); |
| + | if (!err_method) |
| + | printf("no"); |
| + | printf("flash "); |
| + | if (!ignorecase) |
| + | printf("no"); |
| + | printf("ignorecase "); |
| + | if (!showmatch) |
| + | printf("no"); |
| + | printf("showmatch "); |
| + | printf("tabstop=%d ", tabstop); |
| + | #endif /* CONFIG_FEATURE_VI_SETOPTS */ |
| + | printf("\r\n"); |
| + | goto vc2; |
| } | |
| - | break; |
| - | #endif /* CONFIG_FEATURE_VI_SEARCH */ |
| - | case '0': // 0- goto begining of line |
| - | case '1': // 1- |
| - | case '2': // 2- |
| - | case '3': // 3- |
| - | case '4': // 4- |
| - | case '5': // 5- |
| - | case '6': // 6- |
| - | case '7': // 7- |
| - | case '8': // 8- |
| - | case '9': // 9- |
| - | if (c == '0' && cmdcnt < 1) { |
| - | dot_begin(); // this was a standalone zero |
| - | } else { |
| - | cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number |
| + | if (strncasecmp((char *) args, "no", 2) == 0) |
| + | i = 2; // ":set noautoindent" |
| + | #ifdef CONFIG_FEATURE_VI_SETOPTS |
| + | if (strncasecmp((char *) args + i, "autoindent", 10) == 0 || |
| + | strncasecmp((char *) args + i, "ai", 2) == 0) { |
| + | autoindent = (i == 2) ? 0 : 1; |
| } | |
| - | break; |
| - | case ':': // :- the colon mode commands |
| - | p = get_input_line((Byte *) ":"); // get input line- use "status line" |
| - | #ifdef CONFIG_FEATURE_VI_COLON |
| - | colon(p); // execute the command |
| - | #else /* CONFIG_FEATURE_VI_COLON */ |
| - | if (*p == ':') |
| - | p++; // move past the ':' |
| - | cnt = strlen((char *) p); |
| - | if (cnt <= 0) |
| - | break; |
| - | if (strncasecmp((char *) p, "quit", cnt) == 0 || |
| - | strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines |
| - | if (file_modified && p[1] != '!') { |
| - | psbs("No write since last change (:quit! overrides)"); |
| - | } else { |
| - | editing = 0; |
| - | } |
| - | } else if (strncasecmp((char *) p, "write", cnt) == 0 || |
| - | strncasecmp((char *) p, "wq", cnt) == 0 || |
| - | strncasecmp((char *) p, "x", cnt) == 0) { |
| - | cnt = file_write(cfn, text, end - 1); |
| - | file_modified = FALSE; |
| - | psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt); |
| - | if (p[0] == 'x' || p[1] == 'q') { |
| - | editing = 0; |
| - | } |
| - | } else if (strncasecmp((char *) p, "file", cnt) == 0 ) { |
| - | edit_status(); // show current file status |
| - | } else if (sscanf((char *) p, "%d", &j) > 0) { |
| - | dot = find_line(j); // go to line # j |
| - | dot_skip_over_ws(); |
| - | } else { // unrecognised cmd |
| - | ni((Byte *) p); |
| + | if (strncasecmp((char *) args + i, "flash", 5) == 0 || |
| + | strncasecmp((char *) args + i, "fl", 2) == 0) { |
| + | err_method = (i == 2) ? 0 : 1; |
| } | |
| - | #endif /* CONFIG_FEATURE_VI_COLON */ |
| - | break; |
| - | case '<': // <- Left shift something |
| - | case '>': // >- Right shift something |
| - | cnt = count_lines(text, dot); // remember what line we are on |
| - | c1 = get_one_char(); // get the type of thing to delete |
| - | find_range(&p, &q, c1); |
| - | (void) yank_delete(p, q, 1, YANKONLY); // save copy before change |
| - | p = begin_line(p); |
| - | q = end_line(q); |
| - | i = count_lines(p, q); // # of lines we are shifting |
| - | for ( ; i > 0; i--, p = next_line(p)) { |
| - | if (c == '<') { |
| - | // shift left- remove tab or 8 spaces |
| - | if (*p == '\t') { |
| - | // shrink buffer 1 char |
| - | (void) text_hole_delete(p, p); |
| - | } else if (*p == ' ') { |
| - | // we should be calculating columns, not just SPACE |
| - | for (j = 0; *p == ' ' && j < tabstop; j++) { |
| - | (void) text_hole_delete(p, p); |
| + | if (strncasecmp((char *) args + i, "ignorecase", 10) == 0 || |
| + | strncasecmp((char *) args + i, "ic", 2) == 0) { |
| + | ignorecase = (i == 2) ? 0 : 1; |
| + | } |
| + | if (strncasecmp((char *) args + i, "showmatch", 9) == 0 || |
| + | strncasecmp((char *) args + i, "sm", 2) == 0) { |
| + | showmatch = (i == 2) ? 0 : 1; |
| + | } |
| + | if (strncasecmp((char *) args + i, "tabstop", 7) == 0) { |
| + | sscanf(strchr((char *) args + i, '='), "=%d", &ch); |
| + | if (ch > 0 && ch < columns - 1) |
| + | tabstop = ch; |
| + | } |
| + | #endif /* CONFIG_FEATURE_VI_SETOPTS */ |
| + | #endif /* CONFIG_FEATURE_VI_SET */ |
| + | #ifdef CONFIG_FEATURE_VI_SEARCH |
| + | } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern |
| + | Byte *ls, *F, *R; |
| + | int gflag; |
| + | |
| + | // F points to the "find" pattern |
| + | // R points to the "replace" pattern |
| + | // replace the cmd line delimiters "/" with NULLs |
| + | gflag = 0; // global replace flag |
| + | c = orig_buf[1]; // what is the delimiter |
| + | F = orig_buf + 2; // start of "find" |
| + | R = (Byte *) strchr((char *) F, c); // middle delimiter |
| + | if (!R) goto colon_s_fail; |
| + | *R++ = '\0'; // terminate "find" |
| + | buf1 = (Byte *) strchr((char *) R, c); |
| + | if (!buf1) goto colon_s_fail; |
| + | *buf1++ = '\0'; // terminate "replace" |
| + | if (*buf1 == 'g') { // :s/foo/bar/g |
| + | buf1++; |
| + | gflag++; // turn on gflag |
| + | } |
| + | q = begin_line(q); |
| + | if (b < 0) { // maybe :s/foo/bar/ |
| + | q = begin_line(dot); // start with cur line |
| + | b = count_lines(text, q); // cur line number |
| + | } |
| + | if (e < 0) |
| + | e = b; // maybe :.s/foo/bar/ |
| + | for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0 |
| + | ls = q; // orig line start |
| + | vc4: |
| + | buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find" |
| + | if (buf1 != NULL) { |
| + | // we found the "find" pattern- delete it |
| + | (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1); |
| + | // inset the "replace" patern |
| + | (void) string_insert(buf1, R); // insert the string |
| + | // check for "global" :s/foo/bar/g |
| + | if (gflag == 1) { |
| + | if ((buf1 + strlen((char *) R)) < end_line(ls)) { |
| + | q = buf1 + strlen((char *) R); |
| + | goto vc4; // don't let q move past cur line |
| } | |
| } | |
| - | } else if (c == '>') { |
| - | // shift right -- add tab or 8 spaces |
| - | (void) char_insert(p, '\t'); |
| } | |
| + | q = next_line(ls); |
| } | |
| - | dot = find_line(cnt); // what line were we on |
| - | dot_skip_over_ws(); |
| - | end_cmd_q(); // stop adding to q |
| - | break; |
| - | case 'A': // A- append at e-o-l |
| - | dot_end(); // go to e-o-l |
| - | //**** fall thru to ... 'a' |
| - | case 'a': // a- append after current char |
| - | if (*dot != '\n') |
| - | dot++; |
| - | goto dc_i; |
| - | break; |
| - | case 'B': // B- back a blank-delimited Word |
| - | case 'E': // E- end of a blank-delimited word |
| - | case 'W': // W- forward a blank-delimited word |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd(c); |
| - | } // repeat cnt |
| - | dir = FORWARD; |
| - | if (c == 'B') |
| - | dir = BACK; |
| - | if (c == 'W' || isspace(dot[dir])) { |
| - | dot = skip_thing(dot, 1, dir, S_TO_WS); |
| - | dot = skip_thing(dot, 2, dir, S_OVER_WS); |
| + | #endif /* CONFIG_FEATURE_VI_SEARCH */ |
| + | } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version |
| + | psb("%s", vi_Version); |
| + | } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file |
| + | (strncasecmp((char *) cmd, "wq", i) == 0) || |
| + | (strncasecmp((char *) cmd, "x", i) == 0)) { |
| + | // is there a file name to write to? |
| + | if (strlen((char *) args) > 0) { |
| + | fn = args; |
| } | |
| - | if (c != 'W') |
| - | dot = skip_thing(dot, 1, dir, S_BEFORE_WS); |
| - | break; |
| - | case 'C': // C- Change to e-o-l |
| - | case 'D': // D- delete to e-o-l |
| - | save_dot = dot; |
| - | dot = dollar_line(dot); // move to before NL |
| - | // copy text into a register and delete |
| - | dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l |
| - | if (c == 'C') |
| - | goto dc_i; // start inserting |
| - | #ifdef CONFIG_FEATURE_VI_DOT_CMD |
| - | if (c == 'D') |
| - | end_cmd_q(); // stop adding to q |
| - | #endif /* CONFIG_FEATURE_VI_DOT_CMD */ |
| - | break; |
| - | case 'G': // G- goto to a line number (default= E-O-F) |
| - | dot = end - 1; // assume E-O-F |
| - | if (cmdcnt > 0) { |
| - | dot = find_line(cmdcnt); // what line is #cmdcnt |
| + | #ifdef CONFIG_FEATURE_VI_READONLY |
| + | if ((vi_readonly || readonly) && ! useforce) { |
| + | psbs("\"%s\" File is read only", fn); |
| + | goto vc3; |
| } | |
| - | dot_skip_over_ws(); |
| - | break; |
| - | case 'H': // H- goto top line on screen |
| - | dot = screenbegin; |
| - | if (cmdcnt > (rows - 1)) { |
| - | cmdcnt = (rows - 1); |
| + | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| + | // how many lines in text[]? |
| + | li = count_lines(q, r); |
| + | ch = r - q + 1; |
| + | // see if file exists- if not, its just a new file request |
| + | if (useforce) { |
| + | // if "fn" is not write-able, chmod u+w |
| + | // sprintf(syscmd, "chmod u+w %s", fn); |
| + | // system(syscmd); |
| + | forced = TRUE; |
| } | |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd('+'); |
| - | } // repeat cnt |
| - | dot_skip_over_ws(); |
| - | break; |
| - | case 'I': // I- insert before first non-blank |
| - | dot_begin(); // 0 |
| - | dot_skip_over_ws(); |
| - | //**** fall thru to ... 'i' |
| - | case 'i': // i- insert before current char |
| - | case VI_K_INSERT: // Cursor Key Insert |
| - | dc_i: |
| - | cmd_mode = 1; // start insrting |
| - | psb("-- Insert --"); |
| - | break; |
| - | case 'J': // J- join current and next lines together |
| - | if (cmdcnt-- > 2) { |
| - | do_cmd(c); |
| - | } // repeat cnt |
| - | dot_end(); // move to NL |
| - | if (dot < end - 1) { // make sure not last char in text[] |
| - | *dot++ = ' '; // replace NL with space |
| - | while (isblnk(*dot)) { // delete leading WS |
| - | dot_delete(); |
| - | } |
| - | } |
| - | end_cmd_q(); // stop adding to q |
| - | break; |
| - | case 'L': // L- goto bottom line on screen |
| - | dot = end_screen(); |
| - | if (cmdcnt > (rows - 1)) { |
| - | cmdcnt = (rows - 1); |
| - | } |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd('-'); |
| - | } // repeat cnt |
| - | dot_begin(); |
| - | dot_skip_over_ws(); |
| - | break; |
| - | case 'M': // M- goto middle line on screen |
| - | dot = screenbegin; |
| - | for (cnt = 0; cnt < (rows-1) / 2; cnt++) |
| - | dot = next_line(dot); |
| - | break; |
| - | case 'O': // O- open a empty line above |
| - | // 0i\n ESC -i |
| - | p = begin_line(dot); |
| - | if (p[-1] == '\n') { |
| - | dot_prev(); |
| - | case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..." |
| - | dot_end(); |
| - | dot = char_insert(dot, '\n'); |
| - | } else { |
| - | dot_begin(); // 0 |
| - | dot = char_insert(dot, '\n'); // i\n ESC |
| - | dot_prev(); // - |
| - | } |
| - | goto dc_i; |
| - | break; |
| - | case 'R': // R- continuous Replace char |
| - | dc5: |
| - | cmd_mode = 2; |
| - | psb("-- Replace --"); |
| - | break; |
| - | case 'X': // X- delete char before dot |
| - | case 'x': // x- delete the current char |
| - | case 's': // s- substitute the current char |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd(c); |
| - | } // repeat cnt |
| - | dir = 0; |
| - | if (c == 'X') |
| - | dir = -1; |
| - | if (dot[dir] != '\n') { |
| - | if (c == 'X') |
| - | dot--; // delete prev char |
| - | dot = yank_delete(dot, dot, 0, YANKDEL); // delete char |
| + | l = file_write(fn, q, r); |
| + | if (useforce && forced) { |
| + | // chmod u-w |
| + | // sprintf(syscmd, "chmod u-w %s", fn); |
| + | // system(syscmd); |
| + | forced = FALSE; |
| } | |
| - | if (c == 's') |
| - | goto dc_i; // start insrting |
| - | end_cmd_q(); // stop adding to q |
| - | break; |
| - | case 'Z': // Z- if modified, {write}; exit |
| - | // ZZ means to save file (if necessary), then exit |
| - | c1 = get_one_char(); |
| - | if (c1 != 'Z') { |
| - | indicate_error(c); |
| - | break; |
| + | psb("\"%s\" %dL, %dC", fn, li, l); |
| + | if (q == text && r == end - 1 && l == ch) |
| + | file_modified = FALSE; |
| + | if ((cmd[0] == 'x' || cmd[1] == 'q') && l == ch) { |
| + | editing = 0; |
| } | |
| - | if (file_modified |
| #ifdef CONFIG_FEATURE_VI_READONLY | |
| - | && ! vi_readonly |
| - | && ! readonly |
| + | vc3:; |
| #endif /* CONFIG_FEATURE_VI_READONLY */ | |
| - | ) { |
| - | cnt = file_write(cfn, text, end - 1); |
| - | if (cnt == (end - 1 - text + 1)) { |
| - | editing = 0; |
| - | } |
| - | } else { |
| - | editing = 0; |
| - | } |
| - | break; |
| - | case '^': // ^- move to first non-blank on line |
| - | dot_begin(); |
| - | dot_skip_over_ws(); |
| - | break; |
| - | case 'b': // b- back a word |
| - | case 'e': // e- end of word |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd(c); |
| - | } // repeat cnt |
| - | dir = FORWARD; |
| - | if (c == 'b') |
| - | dir = BACK; |
| - | if ((dot + dir) < text || (dot + dir) > end - 1) |
| - | break; |
| - | dot += dir; |
| - | if (isspace(*dot)) { |
| - | dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS); |
| - | } |
| - | if (isalnum(*dot) || *dot == '_') { |
| - | dot = skip_thing(dot, 1, dir, S_END_ALNUM); |
| - | } else if (ispunct(*dot)) { |
| - | dot = skip_thing(dot, 1, dir, S_END_PUNCT); |
| - | } |
| - | break; |
| - | case 'c': // c- change something |
| - | case 'd': // d- delete something |
| - | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| - | case 'y': // y- yank something |
| - | case 'Y': // Y- Yank a line |
| - | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| - | yf = YANKDEL; // assume either "c" or "d" |
| #ifdef CONFIG_FEATURE_VI_YANKMARK | |
| - | if (c == 'y' || c == 'Y') |
| - | yf = YANKONLY; |
| - | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| - | c1 = 'y'; |
| - | if (c != 'Y') |
| - | c1 = get_one_char(); // get the type of thing to delete |
| - | find_range(&p, &q, c1); |
| - | if (c1 == 27) { // ESC- user changed mind and wants out |
| - | c = c1 = 27; // Escape- do nothing |
| - | } else if (strchr("wW", c1)) { |
| - | if (c == 'c') { |
| - | // don't include trailing WS as part of word |
| - | while (isblnk(*q)) { |
| - | if (q <= text || q[-1] == '\n') |
| - | break; |
| - | q--; |
| - | } |
| - | } |
| - | dot = yank_delete(p, q, 0, yf); // delete word |
| - | } else if (strchr("^0bBeEft$", c1)) { |
| - | // single line copy text into a register and delete |
| - | dot = yank_delete(p, q, 0, yf); // delete word |
| - | } else if (strchr("cdykjHL%+-{}\r\n", c1)) { |
| - | // multiple line copy text into a register and delete |
| - | dot = yank_delete(p, q, 1, yf); // delete lines |
| - | if (c == 'c') { |
| - | dot = char_insert(dot, '\n'); |
| - | // on the last line of file don't move to prev line |
| - | if (dot != (end-1)) { |
| - | dot_prev(); |
| - | } |
| - | } else if (c == 'd') { |
| - | dot_begin(); |
| - | dot_skip_over_ws(); |
| - | } |
| - | } else { |
| - | // could not recognize object |
| - | c = c1 = 27; // error- |
| - | indicate_error(c); |
| + | } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines |
| + | if (b < 0) { // no addr given- use defaults |
| + | q = begin_line(dot); // assume .,. for the range |
| + | r = end_line(dot); |
| } | |
| - | if (c1 != 27) { |
| - | // if CHANGING, not deleting, start inserting after the delete |
| - | if (c == 'c') { |
| - | strcpy((char *) buf, "Change"); |
| - | goto dc_i; // start inserting |
| - | } |
| - | if (c == 'd') { |
| - | strcpy((char *) buf, "Delete"); |
| - | } |
| - | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| - | if (c == 'y' || c == 'Y') { |
| - | strcpy((char *) buf, "Yank"); |
| - | } |
| - | p = reg[YDreg]; |
| - | q = p + strlen((char *) p); |
| - | for (cnt = 0; p <= q; p++) { |
| - | if (*p == '\n') |
| - | cnt++; |
| - | } |
| - | psb("%s %d lines (%d chars) using [%c]", |
| - | buf, cnt, strlen((char *) reg[YDreg]), what_reg()); |
| + | text_yank(q, r, YDreg); |
| + | li = count_lines(q, r); |
| + | psb("Yank %d lines (%d chars) into [%c]", |
| + | li, strlen((char *) reg[YDreg]), what_reg()); |
| #endif /* CONFIG_FEATURE_VI_YANKMARK */ | |
| - | end_cmd_q(); // stop adding to q |
| + | } else { |
| + | // cmd unknown |
| + | ni((Byte *) cmd); |
| + | } |
| + | vc1: |
| + | dot = bound_dot(dot); // make sure "dot" is valid |
| + | return; |
| + | #ifdef CONFIG_FEATURE_VI_SEARCH |
| + | colon_s_fail: |
| + | psb(":s expression missing delimiters"); |
| + | return; |
| + | #endif |
| + | |
| + | } |
| + | |
| + | static void Hit_Return(void) |
| + | { |
| + | char c; |
| + | |
| + | standout_start(); // start reverse video |
| + | write(1, "[Hit return to continue]", 24); |
| + | standout_end(); // end reverse video |
| + | while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */ |
| + | ; |
| + | redraw(TRUE); // force redraw all |
| + | } |
| + | #endif /* CONFIG_FEATURE_VI_COLON */ |
| + | |
| + | //----- Synchronize the cursor to Dot -------------------------- |
| + | static void sync_cursor(Byte * d, int *row, int *col) |
| + | { |
| + | Byte *beg_cur, *end_cur; // begin and end of "d" line |
| + | Byte *beg_scr, *end_scr; // begin and end of screen |
| + | Byte *tp; |
| + | int cnt, ro, co; |
| + | |
| + | beg_cur = begin_line(d); // first char of cur line |
| + | end_cur = end_line(d); // last char of cur line |
| + | |
| + | beg_scr = end_scr = screenbegin; // first char of screen |
| + | end_scr = end_screen(); // last char of screen |
| + | |
| + | if (beg_cur < screenbegin) { |
| + | // "d" is before top line on screen |
| + | // how many lines do we have to move |
| + | cnt = count_lines(beg_cur, screenbegin); |
| + | sc1: |
| + | screenbegin = beg_cur; |
| + | if (cnt > (rows - 1) / 2) { |
| + | // we moved too many lines. put "dot" in middle of screen |
| + | for (cnt = 0; cnt < (rows - 1) / 2; cnt++) { |
| + | screenbegin = prev_line(screenbegin); |
| + | } |
| } | |
| - | break; |
| - | case 'k': // k- goto prev line, same col |
| - | case VI_K_UP: // cursor key Up |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd(c); |
| - | } // repeat cnt |
| - | dot_prev(); |
| - | dot = move_to_col(dot, ccol + offset); // try stay in same col |
| - | break; |
| - | case 'r': // r- replace the current char with user input |
| - | c1 = get_one_char(); // get the replacement char |
| - | if (*dot != '\n') { |
| - | *dot = c1; |
| - | file_modified = TRUE; // has the file been modified |
| - | } |
| - | end_cmd_q(); // stop adding to q |
| - | break; |
| - | case 't': // t- move to char prior to next x |
| - | last_forward_char = get_one_char(); |
| - | do_cmd(';'); |
| - | if (*dot == last_forward_char) |
| - | dot_left(); |
| - | last_forward_char= 0; |
| - | break; |
| - | case 'w': // w- forward a word |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd(c); |
| - | } // repeat cnt |
| - | if (isalnum(*dot) || *dot == '_') { // we are on ALNUM |
| - | dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM); |
| - | } else if (ispunct(*dot)) { // we are on PUNCT |
| - | dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT); |
| - | } |
| - | if (dot < end - 1) |
| - | dot++; // move over word |
| - | if (isspace(*dot)) { |
| - | dot = skip_thing(dot, 2, FORWARD, S_OVER_WS); |
| - | } |
| - | break; |
| - | case 'z': // z- |
| - | c1 = get_one_char(); // get the replacement char |
| - | cnt = 0; |
| - | if (c1 == '.') |
| - | cnt = (rows - 2) / 2; // put dot at center |
| - | if (c1 == '-') |
| - | cnt = rows - 2; // put dot at bottom |
| - | screenbegin = begin_line(dot); // start dot at top |
| - | dot_scroll(cnt, -1); |
| - | break; |
| - | case '|': // |- move to column "cmdcnt" |
| - | dot = move_to_col(dot, cmdcnt - 1); // try to move to column |
| - | break; |
| - | case '~': // ~- flip the case of letters a-z -> A-Z |
| - | if (cmdcnt-- > 1) { |
| - | do_cmd(c); |
| - | } // repeat cnt |
| - | if (islower(*dot)) { |
| - | *dot = toupper(*dot); |
| - | file_modified = TRUE; // has the file been modified |
| - | } else if (isupper(*dot)) { |
| - | *dot = tolower(*dot); |
| - | file_modified = TRUE; // has the file been modified |
| + | } else if (beg_cur > end_scr) { |
| + | // "d" is after bottom line on screen |
| + | // how many lines do we have to move |
| + | cnt = count_lines(end_scr, beg_cur); |
| + | if (cnt > (rows - 1) / 2) |
| + | goto sc1; // too many lines |
| + | for (ro = 0; ro < cnt - 1; ro++) { |
| + | // move screen begin the same amount |
| + | screenbegin = next_line(screenbegin); |
| + | // now, move the end of screen |
| + | end_scr = next_line(end_scr); |
| + | end_scr = end_line(end_scr); |
| } | |
| - | dot_right(); |
| - | end_cmd_q(); // stop adding to q |
| - | break; |
| - | //----- The Cursor and Function Keys ----------------------------- |
| - | case VI_K_HOME: // Cursor Key Home |
| - | dot_begin(); |
| - | break; |
| - | // The Fn keys could point to do_macro which could translate them |
| - | case VI_K_FUN1: // Function Key F1 |
| - | case VI_K_FUN2: // Function Key F2 |
| - | case VI_K_FUN3: // Function Key F3 |
| - | case VI_K_FUN4: // Function Key F4 |
| - | case VI_K_FUN5: // Function Key F5 |
| - | case VI_K_FUN6: // Function Key F6 |
| - | case VI_K_FUN7: // Function Key F7 |
| - | case VI_K_FUN8: // Function Key F8 |
| - | case VI_K_FUN9: // Function Key F9 |
| - | case VI_K_FUN10: // Function Key F10 |
| - | case VI_K_FUN11: // Function Key F11 |
| - | case VI_K_FUN12: // Function Key F12 |
| - | break; |
| + | } |
| + | // "d" is on screen- find out which row |
| + | tp = screenbegin; |
| + | for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row |
| + | if (tp == beg_cur) |
| + | break; |
| + | tp = next_line(tp); |
| } | |
| - | dc1: |
| - | // if text[] just became empty, add back an empty line |
| - | if (end == text) { |
| - | (void) char_insert(text, '\n'); // start empty buf with dummy line |
| - | dot = text; |
| + | // find out what col "d" is on |
| + | co = 0; |
| + | do { // drive "co" to correct column |
| + | if (*tp == '\n' || *tp == '\0') |
| + | break; |
| + | if (*tp == '\t') { |
| + | // 7 - (co % 8 ) |
| + | co += ((tabstop - 1) - (co % tabstop)); |
| + | } else if (*tp < ' ') { |
| + | co++; // display as ^X, use 2 columns |
| + | } |
| + | } while (tp++ < d && ++co); |
| + | |
| + | // "co" is the column where "dot" is. |
| + | // The screen has "columns" columns. |
| + | // The currently displayed columns are 0+offset -- columns+ofset |
| + | // |-------------------------------------------------------------| |
| + | // ^ ^ ^ |
| + | // offset | |------- columns ----------------| |
| + | // |
| + | // If "co" is already in this range then we do not have to adjust offset |
| + | // but, we do have to subtract the "offset" bias from "co". |
| + | // If "co" is outside this range then we have to change "offset". |
| + | // If the first char of a line is a tab the cursor will try to stay |
| + | // in column 7, but we have to set offset to 0. |
| + | |
| + | if (co < 0 + offset) { |
| + | offset = co; |
| } | |
| - | // it is OK for dot to exactly equal to end, otherwise check dot validity |
| - | if (dot != end) { |
| - | dot = bound_dot(dot); // make sure "dot" is valid |
| + | if (co >= columns + offset) { |
| + | offset = co - columns + 1; |
| } | |
| - | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| - | check_context(c); // update the current context |
| - | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| + | // if the first char of the line is a tab, and "dot" is sitting on it |
| + | // force offset to 0. |
| + | if (d == beg_cur && *d == '\t') { |
| + | offset = 0; |
| + | } |
| + | co -= offset; |
| - | if (!isdigit(c)) |
| - | cmdcnt = 0; // cmd was not a number, reset cmdcnt |
| - | cnt = dot - begin_line(dot); |
| - | // Try to stay off of the Newline |
| - | if (*dot == '\n' && cnt > 0 && cmd_mode == 0) |
| - | dot--; |
| + | *row = ro; |
| + | *col = co; |
| } | |
| - | //----- The Colon commands ------------------------------------- |
| - | #ifdef CONFIG_FEATURE_VI_COLON |
| - | static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present |
| + | //----- Text Movement Routines --------------------------------- |
| + | static Byte *begin_line(Byte * p) // return pointer to first char cur line |
| { | |
| - | int st; |
| - | Byte *q; |
| - | |
| - | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| - | Byte c; |
| - | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| - | #ifdef CONFIG_FEATURE_VI_SEARCH |
| - | Byte *pat, buf[BUFSIZ]; |
| - | #endif /* CONFIG_FEATURE_VI_SEARCH */ |
| + | while (p > text && p[-1] != '\n') |
| + | p--; // go to cur line B-o-l |
| + | return (p); |
| + | } |
| - | *addr = -1; // assume no addr |
| - | if (*p == '.') { // the current line |
| - | p++; |
| - | q = begin_line(dot); |
| - | *addr = count_lines(text, q); |
| - | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| - | } else if (*p == '\'') { // is this a mark addr |
| - | p++; |
| - | c = tolower(*p); |
| - | p++; |
| - | if (c >= 'a' && c <= 'z') { |
| - | // we have a mark |
| - | c = c - 'a'; |
| - | q = mark[(int) c]; |
| - | if (q != NULL) { // is mark valid |
| - | *addr = count_lines(text, q); // count lines |
| - | } |
| - | } |
| - | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| - | #ifdef CONFIG_FEATURE_VI_SEARCH |
| - | } else if (*p == '/') { // a search pattern |
| - | q = buf; |
| - | for (p++; *p; p++) { |
| - | if (*p == '/') |
| - | break; |
| - | *q++ = *p; |
| - | *q = '\0'; |
| - | } |
| - | pat = (Byte *) xstrdup((char *) buf); // save copy of pattern |
| - | if (*p == '/') |
| - | p++; |
| - | q = char_search(dot, pat, FORWARD, FULL); |
| - | if (q != NULL) { |
| - | *addr = count_lines(text, q); |
| - | } |
| - | free(pat); |
| - | #endif /* CONFIG_FEATURE_VI_SEARCH */ |
| - | } else if (*p == '$') { // the last line in file |
| - | p++; |
| - | q = begin_line(end - 1); |
| - | *addr = count_lines(text, q); |
| - | } else if (isdigit(*p)) { // specific line number |
| - | sscanf((char *) p, "%d%n", addr, &st); |
| - | p += st; |
| - | } else { // I don't reconise this |
| - | // unrecognised address- assume -1 |
| - | *addr = -1; |
| - | } |
| + | static Byte *end_line(Byte * p) // return pointer to NL of cur line line |
| + | { |
| + | while (p < end - 1 && *p != '\n') |
| + | p++; // go to cur line E-o-l |
| return (p); | |
| } | |
| - | static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present |
| + | extern inline Byte *dollar_line(Byte * p) // return pointer to just before NL line |
| { | |
| - | //----- get the address' i.e., 1,3 'a,'b ----- |
| - | // get FIRST addr, if present |
| - | while (isblnk(*p)) |
| - | p++; // skip over leading spaces |
| - | if (*p == '%') { // alias for 1,$ |
| - | p++; |
| - | *b = 1; |
| - | *e = count_lines(text, end-1); |
| - | goto ga0; |
| - | } |
| - | p = get_one_address(p, b); |
| - | while (isblnk(*p)) |
| - | p++; |
| - | if (*p == ',') { // is there a address seperator |
| - | p++; |
| - | while (isblnk(*p)) |
| - | p++; |
| - | // get SECOND addr, if present |
| - | p = get_one_address(p, e); |
| - | } |
| - | ga0: |
| - | while (isblnk(*p)) |
| - | p++; // skip over trailing spaces |
| + | while (p < end - 1 && *p != '\n') |
| + | p++; // go to cur line E-o-l |
| + | // Try to stay off of the Newline |
| + | if (*p == '\n' && (p - begin_line(p)) > 0) |
| + | p--; |
| return (p); | |
| } | |
| - | static void colon(Byte * buf) |
| + | static Byte *prev_line(Byte * p) // return pointer first char prev line |
| { | |
| - | Byte c, *orig_buf, *buf1, *q, *r; |
| - | Byte *fn, cmd[BUFSIZ], args[BUFSIZ]; |
| - | int i, l, li, ch, st, b, e; |
| - | int useforce, forced; |
| - | struct stat st_buf; |
| + | p = begin_line(p); // goto begining of cur line |
| + | if (p[-1] == '\n' && p > text) |
| + | p--; // step to prev line |
| + | p = begin_line(p); // goto begining of prev line |
| + | return (p); |
| + | } |
| - | // :3154 // if (-e line 3154) goto it else stay put |
| - | // :4,33w! foo // write a portion of buffer to file "foo" |
| - | // :w // write all of buffer to current file |
| - | // :q // quit |
| - | // :q! // quit- dont care about modified file |
| - | // :'a,'z!sort -u // filter block through sort |
| - | // :'f // goto mark "f" |
| - | // :'fl // list literal the mark "f" line |
| - | // :.r bar // read file "bar" into buffer before dot |
| - | // :/123/,/abc/d // delete lines from "123" line to "abc" line |
| - | // :/xyz/ // goto the "xyz" line |
| - | // :s/find/replace/ // substitute pattern "find" with "replace" |
| - | // :!<cmd> // run <cmd> then return |
| - | // |
| - | if (strlen((char *) buf) <= 0) |
| - | goto vc1; |
| - | if (*buf == ':') |
| - | buf++; // move past the ':' |
| + | static Byte *next_line(Byte * p) // return pointer first char next line |
| + | { |
| + | p = end_line(p); |
| + | if (*p == '\n' && p < end - 1) |
| + | p++; // step to next line |
| + | return (p); |
| + | } |
| - | forced = useforce = FALSE; |
| - | li = st = ch = i = 0; |
| - | b = e = -1; |
| - | q = text; // assume 1,$ for the range |
| - | r = end - 1; |
| - | li = count_lines(text, end - 1); |
| - | fn = cfn; // default to current file |
| - | memset(cmd, '\0', BUFSIZ); // clear cmd[] |
| - | memset(args, '\0', BUFSIZ); // clear args[] |
| + | //----- Text Information Routines ------------------------------ |
| + | static Byte *end_screen(void) |
| + | { |
| + | Byte *q; |
| + | int cnt; |
| - | // look for optional address(es) :. :1 :1,9 :'q,'a :% |
| - | buf = get_address(buf, &b, &e); |
| + | // find new bottom line |
| + | q = screenbegin; |
| + | for (cnt = 0; cnt < rows - 2; cnt++) |
| + | q = next_line(q); |
| + | q = end_line(q); |
| + | return (q); |
| + | } |
| - | // remember orig command line |
| - | orig_buf = buf; |
| + | static int count_lines(Byte * start, Byte * stop) // count line from start to stop |
| + | { |
| + | Byte *q; |
| + | int cnt; |
| - | // get the COMMAND into cmd[] |
| - | buf1 = cmd; |
| - | while (*buf != '\0') { |
| - | if (isspace(*buf)) |
| - | break; |
| - | *buf1++ = *buf++; |
| - | } |
| - | // get any ARGuments |
| - | while (isblnk(*buf)) |
| - | buf++; |
| - | strcpy((char *) args, (char *) buf); |
| - | buf1 = last_char_is((char *)cmd, '!'); |
| - | if (buf1) { |
| - | useforce = TRUE; |
| - | *buf1 = '\0'; // get rid of ! |
| + | if (stop < start) { // start and stop are backwards- reverse them |
| + | q = start; |
| + | start = stop; |
| + | stop = q; |
| } | |
| - | if (b >= 0) { |
| - | // if there is only one addr, then the addr |
| - | // is the line number of the single line the |
| - | // user wants. So, reset the end |
| - | // pointer to point at end of the "b" line |
| - | q = find_line(b); // what line is #b |
| - | r = end_line(q); |
| - | li = 1; |
| + | cnt = 0; |
| + | stop = end_line(stop); // get to end of this line |
| + | for (q = start; q <= stop && q <= end - 1; q++) { |
| + | if (*q == '\n') |
| + | cnt++; |
| } | |
| - | if (e >= 0) { |
| - | // we were given two addrs. change the |
| - | // end pointer to the addr given by user. |
| - | r = find_line(e); // what line is #e |
| - | r = end_line(r); |
| - | li = e - b + 1; |
| + | return (cnt); |
| + | } |
| + | |
| + | static Byte *find_line(int li) // find begining of line #li |
| + | { |
| + | Byte *q; |
| + | |
| + | for (q = text; li > 1; li--) { |
| + | q = next_line(q); |
| } | |
| - | // ------------ now look for the command ------------ |
| - | i = strlen((char *) cmd); |
| - | if (i == 0) { // :123CR goto line #123 |
| - | if (b >= 0) { |
| - | dot = find_line(b); // what line is #b |
| - | dot_skip_over_ws(); |
| - | } |
| - | } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd |
| - | // :!ls run the <cmd> |
| - | (void) alarm(0); // wait for input- no alarms |
| - | place_cursor(rows - 1, 0, FALSE); // go to Status line |
| - | clear_to_eol(); // clear the line |
| - | cookmode(); |
| - | system(orig_buf+1); // run the cmd |
| - | rawmode(); |
| - | Hit_Return(); // let user see results |
| - | (void) alarm(3); // done waiting for input |
| - | } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address |
| - | if (b < 0) { // no addr given- use defaults |
| - | b = e = count_lines(text, dot); |
| - | } |
| - | psb("%d", b); |
| - | } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines |
| - | if (b < 0) { // no addr given- use defaults |
| - | q = begin_line(dot); // assume .,. for the range |
| - | r = end_line(dot); |
| - | } |
| - | dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines |
| - | dot_skip_over_ws(); |
| - | } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file |
| - | int sr; |
| - | sr= 0; |
| - | // don't edit, if the current file has been modified |
| - | if (file_modified && ! useforce) { |
| - | psbs("No write since last change (:edit! overrides)"); |
| - | goto vc1; |
| - | } |
| - | if (strlen(args) > 0) { |
| - | // the user supplied a file name |
| - | fn= args; |
| - | } else if (cfn != 0 && strlen(cfn) > 0) { |
| - | // no user supplied name- use the current filename |
| - | fn= cfn; |
| - | goto vc5; |
| - | } else { |
| - | // no user file name, no current name- punt |
| - | psbs("No current filename"); |
| - | goto vc1; |
| - | } |
| + | return (q); |
| + | } |
| - | // see if file exists- if not, its just a new file request |
| - | if ((sr=stat((char*)fn, &st_buf)) < 0) { |
| - | // This is just a request for a new file creation. |
| - | // The file_insert below will fail but we get |
| - | // an empty buffer with a file name. Then the "write" |
| - | // command can do the create. |
| - | } else { |
| - | if ((st_buf.st_mode & (S_IFREG)) == 0) { |
| - | // This is not a regular file |
| - | psbs("\"%s\" is not a regular file", fn); |
| - | goto vc1; |
| - | } |
| - | if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) { |
| - | // dont have any read permissions |
| - | psbs("\"%s\" is not readable", fn); |
| - | goto vc1; |
| - | } |
| + | //----- Dot Movement Routines ---------------------------------- |
| + | static void dot_left(void) |
| + | { |
| + | if (dot > text && dot[-1] != '\n') |
| + | dot--; |
| + | } |
| + | |
| + | static void dot_right(void) |
| + | { |
| + | if (dot < end - 1 && *dot != '\n') |
| + | dot++; |
| + | } |
| + | |
| + | static void dot_begin(void) |
| + | { |
| + | dot = begin_line(dot); // return pointer to first char cur line |
| + | } |
| + | |
| + | static void dot_end(void) |
| + | { |
| + | dot = end_line(dot); // return pointer to last char cur line |
| + | } |
| + | |
| + | static Byte *move_to_col(Byte * p, int l) |
| + | { |
| + | int co; |
| + | |
| + | p = begin_line(p); |
| + | co = 0; |
| + | do { |
| + | if (*p == '\n' || *p == '\0') |
| + | break; |
| + | if (*p == '\t') { |
| + | // 7 - (co % 8 ) |
| + | co += ((tabstop - 1) - (co % tabstop)); |
| + | } else if (*p < ' ') { |
| + | co++; // display as ^X, use 2 columns |
| } | |
| + | } while (++co <= l && p++ < end); |
| + | return (p); |
| + | } |
| - | // There is a read-able regular file |
| - | // make this the current file |
| - | q = (Byte *) xstrdup((char *) fn); // save the cfn |
| - | if (cfn != 0) |
| - | free(cfn); // free the old name |
| - | cfn = q; // remember new cfn |
| + | static void dot_next(void) |
| + | { |
| + | dot = next_line(dot); |
| + | } |
| - | vc5: |
| - | // delete all the contents of text[] |
| - | new_text(2 * file_size(fn)); |
| - | screenbegin = dot = end = text; |
| + | static void dot_prev(void) |
| + | { |
| + | dot = prev_line(dot); |
| + | } |
| - | // insert new file |
| - | ch = file_insert(fn, text, file_size(fn)); |
| + | static void dot_scroll(int cnt, int dir) |
| + | { |
| + | Byte *q; |
| - | if (ch < 1) { |
| - | // start empty buf with dummy line |
| - | (void) char_insert(text, '\n'); |
| - | ch= 1; |
| + | for (; cnt > 0; cnt--) { |
| + | if (dir < 0) { |
| + | // scroll Backwards |
| + | // ctrl-Y scroll up one line |
| + | screenbegin = prev_line(screenbegin); |
| + | } else { |
| + | // scroll Forwards |
| + | // ctrl-E scroll down one line |
| + | screenbegin = next_line(screenbegin); |
| } | |
| - | file_modified = FALSE; |
| - | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| - | if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) { |
| - | free(reg[Ureg]); // free orig line reg- for 'U' |
| - | reg[Ureg]= 0; |
| - | } |
| - | if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) { |
| - | free(reg[YDreg]); // free default yank/delete register |
| - | reg[YDreg]= 0; |
| - | } |
| - | for (li = 0; li < 28; li++) { |
| - | mark[li] = 0; |
| - | } // init the marks |
| - | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| - | // how many lines in text[]? |
| - | li = count_lines(text, end - 1); |
| - | psb("\"%s\"%s" |
| - | #ifdef CONFIG_FEATURE_VI_READONLY |
| - | "%s" |
| - | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| - | " %dL, %dC", cfn, |
| - | (sr < 0 ? " [New file]" : ""), |
| - | #ifdef CONFIG_FEATURE_VI_READONLY |
| - | ((vi_readonly || readonly) ? " [Read only]" : ""), |
| - | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| - | li, ch); |
| - | } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this |
| - | if (b != -1 || e != -1) { |
| - | ni((Byte *) "No address allowed on this command"); |
| - | goto vc1; |
| - | } |
| - | if (strlen((char *) args) > 0) { |
| - | // user wants a new filename |
| - | if (cfn != NULL) |
| - | free(cfn); |
| - | cfn = (Byte *) xstrdup((char *) args); |
| - | } else { |
| - | // user wants file status info |
| - | edit_status(); |
| - | } |
| - | } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available |
| - | // print out values of all features |
| - | place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen |
| - | clear_to_eol(); // clear the line |
| - | cookmode(); |
| - | show_help(); |
| - | rawmode(); |
| - | Hit_Return(); |
| - | } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line |
| - | if (b < 0) { // no addr given- use defaults |
| - | q = begin_line(dot); // assume .,. for the range |
| - | r = end_line(dot); |
| - | } |
| - | place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen |
| - | clear_to_eol(); // clear the line |
| - | write(1, "\r\n", 2); |
| - | for (; q <= r; q++) { |
| - | c = *q; |
| - | if (c > '~') |
| - | standout_start(); |
| - | if (c == '\n') { |
| - | write(1, "$\r", 2); |
| - | } else if (*q < ' ') { |
| - | write(1, "^", 1); |
| - | c += '@'; |
| + | } |
| + | // make sure "dot" stays on the screen so we dont scroll off |
| + | if (dot < screenbegin) |
| + | dot = screenbegin; |
| + | q = end_screen(); // find new bottom line |
| + | if (dot > q) |
| + | dot = begin_line(q); // is dot is below bottom line? |
| + | dot_skip_over_ws(); |
| + | } |
| + | |
| + | static void dot_skip_over_ws(void) |
| + | { |
| + | // skip WS |
| + | while (isspace(*dot) && *dot != '\n' && dot < end - 1) |
| + | dot++; |
| + | } |
| + | |
| + | static void dot_delete(void) // delete the char at 'dot' |
| + | { |
| + | (void) text_hole_delete(dot, dot); |
| + | } |
| + | |
| + | static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end" |
| + | { |
| + | if (p >= end && end > text) { |
| + | p = end - 1; |
| + | indicate_error('1'); |
| + | } |
| + | if (p < text) { |
| + | p = text; |
| + | indicate_error('2'); |
| + | } |
| + | return (p); |
| + | } |
| + | |
| + | //----- Helper Utility Routines -------------------------------- |
| + | |
| + | //---------------------------------------------------------------- |
| + | //----- Char Routines -------------------------------------------- |
| + | /* Chars that are part of a word- |
| + | * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz |
| + | * Chars that are Not part of a word (stoppers) |
| + | * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~ |
| + | * Chars that are WhiteSpace |
| + | * TAB NEWLINE VT FF RETURN SPACE |
| + | * DO NOT COUNT NEWLINE AS WHITESPACE |
| + | */ |
| + | |
| + | static Byte *new_screen(int ro, int co) |
| + | { |
| + | int li; |
| + | |
| + | if (screen != 0) |
| + | free(screen); |
| + | screensize = ro * co + 8; |
| + | screen = (Byte *) xmalloc(screensize); |
| + | // initialize the new screen. assume this will be a empty file. |
| + | screen_erase(); |
| + | // non-existant text[] lines start with a tilde (~). |
| + | for (li = 1; li < ro - 1; li++) { |
| + | screen[(li * co) + 0] = '~'; |
| + | } |
| + | return (screen); |
| + | } |
| + | |
| + | static Byte *new_text(int size) |
| + | { |
| + | if (size < 10240) |
| + | size = 10240; // have a minimum size for new files |
| + | if (text != 0) { |
| + | //text -= 4; |
| + | free(text); |
| + | } |
| + | text = (Byte *) xmalloc(size + 8); |
| + | memset(text, '\0', size); // clear new text[] |
| + | //text += 4; // leave some room for "oops" |
| + | textend = text + size - 1; |
| + | //textend -= 4; // leave some root for "oops" |
| + | return (text); |
| + | } |
| + | |
| + | #ifdef CONFIG_FEATURE_VI_SEARCH |
| + | static int mycmp(Byte * s1, Byte * s2, int len) |
| + | { |
| + | int i; |
| + | |
| + | i = strncmp((char *) s1, (char *) s2, len); |
| + | #ifdef CONFIG_FEATURE_VI_SETOPTS |
| + | if (ignorecase) { |
| + | i = strncasecmp((char *) s1, (char *) s2, len); |
| + | } |
| + | #endif /* CONFIG_FEATURE_VI_SETOPTS */ |
| + | return (i); |
| + | } |
| + | |
| + | static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p |
| + | { |
| + | #ifndef REGEX_SEARCH |
| + | Byte *start, *stop; |
| + | int len; |
| + | |
| + | len = strlen((char *) pat); |
| + | if (dir == FORWARD) { |
| + | stop = end - 1; // assume range is p - end-1 |
| + | if (range == LIMITED) |
| + | stop = next_line(p); // range is to next line |
| + | for (start = p; start < stop; start++) { |
| + | if (mycmp(start, pat, len) == 0) { |
| + | return (start); |
| } | |
| - | write(1, &c, 1); |
| - | if (c > '~') |
| - | standout_end(); |
| } | |
| - | #ifdef CONFIG_FEATURE_VI_SET |
| - | vc2: |
| - | #endif /* CONFIG_FEATURE_VI_SET */ |
| - | Hit_Return(); |
| - | } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit |
| - | (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file |
| - | if (useforce) { |
| - | // force end of argv list |
| - | if (*cmd == 'q') { |
| - | optind = save_argc; |
| + | } else if (dir == BACK) { |
| + | stop = text; // assume range is text - p |
| + | if (range == LIMITED) |
| + | stop = prev_line(p); // range is to prev line |
| + | for (start = p - len; start >= stop; start--) { |
| + | if (mycmp(start, pat, len) == 0) { |
| + | return (start); |
| } | |
| - | editing = 0; |
| - | goto vc1; |
| - | } |
| - | // don't exit if the file been modified |
| - | if (file_modified) { |
| - | psbs("No write since last change (:%s! overrides)", |
| - | (*cmd == 'q' ? "quit" : "next")); |
| - | goto vc1; |
| - | } |
| - | // are there other file to edit |
| - | if (*cmd == 'q' && optind < save_argc - 1) { |
| - | psbs("%d more file to edit", (save_argc - optind - 1)); |
| - | goto vc1; |
| - | } |
| - | if (*cmd == 'n' && optind >= save_argc - 1) { |
| - | psbs("No more files to edit"); |
| - | goto vc1; |
| - | } |
| - | editing = 0; |
| - | } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[] |
| - | fn = args; |
| - | if (strlen((char *) fn) <= 0) { |
| - | psbs("No filename given"); |
| - | goto vc1; |
| - | } |
| - | if (b < 0) { // no addr given- use defaults |
| - | q = begin_line(dot); // assume "dot" |
| - | } |
| - | // read after current line- unless user said ":0r foo" |
| - | if (b != 0) |
| - | q = next_line(q); |
| - | #ifdef CONFIG_FEATURE_VI_READONLY |
| - | l= readonly; // remember current files' status |
| - | #endif |
| - | ch = file_insert(fn, q, file_size(fn)); |
| - | #ifdef CONFIG_FEATURE_VI_READONLY |
| - | readonly= l; |
| - | #endif |
| - | if (ch < 0) |
| - | goto vc1; // nothing was inserted |
| - | // how many lines in text[]? |
| - | li = count_lines(q, q + ch - 1); |
| - | psb("\"%s\"" |
| - | #ifdef CONFIG_FEATURE_VI_READONLY |
| - | "%s" |
| - | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| - | " %dL, %dC", fn, |
| - | #ifdef CONFIG_FEATURE_VI_READONLY |
| - | ((vi_readonly || readonly) ? " [Read only]" : ""), |
| - | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| - | li, ch); |
| - | if (ch > 0) { |
| - | // if the insert is before "dot" then we need to update |
| - | if (q <= dot) |
| - | dot += ch; |
| - | file_modified = TRUE; |
| - | } |
| - | } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args |
| - | if (file_modified && ! useforce) { |
| - | psbs("No write since last change (:rewind! overrides)"); |
| - | } else { |
| - | // reset the filenames to edit |
| - | optind = fn_start - 1; |
| - | editing = 0; |
| } | |
| - | #ifdef CONFIG_FEATURE_VI_SET |
| - | } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features |
| - | i = 0; // offset into args |
| - | if (strlen((char *) args) == 0) { |
| - | // print out values of all options |
| - | place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen |
| - | clear_to_eol(); // clear the line |
| - | printf("----------------------------------------\r\n"); |
| - | #ifdef CONFIG_FEATURE_VI_SETOPTS |
| - | if (!autoindent) |
| - | printf("no"); |
| - | printf("autoindent "); |
| - | if (!err_method) |
| - | printf("no"); |
| - | printf("flash "); |
| - | if (!ignorecase) |
| - | printf("no"); |
| - | printf("ignorecase "); |
| - | if (!showmatch) |
| - | printf("no"); |
| - | printf("showmatch "); |
| - | printf("tabstop=%d ", tabstop); |
| - | #endif /* CONFIG_FEATURE_VI_SETOPTS */ |
| - | printf("\r\n"); |
| - | goto vc2; |
| - | } |
| - | if (strncasecmp((char *) args, "no", 2) == 0) |
| - | i = 2; // ":set noautoindent" |
| - | #ifdef CONFIG_FEATURE_VI_SETOPTS |
| - | if (strncasecmp((char *) args + i, "autoindent", 10) == 0 || |
| - | strncasecmp((char *) args + i, "ai", 2) == 0) { |
| - | autoindent = (i == 2) ? 0 : 1; |
| - | } |
| - | if (strncasecmp((char *) args + i, "flash", 5) == 0 || |
| - | strncasecmp((char *) args + i, "fl", 2) == 0) { |
| - | err_method = (i == 2) ? 0 : 1; |
| - | } |
| - | if (strncasecmp((char *) args + i, "ignorecase", 10) == 0 || |
| - | strncasecmp((char *) args + i, "ic", 2) == 0) { |
| - | ignorecase = (i == 2) ? 0 : 1; |
| - | } |
| - | if (strncasecmp((char *) args + i, "showmatch", 9) == 0 || |
| - | strncasecmp((char *) args + i, "sm", 2) == 0) { |
| - | showmatch = (i == 2) ? 0 : 1; |
| - | } |
| - | if (strncasecmp((char *) args + i, "tabstop", 7) == 0) { |
| - | sscanf(strchr((char *) args + i, '='), "=%d", &ch); |
| - | if (ch > 0 && ch < columns - 1) |
| - | tabstop = ch; |
| - | } |
| - | #endif /* CONFIG_FEATURE_VI_SETOPTS */ |
| - | #endif /* CONFIG_FEATURE_VI_SET */ |
| - | #ifdef CONFIG_FEATURE_VI_SEARCH |
| - | } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern |
| - | Byte *ls, *F, *R; |
| - | int gflag; |
| - | |
| - | // F points to the "find" pattern |
| - | // R points to the "replace" pattern |
| - | // replace the cmd line delimiters "/" with NULLs |
| - | gflag = 0; // global replace flag |
| - | c = orig_buf[1]; // what is the delimiter |
| - | F = orig_buf + 2; // start of "find" |
| - | R = (Byte *) strchr((char *) F, c); // middle delimiter |
| - | if (!R) goto colon_s_fail; |
| - | *R++ = '\0'; // terminate "find" |
| - | buf1 = (Byte *) strchr((char *) R, c); |
| - | if (!buf1) goto colon_s_fail; |
| - | *buf1++ = '\0'; // terminate "replace" |
| - | if (*buf1 == 'g') { // :s/foo/bar/g |
| - | buf1++; |
| - | gflag++; // turn on gflag |
| - | } |
| - | q = begin_line(q); |
| - | if (b < 0) { // maybe :s/foo/bar/ |
| - | q = begin_line(dot); // start with cur line |
| - | b = count_lines(text, q); // cur line number |
| - | } |
| - | if (e < 0) |
| - | e = b; // maybe :.s/foo/bar/ |
| - | for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0 |
| - | ls = q; // orig line start |
| - | vc4: |
| - | buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find" |
| - | if (buf1 != NULL) { |
| - | // we found the "find" pattern- delete it |
| - | (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1); |
| - | // inset the "replace" patern |
| - | (void) string_insert(buf1, R); // insert the string |
| - | // check for "global" :s/foo/bar/g |
| - | if (gflag == 1) { |
| - | if ((buf1 + strlen((char *) R)) < end_line(ls)) { |
| - | q = buf1 + strlen((char *) R); |
| - | goto vc4; // don't let q move past cur line |
| - | } |
| - | } |
| - | } |
| - | q = next_line(ls); |
| - | } |
| - | #endif /* CONFIG_FEATURE_VI_SEARCH */ |
| - | } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version |
| - | psb("%s", vi_Version); |
| - | } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file |
| - | (strncasecmp((char *) cmd, "wq", i) == 0) || |
| - | (strncasecmp((char *) cmd, "x", i) == 0)) { |
| - | // is there a file name to write to? |
| - | if (strlen((char *) args) > 0) { |
| - | fn = args; |
| - | } |
| - | #ifdef CONFIG_FEATURE_VI_READONLY |
| - | if ((vi_readonly || readonly) && ! useforce) { |
| - | psbs("\"%s\" File is read only", fn); |
| - | goto vc3; |
| - | } |
| - | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| - | // how many lines in text[]? |
| - | li = count_lines(q, r); |
| - | ch = r - q + 1; |
| - | // see if file exists- if not, its just a new file request |
| - | if (useforce) { |
| - | // if "fn" is not write-able, chmod u+w |
| - | // sprintf(syscmd, "chmod u+w %s", fn); |
| - | // system(syscmd); |
| - | forced = TRUE; |
| - | } |
| - | l = file_write(fn, q, r); |
| - | if (useforce && forced) { |
| - | // chmod u-w |
| - | // sprintf(syscmd, "chmod u-w %s", fn); |
| - | // system(syscmd); |
| - | forced = FALSE; |
| - | } |
| - | psb("\"%s\" %dL, %dC", fn, li, l); |
| - | if (q == text && r == end - 1 && l == ch) |
| - | file_modified = FALSE; |
| - | if ((cmd[0] == 'x' || cmd[1] == 'q') && l == ch) { |
| - | editing = 0; |
| - | } |
| - | #ifdef CONFIG_FEATURE_VI_READONLY |
| - | vc3:; |
| - | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| - | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| - | } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines |
| - | if (b < 0) { // no addr given- use defaults |
| - | q = begin_line(dot); // assume .,. for the range |
| - | r = end_line(dot); |
| - | } |
| - | text_yank(q, r, YDreg); |
| - | li = count_lines(q, r); |
| - | psb("Yank %d lines (%d chars) into [%c]", |
| - | li, strlen((char *) reg[YDreg]), what_reg()); |
| - | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| - | } else { |
| - | // cmd unknown |
| - | ni((Byte *) cmd); |
| } | |
| - | vc1: |
| - | dot = bound_dot(dot); // make sure "dot" is valid |
| - | return; |
| - | #ifdef CONFIG_FEATURE_VI_SEARCH |
| - | colon_s_fail: |
| - | psb(":s expression missing delimiters"); |
| - | return; |
| - | #endif |
| + | // pattern not found |
| + | return (NULL); |
| + | #else /*REGEX_SEARCH */ |
| + | char *q; |
| + | struct re_pattern_buffer preg; |
| + | int i; |
| + | int size, range; |
| - | } |
| + | re_syntax_options = RE_SYNTAX_POSIX_EXTENDED; |
| + | preg.translate = 0; |
| + | preg.fastmap = 0; |
| + | preg.buffer = 0; |
| + | preg.allocated = 0; |
| - | static void Hit_Return(void) |
| - | { |
| - | char c; |
| + | // assume a LIMITED forward search |
| + | q = next_line(p); |
| + | q = end_line(q); |
| + | q = end - 1; |
| + | if (dir == BACK) { |
| + | q = prev_line(p); |
| + | q = text; |
| + | } |
| + | // count the number of chars to search over, forward or backward |
| + | size = q - p; |
| + | if (size < 0) |
| + | size = p - q; |
| + | // RANGE could be negative if we are searching backwards |
| + | range = q - p; |
| - | standout_start(); // start reverse video |
| - | write(1, "[Hit return to continue]", 24); |
| - | standout_end(); // end reverse video |
| - | while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */ |
| - | ; |
| - | redraw(TRUE); // force redraw all |
| + | q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg); |
| + | if (q != 0) { |
| + | // The pattern was not compiled |
| + | psbs("bad search pattern: \"%s\": %s", pat, q); |
| + | i = 0; // return p if pattern not compiled |
| + | goto cs1; |
| + | } |
| + | |
| + | q = p; |
| + | if (range < 0) { |
| + | q = p - size; |
| + | if (q < text) |
| + | q = text; |
| + | } |
| + | // search for the compiled pattern, preg, in p[] |
| + | // range < 0- search backward |
| + | // range > 0- search forward |
| + | // 0 < start < size |
| + | // re_search() < 0 not found or error |
| + | // re_search() > 0 index of found pattern |
| + | // struct pattern char int int int struct reg |
| + | // re_search (*pattern_buffer, *string, size, start, range, *regs) |
| + | i = re_search(&preg, q, size, 0, range, 0); |
| + | if (i == -1) { |
| + | p = 0; |
| + | i = 0; // return NULL if pattern not found |
| + | } |
| + | cs1: |
| + | if (dir == FORWARD) { |
| + | p = p + i; |
| + | } else { |
| + | p = p - i; |
| + | } |
| + | return (p); |
| + | #endif /*REGEX_SEARCH */ |
| } | |
| - | #endif /* CONFIG_FEATURE_VI_COLON */ |
| + | #endif /* CONFIG_FEATURE_VI_SEARCH */ |
| - | //----- Synchronize the cursor to Dot -------------------------- |
| - | static void sync_cursor(Byte * d, int *row, int *col) |
| + | static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p' |
| { | |
| - | Byte *beg_cur, *end_cur; // begin and end of "d" line |
| - | Byte *beg_scr, *end_scr; // begin and end of screen |
| - | Byte *tp; |
| - | int cnt, ro, co; |
| - | |
| - | beg_cur = begin_line(d); // first char of cur line |
| - | end_cur = end_line(d); // last char of cur line |
| - | |
| - | beg_scr = end_scr = screenbegin; // first char of screen |
| - | end_scr = end_screen(); // last char of screen |
| - | |
| - | if (beg_cur < screenbegin) { |
| - | // "d" is before top line on screen |
| - | // how many lines do we have to move |
| - | cnt = count_lines(beg_cur, screenbegin); |
| - | sc1: |
| - | screenbegin = beg_cur; |
| - | if (cnt > (rows - 1) / 2) { |
| - | // we moved too many lines. put "dot" in middle of screen |
| - | for (cnt = 0; cnt < (rows - 1) / 2; cnt++) { |
| - | screenbegin = prev_line(screenbegin); |
| - | } |
| - | } |
| - | } else if (beg_cur > end_scr) { |
| - | // "d" is after bottom line on screen |
| - | // how many lines do we have to move |
| - | cnt = count_lines(end_scr, beg_cur); |
| - | if (cnt > (rows - 1) / 2) |
| - | goto sc1; // too many lines |
| - | for (ro = 0; ro < cnt - 1; ro++) { |
| - | // move screen begin the same amount |
| - | screenbegin = next_line(screenbegin); |
| - | // now, move the end of screen |
| - | end_scr = next_line(end_scr); |
| - | end_scr = end_line(end_scr); |
| + | if (c == 22) { // Is this an ctrl-V? |
| + | p = stupid_insert(p, '^'); // use ^ to indicate literal next |
| + | p--; // backup onto ^ |
| + | refresh(FALSE); // show the ^ |
| + | c = get_one_char(); |
| + | *p = c; |
| + | p++; |
| + | file_modified = TRUE; // has the file been modified |
| + | } else if (c == 27) { // Is this an ESC? |
| + | cmd_mode = 0; |
| + | cmdcnt = 0; |
| + | end_cmd_q(); // stop adding to q |
| + | strcpy((char *) status_buffer, " "); // clear the status buffer |
| + | if ((p[-1] != '\n') && (dot>text)) { |
| + | p--; |
| } | |
| - | } |
| - | // "d" is on screen- find out which row |
| - | tp = screenbegin; |
| - | for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row |
| - | if (tp == beg_cur) |
| - | break; |
| - | tp = next_line(tp); |
| - | } |
| + | } else if (c == erase_char) { // Is this a BS |
| + | // 123456789 |
| + | if ((p[-1] != '\n') && (dot>text)) { |
| + | p--; |
| + | p = text_hole_delete(p, p); // shrink buffer 1 char |
| + | #ifdef CONFIG_FEATURE_VI_DOT_CMD |
| + | // also rmove char from last_modifying_cmd |
| + | if (strlen((char *) last_modifying_cmd) > 0) { |
| + | Byte *q; |
| - | // find out what col "d" is on |
| - | co = 0; |
| - | do { // drive "co" to correct column |
| - | if (*tp == '\n' || *tp == '\0') |
| - | break; |
| - | if (*tp == '\t') { |
| - | // 7 - (co % 8 ) |
| - | co += ((tabstop - 1) - (co % tabstop)); |
| - | } else if (*tp < ' ') { |
| - | co++; // display as ^X, use 2 columns |
| + | q = last_modifying_cmd; |
| + | q[strlen((char *) q) - 1] = '\0'; // erase BS |
| + | q[strlen((char *) q) - 1] = '\0'; // erase prev char |
| + | } |
| + | #endif /* CONFIG_FEATURE_VI_DOT_CMD */ |
| } | |
| - | } while (tp++ < d && ++co); |
| + | } else { |
| + | // insert a char into text[] |
| + | Byte *sp; // "save p" |
| - | // "co" is the column where "dot" is. |
| - | // The screen has "columns" columns. |
| - | // The currently displayed columns are 0+offset -- columns+ofset |
| - | // |-------------------------------------------------------------| |
| - | // ^ ^ ^ |
| - | // offset | |------- columns ----------------| |
| - | // |
| - | // If "co" is already in this range then we do not have to adjust offset |
| - | // but, we do have to subtract the "offset" bias from "co". |
| - | // If "co" is outside this range then we have to change "offset". |
| - | // If the first char of a line is a tab the cursor will try to stay |
| - | // in column 7, but we have to set offset to 0. |
| + | if (c == 13) |
| + | c = '\n'; // translate \r to \n |
| + | sp = p; // remember addr of insert |
| + | p = stupid_insert(p, c); // insert the char |
| + | #ifdef CONFIG_FEATURE_VI_SETOPTS |
| + | if (showmatch && strchr(")]}", *sp) != NULL) { |
| + | showmatching(sp); |
| + | } |
| + | if (autoindent && c == '\n') { // auto indent the new line |
| + | Byte *q; |
| - | if (co < 0 + offset) { |
| - | offset = co; |
| - | } |
| - | if (co >= columns + offset) { |
| - | offset = co - columns + 1; |
| - | } |
| - | // if the first char of the line is a tab, and "dot" is sitting on it |
| - | // force offset to 0. |
| - | if (d == beg_cur && *d == '\t') { |
| - | offset = 0; |
| + | q = prev_line(p); // use prev line as templet |
| + | for (; isblnk(*q); q++) { |
| + | p = stupid_insert(p, *q); // insert the char |
| + | } |
| + | } |
| + | #endif /* CONFIG_FEATURE_VI_SETOPTS */ |
| } | |
| - | co -= offset; |
| - | |
| - | *row = ro; |
| - | *col = co; |
| - | } |
| - | |
| - | //----- Text Movement Routines --------------------------------- |
| - | static Byte *begin_line(Byte * p) // return pointer to first char cur line |
| - | { |
| - | while (p > text && p[-1] != '\n') |
| - | p--; // go to cur line B-o-l |
| return (p); | |
| } | |
| - | static Byte *end_line(Byte * p) // return pointer to NL of cur line line |
| + | static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p' |
| { | |
| - | while (p < end - 1 && *p != '\n') |
| - | p++; // go to cur line E-o-l |
| + | p = text_hole_make(p, 1); |
| + | if (p != 0) { |
| + | *p = c; |
| + | file_modified = TRUE; // has the file been modified |
| + | p++; |
| + | } |
| return (p); | |
| } | |
| - | static Byte *dollar_line(Byte * p) // return pointer to just before NL line |
| + | static Byte find_range(Byte ** start, Byte ** stop, Byte c) |
| { | |
| - | while (p < end - 1 && *p != '\n') |
| - | p++; // go to cur line E-o-l |
| - | // Try to stay off of the Newline |
| - | if (*p == '\n' && (p - begin_line(p)) > 0) |
| - | p--; |
| - | return (p); |
| - | } |
| + | Byte *save_dot, *p, *q; |
| + | int cnt; |
| - | static Byte *prev_line(Byte * p) // return pointer first char prev line |
| - | { |
| - | p = begin_line(p); // goto begining of cur line |
| - | if (p[-1] == '\n' && p > text) |
| - | p--; // step to prev line |
| - | p = begin_line(p); // goto begining of prev line |
| - | return (p); |
| - | } |
| + | save_dot = dot; |
| + | p = q = dot; |
| - | static Byte *next_line(Byte * p) // return pointer first char next line |
| - | { |
| - | p = end_line(p); |
| - | if (*p == '\n' && p < end - 1) |
| - | p++; // step to next line |
| - | return (p); |
| + | if (strchr("cdy><", c)) { |
| + | // these cmds operate on whole lines |
| + | p = q = begin_line(p); |
| + | for (cnt = 1; cnt < cmdcnt; cnt++) { |
| + | q = next_line(q); |
| + | } |
| + | q = end_line(q); |
| + | } else if (strchr("^%$0bBeEft", c)) { |
| + | // These cmds operate on char positions |
| + | do_cmd(c); // execute movement cmd |
| + | q = dot; |
| + | } else if (strchr("wW", c)) { |
| + | do_cmd(c); // execute movement cmd |
| + | if (dot > text) |
| + | dot--; // move back off of next word |
| + | if (dot > text && *dot == '\n') |
| + | dot--; // stay off NL |
| + | q = dot; |
| + | } else if (strchr("H-k{", c)) { |
| + | // these operate on multi-lines backwards |
| + | q = end_line(dot); // find NL |
| + | do_cmd(c); // execute movement cmd |
| + | dot_begin(); |
| + | p = dot; |
| + | } else if (strchr("L+j}\r\n", c)) { |
| + | // these operate on multi-lines forwards |
| + | p = begin_line(dot); |
| + | do_cmd(c); // execute movement cmd |
| + | dot_end(); // find NL |
| + | q = dot; |
| + | } else { |
| + | c = 27; // error- return an ESC char |
| + | //break; |
| + | } |
| + | *start = p; |
| + | *stop = q; |
| + | if (q < p) { |
| + | *start = q; |
| + | *stop = p; |
| + | } |
| + | dot = save_dot; |
| + | return (c); |
| } | |
| - | //----- Text Information Routines ------------------------------ |
| - | static Byte *end_screen(void) |
| + | static int st_test(Byte * p, int type, int dir, Byte * tested) |
| { | |
| - | Byte *q; |
| - | int cnt; |
| - | |
| - | // find new bottom line |
| - | q = screenbegin; |
| - | for (cnt = 0; cnt < rows - 2; cnt++) |
| - | q = next_line(q); |
| - | q = end_line(q); |
| - | return (q); |
| - | } |
| + | Byte c, c0, ci; |
| + | int test, inc; |
| - | static int count_lines(Byte * start, Byte * stop) // count line from start to stop |
| - | { |
| - | Byte *q; |
| - | int cnt; |
| + | inc = dir; |
| + | c = c0 = p[0]; |
| + | ci = p[inc]; |
| + | test = 0; |
| - | if (stop < start) { // start and stop are backwards- reverse them |
| - | q = start; |
| - | start = stop; |
| - | stop = q; |
| + | if (type == S_BEFORE_WS) { |
| + | c = ci; |
| + | test = ((!isspace(c)) || c == '\n'); |
| } | |
| - | cnt = 0; |
| - | stop = end_line(stop); // get to end of this line |
| - | for (q = start; q <= stop && q <= end - 1; q++) { |
| - | if (*q == '\n') |
| - | cnt++; |
| + | if (type == S_TO_WS) { |
| + | c = c0; |
| + | test = ((!isspace(c)) || c == '\n'); |
| } | |
| - | return (cnt); |
| + | if (type == S_OVER_WS) { |
| + | c = c0; |
| + | test = ((isspace(c))); |
| + | } |
| + | if (type == S_END_PUNCT) { |
| + | c = ci; |
| + | test = ((ispunct(c))); |
| + | } |
| + | if (type == S_END_ALNUM) { |
| + | c = ci; |
| + | test = ((isalnum(c)) || c == '_'); |
| + | } |
| + | *tested = c; |
| + | return (test); |
| } | |
| - | static Byte *find_line(int li) // find begining of line #li |
| + | static Byte *skip_thing(Byte * p, int linecnt, int dir, int type) |
| { | |
| - | Byte *q; |
| + | Byte c; |
| - | for (q = text; li > 1; li--) { |
| - | q = next_line(q); |
| - | } |
| - | return (q); |
| + | while (st_test(p, type, dir, &c)) { |
| + | // make sure we limit search to correct number of lines |
| + | if (c == '\n' && --linecnt < 1) |
| + | break; |
| + | if (dir >= 0 && p >= end - 1) |
| + | break; |
| + | if (dir < 0 && p <= text) |
| + | break; |
| + | p += dir; // move to next char |
| + | } |
| + | return (p); |
| } | |
| - | //----- Dot Movement Routines ---------------------------------- |
| - | static void dot_left(void) |
| + | // find matching char of pair () [] {} |
| + | static Byte *find_pair(Byte * p, Byte c) |
| { | |
| - | if (dot > text && dot[-1] != '\n') |
| - | dot--; |
| - | } |
| + | Byte match, *q; |
| + | int dir, level; |
| - | static void dot_right(void) |
| - | { |
| - | if (dot < end - 1 && *dot != '\n') |
| - | dot++; |
| + | match = ')'; |
| + | level = 1; |
| + | dir = 1; // assume forward |
| + | switch (c) { |
| + | case '(': |
| + | match = ')'; |
| + | break; |
| + | case '[': |
| + | match = ']'; |
| + | break; |
| + | case '{': |
| + | match = '}'; |
| + | break; |
| + | case ')': |
| + | match = '('; |
| + | dir = -1; |
| + | break; |
| + | case ']': |
| + | match = '['; |
| + | dir = -1; |
| + | break; |
| + | case '}': |
| + | match = '{'; |
| + | dir = -1; |
| + | break; |
| + | } |
| + | for (q = p + dir; text <= q && q < end; q += dir) { |
| + | // look for match, count levels of pairs (( )) |
| + | if (*q == c) |
| + | level++; // increase pair levels |
| + | if (*q == match) |
| + | level--; // reduce pair level |
| + | if (level == 0) |
| + | break; // found matching pair |
| + | } |
| + | if (level != 0) |
| + | q = NULL; // indicate no match |
| + | return (q); |
| } | |
| - | static void dot_begin(void) |
| + | #ifdef CONFIG_FEATURE_VI_SETOPTS |
| + | // show the matching char of a pair, () [] {} |
| + | static void showmatching(Byte * p) |
| { | |
| - | dot = begin_line(dot); // return pointer to first char cur line |
| - | } |
| + | Byte *q, *save_dot; |
| - | static void dot_end(void) |
| - | { |
| - | dot = end_line(dot); // return pointer to last char cur line |
| + | // we found half of a pair |
| + | q = find_pair(p, *p); // get loc of matching char |
| + | if (q == NULL) { |
| + | indicate_error('3'); // no matching char |
| + | } else { |
| + | // "q" now points to matching pair |
| + | save_dot = dot; // remember where we are |
| + | dot = q; // go to new loc |
| + | refresh(FALSE); // let the user see it |
| + | (void) mysleep(40); // give user some time |
| + | dot = save_dot; // go back to old loc |
| + | refresh(FALSE); |
| + | } |
| } | |
| + | #endif /* CONFIG_FEATURE_VI_SETOPTS */ |
| - | static Byte *move_to_col(Byte * p, int l) |
| + | // open a hole in text[] |
| + | static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole |
| { | |
| - | int co; |
| + | Byte *src, *dest; |
| + | int cnt; |
| - | p = begin_line(p); |
| - | co = 0; |
| - | do { |
| - | if (*p == '\n' || *p == '\0') |
| - | break; |
| - | if (*p == '\t') { |
| - | // 7 - (co % 8 ) |
| - | co += ((tabstop - 1) - (co % tabstop)); |
| - | } else if (*p < ' ') { |
| - | co++; // display as ^X, use 2 columns |
| - | } |
| - | } while (++co <= l && p++ < end); |
| + | if (size <= 0) |
| + | goto thm0; |
| + | src = p; |
| + | dest = p + size; |
| + | cnt = end - src; // the rest of buffer |
| + | if (memmove(dest, src, cnt) != dest) { |
| + | psbs("can't create room for new characters"); |
| + | } |
| + | memset(p, ' ', size); // clear new hole |
| + | end = end + size; // adjust the new END |
| + | file_modified = TRUE; // has the file been modified |
| + | thm0: |
| return (p); | |
| } | |
| - | static void dot_next(void) |
| + | // close a hole in text[] |
| + | static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive |
| { | |
| - | dot = next_line(dot); |
| - | } |
| + | Byte *src, *dest; |
| + | int cnt, hole_size; |
| - | static void dot_prev(void) |
| - | { |
| - | dot = prev_line(dot); |
| + | // move forwards, from beginning |
| + | // assume p <= q |
| + | src = q + 1; |
| + | dest = p; |
| + | if (q < p) { // they are backward- swap them |
| + | src = p + 1; |
| + | dest = q; |
| + | } |
| + | hole_size = q - p + 1; |
| + | cnt = end - src; |
| + | if (src < text || src > end) |
| + | goto thd0; |
| + | if (dest < text || dest >= end) |
| + | goto thd0; |
| + | if (src >= end) |
| + | goto thd_atend; // just delete the end of the buffer |
| + | if (memmove(dest, src, cnt) != dest) { |
| + | psbs("can't delete the character"); |
| + | } |
| + | thd_atend: |
| + | end = end - hole_size; // adjust the new END |
| + | if (dest >= end) |
| + | dest = end - 1; // make sure dest in below end-1 |
| + | if (end <= text) |
| + | dest = end = text; // keep pointers valid |
| + | file_modified = TRUE; // has the file been modified |
| + | thd0: |
| + | return (dest); |
| } | |
| - | static void dot_scroll(int cnt, int dir) |
| + | // copy text into register, then delete text. |
| + | // if dist <= 0, do not include, or go past, a NewLine |
| + | // |
| + | static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf) |
| { | |
| - | Byte *q; |
| + | Byte *p; |
| - | for (; cnt > 0; cnt--) { |
| - | if (dir < 0) { |
| - | // scroll Backwards |
| - | // ctrl-Y scroll up one line |
| - | screenbegin = prev_line(screenbegin); |
| - | } else { |
| - | // scroll Forwards |
| - | // ctrl-E scroll down one line |
| - | screenbegin = next_line(screenbegin); |
| + | // make sure start <= stop |
| + | if (start > stop) { |
| + | // they are backwards, reverse them |
| + | p = start; |
| + | start = stop; |
| + | stop = p; |
| + | } |
| + | if (dist <= 0) { |
| + | // we can not cross NL boundaries |
| + | p = start; |
| + | if (*p == '\n') |
| + | return (p); |
| + | // dont go past a NewLine |
| + | for (; p + 1 <= stop; p++) { |
| + | if (p[1] == '\n') { |
| + | stop = p; // "stop" just before NewLine |
| + | break; |
| + | } |
| } | |
| } | |
| - | // make sure "dot" stays on the screen so we dont scroll off |
| - | if (dot < screenbegin) |
| - | dot = screenbegin; |
| - | q = end_screen(); // find new bottom line |
| - | if (dot > q) |
| - | dot = begin_line(q); // is dot is below bottom line? |
| - | dot_skip_over_ws(); |
| - | } |
| - | |
| - | static void dot_skip_over_ws(void) |
| - | { |
| - | // skip WS |
| - | while (isspace(*dot) && *dot != '\n' && dot < end - 1) |
| - | dot++; |
| + | p = start; |
| + | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| + | text_yank(start, stop, YDreg); |
| + | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| + | if (yf == YANKDEL) { |
| + | p = text_hole_delete(start, stop); |
| + | } // delete lines |
| + | return (p); |
| } | |
| - | static void dot_delete(void) // delete the char at 'dot' |
| + | static void show_help(void) |
| { | |
| - | (void) text_hole_delete(dot, dot); |
| + | puts("These features are available:" |
| + | #ifdef CONFIG_FEATURE_VI_SEARCH |
| + | "\n\tPattern searches with / and ?" |
| + | #endif /* CONFIG_FEATURE_VI_SEARCH */ |
| + | #ifdef CONFIG_FEATURE_VI_DOT_CMD |
| + | "\n\tLast command repeat with \'.\'" |
| + | #endif /* CONFIG_FEATURE_VI_DOT_CMD */ |
| + | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| + | "\n\tLine marking with 'x" |
| + | "\n\tNamed buffers with \"x" |
| + | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| + | #ifdef CONFIG_FEATURE_VI_READONLY |
| + | "\n\tReadonly if vi is called as \"view\"" |
| + | "\n\tReadonly with -R command line arg" |
| + | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| + | #ifdef CONFIG_FEATURE_VI_SET |
| + | "\n\tSome colon mode commands with \':\'" |
| + | #endif /* CONFIG_FEATURE_VI_SET */ |
| + | #ifdef CONFIG_FEATURE_VI_SETOPTS |
| + | "\n\tSettable options with \":set\"" |
| + | #endif /* CONFIG_FEATURE_VI_SETOPTS */ |
| + | #ifdef CONFIG_FEATURE_VI_USE_SIGNALS |
| + | "\n\tSignal catching- ^C" |
| + | "\n\tJob suspend and resume with ^Z" |
| + | #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ |
| + | #ifdef CONFIG_FEATURE_VI_WIN_RESIZE |
| + | "\n\tAdapt to window re-sizes" |
| + | #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */ |
| + | ); |
| } | |
| - | static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end" |
| + | extern inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable |
| { | |
| - | if (p >= end && end > text) { |
| - | p = end - 1; |
| - | indicate_error('1'); |
| - | } |
| - | if (p < text) { |
| - | p = text; |
| - | indicate_error('2'); |
| - | } |
| - | return (p); |
| - | } |
| + | Byte c, b[2]; |
| - | //----- Helper Utility Routines -------------------------------- |
| + | b[1] = '\0'; |
| + | strcpy((char *) buf, ""); // init buf |
| + | if (strlen((char *) s) <= 0) |
| + | s = (Byte *) "(NULL)"; |
| + | for (; *s > '\0'; s++) { |
| + | c = *s; |
| + | if (*s > '~') { |
| + | strcat((char *) buf, SOs); |
| + | c = *s - 128; |
| + | } |
| + | if (*s < ' ') { |
| + | strcat((char *) buf, "^"); |
| + | c += '@'; |
| + | } |
| + | b[0] = c; |
| + | strcat((char *) buf, (char *) b); |
| + | if (*s > '~') |
| + | strcat((char *) buf, SOn); |
| + | if (*s == '\n') { |
| + | strcat((char *) buf, "$"); |
| + | } |
| + | } |
| + | } |
| - | //---------------------------------------------------------------- |
| - | //----- Char Routines -------------------------------------------- |
| - | /* Chars that are part of a word- |
| - | * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz |
| - | * Chars that are Not part of a word (stoppers) |
| - | * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~ |
| - | * Chars that are WhiteSpace |
| - | * TAB NEWLINE VT FF RETURN SPACE |
| - | * DO NOT COUNT NEWLINE AS WHITESPACE |
| - | */ |
| + | #ifdef CONFIG_FEATURE_VI_DOT_CMD |
| + | static void start_new_cmd_q(Byte c) |
| + | { |
| + | // release old cmd |
| + | if (last_modifying_cmd != 0) |
| + | free(last_modifying_cmd); |
| + | // get buffer for new cmd |
| + | last_modifying_cmd = (Byte *) xmalloc(BUFSIZ); |
| + | memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue |
| + | // if there is a current cmd count put it in the buffer first |
| + | if (cmdcnt > 0) |
| + | sprintf((char *) last_modifying_cmd, "%d", cmdcnt); |
| + | // save char c onto queue |
| + | last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c; |
| + | adding2q = 1; |
| + | return; |
| + | } |
| - | static Byte *new_screen(int ro, int co) |
| + | static void end_cmd_q(void) |
| { | |
| - | int li; |
| + | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| + | YDreg = 26; // go back to default Yank/Delete reg |
| + | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| + | adding2q = 0; |
| + | return; |
| + | } |
| + | #endif /* CONFIG_FEATURE_VI_DOT_CMD */ |
| - | if (screen != 0) |
| - | free(screen); |
| - | screensize = ro * co + 8; |
| - | screen = (Byte *) xmalloc(screensize); |
| - | // initialize the new screen. assume this will be a empty file. |
| - | screen_erase(); |
| - | // non-existant text[] lines start with a tilde (~). |
| - | for (li = 1; li < ro - 1; li++) { |
| - | screen[(li * co) + 0] = '~'; |
| + | #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME) |
| + | static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p' |
| + | { |
| + | int cnt, i; |
| + | |
| + | i = strlen((char *) s); |
| + | p = text_hole_make(p, i); |
| + | strncpy((char *) p, (char *) s, i); |
| + | for (cnt = 0; *s != '\0'; s++) { |
| + | if (*s == '\n') |
| + | cnt++; |
| } | |
| - | return (screen); |
| + | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| + | psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg()); |
| + | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| + | return (p); |
| } | |
| + | #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */ |
| - | static Byte *new_text(int size) |
| + | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| + | static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register |
| { | |
| - | if (size < 10240) |
| - | size = 10240; // have a minimum size for new files |
| - | if (text != 0) { |
| - | //text -= 4; |
| - | free(text); |
| + | Byte *t; |
| + | int cnt; |
| + | |
| + | if (q < p) { // they are backwards- reverse them |
| + | t = q; |
| + | q = p; |
| + | p = t; |
| } | |
| - | text = (Byte *) xmalloc(size + 8); |
| - | memset(text, '\0', size); // clear new text[] |
| - | //text += 4; // leave some room for "oops" |
| - | textend = text + size - 1; |
| - | //textend -= 4; // leave some root for "oops" |
| - | return (text); |
| + | cnt = q - p + 1; |
| + | t = reg[dest]; |
| + | if (t != 0) { // if already a yank register |
| + | free(t); // free it |
| + | } |
| + | t = (Byte *) xmalloc(cnt + 1); // get a new register |
| + | memset(t, '\0', cnt + 1); // clear new text[] |
| + | strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer |
| + | reg[dest] = t; |
| + | return (p); |
| } | |
| - | #ifdef CONFIG_FEATURE_VI_SEARCH |
| - | static int mycmp(Byte * s1, Byte * s2, int len) |
| + | static Byte what_reg(void) |
| { | |
| + | Byte c; |
| int i; | |
| - | i = strncmp((char *) s1, (char *) s2, len); |
| - | #ifdef CONFIG_FEATURE_VI_SETOPTS |
| - | if (ignorecase) { |
| - | i = strncasecmp((char *) s1, (char *) s2, len); |
| - | } |
| - | #endif /* CONFIG_FEATURE_VI_SETOPTS */ |
| - | return (i); |
| + | i = 0; |
| + | c = 'D'; // default to D-reg |
| + | if (0 <= YDreg && YDreg <= 25) |
| + | c = 'a' + (Byte) YDreg; |
| + | if (YDreg == 26) |
| + | c = 'D'; |
| + | if (YDreg == 27) |
| + | c = 'U'; |
| + | return (c); |
| } | |
| - | static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p |
| + | static void check_context(Byte cmd) |
| { | |
| - | #ifndef REGEX_SEARCH |
| - | Byte *start, *stop; |
| - | int len; |
| + | // A context is defined to be "modifying text" |
| + | // Any modifying command establishes a new context. |
| - | len = strlen((char *) pat); |
| - | if (dir == FORWARD) { |
| - | stop = end - 1; // assume range is p - end-1 |
| - | if (range == LIMITED) |
| - | stop = next_line(p); // range is to next line |
| - | for (start = p; start < stop; start++) { |
| - | if (mycmp(start, pat, len) == 0) { |
| - | return (start); |
| - | } |
| - | } |
| - | } else if (dir == BACK) { |
| - | stop = text; // assume range is text - p |
| - | if (range == LIMITED) |
| - | stop = prev_line(p); // range is to prev line |
| - | for (start = p - len; start >= stop; start--) { |
| - | if (mycmp(start, pat, len) == 0) { |
| - | return (start); |
| - | } |
| + | if (dot < context_start || dot > context_end) { |
| + | if (strchr((char *) modifying_cmds, cmd) != NULL) { |
| + | // we are trying to modify text[]- make this the current context |
| + | mark[27] = mark[26]; // move cur to prev |
| + | mark[26] = dot; // move local to cur |
| + | context_start = prev_line(prev_line(dot)); |
| + | context_end = next_line(next_line(dot)); |
| + | //loiter= start_loiter= now; |
| } | |
| } | |
| - | // pattern not found |
| - | return (NULL); |
| - | #else /*REGEX_SEARCH */ |
| - | char *q; |
| - | struct re_pattern_buffer preg; |
| - | int i; |
| - | int size, range; |
| - | |
| - | re_syntax_options = RE_SYNTAX_POSIX_EXTENDED; |
| - | preg.translate = 0; |
| - | preg.fastmap = 0; |
| - | preg.buffer = 0; |
| - | preg.allocated = 0; |
| - | |
| - | // assume a LIMITED forward search |
| - | q = next_line(p); |
| - | q = end_line(q); |
| - | q = end - 1; |
| - | if (dir == BACK) { |
| - | q = prev_line(p); |
| - | q = text; |
| - | } |
| - | // count the number of chars to search over, forward or backward |
| - | size = q - p; |
| - | if (size < 0) |
| - | size = p - q; |
| - | // RANGE could be negative if we are searching backwards |
| - | range = q - p; |
| + | return; |
| + | } |
| - | q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg); |
| - | if (q != 0) { |
| - | // The pattern was not compiled |
| - | psbs("bad search pattern: \"%s\": %s", pat, q); |
| - | i = 0; // return p if pattern not compiled |
| - | goto cs1; |
| - | } |
| + | extern inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context |
| + | { |
| + | Byte *tmp; |
| - | q = p; |
| - | if (range < 0) { |
| - | q = p - size; |
| - | if (q < text) |
| - | q = text; |
| - | } |
| - | // search for the compiled pattern, preg, in p[] |
| - | // range < 0- search backward |
| - | // range > 0- search forward |
| - | // 0 < start < size |
| - | // re_search() < 0 not found or error |
| - | // re_search() > 0 index of found pattern |
| - | // struct pattern char int int int struct reg |
| - | // re_search (*pattern_buffer, *string, size, start, range, *regs) |
| - | i = re_search(&preg, q, size, 0, range, 0); |
| - | if (i == -1) { |
| - | p = 0; |
| - | i = 0; // return NULL if pattern not found |
| - | } |
| - | cs1: |
| - | if (dir == FORWARD) { |
| - | p = p + i; |
| - | } else { |
| - | p = p - i; |
| + | // the current context is in mark[26] |
| + | // the previous context is in mark[27] |
| + | // only swap context if other context is valid |
| + | if (text <= mark[27] && mark[27] <= end - 1) { |
| + | tmp = mark[27]; |
| + | mark[27] = mark[26]; |
| + | mark[26] = tmp; |
| + | p = mark[26]; // where we are going- previous context |
| + | context_start = prev_line(prev_line(prev_line(p))); |
| + | context_end = next_line(next_line(next_line(p))); |
| } | |
| return (p); | |
| - | #endif /*REGEX_SEARCH */ |
| } | |
| - | #endif /* CONFIG_FEATURE_VI_SEARCH */ |
| + | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| - | static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p' |
| + | static int isblnk(Byte c) // is the char a blank or tab |
| { | |
| - | if (c == 22) { // Is this an ctrl-V? |
| - | p = stupid_insert(p, '^'); // use ^ to indicate literal next |
| - | p--; // backup onto ^ |
| - | refresh(FALSE); // show the ^ |
| - | c = get_one_char(); |
| - | *p = c; |
| - | p++; |
| - | file_modified = TRUE; // has the file been modified |
| - | } else if (c == 27) { // Is this an ESC? |
| - | cmd_mode = 0; |
| - | cmdcnt = 0; |
| - | end_cmd_q(); // stop adding to q |
| - | strcpy((char *) status_buffer, " "); // clear the status buffer |
| - | if ((p[-1] != '\n') && (dot>text)) { |
| - | p--; |
| - | } |
| - | } else if (c == erase_char) { // Is this a BS |
| - | // 123456789 |
| - | if ((p[-1] != '\n') && (dot>text)) { |
| - | p--; |
| - | p = text_hole_delete(p, p); // shrink buffer 1 char |
| - | #ifdef CONFIG_FEATURE_VI_DOT_CMD |
| - | // also rmove char from last_modifying_cmd |
| - | if (strlen((char *) last_modifying_cmd) > 0) { |
| - | Byte *q; |
| - | |
| - | q = last_modifying_cmd; |
| - | q[strlen((char *) q) - 1] = '\0'; // erase BS |
| - | q[strlen((char *) q) - 1] = '\0'; // erase prev char |
| - | } |
| - | #endif /* CONFIG_FEATURE_VI_DOT_CMD */ |
| - | } |
| - | } else { |
| - | // insert a char into text[] |
| - | Byte *sp; // "save p" |
| - | |
| - | if (c == 13) |
| - | c = '\n'; // translate \r to \n |
| - | sp = p; // remember addr of insert |
| - | p = stupid_insert(p, c); // insert the char |
| - | #ifdef CONFIG_FEATURE_VI_SETOPTS |
| - | if (showmatch && strchr(")]}", *sp) != NULL) { |
| - | showmatching(sp); |
| - | } |
| - | if (autoindent && c == '\n') { // auto indent the new line |
| - | Byte *q; |
| - | |
| - | q = prev_line(p); // use prev line as templet |
| - | for (; isblnk(*q); q++) { |
| - | p = stupid_insert(p, *q); // insert the char |
| - | } |
| - | } |
| - | #endif /* CONFIG_FEATURE_VI_SETOPTS */ |
| - | } |
| - | return (p); |
| + | return (c == ' ' || c == '\t'); |
| } | |
| - | static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p' |
| + | //----- Set terminal attributes -------------------------------- |
| + | static void rawmode(void) |
| { | |
| - | p = text_hole_make(p, 1); |
| - | if (p != 0) { |
| - | *p = c; |
| - | file_modified = TRUE; // has the file been modified |
| - | p++; |
| - | } |
| - | return (p); |
| + | tcgetattr(0, &term_orig); |
| + | term_vi = term_orig; |
| + | term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's |
| + | term_vi.c_iflag &= (~IXON & ~ICRNL); |
| + | term_vi.c_oflag &= (~ONLCR); |
| + | #ifndef linux |
| + | term_vi.c_cc[VMIN] = 1; |
| + | term_vi.c_cc[VTIME] = 0; |
| + | #endif |
| + | erase_char = term_vi.c_cc[VERASE]; |
| + | tcsetattr(0, TCSANOW, &term_vi); |
| } | |
| - | static Byte find_range(Byte ** start, Byte ** stop, Byte c) |
| + | static void cookmode(void) |
| { | |
| - | Byte *save_dot, *p, *q; |
| - | int cnt; |
| - | |
| - | save_dot = dot; |
| - | p = q = dot; |
| - | |
| - | if (strchr("cdy><", c)) { |
| - | // these cmds operate on whole lines |
| - | p = q = begin_line(p); |
| - | for (cnt = 1; cnt < cmdcnt; cnt++) { |
| - | q = next_line(q); |
| - | } |
| - | q = end_line(q); |
| - | } else if (strchr("^%$0bBeEft", c)) { |
| - | // These cmds operate on char positions |
| - | do_cmd(c); // execute movement cmd |
| - | q = dot; |
| - | } else if (strchr("wW", c)) { |
| - | do_cmd(c); // execute movement cmd |
| - | if (dot > text) |
| - | dot--; // move back off of next word |
| - | if (dot > text && *dot == '\n') |
| - | dot--; // stay off NL |
| - | q = dot; |
| - | } else if (strchr("H-k{", c)) { |
| - | // these operate on multi-lines backwards |
| - | q = end_line(dot); // find NL |
| - | do_cmd(c); // execute movement cmd |
| - | dot_begin(); |
| - | p = dot; |
| - | } else if (strchr("L+j}\r\n", c)) { |
| - | // these operate on multi-lines forwards |
| - | p = begin_line(dot); |
| - | do_cmd(c); // execute movement cmd |
| - | dot_end(); // find NL |
| - | q = dot; |
| - | } else { |
| - | c = 27; // error- return an ESC char |
| - | //break; |
| - | } |
| - | *start = p; |
| - | *stop = q; |
| - | if (q < p) { |
| - | *start = q; |
| - | *stop = p; |
| - | } |
| - | dot = save_dot; |
| - | return (c); |
| + | tcsetattr(0, TCSANOW, &term_orig); |
| } | |
| - | static int st_test(Byte * p, int type, int dir, Byte * tested) |
| + | #ifdef CONFIG_FEATURE_VI_WIN_RESIZE |
| + | //----- See what the window size currently is -------------------- |
| + | static void window_size_get(int sig) |
| { | |
| - | Byte c, c0, ci; |
| - | int test, inc; |
| - | |
| - | inc = dir; |
| - | c = c0 = p[0]; |
| - | ci = p[inc]; |
| - | test = 0; |
| + | int i; |
| - | if (type == S_BEFORE_WS) { |
| - | c = ci; |
| - | test = ((!isspace(c)) || c == '\n'); |
| - | } |
| - | if (type == S_TO_WS) { |
| - | c = c0; |
| - | test = ((!isspace(c)) || c == '\n'); |
| - | } |
| - | if (type == S_OVER_WS) { |
| - | c = c0; |
| - | test = ((isspace(c))); |
| + | i = ioctl(0, TIOCGWINSZ, &winsize); |
| + | if (i != 0) { |
| + | // force 24x80 |
| + | winsize.ws_row = 24; |
| + | winsize.ws_col = 80; |
| } | |
| - | if (type == S_END_PUNCT) { |
| - | c = ci; |
| - | test = ((ispunct(c))); |
| + | if (winsize.ws_row <= 1) { |
| + | winsize.ws_row = 24; |
| } | |
| - | if (type == S_END_ALNUM) { |
| - | c = ci; |
| - | test = ((isalnum(c)) || c == '_'); |
| + | if (winsize.ws_col <= 1) { |
| + | winsize.ws_col = 80; |
| } | |
| - | *tested = c; |
| - | return (test); |
| + | rows = (int) winsize.ws_row; |
| + | columns = (int) winsize.ws_col; |
| } | |
| + | #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */ |
| - | static Byte *skip_thing(Byte * p, int linecnt, int dir, int type) |
| + | //----- Come here when we get a window resize signal --------- |
| + | #ifdef CONFIG_FEATURE_VI_USE_SIGNALS |
| + | static void winch_sig(int sig) |
| { | |
| - | Byte c; |
| - | |
| - | while (st_test(p, type, dir, &c)) { |
| - | // make sure we limit search to correct number of lines |
| - | if (c == '\n' && --linecnt < 1) |
| - | break; |
| - | if (dir >= 0 && p >= end - 1) |
| - | break; |
| - | if (dir < 0 && p <= text) |
| - | break; |
| - | p += dir; // move to next char |
| - | } |
| - | return (p); |
| + | signal(SIGWINCH, winch_sig); |
| + | #ifdef CONFIG_FEATURE_VI_WIN_RESIZE |
| + | window_size_get(0); |
| + | #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */ |
| + | new_screen(rows, columns); // get memory for virtual screen |
| + | redraw(TRUE); // re-draw the screen |
| } | |
| - | // find matching char of pair () [] {} |
| - | static Byte *find_pair(Byte * p, Byte c) |
| + | //----- Come here when we get a continue signal ------------------- |
| + | static void cont_sig(int sig) |
| { | |
| - | Byte match, *q; |
| - | int dir, level; |
| + | rawmode(); // terminal to "raw" |
| + | *status_buffer = '\0'; // clear the status buffer |
| + | redraw(TRUE); // re-draw the screen |
| - | match = ')'; |
| - | level = 1; |
| - | dir = 1; // assume forward |
| - | switch (c) { |
| - | case '(': |
| - | match = ')'; |
| - | break; |
| - | case '[': |
| - | match = ']'; |
| - | break; |
| - | case '{': |
| - | match = '}'; |
| - | break; |
| - | case ')': |
| - | match = '('; |
| - | dir = -1; |
| - | break; |
| - | case ']': |
| - | match = '['; |
| - | dir = -1; |
| - | break; |
| - | case '}': |
| - | match = '{'; |
| - | dir = -1; |
| - | break; |
| - | } |
| - | for (q = p + dir; text <= q && q < end; q += dir) { |
| - | // look for match, count levels of pairs (( )) |
| - | if (*q == c) |
| - | level++; // increase pair levels |
| - | if (*q == match) |
| - | level--; // reduce pair level |
| - | if (level == 0) |
| - | break; // found matching pair |
| - | } |
| - | if (level != 0) |
| - | q = NULL; // indicate no match |
| - | return (q); |
| + | signal(SIGTSTP, suspend_sig); |
| + | signal(SIGCONT, SIG_DFL); |
| + | kill(getpid(), SIGCONT); |
| } | |
| - | #ifdef CONFIG_FEATURE_VI_SETOPTS |
| - | // show the matching char of a pair, () [] {} |
| - | static void showmatching(Byte * p) |
| + | //----- Come here when we get a Suspend signal ------------------- |
| + | static void suspend_sig(int sig) |
| { | |
| - | Byte *q, *save_dot; |
| + | place_cursor(rows - 1, 0, FALSE); // go to bottom of screen |
| + | clear_to_eol(); // Erase to end of line |
| + | cookmode(); // terminal to "cooked" |
| - | // we found half of a pair |
| - | q = find_pair(p, *p); // get loc of matching char |
| - | if (q == NULL) { |
| - | indicate_error('3'); // no matching char |
| - | } else { |
| - | // "q" now points to matching pair |
| - | save_dot = dot; // remember where we are |
| - | dot = q; // go to new loc |
| - | refresh(FALSE); // let the user see it |
| - | (void) mysleep(40); // give user some time |
| - | dot = save_dot; // go back to old loc |
| - | refresh(FALSE); |
| - | } |
| + | signal(SIGCONT, cont_sig); |
| + | signal(SIGTSTP, SIG_DFL); |
| + | kill(getpid(), SIGTSTP); |
| } | |
| - | #endif /* CONFIG_FEATURE_VI_SETOPTS */ |
| - | // open a hole in text[] |
| - | static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole |
| + | //----- Come here when we get a signal --------------------------- |
| + | static void catch_sig(int sig) |
| { | |
| - | Byte *src, *dest; |
| - | int cnt; |
| - | |
| - | if (size <= 0) |
| - | goto thm0; |
| - | src = p; |
| - | dest = p + size; |
| - | cnt = end - src; // the rest of buffer |
| - | if (memmove(dest, src, cnt) != dest) { |
| - | psbs("can't create room for new characters"); |
| - | } |
| - | memset(p, ' ', size); // clear new hole |
| - | end = end + size; // adjust the new END |
| - | file_modified = TRUE; // has the file been modified |
| - | thm0: |
| - | return (p); |
| + | signal(SIGHUP, catch_sig); |
| + | signal(SIGINT, catch_sig); |
| + | signal(SIGTERM, catch_sig); |
| + | longjmp(restart, sig); |
| } | |
| - | // close a hole in text[] |
| - | static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive |
| + | static void alarm_sig(int sig) |
| { | |
| - | Byte *src, *dest; |
| - | int cnt, hole_size; |
| - | |
| - | // move forwards, from beginning |
| - | // assume p <= q |
| - | src = q + 1; |
| - | dest = p; |
| - | if (q < p) { // they are backward- swap them |
| - | src = p + 1; |
| - | dest = q; |
| - | } |
| - | hole_size = q - p + 1; |
| - | cnt = end - src; |
| - | if (src < text || src > end) |
| - | goto thd0; |
| - | if (dest < text || dest >= end) |
| - | goto thd0; |
| - | if (src >= end) |
| - | goto thd_atend; // just delete the end of the buffer |
| - | if (memmove(dest, src, cnt) != dest) { |
| - | psbs("can't delete the character"); |
| - | } |
| - | thd_atend: |
| - | end = end - hole_size; // adjust the new END |
| - | if (dest >= end) |
| - | dest = end - 1; // make sure dest in below end-1 |
| - | if (end <= text) |
| - | dest = end = text; // keep pointers valid |
| - | file_modified = TRUE; // has the file been modified |
| - | thd0: |
| - | return (dest); |
| + | signal(SIGALRM, catch_sig); |
| + | longjmp(restart, sig); |
| } | |
| - | // copy text into register, then delete text. |
| - | // if dist <= 0, do not include, or go past, a NewLine |
| - | // |
| - | static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf) |
| + | //----- Come here when we get a core dump signal ----------------- |
| + | static void core_sig(int sig) |
| { | |
| - | Byte *p; |
| + | signal(SIGQUIT, core_sig); |
| + | signal(SIGILL, core_sig); |
| + | signal(SIGTRAP, core_sig); |
| + | signal(SIGIOT, core_sig); |
| + | signal(SIGABRT, core_sig); |
| + | signal(SIGFPE, core_sig); |
| + | signal(SIGBUS, core_sig); |
| + | signal(SIGSEGV, core_sig); |
| + | #ifdef SIGSYS |
| + | signal(SIGSYS, core_sig); |
| + | #endif |
| - | // make sure start <= stop |
| - | if (start > stop) { |
| - | // they are backwards, reverse them |
| - | p = start; |
| - | start = stop; |
| - | stop = p; |
| - | } |
| - | if (dist <= 0) { |
| - | // we can not cross NL boundaries |
| - | p = start; |
| - | if (*p == '\n') |
| - | return (p); |
| - | // dont go past a NewLine |
| - | for (; p + 1 <= stop; p++) { |
| - | if (p[1] == '\n') { |
| - | stop = p; // "stop" just before NewLine |
| - | break; |
| - | } |
| - | } |
| - | } |
| - | p = start; |
| - | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| - | text_yank(start, stop, YDreg); |
| - | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| - | if (yf == YANKDEL) { |
| - | p = text_hole_delete(start, stop); |
| - | } // delete lines |
| - | return (p); |
| - | } |
| + | dot = bound_dot(dot); // make sure "dot" is valid |
| - | static void show_help(void) |
| - | { |
| - | puts("These features are available:" |
| - | #ifdef CONFIG_FEATURE_VI_SEARCH |
| - | "\n\tPattern searches with / and ?" |
| - | #endif /* CONFIG_FEATURE_VI_SEARCH */ |
| - | #ifdef CONFIG_FEATURE_VI_DOT_CMD |
| - | "\n\tLast command repeat with \'.\'" |
| - | #endif /* CONFIG_FEATURE_VI_DOT_CMD */ |
| - | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| - | "\n\tLine marking with 'x" |
| - | "\n\tNamed buffers with \"x" |
| - | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| - | #ifdef CONFIG_FEATURE_VI_READONLY |
| - | "\n\tReadonly if vi is called as \"view\"" |
| - | "\n\tReadonly with -R command line arg" |
| - | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| - | #ifdef CONFIG_FEATURE_VI_SET |
| - | "\n\tSome colon mode commands with \':\'" |
| - | #endif /* CONFIG_FEATURE_VI_SET */ |
| - | #ifdef CONFIG_FEATURE_VI_SETOPTS |
| - | "\n\tSettable options with \":set\"" |
| - | #endif /* CONFIG_FEATURE_VI_SETOPTS */ |
| - | #ifdef CONFIG_FEATURE_VI_USE_SIGNALS |
| - | "\n\tSignal catching- ^C" |
| - | "\n\tJob suspend and resume with ^Z" |
| - | #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ |
| - | #ifdef CONFIG_FEATURE_VI_WIN_RESIZE |
| - | "\n\tAdapt to window re-sizes" |
| - | #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */ |
| - | ); |
| + | longjmp(restart, sig); |
| } | |
| + | #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ |
| - | static void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable |
| + | static int mysleep(int hund) // sleep for 'h' 1/100 seconds |
| { | |
| - | Byte c, b[2]; |
| - | |
| - | b[1] = '\0'; |
| - | strcpy((char *) buf, ""); // init buf |
| - | if (strlen((char *) s) <= 0) |
| - | s = (Byte *) "(NULL)"; |
| - | for (; *s > '\0'; s++) { |
| - | c = *s; |
| - | if (*s > '~') { |
| - | strcat((char *) buf, SOs); |
| - | c = *s - 128; |
| - | } |
| - | if (*s < ' ') { |
| - | strcat((char *) buf, "^"); |
| - | c += '@'; |
| - | } |
| - | b[0] = c; |
| - | strcat((char *) buf, (char *) b); |
| - | if (*s > '~') |
| - | strcat((char *) buf, SOn); |
| - | if (*s == '\n') { |
| - | strcat((char *) buf, "$"); |
| - | } |
| - | } |
| + | // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000 |
| + | FD_ZERO(&rfds); |
| + | FD_SET(0, &rfds); |
| + | tv.tv_sec = 0; |
| + | tv.tv_usec = hund * 10000; |
| + | select(1, &rfds, NULL, NULL, &tv); |
| + | return (FD_ISSET(0, &rfds)); |
| } | |
| - | #ifdef CONFIG_FEATURE_VI_DOT_CMD |
| - | static void start_new_cmd_q(Byte c) |
| + | //----- IO Routines -------------------------------------------- |
| + | static Byte readit(void) // read (maybe cursor) key from stdin |
| { | |
| - | // release old cmd |
| - | if (last_modifying_cmd != 0) |
| - | free(last_modifying_cmd); |
| - | // get buffer for new cmd |
| - | last_modifying_cmd = (Byte *) xmalloc(BUFSIZ); |
| - | memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue |
| - | // if there is a current cmd count put it in the buffer first |
| - | if (cmdcnt > 0) |
| - | sprintf((char *) last_modifying_cmd, "%d", cmdcnt); |
| - | // save char c onto queue |
| - | last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c; |
| - | adding2q = 1; |
| - | return; |
| - | } |
| + | Byte c; |
| + | int i, bufsiz, cnt, cmdindex; |
| + | struct esc_cmds { |
| + | Byte *seq; |
| + | Byte val; |
| + | }; |
| - | static void end_cmd_q(void) |
| - | { |
| - | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| - | YDreg = 26; // go back to default Yank/Delete reg |
| - | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| - | adding2q = 0; |
| - | return; |
| - | } |
| - | #endif /* CONFIG_FEATURE_VI_DOT_CMD */ |
| + | static struct esc_cmds esccmds[] = { |
| + | {(Byte *) "OA", (Byte) VI_K_UP}, // cursor key Up |
| + | {(Byte *) "OB", (Byte) VI_K_DOWN}, // cursor key Down |
| + | {(Byte *) "OC", (Byte) VI_K_RIGHT}, // Cursor Key Right |
| + | {(Byte *) "OD", (Byte) VI_K_LEFT}, // cursor key Left |
| + | {(Byte *) "OH", (Byte) VI_K_HOME}, // Cursor Key Home |
| + | {(Byte *) "OF", (Byte) VI_K_END}, // Cursor Key End |
| + | {(Byte *) "[A", (Byte) VI_K_UP}, // cursor key Up |
| + | {(Byte *) "[B", (Byte) VI_K_DOWN}, // cursor key Down |
| + | {(Byte *) "[C", (Byte) VI_K_RIGHT}, // Cursor Key Right |
| + | {(Byte *) "[D", (Byte) VI_K_LEFT}, // cursor key Left |
| + | {(Byte *) "[H", (Byte) VI_K_HOME}, // Cursor Key Home |
| + | {(Byte *) "[F", (Byte) VI_K_END}, // Cursor Key End |
| + | {(Byte *) "[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert |
| + | {(Byte *) "[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up |
| + | {(Byte *) "[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down |
| + | {(Byte *) "OP", (Byte) VI_K_FUN1}, // Function Key F1 |
| + | {(Byte *) "OQ", (Byte) VI_K_FUN2}, // Function Key F2 |
| + | {(Byte *) "OR", (Byte) VI_K_FUN3}, // Function Key F3 |
| + | {(Byte *) "OS", (Byte) VI_K_FUN4}, // Function Key F4 |
| + | {(Byte *) "[15~", (Byte) VI_K_FUN5}, // Function Key F5 |
| + | {(Byte *) "[17~", (Byte) VI_K_FUN6}, // Function Key F6 |
| + | {(Byte *) "[18~", (Byte) VI_K_FUN7}, // Function Key F7 |
| + | {(Byte *) "[19~", (Byte) VI_K_FUN8}, // Function Key F8 |
| + | {(Byte *) "[20~", (Byte) VI_K_FUN9}, // Function Key F9 |
| + | {(Byte *) "[21~", (Byte) VI_K_FUN10}, // Function Key F10 |
| + | {(Byte *) "[23~", (Byte) VI_K_FUN11}, // Function Key F11 |
| + | {(Byte *) "[24~", (Byte) VI_K_FUN12}, // Function Key F12 |
| + | {(Byte *) "[11~", (Byte) VI_K_FUN1}, // Function Key F1 |
| + | {(Byte *) "[12~", (Byte) VI_K_FUN2}, // Function Key F2 |
| + | {(Byte *) "[13~", (Byte) VI_K_FUN3}, // Function Key F3 |
| + | {(Byte *) "[14~", (Byte) VI_K_FUN4}, // Function Key F4 |
| + | }; |
| - | #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_COLON) || defined(CONFIG_FEATURE_VI_CRASHME) |
| - | static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p' |
| - | { |
| - | int cnt, i; |
| + | #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds)) |
| - | i = strlen((char *) s); |
| - | p = text_hole_make(p, i); |
| - | strncpy((char *) p, (char *) s, i); |
| - | for (cnt = 0; *s != '\0'; s++) { |
| - | if (*s == '\n') |
| - | cnt++; |
| + | (void) alarm(0); // turn alarm OFF while we wait for input |
| + | // get input from User- are there already input chars in Q? |
| + | bufsiz = strlen((char *) readbuffer); |
| + | if (bufsiz <= 0) { |
| + | ri0: |
| + | // the Q is empty, wait for a typed char |
| + | bufsiz = read(0, readbuffer, BUFSIZ - 1); |
| + | if (bufsiz < 0) { |
| + | if (errno == EINTR) |
| + | goto ri0; // interrupted sys call |
| + | if (errno == EBADF) |
| + | editing = 0; |
| + | if (errno == EFAULT) |
| + | editing = 0; |
| + | if (errno == EINVAL) |
| + | editing = 0; |
| + | if (errno == EIO) |
| + | editing = 0; |
| + | errno = 0; |
| + | bufsiz = 0; |
| + | } |
| + | readbuffer[bufsiz] = '\0'; |
| } | |
| - | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| - | psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg()); |
| - | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| - | return (p); |
| - | } |
| - | #endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */ |
| + | // return char if it is not part of ESC sequence |
| + | if (readbuffer[0] != 27) |
| + | goto ri1; |
| - | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| - | static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register |
| - | { |
| - | Byte *t; |
| - | int cnt; |
| + | // This is an ESC char. Is this Esc sequence? |
| + | // Could be bare Esc key. See if there are any |
| + | // more chars to read after the ESC. This would |
| + | // be a Function or Cursor Key sequence. |
| + | FD_ZERO(&rfds); |
| + | FD_SET(0, &rfds); |
| + | tv.tv_sec = 0; |
| + | tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000 |
| - | if (q < p) { // they are backwards- reverse them |
| - | t = q; |
| - | q = p; |
| - | p = t; |
| + | // keep reading while there are input chars and room in buffer |
| + | while (select(1, &rfds, NULL, NULL, &tv) > 0 && bufsiz <= (BUFSIZ - 5)) { |
| + | // read the rest of the ESC string |
| + | i = read(0, (void *) (readbuffer + bufsiz), BUFSIZ - bufsiz); |
| + | if (i > 0) { |
| + | bufsiz += i; |
| + | readbuffer[bufsiz] = '\0'; // Terminate the string |
| + | } |
| } | |
| - | cnt = q - p + 1; |
| - | t = reg[dest]; |
| - | if (t != 0) { // if already a yank register |
| - | free(t); // free it |
| + | // Maybe cursor or function key? |
| + | for (cmdindex = 0; cmdindex < ESCCMDS_COUNT; cmdindex++) { |
| + | cnt = strlen((char *) esccmds[cmdindex].seq); |
| + | i = strncmp((char *) esccmds[cmdindex].seq, (char *) readbuffer, cnt); |
| + | if (i == 0) { |
| + | // is a Cursor key- put derived value back into Q |
| + | readbuffer[0] = esccmds[cmdindex].val; |
| + | // squeeze out the ESC sequence |
| + | for (i = 1; i < cnt; i++) { |
| + | memmove(readbuffer + 1, readbuffer + 2, BUFSIZ - 2); |
| + | readbuffer[BUFSIZ - 1] = '\0'; |
| + | } |
| + | break; |
| + | } |
| } | |
| - | t = (Byte *) xmalloc(cnt + 1); // get a new register |
| - | memset(t, '\0', cnt + 1); // clear new text[] |
| - | strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer |
| - | reg[dest] = t; |
| - | return (p); |
| - | } |
| - | |
| - | static Byte what_reg(void) |
| - | { |
| - | Byte c; |
| - | int i; |
| - | |
| - | i = 0; |
| - | c = 'D'; // default to D-reg |
| - | if (0 <= YDreg && YDreg <= 25) |
| - | c = 'a' + (Byte) YDreg; |
| - | if (YDreg == 26) |
| - | c = 'D'; |
| - | if (YDreg == 27) |
| - | c = 'U'; |
| + | ri1: |
| + | c = readbuffer[0]; |
| + | // remove one char from Q |
| + | memmove(readbuffer, readbuffer + 1, BUFSIZ - 1); |
| + | readbuffer[BUFSIZ - 1] = '\0'; |
| + | (void) alarm(3); // we are done waiting for input, turn alarm ON |
| return (c); | |
| } | |
| - | static void check_context(Byte cmd) |
| + | //----- IO Routines -------------------------------------------- |
| + | static Byte get_one_char() |
| { | |
| - | // A context is defined to be "modifying text" |
| - | // Any modifying command establishes a new context. |
| + | static Byte c; |
| - | if (dot < context_start || dot > context_end) { |
| - | if (strchr((char *) modifying_cmds, cmd) != NULL) { |
| - | // we are trying to modify text[]- make this the current context |
| - | mark[27] = mark[26]; // move cur to prev |
| - | mark[26] = dot; // move local to cur |
| - | context_start = prev_line(prev_line(dot)); |
| - | context_end = next_line(next_line(dot)); |
| - | //loiter= start_loiter= now; |
| + | #ifdef CONFIG_FEATURE_VI_DOT_CMD |
| + | // ! adding2q && ioq == 0 read() |
| + | // ! adding2q && ioq != 0 *ioq |
| + | // adding2q *last_modifying_cmd= read() |
| + | if (!adding2q) { |
| + | // we are not adding to the q. |
| + | // but, we may be reading from a q |
| + | if (ioq == 0) { |
| + | // there is no current q, read from STDIN |
| + | c = readit(); // get the users input |
| + | } else { |
| + | // there is a queue to get chars from first |
| + | c = *ioq++; |
| + | if (c == '\0') { |
| + | // the end of the q, read from STDIN |
| + | free(ioq_start); |
| + | ioq_start = ioq = 0; |
| + | c = readit(); // get the users input |
| + | } |
| + | } |
| + | } else { |
| + | // adding STDIN chars to q |
| + | c = readit(); // get the users input |
| + | if (last_modifying_cmd != 0) { |
| + | // add new char to q |
| + | last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c; |
| } | |
| } | |
| - | return; |
| + | #else /* CONFIG_FEATURE_VI_DOT_CMD */ |
| + | c = readit(); // get the users input |
| + | #endif /* CONFIG_FEATURE_VI_DOT_CMD */ |
| + | return (c); // return the char, where ever it came from |
| } | |
| - | static Byte *swap_context(Byte * p) // goto new context for '' command make this the current context |
| + | static Byte *get_input_line(Byte * prompt) // get input line- use "status line" |
| { | |
| - | Byte *tmp; |
| + | Byte buf[BUFSIZ]; |
| + | Byte c; |
| + | int i; |
| + | static Byte *obufp = NULL; |
| - | // the current context is in mark[26] |
| - | // the previous context is in mark[27] |
| - | // only swap context if other context is valid |
| - | if (text <= mark[27] && mark[27] <= end - 1) { |
| - | tmp = mark[27]; |
| - | mark[27] = mark[26]; |
| - | mark[26] = tmp; |
| - | p = mark[26]; // where we are going- previous context |
| - | context_start = prev_line(prev_line(prev_line(p))); |
| - | context_end = next_line(next_line(next_line(p))); |
| - | } |
| - | return (p); |
| - | } |
| - | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| + | strcpy((char *) buf, (char *) prompt); |
| + | *status_buffer = '\0'; // clear the status buffer |
| + | place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen |
| + | clear_to_eol(); // clear the line |
| + | write(1, prompt, strlen((char *) prompt)); // write out the :, /, or ? prompt |
| - | static int isblnk(Byte c) // is the char a blank or tab |
| - | { |
| - | return (c == ' ' || c == '\t'); |
| + | for (i = strlen((char *) buf); i < BUFSIZ;) { |
| + | c = get_one_char(); // read user input |
| + | if (c == '\n' || c == '\r' || c == 27) |
| + | break; // is this end of input |
| + | if (c == erase_char) { // user wants to erase prev char |
| + | i--; // backup to prev char |
| + | buf[i] = '\0'; // erase the char |
| + | buf[i + 1] = '\0'; // null terminate buffer |
| + | write(1, " ", 3); // erase char on screen |
| + | if (i <= 0) { // user backs up before b-o-l, exit |
| + | break; |
| + | } |
| + | } else { |
| + | buf[i] = c; // save char in buffer |
| + | buf[i + 1] = '\0'; // make sure buffer is null terminated |
| + | write(1, buf + i, 1); // echo the char back to user |
| + | i++; |
| + | } |
| + | } |
| + | refresh(FALSE); |
| + | if (obufp != NULL) |
| + | free(obufp); |
| + | obufp = (Byte *) xstrdup((char *) buf); |
| + | return (obufp); |
| } | |
| - | //----- Set terminal attributes -------------------------------- |
| - | static void rawmode(void) |
| + | static int file_size(Byte * fn) // what is the byte size of "fn" |
| { | |
| - | tcgetattr(0, &term_orig); |
| - | term_vi = term_orig; |
| - | term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's |
| - | term_vi.c_iflag &= (~IXON & ~ICRNL); |
| - | term_vi.c_oflag &= (~ONLCR); |
| - | #ifndef linux |
| - | term_vi.c_cc[VMIN] = 1; |
| - | term_vi.c_cc[VTIME] = 0; |
| - | #endif |
| - | erase_char = term_vi.c_cc[VERASE]; |
| - | tcsetattr(0, TCSANOW, &term_vi); |
| - | } |
| + | struct stat st_buf; |
| + | int cnt, sr; |
| - | static void cookmode(void) |
| - | { |
| - | tcsetattr(0, TCSANOW, &term_orig); |
| + | if (fn == 0 || strlen(fn) <= 0) |
| + | return (-1); |
| + | cnt = -1; |
| + | sr = stat((char *) fn, &st_buf); // see if file exists |
| + | if (sr >= 0) { |
| + | cnt = (int) st_buf.st_size; |
| + | } |
| + | return (cnt); |
| } | |
| - | #ifdef CONFIG_FEATURE_VI_WIN_RESIZE |
| - | //----- See what the window size currently is -------------------- |
| - | static void window_size_get(int sig) |
| + | static int file_insert(Byte * fn, Byte * p, int size) |
| { | |
| - | int i; |
| + | int fd, cnt; |
| - | i = ioctl(0, TIOCGWINSZ, &winsize); |
| - | if (i != 0) { |
| - | // force 24x80 |
| - | winsize.ws_row = 24; |
| - | winsize.ws_col = 80; |
| + | cnt = -1; |
| + | #ifdef CONFIG_FEATURE_VI_READONLY |
| + | readonly = FALSE; |
| + | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| + | if (fn == 0 || strlen((char*) fn) <= 0) { |
| + | psbs("No filename given"); |
| + | goto fi0; |
| } | |
| - | if (winsize.ws_row <= 1) { |
| - | winsize.ws_row = 24; |
| + | if (size == 0) { |
| + | // OK- this is just a no-op |
| + | cnt = 0; |
| + | goto fi0; |
| } | |
| - | if (winsize.ws_col <= 1) { |
| - | winsize.ws_col = 80; |
| + | if (size < 0) { |
| + | psbs("Trying to insert a negative number (%d) of characters", size); |
| + | goto fi0; |
| } | |
| - | rows = (int) winsize.ws_row; |
| - | columns = (int) winsize.ws_col; |
| + | if (p < text || p > end) { |
| + | psbs("Trying to insert file outside of memory"); |
| + | goto fi0; |
| + | } |
| + | |
| + | // see if we can open the file |
| + | #ifdef CONFIG_FEATURE_VI_READONLY |
| + | if (vi_readonly) goto fi1; // do not try write-mode |
| + | #endif |
| + | fd = open((char *) fn, O_RDWR); // assume read & write |
| + | if (fd < 0) { |
| + | // could not open for writing- maybe file is read only |
| + | #ifdef CONFIG_FEATURE_VI_READONLY |
| + | fi1: |
| + | #endif |
| + | fd = open((char *) fn, O_RDONLY); // try read-only |
| + | if (fd < 0) { |
| + | psbs("\"%s\" %s", fn, "could not open file"); |
| + | goto fi0; |
| + | } |
| + | #ifdef CONFIG_FEATURE_VI_READONLY |
| + | // got the file- read-only |
| + | readonly = TRUE; |
| + | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| + | } |
| + | p = text_hole_make(p, size); |
| + | cnt = read(fd, p, size); |
| + | close(fd); |
| + | if (cnt < 0) { |
| + | cnt = -1; |
| + | p = text_hole_delete(p, p + size - 1); // un-do buffer insert |
| + | psbs("could not read file \"%s\"", fn); |
| + | } else if (cnt < size) { |
| + | // There was a partial read, shrink unused space text[] |
| + | p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert |
| + | psbs("could not read all of file \"%s\"", fn); |
| + | } |
| + | if (cnt >= size) |
| + | file_modified = TRUE; |
| + | fi0: |
| + | return (cnt); |
| } | |
| - | #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */ |
| - | //----- Come here when we get a window resize signal --------- |
| - | #ifdef CONFIG_FEATURE_VI_USE_SIGNALS |
| - | static void winch_sig(int sig) |
| + | static int file_write(Byte * fn, Byte * first, Byte * last) |
| { | |
| - | signal(SIGWINCH, winch_sig); |
| - | #ifdef CONFIG_FEATURE_VI_WIN_RESIZE |
| - | window_size_get(0); |
| - | #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */ |
| - | new_screen(rows, columns); // get memory for virtual screen |
| - | redraw(TRUE); // re-draw the screen |
| + | int fd, cnt, charcnt; |
| + | |
| + | if (fn == 0) { |
| + | psbs("No current filename"); |
| + | return (-1); |
| + | } |
| + | charcnt = 0; |
| + | // FIXIT- use the correct umask() |
| + | fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664); |
| + | if (fd < 0) |
| + | return (-1); |
| + | cnt = last - first + 1; |
| + | charcnt = write(fd, first, cnt); |
| + | if (charcnt == cnt) { |
| + | // good write |
| + | //file_modified= FALSE; // the file has not been modified |
| + | } else { |
| + | charcnt = 0; |
| + | } |
| + | close(fd); |
| + | return (charcnt); |
| } | |
| - | //----- Come here when we get a continue signal ------------------- |
| - | static void cont_sig(int sig) |
| + | //----- Terminal Drawing --------------------------------------- |
| + | // The terminal is made up of 'rows' line of 'columns' columns. |
| + | // classicly this would be 24 x 80. |
| + | // screen coordinates |
| + | // 0,0 ... 0,79 |
| + | // 1,0 ... 1,79 |
| + | // . ... . |
| + | // . ... . |
| + | // 22,0 ... 22,79 |
| + | // 23,0 ... 23,79 status line |
| + | // |
| + | |
| + | //----- Move the cursor to row x col (count from 0, not 1) ------- |
| + | static void place_cursor(int row, int col, int opti) |
| { | |
| - | rawmode(); // terminal to "raw" |
| - | *status_buffer = '\0'; // clear the status buffer |
| - | redraw(TRUE); // re-draw the screen |
| + | char cm1[BUFSIZ]; |
| + | char *cm; |
| + | int l; |
| + | #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR |
| + | char cm2[BUFSIZ]; |
| + | Byte *screenp; |
| + | // char cm3[BUFSIZ]; |
| + | int Rrow= last_row; |
| + | #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ |
| + | |
| + | memset(cm1, '\0', BUFSIZ - 1); // clear the buffer |
| - | signal(SIGTSTP, suspend_sig); |
| - | signal(SIGCONT, SIG_DFL); |
| - | kill(getpid(), SIGCONT); |
| + | if (row < 0) row = 0; |
| + | if (row >= rows) row = rows - 1; |
| + | if (col < 0) col = 0; |
| + | if (col >= columns) col = columns - 1; |
| + | |
| + | //----- 1. Try the standard terminal ESC sequence |
| + | sprintf((char *) cm1, CMrc, row + 1, col + 1); |
| + | cm= cm1; |
| + | if (! opti) goto pc0; |
| + | |
| + | #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR |
| + | //----- find the minimum # of chars to move cursor ------------- |
| + | //----- 2. Try moving with discreet chars (Newline, [back]space, ...) |
| + | memset(cm2, '\0', BUFSIZ - 1); // clear the buffer |
| + | |
| + | // move to the correct row |
| + | while (row < Rrow) { |
| + | // the cursor has to move up |
| + | strcat(cm2, CMup); |
| + | Rrow--; |
| + | } |
| + | while (row > Rrow) { |
| + | // the cursor has to move down |
| + | strcat(cm2, CMdown); |
| + | Rrow++; |
| + | } |
| + | |
| + | // now move to the correct column |
| + | strcat(cm2, "\r"); // start at col 0 |
| + | // just send out orignal source char to get to correct place |
| + | screenp = &screen[row * columns]; // start of screen line |
| + | strncat(cm2, screenp, col); |
| + | |
| + | //----- 3. Try some other way of moving cursor |
| + | //--------------------------------------------- |
| + | |
| + | // pick the shortest cursor motion to send out |
| + | cm= cm1; |
| + | if (strlen(cm2) < strlen(cm)) { |
| + | cm= cm2; |
| + | } /* else if (strlen(cm3) < strlen(cm)) { |
| + | cm= cm3; |
| + | } */ |
| + | #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ |
| + | pc0: |
| + | l= strlen(cm); |
| + | if (l) write(1, cm, l); // move the cursor |
| } | |
| - | //----- Come here when we get a Suspend signal ------------------- |
| - | static void suspend_sig(int sig) |
| + | //----- Erase from cursor to end of line ----------------------- |
| + | static void clear_to_eol() |
| { | |
| - | place_cursor(rows - 1, 0, FALSE); // go to bottom of screen |
| - | clear_to_eol(); // Erase to end of line |
| - | cookmode(); // terminal to "cooked" |
| + | write(1, Ceol, strlen(Ceol)); // Erase from cursor to end of line |
| + | } |
| - | signal(SIGCONT, cont_sig); |
| - | signal(SIGTSTP, SIG_DFL); |
| - | kill(getpid(), SIGTSTP); |
| + | //----- Erase from cursor to end of screen ----------------------- |
| + | static void clear_to_eos() |
| + | { |
| + | write(1, Ceos, strlen(Ceos)); // Erase from cursor to end of screen |
| } | |
| - | //----- Come here when we get a signal --------------------------- |
| - | static void catch_sig(int sig) |
| + | //----- Start standout mode ------------------------------------ |
| + | static void standout_start() // send "start reverse video" sequence |
| { | |
| - | signal(SIGHUP, catch_sig); |
| - | signal(SIGINT, catch_sig); |
| - | signal(SIGTERM, catch_sig); |
| - | longjmp(restart, sig); |
| + | write(1, SOs, strlen(SOs)); // Start reverse video mode |
| } | |
| - | static void alarm_sig(int sig) |
| + | //----- End standout mode -------------------------------------- |
| + | static void standout_end() // send "end reverse video" sequence |
| { | |
| - | signal(SIGALRM, catch_sig); |
| - | longjmp(restart, sig); |
| + | write(1, SOn, strlen(SOn)); // End reverse video mode |
| } | |
| - | //----- Come here when we get a core dump signal ----------------- |
| - | static void core_sig(int sig) |
| + | //----- Flash the screen -------------------------------------- |
| + | static void flash(int h) |
| { | |
| - | signal(SIGQUIT, core_sig); |
| - | signal(SIGILL, core_sig); |
| - | signal(SIGTRAP, core_sig); |
| - | signal(SIGIOT, core_sig); |
| - | signal(SIGABRT, core_sig); |
| - | signal(SIGFPE, core_sig); |
| - | signal(SIGBUS, core_sig); |
| - | signal(SIGSEGV, core_sig); |
| - | #ifdef SIGSYS |
| - | signal(SIGSYS, core_sig); |
| - | #endif |
| + | standout_start(); // send "start reverse video" sequence |
| + | redraw(TRUE); |
| + | (void) mysleep(h); |
| + | standout_end(); // send "end reverse video" sequence |
| + | redraw(TRUE); |
| + | } |
| - | dot = bound_dot(dot); // make sure "dot" is valid |
| + | static void beep() |
| + | { |
| + | write(1, bell, strlen(bell)); // send out a bell character |
| + | } |
| - | longjmp(restart, sig); |
| + | static void indicate_error(char c) |
| + | { |
| + | #ifdef CONFIG_FEATURE_VI_CRASHME |
| + | if (crashme > 0) |
| + | return; // generate a random command |
| + | #endif /* CONFIG_FEATURE_VI_CRASHME */ |
| + | if (err_method == 0) { |
| + | beep(); |
| + | } else { |
| + | flash(10); |
| + | } |
| } | |
| - | #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ |
| - | static int mysleep(int hund) // sleep for 'h' 1/100 seconds |
| + | //----- Screen[] Routines -------------------------------------- |
| + | //----- Erase the Screen[] memory ------------------------------ |
| + | static void screen_erase() |
| { | |
| - | // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000 |
| - | FD_ZERO(&rfds); |
| - | FD_SET(0, &rfds); |
| - | tv.tv_sec = 0; |
| - | tv.tv_usec = hund * 10000; |
| - | select(1, &rfds, NULL, NULL, &tv); |
| - | return (FD_ISSET(0, &rfds)); |
| + | memset(screen, ' ', screensize); // clear new screen |
| } | |
| - | //----- IO Routines -------------------------------------------- |
| - | static Byte readit(void) // read (maybe cursor) key from stdin |
| + | //----- Draw the status line at bottom of the screen ------------- |
| + | static void show_status_line(void) |
| { | |
| - | Byte c; |
| - | int i, bufsiz, cnt, cmdindex; |
| - | struct esc_cmds { |
| - | Byte *seq; |
| - | Byte val; |
| - | }; |
| + | static int last_cksum; |
| + | int l, cnt, cksum; |
| - | static struct esc_cmds esccmds[] = { |
| - | {(Byte *) "OA", (Byte) VI_K_UP}, // cursor key Up |
| - | {(Byte *) "OB", (Byte) VI_K_DOWN}, // cursor key Down |
| - | {(Byte *) "OC", (Byte) VI_K_RIGHT}, // Cursor Key Right |
| - | {(Byte *) "OD", (Byte) VI_K_LEFT}, // cursor key Left |
| - | {(Byte *) "OH", (Byte) VI_K_HOME}, // Cursor Key Home |
| - | {(Byte *) "OF", (Byte) VI_K_END}, // Cursor Key End |
| - | {(Byte *) "[A", (Byte) VI_K_UP}, // cursor key Up |
| - | {(Byte *) "[B", (Byte) VI_K_DOWN}, // cursor key Down |
| - | {(Byte *) "[C", (Byte) VI_K_RIGHT}, // Cursor Key Right |
| - | {(Byte *) "[D", (Byte) VI_K_LEFT}, // cursor key Left |
| - | {(Byte *) "[H", (Byte) VI_K_HOME}, // Cursor Key Home |
| - | {(Byte *) "[F", (Byte) VI_K_END}, // Cursor Key End |
| - | {(Byte *) "[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert |
| - | {(Byte *) "[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up |
| - | {(Byte *) "[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down |
| - | {(Byte *) "OP", (Byte) VI_K_FUN1}, // Function Key F1 |
| - | {(Byte *) "OQ", (Byte) VI_K_FUN2}, // Function Key F2 |
| - | {(Byte *) "OR", (Byte) VI_K_FUN3}, // Function Key F3 |
| - | {(Byte *) "OS", (Byte) VI_K_FUN4}, // Function Key F4 |
| - | {(Byte *) "[15~", (Byte) VI_K_FUN5}, // Function Key F5 |
| - | {(Byte *) "[17~", (Byte) VI_K_FUN6}, // Function Key F6 |
| - | {(Byte *) "[18~", (Byte) VI_K_FUN7}, // Function Key F7 |
| - | {(Byte *) "[19~", (Byte) VI_K_FUN8}, // Function Key F8 |
| - | {(Byte *) "[20~", (Byte) VI_K_FUN9}, // Function Key F9 |
| - | {(Byte *) "[21~", (Byte) VI_K_FUN10}, // Function Key F10 |
| - | {(Byte *) "[23~", (Byte) VI_K_FUN11}, // Function Key F11 |
| - | {(Byte *) "[24~", (Byte) VI_K_FUN12}, // Function Key F12 |
| - | {(Byte *) "[11~", (Byte) VI_K_FUN1}, // Function Key F1 |
| - | {(Byte *) "[12~", (Byte) VI_K_FUN2}, // Function Key F2 |
| - | {(Byte *) "[13~", (Byte) VI_K_FUN3}, // Function Key F3 |
| - | {(Byte *) "[14~", (Byte) VI_K_FUN4}, // Function Key F4 |
| - | }; |
| - | |
| - | #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds)) |
| - | |
| - | (void) alarm(0); // turn alarm OFF while we wait for input |
| - | // get input from User- are there already input chars in Q? |
| - | bufsiz = strlen((char *) readbuffer); |
| - | if (bufsiz <= 0) { |
| - | ri0: |
| - | // the Q is empty, wait for a typed char |
| - | bufsiz = read(0, readbuffer, BUFSIZ - 1); |
| - | if (bufsiz < 0) { |
| - | if (errno == EINTR) |
| - | goto ri0; // interrupted sys call |
| - | if (errno == EBADF) |
| - | editing = 0; |
| - | if (errno == EFAULT) |
| - | editing = 0; |
| - | if (errno == EINVAL) |
| - | editing = 0; |
| - | if (errno == EIO) |
| - | editing = 0; |
| - | errno = 0; |
| - | bufsiz = 0; |
| - | } |
| - | readbuffer[bufsiz] = '\0'; |
| + | cnt = strlen((char *) status_buffer); |
| + | for (cksum= l= 0; l < cnt; l++) { cksum += (int)(status_buffer[l]); } |
| + | // don't write the status line unless it changes |
| + | if (cnt > 0 && last_cksum != cksum) { |
| + | last_cksum= cksum; // remember if we have seen this line |
| + | place_cursor(rows - 1, 0, FALSE); // put cursor on status line |
| + | write(1, status_buffer, cnt); |
| + | clear_to_eol(); |
| + | place_cursor(crow, ccol, FALSE); // put cursor back in correct place |
| } | |
| - | // return char if it is not part of ESC sequence |
| - | if (readbuffer[0] != 27) |
| - | goto ri1; |
| + | } |
| - | // This is an ESC char. Is this Esc sequence? |
| - | // Could be bare Esc key. See if there are any |
| - | // more chars to read after the ESC. This would |
| - | // be a Function or Cursor Key sequence. |
| - | FD_ZERO(&rfds); |
| - | FD_SET(0, &rfds); |
| - | tv.tv_sec = 0; |
| - | tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000 |
| + | //----- format the status buffer, the bottom line of screen ------ |
| + | // print status buffer, with STANDOUT mode |
| + | static void psbs(char *format, ...) |
| + | { |
| + | va_list args; |
| - | // keep reading while there are input chars and room in buffer |
| - | while (select(1, &rfds, NULL, NULL, &tv) > 0 && bufsiz <= (BUFSIZ - 5)) { |
| - | // read the rest of the ESC string |
| - | i = read(0, (void *) (readbuffer + bufsiz), BUFSIZ - bufsiz); |
| - | if (i > 0) { |
| - | bufsiz += i; |
| - | readbuffer[bufsiz] = '\0'; // Terminate the string |
| - | } |
| - | } |
| - | // Maybe cursor or function key? |
| - | for (cmdindex = 0; cmdindex < ESCCMDS_COUNT; cmdindex++) { |
| - | cnt = strlen((char *) esccmds[cmdindex].seq); |
| - | i = strncmp((char *) esccmds[cmdindex].seq, (char *) readbuffer, cnt); |
| - | if (i == 0) { |
| - | // is a Cursor key- put derived value back into Q |
| - | readbuffer[0] = esccmds[cmdindex].val; |
| - | // squeeze out the ESC sequence |
| - | for (i = 1; i < cnt; i++) { |
| - | memmove(readbuffer + 1, readbuffer + 2, BUFSIZ - 2); |
| - | readbuffer[BUFSIZ - 1] = '\0'; |
| - | } |
| - | break; |
| - | } |
| - | } |
| - | ri1: |
| - | c = readbuffer[0]; |
| - | // remove one char from Q |
| - | memmove(readbuffer, readbuffer + 1, BUFSIZ - 1); |
| - | readbuffer[BUFSIZ - 1] = '\0'; |
| - | (void) alarm(3); // we are done waiting for input, turn alarm ON |
| - | return (c); |
| + | va_start(args, format); |
| + | strcpy((char *) status_buffer, SOs); // Terminal standout mode on |
| + | vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, |
| + | args); |
| + | strcat((char *) status_buffer, SOn); // Terminal standout mode off |
| + | va_end(args); |
| + | |
| + | return; |
| } | |
| - | //----- IO Routines -------------------------------------------- |
| - | static Byte get_one_char() |
| + | // print status buffer |
| + | static void psb(char *format, ...) |
| { | |
| - | static Byte c; |
| + | va_list args; |
| - | #ifdef CONFIG_FEATURE_VI_DOT_CMD |
| - | // ! adding2q && ioq == 0 read() |
| - | // ! adding2q && ioq != 0 *ioq |
| - | // adding2q *last_modifying_cmd= read() |
| - | if (!adding2q) { |
| - | // we are not adding to the q. |
| - | // but, we may be reading from a q |
| - | if (ioq == 0) { |
| - | // there is no current q, read from STDIN |
| - | c = readit(); // get the users input |
| - | } else { |
| - | // there is a queue to get chars from first |
| - | c = *ioq++; |
| - | if (c == '\0') { |
| - | // the end of the q, read from STDIN |
| - | free(ioq_start); |
| - | ioq_start = ioq = 0; |
| - | c = readit(); // get the users input |
| - | } |
| - | } |
| - | } else { |
| - | // adding STDIN chars to q |
| - | c = readit(); // get the users input |
| - | if (last_modifying_cmd != 0) { |
| - | // add new char to q |
| - | last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c; |
| - | } |
| - | } |
| - | #else /* CONFIG_FEATURE_VI_DOT_CMD */ |
| - | c = readit(); // get the users input |
| - | #endif /* CONFIG_FEATURE_VI_DOT_CMD */ |
| - | return (c); // return the char, where ever it came from |
| + | va_start(args, format); |
| + | vsprintf((char *) status_buffer, format, args); |
| + | va_end(args); |
| + | return; |
| } | |
| - | static Byte *get_input_line(Byte * prompt) // get input line- use "status line" |
| + | static void ni(Byte * s) // display messages |
| { | |
| Byte buf[BUFSIZ]; | |
| - | Byte c; |
| - | int i; |
| - | static Byte *obufp = NULL; |
| - | |
| - | strcpy((char *) buf, (char *) prompt); |
| - | *status_buffer = '\0'; // clear the status buffer |
| - | place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen |
| - | clear_to_eol(); // clear the line |
| - | write(1, prompt, strlen((char *) prompt)); // write out the :, /, or ? prompt |
| - | for (i = strlen((char *) buf); i < BUFSIZ;) { |
| - | c = get_one_char(); // read user input |
| - | if (c == '\n' || c == '\r' || c == 27) |
| - | break; // is this end of input |
| - | if (c == erase_char) { // user wants to erase prev char |
| - | i--; // backup to prev char |
| - | buf[i] = '\0'; // erase the char |
| - | buf[i + 1] = '\0'; // null terminate buffer |
| - | write(1, " ", 3); // erase char on screen |
| - | if (i <= 0) { // user backs up before b-o-l, exit |
| - | break; |
| - | } |
| - | } else { |
| - | buf[i] = c; // save char in buffer |
| - | buf[i + 1] = '\0'; // make sure buffer is null terminated |
| - | write(1, buf + i, 1); // echo the char back to user |
| - | i++; |
| - | } |
| - | } |
| - | refresh(FALSE); |
| - | if (obufp != NULL) |
| - | free(obufp); |
| - | obufp = (Byte *) xstrdup((char *) buf); |
| - | return (obufp); |
| + | print_literal(buf, s); |
| + | psbs("\'%s\' is not implemented", buf); |
| } | |
| - | static int file_size(Byte * fn) // what is the byte size of "fn" |
| + | static void edit_status(void) // show file status on status line |
| { | |
| - | struct stat st_buf; |
| - | int cnt, sr; |
| + | int cur, tot, percent; |
| - | if (fn == 0 || strlen(fn) <= 0) |
| - | return (-1); |
| - | cnt = -1; |
| - | sr = stat((char *) fn, &st_buf); // see if file exists |
| - | if (sr >= 0) { |
| - | cnt = (int) st_buf.st_size; |
| + | cur = count_lines(text, dot); |
| + | tot = count_lines(text, end - 1); |
| + | // current line percent |
| + | // ------------- ~~ ---------- |
| + | // total lines 100 |
| + | if (tot > 0) { |
| + | percent = (100 * cur) / tot; |
| + | } else { |
| + | cur = tot = 0; |
| + | percent = 100; |
| } | |
| - | return (cnt); |
| + | psb("\"%s\"" |
| + | #ifdef CONFIG_FEATURE_VI_READONLY |
| + | "%s" |
| + | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| + | "%s line %d of %d --%d%%--", |
| + | (cfn != 0 ? (char *) cfn : "No file"), |
| + | #ifdef CONFIG_FEATURE_VI_READONLY |
| + | ((vi_readonly || readonly) ? " [Read only]" : ""), |
| + | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| + | (file_modified ? " [modified]" : ""), |
| + | cur, tot, percent); |
| } | |
| - | static int file_insert(Byte * fn, Byte * p, int size) |
| + | //----- Force refresh of all Lines ----------------------------- |
| + | static void redraw(int full_screen) |
| { | |
| - | int fd, cnt; |
| + | place_cursor(0, 0, FALSE); // put cursor in correct place |
| + | clear_to_eos(); // tel terminal to erase display |
| + | screen_erase(); // erase the internal screen buffer |
| + | refresh(full_screen); // this will redraw the entire display |
| + | } |
| - | cnt = -1; |
| - | #ifdef CONFIG_FEATURE_VI_READONLY |
| - | readonly = FALSE; |
| - | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| - | if (fn == 0 || strlen((char*) fn) <= 0) { |
| - | psbs("No filename given"); |
| - | goto fi0; |
| - | } |
| - | if (size == 0) { |
| - | // OK- this is just a no-op |
| - | cnt = 0; |
| - | goto fi0; |
| - | } |
| - | if (size < 0) { |
| - | psbs("Trying to insert a negative number (%d) of characters", size); |
| - | goto fi0; |
| - | } |
| - | if (p < text || p > end) { |
| - | psbs("Trying to insert file outside of memory"); |
| - | goto fi0; |
| - | } |
| - | |
| - | // see if we can open the file |
| - | #ifdef CONFIG_FEATURE_VI_READONLY |
| - | if (vi_readonly) goto fi1; // do not try write-mode |
| - | #endif |
| - | fd = open((char *) fn, O_RDWR); // assume read & write |
| - | if (fd < 0) { |
| - | // could not open for writing- maybe file is read only |
| - | #ifdef CONFIG_FEATURE_VI_READONLY |
| - | fi1: |
| - | #endif |
| - | fd = open((char *) fn, O_RDONLY); // try read-only |
| - | if (fd < 0) { |
| - | psbs("\"%s\" %s", fn, "could not open file"); |
| - | goto fi0; |
| + | //----- Format a text[] line into a buffer --------------------- |
| + | static void format_line(Byte *dest, Byte *src, int li) |
| + | { |
| + | int co; |
| + | Byte c; |
| + | |
| + | for (co= 0; co < MAX_SCR_COLS; co++) { |
| + | c= ' '; // assume blank |
| + | if (li > 0 && co == 0) { |
| + | c = '~'; // not first line, assume Tilde |
| } | |
| - | #ifdef CONFIG_FEATURE_VI_READONLY |
| - | // got the file- read-only |
| - | readonly = TRUE; |
| - | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| - | } |
| - | p = text_hole_make(p, size); |
| - | cnt = read(fd, p, size); |
| - | close(fd); |
| - | if (cnt < 0) { |
| - | cnt = -1; |
| - | p = text_hole_delete(p, p + size - 1); // un-do buffer insert |
| - | psbs("could not read file \"%s\"", fn); |
| - | } else if (cnt < size) { |
| - | // There was a partial read, shrink unused space text[] |
| - | p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert |
| - | psbs("could not read all of file \"%s\"", fn); |
| + | // are there chars in text[] and have we gone past the end |
| + | if (text < end && src < end) { |
| + | c = *src++; |
| + | } |
| + | if (c == '\n') |
| + | break; |
| + | if (c < ' ' || c > '~') { |
| + | if (c == '\t') { |
| + | c = ' '; |
| + | // co % 8 != 7 |
| + | for (; (co % tabstop) != (tabstop - 1); co++) { |
| + | dest[co] = c; |
| + | } |
| + | } else { |
| + | dest[co++] = '^'; |
| + | c |= '@'; // make it visible |
| + | c &= 0x7f; // get rid of hi bit |
| + | } |
| + | } |
| + | // the co++ is done here so that the column will |
| + | // not be overwritten when we blank-out the rest of line |
| + | dest[co] = c; |
| + | if (src >= end) |
| + | break; |
| } | |
| - | if (cnt >= size) |
| - | file_modified = TRUE; |
| - | fi0: |
| - | return (cnt); |
| } | |
| - | static int file_write(Byte * fn, Byte * first, Byte * last) |
| + | //----- Refresh the changed screen lines ----------------------- |
| + | // Copy the source line from text[] into the buffer and note |
| + | // if the current screenline is different from the new buffer. |
| + | // If they differ then that line needs redrawing on the terminal. |
| + | // |
| + | static void refresh(int full_screen) |
| { | |
| - | int fd, cnt, charcnt; |
| + | static int old_offset; |
| + | int li, changed; |
| + | Byte buf[MAX_SCR_COLS]; |
| + | Byte *tp, *sp; // pointer into text[] and screen[] |
| + | #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR |
| + | int last_li= -2; // last line that changed- for optimizing cursor movement |
| + | #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ |
| - | if (fn == 0) { |
| - | psbs("No current filename"); |
| - | return (-1); |
| - | } |
| - | charcnt = 0; |
| - | // FIXIT- use the correct umask() |
| - | fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664); |
| - | if (fd < 0) |
| - | return (-1); |
| - | cnt = last - first + 1; |
| - | charcnt = write(fd, first, cnt); |
| - | if (charcnt == cnt) { |
| - | // good write |
| - | //file_modified= FALSE; // the file has not been modified |
| - | } else { |
| - | charcnt = 0; |
| - | } |
| - | close(fd); |
| - | return (charcnt); |
| - | } |
| + | #ifdef CONFIG_FEATURE_VI_WIN_RESIZE |
| + | window_size_get(0); |
| + | #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */ |
| + | sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot") |
| + | tp = screenbegin; // index into text[] of top line |
| - | //----- Terminal Drawing --------------------------------------- |
| - | // The terminal is made up of 'rows' line of 'columns' columns. |
| - | // classicly this would be 24 x 80. |
| - | // screen coordinates |
| - | // 0,0 ... 0,79 |
| - | // 1,0 ... 1,79 |
| - | // . ... . |
| - | // . ... . |
| - | // 22,0 ... 22,79 |
| - | // 23,0 ... 23,79 status line |
| - | // |
| + | // compare text[] to screen[] and mark screen[] lines that need updating |
| + | for (li = 0; li < rows - 1; li++) { |
| + | int cs, ce; // column start & end |
| + | memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer |
| + | buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer |
| + | // format current text line into buf |
| + | format_line(buf, tp, li); |
| - | //----- Move the cursor to row x col (count from 0, not 1) ------- |
| - | static void place_cursor(int row, int col, int opti) |
| - | { |
| - | char cm1[BUFSIZ]; |
| - | char *cm; |
| - | int l; |
| + | // skip to the end of the current text[] line |
| + | while (tp < end && *tp++ != '\n') /*no-op*/ ; |
| + | |
| + | // see if there are any changes between vitual screen and buf |
| + | changed = FALSE; // assume no change |
| + | cs= 0; |
| + | ce= columns-1; |
| + | sp = &screen[li * columns]; // start of screen line |
| + | if (full_screen) { |
| + | // force re-draw of every single column from 0 - columns-1 |
| + | goto re0; |
| + | } |
| + | // compare newly formatted buffer with virtual screen |
| + | // look forward for first difference between buf and screen |
| + | for ( ; cs <= ce; cs++) { |
| + | if (buf[cs + offset] != sp[cs]) { |
| + | changed = TRUE; // mark for redraw |
| + | break; |
| + | } |
| + | } |
| + | |
| + | // look backward for last difference between buf and screen |
| + | for ( ; ce >= cs; ce--) { |
| + | if (buf[ce + offset] != sp[ce]) { |
| + | changed = TRUE; // mark for redraw |
| + | break; |
| + | } |
| + | } |
| + | // now, cs is index of first diff, and ce is index of last diff |
| + | |
| + | // if horz offset has changed, force a redraw |
| + | if (offset != old_offset) { |
| + | re0: |
| + | changed = TRUE; |
| + | } |
| + | |
| + | // make a sanity check of columns indexes |
| + | if (cs < 0) cs= 0; |
| + | if (ce > columns-1) ce= columns-1; |
| + | if (cs > ce) { cs= 0; ce= columns-1; } |
| + | // is there a change between vitual screen and buf |
| + | if (changed) { |
| + | // copy changed part of buffer to virtual screen |
| + | memmove(sp+cs, buf+(cs+offset), ce-cs+1); |
| + | |
| + | // move cursor to column of first change |
| + | if (offset != old_offset) { |
| + | // opti_cur_move is still too stupid |
| + | // to handle offsets correctly |
| + | place_cursor(li, cs, FALSE); |
| + | } else { |
| #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR | |
| - | char cm2[BUFSIZ]; |
| - | Byte *screenp; |
| - | // char cm3[BUFSIZ]; |
| - | int Rrow= last_row; |
| + | // if this just the next line |
| + | // try to optimize cursor movement |
| + | // otherwise, use standard ESC sequence |
| + | place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE); |
| + | last_li= li; |
| + | #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ |
| + | place_cursor(li, cs, FALSE); // use standard ESC sequence |
| #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ | |
| - | |
| - | memset(cm1, '\0', BUFSIZ - 1); // clear the buffer |
| - | |
| - | if (row < 0) row = 0; |
| - | if (row >= rows) row = rows - 1; |
| - | if (col < 0) col = 0; |
| - | if (col >= columns) col = columns - 1; |
| - | |
| - | //----- 1. Try the standard terminal ESC sequence |
| - | sprintf((char *) cm1, CMrc, row + 1, col + 1); |
| - | cm= cm1; |
| - | if (! opti) goto pc0; |
| + | } |
| + | // write line out to terminal |
| + | write(1, sp+cs, ce-cs+1); |
| #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR | |
| - | //----- find the minimum # of chars to move cursor ------------- |
| - | //----- 2. Try moving with discreet chars (Newline, [back]space, ...) |
| - | memset(cm2, '\0', BUFSIZ - 1); // clear the buffer |
| - | |
| - | // move to the correct row |
| - | while (row < Rrow) { |
| - | // the cursor has to move up |
| - | strcat(cm2, CMup); |
| - | Rrow--; |
| - | } |
| - | while (row > Rrow) { |
| - | // the cursor has to move down |
| - | strcat(cm2, CMdown); |
| - | Rrow++; |
| + | last_row = li; |
| + | #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ |
| + | } |
| } | |
| - | |
| - | // now move to the correct column |
| - | strcat(cm2, "\r"); // start at col 0 |
| - | // just send out orignal source char to get to correct place |
| - | screenp = &screen[row * columns]; // start of screen line |
| - | strncat(cm2, screenp, col); |
| - | |
| - | //----- 3. Try some other way of moving cursor |
| - | //--------------------------------------------- |
| - | // pick the shortest cursor motion to send out |
| - | cm= cm1; |
| - | if (strlen(cm2) < strlen(cm)) { |
| - | cm= cm2; |
| - | } /* else if (strlen(cm3) < strlen(cm)) { |
| - | cm= cm3; |
| - | } */ |
| + | #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR |
| + | place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE); |
| + | last_row = crow; |
| + | #else |
| + | place_cursor(crow, ccol, FALSE); |
| #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ | |
| - | pc0: |
| - | l= strlen(cm); |
| - | if (l) write(1, cm, l); // move the cursor |
| + | |
| + | if (offset != old_offset) |
| + | old_offset = offset; |
| } | |
| - | //----- Erase from cursor to end of line ----------------------- |
| - | static void clear_to_eol() |
| - | { |
| - | write(1, Ceol, strlen(Ceol)); // Erase from cursor to end of line |
| - | } |
| - | |
| - | //----- Erase from cursor to end of screen ----------------------- |
| - | static void clear_to_eos() |
| - | { |
| - | write(1, Ceos, strlen(Ceos)); // Erase from cursor to end of screen |
| - | } |
| - | |
| - | //----- Start standout mode ------------------------------------ |
| - | static void standout_start() // send "start reverse video" sequence |
| - | { |
| - | write(1, SOs, strlen(SOs)); // Start reverse video mode |
| - | } |
| - | |
| - | //----- End standout mode -------------------------------------- |
| - | static void standout_end() // send "end reverse video" sequence |
| - | { |
| - | write(1, SOn, strlen(SOn)); // End reverse video mode |
| - | } |
| + | //--------------------------------------------------------------------- |
| + | //----- the Ascii Chart ----------------------------------------------- |
| + | // |
| + | // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel |
| + | // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si |
| + | // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb |
| + | // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us |
| + | // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 ' |
| + | // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f / |
| + | // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7 |
| + | // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ? |
| + | // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G |
| + | // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O |
| + | // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W |
| + | // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _ |
| + | // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g |
| + | // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o |
| + | // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w |
| + | // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del |
| + | //--------------------------------------------------------------------- |
| - | //----- Flash the screen -------------------------------------- |
| - | static void flash(int h) |
| + | //----- Execute a Vi Command ----------------------------------- |
| + | static void do_cmd(Byte c) |
| { | |
| - | standout_start(); // send "start reverse video" sequence |
| - | redraw(TRUE); |
| - | (void) mysleep(h); |
| - | standout_end(); // send "end reverse video" sequence |
| - | redraw(TRUE); |
| - | } |
| + | Byte c1, *p, *q, *msg, buf[9], *save_dot; |
| + | int cnt, i, j, dir, yf; |
| - | static void beep() |
| - | { |
| - | write(1, bell, strlen(bell)); // send out a bell character |
| - | } |
| + | c1 = c; // quiet the compiler |
| + | cnt = yf = dir = 0; // quiet the compiler |
| + | p = q = save_dot = msg = buf; // quiet the compiler |
| + | memset(buf, '\0', 9); // clear buf |
| - | static void indicate_error(char c) |
| - | { |
| - | #ifdef CONFIG_FEATURE_VI_CRASHME |
| - | if (crashme > 0) |
| - | return; // generate a random command |
| - | #endif /* CONFIG_FEATURE_VI_CRASHME */ |
| - | if (err_method == 0) { |
| - | beep(); |
| - | } else { |
| - | flash(10); |
| + | /* if this is a cursor key, skip these checks */ |
| + | switch (c) { |
| + | case VI_K_UP: |
| + | case VI_K_DOWN: |
| + | case VI_K_LEFT: |
| + | case VI_K_RIGHT: |
| + | case VI_K_HOME: |
| + | case VI_K_END: |
| + | case VI_K_PAGEUP: |
| + | case VI_K_PAGEDOWN: |
| + | goto key_cmd_mode; |
| } | |
| - | } |
| - | |
| - | //----- Screen[] Routines -------------------------------------- |
| - | //----- Erase the Screen[] memory ------------------------------ |
| - | static void screen_erase() |
| - | { |
| - | memset(screen, ' ', screensize); // clear new screen |
| - | } |
| - | |
| - | //----- Draw the status line at bottom of the screen ------------- |
| - | static void show_status_line(void) |
| - | { |
| - | static int last_cksum; |
| - | int l, cnt, cksum; |
| - | cnt = strlen((char *) status_buffer); |
| - | for (cksum= l= 0; l < cnt; l++) { cksum += (int)(status_buffer[l]); } |
| - | // don't write the status line unless it changes |
| - | if (cnt > 0 && last_cksum != cksum) { |
| - | last_cksum= cksum; // remember if we have seen this line |
| - | place_cursor(rows - 1, 0, FALSE); // put cursor on status line |
| - | write(1, status_buffer, cnt); |
| - | clear_to_eol(); |
| - | place_cursor(crow, ccol, FALSE); // put cursor back in correct place |
| + | if (cmd_mode == 2) { |
| + | // we are 'R'eplacing the current *dot with new char |
| + | if (*dot == '\n') { |
| + | // don't Replace past E-o-l |
| + | cmd_mode = 1; // convert to insert |
| + | } else { |
| + | if (1 <= c && c <= 127) { // only ASCII chars |
| + | if (c != 27) |
| + | dot = yank_delete(dot, dot, 0, YANKDEL); // delete char |
| + | dot = char_insert(dot, c); // insert new char |
| + | } |
| + | goto dc1; |
| + | } |
| } | |
| - | } |
| - | |
| - | //----- format the status buffer, the bottom line of screen ------ |
| - | // print status buffer, with STANDOUT mode |
| - | static void psbs(char *format, ...) |
| - | { |
| - | va_list args; |
| - | |
| - | va_start(args, format); |
| - | strcpy((char *) status_buffer, SOs); // Terminal standout mode on |
| - | vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, |
| - | args); |
| - | strcat((char *) status_buffer, SOn); // Terminal standout mode off |
| - | va_end(args); |
| - | |
| - | return; |
| - | } |
| - | |
| - | // print status buffer |
| - | static void psb(char *format, ...) |
| - | { |
| - | va_list args; |
| - | |
| - | va_start(args, format); |
| - | vsprintf((char *) status_buffer, format, args); |
| - | va_end(args); |
| - | return; |
| - | } |
| - | |
| - | static void ni(Byte * s) // display messages |
| - | { |
| - | Byte buf[BUFSIZ]; |
| - | |
| - | print_literal(buf, s); |
| - | psbs("\'%s\' is not implemented", buf); |
| - | } |
| - | |
| - | static void edit_status(void) // show file status on status line |
| - | { |
| - | int cur, tot, percent; |
| - | |
| - | cur = count_lines(text, dot); |
| - | tot = count_lines(text, end - 1); |
| - | // current line percent |
| - | // ------------- ~~ ---------- |
| - | // total lines 100 |
| - | if (tot > 0) { |
| - | percent = (100 * cur) / tot; |
| - | } else { |
| - | cur = tot = 0; |
| - | percent = 100; |
| + | if (cmd_mode == 1) { |
| + | // hitting "Insert" twice means "R" replace mode |
| + | if (c == VI_K_INSERT) goto dc5; |
| + | // insert the char c at "dot" |
| + | if (1 <= c && c <= 127) { |
| + | dot = char_insert(dot, c); // only ASCII chars |
| + | } |
| + | goto dc1; |
| } | |
| - | psb("\"%s\"" |
| - | #ifdef CONFIG_FEATURE_VI_READONLY |
| - | "%s" |
| - | #endif /* CONFIG_FEATURE_VI_READONLY */ |
| - | "%s line %d of %d --%d%%--", |
| - | (cfn != 0 ? (char *) cfn : "No file"), |
| + | |
| + | key_cmd_mode: |
| + | switch (c) { |
| + | //case 0x01: // soh |
| + | //case 0x09: // ht |
| + | //case 0x0b: // vt |
| + | //case 0x0e: // so |
| + | //case 0x0f: // si |
| + | //case 0x10: // dle |
| + | //case 0x11: // dc1 |
| + | //case 0x13: // dc3 |
| + | #ifdef CONFIG_FEATURE_VI_CRASHME |
| + | case 0x14: // dc4 ctrl-T |
| + | crashme = (crashme == 0) ? 1 : 0; |
| + | break; |
| + | #endif /* CONFIG_FEATURE_VI_CRASHME */ |
| + | //case 0x16: // syn |
| + | //case 0x17: // etb |
| + | //case 0x18: // can |
| + | //case 0x1c: // fs |
| + | //case 0x1d: // gs |
| + | //case 0x1e: // rs |
| + | //case 0x1f: // us |
| + | //case '!': // !- |
| + | //case '#': // #- |
| + | //case '&': // &- |
| + | //case '(': // (- |
| + | //case ')': // )- |
| + | //case '*': // *- |
| + | //case ',': // ,- |
| + | //case '=': // =- |
| + | //case '@': // @- |
| + | //case 'F': // F- |
| + | //case 'K': // K- |
| + | //case 'Q': // Q- |
| + | //case 'S': // S- |
| + | //case 'T': // T- |
| + | //case 'V': // V- |
| + | //case '[': // [- |
| + | //case '\\': // \- |
| + | //case ']': // ]- |
| + | //case '_': // _- |
| + | //case '`': // `- |
| + | //case 'g': // g- |
| + | //case 'u': // u- FIXME- there is no undo |
| + | //case 'v': // v- |
| + | default: // unrecognised command |
| + | buf[0] = c; |
| + | buf[1] = '\0'; |
| + | if (c <= ' ') { |
| + | buf[0] = '^'; |
| + | buf[1] = c + '@'; |
| + | buf[2] = '\0'; |
| + | } |
| + | ni((Byte *) buf); |
| + | end_cmd_q(); // stop adding to q |
| + | case 0x00: // nul- ignore |
| + | break; |
| + | case 2: // ctrl-B scroll up full screen |
| + | case VI_K_PAGEUP: // Cursor Key Page Up |
| + | dot_scroll(rows - 2, -1); |
| + | break; |
| + | #ifdef CONFIG_FEATURE_VI_USE_SIGNALS |
| + | case 0x03: // ctrl-C interrupt |
| + | longjmp(restart, 1); |
| + | break; |
| + | case 26: // ctrl-Z suspend |
| + | suspend_sig(SIGTSTP); |
| + | break; |
| + | #endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ |
| + | case 4: // ctrl-D scroll down half screen |
| + | dot_scroll((rows - 2) / 2, 1); |
| + | break; |
| + | case 5: // ctrl-E scroll down one line |
| + | dot_scroll(1, 1); |
| + | break; |
| + | case 6: // ctrl-F scroll down full screen |
| + | case VI_K_PAGEDOWN: // Cursor Key Page Down |
| + | dot_scroll(rows - 2, 1); |
| + | break; |
| + | case 7: // ctrl-G show current status |
| + | edit_status(); |
| + | break; |
| + | case 'h': // h- move left |
| + | case VI_K_LEFT: // cursor key Left |
| + | case 8: // ctrl-H- move left (This may be ERASE char) |
| + | case 127: // DEL- move left (This may be ERASE char) |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd(c); |
| + | } // repeat cnt |
| + | dot_left(); |
| + | break; |
| + | case 10: // Newline ^J |
| + | case 'j': // j- goto next line, same col |
| + | case VI_K_DOWN: // cursor key Down |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd(c); |
| + | } // repeat cnt |
| + | dot_next(); // go to next B-o-l |
| + | dot = move_to_col(dot, ccol + offset); // try stay in same col |
| + | break; |
| + | case 12: // ctrl-L force redraw whole screen |
| + | case 18: // ctrl-R force redraw |
| + | place_cursor(0, 0, FALSE); // put cursor in correct place |
| + | clear_to_eos(); // tel terminal to erase display |
| + | (void) mysleep(10); |
| + | screen_erase(); // erase the internal screen buffer |
| + | refresh(TRUE); // this will redraw the entire display |
| + | break; |
| + | case 13: // Carriage Return ^M |
| + | case '+': // +- goto next line |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd(c); |
| + | } // repeat cnt |
| + | dot_next(); |
| + | dot_skip_over_ws(); |
| + | break; |
| + | case 21: // ctrl-U scroll up half screen |
| + | dot_scroll((rows - 2) / 2, -1); |
| + | break; |
| + | case 25: // ctrl-Y scroll up one line |
| + | dot_scroll(1, -1); |
| + | break; |
| + | case 27: // esc |
| + | if (cmd_mode == 0) |
| + | indicate_error(c); |
| + | cmd_mode = 0; // stop insrting |
| + | end_cmd_q(); |
| + | *status_buffer = '\0'; // clear status buffer |
| + | break; |
| + | case ' ': // move right |
| + | case 'l': // move right |
| + | case VI_K_RIGHT: // Cursor Key Right |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd(c); |
| + | } // repeat cnt |
| + | dot_right(); |
| + | break; |
| + | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| + | case '"': // "- name a register to use for Delete/Yank |
| + | c1 = get_one_char(); |
| + | c1 = tolower(c1); |
| + | if (islower(c1)) { |
| + | YDreg = c1 - 'a'; |
| + | } else { |
| + | indicate_error(c); |
| + | } |
| + | break; |
| + | case '\'': // '- goto a specific mark |
| + | c1 = get_one_char(); |
| + | c1 = tolower(c1); |
| + | if (islower(c1)) { |
| + | c1 = c1 - 'a'; |
| + | // get the b-o-l |
| + | q = mark[(int) c1]; |
| + | if (text <= q && q < end) { |
| + | dot = q; |
| + | dot_begin(); // go to B-o-l |
| + | dot_skip_over_ws(); |
| + | } |
| + | } else if (c1 == '\'') { // goto previous context |
| + | dot = swap_context(dot); // swap current and previous context |
| + | dot_begin(); // go to B-o-l |
| + | dot_skip_over_ws(); |
| + | } else { |
| + | indicate_error(c); |
| + | } |
| + | break; |
| + | case 'm': // m- Mark a line |
| + | // this is really stupid. If there are any inserts or deletes |
| + | // between text[0] and dot then this mark will not point to the |
| + | // correct location! It could be off by many lines! |
| + | // Well..., at least its quick and dirty. |
| + | c1 = get_one_char(); |
| + | c1 = tolower(c1); |
| + | if (islower(c1)) { |
| + | c1 = c1 - 'a'; |
| + | // remember the line |
| + | mark[(int) c1] = dot; |
| + | } else { |
| + | indicate_error(c); |
| + | } |
| + | break; |
| + | case 'P': // P- Put register before |
| + | case 'p': // p- put register after |
| + | p = reg[YDreg]; |
| + | if (p == 0) { |
| + | psbs("Nothing in register %c", what_reg()); |
| + | break; |
| + | } |
| + | // are we putting whole lines or strings |
| + | if (strchr((char *) p, '\n') != NULL) { |
| + | if (c == 'P') { |
| + | dot_begin(); // putting lines- Put above |
| + | } |
| + | if (c == 'p') { |
| + | // are we putting after very last line? |
| + | if (end_line(dot) == (end - 1)) { |
| + | dot = end; // force dot to end of text[] |
| + | } else { |
| + | dot_next(); // next line, then put before |
| + | } |
| + | } |
| + | } else { |
| + | if (c == 'p') |
| + | dot_right(); // move to right, can move to NL |
| + | } |
| + | dot = string_insert(dot, p); // insert the string |
| + | end_cmd_q(); // stop adding to q |
| + | break; |
| + | case 'U': // U- Undo; replace current line with original version |
| + | if (reg[Ureg] != 0) { |
| + | p = begin_line(dot); |
| + | q = end_line(dot); |
| + | p = text_hole_delete(p, q); // delete cur line |
| + | p = string_insert(p, reg[Ureg]); // insert orig line |
| + | dot = p; |
| + | dot_skip_over_ws(); |
| + | } |
| + | break; |
| + | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| + | case '$': // $- goto end of line |
| + | case VI_K_END: // Cursor Key End |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd(c); |
| + | } // repeat cnt |
| + | dot = end_line(dot + 1); |
| + | break; |
| + | case '%': // %- find matching char of pair () [] {} |
| + | for (q = dot; q < end && *q != '\n'; q++) { |
| + | if (strchr("()[]{}", *q) != NULL) { |
| + | // we found half of a pair |
| + | p = find_pair(q, *q); |
| + | if (p == NULL) { |
| + | indicate_error(c); |
| + | } else { |
| + | dot = p; |
| + | } |
| + | break; |
| + | } |
| + | } |
| + | if (*q == '\n') |
| + | indicate_error(c); |
| + | break; |
| + | case 'f': // f- forward to a user specified char |
| + | last_forward_char = get_one_char(); // get the search char |
| + | // |
| + | // dont seperate these two commands. 'f' depends on ';' |
| + | // |
| + | //**** fall thru to ... 'i' |
| + | case ';': // ;- look at rest of line for last forward char |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd(';'); |
| + | } // repeat cnt |
| + | if (last_forward_char == 0) break; |
| + | q = dot + 1; |
| + | while (q < end - 1 && *q != '\n' && *q != last_forward_char) { |
| + | q++; |
| + | } |
| + | if (*q == last_forward_char) |
| + | dot = q; |
| + | break; |
| + | case '-': // -- goto prev line |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd(c); |
| + | } // repeat cnt |
| + | dot_prev(); |
| + | dot_skip_over_ws(); |
| + | break; |
| + | #ifdef CONFIG_FEATURE_VI_DOT_CMD |
| + | case '.': // .- repeat the last modifying command |
| + | // Stuff the last_modifying_cmd back into stdin |
| + | // and let it be re-executed. |
| + | if (last_modifying_cmd != 0) { |
| + | ioq = ioq_start = (Byte *) xstrdup((char *) last_modifying_cmd); |
| + | } |
| + | break; |
| + | #endif /* CONFIG_FEATURE_VI_DOT_CMD */ |
| + | #ifdef CONFIG_FEATURE_VI_SEARCH |
| + | case '?': // /- search for a pattern |
| + | case '/': // /- search for a pattern |
| + | buf[0] = c; |
| + | buf[1] = '\0'; |
| + | q = get_input_line(buf); // get input line- use "status line" |
| + | if (strlen((char *) q) == 1) |
| + | goto dc3; // if no pat re-use old pat |
| + | if (strlen((char *) q) > 1) { // new pat- save it and find |
| + | // there is a new pat |
| + | if (last_search_pattern != 0) { |
| + | free(last_search_pattern); |
| + | } |
| + | last_search_pattern = (Byte *) xstrdup((char *) q); |
| + | goto dc3; // now find the pattern |
| + | } |
| + | // user changed mind and erased the "/"- do nothing |
| + | break; |
| + | case 'N': // N- backward search for last pattern |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd(c); |
| + | } // repeat cnt |
| + | dir = BACK; // assume BACKWARD search |
| + | p = dot - 1; |
| + | if (last_search_pattern[0] == '?') { |
| + | dir = FORWARD; |
| + | p = dot + 1; |
| + | } |
| + | goto dc4; // now search for pattern |
| + | break; |
| + | case 'n': // n- repeat search for last pattern |
| + | // search rest of text[] starting at next char |
| + | // if search fails return orignal "p" not the "p+1" address |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd(c); |
| + | } // repeat cnt |
| + | dc3: |
| + | if (last_search_pattern == 0) { |
| + | msg = (Byte *) "No previous regular expression"; |
| + | goto dc2; |
| + | } |
| + | if (last_search_pattern[0] == '/') { |
| + | dir = FORWARD; // assume FORWARD search |
| + | p = dot + 1; |
| + | } |
| + | if (last_search_pattern[0] == '?') { |
| + | dir = BACK; |
| + | p = dot - 1; |
| + | } |
| + | dc4: |
| + | q = char_search(p, last_search_pattern + 1, dir, FULL); |
| + | if (q != NULL) { |
| + | dot = q; // good search, update "dot" |
| + | msg = (Byte *) ""; |
| + | goto dc2; |
| + | } |
| + | // no pattern found between "dot" and "end"- continue at top |
| + | p = text; |
| + | if (dir == BACK) { |
| + | p = end - 1; |
| + | } |
| + | q = char_search(p, last_search_pattern + 1, dir, FULL); |
| + | if (q != NULL) { // found something |
| + | dot = q; // found new pattern- goto it |
| + | msg = (Byte *) "search hit BOTTOM, continuing at TOP"; |
| + | if (dir == BACK) { |
| + | msg = (Byte *) "search hit TOP, continuing at BOTTOM"; |
| + | } |
| + | } else { |
| + | msg = (Byte *) "Pattern not found"; |
| + | } |
| + | dc2: |
| + | psbs("%s", msg); |
| + | break; |
| + | case '{': // {- move backward paragraph |
| + | q = char_search(dot, (Byte *) "\n\n", BACK, FULL); |
| + | if (q != NULL) { // found blank line |
| + | dot = next_line(q); // move to next blank line |
| + | } |
| + | break; |
| + | case '}': // }- move forward paragraph |
| + | q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL); |
| + | if (q != NULL) { // found blank line |
| + | dot = next_line(q); // move to next blank line |
| + | } |
| + | break; |
| + | #endif /* CONFIG_FEATURE_VI_SEARCH */ |
| + | case '0': // 0- goto begining of line |
| + | case '1': // 1- |
| + | case '2': // 2- |
| + | case '3': // 3- |
| + | case '4': // 4- |
| + | case '5': // 5- |
| + | case '6': // 6- |
| + | case '7': // 7- |
| + | case '8': // 8- |
| + | case '9': // 9- |
| + | if (c == '0' && cmdcnt < 1) { |
| + | dot_begin(); // this was a standalone zero |
| + | } else { |
| + | cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number |
| + | } |
| + | break; |
| + | case ':': // :- the colon mode commands |
| + | p = get_input_line((Byte *) ":"); // get input line- use "status line" |
| + | #ifdef CONFIG_FEATURE_VI_COLON |
| + | colon(p); // execute the command |
| + | #else /* CONFIG_FEATURE_VI_COLON */ |
| + | if (*p == ':') |
| + | p++; // move past the ':' |
| + | cnt = strlen((char *) p); |
| + | if (cnt <= 0) |
| + | break; |
| + | if (strncasecmp((char *) p, "quit", cnt) == 0 || |
| + | strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines |
| + | if (file_modified && p[1] != '!') { |
| + | psbs("No write since last change (:quit! overrides)"); |
| + | } else { |
| + | editing = 0; |
| + | } |
| + | } else if (strncasecmp((char *) p, "write", cnt) == 0 || |
| + | strncasecmp((char *) p, "wq", cnt) == 0 || |
| + | strncasecmp((char *) p, "x", cnt) == 0) { |
| + | cnt = file_write(cfn, text, end - 1); |
| + | file_modified = FALSE; |
| + | psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt); |
| + | if (p[0] == 'x' || p[1] == 'q') { |
| + | editing = 0; |
| + | } |
| + | } else if (strncasecmp((char *) p, "file", cnt) == 0 ) { |
| + | edit_status(); // show current file status |
| + | } else if (sscanf((char *) p, "%d", &j) > 0) { |
| + | dot = find_line(j); // go to line # j |
| + | dot_skip_over_ws(); |
| + | } else { // unrecognised cmd |
| + | ni((Byte *) p); |
| + | } |
| + | #endif /* CONFIG_FEATURE_VI_COLON */ |
| + | break; |
| + | case '<': // <- Left shift something |
| + | case '>': // >- Right shift something |
| + | cnt = count_lines(text, dot); // remember what line we are on |
| + | c1 = get_one_char(); // get the type of thing to delete |
| + | find_range(&p, &q, c1); |
| + | (void) yank_delete(p, q, 1, YANKONLY); // save copy before change |
| + | p = begin_line(p); |
| + | q = end_line(q); |
| + | i = count_lines(p, q); // # of lines we are shifting |
| + | for ( ; i > 0; i--, p = next_line(p)) { |
| + | if (c == '<') { |
| + | // shift left- remove tab or 8 spaces |
| + | if (*p == '\t') { |
| + | // shrink buffer 1 char |
| + | (void) text_hole_delete(p, p); |
| + | } else if (*p == ' ') { |
| + | // we should be calculating columns, not just SPACE |
| + | for (j = 0; *p == ' ' && j < tabstop; j++) { |
| + | (void) text_hole_delete(p, p); |
| + | } |
| + | } |
| + | } else if (c == '>') { |
| + | // shift right -- add tab or 8 spaces |
| + | (void) char_insert(p, '\t'); |
| + | } |
| + | } |
| + | dot = find_line(cnt); // what line were we on |
| + | dot_skip_over_ws(); |
| + | end_cmd_q(); // stop adding to q |
| + | break; |
| + | case 'A': // A- append at e-o-l |
| + | dot_end(); // go to e-o-l |
| + | //**** fall thru to ... 'a' |
| + | case 'a': // a- append after current char |
| + | if (*dot != '\n') |
| + | dot++; |
| + | goto dc_i; |
| + | break; |
| + | case 'B': // B- back a blank-delimited Word |
| + | case 'E': // E- end of a blank-delimited word |
| + | case 'W': // W- forward a blank-delimited word |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd(c); |
| + | } // repeat cnt |
| + | dir = FORWARD; |
| + | if (c == 'B') |
| + | dir = BACK; |
| + | if (c == 'W' || isspace(dot[dir])) { |
| + | dot = skip_thing(dot, 1, dir, S_TO_WS); |
| + | dot = skip_thing(dot, 2, dir, S_OVER_WS); |
| + | } |
| + | if (c != 'W') |
| + | dot = skip_thing(dot, 1, dir, S_BEFORE_WS); |
| + | break; |
| + | case 'C': // C- Change to e-o-l |
| + | case 'D': // D- delete to e-o-l |
| + | save_dot = dot; |
| + | dot = dollar_line(dot); // move to before NL |
| + | // copy text into a register and delete |
| + | dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l |
| + | if (c == 'C') |
| + | goto dc_i; // start inserting |
| + | #ifdef CONFIG_FEATURE_VI_DOT_CMD |
| + | if (c == 'D') |
| + | end_cmd_q(); // stop adding to q |
| + | #endif /* CONFIG_FEATURE_VI_DOT_CMD */ |
| + | break; |
| + | case 'G': // G- goto to a line number (default= E-O-F) |
| + | dot = end - 1; // assume E-O-F |
| + | if (cmdcnt > 0) { |
| + | dot = find_line(cmdcnt); // what line is #cmdcnt |
| + | } |
| + | dot_skip_over_ws(); |
| + | break; |
| + | case 'H': // H- goto top line on screen |
| + | dot = screenbegin; |
| + | if (cmdcnt > (rows - 1)) { |
| + | cmdcnt = (rows - 1); |
| + | } |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd('+'); |
| + | } // repeat cnt |
| + | dot_skip_over_ws(); |
| + | break; |
| + | case 'I': // I- insert before first non-blank |
| + | dot_begin(); // 0 |
| + | dot_skip_over_ws(); |
| + | //**** fall thru to ... 'i' |
| + | case 'i': // i- insert before current char |
| + | case VI_K_INSERT: // Cursor Key Insert |
| + | dc_i: |
| + | cmd_mode = 1; // start insrting |
| + | psb("-- Insert --"); |
| + | break; |
| + | case 'J': // J- join current and next lines together |
| + | if (cmdcnt-- > 2) { |
| + | do_cmd(c); |
| + | } // repeat cnt |
| + | dot_end(); // move to NL |
| + | if (dot < end - 1) { // make sure not last char in text[] |
| + | *dot++ = ' '; // replace NL with space |
| + | while (isblnk(*dot)) { // delete leading WS |
| + | dot_delete(); |
| + | } |
| + | } |
| + | end_cmd_q(); // stop adding to q |
| + | break; |
| + | case 'L': // L- goto bottom line on screen |
| + | dot = end_screen(); |
| + | if (cmdcnt > (rows - 1)) { |
| + | cmdcnt = (rows - 1); |
| + | } |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd('-'); |
| + | } // repeat cnt |
| + | dot_begin(); |
| + | dot_skip_over_ws(); |
| + | break; |
| + | case 'M': // M- goto middle line on screen |
| + | dot = screenbegin; |
| + | for (cnt = 0; cnt < (rows-1) / 2; cnt++) |
| + | dot = next_line(dot); |
| + | break; |
| + | case 'O': // O- open a empty line above |
| + | // 0i\n ESC -i |
| + | p = begin_line(dot); |
| + | if (p[-1] == '\n') { |
| + | dot_prev(); |
| + | case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..." |
| + | dot_end(); |
| + | dot = char_insert(dot, '\n'); |
| + | } else { |
| + | dot_begin(); // 0 |
| + | dot = char_insert(dot, '\n'); // i\n ESC |
| + | dot_prev(); // - |
| + | } |
| + | goto dc_i; |
| + | break; |
| + | case 'R': // R- continuous Replace char |
| + | dc5: |
| + | cmd_mode = 2; |
| + | psb("-- Replace --"); |
| + | break; |
| + | case 'X': // X- delete char before dot |
| + | case 'x': // x- delete the current char |
| + | case 's': // s- substitute the current char |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd(c); |
| + | } // repeat cnt |
| + | dir = 0; |
| + | if (c == 'X') |
| + | dir = -1; |
| + | if (dot[dir] != '\n') { |
| + | if (c == 'X') |
| + | dot--; // delete prev char |
| + | dot = yank_delete(dot, dot, 0, YANKDEL); // delete char |
| + | } |
| + | if (c == 's') |
| + | goto dc_i; // start insrting |
| + | end_cmd_q(); // stop adding to q |
| + | break; |
| + | case 'Z': // Z- if modified, {write}; exit |
| + | // ZZ means to save file (if necessary), then exit |
| + | c1 = get_one_char(); |
| + | if (c1 != 'Z') { |
| + | indicate_error(c); |
| + | break; |
| + | } |
| + | if (file_modified |
| #ifdef CONFIG_FEATURE_VI_READONLY | |
| - | ((vi_readonly || readonly) ? " [Read only]" : ""), |
| + | && ! vi_readonly |
| + | && ! readonly |
| #endif /* CONFIG_FEATURE_VI_READONLY */ | |
| - | (file_modified ? " [modified]" : ""), |
| - | cur, tot, percent); |
| - | } |
| - | |
| - | //----- Force refresh of all Lines ----------------------------- |
| - | static void redraw(int full_screen) |
| - | { |
| - | place_cursor(0, 0, FALSE); // put cursor in correct place |
| - | clear_to_eos(); // tel terminal to erase display |
| - | screen_erase(); // erase the internal screen buffer |
| - | refresh(full_screen); // this will redraw the entire display |
| - | } |
| - | |
| - | //----- Format a text[] line into a buffer --------------------- |
| - | static void format_line(Byte *dest, Byte *src, int li) |
| - | { |
| - | int co; |
| - | Byte c; |
| - | |
| - | for (co= 0; co < MAX_SCR_COLS; co++) { |
| - | c= ' '; // assume blank |
| - | if (li > 0 && co == 0) { |
| - | c = '~'; // not first line, assume Tilde |
| - | } |
| - | // are there chars in text[] and have we gone past the end |
| - | if (text < end && src < end) { |
| - | c = *src++; |
| + | ) { |
| + | cnt = file_write(cfn, text, end - 1); |
| + | if (cnt == (end - 1 - text + 1)) { |
| + | editing = 0; |
| + | } |
| + | } else { |
| + | editing = 0; |
| } | |
| - | if (c == '\n') |
| + | break; |
| + | case '^': // ^- move to first non-blank on line |
| + | dot_begin(); |
| + | dot_skip_over_ws(); |
| + | break; |
| + | case 'b': // b- back a word |
| + | case 'e': // e- end of word |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd(c); |
| + | } // repeat cnt |
| + | dir = FORWARD; |
| + | if (c == 'b') |
| + | dir = BACK; |
| + | if ((dot + dir) < text || (dot + dir) > end - 1) |
| break; | |
| - | if (c < ' ' || c > '~') { |
| - | if (c == '\t') { |
| - | c = ' '; |
| - | // co % 8 != 7 |
| - | for (; (co % tabstop) != (tabstop - 1); co++) { |
| - | dest[co] = c; |
| + | dot += dir; |
| + | if (isspace(*dot)) { |
| + | dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS); |
| + | } |
| + | if (isalnum(*dot) || *dot == '_') { |
| + | dot = skip_thing(dot, 1, dir, S_END_ALNUM); |
| + | } else if (ispunct(*dot)) { |
| + | dot = skip_thing(dot, 1, dir, S_END_PUNCT); |
| + | } |
| + | break; |
| + | case 'c': // c- change something |
| + | case 'd': // d- delete something |
| + | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| + | case 'y': // y- yank something |
| + | case 'Y': // Y- Yank a line |
| + | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| + | yf = YANKDEL; // assume either "c" or "d" |
| + | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| + | if (c == 'y' || c == 'Y') |
| + | yf = YANKONLY; |
| + | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| + | c1 = 'y'; |
| + | if (c != 'Y') |
| + | c1 = get_one_char(); // get the type of thing to delete |
| + | find_range(&p, &q, c1); |
| + | if (c1 == 27) { // ESC- user changed mind and wants out |
| + | c = c1 = 27; // Escape- do nothing |
| + | } else if (strchr("wW", c1)) { |
| + | if (c == 'c') { |
| + | // don't include trailing WS as part of word |
| + | while (isblnk(*q)) { |
| + | if (q <= text || q[-1] == '\n') |
| + | break; |
| + | q--; |
| + | } |
| + | } |
| + | dot = yank_delete(p, q, 0, yf); // delete word |
| + | } else if (strchr("^0bBeEft$", c1)) { |
| + | // single line copy text into a register and delete |
| + | dot = yank_delete(p, q, 0, yf); // delete word |
| + | } else if (strchr("cdykjHL%+-{}\r\n", c1)) { |
| + | // multiple line copy text into a register and delete |
| + | dot = yank_delete(p, q, 1, yf); // delete lines |
| + | if (c == 'c') { |
| + | dot = char_insert(dot, '\n'); |
| + | // on the last line of file don't move to prev line |
| + | if (dot != (end-1)) { |
| + | dot_prev(); |
| } | |
| - | } else { |
| - | dest[co++] = '^'; |
| - | c |= '@'; // make it visible |
| - | c &= 0x7f; // get rid of hi bit |
| + | } else if (c == 'd') { |
| + | dot_begin(); |
| + | dot_skip_over_ws(); |
| } | |
| + | } else { |
| + | // could not recognize object |
| + | c = c1 = 27; // error- |
| + | indicate_error(c); |
| } | |
| - | // the co++ is done here so that the column will |
| - | // not be overwritten when we blank-out the rest of line |
| - | dest[co] = c; |
| - | if (src >= end) |
| - | break; |
| - | } |
| - | } |
| - | |
| - | //----- Refresh the changed screen lines ----------------------- |
| - | // Copy the source line from text[] into the buffer and note |
| - | // if the current screenline is different from the new buffer. |
| - | // If they differ then that line needs redrawing on the terminal. |
| - | // |
| - | static void refresh(int full_screen) |
| - | { |
| - | static int old_offset; |
| - | int li, changed; |
| - | Byte buf[MAX_SCR_COLS]; |
| - | Byte *tp, *sp; // pointer into text[] and screen[] |
| - | #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR |
| - | int last_li= -2; // last line that changed- for optimizing cursor movement |
| - | #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ |
| - | |
| - | #ifdef CONFIG_FEATURE_VI_WIN_RESIZE |
| - | window_size_get(0); |
| - | #endif /* CONFIG_FEATURE_VI_WIN_RESIZE */ |
| - | sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot") |
| - | tp = screenbegin; // index into text[] of top line |
| - | |
| - | // compare text[] to screen[] and mark screen[] lines that need updating |
| - | for (li = 0; li < rows - 1; li++) { |
| - | int cs, ce; // column start & end |
| - | memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer |
| - | buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer |
| - | // format current text line into buf |
| - | format_line(buf, tp, li); |
| - | |
| - | // skip to the end of the current text[] line |
| - | while (tp < end && *tp++ != '\n') /*no-op*/ ; |
| - | |
| - | // see if there are any changes between vitual screen and buf |
| - | changed = FALSE; // assume no change |
| - | cs= 0; |
| - | ce= columns-1; |
| - | sp = &screen[li * columns]; // start of screen line |
| - | if (full_screen) { |
| - | // force re-draw of every single column from 0 - columns-1 |
| - | goto re0; |
| - | } |
| - | // compare newly formatted buffer with virtual screen |
| - | // look forward for first difference between buf and screen |
| - | for ( ; cs <= ce; cs++) { |
| - | if (buf[cs + offset] != sp[cs]) { |
| - | changed = TRUE; // mark for redraw |
| - | break; |
| + | if (c1 != 27) { |
| + | // if CHANGING, not deleting, start inserting after the delete |
| + | if (c == 'c') { |
| + | strcpy((char *) buf, "Change"); |
| + | goto dc_i; // start inserting |
| } | |
| - | } |
| - | |
| - | // look backward for last difference between buf and screen |
| - | for ( ; ce >= cs; ce--) { |
| - | if (buf[ce + offset] != sp[ce]) { |
| - | changed = TRUE; // mark for redraw |
| - | break; |
| + | if (c == 'd') { |
| + | strcpy((char *) buf, "Delete"); |
| + | } |
| + | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| + | if (c == 'y' || c == 'Y') { |
| + | strcpy((char *) buf, "Yank"); |
| + | } |
| + | p = reg[YDreg]; |
| + | q = p + strlen((char *) p); |
| + | for (cnt = 0; p <= q; p++) { |
| + | if (*p == '\n') |
| + | cnt++; |
| } | |
| + | psb("%s %d lines (%d chars) using [%c]", |
| + | buf, cnt, strlen((char *) reg[YDreg]), what_reg()); |
| + | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| + | end_cmd_q(); // stop adding to q |
| } | |
| - | // now, cs is index of first diff, and ce is index of last diff |
| - | |
| - | // if horz offset has changed, force a redraw |
| - | if (offset != old_offset) { |
| - | re0: |
| - | changed = TRUE; |
| + | break; |
| + | case 'k': // k- goto prev line, same col |
| + | case VI_K_UP: // cursor key Up |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd(c); |
| + | } // repeat cnt |
| + | dot_prev(); |
| + | dot = move_to_col(dot, ccol + offset); // try stay in same col |
| + | break; |
| + | case 'r': // r- replace the current char with user input |
| + | c1 = get_one_char(); // get the replacement char |
| + | if (*dot != '\n') { |
| + | *dot = c1; |
| + | file_modified = TRUE; // has the file been modified |
| } | |
| - | |
| - | // make a sanity check of columns indexes |
| - | if (cs < 0) cs= 0; |
| - | if (ce > columns-1) ce= columns-1; |
| - | if (cs > ce) { cs= 0; ce= columns-1; } |
| - | // is there a change between vitual screen and buf |
| - | if (changed) { |
| - | // copy changed part of buffer to virtual screen |
| - | memmove(sp+cs, buf+(cs+offset), ce-cs+1); |
| - | |
| - | // move cursor to column of first change |
| - | if (offset != old_offset) { |
| - | // opti_cur_move is still too stupid |
| - | // to handle offsets correctly |
| - | place_cursor(li, cs, FALSE); |
| - | } else { |
| - | #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR |
| - | // if this just the next line |
| - | // try to optimize cursor movement |
| - | // otherwise, use standard ESC sequence |
| - | place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE); |
| - | last_li= li; |
| - | #else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ |
| - | place_cursor(li, cs, FALSE); // use standard ESC sequence |
| - | #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ |
| - | } |
| - | |
| - | // write line out to terminal |
| - | write(1, sp+cs, ce-cs+1); |
| - | #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR |
| - | last_row = li; |
| - | #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ |
| + | end_cmd_q(); // stop adding to q |
| + | break; |
| + | case 't': // t- move to char prior to next x |
| + | last_forward_char = get_one_char(); |
| + | do_cmd(';'); |
| + | if (*dot == last_forward_char) |
| + | dot_left(); |
| + | last_forward_char= 0; |
| + | break; |
| + | case 'w': // w- forward a word |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd(c); |
| + | } // repeat cnt |
| + | if (isalnum(*dot) || *dot == '_') { // we are on ALNUM |
| + | dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM); |
| + | } else if (ispunct(*dot)) { // we are on PUNCT |
| + | dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT); |
| + | } |
| + | if (dot < end - 1) |
| + | dot++; // move over word |
| + | if (isspace(*dot)) { |
| + | dot = skip_thing(dot, 2, FORWARD, S_OVER_WS); |
| + | } |
| + | break; |
| + | case 'z': // z- |
| + | c1 = get_one_char(); // get the replacement char |
| + | cnt = 0; |
| + | if (c1 == '.') |
| + | cnt = (rows - 2) / 2; // put dot at center |
| + | if (c1 == '-') |
| + | cnt = rows - 2; // put dot at bottom |
| + | screenbegin = begin_line(dot); // start dot at top |
| + | dot_scroll(cnt, -1); |
| + | break; |
| + | case '|': // |- move to column "cmdcnt" |
| + | dot = move_to_col(dot, cmdcnt - 1); // try to move to column |
| + | break; |
| + | case '~': // ~- flip the case of letters a-z -> A-Z |
| + | if (cmdcnt-- > 1) { |
| + | do_cmd(c); |
| + | } // repeat cnt |
| + | if (islower(*dot)) { |
| + | *dot = toupper(*dot); |
| + | file_modified = TRUE; // has the file been modified |
| + | } else if (isupper(*dot)) { |
| + | *dot = tolower(*dot); |
| + | file_modified = TRUE; // has the file been modified |
| } | |
| + | dot_right(); |
| + | end_cmd_q(); // stop adding to q |
| + | break; |
| + | //----- The Cursor and Function Keys ----------------------------- |
| + | case VI_K_HOME: // Cursor Key Home |
| + | dot_begin(); |
| + | break; |
| + | // The Fn keys could point to do_macro which could translate them |
| + | case VI_K_FUN1: // Function Key F1 |
| + | case VI_K_FUN2: // Function Key F2 |
| + | case VI_K_FUN3: // Function Key F3 |
| + | case VI_K_FUN4: // Function Key F4 |
| + | case VI_K_FUN5: // Function Key F5 |
| + | case VI_K_FUN6: // Function Key F6 |
| + | case VI_K_FUN7: // Function Key F7 |
| + | case VI_K_FUN8: // Function Key F8 |
| + | case VI_K_FUN9: // Function Key F9 |
| + | case VI_K_FUN10: // Function Key F10 |
| + | case VI_K_FUN11: // Function Key F11 |
| + | case VI_K_FUN12: // Function Key F12 |
| + | break; |
| } | |
| - | #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR |
| - | place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE); |
| - | last_row = crow; |
| - | #else |
| - | place_cursor(crow, ccol, FALSE); |
| - | #endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ |
| - | |
| - | if (offset != old_offset) |
| - | old_offset = offset; |
| + | dc1: |
| + | // if text[] just became empty, add back an empty line |
| + | if (end == text) { |
| + | (void) char_insert(text, '\n'); // start empty buf with dummy line |
| + | dot = text; |
| + | } |
| + | // it is OK for dot to exactly equal to end, otherwise check dot validity |
| + | if (dot != end) { |
| + | dot = bound_dot(dot); // make sure "dot" is valid |
| + | } |
| + | #ifdef CONFIG_FEATURE_VI_YANKMARK |
| + | check_context(c); // update the current context |
| + | #endif /* CONFIG_FEATURE_VI_YANKMARK */ |
| + | |
| + | if (!isdigit(c)) |
| + | cmdcnt = 0; // cmd was not a number, reset cmdcnt |
| + | cnt = dot - begin_line(dot); |
| + | // Try to stay off of the Newline |
| + | if (*dot == '\n' && cnt > 0 && cmd_mode == 0) |
| + | dot--; |
| } | |