nc_bloaty: use poll() instead of select()
[oweals/busybox.git] / editors / vi.c
index e38667ad1e6d1f65166d8bce3f86dac9aee1cdef..bbaac50df6c1e91d18e7ce28af2ee0d14862291d 100644 (file)
@@ -30,7 +30,7 @@
 //config:        you may wish to use something else.
 //config:
 //config:config FEATURE_VI_MAX_LEN
-//config:      int "Maximum screen width in vi"
+//config:      int "Maximum screen width"
 //config:      range 256 16384
 //config:      default 4096
 //config:      depends on VI
@@ -39,7 +39,7 @@
 //config:        Make it smaller than 4k only if you are very limited on memory.
 //config:
 //config:config FEATURE_VI_8BIT
-//config:      bool "Allow vi to display 8-bit chars (otherwise shows dots)"
+//config:      bool "Allow to display 8-bit chars (otherwise shows dots)"
 //config:      default n
 //config:      depends on VI
 //config:      help
@@ -53,7 +53,7 @@
 //config:      default y
 //config:      depends on VI
 //config:      help
-//config:        Enable a limited set of colon commands for vi. This does not
+//config:        Enable a limited set of colon commands. This does not
 //config:        provide an "ex" mode.
 //config:
 //config:config FEATURE_VI_YANKMARK
 //config:      default y
 //config:      depends on VI
 //config:      help
-//config:        This will enable you to use yank and put, as well as mark in
-//config:        busybox vi.
+//config:        This will enable you to use yank and put, as well as mark.
 //config:
 //config:config FEATURE_VI_SEARCH
 //config:      bool "Enable search and replace cmds"
 //config:      default y
 //config:      depends on VI
 //config:      help
-//config:        Select this if you wish to be able to do search and replace in
-//config:        busybox vi.
+//config:        Select this if you wish to be able to do search and replace.
 //config:
 //config:config FEATURE_VI_REGEX_SEARCH
 //config:      bool "Enable regex in search and replace"
 //config:      default y
 //config:      depends on VI
 //config:      help
-//config:        Selecting this option will make busybox vi signal aware. This will
-//config:        make busybox vi support SIGWINCH to deal with Window Changes, catch
-//config:        Ctrl-Z and Ctrl-C and alarms.
+//config:        Selecting this option will make vi signal aware. This will support
+//config:        SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
 //config:
 //config:config FEATURE_VI_DOT_CMD
 //config:      bool "Remember previous cmd and \".\" cmd"
 //config:      default y
 //config:      depends on VI
 //config:      help
-//config:        Make busybox vi remember the last command and be able to repeat it.
+//config:        Make vi remember the last command and be able to repeat it.
 //config:
 //config:config FEATURE_VI_READONLY
 //config:      bool "Enable -R option and \"view\" mode"
 //config:        open a file in read-only mode.
 //config:
 //config:config FEATURE_VI_SETOPTS
-//config:      bool "Enable set-able options, ai ic showmatch"
+//config:      bool "Enable settable options, ai ic showmatch"
 //config:      default y
 //config:      depends on VI
 //config:      help
 //config:        Enable the editor to set some (ai, ic, showmatch) options.
 //config:
 //config:config FEATURE_VI_SET
-//config:      bool "Support for :set"
+//config:      bool "Support :set"
 //config:      default y
 //config:      depends on VI
-//config:      help
-//config:        Support for ":set".
 //config:
 //config:config FEATURE_VI_WIN_RESIZE
 //config:      bool "Handle window resize"
 //config:      default y
 //config:      depends on VI
 //config:      help
-//config:        Make busybox vi behave nicely with terminals that get resized.
+//config:        Behave nicely with terminals that get resized.
 //config:
 //config:config FEATURE_VI_ASK_TERMINAL
 //config:      bool "Use 'tell me cursor position' ESC sequence to measure window"
 //config:        this option makes vi perform a last-ditch effort to find it:
 //config:        position cursor to 999,999 and ask terminal to report real
 //config:        cursor position using "ESC [ 6 n" escape sequence, then read stdin.
-//config:
 //config:        This is not clean but helps a lot on serial lines and such.
+//config:
 //config:config FEATURE_VI_UNDO
-//config:      bool "Support undo command 'u'"
+//config:      bool "Support undo command \"u\""
 //config:      default y
 //config:      depends on VI
 //config:      help
 //config:        Support the 'u' command to undo insertion, deletion, and replacement
 //config:        of text.
+//config:
 //config:config FEATURE_VI_UNDO_QUEUE
 //config:      bool "Enable undo operation queuing"
 //config:      default y
 //config:        reached, the contents of the queue are committed to the undo stack.
 //config:        This increases the size of the undo code and allows some undo
 //config:        operations (especially un-typing/backspacing) to be far more useful.
+//config:
 //config:config FEATURE_VI_UNDO_QUEUE_MAX
 //config:      int "Maximum undo character queue size"
 //config:      default 256
