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

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

prototoolに関連するプロジェクトをnewlibからmuslを使うよう変更・更新
ntshellをnewlibの下位の実装から、muslのsyscallの実装に変更・更新
以下のOSSをアップデート
・mruby-1.3.0
・musl-1.1.18
・onigmo-6.1.3
・tcc-0.9.27
以下のOSSを追加
・openssl-1.1.0e
・curl-7.57.0
・zlib-1.2.11
以下のmrbgemsを追加
・iij/mruby-digest
・iij/mruby-env
・iij/mruby-errno
・iij/mruby-iijson
・iij/mruby-ipaddr
・iij/mruby-mock
・iij/mruby-require
・iij/mruby-tls-openssl

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