blob: 1dd0b6fb647d94c911cdb197a954107f7b71799d [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"
James Byrne69374872019-07-02 11:35:03 +02001045 bb_simple_error_msg_and_die("can't read user input");
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001046 }
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
Denys Vlasenkofa182a32019-06-08 12:57:07 +02001334 if (YDreg <= 25)
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001335 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;)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002254
2255 *addr = -1; // assume no addr
2256 if (*p == '.') { // the current line
2257 p++;
2258 q = begin_line(dot);
2259 *addr = count_lines(text, q);
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002260 }
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00002261#if ENABLE_FEATURE_VI_YANKMARK
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002262 else if (*p == '\'') { // is this a mark addr
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002263 p++;
2264 c = tolower(*p);
2265 p++;
2266 if (c >= 'a' && c <= 'z') {
2267 // we have a mark
2268 c = c - 'a';
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002269 q = mark[(unsigned char) c];
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002270 if (q != NULL) { // is mark valid
Denis Vlasenko00d84172008-11-24 07:34:42 +00002271 *addr = count_lines(text, q);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002272 }
2273 }
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002274 }
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00002275#endif
2276#if ENABLE_FEATURE_VI_SEARCH
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002277 else if (*p == '/') { // a search pattern
Ron Yorston16bcd502020-01-23 15:31:19 +00002278 q = strchrnul(p + 1, '/');
2279 if (p + 1 != q) {
2280 // save copy of new pattern
2281 free(last_search_pattern);
2282 last_search_pattern = xstrndup(p, q - p);
2283 }
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002284 p = q;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002285 if (*p == '/')
2286 p++;
Ron Yorston16bcd502020-01-23 15:31:19 +00002287 q = char_search(next_line(dot), last_search_pattern + 1,
2288 (FORWARD << 1) | FULL);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002289 if (q != NULL) {
2290 *addr = count_lines(text, q);
2291 }
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002292 }
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00002293#endif
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002294 else if (*p == '$') { // the last line in file
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002295 p++;
2296 q = begin_line(end - 1);
2297 *addr = count_lines(text, q);
2298 } else if (isdigit(*p)) { // specific line number
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002299 sscanf(p, "%d%n", addr, &st);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002300 p += st;
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002301 } else {
Denys Vlasenkob22bbff2009-07-04 16:50:43 +02002302 // unrecognized address - assume -1
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002303 *addr = -1;
2304 }
Denis Vlasenko079f8af2006-11-27 16:49:31 +00002305 return p;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002306}
2307
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002308static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002309{
2310 //----- get the address' i.e., 1,3 'a,'b -----
2311 // get FIRST addr, if present
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002312 while (isblank(*p))
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002313 p++; // skip over leading spaces
2314 if (*p == '%') { // alias for 1,$
2315 p++;
2316 *b = 1;
2317 *e = count_lines(text, end-1);
2318 goto ga0;
2319 }
2320 p = get_one_address(p, b);
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002321 while (isblank(*p))
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002322 p++;
Eric Andersenaff114c2004-04-14 17:51:38 +00002323 if (*p == ',') { // is there a address separator
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002324 p++;
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002325 while (isblank(*p))
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002326 p++;
2327 // get SECOND addr, if present
2328 p = get_one_address(p, e);
2329 }
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002330 ga0:
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002331 while (isblank(*p))
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002332 p++; // skip over trailing spaces
Denis Vlasenko079f8af2006-11-27 16:49:31 +00002333 return p;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002334}
2335
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00002336#if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002337static void setops(const char *args, const char *opname, int flg_no,
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002338 const char *short_opname, int opt)
2339{
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002340 const char *a = args + flg_no;
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02002341 int l = strlen(opname) - 1; // opname have + ' '
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002342
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002343 // maybe strncmp? we had tons of erroneous strncasecmp's...
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002344 if (strncasecmp(a, opname, l) == 0
2345 || strncasecmp(a, short_opname, 2) == 0
2346 ) {
2347 if (flg_no)
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002348 vi_setops &= ~opt;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002349 else
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002350 vi_setops |= opt;
2351 }
2352}
2353#endif
2354
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002355#endif /* FEATURE_VI_COLON */
2356
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002357// buf must be no longer than MAX_INPUT_LEN!
2358static void colon(char *buf)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002359{
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002360#if !ENABLE_FEATURE_VI_COLON
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02002361 // Simple ":cmd" handler with minimal set of commands
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002362 char *p = buf;
2363 int cnt;
2364
2365 if (*p == ':')
2366 p++;
2367 cnt = strlen(p);
2368 if (cnt == 0)
2369 return;
2370 if (strncmp(p, "quit", cnt) == 0
2371 || strncmp(p, "q!", cnt) == 0
2372 ) {
2373 if (modified_count && p[1] != '!') {
2374 status_line_bold("No write since last change (:%s! overrides)", p);
2375 } else {
2376 editing = 0;
2377 }
2378 return;
2379 }
2380 if (strncmp(p, "write", cnt) == 0
2381 || strncmp(p, "wq", cnt) == 0
2382 || strncmp(p, "wn", cnt) == 0
2383 || (p[0] == 'x' && !p[1])
2384 ) {
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01002385 if (modified_count != 0 || p[0] != 'x') {
2386 cnt = file_write(current_filename, text, end - 1);
2387 }
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002388 if (cnt < 0) {
2389 if (cnt == -1)
Denys Vlasenko6f97b302017-09-29 18:17:25 +02002390 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002391 } else {
2392 modified_count = 0;
2393 last_modified_count = -1;
Denys Vlasenko89393592019-04-02 12:45:30 +02002394 status_line("'%s' %uL, %uC",
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002395 current_filename,
2396 count_lines(text, end - 1), cnt
2397 );
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01002398 if (p[0] == 'x'
2399 || p[1] == 'q' || p[1] == 'n'
2400 || p[1] == 'Q' || p[1] == 'N'
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002401 ) {
2402 editing = 0;
2403 }
2404 }
2405 return;
2406 }
2407 if (strncmp(p, "file", cnt) == 0) {
2408 last_status_cksum = 0; // force status update
2409 return;
2410 }
2411 if (sscanf(p, "%d", &cnt) > 0) {
2412 dot = find_line(cnt);
2413 dot_skip_over_ws();
2414 return;
2415 }
2416 not_implemented(p);
2417#else
2418
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002419 char c, *buf1, *q, *r;
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002420 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002421 int i, l, li, b, e;
2422 int useforce;
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002423# if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
2424 char *orig_buf;
2425# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002426
2427 // :3154 // if (-e line 3154) goto it else stay put
2428 // :4,33w! foo // write a portion of buffer to file "foo"
2429 // :w // write all of buffer to current file
2430 // :q // quit
2431 // :q! // quit- dont care about modified file
2432 // :'a,'z!sort -u // filter block through sort
2433 // :'f // goto mark "f"
2434 // :'fl // list literal the mark "f" line
2435 // :.r bar // read file "bar" into buffer before dot
2436 // :/123/,/abc/d // delete lines from "123" line to "abc" line
2437 // :/xyz/ // goto the "xyz" line
2438 // :s/find/replace/ // substitute pattern "find" with "replace"
2439 // :!<cmd> // run <cmd> then return
2440 //
Eric Andersen165e8cb2004-07-20 06:44:46 +00002441
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002442 if (!buf[0])
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002443 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002444 if (*buf == ':')
2445 buf++; // move past the ':'
2446
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002447 li = i = 0;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002448 b = e = -1;
2449 q = text; // assume 1,$ for the range
2450 r = end - 1;
2451 li = count_lines(text, end - 1);
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002452 fn = current_filename;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002453
2454 // look for optional address(es) :. :1 :1,9 :'q,'a :%
2455 buf = get_address(buf, &b, &e);
2456
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002457# if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002458 // remember orig command line
2459 orig_buf = buf;
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002460# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002461
2462 // get the COMMAND into cmd[]
2463 buf1 = cmd;
2464 while (*buf != '\0') {
2465 if (isspace(*buf))
2466 break;
2467 *buf1++ = *buf++;
2468 }
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002469 *buf1 = '\0';
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002470 // get any ARGuments
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002471 while (isblank(*buf))
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002472 buf++;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002473 strcpy(args, buf);
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002474 useforce = FALSE;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002475 buf1 = last_char_is(cmd, '!');
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002476 if (buf1) {
2477 useforce = TRUE;
2478 *buf1 = '\0'; // get rid of !
2479 }
2480 if (b >= 0) {
2481 // if there is only one addr, then the addr
2482 // is the line number of the single line the
2483 // user wants. So, reset the end
2484 // pointer to point at end of the "b" line
2485 q = find_line(b); // what line is #b
2486 r = end_line(q);
2487 li = 1;
2488 }
2489 if (e >= 0) {
2490 // we were given two addrs. change the
2491 // end pointer to the addr given by user.
2492 r = find_line(e); // what line is #e
2493 r = end_line(r);
2494 li = e - b + 1;
2495 }
2496 // ------------ now look for the command ------------
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002497 i = strlen(cmd);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002498 if (i == 0) { // :123CR goto line #123
2499 if (b >= 0) {
2500 dot = find_line(b); // what line is #b
2501 dot_skip_over_ws();
2502 }
Denis Vlasenko249fabf2006-12-19 00:29:22 +00002503 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002504# if ENABLE_FEATURE_ALLOW_EXEC
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002505 else if (cmd[0] == '!') { // run a cmd
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002506 int retcode;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002507 // :!ls run the <cmd>
Denis Vlasenko267e16c2008-10-14 10:34:41 +00002508 go_bottom_and_clear_to_eol();
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002509 cookmode();
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002510 retcode = system(orig_buf + 1); // run the cmd
2511 if (retcode)
2512 printf("\nshell returned %i\n\n", retcode);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002513 rawmode();
2514 Hit_Return(); // let user see results
Denis Vlasenko249fabf2006-12-19 00:29:22 +00002515 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002516# endif
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002517 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002518 if (b < 0) { // no addr given- use defaults
2519 b = e = count_lines(text, dot);
2520 }
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002521 status_line("%d", b);
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002522 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002523 if (b < 0) { // no addr given- use defaults
2524 q = begin_line(dot); // assume .,. for the range
2525 r = end_line(dot);
2526 }
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02002527 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002528 dot_skip_over_ws();
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002529 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002530 int size;
2531
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002532 // don't edit, if the current file has been modified
Denys Vlasenkoe7430862014-04-03 12:47:48 +02002533 if (modified_count && !useforce) {
Dennis Groenenc0657e02012-01-31 14:12:38 +01002534 status_line_bold("No write since last change (:%s! overrides)", cmd);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002535 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002536 }
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002537 if (args[0]) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002538 // the user supplied a file name
Denis Vlasenkoe8a07882007-06-10 15:08:44 +00002539 fn = args;
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002540 } else if (current_filename && current_filename[0]) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002541 // no user supplied name- use the current filename
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002542 // fn = current_filename; was set by default
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002543 } else {
2544 // no user file name, no current name- punt
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002545 status_line_bold("No current filename");
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002546 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002547 }
2548
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002549 size = init_text_buffer(fn);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002550
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002551# if ENABLE_FEATURE_VI_YANKMARK
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002552 if (Ureg >= 0 && Ureg < 28) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002553 free(reg[Ureg]); // free orig line reg- for 'U'
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002554 reg[Ureg] = NULL;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002555 }
Denys Vlasenkofa182a32019-06-08 12:57:07 +02002556 /*if (YDreg < 28) - always true*/ {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002557 free(reg[YDreg]); // free default yank/delete register
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002558 reg[YDreg] = NULL;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002559 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002560# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002561 // how many lines in text[]?
2562 li = count_lines(text, end - 1);
Denys Vlasenko778794d2013-01-22 10:13:52 +01002563 status_line("'%s'%s"
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00002564 IF_FEATURE_VI_READONLY("%s")
Denys Vlasenko89393592019-04-02 12:45:30 +02002565 " %uL, %uC",
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002566 current_filename,
2567 (size < 0 ? " [New file]" : ""),
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00002568 IF_FEATURE_VI_READONLY(
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002569 ((readonly_mode) ? " [Readonly]" : ""),
2570 )
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002571 li, (int)(end - text)
2572 );
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002573 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002574 if (b != -1 || e != -1) {
Denys Vlasenkoc2704542009-11-20 19:14:19 +01002575 status_line_bold("No address allowed on this command");
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002576 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002577 }
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002578 if (args[0]) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002579 // user wants a new filename
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002580 free(current_filename);
2581 current_filename = xstrdup(args);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002582 } else {
2583 // user wants file status info
Paul Fox8552aec2005-09-16 12:20:05 +00002584 last_status_cksum = 0; // force status update
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002585 }
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002586 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002587 // print out values of all features
Denis Vlasenko267e16c2008-10-14 10:34:41 +00002588 go_bottom_and_clear_to_eol();
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002589 cookmode();
2590 show_help();
2591 rawmode();
2592 Hit_Return();
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002593 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002594 if (b < 0) { // no addr given- use defaults
2595 q = begin_line(dot); // assume .,. for the range
2596 r = end_line(dot);
2597 }
Denis Vlasenko267e16c2008-10-14 10:34:41 +00002598 go_bottom_and_clear_to_eol();
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002599 puts("\r");
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002600 for (; q <= r; q++) {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002601 int c_is_no_print;
2602
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002603 c = *q;
Denis Vlasenko2a51af22007-03-21 22:31:24 +00002604 c_is_no_print = (c & 0x80) && !Isprint(c);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002605 if (c_is_no_print) {
2606 c = '.';
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002607 standout_start();
Denis Vlasenkod3c042f2007-12-30 01:59:53 +00002608 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002609 if (c == '\n') {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002610 write1("$\r");
2611 } else if (c < ' ' || c == 127) {
Denis Vlasenko4daad902007-09-27 10:20:47 +00002612 bb_putchar('^');
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002613 if (c == 127)
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002614 c = '?';
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002615 else
2616 c += '@';
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002617 }
Denis Vlasenko4daad902007-09-27 10:20:47 +00002618 bb_putchar(c);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002619 if (c_is_no_print)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002620 standout_end();
2621 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002622 Hit_Return();
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002623 } else if (strncmp(cmd, "quit", i) == 0 // quit
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002624 || strncmp(cmd, "next", i) == 0 // edit next file
Dennis Groenenc0657e02012-01-31 14:12:38 +01002625 || strncmp(cmd, "prev", i) == 0 // edit previous file
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002626 ) {
Denys Vlasenko04cecd52010-04-16 22:13:55 -07002627 int n;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002628 if (useforce) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002629 if (*cmd == 'q') {
Dennis Groenenc0657e02012-01-31 14:12:38 +01002630 // force end of argv list
Denys Vlasenkoa3ce1612019-04-03 16:35:23 +02002631 optind = cmdline_filecnt;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002632 }
2633 editing = 0;
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002634 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002635 }
2636 // don't exit if the file been modified
Denys Vlasenkoe7430862014-04-03 12:47:48 +02002637 if (modified_count) {
Dennis Groenenc0657e02012-01-31 14:12:38 +01002638 status_line_bold("No write since last change (:%s! overrides)", cmd);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002639 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002640 }
2641 // are there other file to edit
Denys Vlasenko89393592019-04-02 12:45:30 +02002642 n = cmdline_filecnt - optind - 1;
Denys Vlasenko04cecd52010-04-16 22:13:55 -07002643 if (*cmd == 'q' && n > 0) {
Denys Vlasenko89393592019-04-02 12:45:30 +02002644 status_line_bold("%u more file(s) to edit", n);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002645 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002646 }
Denys Vlasenko04cecd52010-04-16 22:13:55 -07002647 if (*cmd == 'n' && n <= 0) {
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002648 status_line_bold("No more files to edit");
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002649 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002650 }
Dennis Groenenc0657e02012-01-31 14:12:38 +01002651 if (*cmd == 'p') {
2652 // are there previous files to edit
2653 if (optind < 1) {
2654 status_line_bold("No previous files to edit");
2655 goto ret;
2656 }
2657 optind -= 2;
2658 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002659 editing = 0;
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002660 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002661 int size;
2662
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002663 fn = args;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002664 if (!fn[0]) {
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002665 status_line_bold("No filename given");
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002666 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002667 }
2668 if (b < 0) { // no addr given- use defaults
2669 q = begin_line(dot); // assume "dot"
2670 }
2671 // read after current line- unless user said ":0r foo"
Ron Yorston70f43202014-11-30 20:39:53 +00002672 if (b != 0) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002673 q = next_line(q);
Ron Yorston70f43202014-11-30 20:39:53 +00002674 // read after last line
2675 if (q == end-1)
2676 ++q;
2677 }
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00002678 { // dance around potentially-reallocated text[]
2679 uintptr_t ofs = q - text;
Ron Yorstone5213ce2014-11-30 20:39:25 +00002680 size = file_insert(fn, q, 0);
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00002681 q = text + ofs;
2682 }
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002683 if (size < 0)
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002684 goto ret; // nothing was inserted
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002685 // how many lines in text[]?
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002686 li = count_lines(q, q + size - 1);
Denys Vlasenko778794d2013-01-22 10:13:52 +01002687 status_line("'%s'"
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00002688 IF_FEATURE_VI_READONLY("%s")
Denys Vlasenko89393592019-04-02 12:45:30 +02002689 " %uL, %uC",
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002690 fn,
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00002691 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002692 li, size
2693 );
2694 if (size > 0) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002695 // if the insert is before "dot" then we need to update
2696 if (q <= dot)
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002697 dot += size;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002698 }
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002699 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
Denys Vlasenkoe7430862014-04-03 12:47:48 +02002700 if (modified_count && !useforce) {
Dennis Groenenc0657e02012-01-31 14:12:38 +01002701 status_line_bold("No write since last change (:%s! overrides)", cmd);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002702 } else {
2703 // reset the filenames to edit
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02002704 optind = -1; // start from 0th file
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002705 editing = 0;
2706 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002707# if ENABLE_FEATURE_VI_SET
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002708 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002709# if ENABLE_FEATURE_VI_SETOPTS
Denis Vlasenkof9234132007-03-21 00:03:42 +00002710 char *argp;
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002711# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002712 i = 0; // offset into args
Denys Vlasenko605f2642012-06-11 01:53:33 +02002713 // only blank is regarded as args delimiter. What about tab '\t'?
Denis Vlasenkof9234132007-03-21 00:03:42 +00002714 if (!args[0] || strcasecmp(args, "all") == 0) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002715 // print out values of all options
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002716# if ENABLE_FEATURE_VI_SETOPTS
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002717 status_line_bold(
2718 "%sautoindent "
2719 "%sflash "
2720 "%signorecase "
2721 "%sshowmatch "
2722 "tabstop=%u",
2723 autoindent ? "" : "no",
2724 err_method ? "" : "no",
2725 ignorecase ? "" : "no",
2726 showmatch ? "" : "no",
2727 tabstop
2728 );
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002729# endif
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002730 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002731 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002732# if ENABLE_FEATURE_VI_SETOPTS
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002733 argp = args;
Denis Vlasenkoba2fb712007-04-01 09:39:03 +00002734 while (*argp) {
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002735 if (strncmp(argp, "no", 2) == 0)
Denis Vlasenkof9234132007-03-21 00:03:42 +00002736 i = 2; // ":set noautoindent"
2737 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002738 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
Denis Vlasenkof9234132007-03-21 00:03:42 +00002739 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002740 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
2741 if (strncmp(argp + i, "tabstop=", 8) == 0) {
2742 int t = 0;
2743 sscanf(argp + i+8, "%u", &t);
2744 if (t > 0 && t <= MAX_TABSTOP)
2745 tabstop = t;
Denis Vlasenkof9234132007-03-21 00:03:42 +00002746 }
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002747 argp = skip_non_whitespace(argp);
2748 argp = skip_whitespace(argp);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002749 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002750# endif /* FEATURE_VI_SETOPTS */
2751# endif /* FEATURE_VI_SET */
2752
2753# if ENABLE_FEATURE_VI_SEARCH
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002754 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002755 char *F, *R, *flags;
2756 size_t len_F, len_R;
2757 int gflag; // global replace flag
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002758# if ENABLE_FEATURE_VI_UNDO
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02002759 int dont_chain_first_item = ALLOW_UNDO;
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002760# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002761
2762 // F points to the "find" pattern
2763 // R points to the "replace" pattern
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002764 // replace the cmd line delimiters "/" with NULs
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002765 c = orig_buf[1]; // what is the delimiter
2766 F = orig_buf + 2; // start of "find"
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002767 R = strchr(F, c); // middle delimiter
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00002768 if (!R)
2769 goto colon_s_fail;
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002770 len_F = R - F;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002771 *R++ = '\0'; // terminate "find"
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002772 flags = strchr(R, c);
2773 if (!flags)
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00002774 goto colon_s_fail;
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002775 len_R = flags - R;
2776 *flags++ = '\0'; // terminate "replace"
2777 gflag = *flags;
2778
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002779 q = begin_line(q);
2780 if (b < 0) { // maybe :s/foo/bar/
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002781 q = begin_line(dot); // start with cur line
2782 b = count_lines(text, q); // cur line number
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002783 }
2784 if (e < 0)
2785 e = b; // maybe :.s/foo/bar/
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002786
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002787 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002788 char *ls = q; // orig line start
2789 char *found;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002790 vc4:
Denys Vlasenkob7330462018-11-29 14:39:52 +01002791 found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002792 if (found) {
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00002793 uintptr_t bias;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002794 // we found the "find" pattern - delete it
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02002795 // For undo support, the first item should not be chained
2796 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002797# if ENABLE_FEATURE_VI_UNDO
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02002798 dont_chain_first_item = ALLOW_UNDO_CHAIN;
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002799# endif
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02002800 // insert the "replace" patern
2801 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002802 found += bias;
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00002803 ls += bias;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02002804 //q += bias; - recalculated anyway
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002805 // check for "global" :s/foo/bar/g
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002806 if (gflag == 'g') {
2807 if ((found + len_R) < end_line(ls)) {
2808 q = found + len_R;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002809 goto vc4; // don't let q move past cur line
2810 }
2811 }
2812 }
2813 q = next_line(ls);
2814 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002815# endif /* FEATURE_VI_SEARCH */
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002816 } else if (strncmp(cmd, "version", i) == 0) { // show software version
Denys Vlasenkoeba7fe62017-01-29 19:14:26 +01002817 status_line(BB_VER);
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002818 } else if (strncmp(cmd, "write", i) == 0 // write text to file
2819 || strncmp(cmd, "wq", i) == 0
2820 || strncmp(cmd, "wn", i) == 0
2821 || (cmd[0] == 'x' && !cmd[1])
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002822 ) {
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002823 int size;
2824 //int forced = FALSE;
2825
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002826 // is there a file name to write to?
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002827 if (args[0]) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002828 fn = args;
2829 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002830# if ENABLE_FEATURE_VI_READONLY
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002831 if (readonly_mode && !useforce) {
Denys Vlasenko778794d2013-01-22 10:13:52 +01002832 status_line_bold("'%s' is read only", fn);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002833 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002834 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002835# endif
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002836 //if (useforce) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002837 // if "fn" is not write-able, chmod u+w
2838 // sprintf(syscmd, "chmod u+w %s", fn);
2839 // system(syscmd);
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002840 // forced = TRUE;
2841 //}
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01002842 if (modified_count != 0 || cmd[0] != 'x') {
2843 size = r - q + 1;
2844 l = file_write(fn, q, r);
2845 } else {
2846 size = 0;
2847 l = 0;
2848 }
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002849 //if (useforce && forced) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002850 // chmod u-w
2851 // sprintf(syscmd, "chmod u-w %s", fn);
2852 // system(syscmd);
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002853 // forced = FALSE;
2854 //}
Paul Fox61e45db2005-10-09 14:43:22 +00002855 if (l < 0) {
2856 if (l == -1)
Denys Vlasenko9e7c0022013-03-15 02:17:29 +01002857 status_line_bold_errno(fn);
Paul Fox61e45db2005-10-09 14:43:22 +00002858 } else {
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01002859 // how many lines written
2860 li = count_lines(q, q + l - 1);
Denys Vlasenko89393592019-04-02 12:45:30 +02002861 status_line("'%s' %uL, %uC", fn, li, l);
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01002862 if (l == size) {
2863 if (q == text && q + l == end) {
2864 modified_count = 0;
2865 last_modified_count = -1;
2866 }
2867 if (cmd[0] == 'x'
2868 || cmd[1] == 'q' || cmd[1] == 'n'
2869 || cmd[1] == 'Q' || cmd[1] == 'N'
2870 ) {
2871 editing = 0;
2872 }
Paul Fox61e45db2005-10-09 14:43:22 +00002873 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002874 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002875# if ENABLE_FEATURE_VI_YANKMARK
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002876 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002877 if (b < 0) { // no addr given- use defaults
2878 q = begin_line(dot); // assume .,. for the range
2879 r = end_line(dot);
2880 }
2881 text_yank(q, r, YDreg);
2882 li = count_lines(q, r);
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002883 status_line("Yank %d lines (%d chars) into [%c]",
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002884 li, strlen(reg[YDreg]), what_reg());
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002885# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002886 } else {
2887 // cmd unknown
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002888 not_implemented(cmd);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002889 }
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002890 ret:
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002891 dot = bound_dot(dot); // make sure "dot" is valid
2892 return;
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002893# if ENABLE_FEATURE_VI_SEARCH
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002894 colon_s_fail:
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002895 status_line(":s expression missing delimiters");
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002896# endif
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00002897#endif /* FEATURE_VI_COLON */
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002898}
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002899
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002900//----- Char Routines --------------------------------------------
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02002901// Chars that are part of a word-
2902// 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2903// Chars that are Not part of a word (stoppers)
2904// !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2905// Chars that are WhiteSpace
2906// TAB NEWLINE VT FF RETURN SPACE
2907// DO NOT COUNT NEWLINE AS WHITESPACE
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002908
Denis Vlasenko33875382008-06-21 20:31:50 +00002909static int st_test(char *p, int type, int dir, char *tested)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002910{
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002911 char c, c0, ci;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002912 int test, inc;
2913
2914 inc = dir;
2915 c = c0 = p[0];
2916 ci = p[inc];
2917 test = 0;
2918
2919 if (type == S_BEFORE_WS) {
2920 c = ci;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02002921 test = (!isspace(c) || c == '\n');
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002922 }
2923 if (type == S_TO_WS) {
2924 c = c0;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02002925 test = (!isspace(c) || c == '\n');
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002926 }
2927 if (type == S_OVER_WS) {
2928 c = c0;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02002929 test = isspace(c);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002930 }
2931 if (type == S_END_PUNCT) {
2932 c = ci;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02002933 test = ispunct(c);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002934 }
2935 if (type == S_END_ALNUM) {
2936 c = ci;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02002937 test = (isalnum(c) || c == '_');
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002938 }
2939 *tested = c;
Denis Vlasenkod9e15f22006-11-27 16:49:55 +00002940 return test;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002941}
2942
Denis Vlasenko33875382008-06-21 20:31:50 +00002943static char *skip_thing(char *p, int linecnt, int dir, int type)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002944{
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002945 char c;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002946
2947 while (st_test(p, type, dir, &c)) {
2948 // make sure we limit search to correct number of lines
2949 if (c == '\n' && --linecnt < 1)
2950 break;
2951 if (dir >= 0 && p >= end - 1)
2952 break;
2953 if (dir < 0 && p <= text)
2954 break;
2955 p += dir; // move to next char
2956 }
Denis Vlasenko079f8af2006-11-27 16:49:31 +00002957 return p;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002958}
2959
Denys Vlasenko616e4692019-04-01 14:02:37 +02002960#if ENABLE_FEATURE_VI_USE_SIGNALS
2961static void winch_handler(int sig UNUSED_PARAM)
2962{
2963 int save_errno = errno;
2964 // FIXME: do it in main loop!!!
2965 signal(SIGWINCH, winch_handler);
2966 query_screen_dimensions();
2967 new_screen(rows, columns); // get memory for virtual screen
2968 redraw(TRUE); // re-draw the screen
2969 errno = save_errno;
2970}
2971static void tstp_handler(int sig UNUSED_PARAM)
2972{
2973 int save_errno = errno;
2974
2975 // ioctl inside cookmode() was seen to generate SIGTTOU,
2976 // stopping us too early. Prevent that:
2977 signal(SIGTTOU, SIG_IGN);
2978
2979 go_bottom_and_clear_to_eol();
2980 cookmode(); // terminal to "cooked"
2981
2982 // stop now
2983 //signal(SIGTSTP, SIG_DFL);
2984 //raise(SIGTSTP);
2985 raise(SIGSTOP); // avoid "dance" with TSTP handler - use SIGSTOP instead
2986 //signal(SIGTSTP, tstp_handler);
2987
2988 // we have been "continued" with SIGCONT, restore screen and termios
2989 rawmode(); // terminal to "raw"
2990 last_status_cksum = 0; // force status update
2991 redraw(TRUE); // re-draw the screen
2992
2993 errno = save_errno;
2994}
2995static void int_handler(int sig)
2996{
2997 signal(SIGINT, int_handler);
2998 siglongjmp(restart, sig);
2999}
3000#endif /* FEATURE_VI_USE_SIGNALS */
3001
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003002static void do_cmd(int c);
3003
3004static int find_range(char **start, char **stop, char c)
3005{
3006 char *save_dot, *p, *q, *t;
Ron Yorston7b93e312019-04-28 09:10:16 +01003007 int cnt, multiline = 0, forward;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003008
3009 save_dot = dot;
3010 p = q = dot;
3011
Ron Yorston7b93e312019-04-28 09:10:16 +01003012 // will a 'G' command move forwards or backwards?
3013 forward = cmdcnt == 0 || cmdcnt > count_lines(text, dot);
3014
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003015 if (strchr("cdy><", c)) {
3016 // these cmds operate on whole lines
3017 p = q = begin_line(p);
3018 for (cnt = 1; cnt < cmdcnt; cnt++) {
3019 q = next_line(q);
3020 }
3021 q = end_line(q);
3022 } else if (strchr("^%$0bBeEfth\b\177", c)) {
3023 // These cmds operate on char positions
3024 do_cmd(c); // execute movement cmd
3025 q = dot;
3026 } else if (strchr("wW", c)) {
3027 do_cmd(c); // execute movement cmd
3028 // if we are at the next word's first char
3029 // step back one char
3030 // but check the possibilities when it is true
3031 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
3032 || (ispunct(dot[-1]) && !ispunct(dot[0]))
3033 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
3034 dot--; // move back off of next word
3035 if (dot > text && *dot == '\n')
3036 dot--; // stay off NL
3037 q = dot;
Ron Yorston7b93e312019-04-28 09:10:16 +01003038 } else if (strchr("H-k{", c) || (c == 'G' && !forward)) {
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003039 // these operate on multi-lines backwards
3040 q = end_line(dot); // find NL
3041 do_cmd(c); // execute movement cmd
3042 dot_begin();
3043 p = dot;
Ron Yorston7b93e312019-04-28 09:10:16 +01003044 } else if (strchr("L+j}\r\n", c) || (c == 'G' && forward)) {
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003045 // these operate on multi-lines forwards
3046 p = begin_line(dot);
3047 do_cmd(c); // execute movement cmd
3048 dot_end(); // find NL
3049 q = dot;
3050 } else {
3051 // nothing -- this causes any other values of c to
3052 // represent the one-character range under the
3053 // cursor. this is correct for ' ' and 'l', but
3054 // perhaps no others.
3055 //
3056 }
3057 if (q < p) {
3058 t = q;
3059 q = p;
3060 p = t;
3061 }
3062
3063 // backward char movements don't include start position
3064 if (q > p && strchr("^0bBh\b\177", c)) q--;
3065
3066 multiline = 0;
3067 for (t = p; t <= q; t++) {
3068 if (*t == '\n') {
3069 multiline = 1;
3070 break;
3071 }
3072 }
3073
3074 *start = p;
3075 *stop = q;
3076 dot = save_dot;
3077 return multiline;
3078}
3079
Eric Andersen3f980402001-04-04 17:31:15 +00003080//---------------------------------------------------------------------
3081//----- the Ascii Chart -----------------------------------------------
Eric Andersen3f980402001-04-04 17:31:15 +00003082// 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3083// 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3084// 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3085// 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3086// 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3087// 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3088// 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3089// 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3090// 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3091// 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3092// 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3093// 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3094// 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3095// 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3096// 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3097// 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3098//---------------------------------------------------------------------
3099
3100//----- Execute a Vi Command -----------------------------------
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003101static void do_cmd(int c)
Eric Andersen3f980402001-04-04 17:31:15 +00003102{
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003103 char *p, *q, *save_dot;
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003104 char buf[12];
Denis Vlasenkoc3a9dc82008-10-29 00:58:04 +00003105 int dir;
Paul Foxc51fc7b2008-03-06 01:34:23 +00003106 int cnt, i, j;
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003107 int c1;
Eric Andersen3f980402001-04-04 17:31:15 +00003108
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +00003109// c1 = c; // quiet the compiler
3110// cnt = yf = 0; // quiet the compiler
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003111// p = q = save_dot = buf; // quiet the compiler
3112 memset(buf, '\0', sizeof(buf));
Eric Andersenbff7a602001-11-17 07:15:43 +00003113
Paul Fox8552aec2005-09-16 12:20:05 +00003114 show_status_line();
3115
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02003116 // if this is a cursor key, skip these checks
Eric Andersenbff7a602001-11-17 07:15:43 +00003117 switch (c) {
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003118 case KEYCODE_UP:
3119 case KEYCODE_DOWN:
3120 case KEYCODE_LEFT:
3121 case KEYCODE_RIGHT:
3122 case KEYCODE_HOME:
3123 case KEYCODE_END:
3124 case KEYCODE_PAGEUP:
3125 case KEYCODE_PAGEDOWN:
3126 case KEYCODE_DELETE:
Eric Andersenbff7a602001-11-17 07:15:43 +00003127 goto key_cmd_mode;
3128 }
3129
Eric Andersen3f980402001-04-04 17:31:15 +00003130 if (cmd_mode == 2) {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003131 // flip-flop Insert/Replace mode
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003132 if (c == KEYCODE_INSERT)
Denis Vlasenko2a51af22007-03-21 22:31:24 +00003133 goto dc_i;
Eric Andersen3f980402001-04-04 17:31:15 +00003134 // we are 'R'eplacing the current *dot with new char
3135 if (*dot == '\n') {
3136 // don't Replace past E-o-l
3137 cmd_mode = 1; // convert to insert
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003138 undo_queue_commit();
Eric Andersen3f980402001-04-04 17:31:15 +00003139 } else {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003140 if (1 <= c || Isprint(c)) {
Eric Andersen3f980402001-04-04 17:31:15 +00003141 if (c != 27)
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003142 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3143 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
Eric Andersen3f980402001-04-04 17:31:15 +00003144 }
3145 goto dc1;
3146 }
3147 }
3148 if (cmd_mode == 1) {
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02003149 // hitting "Insert" twice means "R" replace mode
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003150 if (c == KEYCODE_INSERT) goto dc5;
Eric Andersen3f980402001-04-04 17:31:15 +00003151 // insert the char c at "dot"
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003152 if (1 <= c || Isprint(c)) {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003153 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
Eric Andersen3f980402001-04-04 17:31:15 +00003154 }
3155 goto dc1;
3156 }
3157
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003158 key_cmd_mode:
Eric Andersen3f980402001-04-04 17:31:15 +00003159 switch (c) {
Eric Andersen822c3832001-05-07 17:37:43 +00003160 //case 0x01: // soh
3161 //case 0x09: // ht
3162 //case 0x0b: // vt
3163 //case 0x0e: // so
3164 //case 0x0f: // si
3165 //case 0x10: // dle
3166 //case 0x11: // dc1
3167 //case 0x13: // dc3
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003168#if ENABLE_FEATURE_VI_CRASHME
Eric Andersen1c0d3112001-04-16 15:46:44 +00003169 case 0x14: // dc4 ctrl-T
Eric Andersen3f980402001-04-04 17:31:15 +00003170 crashme = (crashme == 0) ? 1 : 0;
Eric Andersen3f980402001-04-04 17:31:15 +00003171 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003172#endif
Eric Andersen822c3832001-05-07 17:37:43 +00003173 //case 0x16: // syn
3174 //case 0x17: // etb
3175 //case 0x18: // can
3176 //case 0x1c: // fs
3177 //case 0x1d: // gs
3178 //case 0x1e: // rs
3179 //case 0x1f: // us
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003180 //case '!': // !-
3181 //case '#': // #-
3182 //case '&': // &-
3183 //case '(': // (-
3184 //case ')': // )-
3185 //case '*': // *-
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003186 //case '=': // =-
3187 //case '@': // @-
3188 //case 'F': // F-
3189 //case 'K': // K-
3190 //case 'Q': // Q-
3191 //case 'S': // S-
3192 //case 'T': // T-
3193 //case 'V': // V-
3194 //case '[': // [-
3195 //case '\\': // \-
3196 //case ']': // ]-
3197 //case '_': // _-
3198 //case '`': // `-
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003199 //case 'v': // v-
Denys Vlasenkob22bbff2009-07-04 16:50:43 +02003200 default: // unrecognized command
Eric Andersen3f980402001-04-04 17:31:15 +00003201 buf[0] = c;
3202 buf[1] = '\0';
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003203 not_implemented(buf);
Eric Andersen3f980402001-04-04 17:31:15 +00003204 end_cmd_q(); // stop adding to q
3205 case 0x00: // nul- ignore
3206 break;
3207 case 2: // ctrl-B scroll up full screen
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003208 case KEYCODE_PAGEUP: // Cursor Key Page Up
Eric Andersen3f980402001-04-04 17:31:15 +00003209 dot_scroll(rows - 2, -1);
3210 break;
Eric Andersen3f980402001-04-04 17:31:15 +00003211 case 4: // ctrl-D scroll down half screen
3212 dot_scroll((rows - 2) / 2, 1);
3213 break;
3214 case 5: // ctrl-E scroll down one line
3215 dot_scroll(1, 1);
3216 break;
3217 case 6: // ctrl-F scroll down full screen
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003218 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
Eric Andersen3f980402001-04-04 17:31:15 +00003219 dot_scroll(rows - 2, 1);
3220 break;
3221 case 7: // ctrl-G show current status
Paul Fox8552aec2005-09-16 12:20:05 +00003222 last_status_cksum = 0; // force status update
Eric Andersen3f980402001-04-04 17:31:15 +00003223 break;
3224 case 'h': // h- move left
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003225 case KEYCODE_LEFT: // cursor key Left
Paul Foxd13b90b2005-07-18 22:17:25 +00003226 case 8: // ctrl-H- move left (This may be ERASE char)
Denis Vlasenko2a51af22007-03-21 22:31:24 +00003227 case 0x7f: // DEL- move left (This may be ERASE char)
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003228 do {
3229 dot_left();
3230 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003231 break;
3232 case 10: // Newline ^J
3233 case 'j': // j- goto next line, same col
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003234 case KEYCODE_DOWN: // cursor key Down
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003235 do {
3236 dot_next(); // go to next B-o-l
3237 // try stay in same col
3238 dot = move_to_col(dot, ccol + offset);
3239 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003240 break;
3241 case 12: // ctrl-L force redraw whole screen
Eric Andersen1c0d3112001-04-16 15:46:44 +00003242 case 18: // ctrl-R force redraw
Ron Yorston55279672016-04-26 15:23:38 +01003243 redraw(TRUE); // this will redraw the entire display
Eric Andersen3f980402001-04-04 17:31:15 +00003244 break;
3245 case 13: // Carriage Return ^M
3246 case '+': // +- goto next line
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003247 do {
3248 dot_next();
3249 dot_skip_over_ws();
3250 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003251 break;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02003252 case 21: // ctrl-U scroll up half screen
Eric Andersen3f980402001-04-04 17:31:15 +00003253 dot_scroll((rows - 2) / 2, -1);
3254 break;
3255 case 25: // ctrl-Y scroll up one line
3256 dot_scroll(1, -1);
3257 break;
Eric Andersen822c3832001-05-07 17:37:43 +00003258 case 27: // esc
Eric Andersen3f980402001-04-04 17:31:15 +00003259 if (cmd_mode == 0)
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003260 indicate_error();
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02003261 cmd_mode = 0; // stop inserting
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003262 undo_queue_commit();
Eric Andersen3f980402001-04-04 17:31:15 +00003263 end_cmd_q();
Paul Fox8552aec2005-09-16 12:20:05 +00003264 last_status_cksum = 0; // force status update
Eric Andersen3f980402001-04-04 17:31:15 +00003265 break;
3266 case ' ': // move right
3267 case 'l': // move right
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003268 case KEYCODE_RIGHT: // Cursor Key Right
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003269 do {
3270 dot_right();
3271 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003272 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003273#if ENABLE_FEATURE_VI_YANKMARK
Eric Andersen3f980402001-04-04 17:31:15 +00003274 case '"': // "- name a register to use for Delete/Yank
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003275 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3276 if ((unsigned)c1 <= 25) { // a-z?
3277 YDreg = c1;
Eric Andersen3f980402001-04-04 17:31:15 +00003278 } else {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003279 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003280 }
3281 break;
3282 case '\'': // '- goto a specific mark
Denys Vlasenko61fcc8c2016-09-28 16:23:05 +02003283 c1 = (get_one_char() | 0x20);
3284 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3285 c1 = (c1 - 'a');
Eric Andersen3f980402001-04-04 17:31:15 +00003286 // get the b-o-l
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003287 q = mark[c1];
Eric Andersen3f980402001-04-04 17:31:15 +00003288 if (text <= q && q < end) {
3289 dot = q;
3290 dot_begin(); // go to B-o-l
3291 dot_skip_over_ws();
3292 }
3293 } else if (c1 == '\'') { // goto previous context
3294 dot = swap_context(dot); // swap current and previous context
3295 dot_begin(); // go to B-o-l
3296 dot_skip_over_ws();
3297 } else {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003298 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003299 }
3300 break;
3301 case 'm': // m- Mark a line
3302 // this is really stupid. If there are any inserts or deletes
3303 // between text[0] and dot then this mark will not point to the
3304 // correct location! It could be off by many lines!
3305 // Well..., at least its quick and dirty.
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003306 c1 = (get_one_char() | 0x20) - 'a';
3307 if ((unsigned)c1 <= 25) { // a-z?
Eric Andersen3f980402001-04-04 17:31:15 +00003308 // remember the line
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003309 mark[c1] = dot;
Eric Andersen3f980402001-04-04 17:31:15 +00003310 } else {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003311 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003312 }
3313 break;
3314 case 'P': // P- Put register before
3315 case 'p': // p- put register after
3316 p = reg[YDreg];
Denis Vlasenko00d84172008-11-24 07:34:42 +00003317 if (p == NULL) {
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003318 status_line_bold("Nothing in register %c", what_reg());
Eric Andersen3f980402001-04-04 17:31:15 +00003319 break;
3320 }
3321 // are we putting whole lines or strings
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003322 if (strchr(p, '\n') != NULL) {
Eric Andersen3f980402001-04-04 17:31:15 +00003323 if (c == 'P') {
3324 dot_begin(); // putting lines- Put above
3325 }
3326 if (c == 'p') {
3327 // are we putting after very last line?
3328 if (end_line(dot) == (end - 1)) {
3329 dot = end; // force dot to end of text[]
3330 } else {
3331 dot_next(); // next line, then put before
3332 }
3333 }
3334 } else {
3335 if (c == 'p')
3336 dot_right(); // move to right, can move to NL
3337 }
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003338 string_insert(dot, p, ALLOW_UNDO); // insert the string
Eric Andersen3f980402001-04-04 17:31:15 +00003339 end_cmd_q(); // stop adding to q
3340 break;
Eric Andersen3f980402001-04-04 17:31:15 +00003341 case 'U': // U- Undo; replace current line with original version
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003342 if (reg[Ureg] != NULL) {
Eric Andersen3f980402001-04-04 17:31:15 +00003343 p = begin_line(dot);
3344 q = end_line(dot);
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003345 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3346 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
Eric Andersen3f980402001-04-04 17:31:15 +00003347 dot = p;
3348 dot_skip_over_ws();
3349 }
3350 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003351#endif /* FEATURE_VI_YANKMARK */
Andrew Fuller4d8ddb82015-05-03 18:18:25 +02003352#if ENABLE_FEATURE_VI_UNDO
3353 case 'u': // u- undo last operation
3354 undo_pop();
3355 break;
3356#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003357 case '$': // $- goto end of line
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003358 case KEYCODE_END: // Cursor Key End
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003359 for (;;) {
3360 dot = end_line(dot);
Denys Vlasenko1fd71292011-11-28 04:55:48 +01003361 if (--cmdcnt <= 0)
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003362 break;
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01003363 dot_next();
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01003364 }
Eric Andersen3f980402001-04-04 17:31:15 +00003365 break;
3366 case '%': // %- find matching char of pair () [] {}
3367 for (q = dot; q < end && *q != '\n'; q++) {
3368 if (strchr("()[]{}", *q) != NULL) {
3369 // we found half of a pair
3370 p = find_pair(q, *q);
3371 if (p == NULL) {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003372 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003373 } else {
3374 dot = p;
3375 }
3376 break;
3377 }
3378 }
3379 if (*q == '\n')
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003380 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003381 break;
3382 case 'f': // f- forward to a user specified char
3383 last_forward_char = get_one_char(); // get the search char
3384 //
Eric Andersenaff114c2004-04-14 17:51:38 +00003385 // dont separate these two commands. 'f' depends on ';'
Eric Andersen3f980402001-04-04 17:31:15 +00003386 //
Bernhard Reutner-Fischera985d302008-02-11 11:44:38 +00003387 //**** fall through to ... ';'
Eric Andersen3f980402001-04-04 17:31:15 +00003388 case ';': // ;- look at rest of line for last forward char
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003389 do {
3390 if (last_forward_char == 0)
3391 break;
3392 q = dot + 1;
3393 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3394 q++;
3395 }
3396 if (*q == last_forward_char)
3397 dot = q;
3398 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003399 break;
Paul Foxb5ee8db2008-02-14 01:17:01 +00003400 case ',': // repeat latest 'f' in opposite direction
Paul Foxb5ee8db2008-02-14 01:17:01 +00003401 if (last_forward_char == 0)
3402 break;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003403 do {
3404 q = dot - 1;
3405 while (q >= text && *q != '\n' && *q != last_forward_char) {
3406 q--;
3407 }
3408 if (q >= text && *q == last_forward_char)
3409 dot = q;
3410 } while (--cmdcnt > 0);
Paul Foxb5ee8db2008-02-14 01:17:01 +00003411 break;
3412
Eric Andersen3f980402001-04-04 17:31:15 +00003413 case '-': // -- goto prev line
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003414 do {
3415 dot_prev();
3416 dot_skip_over_ws();
3417 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003418 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003419#if ENABLE_FEATURE_VI_DOT_CMD
Eric Andersen3f980402001-04-04 17:31:15 +00003420 case '.': // .- repeat the last modifying command
3421 // Stuff the last_modifying_cmd back into stdin
3422 // and let it be re-executed.
Denys Vlasenko2a576082019-04-01 16:15:51 +02003423 if (lmc_len != 0) {
3424 ioq = ioq_start = xstrndup(last_modifying_cmd, lmc_len);
Eric Andersen3f980402001-04-04 17:31:15 +00003425 }
3426 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003427#endif
3428#if ENABLE_FEATURE_VI_SEARCH
Eric Andersen3f980402001-04-04 17:31:15 +00003429 case '?': // /- search for a pattern
3430 case '/': // /- search for a pattern
3431 buf[0] = c;
3432 buf[1] = '\0';
3433 q = get_input_line(buf); // get input line- use "status line"
Paul Fox4917c112008-03-05 16:44:02 +00003434 if (q[0] && !q[1]) {
3435 if (last_search_pattern[0])
Denis Vlasenkoc3a9dc82008-10-29 00:58:04 +00003436 last_search_pattern[0] = c;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003437 goto dc3; // if no pat re-use old pat
Paul Fox4917c112008-03-05 16:44:02 +00003438 }
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003439 if (q[0]) { // strlen(q) > 1: new pat- save it and find
Eric Andersen3f980402001-04-04 17:31:15 +00003440 // there is a new pat
Aaron Lehmanna170e1c2002-11-28 11:27:31 +00003441 free(last_search_pattern);
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003442 last_search_pattern = xstrdup(q);
Eric Andersen3f980402001-04-04 17:31:15 +00003443 goto dc3; // now find the pattern
3444 }
3445 // user changed mind and erased the "/"- do nothing
3446 break;
3447 case 'N': // N- backward search for last pattern
Eric Andersen3f980402001-04-04 17:31:15 +00003448 dir = BACK; // assume BACKWARD search
3449 p = dot - 1;
3450 if (last_search_pattern[0] == '?') {
3451 dir = FORWARD;
3452 p = dot + 1;
3453 }
3454 goto dc4; // now search for pattern
3455 break;
3456 case 'n': // n- repeat search for last pattern
3457 // search rest of text[] starting at next char
3458 // if search fails return orignal "p" not the "p+1" address
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003459 do {
3460 const char *msg;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003461 dc3:
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003462 dir = FORWARD; // assume FORWARD search
3463 p = dot + 1;
3464 if (last_search_pattern[0] == '?') {
3465 dir = BACK;
3466 p = dot - 1;
Eric Andersen3f980402001-04-04 17:31:15 +00003467 }
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003468 dc4:
Denys Vlasenkob7330462018-11-29 14:39:52 +01003469 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003470 if (q != NULL) {
3471 dot = q; // good search, update "dot"
3472 msg = NULL;
3473 goto dc2;
3474 }
3475 // no pattern found between "dot" and "end"- continue at top
3476 p = text;
3477 if (dir == BACK) {
3478 p = end - 1;
3479 }
Denys Vlasenkob7330462018-11-29 14:39:52 +01003480 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003481 if (q != NULL) { // found something
3482 dot = q; // found new pattern- goto it
3483 msg = "search hit BOTTOM, continuing at TOP";
3484 if (dir == BACK) {
3485 msg = "search hit TOP, continuing at BOTTOM";
3486 }
3487 } else {
3488 msg = "Pattern not found";
3489 }
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003490 dc2:
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003491 if (msg)
3492 status_line_bold("%s", msg);
3493 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003494 break;
3495 case '{': // {- move backward paragraph
Denys Vlasenko9a0c4042019-10-25 17:42:23 +02003496 q = char_search(dot, "\n\n", ((unsigned)BACK << 1) | FULL);
Eric Andersen3f980402001-04-04 17:31:15 +00003497 if (q != NULL) { // found blank line
3498 dot = next_line(q); // move to next blank line
3499 }
3500 break;
3501 case '}': // }- move forward paragraph
Denys Vlasenkob7330462018-11-29 14:39:52 +01003502 q = char_search(dot, "\n\n", (FORWARD << 1) | FULL);
Eric Andersen3f980402001-04-04 17:31:15 +00003503 if (q != NULL) { // found blank line
3504 dot = next_line(q); // move to next blank line
3505 }
3506 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003507#endif /* FEATURE_VI_SEARCH */
Maninder Singh97c64912015-05-25 13:46:36 +02003508 case '0': // 0- goto beginning of line
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003509 case '1': // 1-
3510 case '2': // 2-
3511 case '3': // 3-
3512 case '4': // 4-
3513 case '5': // 5-
3514 case '6': // 6-
3515 case '7': // 7-
3516 case '8': // 8-
3517 case '9': // 9-
Eric Andersen3f980402001-04-04 17:31:15 +00003518 if (c == '0' && cmdcnt < 1) {
3519 dot_begin(); // this was a standalone zero
3520 } else {
3521 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3522 }
3523 break;
3524 case ':': // :- the colon mode commands
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003525 p = get_input_line(":"); // get input line- use "status line"
Eric Andersen3f980402001-04-04 17:31:15 +00003526 colon(p); // execute the command
Eric Andersen3f980402001-04-04 17:31:15 +00003527 break;
3528 case '<': // <- Left shift something
3529 case '>': // >- Right shift something
3530 cnt = count_lines(text, dot); // remember what line we are on
3531 c1 = get_one_char(); // get the type of thing to delete
3532 find_range(&p, &q, c1);
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003533 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
Eric Andersen3f980402001-04-04 17:31:15 +00003534 p = begin_line(p);
3535 q = end_line(q);
3536 i = count_lines(p, q); // # of lines we are shifting
3537 for ( ; i > 0; i--, p = next_line(p)) {
3538 if (c == '<') {
3539 // shift left- remove tab or 8 spaces
3540 if (*p == '\t') {
3541 // shrink buffer 1 char
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003542 text_hole_delete(p, p, NO_UNDO);
Eric Andersen3f980402001-04-04 17:31:15 +00003543 } else if (*p == ' ') {
3544 // we should be calculating columns, not just SPACE
3545 for (j = 0; *p == ' ' && j < tabstop; j++) {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003546 text_hole_delete(p, p, NO_UNDO);
Eric Andersen3f980402001-04-04 17:31:15 +00003547 }
3548 }
3549 } else if (c == '>') {
3550 // shift right -- add tab or 8 spaces
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003551 char_insert(p, '\t', ALLOW_UNDO);
Eric Andersen3f980402001-04-04 17:31:15 +00003552 }
3553 }
3554 dot = find_line(cnt); // what line were we on
3555 dot_skip_over_ws();
3556 end_cmd_q(); // stop adding to q
3557 break;
3558 case 'A': // A- append at e-o-l
3559 dot_end(); // go to e-o-l
Bernhard Reutner-Fischera985d302008-02-11 11:44:38 +00003560 //**** fall through to ... 'a'
Eric Andersen3f980402001-04-04 17:31:15 +00003561 case 'a': // a- append after current char
3562 if (*dot != '\n')
3563 dot++;
3564 goto dc_i;
3565 break;
3566 case 'B': // B- back a blank-delimited Word
3567 case 'E': // E- end of a blank-delimited word
3568 case 'W': // W- forward a blank-delimited word
Eric Andersen3f980402001-04-04 17:31:15 +00003569 dir = FORWARD;
3570 if (c == 'B')
3571 dir = BACK;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003572 do {
3573 if (c == 'W' || isspace(dot[dir])) {
3574 dot = skip_thing(dot, 1, dir, S_TO_WS);
3575 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3576 }
3577 if (c != 'W')
3578 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3579 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003580 break;
3581 case 'C': // C- Change to e-o-l
3582 case 'D': // D- delete to e-o-l
3583 save_dot = dot;
3584 dot = dollar_line(dot); // move to before NL
3585 // copy text into a register and delete
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003586 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
Eric Andersen3f980402001-04-04 17:31:15 +00003587 if (c == 'C')
3588 goto dc_i; // start inserting
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003589#if ENABLE_FEATURE_VI_DOT_CMD
Eric Andersen3f980402001-04-04 17:31:15 +00003590 if (c == 'D')
3591 end_cmd_q(); // stop adding to q
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003592#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003593 break;
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003594 case 'g': // 'gg' goto a line number (vim) (default: very first line)
Paul Foxb5ee8db2008-02-14 01:17:01 +00003595 c1 = get_one_char();
3596 if (c1 != 'g') {
3597 buf[0] = 'g';
Denys Vlasenkode1996d2016-09-15 13:53:42 +02003598 // c1 < 0 if the key was special. Try "g<up-arrow>"
3599 // TODO: if Unicode?
3600 buf[1] = (c1 >= 0 ? c1 : '*');
Paul Foxb5ee8db2008-02-14 01:17:01 +00003601 buf[2] = '\0';
3602 not_implemented(buf);
3603 break;
3604 }
3605 if (cmdcnt == 0)
3606 cmdcnt = 1;
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02003607 // fall through
Eric Andersen822c3832001-05-07 17:37:43 +00003608 case 'G': // G- goto to a line number (default= E-O-F)
3609 dot = end - 1; // assume E-O-F
Eric Andersen1c0d3112001-04-16 15:46:44 +00003610 if (cmdcnt > 0) {
Eric Andersen822c3832001-05-07 17:37:43 +00003611 dot = find_line(cmdcnt); // what line is #cmdcnt
Eric Andersen1c0d3112001-04-16 15:46:44 +00003612 }
3613 dot_skip_over_ws();
3614 break;
Eric Andersen3f980402001-04-04 17:31:15 +00003615 case 'H': // H- goto top line on screen
3616 dot = screenbegin;
3617 if (cmdcnt > (rows - 1)) {
3618 cmdcnt = (rows - 1);
3619 }
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01003620 if (--cmdcnt > 0) {
Eric Andersen3f980402001-04-04 17:31:15 +00003621 do_cmd('+');
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01003622 }
Eric Andersen3f980402001-04-04 17:31:15 +00003623 dot_skip_over_ws();
3624 break;
3625 case 'I': // I- insert before first non-blank
3626 dot_begin(); // 0
3627 dot_skip_over_ws();
Bernhard Reutner-Fischera985d302008-02-11 11:44:38 +00003628 //**** fall through to ... 'i'
Eric Andersen3f980402001-04-04 17:31:15 +00003629 case 'i': // i- insert before current char
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003630 case KEYCODE_INSERT: // Cursor Key Insert
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003631 dc_i:
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003632 cmd_mode = 1; // start inserting
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003633 undo_queue_commit(); // commit queue when cmd_mode changes
Eric Andersen3f980402001-04-04 17:31:15 +00003634 break;
3635 case 'J': // J- join current and next lines together
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003636 do {
3637 dot_end(); // move to NL
3638 if (dot < end - 1) { // make sure not last char in text[]
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003639#if ENABLE_FEATURE_VI_UNDO
3640 undo_push(dot, 1, UNDO_DEL);
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003641 *dot++ = ' '; // replace NL with space
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003642 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3643#else
3644 *dot++ = ' ';
Denys Vlasenkoe7430862014-04-03 12:47:48 +02003645 modified_count++;
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003646#endif
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003647 while (isblank(*dot)) { // delete leading WS
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003648 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003649 }
Eric Andersen3f980402001-04-04 17:31:15 +00003650 }
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003651 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003652 end_cmd_q(); // stop adding to q
3653 break;
3654 case 'L': // L- goto bottom line on screen
3655 dot = end_screen();
3656 if (cmdcnt > (rows - 1)) {
3657 cmdcnt = (rows - 1);
3658 }
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01003659 if (--cmdcnt > 0) {
Eric Andersen3f980402001-04-04 17:31:15 +00003660 do_cmd('-');
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01003661 }
Eric Andersen3f980402001-04-04 17:31:15 +00003662 dot_begin();
3663 dot_skip_over_ws();
3664 break;
Eric Andersen822c3832001-05-07 17:37:43 +00003665 case 'M': // M- goto middle line on screen
Eric Andersen1c0d3112001-04-16 15:46:44 +00003666 dot = screenbegin;
3667 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3668 dot = next_line(dot);
3669 break;
Eric Andersen3f980402001-04-04 17:31:15 +00003670 case 'O': // O- open a empty line above
Eric Andersen822c3832001-05-07 17:37:43 +00003671 // 0i\n ESC -i
Eric Andersen3f980402001-04-04 17:31:15 +00003672 p = begin_line(dot);
3673 if (p[-1] == '\n') {
3674 dot_prev();
3675 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3676 dot_end();
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003677 dot = char_insert(dot, '\n', ALLOW_UNDO);
Eric Andersen3f980402001-04-04 17:31:15 +00003678 } else {
3679 dot_begin(); // 0
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003680 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
Eric Andersen3f980402001-04-04 17:31:15 +00003681 dot_prev(); // -
3682 }
3683 goto dc_i;
3684 break;
3685 case 'R': // R- continuous Replace char
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003686 dc5:
Eric Andersen3f980402001-04-04 17:31:15 +00003687 cmd_mode = 2;
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003688 undo_queue_commit();
Eric Andersen3f980402001-04-04 17:31:15 +00003689 break;
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003690 case KEYCODE_DELETE:
Denys Vlasenko49acc1a2015-03-12 21:15:34 +01003691 if (dot < end - 1)
3692 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
3693 break;
Eric Andersen3f980402001-04-04 17:31:15 +00003694 case 'X': // X- delete char before dot
3695 case 'x': // x- delete the current char
3696 case 's': // s- substitute the current char
Eric Andersen3f980402001-04-04 17:31:15 +00003697 dir = 0;
3698 if (c == 'X')
3699 dir = -1;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003700 do {
3701 if (dot[dir] != '\n') {
3702 if (c == 'X')
3703 dot--; // delete prev char
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003704 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003705 }
3706 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003707 end_cmd_q(); // stop adding to q
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003708 if (c == 's')
3709 goto dc_i; // start inserting
Eric Andersen3f980402001-04-04 17:31:15 +00003710 break;
3711 case 'Z': // Z- if modified, {write}; exit
3712 // ZZ means to save file (if necessary), then exit
3713 c1 = get_one_char();
3714 if (c1 != 'Z') {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003715 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003716 break;
3717 }
Denys Vlasenkoe7430862014-04-03 12:47:48 +02003718 if (modified_count) {
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00003719 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
Denys Vlasenko778794d2013-01-22 10:13:52 +01003720 status_line_bold("'%s' is read only", current_filename);
Denis Vlasenko92758142006-10-03 19:56:34 +00003721 break;
Paul Foxf0305b72006-03-28 14:18:21 +00003722 }
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00003723 cnt = file_write(current_filename, text, end - 1);
Paul Fox61e45db2005-10-09 14:43:22 +00003724 if (cnt < 0) {
3725 if (cnt == -1)
Denys Vlasenko6f97b302017-09-29 18:17:25 +02003726 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
Paul Fox61e45db2005-10-09 14:43:22 +00003727 } else if (cnt == (end - 1 - text + 1)) {
Eric Andersen3f980402001-04-04 17:31:15 +00003728 editing = 0;
3729 }
3730 } else {
3731 editing = 0;
3732 }
3733 break;
3734 case '^': // ^- move to first non-blank on line
3735 dot_begin();
3736 dot_skip_over_ws();
3737 break;
3738 case 'b': // b- back a word
3739 case 'e': // e- end of word
Eric Andersen3f980402001-04-04 17:31:15 +00003740 dir = FORWARD;
3741 if (c == 'b')
3742 dir = BACK;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003743 do {
3744 if ((dot + dir) < text || (dot + dir) > end - 1)
3745 break;
3746 dot += dir;
3747 if (isspace(*dot)) {
3748 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3749 }
3750 if (isalnum(*dot) || *dot == '_') {
3751 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3752 } else if (ispunct(*dot)) {
3753 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3754 }
3755 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003756 break;
3757 case 'c': // c- change something
3758 case 'd': // d- delete something
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003759#if ENABLE_FEATURE_VI_YANKMARK
Eric Andersen3f980402001-04-04 17:31:15 +00003760 case 'y': // y- yank something
3761 case 'Y': // Y- Yank a line
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003762#endif
Denis Vlasenkod44c1532008-10-14 12:59:42 +00003763 {
Paul Foxc51fc7b2008-03-06 01:34:23 +00003764 int yf, ml, whole = 0;
Eric Andersen3f980402001-04-04 17:31:15 +00003765 yf = YANKDEL; // assume either "c" or "d"
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003766#if ENABLE_FEATURE_VI_YANKMARK
Eric Andersen3f980402001-04-04 17:31:15 +00003767 if (c == 'y' || c == 'Y')
3768 yf = YANKONLY;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003769#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003770 c1 = 'y';
3771 if (c != 'Y')
3772 c1 = get_one_char(); // get the type of thing to delete
Paul Foxc51fc7b2008-03-06 01:34:23 +00003773 // determine range, and whether it spans lines
3774 ml = find_range(&p, &q, c1);
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003775 place_cursor(0, 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003776 if (c1 == 27) { // ESC- user changed mind and wants out
3777 c = c1 = 27; // Escape- do nothing
3778 } else if (strchr("wW", c1)) {
Ron Yorston4b494222019-04-28 09:09:33 +01003779 ml = 0; // multi-line ranges aren't allowed for words
Eric Andersen3f980402001-04-04 17:31:15 +00003780 if (c == 'c') {
3781 // don't include trailing WS as part of word
Ron Yorston4b494222019-04-28 09:09:33 +01003782 while (isspace(*q) && q > p) {
Eric Andersen3f980402001-04-04 17:31:15 +00003783 q--;
3784 }
3785 }
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003786 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
Paul Foxc51fc7b2008-03-06 01:34:23 +00003787 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3788 // partial 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 word
Ron Yorston7b93e312019-04-28 09:10:16 +01003790 } else if (strchr("cdykjGHL+-{}\r\n", c1)) {
Paul Foxc51fc7b2008-03-06 01:34:23 +00003791 // whole line copy text into a register and delete
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003792 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
Paul Foxc51fc7b2008-03-06 01:34:23 +00003793 whole = 1;
3794 } else {
3795 // could not recognize object
3796 c = c1 = 27; // error-
3797 ml = 0;
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003798 indicate_error();
Paul Foxc51fc7b2008-03-06 01:34:23 +00003799 }
3800 if (ml && whole) {
Eric Andersen1c0d3112001-04-16 15:46:44 +00003801 if (c == 'c') {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003802 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
Eric Andersen1c0d3112001-04-16 15:46:44 +00003803 // on the last line of file don't move to prev line
Paul Foxc51fc7b2008-03-06 01:34:23 +00003804 if (whole && dot != (end-1)) {
Eric Andersen1c0d3112001-04-16 15:46:44 +00003805 dot_prev();
3806 }
3807 } else if (c == 'd') {
Eric Andersen3f980402001-04-04 17:31:15 +00003808 dot_begin();
3809 dot_skip_over_ws();
3810 }
Eric Andersen3f980402001-04-04 17:31:15 +00003811 }
3812 if (c1 != 27) {
3813 // if CHANGING, not deleting, start inserting after the delete
3814 if (c == 'c') {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003815 strcpy(buf, "Change");
Eric Andersen3f980402001-04-04 17:31:15 +00003816 goto dc_i; // start inserting
3817 }
3818 if (c == 'd') {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003819 strcpy(buf, "Delete");
Eric Andersen3f980402001-04-04 17:31:15 +00003820 }
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003821#if ENABLE_FEATURE_VI_YANKMARK
Eric Andersen3f980402001-04-04 17:31:15 +00003822 if (c == 'y' || c == 'Y') {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003823 strcpy(buf, "Yank");
Eric Andersen3f980402001-04-04 17:31:15 +00003824 }
3825 p = reg[YDreg];
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003826 q = p + strlen(p);
Eric Andersen3f980402001-04-04 17:31:15 +00003827 for (cnt = 0; p <= q; p++) {
3828 if (*p == '\n')
3829 cnt++;
3830 }
Denys Vlasenkoeabf4b22019-03-29 14:40:01 +01003831 status_line("%s %u lines (%u chars) using [%c]",
3832 buf, cnt, (unsigned)strlen(reg[YDreg]), what_reg());
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003833#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003834 end_cmd_q(); // stop adding to q
3835 }
3836 break;
Denis Vlasenkod44c1532008-10-14 12:59:42 +00003837 }
Eric Andersen3f980402001-04-04 17:31:15 +00003838 case 'k': // k- goto prev line, same col
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003839 case KEYCODE_UP: // cursor key Up
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003840 do {
3841 dot_prev();
3842 dot = move_to_col(dot, ccol + offset); // try stay in same col
3843 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003844 break;
3845 case 'r': // r- replace the current char with user input
3846 c1 = get_one_char(); // get the replacement char
3847 if (*dot != '\n') {
Ron Yorstondf4e3af2019-02-03 14:01:58 +00003848 dot = text_hole_delete(dot, dot, ALLOW_UNDO);
3849 dot = char_insert(dot, c1, ALLOW_UNDO_CHAIN);
3850 dot_left();
Eric Andersen3f980402001-04-04 17:31:15 +00003851 }
3852 end_cmd_q(); // stop adding to q
3853 break;
Eric Andersen822c3832001-05-07 17:37:43 +00003854 case 't': // t- move to char prior to next x
Tim Rikerc1ef7bd2006-01-25 00:08:53 +00003855 last_forward_char = get_one_char();
3856 do_cmd(';');
3857 if (*dot == last_forward_char)
3858 dot_left();
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003859 last_forward_char = 0;
Eric Andersen822c3832001-05-07 17:37:43 +00003860 break;
Eric Andersen3f980402001-04-04 17:31:15 +00003861 case 'w': // w- forward a word
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003862 do {
3863 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3864 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3865 } else if (ispunct(*dot)) { // we are on PUNCT
3866 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3867 }
3868 if (dot < end - 1)
3869 dot++; // move over word
3870 if (isspace(*dot)) {
3871 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3872 }
3873 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003874 break;
3875 case 'z': // z-
3876 c1 = get_one_char(); // get the replacement char
3877 cnt = 0;
3878 if (c1 == '.')
3879 cnt = (rows - 2) / 2; // put dot at center
3880 if (c1 == '-')
3881 cnt = rows - 2; // put dot at bottom
3882 screenbegin = begin_line(dot); // start dot at top
3883 dot_scroll(cnt, -1);
3884 break;
3885 case '|': // |- move to column "cmdcnt"
3886 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3887 break;
3888 case '~': // ~- flip the case of letters a-z -> A-Z
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003889 do {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003890#if ENABLE_FEATURE_VI_UNDO
3891 if (islower(*dot)) {
3892 undo_push(dot, 1, UNDO_DEL);
3893 *dot = toupper(*dot);
3894 undo_push(dot, 1, UNDO_INS_CHAIN);
3895 } else if (isupper(*dot)) {
3896 undo_push(dot, 1, UNDO_DEL);
3897 *dot = tolower(*dot);
3898 undo_push(dot, 1, UNDO_INS_CHAIN);
3899 }
3900#else
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003901 if (islower(*dot)) {
3902 *dot = toupper(*dot);
Denys Vlasenkoe7430862014-04-03 12:47:48 +02003903 modified_count++;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003904 } else if (isupper(*dot)) {
3905 *dot = tolower(*dot);
Denys Vlasenkoe7430862014-04-03 12:47:48 +02003906 modified_count++;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003907 }
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003908#endif
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003909 dot_right();
3910 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003911 end_cmd_q(); // stop adding to q
3912 break;
3913 //----- The Cursor and Function Keys -----------------------------
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003914 case KEYCODE_HOME: // Cursor Key Home
Eric Andersen3f980402001-04-04 17:31:15 +00003915 dot_begin();
3916 break;
3917 // The Fn keys could point to do_macro which could translate them
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003918#if 0
3919 case KEYCODE_FUN1: // Function Key F1
3920 case KEYCODE_FUN2: // Function Key F2
3921 case KEYCODE_FUN3: // Function Key F3
3922 case KEYCODE_FUN4: // Function Key F4
3923 case KEYCODE_FUN5: // Function Key F5
3924 case KEYCODE_FUN6: // Function Key F6
3925 case KEYCODE_FUN7: // Function Key F7
3926 case KEYCODE_FUN8: // Function Key F8
3927 case KEYCODE_FUN9: // Function Key F9
3928 case KEYCODE_FUN10: // Function Key F10
3929 case KEYCODE_FUN11: // Function Key F11
3930 case KEYCODE_FUN12: // Function Key F12
Eric Andersen3f980402001-04-04 17:31:15 +00003931 break;
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003932#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003933 }
3934
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003935 dc1:
Eric Andersen3f980402001-04-04 17:31:15 +00003936 // if text[] just became empty, add back an empty line
3937 if (end == text) {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003938 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
Eric Andersen3f980402001-04-04 17:31:15 +00003939 dot = text;
3940 }
3941 // it is OK for dot to exactly equal to end, otherwise check dot validity
3942 if (dot != end) {
3943 dot = bound_dot(dot); // make sure "dot" is valid
3944 }
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003945#if ENABLE_FEATURE_VI_YANKMARK
Eric Andersen3f980402001-04-04 17:31:15 +00003946 check_context(c); // update the current context
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003947#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003948
3949 if (!isdigit(c))
3950 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3951 cnt = dot - begin_line(dot);
3952 // Try to stay off of the Newline
3953 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3954 dot--;
3955}
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003956
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02003957// NB! the CRASHME code is unmaintained, and doesn't currently build
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003958#if ENABLE_FEATURE_VI_CRASHME
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003959static int totalcmds = 0;
3960static int Mp = 85; // Movement command Probability
3961static int Np = 90; // Non-movement command Probability
3962static int Dp = 96; // Delete command Probability
3963static int Ip = 97; // Insert command Probability
3964static int Yp = 98; // Yank command Probability
3965static int Pp = 99; // Put command Probability
3966static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003967static const char chars[20] = "\t012345 abcdABCD-=.$";
3968static const char *const words[20] = {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003969 "this", "is", "a", "test",
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003970 "broadcast", "the", "emergency", "of",
3971 "system", "quick", "brown", "fox",
3972 "jumped", "over", "lazy", "dogs",
3973 "back", "January", "Febuary", "March"
3974};
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003975static const char *const lines[20] = {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003976 "You should have received a copy of the GNU General Public License\n",
3977 "char c, cm, *cmd, *cmd1;\n",
3978 "generate a command by percentages\n",
3979 "Numbers may be typed as a prefix to some commands.\n",
3980 "Quit, discarding changes!\n",
3981 "Forced write, if permission originally not valid.\n",
3982 "In general, any ex or ed command (such as substitute or delete).\n",
3983 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3984 "Please get w/ me and I will go over it with you.\n",
3985 "The following is a list of scheduled, committed changes.\n",
3986 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3987 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3988 "Any question about transactions please contact Sterling Huxley.\n",
3989 "I will try to get back to you by Friday, December 31.\n",
3990 "This Change will be implemented on Friday.\n",
3991 "Let me know if you have problems accessing this;\n",
3992 "Sterling Huxley recently added you to the access list.\n",
3993 "Would you like to go to lunch?\n",
3994 "The last command will be automatically run.\n",
3995 "This is too much english for a computer geek.\n",
3996};
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003997static char *multilines[20] = {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003998 "You should have received a copy of the GNU General Public License\n",
3999 "char c, cm, *cmd, *cmd1;\n",
4000 "generate a command by percentages\n",
4001 "Numbers may be typed as a prefix to some commands.\n",
4002 "Quit, discarding changes!\n",
4003 "Forced write, if permission originally not valid.\n",
4004 "In general, any ex or ed command (such as substitute or delete).\n",
4005 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4006 "Please get w/ me and I will go over it with you.\n",
4007 "The following is a list of scheduled, committed changes.\n",
4008 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4009 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4010 "Any question about transactions please contact Sterling Huxley.\n",
4011 "I will try to get back to you by Friday, December 31.\n",
4012 "This Change will be implemented on Friday.\n",
4013 "Let me know if you have problems accessing this;\n",
4014 "Sterling Huxley recently added you to the access list.\n",
4015 "Would you like to go to lunch?\n",
4016 "The last command will be automatically run.\n",
4017 "This is too much english for a computer geek.\n",
4018};
4019
4020// create a random command to execute
4021static void crash_dummy()
4022{
4023 static int sleeptime; // how long to pause between commands
4024 char c, cm, *cmd, *cmd1;
4025 int i, cnt, thing, rbi, startrbi, percent;
4026
4027 // "dot" movement commands
4028 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4029
4030 // is there already a command running?
Denys Vlasenko020f4062009-05-17 16:44:54 +02004031 if (readbuffer[0] > 0)
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004032 goto cd1;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004033 cd0:
Denys Vlasenko020f4062009-05-17 16:44:54 +02004034 readbuffer[0] = 'X';
4035 startrbi = rbi = 1;
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004036 sleeptime = 0; // how long to pause between commands
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004037 memset(readbuffer, '\0', sizeof(readbuffer));
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004038 // generate a command by percentages
4039 percent = (int) lrand48() % 100; // get a number from 0-99
4040 if (percent < Mp) { // Movement commands
4041 // available commands
4042 cmd = cmd1;
4043 M++;
4044 } else if (percent < Np) { // non-movement commands
4045 cmd = "mz<>\'\""; // available commands
4046 N++;
4047 } else if (percent < Dp) { // Delete commands
4048 cmd = "dx"; // available commands
4049 D++;
4050 } else if (percent < Ip) { // Inset commands
4051 cmd = "iIaAsrJ"; // available commands
4052 I++;
4053 } else if (percent < Yp) { // Yank commands
4054 cmd = "yY"; // available commands
4055 Y++;
4056 } else if (percent < Pp) { // Put commands
4057 cmd = "pP"; // available commands
4058 P++;
4059 } else {
4060 // We do not know how to handle this command, try again
4061 U++;
4062 goto cd0;
4063 }
4064 // randomly pick one of the available cmds from "cmd[]"
4065 i = (int) lrand48() % strlen(cmd);
4066 cm = cmd[i];
4067 if (strchr(":\024", cm))
4068 goto cd0; // dont allow colon or ctrl-T commands
4069 readbuffer[rbi++] = cm; // put cmd into input buffer
4070
4071 // now we have the command-
4072 // there are 1, 2, and multi char commands
4073 // find out which and generate the rest of command as necessary
4074 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4075 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4076 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4077 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4078 }
4079 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4080 c = cmd1[thing];
4081 readbuffer[rbi++] = c; // add movement to input buffer
4082 }
4083 if (strchr("iIaAsc", cm)) { // multi-char commands
4084 if (cm == 'c') {
4085 // change some thing
4086 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4087 c = cmd1[thing];
4088 readbuffer[rbi++] = c; // add movement to input buffer
4089 }
4090 thing = (int) lrand48() % 4; // what thing to insert
4091 cnt = (int) lrand48() % 10; // how many to insert
4092 for (i = 0; i < cnt; i++) {
4093 if (thing == 0) { // insert chars
4094 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4095 } else if (thing == 1) { // insert words
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004096 strcat(readbuffer, words[(int) lrand48() % 20]);
4097 strcat(readbuffer, " ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004098 sleeptime = 0; // how fast to type
4099 } else if (thing == 2) { // insert lines
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004100 strcat(readbuffer, lines[(int) lrand48() % 20]);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004101 sleeptime = 0; // how fast to type
4102 } else { // insert multi-lines
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004103 strcat(readbuffer, multilines[(int) lrand48() % 20]);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004104 sleeptime = 0; // how fast to type
4105 }
4106 }
Denys Vlasenko8187e012017-09-13 22:48:30 +02004107 strcat(readbuffer, ESC);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004108 }
Denys Vlasenko020f4062009-05-17 16:44:54 +02004109 readbuffer[0] = strlen(readbuffer + 1);
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004110 cd1:
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004111 totalcmds++;
4112 if (sleeptime > 0)
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004113 mysleep(sleeptime); // sleep 1/100 sec
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004114}
4115
4116// test to see if there are any errors
4117static void crash_test()
4118{
4119 static time_t oldtim;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004120
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004121 time_t tim;
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004122 char d[2], msg[80];
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004123
4124 msg[0] = '\0';
4125 if (end < text) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004126 strcat(msg, "end<text ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004127 }
4128 if (end > textend) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004129 strcat(msg, "end>textend ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004130 }
4131 if (dot < text) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004132 strcat(msg, "dot<text ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004133 }
4134 if (dot > end) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004135 strcat(msg, "dot>end ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004136 }
4137 if (screenbegin < text) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004138 strcat(msg, "screenbegin<text ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004139 }
4140 if (screenbegin > end - 1) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004141 strcat(msg, "screenbegin>end-1 ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004142 }
4143
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004144 if (msg[0]) {
Glenn L McGrath7127b582002-12-03 21:48:15 +00004145 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
Denys Vlasenko04b52892012-06-11 13:51:38 +02004146 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
Denys Vlasenko8131eea2009-11-02 14:19:51 +01004147 fflush_all();
Bernhard Reutner-Fischer5e25ddb2008-05-19 09:48:17 +00004148 while (safe_read(STDIN_FILENO, d, 1) > 0) {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004149 if (d[0] == '\n' || d[0] == '\r')
4150 break;
4151 }
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004152 }
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004153 tim = time(NULL);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004154 if (tim >= (oldtim + 3)) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004155 sprintf(status_buffer,
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004156 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4157 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4158 oldtim = tim;
4159 }
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004160}
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004161#endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004162
4163static void edit_file(char *fn)
4164{
4165#if ENABLE_FEATURE_VI_YANKMARK
4166#define cur_line edit_file__cur_line
4167#endif
4168 int c;
4169#if ENABLE_FEATURE_VI_USE_SIGNALS
4170 int sig;
4171#endif
4172
4173 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
4174 rawmode();
4175 rows = 24;
4176 columns = 80;
4177 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
4178#if ENABLE_FEATURE_VI_ASK_TERMINAL
4179 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
4180 uint64_t k;
4181 write1(ESC"[999;999H" ESC"[6n");
4182 fflush_all();
4183 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
4184 if ((int32_t)k == KEYCODE_CURSOR_POS) {
4185 uint32_t rc = (k >> 32);
4186 columns = (rc & 0x7fff);
4187 if (columns > MAX_SCR_COLS)
4188 columns = MAX_SCR_COLS;
4189 rows = ((rc >> 16) & 0x7fff);
4190 if (rows > MAX_SCR_ROWS)
4191 rows = MAX_SCR_ROWS;
4192 }
4193 }
4194#endif
4195 new_screen(rows, columns); // get memory for virtual screen
4196 init_text_buffer(fn);
4197
4198#if ENABLE_FEATURE_VI_YANKMARK
4199 YDreg = 26; // default Yank/Delete reg
4200// Ureg = 27; - const // hold orig line for "U" cmd
4201 mark[26] = mark[27] = text; // init "previous context"
4202#endif
4203
Denys Vlasenkob29dce42019-04-01 17:17:02 +02004204 last_forward_char = '\0';
4205#if ENABLE_FEATURE_VI_CRASHME
4206 last_input_char = '\0';
4207#endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004208 crow = 0;
4209 ccol = 0;
4210
4211#if ENABLE_FEATURE_VI_USE_SIGNALS
4212 signal(SIGWINCH, winch_handler);
4213 signal(SIGTSTP, tstp_handler);
4214 sig = sigsetjmp(restart, 1);
4215 if (sig != 0) {
4216 screenbegin = dot = text;
4217 }
4218 // int_handler() can jump to "restart",
4219 // must install handler *after* initializing "restart"
4220 signal(SIGINT, int_handler);
4221#endif
4222
4223 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
4224 cmdcnt = 0;
4225 tabstop = 8;
4226 offset = 0; // no horizontal offset
4227 c = '\0';
4228#if ENABLE_FEATURE_VI_DOT_CMD
4229 free(ioq_start);
Denys Vlasenko2a576082019-04-01 16:15:51 +02004230 ioq_start = NULL;
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004231 lmc_len = 0;
4232 adding2q = 0;
4233#endif
4234
4235#if ENABLE_FEATURE_VI_COLON
4236 {
4237 char *p, *q;
4238 int n = 0;
4239
4240 while ((p = initial_cmds[n]) != NULL) {
4241 do {
4242 q = p;
4243 p = strchr(q, '\n');
4244 if (p)
4245 while (*p == '\n')
4246 *p++ = '\0';
4247 if (*q)
4248 colon(q);
4249 } while (p);
4250 free(initial_cmds[n]);
4251 initial_cmds[n] = NULL;
4252 n++;
4253 }
4254 }
4255#endif
4256 redraw(FALSE); // dont force every col re-draw
4257 //------This is the main Vi cmd handling loop -----------------------
4258 while (editing > 0) {
4259#if ENABLE_FEATURE_VI_CRASHME
4260 if (crashme > 0) {
4261 if ((end - text) > 1) {
4262 crash_dummy(); // generate a random command
4263 } else {
4264 crashme = 0;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004265 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO);
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004266 dot = text;
4267 refresh(FALSE);
4268 }
4269 }
4270#endif
Denys Vlasenkob29dce42019-04-01 17:17:02 +02004271 c = get_one_char(); // get a cmd from user
4272#if ENABLE_FEATURE_VI_CRASHME
4273 last_input_char = c;
4274#endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004275#if ENABLE_FEATURE_VI_YANKMARK
4276 // save a copy of the current line- for the 'U" command
4277 if (begin_line(dot) != cur_line) {
4278 cur_line = begin_line(dot);
4279 text_yank(begin_line(dot), end_line(dot), Ureg);
4280 }
4281#endif
4282#if ENABLE_FEATURE_VI_DOT_CMD
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004283 // If c is a command that changes text[],
4284 // (re)start remembering the input for the "." command.
Denys Vlasenko2a576082019-04-01 16:15:51 +02004285 if (!adding2q
4286 && ioq_start == NULL
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004287 && cmd_mode == 0 // command mode
4288 && c > '\0' // exclude NUL and non-ASCII chars
4289 && c < 0x7f // (Unicode and such)
4290 && strchr(modifying_cmds, c)
4291 ) {
4292 start_new_cmd_q(c);
4293 }
4294#endif
4295 do_cmd(c); // execute the user command
4296
4297 // poll to see if there is input already waiting. if we are
4298 // not able to display output fast enough to keep up, skip
4299 // the display update until we catch up with input.
4300 if (!readbuffer[0] && mysleep(0) == 0) {
4301 // no input pending - so update output
4302 refresh(FALSE);
4303 show_status_line();
4304 }
4305#if ENABLE_FEATURE_VI_CRASHME
4306 if (crashme > 0)
4307 crash_test(); // test editor variables
4308#endif
4309 }
4310 //-------------------------------------------------------------------
4311
4312 go_bottom_and_clear_to_eol();
4313 cookmode();
4314#undef cur_line
4315}
4316
4317int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
4318int vi_main(int argc, char **argv)
4319{
4320 int c;
4321
4322 INIT_G();
4323
4324#if ENABLE_FEATURE_VI_UNDO
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004325 //undo_stack_tail = NULL; - already is
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02004326# if ENABLE_FEATURE_VI_UNDO_QUEUE
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004327 undo_queue_state = UNDO_EMPTY;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004328 //undo_q = 0; - already is
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02004329# endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004330#endif
4331
4332#if ENABLE_FEATURE_VI_CRASHME
4333 srand((long) getpid());
4334#endif
4335#ifdef NO_SUCH_APPLET_YET
4336 // if we aren't "vi", we are "view"
4337 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
4338 SET_READONLY_MODE(readonly_mode);
4339 }
4340#endif
4341
4342 // autoindent is not default in vim 7.3
4343 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
4344 // 1- process $HOME/.exrc file (not inplemented yet)
4345 // 2- process EXINIT variable from environment
4346 // 3- process command line args
4347#if ENABLE_FEATURE_VI_COLON
4348 {
4349 char *p = getenv("EXINIT");
4350 if (p && *p)
4351 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
4352 }
4353#endif
4354 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
4355 switch (c) {
4356#if ENABLE_FEATURE_VI_CRASHME
4357 case 'C':
4358 crashme = 1;
4359 break;
4360#endif
4361#if ENABLE_FEATURE_VI_READONLY
4362 case 'R': // Read-only flag
4363 SET_READONLY_MODE(readonly_mode);
4364 break;
4365#endif
4366#if ENABLE_FEATURE_VI_COLON
4367 case 'c': // cmd line vi command
4368 if (*optarg)
4369 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
4370 break;
4371#endif
4372 case 'H':
4373 show_help();
4374 // fall through
4375 default:
4376 bb_show_usage();
4377 return 1;
4378 }
4379 }
4380
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004381 argv += optind;
Denys Vlasenko89393592019-04-02 12:45:30 +02004382 cmdline_filecnt = argc - optind;
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004383
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004384 // "Save cursor, use alternate screen buffer, clear screen"
4385 write1(ESC"[?1049h");
Denys Vlasenko89393592019-04-02 12:45:30 +02004386 // This is the main file handling loop
4387 optind = 0;
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004388 while (1) {
Denys Vlasenko89393592019-04-02 12:45:30 +02004389 edit_file(argv[optind]); // might be NULL on 1st iteration
4390 // NB: optind can be changed by ":next" and ":rewind" commands
4391 optind++;
Denys Vlasenkoa3ce1612019-04-03 16:35:23 +02004392 if (optind >= cmdline_filecnt)
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004393 break;
4394 }
4395 // "Use normal screen buffer, restore cursor"
4396 write1(ESC"[?1049l");
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004397
4398 return 0;
4399}