source: EcnlProtoTool/trunk/prototool/src/libbb/vi.c@ 321

Last change on this file since 321 was 321, checked in by coas-nagasima, 7 years ago

文字コードを設定

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