@@ -251,7 +248,7 @@ enum {
 // cmds modifying text[]
 // vda: removed "aAiIs" as they switch us into insert mode
 // and remembering input for replay after them makes no sense
-static const char modifying_cmds[] = "cCdDJoOpPrRxX<>~";
+static const char modifying_cmds[] ALIGN1 = "cCdDJoOpPrRxX<>~";
 #endif
 
 enum {
@@ -306,8 +303,8 @@ struct globals {
        smallint editing;        // >0 while we are editing a file
                                 // [code audit says "can be 0, 1 or 2 only"]
        smallint cmd_mode;       // 0=command  1=insert 2=replace
-       int file_modified;       // buffer contents changed (counter, not flag!)
-       int last_file_modified;  // = -1;
+       int modified_count;      // buffer contents changed if !0
+       int last_modified_count; // = -1;
        int save_argc;           // how many file names on cmd line
        int cmdcnt;              // repetition count
        unsigned rows, columns;  // the terminal screen is this size
@@ -357,7 +354,7 @@ struct globals {
 #if ENABLE_FEATURE_VI_USE_SIGNALS
        sigjmp_buf restart;     // catch_sig()
 #endif
-       struct termios term_orig, term_vi; // remember what the cooked mode was
+       struct termios term_orig; // remember what the cooked mode was
 #if ENABLE_FEATURE_VI_COLON
        char *initial_cmds[3];  // currently 2 entries, NULL terminated
 #endif
@@ -378,40 +375,39 @@ struct globals {
        char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
 #if ENABLE_FEATURE_VI_UNDO
 // undo_push() operations
-#define UNDO_INS 0
-#define UNDO_DEL 1
-#define UNDO_INS_CHAIN 2
-#define UNDO_DEL_CHAIN 3
+#define UNDO_INS         0
+#define UNDO_DEL         1
+#define UNDO_INS_CHAIN   2
+#define UNDO_DEL_CHAIN   3
 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
 #define UNDO_QUEUED_FLAG 4
-#define UNDO_INS_QUEUED 4
-#define UNDO_DEL_QUEUED 5
-#define UNDO_USE_SPOS 32
-#define UNDO_EMPTY 64
+#define UNDO_INS_QUEUED  4
+#define UNDO_DEL_QUEUED  5
+#define UNDO_USE_SPOS   32
+#define UNDO_EMPTY      64
 // Pass-through flags for functions that can be undone
-#define NO_UNDO 0
-#define ALLOW_UNDO 1
+#define NO_UNDO          0
+#define ALLOW_UNDO       1
 #define ALLOW_UNDO_CHAIN 2
-#if ENABLE_FEATURE_VI_UNDO_QUEUE
+# if ENABLE_FEATURE_VI_UNDO_QUEUE
 #define ALLOW_UNDO_QUEUED 3
        char undo_queue_state;
        int undo_q;
        char *undo_queue_spos;  // Start position of queued operation
        char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
-#else
+# else
 // If undo queuing disabled, don't invoke the missing queue logic
 #define ALLOW_UNDO_QUEUED 1
-#endif /* ENABLE_FEATURE_VI_UNDO_QUEUE */
+# endif
 
        struct undo_object {
                struct undo_object *prev;       // Linking back avoids list traversal (LIFO)
-               int u_type;             // 0=deleted, 1=inserted, 2=swapped
                int start;              // Offset where the data should be restored/deleted
                int length;             // total data size
-               char *undo_text;        // ptr to text that will be inserted
+               uint8_t u_type;         // 0=deleted, 1=inserted, 2=swapped
+               char undo_text[1];      // text that was deleted (if deletion)
        } *undo_stack_tail;
 #endif /* ENABLE_FEATURE_VI_UNDO */
-
 };
 #define G (*ptr_to_globals)
 #define text           (G.text          )
@@ -423,8 +419,8 @@ struct globals {
 #define vi_setops               (G.vi_setops          )
 #define editing                 (G.editing            )
 #define cmd_mode                (G.cmd_mode           )
-#define file_modified           (G.file_modified      )
-#define last_file_modified      (G.last_file_modified )
+#define modified_count          (G.modified_count     )
+#define last_modified_count     (G.last_modified_count)
 #define save_argc               (G.save_argc          )
 #define cmdcnt                  (G.cmdcnt             )
 #define rows                    (G.rows               )
@@ -466,7 +462,6 @@ struct globals {
 #define context_end    (G.context_end   )
 #define restart        (G.restart       )
 #define term_orig      (G.term_orig     )
-#define term_vi        (G.term_vi       )
 #define initial_cmds   (G.initial_cmds  )
 #define readbuffer     (G.readbuffer    )
 #define scr_out_buf    (G.scr_out_buf   )
@@ -485,13 +480,12 @@ struct globals {
 
 #define INIT_G() do { \
        SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
-       last_file_modified = -1; \
+       last_modified_count = -1; \
        /* "" but has space for 2 chars: */ \
        IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
 } while (0)
 
 
-static int init_text_buffer(char *); // init from file or create new
 static void edit_file(char *); // edit one file
 static void do_cmd(int);       // execute a command
 static int next_tabstop(int);
@@ -502,7 +496,7 @@ static char *prev_line(char *);     // return pointer to prev line B-o-l
 static char *next_line(char *);        // return pointer to next line B-o-l
 static char *end_screen(void); // get pointer to last char on screen
 static int count_lines(char *, char *);        // count line from start to stop
-static char *find_line(int);   // find begining of line #li
+static char *find_line(int);   // find beginning of line #li
 static char *move_to_col(char *, int); // move "p" to column l
 static void dot_left(void);    // move dot left- dont leave line
 static void dot_right(void);   // move dot right- dont leave line
@@ -543,10 +537,6 @@ static void cookmode(void);        // return to "cooked" mode on tty
 static int mysleep(int);
 static int readit(void);       // read (maybe cursor) key from stdin
 static int get_one_char(void); // read 1 char from stdin
-static int file_size(const char *);   // what is the byte size of "fn"
-#if !ENABLE_FEATURE_VI_READONLY
-#define file_insert(fn, p, update_ro_status) file_insert(fn, p)
-#endif
 // file_insert might reallocate text[]!
 static int file_insert(const char *, char *, int);
 static int file_write(char *, char *, char *);
@@ -568,8 +558,7 @@ static void redraw(int);    // force a full screen refresh
 static char* format_line(char* /*, int*/);
 static void refresh(int);      // update the terminal from screen[]
 
-static void Indicate_Error(void);       // use flash or beep to indicate error
-#define indicate_error(c) Indicate_Error()
+static void indicate_error(void);       // use flash or beep to indicate error
 static void Hit_Return(void);
 
 #if ENABLE_FEATURE_VI_SEARCH
@@ -578,8 +567,8 @@ static char *char_search(char *, const char *, int, int);   // search for pattern
 #if ENABLE_FEATURE_VI_COLON
 static char *get_one_address(char *, int *);   // get colon addr, if present
 static char *get_address(char *, int *, int *);        // get two colon addrs, if present
-static void colon(char *);     // execute the "colon" mode cmds
 #endif
+static void colon(char *);     // execute the "colon" mode cmds
 #if ENABLE_FEATURE_VI_USE_SIGNALS
 static void winch_sig(int);    // catch window size changes
 static void suspend_sig(int);  // catch ctrl-Z
@@ -608,6 +597,7 @@ static char what_reg(void);         // what is letter of current YDreg
 static void check_context(char);       // remember context for '' command
 #endif
 #if ENABLE_FEATURE_VI_UNDO
+static void flush_undo_data(void);
 static void undo_push(char *, unsigned int, unsigned char);    // Push an operation on the undo stack
 static void undo_pop(void);    // Undo the last operation
 # if ENABLE_FEATURE_VI_UNDO_QUEUE
@@ -616,6 +606,7 @@ static void undo_queue_commit(void);        // Flush any queued objects to the undo sta
 # define undo_queue_commit() ((void)0)
 # endif
 #else
+#define flush_undo_data()   ((void)0)
 #define undo_queue_commit() ((void)0)
 #endif
 
@@ -723,30 +714,29 @@ int vi_main(int argc, char **argv)
 static int init_text_buffer(char *fn)
 {
        int rc;
-       int size = file_size(fn);       // file size. -1 means does not exist.
+
+       flush_undo_data();
+       modified_count = 0;
+       last_modified_count = -1;
+#if ENABLE_FEATURE_VI_YANKMARK
+       /* init the marks */
+       memset(mark, 0, sizeof(mark));
+#endif
 
        /* allocate/reallocate text buffer */
        free(text);
-       text_size = size + 10240;
+       text_size = 10240;
        screenbegin = dot = end = text = xzalloc(text_size);
 
        if (fn != current_filename) {
                free(current_filename);
                current_filename = xstrdup(fn);
        }
-       if (size < 0) {
-               // file dont exist. Start empty buf with dummy line
+       rc = file_insert(fn, text, 1);
+       if (rc < 0) {
+               // file doesnt exist. Start empty buf with dummy line
                char_insert(text, '\n', NO_UNDO);
-               rc = 0;
-       } else {
-               rc = file_insert(fn, text, 1);
        }
-       file_modified = 0;
-       last_file_modified = -1;
-#if ENABLE_FEATURE_VI_YANKMARK
-       /* init the marks. */
-       memset(mark, 0, sizeof(mark));
-#endif
        return rc;
 }
 
@@ -1014,13 +1004,71 @@ static void setops(const char *args, const char *opname, int flg_no,
 }
 #endif
 
+#endif /* FEATURE_VI_COLON */
+
 // buf must be no longer than MAX_INPUT_LEN!
 static void colon(char *buf)
 {
+#if !ENABLE_FEATURE_VI_COLON
+       /* Simple ":cmd" handler with minimal set of commands */
+       char *p = buf;
+       int cnt;
+
+       if (*p == ':')
+               p++;
+       cnt = strlen(p);
+       if (cnt == 0)
+               return;
+       if (strncmp(p, "quit", cnt) == 0
+        || strncmp(p, "q!", cnt) == 0
+       ) {
+               if (modified_count && p[1] != '!') {
+                       status_line_bold("No write since last change (:%s! overrides)", p);
+               } else {
+                       editing = 0;
+               }
+               return;
+       }
+       if (strncmp(p, "write", cnt) == 0
+        || strncmp(p, "wq", cnt) == 0
+        || strncmp(p, "wn", cnt) == 0
+        || (p[0] == 'x' && !p[1])
+       ) {
+               cnt = file_write(current_filename, text, end - 1);
+               if (cnt < 0) {
+                       if (cnt == -1)
+                               status_line_bold("Write error: %s", strerror(errno));
+               } else {
+                       modified_count = 0;
+                       last_modified_count = -1;
+                       status_line("'%s' %dL, %dC",
+                               current_filename,
+                               count_lines(text, end - 1), cnt
+                       );
+                       if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
+                        || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
+                       ) {
+                               editing = 0;
+                       }
+               }
+               return;
+       }
+       if (strncmp(p, "file", cnt) == 0) {
+               last_status_cksum = 0;  // force status update
+               return;
+       }
+       if (sscanf(p, "%d", &cnt) > 0) {
+               dot = find_line(cnt);
+               dot_skip_over_ws();
+               return;
+       }
+       not_implemented(p);
+#else
+
        char c, *orig_buf, *buf1, *q, *r;
        char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
-       int i, l, li, ch, b, e;
-       int useforce, forced = FALSE;
+       int i, l, li, b, e;
+       int useforce;
 
        // :3154        // if (-e line 3154) goto it  else stay put
        // :4,33w! foo  // write a portion of buffer to file "foo"
@@ -1042,7 +1090,7 @@ static void colon(char *buf)
        if (*buf == ':')
                buf++;                  // move past the ':'
 
-       li = ch = i = 0;
+       li = i = 0;
        b = e = -1;
        q = text;                       // assume 1,$ for the range
        r = end - 1;
@@ -1123,8 +1171,10 @@ static void colon(char *buf)
                dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO);        // save, then delete lines
                dot_skip_over_ws();
        } else if (strncmp(cmd, "edit", i) == 0) {      // Edit a file
+               int size;
+
                // don't edit, if the current file has been modified
-               if (file_modified && !useforce) {
+               if (modified_count && !useforce) {
                        status_line_bold("No write since last change (:%s! overrides)", cmd);
                        goto ret;
                }
@@ -1140,8 +1190,7 @@ static void colon(char *buf)
                        goto ret;
                }
 
-               if (init_text_buffer(fn) < 0)
-                       goto ret;
+               size = init_text_buffer(fn);
 
 #if ENABLE_FEATURE_VI_YANKMARK
                if (Ureg >= 0 && Ureg < 28) {
@@ -1157,12 +1206,14 @@ static void colon(char *buf)
                li = count_lines(text, end - 1);
                status_line("'%s'%s"
                        IF_FEATURE_VI_READONLY("%s")
-                       " %dL, %dC", current_filename,
-                       (file_size(fn) < 0 ? " [New file]" : ""),
+                       " %dL, %dC",
+                       current_filename,
+                       (size < 0 ? " [New file]" : ""),
                        IF_FEATURE_VI_READONLY(
                                ((readonly_mode) ? " [Readonly]" : ""),
                        )
-                       li, ch);
+                       li, (int)(end - text)
+               );
        } else if (strncmp(cmd, "file", i) == 0) {      // what File is this
                if (b != -1 || e != -1) {
                        status_line_bold("No address allowed on this command");
@@ -1227,7 +1278,7 @@ static void colon(char *buf)
                        goto ret;
                }
                // don't exit if the file been modified
-               if (file_modified) {
+               if (modified_count) {
                        status_line_bold("No write since last change (:%s! overrides)", cmd);
                        goto ret;
                }
@@ -1251,6 +1302,8 @@ static void colon(char *buf)
                }
                editing = 0;
        } else if (strncmp(cmd, "read", i) == 0) {      // read file into text[]
+               int size;
+
                fn = args;
                if (!fn[0]) {
                        status_line_bold("No filename given");
@@ -1260,30 +1313,35 @@ static void colon(char *buf)
                        q = begin_line(dot);    // assume "dot"
                }
                // read after current line- unless user said ":0r foo"
-               if (b != 0)
+               if (b != 0) {
                        q = next_line(q);
+                       // read after last line
+                       if (q == end-1)
+                               ++q;
+               }
                { // dance around potentially-reallocated text[]
                        uintptr_t ofs = q - text;
-                       ch = file_insert(fn, q, 0);
+                       size = file_insert(fn, q, 0);
                        q = text + ofs;
                }
-               if (ch < 0)
+               if (size < 0)
                        goto ret;       // nothing was inserted
                // how many lines in text[]?
-               li = count_lines(q, q + ch - 1);
+               li = count_lines(q, q + size - 1);
                status_line("'%s'"
                        IF_FEATURE_VI_READONLY("%s")
-                       " %dL, %dC", fn,
+                       " %dL, %dC",
+                       fn,
                        IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
-                       li, ch);
-               if (ch > 0) {
+                       li, size
+               );
+               if (size > 0) {
                        // if the insert is before "dot" then we need to update
                        if (q <= dot)
-                               dot += ch;
-                       // file_modified++;
+                               dot += size;
                }
        } else if (strncmp(cmd, "rewind", i) == 0) {    // rewind cmd line args
-               if (file_modified && !useforce) {
+               if (modified_count && !useforce) {
                        status_line_bold("No write since last change (:%s! overrides)", cmd);
                } else {
                        // reset the filenames to edit
@@ -1399,12 +1457,15 @@ static void colon(char *buf)
                }
 #endif /* FEATURE_VI_SEARCH */
        } else if (strncmp(cmd, "version", i) == 0) {  // show software version
-               status_line(BB_VER " " BB_BT);
+               status_line(BB_VER);
        } else if (strncmp(cmd, "write", i) == 0  // write text to file
                || strncmp(cmd, "wq", i) == 0
                || strncmp(cmd, "wn", i) == 0
                || (cmd[0] == 'x' && !cmd[1])
        ) {
+               int size;
+               //int forced = FALSE;
+
                // is there a file name to write to?
                if (args[0]) {
                        fn = args;
@@ -1417,34 +1478,33 @@ static void colon(char *buf)
 #endif
                // 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) {
+               size = r - q + 1;
+               //if (useforce) {
                        // if "fn" is not write-able, chmod u+w
                        // sprintf(syscmd, "chmod u+w %s", fn);
                        // system(syscmd);
-                       forced = TRUE;
-               }
+                       // forced = TRUE;
+               //}
                l = file_write(fn, q, r);
-               if (useforce && forced) {
+               //if (useforce && forced) {
                        // chmod u-w
                        // sprintf(syscmd, "chmod u-w %s", fn);
                        // system(syscmd);
-                       forced = FALSE;
-               }
+                       // forced = FALSE;
+               //}
                if (l < 0) {
                        if (l == -1)
                                status_line_bold_errno(fn);
                } else {
                        status_line("'%s' %dL, %dC", fn, li, l);
-                       if (q == text && r == end - 1 && l == ch) {
-                               file_modified = 0;
-                               last_file_modified = -1;
+                       if (q == text && r == end - 1 && l == size) {
+                               modified_count = 0;
+                               last_modified_count = -1;
                        }
                        if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
                            || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
                            )
-                        && l == ch
+                        && l == size
                        ) {
                                editing = 0;
                        }
@@ -1471,9 +1531,8 @@ static void colon(char *buf)
  colon_s_fail:
        status_line(":s expression missing delimiters");
 #endif
-}
-
 #endif /* FEATURE_VI_COLON */
+}
 
 static void Hit_Return(void)
 {
@@ -1620,10 +1679,10 @@ static char *dollar_line(char *p) // return pointer to just before NL line
 
 static char *prev_line(char *p) // return pointer first char prev line
 {
-       p = begin_line(p);      // goto begining of cur line
+       p = begin_line(p);      // goto beginning of cur line
        if (p > text && p[-1] == '\n')
                p--;                    // step to prev line
-       p = begin_line(p);      // goto begining of prev line
+       p = begin_line(p);      // goto beginning of prev line
        return p;
 }
 
@@ -1671,7 +1730,7 @@ static int count_lines(char *start, char *stop)
        return cnt;
 }
 
-static char *find_line(int li) // find begining of line #li
+static char *find_line(int li) // find beginning of line #li
 {
        char *q;
 
@@ -1776,11 +1835,11 @@ static char *bound_dot(char *p) // make sure  text[0] <= P < "end"
 {
        if (p >= end && end > text) {
                p = end - 1;
-               indicate_error('1');
+               indicate_error();
        }
        if (p < text) {
                p = text;
-               indicate_error('2');
+               indicate_error();
        }
        return p;
 }
@@ -1940,7 +1999,7 @@ static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
 # endif
                }
 #else
-               file_modified++;
+               modified_count++;
 #endif /* ENABLE_FEATURE_VI_UNDO */
                p++;
        } else if (c == 27) {   // Is this an ESC?
@@ -1953,22 +2012,14 @@ static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
                        p--;
                }
        } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
-               //     123456789
-               if ((p[-1] != '\n') && (dot>text)) {
+               if (p > text) {
                        p--;
                        p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED);  // shrink buffer 1 char
                }
        } else {
-#if ENABLE_FEATURE_VI_SETOPTS
                // insert a char into text[]
-               char *sp;               // "save p"
-#endif
-
                if (c == 13)
                        c = '\n';       // translate \r to \n
-#if ENABLE_FEATURE_VI_SETOPTS
-               sp = p;                 // remember addr of insert
-#endif
 #if ENABLE_FEATURE_VI_UNDO
 # if ENABLE_FEATURE_VI_UNDO_QUEUE
                if (c == '\n')
@@ -1988,12 +2039,12 @@ static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
 # endif
                }
 #else
-               file_modified++;
+               modified_count++;
 #endif /* ENABLE_FEATURE_VI_UNDO */
                p += 1 + stupid_insert(p, c);   // insert the char
 #if ENABLE_FEATURE_VI_SETOPTS
-               if (showmatch && strchr(")]}", *sp) != NULL) {
-                       showmatching(sp);
+               if (showmatch && strchr(")]}", c) != NULL) {
+                       showmatching(p - 1);
                }
                if (autoindent && c == '\n') {  // auto indent the new line
                        char *q;
@@ -2153,34 +2204,32 @@ static char *skip_thing(char *p, int linecnt, int dir, int type)
 }
 
 // find matching char of pair  ()  []  {}
+// will crash if c is not one of these
 static char *find_pair(char *p, const char c)
 {
-       char match, *q;
+       const char *braces = "()[]{}";
+       char match;
        int dir, level;
 
-       match = ')';
+       dir = strchr(braces, c) - braces;
+       dir ^= 1;
+       match = braces[dir];
+       dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */
+
+       // look for match, count levels of pairs  (( ))
        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)
+       for (;;) {
+               p += dir;
+               if (p < text || p >= end)
+                       return NULL;
+               if (*p == c)
                        level++;        // increase pair levels
-               if (*q == match)
+               if (*p == match) {
                        level--;        // reduce pair level
-               if (level == 0)
-                       break;          // found matching pair
+                       if (level == 0)
+                               return p; // found matching pair
+               }
        }
-       if (level != 0)
-               q = NULL;               // indicate no match
-       return q;
 }
 
 #if ENABLE_FEATURE_VI_SETOPTS
@@ -2192,7 +2241,7 @@ static void showmatching(char *p)
        // 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
+               indicate_error();       // no matching char
        } else {
                // "q" now points to matching pair
                save_dot = dot; // remember where we are
@@ -2206,10 +2255,22 @@ static void showmatching(char *p)
 #endif /* FEATURE_VI_SETOPTS */
 
 #if ENABLE_FEATURE_VI_UNDO
+static void flush_undo_data(void)
+{
+       struct undo_object *undo_entry;
+
+       while (undo_stack_tail) {
+               undo_entry = undo_stack_tail;
+               undo_stack_tail = undo_entry->prev;
+               free(undo_entry);
+       }
+}
+
 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
-static void undo_push(char *src, unsigned int length, unsigned char u_type)    // Add to the undo stack
+static void undo_push(char *src, unsigned int length, uint8_t u_type)  // Add to the undo stack
 {
-       struct undo_object *undo_temp;
+       struct undo_object *undo_entry;
+
        // "u_type" values
        // UNDO_INS: insertion, undo will remove from buffer
        // UNDO_DEL: deleted text, undo will restore to buffer
@@ -2237,7 +2298,7 @@ static void undo_push(char *src, unsigned int length, unsigned char u_type)       // A
                case UNDO_DEL:
                        undo_queue_spos = src;
                        undo_q++;
-                       undo_queue[(CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q)] = *src;
+                       undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
                        // If queue is full, dump it into an object
                        if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
                                undo_queue_commit();
@@ -2274,81 +2335,83 @@ static void undo_push(char *src, unsigned int length, unsigned char u_type)     // A
        u_type = u_type & ~UNDO_QUEUED_FLAG;
 #endif
 
-       // Allocate a new undo object and use it as the stack tail
-       undo_temp = undo_stack_tail;
-       undo_stack_tail = xmalloc(sizeof(struct undo_object));
+       // Allocate a new undo object
+       if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
+               // For UNDO_DEL objects, save deleted text
+               if ((src + length) == end)
+                       length--;
+               // If this deletion empties text[], strip the newline. When the buffer becomes
+               // zero-length, a newline is added back, which requires this to compensate.
+               undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
+               memcpy(undo_entry->undo_text, src, length);
+       } else {
+               undo_entry = xzalloc(sizeof(*undo_entry));
+       }
+       undo_entry->length = length;
 #if ENABLE_FEATURE_VI_UNDO_QUEUE
        if ((u_type & UNDO_USE_SPOS) != 0) {
-               undo_stack_tail->start = undo_queue_spos - text;        // use start position from queue
+               undo_entry->start = undo_queue_spos - text;     // use start position from queue
        } else {
-               undo_stack_tail->start = src - text;    // use offset from start of text buffer
+               undo_entry->start = src - text; // use offset from start of text buffer
        }
        u_type = (u_type & ~UNDO_USE_SPOS);
 #else
-       undo_stack_tail->start = src - text;
-#endif /* ENABLE_FEATURE_VI_UNDO_QUEUE */
-       // For UNDO_DEL objects, copy the deleted text somewhere
-       switch (u_type) {
-               case UNDO_DEL:
-               case UNDO_DEL_CHAIN:
-                       if ((src + length) == end)
-                               length--;
-                       // If this deletion empties text[], strip the newline. When the buffer becomes
-                       // zero-length, a newline is added back, which requires this to compensate.
-                       undo_stack_tail->undo_text = xmalloc(length);
-                       memcpy(undo_stack_tail->undo_text, src, length);
-                       break;
-       }
-       undo_stack_tail->prev = undo_temp;
-       undo_stack_tail->length = length;
-       undo_stack_tail->u_type = u_type;
-       file_modified++;
+       undo_entry->start = src - text;
+#endif
+       undo_entry->u_type = u_type;
+
+       // Push it on undo stack
+       undo_entry->prev = undo_stack_tail;
+       undo_stack_tail = undo_entry;
+       modified_count++;
 }
 
 static void undo_pop(void)     // Undo the last operation
 {
-       int repeat = 0;
+       int repeat;
        char *u_start, *u_end;
-       struct undo_object *undo_temp;
+       struct undo_object *undo_entry;
 
        // Commit pending undo queue before popping (should be unnecessary)
        undo_queue_commit();
 
+       undo_entry = undo_stack_tail;
        // Check for an empty undo stack
-       if (undo_stack_tail == NULL) {
+       if (!undo_entry) {
                status_line("Already at oldest change");
                return;
        }
 
-       switch (undo_stack_tail->u_type) {
+       switch (undo_entry->u_type) {
        case UNDO_DEL:
        case UNDO_DEL_CHAIN:
                // make hole and put in text that was deleted; deallocate text
-               u_start = text + undo_stack_tail->start;
-               text_hole_make(u_start, undo_stack_tail->length);
-               memcpy(u_start, undo_stack_tail->undo_text, undo_stack_tail->length);
-               free(undo_stack_tail->undo_text);
+               u_start = text + undo_entry->start;
+               text_hole_make(u_start, undo_entry->length);
+               memcpy(u_start, undo_entry->undo_text, undo_entry->length);
                status_line("Undo [%d] %s %d chars at position %d",
-                       file_modified, "restored",
-                       undo_stack_tail->length, undo_stack_tail->start);
+                       modified_count, "restored",
+                       undo_entry->length, undo_entry->start
+               );
                break;
        case UNDO_INS:
        case UNDO_INS_CHAIN:
                // delete what was inserted
-               u_start = undo_stack_tail->start + text;
-               u_end = u_start - 1 + undo_stack_tail->length;
+               u_start = undo_entry->start + text;
+               u_end = u_start - 1 + undo_entry->length;
                text_hole_delete(u_start, u_end, NO_UNDO);
                status_line("Undo [%d] %s %d chars at position %d",
-                       file_modified, "deleted",
-                       undo_stack_tail->length, undo_stack_tail->start);
+                       modified_count, "deleted",
+                       undo_entry->length, undo_entry->start
+               );
                break;
        }
-       // For chained operations, continue popping all the way down the chain.
+       repeat = 0;
+       switch (undo_entry->u_type) {
        // If this is the end of a chain, lower modification count and refresh display
-       switch (undo_stack_tail->u_type) {
        case UNDO_DEL:
        case UNDO_INS:
-               dot = (text + undo_stack_tail->start);
+               dot = (text + undo_entry->start);
                refresh(FALSE);
                break;
        case UNDO_DEL_CHAIN:
@@ -2357,11 +2420,11 @@ static void undo_pop(void)      // Undo the last operation
                break;
        }
        // Deallocate the undo object we just processed
-       undo_temp = undo_stack_tail->prev;
-       free(undo_stack_tail);
-       undo_stack_tail = undo_temp;
-       file_modified--;
-       if (repeat == 1) {
+       undo_stack_tail = undo_entry->prev;
+       free(undo_entry);
+       modified_count--;
+       // For chained operations, continue popping all the way down the chain.
+       if (repeat) {
                undo_pop();     // Follow the undo chain if one exists
        }
 }
@@ -2372,7 +2435,7 @@ static void undo_queue_commit(void)       // Flush any queued objects to the undo stac
        // Pushes the queue object onto the undo stack
        if (undo_q > 0) {
                // Deleted character undo events grow from the end
-               undo_push((undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q),
+               undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
                        undo_q,
                        (undo_queue_state | UNDO_USE_SPOS)
                );
@@ -2451,13 +2514,13 @@ static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through
                        break;
 # endif
        }
-       file_modified--;
+       modified_count--;
 #endif
        if (src < text || src > end)
                goto thd0;
        if (dest < text || dest >= end)
                goto thd0;
-       file_modified++;
+       modified_count++;
        if (src >= end)
                goto thd_atend; // just delete the end of the buffer
        memmove(dest, src, cnt);
@@ -2655,9 +2718,8 @@ static char *swap_context(char *p) // goto new context for '' command make this
        // 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
+               mark[27] = p;
+               mark[26] = p = tmp;
                context_start = prev_line(prev_line(prev_line(p)));
                context_end = next_line(next_line(next_line(p)));
        }
@@ -2668,15 +2730,9 @@ static char *swap_context(char *p) // goto new context for '' command make this
 //----- Set terminal attributes --------------------------------
 static void rawmode(void)
 {
-       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);
-       term_vi.c_cc[VMIN] = 1;
-       term_vi.c_cc[VTIME] = 0;
-       erase_char = term_vi.c_cc[VERASE];
-       tcsetattr_stdin_TCSANOW(&term_vi);
+       // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
+       set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
+       erase_char = term_orig.c_cc[VERASE];
 }
 
 static void cookmode(void)
@@ -2737,6 +2793,9 @@ static int mysleep(int hund)      // sleep for 'hund' 1/100 seconds or stdin ready
 {
        struct pollfd pfd[1];
 
+       if (hund != 0)
+               fflush_all();
+
        pfd[0].fd = STDIN_FILENO;
        pfd[0].events = POLLIN;
        return safe_poll(pfd, 1, hund*10) > 0;
@@ -2833,62 +2892,50 @@ static char *get_input_line(const char *prompt)
 #undef buf
 }
 
-static int file_size(const char *fn) // what is the byte size of "fn"
-{
-       struct stat st_buf;
-       int cnt;
-
-       cnt = -1;
-       if (fn && stat(fn, &st_buf) == 0)       // see if file exists
-               cnt = (int) st_buf.st_size;
-       return cnt;
-}
-
 // might reallocate text[]!
-static int file_insert(const char *fn, char *p, int update_ro_status)
+static int file_insert(const char *fn, char *p, int initial)
 {
        int cnt = -1;
        int fd, size;
        struct stat statbuf;
 
+       if (p < text)
+               p = text;
+       if (p > end)
+               p = end;
+
+       fd = open(fn, O_RDONLY);
+       if (fd < 0) {
+               if (!initial)
+                       status_line_bold_errno(fn);
+               return cnt;
+       }
+
        /* Validate file */
-       if (stat(fn, &statbuf) < 0) {
+       if (fstat(fd, &statbuf) < 0) {
                status_line_bold_errno(fn);
-               goto fi0;
+               goto fi;
        }
        if (!S_ISREG(statbuf.st_mode)) {
-               // This is not a regular file
                status_line_bold("'%s' is not a regular file", fn);
-               goto fi0;
-       }
-       if (p < text || p > end) {
-               status_line_bold("Trying to insert file outside of memory");
-               goto fi0;
-       }
-
-       // read file to buffer
-       fd = open(fn, O_RDONLY);
-       if (fd < 0) {
-               status_line_bold_errno(fn);
-               goto fi0;
+               goto fi;
        }
        size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
        p += text_hole_make(p, size);
-       cnt = safe_read(fd, p, size);
+       cnt = full_read(fd, p, size);
        if (cnt < 0) {
                status_line_bold_errno(fn);
                p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
        } else if (cnt < size) {
-               // There was a partial read, shrink unused space text[]
-               p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);   // un-do buffer insert
+               // There was a partial read, shrink unused space
+               p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
                status_line_bold("can't read '%s'", fn);
        }
-//     if (cnt >= size)
-//             file_modified++;
+ fi:
        close(fd);
- fi0:
+
 #if ENABLE_FEATURE_VI_READONLY
-       if (update_ro_status
+       if (initial
         && ((access(fn, W_OK) < 0) ||
                /* root will always have access()
                 * so we check fileperms too */
@@ -2921,7 +2968,7 @@ static int file_write(char *fn, char *first, char *last)
        ftruncate(fd, charcnt);
        if (charcnt == cnt) {
                // good write
-               //file_modified = FALSE;
+               //modified_count = FALSE;
        } else {
                charcnt = 0;
        }
@@ -2994,7 +3041,7 @@ static void flash(int h)
        redraw(TRUE);
 }
 
-static void Indicate_Error(void)
+static void indicate_error(void)
 {
 #if ENABLE_FEATURE_VI_CRASHME
        if (crashme > 0)
@@ -3143,7 +3190,7 @@ static int format_edit_status(void)
 
        int cur, percent, ret, trunc_at;
 
-       // file_modified is now a counter rather than a flag.  this
+       // modified_count is now a counter rather than a flag.  this
        // helps reduce the amount of line counting we need to do.
        // (this will cause a mis-reporting of modified status
        // once every MAXINT editing operations.)
@@ -3153,11 +3200,12 @@ static int format_edit_status(void)
        // we're on, then we shouldn't have to do this count_lines()
        cur = count_lines(text, dot);
 
-       // reduce counting -- the total lines can't have
-       // changed if we haven't done any edits.
-       if (file_modified != last_file_modified) {
+       // count_lines() is expensive.
+       // Call it only if something was changed since last time
+       // we were here:
+       if (modified_count != last_modified_count) {
                tot = cur + count_lines(dot, end - 1) - 1;
-               last_file_modified = file_modified;
+               last_modified_count = modified_count;
        }
 
        //    current line         percent
@@ -3184,7 +3232,7 @@ static int format_edit_status(void)
 #if ENABLE_FEATURE_VI_READONLY
                (readonly_mode ? " [Readonly]" : ""),
 #endif
-               (file_modified ? " [Modified]" : ""),
+               (modified_count ? " [Modified]" : ""),
                cur, tot, percent);
 
        if (ret >= 0 && ret < trunc_at)
@@ -3297,7 +3345,7 @@ static void refresh(int full_screen)
                        tp = t + 1;
                }
 
-               // see if there are any changes between vitual screen and out_buf
+               // see if there are any changes between virtual screen and out_buf
                changed = FALSE;        // assume no change
                cs = 0;
                ce = columns - 1;
@@ -3334,7 +3382,7 @@ static void refresh(int full_screen)
                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 out_buf
+               // is there a change between virtual screen and out_buf
                if (changed) {
                        // copy changed part of buffer to virtual screen
                        memcpy(sp+cs, out_buf+cs, ce-cs+1);
@@ -3536,7 +3584,7 @@ static void do_cmd(int c)
                break;
        case 27:                        // esc
                if (cmd_mode == 0)
-                       indicate_error(c);
+                       indicate_error();
                cmd_mode = 0;   // stop insrting
                undo_queue_commit();
                end_cmd_q();
@@ -3555,12 +3603,13 @@ static void do_cmd(int c)
                if ((unsigned)c1 <= 25) { // a-z?
                        YDreg = c1;
                } else {
-                       indicate_error(c);
+                       indicate_error();
                }
                break;
        case '\'':                      // '- goto a specific mark
-               c1 = (get_one_char() | 0x20) - 'a';
-               if ((unsigned)c1 <= 25) { // a-z?
+               c1 = (get_one_char() | 0x20);
+               if ((unsigned)(c1 - 'a') <= 25) { // a-z?
+                       c1 = (c1 - 'a');
                        // get the b-o-l
                        q = mark[c1];
                        if (text <= q && q < end) {
@@ -3573,7 +3622,7 @@ static void do_cmd(int c)
                        dot_begin();    // go to B-o-l
                        dot_skip_over_ws();
                } else {
-                       indicate_error(c);
+                       indicate_error();
                }
                break;
        case 'm':                       // m- Mark a line
@@ -3586,7 +3635,7 @@ static void do_cmd(int c)
                        // remember the line
                        mark[c1] = dot;
                } else {
-                       indicate_error(c);
+                       indicate_error();
                }
                break;
        case 'P':                       // P- Put register before
@@ -3616,11 +3665,6 @@ static void do_cmd(int c)
                string_insert(dot, p, ALLOW_UNDO);      // insert the string
                end_cmd_q();    // stop adding to q
                break;
-#if ENABLE_FEATURE_VI_UNDO
-       case 'u':       // u- undo last operation
-               undo_pop();
-               break;
-#endif
        case 'U':                       // U- Undo; replace current line with original version
                if (reg[Ureg] != NULL) {
                        p = begin_line(dot);
@@ -3632,6 +3676,11 @@ static void do_cmd(int c)
                }
                break;
 #endif /* FEATURE_VI_YANKMARK */
+#if ENABLE_FEATURE_VI_UNDO
+       case 'u':       // u- undo last operation
+               undo_pop();
+               break;
+#endif
        case '$':                       // $- goto end of line
        case KEYCODE_END:               // Cursor Key End
                for (;;) {
@@ -3647,7 +3696,7 @@ static void do_cmd(int c)
                                // we found half of a pair
                                p = find_pair(q, *q);
                                if (p == NULL) {
-                                       indicate_error(c);
+                                       indicate_error();
                                } else {
                                        dot = p;
                                }
@@ -3655,7 +3704,7 @@ static void do_cmd(int c)
                        }
                }
                if (*q == '\n')
-                       indicate_error(c);
+                       indicate_error();
                break;
        case 'f':                       // f- forward to a user specified char
                last_forward_char = get_one_char();     // get the search char
@@ -3784,7 +3833,7 @@ static void do_cmd(int c)
                }
                break;
 #endif /* FEATURE_VI_SEARCH */
-       case '0':                       // 0- goto begining of line
+       case '0':                       // 0- goto beginning of line
        case '1':                       // 1-
        case '2':                       // 2-
        case '3':                       // 3-
@@ -3802,50 +3851,7 @@ static void do_cmd(int c)
                break;
        case ':':                       // :- the colon mode commands
                p = get_input_line(":");        // get input line- use "status line"
-#if ENABLE_FEATURE_VI_COLON
                colon(p);               // execute the command
-#else
-               if (*p == ':')
-                       p++;                            // move past the ':'
-               cnt = strlen(p);
-               if (cnt <= 0)
-                       break;
-               if (strncmp(p, "quit", cnt) == 0
-                || strncmp(p, "q!", cnt) == 0   // delete lines
-               ) {
-                       if (file_modified && p[1] != '!') {
-                               status_line_bold("No write since last change (:%s! overrides)", p);
-                       } else {
-                               editing = 0;
-                       }
-               } else if (strncmp(p, "write", cnt) == 0
-                       || strncmp(p, "wq", cnt) == 0
-                       || strncmp(p, "wn", cnt) == 0
-                       || (p[0] == 'x' && !p[1])
-               ) {
-                       cnt = file_write(current_filename, text, end - 1);
-                       if (cnt < 0) {
-                               if (cnt == -1)
-                                       status_line_bold("Write error: %s", strerror(errno));
-                       } else {
-                               file_modified = 0;
-                               last_file_modified = -1;
-                               status_line("'%s' %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
-                               if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
-                                || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
-                               ) {
-                                       editing = 0;
-                               }
-                       }
-               } else if (strncmp(p, "file", cnt) == 0) {
-                       last_status_cksum = 0;  // force status update
-               } else if (sscanf(p, "%d", &j) > 0) {
-                       dot = find_line(j);             // go to line # j
-                       dot_skip_over_ws();
-               } else {                // unrecognized cmd
-                       not_implemented(p);
-               }
-#endif /* !FEATURE_VI_COLON */
                break;
        case '<':                       // <- Left  shift something
        case '>':                       // >- Right shift something
@@ -3917,7 +3923,9 @@ static void do_cmd(int c)
                c1 = get_one_char();
                if (c1 != 'g') {
                        buf[0] = 'g';
-                       buf[1] = c1; // TODO: if Unicode?
+                       // c1 < 0 if the key was special. Try "g<up-arrow>"
+                       // TODO: if Unicode?
+                       buf[1] = (c1 >= 0 ? c1 : '*');
                        buf[2] = '\0';
                        not_implemented(buf);
                        break;
@@ -3962,7 +3970,7 @@ static void do_cmd(int c)
                                undo_push((dot - 1), 1, UNDO_INS_CHAIN);
 #else
                                *dot++ = ' ';
-                               file_modified++;
+                               modified_count++;
 #endif
                                while (isblank(*dot)) { // delete leading WS
                                        text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
@@ -4008,8 +4016,9 @@ static void do_cmd(int c)
                undo_queue_commit();
                break;
        case KEYCODE_DELETE:
-               c = 'x';
-               // fall through
+               if (dot < end - 1)
+                       dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
+               break;
        case 'X':                       // X- delete char before dot
        case 'x':                       // x- delete the current char
        case 's':                       // s- substitute the current char
@@ -4031,10 +4040,10 @@ static void do_cmd(int c)
                // ZZ means to save file (if necessary), then exit
                c1 = get_one_char();
                if (c1 != 'Z') {
-                       indicate_error(c);
+                       indicate_error();
                        break;
                }
-               if (file_modified) {
+               if (modified_count) {
                        if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
                                status_line_bold("'%s' is read only", current_filename);
                                break;
@@ -4115,7 +4124,7 @@ static void do_cmd(int c)
                        // could not recognize object
                        c = c1 = 27;    // error-
                        ml = 0;
-                       indicate_error(c);
+                       indicate_error();
                }
                if (ml && whole) {
                        if (c == 'c') {
@@ -4171,7 +4180,7 @@ static void do_cmd(int c)
                        undo_push(dot, 1, UNDO_INS_CHAIN);
 #else
                        *dot = c1;
-                       file_modified++;
+                       modified_count++;
 #endif
                }
                end_cmd_q();    // stop adding to q
@@ -4225,10 +4234,10 @@ static void do_cmd(int c)
 #else
                        if (islower(*dot)) {
                                *dot = toupper(*dot);
-                               file_modified++;
+                               modified_count++;
                        } else if (isupper(*dot)) {
                                *dot = tolower(*dot);
-                               file_modified++;
+                               modified_count++;
                        }
 #endif
                        dot_right();