//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
//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
//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
// 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 {
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
#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
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 )
#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 )
#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 )
#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);
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
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 *);
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
#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
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
# define undo_queue_commit() ((void)0)
# endif
#else
+#define flush_undo_data() ((void)0)
#define undo_queue_commit() ((void)0)
#endif
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;
}
}
#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"
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;
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;
}
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) {
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");
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;
}
}
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");
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
}
#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;
#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;
}
colon_s_fail:
status_line(":s expression missing delimiters");
#endif
-}
-
#endif /* FEATURE_VI_COLON */
+}
static void Hit_Return(void)
{
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;
}
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;
{
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;
}
# endif
}
#else
- file_modified++;
+ modified_count++;
#endif /* ENABLE_FEATURE_VI_UNDO */
p++;
} else if (c == 27) { // Is this an ESC?
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')
# 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;
}
// 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
// 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
#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
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();
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:
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
}
}
// 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)
);
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);
// 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)));
}
//----- 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)
{
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;
#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 */
ftruncate(fd, charcnt);
if (charcnt == cnt) {
// good write
- //file_modified = FALSE;
+ //modified_count = FALSE;
} else {
charcnt = 0;
}
redraw(TRUE);
}
-static void Indicate_Error(void)
+static void indicate_error(void)
{
#if ENABLE_FEATURE_VI_CRASHME
if (crashme > 0)
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.)
// 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
#if ENABLE_FEATURE_VI_READONLY
(readonly_mode ? " [Readonly]" : ""),
#endif
- (file_modified ? " [Modified]" : ""),
+ (modified_count ? " [Modified]" : ""),
cur, tot, percent);
if (ret >= 0 && ret < trunc_at)
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;
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);
break;
case 27: // esc
if (cmd_mode == 0)
- indicate_error(c);
+ indicate_error();
cmd_mode = 0; // stop insrting
undo_queue_commit();
end_cmd_q();
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) {
dot_begin(); // go to B-o-l
dot_skip_over_ws();
} else {
- indicate_error(c);
+ indicate_error();
}
break;
case 'm': // m- Mark a line
// remember the line
mark[c1] = dot;
} else {
- indicate_error(c);
+ indicate_error();
}
break;
case 'P': // P- Put register before
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);
}
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 (;;) {
// we found half of a pair
p = find_pair(q, *q);
if (p == NULL) {
- indicate_error(c);
+ indicate_error();
} else {
dot = p;
}
}
}
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
}
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-
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
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;
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);
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
// 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;
// could not recognize object
c = c1 = 27; // error-
ml = 0;
- indicate_error(c);
+ indicate_error();
}
if (ml && whole) {
if (c == 'c') {
undo_push(dot, 1, UNDO_INS_CHAIN);
#else
*dot = c1;
- file_modified++;
+ modified_count++;
#endif
}
end_cmd_q(); // stop adding to q
#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();