blob: d37cd48a3ec3bac21d77d9aac89be27fe99e3931 [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:
Ron Yorstonf9217cd2021-08-19 17:00:17 +010010// ./.exrc
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +020011// add magic to search /foo.*bar
12// add :help command
13// :map macros
14// if mark[] values were line numbers rather than pointers
15// it would be easier to change the mark when add/delete lines
16// More intelligence in refresh()
17// ":r !cmd" and "!cmd" to filter text through an external command
18// An "ex" line oriented mode- maybe using "cmdedit"
19
Walter Harmsb9ba5802011-06-27 02:59:37 +020020//config:config VI
Denys Vlasenkob097a842018-12-28 03:20:17 +010021//config: bool "vi (23 kb)"
Walter Harmsb9ba5802011-06-27 02:59:37 +020022//config: default y
23//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020024//config: 'vi' is a text editor. More specifically, it is the One True
25//config: text editor <grin>. It does, however, have a rather steep
26//config: learning curve. If you are not already comfortable with 'vi'
27//config: you may wish to use something else.
Walter Harmsb9ba5802011-06-27 02:59:37 +020028//config:
29//config:config FEATURE_VI_MAX_LEN
Denys Vlasenkof5604222017-01-10 14:58:54 +010030//config: int "Maximum screen width"
Walter Harmsb9ba5802011-06-27 02:59:37 +020031//config: range 256 16384
32//config: default 4096
33//config: depends on VI
34//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020035//config: Contrary to what you may think, this is not eating much.
36//config: Make it smaller than 4k only if you are very limited on memory.
Walter Harmsb9ba5802011-06-27 02:59:37 +020037//config:
38//config:config FEATURE_VI_8BIT
Denys Vlasenkof5604222017-01-10 14:58:54 +010039//config: bool "Allow to display 8-bit chars (otherwise shows dots)"
Walter Harmsb9ba5802011-06-27 02:59:37 +020040//config: default n
41//config: depends on VI
42//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020043//config: If your terminal can display characters with high bit set,
44//config: you may want to enable this. Note: vi is not Unicode-capable.
45//config: If your terminal combines several 8-bit bytes into one character
46//config: (as in Unicode mode), this will not work properly.
Walter Harmsb9ba5802011-06-27 02:59:37 +020047//config:
48//config:config FEATURE_VI_COLON
49//config: bool "Enable \":\" colon commands (no \"ex\" mode)"
50//config: default y
51//config: depends on VI
52//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020053//config: Enable a limited set of colon commands. This does not
54//config: provide an "ex" mode.
Walter Harmsb9ba5802011-06-27 02:59:37 +020055//config:
Ron Yorstonacd30792021-04-25 11:55:42 +010056//config:config FEATURE_VI_COLON_EXPAND
57//config: bool "Expand \"%\" and \"#\" in colon commands"
58//config: default y
59//config: depends on FEATURE_VI_COLON
60//config: help
61//config: Expand the special characters \"%\" (current filename)
62//config: and \"#\" (alternate filename) in colon commands.
63//config:
Walter Harmsb9ba5802011-06-27 02:59:37 +020064//config:config FEATURE_VI_YANKMARK
65//config: bool "Enable yank/put commands and mark cmds"
66//config: default y
67//config: depends on VI
68//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020069//config: This enables you to use yank and put, as well as mark.
Walter Harmsb9ba5802011-06-27 02:59:37 +020070//config:
71//config:config FEATURE_VI_SEARCH
72//config: bool "Enable search and replace cmds"
73//config: default y
74//config: depends on VI
75//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020076//config: Select this if you wish to be able to do search and replace.
Walter Harmsb9ba5802011-06-27 02:59:37 +020077//config:
78//config:config FEATURE_VI_REGEX_SEARCH
79//config: bool "Enable regex in search and replace"
80//config: default n # Uses GNU regex, which may be unavailable. FIXME
81//config: depends on FEATURE_VI_SEARCH
82//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020083//config: Use extended regex search.
Walter Harmsb9ba5802011-06-27 02:59:37 +020084//config:
85//config:config FEATURE_VI_USE_SIGNALS
86//config: bool "Catch signals"
87//config: default y
88//config: depends on VI
89//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020090//config: Selecting this option will make vi signal aware. This will support
91//config: SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
Walter Harmsb9ba5802011-06-27 02:59:37 +020092//config:
93//config:config FEATURE_VI_DOT_CMD
94//config: bool "Remember previous cmd and \".\" cmd"
95//config: default y
96//config: depends on VI
97//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020098//config: Make vi remember the last command and be able to repeat it.
Walter Harmsb9ba5802011-06-27 02:59:37 +020099//config:
100//config:config FEATURE_VI_READONLY
101//config: bool "Enable -R option and \"view\" mode"
102//config: default y
103//config: depends on VI
104//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +0200105//config: Enable the read-only command line option, which allows the user to
106//config: open a file in read-only mode.
Walter Harmsb9ba5802011-06-27 02:59:37 +0200107//config:
108//config:config FEATURE_VI_SETOPTS
Denys Vlasenkof5604222017-01-10 14:58:54 +0100109//config: bool "Enable settable options, ai ic showmatch"
Walter Harmsb9ba5802011-06-27 02:59:37 +0200110//config: default y
111//config: depends on VI
112//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +0200113//config: Enable the editor to set some (ai, ic, showmatch) options.
Walter Harmsb9ba5802011-06-27 02:59:37 +0200114//config:
115//config:config FEATURE_VI_SET
Denys Vlasenkof5604222017-01-10 14:58:54 +0100116//config: bool "Support :set"
Walter Harmsb9ba5802011-06-27 02:59:37 +0200117//config: default y
118//config: depends on VI
Walter Harmsb9ba5802011-06-27 02:59:37 +0200119//config:
120//config:config FEATURE_VI_WIN_RESIZE
121//config: bool "Handle window resize"
122//config: default y
123//config: depends on VI
124//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +0200125//config: Behave nicely with terminals that get resized.
Walter Harmsb9ba5802011-06-27 02:59:37 +0200126//config:
127//config:config FEATURE_VI_ASK_TERMINAL
128//config: bool "Use 'tell me cursor position' ESC sequence to measure window"
129//config: default y
130//config: depends on VI
131//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +0200132//config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
133//config: this option makes vi perform a last-ditch effort to find it:
134//config: position cursor to 999,999 and ask terminal to report real
135//config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
136//config: This is not clean but helps a lot on serial lines and such.
Denys Vlasenkof5604222017-01-10 14:58:54 +0100137//config:
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200138//config:config FEATURE_VI_UNDO
Denys Vlasenkof5604222017-01-10 14:58:54 +0100139//config: bool "Support undo command \"u\""
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200140//config: default y
141//config: depends on VI
142//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +0200143//config: Support the 'u' command to undo insertion, deletion, and replacement
144//config: of text.
Denys Vlasenkof5604222017-01-10 14:58:54 +0100145//config:
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200146//config:config FEATURE_VI_UNDO_QUEUE
147//config: bool "Enable undo operation queuing"
148//config: default y
149//config: depends on FEATURE_VI_UNDO
150//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +0200151//config: The vi undo functions can use an intermediate queue to greatly lower
152//config: malloc() calls and overhead. When the maximum size of this queue is
153//config: reached, the contents of the queue are committed to the undo stack.
154//config: This increases the size of the undo code and allows some undo
155//config: operations (especially un-typing/backspacing) to be far more useful.
Denys Vlasenkof5604222017-01-10 14:58:54 +0100156//config:
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200157//config:config FEATURE_VI_UNDO_QUEUE_MAX
158//config: int "Maximum undo character queue size"
159//config: default 256
160//config: range 32 65536
161//config: depends on FEATURE_VI_UNDO_QUEUE
162//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +0200163//config: This option sets the number of bytes used at runtime for the queue.
164//config: Smaller values will create more undo objects and reduce the amount
165//config: of typed or backspaced characters that are grouped into one undo
166//config: operation; larger values increase the potential size of each undo
167//config: and will generally malloc() larger objects and less frequently.
168//config: Unless you want more (or less) frequent "undo points" while typing,
169//config: you should probably leave this unchanged.
Ron Yorstonf4a99082021-04-06 13:44:05 +0100170//config:
171//config:config FEATURE_VI_VERBOSE_STATUS
172//config: bool "Enable verbose status reporting"
173//config: default y
174//config: depends on VI
175//config: help
176//config: Enable more verbose reporting of the results of yank, change,
177//config: delete, undo and substitution commands.
Walter Harmsb9ba5802011-06-27 02:59:37 +0200178
179//applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
180
181//kbuild:lib-$(CONFIG_VI) += vi.o
182
Pere Orga6a3e01d2011-04-01 22:56:30 +0200183//usage:#define vi_trivial_usage
Denys Vlasenko6b6826f2021-06-13 01:08:48 +0200184//usage: IF_FEATURE_VI_COLON("[-c CMD] ")IF_FEATURE_VI_READONLY("[-R] ")"[-H] [FILE]..."
Pere Orga6a3e01d2011-04-01 22:56:30 +0200185//usage:#define vi_full_usage "\n\n"
186//usage: "Edit FILE\n"
Pere Orga6a3e01d2011-04-01 22:56:30 +0200187//usage: IF_FEATURE_VI_COLON(
Ron Yorstonf9217cd2021-08-19 17:00:17 +0100188//usage: "\n -c CMD Initial command to run ($EXINIT and ~/.exrc also available)"
Pere Orga6a3e01d2011-04-01 22:56:30 +0200189//usage: )
190//usage: IF_FEATURE_VI_READONLY(
191//usage: "\n -R Read-only"
192//usage: )
Denys Vlasenko605f2642012-06-11 01:53:33 +0200193//usage: "\n -H List available features"
Denys Vlasenko6b6826f2021-06-13 01:08:48 +0200194// note: non-standard, "vim -H" is Hebrew mode (bidi support)
Pere Orga6a3e01d2011-04-01 22:56:30 +0200195
Denis Vlasenkob6adbf12007-05-26 19:00:18 +0000196#include "libbb.h"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200197// Should be after libbb.h: on some systems regex.h needs sys/types.h:
Denys Vlasenko066f3992011-07-03 03:19:43 +0200198#if ENABLE_FEATURE_VI_REGEX_SEARCH
199# include <regex.h>
200#endif
Eric Andersen3f980402001-04-04 17:31:15 +0000201
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200202// the CRASHME code is unmaintained, and doesn't currently build
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +0000203#define ENABLE_FEATURE_VI_CRASHME 0
Ron Yorstonf07772f2021-08-19 16:59:36 +0100204#define IF_FEATURE_VI_CRASHME(...)
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +0000205
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000206
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +0000207#if ENABLE_LOCALE_SUPPORT
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000208
209#if ENABLE_FEATURE_VI_8BIT
Denys Vlasenkoc2704542009-11-20 19:14:19 +0100210//FIXME: this does not work properly for Unicode anyway
211# define Isprint(c) (isprint)(c)
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000212#else
Denys Vlasenkoc2704542009-11-20 19:14:19 +0100213# define Isprint(c) isprint_asciionly(c)
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000214#endif
215
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000216#else
217
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200218// 0x9b is Meta-ESC
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000219#if ENABLE_FEATURE_VI_8BIT
Denys Vlasenko066f3992011-07-03 03:19:43 +0200220# define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000221#else
Denys Vlasenko066f3992011-07-03 03:19:43 +0200222# define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000223#endif
224
225#endif
226
227
Denis Vlasenkoe8a07882007-06-10 15:08:44 +0000228enum {
Denis Vlasenko26b6fba2007-12-21 21:34:37 +0000229 MAX_TABSTOP = 32, // sanity limit
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000230 // User input len. Need not be extra big.
231 // Lines in file being edited *can* be bigger than this.
232 MAX_INPUT_LEN = 128,
233 // Sanity limits. We have only one buffer of this size.
234 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
235 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
Denis Vlasenkoe8a07882007-06-10 15:08:44 +0000236};
Eric Andersen3f980402001-04-04 17:31:15 +0000237
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200238// VT102 ESC sequences.
239// See "Xterm Control Sequences"
240// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
Denys Vlasenko8187e012017-09-13 22:48:30 +0200241#define ESC "\033"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200242// Inverse/Normal text
Denys Vlasenko8187e012017-09-13 22:48:30 +0200243#define ESC_BOLD_TEXT ESC"[7m"
244#define ESC_NORM_TEXT ESC"[m"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200245// Bell
Denys Vlasenko04b52892012-06-11 13:51:38 +0200246#define ESC_BELL "\007"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200247// Clear-to-end-of-line
Denys Vlasenko8187e012017-09-13 22:48:30 +0200248#define ESC_CLEAR2EOL ESC"[K"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200249// Clear-to-end-of-screen.
250// (We use default param here.
251// Full sequence is "ESC [ <num> J",
252// <num> is 0/1/2 = "erase below/above/all".)
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +0200253#define ESC_CLEAR2EOS ESC"[J"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200254// Cursor to given coordinate (1,1: top left)
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +0200255#define ESC_SET_CURSOR_POS ESC"[%u;%uH"
256#define ESC_SET_CURSOR_TOPLEFT ESC"[H"
Denys Vlasenko04b52892012-06-11 13:51:38 +0200257//UNUSED
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200258//// Cursor up and down
Denys Vlasenko8187e012017-09-13 22:48:30 +0200259//#define ESC_CURSOR_UP ESC"[A"
Denys Vlasenko04b52892012-06-11 13:51:38 +0200260//#define ESC_CURSOR_DOWN "\n"
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000261
Ron Yorston3b9233f2021-04-25 11:52:25 +0100262#if ENABLE_FEATURE_VI_DOT_CMD
Denis Vlasenkoded6ad32008-10-14 12:26:30 +0000263// cmds modifying text[]
Denys Vlasenkobbacd032019-04-02 11:50:25 +0200264static const char modifying_cmds[] ALIGN1 = "aAcCdDiIJoOpPrRs""xX<>~";
Denis Vlasenkoded6ad32008-10-14 12:26:30 +0000265#endif
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000266
Rob Landleybc68cd12006-03-10 19:22:06 +0000267enum {
268 YANKONLY = FALSE,
269 YANKDEL = TRUE,
270 FORWARD = 1, // code depends on "1" for array index
271 BACK = -1, // code depends on "-1" for array index
Denys Vlasenko836d0a72018-11-29 14:19:57 +0100272 LIMITED = 0, // char_search() only current line
273 FULL = 1, // char_search() to the end/beginning of entire text
Ron Yorston25d25922021-03-25 14:23:36 +0000274 PARTIAL = 0, // buffer contains partial line
275 WHOLE = 1, // buffer contains whole lines
276 MULTI = 2, // buffer may include newlines
Eric Andersen3f980402001-04-04 17:31:15 +0000277
Rob Landleybc68cd12006-03-10 19:22:06 +0000278 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
279 S_TO_WS = 2, // used in skip_thing() for moving "dot"
280 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
281 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
Denis Vlasenko8e858e22007-03-07 09:35:43 +0000282 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
Ron Yorston50a2db72021-03-28 13:20:01 +0100283
284 C_END = -1, // cursor is at end of line due to '$' command
Rob Landleybc68cd12006-03-10 19:22:06 +0000285};
Eric Andersen3f980402001-04-04 17:31:15 +0000286
Denis Vlasenkob1759462008-06-20 20:20:54 +0000287
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200288// vi.c expects chars to be unsigned.
289// busybox build system provides that, but it's better
290// to audit and fix the source
Eric Andersen3f980402001-04-04 17:31:15 +0000291
Denis Vlasenkob1759462008-06-20 20:20:54 +0000292struct globals {
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200293 // many references - keep near the top of globals
Denis Vlasenkob1759462008-06-20 20:20:54 +0000294 char *text, *end; // pointers to the user data in memory
295 char *dot; // where all the action takes place
296 int text_size; // size of the allocated buffer
297
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200298 // the rest
Ron Yorston47c3eaa2021-04-15 12:04:22 +0100299#if ENABLE_FEATURE_VI_SETOPTS
Ron Yorston9f017d92021-04-06 22:11:21 +0100300 smallint vi_setops; // set by setops()
301#define VI_AUTOINDENT (1 << 0)
Ron Yorston310ef232021-04-17 09:25:11 +0100302#define VI_EXPANDTAB (1 << 1)
303#define VI_ERR_METHOD (1 << 2)
304#define VI_IGNORECASE (1 << 3)
305#define VI_SHOWMATCH (1 << 4)
306#define VI_TABSTOP (1 << 5)
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000307#define autoindent (vi_setops & VI_AUTOINDENT)
Ron Yorston310ef232021-04-17 09:25:11 +0100308#define expandtab (vi_setops & VI_EXPANDTAB )
Ron Yorston9f017d92021-04-06 22:11:21 +0100309#define err_method (vi_setops & VI_ERR_METHOD) // indicate error with beep or flash
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000310#define ignorecase (vi_setops & VI_IGNORECASE)
Ron Yorston9f017d92021-04-06 22:11:21 +0100311#define showmatch (vi_setops & VI_SHOWMATCH )
312// order of constants and strings must match
313#define OPTS_STR \
314 "ai\0""autoindent\0" \
Ron Yorston310ef232021-04-17 09:25:11 +0100315 "et\0""expandtab\0" \
Ron Yorston9f017d92021-04-06 22:11:21 +0100316 "fl\0""flash\0" \
317 "ic\0""ignorecase\0" \
318 "sm\0""showmatch\0" \
319 "ts\0""tabstop\0"
Ron Yorston47c3eaa2021-04-15 12:04:22 +0100320#else
321#define autoindent (0)
Ron Yorston310ef232021-04-17 09:25:11 +0100322#define expandtab (0)
Ron Yorston47c3eaa2021-04-15 12:04:22 +0100323#define err_method (0)
Denys Vlasenkod6a72032021-08-16 01:31:32 +0200324#define ignorecase (0)
Ron Yorston47c3eaa2021-04-15 12:04:22 +0100325#endif
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000326
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000327#if ENABLE_FEATURE_VI_READONLY
Denis Vlasenkob1759462008-06-20 20:20:54 +0000328 smallint readonly_mode;
Denis Vlasenko6a2f7f42007-08-16 10:35:17 +0000329#define SET_READONLY_FILE(flags) ((flags) |= 0x01)
330#define SET_READONLY_MODE(flags) ((flags) |= 0x02)
331#define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
Denis Vlasenkoeaabf062007-07-17 23:14:07 +0000332#else
Denis Vlasenkob1759462008-06-20 20:20:54 +0000333#define SET_READONLY_FILE(flags) ((void)0)
334#define SET_READONLY_MODE(flags) ((void)0)
335#define UNSET_READONLY_FILE(flags) ((void)0)
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000336#endif
Denis Vlasenkoeaabf062007-07-17 23:14:07 +0000337
Denis Vlasenkob1759462008-06-20 20:20:54 +0000338 smallint editing; // >0 while we are editing a file
Denis Vlasenko30cfdf92008-09-21 15:29:29 +0000339 // [code audit says "can be 0, 1 or 2 only"]
Denis Vlasenkob1759462008-06-20 20:20:54 +0000340 smallint cmd_mode; // 0=command 1=insert 2=replace
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200341 int modified_count; // buffer contents changed if !0
342 int last_modified_count; // = -1;
Denys Vlasenko89393592019-04-02 12:45:30 +0200343 int cmdline_filecnt; // how many file names on cmd line
Denis Vlasenkob1759462008-06-20 20:20:54 +0000344 int cmdcnt; // repetition count
345 unsigned rows, columns; // the terminal screen is this size
Denys Vlasenkoc175c462010-04-18 22:09:30 -0700346#if ENABLE_FEATURE_VI_ASK_TERMINAL
347 int get_rowcol_error;
348#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000349 int crow, ccol; // cursor is on Crow x Ccol
350 int offset; // chars scrolled off the screen to the left
351 int have_status_msg; // is default edit status needed?
352 // [don't make smallint!]
353 int last_status_cksum; // hash of current status line
354 char *current_filename;
Ron Yorstonacd30792021-04-25 11:55:42 +0100355#if ENABLE_FEATURE_VI_COLON_EXPAND
356 char *alt_filename;
357#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000358 char *screenbegin; // index into text[], of top line on the screen
359 char *screen; // pointer to the virtual screen buffer
360 int screensize; // and its size
361 int tabstop;
Ron Yorston15f4ac32021-03-28 17:15:30 +0100362 int last_search_char; // last char searched for (int because of Unicode)
363 smallint last_search_cmd; // command used to invoke last char search
Denys Vlasenkob29dce42019-04-01 17:17:02 +0200364#if ENABLE_FEATURE_VI_CRASHME
Denis Vlasenkob1759462008-06-20 20:20:54 +0000365 char last_input_char; // last char read from user
Denys Vlasenkob29dce42019-04-01 17:17:02 +0200366#endif
Denys Vlasenkob65e7f62021-04-15 23:17:01 +0200367#if ENABLE_FEATURE_VI_UNDO_QUEUE
368 char undo_queue_state; // One of UNDO_INS, UNDO_DEL, UNDO_EMPTY
369#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000370
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000371#if ENABLE_FEATURE_VI_DOT_CMD
Denis Vlasenkob1759462008-06-20 20:20:54 +0000372 smallint adding2q; // are we currently adding user input to q
373 int lmc_len; // length of last_modifying_cmd
374 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
Ron Yorstona5445022021-04-06 16:48:07 +0100375 int dotcnt; // number of times to repeat '.' command
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000376#endif
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +0000377#if ENABLE_FEATURE_VI_SEARCH
Denis Vlasenkob1759462008-06-20 20:20:54 +0000378 char *last_search_pattern; // last pattern from a '/' or '?' search
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +0000379#endif
Ron Yorston9659a8d2021-05-20 08:27:48 +0100380#if ENABLE_FEATURE_VI_SETOPTS
381 int indentcol; // column of recently autoindent, 0 or -1
382#endif
Ron Yorston038d4002021-06-16 14:47:00 +0100383 smallint cmd_error;
Denis Vlasenko0112ff52008-10-25 23:23:00 +0000384
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200385 // former statics
Denis Vlasenkob1759462008-06-20 20:20:54 +0000386#if ENABLE_FEATURE_VI_YANKMARK
387 char *edit_file__cur_line;
388#endif
389 int refresh__old_offset;
390 int format_edit_status__tot;
Eric Andersen3f980402001-04-04 17:31:15 +0000391
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200392 // a few references only
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000393#if ENABLE_FEATURE_VI_YANKMARK
Denys Vlasenkoeabf4b22019-03-29 14:40:01 +0100394 smalluint YDreg;//,Ureg;// default delete register and orig line for "U"
395#define Ureg 27
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000396 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
Ron Yorston25d25922021-03-25 14:23:36 +0000397 char regtype[28]; // buffer type: WHOLE, MULTI or PARTIAL
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000398 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000399#endif
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000400#if ENABLE_FEATURE_VI_USE_SIGNALS
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +0200401 sigjmp_buf restart; // int_handler() jumps to location remembered here
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000402#endif
Denys Vlasenko01ccdd12017-01-11 16:17:59 +0100403 struct termios term_orig; // remember what the cooked mode was
Ron Yorston50a2db72021-03-28 13:20:01 +0100404 int cindex; // saved character index for up/down motion
405 smallint keep_index; // retain saved character index
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000406#if ENABLE_FEATURE_VI_COLON
Ron Yorstonf07772f2021-08-19 16:59:36 +0100407 llist_t *initial_cmds;
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000408#endif
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000409 // Should be just enough to hold a key sequence,
Denis Vlasenko25497c12008-10-14 10:25:05 +0000410 // but CRASHME mode uses it as generated command buffer too
411#if ENABLE_FEATURE_VI_CRASHME
Denis Vlasenko1dfeeeb2008-10-18 19:04:37 +0000412 char readbuffer[128];
Denis Vlasenko25497c12008-10-14 10:25:05 +0000413#else
Denis Vlasenko5f6aaf32008-10-25 23:27:29 +0000414 char readbuffer[KEYCODE_BUFFER_SIZE];
Denis Vlasenko25497c12008-10-14 10:25:05 +0000415#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000416#define STATUS_BUFFER_LEN 200
417 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
418#if ENABLE_FEATURE_VI_DOT_CMD
419 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
420#endif
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200421 char get_input_line__buf[MAX_INPUT_LEN]; // former static
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000422
423 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200424
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200425#if ENABLE_FEATURE_VI_UNDO
426// undo_push() operations
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200427#define UNDO_INS 0
428#define UNDO_DEL 1
429#define UNDO_INS_CHAIN 2
430#define UNDO_DEL_CHAIN 3
Ron Yorston9b2a3892021-04-15 12:00:50 +0100431# if ENABLE_FEATURE_VI_UNDO_QUEUE
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200432#define UNDO_INS_QUEUED 4
433#define UNDO_DEL_QUEUED 5
Ron Yorston9b2a3892021-04-15 12:00:50 +0100434# endif
435
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200436// Pass-through flags for functions that can be undone
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200437#define NO_UNDO 0
438#define ALLOW_UNDO 1
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200439#define ALLOW_UNDO_CHAIN 2
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200440# if ENABLE_FEATURE_VI_UNDO_QUEUE
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200441#define ALLOW_UNDO_QUEUED 3
Ron Yorston9b2a3892021-04-15 12:00:50 +0100442# else
443// If undo queuing disabled, don't invoke the missing queue logic
444#define ALLOW_UNDO_QUEUED ALLOW_UNDO
445# endif
446
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200447 struct undo_object {
448 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200449 int start; // Offset where the data should be restored/deleted
450 int length; // total data size
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200451 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
452 char undo_text[1]; // text that was deleted (if deletion)
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200453 } *undo_stack_tail;
Denys Vlasenkob65e7f62021-04-15 23:17:01 +0200454# if ENABLE_FEATURE_VI_UNDO_QUEUE
455#define UNDO_USE_SPOS 32
456#define UNDO_EMPTY 64
457 char *undo_queue_spos; // Start position of queued operation
458 int undo_q;
459 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
460# endif
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200461#endif /* ENABLE_FEATURE_VI_UNDO */
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000462};
463#define G (*ptr_to_globals)
464#define text (G.text )
Denis Vlasenkoeaabf062007-07-17 23:14:07 +0000465#define text_size (G.text_size )
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000466#define end (G.end )
467#define dot (G.dot )
468#define reg (G.reg )
Denis Vlasenkob1759462008-06-20 20:20:54 +0000469
470#define vi_setops (G.vi_setops )
471#define editing (G.editing )
472#define cmd_mode (G.cmd_mode )
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200473#define modified_count (G.modified_count )
474#define last_modified_count (G.last_modified_count)
Denys Vlasenko89393592019-04-02 12:45:30 +0200475#define cmdline_filecnt (G.cmdline_filecnt )
Denis Vlasenkob1759462008-06-20 20:20:54 +0000476#define cmdcnt (G.cmdcnt )
477#define rows (G.rows )
478#define columns (G.columns )
479#define crow (G.crow )
480#define ccol (G.ccol )
481#define offset (G.offset )
482#define status_buffer (G.status_buffer )
483#define have_status_msg (G.have_status_msg )
484#define last_status_cksum (G.last_status_cksum )
485#define current_filename (G.current_filename )
Ron Yorstonacd30792021-04-25 11:55:42 +0100486#define alt_filename (G.alt_filename )
Denis Vlasenkob1759462008-06-20 20:20:54 +0000487#define screen (G.screen )
488#define screensize (G.screensize )
489#define screenbegin (G.screenbegin )
490#define tabstop (G.tabstop )
Ron Yorston15f4ac32021-03-28 17:15:30 +0100491#define last_search_char (G.last_search_char )
492#define last_search_cmd (G.last_search_cmd )
Denys Vlasenkob29dce42019-04-01 17:17:02 +0200493#if ENABLE_FEATURE_VI_CRASHME
Denis Vlasenkob1759462008-06-20 20:20:54 +0000494#define last_input_char (G.last_input_char )
Denys Vlasenkob29dce42019-04-01 17:17:02 +0200495#endif
Denis Vlasenko2a210e52008-06-22 13:20:42 +0000496#if ENABLE_FEATURE_VI_READONLY
Denis Vlasenkob1759462008-06-20 20:20:54 +0000497#define readonly_mode (G.readonly_mode )
Denis Vlasenko2a210e52008-06-22 13:20:42 +0000498#else
499#define readonly_mode 0
500#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000501#define adding2q (G.adding2q )
502#define lmc_len (G.lmc_len )
503#define ioq (G.ioq )
504#define ioq_start (G.ioq_start )
Ron Yorstona5445022021-04-06 16:48:07 +0100505#define dotcnt (G.dotcnt )
Denis Vlasenkob1759462008-06-20 20:20:54 +0000506#define last_search_pattern (G.last_search_pattern)
Ron Yorston9659a8d2021-05-20 08:27:48 +0100507#define indentcol (G.indentcol )
Ron Yorston038d4002021-06-16 14:47:00 +0100508#define cmd_error (G.cmd_error )
Denis Vlasenko2a210e52008-06-22 13:20:42 +0000509
Denis Vlasenkob1759462008-06-20 20:20:54 +0000510#define edit_file__cur_line (G.edit_file__cur_line)
511#define refresh__old_offset (G.refresh__old_offset)
512#define format_edit_status__tot (G.format_edit_status__tot)
513
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000514#define YDreg (G.YDreg )
Denys Vlasenkoeabf4b22019-03-29 14:40:01 +0100515//#define Ureg (G.Ureg )
Ron Yorston25d25922021-03-25 14:23:36 +0000516#define regtype (G.regtype )
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000517#define mark (G.mark )
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000518#define restart (G.restart )
519#define term_orig (G.term_orig )
Ron Yorston50a2db72021-03-28 13:20:01 +0100520#define cindex (G.cindex )
521#define keep_index (G.keep_index )
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000522#define initial_cmds (G.initial_cmds )
Denis Vlasenkoa96425f2007-12-09 04:13:43 +0000523#define readbuffer (G.readbuffer )
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000524#define scr_out_buf (G.scr_out_buf )
Denis Vlasenkob1759462008-06-20 20:20:54 +0000525#define last_modifying_cmd (G.last_modifying_cmd )
526#define get_input_line__buf (G.get_input_line__buf)
527
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200528#if ENABLE_FEATURE_VI_UNDO
529#define undo_stack_tail (G.undo_stack_tail )
530# if ENABLE_FEATURE_VI_UNDO_QUEUE
531#define undo_queue_state (G.undo_queue_state)
532#define undo_q (G.undo_q )
533#define undo_queue (G.undo_queue )
534#define undo_queue_spos (G.undo_queue_spos )
535# endif
536#endif
537
Denis Vlasenkoa96425f2007-12-09 04:13:43 +0000538#define INIT_G() do { \
Denis Vlasenko574f2f42008-02-27 18:41:59 +0000539 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200540 last_modified_count = -1; \
Denis Vlasenko31d58e52008-10-29 13:16:28 +0000541 /* "" but has space for 2 chars: */ \
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000542 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
Ron Yorston52c4b7a2021-06-11 13:22:25 +0100543 tabstop = 8; \
Denis Vlasenkoa96425f2007-12-09 04:13:43 +0000544} while (0)
Eric Andersen3f980402001-04-04 17:31:15 +0000545
Denys Vlasenko6ce60b92019-04-01 15:41:05 +0200546#if ENABLE_FEATURE_VI_CRASHME
547static int crashme = 0;
548#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000549
Eric Andersen3f980402001-04-04 17:31:15 +0000550static void show_status_line(void); // put a message on the bottom line
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000551static void status_line_bold(const char *, ...);
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200552
Denys Vlasenko24bd3502019-04-01 13:55:27 +0200553static void show_help(void)
554{
555 puts("These features are available:"
556#if ENABLE_FEATURE_VI_SEARCH
557 "\n\tPattern searches with / and ?"
558#endif
559#if ENABLE_FEATURE_VI_DOT_CMD
560 "\n\tLast command repeat with ."
561#endif
562#if ENABLE_FEATURE_VI_YANKMARK
563 "\n\tLine marking with 'x"
564 "\n\tNamed buffers with \"x"
565#endif
566#if ENABLE_FEATURE_VI_READONLY
567 //not implemented: "\n\tReadonly if vi is called as \"view\""
568 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
569#endif
570#if ENABLE_FEATURE_VI_SET
571 "\n\tSome colon mode commands with :"
572#endif
573#if ENABLE_FEATURE_VI_SETOPTS
574 "\n\tSettable options with \":set\""
575#endif
576#if ENABLE_FEATURE_VI_USE_SIGNALS
577 "\n\tSignal catching- ^C"
578 "\n\tJob suspend and resume with ^Z"
579#endif
580#if ENABLE_FEATURE_VI_WIN_RESIZE
581 "\n\tAdapt to window re-sizes"
582#endif
583 );
584}
585
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000586static void write1(const char *out)
587{
Ron Yorstoncad3fc72021-02-03 20:47:14 +0100588 fputs_stdout(out);
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000589}
590
Denys Vlasenko2bb651a2010-04-16 20:55:52 -0700591#if ENABLE_FEATURE_VI_WIN_RESIZE
Denys Vlasenkoc5fb0ad2010-07-21 12:39:42 +0200592static int query_screen_dimensions(void)
Denys Vlasenko2bb651a2010-04-16 20:55:52 -0700593{
Denys Vlasenkoc5fb0ad2010-07-21 12:39:42 +0200594 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
Denys Vlasenko2bb651a2010-04-16 20:55:52 -0700595 if (rows > MAX_SCR_ROWS)
596 rows = MAX_SCR_ROWS;
597 if (columns > MAX_SCR_COLS)
598 columns = MAX_SCR_COLS;
Denys Vlasenkoc5fb0ad2010-07-21 12:39:42 +0200599 return err;
Denys Vlasenko2bb651a2010-04-16 20:55:52 -0700600}
601#else
Denys Vlasenko39043ad2018-06-27 14:46:08 +0200602static ALWAYS_INLINE int query_screen_dimensions(void)
603{
604 return 0;
605}
Denys Vlasenko2bb651a2010-04-16 20:55:52 -0700606#endif
607
Denys Vlasenkode697752019-04-01 14:08:00 +0200608// sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
609static int mysleep(int hund)
610{
611 struct pollfd pfd[1];
612
613 if (hund != 0)
614 fflush_all();
615
616 pfd[0].fd = STDIN_FILENO;
617 pfd[0].events = POLLIN;
618 return safe_poll(pfd, 1, hund*10) > 0;
619}
620
621//----- Set terminal attributes --------------------------------
622static void rawmode(void)
623{
624 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
625 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
Denys Vlasenkode697752019-04-01 14:08:00 +0200626}
627
628static void cookmode(void)
629{
630 fflush_all();
631 tcsetattr_stdin_TCSANOW(&term_orig);
632}
633
634//----- Terminal Drawing ---------------------------------------
635// The terminal is made up of 'rows' line of 'columns' columns.
636// classically this would be 24 x 80.
637// screen coordinates
638// 0,0 ... 0,79
639// 1,0 ... 1,79
640// . ... .
641// . ... .
642// 22,0 ... 22,79
643// 23,0 ... 23,79 <- status line
644
645//----- Move the cursor to row x col (count from 0, not 1) -------
646static void place_cursor(int row, int col)
647{
648 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
649
650 if (row < 0) row = 0;
651 if (row >= rows) row = rows - 1;
652 if (col < 0) col = 0;
653 if (col >= columns) col = columns - 1;
654
655 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
656 write1(cm1);
657}
658
659//----- Erase from cursor to end of line -----------------------
660static void clear_to_eol(void)
661{
662 write1(ESC_CLEAR2EOL);
663}
664
665static void go_bottom_and_clear_to_eol(void)
666{
667 place_cursor(rows - 1, 0);
668 clear_to_eol();
669}
670
671//----- Start standout mode ------------------------------------
672static void standout_start(void)
673{
674 write1(ESC_BOLD_TEXT);
675}
676
677//----- End standout mode --------------------------------------
678static void standout_end(void)
679{
680 write1(ESC_NORM_TEXT);
681}
682
683//----- Text Movement Routines ---------------------------------
684static char *begin_line(char *p) // return pointer to first char cur line
685{
686 if (p > text) {
687 p = memrchr(text, '\n', p - text);
688 if (!p)
689 return text;
690 return p + 1;
691 }
692 return p;
693}
694
695static char *end_line(char *p) // return pointer to NL of cur line
696{
697 if (p < end - 1) {
698 p = memchr(p, '\n', end - p - 1);
699 if (!p)
700 return end - 1;
701 }
702 return p;
703}
704
705static char *dollar_line(char *p) // return pointer to just before NL line
706{
707 p = end_line(p);
708 // Try to stay off of the Newline
709 if (*p == '\n' && (p - begin_line(p)) > 0)
710 p--;
711 return p;
712}
713
714static char *prev_line(char *p) // return pointer first char prev line
715{
716 p = begin_line(p); // goto beginning of cur line
717 if (p > text && p[-1] == '\n')
718 p--; // step to prev line
719 p = begin_line(p); // goto beginning of prev line
720 return p;
721}
722
723static char *next_line(char *p) // return pointer first char next line
724{
725 p = end_line(p);
726 if (p < end - 1 && *p == '\n')
727 p++; // step to next line
728 return p;
729}
730
731//----- Text Information Routines ------------------------------
732static char *end_screen(void)
733{
734 char *q;
735 int cnt;
736
737 // find new bottom line
738 q = screenbegin;
739 for (cnt = 0; cnt < rows - 2; cnt++)
740 q = next_line(q);
741 q = end_line(q);
742 return q;
743}
744
745// count line from start to stop
746static int count_lines(char *start, char *stop)
747{
748 char *q;
749 int cnt;
750
751 if (stop < start) { // start and stop are backwards- reverse them
752 q = start;
753 start = stop;
754 stop = q;
755 }
756 cnt = 0;
757 stop = end_line(stop);
758 while (start <= stop && start <= end - 1) {
759 start = end_line(start);
760 if (*start == '\n')
761 cnt++;
762 start++;
763 }
764 return cnt;
765}
766
767static char *find_line(int li) // find beginning of line #li
768{
769 char *q;
770
771 for (q = text; li > 1; li--) {
772 q = next_line(q);
773 }
774 return q;
775}
776
777static int next_tabstop(int col)
778{
779 return col + ((tabstop - 1) - (col % tabstop));
780}
781
Ron Yorstonf277c9e2021-04-17 09:25:47 +0100782static int prev_tabstop(int col)
783{
784 return col - ((col % tabstop) ?: tabstop);
785}
786
Ron Yorston310ef232021-04-17 09:25:11 +0100787static int next_column(char c, int co)
788{
789 if (c == '\t')
790 co = next_tabstop(co);
791 else if ((unsigned char)c < ' ' || c == 0x7f)
792 co++; // display as ^X, use 2 columns
793 return co + 1;
794}
795
Ron Yorston310ef232021-04-17 09:25:11 +0100796static int get_column(char *p)
797{
798 const char *r;
799 int co = 0;
800
801 for (r = begin_line(p); r < p; r++)
802 co = next_column(*r, co);
803 return co;
804}
Ron Yorston310ef232021-04-17 09:25:11 +0100805
Denys Vlasenkode697752019-04-01 14:08:00 +0200806//----- Erase the Screen[] memory ------------------------------
807static void screen_erase(void)
808{
809 memset(screen, ' ', screensize); // clear new screen
810}
811
Denys Vlasenkoe1a1b642019-04-03 16:30:50 +0200812static void new_screen(int ro, int co)
Ron Yorston3e61b592019-04-03 08:56:30 +0100813{
Denys Vlasenkoe1a1b642019-04-03 16:30:50 +0200814 char *s;
Ron Yorston3e61b592019-04-03 08:56:30 +0100815
816 free(screen);
817 screensize = ro * co + 8;
Denys Vlasenkoe1a1b642019-04-03 16:30:50 +0200818 s = screen = xmalloc(screensize);
Ron Yorston3e61b592019-04-03 08:56:30 +0100819 // initialize the new screen. assume this will be a empty file.
820 screen_erase();
Denys Vlasenkoe1a1b642019-04-03 16:30:50 +0200821 // non-existent text[] lines start with a tilde (~).
822 //screen[(1 * co) + 0] = '~';
823 //screen[(2 * co) + 0] = '~';
824 //..
825 //screen[((ro-2) * co) + 0] = '~';
826 ro -= 2;
827 while (--ro >= 0) {
828 s += co;
829 *s = '~';
Ron Yorston3e61b592019-04-03 08:56:30 +0100830 }
Ron Yorston3e61b592019-04-03 08:56:30 +0100831}
832
Denys Vlasenkode697752019-04-01 14:08:00 +0200833//----- Synchronize the cursor to Dot --------------------------
Ron Yorstona25b4c22021-03-28 13:23:51 +0100834static void sync_cursor(char *d, int *row, int *col)
Denys Vlasenkode697752019-04-01 14:08:00 +0200835{
836 char *beg_cur; // begin and end of "d" line
837 char *tp;
838 int cnt, ro, co;
839
840 beg_cur = begin_line(d); // first char of cur line
841
842 if (beg_cur < screenbegin) {
843 // "d" is before top line on screen
844 // how many lines do we have to move
845 cnt = count_lines(beg_cur, screenbegin);
846 sc1:
847 screenbegin = beg_cur;
848 if (cnt > (rows - 1) / 2) {
849 // we moved too many lines. put "dot" in middle of screen
850 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
851 screenbegin = prev_line(screenbegin);
852 }
853 }
854 } else {
855 char *end_scr; // begin and end of screen
856 end_scr = end_screen(); // last char of screen
857 if (beg_cur > end_scr) {
858 // "d" is after bottom line on screen
859 // how many lines do we have to move
860 cnt = count_lines(end_scr, beg_cur);
861 if (cnt > (rows - 1) / 2)
862 goto sc1; // too many lines
863 for (ro = 0; ro < cnt - 1; ro++) {
864 // move screen begin the same amount
865 screenbegin = next_line(screenbegin);
866 // now, move the end of screen
867 end_scr = next_line(end_scr);
868 end_scr = end_line(end_scr);
869 }
870 }
871 }
872 // "d" is on screen- find out which row
873 tp = screenbegin;
874 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
875 if (tp == beg_cur)
876 break;
877 tp = next_line(tp);
878 }
879
880 // find out what col "d" is on
881 co = 0;
Ron Yorstond9d19892021-04-15 12:01:34 +0100882 do { // drive "co" to correct column
Denys Vlasenkode697752019-04-01 14:08:00 +0200883 if (*tp == '\n') //vda || *tp == '\0')
884 break;
Ron Yorston310ef232021-04-17 09:25:11 +0100885 co = next_column(*tp, co) - 1;
Ron Yorstond9d19892021-04-15 12:01:34 +0100886 // inserting text before a tab, don't include its position
887 if (cmd_mode && tp == d - 1 && *d == '\t') {
888 co++;
889 break;
890 }
891 } while (tp++ < d && ++co);
Denys Vlasenkode697752019-04-01 14:08:00 +0200892
893 // "co" is the column where "dot" is.
894 // The screen has "columns" columns.
895 // The currently displayed columns are 0+offset -- columns+ofset
896 // |-------------------------------------------------------------|
897 // ^ ^ ^
898 // offset | |------- columns ----------------|
899 //
900 // If "co" is already in this range then we do not have to adjust offset
901 // but, we do have to subtract the "offset" bias from "co".
902 // If "co" is outside this range then we have to change "offset".
903 // If the first char of a line is a tab the cursor will try to stay
904 // in column 7, but we have to set offset to 0.
905
906 if (co < 0 + offset) {
907 offset = co;
908 }
909 if (co >= columns + offset) {
910 offset = co - columns + 1;
911 }
912 // if the first char of the line is a tab, and "dot" is sitting on it
913 // force offset to 0.
914 if (d == beg_cur && *d == '\t') {
915 offset = 0;
916 }
917 co -= offset;
918
919 *row = ro;
920 *col = co;
921}
922
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +0200923//----- Format a text[] line into a buffer ---------------------
924static char* format_line(char *src /*, int li*/)
925{
926 unsigned char c;
927 int co;
928 int ofs = offset;
929 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
930
931 c = '~'; // char in col 0 in non-existent lines is '~'
932 co = 0;
933 while (co < columns + tabstop) {
934 // have we gone past the end?
935 if (src < end) {
936 c = *src++;
937 if (c == '\n')
938 break;
939 if ((c & 0x80) && !Isprint(c)) {
940 c = '.';
941 }
942 if (c < ' ' || c == 0x7f) {
943 if (c == '\t') {
944 c = ' ';
945 // co % 8 != 7
946 while ((co % tabstop) != (tabstop - 1)) {
947 dest[co++] = c;
948 }
949 } else {
950 dest[co++] = '^';
951 if (c == 0x7f)
952 c = '?';
953 else
954 c += '@'; // Ctrl-X -> 'X'
955 }
956 }
957 }
958 dest[co++] = c;
959 // discard scrolled-off-to-the-left portion,
960 // in tabstop-sized pieces
961 if (ofs >= tabstop && co >= tabstop) {
962 memmove(dest, dest + tabstop, co);
963 co -= tabstop;
964 ofs -= tabstop;
965 }
966 if (src >= end)
967 break;
968 }
969 // check "short line, gigantic offset" case
970 if (co < ofs)
971 ofs = co;
972 // discard last scrolled off part
973 co -= ofs;
974 dest += ofs;
975 // fill the rest with spaces
976 if (co < columns)
977 memset(&dest[co], ' ', columns - co);
978 return dest;
979}
980
981//----- Refresh the changed screen lines -----------------------
982// Copy the source line from text[] into the buffer and note
983// if the current screenline is different from the new buffer.
984// If they differ then that line needs redrawing on the terminal.
985//
986static void refresh(int full_screen)
987{
988#define old_offset refresh__old_offset
989
990 int li, changed;
991 char *tp, *sp; // pointer into text[] and screen[]
992
993 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
994 unsigned c = columns, r = rows;
995 query_screen_dimensions();
996#if ENABLE_FEATURE_VI_USE_SIGNALS
997 full_screen |= (c - columns) | (r - rows);
998#else
999 if (c != columns || r != rows) {
1000 full_screen = TRUE;
1001 // update screen memory since SIGWINCH won't have done it
1002 new_screen(rows, columns);
1003 }
1004#endif
1005 }
1006 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
1007 tp = screenbegin; // index into text[] of top line
1008
1009 // compare text[] to screen[] and mark screen[] lines that need updating
1010 for (li = 0; li < rows - 1; li++) {
1011 int cs, ce; // column start & end
1012 char *out_buf;
1013 // format current text line
1014 out_buf = format_line(tp /*, li*/);
1015
1016 // skip to the end of the current text[] line
1017 if (tp < end) {
1018 char *t = memchr(tp, '\n', end - tp);
1019 if (!t) t = end - 1;
1020 tp = t + 1;
1021 }
1022
1023 // see if there are any changes between virtual screen and out_buf
1024 changed = FALSE; // assume no change
1025 cs = 0;
1026 ce = columns - 1;
1027 sp = &screen[li * columns]; // start of screen line
1028 if (full_screen) {
1029 // force re-draw of every single column from 0 - columns-1
1030 goto re0;
1031 }
1032 // compare newly formatted buffer with virtual screen
1033 // look forward for first difference between buf and screen
1034 for (; cs <= ce; cs++) {
1035 if (out_buf[cs] != sp[cs]) {
1036 changed = TRUE; // mark for redraw
1037 break;
1038 }
1039 }
1040
1041 // look backward for last difference between out_buf and screen
1042 for (; ce >= cs; ce--) {
1043 if (out_buf[ce] != sp[ce]) {
1044 changed = TRUE; // mark for redraw
1045 break;
1046 }
1047 }
1048 // now, cs is index of first diff, and ce is index of last diff
1049
1050 // if horz offset has changed, force a redraw
1051 if (offset != old_offset) {
1052 re0:
1053 changed = TRUE;
1054 }
1055
1056 // make a sanity check of columns indexes
1057 if (cs < 0) cs = 0;
1058 if (ce > columns - 1) ce = columns - 1;
1059 if (cs > ce) { cs = 0; ce = columns - 1; }
1060 // is there a change between virtual screen and out_buf
1061 if (changed) {
1062 // copy changed part of buffer to virtual screen
1063 memcpy(sp+cs, out_buf+cs, ce-cs+1);
1064 place_cursor(li, cs);
1065 // write line out to terminal
1066 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
1067 }
1068 }
1069
1070 place_cursor(crow, ccol);
1071
Ron Yorston50a2db72021-03-28 13:20:01 +01001072 if (!keep_index)
1073 cindex = ccol + offset;
1074
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001075 old_offset = offset;
1076#undef old_offset
1077}
1078
1079//----- Force refresh of all Lines -----------------------------
1080static void redraw(int full_screen)
1081{
1082 // cursor to top,left; clear to the end of screen
1083 write1(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS);
1084 screen_erase(); // erase the internal screen buffer
1085 last_status_cksum = 0; // force status update
1086 refresh(full_screen); // this will redraw the entire display
1087 show_status_line();
1088}
1089
1090//----- Flash the screen --------------------------------------
1091static void flash(int h)
1092{
1093 standout_start();
1094 redraw(TRUE);
1095 mysleep(h);
1096 standout_end();
1097 redraw(TRUE);
1098}
1099
1100static void indicate_error(void)
1101{
1102#if ENABLE_FEATURE_VI_CRASHME
1103 if (crashme > 0)
1104 return;
1105#endif
Ron Yorston038d4002021-06-16 14:47:00 +01001106 cmd_error = TRUE;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001107 if (!err_method) {
1108 write1(ESC_BELL);
1109 } else {
1110 flash(10);
1111 }
1112}
1113
1114//----- IO Routines --------------------------------------------
1115static int readit(void) // read (maybe cursor) key from stdin
1116{
1117 int c;
1118
1119 fflush_all();
1120
1121 // Wait for input. TIMEOUT = -1 makes read_key wait even
1122 // on nonblocking stdin.
1123 // Note: read_key sets errno to 0 on success.
1124 again:
Denys Vlasenko12566e72022-01-17 03:02:40 +01001125 c = safe_read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001126 if (c == -1) { // EOF/error
1127 if (errno == EAGAIN) // paranoia
1128 goto again;
1129 go_bottom_and_clear_to_eol();
1130 cookmode(); // terminal to "cooked"
James Byrne69374872019-07-02 11:35:03 +02001131 bb_simple_error_msg_and_die("can't read user input");
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001132 }
1133 return c;
1134}
1135
Denys Vlasenko2a576082019-04-01 16:15:51 +02001136#if ENABLE_FEATURE_VI_DOT_CMD
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001137static int get_one_char(void)
1138{
1139 int c;
1140
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001141 if (!adding2q) {
1142 // we are not adding to the q.
Denys Vlasenko2a576082019-04-01 16:15:51 +02001143 // but, we may be reading from a saved q.
1144 // (checking "ioq" for NULL is wrong, it's not reset to NULL
1145 // when done - "ioq_start" is reset instead).
1146 if (ioq_start != NULL) {
1147 // there is a queue to get chars from.
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001148 // careful with correct sign expansion!
1149 c = (unsigned char)*ioq++;
Denys Vlasenko2a576082019-04-01 16:15:51 +02001150 if (c != '\0')
1151 return c;
1152 // the end of the q
1153 free(ioq_start);
1154 ioq_start = NULL;
1155 // read from STDIN:
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001156 }
Denys Vlasenko2a576082019-04-01 16:15:51 +02001157 return readit();
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001158 }
Denys Vlasenko2a576082019-04-01 16:15:51 +02001159 // we are adding STDIN chars to q.
1160 c = readit();
Ron Yorstona5445022021-04-06 16:48:07 +01001161 if (lmc_len >= ARRAY_SIZE(last_modifying_cmd) - 2) {
1162 // last_modifying_cmd[] is too small, can't remember the cmd
Denys Vlasenkobbacd032019-04-02 11:50:25 +02001163 // - drop it
1164 adding2q = 0;
1165 lmc_len = 0;
Denys Vlasenko2a576082019-04-01 16:15:51 +02001166 } else {
1167 last_modifying_cmd[lmc_len++] = c;
1168 }
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001169 return c;
1170}
Denys Vlasenko2a576082019-04-01 16:15:51 +02001171#else
1172# define get_one_char() readit()
1173#endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001174
Ron Yorston1e84daf2021-03-28 13:21:34 +01001175// Get type of thing to operate on and adjust count
1176static int get_motion_char(void)
1177{
1178 int c, cnt;
1179
1180 c = get_one_char();
Ron Yorston38ae0f32021-04-15 12:02:43 +01001181 if (isdigit(c)) {
1182 if (c != '0') {
1183 // get any non-zero motion count
1184 for (cnt = 0; isdigit(c); c = get_one_char())
1185 cnt = cnt * 10 + (c - '0');
1186 cmdcnt = (cmdcnt ?: 1) * cnt;
1187 } else {
1188 // ensure standalone '0' works
1189 cmdcnt = 0;
1190 }
Ron Yorston1e84daf2021-03-28 13:21:34 +01001191 }
1192
1193 return c;
1194}
1195
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001196// Get input line (uses "status line" area)
1197static char *get_input_line(const char *prompt)
1198{
1199 // char [MAX_INPUT_LEN]
1200#define buf get_input_line__buf
1201
1202 int c;
1203 int i;
1204
1205 strcpy(buf, prompt);
1206 last_status_cksum = 0; // force status update
1207 go_bottom_and_clear_to_eol();
Denys Vlasenko74e1f322021-05-01 14:00:09 +02001208 write1(buf); // write out the :, /, or ? prompt
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001209
1210 i = strlen(buf);
Ron Yorston852ffbe2021-04-25 11:55:01 +01001211 while (i < MAX_INPUT_LEN - 1) {
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001212 c = get_one_char();
1213 if (c == '\n' || c == '\r' || c == 27)
1214 break; // this is end of input
Denys Vlasenkob29dce42019-04-01 17:17:02 +02001215 if (c == term_orig.c_cc[VERASE] || c == 8 || c == 127) {
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001216 // user wants to erase prev char
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001217 write1("\b \b"); // erase char on screen
Denys Vlasenko74e1f322021-05-01 14:00:09 +02001218 buf[--i] = '\0';
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001219 if (i <= 0) // user backs up before b-o-l, exit
1220 break;
1221 } else if (c > 0 && c < 256) { // exclude Unicode
1222 // (TODO: need to handle Unicode)
1223 buf[i] = c;
1224 buf[++i] = '\0';
1225 bb_putchar(c);
1226 }
1227 }
1228 refresh(FALSE);
1229 return buf;
1230#undef buf
1231}
1232
1233static void Hit_Return(void)
1234{
1235 int c;
1236
1237 standout_start();
1238 write1("[Hit return to continue]");
1239 standout_end();
1240 while ((c = get_one_char()) != '\n' && c != '\r')
1241 continue;
1242 redraw(TRUE); // force redraw all
1243}
1244
1245//----- Draw the status line at bottom of the screen -------------
1246// show file status on status line
1247static int format_edit_status(void)
1248{
1249 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
1250
1251#define tot format_edit_status__tot
1252
1253 int cur, percent, ret, trunc_at;
1254
1255 // modified_count is now a counter rather than a flag. this
1256 // helps reduce the amount of line counting we need to do.
1257 // (this will cause a mis-reporting of modified status
1258 // once every MAXINT editing operations.)
1259
1260 // it would be nice to do a similar optimization here -- if
1261 // we haven't done a motion that could have changed which line
1262 // we're on, then we shouldn't have to do this count_lines()
1263 cur = count_lines(text, dot);
1264
1265 // count_lines() is expensive.
1266 // Call it only if something was changed since last time
1267 // we were here:
1268 if (modified_count != last_modified_count) {
1269 tot = cur + count_lines(dot, end - 1) - 1;
1270 last_modified_count = modified_count;
1271 }
1272
1273 // current line percent
1274 // ------------- ~~ ----------
1275 // total lines 100
1276 if (tot > 0) {
1277 percent = (100 * cur) / tot;
1278 } else {
1279 cur = tot = 0;
1280 percent = 100;
1281 }
1282
1283 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
1284 columns : STATUS_BUFFER_LEN-1;
1285
1286 ret = snprintf(status_buffer, trunc_at+1,
1287#if ENABLE_FEATURE_VI_READONLY
1288 "%c %s%s%s %d/%d %d%%",
1289#else
1290 "%c %s%s %d/%d %d%%",
1291#endif
1292 cmd_mode_indicator[cmd_mode & 3],
1293 (current_filename != NULL ? current_filename : "No file"),
1294#if ENABLE_FEATURE_VI_READONLY
1295 (readonly_mode ? " [Readonly]" : ""),
1296#endif
1297 (modified_count ? " [Modified]" : ""),
1298 cur, tot, percent);
1299
1300 if (ret >= 0 && ret < trunc_at)
1301 return ret; // it all fit
1302
1303 return trunc_at; // had to truncate
1304#undef tot
1305}
1306
1307static int bufsum(char *buf, int count)
1308{
1309 int sum = 0;
1310 char *e = buf + count;
1311 while (buf < e)
1312 sum += (unsigned char) *buf++;
1313 return sum;
1314}
1315
1316static void show_status_line(void)
1317{
1318 int cnt = 0, cksum = 0;
1319
1320 // either we already have an error or status message, or we
1321 // create one.
1322 if (!have_status_msg) {
1323 cnt = format_edit_status();
1324 cksum = bufsum(status_buffer, cnt);
1325 }
1326 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
1327 last_status_cksum = cksum; // remember if we have seen this line
1328 go_bottom_and_clear_to_eol();
1329 write1(status_buffer);
1330 if (have_status_msg) {
1331 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
1332 (columns - 1) ) {
1333 have_status_msg = 0;
1334 Hit_Return();
1335 }
1336 have_status_msg = 0;
1337 }
1338 place_cursor(crow, ccol); // put cursor back in correct place
1339 }
1340 fflush_all();
1341}
1342
1343//----- format the status buffer, the bottom line of screen ------
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02001344static void status_line(const char *format, ...)
1345{
1346 va_list args;
1347
1348 va_start(args, format);
1349 vsnprintf(status_buffer, STATUS_BUFFER_LEN, format, args);
1350 va_end(args);
1351
1352 have_status_msg = 1;
1353}
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001354static void status_line_bold(const char *format, ...)
1355{
1356 va_list args;
1357
1358 va_start(args, format);
1359 strcpy(status_buffer, ESC_BOLD_TEXT);
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02001360 vsnprintf(status_buffer + (sizeof(ESC_BOLD_TEXT)-1),
1361 STATUS_BUFFER_LEN - sizeof(ESC_BOLD_TEXT) - sizeof(ESC_NORM_TEXT),
1362 format, args
1363 );
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001364 strcat(status_buffer, ESC_NORM_TEXT);
1365 va_end(args);
1366
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02001367 have_status_msg = 1 + (sizeof(ESC_BOLD_TEXT)-1) + (sizeof(ESC_NORM_TEXT)-1);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001368}
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001369static void status_line_bold_errno(const char *fn)
1370{
1371 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
1372}
1373
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001374// copy s to buf, convert unprintable
1375static void print_literal(char *buf, const char *s)
1376{
1377 char *d;
1378 unsigned char c;
1379
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001380 if (!s[0])
1381 s = "(NULL)";
1382
1383 d = buf;
1384 for (; *s; s++) {
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001385 c = *s;
Ron Yorston74c4f352021-08-21 14:03:55 +01001386 if ((c & 0x80) && !Isprint(c))
1387 c = '?';
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001388 if (c < ' ' || c == 0x7f) {
1389 *d++ = '^';
1390 c |= '@'; // 0x40
1391 if (c == 0x7f)
1392 c = '?';
1393 }
1394 *d++ = c;
1395 *d = '\0';
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001396 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
1397 break;
1398 }
1399}
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001400static void not_implemented(const char *s)
1401{
1402 char buf[MAX_INPUT_LEN];
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001403 print_literal(buf, s);
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02001404 status_line_bold("'%s' is not implemented", buf);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001405}
1406
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02001407//----- Block insert/delete, undo ops --------------------------
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001408#if ENABLE_FEATURE_VI_YANKMARK
Ron Yorston25d25922021-03-25 14:23:36 +00001409// copy text into a register
1410static char *text_yank(char *p, char *q, int dest, int buftype)
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001411{
Ron Yorstonf4a99082021-04-06 13:44:05 +01001412 char *oldreg = reg[dest];
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001413 int cnt = q - p;
1414 if (cnt < 0) { // they are backwards- reverse them
1415 p = q;
1416 cnt = -cnt;
1417 }
Ron Yorstonf4a99082021-04-06 13:44:05 +01001418 // Don't free register yet. This prevents the memory allocator
1419 // from reusing the free block so we can detect if it's changed.
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001420 reg[dest] = xstrndup(p, cnt + 1);
Ron Yorston25d25922021-03-25 14:23:36 +00001421 regtype[dest] = buftype;
Ron Yorstonf4a99082021-04-06 13:44:05 +01001422 free(oldreg);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001423 return p;
1424}
1425
1426static char what_reg(void)
1427{
1428 char c;
1429
1430 c = 'D'; // default to D-reg
Denys Vlasenkofa182a32019-06-08 12:57:07 +02001431 if (YDreg <= 25)
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001432 c = 'a' + (char) YDreg;
1433 if (YDreg == 26)
1434 c = 'D';
1435 if (YDreg == 27)
1436 c = 'U';
1437 return c;
1438}
1439
1440static void check_context(char cmd)
1441{
Ron Yorston74d565f2021-04-15 12:04:45 +01001442 // Certain movement commands update the context.
1443 if (strchr(":%{}'GHLMz/?Nn", cmd) != NULL) {
1444 mark[27] = mark[26]; // move cur to prev
1445 mark[26] = dot; // move local to cur
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001446 }
1447}
1448
1449static char *swap_context(char *p) // goto new context for '' command make this the current context
1450{
1451 char *tmp;
1452
1453 // the current context is in mark[26]
1454 // the previous context is in mark[27]
1455 // only swap context if other context is valid
1456 if (text <= mark[27] && mark[27] <= end - 1) {
1457 tmp = mark[27];
1458 mark[27] = p;
1459 mark[26] = p = tmp;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001460 }
1461 return p;
1462}
Ron Yorstonf4a99082021-04-06 13:44:05 +01001463
1464# if ENABLE_FEATURE_VI_VERBOSE_STATUS
1465static void yank_status(const char *op, const char *p, int cnt)
1466{
1467 int lines, chars;
1468
1469 lines = chars = 0;
1470 while (*p) {
1471 ++chars;
1472 if (*p++ == '\n')
1473 ++lines;
1474 }
1475 status_line("%s %d lines (%d chars) from [%c]",
1476 op, lines * cnt, chars * cnt, what_reg());
1477}
1478# endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001479#endif /* FEATURE_VI_YANKMARK */
1480
1481#if ENABLE_FEATURE_VI_UNDO
Ron Yorston9b2a3892021-04-15 12:00:50 +01001482static void undo_push(char *, unsigned, int);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001483#endif
1484
1485// open a hole in text[]
1486// might reallocate text[]! use p += text_hole_make(p, ...),
1487// and be careful to not use pointers into potentially freed text[]!
1488static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
1489{
1490 uintptr_t bias = 0;
1491
1492 if (size <= 0)
1493 return bias;
1494 end += size; // adjust the new END
1495 if (end >= (text + text_size)) {
1496 char *new_text;
1497 text_size += end - (text + text_size) + 10240;
1498 new_text = xrealloc(text, text_size);
1499 bias = (new_text - text);
1500 screenbegin += bias;
1501 dot += bias;
1502 end += bias;
1503 p += bias;
1504#if ENABLE_FEATURE_VI_YANKMARK
1505 {
1506 int i;
1507 for (i = 0; i < ARRAY_SIZE(mark); i++)
1508 if (mark[i])
1509 mark[i] += bias;
1510 }
1511#endif
1512 text = new_text;
1513 }
1514 memmove(p + size, p, end - size - p);
1515 memset(p, ' ', size); // clear new hole
1516 return bias;
1517}
1518
1519// close a hole in text[] - delete "p" through "q", inclusive
1520// "undo" value indicates if this operation should be undo-able
1521#if !ENABLE_FEATURE_VI_UNDO
1522#define text_hole_delete(a,b,c) text_hole_delete(a,b)
1523#endif
1524static char *text_hole_delete(char *p, char *q, int undo)
1525{
1526 char *src, *dest;
1527 int cnt, hole_size;
1528
1529 // move forwards, from beginning
1530 // assume p <= q
1531 src = q + 1;
1532 dest = p;
1533 if (q < p) { // they are backward- swap them
1534 src = p + 1;
1535 dest = q;
1536 }
1537 hole_size = q - p + 1;
1538 cnt = end - src;
1539#if ENABLE_FEATURE_VI_UNDO
1540 switch (undo) {
1541 case NO_UNDO:
1542 break;
1543 case ALLOW_UNDO:
1544 undo_push(p, hole_size, UNDO_DEL);
1545 break;
1546 case ALLOW_UNDO_CHAIN:
1547 undo_push(p, hole_size, UNDO_DEL_CHAIN);
1548 break;
1549# if ENABLE_FEATURE_VI_UNDO_QUEUE
1550 case ALLOW_UNDO_QUEUED:
1551 undo_push(p, hole_size, UNDO_DEL_QUEUED);
1552 break;
1553# endif
1554 }
1555 modified_count--;
1556#endif
1557 if (src < text || src > end)
1558 goto thd0;
1559 if (dest < text || dest >= end)
1560 goto thd0;
1561 modified_count++;
1562 if (src >= end)
1563 goto thd_atend; // just delete the end of the buffer
1564 memmove(dest, src, cnt);
1565 thd_atend:
1566 end = end - hole_size; // adjust the new END
1567 if (dest >= end)
1568 dest = end - 1; // make sure dest in below end-1
1569 if (end <= text)
1570 dest = end = text; // keep pointers valid
1571 thd0:
1572 return dest;
1573}
1574
1575#if ENABLE_FEATURE_VI_UNDO
1576
1577# if ENABLE_FEATURE_VI_UNDO_QUEUE
1578// Flush any queued objects to the undo stack
1579static void undo_queue_commit(void)
1580{
1581 // Pushes the queue object onto the undo stack
1582 if (undo_q > 0) {
1583 // Deleted character undo events grow from the end
1584 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
1585 undo_q,
1586 (undo_queue_state | UNDO_USE_SPOS)
1587 );
1588 undo_queue_state = UNDO_EMPTY;
1589 undo_q = 0;
1590 }
1591}
1592# else
1593# define undo_queue_commit() ((void)0)
1594# endif
1595
1596static void flush_undo_data(void)
1597{
1598 struct undo_object *undo_entry;
1599
1600 while (undo_stack_tail) {
1601 undo_entry = undo_stack_tail;
1602 undo_stack_tail = undo_entry->prev;
1603 free(undo_entry);
1604 }
1605}
1606
1607// Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
1608// Add to the undo stack
Ron Yorston9b2a3892021-04-15 12:00:50 +01001609static void undo_push(char *src, unsigned length, int u_type)
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001610{
1611 struct undo_object *undo_entry;
Ron Yorston9b2a3892021-04-15 12:00:50 +01001612# if ENABLE_FEATURE_VI_UNDO_QUEUE
1613 int use_spos = u_type & UNDO_USE_SPOS;
1614# endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001615
1616 // "u_type" values
1617 // UNDO_INS: insertion, undo will remove from buffer
1618 // UNDO_DEL: deleted text, undo will restore to buffer
1619 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
1620 // The CHAIN operations are for handling multiple operations that the user
1621 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
1622 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
Ron Yorston9b2a3892021-04-15 12:00:50 +01001623 // for the INS/DEL operation.
1624 // UNDO_{INS,DEL} ORed with UNDO_USE_SPOS: commit the undo queue
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001625
1626# if ENABLE_FEATURE_VI_UNDO_QUEUE
1627 // This undo queuing functionality groups multiple character typing or backspaces
1628 // into a single large undo object. This greatly reduces calls to malloc() for
1629 // single-character operations while typing and has the side benefit of letting
1630 // an undo operation remove chunks of text rather than a single character.
1631 switch (u_type) {
1632 case UNDO_EMPTY: // Just in case this ever happens...
1633 return;
1634 case UNDO_DEL_QUEUED:
1635 if (length != 1)
1636 return; // Only queue single characters
1637 switch (undo_queue_state) {
1638 case UNDO_EMPTY:
1639 undo_queue_state = UNDO_DEL;
1640 case UNDO_DEL:
1641 undo_queue_spos = src;
1642 undo_q++;
1643 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
1644 // If queue is full, dump it into an object
1645 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1646 undo_queue_commit();
1647 return;
1648 case UNDO_INS:
1649 // Switch from storing inserted text to deleted text
1650 undo_queue_commit();
1651 undo_push(src, length, UNDO_DEL_QUEUED);
1652 return;
1653 }
1654 break;
1655 case UNDO_INS_QUEUED:
1656 if (length < 1)
1657 return;
1658 switch (undo_queue_state) {
1659 case UNDO_EMPTY:
1660 undo_queue_state = UNDO_INS;
1661 undo_queue_spos = src;
1662 case UNDO_INS:
1663 while (length--) {
1664 undo_q++; // Don't need to save any data for insertions
1665 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1666 undo_queue_commit();
1667 }
1668 return;
1669 case UNDO_DEL:
1670 // Switch from storing deleted text to inserted text
1671 undo_queue_commit();
1672 undo_push(src, length, UNDO_INS_QUEUED);
1673 return;
1674 }
1675 break;
1676 }
Ron Yorston9b2a3892021-04-15 12:00:50 +01001677 u_type &= ~UNDO_USE_SPOS;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001678# endif
1679
1680 // Allocate a new undo object
1681 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
1682 // For UNDO_DEL objects, save deleted text
1683 if ((text + length) == end)
1684 length--;
1685 // If this deletion empties text[], strip the newline. When the buffer becomes
1686 // zero-length, a newline is added back, which requires this to compensate.
1687 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
1688 memcpy(undo_entry->undo_text, src, length);
1689 } else {
1690 undo_entry = xzalloc(sizeof(*undo_entry));
1691 }
1692 undo_entry->length = length;
1693# if ENABLE_FEATURE_VI_UNDO_QUEUE
Ron Yorston9b2a3892021-04-15 12:00:50 +01001694 if (use_spos) {
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001695 undo_entry->start = undo_queue_spos - text; // use start position from queue
1696 } else {
1697 undo_entry->start = src - text; // use offset from start of text buffer
1698 }
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001699# else
1700 undo_entry->start = src - text;
1701# endif
1702 undo_entry->u_type = u_type;
1703
1704 // Push it on undo stack
1705 undo_entry->prev = undo_stack_tail;
1706 undo_stack_tail = undo_entry;
1707 modified_count++;
1708}
1709
1710static void undo_push_insert(char *p, int len, int undo)
1711{
1712 switch (undo) {
1713 case ALLOW_UNDO:
1714 undo_push(p, len, UNDO_INS);
1715 break;
1716 case ALLOW_UNDO_CHAIN:
1717 undo_push(p, len, UNDO_INS_CHAIN);
1718 break;
1719# if ENABLE_FEATURE_VI_UNDO_QUEUE
1720 case ALLOW_UNDO_QUEUED:
1721 undo_push(p, len, UNDO_INS_QUEUED);
1722 break;
1723# endif
1724 }
1725}
1726
1727// Undo the last operation
1728static void undo_pop(void)
1729{
1730 int repeat;
1731 char *u_start, *u_end;
1732 struct undo_object *undo_entry;
1733
1734 // Commit pending undo queue before popping (should be unnecessary)
1735 undo_queue_commit();
1736
1737 undo_entry = undo_stack_tail;
1738 // Check for an empty undo stack
1739 if (!undo_entry) {
1740 status_line("Already at oldest change");
1741 return;
1742 }
1743
1744 switch (undo_entry->u_type) {
1745 case UNDO_DEL:
1746 case UNDO_DEL_CHAIN:
1747 // make hole and put in text that was deleted; deallocate text
1748 u_start = text + undo_entry->start;
1749 text_hole_make(u_start, undo_entry->length);
1750 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
Ron Yorstonf4a99082021-04-06 13:44:05 +01001751# if ENABLE_FEATURE_VI_VERBOSE_STATUS
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001752 status_line("Undo [%d] %s %d chars at position %d",
1753 modified_count, "restored",
1754 undo_entry->length, undo_entry->start
1755 );
Ron Yorstonf4a99082021-04-06 13:44:05 +01001756# endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001757 break;
1758 case UNDO_INS:
1759 case UNDO_INS_CHAIN:
1760 // delete what was inserted
1761 u_start = undo_entry->start + text;
1762 u_end = u_start - 1 + undo_entry->length;
1763 text_hole_delete(u_start, u_end, NO_UNDO);
Ron Yorstonf4a99082021-04-06 13:44:05 +01001764# if ENABLE_FEATURE_VI_VERBOSE_STATUS
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001765 status_line("Undo [%d] %s %d chars at position %d",
1766 modified_count, "deleted",
1767 undo_entry->length, undo_entry->start
1768 );
Ron Yorstonf4a99082021-04-06 13:44:05 +01001769# endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001770 break;
1771 }
1772 repeat = 0;
1773 switch (undo_entry->u_type) {
1774 // If this is the end of a chain, lower modification count and refresh display
1775 case UNDO_DEL:
1776 case UNDO_INS:
1777 dot = (text + undo_entry->start);
1778 refresh(FALSE);
1779 break;
1780 case UNDO_DEL_CHAIN:
1781 case UNDO_INS_CHAIN:
1782 repeat = 1;
1783 break;
1784 }
1785 // Deallocate the undo object we just processed
1786 undo_stack_tail = undo_entry->prev;
1787 free(undo_entry);
1788 modified_count--;
1789 // For chained operations, continue popping all the way down the chain.
1790 if (repeat) {
1791 undo_pop(); // Follow the undo chain if one exists
1792 }
1793}
1794
1795#else
1796# define flush_undo_data() ((void)0)
1797# define undo_queue_commit() ((void)0)
1798#endif /* ENABLE_FEATURE_VI_UNDO */
1799
1800//----- Dot Movement Routines ----------------------------------
1801static void dot_left(void)
1802{
1803 undo_queue_commit();
1804 if (dot > text && dot[-1] != '\n')
1805 dot--;
1806}
1807
1808static void dot_right(void)
1809{
1810 undo_queue_commit();
1811 if (dot < end - 1 && *dot != '\n')
1812 dot++;
1813}
1814
1815static void dot_begin(void)
1816{
1817 undo_queue_commit();
1818 dot = begin_line(dot); // return pointer to first char cur line
1819}
1820
1821static void dot_end(void)
1822{
1823 undo_queue_commit();
1824 dot = end_line(dot); // return pointer to last char cur line
1825}
1826
1827static char *move_to_col(char *p, int l)
1828{
1829 int co;
1830
1831 p = begin_line(p);
1832 co = 0;
Ron Yorstond9d19892021-04-15 12:01:34 +01001833 do {
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001834 if (*p == '\n') //vda || *p == '\0')
1835 break;
Ron Yorston310ef232021-04-17 09:25:11 +01001836 co = next_column(*p, co);
1837 } while (co <= l && p++ < end);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001838 return p;
1839}
1840
1841static void dot_next(void)
1842{
1843 undo_queue_commit();
1844 dot = next_line(dot);
1845}
1846
1847static void dot_prev(void)
1848{
1849 undo_queue_commit();
1850 dot = prev_line(dot);
1851}
1852
1853static void dot_skip_over_ws(void)
1854{
1855 // skip WS
1856 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1857 dot++;
1858}
1859
Ron Yorston15f4ac32021-03-28 17:15:30 +01001860static void dot_to_char(int cmd)
1861{
1862 char *q = dot;
1863 int dir = islower(cmd) ? FORWARD : BACK;
1864
1865 if (last_search_char == 0)
1866 return;
1867
1868 do {
1869 do {
1870 q += dir;
Ron Yorston99fb5f22021-04-06 13:44:36 +01001871 if ((dir == FORWARD ? q > end - 1 : q < text) || *q == '\n') {
1872 indicate_error();
Ron Yorston15f4ac32021-03-28 17:15:30 +01001873 return;
Ron Yorston99fb5f22021-04-06 13:44:36 +01001874 }
Ron Yorston15f4ac32021-03-28 17:15:30 +01001875 } while (*q != last_search_char);
1876 } while (--cmdcnt > 0);
1877
1878 dot = q;
1879
1880 // place cursor before/after char as required
1881 if (cmd == 't')
1882 dot_left();
1883 else if (cmd == 'T')
1884 dot_right();
1885}
1886
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001887static void dot_scroll(int cnt, int dir)
1888{
1889 char *q;
1890
1891 undo_queue_commit();
1892 for (; cnt > 0; cnt--) {
1893 if (dir < 0) {
1894 // scroll Backwards
1895 // ctrl-Y scroll up one line
1896 screenbegin = prev_line(screenbegin);
1897 } else {
1898 // scroll Forwards
1899 // ctrl-E scroll down one line
1900 screenbegin = next_line(screenbegin);
1901 }
1902 }
1903 // make sure "dot" stays on the screen so we dont scroll off
1904 if (dot < screenbegin)
1905 dot = screenbegin;
1906 q = end_screen(); // find new bottom line
1907 if (dot > q)
1908 dot = begin_line(q); // is dot is below bottom line?
1909 dot_skip_over_ws();
1910}
1911
1912static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1913{
1914 if (p >= end && end > text) {
1915 p = end - 1;
1916 indicate_error();
1917 }
1918 if (p < text) {
1919 p = text;
1920 indicate_error();
1921 }
1922 return p;
1923}
1924
1925#if ENABLE_FEATURE_VI_DOT_CMD
1926static void start_new_cmd_q(char c)
1927{
1928 // get buffer for new cmd
Ron Yorstona5445022021-04-06 16:48:07 +01001929 dotcnt = cmdcnt ?: 1;
1930 last_modifying_cmd[0] = c;
1931 lmc_len = 1;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001932 adding2q = 1;
1933}
1934static void end_cmd_q(void)
1935{
1936# if ENABLE_FEATURE_VI_YANKMARK
1937 YDreg = 26; // go back to default Yank/Delete reg
1938# endif
1939 adding2q = 0;
1940}
1941#else
1942# define end_cmd_q() ((void)0)
1943#endif /* FEATURE_VI_DOT_CMD */
1944
1945// copy text into register, then delete text.
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001946//
1947#if !ENABLE_FEATURE_VI_UNDO
1948#define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
1949#endif
Ron Yorston25d25922021-03-25 14:23:36 +00001950static char *yank_delete(char *start, char *stop, int buftype, int yf, int undo)
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001951{
1952 char *p;
1953
1954 // make sure start <= stop
1955 if (start > stop) {
1956 // they are backwards, reverse them
1957 p = start;
1958 start = stop;
1959 stop = p;
1960 }
Ron Yorston25d25922021-03-25 14:23:36 +00001961 if (buftype == PARTIAL && *start == '\n')
1962 return start;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001963 p = start;
1964#if ENABLE_FEATURE_VI_YANKMARK
Ron Yorston25d25922021-03-25 14:23:36 +00001965 text_yank(start, stop, YDreg, buftype);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001966#endif
1967 if (yf == YANKDEL) {
1968 p = text_hole_delete(start, stop, undo);
1969 } // delete lines
1970 return p;
1971}
1972
1973// might reallocate text[]!
1974static int file_insert(const char *fn, char *p, int initial)
1975{
1976 int cnt = -1;
1977 int fd, size;
1978 struct stat statbuf;
1979
1980 if (p < text)
1981 p = text;
1982 if (p > end)
1983 p = end;
1984
1985 fd = open(fn, O_RDONLY);
1986 if (fd < 0) {
1987 if (!initial)
1988 status_line_bold_errno(fn);
1989 return cnt;
1990 }
1991
1992 // Validate file
1993 if (fstat(fd, &statbuf) < 0) {
1994 status_line_bold_errno(fn);
1995 goto fi;
1996 }
1997 if (!S_ISREG(statbuf.st_mode)) {
1998 status_line_bold("'%s' is not a regular file", fn);
1999 goto fi;
2000 }
2001 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2002 p += text_hole_make(p, size);
2003 cnt = full_read(fd, p, size);
2004 if (cnt < 0) {
2005 status_line_bold_errno(fn);
2006 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2007 } else if (cnt < size) {
2008 // There was a partial read, shrink unused space
2009 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2010 status_line_bold("can't read '%s'", fn);
2011 }
Ron Yorstondadd9092021-04-25 11:54:24 +01002012# if ENABLE_FEATURE_VI_UNDO
2013 else {
2014 undo_push_insert(p, size, ALLOW_UNDO);
2015 }
2016# endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002017 fi:
2018 close(fd);
2019
2020#if ENABLE_FEATURE_VI_READONLY
2021 if (initial
2022 && ((access(fn, W_OK) < 0) ||
2023 // root will always have access()
2024 // so we check fileperms too
2025 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2026 )
2027 ) {
2028 SET_READONLY_FILE(readonly_mode);
2029 }
2030#endif
2031 return cnt;
2032}
2033
2034// find matching char of pair () [] {}
2035// will crash if c is not one of these
2036static char *find_pair(char *p, const char c)
2037{
2038 const char *braces = "()[]{}";
2039 char match;
2040 int dir, level;
2041
2042 dir = strchr(braces, c) - braces;
2043 dir ^= 1;
2044 match = braces[dir];
2045 dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\}
2046
2047 // look for match, count levels of pairs (( ))
2048 level = 1;
2049 for (;;) {
2050 p += dir;
2051 if (p < text || p >= end)
2052 return NULL;
2053 if (*p == c)
2054 level++; // increase pair levels
2055 if (*p == match) {
2056 level--; // reduce pair level
2057 if (level == 0)
2058 return p; // found matching pair
2059 }
2060 }
2061}
2062
2063#if ENABLE_FEATURE_VI_SETOPTS
2064// show the matching char of a pair, () [] {}
2065static void showmatching(char *p)
2066{
2067 char *q, *save_dot;
2068
2069 // we found half of a pair
2070 q = find_pair(p, *p); // get loc of matching char
2071 if (q == NULL) {
2072 indicate_error(); // no matching char
2073 } else {
2074 // "q" now points to matching pair
2075 save_dot = dot; // remember where we are
2076 dot = q; // go to new loc
2077 refresh(FALSE); // let the user see it
2078 mysleep(40); // give user some time
2079 dot = save_dot; // go back to old loc
2080 refresh(FALSE);
2081 }
2082}
2083#endif /* FEATURE_VI_SETOPTS */
2084
2085// might reallocate text[]! use p += stupid_insert(p, ...),
2086// and be careful to not use pointers into potentially freed text[]!
2087static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2088{
2089 uintptr_t bias;
2090 bias = text_hole_make(p, 1);
2091 p += bias;
2092 *p = c;
2093 return bias;
2094}
2095
Ron Yorston9659a8d2021-05-20 08:27:48 +01002096// find number of characters in indent, p must be at beginning of line
2097static size_t indent_len(char *p)
2098{
2099 char *r = p;
2100
2101 while (r < (end - 1) && isblank(*r))
2102 r++;
2103 return r - p;
2104}
2105
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002106#if !ENABLE_FEATURE_VI_UNDO
2107#define char_insert(a,b,c) char_insert(a,b)
2108#endif
2109static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
2110{
Ron Yorstonac6495f2021-04-15 12:03:58 +01002111#if ENABLE_FEATURE_VI_SETOPTS
Denys Vlasenkob65e7f62021-04-15 23:17:01 +02002112 size_t len;
Ron Yorston16e2fa92021-05-20 08:27:19 +01002113 int col, ntab, nspc;
Ron Yorstonac6495f2021-04-15 12:03:58 +01002114#endif
Ron Yorston9659a8d2021-05-20 08:27:48 +01002115 char *bol = begin_line(p);
Ron Yorstonac6495f2021-04-15 12:03:58 +01002116
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002117 if (c == 22) { // Is this an ctrl-V?
2118 p += stupid_insert(p, '^'); // use ^ to indicate literal next
2119 refresh(FALSE); // show the ^
2120 c = get_one_char();
2121 *p = c;
2122#if ENABLE_FEATURE_VI_UNDO
2123 undo_push_insert(p, 1, undo);
2124#else
2125 modified_count++;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02002126#endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002127 p++;
2128 } else if (c == 27) { // Is this an ESC?
2129 cmd_mode = 0;
2130 undo_queue_commit();
2131 cmdcnt = 0;
2132 end_cmd_q(); // stop adding to q
2133 last_status_cksum = 0; // force status update
S Harris5c89e5a2021-06-21 11:04:49 +01002134 if ((dot > text) && (p[-1] != '\n')) {
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002135 p--;
2136 }
Ron Yorston9659a8d2021-05-20 08:27:48 +01002137#if ENABLE_FEATURE_VI_SETOPTS
2138 if (autoindent) {
2139 len = indent_len(bol);
Ron Yorstonf1d21b72021-06-16 14:45:10 +01002140 if (len && get_column(bol + len) == indentcol && bol[len] == '\n') {
Ron Yorston9659a8d2021-05-20 08:27:48 +01002141 // remove autoindent from otherwise empty line
2142 text_hole_delete(bol, bol + len - 1, undo);
2143 p = bol;
2144 }
Ron Yorstonac6495f2021-04-15 12:03:58 +01002145 }
Ron Yorston9659a8d2021-05-20 08:27:48 +01002146#endif
2147 } else if (c == 4) { // ctrl-D reduces indentation
2148 char *r = bol + indent_len(bol);
2149 int prev = prev_tabstop(get_column(r));
Ron Yorstonf277c9e2021-04-17 09:25:47 +01002150 while (r > bol && get_column(r) > prev) {
2151 if (p > bol)
2152 p--;
2153 r--;
2154 r = text_hole_delete(r, r, ALLOW_UNDO_QUEUED);
2155 }
Ron Yorston9659a8d2021-05-20 08:27:48 +01002156
2157#if ENABLE_FEATURE_VI_SETOPTS
2158 if (autoindent && indentcol && r == end_line(p)) {
2159 // record changed size of autoindent
2160 indentcol = get_column(p);
2161 return p;
2162 }
2163#endif
Ron Yorstonf277c9e2021-04-17 09:25:47 +01002164#if ENABLE_FEATURE_VI_SETOPTS
Ron Yorston310ef232021-04-17 09:25:11 +01002165 } else if (c == '\t' && expandtab) { // expand tab
Ron Yorston16e2fa92021-05-20 08:27:19 +01002166 col = get_column(p);
Ron Yorston310ef232021-04-17 09:25:11 +01002167 col = next_tabstop(col) - col + 1;
2168 while (col--) {
2169# if ENABLE_FEATURE_VI_UNDO
2170 undo_push_insert(p, 1, undo);
2171# else
2172 modified_count++;
2173# endif
2174 p += 1 + stupid_insert(p, ' ');
2175 }
Ron Yorstonac6495f2021-04-15 12:03:58 +01002176#endif
Denys Vlasenkob29dce42019-04-01 17:17:02 +02002177 } else if (c == term_orig.c_cc[VERASE] || c == 8 || c == 127) { // Is this a BS
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002178 if (p > text) {
2179 p--;
2180 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2181 }
2182 } else {
2183 // insert a char into text[]
2184 if (c == 13)
2185 c = '\n'; // translate \r to \n
2186#if ENABLE_FEATURE_VI_UNDO
2187# if ENABLE_FEATURE_VI_UNDO_QUEUE
2188 if (c == '\n')
2189 undo_queue_commit();
2190# endif
2191 undo_push_insert(p, 1, undo);
2192#else
2193 modified_count++;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02002194#endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002195 p += 1 + stupid_insert(p, c); // insert the char
2196#if ENABLE_FEATURE_VI_SETOPTS
2197 if (showmatch && strchr(")]}", c) != NULL) {
2198 showmatching(p - 1);
2199 }
2200 if (autoindent && c == '\n') { // auto indent the new line
Ron Yorston16e2fa92021-05-20 08:27:19 +01002201 // use indent of current/previous line
Ron Yorston9659a8d2021-05-20 08:27:48 +01002202 bol = indentcol < 0 ? p : prev_line(p);
2203 len = indent_len(bol);
2204 col = get_column(bol + len);
2205
2206 if (len && col == indentcol) {
2207 // previous line was empty except for autoindent
2208 // move the indent to the current line
2209 memmove(bol + 1, bol, len);
2210 *bol = '\n';
2211 return p;
2212 }
2213
2214 if (indentcol < 0)
2215 p--; // open above, indent before newly inserted NL
2216
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002217 if (len) {
Ron Yorston9659a8d2021-05-20 08:27:48 +01002218 indentcol = col;
Ron Yorston16e2fa92021-05-20 08:27:19 +01002219 if (expandtab) {
2220 ntab = 0;
2221 nspc = col;
2222 } else {
2223 ntab = col / tabstop;
2224 nspc = col % tabstop;
2225 }
2226 p += text_hole_make(p, ntab + nspc);
Denys Vlasenkob65e7f62021-04-15 23:17:01 +02002227# if ENABLE_FEATURE_VI_UNDO
Ron Yorston16e2fa92021-05-20 08:27:19 +01002228 undo_push_insert(p, ntab + nspc, undo);
Denys Vlasenkob65e7f62021-04-15 23:17:01 +02002229# endif
Ron Yorston16e2fa92021-05-20 08:27:19 +01002230 memset(p, '\t', ntab);
2231 p += ntab;
2232 memset(p, ' ', nspc);
Ron Yorston9659a8d2021-05-20 08:27:48 +01002233 return p + nspc;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002234 }
2235 }
2236#endif
2237 }
Ron Yorston9659a8d2021-05-20 08:27:48 +01002238#if ENABLE_FEATURE_VI_SETOPTS
2239 indentcol = 0;
2240#endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002241 return p;
2242}
2243
Ron Yorstonacd30792021-04-25 11:55:42 +01002244#if ENABLE_FEATURE_VI_COLON_EXPAND
2245static void init_filename(char *fn)
2246{
2247 char *copy = xstrdup(fn);
2248
2249 if (current_filename == NULL) {
2250 current_filename = copy;
2251 } else {
2252 free(alt_filename);
2253 alt_filename = copy;
2254 }
2255}
2256#else
2257# define init_filename(f) ((void)(0))
2258#endif
2259
2260static void update_filename(char *fn)
2261{
2262#if ENABLE_FEATURE_VI_COLON_EXPAND
2263 if (fn == NULL)
2264 return;
2265
2266 if (current_filename == NULL || strcmp(fn, current_filename) != 0) {
2267 free(alt_filename);
2268 alt_filename = current_filename;
2269 current_filename = xstrdup(fn);
2270 }
2271#else
2272 if (fn != current_filename) {
2273 free(current_filename);
2274 current_filename = xstrdup(fn);
2275 }
2276#endif
2277}
2278
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002279// read text from file or create an empty buf
2280// will also update current_filename
2281static int init_text_buffer(char *fn)
2282{
2283 int rc;
2284
2285 // allocate/reallocate text buffer
2286 free(text);
2287 text_size = 10240;
2288 screenbegin = dot = end = text = xzalloc(text_size);
2289
Ron Yorstonacd30792021-04-25 11:55:42 +01002290 update_filename(fn);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002291 rc = file_insert(fn, text, 1);
2292 if (rc < 0) {
2293 // file doesnt exist. Start empty buf with dummy line
2294 char_insert(text, '\n', NO_UNDO);
2295 }
2296
2297 flush_undo_data();
2298 modified_count = 0;
2299 last_modified_count = -1;
2300#if ENABLE_FEATURE_VI_YANKMARK
2301 // init the marks
2302 memset(mark, 0, sizeof(mark));
2303#endif
2304 return rc;
2305}
2306
2307#if ENABLE_FEATURE_VI_YANKMARK \
2308 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2309 || ENABLE_FEATURE_VI_CRASHME
2310// might reallocate text[]! use p += string_insert(p, ...),
2311// and be careful to not use pointers into potentially freed text[]!
2312# if !ENABLE_FEATURE_VI_UNDO
2313# define string_insert(a,b,c) string_insert(a,b)
2314# endif
2315static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2316{
2317 uintptr_t bias;
2318 int i;
2319
2320 i = strlen(s);
2321#if ENABLE_FEATURE_VI_UNDO
2322 undo_push_insert(p, i, undo);
2323#endif
2324 bias = text_hole_make(p, i);
2325 p += bias;
2326 memcpy(p, s, i);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002327 return bias;
2328}
2329#endif
2330
2331static int file_write(char *fn, char *first, char *last)
2332{
2333 int fd, cnt, charcnt;
2334
2335 if (fn == 0) {
2336 status_line_bold("No current filename");
2337 return -2;
2338 }
2339 // By popular request we do not open file with O_TRUNC,
2340 // but instead ftruncate() it _after_ successful write.
2341 // Might reduce amount of data lost on power fail etc.
2342 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2343 if (fd < 0)
2344 return -1;
2345 cnt = last - first + 1;
2346 charcnt = full_write(fd, first, cnt);
2347 ftruncate(fd, charcnt);
2348 if (charcnt == cnt) {
2349 // good write
2350 //modified_count = FALSE;
2351 } else {
2352 charcnt = 0;
2353 }
2354 close(fd);
2355 return charcnt;
2356}
2357
2358#if ENABLE_FEATURE_VI_SEARCH
2359# if ENABLE_FEATURE_VI_REGEX_SEARCH
2360// search for pattern starting at p
2361static char *char_search(char *p, const char *pat, int dir_and_range)
2362{
2363 struct re_pattern_buffer preg;
2364 const char *err;
2365 char *q;
Ron Yorston51358752021-06-21 11:30:39 +01002366 int i, size, range, start;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002367
Ron Yorston29164432021-07-02 08:23:06 +01002368 re_syntax_options = RE_SYNTAX_POSIX_BASIC & (~RE_DOT_NEWLINE);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002369 if (ignorecase)
Ron Yorston29164432021-07-02 08:23:06 +01002370 re_syntax_options |= RE_ICASE;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002371
2372 memset(&preg, 0, sizeof(preg));
2373 err = re_compile_pattern(pat, strlen(pat), &preg);
Ron Yorstonc76c7872021-07-06 07:43:57 +01002374 preg.not_bol = p != text;
2375 preg.not_eol = p != end - 1;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002376 if (err != NULL) {
2377 status_line_bold("bad search pattern '%s': %s", pat, err);
2378 return p;
2379 }
2380
2381 range = (dir_and_range & 1);
2382 q = end - 1; // if FULL
2383 if (range == LIMITED)
2384 q = next_line(p);
2385 if (dir_and_range < 0) { // BACK?
2386 q = text;
2387 if (range == LIMITED)
2388 q = prev_line(p);
2389 }
2390
2391 // RANGE could be negative if we are searching backwards
2392 range = q - p;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002393 if (range < 0) {
Ron Yorston51358752021-06-21 11:30:39 +01002394 size = -range;
2395 start = size;
2396 } else {
2397 size = range;
2398 start = 0;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002399 }
Ron Yorston51358752021-06-21 11:30:39 +01002400 q = p - start;
2401 if (q < text)
2402 q = text;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002403 // search for the compiled pattern, preg, in p[]
Ron Yorston51358752021-06-21 11:30:39 +01002404 // range < 0, start == size: search backward
2405 // range > 0, start == 0: search forward
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002406 // re_search() < 0: not found or error
2407 // re_search() >= 0: index of found pattern
2408 // struct pattern char int int int struct reg
2409 // re_search(*pattern_buffer, *string, size, start, range, *regs)
Ron Yorston51358752021-06-21 11:30:39 +01002410 i = re_search(&preg, q, size, start, range, /*struct re_registers*:*/ NULL);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002411 regfree(&preg);
Ron Yorston51358752021-06-21 11:30:39 +01002412 return i < 0 ? NULL : q + i;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002413}
2414# else
2415# if ENABLE_FEATURE_VI_SETOPTS
2416static int mycmp(const char *s1, const char *s2, int len)
2417{
2418 if (ignorecase) {
2419 return strncasecmp(s1, s2, len);
2420 }
2421 return strncmp(s1, s2, len);
2422}
2423# else
2424# define mycmp strncmp
2425# endif
2426static char *char_search(char *p, const char *pat, int dir_and_range)
2427{
2428 char *start, *stop;
2429 int len;
2430 int range;
2431
2432 len = strlen(pat);
2433 range = (dir_and_range & 1);
2434 if (dir_and_range > 0) { //FORWARD?
2435 stop = end - 1; // assume range is p..end-1
2436 if (range == LIMITED)
2437 stop = next_line(p); // range is to next line
2438 for (start = p; start < stop; start++) {
2439 if (mycmp(start, pat, len) == 0) {
2440 return start;
2441 }
2442 }
2443 } else { //BACK
2444 stop = text; // assume range is text..p
2445 if (range == LIMITED)
2446 stop = prev_line(p); // range is to prev line
2447 for (start = p - len; start >= stop; start--) {
2448 if (mycmp(start, pat, len) == 0) {
2449 return start;
2450 }
2451 }
2452 }
2453 // pattern not found
2454 return NULL;
2455}
2456# endif
2457#endif /* FEATURE_VI_SEARCH */
2458
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002459//----- The Colon commands -------------------------------------
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00002460#if ENABLE_FEATURE_VI_COLON
Ron Yorstona51d9532021-08-29 14:56:02 +01002461// Evaluate colon address expression. Returns a pointer to the
2462// next character or NULL on error. If 'result' contains a valid
2463// address 'valid' is TRUE.
2464static char *get_one_address(char *p, int *result, int *valid)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002465{
Ron Yorstona51d9532021-08-29 14:56:02 +01002466 int num, sign, addr, got_addr;
Ron Yorston5d1bb582021-04-15 12:05:14 +01002467# if ENABLE_FEATURE_VI_YANKMARK || ENABLE_FEATURE_VI_SEARCH
Ron Yorston47f78912021-04-15 12:06:11 +01002468 char *q, c;
Ron Yorston5d1bb582021-04-15 12:05:14 +01002469# endif
Ron Yorston47f78912021-04-15 12:06:11 +01002470 IF_FEATURE_VI_SEARCH(int dir;)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002471
Ron Yorstona51d9532021-08-29 14:56:02 +01002472 got_addr = FALSE;
2473 addr = count_lines(text, dot); // default to current line
Ron Yorstonf2277262021-04-15 12:06:51 +01002474 sign = 0;
2475 for (;;) {
Ron Yorstonf2277262021-04-15 12:06:51 +01002476 if (isblank(*p)) {
Ron Yorstona51d9532021-08-29 14:56:02 +01002477 if (got_addr) {
2478 addr += sign;
2479 sign = 0;
2480 }
Ron Yorstonf2277262021-04-15 12:06:51 +01002481 p++;
Ron Yorstona51d9532021-08-29 14:56:02 +01002482 } else if (!got_addr && *p == '.') { // the current line
Ron Yorstonf2277262021-04-15 12:06:51 +01002483 p++;
Ron Yorstona51d9532021-08-29 14:56:02 +01002484 //addr = count_lines(text, dot);
2485 got_addr = TRUE;
2486 } else if (!got_addr && *p == '$') { // the last line in file
2487 p++;
2488 addr = count_lines(text, end - 1);
2489 got_addr = TRUE;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002490 }
Ron Yorstonf2277262021-04-15 12:06:51 +01002491# if ENABLE_FEATURE_VI_YANKMARK
Ron Yorstona51d9532021-08-29 14:56:02 +01002492 else if (!got_addr && *p == '\'') { // is this a mark addr
Ron Yorstonf2277262021-04-15 12:06:51 +01002493 p++;
2494 c = tolower(*p);
2495 p++;
2496 q = NULL;
2497 if (c >= 'a' && c <= 'z') {
2498 // we have a mark
2499 c = c - 'a';
2500 q = mark[(unsigned char) c];
2501 }
Ron Yorstona51d9532021-08-29 14:56:02 +01002502 if (q == NULL) { // is mark valid
2503 status_line_bold("Mark not set");
Ron Yorstonf2277262021-04-15 12:06:51 +01002504 return NULL;
Ron Yorstona51d9532021-08-29 14:56:02 +01002505 }
2506 addr = count_lines(text, q);
2507 got_addr = TRUE;
Ron Yorstonf2277262021-04-15 12:06:51 +01002508 }
Denys Vlasenko70ee2332021-03-01 14:41:39 +01002509# endif
2510# if ENABLE_FEATURE_VI_SEARCH
Ron Yorstona51d9532021-08-29 14:56:02 +01002511 else if (!got_addr && (*p == '/' || *p == '?')) { // a search pattern
Ron Yorstonf2277262021-04-15 12:06:51 +01002512 c = *p;
2513 q = strchrnul(p + 1, c);
2514 if (p + 1 != q) {
2515 // save copy of new pattern
2516 free(last_search_pattern);
2517 last_search_pattern = xstrndup(p, q - p);
2518 }
2519 p = q;
2520 if (*p == c)
2521 p++;
2522 if (c == '/') {
2523 q = next_line(dot);
2524 dir = (FORWARD << 1) | FULL;
2525 } else {
2526 q = begin_line(dot);
2527 dir = ((unsigned)BACK << 1) | FULL;
2528 }
2529 q = char_search(q, last_search_pattern + 1, dir);
Ron Yorston08ad9342021-08-21 14:02:43 +01002530 if (q == NULL) {
2531 // no match, continue from other end of file
2532 q = char_search(dir > 0 ? text : end - 1,
2533 last_search_pattern + 1, dir);
Ron Yorstona51d9532021-08-29 14:56:02 +01002534 if (q == NULL) {
2535 status_line_bold("Pattern not found");
Ron Yorston08ad9342021-08-21 14:02:43 +01002536 return NULL;
Ron Yorstona51d9532021-08-29 14:56:02 +01002537 }
Ron Yorston08ad9342021-08-21 14:02:43 +01002538 }
Ron Yorstona51d9532021-08-29 14:56:02 +01002539 addr = count_lines(text, q);
2540 got_addr = TRUE;
Ron Yorston16bcd502020-01-23 15:31:19 +00002541 }
Denys Vlasenko70ee2332021-03-01 14:41:39 +01002542# endif
Ron Yorstona51d9532021-08-29 14:56:02 +01002543 else if (isdigit(*p)) {
2544 num = 0;
2545 while (isdigit(*p))
2546 num = num * 10 + *p++ -'0';
2547 if (!got_addr) { // specific line number
Ron Yorstonf2277262021-04-15 12:06:51 +01002548 addr = num;
Ron Yorstona51d9532021-08-29 14:56:02 +01002549 got_addr = TRUE;
Ron Yorstonf2277262021-04-15 12:06:51 +01002550 } else { // offset from current addr
2551 addr += sign >= 0 ? num : -num;
2552 }
2553 sign = 0;
2554 } else if (*p == '-' || *p == '+') {
Ron Yorstona51d9532021-08-29 14:56:02 +01002555 if (!got_addr) { // default address is dot
2556 //addr = count_lines(text, dot);
2557 got_addr = TRUE;
2558 } else {
2559 addr += sign;
Ron Yorstonf2277262021-04-15 12:06:51 +01002560 }
Ron Yorstona51d9532021-08-29 14:56:02 +01002561 sign = *p++ == '-' ? -1 : 1;
Ron Yorstonf2277262021-04-15 12:06:51 +01002562 } else {
2563 addr += sign; // consume unused trailing sign
2564 break;
2565 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002566 }
Ron Yorstonf2277262021-04-15 12:06:51 +01002567 *result = addr;
Ron Yorstona51d9532021-08-29 14:56:02 +01002568 *valid = got_addr;
Denis Vlasenko079f8af2006-11-27 16:49:31 +00002569 return p;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002570}
2571
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002572# define GET_ADDRESS 0
2573# define GET_SEPARATOR 1
Ron Yorston5d1bb582021-04-15 12:05:14 +01002574
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002575// Read line addresses for a colon command. The user can enter as
2576// many as they like but only the last two will be used.
Ron Yorstona51d9532021-08-29 14:56:02 +01002577static char *get_address(char *p, int *b, int *e, unsigned int *got)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002578{
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002579 int state = GET_ADDRESS;
Ron Yorstona51d9532021-08-29 14:56:02 +01002580 int valid;
2581 int addr;
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002582 char *save_dot = dot;
Ron Yorston5d1bb582021-04-15 12:05:14 +01002583
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002584 //----- get the address' i.e., 1,3 'a,'b -----
Ron Yorston5d1bb582021-04-15 12:05:14 +01002585 for (;;) {
2586 if (isblank(*p)) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002587 p++;
Ron Yorstona51d9532021-08-29 14:56:02 +01002588 } else if (state == GET_ADDRESS && *p == '%') { // alias for 1,$
Ron Yorston5d1bb582021-04-15 12:05:14 +01002589 p++;
2590 *b = 1;
2591 *e = count_lines(text, end-1);
Ron Yorstona51d9532021-08-29 14:56:02 +01002592 *got = 3;
2593 state = GET_SEPARATOR;
2594 } else if (state == GET_ADDRESS) {
2595 valid = FALSE;
2596 p = get_one_address(p, &addr, &valid);
2597 // Quit on error or if the address is invalid and isn't of
2598 // the form ',$' or '1,' (in which case it defaults to dot).
2599 if (p == NULL || !(valid || *p == ',' || *p == ';' || *got & 1))
2600 break;
2601 *b = *e;
2602 *e = addr;
2603 *got = (*got << 1) | 1;
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002604 state = GET_SEPARATOR;
2605 } else if (state == GET_SEPARATOR && (*p == ',' || *p == ';')) {
2606 if (*p == ';')
2607 dot = find_line(*e);
Ron Yorston5d1bb582021-04-15 12:05:14 +01002608 p++;
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002609 state = GET_ADDRESS;
Ron Yorston5d1bb582021-04-15 12:05:14 +01002610 } else {
2611 break;
2612 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002613 }
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002614 dot = save_dot;
Denis Vlasenko079f8af2006-11-27 16:49:31 +00002615 return p;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002616}
2617
Denys Vlasenko70ee2332021-03-01 14:41:39 +01002618# if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
Ron Yorston9f017d92021-04-06 22:11:21 +01002619static void setops(char *args, int flg_no)
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002620{
Ron Yorston9f017d92021-04-06 22:11:21 +01002621 char *eq;
2622 int index;
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002623
Ron Yorston9f017d92021-04-06 22:11:21 +01002624 eq = strchr(args, '=');
2625 if (eq) *eq = '\0';
2626 index = index_in_strings(OPTS_STR, args + flg_no);
2627 if (eq) *eq = '=';
2628 if (index < 0) {
2629 bad:
2630 status_line_bold("bad option: %s", args);
2631 return;
2632 }
2633
2634 index = 1 << (index >> 1); // convert to VI_bit
2635
2636 if (index & VI_TABSTOP) {
2637 int t;
2638 if (!eq || flg_no) // no "=NNN" or it is "notabstop"?
2639 goto bad;
2640 t = bb_strtou(eq + 1, NULL, 10);
2641 if (t <= 0 || t > MAX_TABSTOP)
2642 goto bad;
2643 tabstop = t;
2644 return;
2645 }
2646 if (eq) goto bad; // boolean option has "="?
2647 if (flg_no) {
2648 vi_setops &= ~index;
2649 } else {
2650 vi_setops |= index;
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002651 }
2652}
Denys Vlasenko70ee2332021-03-01 14:41:39 +01002653# endif
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002654
Ron Yorstonacd30792021-04-25 11:55:42 +01002655# if ENABLE_FEATURE_VI_COLON_EXPAND
2656static char *expand_args(char *args)
2657{
2658 char *s, *t;
2659 const char *replace;
2660
2661 args = xstrdup(args);
2662 for (s = args; *s; s++) {
2663 if (*s == '%') {
2664 replace = current_filename;
2665 } else if (*s == '#') {
2666 replace = alt_filename;
2667 } else {
2668 if (*s == '\\' && s[1] != '\0') {
2669 for (t = s++; *t; t++)
2670 *t = t[1];
2671 }
2672 continue;
2673 }
2674
2675 if (replace == NULL) {
2676 free(args);
2677 status_line_bold("No previous filename");
2678 return NULL;
2679 }
2680
2681 *s = '\0';
2682 t = xasprintf("%s%s%s", args, replace, s+1);
2683 s = t + (s - args) + strlen(replace);
2684 free(args);
2685 args = t;
2686 }
2687 return args;
2688}
2689# else
2690# define expand_args(a) (a)
2691# endif
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002692#endif /* FEATURE_VI_COLON */
2693
Denys Vlasenko95ac4a42021-07-13 14:38:20 +02002694#if ENABLE_FEATURE_VI_REGEX_SEARCH
2695# define MAX_SUBPATTERN 10 // subpatterns \0 .. \9
2696
Ron Yorston27592012021-07-10 11:00:04 +01002697// Like strchr() but skipping backslash-escaped characters
2698static char *strchr_backslash(const char *s, int c)
2699{
Denys Vlasenko36feb262021-07-13 16:16:21 +02002700 while (*s) {
Ron Yorstone6f41452021-07-13 16:35:43 +01002701 if (*s == c)
Ron Yorston27592012021-07-10 11:00:04 +01002702 return (char *)s;
Denys Vlasenko36feb262021-07-13 16:16:21 +02002703 if (*s == '\\')
2704 if (*++s == '\0')
2705 break;
2706 s++;
Ron Yorston27592012021-07-10 11:00:04 +01002707 }
2708 return NULL;
2709}
2710
Denys Vlasenko95ac4a42021-07-13 14:38:20 +02002711// If the return value is not NULL the caller should free R
2712static char *regex_search(char *q, regex_t *preg, const char *Rorig,
2713 size_t *len_F, size_t *len_R, char **R)
2714{
2715 regmatch_t regmatch[MAX_SUBPATTERN], *cur_match;
2716 char *found = NULL;
2717 const char *t;
2718 char *r;
2719
2720 regmatch[0].rm_so = 0;
2721 regmatch[0].rm_eo = end_line(q) - q;
2722 if (regexec(preg, q, MAX_SUBPATTERN, regmatch, REG_STARTEND) != 0)
2723 return found;
2724
2725 found = q + regmatch[0].rm_so;
2726 *len_F = regmatch[0].rm_eo - regmatch[0].rm_so;
2727 *R = NULL;
2728
2729 fill_result:
2730 // first pass calculates len_R, second fills R
2731 *len_R = 0;
2732 for (t = Rorig, r = *R; *t; t++) {
2733 size_t len = 1; // default is to copy one char from replace pattern
2734 const char *from = t;
2735 if (*t == '\\') {
2736 from = ++t; // skip backslash
2737 if (*t >= '0' && *t < '0' + MAX_SUBPATTERN) {
2738 cur_match = regmatch + (*t - '0');
2739 if (cur_match->rm_so >= 0) {
2740 len = cur_match->rm_eo - cur_match->rm_so;
2741 from = q + cur_match->rm_so;
2742 }
2743 }
2744 }
2745 *len_R += len;
2746 if (*R) {
2747 memcpy(r, from, len);
2748 r += len;
2749 /* *r = '\0'; - xzalloc did it */
2750 }
2751 }
2752 if (*R == NULL) {
2753 *R = xzalloc(*len_R + 1);
2754 goto fill_result;
2755 }
2756
2757 return found;
2758}
Ron Yorston27592012021-07-10 11:00:04 +01002759#else /* !ENABLE_FEATURE_VI_REGEX_SEARCH */
2760# define strchr_backslash(s, c) strchr(s, c)
Denys Vlasenko95ac4a42021-07-13 14:38:20 +02002761#endif /* ENABLE_FEATURE_VI_REGEX_SEARCH */
2762
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002763// buf must be no longer than MAX_INPUT_LEN!
2764static void colon(char *buf)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002765{
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002766#if !ENABLE_FEATURE_VI_COLON
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02002767 // Simple ":cmd" handler with minimal set of commands
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002768 char *p = buf;
2769 int cnt;
2770
2771 if (*p == ':')
2772 p++;
2773 cnt = strlen(p);
2774 if (cnt == 0)
2775 return;
2776 if (strncmp(p, "quit", cnt) == 0
Ron Yorston852ffbe2021-04-25 11:55:01 +01002777 || strcmp(p, "q!") == 0
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002778 ) {
2779 if (modified_count && p[1] != '!') {
2780 status_line_bold("No write since last change (:%s! overrides)", p);
2781 } else {
2782 editing = 0;
2783 }
2784 return;
2785 }
2786 if (strncmp(p, "write", cnt) == 0
Ron Yorston852ffbe2021-04-25 11:55:01 +01002787 || strcmp(p, "wq") == 0
2788 || strcmp(p, "wn") == 0
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002789 || (p[0] == 'x' && !p[1])
2790 ) {
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01002791 if (modified_count != 0 || p[0] != 'x') {
2792 cnt = file_write(current_filename, text, end - 1);
2793 }
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002794 if (cnt < 0) {
2795 if (cnt == -1)
Denys Vlasenko6f97b302017-09-29 18:17:25 +02002796 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002797 } else {
2798 modified_count = 0;
2799 last_modified_count = -1;
Denys Vlasenko89393592019-04-02 12:45:30 +02002800 status_line("'%s' %uL, %uC",
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002801 current_filename,
2802 count_lines(text, end - 1), cnt
2803 );
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01002804 if (p[0] == 'x'
2805 || p[1] == 'q' || p[1] == 'n'
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002806 ) {
2807 editing = 0;
2808 }
2809 }
2810 return;
2811 }
2812 if (strncmp(p, "file", cnt) == 0) {
2813 last_status_cksum = 0; // force status update
2814 return;
2815 }
2816 if (sscanf(p, "%d", &cnt) > 0) {
2817 dot = find_line(cnt);
2818 dot_skip_over_ws();
2819 return;
2820 }
2821 not_implemented(p);
2822#else
2823
Ron Yorstona51d9532021-08-29 14:56:02 +01002824// check how many addresses we got
2825# define GOT_ADDRESS (got & 1)
2826# define GOT_RANGE ((got & 3) == 3)
2827
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002828 char c, *buf1, *q, *r;
Ron Yorstonacd30792021-04-25 11:55:42 +01002829 char *fn, cmd[MAX_INPUT_LEN], *cmdend, *args, *exp = NULL;
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002830 int i, l, li, b, e;
Ron Yorstona51d9532021-08-29 14:56:02 +01002831 unsigned int got;
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002832 int useforce;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002833
2834 // :3154 // if (-e line 3154) goto it else stay put
2835 // :4,33w! foo // write a portion of buffer to file "foo"
2836 // :w // write all of buffer to current file
2837 // :q // quit
2838 // :q! // quit- dont care about modified file
2839 // :'a,'z!sort -u // filter block through sort
2840 // :'f // goto mark "f"
2841 // :'fl // list literal the mark "f" line
2842 // :.r bar // read file "bar" into buffer before dot
2843 // :/123/,/abc/d // delete lines from "123" line to "abc" line
2844 // :/xyz/ // goto the "xyz" line
2845 // :s/find/replace/ // substitute pattern "find" with "replace"
2846 // :!<cmd> // run <cmd> then return
2847 //
Eric Andersen165e8cb2004-07-20 06:44:46 +00002848
Ron Yorstonf9217cd2021-08-19 17:00:17 +01002849 while (*buf == ':')
2850 buf++; // move past leading colons
2851 while (isblank(*buf))
2852 buf++; // move past leading blanks
2853 if (!buf[0] || buf[0] == '"')
2854 goto ret; // ignore empty lines or those starting with '"'
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002855
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002856 li = i = 0;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002857 b = e = -1;
Ron Yorstona51d9532021-08-29 14:56:02 +01002858 got = 0;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002859 li = count_lines(text, end - 1);
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002860 fn = current_filename;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002861
2862 // look for optional address(es) :. :1 :1,9 :'q,'a :%
Ron Yorstona51d9532021-08-29 14:56:02 +01002863 buf = get_address(buf, &b, &e, &got);
Ron Yorstond488def2021-04-15 12:05:45 +01002864 if (buf == NULL) {
Ron Yorstond488def2021-04-15 12:05:45 +01002865 goto ret;
2866 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002867
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002868 // get the COMMAND into cmd[]
Ron Yorston852ffbe2021-04-25 11:55:01 +01002869 strcpy(cmd, buf);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002870 buf1 = cmd;
Ron Yorston852ffbe2021-04-25 11:55:01 +01002871 while (!isspace(*buf1) && *buf1 != '\0') {
2872 buf1++;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002873 }
Ron Yorston852ffbe2021-04-25 11:55:01 +01002874 cmdend = buf1;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002875 // get any ARGuments
Ron Yorston852ffbe2021-04-25 11:55:01 +01002876 while (isblank(*buf1))
2877 buf1++;
2878 args = buf1;
2879 *cmdend = '\0';
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002880 useforce = FALSE;
Ron Yorston852ffbe2021-04-25 11:55:01 +01002881 if (cmdend > cmd && cmdend[-1] == '!') {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002882 useforce = TRUE;
Ron Yorston852ffbe2021-04-25 11:55:01 +01002883 cmdend[-1] = '\0'; // get rid of !
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002884 }
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002885 // assume the command will want a range, certain commands
2886 // (read, substitute) need to adjust these assumptions
Ron Yorstona51d9532021-08-29 14:56:02 +01002887 if (!GOT_ADDRESS) {
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002888 q = text; // no addr, use 1,$ for the range
2889 r = end - 1;
2890 } else {
2891 // at least one addr was given, get its details
Ron Yorstona51d9532021-08-29 14:56:02 +01002892 if (e < 0 || e > li) {
2893 status_line_bold("Invalid range");
2894 goto ret;
2895 }
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002896 q = r = find_line(e);
Ron Yorstona51d9532021-08-29 14:56:02 +01002897 if (!GOT_RANGE) {
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002898 // if there is only one addr, then it's the line
2899 // number of the single line the user wants.
2900 // Reset the end pointer to the end of that line.
2901 r = end_line(q);
2902 li = 1;
2903 } else {
2904 // we were given two addrs. change the
2905 // start pointer to the addr given by user.
Ron Yorstona51d9532021-08-29 14:56:02 +01002906 if (b < 0 || b > li || b > e) {
2907 status_line_bold("Invalid range");
2908 goto ret;
2909 }
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002910 q = find_line(b); // what line is #b
2911 r = end_line(r);
2912 li = e - b + 1;
2913 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002914 }
2915 // ------------ now look for the command ------------
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002916 i = strlen(cmd);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002917 if (i == 0) { // :123CR goto line #123
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002918 if (e >= 0) {
2919 dot = find_line(e); // what line is #e
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002920 dot_skip_over_ws();
2921 }
Denis Vlasenko249fabf2006-12-19 00:29:22 +00002922 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002923# if ENABLE_FEATURE_ALLOW_EXEC
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002924 else if (cmd[0] == '!') { // run a cmd
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002925 int retcode;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002926 // :!ls run the <cmd>
Ron Yorstonacd30792021-04-25 11:55:42 +01002927 exp = expand_args(buf + 1);
2928 if (exp == NULL)
2929 goto ret;
Denis Vlasenko267e16c2008-10-14 10:34:41 +00002930 go_bottom_and_clear_to_eol();
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002931 cookmode();
Ron Yorstonacd30792021-04-25 11:55:42 +01002932 retcode = system(exp); // run the cmd
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002933 if (retcode)
2934 printf("\nshell returned %i\n\n", retcode);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002935 rawmode();
2936 Hit_Return(); // let user see results
Denis Vlasenko249fabf2006-12-19 00:29:22 +00002937 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002938# endif
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002939 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
Ron Yorstona51d9532021-08-29 14:56:02 +01002940 if (!GOT_ADDRESS) { // no addr given- use defaults
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002941 e = count_lines(text, dot);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002942 }
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002943 status_line("%d", e);
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002944 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
Ron Yorstona51d9532021-08-29 14:56:02 +01002945 if (!GOT_ADDRESS) { // no addr given- use defaults
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002946 q = begin_line(dot); // assume .,. for the range
2947 r = end_line(dot);
2948 }
Ron Yorston25d25922021-03-25 14:23:36 +00002949 dot = yank_delete(q, r, WHOLE, YANKDEL, ALLOW_UNDO); // save, then delete lines
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002950 dot_skip_over_ws();
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002951 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002952 int size;
2953
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002954 // don't edit, if the current file has been modified
Denys Vlasenkoe7430862014-04-03 12:47:48 +02002955 if (modified_count && !useforce) {
Dennis Groenenc0657e02012-01-31 14:12:38 +01002956 status_line_bold("No write since last change (:%s! overrides)", cmd);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002957 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002958 }
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002959 if (args[0]) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002960 // the user supplied a file name
Ron Yorstonacd30792021-04-25 11:55:42 +01002961 fn = exp = expand_args(args);
2962 if (exp == NULL)
2963 goto ret;
Ron Yorstondadd9092021-04-25 11:54:24 +01002964 } else if (current_filename == NULL) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002965 // no user file name, no current name- punt
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002966 status_line_bold("No current filename");
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002967 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002968 }
2969
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002970 size = init_text_buffer(fn);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002971
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002972# if ENABLE_FEATURE_VI_YANKMARK
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002973 if (Ureg >= 0 && Ureg < 28) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002974 free(reg[Ureg]); // free orig line reg- for 'U'
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002975 reg[Ureg] = NULL;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002976 }
Denys Vlasenkofa182a32019-06-08 12:57:07 +02002977 /*if (YDreg < 28) - always true*/ {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002978 free(reg[YDreg]); // free default yank/delete register
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002979 reg[YDreg] = NULL;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002980 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002981# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002982 // how many lines in text[]?
2983 li = count_lines(text, end - 1);
Denys Vlasenko778794d2013-01-22 10:13:52 +01002984 status_line("'%s'%s"
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00002985 IF_FEATURE_VI_READONLY("%s")
Denys Vlasenko89393592019-04-02 12:45:30 +02002986 " %uL, %uC",
Ron Yorstonacd30792021-04-25 11:55:42 +01002987 fn,
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002988 (size < 0 ? " [New file]" : ""),
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00002989 IF_FEATURE_VI_READONLY(
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002990 ((readonly_mode) ? " [Readonly]" : ""),
2991 )
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002992 li, (int)(end - text)
2993 );
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002994 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002995 if (e >= 0) {
Denys Vlasenkoc2704542009-11-20 19:14:19 +01002996 status_line_bold("No address allowed on this command");
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002997 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002998 }
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002999 if (args[0]) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003000 // user wants a new filename
Ron Yorstonacd30792021-04-25 11:55:42 +01003001 exp = expand_args(args);
3002 if (exp == NULL)
3003 goto ret;
3004 update_filename(exp);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003005 } else {
3006 // user wants file status info
Paul Fox8552aec2005-09-16 12:20:05 +00003007 last_status_cksum = 0; // force status update
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003008 }
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003009 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003010 // print out values of all features
Denis Vlasenko267e16c2008-10-14 10:34:41 +00003011 go_bottom_and_clear_to_eol();
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003012 cookmode();
3013 show_help();
3014 rawmode();
3015 Hit_Return();
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003016 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
Ron Yorstona51d9532021-08-29 14:56:02 +01003017 if (!GOT_ADDRESS) { // no addr given- use defaults
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003018 q = begin_line(dot); // assume .,. for the range
3019 r = end_line(dot);
3020 }
Denis Vlasenko267e16c2008-10-14 10:34:41 +00003021 go_bottom_and_clear_to_eol();
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003022 puts("\r");
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003023 for (; q <= r; q++) {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003024 int c_is_no_print;
3025
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003026 c = *q;
Denis Vlasenko2a51af22007-03-21 22:31:24 +00003027 c_is_no_print = (c & 0x80) && !Isprint(c);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003028 if (c_is_no_print) {
3029 c = '.';
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003030 standout_start();
Denis Vlasenkod3c042f2007-12-30 01:59:53 +00003031 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003032 if (c == '\n') {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003033 write1("$\r");
3034 } else if (c < ' ' || c == 127) {
Denis Vlasenko4daad902007-09-27 10:20:47 +00003035 bb_putchar('^');
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003036 if (c == 127)
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003037 c = '?';
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003038 else
3039 c += '@';
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003040 }
Denis Vlasenko4daad902007-09-27 10:20:47 +00003041 bb_putchar(c);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003042 if (c_is_no_print)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003043 standout_end();
3044 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003045 Hit_Return();
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003046 } else if (strncmp(cmd, "quit", i) == 0 // quit
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003047 || strncmp(cmd, "next", i) == 0 // edit next file
Dennis Groenenc0657e02012-01-31 14:12:38 +01003048 || strncmp(cmd, "prev", i) == 0 // edit previous file
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003049 ) {
Denys Vlasenko04cecd52010-04-16 22:13:55 -07003050 int n;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003051 if (useforce) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003052 if (*cmd == 'q') {
Dennis Groenenc0657e02012-01-31 14:12:38 +01003053 // force end of argv list
Denys Vlasenkoa3ce1612019-04-03 16:35:23 +02003054 optind = cmdline_filecnt;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003055 }
3056 editing = 0;
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003057 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003058 }
3059 // don't exit if the file been modified
Denys Vlasenkoe7430862014-04-03 12:47:48 +02003060 if (modified_count) {
Dennis Groenenc0657e02012-01-31 14:12:38 +01003061 status_line_bold("No write since last change (:%s! overrides)", cmd);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003062 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003063 }
3064 // are there other file to edit
Denys Vlasenko89393592019-04-02 12:45:30 +02003065 n = cmdline_filecnt - optind - 1;
Denys Vlasenko04cecd52010-04-16 22:13:55 -07003066 if (*cmd == 'q' && n > 0) {
Denys Vlasenko89393592019-04-02 12:45:30 +02003067 status_line_bold("%u more file(s) to edit", n);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003068 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003069 }
Denys Vlasenko04cecd52010-04-16 22:13:55 -07003070 if (*cmd == 'n' && n <= 0) {
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003071 status_line_bold("No more files to edit");
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003072 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003073 }
Dennis Groenenc0657e02012-01-31 14:12:38 +01003074 if (*cmd == 'p') {
3075 // are there previous files to edit
3076 if (optind < 1) {
3077 status_line_bold("No previous files to edit");
3078 goto ret;
3079 }
3080 optind -= 2;
3081 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003082 editing = 0;
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003083 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
Ron Yorstondadd9092021-04-25 11:54:24 +01003084 int size, num;
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003085
Ron Yorstondadd9092021-04-25 11:54:24 +01003086 if (args[0]) {
3087 // the user supplied a file name
Ron Yorstonacd30792021-04-25 11:55:42 +01003088 fn = exp = expand_args(args);
3089 if (exp == NULL)
3090 goto ret;
3091 init_filename(fn);
Ron Yorstondadd9092021-04-25 11:54:24 +01003092 } else if (current_filename == NULL) {
3093 // no user file name, no current name- punt
3094 status_line_bold("No current filename");
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003095 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003096 }
Ron Yorstonf7ed0e82021-06-09 16:11:03 +01003097 if (e == 0) { // user said ":0r foo"
Ron Yorston7a8ceb42021-04-25 11:51:55 +01003098 q = text;
Ron Yorstonf7ed0e82021-06-09 16:11:03 +01003099 } else { // read after given line or current line if none given
Ron Yorstona51d9532021-08-29 14:56:02 +01003100 q = next_line(GOT_ADDRESS ? find_line(e) : dot);
Ron Yorston70f43202014-11-30 20:39:53 +00003101 // read after last line
3102 if (q == end-1)
3103 ++q;
3104 }
Ron Yorstondadd9092021-04-25 11:54:24 +01003105 num = count_lines(text, q);
3106 if (q == end)
3107 num++;
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00003108 { // dance around potentially-reallocated text[]
3109 uintptr_t ofs = q - text;
Ron Yorstone5213ce2014-11-30 20:39:25 +00003110 size = file_insert(fn, q, 0);
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00003111 q = text + ofs;
3112 }
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003113 if (size < 0)
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003114 goto ret; // nothing was inserted
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003115 // how many lines in text[]?
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003116 li = count_lines(q, q + size - 1);
Denys Vlasenko778794d2013-01-22 10:13:52 +01003117 status_line("'%s'"
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00003118 IF_FEATURE_VI_READONLY("%s")
Denys Vlasenko89393592019-04-02 12:45:30 +02003119 " %uL, %uC",
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003120 fn,
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00003121 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003122 li, size
3123 );
Ron Yorstondadd9092021-04-25 11:54:24 +01003124 dot = find_line(num);
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003125 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
Denys Vlasenkoe7430862014-04-03 12:47:48 +02003126 if (modified_count && !useforce) {
Dennis Groenenc0657e02012-01-31 14:12:38 +01003127 status_line_bold("No write since last change (:%s! overrides)", cmd);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003128 } else {
3129 // reset the filenames to edit
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02003130 optind = -1; // start from 0th file
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003131 editing = 0;
3132 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003133# if ENABLE_FEATURE_VI_SET
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003134 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003135# if ENABLE_FEATURE_VI_SETOPTS
Ron Yorston9f017d92021-04-06 22:11:21 +01003136 char *argp, *argn, oldch;
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003137# endif
Denys Vlasenko605f2642012-06-11 01:53:33 +02003138 // only blank is regarded as args delimiter. What about tab '\t'?
Ron Yorston9f017d92021-04-06 22:11:21 +01003139 if (!args[0] || strcmp(args, "all") == 0) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003140 // print out values of all options
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003141# if ENABLE_FEATURE_VI_SETOPTS
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003142 status_line_bold(
3143 "%sautoindent "
Ron Yorston310ef232021-04-17 09:25:11 +01003144 "%sexpandtab "
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003145 "%sflash "
3146 "%signorecase "
3147 "%sshowmatch "
3148 "tabstop=%u",
3149 autoindent ? "" : "no",
Ron Yorston310ef232021-04-17 09:25:11 +01003150 expandtab ? "" : "no",
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003151 err_method ? "" : "no",
3152 ignorecase ? "" : "no",
3153 showmatch ? "" : "no",
3154 tabstop
3155 );
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003156# endif
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003157 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003158 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003159# if ENABLE_FEATURE_VI_SETOPTS
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003160 argp = args;
Denis Vlasenkoba2fb712007-04-01 09:39:03 +00003161 while (*argp) {
Alison Winters63d9da32021-02-27 15:18:45 -08003162 i = 0;
3163 if (argp[0] == 'n' && argp[1] == 'o') // "noXXX"
3164 i = 2;
Ron Yorston9f017d92021-04-06 22:11:21 +01003165 argn = skip_non_whitespace(argp);
3166 oldch = *argn;
3167 *argn = '\0';
3168 setops(argp, i);
3169 *argn = oldch;
3170 argp = skip_whitespace(argn);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003171 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003172# endif /* FEATURE_VI_SETOPTS */
3173# endif /* FEATURE_VI_SET */
3174
3175# if ENABLE_FEATURE_VI_SEARCH
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003176 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003177 char *F, *R, *flags;
3178 size_t len_F, len_R;
Ron Yorston6220b4d2021-04-06 13:43:32 +01003179 int gflag = 0; // global replace flag
3180 int subs = 0; // number of substitutions
Ron Yorstonf4a99082021-04-06 13:44:05 +01003181# if ENABLE_FEATURE_VI_VERBOSE_STATUS
3182 int last_line = 0, lines = 0;
3183# endif
Denys Vlasenko95ac4a42021-07-13 14:38:20 +02003184# if ENABLE_FEATURE_VI_REGEX_SEARCH
3185 regex_t preg;
3186 int cflags;
3187 char *Rorig;
3188# if ENABLE_FEATURE_VI_UNDO
3189 int undo = 0;
3190# endif
3191# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003192
3193 // F points to the "find" pattern
3194 // R points to the "replace" pattern
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003195 // replace the cmd line delimiters "/" with NULs
Ron Yorston852ffbe2021-04-25 11:55:01 +01003196 c = buf[1]; // what is the delimiter
3197 F = buf + 2; // start of "find"
Ron Yorston27592012021-07-10 11:00:04 +01003198 R = strchr_backslash(F, c); // middle delimiter
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00003199 if (!R)
3200 goto colon_s_fail;
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003201 len_F = R - F;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003202 *R++ = '\0'; // terminate "find"
Ron Yorston27592012021-07-10 11:00:04 +01003203 flags = strchr_backslash(R, c);
Ron Yorston6220b4d2021-04-06 13:43:32 +01003204 if (flags) {
3205 *flags++ = '\0'; // terminate "replace"
3206 gflag = *flags;
3207 }
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003208
Ron Yorston5dbbd0a2021-06-25 19:33:31 +01003209 if (len_F) { // save "find" as last search pattern
3210 free(last_search_pattern);
3211 last_search_pattern = xstrdup(F - 1);
3212 last_search_pattern[0] = '/';
3213 } else if (last_search_pattern[1] == '\0') {
3214 status_line_bold("No previous search");
3215 goto ret;
3216 } else {
3217 F = last_search_pattern + 1;
3218 len_F = strlen(F);
3219 }
3220
Ron Yorstona51d9532021-08-29 14:56:02 +01003221 if (!GOT_ADDRESS) { // no addr given
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003222 q = begin_line(dot); // start with cur line
Ron Yorston7a8ceb42021-04-25 11:51:55 +01003223 r = end_line(dot);
3224 b = e = count_lines(text, q); // cur line number
Ron Yorstona51d9532021-08-29 14:56:02 +01003225 } else if (!GOT_RANGE) { // one addr given
Ron Yorston7a8ceb42021-04-25 11:51:55 +01003226 b = e;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003227 }
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003228
Denys Vlasenko95ac4a42021-07-13 14:38:20 +02003229# if ENABLE_FEATURE_VI_REGEX_SEARCH
3230 Rorig = R;
3231 cflags = 0;
3232 if (ignorecase)
3233 cflags = REG_ICASE;
3234 memset(&preg, 0, sizeof(preg));
3235 if (regcomp(&preg, F, cflags) != 0) {
3236 status_line(":s bad search pattern");
3237 goto regex_search_end;
3238 }
3239# else
3240 len_R = strlen(R);
3241# endif
3242
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003243 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003244 char *ls = q; // orig line start
3245 char *found;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003246 vc4:
Denys Vlasenko95ac4a42021-07-13 14:38:20 +02003247# if ENABLE_FEATURE_VI_REGEX_SEARCH
3248 found = regex_search(q, &preg, Rorig, &len_F, &len_R, &R);
3249# else
Denys Vlasenkob7330462018-11-29 14:39:52 +01003250 found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
Denys Vlasenko95ac4a42021-07-13 14:38:20 +02003251# endif
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003252 if (found) {
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00003253 uintptr_t bias;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003254 // we found the "find" pattern - delete it
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003255 // For undo support, the first item should not be chained
Denys Vlasenko95ac4a42021-07-13 14:38:20 +02003256 // This needs to be handled differently depending on
3257 // whether or not regex support is enabled.
3258# if ENABLE_FEATURE_VI_REGEX_SEARCH
3259# define TEST_LEN_F len_F // len_F may be zero
3260# define TEST_UNDO1 undo++
3261# define TEST_UNDO2 undo++
3262# else
3263# define TEST_LEN_F 1 // len_F is never zero
3264# define TEST_UNDO1 subs
3265# define TEST_UNDO2 1
Ron Yorstonf4a99082021-04-06 13:44:05 +01003266# endif
Denys Vlasenko95ac4a42021-07-13 14:38:20 +02003267 if (TEST_LEN_F) // match can be empty, no delete needed
3268 text_hole_delete(found, found + len_F - 1,
Denys Vlasenko36feb262021-07-13 16:16:21 +02003269 TEST_UNDO1 ? ALLOW_UNDO_CHAIN : ALLOW_UNDO);
3270 if (len_R != 0) { // insert the "replace" pattern, if required
Denys Vlasenko95ac4a42021-07-13 14:38:20 +02003271 bias = string_insert(found, R,
Denys Vlasenko36feb262021-07-13 16:16:21 +02003272 TEST_UNDO2 ? ALLOW_UNDO_CHAIN : ALLOW_UNDO);
Denys Vlasenko95ac4a42021-07-13 14:38:20 +02003273 found += bias;
3274 ls += bias;
Denys Vlasenko95ac4a42021-07-13 14:38:20 +02003275 //q += bias; - recalculated anyway
3276 }
3277# if ENABLE_FEATURE_VI_REGEX_SEARCH
3278 free(R);
3279# endif
Denys Vlasenko36feb262021-07-13 16:16:21 +02003280 if (TEST_LEN_F || len_R != 0) {
Denys Vlasenko95ac4a42021-07-13 14:38:20 +02003281 dot = ls;
3282 subs++;
3283# if ENABLE_FEATURE_VI_VERBOSE_STATUS
3284 if (last_line != i) {
3285 last_line = i;
3286 ++lines;
3287 }
3288# endif
3289 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003290 // check for "global" :s/foo/bar/g
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003291 if (gflag == 'g') {
3292 if ((found + len_R) < end_line(ls)) {
3293 q = found + len_R;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003294 goto vc4; // don't let q move past cur line
3295 }
3296 }
3297 }
3298 q = next_line(ls);
3299 }
Ron Yorston6220b4d2021-04-06 13:43:32 +01003300 if (subs == 0) {
3301 status_line_bold("No match");
3302 } else {
3303 dot_skip_over_ws();
Ron Yorstonf4a99082021-04-06 13:44:05 +01003304# if ENABLE_FEATURE_VI_VERBOSE_STATUS
3305 if (subs > 1)
3306 status_line("%d substitutions on %d lines", subs, lines);
3307# endif
Ron Yorston6220b4d2021-04-06 13:43:32 +01003308 }
Denys Vlasenko95ac4a42021-07-13 14:38:20 +02003309# if ENABLE_FEATURE_VI_REGEX_SEARCH
3310 regex_search_end:
3311 regfree(&preg);
3312# endif
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003313# endif /* FEATURE_VI_SEARCH */
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003314 } else if (strncmp(cmd, "version", i) == 0) { // show software version
Denys Vlasenkoeba7fe62017-01-29 19:14:26 +01003315 status_line(BB_VER);
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003316 } else if (strncmp(cmd, "write", i) == 0 // write text to file
Ron Yorston852ffbe2021-04-25 11:55:01 +01003317 || strcmp(cmd, "wq") == 0
3318 || strcmp(cmd, "wn") == 0
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003319 || (cmd[0] == 'x' && !cmd[1])
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003320 ) {
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003321 int size;
3322 //int forced = FALSE;
3323
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003324 // is there a file name to write to?
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003325 if (args[0]) {
Ron Yorston5ae25f42021-03-28 13:18:40 +01003326 struct stat statbuf;
3327
Ron Yorstonacd30792021-04-25 11:55:42 +01003328 exp = expand_args(args);
3329 if (exp == NULL)
3330 goto ret;
3331 if (!useforce && (fn == NULL || strcmp(fn, exp) != 0) &&
3332 stat(exp, &statbuf) == 0) {
Ron Yorston5ae25f42021-03-28 13:18:40 +01003333 status_line_bold("File exists (:w! overrides)");
3334 goto ret;
3335 }
Ron Yorstonacd30792021-04-25 11:55:42 +01003336 fn = exp;
3337 init_filename(fn);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003338 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003339# if ENABLE_FEATURE_VI_READONLY
Ron Yorston09172582021-04-25 11:52:55 +01003340 else if (readonly_mode && !useforce && fn) {
Denys Vlasenko778794d2013-01-22 10:13:52 +01003341 status_line_bold("'%s' is read only", fn);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003342 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003343 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003344# endif
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003345 //if (useforce) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003346 // if "fn" is not write-able, chmod u+w
3347 // sprintf(syscmd, "chmod u+w %s", fn);
3348 // system(syscmd);
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003349 // forced = TRUE;
3350 //}
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01003351 if (modified_count != 0 || cmd[0] != 'x') {
3352 size = r - q + 1;
3353 l = file_write(fn, q, r);
3354 } else {
3355 size = 0;
3356 l = 0;
3357 }
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003358 //if (useforce && forced) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003359 // chmod u-w
3360 // sprintf(syscmd, "chmod u-w %s", fn);
3361 // system(syscmd);
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003362 // forced = FALSE;
3363 //}
Paul Fox61e45db2005-10-09 14:43:22 +00003364 if (l < 0) {
3365 if (l == -1)
Denys Vlasenko9e7c0022013-03-15 02:17:29 +01003366 status_line_bold_errno(fn);
Paul Fox61e45db2005-10-09 14:43:22 +00003367 } else {
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01003368 // how many lines written
3369 li = count_lines(q, q + l - 1);
Denys Vlasenko89393592019-04-02 12:45:30 +02003370 status_line("'%s' %uL, %uC", fn, li, l);
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01003371 if (l == size) {
3372 if (q == text && q + l == end) {
3373 modified_count = 0;
3374 last_modified_count = -1;
3375 }
Ron Yorston8e71f2a2021-05-01 13:15:30 +01003376 if (cmd[1] == 'n') {
3377 editing = 0;
3378 } else if (cmd[0] == 'x' || cmd[1] == 'q') {
3379 // are there other files to edit?
3380 int n = cmdline_filecnt - optind - 1;
3381 if (n > 0) {
3382 if (useforce) {
3383 // force end of argv list
3384 optind = cmdline_filecnt;
3385 } else {
3386 status_line_bold("%u more file(s) to edit", n);
3387 goto ret;
3388 }
3389 }
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01003390 editing = 0;
3391 }
Paul Fox61e45db2005-10-09 14:43:22 +00003392 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003393 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003394# if ENABLE_FEATURE_VI_YANKMARK
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003395 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
Ron Yorstona51d9532021-08-29 14:56:02 +01003396 if (!GOT_ADDRESS) { // no addr given- use defaults
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003397 q = begin_line(dot); // assume .,. for the range
3398 r = end_line(dot);
3399 }
Ron Yorston25d25922021-03-25 14:23:36 +00003400 text_yank(q, r, YDreg, WHOLE);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003401 li = count_lines(q, r);
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003402 status_line("Yank %d lines (%d chars) into [%c]",
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003403 li, strlen(reg[YDreg]), what_reg());
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003404# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003405 } else {
3406 // cmd unknown
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003407 not_implemented(cmd);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003408 }
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003409 ret:
Ron Yorstonacd30792021-04-25 11:55:42 +01003410# if ENABLE_FEATURE_VI_COLON_EXPAND
3411 free(exp);
3412# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003413 dot = bound_dot(dot); // make sure "dot" is valid
3414 return;
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003415# if ENABLE_FEATURE_VI_SEARCH
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003416 colon_s_fail:
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003417 status_line(":s expression missing delimiters");
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003418# endif
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003419#endif /* FEATURE_VI_COLON */
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003420}
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003421
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003422//----- Char Routines --------------------------------------------
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02003423// Chars that are part of a word-
3424// 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
3425// Chars that are Not part of a word (stoppers)
3426// !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
3427// Chars that are WhiteSpace
3428// TAB NEWLINE VT FF RETURN SPACE
3429// DO NOT COUNT NEWLINE AS WHITESPACE
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003430
Denis Vlasenko33875382008-06-21 20:31:50 +00003431static int st_test(char *p, int type, int dir, char *tested)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003432{
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003433 char c, c0, ci;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003434 int test, inc;
3435
3436 inc = dir;
3437 c = c0 = p[0];
3438 ci = p[inc];
3439 test = 0;
3440
3441 if (type == S_BEFORE_WS) {
3442 c = ci;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02003443 test = (!isspace(c) || c == '\n');
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003444 }
3445 if (type == S_TO_WS) {
3446 c = c0;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02003447 test = (!isspace(c) || c == '\n');
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003448 }
3449 if (type == S_OVER_WS) {
3450 c = c0;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02003451 test = isspace(c);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003452 }
3453 if (type == S_END_PUNCT) {
3454 c = ci;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02003455 test = ispunct(c);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003456 }
3457 if (type == S_END_ALNUM) {
3458 c = ci;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02003459 test = (isalnum(c) || c == '_');
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003460 }
3461 *tested = c;
Denis Vlasenkod9e15f22006-11-27 16:49:55 +00003462 return test;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003463}
3464
Denis Vlasenko33875382008-06-21 20:31:50 +00003465static char *skip_thing(char *p, int linecnt, int dir, int type)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003466{
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003467 char c;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003468
3469 while (st_test(p, type, dir, &c)) {
3470 // make sure we limit search to correct number of lines
3471 if (c == '\n' && --linecnt < 1)
3472 break;
3473 if (dir >= 0 && p >= end - 1)
3474 break;
3475 if (dir < 0 && p <= text)
3476 break;
3477 p += dir; // move to next char
3478 }
Denis Vlasenko079f8af2006-11-27 16:49:31 +00003479 return p;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003480}
3481
Denys Vlasenko616e4692019-04-01 14:02:37 +02003482#if ENABLE_FEATURE_VI_USE_SIGNALS
3483static void winch_handler(int sig UNUSED_PARAM)
3484{
3485 int save_errno = errno;
3486 // FIXME: do it in main loop!!!
3487 signal(SIGWINCH, winch_handler);
3488 query_screen_dimensions();
3489 new_screen(rows, columns); // get memory for virtual screen
3490 redraw(TRUE); // re-draw the screen
3491 errno = save_errno;
3492}
3493static void tstp_handler(int sig UNUSED_PARAM)
3494{
3495 int save_errno = errno;
3496
3497 // ioctl inside cookmode() was seen to generate SIGTTOU,
3498 // stopping us too early. Prevent that:
3499 signal(SIGTTOU, SIG_IGN);
3500
3501 go_bottom_and_clear_to_eol();
3502 cookmode(); // terminal to "cooked"
3503
3504 // stop now
3505 //signal(SIGTSTP, SIG_DFL);
3506 //raise(SIGTSTP);
3507 raise(SIGSTOP); // avoid "dance" with TSTP handler - use SIGSTOP instead
3508 //signal(SIGTSTP, tstp_handler);
3509
3510 // we have been "continued" with SIGCONT, restore screen and termios
3511 rawmode(); // terminal to "raw"
3512 last_status_cksum = 0; // force status update
3513 redraw(TRUE); // re-draw the screen
3514
3515 errno = save_errno;
3516}
3517static void int_handler(int sig)
3518{
3519 signal(SIGINT, int_handler);
3520 siglongjmp(restart, sig);
3521}
3522#endif /* FEATURE_VI_USE_SIGNALS */
3523
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003524static void do_cmd(int c);
3525
Ron Yorstond56da682021-03-28 13:23:12 +01003526static int at_eof(const char *s)
3527{
3528 // does 's' point to end of file, even with no terminating newline?
3529 return ((s == end - 2 && s[1] == '\n') || s == end - 1);
3530}
3531
Ron Yorstonb7b11192021-04-06 13:40:23 +01003532static int find_range(char **start, char **stop, int cmd)
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003533{
Ron Yorston24effc72021-04-30 12:56:12 +01003534 char *p, *q, *t;
Ron Yorston25d25922021-03-25 14:23:36 +00003535 int buftype = -1;
Ron Yorstonb7b11192021-04-06 13:40:23 +01003536 int c;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003537
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003538 p = q = dot;
3539
Ron Yorstonb7b11192021-04-06 13:40:23 +01003540#if ENABLE_FEATURE_VI_YANKMARK
3541 if (cmd == 'Y') {
3542 c = 'y';
3543 } else
3544#endif
3545 {
3546 c = get_motion_char();
3547 }
3548
3549#if ENABLE_FEATURE_VI_YANKMARK
3550 if ((cmd == 'Y' || cmd == c) && strchr("cdy><", c)) {
3551#else
3552 if (cmd == c && strchr("cd><", c)) {
3553#endif
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003554 // these cmds operate on whole lines
Ron Yorston25d25922021-03-25 14:23:36 +00003555 buftype = WHOLE;
Ron Yorston038d4002021-06-16 14:47:00 +01003556 if (--cmdcnt > 0) {
Ron Yorston25d25922021-03-25 14:23:36 +00003557 do_cmd('j');
Ron Yorston038d4002021-06-16 14:47:00 +01003558 if (cmd_error)
3559 buftype = -1;
3560 }
Ron Yorstonb7b11192021-04-06 13:40:23 +01003561 } else if (strchr("^%$0bBeEfFtThnN/?|{}\b\177", c)) {
3562 // Most operate on char positions within a line. Of those that
3563 // don't '%' needs no special treatment, search commands are
3564 // marked as MULTI and "{}" are handled below.
3565 buftype = strchr("nN/?", c) ? MULTI : PARTIAL;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003566 do_cmd(c); // execute movement cmd
Ron Yorston25d25922021-03-25 14:23:36 +00003567 if (p == dot) // no movement is an error
3568 buftype = -1;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003569 } else if (strchr("wW", c)) {
Ron Yorston25d25922021-03-25 14:23:36 +00003570 buftype = MULTI;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003571 do_cmd(c); // execute movement cmd
Ron Yorstone577afc2021-04-06 13:41:01 +01003572 // step back one char, but not if we're at end of file,
3573 // or if we are at EOF and search was for 'w' and we're at
3574 // the start of a 'W' word.
3575 if (dot > p && (!at_eof(dot) || (c == 'w' && ispunct(*dot))))
Ron Yorston776b56d2021-03-25 14:21:49 +00003576 dot--;
Ron Yorstonb7b11192021-04-06 13:40:23 +01003577 t = dot;
3578 // don't include trailing WS as part of word
3579 while (dot > p && isspace(*dot)) {
3580 if (*dot-- == '\n')
3581 t = dot;
3582 }
3583 // for non-change operations WS after NL is not part of word
Ron Yorstone577afc2021-04-06 13:41:01 +01003584 if (cmd != 'c' && dot != t && *dot != '\n')
Ron Yorstonb7b11192021-04-06 13:40:23 +01003585 dot = t;
Ron Yorstonb50ac072021-07-07 09:23:30 +01003586 } else if (strchr("GHL+-gjk'\r\n", c)) {
Ron Yorston25d25922021-03-25 14:23:36 +00003587 // these operate on whole lines
3588 buftype = WHOLE;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003589 do_cmd(c); // execute movement cmd
Ron Yorston038d4002021-06-16 14:47:00 +01003590 if (cmd_error)
3591 buftype = -1;
Ron Yorston25d25922021-03-25 14:23:36 +00003592 } else if (c == ' ' || c == 'l') {
Ron Yorston5bef6782021-02-01 11:54:15 +00003593 // forward motion by character
3594 int tmpcnt = (cmdcnt ?: 1);
Ron Yorston25d25922021-03-25 14:23:36 +00003595 buftype = PARTIAL;
Ron Yorston5bef6782021-02-01 11:54:15 +00003596 do_cmd(c); // execute movement cmd
3597 // exclude last char unless range isn't what we expected
3598 // this indicates we've hit EOL
3599 if (tmpcnt == dot - p)
3600 dot--;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003601 }
Ron Yorston25d25922021-03-25 14:23:36 +00003602
Ron Yorstonb7b11192021-04-06 13:40:23 +01003603 if (buftype == -1) {
3604 if (c != 27)
3605 indicate_error();
Ron Yorston25d25922021-03-25 14:23:36 +00003606 return buftype;
Ron Yorstonb7b11192021-04-06 13:40:23 +01003607 }
Ron Yorston25d25922021-03-25 14:23:36 +00003608
3609 q = dot;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003610 if (q < p) {
3611 t = q;
3612 q = p;
3613 p = t;
3614 }
3615
Ron Yorstond56da682021-03-28 13:23:12 +01003616 // movements which don't include end of range
3617 if (q > p) {
Ron Yorstonb7b11192021-04-06 13:40:23 +01003618 if (strchr("^0bBFThnN/?|\b\177", c)) {
Ron Yorstond56da682021-03-28 13:23:12 +01003619 q--;
3620 } else if (strchr("{}", c)) {
3621 buftype = (p == begin_line(p) && (*q == '\n' || at_eof(q))) ?
3622 WHOLE : MULTI;
3623 if (!at_eof(q)) {
3624 q--;
3625 if (q > p && p != begin_line(p))
3626 q--;
3627 }
3628 }
3629 }
3630
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003631 *start = p;
3632 *stop = q;
Ron Yorston25d25922021-03-25 14:23:36 +00003633 return buftype;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003634}
3635
Eric Andersen3f980402001-04-04 17:31:15 +00003636//---------------------------------------------------------------------
3637//----- the Ascii Chart -----------------------------------------------
Eric Andersen3f980402001-04-04 17:31:15 +00003638// 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3639// 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3640// 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3641// 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3642// 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3643// 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3644// 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3645// 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3646// 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3647// 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3648// 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3649// 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3650// 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3651// 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3652// 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3653// 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3654//---------------------------------------------------------------------
3655
3656//----- Execute a Vi Command -----------------------------------
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003657static void do_cmd(int c)
Eric Andersen3f980402001-04-04 17:31:15 +00003658{
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003659 char *p, *q, *save_dot;
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003660 char buf[12];
Denis Vlasenkoc3a9dc82008-10-29 00:58:04 +00003661 int dir;
Paul Foxc51fc7b2008-03-06 01:34:23 +00003662 int cnt, i, j;
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003663 int c1;
Ron Yorston74d565f2021-04-15 12:04:45 +01003664#if ENABLE_FEATURE_VI_YANKMARK
3665 char *orig_dot = dot;
3666#endif
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01003667#if ENABLE_FEATURE_VI_UNDO
3668 int allow_undo = ALLOW_UNDO;
3669 int undo_del = UNDO_DEL;
3670#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003671
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +00003672// c1 = c; // quiet the compiler
3673// cnt = yf = 0; // quiet the compiler
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003674// p = q = save_dot = buf; // quiet the compiler
3675 memset(buf, '\0', sizeof(buf));
Ron Yorston50a2db72021-03-28 13:20:01 +01003676 keep_index = FALSE;
Ron Yorston038d4002021-06-16 14:47:00 +01003677 cmd_error = FALSE;
Eric Andersenbff7a602001-11-17 07:15:43 +00003678
Paul Fox8552aec2005-09-16 12:20:05 +00003679 show_status_line();
3680
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02003681 // if this is a cursor key, skip these checks
Eric Andersenbff7a602001-11-17 07:15:43 +00003682 switch (c) {
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003683 case KEYCODE_UP:
3684 case KEYCODE_DOWN:
3685 case KEYCODE_LEFT:
3686 case KEYCODE_RIGHT:
3687 case KEYCODE_HOME:
3688 case KEYCODE_END:
3689 case KEYCODE_PAGEUP:
3690 case KEYCODE_PAGEDOWN:
3691 case KEYCODE_DELETE:
Eric Andersenbff7a602001-11-17 07:15:43 +00003692 goto key_cmd_mode;
3693 }
3694
Eric Andersen3f980402001-04-04 17:31:15 +00003695 if (cmd_mode == 2) {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003696 // flip-flop Insert/Replace mode
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003697 if (c == KEYCODE_INSERT)
Denis Vlasenko2a51af22007-03-21 22:31:24 +00003698 goto dc_i;
Eric Andersen3f980402001-04-04 17:31:15 +00003699 // we are 'R'eplacing the current *dot with new char
3700 if (*dot == '\n') {
3701 // don't Replace past E-o-l
3702 cmd_mode = 1; // convert to insert
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003703 undo_queue_commit();
Eric Andersen3f980402001-04-04 17:31:15 +00003704 } else {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003705 if (1 <= c || Isprint(c)) {
Eric Andersen3f980402001-04-04 17:31:15 +00003706 if (c != 27)
Ron Yorston25d25922021-03-25 14:23:36 +00003707 dot = yank_delete(dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO); // delete char
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003708 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
Eric Andersen3f980402001-04-04 17:31:15 +00003709 }
3710 goto dc1;
3711 }
3712 }
3713 if (cmd_mode == 1) {
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02003714 // hitting "Insert" twice means "R" replace mode
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003715 if (c == KEYCODE_INSERT) goto dc5;
Eric Andersen3f980402001-04-04 17:31:15 +00003716 // insert the char c at "dot"
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003717 if (1 <= c || Isprint(c)) {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003718 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
Eric Andersen3f980402001-04-04 17:31:15 +00003719 }
3720 goto dc1;
3721 }
3722
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003723 key_cmd_mode:
Eric Andersen3f980402001-04-04 17:31:15 +00003724 switch (c) {
Eric Andersen822c3832001-05-07 17:37:43 +00003725 //case 0x01: // soh
3726 //case 0x09: // ht
3727 //case 0x0b: // vt
3728 //case 0x0e: // so
3729 //case 0x0f: // si
3730 //case 0x10: // dle
3731 //case 0x11: // dc1
3732 //case 0x13: // dc3
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003733#if ENABLE_FEATURE_VI_CRASHME
Eric Andersen1c0d3112001-04-16 15:46:44 +00003734 case 0x14: // dc4 ctrl-T
Eric Andersen3f980402001-04-04 17:31:15 +00003735 crashme = (crashme == 0) ? 1 : 0;
Eric Andersen3f980402001-04-04 17:31:15 +00003736 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003737#endif
Eric Andersen822c3832001-05-07 17:37:43 +00003738 //case 0x16: // syn
3739 //case 0x17: // etb
3740 //case 0x18: // can
3741 //case 0x1c: // fs
3742 //case 0x1d: // gs
3743 //case 0x1e: // rs
3744 //case 0x1f: // us
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003745 //case '!': // !-
3746 //case '#': // #-
3747 //case '&': // &-
3748 //case '(': // (-
3749 //case ')': // )-
3750 //case '*': // *-
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003751 //case '=': // =-
3752 //case '@': // @-
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003753 //case 'K': // K-
3754 //case 'Q': // Q-
3755 //case 'S': // S-
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003756 //case 'V': // V-
3757 //case '[': // [-
3758 //case '\\': // \-
3759 //case ']': // ]-
3760 //case '_': // _-
3761 //case '`': // `-
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003762 //case 'v': // v-
Denys Vlasenkob22bbff2009-07-04 16:50:43 +02003763 default: // unrecognized command
Eric Andersen3f980402001-04-04 17:31:15 +00003764 buf[0] = c;
3765 buf[1] = '\0';
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003766 not_implemented(buf);
Eric Andersen3f980402001-04-04 17:31:15 +00003767 end_cmd_q(); // stop adding to q
3768 case 0x00: // nul- ignore
3769 break;
3770 case 2: // ctrl-B scroll up full screen
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003771 case KEYCODE_PAGEUP: // Cursor Key Page Up
Eric Andersen3f980402001-04-04 17:31:15 +00003772 dot_scroll(rows - 2, -1);
3773 break;
Eric Andersen3f980402001-04-04 17:31:15 +00003774 case 4: // ctrl-D scroll down half screen
3775 dot_scroll((rows - 2) / 2, 1);
3776 break;
3777 case 5: // ctrl-E scroll down one line
3778 dot_scroll(1, 1);
3779 break;
3780 case 6: // ctrl-F scroll down full screen
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003781 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
Eric Andersen3f980402001-04-04 17:31:15 +00003782 dot_scroll(rows - 2, 1);
3783 break;
3784 case 7: // ctrl-G show current status
Paul Fox8552aec2005-09-16 12:20:05 +00003785 last_status_cksum = 0; // force status update
Eric Andersen3f980402001-04-04 17:31:15 +00003786 break;
3787 case 'h': // h- move left
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003788 case KEYCODE_LEFT: // cursor key Left
Paul Foxd13b90b2005-07-18 22:17:25 +00003789 case 8: // ctrl-H- move left (This may be ERASE char)
Denis Vlasenko2a51af22007-03-21 22:31:24 +00003790 case 0x7f: // DEL- move left (This may be ERASE char)
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003791 do {
3792 dot_left();
3793 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003794 break;
3795 case 10: // Newline ^J
3796 case 'j': // j- goto next line, same col
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003797 case KEYCODE_DOWN: // cursor key Down
Ron Yorstonac04eb32021-06-16 14:46:01 +01003798 case 13: // Carriage Return ^M
3799 case '+': // +- goto next line
3800 q = dot;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003801 do {
Ron Yorstonac04eb32021-06-16 14:46:01 +01003802 p = next_line(q);
3803 if (p == end_line(q)) {
3804 indicate_error();
3805 goto dc1;
3806 }
3807 q = p;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003808 } while (--cmdcnt > 0);
Ron Yorstonac04eb32021-06-16 14:46:01 +01003809 dot = q;
3810 if (c == 13 || c == '+') {
3811 dot_skip_over_ws();
3812 } else {
3813 // try to stay in saved column
3814 dot = cindex == C_END ? end_line(dot) : move_to_col(dot, cindex);
3815 keep_index = TRUE;
3816 }
Eric Andersen3f980402001-04-04 17:31:15 +00003817 break;
3818 case 12: // ctrl-L force redraw whole screen
Eric Andersen1c0d3112001-04-16 15:46:44 +00003819 case 18: // ctrl-R force redraw
Ron Yorston55279672016-04-26 15:23:38 +01003820 redraw(TRUE); // this will redraw the entire display
Eric Andersen3f980402001-04-04 17:31:15 +00003821 break;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02003822 case 21: // ctrl-U scroll up half screen
Eric Andersen3f980402001-04-04 17:31:15 +00003823 dot_scroll((rows - 2) / 2, -1);
3824 break;
3825 case 25: // ctrl-Y scroll up one line
3826 dot_scroll(1, -1);
3827 break;
Eric Andersen822c3832001-05-07 17:37:43 +00003828 case 27: // esc
Eric Andersen3f980402001-04-04 17:31:15 +00003829 if (cmd_mode == 0)
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003830 indicate_error();
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02003831 cmd_mode = 0; // stop inserting
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003832 undo_queue_commit();
Eric Andersen3f980402001-04-04 17:31:15 +00003833 end_cmd_q();
Paul Fox8552aec2005-09-16 12:20:05 +00003834 last_status_cksum = 0; // force status update
Eric Andersen3f980402001-04-04 17:31:15 +00003835 break;
3836 case ' ': // move right
3837 case 'l': // move right
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003838 case KEYCODE_RIGHT: // Cursor Key Right
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003839 do {
3840 dot_right();
3841 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003842 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003843#if ENABLE_FEATURE_VI_YANKMARK
Eric Andersen3f980402001-04-04 17:31:15 +00003844 case '"': // "- name a register to use for Delete/Yank
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003845 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3846 if ((unsigned)c1 <= 25) { // a-z?
3847 YDreg = c1;
Eric Andersen3f980402001-04-04 17:31:15 +00003848 } else {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003849 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003850 }
3851 break;
3852 case '\'': // '- goto a specific mark
Denys Vlasenko61fcc8c2016-09-28 16:23:05 +02003853 c1 = (get_one_char() | 0x20);
3854 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3855 c1 = (c1 - 'a');
Eric Andersen3f980402001-04-04 17:31:15 +00003856 // get the b-o-l
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003857 q = mark[c1];
Eric Andersen3f980402001-04-04 17:31:15 +00003858 if (text <= q && q < end) {
3859 dot = q;
3860 dot_begin(); // go to B-o-l
3861 dot_skip_over_ws();
Ron Yorston038d4002021-06-16 14:47:00 +01003862 } else {
3863 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003864 }
3865 } else if (c1 == '\'') { // goto previous context
3866 dot = swap_context(dot); // swap current and previous context
3867 dot_begin(); // go to B-o-l
3868 dot_skip_over_ws();
Ron Yorston74d565f2021-04-15 12:04:45 +01003869#if ENABLE_FEATURE_VI_YANKMARK
3870 orig_dot = dot; // this doesn't update stored contexts
3871#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003872 } else {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003873 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003874 }
3875 break;
3876 case 'm': // m- Mark a line
3877 // this is really stupid. If there are any inserts or deletes
3878 // between text[0] and dot then this mark will not point to the
3879 // correct location! It could be off by many lines!
3880 // Well..., at least its quick and dirty.
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003881 c1 = (get_one_char() | 0x20) - 'a';
3882 if ((unsigned)c1 <= 25) { // a-z?
Eric Andersen3f980402001-04-04 17:31:15 +00003883 // remember the line
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003884 mark[c1] = dot;
Eric Andersen3f980402001-04-04 17:31:15 +00003885 } else {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003886 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003887 }
3888 break;
3889 case 'P': // P- Put register before
3890 case 'p': // p- put register after
3891 p = reg[YDreg];
Denis Vlasenko00d84172008-11-24 07:34:42 +00003892 if (p == NULL) {
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003893 status_line_bold("Nothing in register %c", what_reg());
Eric Andersen3f980402001-04-04 17:31:15 +00003894 break;
3895 }
Ron Yorston951c6de2021-04-10 11:17:38 +01003896 cnt = 0;
Ron Yorstonf4a99082021-04-06 13:44:05 +01003897 i = cmdcnt ?: 1;
3898 // are we putting whole lines or strings
Ron Yorston25d25922021-03-25 14:23:36 +00003899 if (regtype[YDreg] == WHOLE) {
Eric Andersen3f980402001-04-04 17:31:15 +00003900 if (c == 'P') {
3901 dot_begin(); // putting lines- Put above
3902 }
Ron Yorston951c6de2021-04-10 11:17:38 +01003903 else /* if ( c == 'p') */ {
Eric Andersen3f980402001-04-04 17:31:15 +00003904 // are we putting after very last line?
3905 if (end_line(dot) == (end - 1)) {
3906 dot = end; // force dot to end of text[]
3907 } else {
3908 dot_next(); // next line, then put before
3909 }
3910 }
3911 } else {
3912 if (c == 'p')
3913 dot_right(); // move to right, can move to NL
Ron Yorston951c6de2021-04-10 11:17:38 +01003914 // how far to move cursor if register doesn't have a NL
3915 if (strchr(p, '\n') == NULL)
Ron Yorstonf4a99082021-04-06 13:44:05 +01003916 cnt = i * strlen(p) - 1;
Eric Andersen3f980402001-04-04 17:31:15 +00003917 }
Ron Yorston951c6de2021-04-10 11:17:38 +01003918 do {
3919 // dot is adjusted if text[] is reallocated so we don't have to
3920 string_insert(dot, p, allow_undo); // insert the string
3921# if ENABLE_FEATURE_VI_UNDO
3922 allow_undo = ALLOW_UNDO_CHAIN;
3923# endif
3924 } while (--cmdcnt > 0);
3925 dot += cnt;
Ron Yorstond95f89e2021-05-20 08:26:47 +01003926 dot_skip_over_ws();
Ron Yorstonf4a99082021-04-06 13:44:05 +01003927# if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS
3928 yank_status("Put", p, i);
3929# endif
Eric Andersen3f980402001-04-04 17:31:15 +00003930 end_cmd_q(); // stop adding to q
3931 break;
Eric Andersen3f980402001-04-04 17:31:15 +00003932 case 'U': // U- Undo; replace current line with original version
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003933 if (reg[Ureg] != NULL) {
Eric Andersen3f980402001-04-04 17:31:15 +00003934 p = begin_line(dot);
3935 q = end_line(dot);
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003936 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3937 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
Eric Andersen3f980402001-04-04 17:31:15 +00003938 dot = p;
3939 dot_skip_over_ws();
Ron Yorstonf4a99082021-04-06 13:44:05 +01003940# if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS
3941 yank_status("Undo", reg[Ureg], 1);
3942# endif
Eric Andersen3f980402001-04-04 17:31:15 +00003943 }
3944 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003945#endif /* FEATURE_VI_YANKMARK */
Andrew Fuller4d8ddb82015-05-03 18:18:25 +02003946#if ENABLE_FEATURE_VI_UNDO
3947 case 'u': // u- undo last operation
3948 undo_pop();
3949 break;
3950#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003951 case '$': // $- goto end of line
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003952 case KEYCODE_END: // Cursor Key End
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003953 for (;;) {
3954 dot = end_line(dot);
Denys Vlasenko1fd71292011-11-28 04:55:48 +01003955 if (--cmdcnt <= 0)
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003956 break;
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01003957 dot_next();
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01003958 }
Ron Yorston50a2db72021-03-28 13:20:01 +01003959 cindex = C_END;
3960 keep_index = TRUE;
Eric Andersen3f980402001-04-04 17:31:15 +00003961 break;
3962 case '%': // %- find matching char of pair () [] {}
3963 for (q = dot; q < end && *q != '\n'; q++) {
3964 if (strchr("()[]{}", *q) != NULL) {
3965 // we found half of a pair
3966 p = find_pair(q, *q);
3967 if (p == NULL) {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003968 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003969 } else {
3970 dot = p;
3971 }
3972 break;
3973 }
3974 }
3975 if (*q == '\n')
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003976 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003977 break;
3978 case 'f': // f- forward to a user specified char
Ron Yorston15f4ac32021-03-28 17:15:30 +01003979 case 'F': // F- backward to a user specified char
3980 case 't': // t- move to char prior to next x
3981 case 'T': // T- move to char after previous x
3982 last_search_char = get_one_char(); // get the search char
3983 last_search_cmd = c;
3984 // fall through
3985 case ';': // ;- look at rest of line for last search char
3986 case ',': // ,- repeat latest search in opposite direction
3987 dot_to_char(c != ',' ? last_search_cmd : last_search_cmd ^ 0x20);
Eric Andersen3f980402001-04-04 17:31:15 +00003988 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003989#if ENABLE_FEATURE_VI_DOT_CMD
Eric Andersen3f980402001-04-04 17:31:15 +00003990 case '.': // .- repeat the last modifying command
3991 // Stuff the last_modifying_cmd back into stdin
3992 // and let it be re-executed.
Denys Vlasenko2a576082019-04-01 16:15:51 +02003993 if (lmc_len != 0) {
Ron Yorstona5445022021-04-06 16:48:07 +01003994 if (cmdcnt) // update saved count if current count is non-zero
3995 dotcnt = cmdcnt;
3996 last_modifying_cmd[lmc_len] = '\0';
3997 ioq = ioq_start = xasprintf("%u%s", dotcnt, last_modifying_cmd);
Eric Andersen3f980402001-04-04 17:31:15 +00003998 }
3999 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004000#endif
4001#if ENABLE_FEATURE_VI_SEARCH
Ron Yorston7ce0e752021-04-06 13:39:48 +01004002 case 'N': // N- backward search for last pattern
4003 dir = last_search_pattern[0] == '/' ? BACK : FORWARD;
4004 goto dc4; // now search for pattern
4005 break;
4006 case '?': // ?- backward search for a pattern
4007 case '/': // /- forward search for a pattern
Eric Andersen3f980402001-04-04 17:31:15 +00004008 buf[0] = c;
4009 buf[1] = '\0';
4010 q = get_input_line(buf); // get input line- use "status line"
Ron Yorston7ce0e752021-04-06 13:39:48 +01004011 if (!q[0]) // user changed mind and erased the "/"- do nothing
4012 break;
4013 if (!q[1]) { // if no pat re-use old pat
Paul Fox4917c112008-03-05 16:44:02 +00004014 if (last_search_pattern[0])
Denis Vlasenkoc3a9dc82008-10-29 00:58:04 +00004015 last_search_pattern[0] = c;
Ron Yorston7ce0e752021-04-06 13:39:48 +01004016 } else { // strlen(q) > 1: new pat- save it and find
Aaron Lehmanna170e1c2002-11-28 11:27:31 +00004017 free(last_search_pattern);
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004018 last_search_pattern = xstrdup(q);
Eric Andersen3f980402001-04-04 17:31:15 +00004019 }
Ron Yorston7ce0e752021-04-06 13:39:48 +01004020 // fall through
Eric Andersen3f980402001-04-04 17:31:15 +00004021 case 'n': // n- repeat search for last pattern
4022 // search rest of text[] starting at next char
Ron Yorston7ce0e752021-04-06 13:39:48 +01004023 // if search fails "dot" is unchanged
4024 dir = last_search_pattern[0] == '/' ? FORWARD : BACK;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004025 dc4:
Ron Yorston7ce0e752021-04-06 13:39:48 +01004026 if (last_search_pattern[1] == '\0') {
4027 status_line_bold("No previous search");
4028 break;
4029 }
4030 do {
4031 q = char_search(dot + dir, last_search_pattern + 1,
4032 (dir << 1) | FULL);
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004033 if (q != NULL) {
4034 dot = q; // good search, update "dot"
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004035 } else {
Ron Yorston7ce0e752021-04-06 13:39:48 +01004036 // no pattern found between "dot" and top/bottom of file
4037 // continue from other end of file
4038 const char *msg;
4039 q = char_search(dir == FORWARD ? text : end - 1,
4040 last_search_pattern + 1, (dir << 1) | FULL);
4041 if (q != NULL) { // found something
4042 dot = q; // found new pattern- goto it
4043 msg = "search hit %s, continuing at %s";
4044 } else { // pattern is nowhere in file
4045 cmdcnt = 0; // force exit from loop
4046 msg = "Pattern not found";
4047 }
4048 if (dir == FORWARD)
4049 status_line_bold(msg, "BOTTOM", "TOP");
4050 else
4051 status_line_bold(msg, "TOP", "BOTTOM");
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004052 }
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004053 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00004054 break;
4055 case '{': // {- move backward paragraph
Eric Andersen3f980402001-04-04 17:31:15 +00004056 case '}': // }- move forward paragraph
Ron Yorston033fa3d2021-04-15 12:02:11 +01004057 dir = c == '}' ? FORWARD : BACK;
Ron Yorstond3b74822021-03-28 13:22:11 +01004058 do {
Ron Yorston033fa3d2021-04-15 12:02:11 +01004059 int skip = TRUE; // initially skip consecutive empty lines
4060 while (dir == FORWARD ? dot < end - 1 : dot > text) {
4061 if (*dot == '\n' && dot[dir] == '\n') {
4062 if (!skip) {
4063 if (dir == FORWARD)
4064 ++dot; // move to next blank line
4065 goto dc2;
4066 }
4067 }
4068 else {
4069 skip = FALSE;
4070 }
Ron Yorston8b571bd2021-03-28 13:22:43 +01004071 dot += dir;
4072 }
Ron Yorston033fa3d2021-04-15 12:02:11 +01004073 goto dc6; // end of file
4074 dc2: continue;
Ron Yorstond3b74822021-03-28 13:22:11 +01004075 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00004076 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004077#endif /* FEATURE_VI_SEARCH */
Maninder Singh97c64912015-05-25 13:46:36 +02004078 case '0': // 0- goto beginning of line
Eric Andersenc7bda1c2004-03-15 08:29:22 +00004079 case '1': // 1-
4080 case '2': // 2-
4081 case '3': // 3-
4082 case '4': // 4-
4083 case '5': // 5-
4084 case '6': // 6-
4085 case '7': // 7-
4086 case '8': // 8-
4087 case '9': // 9-
Eric Andersen3f980402001-04-04 17:31:15 +00004088 if (c == '0' && cmdcnt < 1) {
4089 dot_begin(); // this was a standalone zero
4090 } else {
4091 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
4092 }
4093 break;
4094 case ':': // :- the colon mode commands
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004095 p = get_input_line(":"); // get input line- use "status line"
Eric Andersen3f980402001-04-04 17:31:15 +00004096 colon(p); // execute the command
Eric Andersen3f980402001-04-04 17:31:15 +00004097 break;
4098 case '<': // <- Left shift something
4099 case '>': // >- Right shift something
4100 cnt = count_lines(text, dot); // remember what line we are on
Ron Yorstonb7b11192021-04-06 13:40:23 +01004101 if (find_range(&p, &q, c) == -1)
Ron Yorston24198f62021-03-30 13:02:32 +01004102 goto dc6;
Eric Andersen3f980402001-04-04 17:31:15 +00004103 i = count_lines(p, q); // # of lines we are shifting
Ron Yorston24effc72021-04-30 12:56:12 +01004104 for (p = begin_line(p); i > 0; i--, p = next_line(p)) {
Eric Andersen3f980402001-04-04 17:31:15 +00004105 if (c == '<') {
Ron Yorston310ef232021-04-17 09:25:11 +01004106 // shift left- remove tab or tabstop spaces
Eric Andersen3f980402001-04-04 17:31:15 +00004107 if (*p == '\t') {
4108 // shrink buffer 1 char
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01004109 text_hole_delete(p, p, allow_undo);
Eric Andersen3f980402001-04-04 17:31:15 +00004110 } else if (*p == ' ') {
4111 // we should be calculating columns, not just SPACE
4112 for (j = 0; *p == ' ' && j < tabstop; j++) {
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01004113 text_hole_delete(p, p, allow_undo);
4114#if ENABLE_FEATURE_VI_UNDO
4115 allow_undo = ALLOW_UNDO_CHAIN;
4116#endif
Eric Andersen3f980402001-04-04 17:31:15 +00004117 }
4118 }
Ron Yorston38e9c8c2021-08-20 08:25:07 +01004119 } else if (/* c == '>' && */ p != end_line(p)) {
4120 // shift right -- add tab or tabstop spaces on non-empty lines
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01004121 char_insert(p, '\t', allow_undo);
Eric Andersen3f980402001-04-04 17:31:15 +00004122 }
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01004123#if ENABLE_FEATURE_VI_UNDO
4124 allow_undo = ALLOW_UNDO_CHAIN;
4125#endif
Eric Andersen3f980402001-04-04 17:31:15 +00004126 }
4127 dot = find_line(cnt); // what line were we on
4128 dot_skip_over_ws();
4129 end_cmd_q(); // stop adding to q
4130 break;
4131 case 'A': // A- append at e-o-l
4132 dot_end(); // go to e-o-l
Bernhard Reutner-Fischera985d302008-02-11 11:44:38 +00004133 //**** fall through to ... 'a'
Eric Andersen3f980402001-04-04 17:31:15 +00004134 case 'a': // a- append after current char
4135 if (*dot != '\n')
4136 dot++;
4137 goto dc_i;
4138 break;
4139 case 'B': // B- back a blank-delimited Word
4140 case 'E': // E- end of a blank-delimited word
4141 case 'W': // W- forward a blank-delimited word
Eric Andersen3f980402001-04-04 17:31:15 +00004142 dir = FORWARD;
4143 if (c == 'B')
4144 dir = BACK;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004145 do {
4146 if (c == 'W' || isspace(dot[dir])) {
4147 dot = skip_thing(dot, 1, dir, S_TO_WS);
4148 dot = skip_thing(dot, 2, dir, S_OVER_WS);
4149 }
4150 if (c != 'W')
4151 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
4152 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00004153 break;
4154 case 'C': // C- Change to e-o-l
4155 case 'D': // D- delete to e-o-l
4156 save_dot = dot;
4157 dot = dollar_line(dot); // move to before NL
4158 // copy text into a register and delete
Ron Yorston25d25922021-03-25 14:23:36 +00004159 dot = yank_delete(save_dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO); // delete to e-o-l
Eric Andersen3f980402001-04-04 17:31:15 +00004160 if (c == 'C')
4161 goto dc_i; // start inserting
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004162#if ENABLE_FEATURE_VI_DOT_CMD
Eric Andersen3f980402001-04-04 17:31:15 +00004163 if (c == 'D')
4164 end_cmd_q(); // stop adding to q
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004165#endif
Eric Andersen3f980402001-04-04 17:31:15 +00004166 break;
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00004167 case 'g': // 'gg' goto a line number (vim) (default: very first line)
Paul Foxb5ee8db2008-02-14 01:17:01 +00004168 c1 = get_one_char();
4169 if (c1 != 'g') {
4170 buf[0] = 'g';
Denys Vlasenkode1996d2016-09-15 13:53:42 +02004171 // c1 < 0 if the key was special. Try "g<up-arrow>"
4172 // TODO: if Unicode?
4173 buf[1] = (c1 >= 0 ? c1 : '*');
Paul Foxb5ee8db2008-02-14 01:17:01 +00004174 buf[2] = '\0';
4175 not_implemented(buf);
Ron Yorstonb50ac072021-07-07 09:23:30 +01004176 cmd_error = TRUE;
Paul Foxb5ee8db2008-02-14 01:17:01 +00004177 break;
4178 }
4179 if (cmdcnt == 0)
4180 cmdcnt = 1;
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02004181 // fall through
Eric Andersen822c3832001-05-07 17:37:43 +00004182 case 'G': // G- goto to a line number (default= E-O-F)
4183 dot = end - 1; // assume E-O-F
Eric Andersen1c0d3112001-04-16 15:46:44 +00004184 if (cmdcnt > 0) {
Eric Andersen822c3832001-05-07 17:37:43 +00004185 dot = find_line(cmdcnt); // what line is #cmdcnt
Eric Andersen1c0d3112001-04-16 15:46:44 +00004186 }
Ron Yorston18871c32021-03-28 13:19:26 +01004187 dot_begin();
Eric Andersen1c0d3112001-04-16 15:46:44 +00004188 dot_skip_over_ws();
4189 break;
Eric Andersen3f980402001-04-04 17:31:15 +00004190 case 'H': // H- goto top line on screen
4191 dot = screenbegin;
4192 if (cmdcnt > (rows - 1)) {
4193 cmdcnt = (rows - 1);
4194 }
Ron Yorstonac04eb32021-06-16 14:46:01 +01004195 while (--cmdcnt > 0) {
4196 dot_next();
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01004197 }
Ron Yorstonac04eb32021-06-16 14:46:01 +01004198 dot_begin();
Eric Andersen3f980402001-04-04 17:31:15 +00004199 dot_skip_over_ws();
4200 break;
4201 case 'I': // I- insert before first non-blank
4202 dot_begin(); // 0
4203 dot_skip_over_ws();
Bernhard Reutner-Fischera985d302008-02-11 11:44:38 +00004204 //**** fall through to ... 'i'
Eric Andersen3f980402001-04-04 17:31:15 +00004205 case 'i': // i- insert before current char
Denis Vlasenko0112ff52008-10-25 23:23:00 +00004206 case KEYCODE_INSERT: // Cursor Key Insert
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004207 dc_i:
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004208 cmd_mode = 1; // start inserting
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004209 undo_queue_commit(); // commit queue when cmd_mode changes
Eric Andersen3f980402001-04-04 17:31:15 +00004210 break;
4211 case 'J': // J- join current and next lines together
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004212 do {
4213 dot_end(); // move to NL
4214 if (dot < end - 1) { // make sure not last char in text[]
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004215#if ENABLE_FEATURE_VI_UNDO
4216 undo_push(dot, 1, UNDO_DEL);
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004217 *dot++ = ' '; // replace NL with space
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004218 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
4219#else
4220 *dot++ = ' ';
Denys Vlasenkoe7430862014-04-03 12:47:48 +02004221 modified_count++;
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004222#endif
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004223 while (isblank(*dot)) { // delete leading WS
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004224 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004225 }
Eric Andersen3f980402001-04-04 17:31:15 +00004226 }
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004227 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00004228 end_cmd_q(); // stop adding to q
4229 break;
4230 case 'L': // L- goto bottom line on screen
4231 dot = end_screen();
4232 if (cmdcnt > (rows - 1)) {
4233 cmdcnt = (rows - 1);
4234 }
Ron Yorstonac04eb32021-06-16 14:46:01 +01004235 while (--cmdcnt > 0) {
4236 dot_prev();
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01004237 }
Eric Andersen3f980402001-04-04 17:31:15 +00004238 dot_begin();
4239 dot_skip_over_ws();
4240 break;
Eric Andersen822c3832001-05-07 17:37:43 +00004241 case 'M': // M- goto middle line on screen
Eric Andersen1c0d3112001-04-16 15:46:44 +00004242 dot = screenbegin;
4243 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
4244 dot = next_line(dot);
Ron Yorston18871c32021-03-28 13:19:26 +01004245 dot_skip_over_ws();
Eric Andersen1c0d3112001-04-16 15:46:44 +00004246 break;
Ron Yorston47c3eaa2021-04-15 12:04:22 +01004247 case 'O': // O- open an empty line above
4248 dot_begin();
Ron Yorston9659a8d2021-05-20 08:27:48 +01004249#if ENABLE_FEATURE_VI_SETOPTS
4250 indentcol = -1;
4251#endif
Ron Yorston47c3eaa2021-04-15 12:04:22 +01004252 goto dc3;
4253 case 'o': // o- open an empty line below
4254 dot_end();
4255 dc3:
4256 dot = char_insert(dot, '\n', ALLOW_UNDO);
4257 if (c == 'O' && !autoindent) {
Ron Yorston9659a8d2021-05-20 08:27:48 +01004258 // done in char_insert() for 'O'+autoindent
Eric Andersen3f980402001-04-04 17:31:15 +00004259 dot_prev();
Eric Andersen3f980402001-04-04 17:31:15 +00004260 }
4261 goto dc_i;
4262 break;
4263 case 'R': // R- continuous Replace char
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004264 dc5:
Eric Andersen3f980402001-04-04 17:31:15 +00004265 cmd_mode = 2;
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004266 undo_queue_commit();
Eric Andersen3f980402001-04-04 17:31:15 +00004267 break;
Denis Vlasenko0112ff52008-10-25 23:23:00 +00004268 case KEYCODE_DELETE:
Denys Vlasenko49acc1a2015-03-12 21:15:34 +01004269 if (dot < end - 1)
Ron Yorston25d25922021-03-25 14:23:36 +00004270 dot = yank_delete(dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO);
Denys Vlasenko49acc1a2015-03-12 21:15:34 +01004271 break;
Eric Andersen3f980402001-04-04 17:31:15 +00004272 case 'X': // X- delete char before dot
4273 case 'x': // x- delete the current char
4274 case 's': // s- substitute the current char
Eric Andersen3f980402001-04-04 17:31:15 +00004275 dir = 0;
4276 if (c == 'X')
4277 dir = -1;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004278 do {
4279 if (dot[dir] != '\n') {
4280 if (c == 'X')
4281 dot--; // delete prev char
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01004282 dot = yank_delete(dot, dot, PARTIAL, YANKDEL, allow_undo); // delete char
4283#if ENABLE_FEATURE_VI_UNDO
4284 allow_undo = ALLOW_UNDO_CHAIN;
4285#endif
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004286 }
4287 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00004288 end_cmd_q(); // stop adding to q
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004289 if (c == 's')
4290 goto dc_i; // start inserting
Eric Andersen3f980402001-04-04 17:31:15 +00004291 break;
4292 case 'Z': // Z- if modified, {write}; exit
4293 // ZZ means to save file (if necessary), then exit
4294 c1 = get_one_char();
4295 if (c1 != 'Z') {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02004296 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00004297 break;
4298 }
Denys Vlasenkoe7430862014-04-03 12:47:48 +02004299 if (modified_count) {
Ron Yorston09172582021-04-25 11:52:55 +01004300 if (ENABLE_FEATURE_VI_READONLY && readonly_mode && current_filename) {
Denys Vlasenko778794d2013-01-22 10:13:52 +01004301 status_line_bold("'%s' is read only", current_filename);
Denis Vlasenko92758142006-10-03 19:56:34 +00004302 break;
Paul Foxf0305b72006-03-28 14:18:21 +00004303 }
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00004304 cnt = file_write(current_filename, text, end - 1);
Paul Fox61e45db2005-10-09 14:43:22 +00004305 if (cnt < 0) {
4306 if (cnt == -1)
Denys Vlasenko6f97b302017-09-29 18:17:25 +02004307 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
Paul Fox61e45db2005-10-09 14:43:22 +00004308 } else if (cnt == (end - 1 - text + 1)) {
Eric Andersen3f980402001-04-04 17:31:15 +00004309 editing = 0;
4310 }
4311 } else {
4312 editing = 0;
4313 }
Ron Yorstonb9aaa372021-04-25 11:53:23 +01004314 // are there other files to edit?
4315 j = cmdline_filecnt - optind - 1;
4316 if (editing == 0 && j > 0) {
4317 editing = 1;
4318 modified_count = 0;
4319 last_modified_count = -1;
4320 status_line_bold("%u more file(s) to edit", j);
4321 }
Eric Andersen3f980402001-04-04 17:31:15 +00004322 break;
4323 case '^': // ^- move to first non-blank on line
4324 dot_begin();
4325 dot_skip_over_ws();
4326 break;
4327 case 'b': // b- back a word
4328 case 'e': // e- end of word
Eric Andersen3f980402001-04-04 17:31:15 +00004329 dir = FORWARD;
4330 if (c == 'b')
4331 dir = BACK;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004332 do {
4333 if ((dot + dir) < text || (dot + dir) > end - 1)
4334 break;
4335 dot += dir;
4336 if (isspace(*dot)) {
4337 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
4338 }
4339 if (isalnum(*dot) || *dot == '_') {
4340 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
4341 } else if (ispunct(*dot)) {
4342 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
4343 }
4344 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00004345 break;
4346 case 'c': // c- change something
4347 case 'd': // d- delete something
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004348#if ENABLE_FEATURE_VI_YANKMARK
Eric Andersen3f980402001-04-04 17:31:15 +00004349 case 'y': // y- yank something
4350 case 'Y': // Y- Yank a line
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004351#endif
Denis Vlasenkod44c1532008-10-14 12:59:42 +00004352 {
Ron Yorstonb7b11192021-04-06 13:40:23 +01004353 int yf = YANKDEL; // assume either "c" or "d"
4354 int buftype;
Ron Yorston25d25922021-03-25 14:23:36 +00004355#if ENABLE_FEATURE_VI_YANKMARK
Ron Yorstonf4a99082021-04-06 13:44:05 +01004356# if ENABLE_FEATURE_VI_VERBOSE_STATUS
Ron Yorston25d25922021-03-25 14:23:36 +00004357 char *savereg = reg[YDreg];
Ron Yorstonf4a99082021-04-06 13:44:05 +01004358# endif
Eric Andersen3f980402001-04-04 17:31:15 +00004359 if (c == 'y' || c == 'Y')
4360 yf = YANKONLY;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004361#endif
Paul Foxc51fc7b2008-03-06 01:34:23 +00004362 // determine range, and whether it spans lines
Ron Yorstonb7b11192021-04-06 13:40:23 +01004363 buftype = find_range(&p, &q, c);
4364 if (buftype == -1) // invalid range
Ron Yorston25d25922021-03-25 14:23:36 +00004365 goto dc6;
Ron Yorston24effc72021-04-30 12:56:12 +01004366 if (buftype == WHOLE) {
4367 save_dot = p; // final cursor position is start of range
4368 p = begin_line(p);
4369 q = end_line(q);
4370 }
Ron Yorston25d25922021-03-25 14:23:36 +00004371 dot = yank_delete(p, q, buftype, yf, ALLOW_UNDO); // delete word
4372 if (buftype == WHOLE) {
Eric Andersen1c0d3112001-04-16 15:46:44 +00004373 if (c == 'c') {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004374 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
Eric Andersen1c0d3112001-04-16 15:46:44 +00004375 // on the last line of file don't move to prev line
Ron Yorston25d25922021-03-25 14:23:36 +00004376 if (dot != (end-1)) {
Eric Andersen1c0d3112001-04-16 15:46:44 +00004377 dot_prev();
4378 }
Ron Yorstond95f89e2021-05-20 08:26:47 +01004379 } else if (c == 'd') {
4380 dot_begin();
4381 dot_skip_over_ws();
Ron Yorston24effc72021-04-30 12:56:12 +01004382 } else {
Ron Yorstond6e653d2021-04-15 12:03:22 +01004383 dot = save_dot;
4384 }
Eric Andersen3f980402001-04-04 17:31:15 +00004385 }
Ron Yorston25d25922021-03-25 14:23:36 +00004386 // if CHANGING, not deleting, start inserting after the delete
4387 if (c == 'c') {
Ron Yorston25d25922021-03-25 14:23:36 +00004388 goto dc_i; // start inserting
4389 }
Ron Yorstonf4a99082021-04-06 13:44:05 +01004390#if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS
Ron Yorston25d25922021-03-25 14:23:36 +00004391 // only update status if a yank has actually happened
Ron Yorstonf4a99082021-04-06 13:44:05 +01004392 if (reg[YDreg] != savereg)
4393 yank_status(c == 'd' ? "Delete" : "Yank", reg[YDreg], 1);
Ron Yorston25d25922021-03-25 14:23:36 +00004394#endif
4395 dc6:
4396 end_cmd_q(); // stop adding to q
Eric Andersen3f980402001-04-04 17:31:15 +00004397 break;
Denis Vlasenkod44c1532008-10-14 12:59:42 +00004398 }
Eric Andersen3f980402001-04-04 17:31:15 +00004399 case 'k': // k- goto prev line, same col
Denis Vlasenko0112ff52008-10-25 23:23:00 +00004400 case KEYCODE_UP: // cursor key Up
Ron Yorstonac04eb32021-06-16 14:46:01 +01004401 case '-': // -- goto prev line
4402 q = dot;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004403 do {
Ron Yorstonac04eb32021-06-16 14:46:01 +01004404 p = prev_line(q);
4405 if (p == begin_line(q)) {
4406 indicate_error();
4407 goto dc1;
4408 }
4409 q = p;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004410 } while (--cmdcnt > 0);
Ron Yorstonac04eb32021-06-16 14:46:01 +01004411 dot = q;
4412 if (c == '-') {
4413 dot_skip_over_ws();
4414 } else {
4415 // try to stay in saved column
4416 dot = cindex == C_END ? end_line(dot) : move_to_col(dot, cindex);
4417 keep_index = TRUE;
4418 }
Eric Andersen3f980402001-04-04 17:31:15 +00004419 break;
4420 case 'r': // r- replace the current char with user input
4421 c1 = get_one_char(); // get the replacement char
Ron Yorstonfe765692021-04-10 11:16:47 +01004422 if (c1 != 27) {
4423 if (end_line(dot) - dot < (cmdcnt ?: 1)) {
4424 indicate_error();
4425 goto dc6;
4426 }
4427 do {
4428 dot = text_hole_delete(dot, dot, allow_undo);
4429#if ENABLE_FEATURE_VI_UNDO
4430 allow_undo = ALLOW_UNDO_CHAIN;
4431#endif
4432 dot = char_insert(dot, c1, allow_undo);
4433 } while (--cmdcnt > 0);
Ron Yorstondf4e3af2019-02-03 14:01:58 +00004434 dot_left();
Eric Andersen3f980402001-04-04 17:31:15 +00004435 }
4436 end_cmd_q(); // stop adding to q
4437 break;
4438 case 'w': // w- forward a word
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004439 do {
4440 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
4441 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
4442 } else if (ispunct(*dot)) { // we are on PUNCT
4443 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
4444 }
4445 if (dot < end - 1)
4446 dot++; // move over word
4447 if (isspace(*dot)) {
4448 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
4449 }
4450 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00004451 break;
4452 case 'z': // z-
4453 c1 = get_one_char(); // get the replacement char
4454 cnt = 0;
4455 if (c1 == '.')
4456 cnt = (rows - 2) / 2; // put dot at center
4457 if (c1 == '-')
4458 cnt = rows - 2; // put dot at bottom
4459 screenbegin = begin_line(dot); // start dot at top
4460 dot_scroll(cnt, -1);
4461 break;
4462 case '|': // |- move to column "cmdcnt"
4463 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
4464 break;
4465 case '~': // ~- flip the case of letters a-z -> A-Z
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004466 do {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004467#if ENABLE_FEATURE_VI_UNDO
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01004468 if (isalpha(*dot)) {
4469 undo_push(dot, 1, undo_del);
4470 *dot = islower(*dot) ? toupper(*dot) : tolower(*dot);
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004471 undo_push(dot, 1, UNDO_INS_CHAIN);
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01004472 undo_del = UNDO_DEL_CHAIN;
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004473 }
4474#else
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004475 if (islower(*dot)) {
4476 *dot = toupper(*dot);
Denys Vlasenkoe7430862014-04-03 12:47:48 +02004477 modified_count++;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004478 } else if (isupper(*dot)) {
4479 *dot = tolower(*dot);
Denys Vlasenkoe7430862014-04-03 12:47:48 +02004480 modified_count++;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004481 }
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004482#endif
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004483 dot_right();
4484 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00004485 end_cmd_q(); // stop adding to q
4486 break;
4487 //----- The Cursor and Function Keys -----------------------------
Denis Vlasenko0112ff52008-10-25 23:23:00 +00004488 case KEYCODE_HOME: // Cursor Key Home
Eric Andersen3f980402001-04-04 17:31:15 +00004489 dot_begin();
4490 break;
4491 // The Fn keys could point to do_macro which could translate them
Denis Vlasenko0112ff52008-10-25 23:23:00 +00004492#if 0
4493 case KEYCODE_FUN1: // Function Key F1
4494 case KEYCODE_FUN2: // Function Key F2
4495 case KEYCODE_FUN3: // Function Key F3
4496 case KEYCODE_FUN4: // Function Key F4
4497 case KEYCODE_FUN5: // Function Key F5
4498 case KEYCODE_FUN6: // Function Key F6
4499 case KEYCODE_FUN7: // Function Key F7
4500 case KEYCODE_FUN8: // Function Key F8
4501 case KEYCODE_FUN9: // Function Key F9
4502 case KEYCODE_FUN10: // Function Key F10
4503 case KEYCODE_FUN11: // Function Key F11
4504 case KEYCODE_FUN12: // Function Key F12
Eric Andersen3f980402001-04-04 17:31:15 +00004505 break;
Denis Vlasenko0112ff52008-10-25 23:23:00 +00004506#endif
Eric Andersen3f980402001-04-04 17:31:15 +00004507 }
4508
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004509 dc1:
Eric Andersen3f980402001-04-04 17:31:15 +00004510 // if text[] just became empty, add back an empty line
4511 if (end == text) {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004512 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
Eric Andersen3f980402001-04-04 17:31:15 +00004513 dot = text;
4514 }
4515 // it is OK for dot to exactly equal to end, otherwise check dot validity
4516 if (dot != end) {
4517 dot = bound_dot(dot); // make sure "dot" is valid
4518 }
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004519#if ENABLE_FEATURE_VI_YANKMARK
Ron Yorston74d565f2021-04-15 12:04:45 +01004520 if (dot != orig_dot)
4521 check_context(c); // update the current context
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004522#endif
Eric Andersen3f980402001-04-04 17:31:15 +00004523
4524 if (!isdigit(c))
4525 cmdcnt = 0; // cmd was not a number, reset cmdcnt
4526 cnt = dot - begin_line(dot);
4527 // Try to stay off of the Newline
4528 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4529 dot--;
4530}
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004531
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004532// NB! the CRASHME code is unmaintained, and doesn't currently build
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004533#if ENABLE_FEATURE_VI_CRASHME
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004534static int totalcmds = 0;
4535static int Mp = 85; // Movement command Probability
4536static int Np = 90; // Non-movement command Probability
4537static int Dp = 96; // Delete command Probability
4538static int Ip = 97; // Insert command Probability
4539static int Yp = 98; // Yank command Probability
4540static int Pp = 99; // Put command Probability
4541static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004542static const char chars[20] = "\t012345 abcdABCD-=.$";
4543static const char *const words[20] = {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004544 "this", "is", "a", "test",
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004545 "broadcast", "the", "emergency", "of",
4546 "system", "quick", "brown", "fox",
4547 "jumped", "over", "lazy", "dogs",
4548 "back", "January", "Febuary", "March"
4549};
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004550static const char *const lines[20] = {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004551 "You should have received a copy of the GNU General Public License\n",
4552 "char c, cm, *cmd, *cmd1;\n",
4553 "generate a command by percentages\n",
4554 "Numbers may be typed as a prefix to some commands.\n",
4555 "Quit, discarding changes!\n",
4556 "Forced write, if permission originally not valid.\n",
4557 "In general, any ex or ed command (such as substitute or delete).\n",
4558 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4559 "Please get w/ me and I will go over it with you.\n",
4560 "The following is a list of scheduled, committed changes.\n",
4561 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4562 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4563 "Any question about transactions please contact Sterling Huxley.\n",
4564 "I will try to get back to you by Friday, December 31.\n",
4565 "This Change will be implemented on Friday.\n",
4566 "Let me know if you have problems accessing this;\n",
4567 "Sterling Huxley recently added you to the access list.\n",
4568 "Would you like to go to lunch?\n",
4569 "The last command will be automatically run.\n",
4570 "This is too much english for a computer geek.\n",
4571};
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004572static char *multilines[20] = {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004573 "You should have received a copy of the GNU General Public License\n",
4574 "char c, cm, *cmd, *cmd1;\n",
4575 "generate a command by percentages\n",
4576 "Numbers may be typed as a prefix to some commands.\n",
4577 "Quit, discarding changes!\n",
4578 "Forced write, if permission originally not valid.\n",
4579 "In general, any ex or ed command (such as substitute or delete).\n",
4580 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4581 "Please get w/ me and I will go over it with you.\n",
4582 "The following is a list of scheduled, committed changes.\n",
4583 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4584 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4585 "Any question about transactions please contact Sterling Huxley.\n",
4586 "I will try to get back to you by Friday, December 31.\n",
4587 "This Change will be implemented on Friday.\n",
4588 "Let me know if you have problems accessing this;\n",
4589 "Sterling Huxley recently added you to the access list.\n",
4590 "Would you like to go to lunch?\n",
4591 "The last command will be automatically run.\n",
4592 "This is too much english for a computer geek.\n",
4593};
4594
4595// create a random command to execute
4596static void crash_dummy()
4597{
4598 static int sleeptime; // how long to pause between commands
4599 char c, cm, *cmd, *cmd1;
4600 int i, cnt, thing, rbi, startrbi, percent;
4601
4602 // "dot" movement commands
4603 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4604
4605 // is there already a command running?
Denys Vlasenko020f4062009-05-17 16:44:54 +02004606 if (readbuffer[0] > 0)
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004607 goto cd1;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004608 cd0:
Denys Vlasenko020f4062009-05-17 16:44:54 +02004609 readbuffer[0] = 'X';
4610 startrbi = rbi = 1;
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004611 sleeptime = 0; // how long to pause between commands
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004612 memset(readbuffer, '\0', sizeof(readbuffer));
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004613 // generate a command by percentages
4614 percent = (int) lrand48() % 100; // get a number from 0-99
4615 if (percent < Mp) { // Movement commands
4616 // available commands
4617 cmd = cmd1;
4618 M++;
4619 } else if (percent < Np) { // non-movement commands
4620 cmd = "mz<>\'\""; // available commands
4621 N++;
4622 } else if (percent < Dp) { // Delete commands
4623 cmd = "dx"; // available commands
4624 D++;
4625 } else if (percent < Ip) { // Inset commands
4626 cmd = "iIaAsrJ"; // available commands
4627 I++;
4628 } else if (percent < Yp) { // Yank commands
4629 cmd = "yY"; // available commands
4630 Y++;
4631 } else if (percent < Pp) { // Put commands
4632 cmd = "pP"; // available commands
4633 P++;
4634 } else {
4635 // We do not know how to handle this command, try again
4636 U++;
4637 goto cd0;
4638 }
4639 // randomly pick one of the available cmds from "cmd[]"
4640 i = (int) lrand48() % strlen(cmd);
4641 cm = cmd[i];
4642 if (strchr(":\024", cm))
4643 goto cd0; // dont allow colon or ctrl-T commands
4644 readbuffer[rbi++] = cm; // put cmd into input buffer
4645
4646 // now we have the command-
4647 // there are 1, 2, and multi char commands
4648 // find out which and generate the rest of command as necessary
4649 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4650 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4651 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4652 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4653 }
4654 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4655 c = cmd1[thing];
4656 readbuffer[rbi++] = c; // add movement to input buffer
4657 }
4658 if (strchr("iIaAsc", cm)) { // multi-char commands
4659 if (cm == 'c') {
4660 // change some thing
4661 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4662 c = cmd1[thing];
4663 readbuffer[rbi++] = c; // add movement to input buffer
4664 }
4665 thing = (int) lrand48() % 4; // what thing to insert
4666 cnt = (int) lrand48() % 10; // how many to insert
4667 for (i = 0; i < cnt; i++) {
4668 if (thing == 0) { // insert chars
4669 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4670 } else if (thing == 1) { // insert words
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004671 strcat(readbuffer, words[(int) lrand48() % 20]);
4672 strcat(readbuffer, " ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004673 sleeptime = 0; // how fast to type
4674 } else if (thing == 2) { // insert lines
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004675 strcat(readbuffer, lines[(int) lrand48() % 20]);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004676 sleeptime = 0; // how fast to type
4677 } else { // insert multi-lines
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004678 strcat(readbuffer, multilines[(int) lrand48() % 20]);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004679 sleeptime = 0; // how fast to type
4680 }
4681 }
Denys Vlasenko8187e012017-09-13 22:48:30 +02004682 strcat(readbuffer, ESC);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004683 }
Denys Vlasenko020f4062009-05-17 16:44:54 +02004684 readbuffer[0] = strlen(readbuffer + 1);
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004685 cd1:
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004686 totalcmds++;
4687 if (sleeptime > 0)
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004688 mysleep(sleeptime); // sleep 1/100 sec
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004689}
4690
4691// test to see if there are any errors
4692static void crash_test()
4693{
4694 static time_t oldtim;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004695
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004696 time_t tim;
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004697 char d[2], msg[80];
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004698
4699 msg[0] = '\0';
4700 if (end < text) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004701 strcat(msg, "end<text ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004702 }
4703 if (end > textend) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004704 strcat(msg, "end>textend ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004705 }
4706 if (dot < text) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004707 strcat(msg, "dot<text ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004708 }
4709 if (dot > end) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004710 strcat(msg, "dot>end ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004711 }
4712 if (screenbegin < text) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004713 strcat(msg, "screenbegin<text ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004714 }
4715 if (screenbegin > end - 1) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004716 strcat(msg, "screenbegin>end-1 ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004717 }
4718
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004719 if (msg[0]) {
Glenn L McGrath7127b582002-12-03 21:48:15 +00004720 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
Denys Vlasenko04b52892012-06-11 13:51:38 +02004721 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
Denys Vlasenko8131eea2009-11-02 14:19:51 +01004722 fflush_all();
Bernhard Reutner-Fischer5e25ddb2008-05-19 09:48:17 +00004723 while (safe_read(STDIN_FILENO, d, 1) > 0) {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004724 if (d[0] == '\n' || d[0] == '\r')
4725 break;
4726 }
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004727 }
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004728 tim = time(NULL);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004729 if (tim >= (oldtim + 3)) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004730 sprintf(status_buffer,
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004731 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4732 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4733 oldtim = tim;
4734 }
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004735}
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004736#endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004737
Ron Yorstonf07772f2021-08-19 16:59:36 +01004738#if ENABLE_FEATURE_VI_COLON
4739static void run_cmds(char *p)
4740{
4741 while (p) {
4742 char *q = p;
4743 p = strchr(q, '\n');
4744 if (p)
4745 while (*p == '\n')
4746 *p++ = '\0';
4747 if (strlen(q) < MAX_INPUT_LEN)
4748 colon(q);
4749 }
4750}
4751#endif
4752
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004753static void edit_file(char *fn)
4754{
4755#if ENABLE_FEATURE_VI_YANKMARK
4756#define cur_line edit_file__cur_line
4757#endif
4758 int c;
4759#if ENABLE_FEATURE_VI_USE_SIGNALS
4760 int sig;
4761#endif
4762
4763 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
4764 rawmode();
4765 rows = 24;
4766 columns = 80;
4767 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
4768#if ENABLE_FEATURE_VI_ASK_TERMINAL
4769 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
4770 uint64_t k;
4771 write1(ESC"[999;999H" ESC"[6n");
4772 fflush_all();
Denys Vlasenko12566e72022-01-17 03:02:40 +01004773 k = safe_read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004774 if ((int32_t)k == KEYCODE_CURSOR_POS) {
4775 uint32_t rc = (k >> 32);
4776 columns = (rc & 0x7fff);
4777 if (columns > MAX_SCR_COLS)
4778 columns = MAX_SCR_COLS;
4779 rows = ((rc >> 16) & 0x7fff);
4780 if (rows > MAX_SCR_ROWS)
4781 rows = MAX_SCR_ROWS;
4782 }
4783 }
4784#endif
4785 new_screen(rows, columns); // get memory for virtual screen
4786 init_text_buffer(fn);
4787
4788#if ENABLE_FEATURE_VI_YANKMARK
4789 YDreg = 26; // default Yank/Delete reg
4790// Ureg = 27; - const // hold orig line for "U" cmd
4791 mark[26] = mark[27] = text; // init "previous context"
4792#endif
4793
Denys Vlasenkob29dce42019-04-01 17:17:02 +02004794#if ENABLE_FEATURE_VI_CRASHME
4795 last_input_char = '\0';
4796#endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004797 crow = 0;
4798 ccol = 0;
4799
4800#if ENABLE_FEATURE_VI_USE_SIGNALS
4801 signal(SIGWINCH, winch_handler);
4802 signal(SIGTSTP, tstp_handler);
4803 sig = sigsetjmp(restart, 1);
4804 if (sig != 0) {
4805 screenbegin = dot = text;
4806 }
4807 // int_handler() can jump to "restart",
4808 // must install handler *after* initializing "restart"
4809 signal(SIGINT, int_handler);
4810#endif
4811
4812 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
4813 cmdcnt = 0;
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004814 offset = 0; // no horizontal offset
4815 c = '\0';
4816#if ENABLE_FEATURE_VI_DOT_CMD
4817 free(ioq_start);
Denys Vlasenko2a576082019-04-01 16:15:51 +02004818 ioq_start = NULL;
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004819 adding2q = 0;
4820#endif
4821
4822#if ENABLE_FEATURE_VI_COLON
Ron Yorstonf07772f2021-08-19 16:59:36 +01004823 while (initial_cmds)
4824 run_cmds((char *)llist_pop(&initial_cmds));
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004825#endif
4826 redraw(FALSE); // dont force every col re-draw
4827 //------This is the main Vi cmd handling loop -----------------------
4828 while (editing > 0) {
4829#if ENABLE_FEATURE_VI_CRASHME
4830 if (crashme > 0) {
4831 if ((end - text) > 1) {
4832 crash_dummy(); // generate a random command
4833 } else {
4834 crashme = 0;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004835 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO);
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004836 dot = text;
4837 refresh(FALSE);
4838 }
4839 }
4840#endif
Denys Vlasenkob29dce42019-04-01 17:17:02 +02004841 c = get_one_char(); // get a cmd from user
4842#if ENABLE_FEATURE_VI_CRASHME
4843 last_input_char = c;
4844#endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004845#if ENABLE_FEATURE_VI_YANKMARK
4846 // save a copy of the current line- for the 'U" command
4847 if (begin_line(dot) != cur_line) {
4848 cur_line = begin_line(dot);
Ron Yorston25d25922021-03-25 14:23:36 +00004849 text_yank(begin_line(dot), end_line(dot), Ureg, PARTIAL);
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004850 }
4851#endif
4852#if ENABLE_FEATURE_VI_DOT_CMD
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004853 // If c is a command that changes text[],
4854 // (re)start remembering the input for the "." command.
Denys Vlasenko2a576082019-04-01 16:15:51 +02004855 if (!adding2q
4856 && ioq_start == NULL
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004857 && cmd_mode == 0 // command mode
4858 && c > '\0' // exclude NUL and non-ASCII chars
4859 && c < 0x7f // (Unicode and such)
4860 && strchr(modifying_cmds, c)
4861 ) {
4862 start_new_cmd_q(c);
4863 }
4864#endif
4865 do_cmd(c); // execute the user command
4866
4867 // poll to see if there is input already waiting. if we are
4868 // not able to display output fast enough to keep up, skip
4869 // the display update until we catch up with input.
4870 if (!readbuffer[0] && mysleep(0) == 0) {
4871 // no input pending - so update output
4872 refresh(FALSE);
4873 show_status_line();
4874 }
4875#if ENABLE_FEATURE_VI_CRASHME
4876 if (crashme > 0)
4877 crash_test(); // test editor variables
4878#endif
4879 }
4880 //-------------------------------------------------------------------
4881
4882 go_bottom_and_clear_to_eol();
4883 cookmode();
4884#undef cur_line
4885}
4886
Ron Yorstonf07772f2021-08-19 16:59:36 +01004887#define VI_OPTSTR \
4888 IF_FEATURE_VI_CRASHME("C") \
4889 IF_FEATURE_VI_COLON("c:*") \
4890 "Hh" \
4891 IF_FEATURE_VI_READONLY("R")
4892
4893enum {
4894 IF_FEATURE_VI_CRASHME(OPTBIT_C,)
4895 IF_FEATURE_VI_COLON(OPTBIT_c,)
4896 OPTBIT_H,
4897 OPTBIT_h,
4898 IF_FEATURE_VI_READONLY(OPTBIT_R,)
4899 OPT_C = IF_FEATURE_VI_CRASHME( (1 << OPTBIT_C)) + 0,
4900 OPT_c = IF_FEATURE_VI_COLON( (1 << OPTBIT_c)) + 0,
4901 OPT_H = 1 << OPTBIT_H,
4902 OPT_h = 1 << OPTBIT_h,
4903 OPT_R = IF_FEATURE_VI_READONLY( (1 << OPTBIT_R)) + 0,
4904};
4905
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004906int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
4907int vi_main(int argc, char **argv)
4908{
Ron Yorstonf07772f2021-08-19 16:59:36 +01004909 int opts;
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004910
4911 INIT_G();
4912
4913#if ENABLE_FEATURE_VI_UNDO
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004914 //undo_stack_tail = NULL; - already is
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02004915# if ENABLE_FEATURE_VI_UNDO_QUEUE
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004916 undo_queue_state = UNDO_EMPTY;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004917 //undo_q = 0; - already is
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02004918# endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004919#endif
4920
4921#if ENABLE_FEATURE_VI_CRASHME
4922 srand((long) getpid());
4923#endif
4924#ifdef NO_SUCH_APPLET_YET
4925 // if we aren't "vi", we are "view"
4926 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
4927 SET_READONLY_MODE(readonly_mode);
4928 }
4929#endif
4930
Ron Yorston9f017d92021-04-06 22:11:21 +01004931 // 0: all of our options are disabled by default in vim
4932 //vi_setops = 0;
Ron Yorstonf07772f2021-08-19 16:59:36 +01004933 opts = getopt32(argv, VI_OPTSTR IF_FEATURE_VI_COLON(, &initial_cmds));
4934
Denys Vlasenko6b6826f2021-06-13 01:08:48 +02004935#if ENABLE_FEATURE_VI_CRASHME
Ron Yorstonf07772f2021-08-19 16:59:36 +01004936 if (opts & OPT_C)
4937 crashme = 1;
Denys Vlasenko6b6826f2021-06-13 01:08:48 +02004938#endif
Ron Yorstonf07772f2021-08-19 16:59:36 +01004939 if (opts & OPT_R)
4940 SET_READONLY_MODE(readonly_mode);
4941 if (opts & OPT_H)
4942 show_help();
4943 if (opts & (OPT_H | OPT_h)) {
4944 bb_show_usage();
4945 return 1;
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004946 }
4947
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004948 argv += optind;
Denys Vlasenko89393592019-04-02 12:45:30 +02004949 cmdline_filecnt = argc - optind;
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004950
Ron Yorstonf07772f2021-08-19 16:59:36 +01004951 // 1- process EXINIT variable from environment
Ron Yorstonf9217cd2021-08-19 17:00:17 +01004952 // 2- if EXINIT is unset process $HOME/.exrc file
Ron Yorstonf07772f2021-08-19 16:59:36 +01004953 // 3- process command line args
4954#if ENABLE_FEATURE_VI_COLON
4955 {
4956 const char *exinit = getenv("EXINIT");
Ron Yorstonf9217cd2021-08-19 17:00:17 +01004957 char *cmds = NULL;
Ron Yorstonf07772f2021-08-19 16:59:36 +01004958
4959 if (exinit) {
Ron Yorstonf9217cd2021-08-19 17:00:17 +01004960 cmds = xstrdup(exinit);
4961 } else {
4962 const char *home = getenv("HOME");
4963
4964 if (home && *home) {
4965 char *exrc = concat_path_file(home, ".exrc");
4966 struct stat st;
4967
4968 // .exrc must belong to and only be writable by user
4969 if (stat(exrc, &st) == 0) {
4970 if ((st.st_mode & (S_IWGRP|S_IWOTH)) == 0
4971 && st.st_uid == getuid()
4972 ) {
4973 cmds = xmalloc_open_read_close(exrc, NULL);
4974 } else {
4975 status_line_bold(".exrc: permission denied");
4976 }
4977 }
4978 free(exrc);
4979 }
4980 }
4981
4982 if (cmds) {
Ron Yorstonf07772f2021-08-19 16:59:36 +01004983 init_text_buffer(NULL);
4984 run_cmds(cmds);
4985 free(cmds);
4986 }
4987 }
4988#endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004989 // "Save cursor, use alternate screen buffer, clear screen"
4990 write1(ESC"[?1049h");
Denys Vlasenko89393592019-04-02 12:45:30 +02004991 // This is the main file handling loop
4992 optind = 0;
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004993 while (1) {
Denys Vlasenko89393592019-04-02 12:45:30 +02004994 edit_file(argv[optind]); // might be NULL on 1st iteration
4995 // NB: optind can be changed by ":next" and ":rewind" commands
4996 optind++;
Denys Vlasenkoa3ce1612019-04-03 16:35:23 +02004997 if (optind >= cmdline_filecnt)
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004998 break;
4999 }
5000 // "Use normal screen buffer, restore cursor"
5001 write1(ESC"[?1049l");
Denys Vlasenko24bd3502019-04-01 13:55:27 +02005002
5003 return 0;
5004}