blob: 8af1ef76be4cf04cf0acf980ae1c32eb671caeca [file] [log] [blame]
Rob Landleye5e1a102006-06-21 01:15:36 +00001/* vi: set sw=4 ts=4: */
Eric Andersen3f980402001-04-04 17:31:15 +00002/*
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
5 *
Denys Vlasenko0ef64bd2010-08-16 20:14:46 +02006 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
Eric Andersen3f980402001-04-04 17:31:15 +00007 */
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02008//
9//Things To Do:
10// EXINIT
11// $HOME/.exrc and ./.exrc
12// add magic to search /foo.*bar
13// add :help command
14// :map macros
15// if mark[] values were line numbers rather than pointers
16// it would be easier to change the mark when add/delete lines
17// More intelligence in refresh()
18// ":r !cmd" and "!cmd" to filter text through an external command
19// An "ex" line oriented mode- maybe using "cmdedit"
20
Walter Harmsb9ba5802011-06-27 02:59:37 +020021//config:config VI
Denys Vlasenkob097a842018-12-28 03:20:17 +010022//config: bool "vi (23 kb)"
Walter Harmsb9ba5802011-06-27 02:59:37 +020023//config: default y
24//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020025//config: 'vi' is a text editor. More specifically, it is the One True
26//config: text editor <grin>. It does, however, have a rather steep
27//config: learning curve. If you are not already comfortable with 'vi'
28//config: you may wish to use something else.
Walter Harmsb9ba5802011-06-27 02:59:37 +020029//config:
30//config:config FEATURE_VI_MAX_LEN
Denys Vlasenkof5604222017-01-10 14:58:54 +010031//config: int "Maximum screen width"
Walter Harmsb9ba5802011-06-27 02:59:37 +020032//config: range 256 16384
33//config: default 4096
34//config: depends on VI
35//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020036//config: Contrary to what you may think, this is not eating much.
37//config: Make it smaller than 4k only if you are very limited on memory.
Walter Harmsb9ba5802011-06-27 02:59:37 +020038//config:
39//config:config FEATURE_VI_8BIT
Denys Vlasenkof5604222017-01-10 14:58:54 +010040//config: bool "Allow to display 8-bit chars (otherwise shows dots)"
Walter Harmsb9ba5802011-06-27 02:59:37 +020041//config: default n
42//config: depends on VI
43//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020044//config: If your terminal can display characters with high bit set,
45//config: you may want to enable this. Note: vi is not Unicode-capable.
46//config: If your terminal combines several 8-bit bytes into one character
47//config: (as in Unicode mode), this will not work properly.
Walter Harmsb9ba5802011-06-27 02:59:37 +020048//config:
49//config:config FEATURE_VI_COLON
50//config: bool "Enable \":\" colon commands (no \"ex\" mode)"
51//config: default y
52//config: depends on VI
53//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020054//config: Enable a limited set of colon commands. This does not
55//config: provide an "ex" mode.
Walter Harmsb9ba5802011-06-27 02:59:37 +020056//config:
57//config:config FEATURE_VI_YANKMARK
58//config: bool "Enable yank/put commands and mark cmds"
59//config: default y
60//config: depends on VI
61//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020062//config: This enables you to use yank and put, as well as mark.
Walter Harmsb9ba5802011-06-27 02:59:37 +020063//config:
64//config:config FEATURE_VI_SEARCH
65//config: bool "Enable search and replace cmds"
66//config: default y
67//config: depends on VI
68//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020069//config: Select this if you wish to be able to do search and replace.
Walter Harmsb9ba5802011-06-27 02:59:37 +020070//config:
71//config:config FEATURE_VI_REGEX_SEARCH
72//config: bool "Enable regex in search and replace"
73//config: default n # Uses GNU regex, which may be unavailable. FIXME
74//config: depends on FEATURE_VI_SEARCH
75//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020076//config: Use extended regex search.
Walter Harmsb9ba5802011-06-27 02:59:37 +020077//config:
78//config:config FEATURE_VI_USE_SIGNALS
79//config: bool "Catch signals"
80//config: default y
81//config: depends on VI
82//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020083//config: Selecting this option will make vi signal aware. This will support
84//config: SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
Walter Harmsb9ba5802011-06-27 02:59:37 +020085//config:
86//config:config FEATURE_VI_DOT_CMD
87//config: bool "Remember previous cmd and \".\" cmd"
88//config: default y
89//config: depends on VI
90//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020091//config: Make vi remember the last command and be able to repeat it.
Walter Harmsb9ba5802011-06-27 02:59:37 +020092//config:
93//config:config FEATURE_VI_READONLY
94//config: bool "Enable -R option and \"view\" mode"
95//config: default y
96//config: depends on VI
97//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020098//config: Enable the read-only command line option, which allows the user to
99//config: open a file in read-only mode.
Walter Harmsb9ba5802011-06-27 02:59:37 +0200100//config:
101//config:config FEATURE_VI_SETOPTS
Denys Vlasenkof5604222017-01-10 14:58:54 +0100102//config: bool "Enable settable options, ai ic showmatch"
Walter Harmsb9ba5802011-06-27 02:59:37 +0200103//config: default y
104//config: depends on VI
105//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +0200106//config: Enable the editor to set some (ai, ic, showmatch) options.
Walter Harmsb9ba5802011-06-27 02:59:37 +0200107//config:
108//config:config FEATURE_VI_SET
Denys Vlasenkof5604222017-01-10 14:58:54 +0100109//config: bool "Support :set"
Walter Harmsb9ba5802011-06-27 02:59:37 +0200110//config: default y
111//config: depends on VI
Walter Harmsb9ba5802011-06-27 02:59:37 +0200112//config:
113//config:config FEATURE_VI_WIN_RESIZE
114//config: bool "Handle window resize"
115//config: default y
116//config: depends on VI
117//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +0200118//config: Behave nicely with terminals that get resized.
Walter Harmsb9ba5802011-06-27 02:59:37 +0200119//config:
120//config:config FEATURE_VI_ASK_TERMINAL
121//config: bool "Use 'tell me cursor position' ESC sequence to measure window"
122//config: default y
123//config: depends on VI
124//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +0200125//config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
126//config: this option makes vi perform a last-ditch effort to find it:
127//config: position cursor to 999,999 and ask terminal to report real
128//config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
129//config: This is not clean but helps a lot on serial lines and such.
Denys Vlasenkof5604222017-01-10 14:58:54 +0100130//config:
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200131//config:config FEATURE_VI_UNDO
Denys Vlasenkof5604222017-01-10 14:58:54 +0100132//config: bool "Support undo command \"u\""
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200133//config: default y
134//config: depends on VI
135//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +0200136//config: Support the 'u' command to undo insertion, deletion, and replacement
137//config: of text.
Denys Vlasenkof5604222017-01-10 14:58:54 +0100138//config:
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200139//config:config FEATURE_VI_UNDO_QUEUE
140//config: bool "Enable undo operation queuing"
141//config: default y
142//config: depends on FEATURE_VI_UNDO
143//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +0200144//config: The vi undo functions can use an intermediate queue to greatly lower
145//config: malloc() calls and overhead. When the maximum size of this queue is
146//config: reached, the contents of the queue are committed to the undo stack.
147//config: This increases the size of the undo code and allows some undo
148//config: operations (especially un-typing/backspacing) to be far more useful.
Denys Vlasenkof5604222017-01-10 14:58:54 +0100149//config:
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200150//config:config FEATURE_VI_UNDO_QUEUE_MAX
151//config: int "Maximum undo character queue size"
152//config: default 256
153//config: range 32 65536
154//config: depends on FEATURE_VI_UNDO_QUEUE
155//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +0200156//config: This option sets the number of bytes used at runtime for the queue.
157//config: Smaller values will create more undo objects and reduce the amount
158//config: of typed or backspaced characters that are grouped into one undo
159//config: operation; larger values increase the potential size of each undo
160//config: and will generally malloc() larger objects and less frequently.
161//config: Unless you want more (or less) frequent "undo points" while typing,
162//config: you should probably leave this unchanged.
Walter Harmsb9ba5802011-06-27 02:59:37 +0200163
164//applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
165
166//kbuild:lib-$(CONFIG_VI) += vi.o
167
Pere Orga6a3e01d2011-04-01 22:56:30 +0200168//usage:#define vi_trivial_usage
169//usage: "[OPTIONS] [FILE]..."
170//usage:#define vi_full_usage "\n\n"
171//usage: "Edit FILE\n"
Pere Orga6a3e01d2011-04-01 22:56:30 +0200172//usage: IF_FEATURE_VI_COLON(
Denys Vlasenko605f2642012-06-11 01:53:33 +0200173//usage: "\n -c CMD Initial command to run ($EXINIT also available)"
Pere Orga6a3e01d2011-04-01 22:56:30 +0200174//usage: )
175//usage: IF_FEATURE_VI_READONLY(
176//usage: "\n -R Read-only"
177//usage: )
Denys Vlasenko605f2642012-06-11 01:53:33 +0200178//usage: "\n -H List available features"
Pere Orga6a3e01d2011-04-01 22:56:30 +0200179
Denis Vlasenkob6adbf12007-05-26 19:00:18 +0000180#include "libbb.h"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200181// Should be after libbb.h: on some systems regex.h needs sys/types.h:
Denys Vlasenko066f3992011-07-03 03:19:43 +0200182#if ENABLE_FEATURE_VI_REGEX_SEARCH
183# include <regex.h>
184#endif
Eric Andersen3f980402001-04-04 17:31:15 +0000185
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200186// the CRASHME code is unmaintained, and doesn't currently build
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +0000187#define ENABLE_FEATURE_VI_CRASHME 0
188
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000189
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +0000190#if ENABLE_LOCALE_SUPPORT
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000191
192#if ENABLE_FEATURE_VI_8BIT
Denys Vlasenkoc2704542009-11-20 19:14:19 +0100193//FIXME: this does not work properly for Unicode anyway
194# define Isprint(c) (isprint)(c)
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000195#else
Denys Vlasenkoc2704542009-11-20 19:14:19 +0100196# define Isprint(c) isprint_asciionly(c)
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000197#endif
198
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000199#else
200
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200201// 0x9b is Meta-ESC
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000202#if ENABLE_FEATURE_VI_8BIT
Denys Vlasenko066f3992011-07-03 03:19:43 +0200203# define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000204#else
Denys Vlasenko066f3992011-07-03 03:19:43 +0200205# define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000206#endif
207
208#endif
209
210
Denis Vlasenkoe8a07882007-06-10 15:08:44 +0000211enum {
Denis Vlasenko26b6fba2007-12-21 21:34:37 +0000212 MAX_TABSTOP = 32, // sanity limit
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000213 // User input len. Need not be extra big.
214 // Lines in file being edited *can* be bigger than this.
215 MAX_INPUT_LEN = 128,
216 // Sanity limits. We have only one buffer of this size.
217 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
218 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
Denis Vlasenkoe8a07882007-06-10 15:08:44 +0000219};
Eric Andersen3f980402001-04-04 17:31:15 +0000220
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200221// VT102 ESC sequences.
222// See "Xterm Control Sequences"
223// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
Denys Vlasenko8187e012017-09-13 22:48:30 +0200224#define ESC "\033"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200225// Inverse/Normal text
Denys Vlasenko8187e012017-09-13 22:48:30 +0200226#define ESC_BOLD_TEXT ESC"[7m"
227#define ESC_NORM_TEXT ESC"[m"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200228// Bell
Denys Vlasenko04b52892012-06-11 13:51:38 +0200229#define ESC_BELL "\007"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200230// Clear-to-end-of-line
Denys Vlasenko8187e012017-09-13 22:48:30 +0200231#define ESC_CLEAR2EOL ESC"[K"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200232// Clear-to-end-of-screen.
233// (We use default param here.
234// Full sequence is "ESC [ <num> J",
235// <num> is 0/1/2 = "erase below/above/all".)
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +0200236#define ESC_CLEAR2EOS ESC"[J"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200237// Cursor to given coordinate (1,1: top left)
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +0200238#define ESC_SET_CURSOR_POS ESC"[%u;%uH"
239#define ESC_SET_CURSOR_TOPLEFT ESC"[H"
Denys Vlasenko04b52892012-06-11 13:51:38 +0200240//UNUSED
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200241//// Cursor up and down
Denys Vlasenko8187e012017-09-13 22:48:30 +0200242//#define ESC_CURSOR_UP ESC"[A"
Denys Vlasenko04b52892012-06-11 13:51:38 +0200243//#define ESC_CURSOR_DOWN "\n"
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000244
Denis Vlasenkoded6ad32008-10-14 12:26:30 +0000245#if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
246// cmds modifying text[]
Denys Vlasenkobbacd032019-04-02 11:50:25 +0200247static const char modifying_cmds[] ALIGN1 = "aAcCdDiIJoOpPrRs""xX<>~";
Denis Vlasenkoded6ad32008-10-14 12:26:30 +0000248#endif
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000249
Rob Landleybc68cd12006-03-10 19:22:06 +0000250enum {
251 YANKONLY = FALSE,
252 YANKDEL = TRUE,
253 FORWARD = 1, // code depends on "1" for array index
254 BACK = -1, // code depends on "-1" for array index
Denys Vlasenko836d0a72018-11-29 14:19:57 +0100255 LIMITED = 0, // char_search() only current line
256 FULL = 1, // char_search() to the end/beginning of entire text
Eric Andersen3f980402001-04-04 17:31:15 +0000257
Rob Landleybc68cd12006-03-10 19:22:06 +0000258 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
259 S_TO_WS = 2, // used in skip_thing() for moving "dot"
260 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
261 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
Denis Vlasenko8e858e22007-03-07 09:35:43 +0000262 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
Rob Landleybc68cd12006-03-10 19:22:06 +0000263};
Eric Andersen3f980402001-04-04 17:31:15 +0000264
Denis Vlasenkob1759462008-06-20 20:20:54 +0000265
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200266// vi.c expects chars to be unsigned.
267// busybox build system provides that, but it's better
268// to audit and fix the source
Eric Andersen3f980402001-04-04 17:31:15 +0000269
Denis Vlasenkob1759462008-06-20 20:20:54 +0000270struct globals {
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200271 // many references - keep near the top of globals
Denis Vlasenkob1759462008-06-20 20:20:54 +0000272 char *text, *end; // pointers to the user data in memory
273 char *dot; // where all the action takes place
274 int text_size; // size of the allocated buffer
275
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200276 // the rest
Denis Vlasenkob1759462008-06-20 20:20:54 +0000277 smallint vi_setops;
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000278#define VI_AUTOINDENT 1
279#define VI_SHOWMATCH 2
280#define VI_IGNORECASE 4
281#define VI_ERR_METHOD 8
282#define autoindent (vi_setops & VI_AUTOINDENT)
283#define showmatch (vi_setops & VI_SHOWMATCH )
284#define ignorecase (vi_setops & VI_IGNORECASE)
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200285// indicate error with beep or flash
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000286#define err_method (vi_setops & VI_ERR_METHOD)
287
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000288#if ENABLE_FEATURE_VI_READONLY
Denis Vlasenkob1759462008-06-20 20:20:54 +0000289 smallint readonly_mode;
Denis Vlasenko6a2f7f42007-08-16 10:35:17 +0000290#define SET_READONLY_FILE(flags) ((flags) |= 0x01)
291#define SET_READONLY_MODE(flags) ((flags) |= 0x02)
292#define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
Denis Vlasenkoeaabf062007-07-17 23:14:07 +0000293#else
Denis Vlasenkob1759462008-06-20 20:20:54 +0000294#define SET_READONLY_FILE(flags) ((void)0)
295#define SET_READONLY_MODE(flags) ((void)0)
296#define UNSET_READONLY_FILE(flags) ((void)0)
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000297#endif
Denis Vlasenkoeaabf062007-07-17 23:14:07 +0000298
Denis Vlasenkob1759462008-06-20 20:20:54 +0000299 smallint editing; // >0 while we are editing a file
Denis Vlasenko30cfdf92008-09-21 15:29:29 +0000300 // [code audit says "can be 0, 1 or 2 only"]
Denis Vlasenkob1759462008-06-20 20:20:54 +0000301 smallint cmd_mode; // 0=command 1=insert 2=replace
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200302 int modified_count; // buffer contents changed if !0
303 int last_modified_count; // = -1;
Denys Vlasenko89393592019-04-02 12:45:30 +0200304 int cmdline_filecnt; // how many file names on cmd line
Denis Vlasenkob1759462008-06-20 20:20:54 +0000305 int cmdcnt; // repetition count
306 unsigned rows, columns; // the terminal screen is this size
Denys Vlasenkoc175c462010-04-18 22:09:30 -0700307#if ENABLE_FEATURE_VI_ASK_TERMINAL
308 int get_rowcol_error;
309#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000310 int crow, ccol; // cursor is on Crow x Ccol
311 int offset; // chars scrolled off the screen to the left
312 int have_status_msg; // is default edit status needed?
313 // [don't make smallint!]
314 int last_status_cksum; // hash of current status line
315 char *current_filename;
316 char *screenbegin; // index into text[], of top line on the screen
317 char *screen; // pointer to the virtual screen buffer
318 int screensize; // and its size
319 int tabstop;
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +0000320 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
Denys Vlasenkob29dce42019-04-01 17:17:02 +0200321#if ENABLE_FEATURE_VI_CRASHME
Denis Vlasenkob1759462008-06-20 20:20:54 +0000322 char last_input_char; // last char read from user
Denys Vlasenkob29dce42019-04-01 17:17:02 +0200323#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000324
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000325#if ENABLE_FEATURE_VI_DOT_CMD
Denis Vlasenkob1759462008-06-20 20:20:54 +0000326 smallint adding2q; // are we currently adding user input to q
327 int lmc_len; // length of last_modifying_cmd
328 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000329#endif
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +0000330#if ENABLE_FEATURE_VI_SEARCH
Denis Vlasenkob1759462008-06-20 20:20:54 +0000331 char *last_search_pattern; // last pattern from a '/' or '?' search
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +0000332#endif
Denis Vlasenko0112ff52008-10-25 23:23:00 +0000333
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200334 // former statics
Denis Vlasenkob1759462008-06-20 20:20:54 +0000335#if ENABLE_FEATURE_VI_YANKMARK
336 char *edit_file__cur_line;
337#endif
338 int refresh__old_offset;
339 int format_edit_status__tot;
Eric Andersen3f980402001-04-04 17:31:15 +0000340
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200341 // a few references only
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000342#if ENABLE_FEATURE_VI_YANKMARK
Denys Vlasenkoeabf4b22019-03-29 14:40:01 +0100343 smalluint YDreg;//,Ureg;// default delete register and orig line for "U"
344#define Ureg 27
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000345 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000346 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
347 char *context_start, *context_end;
348#endif
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000349#if ENABLE_FEATURE_VI_USE_SIGNALS
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +0200350 sigjmp_buf restart; // int_handler() jumps to location remembered here
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000351#endif
Denys Vlasenko01ccdd12017-01-11 16:17:59 +0100352 struct termios term_orig; // remember what the cooked mode was
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000353#if ENABLE_FEATURE_VI_COLON
354 char *initial_cmds[3]; // currently 2 entries, NULL terminated
355#endif
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000356 // Should be just enough to hold a key sequence,
Denis Vlasenko25497c12008-10-14 10:25:05 +0000357 // but CRASHME mode uses it as generated command buffer too
358#if ENABLE_FEATURE_VI_CRASHME
Denis Vlasenko1dfeeeb2008-10-18 19:04:37 +0000359 char readbuffer[128];
Denis Vlasenko25497c12008-10-14 10:25:05 +0000360#else
Denis Vlasenko5f6aaf32008-10-25 23:27:29 +0000361 char readbuffer[KEYCODE_BUFFER_SIZE];
Denis Vlasenko25497c12008-10-14 10:25:05 +0000362#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000363#define STATUS_BUFFER_LEN 200
364 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
365#if ENABLE_FEATURE_VI_DOT_CMD
366 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
367#endif
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200368 char get_input_line__buf[MAX_INPUT_LEN]; // former static
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000369
370 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200371
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200372#if ENABLE_FEATURE_VI_UNDO
373// undo_push() operations
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200374#define UNDO_INS 0
375#define UNDO_DEL 1
376#define UNDO_INS_CHAIN 2
377#define UNDO_DEL_CHAIN 3
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200378// UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
379#define UNDO_QUEUED_FLAG 4
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200380#define UNDO_INS_QUEUED 4
381#define UNDO_DEL_QUEUED 5
382#define UNDO_USE_SPOS 32
383#define UNDO_EMPTY 64
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200384// Pass-through flags for functions that can be undone
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200385#define NO_UNDO 0
386#define ALLOW_UNDO 1
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200387#define ALLOW_UNDO_CHAIN 2
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200388# if ENABLE_FEATURE_VI_UNDO_QUEUE
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200389#define ALLOW_UNDO_QUEUED 3
390 char undo_queue_state;
391 int undo_q;
392 char *undo_queue_spos; // Start position of queued operation
393 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200394# else
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200395// If undo queuing disabled, don't invoke the missing queue logic
396#define ALLOW_UNDO_QUEUED 1
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200397# endif
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200398 struct undo_object {
399 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200400 int start; // Offset where the data should be restored/deleted
401 int length; // total data size
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200402 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
403 char undo_text[1]; // text that was deleted (if deletion)
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200404 } *undo_stack_tail;
405#endif /* ENABLE_FEATURE_VI_UNDO */
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000406};
407#define G (*ptr_to_globals)
408#define text (G.text )
Denis Vlasenkoeaabf062007-07-17 23:14:07 +0000409#define text_size (G.text_size )
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000410#define end (G.end )
411#define dot (G.dot )
412#define reg (G.reg )
Denis Vlasenkob1759462008-06-20 20:20:54 +0000413
414#define vi_setops (G.vi_setops )
415#define editing (G.editing )
416#define cmd_mode (G.cmd_mode )
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200417#define modified_count (G.modified_count )
418#define last_modified_count (G.last_modified_count)
Denys Vlasenko89393592019-04-02 12:45:30 +0200419#define cmdline_filecnt (G.cmdline_filecnt )
Denis Vlasenkob1759462008-06-20 20:20:54 +0000420#define cmdcnt (G.cmdcnt )
421#define rows (G.rows )
422#define columns (G.columns )
423#define crow (G.crow )
424#define ccol (G.ccol )
425#define offset (G.offset )
426#define status_buffer (G.status_buffer )
427#define have_status_msg (G.have_status_msg )
428#define last_status_cksum (G.last_status_cksum )
429#define current_filename (G.current_filename )
430#define screen (G.screen )
431#define screensize (G.screensize )
432#define screenbegin (G.screenbegin )
433#define tabstop (G.tabstop )
Denis Vlasenko0112ff52008-10-25 23:23:00 +0000434#define last_forward_char (G.last_forward_char )
Denys Vlasenkob29dce42019-04-01 17:17:02 +0200435#if ENABLE_FEATURE_VI_CRASHME
Denis Vlasenkob1759462008-06-20 20:20:54 +0000436#define last_input_char (G.last_input_char )
Denys Vlasenkob29dce42019-04-01 17:17:02 +0200437#endif
Denis Vlasenko2a210e52008-06-22 13:20:42 +0000438#if ENABLE_FEATURE_VI_READONLY
Denis Vlasenkob1759462008-06-20 20:20:54 +0000439#define readonly_mode (G.readonly_mode )
Denis Vlasenko2a210e52008-06-22 13:20:42 +0000440#else
441#define readonly_mode 0
442#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000443#define adding2q (G.adding2q )
444#define lmc_len (G.lmc_len )
445#define ioq (G.ioq )
446#define ioq_start (G.ioq_start )
Denis Vlasenkob1759462008-06-20 20:20:54 +0000447#define last_search_pattern (G.last_search_pattern)
Denis Vlasenko2a210e52008-06-22 13:20:42 +0000448
Denis Vlasenkob1759462008-06-20 20:20:54 +0000449#define edit_file__cur_line (G.edit_file__cur_line)
450#define refresh__old_offset (G.refresh__old_offset)
451#define format_edit_status__tot (G.format_edit_status__tot)
452
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000453#define YDreg (G.YDreg )
Denys Vlasenkoeabf4b22019-03-29 14:40:01 +0100454//#define Ureg (G.Ureg )
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000455#define mark (G.mark )
456#define context_start (G.context_start )
457#define context_end (G.context_end )
458#define restart (G.restart )
459#define term_orig (G.term_orig )
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000460#define initial_cmds (G.initial_cmds )
Denis Vlasenkoa96425f2007-12-09 04:13:43 +0000461#define readbuffer (G.readbuffer )
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000462#define scr_out_buf (G.scr_out_buf )
Denis Vlasenkob1759462008-06-20 20:20:54 +0000463#define last_modifying_cmd (G.last_modifying_cmd )
464#define get_input_line__buf (G.get_input_line__buf)
465
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200466#if ENABLE_FEATURE_VI_UNDO
467#define undo_stack_tail (G.undo_stack_tail )
468# if ENABLE_FEATURE_VI_UNDO_QUEUE
469#define undo_queue_state (G.undo_queue_state)
470#define undo_q (G.undo_q )
471#define undo_queue (G.undo_queue )
472#define undo_queue_spos (G.undo_queue_spos )
473# endif
474#endif
475
Denis Vlasenkoa96425f2007-12-09 04:13:43 +0000476#define INIT_G() do { \
Denis Vlasenko574f2f42008-02-27 18:41:59 +0000477 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200478 last_modified_count = -1; \
Denis Vlasenko31d58e52008-10-29 13:16:28 +0000479 /* "" but has space for 2 chars: */ \
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000480 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
Denis Vlasenkoa96425f2007-12-09 04:13:43 +0000481} while (0)
Eric Andersen3f980402001-04-04 17:31:15 +0000482
Denys Vlasenko6ce60b92019-04-01 15:41:05 +0200483#if ENABLE_FEATURE_VI_CRASHME
484static int crashme = 0;
485#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000486
Eric Andersen3f980402001-04-04 17:31:15 +0000487static void show_status_line(void); // put a message on the bottom line
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000488static void status_line_bold(const char *, ...);
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200489
Denys Vlasenko24bd3502019-04-01 13:55:27 +0200490static void show_help(void)
491{
492 puts("These features are available:"
493#if ENABLE_FEATURE_VI_SEARCH
494 "\n\tPattern searches with / and ?"
495#endif
496#if ENABLE_FEATURE_VI_DOT_CMD
497 "\n\tLast command repeat with ."
498#endif
499#if ENABLE_FEATURE_VI_YANKMARK
500 "\n\tLine marking with 'x"
501 "\n\tNamed buffers with \"x"
502#endif
503#if ENABLE_FEATURE_VI_READONLY
504 //not implemented: "\n\tReadonly if vi is called as \"view\""
505 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
506#endif
507#if ENABLE_FEATURE_VI_SET
508 "\n\tSome colon mode commands with :"
509#endif
510#if ENABLE_FEATURE_VI_SETOPTS
511 "\n\tSettable options with \":set\""
512#endif
513#if ENABLE_FEATURE_VI_USE_SIGNALS
514 "\n\tSignal catching- ^C"
515 "\n\tJob suspend and resume with ^Z"
516#endif
517#if ENABLE_FEATURE_VI_WIN_RESIZE
518 "\n\tAdapt to window re-sizes"
519#endif
520 );
521}
522
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000523static void write1(const char *out)
524{
525 fputs(out, stdout);
526}
527
Denys Vlasenko2bb651a2010-04-16 20:55:52 -0700528#if ENABLE_FEATURE_VI_WIN_RESIZE
Denys Vlasenkoc5fb0ad2010-07-21 12:39:42 +0200529static int query_screen_dimensions(void)
Denys Vlasenko2bb651a2010-04-16 20:55:52 -0700530{
Denys Vlasenkoc5fb0ad2010-07-21 12:39:42 +0200531 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
Denys Vlasenko2bb651a2010-04-16 20:55:52 -0700532 if (rows > MAX_SCR_ROWS)
533 rows = MAX_SCR_ROWS;
534 if (columns > MAX_SCR_COLS)
535 columns = MAX_SCR_COLS;
Denys Vlasenkoc5fb0ad2010-07-21 12:39:42 +0200536 return err;
Denys Vlasenko2bb651a2010-04-16 20:55:52 -0700537}
538#else
Denys Vlasenko39043ad2018-06-27 14:46:08 +0200539static ALWAYS_INLINE int query_screen_dimensions(void)
540{
541 return 0;
542}
Denys Vlasenko2bb651a2010-04-16 20:55:52 -0700543#endif
544
Denys Vlasenkode697752019-04-01 14:08:00 +0200545// sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
546static int mysleep(int hund)
547{
548 struct pollfd pfd[1];
549
550 if (hund != 0)
551 fflush_all();
552
553 pfd[0].fd = STDIN_FILENO;
554 pfd[0].events = POLLIN;
555 return safe_poll(pfd, 1, hund*10) > 0;
556}
557
558//----- Set terminal attributes --------------------------------
559static void rawmode(void)
560{
561 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
562 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
Denys Vlasenkode697752019-04-01 14:08:00 +0200563}
564
565static void cookmode(void)
566{
567 fflush_all();
568 tcsetattr_stdin_TCSANOW(&term_orig);
569}
570
571//----- Terminal Drawing ---------------------------------------
572// The terminal is made up of 'rows' line of 'columns' columns.
573// classically this would be 24 x 80.
574// screen coordinates
575// 0,0 ... 0,79
576// 1,0 ... 1,79
577// . ... .
578// . ... .
579// 22,0 ... 22,79
580// 23,0 ... 23,79 <- status line
581
582//----- Move the cursor to row x col (count from 0, not 1) -------
583static void place_cursor(int row, int col)
584{
585 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
586
587 if (row < 0) row = 0;
588 if (row >= rows) row = rows - 1;
589 if (col < 0) col = 0;
590 if (col >= columns) col = columns - 1;
591
592 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
593 write1(cm1);
594}
595
596//----- Erase from cursor to end of line -----------------------
597static void clear_to_eol(void)
598{
599 write1(ESC_CLEAR2EOL);
600}
601
602static void go_bottom_and_clear_to_eol(void)
603{
604 place_cursor(rows - 1, 0);
605 clear_to_eol();
606}
607
608//----- Start standout mode ------------------------------------
609static void standout_start(void)
610{
611 write1(ESC_BOLD_TEXT);
612}
613
614//----- End standout mode --------------------------------------
615static void standout_end(void)
616{
617 write1(ESC_NORM_TEXT);
618}
619
620//----- Text Movement Routines ---------------------------------
621static char *begin_line(char *p) // return pointer to first char cur line
622{
623 if (p > text) {
624 p = memrchr(text, '\n', p - text);
625 if (!p)
626 return text;
627 return p + 1;
628 }
629 return p;
630}
631
632static char *end_line(char *p) // return pointer to NL of cur line
633{
634 if (p < end - 1) {
635 p = memchr(p, '\n', end - p - 1);
636 if (!p)
637 return end - 1;
638 }
639 return p;
640}
641
642static char *dollar_line(char *p) // return pointer to just before NL line
643{
644 p = end_line(p);
645 // Try to stay off of the Newline
646 if (*p == '\n' && (p - begin_line(p)) > 0)
647 p--;
648 return p;
649}
650
651static char *prev_line(char *p) // return pointer first char prev line
652{
653 p = begin_line(p); // goto beginning of cur line
654 if (p > text && p[-1] == '\n')
655 p--; // step to prev line
656 p = begin_line(p); // goto beginning of prev line
657 return p;
658}
659
660static char *next_line(char *p) // return pointer first char next line
661{
662 p = end_line(p);
663 if (p < end - 1 && *p == '\n')
664 p++; // step to next line
665 return p;
666}
667
668//----- Text Information Routines ------------------------------
669static char *end_screen(void)
670{
671 char *q;
672 int cnt;
673
674 // find new bottom line
675 q = screenbegin;
676 for (cnt = 0; cnt < rows - 2; cnt++)
677 q = next_line(q);
678 q = end_line(q);
679 return q;
680}
681
682// count line from start to stop
683static int count_lines(char *start, char *stop)
684{
685 char *q;
686 int cnt;
687
688 if (stop < start) { // start and stop are backwards- reverse them
689 q = start;
690 start = stop;
691 stop = q;
692 }
693 cnt = 0;
694 stop = end_line(stop);
695 while (start <= stop && start <= end - 1) {
696 start = end_line(start);
697 if (*start == '\n')
698 cnt++;
699 start++;
700 }
701 return cnt;
702}
703
704static char *find_line(int li) // find beginning of line #li
705{
706 char *q;
707
708 for (q = text; li > 1; li--) {
709 q = next_line(q);
710 }
711 return q;
712}
713
714static int next_tabstop(int col)
715{
716 return col + ((tabstop - 1) - (col % tabstop));
717}
718
719//----- Erase the Screen[] memory ------------------------------
720static void screen_erase(void)
721{
722 memset(screen, ' ', screensize); // clear new screen
723}
724
Denys Vlasenkoe1a1b642019-04-03 16:30:50 +0200725static void new_screen(int ro, int co)
Ron Yorston3e61b592019-04-03 08:56:30 +0100726{
Denys Vlasenkoe1a1b642019-04-03 16:30:50 +0200727 char *s;
Ron Yorston3e61b592019-04-03 08:56:30 +0100728
729 free(screen);
730 screensize = ro * co + 8;
Denys Vlasenkoe1a1b642019-04-03 16:30:50 +0200731 s = screen = xmalloc(screensize);
Ron Yorston3e61b592019-04-03 08:56:30 +0100732 // initialize the new screen. assume this will be a empty file.
733 screen_erase();
Denys Vlasenkoe1a1b642019-04-03 16:30:50 +0200734 // non-existent text[] lines start with a tilde (~).
735 //screen[(1 * co) + 0] = '~';
736 //screen[(2 * co) + 0] = '~';
737 //..
738 //screen[((ro-2) * co) + 0] = '~';
739 ro -= 2;
740 while (--ro >= 0) {
741 s += co;
742 *s = '~';
Ron Yorston3e61b592019-04-03 08:56:30 +0100743 }
Ron Yorston3e61b592019-04-03 08:56:30 +0100744}
745
Denys Vlasenkode697752019-04-01 14:08:00 +0200746//----- Synchronize the cursor to Dot --------------------------
747static NOINLINE void sync_cursor(char *d, int *row, int *col)
748{
749 char *beg_cur; // begin and end of "d" line
750 char *tp;
751 int cnt, ro, co;
752
753 beg_cur = begin_line(d); // first char of cur line
754
755 if (beg_cur < screenbegin) {
756 // "d" is before top line on screen
757 // how many lines do we have to move
758 cnt = count_lines(beg_cur, screenbegin);
759 sc1:
760 screenbegin = beg_cur;
761 if (cnt > (rows - 1) / 2) {
762 // we moved too many lines. put "dot" in middle of screen
763 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
764 screenbegin = prev_line(screenbegin);
765 }
766 }
767 } else {
768 char *end_scr; // begin and end of screen
769 end_scr = end_screen(); // last char of screen
770 if (beg_cur > end_scr) {
771 // "d" is after bottom line on screen
772 // how many lines do we have to move
773 cnt = count_lines(end_scr, beg_cur);
774 if (cnt > (rows - 1) / 2)
775 goto sc1; // too many lines
776 for (ro = 0; ro < cnt - 1; ro++) {
777 // move screen begin the same amount
778 screenbegin = next_line(screenbegin);
779 // now, move the end of screen
780 end_scr = next_line(end_scr);
781 end_scr = end_line(end_scr);
782 }
783 }
784 }
785 // "d" is on screen- find out which row
786 tp = screenbegin;
787 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
788 if (tp == beg_cur)
789 break;
790 tp = next_line(tp);
791 }
792
793 // find out what col "d" is on
794 co = 0;
795 while (tp < d) { // drive "co" to correct column
796 if (*tp == '\n') //vda || *tp == '\0')
797 break;
798 if (*tp == '\t') {
799 // handle tabs like real vi
800 if (d == tp && cmd_mode) {
801 break;
802 }
803 co = next_tabstop(co);
804 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
805 co++; // display as ^X, use 2 columns
806 }
807 co++;
808 tp++;
809 }
810
811 // "co" is the column where "dot" is.
812 // The screen has "columns" columns.
813 // The currently displayed columns are 0+offset -- columns+ofset
814 // |-------------------------------------------------------------|
815 // ^ ^ ^
816 // offset | |------- columns ----------------|
817 //
818 // If "co" is already in this range then we do not have to adjust offset
819 // but, we do have to subtract the "offset" bias from "co".
820 // If "co" is outside this range then we have to change "offset".
821 // If the first char of a line is a tab the cursor will try to stay
822 // in column 7, but we have to set offset to 0.
823
824 if (co < 0 + offset) {
825 offset = co;
826 }
827 if (co >= columns + offset) {
828 offset = co - columns + 1;
829 }
830 // if the first char of the line is a tab, and "dot" is sitting on it
831 // force offset to 0.
832 if (d == beg_cur && *d == '\t') {
833 offset = 0;
834 }
835 co -= offset;
836
837 *row = ro;
838 *col = co;
839}
840
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +0200841//----- Format a text[] line into a buffer ---------------------
842static char* format_line(char *src /*, int li*/)
843{
844 unsigned char c;
845 int co;
846 int ofs = offset;
847 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
848
849 c = '~'; // char in col 0 in non-existent lines is '~'
850 co = 0;
851 while (co < columns + tabstop) {
852 // have we gone past the end?
853 if (src < end) {
854 c = *src++;
855 if (c == '\n')
856 break;
857 if ((c & 0x80) && !Isprint(c)) {
858 c = '.';
859 }
860 if (c < ' ' || c == 0x7f) {
861 if (c == '\t') {
862 c = ' ';
863 // co % 8 != 7
864 while ((co % tabstop) != (tabstop - 1)) {
865 dest[co++] = c;
866 }
867 } else {
868 dest[co++] = '^';
869 if (c == 0x7f)
870 c = '?';
871 else
872 c += '@'; // Ctrl-X -> 'X'
873 }
874 }
875 }
876 dest[co++] = c;
877 // discard scrolled-off-to-the-left portion,
878 // in tabstop-sized pieces
879 if (ofs >= tabstop && co >= tabstop) {
880 memmove(dest, dest + tabstop, co);
881 co -= tabstop;
882 ofs -= tabstop;
883 }
884 if (src >= end)
885 break;
886 }
887 // check "short line, gigantic offset" case
888 if (co < ofs)
889 ofs = co;
890 // discard last scrolled off part
891 co -= ofs;
892 dest += ofs;
893 // fill the rest with spaces
894 if (co < columns)
895 memset(&dest[co], ' ', columns - co);
896 return dest;
897}
898
899//----- Refresh the changed screen lines -----------------------
900// Copy the source line from text[] into the buffer and note
901// if the current screenline is different from the new buffer.
902// If they differ then that line needs redrawing on the terminal.
903//
904static void refresh(int full_screen)
905{
906#define old_offset refresh__old_offset
907
908 int li, changed;
909 char *tp, *sp; // pointer into text[] and screen[]
910
911 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
912 unsigned c = columns, r = rows;
913 query_screen_dimensions();
914#if ENABLE_FEATURE_VI_USE_SIGNALS
915 full_screen |= (c - columns) | (r - rows);
916#else
917 if (c != columns || r != rows) {
918 full_screen = TRUE;
919 // update screen memory since SIGWINCH won't have done it
920 new_screen(rows, columns);
921 }
922#endif
923 }
924 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
925 tp = screenbegin; // index into text[] of top line
926
927 // compare text[] to screen[] and mark screen[] lines that need updating
928 for (li = 0; li < rows - 1; li++) {
929 int cs, ce; // column start & end
930 char *out_buf;
931 // format current text line
932 out_buf = format_line(tp /*, li*/);
933
934 // skip to the end of the current text[] line
935 if (tp < end) {
936 char *t = memchr(tp, '\n', end - tp);
937 if (!t) t = end - 1;
938 tp = t + 1;
939 }
940
941 // see if there are any changes between virtual screen and out_buf
942 changed = FALSE; // assume no change
943 cs = 0;
944 ce = columns - 1;
945 sp = &screen[li * columns]; // start of screen line
946 if (full_screen) {
947 // force re-draw of every single column from 0 - columns-1
948 goto re0;
949 }
950 // compare newly formatted buffer with virtual screen
951 // look forward for first difference between buf and screen
952 for (; cs <= ce; cs++) {
953 if (out_buf[cs] != sp[cs]) {
954 changed = TRUE; // mark for redraw
955 break;
956 }
957 }
958
959 // look backward for last difference between out_buf and screen
960 for (; ce >= cs; ce--) {
961 if (out_buf[ce] != sp[ce]) {
962 changed = TRUE; // mark for redraw
963 break;
964 }
965 }
966 // now, cs is index of first diff, and ce is index of last diff
967
968 // if horz offset has changed, force a redraw
969 if (offset != old_offset) {
970 re0:
971 changed = TRUE;
972 }
973
974 // make a sanity check of columns indexes
975 if (cs < 0) cs = 0;
976 if (ce > columns - 1) ce = columns - 1;
977 if (cs > ce) { cs = 0; ce = columns - 1; }
978 // is there a change between virtual screen and out_buf
979 if (changed) {
980 // copy changed part of buffer to virtual screen
981 memcpy(sp+cs, out_buf+cs, ce-cs+1);
982 place_cursor(li, cs);
983 // write line out to terminal
984 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
985 }
986 }
987
988 place_cursor(crow, ccol);
989
990 old_offset = offset;
991#undef old_offset
992}
993
994//----- Force refresh of all Lines -----------------------------
995static void redraw(int full_screen)
996{
997 // cursor to top,left; clear to the end of screen
998 write1(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS);
999 screen_erase(); // erase the internal screen buffer
1000 last_status_cksum = 0; // force status update
1001 refresh(full_screen); // this will redraw the entire display
1002 show_status_line();
1003}
1004
1005//----- Flash the screen --------------------------------------
1006static void flash(int h)
1007{
1008 standout_start();
1009 redraw(TRUE);
1010 mysleep(h);
1011 standout_end();
1012 redraw(TRUE);
1013}
1014
1015static void indicate_error(void)
1016{
1017#if ENABLE_FEATURE_VI_CRASHME
1018 if (crashme > 0)
1019 return;
1020#endif
1021 if (!err_method) {
1022 write1(ESC_BELL);
1023 } else {
1024 flash(10);
1025 }
1026}
1027
1028//----- IO Routines --------------------------------------------
1029static int readit(void) // read (maybe cursor) key from stdin
1030{
1031 int c;
1032
1033 fflush_all();
1034
1035 // Wait for input. TIMEOUT = -1 makes read_key wait even
1036 // on nonblocking stdin.
1037 // Note: read_key sets errno to 0 on success.
1038 again:
1039 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
1040 if (c == -1) { // EOF/error
1041 if (errno == EAGAIN) // paranoia
1042 goto again;
1043 go_bottom_and_clear_to_eol();
1044 cookmode(); // terminal to "cooked"
1045 bb_error_msg_and_die("can't read user input");
1046 }
1047 return c;
1048}
1049
Denys Vlasenko2a576082019-04-01 16:15:51 +02001050#if ENABLE_FEATURE_VI_DOT_CMD
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001051static int get_one_char(void)
1052{
1053 int c;
1054
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001055 if (!adding2q) {
1056 // we are not adding to the q.
Denys Vlasenko2a576082019-04-01 16:15:51 +02001057 // but, we may be reading from a saved q.
1058 // (checking "ioq" for NULL is wrong, it's not reset to NULL
1059 // when done - "ioq_start" is reset instead).
1060 if (ioq_start != NULL) {
1061 // there is a queue to get chars from.
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001062 // careful with correct sign expansion!
1063 c = (unsigned char)*ioq++;
Denys Vlasenko2a576082019-04-01 16:15:51 +02001064 if (c != '\0')
1065 return c;
1066 // the end of the q
1067 free(ioq_start);
1068 ioq_start = NULL;
1069 // read from STDIN:
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001070 }
Denys Vlasenko2a576082019-04-01 16:15:51 +02001071 return readit();
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001072 }
Denys Vlasenko2a576082019-04-01 16:15:51 +02001073 // we are adding STDIN chars to q.
1074 c = readit();
Denys Vlasenkobbacd032019-04-02 11:50:25 +02001075 if (lmc_len >= ARRAY_SIZE(last_modifying_cmd) - 1) {
1076 // last_modifying_cmd[] is too small, can't remeber the cmd
1077 // - drop it
1078 adding2q = 0;
1079 lmc_len = 0;
Denys Vlasenko2a576082019-04-01 16:15:51 +02001080 } else {
1081 last_modifying_cmd[lmc_len++] = c;
1082 }
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001083 return c;
1084}
Denys Vlasenko2a576082019-04-01 16:15:51 +02001085#else
1086# define get_one_char() readit()
1087#endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001088
1089// Get input line (uses "status line" area)
1090static char *get_input_line(const char *prompt)
1091{
1092 // char [MAX_INPUT_LEN]
1093#define buf get_input_line__buf
1094
1095 int c;
1096 int i;
1097
1098 strcpy(buf, prompt);
1099 last_status_cksum = 0; // force status update
1100 go_bottom_and_clear_to_eol();
1101 write1(prompt); // write out the :, /, or ? prompt
1102
1103 i = strlen(buf);
1104 while (i < MAX_INPUT_LEN) {
1105 c = get_one_char();
1106 if (c == '\n' || c == '\r' || c == 27)
1107 break; // this is end of input
Denys Vlasenkob29dce42019-04-01 17:17:02 +02001108 if (c == term_orig.c_cc[VERASE] || c == 8 || c == 127) {
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001109 // user wants to erase prev char
1110 buf[--i] = '\0';
1111 write1("\b \b"); // erase char on screen
1112 if (i <= 0) // user backs up before b-o-l, exit
1113 break;
1114 } else if (c > 0 && c < 256) { // exclude Unicode
1115 // (TODO: need to handle Unicode)
1116 buf[i] = c;
1117 buf[++i] = '\0';
1118 bb_putchar(c);
1119 }
1120 }
1121 refresh(FALSE);
1122 return buf;
1123#undef buf
1124}
1125
1126static void Hit_Return(void)
1127{
1128 int c;
1129
1130 standout_start();
1131 write1("[Hit return to continue]");
1132 standout_end();
1133 while ((c = get_one_char()) != '\n' && c != '\r')
1134 continue;
1135 redraw(TRUE); // force redraw all
1136}
1137
1138//----- Draw the status line at bottom of the screen -------------
1139// show file status on status line
1140static int format_edit_status(void)
1141{
1142 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
1143
1144#define tot format_edit_status__tot
1145
1146 int cur, percent, ret, trunc_at;
1147
1148 // modified_count is now a counter rather than a flag. this
1149 // helps reduce the amount of line counting we need to do.
1150 // (this will cause a mis-reporting of modified status
1151 // once every MAXINT editing operations.)
1152
1153 // it would be nice to do a similar optimization here -- if
1154 // we haven't done a motion that could have changed which line
1155 // we're on, then we shouldn't have to do this count_lines()
1156 cur = count_lines(text, dot);
1157
1158 // count_lines() is expensive.
1159 // Call it only if something was changed since last time
1160 // we were here:
1161 if (modified_count != last_modified_count) {
1162 tot = cur + count_lines(dot, end - 1) - 1;
1163 last_modified_count = modified_count;
1164 }
1165
1166 // current line percent
1167 // ------------- ~~ ----------
1168 // total lines 100
1169 if (tot > 0) {
1170 percent = (100 * cur) / tot;
1171 } else {
1172 cur = tot = 0;
1173 percent = 100;
1174 }
1175
1176 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
1177 columns : STATUS_BUFFER_LEN-1;
1178
1179 ret = snprintf(status_buffer, trunc_at+1,
1180#if ENABLE_FEATURE_VI_READONLY
1181 "%c %s%s%s %d/%d %d%%",
1182#else
1183 "%c %s%s %d/%d %d%%",
1184#endif
1185 cmd_mode_indicator[cmd_mode & 3],
1186 (current_filename != NULL ? current_filename : "No file"),
1187#if ENABLE_FEATURE_VI_READONLY
1188 (readonly_mode ? " [Readonly]" : ""),
1189#endif
1190 (modified_count ? " [Modified]" : ""),
1191 cur, tot, percent);
1192
1193 if (ret >= 0 && ret < trunc_at)
1194 return ret; // it all fit
1195
1196 return trunc_at; // had to truncate
1197#undef tot
1198}
1199
1200static int bufsum(char *buf, int count)
1201{
1202 int sum = 0;
1203 char *e = buf + count;
1204 while (buf < e)
1205 sum += (unsigned char) *buf++;
1206 return sum;
1207}
1208
1209static void show_status_line(void)
1210{
1211 int cnt = 0, cksum = 0;
1212
1213 // either we already have an error or status message, or we
1214 // create one.
1215 if (!have_status_msg) {
1216 cnt = format_edit_status();
1217 cksum = bufsum(status_buffer, cnt);
1218 }
1219 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
1220 last_status_cksum = cksum; // remember if we have seen this line
1221 go_bottom_and_clear_to_eol();
1222 write1(status_buffer);
1223 if (have_status_msg) {
1224 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
1225 (columns - 1) ) {
1226 have_status_msg = 0;
1227 Hit_Return();
1228 }
1229 have_status_msg = 0;
1230 }
1231 place_cursor(crow, ccol); // put cursor back in correct place
1232 }
1233 fflush_all();
1234}
1235
1236//----- format the status buffer, the bottom line of screen ------
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02001237static void status_line(const char *format, ...)
1238{
1239 va_list args;
1240
1241 va_start(args, format);
1242 vsnprintf(status_buffer, STATUS_BUFFER_LEN, format, args);
1243 va_end(args);
1244
1245 have_status_msg = 1;
1246}
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001247static void status_line_bold(const char *format, ...)
1248{
1249 va_list args;
1250
1251 va_start(args, format);
1252 strcpy(status_buffer, ESC_BOLD_TEXT);
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02001253 vsnprintf(status_buffer + (sizeof(ESC_BOLD_TEXT)-1),
1254 STATUS_BUFFER_LEN - sizeof(ESC_BOLD_TEXT) - sizeof(ESC_NORM_TEXT),
1255 format, args
1256 );
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001257 strcat(status_buffer, ESC_NORM_TEXT);
1258 va_end(args);
1259
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02001260 have_status_msg = 1 + (sizeof(ESC_BOLD_TEXT)-1) + (sizeof(ESC_NORM_TEXT)-1);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001261}
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001262static void status_line_bold_errno(const char *fn)
1263{
1264 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
1265}
1266
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001267// copy s to buf, convert unprintable
1268static void print_literal(char *buf, const char *s)
1269{
1270 char *d;
1271 unsigned char c;
1272
1273 buf[0] = '\0';
1274 if (!s[0])
1275 s = "(NULL)";
1276
1277 d = buf;
1278 for (; *s; s++) {
1279 int c_is_no_print;
1280
1281 c = *s;
1282 c_is_no_print = (c & 0x80) && !Isprint(c);
1283 if (c_is_no_print) {
1284 strcpy(d, ESC_NORM_TEXT);
1285 d += sizeof(ESC_NORM_TEXT)-1;
1286 c = '.';
1287 }
1288 if (c < ' ' || c == 0x7f) {
1289 *d++ = '^';
1290 c |= '@'; // 0x40
1291 if (c == 0x7f)
1292 c = '?';
1293 }
1294 *d++ = c;
1295 *d = '\0';
1296 if (c_is_no_print) {
1297 strcpy(d, ESC_BOLD_TEXT);
1298 d += sizeof(ESC_BOLD_TEXT)-1;
1299 }
1300 if (*s == '\n') {
1301 *d++ = '$';
1302 *d = '\0';
1303 }
1304 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
1305 break;
1306 }
1307}
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001308static void not_implemented(const char *s)
1309{
1310 char buf[MAX_INPUT_LEN];
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001311 print_literal(buf, s);
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02001312 status_line_bold("'%s' is not implemented", buf);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001313}
1314
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02001315//----- Block insert/delete, undo ops --------------------------
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001316#if ENABLE_FEATURE_VI_YANKMARK
1317static char *text_yank(char *p, char *q, int dest) // copy text into a register
1318{
1319 int cnt = q - p;
1320 if (cnt < 0) { // they are backwards- reverse them
1321 p = q;
1322 cnt = -cnt;
1323 }
1324 free(reg[dest]); // if already a yank register, free it
1325 reg[dest] = xstrndup(p, cnt + 1);
1326 return p;
1327}
1328
1329static char what_reg(void)
1330{
1331 char c;
1332
1333 c = 'D'; // default to D-reg
1334 if (0 <= YDreg && YDreg <= 25)
1335 c = 'a' + (char) YDreg;
1336 if (YDreg == 26)
1337 c = 'D';
1338 if (YDreg == 27)
1339 c = 'U';
1340 return c;
1341}
1342
1343static void check_context(char cmd)
1344{
1345 // A context is defined to be "modifying text"
1346 // Any modifying command establishes a new context.
1347
1348 if (dot < context_start || dot > context_end) {
1349 if (strchr(modifying_cmds, cmd) != NULL) {
1350 // we are trying to modify text[]- make this the current context
1351 mark[27] = mark[26]; // move cur to prev
1352 mark[26] = dot; // move local to cur
1353 context_start = prev_line(prev_line(dot));
1354 context_end = next_line(next_line(dot));
1355 //loiter= start_loiter= now;
1356 }
1357 }
1358}
1359
1360static char *swap_context(char *p) // goto new context for '' command make this the current context
1361{
1362 char *tmp;
1363
1364 // the current context is in mark[26]
1365 // the previous context is in mark[27]
1366 // only swap context if other context is valid
1367 if (text <= mark[27] && mark[27] <= end - 1) {
1368 tmp = mark[27];
1369 mark[27] = p;
1370 mark[26] = p = tmp;
1371 context_start = prev_line(prev_line(prev_line(p)));
1372 context_end = next_line(next_line(next_line(p)));
1373 }
1374 return p;
1375}
1376#endif /* FEATURE_VI_YANKMARK */
1377
1378#if ENABLE_FEATURE_VI_UNDO
1379static void undo_push(char *, unsigned, unsigned char);
1380#endif
1381
1382// open a hole in text[]
1383// might reallocate text[]! use p += text_hole_make(p, ...),
1384// and be careful to not use pointers into potentially freed text[]!
1385static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
1386{
1387 uintptr_t bias = 0;
1388
1389 if (size <= 0)
1390 return bias;
1391 end += size; // adjust the new END
1392 if (end >= (text + text_size)) {
1393 char *new_text;
1394 text_size += end - (text + text_size) + 10240;
1395 new_text = xrealloc(text, text_size);
1396 bias = (new_text - text);
1397 screenbegin += bias;
1398 dot += bias;
1399 end += bias;
1400 p += bias;
1401#if ENABLE_FEATURE_VI_YANKMARK
1402 {
1403 int i;
1404 for (i = 0; i < ARRAY_SIZE(mark); i++)
1405 if (mark[i])
1406 mark[i] += bias;
1407 }
1408#endif
1409 text = new_text;
1410 }
1411 memmove(p + size, p, end - size - p);
1412 memset(p, ' ', size); // clear new hole
1413 return bias;
1414}
1415
1416// close a hole in text[] - delete "p" through "q", inclusive
1417// "undo" value indicates if this operation should be undo-able
1418#if !ENABLE_FEATURE_VI_UNDO
1419#define text_hole_delete(a,b,c) text_hole_delete(a,b)
1420#endif
1421static char *text_hole_delete(char *p, char *q, int undo)
1422{
1423 char *src, *dest;
1424 int cnt, hole_size;
1425
1426 // move forwards, from beginning
1427 // assume p <= q
1428 src = q + 1;
1429 dest = p;
1430 if (q < p) { // they are backward- swap them
1431 src = p + 1;
1432 dest = q;
1433 }
1434 hole_size = q - p + 1;
1435 cnt = end - src;
1436#if ENABLE_FEATURE_VI_UNDO
1437 switch (undo) {
1438 case NO_UNDO:
1439 break;
1440 case ALLOW_UNDO:
1441 undo_push(p, hole_size, UNDO_DEL);
1442 break;
1443 case ALLOW_UNDO_CHAIN:
1444 undo_push(p, hole_size, UNDO_DEL_CHAIN);
1445 break;
1446# if ENABLE_FEATURE_VI_UNDO_QUEUE
1447 case ALLOW_UNDO_QUEUED:
1448 undo_push(p, hole_size, UNDO_DEL_QUEUED);
1449 break;
1450# endif
1451 }
1452 modified_count--;
1453#endif
1454 if (src < text || src > end)
1455 goto thd0;
1456 if (dest < text || dest >= end)
1457 goto thd0;
1458 modified_count++;
1459 if (src >= end)
1460 goto thd_atend; // just delete the end of the buffer
1461 memmove(dest, src, cnt);
1462 thd_atend:
1463 end = end - hole_size; // adjust the new END
1464 if (dest >= end)
1465 dest = end - 1; // make sure dest in below end-1
1466 if (end <= text)
1467 dest = end = text; // keep pointers valid
1468 thd0:
1469 return dest;
1470}
1471
1472#if ENABLE_FEATURE_VI_UNDO
1473
1474# if ENABLE_FEATURE_VI_UNDO_QUEUE
1475// Flush any queued objects to the undo stack
1476static void undo_queue_commit(void)
1477{
1478 // Pushes the queue object onto the undo stack
1479 if (undo_q > 0) {
1480 // Deleted character undo events grow from the end
1481 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
1482 undo_q,
1483 (undo_queue_state | UNDO_USE_SPOS)
1484 );
1485 undo_queue_state = UNDO_EMPTY;
1486 undo_q = 0;
1487 }
1488}
1489# else
1490# define undo_queue_commit() ((void)0)
1491# endif
1492
1493static void flush_undo_data(void)
1494{
1495 struct undo_object *undo_entry;
1496
1497 while (undo_stack_tail) {
1498 undo_entry = undo_stack_tail;
1499 undo_stack_tail = undo_entry->prev;
1500 free(undo_entry);
1501 }
1502}
1503
1504// Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
1505// Add to the undo stack
1506static void undo_push(char *src, unsigned length, uint8_t u_type)
1507{
1508 struct undo_object *undo_entry;
1509
1510 // "u_type" values
1511 // UNDO_INS: insertion, undo will remove from buffer
1512 // UNDO_DEL: deleted text, undo will restore to buffer
1513 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
1514 // The CHAIN operations are for handling multiple operations that the user
1515 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
1516 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
1517 // for the INS/DEL operation. The raw values should be equal to the values of
1518 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
1519
1520# if ENABLE_FEATURE_VI_UNDO_QUEUE
1521 // This undo queuing functionality groups multiple character typing or backspaces
1522 // into a single large undo object. This greatly reduces calls to malloc() for
1523 // single-character operations while typing and has the side benefit of letting
1524 // an undo operation remove chunks of text rather than a single character.
1525 switch (u_type) {
1526 case UNDO_EMPTY: // Just in case this ever happens...
1527 return;
1528 case UNDO_DEL_QUEUED:
1529 if (length != 1)
1530 return; // Only queue single characters
1531 switch (undo_queue_state) {
1532 case UNDO_EMPTY:
1533 undo_queue_state = UNDO_DEL;
1534 case UNDO_DEL:
1535 undo_queue_spos = src;
1536 undo_q++;
1537 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
1538 // If queue is full, dump it into an object
1539 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1540 undo_queue_commit();
1541 return;
1542 case UNDO_INS:
1543 // Switch from storing inserted text to deleted text
1544 undo_queue_commit();
1545 undo_push(src, length, UNDO_DEL_QUEUED);
1546 return;
1547 }
1548 break;
1549 case UNDO_INS_QUEUED:
1550 if (length < 1)
1551 return;
1552 switch (undo_queue_state) {
1553 case UNDO_EMPTY:
1554 undo_queue_state = UNDO_INS;
1555 undo_queue_spos = src;
1556 case UNDO_INS:
1557 while (length--) {
1558 undo_q++; // Don't need to save any data for insertions
1559 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1560 undo_queue_commit();
1561 }
1562 return;
1563 case UNDO_DEL:
1564 // Switch from storing deleted text to inserted text
1565 undo_queue_commit();
1566 undo_push(src, length, UNDO_INS_QUEUED);
1567 return;
1568 }
1569 break;
1570 }
1571# else
1572 // If undo queuing is disabled, ignore the queuing flag entirely
1573 u_type = u_type & ~UNDO_QUEUED_FLAG;
1574# endif
1575
1576 // Allocate a new undo object
1577 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
1578 // For UNDO_DEL objects, save deleted text
1579 if ((text + length) == end)
1580 length--;
1581 // If this deletion empties text[], strip the newline. When the buffer becomes
1582 // zero-length, a newline is added back, which requires this to compensate.
1583 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
1584 memcpy(undo_entry->undo_text, src, length);
1585 } else {
1586 undo_entry = xzalloc(sizeof(*undo_entry));
1587 }
1588 undo_entry->length = length;
1589# if ENABLE_FEATURE_VI_UNDO_QUEUE
1590 if ((u_type & UNDO_USE_SPOS) != 0) {
1591 undo_entry->start = undo_queue_spos - text; // use start position from queue
1592 } else {
1593 undo_entry->start = src - text; // use offset from start of text buffer
1594 }
1595 u_type = (u_type & ~UNDO_USE_SPOS);
1596# else
1597 undo_entry->start = src - text;
1598# endif
1599 undo_entry->u_type = u_type;
1600
1601 // Push it on undo stack
1602 undo_entry->prev = undo_stack_tail;
1603 undo_stack_tail = undo_entry;
1604 modified_count++;
1605}
1606
1607static void undo_push_insert(char *p, int len, int undo)
1608{
1609 switch (undo) {
1610 case ALLOW_UNDO:
1611 undo_push(p, len, UNDO_INS);
1612 break;
1613 case ALLOW_UNDO_CHAIN:
1614 undo_push(p, len, UNDO_INS_CHAIN);
1615 break;
1616# if ENABLE_FEATURE_VI_UNDO_QUEUE
1617 case ALLOW_UNDO_QUEUED:
1618 undo_push(p, len, UNDO_INS_QUEUED);
1619 break;
1620# endif
1621 }
1622}
1623
1624// Undo the last operation
1625static void undo_pop(void)
1626{
1627 int repeat;
1628 char *u_start, *u_end;
1629 struct undo_object *undo_entry;
1630
1631 // Commit pending undo queue before popping (should be unnecessary)
1632 undo_queue_commit();
1633
1634 undo_entry = undo_stack_tail;
1635 // Check for an empty undo stack
1636 if (!undo_entry) {
1637 status_line("Already at oldest change");
1638 return;
1639 }
1640
1641 switch (undo_entry->u_type) {
1642 case UNDO_DEL:
1643 case UNDO_DEL_CHAIN:
1644 // make hole and put in text that was deleted; deallocate text
1645 u_start = text + undo_entry->start;
1646 text_hole_make(u_start, undo_entry->length);
1647 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
1648 status_line("Undo [%d] %s %d chars at position %d",
1649 modified_count, "restored",
1650 undo_entry->length, undo_entry->start
1651 );
1652 break;
1653 case UNDO_INS:
1654 case UNDO_INS_CHAIN:
1655 // delete what was inserted
1656 u_start = undo_entry->start + text;
1657 u_end = u_start - 1 + undo_entry->length;
1658 text_hole_delete(u_start, u_end, NO_UNDO);
1659 status_line("Undo [%d] %s %d chars at position %d",
1660 modified_count, "deleted",
1661 undo_entry->length, undo_entry->start
1662 );
1663 break;
1664 }
1665 repeat = 0;
1666 switch (undo_entry->u_type) {
1667 // If this is the end of a chain, lower modification count and refresh display
1668 case UNDO_DEL:
1669 case UNDO_INS:
1670 dot = (text + undo_entry->start);
1671 refresh(FALSE);
1672 break;
1673 case UNDO_DEL_CHAIN:
1674 case UNDO_INS_CHAIN:
1675 repeat = 1;
1676 break;
1677 }
1678 // Deallocate the undo object we just processed
1679 undo_stack_tail = undo_entry->prev;
1680 free(undo_entry);
1681 modified_count--;
1682 // For chained operations, continue popping all the way down the chain.
1683 if (repeat) {
1684 undo_pop(); // Follow the undo chain if one exists
1685 }
1686}
1687
1688#else
1689# define flush_undo_data() ((void)0)
1690# define undo_queue_commit() ((void)0)
1691#endif /* ENABLE_FEATURE_VI_UNDO */
1692
1693//----- Dot Movement Routines ----------------------------------
1694static void dot_left(void)
1695{
1696 undo_queue_commit();
1697 if (dot > text && dot[-1] != '\n')
1698 dot--;
1699}
1700
1701static void dot_right(void)
1702{
1703 undo_queue_commit();
1704 if (dot < end - 1 && *dot != '\n')
1705 dot++;
1706}
1707
1708static void dot_begin(void)
1709{
1710 undo_queue_commit();
1711 dot = begin_line(dot); // return pointer to first char cur line
1712}
1713
1714static void dot_end(void)
1715{
1716 undo_queue_commit();
1717 dot = end_line(dot); // return pointer to last char cur line
1718}
1719
1720static char *move_to_col(char *p, int l)
1721{
1722 int co;
1723
1724 p = begin_line(p);
1725 co = 0;
1726 while (co < l && p < end) {
1727 if (*p == '\n') //vda || *p == '\0')
1728 break;
1729 if (*p == '\t') {
1730 co = next_tabstop(co);
1731 } else if (*p < ' ' || *p == 127) {
1732 co++; // display as ^X, use 2 columns
1733 }
1734 co++;
1735 p++;
1736 }
1737 return p;
1738}
1739
1740static void dot_next(void)
1741{
1742 undo_queue_commit();
1743 dot = next_line(dot);
1744}
1745
1746static void dot_prev(void)
1747{
1748 undo_queue_commit();
1749 dot = prev_line(dot);
1750}
1751
1752static void dot_skip_over_ws(void)
1753{
1754 // skip WS
1755 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1756 dot++;
1757}
1758
1759static void dot_scroll(int cnt, int dir)
1760{
1761 char *q;
1762
1763 undo_queue_commit();
1764 for (; cnt > 0; cnt--) {
1765 if (dir < 0) {
1766 // scroll Backwards
1767 // ctrl-Y scroll up one line
1768 screenbegin = prev_line(screenbegin);
1769 } else {
1770 // scroll Forwards
1771 // ctrl-E scroll down one line
1772 screenbegin = next_line(screenbegin);
1773 }
1774 }
1775 // make sure "dot" stays on the screen so we dont scroll off
1776 if (dot < screenbegin)
1777 dot = screenbegin;
1778 q = end_screen(); // find new bottom line
1779 if (dot > q)
1780 dot = begin_line(q); // is dot is below bottom line?
1781 dot_skip_over_ws();
1782}
1783
1784static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1785{
1786 if (p >= end && end > text) {
1787 p = end - 1;
1788 indicate_error();
1789 }
1790 if (p < text) {
1791 p = text;
1792 indicate_error();
1793 }
1794 return p;
1795}
1796
1797#if ENABLE_FEATURE_VI_DOT_CMD
1798static void start_new_cmd_q(char c)
1799{
1800 // get buffer for new cmd
1801 // if there is a current cmd count put it in the buffer first
1802 if (cmdcnt > 0) {
Denys Vlasenko2a576082019-04-01 16:15:51 +02001803 lmc_len = sprintf(last_modifying_cmd, "%u%c", cmdcnt, c);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001804 } else { // just save char c onto queue
1805 last_modifying_cmd[0] = c;
1806 lmc_len = 1;
1807 }
1808 adding2q = 1;
1809}
1810static void end_cmd_q(void)
1811{
1812# if ENABLE_FEATURE_VI_YANKMARK
1813 YDreg = 26; // go back to default Yank/Delete reg
1814# endif
1815 adding2q = 0;
1816}
1817#else
1818# define end_cmd_q() ((void)0)
1819#endif /* FEATURE_VI_DOT_CMD */
1820
1821// copy text into register, then delete text.
1822// if dist <= 0, do not include, or go past, a NewLine
1823//
1824#if !ENABLE_FEATURE_VI_UNDO
1825#define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
1826#endif
1827static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
1828{
1829 char *p;
1830
1831 // make sure start <= stop
1832 if (start > stop) {
1833 // they are backwards, reverse them
1834 p = start;
1835 start = stop;
1836 stop = p;
1837 }
1838 if (dist <= 0) {
1839 // we cannot cross NL boundaries
1840 p = start;
1841 if (*p == '\n')
1842 return p;
1843 // dont go past a NewLine
1844 for (; p + 1 <= stop; p++) {
1845 if (p[1] == '\n') {
1846 stop = p; // "stop" just before NewLine
1847 break;
1848 }
1849 }
1850 }
1851 p = start;
1852#if ENABLE_FEATURE_VI_YANKMARK
1853 text_yank(start, stop, YDreg);
1854#endif
1855 if (yf == YANKDEL) {
1856 p = text_hole_delete(start, stop, undo);
1857 } // delete lines
1858 return p;
1859}
1860
1861// might reallocate text[]!
1862static int file_insert(const char *fn, char *p, int initial)
1863{
1864 int cnt = -1;
1865 int fd, size;
1866 struct stat statbuf;
1867
1868 if (p < text)
1869 p = text;
1870 if (p > end)
1871 p = end;
1872
1873 fd = open(fn, O_RDONLY);
1874 if (fd < 0) {
1875 if (!initial)
1876 status_line_bold_errno(fn);
1877 return cnt;
1878 }
1879
1880 // Validate file
1881 if (fstat(fd, &statbuf) < 0) {
1882 status_line_bold_errno(fn);
1883 goto fi;
1884 }
1885 if (!S_ISREG(statbuf.st_mode)) {
1886 status_line_bold("'%s' is not a regular file", fn);
1887 goto fi;
1888 }
1889 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
1890 p += text_hole_make(p, size);
1891 cnt = full_read(fd, p, size);
1892 if (cnt < 0) {
1893 status_line_bold_errno(fn);
1894 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
1895 } else if (cnt < size) {
1896 // There was a partial read, shrink unused space
1897 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
1898 status_line_bold("can't read '%s'", fn);
1899 }
1900 fi:
1901 close(fd);
1902
1903#if ENABLE_FEATURE_VI_READONLY
1904 if (initial
1905 && ((access(fn, W_OK) < 0) ||
1906 // root will always have access()
1907 // so we check fileperms too
1908 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
1909 )
1910 ) {
1911 SET_READONLY_FILE(readonly_mode);
1912 }
1913#endif
1914 return cnt;
1915}
1916
1917// find matching char of pair () [] {}
1918// will crash if c is not one of these
1919static char *find_pair(char *p, const char c)
1920{
1921 const char *braces = "()[]{}";
1922 char match;
1923 int dir, level;
1924
1925 dir = strchr(braces, c) - braces;
1926 dir ^= 1;
1927 match = braces[dir];
1928 dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\}
1929
1930 // look for match, count levels of pairs (( ))
1931 level = 1;
1932 for (;;) {
1933 p += dir;
1934 if (p < text || p >= end)
1935 return NULL;
1936 if (*p == c)
1937 level++; // increase pair levels
1938 if (*p == match) {
1939 level--; // reduce pair level
1940 if (level == 0)
1941 return p; // found matching pair
1942 }
1943 }
1944}
1945
1946#if ENABLE_FEATURE_VI_SETOPTS
1947// show the matching char of a pair, () [] {}
1948static void showmatching(char *p)
1949{
1950 char *q, *save_dot;
1951
1952 // we found half of a pair
1953 q = find_pair(p, *p); // get loc of matching char
1954 if (q == NULL) {
1955 indicate_error(); // no matching char
1956 } else {
1957 // "q" now points to matching pair
1958 save_dot = dot; // remember where we are
1959 dot = q; // go to new loc
1960 refresh(FALSE); // let the user see it
1961 mysleep(40); // give user some time
1962 dot = save_dot; // go back to old loc
1963 refresh(FALSE);
1964 }
1965}
1966#endif /* FEATURE_VI_SETOPTS */
1967
1968// might reallocate text[]! use p += stupid_insert(p, ...),
1969// and be careful to not use pointers into potentially freed text[]!
1970static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1971{
1972 uintptr_t bias;
1973 bias = text_hole_make(p, 1);
1974 p += bias;
1975 *p = c;
1976 return bias;
1977}
1978
1979#if !ENABLE_FEATURE_VI_UNDO
1980#define char_insert(a,b,c) char_insert(a,b)
1981#endif
1982static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1983{
1984 if (c == 22) { // Is this an ctrl-V?
1985 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1986 refresh(FALSE); // show the ^
1987 c = get_one_char();
1988 *p = c;
1989#if ENABLE_FEATURE_VI_UNDO
1990 undo_push_insert(p, 1, undo);
1991#else
1992 modified_count++;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02001993#endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001994 p++;
1995 } else if (c == 27) { // Is this an ESC?
1996 cmd_mode = 0;
1997 undo_queue_commit();
1998 cmdcnt = 0;
1999 end_cmd_q(); // stop adding to q
2000 last_status_cksum = 0; // force status update
2001 if ((p[-1] != '\n') && (dot > text)) {
2002 p--;
2003 }
Denys Vlasenkob29dce42019-04-01 17:17:02 +02002004 } else if (c == term_orig.c_cc[VERASE] || c == 8 || c == 127) { // Is this a BS
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002005 if (p > text) {
2006 p--;
2007 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2008 }
2009 } else {
2010 // insert a char into text[]
2011 if (c == 13)
2012 c = '\n'; // translate \r to \n
2013#if ENABLE_FEATURE_VI_UNDO
2014# if ENABLE_FEATURE_VI_UNDO_QUEUE
2015 if (c == '\n')
2016 undo_queue_commit();
2017# endif
2018 undo_push_insert(p, 1, undo);
2019#else
2020 modified_count++;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02002021#endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002022 p += 1 + stupid_insert(p, c); // insert the char
2023#if ENABLE_FEATURE_VI_SETOPTS
2024 if (showmatch && strchr(")]}", c) != NULL) {
2025 showmatching(p - 1);
2026 }
2027 if (autoindent && c == '\n') { // auto indent the new line
2028 char *q;
2029 size_t len;
2030 q = prev_line(p); // use prev line as template
2031 len = strspn(q, " \t"); // space or tab
2032 if (len) {
2033 uintptr_t bias;
2034 bias = text_hole_make(p, len);
2035 p += bias;
2036 q += bias;
2037#if ENABLE_FEATURE_VI_UNDO
2038 undo_push_insert(p, len, undo);
2039#endif
2040 memcpy(p, q, len);
2041 p += len;
2042 }
2043 }
2044#endif
2045 }
2046 return p;
2047}
2048
2049// read text from file or create an empty buf
2050// will also update current_filename
2051static int init_text_buffer(char *fn)
2052{
2053 int rc;
2054
2055 // allocate/reallocate text buffer
2056 free(text);
2057 text_size = 10240;
2058 screenbegin = dot = end = text = xzalloc(text_size);
2059
2060 if (fn != current_filename) {
2061 free(current_filename);
2062 current_filename = xstrdup(fn);
2063 }
2064 rc = file_insert(fn, text, 1);
2065 if (rc < 0) {
2066 // file doesnt exist. Start empty buf with dummy line
2067 char_insert(text, '\n', NO_UNDO);
2068 }
2069
2070 flush_undo_data();
2071 modified_count = 0;
2072 last_modified_count = -1;
2073#if ENABLE_FEATURE_VI_YANKMARK
2074 // init the marks
2075 memset(mark, 0, sizeof(mark));
2076#endif
2077 return rc;
2078}
2079
2080#if ENABLE_FEATURE_VI_YANKMARK \
2081 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2082 || ENABLE_FEATURE_VI_CRASHME
2083// might reallocate text[]! use p += string_insert(p, ...),
2084// and be careful to not use pointers into potentially freed text[]!
2085# if !ENABLE_FEATURE_VI_UNDO
2086# define string_insert(a,b,c) string_insert(a,b)
2087# endif
2088static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2089{
2090 uintptr_t bias;
2091 int i;
2092
2093 i = strlen(s);
2094#if ENABLE_FEATURE_VI_UNDO
2095 undo_push_insert(p, i, undo);
2096#endif
2097 bias = text_hole_make(p, i);
2098 p += bias;
2099 memcpy(p, s, i);
2100#if ENABLE_FEATURE_VI_YANKMARK
2101 {
2102 int cnt;
2103 for (cnt = 0; *s != '\0'; s++) {
2104 if (*s == '\n')
2105 cnt++;
2106 }
2107 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2108 }
2109#endif
2110 return bias;
2111}
2112#endif
2113
2114static int file_write(char *fn, char *first, char *last)
2115{
2116 int fd, cnt, charcnt;
2117
2118 if (fn == 0) {
2119 status_line_bold("No current filename");
2120 return -2;
2121 }
2122 // By popular request we do not open file with O_TRUNC,
2123 // but instead ftruncate() it _after_ successful write.
2124 // Might reduce amount of data lost on power fail etc.
2125 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2126 if (fd < 0)
2127 return -1;
2128 cnt = last - first + 1;
2129 charcnt = full_write(fd, first, cnt);
2130 ftruncate(fd, charcnt);
2131 if (charcnt == cnt) {
2132 // good write
2133 //modified_count = FALSE;
2134 } else {
2135 charcnt = 0;
2136 }
2137 close(fd);
2138 return charcnt;
2139}
2140
2141#if ENABLE_FEATURE_VI_SEARCH
2142# if ENABLE_FEATURE_VI_REGEX_SEARCH
2143// search for pattern starting at p
2144static char *char_search(char *p, const char *pat, int dir_and_range)
2145{
2146 struct re_pattern_buffer preg;
2147 const char *err;
2148 char *q;
2149 int i;
2150 int size;
2151 int range;
2152
2153 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
2154 if (ignorecase)
2155 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
2156
2157 memset(&preg, 0, sizeof(preg));
2158 err = re_compile_pattern(pat, strlen(pat), &preg);
2159 if (err != NULL) {
2160 status_line_bold("bad search pattern '%s': %s", pat, err);
2161 return p;
2162 }
2163
2164 range = (dir_and_range & 1);
2165 q = end - 1; // if FULL
2166 if (range == LIMITED)
2167 q = next_line(p);
2168 if (dir_and_range < 0) { // BACK?
2169 q = text;
2170 if (range == LIMITED)
2171 q = prev_line(p);
2172 }
2173
2174 // RANGE could be negative if we are searching backwards
2175 range = q - p;
2176 q = p;
2177 size = range;
2178 if (range < 0) {
2179 size = -size;
2180 q = p - size;
2181 if (q < text)
2182 q = text;
2183 }
2184 // search for the compiled pattern, preg, in p[]
2185 // range < 0: search backward
2186 // range > 0: search forward
2187 // 0 < start < size
2188 // re_search() < 0: not found or error
2189 // re_search() >= 0: index of found pattern
2190 // struct pattern char int int int struct reg
2191 // re_search(*pattern_buffer, *string, size, start, range, *regs)
2192 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
2193 regfree(&preg);
2194 if (i < 0)
2195 return NULL;
2196 if (dir_and_range > 0) // FORWARD?
2197 p = p + i;
2198 else
2199 p = p - i;
2200 return p;
2201}
2202# else
2203# if ENABLE_FEATURE_VI_SETOPTS
2204static int mycmp(const char *s1, const char *s2, int len)
2205{
2206 if (ignorecase) {
2207 return strncasecmp(s1, s2, len);
2208 }
2209 return strncmp(s1, s2, len);
2210}
2211# else
2212# define mycmp strncmp
2213# endif
2214static char *char_search(char *p, const char *pat, int dir_and_range)
2215{
2216 char *start, *stop;
2217 int len;
2218 int range;
2219
2220 len = strlen(pat);
2221 range = (dir_and_range & 1);
2222 if (dir_and_range > 0) { //FORWARD?
2223 stop = end - 1; // assume range is p..end-1
2224 if (range == LIMITED)
2225 stop = next_line(p); // range is to next line
2226 for (start = p; start < stop; start++) {
2227 if (mycmp(start, pat, len) == 0) {
2228 return start;
2229 }
2230 }
2231 } else { //BACK
2232 stop = text; // assume range is text..p
2233 if (range == LIMITED)
2234 stop = prev_line(p); // range is to prev line
2235 for (start = p - len; start >= stop; start--) {
2236 if (mycmp(start, pat, len) == 0) {
2237 return start;
2238 }
2239 }
2240 }
2241 // pattern not found
2242 return NULL;
2243}
2244# endif
2245#endif /* FEATURE_VI_SEARCH */
2246
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002247//----- The Colon commands -------------------------------------
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00002248#if ENABLE_FEATURE_VI_COLON
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002249static char *get_one_address(char *p, int *addr) // get colon addr, if present
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002250{
2251 int st;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002252 char *q;
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00002253 IF_FEATURE_VI_YANKMARK(char c;)
2254 IF_FEATURE_VI_SEARCH(char *pat;)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002255
2256 *addr = -1; // assume no addr
2257 if (*p == '.') { // the current line
2258 p++;
2259 q = begin_line(dot);
2260 *addr = count_lines(text, q);
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002261 }
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00002262#if ENABLE_FEATURE_VI_YANKMARK
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002263 else if (*p == '\'') { // is this a mark addr
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002264 p++;
2265 c = tolower(*p);
2266 p++;
2267 if (c >= 'a' && c <= 'z') {
2268 // we have a mark
2269 c = c - 'a';
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002270 q = mark[(unsigned char) c];
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002271 if (q != NULL) { // is mark valid
Denis Vlasenko00d84172008-11-24 07:34:42 +00002272 *addr = count_lines(text, q);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002273 }
2274 }
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002275 }
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00002276#endif
2277#if ENABLE_FEATURE_VI_SEARCH
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002278 else if (*p == '/') { // a search pattern
2279 q = strchrnul(++p, '/');
2280 pat = xstrndup(p, q - p); // save copy of pattern
2281 p = q;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002282 if (*p == '/')
2283 p++;
Denys Vlasenkob7330462018-11-29 14:39:52 +01002284 q = char_search(dot, pat, (FORWARD << 1) | FULL);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002285 if (q != NULL) {
2286 *addr = count_lines(text, q);
2287 }
2288 free(pat);
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002289 }
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00002290#endif
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002291 else if (*p == '$') { // the last line in file
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002292 p++;
2293 q = begin_line(end - 1);
2294 *addr = count_lines(text, q);
2295 } else if (isdigit(*p)) { // specific line number
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002296 sscanf(p, "%d%n", addr, &st);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002297 p += st;
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002298 } else {
Denys Vlasenkob22bbff2009-07-04 16:50:43 +02002299 // unrecognized address - assume -1
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002300 *addr = -1;
2301 }
Denis Vlasenko079f8af2006-11-27 16:49:31 +00002302 return p;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002303}
2304
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002305static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002306{
2307 //----- get the address' i.e., 1,3 'a,'b -----
2308 // get FIRST addr, if present
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002309 while (isblank(*p))
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002310 p++; // skip over leading spaces
2311 if (*p == '%') { // alias for 1,$
2312 p++;
2313 *b = 1;
2314 *e = count_lines(text, end-1);
2315 goto ga0;
2316 }
2317 p = get_one_address(p, b);
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002318 while (isblank(*p))
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002319 p++;
Eric Andersenaff114c2004-04-14 17:51:38 +00002320 if (*p == ',') { // is there a address separator
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002321 p++;
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002322 while (isblank(*p))
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002323 p++;
2324 // get SECOND addr, if present
2325 p = get_one_address(p, e);
2326 }
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002327 ga0:
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002328 while (isblank(*p))
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002329 p++; // skip over trailing spaces
Denis Vlasenko079f8af2006-11-27 16:49:31 +00002330 return p;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002331}
2332
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00002333#if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002334static void setops(const char *args, const char *opname, int flg_no,
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002335 const char *short_opname, int opt)
2336{
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002337 const char *a = args + flg_no;
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02002338 int l = strlen(opname) - 1; // opname have + ' '
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002339
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002340 // maybe strncmp? we had tons of erroneous strncasecmp's...
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002341 if (strncasecmp(a, opname, l) == 0
2342 || strncasecmp(a, short_opname, 2) == 0
2343 ) {
2344 if (flg_no)
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002345 vi_setops &= ~opt;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002346 else
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002347 vi_setops |= opt;
2348 }
2349}
2350#endif
2351
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002352#endif /* FEATURE_VI_COLON */
2353
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002354// buf must be no longer than MAX_INPUT_LEN!
2355static void colon(char *buf)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002356{
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002357#if !ENABLE_FEATURE_VI_COLON
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02002358 // Simple ":cmd" handler with minimal set of commands
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002359 char *p = buf;
2360 int cnt;
2361
2362 if (*p == ':')
2363 p++;
2364 cnt = strlen(p);
2365 if (cnt == 0)
2366 return;
2367 if (strncmp(p, "quit", cnt) == 0
2368 || strncmp(p, "q!", cnt) == 0
2369 ) {
2370 if (modified_count && p[1] != '!') {
2371 status_line_bold("No write since last change (:%s! overrides)", p);
2372 } else {
2373 editing = 0;
2374 }
2375 return;
2376 }
2377 if (strncmp(p, "write", cnt) == 0
2378 || strncmp(p, "wq", cnt) == 0
2379 || strncmp(p, "wn", cnt) == 0
2380 || (p[0] == 'x' && !p[1])
2381 ) {
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01002382 if (modified_count != 0 || p[0] != 'x') {
2383 cnt = file_write(current_filename, text, end - 1);
2384 }
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002385 if (cnt < 0) {
2386 if (cnt == -1)
Denys Vlasenko6f97b302017-09-29 18:17:25 +02002387 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002388 } else {
2389 modified_count = 0;
2390 last_modified_count = -1;
Denys Vlasenko89393592019-04-02 12:45:30 +02002391 status_line("'%s' %uL, %uC",
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002392 current_filename,
2393 count_lines(text, end - 1), cnt
2394 );
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01002395 if (p[0] == 'x'
2396 || p[1] == 'q' || p[1] == 'n'
2397 || p[1] == 'Q' || p[1] == 'N'
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002398 ) {
2399 editing = 0;
2400 }
2401 }
2402 return;
2403 }
2404 if (strncmp(p, "file", cnt) == 0) {
2405 last_status_cksum = 0; // force status update
2406 return;
2407 }
2408 if (sscanf(p, "%d", &cnt) > 0) {
2409 dot = find_line(cnt);
2410 dot_skip_over_ws();
2411 return;
2412 }
2413 not_implemented(p);
2414#else
2415
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002416 char c, *buf1, *q, *r;
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002417 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002418 int i, l, li, b, e;
2419 int useforce;
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002420# if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
2421 char *orig_buf;
2422# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002423
2424 // :3154 // if (-e line 3154) goto it else stay put
2425 // :4,33w! foo // write a portion of buffer to file "foo"
2426 // :w // write all of buffer to current file
2427 // :q // quit
2428 // :q! // quit- dont care about modified file
2429 // :'a,'z!sort -u // filter block through sort
2430 // :'f // goto mark "f"
2431 // :'fl // list literal the mark "f" line
2432 // :.r bar // read file "bar" into buffer before dot
2433 // :/123/,/abc/d // delete lines from "123" line to "abc" line
2434 // :/xyz/ // goto the "xyz" line
2435 // :s/find/replace/ // substitute pattern "find" with "replace"
2436 // :!<cmd> // run <cmd> then return
2437 //
Eric Andersen165e8cb2004-07-20 06:44:46 +00002438
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002439 if (!buf[0])
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002440 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002441 if (*buf == ':')
2442 buf++; // move past the ':'
2443
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002444 li = i = 0;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002445 b = e = -1;
2446 q = text; // assume 1,$ for the range
2447 r = end - 1;
2448 li = count_lines(text, end - 1);
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002449 fn = current_filename;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002450
2451 // look for optional address(es) :. :1 :1,9 :'q,'a :%
2452 buf = get_address(buf, &b, &e);
2453
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002454# if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002455 // remember orig command line
2456 orig_buf = buf;
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002457# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002458
2459 // get the COMMAND into cmd[]
2460 buf1 = cmd;
2461 while (*buf != '\0') {
2462 if (isspace(*buf))
2463 break;
2464 *buf1++ = *buf++;
2465 }
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002466 *buf1 = '\0';
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002467 // get any ARGuments
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002468 while (isblank(*buf))
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002469 buf++;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002470 strcpy(args, buf);
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002471 useforce = FALSE;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002472 buf1 = last_char_is(cmd, '!');
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002473 if (buf1) {
2474 useforce = TRUE;
2475 *buf1 = '\0'; // get rid of !
2476 }
2477 if (b >= 0) {
2478 // if there is only one addr, then the addr
2479 // is the line number of the single line the
2480 // user wants. So, reset the end
2481 // pointer to point at end of the "b" line
2482 q = find_line(b); // what line is #b
2483 r = end_line(q);
2484 li = 1;
2485 }
2486 if (e >= 0) {
2487 // we were given two addrs. change the
2488 // end pointer to the addr given by user.
2489 r = find_line(e); // what line is #e
2490 r = end_line(r);
2491 li = e - b + 1;
2492 }
2493 // ------------ now look for the command ------------
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002494 i = strlen(cmd);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002495 if (i == 0) { // :123CR goto line #123
2496 if (b >= 0) {
2497 dot = find_line(b); // what line is #b
2498 dot_skip_over_ws();
2499 }
Denis Vlasenko249fabf2006-12-19 00:29:22 +00002500 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002501# if ENABLE_FEATURE_ALLOW_EXEC
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002502 else if (cmd[0] == '!') { // run a cmd
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002503 int retcode;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002504 // :!ls run the <cmd>
Denis Vlasenko267e16c2008-10-14 10:34:41 +00002505 go_bottom_and_clear_to_eol();
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002506 cookmode();
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002507 retcode = system(orig_buf + 1); // run the cmd
2508 if (retcode)
2509 printf("\nshell returned %i\n\n", retcode);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002510 rawmode();
2511 Hit_Return(); // let user see results
Denis Vlasenko249fabf2006-12-19 00:29:22 +00002512 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002513# endif
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002514 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002515 if (b < 0) { // no addr given- use defaults
2516 b = e = count_lines(text, dot);
2517 }
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002518 status_line("%d", b);
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002519 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002520 if (b < 0) { // no addr given- use defaults
2521 q = begin_line(dot); // assume .,. for the range
2522 r = end_line(dot);
2523 }
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02002524 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002525 dot_skip_over_ws();
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002526 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002527 int size;
2528
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002529 // don't edit, if the current file has been modified
Denys Vlasenkoe7430862014-04-03 12:47:48 +02002530 if (modified_count && !useforce) {
Dennis Groenenc0657e02012-01-31 14:12:38 +01002531 status_line_bold("No write since last change (:%s! overrides)", cmd);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002532 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002533 }
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002534 if (args[0]) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002535 // the user supplied a file name
Denis Vlasenkoe8a07882007-06-10 15:08:44 +00002536 fn = args;
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002537 } else if (current_filename && current_filename[0]) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002538 // no user supplied name- use the current filename
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002539 // fn = current_filename; was set by default
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002540 } else {
2541 // no user file name, no current name- punt
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002542 status_line_bold("No current filename");
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002543 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002544 }
2545
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002546 size = init_text_buffer(fn);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002547
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002548# if ENABLE_FEATURE_VI_YANKMARK
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002549 if (Ureg >= 0 && Ureg < 28) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002550 free(reg[Ureg]); // free orig line reg- for 'U'
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002551 reg[Ureg] = NULL;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002552 }
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002553 if (YDreg >= 0 && YDreg < 28) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002554 free(reg[YDreg]); // free default yank/delete register
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002555 reg[YDreg] = NULL;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002556 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002557# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002558 // how many lines in text[]?
2559 li = count_lines(text, end - 1);
Denys Vlasenko778794d2013-01-22 10:13:52 +01002560 status_line("'%s'%s"
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00002561 IF_FEATURE_VI_READONLY("%s")
Denys Vlasenko89393592019-04-02 12:45:30 +02002562 " %uL, %uC",
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002563 current_filename,
2564 (size < 0 ? " [New file]" : ""),
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00002565 IF_FEATURE_VI_READONLY(
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002566 ((readonly_mode) ? " [Readonly]" : ""),
2567 )
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002568 li, (int)(end - text)
2569 );
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002570 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002571 if (b != -1 || e != -1) {
Denys Vlasenkoc2704542009-11-20 19:14:19 +01002572 status_line_bold("No address allowed on this command");
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002573 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002574 }
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002575 if (args[0]) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002576 // user wants a new filename
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002577 free(current_filename);
2578 current_filename = xstrdup(args);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002579 } else {
2580 // user wants file status info
Paul Fox8552aec2005-09-16 12:20:05 +00002581 last_status_cksum = 0; // force status update
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002582 }
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002583 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002584 // print out values of all features
Denis Vlasenko267e16c2008-10-14 10:34:41 +00002585 go_bottom_and_clear_to_eol();
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002586 cookmode();
2587 show_help();
2588 rawmode();
2589 Hit_Return();
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002590 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002591 if (b < 0) { // no addr given- use defaults
2592 q = begin_line(dot); // assume .,. for the range
2593 r = end_line(dot);
2594 }
Denis Vlasenko267e16c2008-10-14 10:34:41 +00002595 go_bottom_and_clear_to_eol();
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002596 puts("\r");
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002597 for (; q <= r; q++) {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002598 int c_is_no_print;
2599
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002600 c = *q;
Denis Vlasenko2a51af22007-03-21 22:31:24 +00002601 c_is_no_print = (c & 0x80) && !Isprint(c);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002602 if (c_is_no_print) {
2603 c = '.';
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002604 standout_start();
Denis Vlasenkod3c042f2007-12-30 01:59:53 +00002605 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002606 if (c == '\n') {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002607 write1("$\r");
2608 } else if (c < ' ' || c == 127) {
Denis Vlasenko4daad902007-09-27 10:20:47 +00002609 bb_putchar('^');
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002610 if (c == 127)
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002611 c = '?';
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002612 else
2613 c += '@';
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002614 }
Denis Vlasenko4daad902007-09-27 10:20:47 +00002615 bb_putchar(c);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002616 if (c_is_no_print)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002617 standout_end();
2618 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002619 Hit_Return();
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002620 } else if (strncmp(cmd, "quit", i) == 0 // quit
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002621 || strncmp(cmd, "next", i) == 0 // edit next file
Dennis Groenenc0657e02012-01-31 14:12:38 +01002622 || strncmp(cmd, "prev", i) == 0 // edit previous file
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002623 ) {
Denys Vlasenko04cecd52010-04-16 22:13:55 -07002624 int n;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002625 if (useforce) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002626 if (*cmd == 'q') {
Dennis Groenenc0657e02012-01-31 14:12:38 +01002627 // force end of argv list
Denys Vlasenkoa3ce1612019-04-03 16:35:23 +02002628 optind = cmdline_filecnt;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002629 }
2630 editing = 0;
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002631 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002632 }
2633 // don't exit if the file been modified
Denys Vlasenkoe7430862014-04-03 12:47:48 +02002634 if (modified_count) {
Dennis Groenenc0657e02012-01-31 14:12:38 +01002635 status_line_bold("No write since last change (:%s! overrides)", cmd);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002636 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002637 }
2638 // are there other file to edit
Denys Vlasenko89393592019-04-02 12:45:30 +02002639 n = cmdline_filecnt - optind - 1;
Denys Vlasenko04cecd52010-04-16 22:13:55 -07002640 if (*cmd == 'q' && n > 0) {
Denys Vlasenko89393592019-04-02 12:45:30 +02002641 status_line_bold("%u more file(s) to edit", n);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002642 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002643 }
Denys Vlasenko04cecd52010-04-16 22:13:55 -07002644 if (*cmd == 'n' && n <= 0) {
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002645 status_line_bold("No more files to edit");
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002646 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002647 }
Dennis Groenenc0657e02012-01-31 14:12:38 +01002648 if (*cmd == 'p') {
2649 // are there previous files to edit
2650 if (optind < 1) {
2651 status_line_bold("No previous files to edit");
2652 goto ret;
2653 }
2654 optind -= 2;
2655 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002656 editing = 0;
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002657 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002658 int size;
2659
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002660 fn = args;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002661 if (!fn[0]) {
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002662 status_line_bold("No filename given");
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002663 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002664 }
2665 if (b < 0) { // no addr given- use defaults
2666 q = begin_line(dot); // assume "dot"
2667 }
2668 // read after current line- unless user said ":0r foo"
Ron Yorston70f43202014-11-30 20:39:53 +00002669 if (b != 0) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002670 q = next_line(q);
Ron Yorston70f43202014-11-30 20:39:53 +00002671 // read after last line
2672 if (q == end-1)
2673 ++q;
2674 }
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00002675 { // dance around potentially-reallocated text[]
2676 uintptr_t ofs = q - text;
Ron Yorstone5213ce2014-11-30 20:39:25 +00002677 size = file_insert(fn, q, 0);
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00002678 q = text + ofs;
2679 }
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002680 if (size < 0)
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002681 goto ret; // nothing was inserted
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002682 // how many lines in text[]?
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002683 li = count_lines(q, q + size - 1);
Denys Vlasenko778794d2013-01-22 10:13:52 +01002684 status_line("'%s'"
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00002685 IF_FEATURE_VI_READONLY("%s")
Denys Vlasenko89393592019-04-02 12:45:30 +02002686 " %uL, %uC",
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002687 fn,
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00002688 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002689 li, size
2690 );
2691 if (size > 0) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002692 // if the insert is before "dot" then we need to update
2693 if (q <= dot)
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002694 dot += size;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002695 }
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002696 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
Denys Vlasenkoe7430862014-04-03 12:47:48 +02002697 if (modified_count && !useforce) {
Dennis Groenenc0657e02012-01-31 14:12:38 +01002698 status_line_bold("No write since last change (:%s! overrides)", cmd);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002699 } else {
2700 // reset the filenames to edit
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02002701 optind = -1; // start from 0th file
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002702 editing = 0;
2703 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002704# if ENABLE_FEATURE_VI_SET
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002705 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002706# if ENABLE_FEATURE_VI_SETOPTS
Denis Vlasenkof9234132007-03-21 00:03:42 +00002707 char *argp;
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002708# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002709 i = 0; // offset into args
Denys Vlasenko605f2642012-06-11 01:53:33 +02002710 // only blank is regarded as args delimiter. What about tab '\t'?
Denis Vlasenkof9234132007-03-21 00:03:42 +00002711 if (!args[0] || strcasecmp(args, "all") == 0) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002712 // print out values of all options
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002713# if ENABLE_FEATURE_VI_SETOPTS
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002714 status_line_bold(
2715 "%sautoindent "
2716 "%sflash "
2717 "%signorecase "
2718 "%sshowmatch "
2719 "tabstop=%u",
2720 autoindent ? "" : "no",
2721 err_method ? "" : "no",
2722 ignorecase ? "" : "no",
2723 showmatch ? "" : "no",
2724 tabstop
2725 );
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002726# endif
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002727 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002728 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002729# if ENABLE_FEATURE_VI_SETOPTS
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002730 argp = args;
Denis Vlasenkoba2fb712007-04-01 09:39:03 +00002731 while (*argp) {
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002732 if (strncmp(argp, "no", 2) == 0)
Denis Vlasenkof9234132007-03-21 00:03:42 +00002733 i = 2; // ":set noautoindent"
2734 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002735 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
Denis Vlasenkof9234132007-03-21 00:03:42 +00002736 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002737 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
2738 if (strncmp(argp + i, "tabstop=", 8) == 0) {
2739 int t = 0;
2740 sscanf(argp + i+8, "%u", &t);
2741 if (t > 0 && t <= MAX_TABSTOP)
2742 tabstop = t;
Denis Vlasenkof9234132007-03-21 00:03:42 +00002743 }
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002744 argp = skip_non_whitespace(argp);
2745 argp = skip_whitespace(argp);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002746 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002747# endif /* FEATURE_VI_SETOPTS */
2748# endif /* FEATURE_VI_SET */
2749
2750# if ENABLE_FEATURE_VI_SEARCH
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002751 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002752 char *F, *R, *flags;
2753 size_t len_F, len_R;
2754 int gflag; // global replace flag
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002755# if ENABLE_FEATURE_VI_UNDO
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02002756 int dont_chain_first_item = ALLOW_UNDO;
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002757# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002758
2759 // F points to the "find" pattern
2760 // R points to the "replace" pattern
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002761 // replace the cmd line delimiters "/" with NULs
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002762 c = orig_buf[1]; // what is the delimiter
2763 F = orig_buf + 2; // start of "find"
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002764 R = strchr(F, c); // middle delimiter
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00002765 if (!R)
2766 goto colon_s_fail;
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002767 len_F = R - F;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002768 *R++ = '\0'; // terminate "find"
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002769 flags = strchr(R, c);
2770 if (!flags)
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00002771 goto colon_s_fail;
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002772 len_R = flags - R;
2773 *flags++ = '\0'; // terminate "replace"
2774 gflag = *flags;
2775
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002776 q = begin_line(q);
2777 if (b < 0) { // maybe :s/foo/bar/
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002778 q = begin_line(dot); // start with cur line
2779 b = count_lines(text, q); // cur line number
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002780 }
2781 if (e < 0)
2782 e = b; // maybe :.s/foo/bar/
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002783
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002784 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002785 char *ls = q; // orig line start
2786 char *found;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002787 vc4:
Denys Vlasenkob7330462018-11-29 14:39:52 +01002788 found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002789 if (found) {
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00002790 uintptr_t bias;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002791 // we found the "find" pattern - delete it
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02002792 // For undo support, the first item should not be chained
2793 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002794# if ENABLE_FEATURE_VI_UNDO
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02002795 dont_chain_first_item = ALLOW_UNDO_CHAIN;
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002796# endif
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02002797 // insert the "replace" patern
2798 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002799 found += bias;
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00002800 ls += bias;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02002801 //q += bias; - recalculated anyway
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002802 // check for "global" :s/foo/bar/g
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002803 if (gflag == 'g') {
2804 if ((found + len_R) < end_line(ls)) {
2805 q = found + len_R;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002806 goto vc4; // don't let q move past cur line
2807 }
2808 }
2809 }
2810 q = next_line(ls);
2811 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002812# endif /* FEATURE_VI_SEARCH */
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002813 } else if (strncmp(cmd, "version", i) == 0) { // show software version
Denys Vlasenkoeba7fe62017-01-29 19:14:26 +01002814 status_line(BB_VER);
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002815 } else if (strncmp(cmd, "write", i) == 0 // write text to file
2816 || strncmp(cmd, "wq", i) == 0
2817 || strncmp(cmd, "wn", i) == 0
2818 || (cmd[0] == 'x' && !cmd[1])
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002819 ) {
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002820 int size;
2821 //int forced = FALSE;
2822
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002823 // is there a file name to write to?
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002824 if (args[0]) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002825 fn = args;
2826 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002827# if ENABLE_FEATURE_VI_READONLY
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002828 if (readonly_mode && !useforce) {
Denys Vlasenko778794d2013-01-22 10:13:52 +01002829 status_line_bold("'%s' is read only", fn);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002830 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002831 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002832# endif
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002833 //if (useforce) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002834 // if "fn" is not write-able, chmod u+w
2835 // sprintf(syscmd, "chmod u+w %s", fn);
2836 // system(syscmd);
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002837 // forced = TRUE;
2838 //}
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01002839 if (modified_count != 0 || cmd[0] != 'x') {
2840 size = r - q + 1;
2841 l = file_write(fn, q, r);
2842 } else {
2843 size = 0;
2844 l = 0;
2845 }
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002846 //if (useforce && forced) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002847 // chmod u-w
2848 // sprintf(syscmd, "chmod u-w %s", fn);
2849 // system(syscmd);
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002850 // forced = FALSE;
2851 //}
Paul Fox61e45db2005-10-09 14:43:22 +00002852 if (l < 0) {
2853 if (l == -1)
Denys Vlasenko9e7c0022013-03-15 02:17:29 +01002854 status_line_bold_errno(fn);
Paul Fox61e45db2005-10-09 14:43:22 +00002855 } else {
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01002856 // how many lines written
2857 li = count_lines(q, q + l - 1);
Denys Vlasenko89393592019-04-02 12:45:30 +02002858 status_line("'%s' %uL, %uC", fn, li, l);
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01002859 if (l == size) {
2860 if (q == text && q + l == end) {
2861 modified_count = 0;
2862 last_modified_count = -1;
2863 }
2864 if (cmd[0] == 'x'
2865 || cmd[1] == 'q' || cmd[1] == 'n'
2866 || cmd[1] == 'Q' || cmd[1] == 'N'
2867 ) {
2868 editing = 0;
2869 }
Paul Fox61e45db2005-10-09 14:43:22 +00002870 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002871 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002872# if ENABLE_FEATURE_VI_YANKMARK
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002873 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002874 if (b < 0) { // no addr given- use defaults
2875 q = begin_line(dot); // assume .,. for the range
2876 r = end_line(dot);
2877 }
2878 text_yank(q, r, YDreg);
2879 li = count_lines(q, r);
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002880 status_line("Yank %d lines (%d chars) into [%c]",
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002881 li, strlen(reg[YDreg]), what_reg());
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002882# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002883 } else {
2884 // cmd unknown
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002885 not_implemented(cmd);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002886 }
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002887 ret:
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002888 dot = bound_dot(dot); // make sure "dot" is valid
2889 return;
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002890# if ENABLE_FEATURE_VI_SEARCH
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002891 colon_s_fail:
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002892 status_line(":s expression missing delimiters");
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002893# endif
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00002894#endif /* FEATURE_VI_COLON */
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002895}
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002896
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002897//----- Char Routines --------------------------------------------
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02002898// Chars that are part of a word-
2899// 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2900// Chars that are Not part of a word (stoppers)
2901// !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2902// Chars that are WhiteSpace
2903// TAB NEWLINE VT FF RETURN SPACE
2904// DO NOT COUNT NEWLINE AS WHITESPACE
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002905
Denis Vlasenko33875382008-06-21 20:31:50 +00002906static int st_test(char *p, int type, int dir, char *tested)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002907{
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002908 char c, c0, ci;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002909 int test, inc;
2910
2911 inc = dir;
2912 c = c0 = p[0];
2913 ci = p[inc];
2914 test = 0;
2915
2916 if (type == S_BEFORE_WS) {
2917 c = ci;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02002918 test = (!isspace(c) || c == '\n');
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002919 }
2920 if (type == S_TO_WS) {
2921 c = c0;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02002922 test = (!isspace(c) || c == '\n');
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002923 }
2924 if (type == S_OVER_WS) {
2925 c = c0;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02002926 test = isspace(c);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002927 }
2928 if (type == S_END_PUNCT) {
2929 c = ci;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02002930 test = ispunct(c);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002931 }
2932 if (type == S_END_ALNUM) {
2933 c = ci;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02002934 test = (isalnum(c) || c == '_');
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002935 }
2936 *tested = c;
Denis Vlasenkod9e15f22006-11-27 16:49:55 +00002937 return test;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002938}
2939
Denis Vlasenko33875382008-06-21 20:31:50 +00002940static char *skip_thing(char *p, int linecnt, int dir, int type)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002941{
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002942 char c;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002943
2944 while (st_test(p, type, dir, &c)) {
2945 // make sure we limit search to correct number of lines
2946 if (c == '\n' && --linecnt < 1)
2947 break;
2948 if (dir >= 0 && p >= end - 1)
2949 break;
2950 if (dir < 0 && p <= text)
2951 break;
2952 p += dir; // move to next char
2953 }
Denis Vlasenko079f8af2006-11-27 16:49:31 +00002954 return p;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002955}
2956
Denys Vlasenko616e4692019-04-01 14:02:37 +02002957#if ENABLE_FEATURE_VI_USE_SIGNALS
2958static void winch_handler(int sig UNUSED_PARAM)
2959{
2960 int save_errno = errno;
2961 // FIXME: do it in main loop!!!
2962 signal(SIGWINCH, winch_handler);
2963 query_screen_dimensions();
2964 new_screen(rows, columns); // get memory for virtual screen
2965 redraw(TRUE); // re-draw the screen
2966 errno = save_errno;
2967}
2968static void tstp_handler(int sig UNUSED_PARAM)
2969{
2970 int save_errno = errno;
2971
2972 // ioctl inside cookmode() was seen to generate SIGTTOU,
2973 // stopping us too early. Prevent that:
2974 signal(SIGTTOU, SIG_IGN);
2975
2976 go_bottom_and_clear_to_eol();
2977 cookmode(); // terminal to "cooked"
2978
2979 // stop now
2980 //signal(SIGTSTP, SIG_DFL);
2981 //raise(SIGTSTP);
2982 raise(SIGSTOP); // avoid "dance" with TSTP handler - use SIGSTOP instead
2983 //signal(SIGTSTP, tstp_handler);
2984
2985 // we have been "continued" with SIGCONT, restore screen and termios
2986 rawmode(); // terminal to "raw"
2987 last_status_cksum = 0; // force status update
2988 redraw(TRUE); // re-draw the screen
2989
2990 errno = save_errno;
2991}
2992static void int_handler(int sig)
2993{
2994 signal(SIGINT, int_handler);
2995 siglongjmp(restart, sig);
2996}
2997#endif /* FEATURE_VI_USE_SIGNALS */
2998
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02002999static void do_cmd(int c);
3000
3001static int find_range(char **start, char **stop, char c)
3002{
3003 char *save_dot, *p, *q, *t;
Ron Yorston7b93e312019-04-28 09:10:16 +01003004 int cnt, multiline = 0, forward;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003005
3006 save_dot = dot;
3007 p = q = dot;
3008
Ron Yorston7b93e312019-04-28 09:10:16 +01003009 // will a 'G' command move forwards or backwards?
3010 forward = cmdcnt == 0 || cmdcnt > count_lines(text, dot);
3011
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003012 if (strchr("cdy><", c)) {
3013 // these cmds operate on whole lines
3014 p = q = begin_line(p);
3015 for (cnt = 1; cnt < cmdcnt; cnt++) {
3016 q = next_line(q);
3017 }
3018 q = end_line(q);
3019 } else if (strchr("^%$0bBeEfth\b\177", c)) {
3020 // These cmds operate on char positions
3021 do_cmd(c); // execute movement cmd
3022 q = dot;
3023 } else if (strchr("wW", c)) {
3024 do_cmd(c); // execute movement cmd
3025 // if we are at the next word's first char
3026 // step back one char
3027 // but check the possibilities when it is true
3028 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
3029 || (ispunct(dot[-1]) && !ispunct(dot[0]))
3030 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
3031 dot--; // move back off of next word
3032 if (dot > text && *dot == '\n')
3033 dot--; // stay off NL
3034 q = dot;
Ron Yorston7b93e312019-04-28 09:10:16 +01003035 } else if (strchr("H-k{", c) || (c == 'G' && !forward)) {
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003036 // these operate on multi-lines backwards
3037 q = end_line(dot); // find NL
3038 do_cmd(c); // execute movement cmd
3039 dot_begin();
3040 p = dot;
Ron Yorston7b93e312019-04-28 09:10:16 +01003041 } else if (strchr("L+j}\r\n", c) || (c == 'G' && forward)) {
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003042 // these operate on multi-lines forwards
3043 p = begin_line(dot);
3044 do_cmd(c); // execute movement cmd
3045 dot_end(); // find NL
3046 q = dot;
3047 } else {
3048 // nothing -- this causes any other values of c to
3049 // represent the one-character range under the
3050 // cursor. this is correct for ' ' and 'l', but
3051 // perhaps no others.
3052 //
3053 }
3054 if (q < p) {
3055 t = q;
3056 q = p;
3057 p = t;
3058 }
3059
3060 // backward char movements don't include start position
3061 if (q > p && strchr("^0bBh\b\177", c)) q--;
3062
3063 multiline = 0;
3064 for (t = p; t <= q; t++) {
3065 if (*t == '\n') {
3066 multiline = 1;
3067 break;
3068 }
3069 }
3070
3071 *start = p;
3072 *stop = q;
3073 dot = save_dot;
3074 return multiline;
3075}
3076
Eric Andersen3f980402001-04-04 17:31:15 +00003077//---------------------------------------------------------------------
3078//----- the Ascii Chart -----------------------------------------------
Eric Andersen3f980402001-04-04 17:31:15 +00003079// 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3080// 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3081// 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3082// 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3083// 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3084// 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3085// 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3086// 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3087// 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3088// 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3089// 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3090// 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3091// 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3092// 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3093// 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3094// 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3095//---------------------------------------------------------------------
3096
3097//----- Execute a Vi Command -----------------------------------
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003098static void do_cmd(int c)
Eric Andersen3f980402001-04-04 17:31:15 +00003099{
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003100 char *p, *q, *save_dot;
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003101 char buf[12];
Denis Vlasenkoc3a9dc82008-10-29 00:58:04 +00003102 int dir;
Paul Foxc51fc7b2008-03-06 01:34:23 +00003103 int cnt, i, j;
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003104 int c1;
Eric Andersen3f980402001-04-04 17:31:15 +00003105
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +00003106// c1 = c; // quiet the compiler
3107// cnt = yf = 0; // quiet the compiler
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003108// p = q = save_dot = buf; // quiet the compiler
3109 memset(buf, '\0', sizeof(buf));
Eric Andersenbff7a602001-11-17 07:15:43 +00003110
Paul Fox8552aec2005-09-16 12:20:05 +00003111 show_status_line();
3112
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02003113 // if this is a cursor key, skip these checks
Eric Andersenbff7a602001-11-17 07:15:43 +00003114 switch (c) {
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003115 case KEYCODE_UP:
3116 case KEYCODE_DOWN:
3117 case KEYCODE_LEFT:
3118 case KEYCODE_RIGHT:
3119 case KEYCODE_HOME:
3120 case KEYCODE_END:
3121 case KEYCODE_PAGEUP:
3122 case KEYCODE_PAGEDOWN:
3123 case KEYCODE_DELETE:
Eric Andersenbff7a602001-11-17 07:15:43 +00003124 goto key_cmd_mode;
3125 }
3126
Eric Andersen3f980402001-04-04 17:31:15 +00003127 if (cmd_mode == 2) {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003128 // flip-flop Insert/Replace mode
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003129 if (c == KEYCODE_INSERT)
Denis Vlasenko2a51af22007-03-21 22:31:24 +00003130 goto dc_i;
Eric Andersen3f980402001-04-04 17:31:15 +00003131 // we are 'R'eplacing the current *dot with new char
3132 if (*dot == '\n') {
3133 // don't Replace past E-o-l
3134 cmd_mode = 1; // convert to insert
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003135 undo_queue_commit();
Eric Andersen3f980402001-04-04 17:31:15 +00003136 } else {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003137 if (1 <= c || Isprint(c)) {
Eric Andersen3f980402001-04-04 17:31:15 +00003138 if (c != 27)
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003139 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3140 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
Eric Andersen3f980402001-04-04 17:31:15 +00003141 }
3142 goto dc1;
3143 }
3144 }
3145 if (cmd_mode == 1) {
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02003146 // hitting "Insert" twice means "R" replace mode
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003147 if (c == KEYCODE_INSERT) goto dc5;
Eric Andersen3f980402001-04-04 17:31:15 +00003148 // insert the char c at "dot"
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003149 if (1 <= c || Isprint(c)) {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003150 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
Eric Andersen3f980402001-04-04 17:31:15 +00003151 }
3152 goto dc1;
3153 }
3154
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003155 key_cmd_mode:
Eric Andersen3f980402001-04-04 17:31:15 +00003156 switch (c) {
Eric Andersen822c3832001-05-07 17:37:43 +00003157 //case 0x01: // soh
3158 //case 0x09: // ht
3159 //case 0x0b: // vt
3160 //case 0x0e: // so
3161 //case 0x0f: // si
3162 //case 0x10: // dle
3163 //case 0x11: // dc1
3164 //case 0x13: // dc3
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003165#if ENABLE_FEATURE_VI_CRASHME
Eric Andersen1c0d3112001-04-16 15:46:44 +00003166 case 0x14: // dc4 ctrl-T
Eric Andersen3f980402001-04-04 17:31:15 +00003167 crashme = (crashme == 0) ? 1 : 0;
Eric Andersen3f980402001-04-04 17:31:15 +00003168 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003169#endif
Eric Andersen822c3832001-05-07 17:37:43 +00003170 //case 0x16: // syn
3171 //case 0x17: // etb
3172 //case 0x18: // can
3173 //case 0x1c: // fs
3174 //case 0x1d: // gs
3175 //case 0x1e: // rs
3176 //case 0x1f: // us
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003177 //case '!': // !-
3178 //case '#': // #-
3179 //case '&': // &-
3180 //case '(': // (-
3181 //case ')': // )-
3182 //case '*': // *-
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003183 //case '=': // =-
3184 //case '@': // @-
3185 //case 'F': // F-
3186 //case 'K': // K-
3187 //case 'Q': // Q-
3188 //case 'S': // S-
3189 //case 'T': // T-
3190 //case 'V': // V-
3191 //case '[': // [-
3192 //case '\\': // \-
3193 //case ']': // ]-
3194 //case '_': // _-
3195 //case '`': // `-
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003196 //case 'v': // v-
Denys Vlasenkob22bbff2009-07-04 16:50:43 +02003197 default: // unrecognized command
Eric Andersen3f980402001-04-04 17:31:15 +00003198 buf[0] = c;
3199 buf[1] = '\0';
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003200 not_implemented(buf);
Eric Andersen3f980402001-04-04 17:31:15 +00003201 end_cmd_q(); // stop adding to q
3202 case 0x00: // nul- ignore
3203 break;
3204 case 2: // ctrl-B scroll up full screen
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003205 case KEYCODE_PAGEUP: // Cursor Key Page Up
Eric Andersen3f980402001-04-04 17:31:15 +00003206 dot_scroll(rows - 2, -1);
3207 break;
Eric Andersen3f980402001-04-04 17:31:15 +00003208 case 4: // ctrl-D scroll down half screen
3209 dot_scroll((rows - 2) / 2, 1);
3210 break;
3211 case 5: // ctrl-E scroll down one line
3212 dot_scroll(1, 1);
3213 break;
3214 case 6: // ctrl-F scroll down full screen
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003215 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
Eric Andersen3f980402001-04-04 17:31:15 +00003216 dot_scroll(rows - 2, 1);
3217 break;
3218 case 7: // ctrl-G show current status
Paul Fox8552aec2005-09-16 12:20:05 +00003219 last_status_cksum = 0; // force status update
Eric Andersen3f980402001-04-04 17:31:15 +00003220 break;
3221 case 'h': // h- move left
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003222 case KEYCODE_LEFT: // cursor key Left
Paul Foxd13b90b2005-07-18 22:17:25 +00003223 case 8: // ctrl-H- move left (This may be ERASE char)
Denis Vlasenko2a51af22007-03-21 22:31:24 +00003224 case 0x7f: // DEL- move left (This may be ERASE char)
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003225 do {
3226 dot_left();
3227 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003228 break;
3229 case 10: // Newline ^J
3230 case 'j': // j- goto next line, same col
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003231 case KEYCODE_DOWN: // cursor key Down
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003232 do {
3233 dot_next(); // go to next B-o-l
3234 // try stay in same col
3235 dot = move_to_col(dot, ccol + offset);
3236 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003237 break;
3238 case 12: // ctrl-L force redraw whole screen
Eric Andersen1c0d3112001-04-16 15:46:44 +00003239 case 18: // ctrl-R force redraw
Ron Yorston55279672016-04-26 15:23:38 +01003240 redraw(TRUE); // this will redraw the entire display
Eric Andersen3f980402001-04-04 17:31:15 +00003241 break;
3242 case 13: // Carriage Return ^M
3243 case '+': // +- goto next line
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003244 do {
3245 dot_next();
3246 dot_skip_over_ws();
3247 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003248 break;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02003249 case 21: // ctrl-U scroll up half screen
Eric Andersen3f980402001-04-04 17:31:15 +00003250 dot_scroll((rows - 2) / 2, -1);
3251 break;
3252 case 25: // ctrl-Y scroll up one line
3253 dot_scroll(1, -1);
3254 break;
Eric Andersen822c3832001-05-07 17:37:43 +00003255 case 27: // esc
Eric Andersen3f980402001-04-04 17:31:15 +00003256 if (cmd_mode == 0)
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003257 indicate_error();
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02003258 cmd_mode = 0; // stop inserting
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003259 undo_queue_commit();
Eric Andersen3f980402001-04-04 17:31:15 +00003260 end_cmd_q();
Paul Fox8552aec2005-09-16 12:20:05 +00003261 last_status_cksum = 0; // force status update
Eric Andersen3f980402001-04-04 17:31:15 +00003262 break;
3263 case ' ': // move right
3264 case 'l': // move right
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003265 case KEYCODE_RIGHT: // Cursor Key Right
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003266 do {
3267 dot_right();
3268 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003269 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003270#if ENABLE_FEATURE_VI_YANKMARK
Eric Andersen3f980402001-04-04 17:31:15 +00003271 case '"': // "- name a register to use for Delete/Yank
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003272 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3273 if ((unsigned)c1 <= 25) { // a-z?
3274 YDreg = c1;
Eric Andersen3f980402001-04-04 17:31:15 +00003275 } else {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003276 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003277 }
3278 break;
3279 case '\'': // '- goto a specific mark
Denys Vlasenko61fcc8c2016-09-28 16:23:05 +02003280 c1 = (get_one_char() | 0x20);
3281 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3282 c1 = (c1 - 'a');
Eric Andersen3f980402001-04-04 17:31:15 +00003283 // get the b-o-l
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003284 q = mark[c1];
Eric Andersen3f980402001-04-04 17:31:15 +00003285 if (text <= q && q < end) {
3286 dot = q;
3287 dot_begin(); // go to B-o-l
3288 dot_skip_over_ws();
3289 }
3290 } else if (c1 == '\'') { // goto previous context
3291 dot = swap_context(dot); // swap current and previous context
3292 dot_begin(); // go to B-o-l
3293 dot_skip_over_ws();
3294 } else {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003295 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003296 }
3297 break;
3298 case 'm': // m- Mark a line
3299 // this is really stupid. If there are any inserts or deletes
3300 // between text[0] and dot then this mark will not point to the
3301 // correct location! It could be off by many lines!
3302 // Well..., at least its quick and dirty.
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003303 c1 = (get_one_char() | 0x20) - 'a';
3304 if ((unsigned)c1 <= 25) { // a-z?
Eric Andersen3f980402001-04-04 17:31:15 +00003305 // remember the line
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003306 mark[c1] = dot;
Eric Andersen3f980402001-04-04 17:31:15 +00003307 } else {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003308 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003309 }
3310 break;
3311 case 'P': // P- Put register before
3312 case 'p': // p- put register after
3313 p = reg[YDreg];
Denis Vlasenko00d84172008-11-24 07:34:42 +00003314 if (p == NULL) {
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003315 status_line_bold("Nothing in register %c", what_reg());
Eric Andersen3f980402001-04-04 17:31:15 +00003316 break;
3317 }
3318 // are we putting whole lines or strings
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003319 if (strchr(p, '\n') != NULL) {
Eric Andersen3f980402001-04-04 17:31:15 +00003320 if (c == 'P') {
3321 dot_begin(); // putting lines- Put above
3322 }
3323 if (c == 'p') {
3324 // are we putting after very last line?
3325 if (end_line(dot) == (end - 1)) {
3326 dot = end; // force dot to end of text[]
3327 } else {
3328 dot_next(); // next line, then put before
3329 }
3330 }
3331 } else {
3332 if (c == 'p')
3333 dot_right(); // move to right, can move to NL
3334 }
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003335 string_insert(dot, p, ALLOW_UNDO); // insert the string
Eric Andersen3f980402001-04-04 17:31:15 +00003336 end_cmd_q(); // stop adding to q
3337 break;
Eric Andersen3f980402001-04-04 17:31:15 +00003338 case 'U': // U- Undo; replace current line with original version
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003339 if (reg[Ureg] != NULL) {
Eric Andersen3f980402001-04-04 17:31:15 +00003340 p = begin_line(dot);
3341 q = end_line(dot);
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003342 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3343 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
Eric Andersen3f980402001-04-04 17:31:15 +00003344 dot = p;
3345 dot_skip_over_ws();
3346 }
3347 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003348#endif /* FEATURE_VI_YANKMARK */
Andrew Fuller4d8ddb82015-05-03 18:18:25 +02003349#if ENABLE_FEATURE_VI_UNDO
3350 case 'u': // u- undo last operation
3351 undo_pop();
3352 break;
3353#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003354 case '$': // $- goto end of line
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003355 case KEYCODE_END: // Cursor Key End
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003356 for (;;) {
3357 dot = end_line(dot);
Denys Vlasenko1fd71292011-11-28 04:55:48 +01003358 if (--cmdcnt <= 0)
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003359 break;
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01003360 dot_next();
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01003361 }
Eric Andersen3f980402001-04-04 17:31:15 +00003362 break;
3363 case '%': // %- find matching char of pair () [] {}
3364 for (q = dot; q < end && *q != '\n'; q++) {
3365 if (strchr("()[]{}", *q) != NULL) {
3366 // we found half of a pair
3367 p = find_pair(q, *q);
3368 if (p == NULL) {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003369 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003370 } else {
3371 dot = p;
3372 }
3373 break;
3374 }
3375 }
3376 if (*q == '\n')
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003377 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003378 break;
3379 case 'f': // f- forward to a user specified char
3380 last_forward_char = get_one_char(); // get the search char
3381 //
Eric Andersenaff114c2004-04-14 17:51:38 +00003382 // dont separate these two commands. 'f' depends on ';'
Eric Andersen3f980402001-04-04 17:31:15 +00003383 //
Bernhard Reutner-Fischera985d302008-02-11 11:44:38 +00003384 //**** fall through to ... ';'
Eric Andersen3f980402001-04-04 17:31:15 +00003385 case ';': // ;- look at rest of line for last forward char
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003386 do {
3387 if (last_forward_char == 0)
3388 break;
3389 q = dot + 1;
3390 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3391 q++;
3392 }
3393 if (*q == last_forward_char)
3394 dot = q;
3395 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003396 break;
Paul Foxb5ee8db2008-02-14 01:17:01 +00003397 case ',': // repeat latest 'f' in opposite direction
Paul Foxb5ee8db2008-02-14 01:17:01 +00003398 if (last_forward_char == 0)
3399 break;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003400 do {
3401 q = dot - 1;
3402 while (q >= text && *q != '\n' && *q != last_forward_char) {
3403 q--;
3404 }
3405 if (q >= text && *q == last_forward_char)
3406 dot = q;
3407 } while (--cmdcnt > 0);
Paul Foxb5ee8db2008-02-14 01:17:01 +00003408 break;
3409
Eric Andersen3f980402001-04-04 17:31:15 +00003410 case '-': // -- goto prev line
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003411 do {
3412 dot_prev();
3413 dot_skip_over_ws();
3414 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003415 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003416#if ENABLE_FEATURE_VI_DOT_CMD
Eric Andersen3f980402001-04-04 17:31:15 +00003417 case '.': // .- repeat the last modifying command
3418 // Stuff the last_modifying_cmd back into stdin
3419 // and let it be re-executed.
Denys Vlasenko2a576082019-04-01 16:15:51 +02003420 if (lmc_len != 0) {
3421 ioq = ioq_start = xstrndup(last_modifying_cmd, lmc_len);
Eric Andersen3f980402001-04-04 17:31:15 +00003422 }
3423 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003424#endif
3425#if ENABLE_FEATURE_VI_SEARCH
Eric Andersen3f980402001-04-04 17:31:15 +00003426 case '?': // /- search for a pattern
3427 case '/': // /- search for a pattern
3428 buf[0] = c;
3429 buf[1] = '\0';
3430 q = get_input_line(buf); // get input line- use "status line"
Paul Fox4917c112008-03-05 16:44:02 +00003431 if (q[0] && !q[1]) {
3432 if (last_search_pattern[0])
Denis Vlasenkoc3a9dc82008-10-29 00:58:04 +00003433 last_search_pattern[0] = c;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003434 goto dc3; // if no pat re-use old pat
Paul Fox4917c112008-03-05 16:44:02 +00003435 }
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003436 if (q[0]) { // strlen(q) > 1: new pat- save it and find
Eric Andersen3f980402001-04-04 17:31:15 +00003437 // there is a new pat
Aaron Lehmanna170e1c2002-11-28 11:27:31 +00003438 free(last_search_pattern);
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003439 last_search_pattern = xstrdup(q);
Eric Andersen3f980402001-04-04 17:31:15 +00003440 goto dc3; // now find the pattern
3441 }
3442 // user changed mind and erased the "/"- do nothing
3443 break;
3444 case 'N': // N- backward search for last pattern
Eric Andersen3f980402001-04-04 17:31:15 +00003445 dir = BACK; // assume BACKWARD search
3446 p = dot - 1;
3447 if (last_search_pattern[0] == '?') {
3448 dir = FORWARD;
3449 p = dot + 1;
3450 }
3451 goto dc4; // now search for pattern
3452 break;
3453 case 'n': // n- repeat search for last pattern
3454 // search rest of text[] starting at next char
3455 // if search fails return orignal "p" not the "p+1" address
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003456 do {
3457 const char *msg;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003458 dc3:
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003459 dir = FORWARD; // assume FORWARD search
3460 p = dot + 1;
3461 if (last_search_pattern[0] == '?') {
3462 dir = BACK;
3463 p = dot - 1;
Eric Andersen3f980402001-04-04 17:31:15 +00003464 }
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003465 dc4:
Denys Vlasenkob7330462018-11-29 14:39:52 +01003466 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003467 if (q != NULL) {
3468 dot = q; // good search, update "dot"
3469 msg = NULL;
3470 goto dc2;
3471 }
3472 // no pattern found between "dot" and "end"- continue at top
3473 p = text;
3474 if (dir == BACK) {
3475 p = end - 1;
3476 }
Denys Vlasenkob7330462018-11-29 14:39:52 +01003477 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003478 if (q != NULL) { // found something
3479 dot = q; // found new pattern- goto it
3480 msg = "search hit BOTTOM, continuing at TOP";
3481 if (dir == BACK) {
3482 msg = "search hit TOP, continuing at BOTTOM";
3483 }
3484 } else {
3485 msg = "Pattern not found";
3486 }
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003487 dc2:
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003488 if (msg)
3489 status_line_bold("%s", msg);
3490 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003491 break;
3492 case '{': // {- move backward paragraph
Denys Vlasenkob7330462018-11-29 14:39:52 +01003493 q = char_search(dot, "\n\n", (BACK << 1) | FULL);
Eric Andersen3f980402001-04-04 17:31:15 +00003494 if (q != NULL) { // found blank line
3495 dot = next_line(q); // move to next blank line
3496 }
3497 break;
3498 case '}': // }- move forward paragraph
Denys Vlasenkob7330462018-11-29 14:39:52 +01003499 q = char_search(dot, "\n\n", (FORWARD << 1) | FULL);
Eric Andersen3f980402001-04-04 17:31:15 +00003500 if (q != NULL) { // found blank line
3501 dot = next_line(q); // move to next blank line
3502 }
3503 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003504#endif /* FEATURE_VI_SEARCH */
Maninder Singh97c64912015-05-25 13:46:36 +02003505 case '0': // 0- goto beginning of line
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003506 case '1': // 1-
3507 case '2': // 2-
3508 case '3': // 3-
3509 case '4': // 4-
3510 case '5': // 5-
3511 case '6': // 6-
3512 case '7': // 7-
3513 case '8': // 8-
3514 case '9': // 9-
Eric Andersen3f980402001-04-04 17:31:15 +00003515 if (c == '0' && cmdcnt < 1) {
3516 dot_begin(); // this was a standalone zero
3517 } else {
3518 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3519 }
3520 break;
3521 case ':': // :- the colon mode commands
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003522 p = get_input_line(":"); // get input line- use "status line"
Eric Andersen3f980402001-04-04 17:31:15 +00003523 colon(p); // execute the command
Eric Andersen3f980402001-04-04 17:31:15 +00003524 break;
3525 case '<': // <- Left shift something
3526 case '>': // >- Right shift something
3527 cnt = count_lines(text, dot); // remember what line we are on
3528 c1 = get_one_char(); // get the type of thing to delete
3529 find_range(&p, &q, c1);
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003530 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
Eric Andersen3f980402001-04-04 17:31:15 +00003531 p = begin_line(p);
3532 q = end_line(q);
3533 i = count_lines(p, q); // # of lines we are shifting
3534 for ( ; i > 0; i--, p = next_line(p)) {
3535 if (c == '<') {
3536 // shift left- remove tab or 8 spaces
3537 if (*p == '\t') {
3538 // shrink buffer 1 char
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003539 text_hole_delete(p, p, NO_UNDO);
Eric Andersen3f980402001-04-04 17:31:15 +00003540 } else if (*p == ' ') {
3541 // we should be calculating columns, not just SPACE
3542 for (j = 0; *p == ' ' && j < tabstop; j++) {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003543 text_hole_delete(p, p, NO_UNDO);
Eric Andersen3f980402001-04-04 17:31:15 +00003544 }
3545 }
3546 } else if (c == '>') {
3547 // shift right -- add tab or 8 spaces
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003548 char_insert(p, '\t', ALLOW_UNDO);
Eric Andersen3f980402001-04-04 17:31:15 +00003549 }
3550 }
3551 dot = find_line(cnt); // what line were we on
3552 dot_skip_over_ws();
3553 end_cmd_q(); // stop adding to q
3554 break;
3555 case 'A': // A- append at e-o-l
3556 dot_end(); // go to e-o-l
Bernhard Reutner-Fischera985d302008-02-11 11:44:38 +00003557 //**** fall through to ... 'a'
Eric Andersen3f980402001-04-04 17:31:15 +00003558 case 'a': // a- append after current char
3559 if (*dot != '\n')
3560 dot++;
3561 goto dc_i;
3562 break;
3563 case 'B': // B- back a blank-delimited Word
3564 case 'E': // E- end of a blank-delimited word
3565 case 'W': // W- forward a blank-delimited word
Eric Andersen3f980402001-04-04 17:31:15 +00003566 dir = FORWARD;
3567 if (c == 'B')
3568 dir = BACK;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003569 do {
3570 if (c == 'W' || isspace(dot[dir])) {
3571 dot = skip_thing(dot, 1, dir, S_TO_WS);
3572 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3573 }
3574 if (c != 'W')
3575 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3576 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003577 break;
3578 case 'C': // C- Change to e-o-l
3579 case 'D': // D- delete to e-o-l
3580 save_dot = dot;
3581 dot = dollar_line(dot); // move to before NL
3582 // copy text into a register and delete
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003583 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
Eric Andersen3f980402001-04-04 17:31:15 +00003584 if (c == 'C')
3585 goto dc_i; // start inserting
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003586#if ENABLE_FEATURE_VI_DOT_CMD
Eric Andersen3f980402001-04-04 17:31:15 +00003587 if (c == 'D')
3588 end_cmd_q(); // stop adding to q
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003589#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003590 break;
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003591 case 'g': // 'gg' goto a line number (vim) (default: very first line)
Paul Foxb5ee8db2008-02-14 01:17:01 +00003592 c1 = get_one_char();
3593 if (c1 != 'g') {
3594 buf[0] = 'g';
Denys Vlasenkode1996d2016-09-15 13:53:42 +02003595 // c1 < 0 if the key was special. Try "g<up-arrow>"
3596 // TODO: if Unicode?
3597 buf[1] = (c1 >= 0 ? c1 : '*');
Paul Foxb5ee8db2008-02-14 01:17:01 +00003598 buf[2] = '\0';
3599 not_implemented(buf);
3600 break;
3601 }
3602 if (cmdcnt == 0)
3603 cmdcnt = 1;
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02003604 // fall through
Eric Andersen822c3832001-05-07 17:37:43 +00003605 case 'G': // G- goto to a line number (default= E-O-F)
3606 dot = end - 1; // assume E-O-F
Eric Andersen1c0d3112001-04-16 15:46:44 +00003607 if (cmdcnt > 0) {
Eric Andersen822c3832001-05-07 17:37:43 +00003608 dot = find_line(cmdcnt); // what line is #cmdcnt
Eric Andersen1c0d3112001-04-16 15:46:44 +00003609 }
3610 dot_skip_over_ws();
3611 break;
Eric Andersen3f980402001-04-04 17:31:15 +00003612 case 'H': // H- goto top line on screen
3613 dot = screenbegin;
3614 if (cmdcnt > (rows - 1)) {
3615 cmdcnt = (rows - 1);
3616 }
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01003617 if (--cmdcnt > 0) {
Eric Andersen3f980402001-04-04 17:31:15 +00003618 do_cmd('+');
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01003619 }
Eric Andersen3f980402001-04-04 17:31:15 +00003620 dot_skip_over_ws();
3621 break;
3622 case 'I': // I- insert before first non-blank
3623 dot_begin(); // 0
3624 dot_skip_over_ws();
Bernhard Reutner-Fischera985d302008-02-11 11:44:38 +00003625 //**** fall through to ... 'i'
Eric Andersen3f980402001-04-04 17:31:15 +00003626 case 'i': // i- insert before current char
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003627 case KEYCODE_INSERT: // Cursor Key Insert
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003628 dc_i:
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003629 cmd_mode = 1; // start inserting
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003630 undo_queue_commit(); // commit queue when cmd_mode changes
Eric Andersen3f980402001-04-04 17:31:15 +00003631 break;
3632 case 'J': // J- join current and next lines together
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003633 do {
3634 dot_end(); // move to NL
3635 if (dot < end - 1) { // make sure not last char in text[]
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003636#if ENABLE_FEATURE_VI_UNDO
3637 undo_push(dot, 1, UNDO_DEL);
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003638 *dot++ = ' '; // replace NL with space
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003639 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3640#else
3641 *dot++ = ' ';
Denys Vlasenkoe7430862014-04-03 12:47:48 +02003642 modified_count++;
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003643#endif
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003644 while (isblank(*dot)) { // delete leading WS
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003645 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003646 }
Eric Andersen3f980402001-04-04 17:31:15 +00003647 }
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003648 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003649 end_cmd_q(); // stop adding to q
3650 break;
3651 case 'L': // L- goto bottom line on screen
3652 dot = end_screen();
3653 if (cmdcnt > (rows - 1)) {
3654 cmdcnt = (rows - 1);
3655 }
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01003656 if (--cmdcnt > 0) {
Eric Andersen3f980402001-04-04 17:31:15 +00003657 do_cmd('-');
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01003658 }
Eric Andersen3f980402001-04-04 17:31:15 +00003659 dot_begin();
3660 dot_skip_over_ws();
3661 break;
Eric Andersen822c3832001-05-07 17:37:43 +00003662 case 'M': // M- goto middle line on screen
Eric Andersen1c0d3112001-04-16 15:46:44 +00003663 dot = screenbegin;
3664 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3665 dot = next_line(dot);
3666 break;
Eric Andersen3f980402001-04-04 17:31:15 +00003667 case 'O': // O- open a empty line above
Eric Andersen822c3832001-05-07 17:37:43 +00003668 // 0i\n ESC -i
Eric Andersen3f980402001-04-04 17:31:15 +00003669 p = begin_line(dot);
3670 if (p[-1] == '\n') {
3671 dot_prev();
3672 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3673 dot_end();
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003674 dot = char_insert(dot, '\n', ALLOW_UNDO);
Eric Andersen3f980402001-04-04 17:31:15 +00003675 } else {
3676 dot_begin(); // 0
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003677 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
Eric Andersen3f980402001-04-04 17:31:15 +00003678 dot_prev(); // -
3679 }
3680 goto dc_i;
3681 break;
3682 case 'R': // R- continuous Replace char
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003683 dc5:
Eric Andersen3f980402001-04-04 17:31:15 +00003684 cmd_mode = 2;
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003685 undo_queue_commit();
Eric Andersen3f980402001-04-04 17:31:15 +00003686 break;
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003687 case KEYCODE_DELETE:
Denys Vlasenko49acc1a2015-03-12 21:15:34 +01003688 if (dot < end - 1)
3689 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
3690 break;
Eric Andersen3f980402001-04-04 17:31:15 +00003691 case 'X': // X- delete char before dot
3692 case 'x': // x- delete the current char
3693 case 's': // s- substitute the current char
Eric Andersen3f980402001-04-04 17:31:15 +00003694 dir = 0;
3695 if (c == 'X')
3696 dir = -1;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003697 do {
3698 if (dot[dir] != '\n') {
3699 if (c == 'X')
3700 dot--; // delete prev char
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003701 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003702 }
3703 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003704 end_cmd_q(); // stop adding to q
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003705 if (c == 's')
3706 goto dc_i; // start inserting
Eric Andersen3f980402001-04-04 17:31:15 +00003707 break;
3708 case 'Z': // Z- if modified, {write}; exit
3709 // ZZ means to save file (if necessary), then exit
3710 c1 = get_one_char();
3711 if (c1 != 'Z') {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003712 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003713 break;
3714 }
Denys Vlasenkoe7430862014-04-03 12:47:48 +02003715 if (modified_count) {
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00003716 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
Denys Vlasenko778794d2013-01-22 10:13:52 +01003717 status_line_bold("'%s' is read only", current_filename);
Denis Vlasenko92758142006-10-03 19:56:34 +00003718 break;
Paul Foxf0305b72006-03-28 14:18:21 +00003719 }
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00003720 cnt = file_write(current_filename, text, end - 1);
Paul Fox61e45db2005-10-09 14:43:22 +00003721 if (cnt < 0) {
3722 if (cnt == -1)
Denys Vlasenko6f97b302017-09-29 18:17:25 +02003723 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
Paul Fox61e45db2005-10-09 14:43:22 +00003724 } else if (cnt == (end - 1 - text + 1)) {
Eric Andersen3f980402001-04-04 17:31:15 +00003725 editing = 0;
3726 }
3727 } else {
3728 editing = 0;
3729 }
3730 break;
3731 case '^': // ^- move to first non-blank on line
3732 dot_begin();
3733 dot_skip_over_ws();
3734 break;
3735 case 'b': // b- back a word
3736 case 'e': // e- end of word
Eric Andersen3f980402001-04-04 17:31:15 +00003737 dir = FORWARD;
3738 if (c == 'b')
3739 dir = BACK;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003740 do {
3741 if ((dot + dir) < text || (dot + dir) > end - 1)
3742 break;
3743 dot += dir;
3744 if (isspace(*dot)) {
3745 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3746 }
3747 if (isalnum(*dot) || *dot == '_') {
3748 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3749 } else if (ispunct(*dot)) {
3750 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3751 }
3752 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003753 break;
3754 case 'c': // c- change something
3755 case 'd': // d- delete something
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003756#if ENABLE_FEATURE_VI_YANKMARK
Eric Andersen3f980402001-04-04 17:31:15 +00003757 case 'y': // y- yank something
3758 case 'Y': // Y- Yank a line
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003759#endif
Denis Vlasenkod44c1532008-10-14 12:59:42 +00003760 {
Paul Foxc51fc7b2008-03-06 01:34:23 +00003761 int yf, ml, whole = 0;
Eric Andersen3f980402001-04-04 17:31:15 +00003762 yf = YANKDEL; // assume either "c" or "d"
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003763#if ENABLE_FEATURE_VI_YANKMARK
Eric Andersen3f980402001-04-04 17:31:15 +00003764 if (c == 'y' || c == 'Y')
3765 yf = YANKONLY;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003766#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003767 c1 = 'y';
3768 if (c != 'Y')
3769 c1 = get_one_char(); // get the type of thing to delete
Paul Foxc51fc7b2008-03-06 01:34:23 +00003770 // determine range, and whether it spans lines
3771 ml = find_range(&p, &q, c1);
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003772 place_cursor(0, 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003773 if (c1 == 27) { // ESC- user changed mind and wants out
3774 c = c1 = 27; // Escape- do nothing
3775 } else if (strchr("wW", c1)) {
Ron Yorston4b494222019-04-28 09:09:33 +01003776 ml = 0; // multi-line ranges aren't allowed for words
Eric Andersen3f980402001-04-04 17:31:15 +00003777 if (c == 'c') {
3778 // don't include trailing WS as part of word
Ron Yorston4b494222019-04-28 09:09:33 +01003779 while (isspace(*q) && q > p) {
Eric Andersen3f980402001-04-04 17:31:15 +00003780 q--;
3781 }
3782 }
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003783 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
Paul Foxc51fc7b2008-03-06 01:34:23 +00003784 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3785 // partial line copy text into a register and delete
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003786 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
Ron Yorston7b93e312019-04-28 09:10:16 +01003787 } else if (strchr("cdykjGHL+-{}\r\n", c1)) {
Paul Foxc51fc7b2008-03-06 01:34:23 +00003788 // whole line copy text into a register and delete
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003789 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
Paul Foxc51fc7b2008-03-06 01:34:23 +00003790 whole = 1;
3791 } else {
3792 // could not recognize object
3793 c = c1 = 27; // error-
3794 ml = 0;
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003795 indicate_error();
Paul Foxc51fc7b2008-03-06 01:34:23 +00003796 }
3797 if (ml && whole) {
Eric Andersen1c0d3112001-04-16 15:46:44 +00003798 if (c == 'c') {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003799 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
Eric Andersen1c0d3112001-04-16 15:46:44 +00003800 // on the last line of file don't move to prev line
Paul Foxc51fc7b2008-03-06 01:34:23 +00003801 if (whole && dot != (end-1)) {
Eric Andersen1c0d3112001-04-16 15:46:44 +00003802 dot_prev();
3803 }
3804 } else if (c == 'd') {
Eric Andersen3f980402001-04-04 17:31:15 +00003805 dot_begin();
3806 dot_skip_over_ws();
3807 }
Eric Andersen3f980402001-04-04 17:31:15 +00003808 }
3809 if (c1 != 27) {
3810 // if CHANGING, not deleting, start inserting after the delete
3811 if (c == 'c') {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003812 strcpy(buf, "Change");
Eric Andersen3f980402001-04-04 17:31:15 +00003813 goto dc_i; // start inserting
3814 }
3815 if (c == 'd') {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003816 strcpy(buf, "Delete");
Eric Andersen3f980402001-04-04 17:31:15 +00003817 }
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003818#if ENABLE_FEATURE_VI_YANKMARK
Eric Andersen3f980402001-04-04 17:31:15 +00003819 if (c == 'y' || c == 'Y') {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003820 strcpy(buf, "Yank");
Eric Andersen3f980402001-04-04 17:31:15 +00003821 }
3822 p = reg[YDreg];
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003823 q = p + strlen(p);
Eric Andersen3f980402001-04-04 17:31:15 +00003824 for (cnt = 0; p <= q; p++) {
3825 if (*p == '\n')
3826 cnt++;
3827 }
Denys Vlasenkoeabf4b22019-03-29 14:40:01 +01003828 status_line("%s %u lines (%u chars) using [%c]",
3829 buf, cnt, (unsigned)strlen(reg[YDreg]), what_reg());
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003830#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003831 end_cmd_q(); // stop adding to q
3832 }
3833 break;
Denis Vlasenkod44c1532008-10-14 12:59:42 +00003834 }
Eric Andersen3f980402001-04-04 17:31:15 +00003835 case 'k': // k- goto prev line, same col
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003836 case KEYCODE_UP: // cursor key Up
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003837 do {
3838 dot_prev();
3839 dot = move_to_col(dot, ccol + offset); // try stay in same col
3840 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003841 break;
3842 case 'r': // r- replace the current char with user input
3843 c1 = get_one_char(); // get the replacement char
3844 if (*dot != '\n') {
Ron Yorstondf4e3af2019-02-03 14:01:58 +00003845 dot = text_hole_delete(dot, dot, ALLOW_UNDO);
3846 dot = char_insert(dot, c1, ALLOW_UNDO_CHAIN);
3847 dot_left();
Eric Andersen3f980402001-04-04 17:31:15 +00003848 }
3849 end_cmd_q(); // stop adding to q
3850 break;
Eric Andersen822c3832001-05-07 17:37:43 +00003851 case 't': // t- move to char prior to next x
Tim Rikerc1ef7bd2006-01-25 00:08:53 +00003852 last_forward_char = get_one_char();
3853 do_cmd(';');
3854 if (*dot == last_forward_char)
3855 dot_left();
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003856 last_forward_char = 0;
Eric Andersen822c3832001-05-07 17:37:43 +00003857 break;
Eric Andersen3f980402001-04-04 17:31:15 +00003858 case 'w': // w- forward a word
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003859 do {
3860 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3861 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3862 } else if (ispunct(*dot)) { // we are on PUNCT
3863 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3864 }
3865 if (dot < end - 1)
3866 dot++; // move over word
3867 if (isspace(*dot)) {
3868 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3869 }
3870 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003871 break;
3872 case 'z': // z-
3873 c1 = get_one_char(); // get the replacement char
3874 cnt = 0;
3875 if (c1 == '.')
3876 cnt = (rows - 2) / 2; // put dot at center
3877 if (c1 == '-')
3878 cnt = rows - 2; // put dot at bottom
3879 screenbegin = begin_line(dot); // start dot at top
3880 dot_scroll(cnt, -1);
3881 break;
3882 case '|': // |- move to column "cmdcnt"
3883 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3884 break;
3885 case '~': // ~- flip the case of letters a-z -> A-Z
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003886 do {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003887#if ENABLE_FEATURE_VI_UNDO
3888 if (islower(*dot)) {
3889 undo_push(dot, 1, UNDO_DEL);
3890 *dot = toupper(*dot);
3891 undo_push(dot, 1, UNDO_INS_CHAIN);
3892 } else if (isupper(*dot)) {
3893 undo_push(dot, 1, UNDO_DEL);
3894 *dot = tolower(*dot);
3895 undo_push(dot, 1, UNDO_INS_CHAIN);
3896 }
3897#else
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003898 if (islower(*dot)) {
3899 *dot = toupper(*dot);
Denys Vlasenkoe7430862014-04-03 12:47:48 +02003900 modified_count++;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003901 } else if (isupper(*dot)) {
3902 *dot = tolower(*dot);
Denys Vlasenkoe7430862014-04-03 12:47:48 +02003903 modified_count++;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003904 }
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003905#endif
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003906 dot_right();
3907 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003908 end_cmd_q(); // stop adding to q
3909 break;
3910 //----- The Cursor and Function Keys -----------------------------
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003911 case KEYCODE_HOME: // Cursor Key Home
Eric Andersen3f980402001-04-04 17:31:15 +00003912 dot_begin();
3913 break;
3914 // The Fn keys could point to do_macro which could translate them
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003915#if 0
3916 case KEYCODE_FUN1: // Function Key F1
3917 case KEYCODE_FUN2: // Function Key F2
3918 case KEYCODE_FUN3: // Function Key F3
3919 case KEYCODE_FUN4: // Function Key F4
3920 case KEYCODE_FUN5: // Function Key F5
3921 case KEYCODE_FUN6: // Function Key F6
3922 case KEYCODE_FUN7: // Function Key F7
3923 case KEYCODE_FUN8: // Function Key F8
3924 case KEYCODE_FUN9: // Function Key F9
3925 case KEYCODE_FUN10: // Function Key F10
3926 case KEYCODE_FUN11: // Function Key F11
3927 case KEYCODE_FUN12: // Function Key F12
Eric Andersen3f980402001-04-04 17:31:15 +00003928 break;
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003929#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003930 }
3931
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003932 dc1:
Eric Andersen3f980402001-04-04 17:31:15 +00003933 // if text[] just became empty, add back an empty line
3934 if (end == text) {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003935 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
Eric Andersen3f980402001-04-04 17:31:15 +00003936 dot = text;
3937 }
3938 // it is OK for dot to exactly equal to end, otherwise check dot validity
3939 if (dot != end) {
3940 dot = bound_dot(dot); // make sure "dot" is valid
3941 }
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003942#if ENABLE_FEATURE_VI_YANKMARK
Eric Andersen3f980402001-04-04 17:31:15 +00003943 check_context(c); // update the current context
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003944#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003945
3946 if (!isdigit(c))
3947 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3948 cnt = dot - begin_line(dot);
3949 // Try to stay off of the Newline
3950 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3951 dot--;
3952}
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003953
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02003954// NB! the CRASHME code is unmaintained, and doesn't currently build
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003955#if ENABLE_FEATURE_VI_CRASHME
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003956static int totalcmds = 0;
3957static int Mp = 85; // Movement command Probability
3958static int Np = 90; // Non-movement command Probability
3959static int Dp = 96; // Delete command Probability
3960static int Ip = 97; // Insert command Probability
3961static int Yp = 98; // Yank command Probability
3962static int Pp = 99; // Put command Probability
3963static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003964static const char chars[20] = "\t012345 abcdABCD-=.$";
3965static const char *const words[20] = {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003966 "this", "is", "a", "test",
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003967 "broadcast", "the", "emergency", "of",
3968 "system", "quick", "brown", "fox",
3969 "jumped", "over", "lazy", "dogs",
3970 "back", "January", "Febuary", "March"
3971};
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003972static const char *const lines[20] = {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003973 "You should have received a copy of the GNU General Public License\n",
3974 "char c, cm, *cmd, *cmd1;\n",
3975 "generate a command by percentages\n",
3976 "Numbers may be typed as a prefix to some commands.\n",
3977 "Quit, discarding changes!\n",
3978 "Forced write, if permission originally not valid.\n",
3979 "In general, any ex or ed command (such as substitute or delete).\n",
3980 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3981 "Please get w/ me and I will go over it with you.\n",
3982 "The following is a list of scheduled, committed changes.\n",
3983 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3984 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3985 "Any question about transactions please contact Sterling Huxley.\n",
3986 "I will try to get back to you by Friday, December 31.\n",
3987 "This Change will be implemented on Friday.\n",
3988 "Let me know if you have problems accessing this;\n",
3989 "Sterling Huxley recently added you to the access list.\n",
3990 "Would you like to go to lunch?\n",
3991 "The last command will be automatically run.\n",
3992 "This is too much english for a computer geek.\n",
3993};
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003994static char *multilines[20] = {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003995 "You should have received a copy of the GNU General Public License\n",
3996 "char c, cm, *cmd, *cmd1;\n",
3997 "generate a command by percentages\n",
3998 "Numbers may be typed as a prefix to some commands.\n",
3999 "Quit, discarding changes!\n",
4000 "Forced write, if permission originally not valid.\n",
4001 "In general, any ex or ed command (such as substitute or delete).\n",
4002 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4003 "Please get w/ me and I will go over it with you.\n",
4004 "The following is a list of scheduled, committed changes.\n",
4005 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4006 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4007 "Any question about transactions please contact Sterling Huxley.\n",
4008 "I will try to get back to you by Friday, December 31.\n",
4009 "This Change will be implemented on Friday.\n",
4010 "Let me know if you have problems accessing this;\n",
4011 "Sterling Huxley recently added you to the access list.\n",
4012 "Would you like to go to lunch?\n",
4013 "The last command will be automatically run.\n",
4014 "This is too much english for a computer geek.\n",
4015};
4016
4017// create a random command to execute
4018static void crash_dummy()
4019{
4020 static int sleeptime; // how long to pause between commands
4021 char c, cm, *cmd, *cmd1;
4022 int i, cnt, thing, rbi, startrbi, percent;
4023
4024 // "dot" movement commands
4025 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4026
4027 // is there already a command running?
Denys Vlasenko020f4062009-05-17 16:44:54 +02004028 if (readbuffer[0] > 0)
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004029 goto cd1;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004030 cd0:
Denys Vlasenko020f4062009-05-17 16:44:54 +02004031 readbuffer[0] = 'X';
4032 startrbi = rbi = 1;
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004033 sleeptime = 0; // how long to pause between commands
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004034 memset(readbuffer, '\0', sizeof(readbuffer));
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004035 // generate a command by percentages
4036 percent = (int) lrand48() % 100; // get a number from 0-99
4037 if (percent < Mp) { // Movement commands
4038 // available commands
4039 cmd = cmd1;
4040 M++;
4041 } else if (percent < Np) { // non-movement commands
4042 cmd = "mz<>\'\""; // available commands
4043 N++;
4044 } else if (percent < Dp) { // Delete commands
4045 cmd = "dx"; // available commands
4046 D++;
4047 } else if (percent < Ip) { // Inset commands
4048 cmd = "iIaAsrJ"; // available commands
4049 I++;
4050 } else if (percent < Yp) { // Yank commands
4051 cmd = "yY"; // available commands
4052 Y++;
4053 } else if (percent < Pp) { // Put commands
4054 cmd = "pP"; // available commands
4055 P++;
4056 } else {
4057 // We do not know how to handle this command, try again
4058 U++;
4059 goto cd0;
4060 }
4061 // randomly pick one of the available cmds from "cmd[]"
4062 i = (int) lrand48() % strlen(cmd);
4063 cm = cmd[i];
4064 if (strchr(":\024", cm))
4065 goto cd0; // dont allow colon or ctrl-T commands
4066 readbuffer[rbi++] = cm; // put cmd into input buffer
4067
4068 // now we have the command-
4069 // there are 1, 2, and multi char commands
4070 // find out which and generate the rest of command as necessary
4071 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4072 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4073 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4074 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4075 }
4076 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4077 c = cmd1[thing];
4078 readbuffer[rbi++] = c; // add movement to input buffer
4079 }
4080 if (strchr("iIaAsc", cm)) { // multi-char commands
4081 if (cm == 'c') {
4082 // change some thing
4083 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4084 c = cmd1[thing];
4085 readbuffer[rbi++] = c; // add movement to input buffer
4086 }
4087 thing = (int) lrand48() % 4; // what thing to insert
4088 cnt = (int) lrand48() % 10; // how many to insert
4089 for (i = 0; i < cnt; i++) {
4090 if (thing == 0) { // insert chars
4091 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4092 } else if (thing == 1) { // insert words
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004093 strcat(readbuffer, words[(int) lrand48() % 20]);
4094 strcat(readbuffer, " ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004095 sleeptime = 0; // how fast to type
4096 } else if (thing == 2) { // insert lines
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004097 strcat(readbuffer, lines[(int) lrand48() % 20]);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004098 sleeptime = 0; // how fast to type
4099 } else { // insert multi-lines
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004100 strcat(readbuffer, multilines[(int) lrand48() % 20]);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004101 sleeptime = 0; // how fast to type
4102 }
4103 }
Denys Vlasenko8187e012017-09-13 22:48:30 +02004104 strcat(readbuffer, ESC);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004105 }
Denys Vlasenko020f4062009-05-17 16:44:54 +02004106 readbuffer[0] = strlen(readbuffer + 1);
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004107 cd1:
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004108 totalcmds++;
4109 if (sleeptime > 0)
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004110 mysleep(sleeptime); // sleep 1/100 sec
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004111}
4112
4113// test to see if there are any errors
4114static void crash_test()
4115{
4116 static time_t oldtim;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004117
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004118 time_t tim;
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004119 char d[2], msg[80];
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004120
4121 msg[0] = '\0';
4122 if (end < text) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004123 strcat(msg, "end<text ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004124 }
4125 if (end > textend) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004126 strcat(msg, "end>textend ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004127 }
4128 if (dot < text) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004129 strcat(msg, "dot<text ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004130 }
4131 if (dot > end) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004132 strcat(msg, "dot>end ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004133 }
4134 if (screenbegin < text) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004135 strcat(msg, "screenbegin<text ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004136 }
4137 if (screenbegin > end - 1) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004138 strcat(msg, "screenbegin>end-1 ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004139 }
4140
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004141 if (msg[0]) {
Glenn L McGrath7127b582002-12-03 21:48:15 +00004142 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
Denys Vlasenko04b52892012-06-11 13:51:38 +02004143 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
Denys Vlasenko8131eea2009-11-02 14:19:51 +01004144 fflush_all();
Bernhard Reutner-Fischer5e25ddb2008-05-19 09:48:17 +00004145 while (safe_read(STDIN_FILENO, d, 1) > 0) {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004146 if (d[0] == '\n' || d[0] == '\r')
4147 break;
4148 }
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004149 }
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004150 tim = time(NULL);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004151 if (tim >= (oldtim + 3)) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004152 sprintf(status_buffer,
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004153 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4154 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4155 oldtim = tim;
4156 }
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004157}
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004158#endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004159
4160static void edit_file(char *fn)
4161{
4162#if ENABLE_FEATURE_VI_YANKMARK
4163#define cur_line edit_file__cur_line
4164#endif
4165 int c;
4166#if ENABLE_FEATURE_VI_USE_SIGNALS
4167 int sig;
4168#endif
4169
4170 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
4171 rawmode();
4172 rows = 24;
4173 columns = 80;
4174 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
4175#if ENABLE_FEATURE_VI_ASK_TERMINAL
4176 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
4177 uint64_t k;
4178 write1(ESC"[999;999H" ESC"[6n");
4179 fflush_all();
4180 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
4181 if ((int32_t)k == KEYCODE_CURSOR_POS) {
4182 uint32_t rc = (k >> 32);
4183 columns = (rc & 0x7fff);
4184 if (columns > MAX_SCR_COLS)
4185 columns = MAX_SCR_COLS;
4186 rows = ((rc >> 16) & 0x7fff);
4187 if (rows > MAX_SCR_ROWS)
4188 rows = MAX_SCR_ROWS;
4189 }
4190 }
4191#endif
4192 new_screen(rows, columns); // get memory for virtual screen
4193 init_text_buffer(fn);
4194
4195#if ENABLE_FEATURE_VI_YANKMARK
4196 YDreg = 26; // default Yank/Delete reg
4197// Ureg = 27; - const // hold orig line for "U" cmd
4198 mark[26] = mark[27] = text; // init "previous context"
4199#endif
4200
Denys Vlasenkob29dce42019-04-01 17:17:02 +02004201 last_forward_char = '\0';
4202#if ENABLE_FEATURE_VI_CRASHME
4203 last_input_char = '\0';
4204#endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004205 crow = 0;
4206 ccol = 0;
4207
4208#if ENABLE_FEATURE_VI_USE_SIGNALS
4209 signal(SIGWINCH, winch_handler);
4210 signal(SIGTSTP, tstp_handler);
4211 sig = sigsetjmp(restart, 1);
4212 if (sig != 0) {
4213 screenbegin = dot = text;
4214 }
4215 // int_handler() can jump to "restart",
4216 // must install handler *after* initializing "restart"
4217 signal(SIGINT, int_handler);
4218#endif
4219
4220 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
4221 cmdcnt = 0;
4222 tabstop = 8;
4223 offset = 0; // no horizontal offset
4224 c = '\0';
4225#if ENABLE_FEATURE_VI_DOT_CMD
4226 free(ioq_start);
Denys Vlasenko2a576082019-04-01 16:15:51 +02004227 ioq_start = NULL;
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004228 lmc_len = 0;
4229 adding2q = 0;
4230#endif
4231
4232#if ENABLE_FEATURE_VI_COLON
4233 {
4234 char *p, *q;
4235 int n = 0;
4236
4237 while ((p = initial_cmds[n]) != NULL) {
4238 do {
4239 q = p;
4240 p = strchr(q, '\n');
4241 if (p)
4242 while (*p == '\n')
4243 *p++ = '\0';
4244 if (*q)
4245 colon(q);
4246 } while (p);
4247 free(initial_cmds[n]);
4248 initial_cmds[n] = NULL;
4249 n++;
4250 }
4251 }
4252#endif
4253 redraw(FALSE); // dont force every col re-draw
4254 //------This is the main Vi cmd handling loop -----------------------
4255 while (editing > 0) {
4256#if ENABLE_FEATURE_VI_CRASHME
4257 if (crashme > 0) {
4258 if ((end - text) > 1) {
4259 crash_dummy(); // generate a random command
4260 } else {
4261 crashme = 0;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004262 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO);
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004263 dot = text;
4264 refresh(FALSE);
4265 }
4266 }
4267#endif
Denys Vlasenkob29dce42019-04-01 17:17:02 +02004268 c = get_one_char(); // get a cmd from user
4269#if ENABLE_FEATURE_VI_CRASHME
4270 last_input_char = c;
4271#endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004272#if ENABLE_FEATURE_VI_YANKMARK
4273 // save a copy of the current line- for the 'U" command
4274 if (begin_line(dot) != cur_line) {
4275 cur_line = begin_line(dot);
4276 text_yank(begin_line(dot), end_line(dot), Ureg);
4277 }
4278#endif
4279#if ENABLE_FEATURE_VI_DOT_CMD
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004280 // If c is a command that changes text[],
4281 // (re)start remembering the input for the "." command.
Denys Vlasenko2a576082019-04-01 16:15:51 +02004282 if (!adding2q
4283 && ioq_start == NULL
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004284 && cmd_mode == 0 // command mode
4285 && c > '\0' // exclude NUL and non-ASCII chars
4286 && c < 0x7f // (Unicode and such)
4287 && strchr(modifying_cmds, c)
4288 ) {
4289 start_new_cmd_q(c);
4290 }
4291#endif
4292 do_cmd(c); // execute the user command
4293
4294 // poll to see if there is input already waiting. if we are
4295 // not able to display output fast enough to keep up, skip
4296 // the display update until we catch up with input.
4297 if (!readbuffer[0] && mysleep(0) == 0) {
4298 // no input pending - so update output
4299 refresh(FALSE);
4300 show_status_line();
4301 }
4302#if ENABLE_FEATURE_VI_CRASHME
4303 if (crashme > 0)
4304 crash_test(); // test editor variables
4305#endif
4306 }
4307 //-------------------------------------------------------------------
4308
4309 go_bottom_and_clear_to_eol();
4310 cookmode();
4311#undef cur_line
4312}
4313
4314int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
4315int vi_main(int argc, char **argv)
4316{
4317 int c;
4318
4319 INIT_G();
4320
4321#if ENABLE_FEATURE_VI_UNDO
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004322 //undo_stack_tail = NULL; - already is
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02004323# if ENABLE_FEATURE_VI_UNDO_QUEUE
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004324 undo_queue_state = UNDO_EMPTY;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004325 //undo_q = 0; - already is
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02004326# endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004327#endif
4328
4329#if ENABLE_FEATURE_VI_CRASHME
4330 srand((long) getpid());
4331#endif
4332#ifdef NO_SUCH_APPLET_YET
4333 // if we aren't "vi", we are "view"
4334 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
4335 SET_READONLY_MODE(readonly_mode);
4336 }
4337#endif
4338
4339 // autoindent is not default in vim 7.3
4340 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
4341 // 1- process $HOME/.exrc file (not inplemented yet)
4342 // 2- process EXINIT variable from environment
4343 // 3- process command line args
4344#if ENABLE_FEATURE_VI_COLON
4345 {
4346 char *p = getenv("EXINIT");
4347 if (p && *p)
4348 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
4349 }
4350#endif
4351 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
4352 switch (c) {
4353#if ENABLE_FEATURE_VI_CRASHME
4354 case 'C':
4355 crashme = 1;
4356 break;
4357#endif
4358#if ENABLE_FEATURE_VI_READONLY
4359 case 'R': // Read-only flag
4360 SET_READONLY_MODE(readonly_mode);
4361 break;
4362#endif
4363#if ENABLE_FEATURE_VI_COLON
4364 case 'c': // cmd line vi command
4365 if (*optarg)
4366 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
4367 break;
4368#endif
4369 case 'H':
4370 show_help();
4371 // fall through
4372 default:
4373 bb_show_usage();
4374 return 1;
4375 }
4376 }
4377
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004378 argv += optind;
Denys Vlasenko89393592019-04-02 12:45:30 +02004379 cmdline_filecnt = argc - optind;
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004380
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004381 // "Save cursor, use alternate screen buffer, clear screen"
4382 write1(ESC"[?1049h");
Denys Vlasenko89393592019-04-02 12:45:30 +02004383 // This is the main file handling loop
4384 optind = 0;
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004385 while (1) {
Denys Vlasenko89393592019-04-02 12:45:30 +02004386 edit_file(argv[optind]); // might be NULL on 1st iteration
4387 // NB: optind can be changed by ":next" and ":rewind" commands
4388 optind++;
Denys Vlasenkoa3ce1612019-04-03 16:35:23 +02004389 if (optind >= cmdline_filecnt)
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004390 break;
4391 }
4392 // "Use normal screen buffer, restore cursor"
4393 write1(ESC"[?1049l");
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004394
4395 return 0;
4396}