aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coreutils/fsync.c60
-rw-r--r--coreutils/sync.c102
-rw-r--r--editors/vi.c4339
-rw-r--r--shell/ash.c15
4 files changed, 2191 insertions, 2325 deletions
diff --git a/coreutils/fsync.c b/coreutils/fsync.c
deleted file mode 100644
index c7cba9f61..000000000
--- a/coreutils/fsync.c
+++ /dev/null
@@ -1,60 +0,0 @@
1/* vi: set sw=4 ts=4: */
2/*
3 * Mini fsync implementation for busybox
4 *
5 * Copyright (C) 2008 Nokia Corporation. All rights reserved.
6 *
7 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8 */
9//config:config FSYNC
10//config: bool "fsync (3.6 kb)"
11//config: default y
12//config: help
13//config: fsync is used to flush file-related cached blocks to disk.
14
15//applet:IF_FSYNC(APPLET_NOFORK(fsync, fsync, BB_DIR_BIN, BB_SUID_DROP, fsync))
16
17//kbuild:lib-$(CONFIG_FSYNC) += fsync.o
18
19//usage:#define fsync_trivial_usage
20//usage: "[-d] FILE..."
21//usage:#define fsync_full_usage "\n\n"
22//usage: "Write files' buffered blocks to disk\n"
23//usage: "\n -d Avoid syncing metadata"
24
25#include "libbb.h"
26#ifndef O_NOATIME
27# define O_NOATIME 0
28#endif
29
30/* This is a NOFORK applet. Be very careful! */
31
32int fsync_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
33int fsync_main(int argc UNUSED_PARAM, char **argv)
34{
35 int status;
36 int opts;
37
38 opts = getopt32(argv, "d"); /* fdatasync */
39 argv += optind;
40 if (!*argv) {
41 bb_show_usage();
42 }
43
44 status = EXIT_SUCCESS;
45 do {
46 int fd = open_or_warn(*argv, O_NOATIME | O_NOCTTY | O_RDONLY);
47
48 if (fd == -1) {
49 status = EXIT_FAILURE;
50 continue;
51 }
52 if ((opts ? fdatasync(fd) : fsync(fd))) {
53 //status = EXIT_FAILURE; - do we want this?
54 bb_simple_perror_msg(*argv);
55 }
56 close(fd);
57 } while (*++argv);
58
59 return status;
60}
diff --git a/coreutils/sync.c b/coreutils/sync.c
index b93476aee..ea328a54c 100644
--- a/coreutils/sync.c
+++ b/coreutils/sync.c
@@ -20,6 +20,7 @@
20//config: sync -d FILE... executes fdatasync() on each FILE. 20//config: sync -d FILE... executes fdatasync() on each FILE.
21//config: sync -f FILE... executes syncfs() on each FILE. 21//config: sync -f FILE... executes syncfs() on each FILE.
22 22
23// APPLET_NOFORK:name main location suid_type help
23//applet:IF_SYNC(APPLET_NOFORK(sync, sync, BB_DIR_BIN, BB_SUID_DROP, sync)) 24//applet:IF_SYNC(APPLET_NOFORK(sync, sync, BB_DIR_BIN, BB_SUID_DROP, sync))
24 25
25//kbuild:lib-$(CONFIG_SYNC) += sync.o 26//kbuild:lib-$(CONFIG_SYNC) += sync.o
@@ -42,61 +43,98 @@
42 43
43/* This is a NOFORK applet. Be very careful! */ 44/* This is a NOFORK applet. Be very careful! */
44 45
45int sync_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 46#if ENABLE_FEATURE_SYNC_FANCY || ENABLE_FSYNC
46int sync_main(int argc UNUSED_PARAM, char **argv IF_NOT_DESKTOP(UNUSED_PARAM)) 47static int sync_common(int opts, char **argv)
47{ 48{
48#if !ENABLE_FEATURE_SYNC_FANCY 49 int ret;
49 /* coreutils-6.9 compat */
50 bb_warn_ignoring_args(argv[1]);
51 sync();
52 return EXIT_SUCCESS;
53#else
54 unsigned opts;
55 int ret = EXIT_SUCCESS;
56
57 enum { 50 enum {
58 OPT_DATASYNC = (1 << 0), 51 OPT_DATASYNC = (1 << 0),
59 OPT_SYNCFS = (1 << 1), 52 OPT_SYNCFS = (1 << 1),
60 }; 53 };
61 54
62 opts = getopt32(argv, "^" "df" "\0" "d--f:f--d"); 55 ret = EXIT_SUCCESS;
63 argv += optind; 56 do {
64 57 /* GNU "sync FILE" uses O_NONBLOCK open */
65 /* Handle the no-argument case. */ 58 int fd = open_or_warn(*argv, /*O_NOATIME |*/ O_NOCTTY | O_RDONLY | O_NONBLOCK);
66 if (!argv[0]) 59 /* open(NOATIME) can only be used by owner or root, don't use NOATIME here */
67 sync();
68
69 while (*argv) {
70 int fd = open_or_warn(*argv, O_RDONLY);
71 60
72 if (fd < 0) { 61 if (fd < 0) {
73 ret = EXIT_FAILURE; 62 ret = EXIT_FAILURE;
74 goto next; 63 goto next;
75 } 64 }
76 if (opts & OPT_DATASYNC) { 65# if ENABLE_FEATURE_SYNC_FANCY
77 if (fdatasync(fd))
78 goto err;
79 goto do_close;
80 }
81 if (opts & OPT_SYNCFS) { 66 if (opts & OPT_SYNCFS) {
82 /* 67 /*
83 * syncfs is documented to only fail with EBADF, 68 * syncfs is documented to only fail with EBADF,
84 * which can't happen here. So, no error checks. 69 * which can't happen here. So, no error checks.
85 */ 70 */
86 syncfs(fd); 71 syncfs(fd);
87 goto do_close; 72 } else
88 } 73# endif
89 if (fsync(fd)) { 74 if (((opts & OPT_DATASYNC) ? fdatasync(fd) : fsync(fd)) != 0) {
90 err:
91 bb_simple_perror_msg(*argv); 75 bb_simple_perror_msg(*argv);
92 ret = EXIT_FAILURE; 76 ret = EXIT_FAILURE;
93 } 77 }
94 do_close:
95 close(fd); 78 close(fd);
96 next: 79 next:
97 ++argv; 80 argv++;
98 } 81 } while (*argv);
99 82
100 return ret; 83 return ret;
84}
85#endif
86
87#if ENABLE_SYNC
88int sync_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
89int sync_main(int argc UNUSED_PARAM, char **argv IF_NOT_DESKTOP(UNUSED_PARAM))
90{
91# if !ENABLE_FEATURE_SYNC_FANCY
92 /* coreutils-6.9 compat */
93 bb_warn_ignoring_args(argv[1]);
94 sync();
95 return EXIT_SUCCESS;
96# else
97 unsigned opts = getopt32(argv, "^" "df" "\0" "d--f:f--d");
98 argv += optind;
99 if (!argv[0]) {
100 sync();
101 return EXIT_SUCCESS;
102 }
103 return sync_common(opts, argv);
104# endif
105}
101#endif 106#endif
107
108/*
109 * Mini fsync implementation for busybox
110 *
111 * Copyright (C) 2008 Nokia Corporation. All rights reserved.
112 *
113 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
114 */
115//config:config FSYNC
116//config: bool "fsync (3.6 kb)"
117//config: default y
118//config: help
119//config: fsync is used to flush file-related cached blocks to disk.
120
121// APPLET_NOFORK:name main location suid_type help
122//applet:IF_FSYNC(APPLET_NOFORK(fsync, fsync, BB_DIR_BIN, BB_SUID_DROP, fsync))
123
124//kbuild:lib-$(CONFIG_FSYNC) += sync.o
125
126//usage:#define fsync_trivial_usage
127//usage: "[-d] FILE..."
128//usage:#define fsync_full_usage "\n\n"
129//usage: "Write all buffered blocks in FILEs to disk\n"
130//usage: "\n -d Avoid syncing metadata"
131
132#if ENABLE_FSYNC
133int fsync_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
134int fsync_main(int argc UNUSED_PARAM, char **argv)
135{
136 int opts = getopt32(argv, "^" "d" "\0" "-1"/*min 1 arg*/);
137 argv += optind;
138 return sync_common(opts, argv);
102} 139}
140#endif
diff --git a/editors/vi.c b/editors/vi.c
index 2a7d9f0a8..91a3e0ac1 100644
--- a/editors/vi.c
+++ b/editors/vi.c
@@ -5,19 +5,19 @@
5 * 5 *
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree. 6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
7 */ 7 */
8/* 8//
9 * Things To Do: 9//Things To Do:
10 * EXINIT 10// EXINIT
11 * $HOME/.exrc and ./.exrc 11// $HOME/.exrc and ./.exrc
12 * add magic to search /foo.*bar 12// add magic to search /foo.*bar
13 * add :help command 13// add :help command
14 * :map macros 14// :map macros
15 * if mark[] values were line numbers rather than pointers 15// if mark[] values were line numbers rather than pointers
16 * it would be easier to change the mark when add/delete lines 16// it would be easier to change the mark when add/delete lines
17 * More intelligence in refresh() 17// More intelligence in refresh()
18 * ":r !cmd" and "!cmd" to filter text through an external command 18// ":r !cmd" and "!cmd" to filter text through an external command
19 * An "ex" line oriented mode- maybe using "cmdedit" 19// An "ex" line oriented mode- maybe using "cmdedit"
20 */ 20
21//config:config VI 21//config:config VI
22//config: bool "vi (23 kb)" 22//config: bool "vi (23 kb)"
23//config: default y 23//config: default y
@@ -178,12 +178,12 @@
178//usage: "\n -H List available features" 178//usage: "\n -H List available features"
179 179
180#include "libbb.h" 180#include "libbb.h"
181/* Should be after libbb.h: on some systems regex.h needs sys/types.h: */ 181// Should be after libbb.h: on some systems regex.h needs sys/types.h:
182#if ENABLE_FEATURE_VI_REGEX_SEARCH 182#if ENABLE_FEATURE_VI_REGEX_SEARCH
183# include <regex.h> 183# include <regex.h>
184#endif 184#endif
185 185
186/* the CRASHME code is unmaintained, and doesn't currently build */ 186// the CRASHME code is unmaintained, and doesn't currently build
187#define ENABLE_FEATURE_VI_CRASHME 0 187#define ENABLE_FEATURE_VI_CRASHME 0
188 188
189 189
@@ -198,7 +198,7 @@
198 198
199#else 199#else
200 200
201/* 0x9b is Meta-ESC */ 201// 0x9b is Meta-ESC
202#if ENABLE_FEATURE_VI_8BIT 202#if ENABLE_FEATURE_VI_8BIT
203# define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b) 203# define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
204#else 204#else
@@ -218,36 +218,33 @@ enum {
218 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN, 218 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
219}; 219};
220 220
221/* VT102 ESC sequences. 221// VT102 ESC sequences.
222 * See "Xterm Control Sequences" 222// See "Xterm Control Sequences"
223 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html 223// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
224 */
225#define ESC "\033" 224#define ESC "\033"
226/* Inverse/Normal text */ 225// Inverse/Normal text
227#define ESC_BOLD_TEXT ESC"[7m" 226#define ESC_BOLD_TEXT ESC"[7m"
228#define ESC_NORM_TEXT ESC"[m" 227#define ESC_NORM_TEXT ESC"[m"
229/* Bell */ 228// Bell
230#define ESC_BELL "\007" 229#define ESC_BELL "\007"
231/* Clear-to-end-of-line */ 230// Clear-to-end-of-line
232#define ESC_CLEAR2EOL ESC"[K" 231#define ESC_CLEAR2EOL ESC"[K"
233/* Clear-to-end-of-screen. 232// Clear-to-end-of-screen.
234 * (We use default param here. 233// (We use default param here.
235 * Full sequence is "ESC [ <num> J", 234// Full sequence is "ESC [ <num> J",
236 * <num> is 0/1/2 = "erase below/above/all".) 235// <num> is 0/1/2 = "erase below/above/all".)
237 */ 236#define ESC_CLEAR2EOS ESC"[J"
238#define ESC_CLEAR2EOS ESC"[J" 237// Cursor to given coordinate (1,1: top left)
239/* Cursor to given coordinate (1,1: top left) */ 238#define ESC_SET_CURSOR_POS ESC"[%u;%uH"
240#define ESC_SET_CURSOR_POS ESC"[%u;%uH" 239#define ESC_SET_CURSOR_TOPLEFT ESC"[H"
241//UNUSED 240//UNUSED
242///* Cursor up and down */ 241//// Cursor up and down
243//#define ESC_CURSOR_UP ESC"[A" 242//#define ESC_CURSOR_UP ESC"[A"
244//#define ESC_CURSOR_DOWN "\n" 243//#define ESC_CURSOR_DOWN "\n"
245 244
246#if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK 245#if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
247// cmds modifying text[] 246// cmds modifying text[]
248// vda: removed "aAiIs" as they switch us into insert mode 247static const char modifying_cmds[] ALIGN1 = "aAcCdDiIJoOpPrRs""xX<>~";
249// and remembering input for replay after them makes no sense
250static const char modifying_cmds[] ALIGN1 = "cCdDJoOpPrRxX<>~";
251#endif 248#endif
252 249
253enum { 250enum {
@@ -266,17 +263,17 @@ enum {
266}; 263};
267 264
268 265
269/* vi.c expects chars to be unsigned. */ 266// vi.c expects chars to be unsigned.
270/* busybox build system provides that, but it's better */ 267// busybox build system provides that, but it's better
271/* to audit and fix the source */ 268// to audit and fix the source
272 269
273struct globals { 270struct globals {
274 /* many references - keep near the top of globals */ 271 // many references - keep near the top of globals
275 char *text, *end; // pointers to the user data in memory 272 char *text, *end; // pointers to the user data in memory
276 char *dot; // where all the action takes place 273 char *dot; // where all the action takes place
277 int text_size; // size of the allocated buffer 274 int text_size; // size of the allocated buffer
278 275
279 /* the rest */ 276 // the rest
280 smallint vi_setops; 277 smallint vi_setops;
281#define VI_AUTOINDENT 1 278#define VI_AUTOINDENT 1
282#define VI_SHOWMATCH 2 279#define VI_SHOWMATCH 2
@@ -285,7 +282,7 @@ struct globals {
285#define autoindent (vi_setops & VI_AUTOINDENT) 282#define autoindent (vi_setops & VI_AUTOINDENT)
286#define showmatch (vi_setops & VI_SHOWMATCH ) 283#define showmatch (vi_setops & VI_SHOWMATCH )
287#define ignorecase (vi_setops & VI_IGNORECASE) 284#define ignorecase (vi_setops & VI_IGNORECASE)
288/* indicate error with beep or flash */ 285// indicate error with beep or flash
289#define err_method (vi_setops & VI_ERR_METHOD) 286#define err_method (vi_setops & VI_ERR_METHOD)
290 287
291#if ENABLE_FEATURE_VI_READONLY 288#if ENABLE_FEATURE_VI_READONLY
@@ -304,7 +301,7 @@ struct globals {
304 smallint cmd_mode; // 0=command 1=insert 2=replace 301 smallint cmd_mode; // 0=command 1=insert 2=replace
305 int modified_count; // buffer contents changed if !0 302 int modified_count; // buffer contents changed if !0
306 int last_modified_count; // = -1; 303 int last_modified_count; // = -1;
307 int save_argc; // how many file names on cmd line 304 int cmdline_filecnt; // how many file names on cmd line
308 int cmdcnt; // repetition count 305 int cmdcnt; // repetition count
309 unsigned rows, columns; // the terminal screen is this size 306 unsigned rows, columns; // the terminal screen is this size
310#if ENABLE_FEATURE_VI_ASK_TERMINAL 307#if ENABLE_FEATURE_VI_ASK_TERMINAL
@@ -321,29 +318,27 @@ struct globals {
321 int screensize; // and its size 318 int screensize; // and its size
322 int tabstop; 319 int tabstop;
323 int last_forward_char; // last char searched for with 'f' (int because of Unicode) 320 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
324 char erase_char; // the users erase character 321#if ENABLE_FEATURE_VI_CRASHME
325 char last_input_char; // last char read from user 322 char last_input_char; // last char read from user
323#endif
326 324
327#if ENABLE_FEATURE_VI_DOT_CMD 325#if ENABLE_FEATURE_VI_DOT_CMD
328 smallint adding2q; // are we currently adding user input to q 326 smallint adding2q; // are we currently adding user input to q
329 int lmc_len; // length of last_modifying_cmd 327 int lmc_len; // length of last_modifying_cmd
330 char *ioq, *ioq_start; // pointer to string for get_one_char to "read" 328 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
331#endif 329#endif
332#if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
333 int my_pid;
334#endif
335#if ENABLE_FEATURE_VI_SEARCH 330#if ENABLE_FEATURE_VI_SEARCH
336 char *last_search_pattern; // last pattern from a '/' or '?' search 331 char *last_search_pattern; // last pattern from a '/' or '?' search
337#endif 332#endif
338 333
339 /* former statics */ 334 // former statics
340#if ENABLE_FEATURE_VI_YANKMARK 335#if ENABLE_FEATURE_VI_YANKMARK
341 char *edit_file__cur_line; 336 char *edit_file__cur_line;
342#endif 337#endif
343 int refresh__old_offset; 338 int refresh__old_offset;
344 int format_edit_status__tot; 339 int format_edit_status__tot;
345 340
346 /* a few references only */ 341 // a few references only
347#if ENABLE_FEATURE_VI_YANKMARK 342#if ENABLE_FEATURE_VI_YANKMARK
348 smalluint YDreg;//,Ureg;// default delete register and orig line for "U" 343 smalluint YDreg;//,Ureg;// default delete register and orig line for "U"
349#define Ureg 27 344#define Ureg 27
@@ -352,7 +347,7 @@ struct globals {
352 char *context_start, *context_end; 347 char *context_start, *context_end;
353#endif 348#endif
354#if ENABLE_FEATURE_VI_USE_SIGNALS 349#if ENABLE_FEATURE_VI_USE_SIGNALS
355 sigjmp_buf restart; // catch_sig() 350 sigjmp_buf restart; // int_handler() jumps to location remembered here
356#endif 351#endif
357#if !ENABLE_PLATFORM_MINGW32 352#if !ENABLE_PLATFORM_MINGW32
358 struct termios term_orig; // remember what the cooked mode was 353 struct termios term_orig; // remember what the cooked mode was
@@ -372,9 +367,10 @@ struct globals {
372#if ENABLE_FEATURE_VI_DOT_CMD 367#if ENABLE_FEATURE_VI_DOT_CMD
373 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "." 368 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
374#endif 369#endif
375 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */ 370 char get_input_line__buf[MAX_INPUT_LEN]; // former static
376 371
377 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2]; 372 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
373
378#if ENABLE_FEATURE_VI_UNDO 374#if ENABLE_FEATURE_VI_UNDO
379// undo_push() operations 375// undo_push() operations
380#define UNDO_INS 0 376#define UNDO_INS 0
@@ -401,7 +397,6 @@ struct globals {
401// If undo queuing disabled, don't invoke the missing queue logic 397// If undo queuing disabled, don't invoke the missing queue logic
402#define ALLOW_UNDO_QUEUED 1 398#define ALLOW_UNDO_QUEUED 1
403# endif 399# endif
404
405 struct undo_object { 400 struct undo_object {
406 struct undo_object *prev; // Linking back avoids list traversal (LIFO) 401 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
407 int start; // Offset where the data should be restored/deleted 402 int start; // Offset where the data should be restored/deleted
@@ -423,7 +418,7 @@ struct globals {
423#define cmd_mode (G.cmd_mode ) 418#define cmd_mode (G.cmd_mode )
424#define modified_count (G.modified_count ) 419#define modified_count (G.modified_count )
425#define last_modified_count (G.last_modified_count) 420#define last_modified_count (G.last_modified_count)
426#define save_argc (G.save_argc ) 421#define cmdline_filecnt (G.cmdline_filecnt )
427#define cmdcnt (G.cmdcnt ) 422#define cmdcnt (G.cmdcnt )
428#define rows (G.rows ) 423#define rows (G.rows )
429#define columns (G.columns ) 424#define columns (G.columns )
@@ -439,8 +434,9 @@ struct globals {
439#define screenbegin (G.screenbegin ) 434#define screenbegin (G.screenbegin )
440#define tabstop (G.tabstop ) 435#define tabstop (G.tabstop )
441#define last_forward_char (G.last_forward_char ) 436#define last_forward_char (G.last_forward_char )
442#define erase_char (G.erase_char ) 437#if ENABLE_FEATURE_VI_CRASHME
443#define last_input_char (G.last_input_char ) 438#define last_input_char (G.last_input_char )
439#endif
444#if ENABLE_FEATURE_VI_READONLY 440#if ENABLE_FEATURE_VI_READONLY
445#define readonly_mode (G.readonly_mode ) 441#define readonly_mode (G.readonly_mode )
446#else 442#else
@@ -450,7 +446,6 @@ struct globals {
450#define lmc_len (G.lmc_len ) 446#define lmc_len (G.lmc_len )
451#define ioq (G.ioq ) 447#define ioq (G.ioq )
452#define ioq_start (G.ioq_start ) 448#define ioq_start (G.ioq_start )
453#define my_pid (G.my_pid )
454#define last_search_pattern (G.last_search_pattern) 449#define last_search_pattern (G.last_search_pattern)
455 450
456#define edit_file__cur_line (G.edit_file__cur_line) 451#define edit_file__cur_line (G.edit_file__cur_line)
@@ -487,238 +482,1638 @@ struct globals {
487 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \ 482 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
488} while (0) 483} while (0)
489 484
485#if ENABLE_FEATURE_VI_CRASHME
486static int crashme = 0;
487#endif
490 488
491static void edit_file(char *); // edit one file 489static void show_status_line(void); // put a message on the bottom line
492static void do_cmd(int); // execute a command 490static void status_line_bold(const char *, ...);
493static int next_tabstop(int); 491
494static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot 492static void show_help(void)
495static char *begin_line(char *); // return pointer to cur line B-o-l 493{
496static char *end_line(char *); // return pointer to cur line E-o-l 494 puts("These features are available:"
497static char *prev_line(char *); // return pointer to prev line B-o-l 495#if ENABLE_FEATURE_VI_SEARCH
498static char *next_line(char *); // return pointer to next line B-o-l 496 "\n\tPattern searches with / and ?"
499static char *end_screen(void); // get pointer to last char on screen
500static int count_lines(char *, char *); // count line from start to stop
501static char *find_line(int); // find beginning of line #li
502static char *move_to_col(char *, int); // move "p" to column l
503static void dot_left(void); // move dot left- dont leave line
504static void dot_right(void); // move dot right- dont leave line
505static void dot_begin(void); // move dot to B-o-l
506static void dot_end(void); // move dot to E-o-l
507static void dot_next(void); // move dot to next line B-o-l
508static void dot_prev(void); // move dot to prev line B-o-l
509static void dot_scroll(int, int); // move the screen up or down
510static void dot_skip_over_ws(void); // move dot pat WS
511static char *bound_dot(char *); // make sure text[0] <= P < "end"
512static char *new_screen(int, int); // malloc virtual screen memory
513#if !ENABLE_FEATURE_VI_UNDO
514#define char_insert(a,b,c) char_insert(a,b)
515#endif 497#endif
516static char *char_insert(char *, char, int); // insert the char c at 'p' 498#if ENABLE_FEATURE_VI_DOT_CMD
517// might reallocate text[]! use p += stupid_insert(p, ...), 499 "\n\tLast command repeat with ."
518// and be careful to not use pointers into potentially freed text[]!
519static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
520static int find_range(char **, char **, char); // return pointers for an object
521static int st_test(char *, int, int, char *); // helper for skip_thing()
522static char *skip_thing(char *, int, int, int); // skip some object
523static char *find_pair(char *, char); // find matching pair () [] {}
524#if !ENABLE_FEATURE_VI_UNDO
525#define text_hole_delete(a,b,c) text_hole_delete(a,b)
526#endif 500#endif
527static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole 501#if ENABLE_FEATURE_VI_YANKMARK
528// might reallocate text[]! use p += text_hole_make(p, ...), 502 "\n\tLine marking with 'x"
529// and be careful to not use pointers into potentially freed text[]! 503 "\n\tNamed buffers with \"x"
530static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole 504#endif
531#if !ENABLE_FEATURE_VI_UNDO 505#if ENABLE_FEATURE_VI_READONLY
532#define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d) 506 //not implemented: "\n\tReadonly if vi is called as \"view\""
507 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
508#endif
509#if ENABLE_FEATURE_VI_SET
510 "\n\tSome colon mode commands with :"
533#endif 511#endif
534static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete 512#if ENABLE_FEATURE_VI_SETOPTS
535static void show_help(void); // display some help info 513 "\n\tSettable options with \":set\""
536static void rawmode(void); // set "raw" mode on tty 514#endif
537static void cookmode(void); // return to "cooked" mode on tty 515#if ENABLE_FEATURE_VI_USE_SIGNALS
516 "\n\tSignal catching- ^C"
517 "\n\tJob suspend and resume with ^Z"
518#endif
519#if ENABLE_FEATURE_VI_WIN_RESIZE
520 "\n\tAdapt to window re-sizes"
521#endif
522 );
523}
524
525static void write1(const char *out)
526{
527 fputs(out, stdout);
528}
529
530#if ENABLE_FEATURE_VI_WIN_RESIZE
531static int query_screen_dimensions(void)
532{
533 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
534 if (rows > MAX_SCR_ROWS)
535 rows = MAX_SCR_ROWS;
536 if (columns > MAX_SCR_COLS)
537 columns = MAX_SCR_COLS;
538 return err;
539}
540#else
541static ALWAYS_INLINE int query_screen_dimensions(void)
542{
543 return 0;
544}
545#endif
546
538// sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready) 547// sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
539static int mysleep(int); 548static int mysleep(int hund)
540static int readit(void); // read (maybe cursor) key from stdin 549{
541static int get_one_char(void); // read 1 char from stdin 550#if ENABLE_PLATFORM_MINGW32
542// file_insert might reallocate text[]! 551 HANDLE h = GetStdHandle(STD_INPUT_HANDLE);
543static int file_insert(const char *, char *, int); 552 DWORD ret;
544static int file_write(char *, char *, char *);
545static void place_cursor(int, int);
546static void screen_erase(void);
547static void clear_to_eol(void);
548static void clear_to_eos(void);
549static void go_bottom_and_clear_to_eol(void);
550static void standout_start(void); // send "start reverse video" sequence
551static void standout_end(void); // send "end reverse video" sequence
552static void flash(int); // flash the terminal screen
553static void show_status_line(void); // put a message on the bottom line
554static void status_line(const char *, ...); // print to status buf
555static void status_line_bold(const char *, ...);
556static void status_line_bold_errno(const char *fn);
557static void not_implemented(const char *); // display "Not implemented" message
558static int format_edit_status(void); // format file status on status line
559static void redraw(int); // force a full screen refresh
560static char* format_line(char* /*, int*/);
561static void refresh(int); // update the terminal from screen[]
562 553
563static void indicate_error(void); // use flash or beep to indicate error 554 if (hund == 0) {
564static void Hit_Return(void); 555 // Allow two events in the queue. Otherwise pasted test isn't
556 // displayed because there's still a key release event waiting
557 // after the last character is processed.
558 DWORD nevent_out;
565 559
566#if ENABLE_FEATURE_VI_SEARCH 560 ret = GetNumberOfConsoleInputEvents(h, &nevent_out);
567static char *char_search(char *, const char *, int); // search for pattern starting at p 561 return ret != 0 ? (nevent_out > 2) : 0;
562 }
563 fflush_all();
564 ret = WaitForSingleObject(h, hund*10);
565 return ret != WAIT_TIMEOUT;
566#else
567 struct pollfd pfd[1];
568
569 if (hund != 0)
570 fflush_all();
571
572 pfd[0].fd = STDIN_FILENO;
573 pfd[0].events = POLLIN;
574 return safe_poll(pfd, 1, hund*10) > 0;
568#endif 575#endif
569#if ENABLE_FEATURE_VI_COLON 576}
570static char *get_one_address(char *, int *); // get colon addr, if present 577
571static char *get_address(char *, int *, int *); // get two colon addrs, if present 578//----- Set terminal attributes --------------------------------
579#if !ENABLE_PLATFORM_MINGW32
580static void rawmode(void)
581{
582 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
583 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
584}
585
586static void cookmode(void)
587{
588 fflush_all();
589 tcsetattr_stdin_TCSANOW(&term_orig);
590}
591#else
592#define rawmode() ((void)0)
593#define cookmode fflush_all
572#endif 594#endif
573static void colon(char *); // execute the "colon" mode cmds 595
596//----- Terminal Drawing ---------------------------------------
597// The terminal is made up of 'rows' line of 'columns' columns.
598// classically this would be 24 x 80.
599// screen coordinates
600// 0,0 ... 0,79
601// 1,0 ... 1,79
602// . ... .
603// . ... .
604// 22,0 ... 22,79
605// 23,0 ... 23,79 <- status line
606
607//----- Move the cursor to row x col (count from 0, not 1) -------
608static void place_cursor(int row, int col)
609{
610 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
611
612 if (row < 0) row = 0;
613 if (row >= rows) row = rows - 1;
614 if (col < 0) col = 0;
615 if (col >= columns) col = columns - 1;
616
617 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
618 write1(cm1);
619}
620
621//----- Erase from cursor to end of line -----------------------
622static void clear_to_eol(void)
623{
624 write1(ESC_CLEAR2EOL);
625}
626
627static void go_bottom_and_clear_to_eol(void)
628{
629 place_cursor(rows - 1, 0);
630 clear_to_eol();
631}
632
633//----- Start standout mode ------------------------------------
634static void standout_start(void)
635{
636 write1(ESC_BOLD_TEXT);
637}
638
639//----- End standout mode --------------------------------------
640static void standout_end(void)
641{
642 write1(ESC_NORM_TEXT);
643}
644
645//----- Text Movement Routines ---------------------------------
646static char *begin_line(char *p) // return pointer to first char cur line
647{
648 if (p > text) {
649 p = memrchr(text, '\n', p - text);
650 if (!p)
651 return text;
652 return p + 1;
653 }
654 return p;
655}
656
657static char *end_line(char *p) // return pointer to NL of cur line
658{
659 if (p < end - 1) {
660 p = memchr(p, '\n', end - p - 1);
661 if (!p)
662 return end - 1;
663 }
664 return p;
665}
666
667static char *dollar_line(char *p) // return pointer to just before NL line
668{
669 p = end_line(p);
670 // Try to stay off of the Newline
671 if (*p == '\n' && (p - begin_line(p)) > 0)
672 p--;
673 return p;
674}
675
676static char *prev_line(char *p) // return pointer first char prev line
677{
678 p = begin_line(p); // goto beginning of cur line
679 if (p > text && p[-1] == '\n')
680 p--; // step to prev line
681 p = begin_line(p); // goto beginning of prev line
682 return p;
683}
684
685static char *next_line(char *p) // return pointer first char next line
686{
687 p = end_line(p);
688 if (p < end - 1 && *p == '\n')
689 p++; // step to next line
690 return p;
691}
692
693//----- Text Information Routines ------------------------------
694static char *end_screen(void)
695{
696 char *q;
697 int cnt;
698
699 // find new bottom line
700 q = screenbegin;
701 for (cnt = 0; cnt < rows - 2; cnt++)
702 q = next_line(q);
703 q = end_line(q);
704 return q;
705}
706
707// count line from start to stop
708static int count_lines(char *start, char *stop)
709{
710 char *q;
711 int cnt;
712
713 if (stop < start) { // start and stop are backwards- reverse them
714 q = start;
715 start = stop;
716 stop = q;
717 }
718 cnt = 0;
719 stop = end_line(stop);
720 while (start <= stop && start <= end - 1) {
721 start = end_line(start);
722 if (*start == '\n')
723 cnt++;
724 start++;
725 }
726 return cnt;
727}
728
729static char *find_line(int li) // find beginning of line #li
730{
731 char *q;
732
733 for (q = text; li > 1; li--) {
734 q = next_line(q);
735 }
736 return q;
737}
738
739static int next_tabstop(int col)
740{
741 return col + ((tabstop - 1) - (col % tabstop));
742}
743
744//----- Erase the Screen[] memory ------------------------------
745static void screen_erase(void)
746{
747 memset(screen, ' ', screensize); // clear new screen
748}
749
750static void new_screen(int ro, int co)
751{
752 char *s;
753
754 free(screen);
755 screensize = ro * co + 8;
756 s = screen = xmalloc(screensize);
757 // initialize the new screen. assume this will be a empty file.
758 screen_erase();
759 // non-existent text[] lines start with a tilde (~).
760 //screen[(1 * co) + 0] = '~';
761 //screen[(2 * co) + 0] = '~';
762 //..
763 //screen[((ro-2) * co) + 0] = '~';
764 ro -= 2;
765 while (--ro >= 0) {
766 s += co;
767 *s = '~';
768 }
769}
770
771//----- Synchronize the cursor to Dot --------------------------
772static NOINLINE void sync_cursor(char *d, int *row, int *col)
773{
774 char *beg_cur; // begin and end of "d" line
775 char *tp;
776 int cnt, ro, co;
777
778 beg_cur = begin_line(d); // first char of cur line
779
780 if (beg_cur < screenbegin) {
781 // "d" is before top line on screen
782 // how many lines do we have to move
783 cnt = count_lines(beg_cur, screenbegin);
784 sc1:
785 screenbegin = beg_cur;
786 if (cnt > (rows - 1) / 2) {
787 // we moved too many lines. put "dot" in middle of screen
788 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
789 screenbegin = prev_line(screenbegin);
790 }
791 }
792 } else {
793 char *end_scr; // begin and end of screen
794 end_scr = end_screen(); // last char of screen
795 if (beg_cur > end_scr) {
796 // "d" is after bottom line on screen
797 // how many lines do we have to move
798 cnt = count_lines(end_scr, beg_cur);
799 if (cnt > (rows - 1) / 2)
800 goto sc1; // too many lines
801 for (ro = 0; ro < cnt - 1; ro++) {
802 // move screen begin the same amount
803 screenbegin = next_line(screenbegin);
804 // now, move the end of screen
805 end_scr = next_line(end_scr);
806 end_scr = end_line(end_scr);
807 }
808 }
809 }
810 // "d" is on screen- find out which row
811 tp = screenbegin;
812 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
813 if (tp == beg_cur)
814 break;
815 tp = next_line(tp);
816 }
817
818 // find out what col "d" is on
819 co = 0;
820 while (tp < d) { // drive "co" to correct column
821 if (*tp == '\n') //vda || *tp == '\0')
822 break;
823 if (*tp == '\t') {
824 // handle tabs like real vi
825 if (d == tp && cmd_mode) {
826 break;
827 }
828 co = next_tabstop(co);
829 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
830 co++; // display as ^X, use 2 columns
831 }
832 co++;
833 tp++;
834 }
835
836 // "co" is the column where "dot" is.
837 // The screen has "columns" columns.
838 // The currently displayed columns are 0+offset -- columns+ofset
839 // |-------------------------------------------------------------|
840 // ^ ^ ^
841 // offset | |------- columns ----------------|
842 //
843 // If "co" is already in this range then we do not have to adjust offset
844 // but, we do have to subtract the "offset" bias from "co".
845 // If "co" is outside this range then we have to change "offset".
846 // If the first char of a line is a tab the cursor will try to stay
847 // in column 7, but we have to set offset to 0.
848
849 if (co < 0 + offset) {
850 offset = co;
851 }
852 if (co >= columns + offset) {
853 offset = co - columns + 1;
854 }
855 // if the first char of the line is a tab, and "dot" is sitting on it
856 // force offset to 0.
857 if (d == beg_cur && *d == '\t') {
858 offset = 0;
859 }
860 co -= offset;
861
862 *row = ro;
863 *col = co;
864}
865
866//----- Format a text[] line into a buffer ---------------------
867static char* format_line(char *src /*, int li*/)
868{
869 unsigned char c;
870 int co;
871 int ofs = offset;
872 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
873
874 c = '~'; // char in col 0 in non-existent lines is '~'
875 co = 0;
876 while (co < columns + tabstop) {
877 // have we gone past the end?
878 if (src < end) {
879 c = *src++;
880 if (c == '\n')
881 break;
882 if ((c & 0x80) && !Isprint(c)) {
883 c = '.';
884 }
885 if (c < ' ' || c == 0x7f) {
886 if (c == '\t') {
887 c = ' ';
888 // co % 8 != 7
889 while ((co % tabstop) != (tabstop - 1)) {
890 dest[co++] = c;
891 }
892 } else {
893 dest[co++] = '^';
894 if (c == 0x7f)
895 c = '?';
896 else
897 c += '@'; // Ctrl-X -> 'X'
898 }
899 }
900 }
901 dest[co++] = c;
902 // discard scrolled-off-to-the-left portion,
903 // in tabstop-sized pieces
904 if (ofs >= tabstop && co >= tabstop) {
905 memmove(dest, dest + tabstop, co);
906 co -= tabstop;
907 ofs -= tabstop;
908 }
909 if (src >= end)
910 break;
911 }
912 // check "short line, gigantic offset" case
913 if (co < ofs)
914 ofs = co;
915 // discard last scrolled off part
916 co -= ofs;
917 dest += ofs;
918 // fill the rest with spaces
919 if (co < columns)
920 memset(&dest[co], ' ', columns - co);
921 return dest;
922}
923
924//----- Refresh the changed screen lines -----------------------
925// Copy the source line from text[] into the buffer and note
926// if the current screenline is different from the new buffer.
927// If they differ then that line needs redrawing on the terminal.
928//
929static void refresh(int full_screen)
930{
931#define old_offset refresh__old_offset
932
933 int li, changed;
934 char *tp, *sp; // pointer into text[] and screen[]
935
936 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
937 unsigned c = columns, r = rows;
938 query_screen_dimensions();
574#if ENABLE_FEATURE_VI_USE_SIGNALS 939#if ENABLE_FEATURE_VI_USE_SIGNALS
575static void winch_sig(int); // catch window size changes 940 full_screen |= (c - columns) | (r - rows);
576static void suspend_sig(int); // catch ctrl-Z 941#else
577static void catch_sig(int); // catch ctrl-C and alarm time-outs 942 if (c != columns || r != rows) {
943 full_screen = TRUE;
944 // update screen memory since SIGWINCH won't have done it
945 new_screen(rows, columns);
946 }
947#endif
948 }
949 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
950 tp = screenbegin; // index into text[] of top line
951
952 // compare text[] to screen[] and mark screen[] lines that need updating
953 for (li = 0; li < rows - 1; li++) {
954 int cs, ce; // column start & end
955 char *out_buf;
956 // format current text line
957 out_buf = format_line(tp /*, li*/);
958
959 // skip to the end of the current text[] line
960 if (tp < end) {
961 char *t = memchr(tp, '\n', end - tp);
962 if (!t) t = end - 1;
963 tp = t + 1;
964 }
965
966 // see if there are any changes between virtual screen and out_buf
967 changed = FALSE; // assume no change
968 cs = 0;
969 ce = columns - 1;
970 sp = &screen[li * columns]; // start of screen line
971 if (full_screen) {
972 // force re-draw of every single column from 0 - columns-1
973 goto re0;
974 }
975 // compare newly formatted buffer with virtual screen
976 // look forward for first difference between buf and screen
977 for (; cs <= ce; cs++) {
978 if (out_buf[cs] != sp[cs]) {
979 changed = TRUE; // mark for redraw
980 break;
981 }
982 }
983
984 // look backward for last difference between out_buf and screen
985 for (; ce >= cs; ce--) {
986 if (out_buf[ce] != sp[ce]) {
987 changed = TRUE; // mark for redraw
988 break;
989 }
990 }
991 // now, cs is index of first diff, and ce is index of last diff
992
993 // if horz offset has changed, force a redraw
994 if (offset != old_offset) {
995 re0:
996 changed = TRUE;
997 }
998
999 // make a sanity check of columns indexes
1000 if (cs < 0) cs = 0;
1001 if (ce > columns - 1) ce = columns - 1;
1002 if (cs > ce) { cs = 0; ce = columns - 1; }
1003 // is there a change between virtual screen and out_buf
1004 if (changed) {
1005 // copy changed part of buffer to virtual screen
1006 memcpy(sp+cs, out_buf+cs, ce-cs+1);
1007 place_cursor(li, cs);
1008 // write line out to terminal
1009 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
1010 }
1011 }
1012
1013 place_cursor(crow, ccol);
1014
1015 old_offset = offset;
1016#undef old_offset
1017}
1018
1019//----- Force refresh of all Lines -----------------------------
1020static void redraw(int full_screen)
1021{
1022 // cursor to top,left; clear to the end of screen
1023#if !ENABLE_PLATFORM_MINGW32
1024 write1(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS);
1025#else
1026 write1(ESC_SET_CURSOR_TOPLEFT);
1027 reset_screen();
578#endif 1028#endif
1029 screen_erase(); // erase the internal screen buffer
1030 last_status_cksum = 0; // force status update
1031 refresh(full_screen); // this will redraw the entire display
1032 show_status_line();
1033}
1034
1035//----- Flash the screen --------------------------------------
1036static void flash(int h)
1037{
1038 standout_start();
1039 redraw(TRUE);
1040 mysleep(h);
1041 standout_end();
1042 redraw(TRUE);
1043}
1044
1045static void indicate_error(void)
1046{
1047#if ENABLE_FEATURE_VI_CRASHME
1048 if (crashme > 0)
1049 return;
1050#endif
1051 if (!err_method) {
1052 write1(ESC_BELL);
1053 } else {
1054 flash(10);
1055 }
1056}
1057
1058//----- IO Routines --------------------------------------------
1059static int readit(void) // read (maybe cursor) key from stdin
1060{
1061 int c;
1062
1063 fflush_all();
1064
1065 // Wait for input. TIMEOUT = -1 makes read_key wait even
1066 // on nonblocking stdin.
1067 // Note: read_key sets errno to 0 on success.
1068 again:
1069 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
1070 if (c == -1) { // EOF/error
1071 if (errno == EAGAIN) // paranoia
1072 goto again;
1073 go_bottom_and_clear_to_eol();
1074 cookmode(); // terminal to "cooked"
1075 bb_error_msg_and_die("can't read user input");
1076 }
1077 return c;
1078}
1079
579#if ENABLE_FEATURE_VI_DOT_CMD 1080#if ENABLE_FEATURE_VI_DOT_CMD
580static void start_new_cmd_q(char); // new queue for command 1081static int get_one_char(void)
581static void end_cmd_q(void); // stop saving input chars 1082{
1083 int c;
1084
1085 if (!adding2q) {
1086 // we are not adding to the q.
1087 // but, we may be reading from a saved q.
1088 // (checking "ioq" for NULL is wrong, it's not reset to NULL
1089 // when done - "ioq_start" is reset instead).
1090 if (ioq_start != NULL) {
1091 // there is a queue to get chars from.
1092 // careful with correct sign expansion!
1093 c = (unsigned char)*ioq++;
1094 if (c != '\0')
1095 return c;
1096 // the end of the q
1097 free(ioq_start);
1098 ioq_start = NULL;
1099 // read from STDIN:
1100 }
1101 return readit();
1102 }
1103 // we are adding STDIN chars to q.
1104 c = readit();
1105 if (lmc_len >= ARRAY_SIZE(last_modifying_cmd) - 1) {
1106 // last_modifying_cmd[] is too small, can't remeber the cmd
1107 // - drop it
1108 adding2q = 0;
1109 lmc_len = 0;
1110 } else {
1111 last_modifying_cmd[lmc_len++] = c;
1112 }
1113 return c;
1114}
582#else 1115#else
583#define end_cmd_q() ((void)0) 1116# define get_one_char() readit()
584#endif 1117#endif
585#if ENABLE_FEATURE_VI_SETOPTS 1118
586static void showmatching(char *); // show the matching pair () [] {} 1119// Get input line (uses "status line" area)
1120static char *get_input_line(const char *prompt)
1121{
1122 // char [MAX_INPUT_LEN]
1123#define buf get_input_line__buf
1124
1125 int c;
1126 int i;
1127
1128 strcpy(buf, prompt);
1129 last_status_cksum = 0; // force status update
1130 go_bottom_and_clear_to_eol();
1131 write1(prompt); // write out the :, /, or ? prompt
1132
1133 i = strlen(buf);
1134 while (i < MAX_INPUT_LEN) {
1135 c = get_one_char();
1136 if (c == '\n' || c == '\r' || c == 27)
1137 break; // this is end of input
1138#if !ENABLE_PLATFORM_MINGW32
1139 if (c == term_orig.c_cc[VERASE] || c == 8 || c == 127) {
1140#else
1141 if (c == 8 || c == 127) {
587#endif 1142#endif
588#if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME 1143 // user wants to erase prev char
589// might reallocate text[]! use p += string_insert(p, ...), 1144 buf[--i] = '\0';
590// and be careful to not use pointers into potentially freed text[]! 1145 write1("\b \b"); // erase char on screen
591# if !ENABLE_FEATURE_VI_UNDO 1146 if (i <= 0) // user backs up before b-o-l, exit
592#define string_insert(a,b,c) string_insert(a,b) 1147 break;
593# endif 1148 } else if (c > 0 && c < 256) { // exclude Unicode
594static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p' 1149 // (TODO: need to handle Unicode)
1150 buf[i] = c;
1151 buf[++i] = '\0';
1152 bb_putchar(c);
1153 }
1154 }
1155 refresh(FALSE);
1156 return buf;
1157#undef buf
1158}
1159
1160static void Hit_Return(void)
1161{
1162 int c;
1163
1164 standout_start();
1165 write1("[Hit return to continue]");
1166 standout_end();
1167 while ((c = get_one_char()) != '\n' && c != '\r')
1168 continue;
1169 redraw(TRUE); // force redraw all
1170}
1171
1172//----- Draw the status line at bottom of the screen -------------
1173// show file status on status line
1174static int format_edit_status(void)
1175{
1176 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
1177
1178#define tot format_edit_status__tot
1179
1180 int cur, percent, ret, trunc_at;
1181
1182 // modified_count is now a counter rather than a flag. this
1183 // helps reduce the amount of line counting we need to do.
1184 // (this will cause a mis-reporting of modified status
1185 // once every MAXINT editing operations.)
1186
1187 // it would be nice to do a similar optimization here -- if
1188 // we haven't done a motion that could have changed which line
1189 // we're on, then we shouldn't have to do this count_lines()
1190 cur = count_lines(text, dot);
1191
1192 // count_lines() is expensive.
1193 // Call it only if something was changed since last time
1194 // we were here:
1195 if (modified_count != last_modified_count) {
1196 tot = cur + count_lines(dot, end - 1) - 1;
1197 last_modified_count = modified_count;
1198 }
1199
1200 // current line percent
1201 // ------------- ~~ ----------
1202 // total lines 100
1203 if (tot > 0) {
1204 percent = (100 * cur) / tot;
1205 } else {
1206 cur = tot = 0;
1207 percent = 100;
1208 }
1209
1210 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
1211 columns : STATUS_BUFFER_LEN-1;
1212
1213 ret = snprintf(status_buffer, trunc_at+1,
1214#if ENABLE_FEATURE_VI_READONLY
1215 "%c %s%s%s %d/%d %d%%",
1216#else
1217 "%c %s%s %d/%d %d%%",
1218#endif
1219 cmd_mode_indicator[cmd_mode & 3],
1220 (current_filename != NULL ? current_filename : "No file"),
1221#if ENABLE_FEATURE_VI_READONLY
1222 (readonly_mode ? " [Readonly]" : ""),
1223#endif
1224 (modified_count ? " [Modified]" : ""),
1225 cur, tot, percent);
1226
1227 if (ret >= 0 && ret < trunc_at)
1228 return ret; // it all fit
1229
1230 return trunc_at; // had to truncate
1231#undef tot
1232}
1233
1234static int bufsum(char *buf, int count)
1235{
1236 int sum = 0;
1237 char *e = buf + count;
1238 while (buf < e)
1239 sum += (unsigned char) *buf++;
1240 return sum;
1241}
1242
1243static void show_status_line(void)
1244{
1245 int cnt = 0, cksum = 0;
1246
1247 // either we already have an error or status message, or we
1248 // create one.
1249 if (!have_status_msg) {
1250 cnt = format_edit_status();
1251 cksum = bufsum(status_buffer, cnt);
1252 }
1253 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
1254 last_status_cksum = cksum; // remember if we have seen this line
1255 go_bottom_and_clear_to_eol();
1256 write1(status_buffer);
1257 if (have_status_msg) {
1258 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
1259 (columns - 1) ) {
1260 have_status_msg = 0;
1261 Hit_Return();
1262 }
1263 have_status_msg = 0;
1264 }
1265 place_cursor(crow, ccol); // put cursor back in correct place
1266 }
1267 fflush_all();
1268}
1269
1270//----- format the status buffer, the bottom line of screen ------
1271static void status_line(const char *format, ...)
1272{
1273 va_list args;
1274
1275 va_start(args, format);
1276 vsnprintf(status_buffer, STATUS_BUFFER_LEN, format, args);
1277 va_end(args);
1278
1279 have_status_msg = 1;
1280}
1281static void status_line_bold(const char *format, ...)
1282{
1283 va_list args;
1284
1285 va_start(args, format);
1286 strcpy(status_buffer, ESC_BOLD_TEXT);
1287 vsnprintf(status_buffer + (sizeof(ESC_BOLD_TEXT)-1),
1288 STATUS_BUFFER_LEN - sizeof(ESC_BOLD_TEXT) - sizeof(ESC_NORM_TEXT),
1289 format, args
1290 );
1291 strcat(status_buffer, ESC_NORM_TEXT);
1292 va_end(args);
1293
1294 have_status_msg = 1 + (sizeof(ESC_BOLD_TEXT)-1) + (sizeof(ESC_NORM_TEXT)-1);
1295}
1296static void status_line_bold_errno(const char *fn)
1297{
1298 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
1299}
1300
1301// copy s to buf, convert unprintable
1302static void print_literal(char *buf, const char *s)
1303{
1304 char *d;
1305 unsigned char c;
1306
1307 buf[0] = '\0';
1308 if (!s[0])
1309 s = "(NULL)";
1310
1311 d = buf;
1312 for (; *s; s++) {
1313 int c_is_no_print;
1314
1315 c = *s;
1316 c_is_no_print = (c & 0x80) && !Isprint(c);
1317 if (c_is_no_print) {
1318 strcpy(d, ESC_NORM_TEXT);
1319 d += sizeof(ESC_NORM_TEXT)-1;
1320 c = '.';
1321 }
1322 if (c < ' ' || c == 0x7f) {
1323 *d++ = '^';
1324 c |= '@'; // 0x40
1325 if (c == 0x7f)
1326 c = '?';
1327 }
1328 *d++ = c;
1329 *d = '\0';
1330 if (c_is_no_print) {
1331 strcpy(d, ESC_BOLD_TEXT);
1332 d += sizeof(ESC_BOLD_TEXT)-1;
1333 }
1334 if (*s == '\n') {
1335 *d++ = '$';
1336 *d = '\0';
1337 }
1338 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
1339 break;
1340 }
1341}
1342static void not_implemented(const char *s)
1343{
1344 char buf[MAX_INPUT_LEN];
1345 print_literal(buf, s);
1346 status_line_bold("'%s' is not implemented", buf);
1347}
1348
1349//----- Block insert/delete, undo ops --------------------------
1350#if ENABLE_FEATURE_VI_YANKMARK
1351static char *text_yank(char *p, char *q, int dest) // copy text into a register
1352{
1353 int cnt = q - p;
1354 if (cnt < 0) { // they are backwards- reverse them
1355 p = q;
1356 cnt = -cnt;
1357 }
1358 free(reg[dest]); // if already a yank register, free it
1359 reg[dest] = xstrndup(p, cnt + 1);
1360 return p;
1361}
1362
1363static char what_reg(void)
1364{
1365 char c;
1366
1367 c = 'D'; // default to D-reg
1368 if (0 <= YDreg && YDreg <= 25)
1369 c = 'a' + (char) YDreg;
1370 if (YDreg == 26)
1371 c = 'D';
1372 if (YDreg == 27)
1373 c = 'U';
1374 return c;
1375}
1376
1377static void check_context(char cmd)
1378{
1379 // A context is defined to be "modifying text"
1380 // Any modifying command establishes a new context.
1381
1382 if (dot < context_start || dot > context_end) {
1383 if (strchr(modifying_cmds, cmd) != NULL) {
1384 // we are trying to modify text[]- make this the current context
1385 mark[27] = mark[26]; // move cur to prev
1386 mark[26] = dot; // move local to cur
1387 context_start = prev_line(prev_line(dot));
1388 context_end = next_line(next_line(dot));
1389 //loiter= start_loiter= now;
1390 }
1391 }
1392}
1393
1394static char *swap_context(char *p) // goto new context for '' command make this the current context
1395{
1396 char *tmp;
1397
1398 // the current context is in mark[26]
1399 // the previous context is in mark[27]
1400 // only swap context if other context is valid
1401 if (text <= mark[27] && mark[27] <= end - 1) {
1402 tmp = mark[27];
1403 mark[27] = p;
1404 mark[26] = p = tmp;
1405 context_start = prev_line(prev_line(prev_line(p)));
1406 context_end = next_line(next_line(next_line(p)));
1407 }
1408 return p;
1409}
1410#endif /* FEATURE_VI_YANKMARK */
1411
1412#if ENABLE_FEATURE_VI_UNDO
1413static void undo_push(char *, unsigned, unsigned char);
595#endif 1414#endif
1415
1416// open a hole in text[]
1417// might reallocate text[]! use p += text_hole_make(p, ...),
1418// and be careful to not use pointers into potentially freed text[]!
1419static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
1420{
1421 uintptr_t bias = 0;
1422
1423 if (size <= 0)
1424 return bias;
1425 end += size; // adjust the new END
1426 if (end >= (text + text_size)) {
1427 char *new_text;
1428 text_size += end - (text + text_size) + 10240;
1429 new_text = xrealloc(text, text_size);
1430 bias = (new_text - text);
1431 screenbegin += bias;
1432 dot += bias;
1433 end += bias;
1434 p += bias;
596#if ENABLE_FEATURE_VI_YANKMARK 1435#if ENABLE_FEATURE_VI_YANKMARK
597static char *text_yank(char *, char *, int); // save copy of "p" into a register 1436 {
598static char what_reg(void); // what is letter of current YDreg 1437 int i;
599static void check_context(char); // remember context for '' command 1438 for (i = 0; i < ARRAY_SIZE(mark); i++)
1439 if (mark[i])
1440 mark[i] += bias;
1441 }
1442#endif
1443 text = new_text;
1444 }
1445 memmove(p + size, p, end - size - p);
1446 memset(p, ' ', size); // clear new hole
1447 return bias;
1448}
1449
1450// close a hole in text[] - delete "p" through "q", inclusive
1451// "undo" value indicates if this operation should be undo-able
1452#if !ENABLE_FEATURE_VI_UNDO
1453#define text_hole_delete(a,b,c) text_hole_delete(a,b)
1454#endif
1455static char *text_hole_delete(char *p, char *q, int undo)
1456{
1457 char *src, *dest;
1458 int cnt, hole_size;
1459
1460 // move forwards, from beginning
1461 // assume p <= q
1462 src = q + 1;
1463 dest = p;
1464 if (q < p) { // they are backward- swap them
1465 src = p + 1;
1466 dest = q;
1467 }
1468 hole_size = q - p + 1;
1469 cnt = end - src;
1470#if ENABLE_FEATURE_VI_UNDO
1471 switch (undo) {
1472 case NO_UNDO:
1473 break;
1474 case ALLOW_UNDO:
1475 undo_push(p, hole_size, UNDO_DEL);
1476 break;
1477 case ALLOW_UNDO_CHAIN:
1478 undo_push(p, hole_size, UNDO_DEL_CHAIN);
1479 break;
1480# if ENABLE_FEATURE_VI_UNDO_QUEUE
1481 case ALLOW_UNDO_QUEUED:
1482 undo_push(p, hole_size, UNDO_DEL_QUEUED);
1483 break;
1484# endif
1485 }
1486 modified_count--;
600#endif 1487#endif
1488 if (src < text || src > end)
1489 goto thd0;
1490 if (dest < text || dest >= end)
1491 goto thd0;
1492 modified_count++;
1493 if (src >= end)
1494 goto thd_atend; // just delete the end of the buffer
1495 memmove(dest, src, cnt);
1496 thd_atend:
1497 end = end - hole_size; // adjust the new END
1498 if (dest >= end)
1499 dest = end - 1; // make sure dest in below end-1
1500 if (end <= text)
1501 dest = end = text; // keep pointers valid
1502 thd0:
1503 return dest;
1504}
1505
601#if ENABLE_FEATURE_VI_UNDO 1506#if ENABLE_FEATURE_VI_UNDO
602static void flush_undo_data(void); 1507
603static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack
604static void undo_push_insert(char *, int, int); // convenience function
605static void undo_pop(void); // Undo the last operation
606# if ENABLE_FEATURE_VI_UNDO_QUEUE 1508# if ENABLE_FEATURE_VI_UNDO_QUEUE
607static void undo_queue_commit(void); // Flush any queued objects to the undo stack 1509// Flush any queued objects to the undo stack
1510static void undo_queue_commit(void)
1511{
1512 // Pushes the queue object onto the undo stack
1513 if (undo_q > 0) {
1514 // Deleted character undo events grow from the end
1515 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
1516 undo_q,
1517 (undo_queue_state | UNDO_USE_SPOS)
1518 );
1519 undo_queue_state = UNDO_EMPTY;
1520 undo_q = 0;
1521 }
1522}
608# else 1523# else
609# define undo_queue_commit() ((void)0) 1524# define undo_queue_commit() ((void)0)
1525# endif
1526
1527static void flush_undo_data(void)
1528{
1529 struct undo_object *undo_entry;
1530
1531 while (undo_stack_tail) {
1532 undo_entry = undo_stack_tail;
1533 undo_stack_tail = undo_entry->prev;
1534 free(undo_entry);
1535 }
1536}
1537
1538// Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
1539// Add to the undo stack
1540static void undo_push(char *src, unsigned length, uint8_t u_type)
1541{
1542 struct undo_object *undo_entry;
1543
1544 // "u_type" values
1545 // UNDO_INS: insertion, undo will remove from buffer
1546 // UNDO_DEL: deleted text, undo will restore to buffer
1547 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
1548 // The CHAIN operations are for handling multiple operations that the user
1549 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
1550 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
1551 // for the INS/DEL operation. The raw values should be equal to the values of
1552 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
1553
1554# if ENABLE_FEATURE_VI_UNDO_QUEUE
1555 // This undo queuing functionality groups multiple character typing or backspaces
1556 // into a single large undo object. This greatly reduces calls to malloc() for
1557 // single-character operations while typing and has the side benefit of letting
1558 // an undo operation remove chunks of text rather than a single character.
1559 switch (u_type) {
1560 case UNDO_EMPTY: // Just in case this ever happens...
1561 return;
1562 case UNDO_DEL_QUEUED:
1563 if (length != 1)
1564 return; // Only queue single characters
1565 switch (undo_queue_state) {
1566 case UNDO_EMPTY:
1567 undo_queue_state = UNDO_DEL;
1568 case UNDO_DEL:
1569 undo_queue_spos = src;
1570 undo_q++;
1571 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
1572 // If queue is full, dump it into an object
1573 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1574 undo_queue_commit();
1575 return;
1576 case UNDO_INS:
1577 // Switch from storing inserted text to deleted text
1578 undo_queue_commit();
1579 undo_push(src, length, UNDO_DEL_QUEUED);
1580 return;
1581 }
1582 break;
1583 case UNDO_INS_QUEUED:
1584 if (length < 1)
1585 return;
1586 switch (undo_queue_state) {
1587 case UNDO_EMPTY:
1588 undo_queue_state = UNDO_INS;
1589 undo_queue_spos = src;
1590 case UNDO_INS:
1591 while (length--) {
1592 undo_q++; // Don't need to save any data for insertions
1593 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1594 undo_queue_commit();
1595 }
1596 return;
1597 case UNDO_DEL:
1598 // Switch from storing deleted text to inserted text
1599 undo_queue_commit();
1600 undo_push(src, length, UNDO_INS_QUEUED);
1601 return;
1602 }
1603 break;
1604 }
1605# else
1606 // If undo queuing is disabled, ignore the queuing flag entirely
1607 u_type = u_type & ~UNDO_QUEUED_FLAG;
1608# endif
1609
1610 // Allocate a new undo object
1611 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
1612 // For UNDO_DEL objects, save deleted text
1613 if ((text + length) == end)
1614 length--;
1615 // If this deletion empties text[], strip the newline. When the buffer becomes
1616 // zero-length, a newline is added back, which requires this to compensate.
1617 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
1618 memcpy(undo_entry->undo_text, src, length);
1619 } else {
1620 undo_entry = xzalloc(sizeof(*undo_entry));
1621 }
1622 undo_entry->length = length;
1623# if ENABLE_FEATURE_VI_UNDO_QUEUE
1624 if ((u_type & UNDO_USE_SPOS) != 0) {
1625 undo_entry->start = undo_queue_spos - text; // use start position from queue
1626 } else {
1627 undo_entry->start = src - text; // use offset from start of text buffer
1628 }
1629 u_type = (u_type & ~UNDO_USE_SPOS);
1630# else
1631 undo_entry->start = src - text;
610# endif 1632# endif
1633 undo_entry->u_type = u_type;
1634
1635 // Push it on undo stack
1636 undo_entry->prev = undo_stack_tail;
1637 undo_stack_tail = undo_entry;
1638 modified_count++;
1639}
1640
1641static void undo_push_insert(char *p, int len, int undo)
1642{
1643 switch (undo) {
1644 case ALLOW_UNDO:
1645 undo_push(p, len, UNDO_INS);
1646 break;
1647 case ALLOW_UNDO_CHAIN:
1648 undo_push(p, len, UNDO_INS_CHAIN);
1649 break;
1650# if ENABLE_FEATURE_VI_UNDO_QUEUE
1651 case ALLOW_UNDO_QUEUED:
1652 undo_push(p, len, UNDO_INS_QUEUED);
1653 break;
1654# endif
1655 }
1656}
1657
1658// Undo the last operation
1659static void undo_pop(void)
1660{
1661 int repeat;
1662 char *u_start, *u_end;
1663 struct undo_object *undo_entry;
1664
1665 // Commit pending undo queue before popping (should be unnecessary)
1666 undo_queue_commit();
1667
1668 undo_entry = undo_stack_tail;
1669 // Check for an empty undo stack
1670 if (!undo_entry) {
1671 status_line("Already at oldest change");
1672 return;
1673 }
1674
1675 switch (undo_entry->u_type) {
1676 case UNDO_DEL:
1677 case UNDO_DEL_CHAIN:
1678 // make hole and put in text that was deleted; deallocate text
1679 u_start = text + undo_entry->start;
1680 text_hole_make(u_start, undo_entry->length);
1681 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
1682 status_line("Undo [%d] %s %d chars at position %d",
1683 modified_count, "restored",
1684 undo_entry->length, undo_entry->start
1685 );
1686 break;
1687 case UNDO_INS:
1688 case UNDO_INS_CHAIN:
1689 // delete what was inserted
1690 u_start = undo_entry->start + text;
1691 u_end = u_start - 1 + undo_entry->length;
1692 text_hole_delete(u_start, u_end, NO_UNDO);
1693 status_line("Undo [%d] %s %d chars at position %d",
1694 modified_count, "deleted",
1695 undo_entry->length, undo_entry->start
1696 );
1697 break;
1698 }
1699 repeat = 0;
1700 switch (undo_entry->u_type) {
1701 // If this is the end of a chain, lower modification count and refresh display
1702 case UNDO_DEL:
1703 case UNDO_INS:
1704 dot = (text + undo_entry->start);
1705 refresh(FALSE);
1706 break;
1707 case UNDO_DEL_CHAIN:
1708 case UNDO_INS_CHAIN:
1709 repeat = 1;
1710 break;
1711 }
1712 // Deallocate the undo object we just processed
1713 undo_stack_tail = undo_entry->prev;
1714 free(undo_entry);
1715 modified_count--;
1716 // For chained operations, continue popping all the way down the chain.
1717 if (repeat) {
1718 undo_pop(); // Follow the undo chain if one exists
1719 }
1720}
1721
611#else 1722#else
612#define flush_undo_data() ((void)0) 1723# define flush_undo_data() ((void)0)
613#define undo_queue_commit() ((void)0) 1724# define undo_queue_commit() ((void)0)
614#endif 1725#endif /* ENABLE_FEATURE_VI_UNDO */
615 1726
616#if ENABLE_FEATURE_VI_CRASHME 1727//----- Dot Movement Routines ----------------------------------
617static void crash_dummy(); 1728static void dot_left(void)
618static void crash_test(); 1729{
619static int crashme = 0; 1730 undo_queue_commit();
620#endif 1731 if (dot > text && dot[-1] != '\n')
1732 dot--;
1733}
621 1734
622static void write1(const char *out) 1735static void dot_right(void)
623{ 1736{
624 fputs(out, stdout); 1737 undo_queue_commit();
1738 if (dot < end - 1 && *dot != '\n')
1739 dot++;
625} 1740}
626 1741
627int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 1742static void dot_begin(void)
628int vi_main(int argc, char **argv)
629{ 1743{
630 int c; 1744 undo_queue_commit();
1745 dot = begin_line(dot); // return pointer to first char cur line
1746}
631 1747
632 INIT_G(); 1748static void dot_end(void)
1749{
1750 undo_queue_commit();
1751 dot = end_line(dot); // return pointer to last char cur line
1752}
633 1753
634#if ENABLE_FEATURE_VI_UNDO 1754static char *move_to_col(char *p, int l)
635 /* undo_stack_tail = NULL; - already is */ 1755{
636#if ENABLE_FEATURE_VI_UNDO_QUEUE 1756 int co;
637 undo_queue_state = UNDO_EMPTY; 1757
638 /* undo_q = 0; - already is */ 1758 p = begin_line(p);
1759 co = 0;
1760 while (co < l && p < end) {
1761 if (*p == '\n') //vda || *p == '\0')
1762 break;
1763 if (*p == '\t') {
1764 co = next_tabstop(co);
1765 } else if (*p < ' ' || *p == 127) {
1766 co++; // display as ^X, use 2 columns
1767 }
1768 co++;
1769 p++;
1770 }
1771 return p;
1772}
1773
1774static void dot_next(void)
1775{
1776 undo_queue_commit();
1777 dot = next_line(dot);
1778}
1779
1780static void dot_prev(void)
1781{
1782 undo_queue_commit();
1783 dot = prev_line(dot);
1784}
1785
1786static void dot_skip_over_ws(void)
1787{
1788 // skip WS
1789 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1790 dot++;
1791}
1792
1793static void dot_scroll(int cnt, int dir)
1794{
1795 char *q;
1796
1797 undo_queue_commit();
1798 for (; cnt > 0; cnt--) {
1799 if (dir < 0) {
1800 // scroll Backwards
1801 // ctrl-Y scroll up one line
1802 screenbegin = prev_line(screenbegin);
1803 } else {
1804 // scroll Forwards
1805 // ctrl-E scroll down one line
1806 screenbegin = next_line(screenbegin);
1807 }
1808 }
1809 // make sure "dot" stays on the screen so we dont scroll off
1810 if (dot < screenbegin)
1811 dot = screenbegin;
1812 q = end_screen(); // find new bottom line
1813 if (dot > q)
1814 dot = begin_line(q); // is dot is below bottom line?
1815 dot_skip_over_ws();
1816}
1817
1818static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1819{
1820 if (p >= end && end > text) {
1821 p = end - 1;
1822 indicate_error();
1823 }
1824 if (p < text) {
1825 p = text;
1826 indicate_error();
1827 }
1828 return p;
1829}
1830
1831#if ENABLE_FEATURE_VI_DOT_CMD
1832static void start_new_cmd_q(char c)
1833{
1834 // get buffer for new cmd
1835 // if there is a current cmd count put it in the buffer first
1836 if (cmdcnt > 0) {
1837 lmc_len = sprintf(last_modifying_cmd, "%u%c", cmdcnt, c);
1838 } else { // just save char c onto queue
1839 last_modifying_cmd[0] = c;
1840 lmc_len = 1;
1841 }
1842 adding2q = 1;
1843}
1844static void end_cmd_q(void)
1845{
1846# if ENABLE_FEATURE_VI_YANKMARK
1847 YDreg = 26; // go back to default Yank/Delete reg
1848# endif
1849 adding2q = 0;
1850}
1851#else
1852# define end_cmd_q() ((void)0)
1853#endif /* FEATURE_VI_DOT_CMD */
1854
1855// copy text into register, then delete text.
1856// if dist <= 0, do not include, or go past, a NewLine
1857//
1858#if !ENABLE_FEATURE_VI_UNDO
1859#define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
639#endif 1860#endif
1861static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
1862{
1863 char *p;
1864
1865 // make sure start <= stop
1866 if (start > stop) {
1867 // they are backwards, reverse them
1868 p = start;
1869 start = stop;
1870 stop = p;
1871 }
1872 if (dist <= 0) {
1873 // we cannot cross NL boundaries
1874 p = start;
1875 if (*p == '\n')
1876 return p;
1877 // dont go past a NewLine
1878 for (; p + 1 <= stop; p++) {
1879 if (p[1] == '\n') {
1880 stop = p; // "stop" just before NewLine
1881 break;
1882 }
1883 }
1884 }
1885 p = start;
1886#if ENABLE_FEATURE_VI_YANKMARK
1887 text_yank(start, stop, YDreg);
640#endif 1888#endif
1889 if (yf == YANKDEL) {
1890 p = text_hole_delete(start, stop, undo);
1891 } // delete lines
1892 return p;
1893}
641 1894
642#if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME 1895#if ENABLE_PLATFORM_MINGW32
643 my_pid = getpid(); 1896static int count_cr(char *p, int len)
1897{
1898 int i, cnt;
1899
1900 for (i = cnt = 0; i < len; ++i)
1901 if (p[i] == '\n')
1902 ++cnt;
1903 return cnt;
1904}
644#endif 1905#endif
645#if ENABLE_FEATURE_VI_CRASHME 1906
646 srand((long) my_pid); 1907// might reallocate text[]!
1908static int file_insert(const char *fn, char *p, int initial)
1909{
1910 int cnt = -1;
1911 int fd, size;
1912 struct stat statbuf;
1913
1914 if (p < text)
1915 p = text;
1916 if (p > end)
1917 p = end;
1918
1919#if !ENABLE_PLATFORM_MINGW32
1920 fd = open(fn, O_RDONLY);
1921#else
1922 fd = open(fn, O_RDONLY | _O_TEXT);
647#endif 1923#endif
648#ifdef NO_SUCH_APPLET_YET 1924 if (fd < 0) {
649 /* If we aren't "vi", we are "view" */ 1925 if (!initial)
650 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) { 1926 status_line_bold_errno(fn);
651 SET_READONLY_MODE(readonly_mode); 1927 return cnt;
652 } 1928 }
653#endif
654 1929
655 // autoindent is not default in vim 7.3 1930 // Validate file
656 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE; 1931 if (fstat(fd, &statbuf) < 0) {
657 // 1- process $HOME/.exrc file (not inplemented yet) 1932 status_line_bold_errno(fn);
658 // 2- process EXINIT variable from environment 1933 goto fi;
659 // 3- process command line args
660#if ENABLE_FEATURE_VI_COLON
661 {
662 char *p = getenv("EXINIT");
663 if (p && *p)
664 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
665 } 1934 }
1935 if (!S_ISREG(statbuf.st_mode)) {
1936 status_line_bold("'%s' is not a regular file", fn);
1937 goto fi;
1938 }
1939 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
1940 p += text_hole_make(p, size);
1941 cnt = full_read(fd, p, size);
1942 if (cnt < 0) {
1943 status_line_bold_errno(fn);
1944 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
1945 } else if (cnt < size) {
1946#if ENABLE_PLATFORM_MINGW32
1947 // On WIN32 a partial read might just mean CRs have been removed
1948 int cnt_cr = cnt + count_cr(p, cnt);
666#endif 1949#endif
667 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) { 1950 // There was a partial read, shrink unused space
668 switch (c) { 1951 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
669#if ENABLE_FEATURE_VI_CRASHME 1952#if ENABLE_PLATFORM_MINGW32
670 case 'C': 1953 if (cnt_cr < size)
671 crashme = 1;
672 break;
673#endif 1954#endif
1955 status_line_bold("can't read '%s'", fn);
1956 }
1957 fi:
1958 close(fd);
1959
674#if ENABLE_FEATURE_VI_READONLY 1960#if ENABLE_FEATURE_VI_READONLY
675 case 'R': // Read-only flag 1961 if (initial
676 SET_READONLY_MODE(readonly_mode); 1962 && ((access(fn, W_OK) < 0) ||
677 break; 1963 // root will always have access()
678#endif 1964 // so we check fileperms too
679#if ENABLE_FEATURE_VI_COLON 1965 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
680 case 'c': // cmd line vi command 1966 )
681 if (*optarg) 1967 ) {
682 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN); 1968 SET_READONLY_FILE(readonly_mode);
683 break; 1969 }
684#endif 1970#endif
685 case 'H': 1971 return cnt;
686 show_help(); 1972}
687 /* fall through */ 1973
688 default: 1974// find matching char of pair () [] {}
689 bb_show_usage(); 1975// will crash if c is not one of these
690 return 1; 1976static char *find_pair(char *p, const char c)
1977{
1978 const char *braces = "()[]{}";
1979 char match;
1980 int dir, level;
1981
1982 dir = strchr(braces, c) - braces;
1983 dir ^= 1;
1984 match = braces[dir];
1985 dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\}
1986
1987 // look for match, count levels of pairs (( ))
1988 level = 1;
1989 for (;;) {
1990 p += dir;
1991 if (p < text || p >= end)
1992 return NULL;
1993 if (*p == c)
1994 level++; // increase pair levels
1995 if (*p == match) {
1996 level--; // reduce pair level
1997 if (level == 0)
1998 return p; // found matching pair
691 } 1999 }
692 } 2000 }
2001}
693 2002
694 // The argv array can be used by the ":next" and ":rewind" commands 2003#if ENABLE_FEATURE_VI_SETOPTS
695 argv += optind; 2004// show the matching char of a pair, () [] {}
696 argc -= optind; 2005static void showmatching(char *p)
2006{
2007 char *q, *save_dot;
697 2008
698 //----- This is the main file handling loop -------------- 2009 // we found half of a pair
699 save_argc = argc; 2010 q = find_pair(p, *p); // get loc of matching char
700 optind = 0; 2011 if (q == NULL) {
701 // "Save cursor, use alternate screen buffer, clear screen" 2012 indicate_error(); // no matching char
702 write1(ESC"[?1049h"); 2013 } else {
703 while (1) { 2014 // "q" now points to matching pair
704 edit_file(argv[optind]); /* param might be NULL */ 2015 save_dot = dot; // remember where we are
705 if (++optind >= argc) 2016 dot = q; // go to new loc
706 break; 2017 refresh(FALSE); // let the user see it
2018 mysleep(40); // give user some time
2019 dot = save_dot; // go back to old loc
2020 refresh(FALSE);
707 } 2021 }
708 // "Use normal screen buffer, restore cursor" 2022}
709 write1(ESC"[?1049l"); 2023#endif /* FEATURE_VI_SETOPTS */
710 //-----------------------------------------------------------
711 2024
712 return 0; 2025// might reallocate text[]! use p += stupid_insert(p, ...),
2026// and be careful to not use pointers into potentially freed text[]!
2027static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2028{
2029 uintptr_t bias;
2030 bias = text_hole_make(p, 1);
2031 p += bias;
2032 *p = c;
2033 return bias;
2034}
2035
2036#if !ENABLE_FEATURE_VI_UNDO
2037#define char_insert(a,b,c) char_insert(a,b)
2038#endif
2039static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
2040{
2041 if (c == 22) { // Is this an ctrl-V?
2042 p += stupid_insert(p, '^'); // use ^ to indicate literal next
2043 refresh(FALSE); // show the ^
2044 c = get_one_char();
2045 *p = c;
2046#if ENABLE_FEATURE_VI_UNDO
2047 undo_push_insert(p, 1, undo);
2048#else
2049 modified_count++;
2050#endif
2051 p++;
2052 } else if (c == 27) { // Is this an ESC?
2053 cmd_mode = 0;
2054 undo_queue_commit();
2055 cmdcnt = 0;
2056 end_cmd_q(); // stop adding to q
2057 last_status_cksum = 0; // force status update
2058 if ((p[-1] != '\n') && (dot > text)) {
2059 p--;
2060 }
2061#if !ENABLE_PLATFORM_MINGW32
2062 } else if (c == term_orig.c_cc[VERASE] || c == 8 || c == 127) { // Is this a BS
2063#else
2064 } else if (c == 8 || c == 127) { // Is this a BS
2065#endif
2066 if (p > text) {
2067 p--;
2068 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2069 }
2070 } else {
2071 // insert a char into text[]
2072 if (c == 13)
2073 c = '\n'; // translate \r to \n
2074#if ENABLE_FEATURE_VI_UNDO
2075# if ENABLE_FEATURE_VI_UNDO_QUEUE
2076 if (c == '\n')
2077 undo_queue_commit();
2078# endif
2079 undo_push_insert(p, 1, undo);
2080#else
2081 modified_count++;
2082#endif
2083 p += 1 + stupid_insert(p, c); // insert the char
2084#if ENABLE_FEATURE_VI_SETOPTS
2085 if (showmatch && strchr(")]}", c) != NULL) {
2086 showmatching(p - 1);
2087 }
2088 if (autoindent && c == '\n') { // auto indent the new line
2089 char *q;
2090 size_t len;
2091 q = prev_line(p); // use prev line as template
2092 len = strspn(q, " \t"); // space or tab
2093 if (len) {
2094 uintptr_t bias;
2095 bias = text_hole_make(p, len);
2096 p += bias;
2097 q += bias;
2098#if ENABLE_FEATURE_VI_UNDO
2099 undo_push_insert(p, len, undo);
2100#endif
2101 memcpy(p, q, len);
2102 p += len;
2103 }
2104 }
2105#endif
2106 }
2107 return p;
713} 2108}
714 2109
715/* read text from file or create an empty buf */ 2110// read text from file or create an empty buf
716/* will also update current_filename */ 2111// will also update current_filename
717static int init_text_buffer(char *fn) 2112static int init_text_buffer(char *fn)
718{ 2113{
719 int rc; 2114 int rc;
720 2115
721 /* allocate/reallocate text buffer */ 2116 // allocate/reallocate text buffer
722 free(text); 2117 free(text);
723 text_size = 10240; 2118 text_size = 10240;
724 screenbegin = dot = end = text = xzalloc(text_size); 2119 screenbegin = dot = end = text = xzalloc(text_size);
@@ -737,173 +2132,188 @@ static int init_text_buffer(char *fn)
737 modified_count = 0; 2132 modified_count = 0;
738 last_modified_count = -1; 2133 last_modified_count = -1;
739#if ENABLE_FEATURE_VI_YANKMARK 2134#if ENABLE_FEATURE_VI_YANKMARK
740 /* init the marks */ 2135 // init the marks
741 memset(mark, 0, sizeof(mark)); 2136 memset(mark, 0, sizeof(mark));
742#endif 2137#endif
743 return rc; 2138 return rc;
744} 2139}
745 2140
746#if ENABLE_FEATURE_VI_WIN_RESIZE 2141#if ENABLE_FEATURE_VI_YANKMARK \
747static int query_screen_dimensions(void) 2142 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
748{ 2143 || ENABLE_FEATURE_VI_CRASHME
749 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows); 2144// might reallocate text[]! use p += string_insert(p, ...),
750 if (rows > MAX_SCR_ROWS) 2145// and be careful to not use pointers into potentially freed text[]!
751 rows = MAX_SCR_ROWS; 2146# if !ENABLE_FEATURE_VI_UNDO
752 if (columns > MAX_SCR_COLS) 2147# define string_insert(a,b,c) string_insert(a,b)
753 columns = MAX_SCR_COLS; 2148# endif
754 return err; 2149static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
755}
756#else
757static ALWAYS_INLINE int query_screen_dimensions(void)
758{ 2150{
759 return 0; 2151 uintptr_t bias;
760} 2152 int i;
761#endif
762 2153
763static void edit_file(char *fn) 2154 i = strlen(s);
764{ 2155#if ENABLE_FEATURE_VI_UNDO
765#if ENABLE_FEATURE_VI_YANKMARK 2156 undo_push_insert(p, i, undo);
766#define cur_line edit_file__cur_line
767#endif
768 int c;
769#if ENABLE_FEATURE_VI_USE_SIGNALS
770 int sig;
771#endif 2157#endif
772 2158 bias = text_hole_make(p, i);
773 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files 2159 p += bias;
774 rawmode(); 2160 memcpy(p, s, i);
775 rows = 24; 2161#if ENABLE_FEATURE_VI_YANKMARK
776 columns = 80; 2162 {
777 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions(); 2163 int cnt;
778#if ENABLE_FEATURE_VI_ASK_TERMINAL 2164 for (cnt = 0; *s != '\0'; s++) {
779 if (G.get_rowcol_error /* TODO? && no input on stdin */) { 2165 if (*s == '\n')
780 uint64_t k; 2166 cnt++;
781 write1(ESC"[999;999H" ESC"[6n");
782 fflush_all();
783 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
784 if ((int32_t)k == KEYCODE_CURSOR_POS) {
785 uint32_t rc = (k >> 32);
786 columns = (rc & 0x7fff);
787 if (columns > MAX_SCR_COLS)
788 columns = MAX_SCR_COLS;
789 rows = ((rc >> 16) & 0x7fff);
790 if (rows > MAX_SCR_ROWS)
791 rows = MAX_SCR_ROWS;
792 } 2167 }
2168 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
793 } 2169 }
794#endif 2170#endif
795 new_screen(rows, columns); // get memory for virtual screen 2171 return bias;
796 init_text_buffer(fn); 2172}
797
798#if ENABLE_FEATURE_VI_YANKMARK
799 YDreg = 26; // default Yank/Delete reg
800// Ureg = 27; - const // hold orig line for "U" cmd
801 mark[26] = mark[27] = text; // init "previous context"
802#endif 2173#endif
803 2174
804 last_forward_char = last_input_char = '\0'; 2175static int file_write(char *fn, char *first, char *last)
805 crow = 0; 2176{
806 ccol = 0; 2177 int fd, cnt, charcnt;
807 2178
808#if ENABLE_FEATURE_VI_USE_SIGNALS 2179 if (fn == 0) {
809 signal(SIGINT, catch_sig); 2180 status_line_bold("No current filename");
810 signal(SIGWINCH, winch_sig); 2181 return -2;
811 signal(SIGTSTP, suspend_sig);
812 sig = sigsetjmp(restart, 1);
813 if (sig != 0) {
814 screenbegin = dot = text;
815 } 2182 }
2183 // By popular request we do not open file with O_TRUNC,
2184 // but instead ftruncate() it _after_ successful write.
2185 // Might reduce amount of data lost on power fail etc.
2186#if !ENABLE_PLATFORM_MINGW32
2187 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2188#else
2189 fd = open(fn, (O_WRONLY | O_CREAT | _O_TEXT), 0666);
816#endif 2190#endif
817 2191 if (fd < 0)
818 cmd_mode = 0; // 0=command 1=insert 2='R'eplace 2192 return -1;
819 cmdcnt = 0; 2193 cnt = last - first + 1;
820 tabstop = 8; 2194 charcnt = full_write(fd, first, cnt);
821 offset = 0; // no horizontal offset 2195#if !ENABLE_PLATFORM_MINGW32
822 c = '\0'; 2196 ftruncate(fd, charcnt);
823#if ENABLE_FEATURE_VI_DOT_CMD 2197#else
824 free(ioq_start); 2198 // File was written in text mode; this makes it bigger so adjust
825 ioq = ioq_start = NULL; 2199 // the truncation to match.
826 lmc_len = 0; 2200 ftruncate(fd, charcnt + count_cr(first, cnt));
827 adding2q = 0;
828#endif 2201#endif
2202 if (charcnt == cnt) {
2203 // good write
2204 //modified_count = FALSE;
2205 } else {
2206 charcnt = 0;
2207 }
2208 close(fd);
2209 return charcnt;
2210}
829 2211
830#if ENABLE_FEATURE_VI_COLON 2212#if ENABLE_FEATURE_VI_SEARCH
831 { 2213# if ENABLE_FEATURE_VI_REGEX_SEARCH
832 char *p, *q; 2214// search for pattern starting at p
833 int n = 0; 2215static char *char_search(char *p, const char *pat, int dir_and_range)
2216{
2217 struct re_pattern_buffer preg;
2218 const char *err;
2219 char *q;
2220 int i;
2221 int size;
2222 int range;
834 2223
835 while ((p = initial_cmds[n]) != NULL) { 2224 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
836 do { 2225 if (ignorecase)
837 q = p; 2226 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
838 p = strchr(q, '\n'); 2227
839 if (p) 2228 memset(&preg, 0, sizeof(preg));
840 while (*p == '\n') 2229 err = re_compile_pattern(pat, strlen(pat), &preg);
841 *p++ = '\0'; 2230 if (err != NULL) {
842 if (*q) 2231 status_line_bold("bad search pattern '%s': %s", pat, err);
843 colon(q); 2232 return p;
844 } while (p);
845 free(initial_cmds[n]);
846 initial_cmds[n] = NULL;
847 n++;
848 }
849 } 2233 }
850#endif 2234
851 redraw(FALSE); // dont force every col re-draw 2235 range = (dir_and_range & 1);
852 //------This is the main Vi cmd handling loop ----------------------- 2236 q = end - 1; // if FULL
853 while (editing > 0) { 2237 if (range == LIMITED)
854#if ENABLE_FEATURE_VI_CRASHME 2238 q = next_line(p);
855 if (crashme > 0) { 2239 if (dir_and_range < 0) { // BACK?
856 if ((end - text) > 1) { 2240 q = text;
857 crash_dummy(); // generate a random command 2241 if (range == LIMITED)
858 } else { 2242 q = prev_line(p);
859 crashme = 0; 2243 }
860 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string 2244
861 dot = text; 2245 // RANGE could be negative if we are searching backwards
862 refresh(FALSE); 2246 range = q - p;
2247 q = p;
2248 size = range;
2249 if (range < 0) {
2250 size = -size;
2251 q = p - size;
2252 if (q < text)
2253 q = text;
2254 }
2255 // search for the compiled pattern, preg, in p[]
2256 // range < 0: search backward
2257 // range > 0: search forward
2258 // 0 < start < size
2259 // re_search() < 0: not found or error
2260 // re_search() >= 0: index of found pattern
2261 // struct pattern char int int int struct reg
2262 // re_search(*pattern_buffer, *string, size, start, range, *regs)
2263 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
2264 regfree(&preg);
2265 if (i < 0)
2266 return NULL;
2267 if (dir_and_range > 0) // FORWARD?
2268 p = p + i;
2269 else
2270 p = p - i;
2271 return p;
2272}
2273# else
2274# if ENABLE_FEATURE_VI_SETOPTS
2275static int mycmp(const char *s1, const char *s2, int len)
2276{
2277 if (ignorecase) {
2278 return strncasecmp(s1, s2, len);
2279 }
2280 return strncmp(s1, s2, len);
2281}
2282# else
2283# define mycmp strncmp
2284# endif
2285static char *char_search(char *p, const char *pat, int dir_and_range)
2286{
2287 char *start, *stop;
2288 int len;
2289 int range;
2290
2291 len = strlen(pat);
2292 range = (dir_and_range & 1);
2293 if (dir_and_range > 0) { //FORWARD?
2294 stop = end - 1; // assume range is p..end-1
2295 if (range == LIMITED)
2296 stop = next_line(p); // range is to next line
2297 for (start = p; start < stop; start++) {
2298 if (mycmp(start, pat, len) == 0) {
2299 return start;
863 } 2300 }
864 } 2301 }
865#endif 2302 } else { //BACK
866 last_input_char = c = get_one_char(); // get a cmd from user 2303 stop = text; // assume range is text..p
867#if ENABLE_FEATURE_VI_YANKMARK 2304 if (range == LIMITED)
868 // save a copy of the current line- for the 'U" command 2305 stop = prev_line(p); // range is to prev line
869 if (begin_line(dot) != cur_line) { 2306 for (start = p - len; start >= stop; start--) {
870 cur_line = begin_line(dot); 2307 if (mycmp(start, pat, len) == 0) {
871 text_yank(begin_line(dot), end_line(dot), Ureg); 2308 return start;
872 } 2309 }
873#endif
874#if ENABLE_FEATURE_VI_DOT_CMD
875 // These are commands that change text[].
876 // Remember the input for the "." command
877 if (!adding2q && ioq_start == NULL
878 && cmd_mode == 0 // command mode
879 && c > '\0' // exclude NUL and non-ASCII chars
880 && c < 0x7f // (Unicode and such)
881 && strchr(modifying_cmds, c)
882 ) {
883 start_new_cmd_q(c);
884 }
885#endif
886 do_cmd(c); // execute the user command
887
888 // poll to see if there is input already waiting. if we are
889 // not able to display output fast enough to keep up, skip
890 // the display update until we catch up with input.
891 if (!readbuffer[0] && mysleep(0) == 0) {
892 // no input pending - so update output
893 refresh(FALSE);
894 show_status_line();
895 } 2310 }
896#if ENABLE_FEATURE_VI_CRASHME
897 if (crashme > 0)
898 crash_test(); // test editor variables
899#endif
900 } 2311 }
901 //------------------------------------------------------------------- 2312 // pattern not found
902 2313 return NULL;
903 go_bottom_and_clear_to_eol();
904 cookmode();
905#undef cur_line
906} 2314}
2315# endif
2316#endif /* FEATURE_VI_SEARCH */
907 2317
908//----- The Colon commands ------------------------------------- 2318//----- The Colon commands -------------------------------------
909#if ENABLE_FEATURE_VI_COLON 2319#if ENABLE_FEATURE_VI_COLON
@@ -996,7 +2406,7 @@ static void setops(const char *args, const char *opname, int flg_no,
996 const char *short_opname, int opt) 2406 const char *short_opname, int opt)
997{ 2407{
998 const char *a = args + flg_no; 2408 const char *a = args + flg_no;
999 int l = strlen(opname) - 1; /* opname have + ' ' */ 2409 int l = strlen(opname) - 1; // opname have + ' '
1000 2410
1001 // maybe strncmp? we had tons of erroneous strncasecmp's... 2411 // maybe strncmp? we had tons of erroneous strncasecmp's...
1002 if (strncasecmp(a, opname, l) == 0 2412 if (strncasecmp(a, opname, l) == 0
@@ -1016,7 +2426,7 @@ static void setops(const char *args, const char *opname, int flg_no,
1016static void colon(char *buf) 2426static void colon(char *buf)
1017{ 2427{
1018#if !ENABLE_FEATURE_VI_COLON 2428#if !ENABLE_FEATURE_VI_COLON
1019 /* Simple ":cmd" handler with minimal set of commands */ 2429 // Simple ":cmd" handler with minimal set of commands
1020 char *p = buf; 2430 char *p = buf;
1021 int cnt; 2431 int cnt;
1022 2432
@@ -1049,7 +2459,7 @@ static void colon(char *buf)
1049 } else { 2459 } else {
1050 modified_count = 0; 2460 modified_count = 0;
1051 last_modified_count = -1; 2461 last_modified_count = -1;
1052 status_line("'%s' %dL, %dC", 2462 status_line("'%s' %uL, %uC",
1053 current_filename, 2463 current_filename,
1054 count_lines(text, end - 1), cnt 2464 count_lines(text, end - 1), cnt
1055 ); 2465 );
@@ -1220,7 +2630,7 @@ static void colon(char *buf)
1220 li = count_lines(text, end - 1); 2630 li = count_lines(text, end - 1);
1221 status_line("'%s'%s" 2631 status_line("'%s'%s"
1222 IF_FEATURE_VI_READONLY("%s") 2632 IF_FEATURE_VI_READONLY("%s")
1223 " %dL, %dC", 2633 " %uL, %uC",
1224 current_filename, 2634 current_filename,
1225 (size < 0 ? " [New file]" : ""), 2635 (size < 0 ? " [New file]" : ""),
1226 IF_FEATURE_VI_READONLY( 2636 IF_FEATURE_VI_READONLY(
@@ -1286,7 +2696,7 @@ static void colon(char *buf)
1286 if (useforce) { 2696 if (useforce) {
1287 if (*cmd == 'q') { 2697 if (*cmd == 'q') {
1288 // force end of argv list 2698 // force end of argv list
1289 optind = save_argc; 2699 optind = cmdline_filecnt;
1290 } 2700 }
1291 editing = 0; 2701 editing = 0;
1292 goto ret; 2702 goto ret;
@@ -1297,9 +2707,9 @@ static void colon(char *buf)
1297 goto ret; 2707 goto ret;
1298 } 2708 }
1299 // are there other file to edit 2709 // are there other file to edit
1300 n = save_argc - optind - 1; 2710 n = cmdline_filecnt - optind - 1;
1301 if (*cmd == 'q' && n > 0) { 2711 if (*cmd == 'q' && n > 0) {
1302 status_line_bold("%d more file(s) to edit", n); 2712 status_line_bold("%u more file(s) to edit", n);
1303 goto ret; 2713 goto ret;
1304 } 2714 }
1305 if (*cmd == 'n' && n <= 0) { 2715 if (*cmd == 'n' && n <= 0) {
@@ -1344,7 +2754,7 @@ static void colon(char *buf)
1344 li = count_lines(q, q + size - 1); 2754 li = count_lines(q, q + size - 1);
1345 status_line("'%s'" 2755 status_line("'%s'"
1346 IF_FEATURE_VI_READONLY("%s") 2756 IF_FEATURE_VI_READONLY("%s")
1347 " %dL, %dC", 2757 " %uL, %uC",
1348 fn, 2758 fn,
1349 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),) 2759 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1350 li, size 2760 li, size
@@ -1359,7 +2769,7 @@ static void colon(char *buf)
1359 status_line_bold("No write since last change (:%s! overrides)", cmd); 2769 status_line_bold("No write since last change (:%s! overrides)", cmd);
1360 } else { 2770 } else {
1361 // reset the filenames to edit 2771 // reset the filenames to edit
1362 optind = -1; /* start from 0th file */ 2772 optind = -1; // start from 0th file
1363 editing = 0; 2773 editing = 0;
1364 } 2774 }
1365# if ENABLE_FEATURE_VI_SET 2775# if ENABLE_FEATURE_VI_SET
@@ -1459,7 +2869,7 @@ static void colon(char *buf)
1459 bias = string_insert(found, R, ALLOW_UNDO_CHAIN); 2869 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
1460 found += bias; 2870 found += bias;
1461 ls += bias; 2871 ls += bias;
1462 /*q += bias; - recalculated anyway */ 2872 //q += bias; - recalculated anyway
1463 // check for "global" :s/foo/bar/g 2873 // check for "global" :s/foo/bar/g
1464 if (gflag == 'g') { 2874 if (gflag == 'g') {
1465 if ((found + len_R) < end_line(ls)) { 2875 if ((found + len_R) < end_line(ls)) {
@@ -1516,7 +2926,7 @@ static void colon(char *buf)
1516 } else { 2926 } else {
1517 // how many lines written 2927 // how many lines written
1518 li = count_lines(q, q + l - 1); 2928 li = count_lines(q, q + l - 1);
1519 status_line("'%s' %dL, %dC", fn, li, l); 2929 status_line("'%s' %uL, %uC", fn, li, l);
1520 if (l == size) { 2930 if (l == size) {
1521 if (q == text && q + l == end) { 2931 if (q == text && q + l == end) {
1522 modified_count = 0; 2932 modified_count = 0;
@@ -1555,535 +2965,109 @@ static void colon(char *buf)
1555#endif /* FEATURE_VI_COLON */ 2965#endif /* FEATURE_VI_COLON */
1556} 2966}
1557 2967
1558static void Hit_Return(void) 2968//----- Char Routines --------------------------------------------
1559{ 2969// Chars that are part of a word-
1560 int c; 2970// 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1561 2971// Chars that are Not part of a word (stoppers)
1562 standout_start(); 2972// !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1563 write1("[Hit return to continue]"); 2973// Chars that are WhiteSpace
1564 standout_end(); 2974// TAB NEWLINE VT FF RETURN SPACE
1565 while ((c = get_one_char()) != '\n' && c != '\r') 2975// DO NOT COUNT NEWLINE AS WHITESPACE
1566 continue;
1567 redraw(TRUE); // force redraw all
1568}
1569
1570static int next_tabstop(int col)
1571{
1572 return col + ((tabstop - 1) - (col % tabstop));
1573}
1574 2976
1575//----- Synchronize the cursor to Dot -------------------------- 2977static int st_test(char *p, int type, int dir, char *tested)
1576static NOINLINE void sync_cursor(char *d, int *row, int *col)
1577{ 2978{
1578 char *beg_cur; // begin and end of "d" line 2979 char c, c0, ci;
1579 char *tp; 2980 int test, inc;
1580 int cnt, ro, co;
1581
1582 beg_cur = begin_line(d); // first char of cur line
1583
1584 if (beg_cur < screenbegin) {
1585 // "d" is before top line on screen
1586 // how many lines do we have to move
1587 cnt = count_lines(beg_cur, screenbegin);
1588 sc1:
1589 screenbegin = beg_cur;
1590 if (cnt > (rows - 1) / 2) {
1591 // we moved too many lines. put "dot" in middle of screen
1592 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1593 screenbegin = prev_line(screenbegin);
1594 }
1595 }
1596 } else {
1597 char *end_scr; // begin and end of screen
1598 end_scr = end_screen(); // last char of screen
1599 if (beg_cur > end_scr) {
1600 // "d" is after bottom line on screen
1601 // how many lines do we have to move
1602 cnt = count_lines(end_scr, beg_cur);
1603 if (cnt > (rows - 1) / 2)
1604 goto sc1; // too many lines
1605 for (ro = 0; ro < cnt - 1; ro++) {
1606 // move screen begin the same amount
1607 screenbegin = next_line(screenbegin);
1608 // now, move the end of screen
1609 end_scr = next_line(end_scr);
1610 end_scr = end_line(end_scr);
1611 }
1612 }
1613 }
1614 // "d" is on screen- find out which row
1615 tp = screenbegin;
1616 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1617 if (tp == beg_cur)
1618 break;
1619 tp = next_line(tp);
1620 }
1621
1622 // find out what col "d" is on
1623 co = 0;
1624 while (tp < d) { // drive "co" to correct column
1625 if (*tp == '\n') //vda || *tp == '\0')
1626 break;
1627 if (*tp == '\t') {
1628 // handle tabs like real vi
1629 if (d == tp && cmd_mode) {
1630 break;
1631 }
1632 co = next_tabstop(co);
1633 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1634 co++; // display as ^X, use 2 columns
1635 }
1636 co++;
1637 tp++;
1638 }
1639
1640 // "co" is the column where "dot" is.
1641 // The screen has "columns" columns.
1642 // The currently displayed columns are 0+offset -- columns+ofset
1643 // |-------------------------------------------------------------|
1644 // ^ ^ ^
1645 // offset | |------- columns ----------------|
1646 //
1647 // If "co" is already in this range then we do not have to adjust offset
1648 // but, we do have to subtract the "offset" bias from "co".
1649 // If "co" is outside this range then we have to change "offset".
1650 // If the first char of a line is a tab the cursor will try to stay
1651 // in column 7, but we have to set offset to 0.
1652
1653 if (co < 0 + offset) {
1654 offset = co;
1655 }
1656 if (co >= columns + offset) {
1657 offset = co - columns + 1;
1658 }
1659 // if the first char of the line is a tab, and "dot" is sitting on it
1660 // force offset to 0.
1661 if (d == beg_cur && *d == '\t') {
1662 offset = 0;
1663 }
1664 co -= offset;
1665 2981
1666 *row = ro; 2982 inc = dir;
1667 *col = co; 2983 c = c0 = p[0];
1668} 2984 ci = p[inc];
2985 test = 0;
1669 2986
1670//----- Text Movement Routines --------------------------------- 2987 if (type == S_BEFORE_WS) {
1671static char *begin_line(char *p) // return pointer to first char cur line 2988 c = ci;
1672{ 2989 test = (!isspace(c) || c == '\n');
1673 if (p > text) {
1674 p = memrchr(text, '\n', p - text);
1675 if (!p)
1676 return text;
1677 return p + 1;
1678 } 2990 }
1679 return p; 2991 if (type == S_TO_WS) {
1680} 2992 c = c0;
1681 2993 test = (!isspace(c) || c == '\n');
1682static char *end_line(char *p) // return pointer to NL of cur line
1683{
1684 if (p < end - 1) {
1685 p = memchr(p, '\n', end - p - 1);
1686 if (!p)
1687 return end - 1;
1688 } 2994 }
1689 return p; 2995 if (type == S_OVER_WS) {
1690} 2996 c = c0;
1691 2997 test = isspace(c);
1692static char *dollar_line(char *p) // return pointer to just before NL line
1693{
1694 p = end_line(p);
1695 // Try to stay off of the Newline
1696 if (*p == '\n' && (p - begin_line(p)) > 0)
1697 p--;
1698 return p;
1699}
1700
1701static char *prev_line(char *p) // return pointer first char prev line
1702{
1703 p = begin_line(p); // goto beginning of cur line
1704 if (p > text && p[-1] == '\n')
1705 p--; // step to prev line
1706 p = begin_line(p); // goto beginning of prev line
1707 return p;
1708}
1709
1710static char *next_line(char *p) // return pointer first char next line
1711{
1712 p = end_line(p);
1713 if (p < end - 1 && *p == '\n')
1714 p++; // step to next line
1715 return p;
1716}
1717
1718//----- Text Information Routines ------------------------------
1719static char *end_screen(void)
1720{
1721 char *q;
1722 int cnt;
1723
1724 // find new bottom line
1725 q = screenbegin;
1726 for (cnt = 0; cnt < rows - 2; cnt++)
1727 q = next_line(q);
1728 q = end_line(q);
1729 return q;
1730}
1731
1732// count line from start to stop
1733static int count_lines(char *start, char *stop)
1734{
1735 char *q;
1736 int cnt;
1737
1738 if (stop < start) { // start and stop are backwards- reverse them
1739 q = start;
1740 start = stop;
1741 stop = q;
1742 } 2998 }
1743 cnt = 0; 2999 if (type == S_END_PUNCT) {
1744 stop = end_line(stop); 3000 c = ci;
1745 while (start <= stop && start <= end - 1) { 3001 test = ispunct(c);
1746 start = end_line(start);
1747 if (*start == '\n')
1748 cnt++;
1749 start++;
1750 } 3002 }
1751 return cnt; 3003 if (type == S_END_ALNUM) {
1752} 3004 c = ci;
1753 3005 test = (isalnum(c) || c == '_');
1754static char *find_line(int li) // find beginning of line #li
1755{
1756 char *q;
1757
1758 for (q = text; li > 1; li--) {
1759 q = next_line(q);
1760 } 3006 }
1761 return q; 3007 *tested = c;
1762} 3008 return test;
1763
1764//----- Dot Movement Routines ----------------------------------
1765static void dot_left(void)
1766{
1767 undo_queue_commit();
1768 if (dot > text && dot[-1] != '\n')
1769 dot--;
1770}
1771
1772static void dot_right(void)
1773{
1774 undo_queue_commit();
1775 if (dot < end - 1 && *dot != '\n')
1776 dot++;
1777}
1778
1779static void dot_begin(void)
1780{
1781 undo_queue_commit();
1782 dot = begin_line(dot); // return pointer to first char cur line
1783}
1784
1785static void dot_end(void)
1786{
1787 undo_queue_commit();
1788 dot = end_line(dot); // return pointer to last char cur line
1789} 3009}
1790 3010
1791static char *move_to_col(char *p, int l) 3011static char *skip_thing(char *p, int linecnt, int dir, int type)
1792{ 3012{
1793 int co; 3013 char c;
1794 3014
1795 p = begin_line(p); 3015 while (st_test(p, type, dir, &c)) {
1796 co = 0; 3016 // make sure we limit search to correct number of lines
1797 while (co < l && p < end) { 3017 if (c == '\n' && --linecnt < 1)
1798 if (*p == '\n') //vda || *p == '\0')
1799 break; 3018 break;
1800 if (*p == '\t') { 3019 if (dir >= 0 && p >= end - 1)
1801 co = next_tabstop(co); 3020 break;
1802 } else if (*p < ' ' || *p == 127) { 3021 if (dir < 0 && p <= text)
1803 co++; // display as ^X, use 2 columns 3022 break;
1804 } 3023 p += dir; // move to next char
1805 co++;
1806 p++;
1807 }
1808 return p;
1809}
1810
1811static void dot_next(void)
1812{
1813 undo_queue_commit();
1814 dot = next_line(dot);
1815}
1816
1817static void dot_prev(void)
1818{
1819 undo_queue_commit();
1820 dot = prev_line(dot);
1821}
1822
1823static void dot_scroll(int cnt, int dir)
1824{
1825 char *q;
1826
1827 undo_queue_commit();
1828 for (; cnt > 0; cnt--) {
1829 if (dir < 0) {
1830 // scroll Backwards
1831 // ctrl-Y scroll up one line
1832 screenbegin = prev_line(screenbegin);
1833 } else {
1834 // scroll Forwards
1835 // ctrl-E scroll down one line
1836 screenbegin = next_line(screenbegin);
1837 }
1838 }
1839 // make sure "dot" stays on the screen so we dont scroll off
1840 if (dot < screenbegin)
1841 dot = screenbegin;
1842 q = end_screen(); // find new bottom line
1843 if (dot > q)
1844 dot = begin_line(q); // is dot is below bottom line?
1845 dot_skip_over_ws();
1846}
1847
1848static void dot_skip_over_ws(void)
1849{
1850 // skip WS
1851 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1852 dot++;
1853}
1854
1855static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1856{
1857 if (p >= end && end > text) {
1858 p = end - 1;
1859 indicate_error();
1860 }
1861 if (p < text) {
1862 p = text;
1863 indicate_error();
1864 } 3024 }
1865 return p; 3025 return p;
1866} 3026}
1867 3027
1868//----- Helper Utility Routines -------------------------------- 3028#if ENABLE_FEATURE_VI_USE_SIGNALS
1869 3029static void winch_handler(int sig UNUSED_PARAM)
1870//----------------------------------------------------------------
1871//----- Char Routines --------------------------------------------
1872/* Chars that are part of a word-
1873 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1874 * Chars that are Not part of a word (stoppers)
1875 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1876 * Chars that are WhiteSpace
1877 * TAB NEWLINE VT FF RETURN SPACE
1878 * DO NOT COUNT NEWLINE AS WHITESPACE
1879 */
1880
1881static char *new_screen(int ro, int co)
1882{ 3030{
1883 int li; 3031 int save_errno = errno;
1884 3032 // FIXME: do it in main loop!!!
1885 free(screen); 3033 signal(SIGWINCH, winch_handler);
1886 screensize = ro * co + 8; 3034 query_screen_dimensions();
1887 screen = xmalloc(screensize); 3035 new_screen(rows, columns); // get memory for virtual screen
1888 // initialize the new screen. assume this will be a empty file. 3036 redraw(TRUE); // re-draw the screen
1889 screen_erase(); 3037 errno = save_errno;
1890 // non-existent text[] lines start with a tilde (~).
1891 for (li = 1; li < ro - 1; li++) {
1892 screen[(li * co) + 0] = '~';
1893 }
1894 return screen;
1895} 3038}
1896 3039static void tstp_handler(int sig UNUSED_PARAM)
1897#if ENABLE_FEATURE_VI_SEARCH
1898
1899# if ENABLE_FEATURE_VI_REGEX_SEARCH
1900
1901// search for pattern starting at p
1902static char *char_search(char *p, const char *pat, int dir_and_range)
1903{ 3040{
1904 struct re_pattern_buffer preg; 3041 int save_errno = errno;
1905 const char *err;
1906 char *q;
1907 int i;
1908 int size;
1909 int range;
1910
1911 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1912 if (ignorecase)
1913 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
1914
1915 memset(&preg, 0, sizeof(preg));
1916 err = re_compile_pattern(pat, strlen(pat), &preg);
1917 if (err != NULL) {
1918 status_line_bold("bad search pattern '%s': %s", pat, err);
1919 return p;
1920 }
1921
1922 range = (dir_and_range & 1);
1923 q = end - 1; // if FULL
1924 if (range == LIMITED)
1925 q = next_line(p);
1926 if (dir_and_range < 0) { // BACK?
1927 q = text;
1928 if (range == LIMITED)
1929 q = prev_line(p);
1930 }
1931 3042
1932 // RANGE could be negative if we are searching backwards 3043 // ioctl inside cookmode() was seen to generate SIGTTOU,
1933 range = q - p; 3044 // stopping us too early. Prevent that:
1934 q = p; 3045 signal(SIGTTOU, SIG_IGN);
1935 size = range;
1936 if (range < 0) {
1937 size = -size;
1938 q = p - size;
1939 if (q < text)
1940 q = text;
1941 }
1942 // search for the compiled pattern, preg, in p[]
1943 // range < 0: search backward
1944 // range > 0: search forward
1945 // 0 < start < size
1946 // re_search() < 0: not found or error
1947 // re_search() >= 0: index of found pattern
1948 // struct pattern char int int int struct reg
1949 // re_search(*pattern_buffer, *string, size, start, range, *regs)
1950 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
1951 regfree(&preg);
1952 if (i < 0)
1953 return NULL;
1954 if (dir_and_range > 0) // FORWARD?
1955 p = p + i;
1956 else
1957 p = p - i;
1958 return p;
1959}
1960 3046
1961# else 3047 go_bottom_and_clear_to_eol();
3048 cookmode(); // terminal to "cooked"
1962 3049
1963# if ENABLE_FEATURE_VI_SETOPTS 3050 // stop now
1964static int mycmp(const char *s1, const char *s2, int len) 3051 //signal(SIGTSTP, SIG_DFL);
1965{ 3052 //raise(SIGTSTP);
1966 if (ignorecase) { 3053 raise(SIGSTOP); // avoid "dance" with TSTP handler - use SIGSTOP instead
1967 return strncasecmp(s1, s2, len); 3054 //signal(SIGTSTP, tstp_handler);
1968 }
1969 return strncmp(s1, s2, len);
1970}
1971# else
1972# define mycmp strncmp
1973# endif
1974 3055
1975static char *char_search(char *p, const char *pat, int dir_and_range) 3056 // we have been "continued" with SIGCONT, restore screen and termios
1976{ 3057 rawmode(); // terminal to "raw"
1977 char *start, *stop; 3058 last_status_cksum = 0; // force status update
1978 int len; 3059 redraw(TRUE); // re-draw the screen
1979 int range;
1980 3060
1981 len = strlen(pat); 3061 errno = save_errno;
1982 range = (dir_and_range & 1);
1983 if (dir_and_range > 0) { //FORWARD?
1984 stop = end - 1; // assume range is p..end-1
1985 if (range == LIMITED)
1986 stop = next_line(p); // range is to next line
1987 for (start = p; start < stop; start++) {
1988 if (mycmp(start, pat, len) == 0) {
1989 return start;
1990 }
1991 }
1992 } else { //BACK
1993 stop = text; // assume range is text..p
1994 if (range == LIMITED)
1995 stop = prev_line(p); // range is to prev line
1996 for (start = p - len; start >= stop; start--) {
1997 if (mycmp(start, pat, len) == 0) {
1998 return start;
1999 }
2000 }
2001 }
2002 // pattern not found
2003 return NULL;
2004} 3062}
2005 3063static void int_handler(int sig)
2006# endif
2007
2008#endif /* FEATURE_VI_SEARCH */
2009
2010static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
2011{ 3064{
2012 if (c == 22) { // Is this an ctrl-V? 3065 signal(SIGINT, int_handler);
2013 p += stupid_insert(p, '^'); // use ^ to indicate literal next 3066 siglongjmp(restart, sig);
2014 refresh(FALSE); // show the ^
2015 c = get_one_char();
2016 *p = c;
2017#if ENABLE_FEATURE_VI_UNDO
2018 undo_push_insert(p, 1, undo);
2019#else
2020 modified_count++;
2021#endif /* ENABLE_FEATURE_VI_UNDO */
2022 p++;
2023 } else if (c == 27) { // Is this an ESC?
2024 cmd_mode = 0;
2025 undo_queue_commit();
2026 cmdcnt = 0;
2027 end_cmd_q(); // stop adding to q
2028 last_status_cksum = 0; // force status update
2029 if ((p[-1] != '\n') && (dot > text)) {
2030 p--;
2031 }
2032 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
2033 if (p > text) {
2034 p--;
2035 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2036 }
2037 } else {
2038 // insert a char into text[]
2039 if (c == 13)
2040 c = '\n'; // translate \r to \n
2041#if ENABLE_FEATURE_VI_UNDO
2042# if ENABLE_FEATURE_VI_UNDO_QUEUE
2043 if (c == '\n')
2044 undo_queue_commit();
2045# endif
2046 undo_push_insert(p, 1, undo);
2047#else
2048 modified_count++;
2049#endif /* ENABLE_FEATURE_VI_UNDO */
2050 p += 1 + stupid_insert(p, c); // insert the char
2051#if ENABLE_FEATURE_VI_SETOPTS
2052 if (showmatch && strchr(")]}", c) != NULL) {
2053 showmatching(p - 1);
2054 }
2055 if (autoindent && c == '\n') { // auto indent the new line
2056 char *q;
2057 size_t len;
2058 q = prev_line(p); // use prev line as template
2059 len = strspn(q, " \t"); // space or tab
2060 if (len) {
2061 uintptr_t bias;
2062 bias = text_hole_make(p, len);
2063 p += bias;
2064 q += bias;
2065#if ENABLE_FEATURE_VI_UNDO
2066 undo_push_insert(p, len, undo);
2067#endif
2068 memcpy(p, q, len);
2069 p += len;
2070 }
2071 }
2072#endif
2073 }
2074 return p;
2075} 3067}
3068#endif /* FEATURE_VI_USE_SIGNALS */
2076 3069
2077// might reallocate text[]! use p += stupid_insert(p, ...), 3070static void do_cmd(int c);
2078// and be careful to not use pointers into potentially freed text[]!
2079static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2080{
2081 uintptr_t bias;
2082 bias = text_hole_make(p, 1);
2083 p += bias;
2084 *p = c;
2085 return bias;
2086}
2087 3071
2088static int find_range(char **start, char **stop, char c) 3072static int find_range(char **start, char **stop, char c)
2089{ 3073{
@@ -2158,1342 +3142,8 @@ static int find_range(char **start, char **stop, char c)
2158 return multiline; 3142 return multiline;
2159} 3143}
2160 3144
2161static int st_test(char *p, int type, int dir, char *tested)
2162{
2163 char c, c0, ci;
2164 int test, inc;
2165
2166 inc = dir;
2167 c = c0 = p[0];
2168 ci = p[inc];
2169 test = 0;
2170
2171 if (type == S_BEFORE_WS) {
2172 c = ci;
2173 test = (!isspace(c) || c == '\n');
2174 }
2175 if (type == S_TO_WS) {
2176 c = c0;
2177 test = (!isspace(c) || c == '\n');
2178 }
2179 if (type == S_OVER_WS) {
2180 c = c0;
2181 test = isspace(c);
2182 }
2183 if (type == S_END_PUNCT) {
2184 c = ci;
2185 test = ispunct(c);
2186 }
2187 if (type == S_END_ALNUM) {
2188 c = ci;
2189 test = (isalnum(c) || c == '_');
2190 }
2191 *tested = c;
2192 return test;
2193}
2194
2195static char *skip_thing(char *p, int linecnt, int dir, int type)
2196{
2197 char c;
2198
2199 while (st_test(p, type, dir, &c)) {
2200 // make sure we limit search to correct number of lines
2201 if (c == '\n' && --linecnt < 1)
2202 break;
2203 if (dir >= 0 && p >= end - 1)
2204 break;
2205 if (dir < 0 && p <= text)
2206 break;
2207 p += dir; // move to next char
2208 }
2209 return p;
2210}
2211
2212// find matching char of pair () [] {}
2213// will crash if c is not one of these
2214static char *find_pair(char *p, const char c)
2215{
2216 const char *braces = "()[]{}";
2217 char match;
2218 int dir, level;
2219
2220 dir = strchr(braces, c) - braces;
2221 dir ^= 1;
2222 match = braces[dir];
2223 dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */
2224
2225 // look for match, count levels of pairs (( ))
2226 level = 1;
2227 for (;;) {
2228 p += dir;
2229 if (p < text || p >= end)
2230 return NULL;
2231 if (*p == c)
2232 level++; // increase pair levels
2233 if (*p == match) {
2234 level--; // reduce pair level
2235 if (level == 0)
2236 return p; // found matching pair
2237 }
2238 }
2239}
2240
2241#if ENABLE_FEATURE_VI_SETOPTS
2242// show the matching char of a pair, () [] {}
2243static void showmatching(char *p)
2244{
2245 char *q, *save_dot;
2246
2247 // we found half of a pair
2248 q = find_pair(p, *p); // get loc of matching char
2249 if (q == NULL) {
2250 indicate_error(); // no matching char
2251 } else {
2252 // "q" now points to matching pair
2253 save_dot = dot; // remember where we are
2254 dot = q; // go to new loc
2255 refresh(FALSE); // let the user see it
2256 mysleep(40); // give user some time
2257 dot = save_dot; // go back to old loc
2258 refresh(FALSE);
2259 }
2260}
2261#endif /* FEATURE_VI_SETOPTS */
2262
2263#if ENABLE_FEATURE_VI_UNDO
2264static void flush_undo_data(void)
2265{
2266 struct undo_object *undo_entry;
2267
2268 while (undo_stack_tail) {
2269 undo_entry = undo_stack_tail;
2270 undo_stack_tail = undo_entry->prev;
2271 free(undo_entry);
2272 }
2273}
2274
2275// Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
2276static void undo_push(char *src, unsigned int length, uint8_t u_type) // Add to the undo stack
2277{
2278 struct undo_object *undo_entry;
2279
2280 // "u_type" values
2281 // UNDO_INS: insertion, undo will remove from buffer
2282 // UNDO_DEL: deleted text, undo will restore to buffer
2283 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
2284 // The CHAIN operations are for handling multiple operations that the user
2285 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
2286 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
2287 // for the INS/DEL operation. The raw values should be equal to the values of
2288 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
2289
2290#if ENABLE_FEATURE_VI_UNDO_QUEUE
2291 // This undo queuing functionality groups multiple character typing or backspaces
2292 // into a single large undo object. This greatly reduces calls to malloc() for
2293 // single-character operations while typing and has the side benefit of letting
2294 // an undo operation remove chunks of text rather than a single character.
2295 switch (u_type) {
2296 case UNDO_EMPTY: // Just in case this ever happens...
2297 return;
2298 case UNDO_DEL_QUEUED:
2299 if (length != 1)
2300 return; // Only queue single characters
2301 switch (undo_queue_state) {
2302 case UNDO_EMPTY:
2303 undo_queue_state = UNDO_DEL;
2304 case UNDO_DEL:
2305 undo_queue_spos = src;
2306 undo_q++;
2307 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
2308 // If queue is full, dump it into an object
2309 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2310 undo_queue_commit();
2311 return;
2312 case UNDO_INS:
2313 // Switch from storing inserted text to deleted text
2314 undo_queue_commit();
2315 undo_push(src, length, UNDO_DEL_QUEUED);
2316 return;
2317 }
2318 break;
2319 case UNDO_INS_QUEUED:
2320 if (length < 1)
2321 return;
2322 switch (undo_queue_state) {
2323 case UNDO_EMPTY:
2324 undo_queue_state = UNDO_INS;
2325 undo_queue_spos = src;
2326 case UNDO_INS:
2327 while (length--) {
2328 undo_q++; // Don't need to save any data for insertions
2329 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2330 undo_queue_commit();
2331 }
2332 return;
2333 case UNDO_DEL:
2334 // Switch from storing deleted text to inserted text
2335 undo_queue_commit();
2336 undo_push(src, length, UNDO_INS_QUEUED);
2337 return;
2338 }
2339 break;
2340 }
2341#else
2342 // If undo queuing is disabled, ignore the queuing flag entirely
2343 u_type = u_type & ~UNDO_QUEUED_FLAG;
2344#endif
2345
2346 // Allocate a new undo object
2347 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
2348 // For UNDO_DEL objects, save deleted text
2349 if ((text + length) == end)
2350 length--;
2351 // If this deletion empties text[], strip the newline. When the buffer becomes
2352 // zero-length, a newline is added back, which requires this to compensate.
2353 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
2354 memcpy(undo_entry->undo_text, src, length);
2355 } else {
2356 undo_entry = xzalloc(sizeof(*undo_entry));
2357 }
2358 undo_entry->length = length;
2359#if ENABLE_FEATURE_VI_UNDO_QUEUE
2360 if ((u_type & UNDO_USE_SPOS) != 0) {
2361 undo_entry->start = undo_queue_spos - text; // use start position from queue
2362 } else {
2363 undo_entry->start = src - text; // use offset from start of text buffer
2364 }
2365 u_type = (u_type & ~UNDO_USE_SPOS);
2366#else
2367 undo_entry->start = src - text;
2368#endif
2369 undo_entry->u_type = u_type;
2370
2371 // Push it on undo stack
2372 undo_entry->prev = undo_stack_tail;
2373 undo_stack_tail = undo_entry;
2374 modified_count++;
2375}
2376
2377static void undo_push_insert(char *p, int len, int undo)
2378{
2379 switch (undo) {
2380 case ALLOW_UNDO:
2381 undo_push(p, len, UNDO_INS);
2382 break;
2383 case ALLOW_UNDO_CHAIN:
2384 undo_push(p, len, UNDO_INS_CHAIN);
2385 break;
2386# if ENABLE_FEATURE_VI_UNDO_QUEUE
2387 case ALLOW_UNDO_QUEUED:
2388 undo_push(p, len, UNDO_INS_QUEUED);
2389 break;
2390# endif
2391 }
2392}
2393
2394static void undo_pop(void) // Undo the last operation
2395{
2396 int repeat;
2397 char *u_start, *u_end;
2398 struct undo_object *undo_entry;
2399
2400 // Commit pending undo queue before popping (should be unnecessary)
2401 undo_queue_commit();
2402
2403 undo_entry = undo_stack_tail;
2404 // Check for an empty undo stack
2405 if (!undo_entry) {
2406 status_line("Already at oldest change");
2407 return;
2408 }
2409
2410 switch (undo_entry->u_type) {
2411 case UNDO_DEL:
2412 case UNDO_DEL_CHAIN:
2413 // make hole and put in text that was deleted; deallocate text
2414 u_start = text + undo_entry->start;
2415 text_hole_make(u_start, undo_entry->length);
2416 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
2417 status_line("Undo [%d] %s %d chars at position %d",
2418 modified_count, "restored",
2419 undo_entry->length, undo_entry->start
2420 );
2421 break;
2422 case UNDO_INS:
2423 case UNDO_INS_CHAIN:
2424 // delete what was inserted
2425 u_start = undo_entry->start + text;
2426 u_end = u_start - 1 + undo_entry->length;
2427 text_hole_delete(u_start, u_end, NO_UNDO);
2428 status_line("Undo [%d] %s %d chars at position %d",
2429 modified_count, "deleted",
2430 undo_entry->length, undo_entry->start
2431 );
2432 break;
2433 }
2434 repeat = 0;
2435 switch (undo_entry->u_type) {
2436 // If this is the end of a chain, lower modification count and refresh display
2437 case UNDO_DEL:
2438 case UNDO_INS:
2439 dot = (text + undo_entry->start);
2440 refresh(FALSE);
2441 break;
2442 case UNDO_DEL_CHAIN:
2443 case UNDO_INS_CHAIN:
2444 repeat = 1;
2445 break;
2446 }
2447 // Deallocate the undo object we just processed
2448 undo_stack_tail = undo_entry->prev;
2449 free(undo_entry);
2450 modified_count--;
2451 // For chained operations, continue popping all the way down the chain.
2452 if (repeat) {
2453 undo_pop(); // Follow the undo chain if one exists
2454 }
2455}
2456
2457#if ENABLE_FEATURE_VI_UNDO_QUEUE
2458static void undo_queue_commit(void) // Flush any queued objects to the undo stack
2459{
2460 // Pushes the queue object onto the undo stack
2461 if (undo_q > 0) {
2462 // Deleted character undo events grow from the end
2463 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
2464 undo_q,
2465 (undo_queue_state | UNDO_USE_SPOS)
2466 );
2467 undo_queue_state = UNDO_EMPTY;
2468 undo_q = 0;
2469 }
2470}
2471#endif
2472
2473#endif /* ENABLE_FEATURE_VI_UNDO */
2474
2475// open a hole in text[]
2476// might reallocate text[]! use p += text_hole_make(p, ...),
2477// and be careful to not use pointers into potentially freed text[]!
2478static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2479{
2480 uintptr_t bias = 0;
2481
2482 if (size <= 0)
2483 return bias;
2484 end += size; // adjust the new END
2485 if (end >= (text + text_size)) {
2486 char *new_text;
2487 text_size += end - (text + text_size) + 10240;
2488 new_text = xrealloc(text, text_size);
2489 bias = (new_text - text);
2490 screenbegin += bias;
2491 dot += bias;
2492 end += bias;
2493 p += bias;
2494#if ENABLE_FEATURE_VI_YANKMARK
2495 {
2496 int i;
2497 for (i = 0; i < ARRAY_SIZE(mark); i++)
2498 if (mark[i])
2499 mark[i] += bias;
2500 }
2501#endif
2502 text = new_text;
2503 }
2504 memmove(p + size, p, end - size - p);
2505 memset(p, ' ', size); // clear new hole
2506 return bias;
2507}
2508
2509// close a hole in text[]
2510// "undo" value indicates if this operation should be undo-able
2511static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
2512{
2513 char *src, *dest;
2514 int cnt, hole_size;
2515
2516 // move forwards, from beginning
2517 // assume p <= q
2518 src = q + 1;
2519 dest = p;
2520 if (q < p) { // they are backward- swap them
2521 src = p + 1;
2522 dest = q;
2523 }
2524 hole_size = q - p + 1;
2525 cnt = end - src;
2526#if ENABLE_FEATURE_VI_UNDO
2527 switch (undo) {
2528 case NO_UNDO:
2529 break;
2530 case ALLOW_UNDO:
2531 undo_push(p, hole_size, UNDO_DEL);
2532 break;
2533 case ALLOW_UNDO_CHAIN:
2534 undo_push(p, hole_size, UNDO_DEL_CHAIN);
2535 break;
2536# if ENABLE_FEATURE_VI_UNDO_QUEUE
2537 case ALLOW_UNDO_QUEUED:
2538 undo_push(p, hole_size, UNDO_DEL_QUEUED);
2539 break;
2540# endif
2541 }
2542 modified_count--;
2543#endif
2544 if (src < text || src > end)
2545 goto thd0;
2546 if (dest < text || dest >= end)
2547 goto thd0;
2548 modified_count++;
2549 if (src >= end)
2550 goto thd_atend; // just delete the end of the buffer
2551 memmove(dest, src, cnt);
2552 thd_atend:
2553 end = end - hole_size; // adjust the new END
2554 if (dest >= end)
2555 dest = end - 1; // make sure dest in below end-1
2556 if (end <= text)
2557 dest = end = text; // keep pointers valid
2558 thd0:
2559 return dest;
2560}
2561
2562// copy text into register, then delete text.
2563// if dist <= 0, do not include, or go past, a NewLine
2564//
2565static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
2566{
2567 char *p;
2568
2569 // make sure start <= stop
2570 if (start > stop) {
2571 // they are backwards, reverse them
2572 p = start;
2573 start = stop;
2574 stop = p;
2575 }
2576 if (dist <= 0) {
2577 // we cannot cross NL boundaries
2578 p = start;
2579 if (*p == '\n')
2580 return p;
2581 // dont go past a NewLine
2582 for (; p + 1 <= stop; p++) {
2583 if (p[1] == '\n') {
2584 stop = p; // "stop" just before NewLine
2585 break;
2586 }
2587 }
2588 }
2589 p = start;
2590#if ENABLE_FEATURE_VI_YANKMARK
2591 text_yank(start, stop, YDreg);
2592#endif
2593 if (yf == YANKDEL) {
2594 p = text_hole_delete(start, stop, undo);
2595 } // delete lines
2596 return p;
2597}
2598
2599static void show_help(void)
2600{
2601 puts("These features are available:"
2602#if ENABLE_FEATURE_VI_SEARCH
2603 "\n\tPattern searches with / and ?"
2604#endif
2605#if ENABLE_FEATURE_VI_DOT_CMD
2606 "\n\tLast command repeat with ."
2607#endif
2608#if ENABLE_FEATURE_VI_YANKMARK
2609 "\n\tLine marking with 'x"
2610 "\n\tNamed buffers with \"x"
2611#endif
2612#if ENABLE_FEATURE_VI_READONLY
2613 //not implemented: "\n\tReadonly if vi is called as \"view\""
2614 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2615#endif
2616#if ENABLE_FEATURE_VI_SET
2617 "\n\tSome colon mode commands with :"
2618#endif
2619#if ENABLE_FEATURE_VI_SETOPTS
2620 "\n\tSettable options with \":set\""
2621#endif
2622#if ENABLE_FEATURE_VI_USE_SIGNALS
2623 "\n\tSignal catching- ^C"
2624 "\n\tJob suspend and resume with ^Z"
2625#endif
2626#if ENABLE_FEATURE_VI_WIN_RESIZE
2627 "\n\tAdapt to window re-sizes"
2628#endif
2629 );
2630}
2631
2632#if ENABLE_FEATURE_VI_DOT_CMD
2633static void start_new_cmd_q(char c)
2634{
2635 // get buffer for new cmd
2636 // if there is a current cmd count put it in the buffer first
2637 if (cmdcnt > 0) {
2638 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2639 } else { // just save char c onto queue
2640 last_modifying_cmd[0] = c;
2641 lmc_len = 1;
2642 }
2643 adding2q = 1;
2644}
2645
2646static void end_cmd_q(void)
2647{
2648#if ENABLE_FEATURE_VI_YANKMARK
2649 YDreg = 26; // go back to default Yank/Delete reg
2650#endif
2651 adding2q = 0;
2652}
2653#endif /* FEATURE_VI_DOT_CMD */
2654
2655#if ENABLE_FEATURE_VI_YANKMARK \
2656 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2657 || ENABLE_FEATURE_VI_CRASHME
2658// might reallocate text[]! use p += string_insert(p, ...),
2659// and be careful to not use pointers into potentially freed text[]!
2660static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2661{
2662 uintptr_t bias;
2663 int i;
2664
2665 i = strlen(s);
2666#if ENABLE_FEATURE_VI_UNDO
2667 undo_push_insert(p, i, undo);
2668#endif
2669 bias = text_hole_make(p, i);
2670 p += bias;
2671 memcpy(p, s, i);
2672#if ENABLE_FEATURE_VI_YANKMARK
2673 {
2674 int cnt;
2675 for (cnt = 0; *s != '\0'; s++) {
2676 if (*s == '\n')
2677 cnt++;
2678 }
2679 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2680 }
2681#endif
2682 return bias;
2683}
2684#endif
2685
2686#if ENABLE_FEATURE_VI_YANKMARK
2687static char *text_yank(char *p, char *q, int dest) // copy text into a register
2688{
2689 int cnt = q - p;
2690 if (cnt < 0) { // they are backwards- reverse them
2691 p = q;
2692 cnt = -cnt;
2693 }
2694 free(reg[dest]); // if already a yank register, free it
2695 reg[dest] = xstrndup(p, cnt + 1);
2696 return p;
2697}
2698
2699static char what_reg(void)
2700{
2701 char c;
2702
2703 c = 'D'; // default to D-reg
2704 if (0 <= YDreg && YDreg <= 25)
2705 c = 'a' + (char) YDreg;
2706 if (YDreg == 26)
2707 c = 'D';
2708 if (YDreg == 27)
2709 c = 'U';
2710 return c;
2711}
2712
2713static void check_context(char cmd)
2714{
2715 // A context is defined to be "modifying text"
2716 // Any modifying command establishes a new context.
2717
2718 if (dot < context_start || dot > context_end) {
2719 if (strchr(modifying_cmds, cmd) != NULL) {
2720 // we are trying to modify text[]- make this the current context
2721 mark[27] = mark[26]; // move cur to prev
2722 mark[26] = dot; // move local to cur
2723 context_start = prev_line(prev_line(dot));
2724 context_end = next_line(next_line(dot));
2725 //loiter= start_loiter= now;
2726 }
2727 }
2728}
2729
2730static char *swap_context(char *p) // goto new context for '' command make this the current context
2731{
2732 char *tmp;
2733
2734 // the current context is in mark[26]
2735 // the previous context is in mark[27]
2736 // only swap context if other context is valid
2737 if (text <= mark[27] && mark[27] <= end - 1) {
2738 tmp = mark[27];
2739 mark[27] = p;
2740 mark[26] = p = tmp;
2741 context_start = prev_line(prev_line(prev_line(p)));
2742 context_end = next_line(next_line(next_line(p)));
2743 }
2744 return p;
2745}
2746#endif /* FEATURE_VI_YANKMARK */
2747
2748//----- Set terminal attributes --------------------------------
2749static void rawmode(void)
2750{
2751#if !ENABLE_PLATFORM_MINGW32
2752 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
2753 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
2754 erase_char = term_orig.c_cc[VERASE];
2755#endif
2756}
2757
2758static void cookmode(void)
2759{
2760 fflush_all();
2761#if !ENABLE_PLATFORM_MINGW32
2762 tcsetattr_stdin_TCSANOW(&term_orig);
2763#endif
2764}
2765
2766#if ENABLE_FEATURE_VI_USE_SIGNALS
2767//----- Come here when we get a window resize signal ---------
2768static void winch_sig(int sig UNUSED_PARAM)
2769{
2770 int save_errno = errno;
2771 // FIXME: do it in main loop!!!
2772 signal(SIGWINCH, winch_sig);
2773 query_screen_dimensions();
2774 new_screen(rows, columns); // get memory for virtual screen
2775 redraw(TRUE); // re-draw the screen
2776 errno = save_errno;
2777}
2778
2779//----- Come here when we get a continue signal -------------------
2780static void cont_sig(int sig UNUSED_PARAM)
2781{
2782 int save_errno = errno;
2783 rawmode(); // terminal to "raw"
2784 last_status_cksum = 0; // force status update
2785 redraw(TRUE); // re-draw the screen
2786
2787 signal(SIGTSTP, suspend_sig);
2788 signal(SIGCONT, SIG_DFL);
2789 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2790 errno = save_errno;
2791}
2792
2793//----- Come here when we get a Suspend signal -------------------
2794static void suspend_sig(int sig UNUSED_PARAM)
2795{
2796 int save_errno = errno;
2797 go_bottom_and_clear_to_eol();
2798 cookmode(); // terminal to "cooked"
2799
2800 signal(SIGCONT, cont_sig);
2801 signal(SIGTSTP, SIG_DFL);
2802 kill(my_pid, SIGTSTP);
2803 errno = save_errno;
2804}
2805
2806//----- Come here when we get a signal ---------------------------
2807static void catch_sig(int sig)
2808{
2809 signal(SIGINT, catch_sig);
2810 siglongjmp(restart, sig);
2811}
2812#endif /* FEATURE_VI_USE_SIGNALS */
2813
2814static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2815{
2816#if ENABLE_PLATFORM_MINGW32
2817 HANDLE h = GetStdHandle(STD_INPUT_HANDLE);
2818 DWORD ret;
2819
2820 if (hund == 0) {
2821 /* Allow two events in the queue. Otherwise pasted test isn't
2822 * displayed because there's still a key release event waiting
2823 * after the last character is processed. */
2824 DWORD nevent_out;
2825
2826 ret = GetNumberOfConsoleInputEvents(h, &nevent_out);
2827 return ret != 0 ? (nevent_out > 2) : 0;
2828 }
2829 fflush_all();
2830 ret = WaitForSingleObject(h, hund*10);
2831 return ret != WAIT_TIMEOUT;
2832#else
2833 struct pollfd pfd[1];
2834
2835 if (hund != 0)
2836 fflush_all();
2837
2838 pfd[0].fd = STDIN_FILENO;
2839 pfd[0].events = POLLIN;
2840 return safe_poll(pfd, 1, hund*10) > 0;
2841#endif
2842}
2843
2844//----- IO Routines --------------------------------------------
2845static int readit(void) // read (maybe cursor) key from stdin
2846{
2847 int c;
2848
2849 fflush_all();
2850
2851 // Wait for input. TIMEOUT = -1 makes read_key wait even
2852 // on nonblocking stdin.
2853 // Note: read_key sets errno to 0 on success.
2854 again:
2855 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
2856 if (c == -1) { // EOF/error
2857 if (errno == EAGAIN) // paranoia
2858 goto again;
2859 go_bottom_and_clear_to_eol();
2860 cookmode(); // terminal to "cooked"
2861 bb_error_msg_and_die("can't read user input");
2862 }
2863 return c;
2864}
2865
2866//----- IO Routines --------------------------------------------
2867static int get_one_char(void)
2868{
2869 int c;
2870
2871#if ENABLE_FEATURE_VI_DOT_CMD
2872 if (!adding2q) {
2873 // we are not adding to the q.
2874 // but, we may be reading from a q
2875 if (ioq == 0) {
2876 // there is no current q, read from STDIN
2877 c = readit(); // get the users input
2878 } else {
2879 // there is a queue to get chars from first
2880 // careful with correct sign expansion!
2881 c = (unsigned char)*ioq++;
2882 if (c == '\0') {
2883 // the end of the q, read from STDIN
2884 free(ioq_start);
2885 ioq_start = ioq = 0;
2886 c = readit(); // get the users input
2887 }
2888 }
2889 } else {
2890 // adding STDIN chars to q
2891 c = readit(); // get the users input
2892 if (lmc_len >= MAX_INPUT_LEN - 1) {
2893 status_line_bold("last_modifying_cmd overrun");
2894 } else {
2895 // add new char to q
2896 last_modifying_cmd[lmc_len++] = c;
2897 }
2898 }
2899#else
2900 c = readit(); // get the users input
2901#endif /* FEATURE_VI_DOT_CMD */
2902 return c;
2903}
2904
2905// Get input line (uses "status line" area)
2906static char *get_input_line(const char *prompt)
2907{
2908 // char [MAX_INPUT_LEN]
2909#define buf get_input_line__buf
2910
2911 int c;
2912 int i;
2913
2914 strcpy(buf, prompt);
2915 last_status_cksum = 0; // force status update
2916 go_bottom_and_clear_to_eol();
2917 write1(prompt); // write out the :, /, or ? prompt
2918
2919 i = strlen(buf);
2920 while (i < MAX_INPUT_LEN) {
2921 c = get_one_char();
2922 if (c == '\n' || c == '\r' || c == 27)
2923 break; // this is end of input
2924 if (c == erase_char || c == 8 || c == 127) {
2925 // user wants to erase prev char
2926 buf[--i] = '\0';
2927 write1("\b \b"); // erase char on screen
2928 if (i <= 0) // user backs up before b-o-l, exit
2929 break;
2930 } else if (c > 0 && c < 256) { // exclude Unicode
2931 // (TODO: need to handle Unicode)
2932 buf[i] = c;
2933 buf[++i] = '\0';
2934 bb_putchar(c);
2935 }
2936 }
2937 refresh(FALSE);
2938 return buf;
2939#undef buf
2940}
2941
2942#if ENABLE_PLATFORM_MINGW32
2943static int count_cr(char *p, int len)
2944{
2945 int i, cnt;
2946
2947 for (i = cnt = 0; i < len; ++i)
2948 if (p[i] == '\n')
2949 ++cnt;
2950 return cnt;
2951}
2952#endif
2953
2954// might reallocate text[]!
2955static int file_insert(const char *fn, char *p, int initial)
2956{
2957 int cnt = -1;
2958 int fd, size;
2959 struct stat statbuf;
2960
2961 if (p < text)
2962 p = text;
2963 if (p > end)
2964 p = end;
2965
2966#if !ENABLE_PLATFORM_MINGW32
2967 fd = open(fn, O_RDONLY);
2968#else
2969 fd = open(fn, O_RDONLY | _O_TEXT);
2970#endif
2971 if (fd < 0) {
2972 if (!initial)
2973 status_line_bold_errno(fn);
2974 return cnt;
2975 }
2976
2977 /* Validate file */
2978 if (fstat(fd, &statbuf) < 0) {
2979 status_line_bold_errno(fn);
2980 goto fi;
2981 }
2982 if (!S_ISREG(statbuf.st_mode)) {
2983 status_line_bold("'%s' is not a regular file", fn);
2984 goto fi;
2985 }
2986 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2987 p += text_hole_make(p, size);
2988 cnt = full_read(fd, p, size);
2989 if (cnt < 0) {
2990 status_line_bold_errno(fn);
2991 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2992 } else if (cnt < size) {
2993#if ENABLE_PLATFORM_MINGW32
2994 // On WIN32 a partial read might just mean CRs have been removed
2995 int cnt_cr = cnt + count_cr(p, cnt);
2996#endif
2997 // There was a partial read, shrink unused space
2998 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2999#if ENABLE_PLATFORM_MINGW32
3000 if (cnt_cr < size)
3001#endif
3002 status_line_bold("can't read '%s'", fn);
3003 }
3004 fi:
3005 close(fd);
3006
3007#if ENABLE_FEATURE_VI_READONLY
3008 if (initial
3009 && ((access(fn, W_OK) < 0) ||
3010 /* root will always have access()
3011 * so we check fileperms too */
3012 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
3013 )
3014 ) {
3015 SET_READONLY_FILE(readonly_mode);
3016 }
3017#endif
3018 return cnt;
3019}
3020
3021static int file_write(char *fn, char *first, char *last)
3022{
3023 int fd, cnt, charcnt;
3024
3025 if (fn == 0) {
3026 status_line_bold("No current filename");
3027 return -2;
3028 }
3029 /* By popular request we do not open file with O_TRUNC,
3030 * but instead ftruncate() it _after_ successful write.
3031 * Might reduce amount of data lost on power fail etc.
3032 */
3033#if !ENABLE_PLATFORM_MINGW32
3034 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
3035#else
3036 fd = open(fn, (O_WRONLY | O_CREAT | _O_TEXT), 0666);
3037#endif
3038 if (fd < 0)
3039 return -1;
3040 cnt = last - first + 1;
3041 charcnt = full_write(fd, first, cnt);
3042#if !ENABLE_PLATFORM_MINGW32
3043 ftruncate(fd, charcnt);
3044#else
3045 /* File was written in text mode; this makes it bigger so adjust
3046 * the truncation to match. */
3047 ftruncate(fd, charcnt + count_cr(first, cnt));
3048#endif
3049 if (charcnt == cnt) {
3050 // good write
3051 //modified_count = FALSE;
3052 } else {
3053 charcnt = 0;
3054 }
3055 close(fd);
3056 return charcnt;
3057}
3058
3059//----- Terminal Drawing ---------------------------------------
3060// The terminal is made up of 'rows' line of 'columns' columns.
3061// classically this would be 24 x 80.
3062// screen coordinates
3063// 0,0 ... 0,79
3064// 1,0 ... 1,79
3065// . ... .
3066// . ... .
3067// 22,0 ... 22,79
3068// 23,0 ... 23,79 <- status line
3069
3070//----- Move the cursor to row x col (count from 0, not 1) -------
3071static void place_cursor(int row, int col)
3072{
3073 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
3074
3075 if (row < 0) row = 0;
3076 if (row >= rows) row = rows - 1;
3077 if (col < 0) col = 0;
3078 if (col >= columns) col = columns - 1;
3079
3080 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
3081 write1(cm1);
3082}
3083
3084//----- Erase from cursor to end of line -----------------------
3085static void clear_to_eol(void)
3086{
3087 write1(ESC_CLEAR2EOL);
3088}
3089
3090static void go_bottom_and_clear_to_eol(void)
3091{
3092 place_cursor(rows - 1, 0);
3093 clear_to_eol();
3094}
3095
3096//----- Erase from cursor to end of screen -----------------------
3097static void clear_to_eos(void)
3098{
3099#if !ENABLE_PLATFORM_MINGW32
3100 write1(ESC_CLEAR2EOS);
3101#else
3102 /* in practice clear_to_eos() always clears the entire screen */
3103 reset_screen();
3104#endif
3105}
3106
3107//----- Start standout mode ------------------------------------
3108static void standout_start(void)
3109{
3110 write1(ESC_BOLD_TEXT);
3111}
3112
3113//----- End standout mode --------------------------------------
3114static void standout_end(void)
3115{
3116 write1(ESC_NORM_TEXT);
3117}
3118
3119//----- Flash the screen --------------------------------------
3120static void flash(int h)
3121{
3122 standout_start();
3123 redraw(TRUE);
3124 mysleep(h);
3125 standout_end();
3126 redraw(TRUE);
3127}
3128
3129static void indicate_error(void)
3130{
3131#if ENABLE_FEATURE_VI_CRASHME
3132 if (crashme > 0)
3133 return; // generate a random command
3134#endif
3135 if (!err_method) {
3136 write1(ESC_BELL);
3137 } else {
3138 flash(10);
3139 }
3140}
3141
3142//----- Screen[] Routines --------------------------------------
3143//----- Erase the Screen[] memory ------------------------------
3144static void screen_erase(void)
3145{
3146 memset(screen, ' ', screensize); // clear new screen
3147}
3148
3149static int bufsum(char *buf, int count)
3150{
3151 int sum = 0;
3152 char *e = buf + count;
3153
3154 while (buf < e)
3155 sum += (unsigned char) *buf++;
3156 return sum;
3157}
3158
3159//----- Draw the status line at bottom of the screen -------------
3160static void show_status_line(void)
3161{
3162 int cnt = 0, cksum = 0;
3163
3164 // either we already have an error or status message, or we
3165 // create one.
3166 if (!have_status_msg) {
3167 cnt = format_edit_status();
3168 cksum = bufsum(status_buffer, cnt);
3169 }
3170 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
3171 last_status_cksum = cksum; // remember if we have seen this line
3172 go_bottom_and_clear_to_eol();
3173 write1(status_buffer);
3174 if (have_status_msg) {
3175 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
3176 (columns - 1) ) {
3177 have_status_msg = 0;
3178 Hit_Return();
3179 }
3180 have_status_msg = 0;
3181 }
3182 place_cursor(crow, ccol); // put cursor back in correct place
3183 }
3184 fflush_all();
3185}
3186
3187//----- format the status buffer, the bottom line of screen ------
3188// format status buffer, with STANDOUT mode
3189static void status_line_bold(const char *format, ...)
3190{
3191 va_list args;
3192
3193 va_start(args, format);
3194 strcpy(status_buffer, ESC_BOLD_TEXT);
3195 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
3196 strcat(status_buffer, ESC_NORM_TEXT);
3197 va_end(args);
3198
3199 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
3200}
3201
3202static void status_line_bold_errno(const char *fn)
3203{
3204 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
3205}
3206
3207// format status buffer
3208static void status_line(const char *format, ...)
3209{
3210 va_list args;
3211
3212 va_start(args, format);
3213 vsprintf(status_buffer, format, args);
3214 va_end(args);
3215
3216 have_status_msg = 1;
3217}
3218
3219// copy s to buf, convert unprintable
3220static void print_literal(char *buf, const char *s)
3221{
3222 char *d;
3223 unsigned char c;
3224
3225 buf[0] = '\0';
3226 if (!s[0])
3227 s = "(NULL)";
3228
3229 d = buf;
3230 for (; *s; s++) {
3231 int c_is_no_print;
3232
3233 c = *s;
3234 c_is_no_print = (c & 0x80) && !Isprint(c);
3235 if (c_is_no_print) {
3236 strcpy(d, ESC_NORM_TEXT);
3237 d += sizeof(ESC_NORM_TEXT)-1;
3238 c = '.';
3239 }
3240 if (c < ' ' || c == 0x7f) {
3241 *d++ = '^';
3242 c |= '@'; /* 0x40 */
3243 if (c == 0x7f)
3244 c = '?';
3245 }
3246 *d++ = c;
3247 *d = '\0';
3248 if (c_is_no_print) {
3249 strcpy(d, ESC_BOLD_TEXT);
3250 d += sizeof(ESC_BOLD_TEXT)-1;
3251 }
3252 if (*s == '\n') {
3253 *d++ = '$';
3254 *d = '\0';
3255 }
3256 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
3257 break;
3258 }
3259}
3260
3261static void not_implemented(const char *s)
3262{
3263 char buf[MAX_INPUT_LEN];
3264
3265 print_literal(buf, s);
3266 status_line_bold("\'%s\' is not implemented", buf);
3267}
3268
3269// show file status on status line
3270static int format_edit_status(void)
3271{
3272 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
3273
3274#define tot format_edit_status__tot
3275
3276 int cur, percent, ret, trunc_at;
3277
3278 // modified_count is now a counter rather than a flag. this
3279 // helps reduce the amount of line counting we need to do.
3280 // (this will cause a mis-reporting of modified status
3281 // once every MAXINT editing operations.)
3282
3283 // it would be nice to do a similar optimization here -- if
3284 // we haven't done a motion that could have changed which line
3285 // we're on, then we shouldn't have to do this count_lines()
3286 cur = count_lines(text, dot);
3287
3288 // count_lines() is expensive.
3289 // Call it only if something was changed since last time
3290 // we were here:
3291 if (modified_count != last_modified_count) {
3292 tot = cur + count_lines(dot, end - 1) - 1;
3293 last_modified_count = modified_count;
3294 }
3295
3296 // current line percent
3297 // ------------- ~~ ----------
3298 // total lines 100
3299 if (tot > 0) {
3300 percent = (100 * cur) / tot;
3301 } else {
3302 cur = tot = 0;
3303 percent = 100;
3304 }
3305
3306 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
3307 columns : STATUS_BUFFER_LEN-1;
3308
3309 ret = snprintf(status_buffer, trunc_at+1,
3310#if ENABLE_FEATURE_VI_READONLY
3311 "%c %s%s%s %d/%d %d%%",
3312#else
3313 "%c %s%s %d/%d %d%%",
3314#endif
3315 cmd_mode_indicator[cmd_mode & 3],
3316 (current_filename != NULL ? current_filename : "No file"),
3317#if ENABLE_FEATURE_VI_READONLY
3318 (readonly_mode ? " [Readonly]" : ""),
3319#endif
3320 (modified_count ? " [Modified]" : ""),
3321 cur, tot, percent);
3322
3323 if (ret >= 0 && ret < trunc_at)
3324 return ret; /* it all fit */
3325
3326 return trunc_at; /* had to truncate */
3327#undef tot
3328}
3329
3330//----- Force refresh of all Lines -----------------------------
3331static void redraw(int full_screen)
3332{
3333 place_cursor(0, 0);
3334 clear_to_eos();
3335 screen_erase(); // erase the internal screen buffer
3336 last_status_cksum = 0; // force status update
3337 refresh(full_screen); // this will redraw the entire display
3338 show_status_line();
3339}
3340
3341//----- Format a text[] line into a buffer ---------------------
3342static char* format_line(char *src /*, int li*/)
3343{
3344 unsigned char c;
3345 int co;
3346 int ofs = offset;
3347 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
3348
3349 c = '~'; // char in col 0 in non-existent lines is '~'
3350 co = 0;
3351 while (co < columns + tabstop) {
3352 // have we gone past the end?
3353 if (src < end) {
3354 c = *src++;
3355 if (c == '\n')
3356 break;
3357 if ((c & 0x80) && !Isprint(c)) {
3358 c = '.';
3359 }
3360 if (c < ' ' || c == 0x7f) {
3361 if (c == '\t') {
3362 c = ' ';
3363 // co % 8 != 7
3364 while ((co % tabstop) != (tabstop - 1)) {
3365 dest[co++] = c;
3366 }
3367 } else {
3368 dest[co++] = '^';
3369 if (c == 0x7f)
3370 c = '?';
3371 else
3372 c += '@'; // Ctrl-X -> 'X'
3373 }
3374 }
3375 }
3376 dest[co++] = c;
3377 // discard scrolled-off-to-the-left portion,
3378 // in tabstop-sized pieces
3379 if (ofs >= tabstop && co >= tabstop) {
3380 memmove(dest, dest + tabstop, co);
3381 co -= tabstop;
3382 ofs -= tabstop;
3383 }
3384 if (src >= end)
3385 break;
3386 }
3387 // check "short line, gigantic offset" case
3388 if (co < ofs)
3389 ofs = co;
3390 // discard last scrolled off part
3391 co -= ofs;
3392 dest += ofs;
3393 // fill the rest with spaces
3394 if (co < columns)
3395 memset(&dest[co], ' ', columns - co);
3396 return dest;
3397}
3398
3399//----- Refresh the changed screen lines -----------------------
3400// Copy the source line from text[] into the buffer and note
3401// if the current screenline is different from the new buffer.
3402// If they differ then that line needs redrawing on the terminal.
3403//
3404static void refresh(int full_screen)
3405{
3406#define old_offset refresh__old_offset
3407
3408 int li, changed;
3409 char *tp, *sp; // pointer into text[] and screen[]
3410
3411 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
3412 unsigned c = columns, r = rows;
3413 query_screen_dimensions();
3414#if ENABLE_FEATURE_VI_USE_SIGNALS
3415 full_screen |= (c - columns) | (r - rows);
3416#else
3417 if (c != columns || r != rows) {
3418 full_screen = TRUE;
3419 /* update screen memory since SIGWINCH won't have done it */
3420 new_screen(rows, columns);
3421 }
3422#endif
3423 }
3424 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3425 tp = screenbegin; // index into text[] of top line
3426
3427 // compare text[] to screen[] and mark screen[] lines that need updating
3428 for (li = 0; li < rows - 1; li++) {
3429 int cs, ce; // column start & end
3430 char *out_buf;
3431 // format current text line
3432 out_buf = format_line(tp /*, li*/);
3433
3434 // skip to the end of the current text[] line
3435 if (tp < end) {
3436 char *t = memchr(tp, '\n', end - tp);
3437 if (!t) t = end - 1;
3438 tp = t + 1;
3439 }
3440
3441 // see if there are any changes between virtual screen and out_buf
3442 changed = FALSE; // assume no change
3443 cs = 0;
3444 ce = columns - 1;
3445 sp = &screen[li * columns]; // start of screen line
3446 if (full_screen) {
3447 // force re-draw of every single column from 0 - columns-1
3448 goto re0;
3449 }
3450 // compare newly formatted buffer with virtual screen
3451 // look forward for first difference between buf and screen
3452 for (; cs <= ce; cs++) {
3453 if (out_buf[cs] != sp[cs]) {
3454 changed = TRUE; // mark for redraw
3455 break;
3456 }
3457 }
3458
3459 // look backward for last difference between out_buf and screen
3460 for (; ce >= cs; ce--) {
3461 if (out_buf[ce] != sp[ce]) {
3462 changed = TRUE; // mark for redraw
3463 break;
3464 }
3465 }
3466 // now, cs is index of first diff, and ce is index of last diff
3467
3468 // if horz offset has changed, force a redraw
3469 if (offset != old_offset) {
3470 re0:
3471 changed = TRUE;
3472 }
3473
3474 // make a sanity check of columns indexes
3475 if (cs < 0) cs = 0;
3476 if (ce > columns - 1) ce = columns - 1;
3477 if (cs > ce) { cs = 0; ce = columns - 1; }
3478 // is there a change between virtual screen and out_buf
3479 if (changed) {
3480 // copy changed part of buffer to virtual screen
3481 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3482 place_cursor(li, cs);
3483 // write line out to terminal
3484 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3485 }
3486 }
3487
3488 place_cursor(crow, ccol);
3489
3490 old_offset = offset;
3491#undef old_offset
3492}
3493
3494//--------------------------------------------------------------------- 3145//---------------------------------------------------------------------
3495//----- the Ascii Chart ----------------------------------------------- 3146//----- the Ascii Chart -----------------------------------------------
3496//
3497// 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel 3147// 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3498// 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si 3148// 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3499// 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb 3149// 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
@@ -3528,7 +3178,7 @@ static void do_cmd(int c)
3528 3178
3529 show_status_line(); 3179 show_status_line();
3530 3180
3531 /* if this is a cursor key, skip these checks */ 3181 // if this is a cursor key, skip these checks
3532 switch (c) { 3182 switch (c) {
3533 case KEYCODE_UP: 3183 case KEYCODE_UP:
3534 case KEYCODE_DOWN: 3184 case KEYCODE_DOWN:
@@ -3561,7 +3211,7 @@ static void do_cmd(int c)
3561 } 3211 }
3562 } 3212 }
3563 if (cmd_mode == 1) { 3213 if (cmd_mode == 1) {
3564 // hitting "Insert" twice means "R" replace mode 3214 // hitting "Insert" twice means "R" replace mode
3565 if (c == KEYCODE_INSERT) goto dc5; 3215 if (c == KEYCODE_INSERT) goto dc5;
3566 // insert the char c at "dot" 3216 // insert the char c at "dot"
3567 if (1 <= c || Isprint(c)) { 3217 if (1 <= c || Isprint(c)) {
@@ -3664,7 +3314,7 @@ static void do_cmd(int c)
3664 dot_skip_over_ws(); 3314 dot_skip_over_ws();
3665 } while (--cmdcnt > 0); 3315 } while (--cmdcnt > 0);
3666 break; 3316 break;
3667 case 21: // ctrl-U scroll up half screen 3317 case 21: // ctrl-U scroll up half screen
3668 dot_scroll((rows - 2) / 2, -1); 3318 dot_scroll((rows - 2) / 2, -1);
3669 break; 3319 break;
3670 case 25: // ctrl-Y scroll up one line 3320 case 25: // ctrl-Y scroll up one line
@@ -3673,7 +3323,7 @@ static void do_cmd(int c)
3673 case 27: // esc 3323 case 27: // esc
3674 if (cmd_mode == 0) 3324 if (cmd_mode == 0)
3675 indicate_error(); 3325 indicate_error();
3676 cmd_mode = 0; // stop insrting 3326 cmd_mode = 0; // stop inserting
3677 undo_queue_commit(); 3327 undo_queue_commit();
3678 end_cmd_q(); 3328 end_cmd_q();
3679 last_status_cksum = 0; // force status update 3329 last_status_cksum = 0; // force status update
@@ -3835,9 +3485,8 @@ static void do_cmd(int c)
3835 case '.': // .- repeat the last modifying command 3485 case '.': // .- repeat the last modifying command
3836 // Stuff the last_modifying_cmd back into stdin 3486 // Stuff the last_modifying_cmd back into stdin
3837 // and let it be re-executed. 3487 // and let it be re-executed.
3838 if (lmc_len > 0) { 3488 if (lmc_len != 0) {
3839 last_modifying_cmd[lmc_len] = 0; 3489 ioq = ioq_start = xstrndup(last_modifying_cmd, lmc_len);
3840 ioq = ioq_start = xstrdup(last_modifying_cmd);
3841 } 3490 }
3842 break; 3491 break;
3843#endif 3492#endif
@@ -4020,7 +3669,7 @@ static void do_cmd(int c)
4020 } 3669 }
4021 if (cmdcnt == 0) 3670 if (cmdcnt == 0)
4022 cmdcnt = 1; 3671 cmdcnt = 1;
4023 /* fall through */ 3672 // fall through
4024 case 'G': // G- goto to a line number (default= E-O-F) 3673 case 'G': // G- goto to a line number (default= E-O-F)
4025 dot = end - 1; // assume E-O-F 3674 dot = end - 1; // assume E-O-F
4026 if (cmdcnt > 0) { 3675 if (cmdcnt > 0) {
@@ -4371,7 +4020,7 @@ static void do_cmd(int c)
4371 dot--; 4020 dot--;
4372} 4021}
4373 4022
4374/* NB! the CRASHME code is unmaintained, and doesn't currently build */ 4023// NB! the CRASHME code is unmaintained, and doesn't currently build
4375#if ENABLE_FEATURE_VI_CRASHME 4024#if ENABLE_FEATURE_VI_CRASHME
4376static int totalcmds = 0; 4025static int totalcmds = 0;
4377static int Mp = 85; // Movement command Probability 4026static int Mp = 85; // Movement command Probability
@@ -4576,3 +4225,241 @@ static void crash_test()
4576 } 4225 }
4577} 4226}
4578#endif 4227#endif
4228
4229static void edit_file(char *fn)
4230{
4231#if ENABLE_FEATURE_VI_YANKMARK
4232#define cur_line edit_file__cur_line
4233#endif
4234 int c;
4235#if ENABLE_FEATURE_VI_USE_SIGNALS
4236 int sig;
4237#endif
4238
4239 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
4240 rawmode();
4241 rows = 24;
4242 columns = 80;
4243 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
4244#if ENABLE_FEATURE_VI_ASK_TERMINAL
4245 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
4246 uint64_t k;
4247 write1(ESC"[999;999H" ESC"[6n");
4248 fflush_all();
4249 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
4250 if ((int32_t)k == KEYCODE_CURSOR_POS) {
4251 uint32_t rc = (k >> 32);
4252 columns = (rc & 0x7fff);
4253 if (columns > MAX_SCR_COLS)
4254 columns = MAX_SCR_COLS;
4255 rows = ((rc >> 16) & 0x7fff);
4256 if (rows > MAX_SCR_ROWS)
4257 rows = MAX_SCR_ROWS;
4258 }
4259 }
4260#endif
4261 new_screen(rows, columns); // get memory for virtual screen
4262 init_text_buffer(fn);
4263
4264#if ENABLE_FEATURE_VI_YANKMARK
4265 YDreg = 26; // default Yank/Delete reg
4266// Ureg = 27; - const // hold orig line for "U" cmd
4267 mark[26] = mark[27] = text; // init "previous context"
4268#endif
4269
4270 last_forward_char = '\0';
4271#if ENABLE_FEATURE_VI_CRASHME
4272 last_input_char = '\0';
4273#endif
4274 crow = 0;
4275 ccol = 0;
4276
4277#if ENABLE_FEATURE_VI_USE_SIGNALS
4278 signal(SIGWINCH, winch_handler);
4279 signal(SIGTSTP, tstp_handler);
4280 sig = sigsetjmp(restart, 1);
4281 if (sig != 0) {
4282 screenbegin = dot = text;
4283 }
4284 // int_handler() can jump to "restart",
4285 // must install handler *after* initializing "restart"
4286 signal(SIGINT, int_handler);
4287#endif
4288
4289 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
4290 cmdcnt = 0;
4291 tabstop = 8;
4292 offset = 0; // no horizontal offset
4293 c = '\0';
4294#if ENABLE_FEATURE_VI_DOT_CMD
4295 free(ioq_start);
4296 ioq_start = NULL;
4297 lmc_len = 0;
4298 adding2q = 0;
4299#endif
4300
4301#if ENABLE_FEATURE_VI_COLON
4302 {
4303 char *p, *q;
4304 int n = 0;
4305
4306 while ((p = initial_cmds[n]) != NULL) {
4307 do {
4308 q = p;
4309 p = strchr(q, '\n');
4310 if (p)
4311 while (*p == '\n')
4312 *p++ = '\0';
4313 if (*q)
4314 colon(q);
4315 } while (p);
4316 free(initial_cmds[n]);
4317 initial_cmds[n] = NULL;
4318 n++;
4319 }
4320 }
4321#endif
4322 redraw(FALSE); // dont force every col re-draw
4323 //------This is the main Vi cmd handling loop -----------------------
4324 while (editing > 0) {
4325#if ENABLE_FEATURE_VI_CRASHME
4326 if (crashme > 0) {
4327 if ((end - text) > 1) {
4328 crash_dummy(); // generate a random command
4329 } else {
4330 crashme = 0;
4331 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO);
4332 dot = text;
4333 refresh(FALSE);
4334 }
4335 }
4336#endif
4337 c = get_one_char(); // get a cmd from user
4338#if ENABLE_FEATURE_VI_CRASHME
4339 last_input_char = c;
4340#endif
4341#if ENABLE_FEATURE_VI_YANKMARK
4342 // save a copy of the current line- for the 'U" command
4343 if (begin_line(dot) != cur_line) {
4344 cur_line = begin_line(dot);
4345 text_yank(begin_line(dot), end_line(dot), Ureg);
4346 }
4347#endif
4348#if ENABLE_FEATURE_VI_DOT_CMD
4349 // If c is a command that changes text[],
4350 // (re)start remembering the input for the "." command.
4351 if (!adding2q
4352 && ioq_start == NULL
4353 && cmd_mode == 0 // command mode
4354 && c > '\0' // exclude NUL and non-ASCII chars
4355 && c < 0x7f // (Unicode and such)
4356 && strchr(modifying_cmds, c)
4357 ) {
4358 start_new_cmd_q(c);
4359 }
4360#endif
4361 do_cmd(c); // execute the user command
4362
4363 // poll to see if there is input already waiting. if we are
4364 // not able to display output fast enough to keep up, skip
4365 // the display update until we catch up with input.
4366 if (!readbuffer[0] && mysleep(0) == 0) {
4367 // no input pending - so update output
4368 refresh(FALSE);
4369 show_status_line();
4370 }
4371#if ENABLE_FEATURE_VI_CRASHME
4372 if (crashme > 0)
4373 crash_test(); // test editor variables
4374#endif
4375 }
4376 //-------------------------------------------------------------------
4377
4378 go_bottom_and_clear_to_eol();
4379 cookmode();
4380#undef cur_line
4381}
4382
4383int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
4384int vi_main(int argc, char **argv)
4385{
4386 int c;
4387
4388 INIT_G();
4389
4390#if ENABLE_FEATURE_VI_UNDO
4391 //undo_stack_tail = NULL; - already is
4392# if ENABLE_FEATURE_VI_UNDO_QUEUE
4393 undo_queue_state = UNDO_EMPTY;
4394 //undo_q = 0; - already is
4395# endif
4396#endif
4397
4398#if ENABLE_FEATURE_VI_CRASHME
4399 srand((long) getpid());
4400#endif
4401#ifdef NO_SUCH_APPLET_YET
4402 // if we aren't "vi", we are "view"
4403 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
4404 SET_READONLY_MODE(readonly_mode);
4405 }
4406#endif
4407
4408 // autoindent is not default in vim 7.3
4409 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
4410 // 1- process $HOME/.exrc file (not inplemented yet)
4411 // 2- process EXINIT variable from environment
4412 // 3- process command line args
4413#if ENABLE_FEATURE_VI_COLON
4414 {
4415 char *p = getenv("EXINIT");
4416 if (p && *p)
4417 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
4418 }
4419#endif
4420 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
4421 switch (c) {
4422#if ENABLE_FEATURE_VI_CRASHME
4423 case 'C':
4424 crashme = 1;
4425 break;
4426#endif
4427#if ENABLE_FEATURE_VI_READONLY
4428 case 'R': // Read-only flag
4429 SET_READONLY_MODE(readonly_mode);
4430 break;
4431#endif
4432#if ENABLE_FEATURE_VI_COLON
4433 case 'c': // cmd line vi command
4434 if (*optarg)
4435 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
4436 break;
4437#endif
4438 case 'H':
4439 show_help();
4440 // fall through
4441 default:
4442 bb_show_usage();
4443 return 1;
4444 }
4445 }
4446
4447 argv += optind;
4448 cmdline_filecnt = argc - optind;
4449
4450 // "Save cursor, use alternate screen buffer, clear screen"
4451 write1(ESC"[?1049h");
4452 // This is the main file handling loop
4453 optind = 0;
4454 while (1) {
4455 edit_file(argv[optind]); // might be NULL on 1st iteration
4456 // NB: optind can be changed by ":next" and ":rewind" commands
4457 optind++;
4458 if (optind >= cmdline_filecnt)
4459 break;
4460 }
4461 // "Use normal screen buffer, restore cursor"
4462 write1(ESC"[?1049l");
4463
4464 return 0;
4465}
diff --git a/shell/ash.c b/shell/ash.c
index 6bc1dba24..c4f0880c8 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -258,6 +258,7 @@
258#define BASH_XTRACEFD ENABLE_ASH_BASH_COMPAT 258#define BASH_XTRACEFD ENABLE_ASH_BASH_COMPAT
259#define BASH_READ_D ENABLE_ASH_BASH_COMPAT 259#define BASH_READ_D ENABLE_ASH_BASH_COMPAT
260#define IF_BASH_READ_D IF_ASH_BASH_COMPAT 260#define IF_BASH_READ_D IF_ASH_BASH_COMPAT
261#define BASH_WAIT_N ENABLE_ASH_BASH_COMPAT
261 262
262#if defined(__ANDROID_API__) && __ANDROID_API__ <= 24 263#if defined(__ANDROID_API__) && __ANDROID_API__ <= 24
263/* Bionic at least up to version 24 has no glob() */ 264/* Bionic at least up to version 24 has no glob() */
@@ -4705,7 +4706,7 @@ wait_block_or_sig(int *status)
4705#define DOWAIT_NONBLOCK 0 4706#define DOWAIT_NONBLOCK 0
4706#define DOWAIT_BLOCK 1 4707#define DOWAIT_BLOCK 1
4707#define DOWAIT_BLOCK_OR_SIG 2 4708#define DOWAIT_BLOCK_OR_SIG 2
4708#if ENABLE_ASH_BASH_COMPAT 4709#if BASH_WAIT_N
4709# define DOWAIT_JOBSTATUS 0x10 /* OR this to get job's exitstatus instead of pid */ 4710# define DOWAIT_JOBSTATUS 0x10 /* OR this to get job's exitstatus instead of pid */
4710#endif 4711#endif
4711 4712
@@ -4716,7 +4717,7 @@ dowait(int block, struct job *job)
4716 int status; 4717 int status;
4717 struct job *jp; 4718 struct job *jp;
4718 struct job *thisjob; 4719 struct job *thisjob;
4719#if ENABLE_ASH_BASH_COMPAT 4720#if BASH_WAIT_N
4720 bool want_jobexitstatus = (block & DOWAIT_JOBSTATUS); 4721 bool want_jobexitstatus = (block & DOWAIT_JOBSTATUS);
4721 block = (block & ~DOWAIT_JOBSTATUS); 4722 block = (block & ~DOWAIT_JOBSTATUS);
4722#endif 4723#endif
@@ -4823,7 +4824,7 @@ dowait(int block, struct job *job)
4823 out: 4824 out:
4824 INT_ON; 4825 INT_ON;
4825 4826
4826#if ENABLE_ASH_BASH_COMPAT 4827#if BASH_WAIT_N
4827 if (want_jobexitstatus) { 4828 if (want_jobexitstatus) {
4828 pid = -1; 4829 pid = -1;
4829 if (thisjob && thisjob->state == JOBDONE) 4830 if (thisjob && thisjob->state == JOBDONE)
@@ -5012,7 +5013,7 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
5012 struct job *job; 5013 struct job *job;
5013 int retval; 5014 int retval;
5014 struct job *jp; 5015 struct job *jp;
5015#if ENABLE_ASH_BASH_COMPAT 5016#if BASH_WAIT_N
5016 int status; 5017 int status;
5017 char one = nextopt("n"); 5018 char one = nextopt("n");
5018#else 5019#else
@@ -5025,7 +5026,7 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
5025 /* wait for all jobs / one job if -n */ 5026 /* wait for all jobs / one job if -n */
5026 for (;;) { 5027 for (;;) {
5027 jp = curjob; 5028 jp = curjob;
5028#if ENABLE_ASH_BASH_COMPAT 5029#if BASH_WAIT_N
5029 if (one && !jp) 5030 if (one && !jp)
5030 /* exitcode of "wait -n" with nothing to wait for is 127, not 0 */ 5031 /* exitcode of "wait -n" with nothing to wait for is 127, not 0 */
5031 retval = 127; 5032 retval = 127;
@@ -5045,7 +5046,7 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
5045 * with an exit status greater than 128, immediately after which 5046 * with an exit status greater than 128, immediately after which
5046 * the trap is executed." 5047 * the trap is executed."
5047 */ 5048 */
5048#if ENABLE_ASH_BASH_COMPAT 5049#if BASH_WAIT_N
5049 status = dowait(DOWAIT_BLOCK_OR_SIG | DOWAIT_JOBSTATUS, NULL); 5050 status = dowait(DOWAIT_BLOCK_OR_SIG | DOWAIT_JOBSTATUS, NULL);
5050#else 5051#else
5051 dowait(DOWAIT_BLOCK_OR_SIG, NULL); 5052 dowait(DOWAIT_BLOCK_OR_SIG, NULL);
@@ -5056,7 +5057,7 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
5056 */ 5057 */
5057 if (pending_sig) 5058 if (pending_sig)
5058 goto sigout; 5059 goto sigout;
5059#if ENABLE_ASH_BASH_COMPAT 5060#if BASH_WAIT_N
5060 if (one) { 5061 if (one) {
5061 /* wait -n waits for one _job_, not one _process_. 5062 /* wait -n waits for one _job_, not one _process_.
5062 * date; sleep 3 & sleep 2 | sleep 1 & wait -n; date 5063 * date; sleep 3 & sleep 2 | sleep 1 & wait -n; date