Changeset 3621 in MondoRescue for branches/3.3/mindi-busybox/editors/vi.c


Ignore:
Timestamp:
Dec 20, 2016, 4:07:32 PM (7 years ago)
Author:
Bruno Cornec
Message:

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

Location:
branches/3.3
Files:
1 edited
1 copied

Legend:

Unmodified
Added
Removed
  • branches/3.3/mindi-busybox/editors/vi.c

    r3232 r3621  
    1818 *  More intelligence in refresh()
    1919 *  ":r !cmd"  and  "!cmd"  to filter text through an external command
    20  *  A true "undo" facility
    2120 *  An "ex" line oriented mode- maybe using "cmdedit"
    2221 */
     
    137136//config:
    138137//config:     This is not clean but helps a lot on serial lines and such.
     138//config:config FEATURE_VI_UNDO
     139//config:   bool "Support undo command 'u'"
     140//config:   default y
     141//config:   depends on VI
     142//config:   help
     143//config:     Support the 'u' command to undo insertion, deletion, and replacement
     144//config:     of text.
     145//config:config FEATURE_VI_UNDO_QUEUE
     146//config:   bool "Enable undo operation queuing"
     147//config:   default y
     148//config:   depends on FEATURE_VI_UNDO
     149//config:   help
     150//config:     The vi undo functions can use an intermediate queue to greatly lower
     151//config:     malloc() calls and overhead. When the maximum size of this queue is
     152//config:     reached, the contents of the queue are committed to the undo stack.
     153//config:     This increases the size of the undo code and allows some undo
     154//config:     operations (especially un-typing/backspacing) to be far more useful.
     155//config:config FEATURE_VI_UNDO_QUEUE_MAX
     156//config:   int "Maximum undo character queue size"
     157//config:   default 256
     158//config:   range 32 65536
     159//config:   depends on FEATURE_VI_UNDO_QUEUE
     160//config:   help
     161//config:     This option sets the number of bytes used at runtime for the queue.
     162//config:     Smaller values will create more undo objects and reduce the amount
     163//config:     of typed or backspaced characters that are grouped into one undo
     164//config:     operation; larger values increase the potential size of each undo
     165//config:     and will generally malloc() larger objects and less frequently.
     166//config:     Unless you want more (or less) frequent "undo points" while typing,
     167//config:     you should probably leave this unchanged.
    139168
    140169//applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
     
    223252// vda: removed "aAiIs" as they switch us into insert mode
    224253// and remembering input for replay after them makes no sense
    225 static const char modifying_cmds[] = "cCdDJoOpPrRxX<>~";
     254static const char modifying_cmds[] ALIGN1 = "cCdDJoOpPrRxX<>~";
    226255#endif
    227256
     
    278307                             // [code audit says "can be 0, 1 or 2 only"]
    279308    smallint cmd_mode;       // 0=command  1=insert 2=replace
    280     int file_modified;       // buffer contents changed (counter, not flag!)
    281     int last_file_modified; // = -1;
     309    int modified_count;      // buffer contents changed if !0
     310    int last_modified_count; // = -1;
    282311    int save_argc;           // how many file names on cmd line
    283312    int cmdcnt;              // repetition count
     
    348377
    349378    char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
     379#if ENABLE_FEATURE_VI_UNDO
     380// undo_push() operations
     381#define UNDO_INS         0
     382#define UNDO_DEL         1
     383#define UNDO_INS_CHAIN   2
     384#define UNDO_DEL_CHAIN   3
     385// UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
     386#define UNDO_QUEUED_FLAG 4
     387#define UNDO_INS_QUEUED  4
     388#define UNDO_DEL_QUEUED  5
     389#define UNDO_USE_SPOS   32
     390#define UNDO_EMPTY      64
     391// Pass-through flags for functions that can be undone
     392#define NO_UNDO          0
     393#define ALLOW_UNDO       1
     394#define ALLOW_UNDO_CHAIN 2
     395# if ENABLE_FEATURE_VI_UNDO_QUEUE
     396#define ALLOW_UNDO_QUEUED 3
     397    char undo_queue_state;
     398    int undo_q;
     399    char *undo_queue_spos;  // Start position of queued operation
     400    char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
     401# else
     402// If undo queuing disabled, don't invoke the missing queue logic
     403#define ALLOW_UNDO_QUEUED 1
     404# endif
     405
     406    struct undo_object {
     407        struct undo_object *prev;   // Linking back avoids list traversal (LIFO)
     408        int start;      // Offset where the data should be restored/deleted
     409        int length;     // total data size
     410        uint8_t u_type;     // 0=deleted, 1=inserted, 2=swapped
     411        char undo_text[1];  // text that was deleted (if deletion)
     412    } *undo_stack_tail;
     413#endif /* ENABLE_FEATURE_VI_UNDO */
    350414};
    351415#define G (*ptr_to_globals)
     
    359423#define editing                 (G.editing            )
    360424#define cmd_mode                (G.cmd_mode           )
    361 #define file_modified           (G.file_modified      )
    362 #define last_file_modified      (G.last_file_modified )
     425#define modified_count          (G.modified_count     )
     426#define last_modified_count     (G.last_modified_count)
    363427#define save_argc               (G.save_argc          )
    364428#define cmdcnt                  (G.cmdcnt             )
     
    409473#define get_input_line__buf (G.get_input_line__buf)
    410474
     475#if ENABLE_FEATURE_VI_UNDO
     476#define undo_stack_tail  (G.undo_stack_tail )
     477# if ENABLE_FEATURE_VI_UNDO_QUEUE
     478#define undo_queue_state (G.undo_queue_state)
     479#define undo_q           (G.undo_q          )
     480#define undo_queue       (G.undo_queue      )
     481#define undo_queue_spos  (G.undo_queue_spos )
     482# endif
     483#endif
     484
    411485#define INIT_G() do { \
    412486    SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
    413     last_file_modified = -1; \
     487    last_modified_count = -1; \
    414488    /* "" but has space for 2 chars: */ \
    415489    IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
     
    417491
    418492
    419 static int init_text_buffer(char *); // init from file or create new
    420493static void edit_file(char *);  // edit one file
    421494static void do_cmd(int);    // execute a command
     
    428501static char *end_screen(void);  // get pointer to last char on screen
    429502static int count_lines(char *, char *); // count line from start to stop
    430 static char *find_line(int);    // find begining of line #li
     503static char *find_line(int);    // find beginning of line #li
    431504static char *move_to_col(char *, int);  // move "p" to column l
    432505static void dot_left(void); // move dot left- dont leave line
     
    438511static void dot_scroll(int, int);   // move the screen up or down
    439512static void dot_skip_over_ws(void); // move dot pat WS
    440 static void dot_delete(void);   // delete the char at 'dot'
    441513static char *bound_dot(char *); // make sure  text[0] <= P < "end"
    442514static char *new_screen(int, int);  // malloc virtual screen memory
    443 static char *char_insert(char *, char); // insert the char c at 'p'
     515#if !ENABLE_FEATURE_VI_UNDO
     516#define char_insert(a,b,c) char_insert(a,b)
     517#endif
     518static char *char_insert(char *, char, int);    // insert the char c at 'p'
    444519// might reallocate text[]! use p += stupid_insert(p, ...),
    445520// and be careful to not use pointers into potentially freed text[]!
     
    449524static char *skip_thing(char *, int, int, int); // skip some object
    450525static char *find_pair(char *, char);   // find matching pair ()  []  {}
    451 static char *text_hole_delete(char *, char *);  // at "p", delete a 'size' byte hole
     526#if !ENABLE_FEATURE_VI_UNDO
     527#define text_hole_delete(a,b,c) text_hole_delete(a,b)
     528#endif
     529static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
    452530// might reallocate text[]! use p += text_hole_make(p, ...),
    453531// and be careful to not use pointers into potentially freed text[]!
    454532static uintptr_t text_hole_make(char *, int);   // at "p", make a 'size' byte hole
    455 static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete
     533#if !ENABLE_FEATURE_VI_UNDO
     534#define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
     535#endif
     536static char *yank_delete(char *, char *, int, int, int);    // yank text[] into register then delete
    456537static void show_help(void);    // display some help info
    457538static void rawmode(void);  // set "raw" mode on tty
     
    461542static int readit(void);    // read (maybe cursor) key from stdin
    462543static int get_one_char(void);  // read 1 char from stdin
    463 static int file_size(const char *);   // what is the byte size of "fn"
    464 #if !ENABLE_FEATURE_VI_READONLY
    465 #define file_insert(fn, p, update_ro_status) file_insert(fn, p)
    466 #endif
    467544// file_insert might reallocate text[]!
    468545static int file_insert(const char *, char *, int);
     
    479556static void status_line(const char *, ...);     // print to status buf
    480557static void status_line_bold(const char *, ...);
     558static void status_line_bold_errno(const char *fn);
    481559static void not_implemented(const char *); // display "Not implemented" message
    482560static int format_edit_status(void);    // format file status on status line
     
    485563static void refresh(int);   // update the terminal from screen[]
    486564
    487 static void Indicate_Error(void);       // use flash or beep to indicate error
    488 #define indicate_error(c) Indicate_Error()
     565static void indicate_error(void);       // use flash or beep to indicate error
    489566static void Hit_Return(void);
    490567
     
    495572static char *get_one_address(char *, int *);    // get colon addr, if present
    496573static char *get_address(char *, int *, int *); // get two colon addrs, if present
     574#endif
    497575static void colon(char *);  // execute the "colon" mode cmds
    498 #endif
    499576#if ENABLE_FEATURE_VI_USE_SIGNALS
    500577static void winch_sig(int); // catch window size changes
     
    514591// might reallocate text[]! use p += string_insert(p, ...),
    515592// and be careful to not use pointers into potentially freed text[]!
    516 static uintptr_t string_insert(char *, const char *);   // insert the string at 'p'
     593# if !ENABLE_FEATURE_VI_UNDO
     594#define string_insert(a,b,c) string_insert(a,b)
     595# endif
     596static uintptr_t string_insert(char *, const char *, int);  // insert the string at 'p'
    517597#endif
    518598#if ENABLE_FEATURE_VI_YANKMARK
     
    521601static void check_context(char);    // remember context for '' command
    522602#endif
     603#if ENABLE_FEATURE_VI_UNDO
     604static void flush_undo_data(void);
     605static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack
     606static void undo_pop(void); // Undo the last operation
     607# if ENABLE_FEATURE_VI_UNDO_QUEUE
     608static void undo_queue_commit(void);    // Flush any queued objects to the undo stack
     609# else
     610# define undo_queue_commit() ((void)0)
     611# endif
     612#else
     613#define flush_undo_data()   ((void)0)
     614#define undo_queue_commit() ((void)0)
     615#endif
     616
    523617#if ENABLE_FEATURE_VI_CRASHME
    524618static void crash_dummy();
     
    527621#endif
    528622
    529 
    530623static void write1(const char *out)
    531624{
     
    539632
    540633    INIT_G();
     634
     635#if ENABLE_FEATURE_VI_UNDO
     636    /* undo_stack_tail = NULL; - already is */
     637#if ENABLE_FEATURE_VI_UNDO_QUEUE
     638    undo_queue_state = UNDO_EMPTY;
     639    /* undo_q = 0; - already is  */
     640#endif
     641#endif
    541642
    542643#if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
     
    618719{
    619720    int rc;
    620     int size = file_size(fn);   // file size. -1 means does not exist.
     721
     722    flush_undo_data();
     723    modified_count = 0;
     724    last_modified_count = -1;
     725#if ENABLE_FEATURE_VI_YANKMARK
     726    /* init the marks */
     727    memset(mark, 0, sizeof(mark));
     728#endif
    621729
    622730    /* allocate/reallocate text buffer */
    623731    free(text);
    624     text_size = size + 10240;
     732    text_size = 10240;
    625733    screenbegin = dot = end = text = xzalloc(text_size);
    626734
     
    629737        current_filename = xstrdup(fn);
    630738    }
    631     if (size < 0) {
    632         // file dont exist. Start empty buf with dummy line
    633         char_insert(text, '\n');
    634         rc = 0;
    635     } else {
    636         rc = file_insert(fn, text, 1);
    637     }
    638     file_modified = 0;
    639     last_file_modified = -1;
    640 #if ENABLE_FEATURE_VI_YANKMARK
    641     /* init the marks. */
    642     memset(mark, 0, sizeof(mark));
    643 #endif
     739    rc = file_insert(fn, text, 1);
     740    if (rc < 0) {
     741        // file doesnt exist. Start empty buf with dummy line
     742        char_insert(text, '\n', NO_UNDO);
     743    }
    644744    return rc;
    645745}
     
    756856            } else {
    757857                crashme = 0;
    758                 string_insert(text, "\n\n#####  Ran out of text to work on.  #####\n\n"); // insert the string
     858                string_insert(text, "\n\n#####  Ran out of text to work on.  #####\n\n", NO_UNDO); // insert the string
    759859                dot = text;
    760860                refresh(FALSE);
     
    9091009#endif
    9101010
     1011#endif /* FEATURE_VI_COLON */
     1012
    9111013// buf must be no longer than MAX_INPUT_LEN!
    9121014static void colon(char *buf)
    9131015{
     1016#if !ENABLE_FEATURE_VI_COLON
     1017    /* Simple ":cmd" handler with minimal set of commands */
     1018    char *p = buf;
     1019    int cnt;
     1020
     1021    if (*p == ':')
     1022        p++;
     1023    cnt = strlen(p);
     1024    if (cnt == 0)
     1025        return;
     1026    if (strncmp(p, "quit", cnt) == 0
     1027     || strncmp(p, "q!", cnt) == 0
     1028    ) {
     1029        if (modified_count && p[1] != '!') {
     1030            status_line_bold("No write since last change (:%s! overrides)", p);
     1031        } else {
     1032            editing = 0;
     1033        }
     1034        return;
     1035    }
     1036    if (strncmp(p, "write", cnt) == 0
     1037     || strncmp(p, "wq", cnt) == 0
     1038     || strncmp(p, "wn", cnt) == 0
     1039     || (p[0] == 'x' && !p[1])
     1040    ) {
     1041        cnt = file_write(current_filename, text, end - 1);
     1042        if (cnt < 0) {
     1043            if (cnt == -1)
     1044                status_line_bold("Write error: %s", strerror(errno));
     1045        } else {
     1046            modified_count = 0;
     1047            last_modified_count = -1;
     1048            status_line("'%s' %dL, %dC",
     1049                current_filename,
     1050                count_lines(text, end - 1), cnt
     1051            );
     1052            if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
     1053             || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
     1054            ) {
     1055                editing = 0;
     1056            }
     1057        }
     1058        return;
     1059    }
     1060    if (strncmp(p, "file", cnt) == 0) {
     1061        last_status_cksum = 0;  // force status update
     1062        return;
     1063    }
     1064    if (sscanf(p, "%d", &cnt) > 0) {
     1065        dot = find_line(cnt);
     1066        dot_skip_over_ws();
     1067        return;
     1068    }
     1069    not_implemented(p);
     1070#else
     1071
    9141072    char c, *orig_buf, *buf1, *q, *r;
    9151073    char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
    916     int i, l, li, ch, b, e;
    917     int useforce, forced = FALSE;
     1074    int i, l, li, b, e;
     1075    int useforce;
    9181076
    9191077    // :3154    // if (-e line 3154) goto it  else stay put
     
    9371095        buf++;          // move past the ':'
    9381096
    939     li = ch = i = 0;
     1097    li = i = 0;
    9401098    b = e = -1;
    9411099    q = text;           // assume 1,$ for the range
     
    10151173            r = end_line(dot);
    10161174        }
    1017         dot = yank_delete(q, r, 1, YANKDEL);    // save, then delete lines
     1175        dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO);    // save, then delete lines
    10181176        dot_skip_over_ws();
    10191177    } else if (strncmp(cmd, "edit", i) == 0) {  // Edit a file
     1178        int size;
     1179
    10201180        // don't edit, if the current file has been modified
    1021         if (file_modified && !useforce) {
     1181        if (modified_count && !useforce) {
    10221182            status_line_bold("No write since last change (:%s! overrides)", cmd);
    10231183            goto ret;
     
    10351195        }
    10361196
    1037         if (init_text_buffer(fn) < 0)
    1038             goto ret;
     1197        size = init_text_buffer(fn);
    10391198
    10401199#if ENABLE_FEATURE_VI_YANKMARK
     
    10501209        // how many lines in text[]?
    10511210        li = count_lines(text, end - 1);
    1052         status_line("\"%s\"%s"
     1211        status_line("'%s'%s"
    10531212            IF_FEATURE_VI_READONLY("%s")
    1054             " %dL, %dC", current_filename,
    1055             (file_size(fn) < 0 ? " [New file]" : ""),
     1213            " %dL, %dC",
     1214            current_filename,
     1215            (size < 0 ? " [New file]" : ""),
    10561216            IF_FEATURE_VI_READONLY(
    10571217                ((readonly_mode) ? " [Readonly]" : ""),
    10581218            )
    1059             li, ch);
     1219            li, (int)(end - text)
     1220        );
    10601221    } else if (strncmp(cmd, "file", i) == 0) {  // what File is this
    10611222        if (b != -1 || e != -1) {
     
    11221283        }
    11231284        // don't exit if the file been modified
    1124         if (file_modified) {
     1285        if (modified_count) {
    11251286            status_line_bold("No write since last change (:%s! overrides)", cmd);
    11261287            goto ret;
     
    11461307        editing = 0;
    11471308    } else if (strncmp(cmd, "read", i) == 0) {  // read file into text[]
     1309        int size;
     1310
    11481311        fn = args;
    11491312        if (!fn[0]) {
     
    11551318        }
    11561319        // read after current line- unless user said ":0r foo"
    1157         if (b != 0)
     1320        if (b != 0) {
    11581321            q = next_line(q);
     1322            // read after last line
     1323            if (q == end-1)
     1324                ++q;
     1325        }
    11591326        { // dance around potentially-reallocated text[]
    11601327            uintptr_t ofs = q - text;
    1161             ch = file_insert(fn, q, 0);
     1328            size = file_insert(fn, q, 0);
    11621329            q = text + ofs;
    11631330        }
    1164         if (ch < 0)
     1331        if (size < 0)
    11651332            goto ret;   // nothing was inserted
    11661333        // how many lines in text[]?
    1167         li = count_lines(q, q + ch - 1);
    1168         status_line("\"%s\""
     1334        li = count_lines(q, q + size - 1);
     1335        status_line("'%s'"
    11691336            IF_FEATURE_VI_READONLY("%s")
    1170             " %dL, %dC", fn,
     1337            " %dL, %dC",
     1338            fn,
    11711339            IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
    1172             li, ch);
    1173         if (ch > 0) {
     1340            li, size
     1341        );
     1342        if (size > 0) {
    11741343            // if the insert is before "dot" then we need to update
    11751344            if (q <= dot)
    1176                 dot += ch;
    1177             /*file_modified++; - done by file_insert */
     1345                dot += size;
    11781346        }
    11791347    } else if (strncmp(cmd, "rewind", i) == 0) {    // rewind cmd line args
    1180         if (file_modified && !useforce) {
     1348        if (modified_count && !useforce) {
    11811349            status_line_bold("No write since last change (:%s! overrides)", cmd);
    11821350        } else {
     
    12351403        size_t len_F, len_R;
    12361404        int gflag;      // global replace flag
     1405#if ENABLE_FEATURE_VI_UNDO
     1406        int dont_chain_first_item = ALLOW_UNDO;
     1407#endif
    12371408
    12381409        // F points to the "find" pattern
     
    12691440                uintptr_t bias;
    12701441                // we found the "find" pattern - delete it
    1271                 text_hole_delete(found, found + len_F - 1);
    1272                 // inset the "replace" patern
    1273                 bias = string_insert(found, R); // insert the string
     1442                // For undo support, the first item should not be chained
     1443                text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
     1444#if ENABLE_FEATURE_VI_UNDO
     1445                dont_chain_first_item = ALLOW_UNDO_CHAIN;
     1446#endif
     1447                // insert the "replace" patern
     1448                bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
    12741449                found += bias;
    12751450                ls += bias;
     
    12931468            || (cmd[0] == 'x' && !cmd[1])
    12941469    ) {
     1470        int size;
     1471        //int forced = FALSE;
     1472
    12951473        // is there a file name to write to?
    12961474        if (args[0]) {
     
    12991477#if ENABLE_FEATURE_VI_READONLY
    13001478        if (readonly_mode && !useforce) {
    1301             status_line_bold("\"%s\" File is read only", fn);
     1479            status_line_bold("'%s' is read only", fn);
    13021480            goto ret;
    13031481        }
     
    13051483        // how many lines in text[]?
    13061484        li = count_lines(q, r);
    1307         ch = r - q + 1;
    1308         // see if file exists- if not, its just a new file request
    1309         if (useforce) {
     1485        size = r - q + 1;
     1486        //if (useforce) {
    13101487            // if "fn" is not write-able, chmod u+w
    13111488            // sprintf(syscmd, "chmod u+w %s", fn);
    13121489            // system(syscmd);
    1313             forced = TRUE;
    1314         }
     1490            // forced = TRUE;
     1491        //}
    13151492        l = file_write(fn, q, r);
    1316         if (useforce && forced) {
     1493        //if (useforce && forced) {
    13171494            // chmod u-w
    13181495            // sprintf(syscmd, "chmod u-w %s", fn);
    13191496            // system(syscmd);
    1320             forced = FALSE;
    1321         }
     1497            // forced = FALSE;
     1498        //}
    13221499        if (l < 0) {
    13231500            if (l == -1)
    1324                 status_line_bold("\"%s\" %s", fn, strerror(errno));
     1501                status_line_bold_errno(fn);
    13251502        } else {
    1326             status_line("\"%s\" %dL, %dC", fn, li, l);
    1327             if (q == text && r == end - 1 && l == ch) {
    1328                 file_modified = 0;
    1329                 last_file_modified = -1;
     1503            status_line("'%s' %dL, %dC", fn, li, l);
     1504            if (q == text && r == end - 1 && l == size) {
     1505                modified_count = 0;
     1506                last_modified_count = -1;
    13301507            }
    13311508            if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
    13321509                || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
    13331510                )
    1334              && l == ch
     1511             && l == size
    13351512            ) {
    13361513                editing = 0;
     
    13591536    status_line(":s expression missing delimiters");
    13601537#endif
    1361 }
    1362 
    13631538#endif /* FEATURE_VI_COLON */
     1539}
    13641540
    13651541static void Hit_Return(void)
     
    15081684static char *prev_line(char *p) // return pointer first char prev line
    15091685{
    1510     p = begin_line(p);  // goto begining of cur line
     1686    p = begin_line(p);  // goto beginning of cur line
    15111687    if (p > text && p[-1] == '\n')
    15121688        p--;            // step to prev line
    1513     p = begin_line(p);  // goto begining of prev line
     1689    p = begin_line(p);  // goto beginning of prev line
    15141690    return p;
    15151691}
     
    15591735}
    15601736
    1561 static char *find_line(int li)  // find begining of line #li
     1737static char *find_line(int li)  // find beginning of line #li
    15621738{
    15631739    char *q;
     
    15721748static void dot_left(void)
    15731749{
     1750    undo_queue_commit();
    15741751    if (dot > text && dot[-1] != '\n')
    15751752        dot--;
     
    15781755static void dot_right(void)
    15791756{
     1757    undo_queue_commit();
    15801758    if (dot < end - 1 && *dot != '\n')
    15811759        dot++;
     
    15841762static void dot_begin(void)
    15851763{
     1764    undo_queue_commit();
    15861765    dot = begin_line(dot);  // return pointer to first char cur line
    15871766}
     
    15891768static void dot_end(void)
    15901769{
     1770    undo_queue_commit();
    15911771    dot = end_line(dot);    // return pointer to last char cur line
    15921772}
     
    16141794static void dot_next(void)
    16151795{
     1796    undo_queue_commit();
    16161797    dot = next_line(dot);
    16171798}
     
    16191800static void dot_prev(void)
    16201801{
     1802    undo_queue_commit();
    16211803    dot = prev_line(dot);
    16221804}
     
    16261808    char *q;
    16271809
     1810    undo_queue_commit();
    16281811    for (; cnt > 0; cnt--) {
    16291812        if (dir < 0) {
     
    16531836}
    16541837
    1655 static void dot_delete(void)    // delete the char at 'dot'
    1656 {
    1657     text_hole_delete(dot, dot);
    1658 }
    1659 
    16601838static char *bound_dot(char *p) // make sure  text[0] <= P < "end"
    16611839{
    16621840    if (p >= end && end > text) {
    16631841        p = end - 1;
    1664         indicate_error('1');
     1842        indicate_error();
    16651843    }
    16661844    if (p < text) {
    16671845        p = text;
    1668         indicate_error('2');
     1846        indicate_error();
    16691847    }
    16701848    return p;
     
    17071885static char *char_search(char *p, const char *pat, int dir, int range)
    17081886{
     1887    struct re_pattern_buffer preg;
     1888    const char *err;
    17091889    char *q;
    1710     struct re_pattern_buffer preg;
    17111890    int i;
    17121891    int size;
    17131892
    17141893    re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
    1715     preg.translate = 0;
    1716     preg.fastmap = 0;
    1717     preg.buffer = 0;
    1718     preg.allocated = 0;
     1894    if (ignorecase)
     1895        re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
     1896
     1897    memset(&preg, 0, sizeof(preg));
     1898    err = re_compile_pattern(pat, strlen(pat), &preg);
     1899    if (err != NULL) {
     1900        status_line_bold("bad search pattern '%s': %s", pat, err);
     1901        return p;
     1902    }
    17191903
    17201904    // assume a LIMITED forward search
    1721     q = next_line(p);
    1722     q = end_line(q);
    17231905    q = end - 1;
    1724     if (dir == BACK) {
    1725         q = prev_line(p);
     1906    if (dir == BACK)
    17261907        q = text;
    1727     }
    1728     // count the number of chars to search over, forward or backward
    1729     size = q - p;
    1730     if (size < 0)
    1731         size = p - q;
    17321908    // RANGE could be negative if we are searching backwards
    17331909    range = q - p;
    1734 
    1735     q = (char *)re_compile_pattern(pat, strlen(pat), (struct re_pattern_buffer *)&preg);
    1736     if (q != 0) {
    1737         // The pattern was not compiled
    1738         status_line_bold("bad search pattern: \"%s\": %s", pat, q);
    1739         i = 0;          // return p if pattern not compiled
    1740         goto cs1;
    1741     }
    1742 
    17431910    q = p;
     1911    size = range;
    17441912    if (range < 0) {
     1913        size = -size;
    17451914        q = p - size;
    17461915        if (q < text)
     
    17481917    }
    17491918    // search for the compiled pattern, preg, in p[]
    1750     // range < 0- search backward
    1751     // range > 0- search forward
     1919    // range < 0: search backward
     1920    // range > 0: search forward
    17521921    // 0 < start < size
    1753     // re_search() < 0  not found or error
    1754     // re_search() > 0  index of found pattern
    1755     //            struct pattern    char     int    int    int     struct reg
    1756     // re_search (*pattern_buffer,  *string, size,  start, range,  *regs)
    1757     i = re_search(&preg, q, size, 0, range, 0);
    1758     if (i == -1) {
    1759         p = 0;
    1760         i = 0;          // return NULL if pattern not found
    1761     }
    1762  cs1:
    1763     if (dir == FORWARD) {
     1922    // re_search() < 0: not found or error
     1923    // re_search() >= 0: index of found pattern
     1924    //           struct pattern   char     int   int    int    struct reg
     1925    // re_search(*pattern_buffer, *string, size, start, range, *regs)
     1926    i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
     1927    regfree(&preg);
     1928    if (i < 0)
     1929        return NULL;
     1930    if (dir == FORWARD)
    17641931        p = p + i;
    1765     } else {
     1932    else
    17661933        p = p - i;
    1767     }
    17681934    return p;
    17691935}
     
    17901956    len = strlen(pat);
    17911957    if (dir == FORWARD) {
    1792         stop = end - 1; // assume range is p - end-1
     1958        stop = end - 1; // assume range is p..end-1
    17931959        if (range == LIMITED)
    17941960            stop = next_line(p);    // range is to next line
     
    17991965        }
    18001966    } else if (dir == BACK) {
    1801         stop = text;    // assume range is text - p
     1967        stop = text;    // assume range is text..p
    18021968        if (range == LIMITED)
    18031969            stop = prev_line(p);    // range is to prev line
     
    18161982#endif /* FEATURE_VI_SEARCH */
    18171983
    1818 static char *char_insert(char *p, char c) // insert the char c at 'p'
     1984static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
    18191985{
    18201986    if (c == 22) {      // Is this an ctrl-V?
     
    18231989        c = get_one_char();
    18241990        *p = c;
     1991#if ENABLE_FEATURE_VI_UNDO
     1992        switch (undo) {
     1993            case ALLOW_UNDO:
     1994                undo_push(p, 1, UNDO_INS);
     1995                break;
     1996            case ALLOW_UNDO_CHAIN:
     1997                undo_push(p, 1, UNDO_INS_CHAIN);
     1998                break;
     1999# if ENABLE_FEATURE_VI_UNDO_QUEUE
     2000            case ALLOW_UNDO_QUEUED:
     2001                undo_push(p, 1, UNDO_INS_QUEUED);
     2002                break;
     2003# endif
     2004        }
     2005#else
     2006        modified_count++;
     2007#endif /* ENABLE_FEATURE_VI_UNDO */
    18252008        p++;
    1826         file_modified++;
    18272009    } else if (c == 27) {   // Is this an ESC?
    18282010        cmd_mode = 0;
     2011        undo_queue_commit();
    18292012        cmdcnt = 0;
    18302013        end_cmd_q();    // stop adding to q
     
    18342017        }
    18352018    } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
    1836         //     123456789
    1837         if ((p[-1] != '\n') && (dot>text)) {
     2019        if (p > text) {
    18382020            p--;
    1839             p = text_hole_delete(p, p); // shrink buffer 1 char
     2021            p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED);  // shrink buffer 1 char
    18402022        }
    18412023    } else {
    1842 #if ENABLE_FEATURE_VI_SETOPTS
    18432024        // insert a char into text[]
    1844         char *sp;       // "save p"
    1845 #endif
    1846 
    18472025        if (c == 13)
    18482026            c = '\n';   // translate \r to \n
    1849 #if ENABLE_FEATURE_VI_SETOPTS
    1850         sp = p;         // remember addr of insert
    1851 #endif
     2027#if ENABLE_FEATURE_VI_UNDO
     2028# if ENABLE_FEATURE_VI_UNDO_QUEUE
     2029        if (c == '\n')
     2030            undo_queue_commit();
     2031# endif
     2032        switch (undo) {
     2033            case ALLOW_UNDO:
     2034                undo_push(p, 1, UNDO_INS);
     2035                break;
     2036            case ALLOW_UNDO_CHAIN:
     2037                undo_push(p, 1, UNDO_INS_CHAIN);
     2038                break;
     2039# if ENABLE_FEATURE_VI_UNDO_QUEUE
     2040            case ALLOW_UNDO_QUEUED:
     2041                undo_push(p, 1, UNDO_INS_QUEUED);
     2042                break;
     2043# endif
     2044        }
     2045#else
     2046        modified_count++;
     2047#endif /* ENABLE_FEATURE_VI_UNDO */
    18522048        p += 1 + stupid_insert(p, c);   // insert the char
    18532049#if ENABLE_FEATURE_VI_SETOPTS
    1854         if (showmatch && strchr(")]}", *sp) != NULL) {
    1855             showmatching(sp);
     2050        if (showmatch && strchr(")]}", c) != NULL) {
     2051            showmatching(p - 1);
    18562052        }
    18572053        if (autoindent && c == '\n') {  // auto indent the new line
     
    18652061                p += bias;
    18662062                q += bias;
     2063#if ENABLE_FEATURE_VI_UNDO
     2064                undo_push(p, len, UNDO_INS);
     2065#endif
    18672066                memcpy(p, q, len);
    18682067                p += len;
     
    18822081    p += bias;
    18832082    *p = c;
    1884     //file_modified++; - done by text_hole_make()
    18852083    return bias;
    18862084}
     
    20112209
    20122210// find matching char of pair  ()  []  {}
     2211// will crash if c is not one of these
    20132212static char *find_pair(char *p, const char c)
    20142213{
    2015     char match, *q;
     2214    const char *braces = "()[]{}";
     2215    char match;
    20162216    int dir, level;
    20172217
    2018     match = ')';
     2218    dir = strchr(braces, c) - braces;
     2219    dir ^= 1;
     2220    match = braces[dir];
     2221    dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */
     2222
     2223    // look for match, count levels of pairs  (( ))
    20192224    level = 1;
    2020     dir = 1;            // assume forward
    2021     switch (c) {
    2022     case '(': match = ')'; break;
    2023     case '[': match = ']'; break;
    2024     case '{': match = '}'; break;
    2025     case ')': match = '('; dir = -1; break;
    2026     case ']': match = '['; dir = -1; break;
    2027     case '}': match = '{'; dir = -1; break;
    2028     }
    2029     for (q = p + dir; text <= q && q < end; q += dir) {
    2030         // look for match, count levels of pairs  (( ))
    2031         if (*q == c)
     2225    for (;;) {
     2226        p += dir;
     2227        if (p < text || p >= end)
     2228            return NULL;
     2229        if (*p == c)
    20322230            level++;    // increase pair levels
    2033         if (*q == match)
     2231        if (*p == match) {
    20342232            level--;    // reduce pair level
    2035         if (level == 0)
    2036             break;      // found matching pair
    2037     }
    2038     if (level != 0)
    2039         q = NULL;       // indicate no match
    2040     return q;
     2233            if (level == 0)
     2234                return p; // found matching pair
     2235        }
     2236    }
    20412237}
    20422238
     
    20502246    q = find_pair(p, *p);   // get loc of matching char
    20512247    if (q == NULL) {
    2052         indicate_error('3');    // no matching char
     2248        indicate_error();   // no matching char
    20532249    } else {
    20542250        // "q" now points to matching pair
     
    20622258}
    20632259#endif /* FEATURE_VI_SETOPTS */
     2260
     2261#if ENABLE_FEATURE_VI_UNDO
     2262static void flush_undo_data(void)
     2263{
     2264    struct undo_object *undo_entry;
     2265
     2266    while (undo_stack_tail) {
     2267        undo_entry = undo_stack_tail;
     2268        undo_stack_tail = undo_entry->prev;
     2269        free(undo_entry);
     2270    }
     2271}
     2272
     2273// Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
     2274static void undo_push(char *src, unsigned int length, uint8_t u_type)   // Add to the undo stack
     2275{
     2276    struct undo_object *undo_entry;
     2277
     2278    // "u_type" values
     2279    // UNDO_INS: insertion, undo will remove from buffer
     2280    // UNDO_DEL: deleted text, undo will restore to buffer
     2281    // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
     2282    // The CHAIN operations are for handling multiple operations that the user
     2283    // performs with a single action, i.e. REPLACE mode or find-and-replace commands
     2284    // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
     2285    // for the INS/DEL operation. The raw values should be equal to the values of
     2286    // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
     2287
     2288#if ENABLE_FEATURE_VI_UNDO_QUEUE
     2289    // This undo queuing functionality groups multiple character typing or backspaces
     2290    // into a single large undo object. This greatly reduces calls to malloc() for
     2291    // single-character operations while typing and has the side benefit of letting
     2292    // an undo operation remove chunks of text rather than a single character.
     2293    switch (u_type) {
     2294    case UNDO_EMPTY:    // Just in case this ever happens...
     2295        return;
     2296    case UNDO_DEL_QUEUED:
     2297        if (length != 1)
     2298            return; // Only queue single characters
     2299        switch (undo_queue_state) {
     2300        case UNDO_EMPTY:
     2301            undo_queue_state = UNDO_DEL;
     2302        case UNDO_DEL:
     2303            undo_queue_spos = src;
     2304            undo_q++;
     2305            undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
     2306            // If queue is full, dump it into an object
     2307            if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
     2308                undo_queue_commit();
     2309            return;
     2310        case UNDO_INS:
     2311            // Switch from storing inserted text to deleted text
     2312            undo_queue_commit();
     2313            undo_push(src, length, UNDO_DEL_QUEUED);
     2314            return;
     2315        }
     2316        break;
     2317    case UNDO_INS_QUEUED:
     2318        if (length != 1)
     2319            return;
     2320        switch (undo_queue_state) {
     2321        case UNDO_EMPTY:
     2322            undo_queue_state = UNDO_INS;
     2323            undo_queue_spos = src;
     2324        case UNDO_INS:
     2325            undo_q++;   // Don't need to save any data for insertions
     2326            if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
     2327                undo_queue_commit();
     2328            return;
     2329        case UNDO_DEL:
     2330            // Switch from storing deleted text to inserted text
     2331            undo_queue_commit();
     2332            undo_push(src, length, UNDO_INS_QUEUED);
     2333            return;
     2334        }
     2335        break;
     2336    }
     2337#else
     2338    // If undo queuing is disabled, ignore the queuing flag entirely
     2339    u_type = u_type & ~UNDO_QUEUED_FLAG;
     2340#endif
     2341
     2342    // Allocate a new undo object
     2343    if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
     2344        // For UNDO_DEL objects, save deleted text
     2345        if ((src + length) == end)
     2346            length--;
     2347        // If this deletion empties text[], strip the newline. When the buffer becomes
     2348        // zero-length, a newline is added back, which requires this to compensate.
     2349        undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
     2350        memcpy(undo_entry->undo_text, src, length);
     2351    } else {
     2352        undo_entry = xzalloc(sizeof(*undo_entry));
     2353    }
     2354    undo_entry->length = length;
     2355#if ENABLE_FEATURE_VI_UNDO_QUEUE
     2356    if ((u_type & UNDO_USE_SPOS) != 0) {
     2357        undo_entry->start = undo_queue_spos - text; // use start position from queue
     2358    } else {
     2359        undo_entry->start = src - text; // use offset from start of text buffer
     2360    }
     2361    u_type = (u_type & ~UNDO_USE_SPOS);
     2362#else
     2363    undo_entry->start = src - text;
     2364#endif
     2365    undo_entry->u_type = u_type;
     2366
     2367    // Push it on undo stack
     2368    undo_entry->prev = undo_stack_tail;
     2369    undo_stack_tail = undo_entry;
     2370    modified_count++;
     2371}
     2372
     2373static void undo_pop(void)  // Undo the last operation
     2374{
     2375    int repeat;
     2376    char *u_start, *u_end;
     2377    struct undo_object *undo_entry;
     2378
     2379    // Commit pending undo queue before popping (should be unnecessary)
     2380    undo_queue_commit();
     2381
     2382    undo_entry = undo_stack_tail;
     2383    // Check for an empty undo stack
     2384    if (!undo_entry) {
     2385        status_line("Already at oldest change");
     2386        return;
     2387    }
     2388
     2389    switch (undo_entry->u_type) {
     2390    case UNDO_DEL:
     2391    case UNDO_DEL_CHAIN:
     2392        // make hole and put in text that was deleted; deallocate text
     2393        u_start = text + undo_entry->start;
     2394        text_hole_make(u_start, undo_entry->length);
     2395        memcpy(u_start, undo_entry->undo_text, undo_entry->length);
     2396        status_line("Undo [%d] %s %d chars at position %d",
     2397            modified_count, "restored",
     2398            undo_entry->length, undo_entry->start
     2399        );
     2400        break;
     2401    case UNDO_INS:
     2402    case UNDO_INS_CHAIN:
     2403        // delete what was inserted
     2404        u_start = undo_entry->start + text;
     2405        u_end = u_start - 1 + undo_entry->length;
     2406        text_hole_delete(u_start, u_end, NO_UNDO);
     2407        status_line("Undo [%d] %s %d chars at position %d",
     2408            modified_count, "deleted",
     2409            undo_entry->length, undo_entry->start
     2410        );
     2411        break;
     2412    }
     2413    repeat = 0;
     2414    switch (undo_entry->u_type) {
     2415    // If this is the end of a chain, lower modification count and refresh display
     2416    case UNDO_DEL:
     2417    case UNDO_INS:
     2418        dot = (text + undo_entry->start);
     2419        refresh(FALSE);
     2420        break;
     2421    case UNDO_DEL_CHAIN:
     2422    case UNDO_INS_CHAIN:
     2423        repeat = 1;
     2424        break;
     2425    }
     2426    // Deallocate the undo object we just processed
     2427    undo_stack_tail = undo_entry->prev;
     2428    free(undo_entry);
     2429    modified_count--;
     2430    // For chained operations, continue popping all the way down the chain.
     2431    if (repeat) {
     2432        undo_pop(); // Follow the undo chain if one exists
     2433    }
     2434}
     2435
     2436#if ENABLE_FEATURE_VI_UNDO_QUEUE
     2437static void undo_queue_commit(void) // Flush any queued objects to the undo stack
     2438{
     2439    // Pushes the queue object onto the undo stack
     2440    if (undo_q > 0) {
     2441        // Deleted character undo events grow from the end
     2442        undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
     2443            undo_q,
     2444            (undo_queue_state | UNDO_USE_SPOS)
     2445        );
     2446        undo_queue_state = UNDO_EMPTY;
     2447        undo_q = 0;
     2448    }
     2449}
     2450#endif
     2451
     2452#endif /* ENABLE_FEATURE_VI_UNDO */
    20642453
    20652454// open a hole in text[]
     
    20942483    memmove(p + size, p, end - size - p);
    20952484    memset(p, ' ', size);   // clear new hole
    2096     file_modified++;
    20972485    return bias;
    20982486}
    20992487
    21002488//  close a hole in text[]
    2101 static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive
     2489//  "undo" value indicates if this operation should be undo-able
     2490static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
    21022491{
    21032492    char *src, *dest;
     
    21142503    hole_size = q - p + 1;
    21152504    cnt = end - src;
     2505#if ENABLE_FEATURE_VI_UNDO
     2506    switch (undo) {
     2507        case NO_UNDO:
     2508            break;
     2509        case ALLOW_UNDO:
     2510            undo_push(p, hole_size, UNDO_DEL);
     2511            break;
     2512        case ALLOW_UNDO_CHAIN:
     2513            undo_push(p, hole_size, UNDO_DEL_CHAIN);
     2514            break;
     2515# if ENABLE_FEATURE_VI_UNDO_QUEUE
     2516        case ALLOW_UNDO_QUEUED:
     2517            undo_push(p, hole_size, UNDO_DEL_QUEUED);
     2518            break;
     2519# endif
     2520    }
     2521    modified_count--;
     2522#endif
    21162523    if (src < text || src > end)
    21172524        goto thd0;
    21182525    if (dest < text || dest >= end)
    21192526        goto thd0;
     2527    modified_count++;
    21202528    if (src >= end)
    21212529        goto thd_atend; // just delete the end of the buffer
     
    21272535    if (end <= text)
    21282536        dest = end = text;  // keep pointers valid
    2129     file_modified++;
    21302537 thd0:
    21312538    return dest;
     
    21352542// if dist <= 0, do not include, or go past, a NewLine
    21362543//
    2137 static char *yank_delete(char *start, char *stop, int dist, int yf)
     2544static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
    21382545{
    21392546    char *p;
     
    21642571#endif
    21652572    if (yf == YANKDEL) {
    2166         p = text_hole_delete(start, stop);
     2573        p = text_hole_delete(start, stop, undo);
    21672574    }                   // delete lines
    21682575    return p;
     
    22302637// might reallocate text[]! use p += string_insert(p, ...),
    22312638// and be careful to not use pointers into potentially freed text[]!
    2232 static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p'
     2639static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
    22332640{
    22342641    uintptr_t bias;
     
    22362643
    22372644    i = strlen(s);
     2645#if ENABLE_FEATURE_VI_UNDO
     2646    switch (undo) {
     2647        case ALLOW_UNDO:
     2648            undo_push(p, i, UNDO_INS);
     2649            break;
     2650        case ALLOW_UNDO_CHAIN:
     2651            undo_push(p, i, UNDO_INS_CHAIN);
     2652            break;
     2653    }
     2654#endif
    22382655    bias = text_hole_make(p, i);
    22392656    p += bias;
     
    23872804{
    23882805    struct pollfd pfd[1];
     2806
     2807    if (hund != 0)
     2808        fflush_all();
    23892809
    23902810    pfd[0].fd = STDIN_FILENO;
     
    24842904}
    24852905
    2486 static int file_size(const char *fn) // what is the byte size of "fn"
    2487 {
    2488     struct stat st_buf;
    2489     int cnt;
    2490 
    2491     cnt = -1;
    2492     if (fn && stat(fn, &st_buf) == 0)   // see if file exists
    2493         cnt = (int) st_buf.st_size;
    2494     return cnt;
    2495 }
    2496 
    24972906// might reallocate text[]!
    2498 static int file_insert(const char *fn, char *p, int update_ro_status)
     2907static int file_insert(const char *fn, char *p, int initial)
    24992908{
    25002909    int cnt = -1;
     
    25022911    struct stat statbuf;
    25032912
    2504     /* Validate file */
    2505     if (stat(fn, &statbuf) < 0) {
    2506         status_line_bold("\"%s\" %s", fn, strerror(errno));
    2507         goto fi0;
    2508     }
    2509     if (!S_ISREG(statbuf.st_mode)) {
    2510         // This is not a regular file
    2511         status_line_bold("\"%s\" Not a regular file", fn);
    2512         goto fi0;
    2513     }
    2514     if (p < text || p > end) {
    2515         status_line_bold("Trying to insert file outside of memory");
    2516         goto fi0;
    2517     }
    2518 
    2519     // read file to buffer
     2913    if (p < text)
     2914        p = text;
     2915    if (p > end)
     2916        p = end;
     2917
    25202918    fd = open(fn, O_RDONLY);
    25212919    if (fd < 0) {
    2522         status_line_bold("\"%s\" %s", fn, strerror(errno));
    2523         goto fi0;
    2524     }
    2525     size = statbuf.st_size;
     2920        if (!initial)
     2921            status_line_bold_errno(fn);
     2922        return cnt;
     2923    }
     2924
     2925    /* Validate file */
     2926    if (fstat(fd, &statbuf) < 0) {
     2927        status_line_bold_errno(fn);
     2928        goto fi;
     2929    }
     2930    if (!S_ISREG(statbuf.st_mode)) {
     2931        status_line_bold("'%s' is not a regular file", fn);
     2932        goto fi;
     2933    }
     2934    size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
    25262935    p += text_hole_make(p, size);
    2527     cnt = safe_read(fd, p, size);
     2936    cnt = full_read(fd, p, size);
    25282937    if (cnt < 0) {
    2529         status_line_bold("\"%s\" %s", fn, strerror(errno));
    2530         p = text_hole_delete(p, p + size - 1);  // un-do buffer insert
     2938        status_line_bold_errno(fn);
     2939        p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
    25312940    } else if (cnt < size) {
    2532         // There was a partial read, shrink unused space text[]
    2533         p = text_hole_delete(p + cnt, p + (size - cnt) - 1);    // un-do buffer insert
    2534         status_line_bold("can't read all of file \"%s\"", fn);
    2535     }
    2536     if (cnt >= size)
    2537         file_modified++;
     2941        // There was a partial read, shrink unused space
     2942        p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
     2943        status_line_bold("can't read '%s'", fn);
     2944    }
     2945 fi:
    25382946    close(fd);
    2539  fi0:
     2947
    25402948#if ENABLE_FEATURE_VI_READONLY
    2541     if (update_ro_status
     2949    if (initial
    25422950     && ((access(fn, W_OK) < 0) ||
    25432951        /* root will always have access()
     
    25722980    if (charcnt == cnt) {
    25732981        // good write
    2574         //file_modified = FALSE;
     2982        //modified_count = FALSE;
    25752983    } else {
    25762984        charcnt = 0;
     
    26453053}
    26463054
    2647 static void Indicate_Error(void)
     3055static void indicate_error(void)
    26483056{
    26493057#if ENABLE_FEATURE_VI_CRASHME
     
    27183126}
    27193127
     3128static void status_line_bold_errno(const char *fn)
     3129{
     3130    status_line_bold("'%s' %s", fn, strerror(errno));
     3131}
     3132
    27203133// format status buffer
    27213134static void status_line(const char *format, ...)
     
    27893202    int cur, percent, ret, trunc_at;
    27903203
    2791     // file_modified is now a counter rather than a flag.  this
     3204    // modified_count is now a counter rather than a flag.  this
    27923205    // helps reduce the amount of line counting we need to do.
    27933206    // (this will cause a mis-reporting of modified status
     
    27993212    cur = count_lines(text, dot);
    28003213
    2801     // reduce counting -- the total lines can't have
    2802     // changed if we haven't done any edits.
    2803     if (file_modified != last_file_modified) {
     3214    // count_lines() is expensive.
     3215    // Call it only if something was changed since last time
     3216    // we were here:
     3217    if (modified_count != last_modified_count) {
    28043218        tot = cur + count_lines(dot, end - 1) - 1;
    2805         last_file_modified = file_modified;
     3219        last_modified_count = modified_count;
    28063220    }
    28073221
     
    28303244        (readonly_mode ? " [Readonly]" : ""),
    28313245#endif
    2832         (file_modified ? " [Modified]" : ""),
     3246        (modified_count ? " [Modified]" : ""),
    28333247        cur, tot, percent);
    28343248
     
    29433357        }
    29443358
    2945         // see if there are any changes between vitual screen and out_buf
     3359        // see if there are any changes between virtual screen and out_buf
    29463360        changed = FALSE;    // assume no change
    29473361        cs = 0;
     
    29803394        if (ce > columns - 1) ce = columns - 1;
    29813395        if (cs > ce) { cs = 0; ce = columns - 1; }
    2982         // is there a change between vitual screen and out_buf
     3396        // is there a change between virtual screen and out_buf
    29833397        if (changed) {
    29843398            // copy changed part of buffer to virtual screen
     
    30553469            // don't Replace past E-o-l
    30563470            cmd_mode = 1;   // convert to insert
     3471            undo_queue_commit();
    30573472        } else {
    30583473            if (1 <= c || Isprint(c)) {
    30593474                if (c != 27)
    3060                     dot = yank_delete(dot, dot, 0, YANKDEL);    // delete char
    3061                 dot = char_insert(dot, c);  // insert new char
     3475                    dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO);    // delete char
     3476                dot = char_insert(dot, c, ALLOW_UNDO_CHAIN);    // insert new char
    30623477            }
    30633478            goto dc1;
     
    30693484        // insert the char c at "dot"
    30703485        if (1 <= c || Isprint(c)) {
    3071             dot = char_insert(dot, c);
     3486            dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
    30723487        }
    30733488        goto dc1;
     
    31153530        //case '_': // _-
    31163531        //case '`': // `-
    3117         //case 'u': // u- FIXME- there is no undo
    31183532        //case 'v': // v-
    31193533    default:            // unrecognized command
     
    31823596    case 27:            // esc
    31833597        if (cmd_mode == 0)
    3184             indicate_error(c);
     3598            indicate_error();
    31853599        cmd_mode = 0;   // stop insrting
     3600        undo_queue_commit();
    31863601        end_cmd_q();
    31873602        last_status_cksum = 0;  // force status update
     
    32003615            YDreg = c1;
    32013616        } else {
    3202             indicate_error(c);
     3617            indicate_error();
    32033618        }
    32043619        break;
     
    32183633            dot_skip_over_ws();
    32193634        } else {
    3220             indicate_error(c);
     3635            indicate_error();
    32213636        }
    32223637        break;
     
    32313646            mark[c1] = dot;
    32323647        } else {
    3233             indicate_error(c);
     3648            indicate_error();
    32343649        }
    32353650        break;
     
    32583673                dot_right();    // move to right, can move to NL
    32593674        }
    3260         string_insert(dot, p);  // insert the string
     3675        string_insert(dot, p, ALLOW_UNDO);  // insert the string
    32613676        end_cmd_q();    // stop adding to q
    32623677        break;
     
    32653680            p = begin_line(dot);
    32663681            q = end_line(dot);
    3267             p = text_hole_delete(p, q); // delete cur line
    3268             p += string_insert(p, reg[Ureg]);   // insert orig line
     3682            p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
     3683            p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
    32693684            dot = p;
    32703685            dot_skip_over_ws();
     
    32723687        break;
    32733688#endif /* FEATURE_VI_YANKMARK */
     3689#if ENABLE_FEATURE_VI_UNDO
     3690    case 'u':   // u- undo last operation
     3691        undo_pop();
     3692        break;
     3693#endif
    32743694    case '$':           // $- goto end of line
    32753695    case KEYCODE_END:       // Cursor Key End
     
    32873707                p = find_pair(q, *q);
    32883708                if (p == NULL) {
    3289                     indicate_error(c);
     3709                    indicate_error();
    32903710                } else {
    32913711                    dot = p;
     
    32953715        }
    32963716        if (*q == '\n')
    3297             indicate_error(c);
     3717            indicate_error();
    32983718        break;
    32993719    case 'f':           // f- forward to a user specified char
     
    34243844        break;
    34253845#endif /* FEATURE_VI_SEARCH */
    3426     case '0':           // 0- goto begining of line
     3846    case '0':           // 0- goto beginning of line
    34273847    case '1':           // 1-
    34283848    case '2':           // 2-
     
    34423862    case ':':           // :- the colon mode commands
    34433863        p = get_input_line(":");    // get input line- use "status line"
    3444 #if ENABLE_FEATURE_VI_COLON
    34453864        colon(p);       // execute the command
    3446 #else
    3447         if (*p == ':')
    3448             p++;                // move past the ':'
    3449         cnt = strlen(p);
    3450         if (cnt <= 0)
    3451             break;
    3452         if (strncmp(p, "quit", cnt) == 0
    3453          || strncmp(p, "q!", cnt) == 0   // delete lines
    3454         ) {
    3455             if (file_modified && p[1] != '!') {
    3456                 status_line_bold("No write since last change (:%s! overrides)", p);
    3457             } else {
    3458                 editing = 0;
    3459             }
    3460         } else if (strncmp(p, "write", cnt) == 0
    3461                 || strncmp(p, "wq", cnt) == 0
    3462                 || strncmp(p, "wn", cnt) == 0
    3463                 || (p[0] == 'x' && !p[1])
    3464         ) {
    3465             cnt = file_write(current_filename, text, end - 1);
    3466             if (cnt < 0) {
    3467                 if (cnt == -1)
    3468                     status_line_bold("Write error: %s", strerror(errno));
    3469             } else {
    3470                 file_modified = 0;
    3471                 last_file_modified = -1;
    3472                 status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
    3473                 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
    3474                  || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
    3475                 ) {
    3476                     editing = 0;
    3477                 }
    3478             }
    3479         } else if (strncmp(p, "file", cnt) == 0) {
    3480             last_status_cksum = 0;  // force status update
    3481         } else if (sscanf(p, "%d", &j) > 0) {
    3482             dot = find_line(j);     // go to line # j
    3483             dot_skip_over_ws();
    3484         } else {        // unrecognized cmd
    3485             not_implemented(p);
    3486         }
    3487 #endif /* !FEATURE_VI_COLON */
    34883865        break;
    34893866    case '<':           // <- Left  shift something
     
    34923869        c1 = get_one_char();    // get the type of thing to delete
    34933870        find_range(&p, &q, c1);
    3494         yank_delete(p, q, 1, YANKONLY); // save copy before change
     3871        yank_delete(p, q, 1, YANKONLY, NO_UNDO);    // save copy before change
    34953872        p = begin_line(p);
    34963873        q = end_line(q);
     
    35013878                if (*p == '\t') {
    35023879                    // shrink buffer 1 char
    3503                     text_hole_delete(p, p);
     3880                    text_hole_delete(p, p, NO_UNDO);
    35043881                } else if (*p == ' ') {
    35053882                    // we should be calculating columns, not just SPACE
    35063883                    for (j = 0; *p == ' ' && j < tabstop; j++) {
    3507                         text_hole_delete(p, p);
     3884                        text_hole_delete(p, p, NO_UNDO);
    35083885                    }
    35093886                }
    35103887            } else if (c == '>') {
    35113888                // shift right -- add tab or 8 spaces
    3512                 char_insert(p, '\t');
     3889                char_insert(p, '\t', ALLOW_UNDO);
    35133890            }
    35143891        }
     
    35453922        dot = dollar_line(dot); // move to before NL
    35463923        // copy text into a register and delete
    3547         dot = yank_delete(save_dot, dot, 0, YANKDEL);   // delete to e-o-l
     3924        dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO);   // delete to e-o-l
    35483925        if (c == 'C')
    35493926            goto dc_i;  // start inserting
     
    35903967 dc_i:
    35913968        cmd_mode = 1;   // start inserting
     3969        undo_queue_commit();    // commit queue when cmd_mode changes
    35923970        break;
    35933971    case 'J':           // J- join current and next lines together
     
    35953973            dot_end();      // move to NL
    35963974            if (dot < end - 1) {    // make sure not last char in text[]
     3975#if ENABLE_FEATURE_VI_UNDO
     3976                undo_push(dot, 1, UNDO_DEL);
    35973977                *dot++ = ' ';   // replace NL with space
    3598                 file_modified++;
     3978                undo_push((dot - 1), 1, UNDO_INS_CHAIN);
     3979#else
     3980                *dot++ = ' ';
     3981                modified_count++;
     3982#endif
    35993983                while (isblank(*dot)) { // delete leading WS
    3600                     dot_delete();
     3984                    text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
    36013985                }
    36023986            }
     
    36274011    case 'o':           // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
    36284012            dot_end();
    3629             dot = char_insert(dot, '\n');
     4013            dot = char_insert(dot, '\n', ALLOW_UNDO);
    36304014        } else {
    36314015            dot_begin();    // 0
    3632             dot = char_insert(dot, '\n');   // i\n ESC
     4016            dot = char_insert(dot, '\n', ALLOW_UNDO);   // i\n ESC
    36334017            dot_prev(); // -
    36344018        }
     
    36384022 dc5:
    36394023        cmd_mode = 2;
     4024        undo_queue_commit();
    36404025        break;
    36414026    case KEYCODE_DELETE:
    3642         c = 'x';
    3643         // fall through
     4027        if (dot < end - 1)
     4028            dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
     4029        break;
    36444030    case 'X':           // X- delete char before dot
    36454031    case 'x':           // x- delete the current char
     
    36524038                if (c == 'X')
    36534039                    dot--;  // delete prev char
    3654                 dot = yank_delete(dot, dot, 0, YANKDEL);    // delete char
     4040                dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO);    // delete char
    36554041            }
    36564042        } while (--cmdcnt > 0);
     
    36634049        c1 = get_one_char();
    36644050        if (c1 != 'Z') {
    3665             indicate_error(c);
     4051            indicate_error();
    36664052            break;
    36674053        }
    3668         if (file_modified) {
     4054        if (modified_count) {
    36694055            if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
    3670                 status_line_bold("\"%s\" File is read only", current_filename);
     4056                status_line_bold("'%s' is read only", current_filename);
    36714057                break;
    36724058            }
     
    37234109        // determine range, and whether it spans lines
    37244110        ml = find_range(&p, &q, c1);
     4111        place_cursor(0, 0);
    37254112        if (c1 == 27) { // ESC- user changed mind and wants out
    37264113            c = c1 = 27;    // Escape- do nothing
     
    37344121                }
    37354122            }
    3736             dot = yank_delete(p, q, ml, yf);    // delete word
     4123            dot = yank_delete(p, q, ml, yf, ALLOW_UNDO);    // delete word
    37374124        } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
    37384125            // partial line copy text into a register and delete
    3739             dot = yank_delete(p, q, ml, yf);    // delete word
     4126            dot = yank_delete(p, q, ml, yf, ALLOW_UNDO);    // delete word
    37404127        } else if (strchr("cdykjHL+-{}\r\n", c1)) {
    37414128            // whole line copy text into a register and delete
    3742             dot = yank_delete(p, q, ml, yf);    // delete lines
     4129            dot = yank_delete(p, q, ml, yf, ALLOW_UNDO);    // delete lines
    37434130            whole = 1;
    37444131        } else {
     
    37464133            c = c1 = 27;    // error-
    37474134            ml = 0;
    3748             indicate_error(c);
     4135            indicate_error();
    37494136        }
    37504137        if (ml && whole) {
    37514138            if (c == 'c') {
    3752                 dot = char_insert(dot, '\n');
     4139                dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
    37534140                // on the last line of file don't move to prev line
    37544141                if (whole && dot != (end-1)) {
     
    37964183        c1 = get_one_char();    // get the replacement char
    37974184        if (*dot != '\n') {
     4185#if ENABLE_FEATURE_VI_UNDO
     4186            undo_push(dot, 1, UNDO_DEL);
    37984187            *dot = c1;
    3799             file_modified++;
     4188            undo_push(dot, 1, UNDO_INS_CHAIN);
     4189#else
     4190            *dot = c1;
     4191            modified_count++;
     4192#endif
    38004193        }
    38014194        end_cmd_q();    // stop adding to q
     
    38374230    case '~':           // ~- flip the case of letters   a-z -> A-Z
    38384231        do {
     4232#if ENABLE_FEATURE_VI_UNDO
     4233            if (islower(*dot)) {
     4234                undo_push(dot, 1, UNDO_DEL);
     4235                *dot = toupper(*dot);
     4236                undo_push(dot, 1, UNDO_INS_CHAIN);
     4237            } else if (isupper(*dot)) {
     4238                undo_push(dot, 1, UNDO_DEL);
     4239                *dot = tolower(*dot);
     4240                undo_push(dot, 1, UNDO_INS_CHAIN);
     4241            }
     4242#else
    38394243            if (islower(*dot)) {
    38404244                *dot = toupper(*dot);
    3841                 file_modified++;
     4245                modified_count++;
    38424246            } else if (isupper(*dot)) {
    38434247                *dot = tolower(*dot);
    3844                 file_modified++;
    3845             }
     4248                modified_count++;
     4249            }
     4250#endif
    38464251            dot_right();
    38474252        } while (--cmdcnt > 0);
     
    38734278    // if text[] just became empty, add back an empty line
    38744279    if (end == text) {
    3875         char_insert(text, '\n');    // start empty buf with dummy line
     4280        char_insert(text, '\n', NO_UNDO);   // start empty buf with dummy line
    38764281        dot = text;
    38774282    }
Note: See TracChangeset for help on using the changeset viewer.