source: MondoRescue/branches/3.2/mindi-busybox/editors/vi.c@ 3232

Last change on this file since 3232 was 3232, checked in by Bruno Cornec, 10 years ago
  • Update mindi-busybox to 1.21.1
File size: 109.6 KB
Line 
1/* vi: set sw=4 ts=4: */
2/*
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
5 *
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
7 */
8
9/*
10 * Things To Do:
11 * EXINIT
12 * $HOME/.exrc and ./.exrc
13 * add magic to search /foo.*bar
14 * add :help command
15 * :map macros
16 * if mark[] values were line numbers rather than pointers
17 * it would be easier to change the mark when add/delete lines
18 * More intelligence in refresh()
19 * ":r !cmd" and "!cmd" to filter text through an external command
20 * A true "undo" facility
21 * An "ex" line oriented mode- maybe using "cmdedit"
22 */
23
24//config:config VI
25//config: bool "vi"
26//config: default y
27//config: help
28//config: 'vi' is a text editor. More specifically, it is the One True
29//config: text editor <grin>. It does, however, have a rather steep
30//config: learning curve. If you are not already comfortable with 'vi'
31//config: you may wish to use something else.
32//config:
33//config:config FEATURE_VI_MAX_LEN
34//config: int "Maximum screen width in vi"
35//config: range 256 16384
36//config: default 4096
37//config: depends on VI
38//config: help
39//config: Contrary to what you may think, this is not eating much.
40//config: Make it smaller than 4k only if you are very limited on memory.
41//config:
42//config:config FEATURE_VI_8BIT
43//config: bool "Allow vi to display 8-bit chars (otherwise shows dots)"
44//config: default n
45//config: depends on VI
46//config: help
47//config: If your terminal can display characters with high bit set,
48//config: you may want to enable this. Note: vi is not Unicode-capable.
49//config: If your terminal combines several 8-bit bytes into one character
50//config: (as in Unicode mode), this will not work properly.
51//config:
52//config:config FEATURE_VI_COLON
53//config: bool "Enable \":\" colon commands (no \"ex\" mode)"
54//config: default y
55//config: depends on VI
56//config: help
57//config: Enable a limited set of colon commands for vi. This does not
58//config: provide an "ex" mode.
59//config:
60//config:config FEATURE_VI_YANKMARK
61//config: bool "Enable yank/put commands and mark cmds"
62//config: default y
63//config: depends on VI
64//config: help
65//config: This will enable you to use yank and put, as well as mark in
66//config: busybox vi.
67//config:
68//config:config FEATURE_VI_SEARCH
69//config: bool "Enable search and replace cmds"
70//config: default y
71//config: depends on VI
72//config: help
73//config: Select this if you wish to be able to do search and replace in
74//config: busybox vi.
75//config:
76//config:config FEATURE_VI_REGEX_SEARCH
77//config: bool "Enable regex in search and replace"
78//config: default n # Uses GNU regex, which may be unavailable. FIXME
79//config: depends on FEATURE_VI_SEARCH
80//config: help
81//config: Use extended regex search.
82//config:
83//config:config FEATURE_VI_USE_SIGNALS
84//config: bool "Catch signals"
85//config: default y
86//config: depends on VI
87//config: help
88//config: Selecting this option will make busybox vi signal aware. This will
89//config: make busybox vi support SIGWINCH to deal with Window Changes, catch
90//config: Ctrl-Z and Ctrl-C and alarms.
91//config:
92//config:config FEATURE_VI_DOT_CMD
93//config: bool "Remember previous cmd and \".\" cmd"
94//config: default y
95//config: depends on VI
96//config: help
97//config: Make busybox vi remember the last command and be able to repeat it.
98//config:
99//config:config FEATURE_VI_READONLY
100//config: bool "Enable -R option and \"view\" mode"
101//config: default y
102//config: depends on VI
103//config: help
104//config: Enable the read-only command line option, which allows the user to
105//config: open a file in read-only mode.
106//config:
107//config:config FEATURE_VI_SETOPTS
108//config: bool "Enable set-able options, ai ic showmatch"
109//config: default y
110//config: depends on VI
111//config: help
112//config: Enable the editor to set some (ai, ic, showmatch) options.
113//config:
114//config:config FEATURE_VI_SET
115//config: bool "Support for :set"
116//config: default y
117//config: depends on VI
118//config: help
119//config: Support for ":set".
120//config:
121//config:config FEATURE_VI_WIN_RESIZE
122//config: bool "Handle window resize"
123//config: default y
124//config: depends on VI
125//config: help
126//config: Make busybox vi behave nicely with terminals that get resized.
127//config:
128//config:config FEATURE_VI_ASK_TERMINAL
129//config: bool "Use 'tell me cursor position' ESC sequence to measure window"
130//config: default y
131//config: depends on VI
132//config: help
133//config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
134//config: this option makes vi perform a last-ditch effort to find it:
135//config: position cursor to 999,999 and ask terminal to report real
136//config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
137//config:
138//config: This is not clean but helps a lot on serial lines and such.
139
140//applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
141
142//kbuild:lib-$(CONFIG_VI) += vi.o
143
144//usage:#define vi_trivial_usage
145//usage: "[OPTIONS] [FILE]..."
146//usage:#define vi_full_usage "\n\n"
147//usage: "Edit FILE\n"
148//usage: IF_FEATURE_VI_COLON(
149//usage: "\n -c CMD Initial command to run ($EXINIT also available)"
150//usage: )
151//usage: IF_FEATURE_VI_READONLY(
152//usage: "\n -R Read-only"
153//usage: )
154//usage: "\n -H List available features"
155
156#include "libbb.h"
157/* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
158#if ENABLE_FEATURE_VI_REGEX_SEARCH
159# include <regex.h>
160#endif
161
162/* the CRASHME code is unmaintained, and doesn't currently build */
163#define ENABLE_FEATURE_VI_CRASHME 0
164
165
166#if ENABLE_LOCALE_SUPPORT
167
168#if ENABLE_FEATURE_VI_8BIT
169//FIXME: this does not work properly for Unicode anyway
170# define Isprint(c) (isprint)(c)
171#else
172# define Isprint(c) isprint_asciionly(c)
173#endif
174
175#else
176
177/* 0x9b is Meta-ESC */
178#if ENABLE_FEATURE_VI_8BIT
179# define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
180#else
181# define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
182#endif
183
184#endif
185
186
187enum {
188 MAX_TABSTOP = 32, // sanity limit
189 // User input len. Need not be extra big.
190 // Lines in file being edited *can* be bigger than this.
191 MAX_INPUT_LEN = 128,
192 // Sanity limits. We have only one buffer of this size.
193 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
194 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
195};
196
197/* VT102 ESC sequences.
198 * See "Xterm Control Sequences"
199 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
200 */
201/* Inverse/Normal text */
202#define ESC_BOLD_TEXT "\033[7m"
203#define ESC_NORM_TEXT "\033[0m"
204/* Bell */
205#define ESC_BELL "\007"
206/* Clear-to-end-of-line */
207#define ESC_CLEAR2EOL "\033[K"
208/* Clear-to-end-of-screen.
209 * (We use default param here.
210 * Full sequence is "ESC [ <num> J",
211 * <num> is 0/1/2 = "erase below/above/all".)
212 */
213#define ESC_CLEAR2EOS "\033[J"
214/* Cursor to given coordinate (1,1: top left) */
215#define ESC_SET_CURSOR_POS "\033[%u;%uH"
216//UNUSED
217///* Cursor up and down */
218//#define ESC_CURSOR_UP "\033[A"
219//#define ESC_CURSOR_DOWN "\n"
220
221#if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
222// cmds modifying text[]
223// vda: removed "aAiIs" as they switch us into insert mode
224// and remembering input for replay after them makes no sense
225static const char modifying_cmds[] = "cCdDJoOpPrRxX<>~";
226#endif
227
228enum {
229 YANKONLY = FALSE,
230 YANKDEL = TRUE,
231 FORWARD = 1, // code depends on "1" for array index
232 BACK = -1, // code depends on "-1" for array index
233 LIMITED = 0, // how much of text[] in char_search
234 FULL = 1, // how much of text[] in char_search
235
236 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
237 S_TO_WS = 2, // used in skip_thing() for moving "dot"
238 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
239 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
240 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
241};
242
243
244/* vi.c expects chars to be unsigned. */
245/* busybox build system provides that, but it's better */
246/* to audit and fix the source */
247
248struct globals {
249 /* many references - keep near the top of globals */
250 char *text, *end; // pointers to the user data in memory
251 char *dot; // where all the action takes place
252 int text_size; // size of the allocated buffer
253
254 /* the rest */
255 smallint vi_setops;
256#define VI_AUTOINDENT 1
257#define VI_SHOWMATCH 2
258#define VI_IGNORECASE 4
259#define VI_ERR_METHOD 8
260#define autoindent (vi_setops & VI_AUTOINDENT)
261#define showmatch (vi_setops & VI_SHOWMATCH )
262#define ignorecase (vi_setops & VI_IGNORECASE)
263/* indicate error with beep or flash */
264#define err_method (vi_setops & VI_ERR_METHOD)
265
266#if ENABLE_FEATURE_VI_READONLY
267 smallint readonly_mode;
268#define SET_READONLY_FILE(flags) ((flags) |= 0x01)
269#define SET_READONLY_MODE(flags) ((flags) |= 0x02)
270#define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
271#else
272#define SET_READONLY_FILE(flags) ((void)0)
273#define SET_READONLY_MODE(flags) ((void)0)
274#define UNSET_READONLY_FILE(flags) ((void)0)
275#endif
276
277 smallint editing; // >0 while we are editing a file
278 // [code audit says "can be 0, 1 or 2 only"]
279 smallint cmd_mode; // 0=command 1=insert 2=replace
280 int file_modified; // buffer contents changed (counter, not flag!)
281 int last_file_modified; // = -1;
282 int save_argc; // how many file names on cmd line
283 int cmdcnt; // repetition count
284 unsigned rows, columns; // the terminal screen is this size
285#if ENABLE_FEATURE_VI_ASK_TERMINAL
286 int get_rowcol_error;
287#endif
288 int crow, ccol; // cursor is on Crow x Ccol
289 int offset; // chars scrolled off the screen to the left
290 int have_status_msg; // is default edit status needed?
291 // [don't make smallint!]
292 int last_status_cksum; // hash of current status line
293 char *current_filename;
294 char *screenbegin; // index into text[], of top line on the screen
295 char *screen; // pointer to the virtual screen buffer
296 int screensize; // and its size
297 int tabstop;
298 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
299 char erase_char; // the users erase character
300 char last_input_char; // last char read from user
301
302#if ENABLE_FEATURE_VI_DOT_CMD
303 smallint adding2q; // are we currently adding user input to q
304 int lmc_len; // length of last_modifying_cmd
305 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
306#endif
307#if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
308 int my_pid;
309#endif
310#if ENABLE_FEATURE_VI_SEARCH
311 char *last_search_pattern; // last pattern from a '/' or '?' search
312#endif
313
314 /* former statics */
315#if ENABLE_FEATURE_VI_YANKMARK
316 char *edit_file__cur_line;
317#endif
318 int refresh__old_offset;
319 int format_edit_status__tot;
320
321 /* a few references only */
322#if ENABLE_FEATURE_VI_YANKMARK
323 int YDreg, Ureg; // default delete register and orig line for "U"
324 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
325 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
326 char *context_start, *context_end;
327#endif
328#if ENABLE_FEATURE_VI_USE_SIGNALS
329 sigjmp_buf restart; // catch_sig()
330#endif
331 struct termios term_orig, term_vi; // remember what the cooked mode was
332#if ENABLE_FEATURE_VI_COLON
333 char *initial_cmds[3]; // currently 2 entries, NULL terminated
334#endif
335 // Should be just enough to hold a key sequence,
336 // but CRASHME mode uses it as generated command buffer too
337#if ENABLE_FEATURE_VI_CRASHME
338 char readbuffer[128];
339#else
340 char readbuffer[KEYCODE_BUFFER_SIZE];
341#endif
342#define STATUS_BUFFER_LEN 200
343 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
344#if ENABLE_FEATURE_VI_DOT_CMD
345 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
346#endif
347 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
348
349 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
350};
351#define G (*ptr_to_globals)
352#define text (G.text )
353#define text_size (G.text_size )
354#define end (G.end )
355#define dot (G.dot )
356#define reg (G.reg )
357
358#define vi_setops (G.vi_setops )
359#define editing (G.editing )
360#define cmd_mode (G.cmd_mode )
361#define file_modified (G.file_modified )
362#define last_file_modified (G.last_file_modified )
363#define save_argc (G.save_argc )
364#define cmdcnt (G.cmdcnt )
365#define rows (G.rows )
366#define columns (G.columns )
367#define crow (G.crow )
368#define ccol (G.ccol )
369#define offset (G.offset )
370#define status_buffer (G.status_buffer )
371#define have_status_msg (G.have_status_msg )
372#define last_status_cksum (G.last_status_cksum )
373#define current_filename (G.current_filename )
374#define screen (G.screen )
375#define screensize (G.screensize )
376#define screenbegin (G.screenbegin )
377#define tabstop (G.tabstop )
378#define last_forward_char (G.last_forward_char )
379#define erase_char (G.erase_char )
380#define last_input_char (G.last_input_char )
381#if ENABLE_FEATURE_VI_READONLY
382#define readonly_mode (G.readonly_mode )
383#else
384#define readonly_mode 0
385#endif
386#define adding2q (G.adding2q )
387#define lmc_len (G.lmc_len )
388#define ioq (G.ioq )
389#define ioq_start (G.ioq_start )
390#define my_pid (G.my_pid )
391#define last_search_pattern (G.last_search_pattern)
392
393#define edit_file__cur_line (G.edit_file__cur_line)
394#define refresh__old_offset (G.refresh__old_offset)
395#define format_edit_status__tot (G.format_edit_status__tot)
396
397#define YDreg (G.YDreg )
398#define Ureg (G.Ureg )
399#define mark (G.mark )
400#define context_start (G.context_start )
401#define context_end (G.context_end )
402#define restart (G.restart )
403#define term_orig (G.term_orig )
404#define term_vi (G.term_vi )
405#define initial_cmds (G.initial_cmds )
406#define readbuffer (G.readbuffer )
407#define scr_out_buf (G.scr_out_buf )
408#define last_modifying_cmd (G.last_modifying_cmd )
409#define get_input_line__buf (G.get_input_line__buf)
410
411#define INIT_G() do { \
412 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
413 last_file_modified = -1; \
414 /* "" but has space for 2 chars: */ \
415 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
416} while (0)
417
418
419static int init_text_buffer(char *); // init from file or create new
420static void edit_file(char *); // edit one file
421static void do_cmd(int); // execute a command
422static int next_tabstop(int);
423static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
424static char *begin_line(char *); // return pointer to cur line B-o-l
425static char *end_line(char *); // return pointer to cur line E-o-l
426static char *prev_line(char *); // return pointer to prev line B-o-l
427static char *next_line(char *); // return pointer to next line B-o-l
428static char *end_screen(void); // get pointer to last char on screen
429static int count_lines(char *, char *); // count line from start to stop
430static char *find_line(int); // find begining of line #li
431static char *move_to_col(char *, int); // move "p" to column l
432static void dot_left(void); // move dot left- dont leave line
433static void dot_right(void); // move dot right- dont leave line
434static void dot_begin(void); // move dot to B-o-l
435static void dot_end(void); // move dot to E-o-l
436static void dot_next(void); // move dot to next line B-o-l
437static void dot_prev(void); // move dot to prev line B-o-l
438static void dot_scroll(int, int); // move the screen up or down
439static void dot_skip_over_ws(void); // move dot pat WS
440static void dot_delete(void); // delete the char at 'dot'
441static char *bound_dot(char *); // make sure text[0] <= P < "end"
442static char *new_screen(int, int); // malloc virtual screen memory
443static char *char_insert(char *, char); // insert the char c at 'p'
444// might reallocate text[]! use p += stupid_insert(p, ...),
445// and be careful to not use pointers into potentially freed text[]!
446static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
447static int find_range(char **, char **, char); // return pointers for an object
448static int st_test(char *, int, int, char *); // helper for skip_thing()
449static char *skip_thing(char *, int, int, int); // skip some object
450static char *find_pair(char *, char); // find matching pair () [] {}
451static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole
452// might reallocate text[]! use p += text_hole_make(p, ...),
453// and be careful to not use pointers into potentially freed text[]!
454static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
455static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete
456static void show_help(void); // display some help info
457static void rawmode(void); // set "raw" mode on tty
458static void cookmode(void); // return to "cooked" mode on tty
459// sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
460static int mysleep(int);
461static int readit(void); // read (maybe cursor) key from stdin
462static int get_one_char(void); // read 1 char from stdin
463static int file_size(const char *); // what is the byte size of "fn"
464#if !ENABLE_FEATURE_VI_READONLY
465#define file_insert(fn, p, update_ro_status) file_insert(fn, p)
466#endif
467// file_insert might reallocate text[]!
468static int file_insert(const char *, char *, int);
469static int file_write(char *, char *, char *);
470static void place_cursor(int, int);
471static void screen_erase(void);
472static void clear_to_eol(void);
473static void clear_to_eos(void);
474static void go_bottom_and_clear_to_eol(void);
475static void standout_start(void); // send "start reverse video" sequence
476static void standout_end(void); // send "end reverse video" sequence
477static void flash(int); // flash the terminal screen
478static void show_status_line(void); // put a message on the bottom line
479static void status_line(const char *, ...); // print to status buf
480static void status_line_bold(const char *, ...);
481static void not_implemented(const char *); // display "Not implemented" message
482static int format_edit_status(void); // format file status on status line
483static void redraw(int); // force a full screen refresh
484static char* format_line(char* /*, int*/);
485static void refresh(int); // update the terminal from screen[]
486
487static void Indicate_Error(void); // use flash or beep to indicate error
488#define indicate_error(c) Indicate_Error()
489static void Hit_Return(void);
490
491#if ENABLE_FEATURE_VI_SEARCH
492static char *char_search(char *, const char *, int, int); // search for pattern starting at p
493#endif
494#if ENABLE_FEATURE_VI_COLON
495static char *get_one_address(char *, int *); // get colon addr, if present
496static char *get_address(char *, int *, int *); // get two colon addrs, if present
497static void colon(char *); // execute the "colon" mode cmds
498#endif
499#if ENABLE_FEATURE_VI_USE_SIGNALS
500static void winch_sig(int); // catch window size changes
501static void suspend_sig(int); // catch ctrl-Z
502static void catch_sig(int); // catch ctrl-C and alarm time-outs
503#endif
504#if ENABLE_FEATURE_VI_DOT_CMD
505static void start_new_cmd_q(char); // new queue for command
506static void end_cmd_q(void); // stop saving input chars
507#else
508#define end_cmd_q() ((void)0)
509#endif
510#if ENABLE_FEATURE_VI_SETOPTS
511static void showmatching(char *); // show the matching pair () [] {}
512#endif
513#if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
514// might reallocate text[]! use p += string_insert(p, ...),
515// and be careful to not use pointers into potentially freed text[]!
516static uintptr_t string_insert(char *, const char *); // insert the string at 'p'
517#endif
518#if ENABLE_FEATURE_VI_YANKMARK
519static char *text_yank(char *, char *, int); // save copy of "p" into a register
520static char what_reg(void); // what is letter of current YDreg
521static void check_context(char); // remember context for '' command
522#endif
523#if ENABLE_FEATURE_VI_CRASHME
524static void crash_dummy();
525static void crash_test();
526static int crashme = 0;
527#endif
528
529
530static void write1(const char *out)
531{
532 fputs(out, stdout);
533}
534
535int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
536int vi_main(int argc, char **argv)
537{
538 int c;
539
540 INIT_G();
541
542#if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
543 my_pid = getpid();
544#endif
545#if ENABLE_FEATURE_VI_CRASHME
546 srand((long) my_pid);
547#endif
548#ifdef NO_SUCH_APPLET_YET
549 /* If we aren't "vi", we are "view" */
550 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
551 SET_READONLY_MODE(readonly_mode);
552 }
553#endif
554
555 // autoindent is not default in vim 7.3
556 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
557 // 1- process $HOME/.exrc file (not inplemented yet)
558 // 2- process EXINIT variable from environment
559 // 3- process command line args
560#if ENABLE_FEATURE_VI_COLON
561 {
562 char *p = getenv("EXINIT");
563 if (p && *p)
564 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
565 }
566#endif
567 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
568 switch (c) {
569#if ENABLE_FEATURE_VI_CRASHME
570 case 'C':
571 crashme = 1;
572 break;
573#endif
574#if ENABLE_FEATURE_VI_READONLY
575 case 'R': // Read-only flag
576 SET_READONLY_MODE(readonly_mode);
577 break;
578#endif
579#if ENABLE_FEATURE_VI_COLON
580 case 'c': // cmd line vi command
581 if (*optarg)
582 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
583 break;
584#endif
585 case 'H':
586 show_help();
587 /* fall through */
588 default:
589 bb_show_usage();
590 return 1;
591 }
592 }
593
594 // The argv array can be used by the ":next" and ":rewind" commands
595 argv += optind;
596 argc -= optind;
597
598 //----- This is the main file handling loop --------------
599 save_argc = argc;
600 optind = 0;
601 // "Save cursor, use alternate screen buffer, clear screen"
602 write1("\033[?1049h");
603 while (1) {
604 edit_file(argv[optind]); /* param might be NULL */
605 if (++optind >= argc)
606 break;
607 }
608 // "Use normal screen buffer, restore cursor"
609 write1("\033[?1049l");
610 //-----------------------------------------------------------
611
612 return 0;
613}
614
615/* read text from file or create an empty buf */
616/* will also update current_filename */
617static int init_text_buffer(char *fn)
618{
619 int rc;
620 int size = file_size(fn); // file size. -1 means does not exist.
621
622 /* allocate/reallocate text buffer */
623 free(text);
624 text_size = size + 10240;
625 screenbegin = dot = end = text = xzalloc(text_size);
626
627 if (fn != current_filename) {
628 free(current_filename);
629 current_filename = xstrdup(fn);
630 }
631 if (size < 0) {
632 // file dont exist. Start empty buf with dummy line
633 char_insert(text, '\n');
634 rc = 0;
635 } else {
636 rc = file_insert(fn, text, 1);
637 }
638 file_modified = 0;
639 last_file_modified = -1;
640#if ENABLE_FEATURE_VI_YANKMARK
641 /* init the marks. */
642 memset(mark, 0, sizeof(mark));
643#endif
644 return rc;
645}
646
647#if ENABLE_FEATURE_VI_WIN_RESIZE
648static int query_screen_dimensions(void)
649{
650 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
651 if (rows > MAX_SCR_ROWS)
652 rows = MAX_SCR_ROWS;
653 if (columns > MAX_SCR_COLS)
654 columns = MAX_SCR_COLS;
655 return err;
656}
657#else
658# define query_screen_dimensions() (0)
659#endif
660
661static void edit_file(char *fn)
662{
663#if ENABLE_FEATURE_VI_YANKMARK
664#define cur_line edit_file__cur_line
665#endif
666 int c;
667#if ENABLE_FEATURE_VI_USE_SIGNALS
668 int sig;
669#endif
670
671 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
672 rawmode();
673 rows = 24;
674 columns = 80;
675 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
676#if ENABLE_FEATURE_VI_ASK_TERMINAL
677 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
678 uint64_t k;
679 write1("\033[999;999H" "\033[6n");
680 fflush_all();
681 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
682 if ((int32_t)k == KEYCODE_CURSOR_POS) {
683 uint32_t rc = (k >> 32);
684 columns = (rc & 0x7fff);
685 if (columns > MAX_SCR_COLS)
686 columns = MAX_SCR_COLS;
687 rows = ((rc >> 16) & 0x7fff);
688 if (rows > MAX_SCR_ROWS)
689 rows = MAX_SCR_ROWS;
690 }
691 }
692#endif
693 new_screen(rows, columns); // get memory for virtual screen
694 init_text_buffer(fn);
695
696#if ENABLE_FEATURE_VI_YANKMARK
697 YDreg = 26; // default Yank/Delete reg
698 Ureg = 27; // hold orig line for "U" cmd
699 mark[26] = mark[27] = text; // init "previous context"
700#endif
701
702 last_forward_char = last_input_char = '\0';
703 crow = 0;
704 ccol = 0;
705
706#if ENABLE_FEATURE_VI_USE_SIGNALS
707 signal(SIGINT, catch_sig);
708 signal(SIGWINCH, winch_sig);
709 signal(SIGTSTP, suspend_sig);
710 sig = sigsetjmp(restart, 1);
711 if (sig != 0) {
712 screenbegin = dot = text;
713 }
714#endif
715
716 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
717 cmdcnt = 0;
718 tabstop = 8;
719 offset = 0; // no horizontal offset
720 c = '\0';
721#if ENABLE_FEATURE_VI_DOT_CMD
722 free(ioq_start);
723 ioq = ioq_start = NULL;
724 lmc_len = 0;
725 adding2q = 0;
726#endif
727
728#if ENABLE_FEATURE_VI_COLON
729 {
730 char *p, *q;
731 int n = 0;
732
733 while ((p = initial_cmds[n]) != NULL) {
734 do {
735 q = p;
736 p = strchr(q, '\n');
737 if (p)
738 while (*p == '\n')
739 *p++ = '\0';
740 if (*q)
741 colon(q);
742 } while (p);
743 free(initial_cmds[n]);
744 initial_cmds[n] = NULL;
745 n++;
746 }
747 }
748#endif
749 redraw(FALSE); // dont force every col re-draw
750 //------This is the main Vi cmd handling loop -----------------------
751 while (editing > 0) {
752#if ENABLE_FEATURE_VI_CRASHME
753 if (crashme > 0) {
754 if ((end - text) > 1) {
755 crash_dummy(); // generate a random command
756 } else {
757 crashme = 0;
758 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
759 dot = text;
760 refresh(FALSE);
761 }
762 }
763#endif
764 last_input_char = c = get_one_char(); // get a cmd from user
765#if ENABLE_FEATURE_VI_YANKMARK
766 // save a copy of the current line- for the 'U" command
767 if (begin_line(dot) != cur_line) {
768 cur_line = begin_line(dot);
769 text_yank(begin_line(dot), end_line(dot), Ureg);
770 }
771#endif
772#if ENABLE_FEATURE_VI_DOT_CMD
773 // These are commands that change text[].
774 // Remember the input for the "." command
775 if (!adding2q && ioq_start == NULL
776 && cmd_mode == 0 // command mode
777 && c > '\0' // exclude NUL and non-ASCII chars
778 && c < 0x7f // (Unicode and such)
779 && strchr(modifying_cmds, c)
780 ) {
781 start_new_cmd_q(c);
782 }
783#endif
784 do_cmd(c); // execute the user command
785
786 // poll to see if there is input already waiting. if we are
787 // not able to display output fast enough to keep up, skip
788 // the display update until we catch up with input.
789 if (!readbuffer[0] && mysleep(0) == 0) {
790 // no input pending - so update output
791 refresh(FALSE);
792 show_status_line();
793 }
794#if ENABLE_FEATURE_VI_CRASHME
795 if (crashme > 0)
796 crash_test(); // test editor variables
797#endif
798 }
799 //-------------------------------------------------------------------
800
801 go_bottom_and_clear_to_eol();
802 cookmode();
803#undef cur_line
804}
805
806//----- The Colon commands -------------------------------------
807#if ENABLE_FEATURE_VI_COLON
808static char *get_one_address(char *p, int *addr) // get colon addr, if present
809{
810 int st;
811 char *q;
812 IF_FEATURE_VI_YANKMARK(char c;)
813 IF_FEATURE_VI_SEARCH(char *pat;)
814
815 *addr = -1; // assume no addr
816 if (*p == '.') { // the current line
817 p++;
818 q = begin_line(dot);
819 *addr = count_lines(text, q);
820 }
821#if ENABLE_FEATURE_VI_YANKMARK
822 else if (*p == '\'') { // is this a mark addr
823 p++;
824 c = tolower(*p);
825 p++;
826 if (c >= 'a' && c <= 'z') {
827 // we have a mark
828 c = c - 'a';
829 q = mark[(unsigned char) c];
830 if (q != NULL) { // is mark valid
831 *addr = count_lines(text, q);
832 }
833 }
834 }
835#endif
836#if ENABLE_FEATURE_VI_SEARCH
837 else if (*p == '/') { // a search pattern
838 q = strchrnul(++p, '/');
839 pat = xstrndup(p, q - p); // save copy of pattern
840 p = q;
841 if (*p == '/')
842 p++;
843 q = char_search(dot, pat, FORWARD, FULL);
844 if (q != NULL) {
845 *addr = count_lines(text, q);
846 }
847 free(pat);
848 }
849#endif
850 else if (*p == '$') { // the last line in file
851 p++;
852 q = begin_line(end - 1);
853 *addr = count_lines(text, q);
854 } else if (isdigit(*p)) { // specific line number
855 sscanf(p, "%d%n", addr, &st);
856 p += st;
857 } else {
858 // unrecognized address - assume -1
859 *addr = -1;
860 }
861 return p;
862}
863
864static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
865{
866 //----- get the address' i.e., 1,3 'a,'b -----
867 // get FIRST addr, if present
868 while (isblank(*p))
869 p++; // skip over leading spaces
870 if (*p == '%') { // alias for 1,$
871 p++;
872 *b = 1;
873 *e = count_lines(text, end-1);
874 goto ga0;
875 }
876 p = get_one_address(p, b);
877 while (isblank(*p))
878 p++;
879 if (*p == ',') { // is there a address separator
880 p++;
881 while (isblank(*p))
882 p++;
883 // get SECOND addr, if present
884 p = get_one_address(p, e);
885 }
886 ga0:
887 while (isblank(*p))
888 p++; // skip over trailing spaces
889 return p;
890}
891
892#if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
893static void setops(const char *args, const char *opname, int flg_no,
894 const char *short_opname, int opt)
895{
896 const char *a = args + flg_no;
897 int l = strlen(opname) - 1; /* opname have + ' ' */
898
899 // maybe strncmp? we had tons of erroneous strncasecmp's...
900 if (strncasecmp(a, opname, l) == 0
901 || strncasecmp(a, short_opname, 2) == 0
902 ) {
903 if (flg_no)
904 vi_setops &= ~opt;
905 else
906 vi_setops |= opt;
907 }
908}
909#endif
910
911// buf must be no longer than MAX_INPUT_LEN!
912static void colon(char *buf)
913{
914 char c, *orig_buf, *buf1, *q, *r;
915 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
916 int i, l, li, ch, b, e;
917 int useforce, forced = FALSE;
918
919 // :3154 // if (-e line 3154) goto it else stay put
920 // :4,33w! foo // write a portion of buffer to file "foo"
921 // :w // write all of buffer to current file
922 // :q // quit
923 // :q! // quit- dont care about modified file
924 // :'a,'z!sort -u // filter block through sort
925 // :'f // goto mark "f"
926 // :'fl // list literal the mark "f" line
927 // :.r bar // read file "bar" into buffer before dot
928 // :/123/,/abc/d // delete lines from "123" line to "abc" line
929 // :/xyz/ // goto the "xyz" line
930 // :s/find/replace/ // substitute pattern "find" with "replace"
931 // :!<cmd> // run <cmd> then return
932 //
933
934 if (!buf[0])
935 goto ret;
936 if (*buf == ':')
937 buf++; // move past the ':'
938
939 li = ch = i = 0;
940 b = e = -1;
941 q = text; // assume 1,$ for the range
942 r = end - 1;
943 li = count_lines(text, end - 1);
944 fn = current_filename;
945
946 // look for optional address(es) :. :1 :1,9 :'q,'a :%
947 buf = get_address(buf, &b, &e);
948
949 // remember orig command line
950 orig_buf = buf;
951
952 // get the COMMAND into cmd[]
953 buf1 = cmd;
954 while (*buf != '\0') {
955 if (isspace(*buf))
956 break;
957 *buf1++ = *buf++;
958 }
959 *buf1 = '\0';
960 // get any ARGuments
961 while (isblank(*buf))
962 buf++;
963 strcpy(args, buf);
964 useforce = FALSE;
965 buf1 = last_char_is(cmd, '!');
966 if (buf1) {
967 useforce = TRUE;
968 *buf1 = '\0'; // get rid of !
969 }
970 if (b >= 0) {
971 // if there is only one addr, then the addr
972 // is the line number of the single line the
973 // user wants. So, reset the end
974 // pointer to point at end of the "b" line
975 q = find_line(b); // what line is #b
976 r = end_line(q);
977 li = 1;
978 }
979 if (e >= 0) {
980 // we were given two addrs. change the
981 // end pointer to the addr given by user.
982 r = find_line(e); // what line is #e
983 r = end_line(r);
984 li = e - b + 1;
985 }
986 // ------------ now look for the command ------------
987 i = strlen(cmd);
988 if (i == 0) { // :123CR goto line #123
989 if (b >= 0) {
990 dot = find_line(b); // what line is #b
991 dot_skip_over_ws();
992 }
993 }
994#if ENABLE_FEATURE_ALLOW_EXEC
995 else if (cmd[0] == '!') { // run a cmd
996 int retcode;
997 // :!ls run the <cmd>
998 go_bottom_and_clear_to_eol();
999 cookmode();
1000 retcode = system(orig_buf + 1); // run the cmd
1001 if (retcode)
1002 printf("\nshell returned %i\n\n", retcode);
1003 rawmode();
1004 Hit_Return(); // let user see results
1005 }
1006#endif
1007 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1008 if (b < 0) { // no addr given- use defaults
1009 b = e = count_lines(text, dot);
1010 }
1011 status_line("%d", b);
1012 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1013 if (b < 0) { // no addr given- use defaults
1014 q = begin_line(dot); // assume .,. for the range
1015 r = end_line(dot);
1016 }
1017 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
1018 dot_skip_over_ws();
1019 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1020 // don't edit, if the current file has been modified
1021 if (file_modified && !useforce) {
1022 status_line_bold("No write since last change (:%s! overrides)", cmd);
1023 goto ret;
1024 }
1025 if (args[0]) {
1026 // the user supplied a file name
1027 fn = args;
1028 } else if (current_filename && current_filename[0]) {
1029 // no user supplied name- use the current filename
1030 // fn = current_filename; was set by default
1031 } else {
1032 // no user file name, no current name- punt
1033 status_line_bold("No current filename");
1034 goto ret;
1035 }
1036
1037 if (init_text_buffer(fn) < 0)
1038 goto ret;
1039
1040#if ENABLE_FEATURE_VI_YANKMARK
1041 if (Ureg >= 0 && Ureg < 28) {
1042 free(reg[Ureg]); // free orig line reg- for 'U'
1043 reg[Ureg] = NULL;
1044 }
1045 if (YDreg >= 0 && YDreg < 28) {
1046 free(reg[YDreg]); // free default yank/delete register
1047 reg[YDreg] = NULL;
1048 }
1049#endif
1050 // how many lines in text[]?
1051 li = count_lines(text, end - 1);
1052 status_line("\"%s\"%s"
1053 IF_FEATURE_VI_READONLY("%s")
1054 " %dL, %dC", current_filename,
1055 (file_size(fn) < 0 ? " [New file]" : ""),
1056 IF_FEATURE_VI_READONLY(
1057 ((readonly_mode) ? " [Readonly]" : ""),
1058 )
1059 li, ch);
1060 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1061 if (b != -1 || e != -1) {
1062 status_line_bold("No address allowed on this command");
1063 goto ret;
1064 }
1065 if (args[0]) {
1066 // user wants a new filename
1067 free(current_filename);
1068 current_filename = xstrdup(args);
1069 } else {
1070 // user wants file status info
1071 last_status_cksum = 0; // force status update
1072 }
1073 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1074 // print out values of all features
1075 go_bottom_and_clear_to_eol();
1076 cookmode();
1077 show_help();
1078 rawmode();
1079 Hit_Return();
1080 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1081 if (b < 0) { // no addr given- use defaults
1082 q = begin_line(dot); // assume .,. for the range
1083 r = end_line(dot);
1084 }
1085 go_bottom_and_clear_to_eol();
1086 puts("\r");
1087 for (; q <= r; q++) {
1088 int c_is_no_print;
1089
1090 c = *q;
1091 c_is_no_print = (c & 0x80) && !Isprint(c);
1092 if (c_is_no_print) {
1093 c = '.';
1094 standout_start();
1095 }
1096 if (c == '\n') {
1097 write1("$\r");
1098 } else if (c < ' ' || c == 127) {
1099 bb_putchar('^');
1100 if (c == 127)
1101 c = '?';
1102 else
1103 c += '@';
1104 }
1105 bb_putchar(c);
1106 if (c_is_no_print)
1107 standout_end();
1108 }
1109 Hit_Return();
1110 } else if (strncmp(cmd, "quit", i) == 0 // quit
1111 || strncmp(cmd, "next", i) == 0 // edit next file
1112 || strncmp(cmd, "prev", i) == 0 // edit previous file
1113 ) {
1114 int n;
1115 if (useforce) {
1116 if (*cmd == 'q') {
1117 // force end of argv list
1118 optind = save_argc;
1119 }
1120 editing = 0;
1121 goto ret;
1122 }
1123 // don't exit if the file been modified
1124 if (file_modified) {
1125 status_line_bold("No write since last change (:%s! overrides)", cmd);
1126 goto ret;
1127 }
1128 // are there other file to edit
1129 n = save_argc - optind - 1;
1130 if (*cmd == 'q' && n > 0) {
1131 status_line_bold("%d more file(s) to edit", n);
1132 goto ret;
1133 }
1134 if (*cmd == 'n' && n <= 0) {
1135 status_line_bold("No more files to edit");
1136 goto ret;
1137 }
1138 if (*cmd == 'p') {
1139 // are there previous files to edit
1140 if (optind < 1) {
1141 status_line_bold("No previous files to edit");
1142 goto ret;
1143 }
1144 optind -= 2;
1145 }
1146 editing = 0;
1147 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1148 fn = args;
1149 if (!fn[0]) {
1150 status_line_bold("No filename given");
1151 goto ret;
1152 }
1153 if (b < 0) { // no addr given- use defaults
1154 q = begin_line(dot); // assume "dot"
1155 }
1156 // read after current line- unless user said ":0r foo"
1157 if (b != 0)
1158 q = next_line(q);
1159 { // dance around potentially-reallocated text[]
1160 uintptr_t ofs = q - text;
1161 ch = file_insert(fn, q, 0);
1162 q = text + ofs;
1163 }
1164 if (ch < 0)
1165 goto ret; // nothing was inserted
1166 // how many lines in text[]?
1167 li = count_lines(q, q + ch - 1);
1168 status_line("\"%s\""
1169 IF_FEATURE_VI_READONLY("%s")
1170 " %dL, %dC", fn,
1171 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1172 li, ch);
1173 if (ch > 0) {
1174 // if the insert is before "dot" then we need to update
1175 if (q <= dot)
1176 dot += ch;
1177 /*file_modified++; - done by file_insert */
1178 }
1179 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1180 if (file_modified && !useforce) {
1181 status_line_bold("No write since last change (:%s! overrides)", cmd);
1182 } else {
1183 // reset the filenames to edit
1184 optind = -1; /* start from 0th file */
1185 editing = 0;
1186 }
1187#if ENABLE_FEATURE_VI_SET
1188 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1189#if ENABLE_FEATURE_VI_SETOPTS
1190 char *argp;
1191#endif
1192 i = 0; // offset into args
1193 // only blank is regarded as args delimiter. What about tab '\t'?
1194 if (!args[0] || strcasecmp(args, "all") == 0) {
1195 // print out values of all options
1196#if ENABLE_FEATURE_VI_SETOPTS
1197 status_line_bold(
1198 "%sautoindent "
1199 "%sflash "
1200 "%signorecase "
1201 "%sshowmatch "
1202 "tabstop=%u",
1203 autoindent ? "" : "no",
1204 err_method ? "" : "no",
1205 ignorecase ? "" : "no",
1206 showmatch ? "" : "no",
1207 tabstop
1208 );
1209#endif
1210 goto ret;
1211 }
1212#if ENABLE_FEATURE_VI_SETOPTS
1213 argp = args;
1214 while (*argp) {
1215 if (strncmp(argp, "no", 2) == 0)
1216 i = 2; // ":set noautoindent"
1217 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1218 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1219 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1220 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1221 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1222 int t = 0;
1223 sscanf(argp + i+8, "%u", &t);
1224 if (t > 0 && t <= MAX_TABSTOP)
1225 tabstop = t;
1226 }
1227 argp = skip_non_whitespace(argp);
1228 argp = skip_whitespace(argp);
1229 }
1230#endif /* FEATURE_VI_SETOPTS */
1231#endif /* FEATURE_VI_SET */
1232#if ENABLE_FEATURE_VI_SEARCH
1233 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1234 char *F, *R, *flags;
1235 size_t len_F, len_R;
1236 int gflag; // global replace flag
1237
1238 // F points to the "find" pattern
1239 // R points to the "replace" pattern
1240 // replace the cmd line delimiters "/" with NULs
1241 c = orig_buf[1]; // what is the delimiter
1242 F = orig_buf + 2; // start of "find"
1243 R = strchr(F, c); // middle delimiter
1244 if (!R)
1245 goto colon_s_fail;
1246 len_F = R - F;
1247 *R++ = '\0'; // terminate "find"
1248 flags = strchr(R, c);
1249 if (!flags)
1250 goto colon_s_fail;
1251 len_R = flags - R;
1252 *flags++ = '\0'; // terminate "replace"
1253 gflag = *flags;
1254
1255 q = begin_line(q);
1256 if (b < 0) { // maybe :s/foo/bar/
1257 q = begin_line(dot); // start with cur line
1258 b = count_lines(text, q); // cur line number
1259 }
1260 if (e < 0)
1261 e = b; // maybe :.s/foo/bar/
1262
1263 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1264 char *ls = q; // orig line start
1265 char *found;
1266 vc4:
1267 found = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1268 if (found) {
1269 uintptr_t bias;
1270 // we found the "find" pattern - delete it
1271 text_hole_delete(found, found + len_F - 1);
1272 // inset the "replace" patern
1273 bias = string_insert(found, R); // insert the string
1274 found += bias;
1275 ls += bias;
1276 /*q += bias; - recalculated anyway */
1277 // check for "global" :s/foo/bar/g
1278 if (gflag == 'g') {
1279 if ((found + len_R) < end_line(ls)) {
1280 q = found + len_R;
1281 goto vc4; // don't let q move past cur line
1282 }
1283 }
1284 }
1285 q = next_line(ls);
1286 }
1287#endif /* FEATURE_VI_SEARCH */
1288 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1289 status_line(BB_VER " " BB_BT);
1290 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1291 || strncmp(cmd, "wq", i) == 0
1292 || strncmp(cmd, "wn", i) == 0
1293 || (cmd[0] == 'x' && !cmd[1])
1294 ) {
1295 // is there a file name to write to?
1296 if (args[0]) {
1297 fn = args;
1298 }
1299#if ENABLE_FEATURE_VI_READONLY
1300 if (readonly_mode && !useforce) {
1301 status_line_bold("\"%s\" File is read only", fn);
1302 goto ret;
1303 }
1304#endif
1305 // how many lines in text[]?
1306 li = count_lines(q, r);
1307 ch = r - q + 1;
1308 // see if file exists- if not, its just a new file request
1309 if (useforce) {
1310 // if "fn" is not write-able, chmod u+w
1311 // sprintf(syscmd, "chmod u+w %s", fn);
1312 // system(syscmd);
1313 forced = TRUE;
1314 }
1315 l = file_write(fn, q, r);
1316 if (useforce && forced) {
1317 // chmod u-w
1318 // sprintf(syscmd, "chmod u-w %s", fn);
1319 // system(syscmd);
1320 forced = FALSE;
1321 }
1322 if (l < 0) {
1323 if (l == -1)
1324 status_line_bold("\"%s\" %s", fn, strerror(errno));
1325 } else {
1326 status_line("\"%s\" %dL, %dC", fn, li, l);
1327 if (q == text && r == end - 1 && l == ch) {
1328 file_modified = 0;
1329 last_file_modified = -1;
1330 }
1331 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
1332 || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
1333 )
1334 && l == ch
1335 ) {
1336 editing = 0;
1337 }
1338 }
1339#if ENABLE_FEATURE_VI_YANKMARK
1340 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1341 if (b < 0) { // no addr given- use defaults
1342 q = begin_line(dot); // assume .,. for the range
1343 r = end_line(dot);
1344 }
1345 text_yank(q, r, YDreg);
1346 li = count_lines(q, r);
1347 status_line("Yank %d lines (%d chars) into [%c]",
1348 li, strlen(reg[YDreg]), what_reg());
1349#endif
1350 } else {
1351 // cmd unknown
1352 not_implemented(cmd);
1353 }
1354 ret:
1355 dot = bound_dot(dot); // make sure "dot" is valid
1356 return;
1357#if ENABLE_FEATURE_VI_SEARCH
1358 colon_s_fail:
1359 status_line(":s expression missing delimiters");
1360#endif
1361}
1362
1363#endif /* FEATURE_VI_COLON */
1364
1365static void Hit_Return(void)
1366{
1367 int c;
1368
1369 standout_start();
1370 write1("[Hit return to continue]");
1371 standout_end();
1372 while ((c = get_one_char()) != '\n' && c != '\r')
1373 continue;
1374 redraw(TRUE); // force redraw all
1375}
1376
1377static int next_tabstop(int col)
1378{
1379 return col + ((tabstop - 1) - (col % tabstop));
1380}
1381
1382//----- Synchronize the cursor to Dot --------------------------
1383static NOINLINE void sync_cursor(char *d, int *row, int *col)
1384{
1385 char *beg_cur; // begin and end of "d" line
1386 char *tp;
1387 int cnt, ro, co;
1388
1389 beg_cur = begin_line(d); // first char of cur line
1390
1391 if (beg_cur < screenbegin) {
1392 // "d" is before top line on screen
1393 // how many lines do we have to move
1394 cnt = count_lines(beg_cur, screenbegin);
1395 sc1:
1396 screenbegin = beg_cur;
1397 if (cnt > (rows - 1) / 2) {
1398 // we moved too many lines. put "dot" in middle of screen
1399 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1400 screenbegin = prev_line(screenbegin);
1401 }
1402 }
1403 } else {
1404 char *end_scr; // begin and end of screen
1405 end_scr = end_screen(); // last char of screen
1406 if (beg_cur > end_scr) {
1407 // "d" is after bottom line on screen
1408 // how many lines do we have to move
1409 cnt = count_lines(end_scr, beg_cur);
1410 if (cnt > (rows - 1) / 2)
1411 goto sc1; // too many lines
1412 for (ro = 0; ro < cnt - 1; ro++) {
1413 // move screen begin the same amount
1414 screenbegin = next_line(screenbegin);
1415 // now, move the end of screen
1416 end_scr = next_line(end_scr);
1417 end_scr = end_line(end_scr);
1418 }
1419 }
1420 }
1421 // "d" is on screen- find out which row
1422 tp = screenbegin;
1423 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1424 if (tp == beg_cur)
1425 break;
1426 tp = next_line(tp);
1427 }
1428
1429 // find out what col "d" is on
1430 co = 0;
1431 while (tp < d) { // drive "co" to correct column
1432 if (*tp == '\n') //vda || *tp == '\0')
1433 break;
1434 if (*tp == '\t') {
1435 // handle tabs like real vi
1436 if (d == tp && cmd_mode) {
1437 break;
1438 }
1439 co = next_tabstop(co);
1440 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1441 co++; // display as ^X, use 2 columns
1442 }
1443 co++;
1444 tp++;
1445 }
1446
1447 // "co" is the column where "dot" is.
1448 // The screen has "columns" columns.
1449 // The currently displayed columns are 0+offset -- columns+ofset
1450 // |-------------------------------------------------------------|
1451 // ^ ^ ^
1452 // offset | |------- columns ----------------|
1453 //
1454 // If "co" is already in this range then we do not have to adjust offset
1455 // but, we do have to subtract the "offset" bias from "co".
1456 // If "co" is outside this range then we have to change "offset".
1457 // If the first char of a line is a tab the cursor will try to stay
1458 // in column 7, but we have to set offset to 0.
1459
1460 if (co < 0 + offset) {
1461 offset = co;
1462 }
1463 if (co >= columns + offset) {
1464 offset = co - columns + 1;
1465 }
1466 // if the first char of the line is a tab, and "dot" is sitting on it
1467 // force offset to 0.
1468 if (d == beg_cur && *d == '\t') {
1469 offset = 0;
1470 }
1471 co -= offset;
1472
1473 *row = ro;
1474 *col = co;
1475}
1476
1477//----- Text Movement Routines ---------------------------------
1478static char *begin_line(char *p) // return pointer to first char cur line
1479{
1480 if (p > text) {
1481 p = memrchr(text, '\n', p - text);
1482 if (!p)
1483 return text;
1484 return p + 1;
1485 }
1486 return p;
1487}
1488
1489static char *end_line(char *p) // return pointer to NL of cur line
1490{
1491 if (p < end - 1) {
1492 p = memchr(p, '\n', end - p - 1);
1493 if (!p)
1494 return end - 1;
1495 }
1496 return p;
1497}
1498
1499static char *dollar_line(char *p) // return pointer to just before NL line
1500{
1501 p = end_line(p);
1502 // Try to stay off of the Newline
1503 if (*p == '\n' && (p - begin_line(p)) > 0)
1504 p--;
1505 return p;
1506}
1507
1508static char *prev_line(char *p) // return pointer first char prev line
1509{
1510 p = begin_line(p); // goto begining of cur line
1511 if (p > text && p[-1] == '\n')
1512 p--; // step to prev line
1513 p = begin_line(p); // goto begining of prev line
1514 return p;
1515}
1516
1517static char *next_line(char *p) // return pointer first char next line
1518{
1519 p = end_line(p);
1520 if (p < end - 1 && *p == '\n')
1521 p++; // step to next line
1522 return p;
1523}
1524
1525//----- Text Information Routines ------------------------------
1526static char *end_screen(void)
1527{
1528 char *q;
1529 int cnt;
1530
1531 // find new bottom line
1532 q = screenbegin;
1533 for (cnt = 0; cnt < rows - 2; cnt++)
1534 q = next_line(q);
1535 q = end_line(q);
1536 return q;
1537}
1538
1539// count line from start to stop
1540static int count_lines(char *start, char *stop)
1541{
1542 char *q;
1543 int cnt;
1544
1545 if (stop < start) { // start and stop are backwards- reverse them
1546 q = start;
1547 start = stop;
1548 stop = q;
1549 }
1550 cnt = 0;
1551 stop = end_line(stop);
1552 while (start <= stop && start <= end - 1) {
1553 start = end_line(start);
1554 if (*start == '\n')
1555 cnt++;
1556 start++;
1557 }
1558 return cnt;
1559}
1560
1561static char *find_line(int li) // find begining of line #li
1562{
1563 char *q;
1564
1565 for (q = text; li > 1; li--) {
1566 q = next_line(q);
1567 }
1568 return q;
1569}
1570
1571//----- Dot Movement Routines ----------------------------------
1572static void dot_left(void)
1573{
1574 if (dot > text && dot[-1] != '\n')
1575 dot--;
1576}
1577
1578static void dot_right(void)
1579{
1580 if (dot < end - 1 && *dot != '\n')
1581 dot++;
1582}
1583
1584static void dot_begin(void)
1585{
1586 dot = begin_line(dot); // return pointer to first char cur line
1587}
1588
1589static void dot_end(void)
1590{
1591 dot = end_line(dot); // return pointer to last char cur line
1592}
1593
1594static char *move_to_col(char *p, int l)
1595{
1596 int co;
1597
1598 p = begin_line(p);
1599 co = 0;
1600 while (co < l && p < end) {
1601 if (*p == '\n') //vda || *p == '\0')
1602 break;
1603 if (*p == '\t') {
1604 co = next_tabstop(co);
1605 } else if (*p < ' ' || *p == 127) {
1606 co++; // display as ^X, use 2 columns
1607 }
1608 co++;
1609 p++;
1610 }
1611 return p;
1612}
1613
1614static void dot_next(void)
1615{
1616 dot = next_line(dot);
1617}
1618
1619static void dot_prev(void)
1620{
1621 dot = prev_line(dot);
1622}
1623
1624static void dot_scroll(int cnt, int dir)
1625{
1626 char *q;
1627
1628 for (; cnt > 0; cnt--) {
1629 if (dir < 0) {
1630 // scroll Backwards
1631 // ctrl-Y scroll up one line
1632 screenbegin = prev_line(screenbegin);
1633 } else {
1634 // scroll Forwards
1635 // ctrl-E scroll down one line
1636 screenbegin = next_line(screenbegin);
1637 }
1638 }
1639 // make sure "dot" stays on the screen so we dont scroll off
1640 if (dot < screenbegin)
1641 dot = screenbegin;
1642 q = end_screen(); // find new bottom line
1643 if (dot > q)
1644 dot = begin_line(q); // is dot is below bottom line?
1645 dot_skip_over_ws();
1646}
1647
1648static void dot_skip_over_ws(void)
1649{
1650 // skip WS
1651 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1652 dot++;
1653}
1654
1655static void dot_delete(void) // delete the char at 'dot'
1656{
1657 text_hole_delete(dot, dot);
1658}
1659
1660static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1661{
1662 if (p >= end && end > text) {
1663 p = end - 1;
1664 indicate_error('1');
1665 }
1666 if (p < text) {
1667 p = text;
1668 indicate_error('2');
1669 }
1670 return p;
1671}
1672
1673//----- Helper Utility Routines --------------------------------
1674
1675//----------------------------------------------------------------
1676//----- Char Routines --------------------------------------------
1677/* Chars that are part of a word-
1678 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1679 * Chars that are Not part of a word (stoppers)
1680 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1681 * Chars that are WhiteSpace
1682 * TAB NEWLINE VT FF RETURN SPACE
1683 * DO NOT COUNT NEWLINE AS WHITESPACE
1684 */
1685
1686static char *new_screen(int ro, int co)
1687{
1688 int li;
1689
1690 free(screen);
1691 screensize = ro * co + 8;
1692 screen = xmalloc(screensize);
1693 // initialize the new screen. assume this will be a empty file.
1694 screen_erase();
1695 // non-existent text[] lines start with a tilde (~).
1696 for (li = 1; li < ro - 1; li++) {
1697 screen[(li * co) + 0] = '~';
1698 }
1699 return screen;
1700}
1701
1702#if ENABLE_FEATURE_VI_SEARCH
1703
1704# if ENABLE_FEATURE_VI_REGEX_SEARCH
1705
1706// search for pattern starting at p
1707static char *char_search(char *p, const char *pat, int dir, int range)
1708{
1709 char *q;
1710 struct re_pattern_buffer preg;
1711 int i;
1712 int size;
1713
1714 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1715 preg.translate = 0;
1716 preg.fastmap = 0;
1717 preg.buffer = 0;
1718 preg.allocated = 0;
1719
1720 // assume a LIMITED forward search
1721 q = next_line(p);
1722 q = end_line(q);
1723 q = end - 1;
1724 if (dir == BACK) {
1725 q = prev_line(p);
1726 q = text;
1727 }
1728 // count the number of chars to search over, forward or backward
1729 size = q - p;
1730 if (size < 0)
1731 size = p - q;
1732 // RANGE could be negative if we are searching backwards
1733 range = q - p;
1734
1735 q = (char *)re_compile_pattern(pat, strlen(pat), (struct re_pattern_buffer *)&preg);
1736 if (q != 0) {
1737 // The pattern was not compiled
1738 status_line_bold("bad search pattern: \"%s\": %s", pat, q);
1739 i = 0; // return p if pattern not compiled
1740 goto cs1;
1741 }
1742
1743 q = p;
1744 if (range < 0) {
1745 q = p - size;
1746 if (q < text)
1747 q = text;
1748 }
1749 // search for the compiled pattern, preg, in p[]
1750 // range < 0- search backward
1751 // range > 0- search forward
1752 // 0 < start < size
1753 // re_search() < 0 not found or error
1754 // re_search() > 0 index of found pattern
1755 // struct pattern char int int int struct reg
1756 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1757 i = re_search(&preg, q, size, 0, range, 0);
1758 if (i == -1) {
1759 p = 0;
1760 i = 0; // return NULL if pattern not found
1761 }
1762 cs1:
1763 if (dir == FORWARD) {
1764 p = p + i;
1765 } else {
1766 p = p - i;
1767 }
1768 return p;
1769}
1770
1771# else
1772
1773# if ENABLE_FEATURE_VI_SETOPTS
1774static int mycmp(const char *s1, const char *s2, int len)
1775{
1776 if (ignorecase) {
1777 return strncasecmp(s1, s2, len);
1778 }
1779 return strncmp(s1, s2, len);
1780}
1781# else
1782# define mycmp strncmp
1783# endif
1784
1785static char *char_search(char *p, const char *pat, int dir, int range)
1786{
1787 char *start, *stop;
1788 int len;
1789
1790 len = strlen(pat);
1791 if (dir == FORWARD) {
1792 stop = end - 1; // assume range is p - end-1
1793 if (range == LIMITED)
1794 stop = next_line(p); // range is to next line
1795 for (start = p; start < stop; start++) {
1796 if (mycmp(start, pat, len) == 0) {
1797 return start;
1798 }
1799 }
1800 } else if (dir == BACK) {
1801 stop = text; // assume range is text - p
1802 if (range == LIMITED)
1803 stop = prev_line(p); // range is to prev line
1804 for (start = p - len; start >= stop; start--) {
1805 if (mycmp(start, pat, len) == 0) {
1806 return start;
1807 }
1808 }
1809 }
1810 // pattern not found
1811 return NULL;
1812}
1813
1814# endif
1815
1816#endif /* FEATURE_VI_SEARCH */
1817
1818static char *char_insert(char *p, char c) // insert the char c at 'p'
1819{
1820 if (c == 22) { // Is this an ctrl-V?
1821 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1822 refresh(FALSE); // show the ^
1823 c = get_one_char();
1824 *p = c;
1825 p++;
1826 file_modified++;
1827 } else if (c == 27) { // Is this an ESC?
1828 cmd_mode = 0;
1829 cmdcnt = 0;
1830 end_cmd_q(); // stop adding to q
1831 last_status_cksum = 0; // force status update
1832 if ((p[-1] != '\n') && (dot > text)) {
1833 p--;
1834 }
1835 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1836 // 123456789
1837 if ((p[-1] != '\n') && (dot>text)) {
1838 p--;
1839 p = text_hole_delete(p, p); // shrink buffer 1 char
1840 }
1841 } else {
1842#if ENABLE_FEATURE_VI_SETOPTS
1843 // insert a char into text[]
1844 char *sp; // "save p"
1845#endif
1846
1847 if (c == 13)
1848 c = '\n'; // translate \r to \n
1849#if ENABLE_FEATURE_VI_SETOPTS
1850 sp = p; // remember addr of insert
1851#endif
1852 p += 1 + stupid_insert(p, c); // insert the char
1853#if ENABLE_FEATURE_VI_SETOPTS
1854 if (showmatch && strchr(")]}", *sp) != NULL) {
1855 showmatching(sp);
1856 }
1857 if (autoindent && c == '\n') { // auto indent the new line
1858 char *q;
1859 size_t len;
1860 q = prev_line(p); // use prev line as template
1861 len = strspn(q, " \t"); // space or tab
1862 if (len) {
1863 uintptr_t bias;
1864 bias = text_hole_make(p, len);
1865 p += bias;
1866 q += bias;
1867 memcpy(p, q, len);
1868 p += len;
1869 }
1870 }
1871#endif
1872 }
1873 return p;
1874}
1875
1876// might reallocate text[]! use p += stupid_insert(p, ...),
1877// and be careful to not use pointers into potentially freed text[]!
1878static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1879{
1880 uintptr_t bias;
1881 bias = text_hole_make(p, 1);
1882 p += bias;
1883 *p = c;
1884 //file_modified++; - done by text_hole_make()
1885 return bias;
1886}
1887
1888static int find_range(char **start, char **stop, char c)
1889{
1890 char *save_dot, *p, *q, *t;
1891 int cnt, multiline = 0;
1892
1893 save_dot = dot;
1894 p = q = dot;
1895
1896 if (strchr("cdy><", c)) {
1897 // these cmds operate on whole lines
1898 p = q = begin_line(p);
1899 for (cnt = 1; cnt < cmdcnt; cnt++) {
1900 q = next_line(q);
1901 }
1902 q = end_line(q);
1903 } else if (strchr("^%$0bBeEfth\b\177", c)) {
1904 // These cmds operate on char positions
1905 do_cmd(c); // execute movement cmd
1906 q = dot;
1907 } else if (strchr("wW", c)) {
1908 do_cmd(c); // execute movement cmd
1909 // if we are at the next word's first char
1910 // step back one char
1911 // but check the possibilities when it is true
1912 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1913 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1914 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1915 dot--; // move back off of next word
1916 if (dot > text && *dot == '\n')
1917 dot--; // stay off NL
1918 q = dot;
1919 } else if (strchr("H-k{", c)) {
1920 // these operate on multi-lines backwards
1921 q = end_line(dot); // find NL
1922 do_cmd(c); // execute movement cmd
1923 dot_begin();
1924 p = dot;
1925 } else if (strchr("L+j}\r\n", c)) {
1926 // these operate on multi-lines forwards
1927 p = begin_line(dot);
1928 do_cmd(c); // execute movement cmd
1929 dot_end(); // find NL
1930 q = dot;
1931 } else {
1932 // nothing -- this causes any other values of c to
1933 // represent the one-character range under the
1934 // cursor. this is correct for ' ' and 'l', but
1935 // perhaps no others.
1936 //
1937 }
1938 if (q < p) {
1939 t = q;
1940 q = p;
1941 p = t;
1942 }
1943
1944 // backward char movements don't include start position
1945 if (q > p && strchr("^0bBh\b\177", c)) q--;
1946
1947 multiline = 0;
1948 for (t = p; t <= q; t++) {
1949 if (*t == '\n') {
1950 multiline = 1;
1951 break;
1952 }
1953 }
1954
1955 *start = p;
1956 *stop = q;
1957 dot = save_dot;
1958 return multiline;
1959}
1960
1961static int st_test(char *p, int type, int dir, char *tested)
1962{
1963 char c, c0, ci;
1964 int test, inc;
1965
1966 inc = dir;
1967 c = c0 = p[0];
1968 ci = p[inc];
1969 test = 0;
1970
1971 if (type == S_BEFORE_WS) {
1972 c = ci;
1973 test = (!isspace(c) || c == '\n');
1974 }
1975 if (type == S_TO_WS) {
1976 c = c0;
1977 test = (!isspace(c) || c == '\n');
1978 }
1979 if (type == S_OVER_WS) {
1980 c = c0;
1981 test = isspace(c);
1982 }
1983 if (type == S_END_PUNCT) {
1984 c = ci;
1985 test = ispunct(c);
1986 }
1987 if (type == S_END_ALNUM) {
1988 c = ci;
1989 test = (isalnum(c) || c == '_');
1990 }
1991 *tested = c;
1992 return test;
1993}
1994
1995static char *skip_thing(char *p, int linecnt, int dir, int type)
1996{
1997 char c;
1998
1999 while (st_test(p, type, dir, &c)) {
2000 // make sure we limit search to correct number of lines
2001 if (c == '\n' && --linecnt < 1)
2002 break;
2003 if (dir >= 0 && p >= end - 1)
2004 break;
2005 if (dir < 0 && p <= text)
2006 break;
2007 p += dir; // move to next char
2008 }
2009 return p;
2010}
2011
2012// find matching char of pair () [] {}
2013static char *find_pair(char *p, const char c)
2014{
2015 char match, *q;
2016 int dir, level;
2017
2018 match = ')';
2019 level = 1;
2020 dir = 1; // assume forward
2021 switch (c) {
2022 case '(': match = ')'; break;
2023 case '[': match = ']'; break;
2024 case '{': match = '}'; break;
2025 case ')': match = '('; dir = -1; break;
2026 case ']': match = '['; dir = -1; break;
2027 case '}': match = '{'; dir = -1; break;
2028 }
2029 for (q = p + dir; text <= q && q < end; q += dir) {
2030 // look for match, count levels of pairs (( ))
2031 if (*q == c)
2032 level++; // increase pair levels
2033 if (*q == match)
2034 level--; // reduce pair level
2035 if (level == 0)
2036 break; // found matching pair
2037 }
2038 if (level != 0)
2039 q = NULL; // indicate no match
2040 return q;
2041}
2042
2043#if ENABLE_FEATURE_VI_SETOPTS
2044// show the matching char of a pair, () [] {}
2045static void showmatching(char *p)
2046{
2047 char *q, *save_dot;
2048
2049 // we found half of a pair
2050 q = find_pair(p, *p); // get loc of matching char
2051 if (q == NULL) {
2052 indicate_error('3'); // no matching char
2053 } else {
2054 // "q" now points to matching pair
2055 save_dot = dot; // remember where we are
2056 dot = q; // go to new loc
2057 refresh(FALSE); // let the user see it
2058 mysleep(40); // give user some time
2059 dot = save_dot; // go back to old loc
2060 refresh(FALSE);
2061 }
2062}
2063#endif /* FEATURE_VI_SETOPTS */
2064
2065// open a hole in text[]
2066// might reallocate text[]! use p += text_hole_make(p, ...),
2067// and be careful to not use pointers into potentially freed text[]!
2068static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2069{
2070 uintptr_t bias = 0;
2071
2072 if (size <= 0)
2073 return bias;
2074 end += size; // adjust the new END
2075 if (end >= (text + text_size)) {
2076 char *new_text;
2077 text_size += end - (text + text_size) + 10240;
2078 new_text = xrealloc(text, text_size);
2079 bias = (new_text - text);
2080 screenbegin += bias;
2081 dot += bias;
2082 end += bias;
2083 p += bias;
2084#if ENABLE_FEATURE_VI_YANKMARK
2085 {
2086 int i;
2087 for (i = 0; i < ARRAY_SIZE(mark); i++)
2088 if (mark[i])
2089 mark[i] += bias;
2090 }
2091#endif
2092 text = new_text;
2093 }
2094 memmove(p + size, p, end - size - p);
2095 memset(p, ' ', size); // clear new hole
2096 file_modified++;
2097 return bias;
2098}
2099
2100// close a hole in text[]
2101static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive
2102{
2103 char *src, *dest;
2104 int cnt, hole_size;
2105
2106 // move forwards, from beginning
2107 // assume p <= q
2108 src = q + 1;
2109 dest = p;
2110 if (q < p) { // they are backward- swap them
2111 src = p + 1;
2112 dest = q;
2113 }
2114 hole_size = q - p + 1;
2115 cnt = end - src;
2116 if (src < text || src > end)
2117 goto thd0;
2118 if (dest < text || dest >= end)
2119 goto thd0;
2120 if (src >= end)
2121 goto thd_atend; // just delete the end of the buffer
2122 memmove(dest, src, cnt);
2123 thd_atend:
2124 end = end - hole_size; // adjust the new END
2125 if (dest >= end)
2126 dest = end - 1; // make sure dest in below end-1
2127 if (end <= text)
2128 dest = end = text; // keep pointers valid
2129 file_modified++;
2130 thd0:
2131 return dest;
2132}
2133
2134// copy text into register, then delete text.
2135// if dist <= 0, do not include, or go past, a NewLine
2136//
2137static char *yank_delete(char *start, char *stop, int dist, int yf)
2138{
2139 char *p;
2140
2141 // make sure start <= stop
2142 if (start > stop) {
2143 // they are backwards, reverse them
2144 p = start;
2145 start = stop;
2146 stop = p;
2147 }
2148 if (dist <= 0) {
2149 // we cannot cross NL boundaries
2150 p = start;
2151 if (*p == '\n')
2152 return p;
2153 // dont go past a NewLine
2154 for (; p + 1 <= stop; p++) {
2155 if (p[1] == '\n') {
2156 stop = p; // "stop" just before NewLine
2157 break;
2158 }
2159 }
2160 }
2161 p = start;
2162#if ENABLE_FEATURE_VI_YANKMARK
2163 text_yank(start, stop, YDreg);
2164#endif
2165 if (yf == YANKDEL) {
2166 p = text_hole_delete(start, stop);
2167 } // delete lines
2168 return p;
2169}
2170
2171static void show_help(void)
2172{
2173 puts("These features are available:"
2174#if ENABLE_FEATURE_VI_SEARCH
2175 "\n\tPattern searches with / and ?"
2176#endif
2177#if ENABLE_FEATURE_VI_DOT_CMD
2178 "\n\tLast command repeat with ."
2179#endif
2180#if ENABLE_FEATURE_VI_YANKMARK
2181 "\n\tLine marking with 'x"
2182 "\n\tNamed buffers with \"x"
2183#endif
2184#if ENABLE_FEATURE_VI_READONLY
2185 //not implemented: "\n\tReadonly if vi is called as \"view\""
2186 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2187#endif
2188#if ENABLE_FEATURE_VI_SET
2189 "\n\tSome colon mode commands with :"
2190#endif
2191#if ENABLE_FEATURE_VI_SETOPTS
2192 "\n\tSettable options with \":set\""
2193#endif
2194#if ENABLE_FEATURE_VI_USE_SIGNALS
2195 "\n\tSignal catching- ^C"
2196 "\n\tJob suspend and resume with ^Z"
2197#endif
2198#if ENABLE_FEATURE_VI_WIN_RESIZE
2199 "\n\tAdapt to window re-sizes"
2200#endif
2201 );
2202}
2203
2204#if ENABLE_FEATURE_VI_DOT_CMD
2205static void start_new_cmd_q(char c)
2206{
2207 // get buffer for new cmd
2208 // if there is a current cmd count put it in the buffer first
2209 if (cmdcnt > 0) {
2210 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2211 } else { // just save char c onto queue
2212 last_modifying_cmd[0] = c;
2213 lmc_len = 1;
2214 }
2215 adding2q = 1;
2216}
2217
2218static void end_cmd_q(void)
2219{
2220#if ENABLE_FEATURE_VI_YANKMARK
2221 YDreg = 26; // go back to default Yank/Delete reg
2222#endif
2223 adding2q = 0;
2224}
2225#endif /* FEATURE_VI_DOT_CMD */
2226
2227#if ENABLE_FEATURE_VI_YANKMARK \
2228 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2229 || ENABLE_FEATURE_VI_CRASHME
2230// might reallocate text[]! use p += string_insert(p, ...),
2231// and be careful to not use pointers into potentially freed text[]!
2232static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p'
2233{
2234 uintptr_t bias;
2235 int i;
2236
2237 i = strlen(s);
2238 bias = text_hole_make(p, i);
2239 p += bias;
2240 memcpy(p, s, i);
2241#if ENABLE_FEATURE_VI_YANKMARK
2242 {
2243 int cnt;
2244 for (cnt = 0; *s != '\0'; s++) {
2245 if (*s == '\n')
2246 cnt++;
2247 }
2248 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2249 }
2250#endif
2251 return bias;
2252}
2253#endif
2254
2255#if ENABLE_FEATURE_VI_YANKMARK
2256static char *text_yank(char *p, char *q, int dest) // copy text into a register
2257{
2258 int cnt = q - p;
2259 if (cnt < 0) { // they are backwards- reverse them
2260 p = q;
2261 cnt = -cnt;
2262 }
2263 free(reg[dest]); // if already a yank register, free it
2264 reg[dest] = xstrndup(p, cnt + 1);
2265 return p;
2266}
2267
2268static char what_reg(void)
2269{
2270 char c;
2271
2272 c = 'D'; // default to D-reg
2273 if (0 <= YDreg && YDreg <= 25)
2274 c = 'a' + (char) YDreg;
2275 if (YDreg == 26)
2276 c = 'D';
2277 if (YDreg == 27)
2278 c = 'U';
2279 return c;
2280}
2281
2282static void check_context(char cmd)
2283{
2284 // A context is defined to be "modifying text"
2285 // Any modifying command establishes a new context.
2286
2287 if (dot < context_start || dot > context_end) {
2288 if (strchr(modifying_cmds, cmd) != NULL) {
2289 // we are trying to modify text[]- make this the current context
2290 mark[27] = mark[26]; // move cur to prev
2291 mark[26] = dot; // move local to cur
2292 context_start = prev_line(prev_line(dot));
2293 context_end = next_line(next_line(dot));
2294 //loiter= start_loiter= now;
2295 }
2296 }
2297}
2298
2299static char *swap_context(char *p) // goto new context for '' command make this the current context
2300{
2301 char *tmp;
2302
2303 // the current context is in mark[26]
2304 // the previous context is in mark[27]
2305 // only swap context if other context is valid
2306 if (text <= mark[27] && mark[27] <= end - 1) {
2307 tmp = mark[27];
2308 mark[27] = mark[26];
2309 mark[26] = tmp;
2310 p = mark[26]; // where we are going- previous context
2311 context_start = prev_line(prev_line(prev_line(p)));
2312 context_end = next_line(next_line(next_line(p)));
2313 }
2314 return p;
2315}
2316#endif /* FEATURE_VI_YANKMARK */
2317
2318//----- Set terminal attributes --------------------------------
2319static void rawmode(void)
2320{
2321 tcgetattr(0, &term_orig);
2322 term_vi = term_orig;
2323 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG on - allow intr's
2324 term_vi.c_iflag &= (~IXON & ~ICRNL);
2325 term_vi.c_oflag &= (~ONLCR);
2326 term_vi.c_cc[VMIN] = 1;
2327 term_vi.c_cc[VTIME] = 0;
2328 erase_char = term_vi.c_cc[VERASE];
2329 tcsetattr_stdin_TCSANOW(&term_vi);
2330}
2331
2332static void cookmode(void)
2333{
2334 fflush_all();
2335 tcsetattr_stdin_TCSANOW(&term_orig);
2336}
2337
2338#if ENABLE_FEATURE_VI_USE_SIGNALS
2339//----- Come here when we get a window resize signal ---------
2340static void winch_sig(int sig UNUSED_PARAM)
2341{
2342 int save_errno = errno;
2343 // FIXME: do it in main loop!!!
2344 signal(SIGWINCH, winch_sig);
2345 query_screen_dimensions();
2346 new_screen(rows, columns); // get memory for virtual screen
2347 redraw(TRUE); // re-draw the screen
2348 errno = save_errno;
2349}
2350
2351//----- Come here when we get a continue signal -------------------
2352static void cont_sig(int sig UNUSED_PARAM)
2353{
2354 int save_errno = errno;
2355 rawmode(); // terminal to "raw"
2356 last_status_cksum = 0; // force status update
2357 redraw(TRUE); // re-draw the screen
2358
2359 signal(SIGTSTP, suspend_sig);
2360 signal(SIGCONT, SIG_DFL);
2361 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2362 errno = save_errno;
2363}
2364
2365//----- Come here when we get a Suspend signal -------------------
2366static void suspend_sig(int sig UNUSED_PARAM)
2367{
2368 int save_errno = errno;
2369 go_bottom_and_clear_to_eol();
2370 cookmode(); // terminal to "cooked"
2371
2372 signal(SIGCONT, cont_sig);
2373 signal(SIGTSTP, SIG_DFL);
2374 kill(my_pid, SIGTSTP);
2375 errno = save_errno;
2376}
2377
2378//----- Come here when we get a signal ---------------------------
2379static void catch_sig(int sig)
2380{
2381 signal(SIGINT, catch_sig);
2382 siglongjmp(restart, sig);
2383}
2384#endif /* FEATURE_VI_USE_SIGNALS */
2385
2386static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2387{
2388 struct pollfd pfd[1];
2389
2390 pfd[0].fd = STDIN_FILENO;
2391 pfd[0].events = POLLIN;
2392 return safe_poll(pfd, 1, hund*10) > 0;
2393}
2394
2395//----- IO Routines --------------------------------------------
2396static int readit(void) // read (maybe cursor) key from stdin
2397{
2398 int c;
2399
2400 fflush_all();
2401 c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2);
2402 if (c == -1) { // EOF/error
2403 go_bottom_and_clear_to_eol();
2404 cookmode(); // terminal to "cooked"
2405 bb_error_msg_and_die("can't read user input");
2406 }
2407 return c;
2408}
2409
2410//----- IO Routines --------------------------------------------
2411static int get_one_char(void)
2412{
2413 int c;
2414
2415#if ENABLE_FEATURE_VI_DOT_CMD
2416 if (!adding2q) {
2417 // we are not adding to the q.
2418 // but, we may be reading from a q
2419 if (ioq == 0) {
2420 // there is no current q, read from STDIN
2421 c = readit(); // get the users input
2422 } else {
2423 // there is a queue to get chars from first
2424 // careful with correct sign expansion!
2425 c = (unsigned char)*ioq++;
2426 if (c == '\0') {
2427 // the end of the q, read from STDIN
2428 free(ioq_start);
2429 ioq_start = ioq = 0;
2430 c = readit(); // get the users input
2431 }
2432 }
2433 } else {
2434 // adding STDIN chars to q
2435 c = readit(); // get the users input
2436 if (lmc_len >= MAX_INPUT_LEN - 1) {
2437 status_line_bold("last_modifying_cmd overrun");
2438 } else {
2439 // add new char to q
2440 last_modifying_cmd[lmc_len++] = c;
2441 }
2442 }
2443#else
2444 c = readit(); // get the users input
2445#endif /* FEATURE_VI_DOT_CMD */
2446 return c;
2447}
2448
2449// Get input line (uses "status line" area)
2450static char *get_input_line(const char *prompt)
2451{
2452 // char [MAX_INPUT_LEN]
2453#define buf get_input_line__buf
2454
2455 int c;
2456 int i;
2457
2458 strcpy(buf, prompt);
2459 last_status_cksum = 0; // force status update
2460 go_bottom_and_clear_to_eol();
2461 write1(prompt); // write out the :, /, or ? prompt
2462
2463 i = strlen(buf);
2464 while (i < MAX_INPUT_LEN) {
2465 c = get_one_char();
2466 if (c == '\n' || c == '\r' || c == 27)
2467 break; // this is end of input
2468 if (c == erase_char || c == 8 || c == 127) {
2469 // user wants to erase prev char
2470 buf[--i] = '\0';
2471 write1("\b \b"); // erase char on screen
2472 if (i <= 0) // user backs up before b-o-l, exit
2473 break;
2474 } else if (c > 0 && c < 256) { // exclude Unicode
2475 // (TODO: need to handle Unicode)
2476 buf[i] = c;
2477 buf[++i] = '\0';
2478 bb_putchar(c);
2479 }
2480 }
2481 refresh(FALSE);
2482 return buf;
2483#undef buf
2484}
2485
2486static int file_size(const char *fn) // what is the byte size of "fn"
2487{
2488 struct stat st_buf;
2489 int cnt;
2490
2491 cnt = -1;
2492 if (fn && stat(fn, &st_buf) == 0) // see if file exists
2493 cnt = (int) st_buf.st_size;
2494 return cnt;
2495}
2496
2497// might reallocate text[]!
2498static int file_insert(const char *fn, char *p, int update_ro_status)
2499{
2500 int cnt = -1;
2501 int fd, size;
2502 struct stat statbuf;
2503
2504 /* Validate file */
2505 if (stat(fn, &statbuf) < 0) {
2506 status_line_bold("\"%s\" %s", fn, strerror(errno));
2507 goto fi0;
2508 }
2509 if (!S_ISREG(statbuf.st_mode)) {
2510 // This is not a regular file
2511 status_line_bold("\"%s\" Not a regular file", fn);
2512 goto fi0;
2513 }
2514 if (p < text || p > end) {
2515 status_line_bold("Trying to insert file outside of memory");
2516 goto fi0;
2517 }
2518
2519 // read file to buffer
2520 fd = open(fn, O_RDONLY);
2521 if (fd < 0) {
2522 status_line_bold("\"%s\" %s", fn, strerror(errno));
2523 goto fi0;
2524 }
2525 size = statbuf.st_size;
2526 p += text_hole_make(p, size);
2527 cnt = safe_read(fd, p, size);
2528 if (cnt < 0) {
2529 status_line_bold("\"%s\" %s", fn, strerror(errno));
2530 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2531 } else if (cnt < size) {
2532 // There was a partial read, shrink unused space text[]
2533 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2534 status_line_bold("can't read all of file \"%s\"", fn);
2535 }
2536 if (cnt >= size)
2537 file_modified++;
2538 close(fd);
2539 fi0:
2540#if ENABLE_FEATURE_VI_READONLY
2541 if (update_ro_status
2542 && ((access(fn, W_OK) < 0) ||
2543 /* root will always have access()
2544 * so we check fileperms too */
2545 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2546 )
2547 ) {
2548 SET_READONLY_FILE(readonly_mode);
2549 }
2550#endif
2551 return cnt;
2552}
2553
2554static int file_write(char *fn, char *first, char *last)
2555{
2556 int fd, cnt, charcnt;
2557
2558 if (fn == 0) {
2559 status_line_bold("No current filename");
2560 return -2;
2561 }
2562 /* By popular request we do not open file with O_TRUNC,
2563 * but instead ftruncate() it _after_ successful write.
2564 * Might reduce amount of data lost on power fail etc.
2565 */
2566 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2567 if (fd < 0)
2568 return -1;
2569 cnt = last - first + 1;
2570 charcnt = full_write(fd, first, cnt);
2571 ftruncate(fd, charcnt);
2572 if (charcnt == cnt) {
2573 // good write
2574 //file_modified = FALSE;
2575 } else {
2576 charcnt = 0;
2577 }
2578 close(fd);
2579 return charcnt;
2580}
2581
2582//----- Terminal Drawing ---------------------------------------
2583// The terminal is made up of 'rows' line of 'columns' columns.
2584// classically this would be 24 x 80.
2585// screen coordinates
2586// 0,0 ... 0,79
2587// 1,0 ... 1,79
2588// . ... .
2589// . ... .
2590// 22,0 ... 22,79
2591// 23,0 ... 23,79 <- status line
2592
2593//----- Move the cursor to row x col (count from 0, not 1) -------
2594static void place_cursor(int row, int col)
2595{
2596 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
2597
2598 if (row < 0) row = 0;
2599 if (row >= rows) row = rows - 1;
2600 if (col < 0) col = 0;
2601 if (col >= columns) col = columns - 1;
2602
2603 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
2604 write1(cm1);
2605}
2606
2607//----- Erase from cursor to end of line -----------------------
2608static void clear_to_eol(void)
2609{
2610 write1(ESC_CLEAR2EOL);
2611}
2612
2613static void go_bottom_and_clear_to_eol(void)
2614{
2615 place_cursor(rows - 1, 0);
2616 clear_to_eol();
2617}
2618
2619//----- Erase from cursor to end of screen -----------------------
2620static void clear_to_eos(void)
2621{
2622 write1(ESC_CLEAR2EOS);
2623}
2624
2625//----- Start standout mode ------------------------------------
2626static void standout_start(void)
2627{
2628 write1(ESC_BOLD_TEXT);
2629}
2630
2631//----- End standout mode --------------------------------------
2632static void standout_end(void)
2633{
2634 write1(ESC_NORM_TEXT);
2635}
2636
2637//----- Flash the screen --------------------------------------
2638static void flash(int h)
2639{
2640 standout_start();
2641 redraw(TRUE);
2642 mysleep(h);
2643 standout_end();
2644 redraw(TRUE);
2645}
2646
2647static void Indicate_Error(void)
2648{
2649#if ENABLE_FEATURE_VI_CRASHME
2650 if (crashme > 0)
2651 return; // generate a random command
2652#endif
2653 if (!err_method) {
2654 write1(ESC_BELL);
2655 } else {
2656 flash(10);
2657 }
2658}
2659
2660//----- Screen[] Routines --------------------------------------
2661//----- Erase the Screen[] memory ------------------------------
2662static void screen_erase(void)
2663{
2664 memset(screen, ' ', screensize); // clear new screen
2665}
2666
2667static int bufsum(char *buf, int count)
2668{
2669 int sum = 0;
2670 char *e = buf + count;
2671
2672 while (buf < e)
2673 sum += (unsigned char) *buf++;
2674 return sum;
2675}
2676
2677//----- Draw the status line at bottom of the screen -------------
2678static void show_status_line(void)
2679{
2680 int cnt = 0, cksum = 0;
2681
2682 // either we already have an error or status message, or we
2683 // create one.
2684 if (!have_status_msg) {
2685 cnt = format_edit_status();
2686 cksum = bufsum(status_buffer, cnt);
2687 }
2688 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2689 last_status_cksum = cksum; // remember if we have seen this line
2690 go_bottom_and_clear_to_eol();
2691 write1(status_buffer);
2692 if (have_status_msg) {
2693 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2694 (columns - 1) ) {
2695 have_status_msg = 0;
2696 Hit_Return();
2697 }
2698 have_status_msg = 0;
2699 }
2700 place_cursor(crow, ccol); // put cursor back in correct place
2701 }
2702 fflush_all();
2703}
2704
2705//----- format the status buffer, the bottom line of screen ------
2706// format status buffer, with STANDOUT mode
2707static void status_line_bold(const char *format, ...)
2708{
2709 va_list args;
2710
2711 va_start(args, format);
2712 strcpy(status_buffer, ESC_BOLD_TEXT);
2713 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
2714 strcat(status_buffer, ESC_NORM_TEXT);
2715 va_end(args);
2716
2717 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
2718}
2719
2720// format status buffer
2721static void status_line(const char *format, ...)
2722{
2723 va_list args;
2724
2725 va_start(args, format);
2726 vsprintf(status_buffer, format, args);
2727 va_end(args);
2728
2729 have_status_msg = 1;
2730}
2731
2732// copy s to buf, convert unprintable
2733static void print_literal(char *buf, const char *s)
2734{
2735 char *d;
2736 unsigned char c;
2737
2738 buf[0] = '\0';
2739 if (!s[0])
2740 s = "(NULL)";
2741
2742 d = buf;
2743 for (; *s; s++) {
2744 int c_is_no_print;
2745
2746 c = *s;
2747 c_is_no_print = (c & 0x80) && !Isprint(c);
2748 if (c_is_no_print) {
2749 strcpy(d, ESC_NORM_TEXT);
2750 d += sizeof(ESC_NORM_TEXT)-1;
2751 c = '.';
2752 }
2753 if (c < ' ' || c == 0x7f) {
2754 *d++ = '^';
2755 c |= '@'; /* 0x40 */
2756 if (c == 0x7f)
2757 c = '?';
2758 }
2759 *d++ = c;
2760 *d = '\0';
2761 if (c_is_no_print) {
2762 strcpy(d, ESC_BOLD_TEXT);
2763 d += sizeof(ESC_BOLD_TEXT)-1;
2764 }
2765 if (*s == '\n') {
2766 *d++ = '$';
2767 *d = '\0';
2768 }
2769 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
2770 break;
2771 }
2772}
2773
2774static void not_implemented(const char *s)
2775{
2776 char buf[MAX_INPUT_LEN];
2777
2778 print_literal(buf, s);
2779 status_line_bold("\'%s\' is not implemented", buf);
2780}
2781
2782// show file status on status line
2783static int format_edit_status(void)
2784{
2785 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2786
2787#define tot format_edit_status__tot
2788
2789 int cur, percent, ret, trunc_at;
2790
2791 // file_modified is now a counter rather than a flag. this
2792 // helps reduce the amount of line counting we need to do.
2793 // (this will cause a mis-reporting of modified status
2794 // once every MAXINT editing operations.)
2795
2796 // it would be nice to do a similar optimization here -- if
2797 // we haven't done a motion that could have changed which line
2798 // we're on, then we shouldn't have to do this count_lines()
2799 cur = count_lines(text, dot);
2800
2801 // reduce counting -- the total lines can't have
2802 // changed if we haven't done any edits.
2803 if (file_modified != last_file_modified) {
2804 tot = cur + count_lines(dot, end - 1) - 1;
2805 last_file_modified = file_modified;
2806 }
2807
2808 // current line percent
2809 // ------------- ~~ ----------
2810 // total lines 100
2811 if (tot > 0) {
2812 percent = (100 * cur) / tot;
2813 } else {
2814 cur = tot = 0;
2815 percent = 100;
2816 }
2817
2818 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2819 columns : STATUS_BUFFER_LEN-1;
2820
2821 ret = snprintf(status_buffer, trunc_at+1,
2822#if ENABLE_FEATURE_VI_READONLY
2823 "%c %s%s%s %d/%d %d%%",
2824#else
2825 "%c %s%s %d/%d %d%%",
2826#endif
2827 cmd_mode_indicator[cmd_mode & 3],
2828 (current_filename != NULL ? current_filename : "No file"),
2829#if ENABLE_FEATURE_VI_READONLY
2830 (readonly_mode ? " [Readonly]" : ""),
2831#endif
2832 (file_modified ? " [Modified]" : ""),
2833 cur, tot, percent);
2834
2835 if (ret >= 0 && ret < trunc_at)
2836 return ret; /* it all fit */
2837
2838 return trunc_at; /* had to truncate */
2839#undef tot
2840}
2841
2842//----- Force refresh of all Lines -----------------------------
2843static void redraw(int full_screen)
2844{
2845 place_cursor(0, 0);
2846 clear_to_eos();
2847 screen_erase(); // erase the internal screen buffer
2848 last_status_cksum = 0; // force status update
2849 refresh(full_screen); // this will redraw the entire display
2850 show_status_line();
2851}
2852
2853//----- Format a text[] line into a buffer ---------------------
2854static char* format_line(char *src /*, int li*/)
2855{
2856 unsigned char c;
2857 int co;
2858 int ofs = offset;
2859 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
2860
2861 c = '~'; // char in col 0 in non-existent lines is '~'
2862 co = 0;
2863 while (co < columns + tabstop) {
2864 // have we gone past the end?
2865 if (src < end) {
2866 c = *src++;
2867 if (c == '\n')
2868 break;
2869 if ((c & 0x80) && !Isprint(c)) {
2870 c = '.';
2871 }
2872 if (c < ' ' || c == 0x7f) {
2873 if (c == '\t') {
2874 c = ' ';
2875 // co % 8 != 7
2876 while ((co % tabstop) != (tabstop - 1)) {
2877 dest[co++] = c;
2878 }
2879 } else {
2880 dest[co++] = '^';
2881 if (c == 0x7f)
2882 c = '?';
2883 else
2884 c += '@'; // Ctrl-X -> 'X'
2885 }
2886 }
2887 }
2888 dest[co++] = c;
2889 // discard scrolled-off-to-the-left portion,
2890 // in tabstop-sized pieces
2891 if (ofs >= tabstop && co >= tabstop) {
2892 memmove(dest, dest + tabstop, co);
2893 co -= tabstop;
2894 ofs -= tabstop;
2895 }
2896 if (src >= end)
2897 break;
2898 }
2899 // check "short line, gigantic offset" case
2900 if (co < ofs)
2901 ofs = co;
2902 // discard last scrolled off part
2903 co -= ofs;
2904 dest += ofs;
2905 // fill the rest with spaces
2906 if (co < columns)
2907 memset(&dest[co], ' ', columns - co);
2908 return dest;
2909}
2910
2911//----- Refresh the changed screen lines -----------------------
2912// Copy the source line from text[] into the buffer and note
2913// if the current screenline is different from the new buffer.
2914// If they differ then that line needs redrawing on the terminal.
2915//
2916static void refresh(int full_screen)
2917{
2918#define old_offset refresh__old_offset
2919
2920 int li, changed;
2921 char *tp, *sp; // pointer into text[] and screen[]
2922
2923 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
2924 unsigned c = columns, r = rows;
2925 query_screen_dimensions();
2926 full_screen |= (c - columns) | (r - rows);
2927 }
2928 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2929 tp = screenbegin; // index into text[] of top line
2930
2931 // compare text[] to screen[] and mark screen[] lines that need updating
2932 for (li = 0; li < rows - 1; li++) {
2933 int cs, ce; // column start & end
2934 char *out_buf;
2935 // format current text line
2936 out_buf = format_line(tp /*, li*/);
2937
2938 // skip to the end of the current text[] line
2939 if (tp < end) {
2940 char *t = memchr(tp, '\n', end - tp);
2941 if (!t) t = end - 1;
2942 tp = t + 1;
2943 }
2944
2945 // see if there are any changes between vitual screen and out_buf
2946 changed = FALSE; // assume no change
2947 cs = 0;
2948 ce = columns - 1;
2949 sp = &screen[li * columns]; // start of screen line
2950 if (full_screen) {
2951 // force re-draw of every single column from 0 - columns-1
2952 goto re0;
2953 }
2954 // compare newly formatted buffer with virtual screen
2955 // look forward for first difference between buf and screen
2956 for (; cs <= ce; cs++) {
2957 if (out_buf[cs] != sp[cs]) {
2958 changed = TRUE; // mark for redraw
2959 break;
2960 }
2961 }
2962
2963 // look backward for last difference between out_buf and screen
2964 for (; ce >= cs; ce--) {
2965 if (out_buf[ce] != sp[ce]) {
2966 changed = TRUE; // mark for redraw
2967 break;
2968 }
2969 }
2970 // now, cs is index of first diff, and ce is index of last diff
2971
2972 // if horz offset has changed, force a redraw
2973 if (offset != old_offset) {
2974 re0:
2975 changed = TRUE;
2976 }
2977
2978 // make a sanity check of columns indexes
2979 if (cs < 0) cs = 0;
2980 if (ce > columns - 1) ce = columns - 1;
2981 if (cs > ce) { cs = 0; ce = columns - 1; }
2982 // is there a change between vitual screen and out_buf
2983 if (changed) {
2984 // copy changed part of buffer to virtual screen
2985 memcpy(sp+cs, out_buf+cs, ce-cs+1);
2986 place_cursor(li, cs);
2987 // write line out to terminal
2988 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
2989 }
2990 }
2991
2992 place_cursor(crow, ccol);
2993
2994 old_offset = offset;
2995#undef old_offset
2996}
2997
2998//---------------------------------------------------------------------
2999//----- the Ascii Chart -----------------------------------------------
3000//
3001// 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3002// 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3003// 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3004// 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3005// 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3006// 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3007// 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3008// 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3009// 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3010// 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3011// 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3012// 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3013// 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3014// 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3015// 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3016// 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3017//---------------------------------------------------------------------
3018
3019//----- Execute a Vi Command -----------------------------------
3020static void do_cmd(int c)
3021{
3022 char *p, *q, *save_dot;
3023 char buf[12];
3024 int dir;
3025 int cnt, i, j;
3026 int c1;
3027
3028// c1 = c; // quiet the compiler
3029// cnt = yf = 0; // quiet the compiler
3030// p = q = save_dot = buf; // quiet the compiler
3031 memset(buf, '\0', sizeof(buf));
3032
3033 show_status_line();
3034
3035 /* if this is a cursor key, skip these checks */
3036 switch (c) {
3037 case KEYCODE_UP:
3038 case KEYCODE_DOWN:
3039 case KEYCODE_LEFT:
3040 case KEYCODE_RIGHT:
3041 case KEYCODE_HOME:
3042 case KEYCODE_END:
3043 case KEYCODE_PAGEUP:
3044 case KEYCODE_PAGEDOWN:
3045 case KEYCODE_DELETE:
3046 goto key_cmd_mode;
3047 }
3048
3049 if (cmd_mode == 2) {
3050 // flip-flop Insert/Replace mode
3051 if (c == KEYCODE_INSERT)
3052 goto dc_i;
3053 // we are 'R'eplacing the current *dot with new char
3054 if (*dot == '\n') {
3055 // don't Replace past E-o-l
3056 cmd_mode = 1; // convert to insert
3057 } else {
3058 if (1 <= c || Isprint(c)) {
3059 if (c != 27)
3060 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3061 dot = char_insert(dot, c); // insert new char
3062 }
3063 goto dc1;
3064 }
3065 }
3066 if (cmd_mode == 1) {
3067 // hitting "Insert" twice means "R" replace mode
3068 if (c == KEYCODE_INSERT) goto dc5;
3069 // insert the char c at "dot"
3070 if (1 <= c || Isprint(c)) {
3071 dot = char_insert(dot, c);
3072 }
3073 goto dc1;
3074 }
3075
3076 key_cmd_mode:
3077 switch (c) {
3078 //case 0x01: // soh
3079 //case 0x09: // ht
3080 //case 0x0b: // vt
3081 //case 0x0e: // so
3082 //case 0x0f: // si
3083 //case 0x10: // dle
3084 //case 0x11: // dc1
3085 //case 0x13: // dc3
3086#if ENABLE_FEATURE_VI_CRASHME
3087 case 0x14: // dc4 ctrl-T
3088 crashme = (crashme == 0) ? 1 : 0;
3089 break;
3090#endif
3091 //case 0x16: // syn
3092 //case 0x17: // etb
3093 //case 0x18: // can
3094 //case 0x1c: // fs
3095 //case 0x1d: // gs
3096 //case 0x1e: // rs
3097 //case 0x1f: // us
3098 //case '!': // !-
3099 //case '#': // #-
3100 //case '&': // &-
3101 //case '(': // (-
3102 //case ')': // )-
3103 //case '*': // *-
3104 //case '=': // =-
3105 //case '@': // @-
3106 //case 'F': // F-
3107 //case 'K': // K-
3108 //case 'Q': // Q-
3109 //case 'S': // S-
3110 //case 'T': // T-
3111 //case 'V': // V-
3112 //case '[': // [-
3113 //case '\\': // \-
3114 //case ']': // ]-
3115 //case '_': // _-
3116 //case '`': // `-
3117 //case 'u': // u- FIXME- there is no undo
3118 //case 'v': // v-
3119 default: // unrecognized command
3120 buf[0] = c;
3121 buf[1] = '\0';
3122 not_implemented(buf);
3123 end_cmd_q(); // stop adding to q
3124 case 0x00: // nul- ignore
3125 break;
3126 case 2: // ctrl-B scroll up full screen
3127 case KEYCODE_PAGEUP: // Cursor Key Page Up
3128 dot_scroll(rows - 2, -1);
3129 break;
3130 case 4: // ctrl-D scroll down half screen
3131 dot_scroll((rows - 2) / 2, 1);
3132 break;
3133 case 5: // ctrl-E scroll down one line
3134 dot_scroll(1, 1);
3135 break;
3136 case 6: // ctrl-F scroll down full screen
3137 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3138 dot_scroll(rows - 2, 1);
3139 break;
3140 case 7: // ctrl-G show current status
3141 last_status_cksum = 0; // force status update
3142 break;
3143 case 'h': // h- move left
3144 case KEYCODE_LEFT: // cursor key Left
3145 case 8: // ctrl-H- move left (This may be ERASE char)
3146 case 0x7f: // DEL- move left (This may be ERASE char)
3147 do {
3148 dot_left();
3149 } while (--cmdcnt > 0);
3150 break;
3151 case 10: // Newline ^J
3152 case 'j': // j- goto next line, same col
3153 case KEYCODE_DOWN: // cursor key Down
3154 do {
3155 dot_next(); // go to next B-o-l
3156 // try stay in same col
3157 dot = move_to_col(dot, ccol + offset);
3158 } while (--cmdcnt > 0);
3159 break;
3160 case 12: // ctrl-L force redraw whole screen
3161 case 18: // ctrl-R force redraw
3162 place_cursor(0, 0);
3163 clear_to_eos();
3164 //mysleep(10); // why???
3165 screen_erase(); // erase the internal screen buffer
3166 last_status_cksum = 0; // force status update
3167 refresh(TRUE); // this will redraw the entire display
3168 break;
3169 case 13: // Carriage Return ^M
3170 case '+': // +- goto next line
3171 do {
3172 dot_next();
3173 dot_skip_over_ws();
3174 } while (--cmdcnt > 0);
3175 break;
3176 case 21: // ctrl-U scroll up half screen
3177 dot_scroll((rows - 2) / 2, -1);
3178 break;
3179 case 25: // ctrl-Y scroll up one line
3180 dot_scroll(1, -1);
3181 break;
3182 case 27: // esc
3183 if (cmd_mode == 0)
3184 indicate_error(c);
3185 cmd_mode = 0; // stop insrting
3186 end_cmd_q();
3187 last_status_cksum = 0; // force status update
3188 break;
3189 case ' ': // move right
3190 case 'l': // move right
3191 case KEYCODE_RIGHT: // Cursor Key Right
3192 do {
3193 dot_right();
3194 } while (--cmdcnt > 0);
3195 break;
3196#if ENABLE_FEATURE_VI_YANKMARK
3197 case '"': // "- name a register to use for Delete/Yank
3198 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3199 if ((unsigned)c1 <= 25) { // a-z?
3200 YDreg = c1;
3201 } else {
3202 indicate_error(c);
3203 }
3204 break;
3205 case '\'': // '- goto a specific mark
3206 c1 = (get_one_char() | 0x20) - 'a';
3207 if ((unsigned)c1 <= 25) { // a-z?
3208 // get the b-o-l
3209 q = mark[c1];
3210 if (text <= q && q < end) {
3211 dot = q;
3212 dot_begin(); // go to B-o-l
3213 dot_skip_over_ws();
3214 }
3215 } else if (c1 == '\'') { // goto previous context
3216 dot = swap_context(dot); // swap current and previous context
3217 dot_begin(); // go to B-o-l
3218 dot_skip_over_ws();
3219 } else {
3220 indicate_error(c);
3221 }
3222 break;
3223 case 'm': // m- Mark a line
3224 // this is really stupid. If there are any inserts or deletes
3225 // between text[0] and dot then this mark will not point to the
3226 // correct location! It could be off by many lines!
3227 // Well..., at least its quick and dirty.
3228 c1 = (get_one_char() | 0x20) - 'a';
3229 if ((unsigned)c1 <= 25) { // a-z?
3230 // remember the line
3231 mark[c1] = dot;
3232 } else {
3233 indicate_error(c);
3234 }
3235 break;
3236 case 'P': // P- Put register before
3237 case 'p': // p- put register after
3238 p = reg[YDreg];
3239 if (p == NULL) {
3240 status_line_bold("Nothing in register %c", what_reg());
3241 break;
3242 }
3243 // are we putting whole lines or strings
3244 if (strchr(p, '\n') != NULL) {
3245 if (c == 'P') {
3246 dot_begin(); // putting lines- Put above
3247 }
3248 if (c == 'p') {
3249 // are we putting after very last line?
3250 if (end_line(dot) == (end - 1)) {
3251 dot = end; // force dot to end of text[]
3252 } else {
3253 dot_next(); // next line, then put before
3254 }
3255 }
3256 } else {
3257 if (c == 'p')
3258 dot_right(); // move to right, can move to NL
3259 }
3260 string_insert(dot, p); // insert the string
3261 end_cmd_q(); // stop adding to q
3262 break;
3263 case 'U': // U- Undo; replace current line with original version
3264 if (reg[Ureg] != NULL) {
3265 p = begin_line(dot);
3266 q = end_line(dot);
3267 p = text_hole_delete(p, q); // delete cur line
3268 p += string_insert(p, reg[Ureg]); // insert orig line
3269 dot = p;
3270 dot_skip_over_ws();
3271 }
3272 break;
3273#endif /* FEATURE_VI_YANKMARK */
3274 case '$': // $- goto end of line
3275 case KEYCODE_END: // Cursor Key End
3276 for (;;) {
3277 dot = end_line(dot);
3278 if (--cmdcnt <= 0)
3279 break;
3280 dot_next();
3281 }
3282 break;
3283 case '%': // %- find matching char of pair () [] {}
3284 for (q = dot; q < end && *q != '\n'; q++) {
3285 if (strchr("()[]{}", *q) != NULL) {
3286 // we found half of a pair
3287 p = find_pair(q, *q);
3288 if (p == NULL) {
3289 indicate_error(c);
3290 } else {
3291 dot = p;
3292 }
3293 break;
3294 }
3295 }
3296 if (*q == '\n')
3297 indicate_error(c);
3298 break;
3299 case 'f': // f- forward to a user specified char
3300 last_forward_char = get_one_char(); // get the search char
3301 //
3302 // dont separate these two commands. 'f' depends on ';'
3303 //
3304 //**** fall through to ... ';'
3305 case ';': // ;- look at rest of line for last forward char
3306 do {
3307 if (last_forward_char == 0)
3308 break;
3309 q = dot + 1;
3310 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3311 q++;
3312 }
3313 if (*q == last_forward_char)
3314 dot = q;
3315 } while (--cmdcnt > 0);
3316 break;
3317 case ',': // repeat latest 'f' in opposite direction
3318 if (last_forward_char == 0)
3319 break;
3320 do {
3321 q = dot - 1;
3322 while (q >= text && *q != '\n' && *q != last_forward_char) {
3323 q--;
3324 }
3325 if (q >= text && *q == last_forward_char)
3326 dot = q;
3327 } while (--cmdcnt > 0);
3328 break;
3329
3330 case '-': // -- goto prev line
3331 do {
3332 dot_prev();
3333 dot_skip_over_ws();
3334 } while (--cmdcnt > 0);
3335 break;
3336#if ENABLE_FEATURE_VI_DOT_CMD
3337 case '.': // .- repeat the last modifying command
3338 // Stuff the last_modifying_cmd back into stdin
3339 // and let it be re-executed.
3340 if (lmc_len > 0) {
3341 last_modifying_cmd[lmc_len] = 0;
3342 ioq = ioq_start = xstrdup(last_modifying_cmd);
3343 }
3344 break;
3345#endif
3346#if ENABLE_FEATURE_VI_SEARCH
3347 case '?': // /- search for a pattern
3348 case '/': // /- search for a pattern
3349 buf[0] = c;
3350 buf[1] = '\0';
3351 q = get_input_line(buf); // get input line- use "status line"
3352 if (q[0] && !q[1]) {
3353 if (last_search_pattern[0])
3354 last_search_pattern[0] = c;
3355 goto dc3; // if no pat re-use old pat
3356 }
3357 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3358 // there is a new pat
3359 free(last_search_pattern);
3360 last_search_pattern = xstrdup(q);
3361 goto dc3; // now find the pattern
3362 }
3363 // user changed mind and erased the "/"- do nothing
3364 break;
3365 case 'N': // N- backward search for last pattern
3366 dir = BACK; // assume BACKWARD search
3367 p = dot - 1;
3368 if (last_search_pattern[0] == '?') {
3369 dir = FORWARD;
3370 p = dot + 1;
3371 }
3372 goto dc4; // now search for pattern
3373 break;
3374 case 'n': // n- repeat search for last pattern
3375 // search rest of text[] starting at next char
3376 // if search fails return orignal "p" not the "p+1" address
3377 do {
3378 const char *msg;
3379 dc3:
3380 dir = FORWARD; // assume FORWARD search
3381 p = dot + 1;
3382 if (last_search_pattern[0] == '?') {
3383 dir = BACK;
3384 p = dot - 1;
3385 }
3386 dc4:
3387 q = char_search(p, last_search_pattern + 1, dir, FULL);
3388 if (q != NULL) {
3389 dot = q; // good search, update "dot"
3390 msg = NULL;
3391 goto dc2;
3392 }
3393 // no pattern found between "dot" and "end"- continue at top
3394 p = text;
3395 if (dir == BACK) {
3396 p = end - 1;
3397 }
3398 q = char_search(p, last_search_pattern + 1, dir, FULL);
3399 if (q != NULL) { // found something
3400 dot = q; // found new pattern- goto it
3401 msg = "search hit BOTTOM, continuing at TOP";
3402 if (dir == BACK) {
3403 msg = "search hit TOP, continuing at BOTTOM";
3404 }
3405 } else {
3406 msg = "Pattern not found";
3407 }
3408 dc2:
3409 if (msg)
3410 status_line_bold("%s", msg);
3411 } while (--cmdcnt > 0);
3412 break;
3413 case '{': // {- move backward paragraph
3414 q = char_search(dot, "\n\n", BACK, FULL);
3415 if (q != NULL) { // found blank line
3416 dot = next_line(q); // move to next blank line
3417 }
3418 break;
3419 case '}': // }- move forward paragraph
3420 q = char_search(dot, "\n\n", FORWARD, FULL);
3421 if (q != NULL) { // found blank line
3422 dot = next_line(q); // move to next blank line
3423 }
3424 break;
3425#endif /* FEATURE_VI_SEARCH */
3426 case '0': // 0- goto begining of line
3427 case '1': // 1-
3428 case '2': // 2-
3429 case '3': // 3-
3430 case '4': // 4-
3431 case '5': // 5-
3432 case '6': // 6-
3433 case '7': // 7-
3434 case '8': // 8-
3435 case '9': // 9-
3436 if (c == '0' && cmdcnt < 1) {
3437 dot_begin(); // this was a standalone zero
3438 } else {
3439 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3440 }
3441 break;
3442 case ':': // :- the colon mode commands
3443 p = get_input_line(":"); // get input line- use "status line"
3444#if ENABLE_FEATURE_VI_COLON
3445 colon(p); // execute the command
3446#else
3447 if (*p == ':')
3448 p++; // move past the ':'
3449 cnt = strlen(p);
3450 if (cnt <= 0)
3451 break;
3452 if (strncmp(p, "quit", cnt) == 0
3453 || strncmp(p, "q!", cnt) == 0 // delete lines
3454 ) {
3455 if (file_modified && p[1] != '!') {
3456 status_line_bold("No write since last change (:%s! overrides)", p);
3457 } else {
3458 editing = 0;
3459 }
3460 } else if (strncmp(p, "write", cnt) == 0
3461 || strncmp(p, "wq", cnt) == 0
3462 || strncmp(p, "wn", cnt) == 0
3463 || (p[0] == 'x' && !p[1])
3464 ) {
3465 cnt = file_write(current_filename, text, end - 1);
3466 if (cnt < 0) {
3467 if (cnt == -1)
3468 status_line_bold("Write error: %s", strerror(errno));
3469 } else {
3470 file_modified = 0;
3471 last_file_modified = -1;
3472 status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3473 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3474 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3475 ) {
3476 editing = 0;
3477 }
3478 }
3479 } else if (strncmp(p, "file", cnt) == 0) {
3480 last_status_cksum = 0; // force status update
3481 } else if (sscanf(p, "%d", &j) > 0) {
3482 dot = find_line(j); // go to line # j
3483 dot_skip_over_ws();
3484 } else { // unrecognized cmd
3485 not_implemented(p);
3486 }
3487#endif /* !FEATURE_VI_COLON */
3488 break;
3489 case '<': // <- Left shift something
3490 case '>': // >- Right shift something
3491 cnt = count_lines(text, dot); // remember what line we are on
3492 c1 = get_one_char(); // get the type of thing to delete
3493 find_range(&p, &q, c1);
3494 yank_delete(p, q, 1, YANKONLY); // save copy before change
3495 p = begin_line(p);
3496 q = end_line(q);
3497 i = count_lines(p, q); // # of lines we are shifting
3498 for ( ; i > 0; i--, p = next_line(p)) {
3499 if (c == '<') {
3500 // shift left- remove tab or 8 spaces
3501 if (*p == '\t') {
3502 // shrink buffer 1 char
3503 text_hole_delete(p, p);
3504 } else if (*p == ' ') {
3505 // we should be calculating columns, not just SPACE
3506 for (j = 0; *p == ' ' && j < tabstop; j++) {
3507 text_hole_delete(p, p);
3508 }
3509 }
3510 } else if (c == '>') {
3511 // shift right -- add tab or 8 spaces
3512 char_insert(p, '\t');
3513 }
3514 }
3515 dot = find_line(cnt); // what line were we on
3516 dot_skip_over_ws();
3517 end_cmd_q(); // stop adding to q
3518 break;
3519 case 'A': // A- append at e-o-l
3520 dot_end(); // go to e-o-l
3521 //**** fall through to ... 'a'
3522 case 'a': // a- append after current char
3523 if (*dot != '\n')
3524 dot++;
3525 goto dc_i;
3526 break;
3527 case 'B': // B- back a blank-delimited Word
3528 case 'E': // E- end of a blank-delimited word
3529 case 'W': // W- forward a blank-delimited word
3530 dir = FORWARD;
3531 if (c == 'B')
3532 dir = BACK;
3533 do {
3534 if (c == 'W' || isspace(dot[dir])) {
3535 dot = skip_thing(dot, 1, dir, S_TO_WS);
3536 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3537 }
3538 if (c != 'W')
3539 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3540 } while (--cmdcnt > 0);
3541 break;
3542 case 'C': // C- Change to e-o-l
3543 case 'D': // D- delete to e-o-l
3544 save_dot = dot;
3545 dot = dollar_line(dot); // move to before NL
3546 // copy text into a register and delete
3547 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3548 if (c == 'C')
3549 goto dc_i; // start inserting
3550#if ENABLE_FEATURE_VI_DOT_CMD
3551 if (c == 'D')
3552 end_cmd_q(); // stop adding to q
3553#endif
3554 break;
3555 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3556 c1 = get_one_char();
3557 if (c1 != 'g') {
3558 buf[0] = 'g';
3559 buf[1] = c1; // TODO: if Unicode?
3560 buf[2] = '\0';
3561 not_implemented(buf);
3562 break;
3563 }
3564 if (cmdcnt == 0)
3565 cmdcnt = 1;
3566 /* fall through */
3567 case 'G': // G- goto to a line number (default= E-O-F)
3568 dot = end - 1; // assume E-O-F
3569 if (cmdcnt > 0) {
3570 dot = find_line(cmdcnt); // what line is #cmdcnt
3571 }
3572 dot_skip_over_ws();
3573 break;
3574 case 'H': // H- goto top line on screen
3575 dot = screenbegin;
3576 if (cmdcnt > (rows - 1)) {
3577 cmdcnt = (rows - 1);
3578 }
3579 if (--cmdcnt > 0) {
3580 do_cmd('+');
3581 }
3582 dot_skip_over_ws();
3583 break;
3584 case 'I': // I- insert before first non-blank
3585 dot_begin(); // 0
3586 dot_skip_over_ws();
3587 //**** fall through to ... 'i'
3588 case 'i': // i- insert before current char
3589 case KEYCODE_INSERT: // Cursor Key Insert
3590 dc_i:
3591 cmd_mode = 1; // start inserting
3592 break;
3593 case 'J': // J- join current and next lines together
3594 do {
3595 dot_end(); // move to NL
3596 if (dot < end - 1) { // make sure not last char in text[]
3597 *dot++ = ' '; // replace NL with space
3598 file_modified++;
3599 while (isblank(*dot)) { // delete leading WS
3600 dot_delete();
3601 }
3602 }
3603 } while (--cmdcnt > 0);
3604 end_cmd_q(); // stop adding to q
3605 break;
3606 case 'L': // L- goto bottom line on screen
3607 dot = end_screen();
3608 if (cmdcnt > (rows - 1)) {
3609 cmdcnt = (rows - 1);
3610 }
3611 if (--cmdcnt > 0) {
3612 do_cmd('-');
3613 }
3614 dot_begin();
3615 dot_skip_over_ws();
3616 break;
3617 case 'M': // M- goto middle line on screen
3618 dot = screenbegin;
3619 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3620 dot = next_line(dot);
3621 break;
3622 case 'O': // O- open a empty line above
3623 // 0i\n ESC -i
3624 p = begin_line(dot);
3625 if (p[-1] == '\n') {
3626 dot_prev();
3627 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3628 dot_end();
3629 dot = char_insert(dot, '\n');
3630 } else {
3631 dot_begin(); // 0
3632 dot = char_insert(dot, '\n'); // i\n ESC
3633 dot_prev(); // -
3634 }
3635 goto dc_i;
3636 break;
3637 case 'R': // R- continuous Replace char
3638 dc5:
3639 cmd_mode = 2;
3640 break;
3641 case KEYCODE_DELETE:
3642 c = 'x';
3643 // fall through
3644 case 'X': // X- delete char before dot
3645 case 'x': // x- delete the current char
3646 case 's': // s- substitute the current char
3647 dir = 0;
3648 if (c == 'X')
3649 dir = -1;
3650 do {
3651 if (dot[dir] != '\n') {
3652 if (c == 'X')
3653 dot--; // delete prev char
3654 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3655 }
3656 } while (--cmdcnt > 0);
3657 end_cmd_q(); // stop adding to q
3658 if (c == 's')
3659 goto dc_i; // start inserting
3660 break;
3661 case 'Z': // Z- if modified, {write}; exit
3662 // ZZ means to save file (if necessary), then exit
3663 c1 = get_one_char();
3664 if (c1 != 'Z') {
3665 indicate_error(c);
3666 break;
3667 }
3668 if (file_modified) {
3669 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3670 status_line_bold("\"%s\" File is read only", current_filename);
3671 break;
3672 }
3673 cnt = file_write(current_filename, text, end - 1);
3674 if (cnt < 0) {
3675 if (cnt == -1)
3676 status_line_bold("Write error: %s", strerror(errno));
3677 } else if (cnt == (end - 1 - text + 1)) {
3678 editing = 0;
3679 }
3680 } else {
3681 editing = 0;
3682 }
3683 break;
3684 case '^': // ^- move to first non-blank on line
3685 dot_begin();
3686 dot_skip_over_ws();
3687 break;
3688 case 'b': // b- back a word
3689 case 'e': // e- end of word
3690 dir = FORWARD;
3691 if (c == 'b')
3692 dir = BACK;
3693 do {
3694 if ((dot + dir) < text || (dot + dir) > end - 1)
3695 break;
3696 dot += dir;
3697 if (isspace(*dot)) {
3698 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3699 }
3700 if (isalnum(*dot) || *dot == '_') {
3701 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3702 } else if (ispunct(*dot)) {
3703 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3704 }
3705 } while (--cmdcnt > 0);
3706 break;
3707 case 'c': // c- change something
3708 case 'd': // d- delete something
3709#if ENABLE_FEATURE_VI_YANKMARK
3710 case 'y': // y- yank something
3711 case 'Y': // Y- Yank a line
3712#endif
3713 {
3714 int yf, ml, whole = 0;
3715 yf = YANKDEL; // assume either "c" or "d"
3716#if ENABLE_FEATURE_VI_YANKMARK
3717 if (c == 'y' || c == 'Y')
3718 yf = YANKONLY;
3719#endif
3720 c1 = 'y';
3721 if (c != 'Y')
3722 c1 = get_one_char(); // get the type of thing to delete
3723 // determine range, and whether it spans lines
3724 ml = find_range(&p, &q, c1);
3725 if (c1 == 27) { // ESC- user changed mind and wants out
3726 c = c1 = 27; // Escape- do nothing
3727 } else if (strchr("wW", c1)) {
3728 if (c == 'c') {
3729 // don't include trailing WS as part of word
3730 while (isblank(*q)) {
3731 if (q <= text || q[-1] == '\n')
3732 break;
3733 q--;
3734 }
3735 }
3736 dot = yank_delete(p, q, ml, yf); // delete word
3737 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3738 // partial line copy text into a register and delete
3739 dot = yank_delete(p, q, ml, yf); // delete word
3740 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
3741 // whole line copy text into a register and delete
3742 dot = yank_delete(p, q, ml, yf); // delete lines
3743 whole = 1;
3744 } else {
3745 // could not recognize object
3746 c = c1 = 27; // error-
3747 ml = 0;
3748 indicate_error(c);
3749 }
3750 if (ml && whole) {
3751 if (c == 'c') {
3752 dot = char_insert(dot, '\n');
3753 // on the last line of file don't move to prev line
3754 if (whole && dot != (end-1)) {
3755 dot_prev();
3756 }
3757 } else if (c == 'd') {
3758 dot_begin();
3759 dot_skip_over_ws();
3760 }
3761 }
3762 if (c1 != 27) {
3763 // if CHANGING, not deleting, start inserting after the delete
3764 if (c == 'c') {
3765 strcpy(buf, "Change");
3766 goto dc_i; // start inserting
3767 }
3768 if (c == 'd') {
3769 strcpy(buf, "Delete");
3770 }
3771#if ENABLE_FEATURE_VI_YANKMARK
3772 if (c == 'y' || c == 'Y') {
3773 strcpy(buf, "Yank");
3774 }
3775 p = reg[YDreg];
3776 q = p + strlen(p);
3777 for (cnt = 0; p <= q; p++) {
3778 if (*p == '\n')
3779 cnt++;
3780 }
3781 status_line("%s %d lines (%d chars) using [%c]",
3782 buf, cnt, strlen(reg[YDreg]), what_reg());
3783#endif
3784 end_cmd_q(); // stop adding to q
3785 }
3786 break;
3787 }
3788 case 'k': // k- goto prev line, same col
3789 case KEYCODE_UP: // cursor key Up
3790 do {
3791 dot_prev();
3792 dot = move_to_col(dot, ccol + offset); // try stay in same col
3793 } while (--cmdcnt > 0);
3794 break;
3795 case 'r': // r- replace the current char with user input
3796 c1 = get_one_char(); // get the replacement char
3797 if (*dot != '\n') {
3798 *dot = c1;
3799 file_modified++;
3800 }
3801 end_cmd_q(); // stop adding to q
3802 break;
3803 case 't': // t- move to char prior to next x
3804 last_forward_char = get_one_char();
3805 do_cmd(';');
3806 if (*dot == last_forward_char)
3807 dot_left();
3808 last_forward_char = 0;
3809 break;
3810 case 'w': // w- forward a word
3811 do {
3812 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3813 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3814 } else if (ispunct(*dot)) { // we are on PUNCT
3815 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3816 }
3817 if (dot < end - 1)
3818 dot++; // move over word
3819 if (isspace(*dot)) {
3820 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3821 }
3822 } while (--cmdcnt > 0);
3823 break;
3824 case 'z': // z-
3825 c1 = get_one_char(); // get the replacement char
3826 cnt = 0;
3827 if (c1 == '.')
3828 cnt = (rows - 2) / 2; // put dot at center
3829 if (c1 == '-')
3830 cnt = rows - 2; // put dot at bottom
3831 screenbegin = begin_line(dot); // start dot at top
3832 dot_scroll(cnt, -1);
3833 break;
3834 case '|': // |- move to column "cmdcnt"
3835 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3836 break;
3837 case '~': // ~- flip the case of letters a-z -> A-Z
3838 do {
3839 if (islower(*dot)) {
3840 *dot = toupper(*dot);
3841 file_modified++;
3842 } else if (isupper(*dot)) {
3843 *dot = tolower(*dot);
3844 file_modified++;
3845 }
3846 dot_right();
3847 } while (--cmdcnt > 0);
3848 end_cmd_q(); // stop adding to q
3849 break;
3850 //----- The Cursor and Function Keys -----------------------------
3851 case KEYCODE_HOME: // Cursor Key Home
3852 dot_begin();
3853 break;
3854 // The Fn keys could point to do_macro which could translate them
3855#if 0
3856 case KEYCODE_FUN1: // Function Key F1
3857 case KEYCODE_FUN2: // Function Key F2
3858 case KEYCODE_FUN3: // Function Key F3
3859 case KEYCODE_FUN4: // Function Key F4
3860 case KEYCODE_FUN5: // Function Key F5
3861 case KEYCODE_FUN6: // Function Key F6
3862 case KEYCODE_FUN7: // Function Key F7
3863 case KEYCODE_FUN8: // Function Key F8
3864 case KEYCODE_FUN9: // Function Key F9
3865 case KEYCODE_FUN10: // Function Key F10
3866 case KEYCODE_FUN11: // Function Key F11
3867 case KEYCODE_FUN12: // Function Key F12
3868 break;
3869#endif
3870 }
3871
3872 dc1:
3873 // if text[] just became empty, add back an empty line
3874 if (end == text) {
3875 char_insert(text, '\n'); // start empty buf with dummy line
3876 dot = text;
3877 }
3878 // it is OK for dot to exactly equal to end, otherwise check dot validity
3879 if (dot != end) {
3880 dot = bound_dot(dot); // make sure "dot" is valid
3881 }
3882#if ENABLE_FEATURE_VI_YANKMARK
3883 check_context(c); // update the current context
3884#endif
3885
3886 if (!isdigit(c))
3887 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3888 cnt = dot - begin_line(dot);
3889 // Try to stay off of the Newline
3890 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3891 dot--;
3892}
3893
3894/* NB! the CRASHME code is unmaintained, and doesn't currently build */
3895#if ENABLE_FEATURE_VI_CRASHME
3896static int totalcmds = 0;
3897static int Mp = 85; // Movement command Probability
3898static int Np = 90; // Non-movement command Probability
3899static int Dp = 96; // Delete command Probability
3900static int Ip = 97; // Insert command Probability
3901static int Yp = 98; // Yank command Probability
3902static int Pp = 99; // Put command Probability
3903static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3904static const char chars[20] = "\t012345 abcdABCD-=.$";
3905static const char *const words[20] = {
3906 "this", "is", "a", "test",
3907 "broadcast", "the", "emergency", "of",
3908 "system", "quick", "brown", "fox",
3909 "jumped", "over", "lazy", "dogs",
3910 "back", "January", "Febuary", "March"
3911};
3912static const char *const lines[20] = {
3913 "You should have received a copy of the GNU General Public License\n",
3914 "char c, cm, *cmd, *cmd1;\n",
3915 "generate a command by percentages\n",
3916 "Numbers may be typed as a prefix to some commands.\n",
3917 "Quit, discarding changes!\n",
3918 "Forced write, if permission originally not valid.\n",
3919 "In general, any ex or ed command (such as substitute or delete).\n",
3920 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3921 "Please get w/ me and I will go over it with you.\n",
3922 "The following is a list of scheduled, committed changes.\n",
3923 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3924 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3925 "Any question about transactions please contact Sterling Huxley.\n",
3926 "I will try to get back to you by Friday, December 31.\n",
3927 "This Change will be implemented on Friday.\n",
3928 "Let me know if you have problems accessing this;\n",
3929 "Sterling Huxley recently added you to the access list.\n",
3930 "Would you like to go to lunch?\n",
3931 "The last command will be automatically run.\n",
3932 "This is too much english for a computer geek.\n",
3933};
3934static char *multilines[20] = {
3935 "You should have received a copy of the GNU General Public License\n",
3936 "char c, cm, *cmd, *cmd1;\n",
3937 "generate a command by percentages\n",
3938 "Numbers may be typed as a prefix to some commands.\n",
3939 "Quit, discarding changes!\n",
3940 "Forced write, if permission originally not valid.\n",
3941 "In general, any ex or ed command (such as substitute or delete).\n",
3942 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3943 "Please get w/ me and I will go over it with you.\n",
3944 "The following is a list of scheduled, committed changes.\n",
3945 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3946 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3947 "Any question about transactions please contact Sterling Huxley.\n",
3948 "I will try to get back to you by Friday, December 31.\n",
3949 "This Change will be implemented on Friday.\n",
3950 "Let me know if you have problems accessing this;\n",
3951 "Sterling Huxley recently added you to the access list.\n",
3952 "Would you like to go to lunch?\n",
3953 "The last command will be automatically run.\n",
3954 "This is too much english for a computer geek.\n",
3955};
3956
3957// create a random command to execute
3958static void crash_dummy()
3959{
3960 static int sleeptime; // how long to pause between commands
3961 char c, cm, *cmd, *cmd1;
3962 int i, cnt, thing, rbi, startrbi, percent;
3963
3964 // "dot" movement commands
3965 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3966
3967 // is there already a command running?
3968 if (readbuffer[0] > 0)
3969 goto cd1;
3970 cd0:
3971 readbuffer[0] = 'X';
3972 startrbi = rbi = 1;
3973 sleeptime = 0; // how long to pause between commands
3974 memset(readbuffer, '\0', sizeof(readbuffer));
3975 // generate a command by percentages
3976 percent = (int) lrand48() % 100; // get a number from 0-99
3977 if (percent < Mp) { // Movement commands
3978 // available commands
3979 cmd = cmd1;
3980 M++;
3981 } else if (percent < Np) { // non-movement commands
3982 cmd = "mz<>\'\""; // available commands
3983 N++;
3984 } else if (percent < Dp) { // Delete commands
3985 cmd = "dx"; // available commands
3986 D++;
3987 } else if (percent < Ip) { // Inset commands
3988 cmd = "iIaAsrJ"; // available commands
3989 I++;
3990 } else if (percent < Yp) { // Yank commands
3991 cmd = "yY"; // available commands
3992 Y++;
3993 } else if (percent < Pp) { // Put commands
3994 cmd = "pP"; // available commands
3995 P++;
3996 } else {
3997 // We do not know how to handle this command, try again
3998 U++;
3999 goto cd0;
4000 }
4001 // randomly pick one of the available cmds from "cmd[]"
4002 i = (int) lrand48() % strlen(cmd);
4003 cm = cmd[i];
4004 if (strchr(":\024", cm))
4005 goto cd0; // dont allow colon or ctrl-T commands
4006 readbuffer[rbi++] = cm; // put cmd into input buffer
4007
4008 // now we have the command-
4009 // there are 1, 2, and multi char commands
4010 // find out which and generate the rest of command as necessary
4011 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4012 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4013 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4014 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4015 }
4016 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4017 c = cmd1[thing];
4018 readbuffer[rbi++] = c; // add movement to input buffer
4019 }
4020 if (strchr("iIaAsc", cm)) { // multi-char commands
4021 if (cm == 'c') {
4022 // change some thing
4023 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4024 c = cmd1[thing];
4025 readbuffer[rbi++] = c; // add movement to input buffer
4026 }
4027 thing = (int) lrand48() % 4; // what thing to insert
4028 cnt = (int) lrand48() % 10; // how many to insert
4029 for (i = 0; i < cnt; i++) {
4030 if (thing == 0) { // insert chars
4031 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4032 } else if (thing == 1) { // insert words
4033 strcat(readbuffer, words[(int) lrand48() % 20]);
4034 strcat(readbuffer, " ");
4035 sleeptime = 0; // how fast to type
4036 } else if (thing == 2) { // insert lines
4037 strcat(readbuffer, lines[(int) lrand48() % 20]);
4038 sleeptime = 0; // how fast to type
4039 } else { // insert multi-lines
4040 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4041 sleeptime = 0; // how fast to type
4042 }
4043 }
4044 strcat(readbuffer, "\033");
4045 }
4046 readbuffer[0] = strlen(readbuffer + 1);
4047 cd1:
4048 totalcmds++;
4049 if (sleeptime > 0)
4050 mysleep(sleeptime); // sleep 1/100 sec
4051}
4052
4053// test to see if there are any errors
4054static void crash_test()
4055{
4056 static time_t oldtim;
4057
4058 time_t tim;
4059 char d[2], msg[80];
4060
4061 msg[0] = '\0';
4062 if (end < text) {
4063 strcat(msg, "end<text ");
4064 }
4065 if (end > textend) {
4066 strcat(msg, "end>textend ");
4067 }
4068 if (dot < text) {
4069 strcat(msg, "dot<text ");
4070 }
4071 if (dot > end) {
4072 strcat(msg, "dot>end ");
4073 }
4074 if (screenbegin < text) {
4075 strcat(msg, "screenbegin<text ");
4076 }
4077 if (screenbegin > end - 1) {
4078 strcat(msg, "screenbegin>end-1 ");
4079 }
4080
4081 if (msg[0]) {
4082 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4083 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4084 fflush_all();
4085 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4086 if (d[0] == '\n' || d[0] == '\r')
4087 break;
4088 }
4089 }
4090 tim = time(NULL);
4091 if (tim >= (oldtim + 3)) {
4092 sprintf(status_buffer,
4093 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4094 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4095 oldtim = tim;
4096 }
4097}
4098#endif
Note: See TracBrowser for help on using the repository browser.