blob: 38ce1fdf06cc117135e86359bc6f9b36edce3903 [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:
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +020010// $HOME/.exrc and ./.exrc
11// 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(
Denys Vlasenko605f2642012-06-11 01:53:33 +0200188//usage: "\n -c CMD Initial command to run ($EXINIT 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
204
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000205
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +0000206#if ENABLE_LOCALE_SUPPORT
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000207
208#if ENABLE_FEATURE_VI_8BIT
Denys Vlasenkoc2704542009-11-20 19:14:19 +0100209//FIXME: this does not work properly for Unicode anyway
210# define Isprint(c) (isprint)(c)
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000211#else
Denys Vlasenkoc2704542009-11-20 19:14:19 +0100212# define Isprint(c) isprint_asciionly(c)
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000213#endif
214
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000215#else
216
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200217// 0x9b is Meta-ESC
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000218#if ENABLE_FEATURE_VI_8BIT
Denys Vlasenko066f3992011-07-03 03:19:43 +0200219# define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000220#else
Denys Vlasenko066f3992011-07-03 03:19:43 +0200221# define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +0000222#endif
223
224#endif
225
226
Denis Vlasenkoe8a07882007-06-10 15:08:44 +0000227enum {
Denis Vlasenko26b6fba2007-12-21 21:34:37 +0000228 MAX_TABSTOP = 32, // sanity limit
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000229 // User input len. Need not be extra big.
230 // Lines in file being edited *can* be bigger than this.
231 MAX_INPUT_LEN = 128,
232 // Sanity limits. We have only one buffer of this size.
233 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
234 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
Denis Vlasenkoe8a07882007-06-10 15:08:44 +0000235};
Eric Andersen3f980402001-04-04 17:31:15 +0000236
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200237// VT102 ESC sequences.
238// See "Xterm Control Sequences"
239// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
Denys Vlasenko8187e012017-09-13 22:48:30 +0200240#define ESC "\033"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200241// Inverse/Normal text
Denys Vlasenko8187e012017-09-13 22:48:30 +0200242#define ESC_BOLD_TEXT ESC"[7m"
243#define ESC_NORM_TEXT ESC"[m"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200244// Bell
Denys Vlasenko04b52892012-06-11 13:51:38 +0200245#define ESC_BELL "\007"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200246// Clear-to-end-of-line
Denys Vlasenko8187e012017-09-13 22:48:30 +0200247#define ESC_CLEAR2EOL ESC"[K"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200248// Clear-to-end-of-screen.
249// (We use default param here.
250// Full sequence is "ESC [ <num> J",
251// <num> is 0/1/2 = "erase below/above/all".)
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +0200252#define ESC_CLEAR2EOS ESC"[J"
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200253// Cursor to given coordinate (1,1: top left)
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +0200254#define ESC_SET_CURSOR_POS ESC"[%u;%uH"
255#define ESC_SET_CURSOR_TOPLEFT ESC"[H"
Denys Vlasenko04b52892012-06-11 13:51:38 +0200256//UNUSED
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200257//// Cursor up and down
Denys Vlasenko8187e012017-09-13 22:48:30 +0200258//#define ESC_CURSOR_UP ESC"[A"
Denys Vlasenko04b52892012-06-11 13:51:38 +0200259//#define ESC_CURSOR_DOWN "\n"
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000260
Ron Yorston3b9233f2021-04-25 11:52:25 +0100261#if ENABLE_FEATURE_VI_DOT_CMD
Denis Vlasenkoded6ad32008-10-14 12:26:30 +0000262// cmds modifying text[]
Denys Vlasenkobbacd032019-04-02 11:50:25 +0200263static const char modifying_cmds[] ALIGN1 = "aAcCdDiIJoOpPrRs""xX<>~";
Denis Vlasenkoded6ad32008-10-14 12:26:30 +0000264#endif
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000265
Rob Landleybc68cd12006-03-10 19:22:06 +0000266enum {
267 YANKONLY = FALSE,
268 YANKDEL = TRUE,
269 FORWARD = 1, // code depends on "1" for array index
270 BACK = -1, // code depends on "-1" for array index
Denys Vlasenko836d0a72018-11-29 14:19:57 +0100271 LIMITED = 0, // char_search() only current line
272 FULL = 1, // char_search() to the end/beginning of entire text
Ron Yorston25d25922021-03-25 14:23:36 +0000273 PARTIAL = 0, // buffer contains partial line
274 WHOLE = 1, // buffer contains whole lines
275 MULTI = 2, // buffer may include newlines
Eric Andersen3f980402001-04-04 17:31:15 +0000276
Rob Landleybc68cd12006-03-10 19:22:06 +0000277 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
278 S_TO_WS = 2, // used in skip_thing() for moving "dot"
279 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
280 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
Denis Vlasenko8e858e22007-03-07 09:35:43 +0000281 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
Ron Yorston50a2db72021-03-28 13:20:01 +0100282
283 C_END = -1, // cursor is at end of line due to '$' command
Rob Landleybc68cd12006-03-10 19:22:06 +0000284};
Eric Andersen3f980402001-04-04 17:31:15 +0000285
Denis Vlasenkob1759462008-06-20 20:20:54 +0000286
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200287// vi.c expects chars to be unsigned.
288// busybox build system provides that, but it's better
289// to audit and fix the source
Eric Andersen3f980402001-04-04 17:31:15 +0000290
Denis Vlasenkob1759462008-06-20 20:20:54 +0000291struct globals {
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200292 // many references - keep near the top of globals
Denis Vlasenkob1759462008-06-20 20:20:54 +0000293 char *text, *end; // pointers to the user data in memory
294 char *dot; // where all the action takes place
295 int text_size; // size of the allocated buffer
296
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200297 // the rest
Ron Yorston47c3eaa2021-04-15 12:04:22 +0100298#if ENABLE_FEATURE_VI_SETOPTS
Ron Yorston9f017d92021-04-06 22:11:21 +0100299 smallint vi_setops; // set by setops()
300#define VI_AUTOINDENT (1 << 0)
Ron Yorston310ef232021-04-17 09:25:11 +0100301#define VI_EXPANDTAB (1 << 1)
302#define VI_ERR_METHOD (1 << 2)
303#define VI_IGNORECASE (1 << 3)
304#define VI_SHOWMATCH (1 << 4)
305#define VI_TABSTOP (1 << 5)
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000306#define autoindent (vi_setops & VI_AUTOINDENT)
Ron Yorston310ef232021-04-17 09:25:11 +0100307#define expandtab (vi_setops & VI_EXPANDTAB )
Ron Yorston9f017d92021-04-06 22:11:21 +0100308#define err_method (vi_setops & VI_ERR_METHOD) // indicate error with beep or flash
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000309#define ignorecase (vi_setops & VI_IGNORECASE)
Ron Yorston9f017d92021-04-06 22:11:21 +0100310#define showmatch (vi_setops & VI_SHOWMATCH )
311// order of constants and strings must match
312#define OPTS_STR \
313 "ai\0""autoindent\0" \
Ron Yorston310ef232021-04-17 09:25:11 +0100314 "et\0""expandtab\0" \
Ron Yorston9f017d92021-04-06 22:11:21 +0100315 "fl\0""flash\0" \
316 "ic\0""ignorecase\0" \
317 "sm\0""showmatch\0" \
318 "ts\0""tabstop\0"
Ron Yorston47c3eaa2021-04-15 12:04:22 +0100319#else
320#define autoindent (0)
Ron Yorston310ef232021-04-17 09:25:11 +0100321#define expandtab (0)
Ron Yorston47c3eaa2021-04-15 12:04:22 +0100322#define err_method (0)
Ron Yorston47c3eaa2021-04-15 12:04:22 +0100323#endif
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000324
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000325#if ENABLE_FEATURE_VI_READONLY
Denis Vlasenkob1759462008-06-20 20:20:54 +0000326 smallint readonly_mode;
Denis Vlasenko6a2f7f42007-08-16 10:35:17 +0000327#define SET_READONLY_FILE(flags) ((flags) |= 0x01)
328#define SET_READONLY_MODE(flags) ((flags) |= 0x02)
329#define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
Denis Vlasenkoeaabf062007-07-17 23:14:07 +0000330#else
Denis Vlasenkob1759462008-06-20 20:20:54 +0000331#define SET_READONLY_FILE(flags) ((void)0)
332#define SET_READONLY_MODE(flags) ((void)0)
333#define UNSET_READONLY_FILE(flags) ((void)0)
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000334#endif
Denis Vlasenkoeaabf062007-07-17 23:14:07 +0000335
Denis Vlasenkob1759462008-06-20 20:20:54 +0000336 smallint editing; // >0 while we are editing a file
Denis Vlasenko30cfdf92008-09-21 15:29:29 +0000337 // [code audit says "can be 0, 1 or 2 only"]
Denis Vlasenkob1759462008-06-20 20:20:54 +0000338 smallint cmd_mode; // 0=command 1=insert 2=replace
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200339 int modified_count; // buffer contents changed if !0
340 int last_modified_count; // = -1;
Denys Vlasenko89393592019-04-02 12:45:30 +0200341 int cmdline_filecnt; // how many file names on cmd line
Denis Vlasenkob1759462008-06-20 20:20:54 +0000342 int cmdcnt; // repetition count
343 unsigned rows, columns; // the terminal screen is this size
Denys Vlasenkoc175c462010-04-18 22:09:30 -0700344#if ENABLE_FEATURE_VI_ASK_TERMINAL
345 int get_rowcol_error;
346#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000347 int crow, ccol; // cursor is on Crow x Ccol
348 int offset; // chars scrolled off the screen to the left
349 int have_status_msg; // is default edit status needed?
350 // [don't make smallint!]
351 int last_status_cksum; // hash of current status line
352 char *current_filename;
Ron Yorstonacd30792021-04-25 11:55:42 +0100353#if ENABLE_FEATURE_VI_COLON_EXPAND
354 char *alt_filename;
355#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000356 char *screenbegin; // index into text[], of top line on the screen
357 char *screen; // pointer to the virtual screen buffer
358 int screensize; // and its size
359 int tabstop;
Ron Yorston15f4ac32021-03-28 17:15:30 +0100360 int last_search_char; // last char searched for (int because of Unicode)
361 smallint last_search_cmd; // command used to invoke last char search
Denys Vlasenkob29dce42019-04-01 17:17:02 +0200362#if ENABLE_FEATURE_VI_CRASHME
Denis Vlasenkob1759462008-06-20 20:20:54 +0000363 char last_input_char; // last char read from user
Denys Vlasenkob29dce42019-04-01 17:17:02 +0200364#endif
Denys Vlasenkob65e7f62021-04-15 23:17:01 +0200365#if ENABLE_FEATURE_VI_UNDO_QUEUE
366 char undo_queue_state; // One of UNDO_INS, UNDO_DEL, UNDO_EMPTY
367#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000368
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000369#if ENABLE_FEATURE_VI_DOT_CMD
Denis Vlasenkob1759462008-06-20 20:20:54 +0000370 smallint adding2q; // are we currently adding user input to q
371 int lmc_len; // length of last_modifying_cmd
372 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
Ron Yorstona5445022021-04-06 16:48:07 +0100373 int dotcnt; // number of times to repeat '.' command
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000374#endif
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +0000375#if ENABLE_FEATURE_VI_SEARCH
Denis Vlasenkob1759462008-06-20 20:20:54 +0000376 char *last_search_pattern; // last pattern from a '/' or '?' search
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +0000377#endif
Ron Yorston9659a8d2021-05-20 08:27:48 +0100378#if ENABLE_FEATURE_VI_SETOPTS
379 int indentcol; // column of recently autoindent, 0 or -1
380#endif
Ron Yorston038d4002021-06-16 14:47:00 +0100381 smallint cmd_error;
Denis Vlasenko0112ff52008-10-25 23:23:00 +0000382
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200383 // former statics
Denis Vlasenkob1759462008-06-20 20:20:54 +0000384#if ENABLE_FEATURE_VI_YANKMARK
385 char *edit_file__cur_line;
386#endif
387 int refresh__old_offset;
388 int format_edit_status__tot;
Eric Andersen3f980402001-04-04 17:31:15 +0000389
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200390 // a few references only
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000391#if ENABLE_FEATURE_VI_YANKMARK
Denys Vlasenkoeabf4b22019-03-29 14:40:01 +0100392 smalluint YDreg;//,Ureg;// default delete register and orig line for "U"
393#define Ureg 27
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000394 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
Ron Yorston25d25922021-03-25 14:23:36 +0000395 char regtype[28]; // buffer type: WHOLE, MULTI or PARTIAL
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000396 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000397#endif
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000398#if ENABLE_FEATURE_VI_USE_SIGNALS
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +0200399 sigjmp_buf restart; // int_handler() jumps to location remembered here
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000400#endif
Denys Vlasenko01ccdd12017-01-11 16:17:59 +0100401 struct termios term_orig; // remember what the cooked mode was
Ron Yorston50a2db72021-03-28 13:20:01 +0100402 int cindex; // saved character index for up/down motion
403 smallint keep_index; // retain saved character index
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000404#if ENABLE_FEATURE_VI_COLON
405 char *initial_cmds[3]; // currently 2 entries, NULL terminated
406#endif
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000407 // Should be just enough to hold a key sequence,
Denis Vlasenko25497c12008-10-14 10:25:05 +0000408 // but CRASHME mode uses it as generated command buffer too
409#if ENABLE_FEATURE_VI_CRASHME
Denis Vlasenko1dfeeeb2008-10-18 19:04:37 +0000410 char readbuffer[128];
Denis Vlasenko25497c12008-10-14 10:25:05 +0000411#else
Denis Vlasenko5f6aaf32008-10-25 23:27:29 +0000412 char readbuffer[KEYCODE_BUFFER_SIZE];
Denis Vlasenko25497c12008-10-14 10:25:05 +0000413#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000414#define STATUS_BUFFER_LEN 200
415 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
416#if ENABLE_FEATURE_VI_DOT_CMD
417 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
418#endif
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200419 char get_input_line__buf[MAX_INPUT_LEN]; // former static
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000420
421 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +0200422
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200423#if ENABLE_FEATURE_VI_UNDO
424// undo_push() operations
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200425#define UNDO_INS 0
426#define UNDO_DEL 1
427#define UNDO_INS_CHAIN 2
428#define UNDO_DEL_CHAIN 3
Ron Yorston9b2a3892021-04-15 12:00:50 +0100429# if ENABLE_FEATURE_VI_UNDO_QUEUE
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200430#define UNDO_INS_QUEUED 4
431#define UNDO_DEL_QUEUED 5
Ron Yorston9b2a3892021-04-15 12:00:50 +0100432# endif
433
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200434// Pass-through flags for functions that can be undone
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200435#define NO_UNDO 0
436#define ALLOW_UNDO 1
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200437#define ALLOW_UNDO_CHAIN 2
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200438# if ENABLE_FEATURE_VI_UNDO_QUEUE
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200439#define ALLOW_UNDO_QUEUED 3
Ron Yorston9b2a3892021-04-15 12:00:50 +0100440# else
441// If undo queuing disabled, don't invoke the missing queue logic
442#define ALLOW_UNDO_QUEUED ALLOW_UNDO
443# endif
444
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200445 struct undo_object {
446 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200447 int start; // Offset where the data should be restored/deleted
448 int length; // total data size
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200449 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
450 char undo_text[1]; // text that was deleted (if deletion)
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200451 } *undo_stack_tail;
Denys Vlasenkob65e7f62021-04-15 23:17:01 +0200452# if ENABLE_FEATURE_VI_UNDO_QUEUE
453#define UNDO_USE_SPOS 32
454#define UNDO_EMPTY 64
455 char *undo_queue_spos; // Start position of queued operation
456 int undo_q;
457 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
458# endif
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200459#endif /* ENABLE_FEATURE_VI_UNDO */
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000460};
461#define G (*ptr_to_globals)
462#define text (G.text )
Denis Vlasenkoeaabf062007-07-17 23:14:07 +0000463#define text_size (G.text_size )
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000464#define end (G.end )
465#define dot (G.dot )
466#define reg (G.reg )
Denis Vlasenkob1759462008-06-20 20:20:54 +0000467
468#define vi_setops (G.vi_setops )
469#define editing (G.editing )
470#define cmd_mode (G.cmd_mode )
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200471#define modified_count (G.modified_count )
472#define last_modified_count (G.last_modified_count)
Denys Vlasenko89393592019-04-02 12:45:30 +0200473#define cmdline_filecnt (G.cmdline_filecnt )
Denis Vlasenkob1759462008-06-20 20:20:54 +0000474#define cmdcnt (G.cmdcnt )
475#define rows (G.rows )
476#define columns (G.columns )
477#define crow (G.crow )
478#define ccol (G.ccol )
479#define offset (G.offset )
480#define status_buffer (G.status_buffer )
481#define have_status_msg (G.have_status_msg )
482#define last_status_cksum (G.last_status_cksum )
483#define current_filename (G.current_filename )
Ron Yorstonacd30792021-04-25 11:55:42 +0100484#define alt_filename (G.alt_filename )
Denis Vlasenkob1759462008-06-20 20:20:54 +0000485#define screen (G.screen )
486#define screensize (G.screensize )
487#define screenbegin (G.screenbegin )
488#define tabstop (G.tabstop )
Ron Yorston15f4ac32021-03-28 17:15:30 +0100489#define last_search_char (G.last_search_char )
490#define last_search_cmd (G.last_search_cmd )
Denys Vlasenkob29dce42019-04-01 17:17:02 +0200491#if ENABLE_FEATURE_VI_CRASHME
Denis Vlasenkob1759462008-06-20 20:20:54 +0000492#define last_input_char (G.last_input_char )
Denys Vlasenkob29dce42019-04-01 17:17:02 +0200493#endif
Denis Vlasenko2a210e52008-06-22 13:20:42 +0000494#if ENABLE_FEATURE_VI_READONLY
Denis Vlasenkob1759462008-06-20 20:20:54 +0000495#define readonly_mode (G.readonly_mode )
Denis Vlasenko2a210e52008-06-22 13:20:42 +0000496#else
497#define readonly_mode 0
498#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000499#define adding2q (G.adding2q )
500#define lmc_len (G.lmc_len )
501#define ioq (G.ioq )
502#define ioq_start (G.ioq_start )
Ron Yorstona5445022021-04-06 16:48:07 +0100503#define dotcnt (G.dotcnt )
Denis Vlasenkob1759462008-06-20 20:20:54 +0000504#define last_search_pattern (G.last_search_pattern)
Ron Yorston9659a8d2021-05-20 08:27:48 +0100505#define indentcol (G.indentcol )
Ron Yorston038d4002021-06-16 14:47:00 +0100506#define cmd_error (G.cmd_error )
Denis Vlasenko2a210e52008-06-22 13:20:42 +0000507
Denis Vlasenkob1759462008-06-20 20:20:54 +0000508#define edit_file__cur_line (G.edit_file__cur_line)
509#define refresh__old_offset (G.refresh__old_offset)
510#define format_edit_status__tot (G.format_edit_status__tot)
511
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000512#define YDreg (G.YDreg )
Denys Vlasenkoeabf4b22019-03-29 14:40:01 +0100513//#define Ureg (G.Ureg )
Ron Yorston25d25922021-03-25 14:23:36 +0000514#define regtype (G.regtype )
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000515#define mark (G.mark )
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000516#define restart (G.restart )
517#define term_orig (G.term_orig )
Ron Yorston50a2db72021-03-28 13:20:01 +0100518#define cindex (G.cindex )
519#define keep_index (G.keep_index )
Denis Vlasenko0b3b41b2007-05-30 02:01:40 +0000520#define initial_cmds (G.initial_cmds )
Denis Vlasenkoa96425f2007-12-09 04:13:43 +0000521#define readbuffer (G.readbuffer )
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000522#define scr_out_buf (G.scr_out_buf )
Denis Vlasenkob1759462008-06-20 20:20:54 +0000523#define last_modifying_cmd (G.last_modifying_cmd )
524#define get_input_line__buf (G.get_input_line__buf)
525
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200526#if ENABLE_FEATURE_VI_UNDO
527#define undo_stack_tail (G.undo_stack_tail )
528# if ENABLE_FEATURE_VI_UNDO_QUEUE
529#define undo_queue_state (G.undo_queue_state)
530#define undo_q (G.undo_q )
531#define undo_queue (G.undo_queue )
532#define undo_queue_spos (G.undo_queue_spos )
533# endif
534#endif
535
Denis Vlasenkoa96425f2007-12-09 04:13:43 +0000536#define INIT_G() do { \
Denis Vlasenko574f2f42008-02-27 18:41:59 +0000537 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
Denys Vlasenkoe7430862014-04-03 12:47:48 +0200538 last_modified_count = -1; \
Denis Vlasenko31d58e52008-10-29 13:16:28 +0000539 /* "" but has space for 2 chars: */ \
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000540 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
Ron Yorston52c4b7a2021-06-11 13:22:25 +0100541 tabstop = 8; \
Denis Vlasenkoa96425f2007-12-09 04:13:43 +0000542} while (0)
Eric Andersen3f980402001-04-04 17:31:15 +0000543
Denys Vlasenko6ce60b92019-04-01 15:41:05 +0200544#if ENABLE_FEATURE_VI_CRASHME
545static int crashme = 0;
546#endif
Denis Vlasenkob1759462008-06-20 20:20:54 +0000547
Eric Andersen3f980402001-04-04 17:31:15 +0000548static void show_status_line(void); // put a message on the bottom line
Denis Vlasenko88adfcd2007-12-22 15:40:13 +0000549static void status_line_bold(const char *, ...);
Jody Bruchona8d6f9b2014-04-02 13:49:26 +0200550
Denys Vlasenko24bd3502019-04-01 13:55:27 +0200551static void show_help(void)
552{
553 puts("These features are available:"
554#if ENABLE_FEATURE_VI_SEARCH
555 "\n\tPattern searches with / and ?"
556#endif
557#if ENABLE_FEATURE_VI_DOT_CMD
558 "\n\tLast command repeat with ."
559#endif
560#if ENABLE_FEATURE_VI_YANKMARK
561 "\n\tLine marking with 'x"
562 "\n\tNamed buffers with \"x"
563#endif
564#if ENABLE_FEATURE_VI_READONLY
565 //not implemented: "\n\tReadonly if vi is called as \"view\""
566 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
567#endif
568#if ENABLE_FEATURE_VI_SET
569 "\n\tSome colon mode commands with :"
570#endif
571#if ENABLE_FEATURE_VI_SETOPTS
572 "\n\tSettable options with \":set\""
573#endif
574#if ENABLE_FEATURE_VI_USE_SIGNALS
575 "\n\tSignal catching- ^C"
576 "\n\tJob suspend and resume with ^Z"
577#endif
578#if ENABLE_FEATURE_VI_WIN_RESIZE
579 "\n\tAdapt to window re-sizes"
580#endif
581 );
582}
583
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000584static void write1(const char *out)
585{
Ron Yorstoncad3fc72021-02-03 20:47:14 +0100586 fputs_stdout(out);
Glenn L McGrath09adaca2002-12-02 21:18:10 +0000587}
588
Denys Vlasenko2bb651a2010-04-16 20:55:52 -0700589#if ENABLE_FEATURE_VI_WIN_RESIZE
Denys Vlasenkoc5fb0ad2010-07-21 12:39:42 +0200590static int query_screen_dimensions(void)
Denys Vlasenko2bb651a2010-04-16 20:55:52 -0700591{
Denys Vlasenkoc5fb0ad2010-07-21 12:39:42 +0200592 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
Denys Vlasenko2bb651a2010-04-16 20:55:52 -0700593 if (rows > MAX_SCR_ROWS)
594 rows = MAX_SCR_ROWS;
595 if (columns > MAX_SCR_COLS)
596 columns = MAX_SCR_COLS;
Denys Vlasenkoc5fb0ad2010-07-21 12:39:42 +0200597 return err;
Denys Vlasenko2bb651a2010-04-16 20:55:52 -0700598}
599#else
Denys Vlasenko39043ad2018-06-27 14:46:08 +0200600static ALWAYS_INLINE int query_screen_dimensions(void)
601{
602 return 0;
603}
Denys Vlasenko2bb651a2010-04-16 20:55:52 -0700604#endif
605
Denys Vlasenkode697752019-04-01 14:08:00 +0200606// sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
607static int mysleep(int hund)
608{
609 struct pollfd pfd[1];
610
611 if (hund != 0)
612 fflush_all();
613
614 pfd[0].fd = STDIN_FILENO;
615 pfd[0].events = POLLIN;
616 return safe_poll(pfd, 1, hund*10) > 0;
617}
618
619//----- Set terminal attributes --------------------------------
620static void rawmode(void)
621{
622 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
623 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
Denys Vlasenkode697752019-04-01 14:08:00 +0200624}
625
626static void cookmode(void)
627{
628 fflush_all();
629 tcsetattr_stdin_TCSANOW(&term_orig);
630}
631
632//----- Terminal Drawing ---------------------------------------
633// The terminal is made up of 'rows' line of 'columns' columns.
634// classically this would be 24 x 80.
635// screen coordinates
636// 0,0 ... 0,79
637// 1,0 ... 1,79
638// . ... .
639// . ... .
640// 22,0 ... 22,79
641// 23,0 ... 23,79 <- status line
642
643//----- Move the cursor to row x col (count from 0, not 1) -------
644static void place_cursor(int row, int col)
645{
646 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
647
648 if (row < 0) row = 0;
649 if (row >= rows) row = rows - 1;
650 if (col < 0) col = 0;
651 if (col >= columns) col = columns - 1;
652
653 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
654 write1(cm1);
655}
656
657//----- Erase from cursor to end of line -----------------------
658static void clear_to_eol(void)
659{
660 write1(ESC_CLEAR2EOL);
661}
662
663static void go_bottom_and_clear_to_eol(void)
664{
665 place_cursor(rows - 1, 0);
666 clear_to_eol();
667}
668
669//----- Start standout mode ------------------------------------
670static void standout_start(void)
671{
672 write1(ESC_BOLD_TEXT);
673}
674
675//----- End standout mode --------------------------------------
676static void standout_end(void)
677{
678 write1(ESC_NORM_TEXT);
679}
680
681//----- Text Movement Routines ---------------------------------
682static char *begin_line(char *p) // return pointer to first char cur line
683{
684 if (p > text) {
685 p = memrchr(text, '\n', p - text);
686 if (!p)
687 return text;
688 return p + 1;
689 }
690 return p;
691}
692
693static char *end_line(char *p) // return pointer to NL of cur line
694{
695 if (p < end - 1) {
696 p = memchr(p, '\n', end - p - 1);
697 if (!p)
698 return end - 1;
699 }
700 return p;
701}
702
703static char *dollar_line(char *p) // return pointer to just before NL line
704{
705 p = end_line(p);
706 // Try to stay off of the Newline
707 if (*p == '\n' && (p - begin_line(p)) > 0)
708 p--;
709 return p;
710}
711
712static char *prev_line(char *p) // return pointer first char prev line
713{
714 p = begin_line(p); // goto beginning of cur line
715 if (p > text && p[-1] == '\n')
716 p--; // step to prev line
717 p = begin_line(p); // goto beginning of prev line
718 return p;
719}
720
721static char *next_line(char *p) // return pointer first char next line
722{
723 p = end_line(p);
724 if (p < end - 1 && *p == '\n')
725 p++; // step to next line
726 return p;
727}
728
729//----- Text Information Routines ------------------------------
730static char *end_screen(void)
731{
732 char *q;
733 int cnt;
734
735 // find new bottom line
736 q = screenbegin;
737 for (cnt = 0; cnt < rows - 2; cnt++)
738 q = next_line(q);
739 q = end_line(q);
740 return q;
741}
742
743// count line from start to stop
744static int count_lines(char *start, char *stop)
745{
746 char *q;
747 int cnt;
748
749 if (stop < start) { // start and stop are backwards- reverse them
750 q = start;
751 start = stop;
752 stop = q;
753 }
754 cnt = 0;
755 stop = end_line(stop);
756 while (start <= stop && start <= end - 1) {
757 start = end_line(start);
758 if (*start == '\n')
759 cnt++;
760 start++;
761 }
762 return cnt;
763}
764
765static char *find_line(int li) // find beginning of line #li
766{
767 char *q;
768
769 for (q = text; li > 1; li--) {
770 q = next_line(q);
771 }
772 return q;
773}
774
775static int next_tabstop(int col)
776{
777 return col + ((tabstop - 1) - (col % tabstop));
778}
779
Ron Yorstonf277c9e2021-04-17 09:25:47 +0100780static int prev_tabstop(int col)
781{
782 return col - ((col % tabstop) ?: tabstop);
783}
784
Ron Yorston310ef232021-04-17 09:25:11 +0100785static int next_column(char c, int co)
786{
787 if (c == '\t')
788 co = next_tabstop(co);
789 else if ((unsigned char)c < ' ' || c == 0x7f)
790 co++; // display as ^X, use 2 columns
791 return co + 1;
792}
793
Ron Yorston310ef232021-04-17 09:25:11 +0100794static int get_column(char *p)
795{
796 const char *r;
797 int co = 0;
798
799 for (r = begin_line(p); r < p; r++)
800 co = next_column(*r, co);
801 return co;
802}
Ron Yorston310ef232021-04-17 09:25:11 +0100803
Denys Vlasenkode697752019-04-01 14:08:00 +0200804//----- Erase the Screen[] memory ------------------------------
805static void screen_erase(void)
806{
807 memset(screen, ' ', screensize); // clear new screen
808}
809
Denys Vlasenkoe1a1b642019-04-03 16:30:50 +0200810static void new_screen(int ro, int co)
Ron Yorston3e61b592019-04-03 08:56:30 +0100811{
Denys Vlasenkoe1a1b642019-04-03 16:30:50 +0200812 char *s;
Ron Yorston3e61b592019-04-03 08:56:30 +0100813
814 free(screen);
815 screensize = ro * co + 8;
Denys Vlasenkoe1a1b642019-04-03 16:30:50 +0200816 s = screen = xmalloc(screensize);
Ron Yorston3e61b592019-04-03 08:56:30 +0100817 // initialize the new screen. assume this will be a empty file.
818 screen_erase();
Denys Vlasenkoe1a1b642019-04-03 16:30:50 +0200819 // non-existent text[] lines start with a tilde (~).
820 //screen[(1 * co) + 0] = '~';
821 //screen[(2 * co) + 0] = '~';
822 //..
823 //screen[((ro-2) * co) + 0] = '~';
824 ro -= 2;
825 while (--ro >= 0) {
826 s += co;
827 *s = '~';
Ron Yorston3e61b592019-04-03 08:56:30 +0100828 }
Ron Yorston3e61b592019-04-03 08:56:30 +0100829}
830
Denys Vlasenkode697752019-04-01 14:08:00 +0200831//----- Synchronize the cursor to Dot --------------------------
Ron Yorstona25b4c22021-03-28 13:23:51 +0100832static void sync_cursor(char *d, int *row, int *col)
Denys Vlasenkode697752019-04-01 14:08:00 +0200833{
834 char *beg_cur; // begin and end of "d" line
835 char *tp;
836 int cnt, ro, co;
837
838 beg_cur = begin_line(d); // first char of cur line
839
840 if (beg_cur < screenbegin) {
841 // "d" is before top line on screen
842 // how many lines do we have to move
843 cnt = count_lines(beg_cur, screenbegin);
844 sc1:
845 screenbegin = beg_cur;
846 if (cnt > (rows - 1) / 2) {
847 // we moved too many lines. put "dot" in middle of screen
848 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
849 screenbegin = prev_line(screenbegin);
850 }
851 }
852 } else {
853 char *end_scr; // begin and end of screen
854 end_scr = end_screen(); // last char of screen
855 if (beg_cur > end_scr) {
856 // "d" is after bottom line on screen
857 // how many lines do we have to move
858 cnt = count_lines(end_scr, beg_cur);
859 if (cnt > (rows - 1) / 2)
860 goto sc1; // too many lines
861 for (ro = 0; ro < cnt - 1; ro++) {
862 // move screen begin the same amount
863 screenbegin = next_line(screenbegin);
864 // now, move the end of screen
865 end_scr = next_line(end_scr);
866 end_scr = end_line(end_scr);
867 }
868 }
869 }
870 // "d" is on screen- find out which row
871 tp = screenbegin;
872 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
873 if (tp == beg_cur)
874 break;
875 tp = next_line(tp);
876 }
877
878 // find out what col "d" is on
879 co = 0;
Ron Yorstond9d19892021-04-15 12:01:34 +0100880 do { // drive "co" to correct column
Denys Vlasenkode697752019-04-01 14:08:00 +0200881 if (*tp == '\n') //vda || *tp == '\0')
882 break;
Ron Yorston310ef232021-04-17 09:25:11 +0100883 co = next_column(*tp, co) - 1;
Ron Yorstond9d19892021-04-15 12:01:34 +0100884 // inserting text before a tab, don't include its position
885 if (cmd_mode && tp == d - 1 && *d == '\t') {
886 co++;
887 break;
888 }
889 } while (tp++ < d && ++co);
Denys Vlasenkode697752019-04-01 14:08:00 +0200890
891 // "co" is the column where "dot" is.
892 // The screen has "columns" columns.
893 // The currently displayed columns are 0+offset -- columns+ofset
894 // |-------------------------------------------------------------|
895 // ^ ^ ^
896 // offset | |------- columns ----------------|
897 //
898 // If "co" is already in this range then we do not have to adjust offset
899 // but, we do have to subtract the "offset" bias from "co".
900 // If "co" is outside this range then we have to change "offset".
901 // If the first char of a line is a tab the cursor will try to stay
902 // in column 7, but we have to set offset to 0.
903
904 if (co < 0 + offset) {
905 offset = co;
906 }
907 if (co >= columns + offset) {
908 offset = co - columns + 1;
909 }
910 // if the first char of the line is a tab, and "dot" is sitting on it
911 // force offset to 0.
912 if (d == beg_cur && *d == '\t') {
913 offset = 0;
914 }
915 co -= offset;
916
917 *row = ro;
918 *col = co;
919}
920
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +0200921//----- Format a text[] line into a buffer ---------------------
922static char* format_line(char *src /*, int li*/)
923{
924 unsigned char c;
925 int co;
926 int ofs = offset;
927 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
928
929 c = '~'; // char in col 0 in non-existent lines is '~'
930 co = 0;
931 while (co < columns + tabstop) {
932 // have we gone past the end?
933 if (src < end) {
934 c = *src++;
935 if (c == '\n')
936 break;
937 if ((c & 0x80) && !Isprint(c)) {
938 c = '.';
939 }
940 if (c < ' ' || c == 0x7f) {
941 if (c == '\t') {
942 c = ' ';
943 // co % 8 != 7
944 while ((co % tabstop) != (tabstop - 1)) {
945 dest[co++] = c;
946 }
947 } else {
948 dest[co++] = '^';
949 if (c == 0x7f)
950 c = '?';
951 else
952 c += '@'; // Ctrl-X -> 'X'
953 }
954 }
955 }
956 dest[co++] = c;
957 // discard scrolled-off-to-the-left portion,
958 // in tabstop-sized pieces
959 if (ofs >= tabstop && co >= tabstop) {
960 memmove(dest, dest + tabstop, co);
961 co -= tabstop;
962 ofs -= tabstop;
963 }
964 if (src >= end)
965 break;
966 }
967 // check "short line, gigantic offset" case
968 if (co < ofs)
969 ofs = co;
970 // discard last scrolled off part
971 co -= ofs;
972 dest += ofs;
973 // fill the rest with spaces
974 if (co < columns)
975 memset(&dest[co], ' ', columns - co);
976 return dest;
977}
978
979//----- Refresh the changed screen lines -----------------------
980// Copy the source line from text[] into the buffer and note
981// if the current screenline is different from the new buffer.
982// If they differ then that line needs redrawing on the terminal.
983//
984static void refresh(int full_screen)
985{
986#define old_offset refresh__old_offset
987
988 int li, changed;
989 char *tp, *sp; // pointer into text[] and screen[]
990
991 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
992 unsigned c = columns, r = rows;
993 query_screen_dimensions();
994#if ENABLE_FEATURE_VI_USE_SIGNALS
995 full_screen |= (c - columns) | (r - rows);
996#else
997 if (c != columns || r != rows) {
998 full_screen = TRUE;
999 // update screen memory since SIGWINCH won't have done it
1000 new_screen(rows, columns);
1001 }
1002#endif
1003 }
1004 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
1005 tp = screenbegin; // index into text[] of top line
1006
1007 // compare text[] to screen[] and mark screen[] lines that need updating
1008 for (li = 0; li < rows - 1; li++) {
1009 int cs, ce; // column start & end
1010 char *out_buf;
1011 // format current text line
1012 out_buf = format_line(tp /*, li*/);
1013
1014 // skip to the end of the current text[] line
1015 if (tp < end) {
1016 char *t = memchr(tp, '\n', end - tp);
1017 if (!t) t = end - 1;
1018 tp = t + 1;
1019 }
1020
1021 // see if there are any changes between virtual screen and out_buf
1022 changed = FALSE; // assume no change
1023 cs = 0;
1024 ce = columns - 1;
1025 sp = &screen[li * columns]; // start of screen line
1026 if (full_screen) {
1027 // force re-draw of every single column from 0 - columns-1
1028 goto re0;
1029 }
1030 // compare newly formatted buffer with virtual screen
1031 // look forward for first difference between buf and screen
1032 for (; cs <= ce; cs++) {
1033 if (out_buf[cs] != sp[cs]) {
1034 changed = TRUE; // mark for redraw
1035 break;
1036 }
1037 }
1038
1039 // look backward for last difference between out_buf and screen
1040 for (; ce >= cs; ce--) {
1041 if (out_buf[ce] != sp[ce]) {
1042 changed = TRUE; // mark for redraw
1043 break;
1044 }
1045 }
1046 // now, cs is index of first diff, and ce is index of last diff
1047
1048 // if horz offset has changed, force a redraw
1049 if (offset != old_offset) {
1050 re0:
1051 changed = TRUE;
1052 }
1053
1054 // make a sanity check of columns indexes
1055 if (cs < 0) cs = 0;
1056 if (ce > columns - 1) ce = columns - 1;
1057 if (cs > ce) { cs = 0; ce = columns - 1; }
1058 // is there a change between virtual screen and out_buf
1059 if (changed) {
1060 // copy changed part of buffer to virtual screen
1061 memcpy(sp+cs, out_buf+cs, ce-cs+1);
1062 place_cursor(li, cs);
1063 // write line out to terminal
1064 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
1065 }
1066 }
1067
1068 place_cursor(crow, ccol);
1069
Ron Yorston50a2db72021-03-28 13:20:01 +01001070 if (!keep_index)
1071 cindex = ccol + offset;
1072
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001073 old_offset = offset;
1074#undef old_offset
1075}
1076
1077//----- Force refresh of all Lines -----------------------------
1078static void redraw(int full_screen)
1079{
1080 // cursor to top,left; clear to the end of screen
1081 write1(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS);
1082 screen_erase(); // erase the internal screen buffer
1083 last_status_cksum = 0; // force status update
1084 refresh(full_screen); // this will redraw the entire display
1085 show_status_line();
1086}
1087
1088//----- Flash the screen --------------------------------------
1089static void flash(int h)
1090{
1091 standout_start();
1092 redraw(TRUE);
1093 mysleep(h);
1094 standout_end();
1095 redraw(TRUE);
1096}
1097
1098static void indicate_error(void)
1099{
1100#if ENABLE_FEATURE_VI_CRASHME
1101 if (crashme > 0)
1102 return;
1103#endif
Ron Yorston038d4002021-06-16 14:47:00 +01001104 cmd_error = TRUE;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001105 if (!err_method) {
1106 write1(ESC_BELL);
1107 } else {
1108 flash(10);
1109 }
1110}
1111
1112//----- IO Routines --------------------------------------------
1113static int readit(void) // read (maybe cursor) key from stdin
1114{
1115 int c;
1116
1117 fflush_all();
1118
1119 // Wait for input. TIMEOUT = -1 makes read_key wait even
1120 // on nonblocking stdin.
1121 // Note: read_key sets errno to 0 on success.
1122 again:
1123 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
1124 if (c == -1) { // EOF/error
1125 if (errno == EAGAIN) // paranoia
1126 goto again;
1127 go_bottom_and_clear_to_eol();
1128 cookmode(); // terminal to "cooked"
James Byrne69374872019-07-02 11:35:03 +02001129 bb_simple_error_msg_and_die("can't read user input");
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001130 }
1131 return c;
1132}
1133
Denys Vlasenko2a576082019-04-01 16:15:51 +02001134#if ENABLE_FEATURE_VI_DOT_CMD
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001135static int get_one_char(void)
1136{
1137 int c;
1138
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001139 if (!adding2q) {
1140 // we are not adding to the q.
Denys Vlasenko2a576082019-04-01 16:15:51 +02001141 // but, we may be reading from a saved q.
1142 // (checking "ioq" for NULL is wrong, it's not reset to NULL
1143 // when done - "ioq_start" is reset instead).
1144 if (ioq_start != NULL) {
1145 // there is a queue to get chars from.
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001146 // careful with correct sign expansion!
1147 c = (unsigned char)*ioq++;
Denys Vlasenko2a576082019-04-01 16:15:51 +02001148 if (c != '\0')
1149 return c;
1150 // the end of the q
1151 free(ioq_start);
1152 ioq_start = NULL;
1153 // read from STDIN:
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001154 }
Denys Vlasenko2a576082019-04-01 16:15:51 +02001155 return readit();
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001156 }
Denys Vlasenko2a576082019-04-01 16:15:51 +02001157 // we are adding STDIN chars to q.
1158 c = readit();
Ron Yorstona5445022021-04-06 16:48:07 +01001159 if (lmc_len >= ARRAY_SIZE(last_modifying_cmd) - 2) {
1160 // last_modifying_cmd[] is too small, can't remember the cmd
Denys Vlasenkobbacd032019-04-02 11:50:25 +02001161 // - drop it
1162 adding2q = 0;
1163 lmc_len = 0;
Denys Vlasenko2a576082019-04-01 16:15:51 +02001164 } else {
1165 last_modifying_cmd[lmc_len++] = c;
1166 }
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001167 return c;
1168}
Denys Vlasenko2a576082019-04-01 16:15:51 +02001169#else
1170# define get_one_char() readit()
1171#endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001172
Ron Yorston1e84daf2021-03-28 13:21:34 +01001173// Get type of thing to operate on and adjust count
1174static int get_motion_char(void)
1175{
1176 int c, cnt;
1177
1178 c = get_one_char();
Ron Yorston38ae0f32021-04-15 12:02:43 +01001179 if (isdigit(c)) {
1180 if (c != '0') {
1181 // get any non-zero motion count
1182 for (cnt = 0; isdigit(c); c = get_one_char())
1183 cnt = cnt * 10 + (c - '0');
1184 cmdcnt = (cmdcnt ?: 1) * cnt;
1185 } else {
1186 // ensure standalone '0' works
1187 cmdcnt = 0;
1188 }
Ron Yorston1e84daf2021-03-28 13:21:34 +01001189 }
1190
1191 return c;
1192}
1193
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001194// Get input line (uses "status line" area)
1195static char *get_input_line(const char *prompt)
1196{
1197 // char [MAX_INPUT_LEN]
1198#define buf get_input_line__buf
1199
1200 int c;
1201 int i;
1202
1203 strcpy(buf, prompt);
1204 last_status_cksum = 0; // force status update
1205 go_bottom_and_clear_to_eol();
Denys Vlasenko74e1f322021-05-01 14:00:09 +02001206 write1(buf); // write out the :, /, or ? prompt
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001207
1208 i = strlen(buf);
Ron Yorston852ffbe2021-04-25 11:55:01 +01001209 while (i < MAX_INPUT_LEN - 1) {
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001210 c = get_one_char();
1211 if (c == '\n' || c == '\r' || c == 27)
1212 break; // this is end of input
Denys Vlasenkob29dce42019-04-01 17:17:02 +02001213 if (c == term_orig.c_cc[VERASE] || c == 8 || c == 127) {
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001214 // user wants to erase prev char
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001215 write1("\b \b"); // erase char on screen
Denys Vlasenko74e1f322021-05-01 14:00:09 +02001216 buf[--i] = '\0';
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001217 if (i <= 0) // user backs up before b-o-l, exit
1218 break;
1219 } else if (c > 0 && c < 256) { // exclude Unicode
1220 // (TODO: need to handle Unicode)
1221 buf[i] = c;
1222 buf[++i] = '\0';
1223 bb_putchar(c);
1224 }
1225 }
1226 refresh(FALSE);
1227 return buf;
1228#undef buf
1229}
1230
1231static void Hit_Return(void)
1232{
1233 int c;
1234
1235 standout_start();
1236 write1("[Hit return to continue]");
1237 standout_end();
1238 while ((c = get_one_char()) != '\n' && c != '\r')
1239 continue;
1240 redraw(TRUE); // force redraw all
1241}
1242
1243//----- Draw the status line at bottom of the screen -------------
1244// show file status on status line
1245static int format_edit_status(void)
1246{
1247 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
1248
1249#define tot format_edit_status__tot
1250
1251 int cur, percent, ret, trunc_at;
1252
1253 // modified_count is now a counter rather than a flag. this
1254 // helps reduce the amount of line counting we need to do.
1255 // (this will cause a mis-reporting of modified status
1256 // once every MAXINT editing operations.)
1257
1258 // it would be nice to do a similar optimization here -- if
1259 // we haven't done a motion that could have changed which line
1260 // we're on, then we shouldn't have to do this count_lines()
1261 cur = count_lines(text, dot);
1262
1263 // count_lines() is expensive.
1264 // Call it only if something was changed since last time
1265 // we were here:
1266 if (modified_count != last_modified_count) {
1267 tot = cur + count_lines(dot, end - 1) - 1;
1268 last_modified_count = modified_count;
1269 }
1270
1271 // current line percent
1272 // ------------- ~~ ----------
1273 // total lines 100
1274 if (tot > 0) {
1275 percent = (100 * cur) / tot;
1276 } else {
1277 cur = tot = 0;
1278 percent = 100;
1279 }
1280
1281 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
1282 columns : STATUS_BUFFER_LEN-1;
1283
1284 ret = snprintf(status_buffer, trunc_at+1,
1285#if ENABLE_FEATURE_VI_READONLY
1286 "%c %s%s%s %d/%d %d%%",
1287#else
1288 "%c %s%s %d/%d %d%%",
1289#endif
1290 cmd_mode_indicator[cmd_mode & 3],
1291 (current_filename != NULL ? current_filename : "No file"),
1292#if ENABLE_FEATURE_VI_READONLY
1293 (readonly_mode ? " [Readonly]" : ""),
1294#endif
1295 (modified_count ? " [Modified]" : ""),
1296 cur, tot, percent);
1297
1298 if (ret >= 0 && ret < trunc_at)
1299 return ret; // it all fit
1300
1301 return trunc_at; // had to truncate
1302#undef tot
1303}
1304
1305static int bufsum(char *buf, int count)
1306{
1307 int sum = 0;
1308 char *e = buf + count;
1309 while (buf < e)
1310 sum += (unsigned char) *buf++;
1311 return sum;
1312}
1313
1314static void show_status_line(void)
1315{
1316 int cnt = 0, cksum = 0;
1317
1318 // either we already have an error or status message, or we
1319 // create one.
1320 if (!have_status_msg) {
1321 cnt = format_edit_status();
1322 cksum = bufsum(status_buffer, cnt);
1323 }
1324 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
1325 last_status_cksum = cksum; // remember if we have seen this line
1326 go_bottom_and_clear_to_eol();
1327 write1(status_buffer);
1328 if (have_status_msg) {
1329 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
1330 (columns - 1) ) {
1331 have_status_msg = 0;
1332 Hit_Return();
1333 }
1334 have_status_msg = 0;
1335 }
1336 place_cursor(crow, ccol); // put cursor back in correct place
1337 }
1338 fflush_all();
1339}
1340
1341//----- format the status buffer, the bottom line of screen ------
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02001342static void status_line(const char *format, ...)
1343{
1344 va_list args;
1345
1346 va_start(args, format);
1347 vsnprintf(status_buffer, STATUS_BUFFER_LEN, format, args);
1348 va_end(args);
1349
1350 have_status_msg = 1;
1351}
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001352static void status_line_bold(const char *format, ...)
1353{
1354 va_list args;
1355
1356 va_start(args, format);
1357 strcpy(status_buffer, ESC_BOLD_TEXT);
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02001358 vsnprintf(status_buffer + (sizeof(ESC_BOLD_TEXT)-1),
1359 STATUS_BUFFER_LEN - sizeof(ESC_BOLD_TEXT) - sizeof(ESC_NORM_TEXT),
1360 format, args
1361 );
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001362 strcat(status_buffer, ESC_NORM_TEXT);
1363 va_end(args);
1364
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02001365 have_status_msg = 1 + (sizeof(ESC_BOLD_TEXT)-1) + (sizeof(ESC_NORM_TEXT)-1);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001366}
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001367static void status_line_bold_errno(const char *fn)
1368{
1369 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
1370}
1371
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001372// copy s to buf, convert unprintable
1373static void print_literal(char *buf, const char *s)
1374{
1375 char *d;
1376 unsigned char c;
1377
1378 buf[0] = '\0';
1379 if (!s[0])
1380 s = "(NULL)";
1381
1382 d = buf;
1383 for (; *s; s++) {
1384 int c_is_no_print;
1385
1386 c = *s;
1387 c_is_no_print = (c & 0x80) && !Isprint(c);
1388 if (c_is_no_print) {
1389 strcpy(d, ESC_NORM_TEXT);
1390 d += sizeof(ESC_NORM_TEXT)-1;
1391 c = '.';
1392 }
1393 if (c < ' ' || c == 0x7f) {
1394 *d++ = '^';
1395 c |= '@'; // 0x40
1396 if (c == 0x7f)
1397 c = '?';
1398 }
1399 *d++ = c;
1400 *d = '\0';
1401 if (c_is_no_print) {
1402 strcpy(d, ESC_BOLD_TEXT);
1403 d += sizeof(ESC_BOLD_TEXT)-1;
1404 }
1405 if (*s == '\n') {
1406 *d++ = '$';
1407 *d = '\0';
1408 }
1409 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
1410 break;
1411 }
1412}
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001413static void not_implemented(const char *s)
1414{
1415 char buf[MAX_INPUT_LEN];
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001416 print_literal(buf, s);
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02001417 status_line_bold("'%s' is not implemented", buf);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001418}
1419
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02001420//----- Block insert/delete, undo ops --------------------------
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001421#if ENABLE_FEATURE_VI_YANKMARK
Ron Yorston25d25922021-03-25 14:23:36 +00001422// copy text into a register
1423static char *text_yank(char *p, char *q, int dest, int buftype)
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001424{
Ron Yorstonf4a99082021-04-06 13:44:05 +01001425 char *oldreg = reg[dest];
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001426 int cnt = q - p;
1427 if (cnt < 0) { // they are backwards- reverse them
1428 p = q;
1429 cnt = -cnt;
1430 }
Ron Yorstonf4a99082021-04-06 13:44:05 +01001431 // Don't free register yet. This prevents the memory allocator
1432 // from reusing the free block so we can detect if it's changed.
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001433 reg[dest] = xstrndup(p, cnt + 1);
Ron Yorston25d25922021-03-25 14:23:36 +00001434 regtype[dest] = buftype;
Ron Yorstonf4a99082021-04-06 13:44:05 +01001435 free(oldreg);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001436 return p;
1437}
1438
1439static char what_reg(void)
1440{
1441 char c;
1442
1443 c = 'D'; // default to D-reg
Denys Vlasenkofa182a32019-06-08 12:57:07 +02001444 if (YDreg <= 25)
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001445 c = 'a' + (char) YDreg;
1446 if (YDreg == 26)
1447 c = 'D';
1448 if (YDreg == 27)
1449 c = 'U';
1450 return c;
1451}
1452
1453static void check_context(char cmd)
1454{
Ron Yorston74d565f2021-04-15 12:04:45 +01001455 // Certain movement commands update the context.
1456 if (strchr(":%{}'GHLMz/?Nn", cmd) != NULL) {
1457 mark[27] = mark[26]; // move cur to prev
1458 mark[26] = dot; // move local to cur
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001459 }
1460}
1461
1462static char *swap_context(char *p) // goto new context for '' command make this the current context
1463{
1464 char *tmp;
1465
1466 // the current context is in mark[26]
1467 // the previous context is in mark[27]
1468 // only swap context if other context is valid
1469 if (text <= mark[27] && mark[27] <= end - 1) {
1470 tmp = mark[27];
1471 mark[27] = p;
1472 mark[26] = p = tmp;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001473 }
1474 return p;
1475}
Ron Yorstonf4a99082021-04-06 13:44:05 +01001476
1477# if ENABLE_FEATURE_VI_VERBOSE_STATUS
1478static void yank_status(const char *op, const char *p, int cnt)
1479{
1480 int lines, chars;
1481
1482 lines = chars = 0;
1483 while (*p) {
1484 ++chars;
1485 if (*p++ == '\n')
1486 ++lines;
1487 }
1488 status_line("%s %d lines (%d chars) from [%c]",
1489 op, lines * cnt, chars * cnt, what_reg());
1490}
1491# endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001492#endif /* FEATURE_VI_YANKMARK */
1493
1494#if ENABLE_FEATURE_VI_UNDO
Ron Yorston9b2a3892021-04-15 12:00:50 +01001495static void undo_push(char *, unsigned, int);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001496#endif
1497
1498// open a hole in text[]
1499// might reallocate text[]! use p += text_hole_make(p, ...),
1500// and be careful to not use pointers into potentially freed text[]!
1501static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
1502{
1503 uintptr_t bias = 0;
1504
1505 if (size <= 0)
1506 return bias;
1507 end += size; // adjust the new END
1508 if (end >= (text + text_size)) {
1509 char *new_text;
1510 text_size += end - (text + text_size) + 10240;
1511 new_text = xrealloc(text, text_size);
1512 bias = (new_text - text);
1513 screenbegin += bias;
1514 dot += bias;
1515 end += bias;
1516 p += bias;
1517#if ENABLE_FEATURE_VI_YANKMARK
1518 {
1519 int i;
1520 for (i = 0; i < ARRAY_SIZE(mark); i++)
1521 if (mark[i])
1522 mark[i] += bias;
1523 }
1524#endif
1525 text = new_text;
1526 }
1527 memmove(p + size, p, end - size - p);
1528 memset(p, ' ', size); // clear new hole
1529 return bias;
1530}
1531
1532// close a hole in text[] - delete "p" through "q", inclusive
1533// "undo" value indicates if this operation should be undo-able
1534#if !ENABLE_FEATURE_VI_UNDO
1535#define text_hole_delete(a,b,c) text_hole_delete(a,b)
1536#endif
1537static char *text_hole_delete(char *p, char *q, int undo)
1538{
1539 char *src, *dest;
1540 int cnt, hole_size;
1541
1542 // move forwards, from beginning
1543 // assume p <= q
1544 src = q + 1;
1545 dest = p;
1546 if (q < p) { // they are backward- swap them
1547 src = p + 1;
1548 dest = q;
1549 }
1550 hole_size = q - p + 1;
1551 cnt = end - src;
1552#if ENABLE_FEATURE_VI_UNDO
1553 switch (undo) {
1554 case NO_UNDO:
1555 break;
1556 case ALLOW_UNDO:
1557 undo_push(p, hole_size, UNDO_DEL);
1558 break;
1559 case ALLOW_UNDO_CHAIN:
1560 undo_push(p, hole_size, UNDO_DEL_CHAIN);
1561 break;
1562# if ENABLE_FEATURE_VI_UNDO_QUEUE
1563 case ALLOW_UNDO_QUEUED:
1564 undo_push(p, hole_size, UNDO_DEL_QUEUED);
1565 break;
1566# endif
1567 }
1568 modified_count--;
1569#endif
1570 if (src < text || src > end)
1571 goto thd0;
1572 if (dest < text || dest >= end)
1573 goto thd0;
1574 modified_count++;
1575 if (src >= end)
1576 goto thd_atend; // just delete the end of the buffer
1577 memmove(dest, src, cnt);
1578 thd_atend:
1579 end = end - hole_size; // adjust the new END
1580 if (dest >= end)
1581 dest = end - 1; // make sure dest in below end-1
1582 if (end <= text)
1583 dest = end = text; // keep pointers valid
1584 thd0:
1585 return dest;
1586}
1587
1588#if ENABLE_FEATURE_VI_UNDO
1589
1590# if ENABLE_FEATURE_VI_UNDO_QUEUE
1591// Flush any queued objects to the undo stack
1592static void undo_queue_commit(void)
1593{
1594 // Pushes the queue object onto the undo stack
1595 if (undo_q > 0) {
1596 // Deleted character undo events grow from the end
1597 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
1598 undo_q,
1599 (undo_queue_state | UNDO_USE_SPOS)
1600 );
1601 undo_queue_state = UNDO_EMPTY;
1602 undo_q = 0;
1603 }
1604}
1605# else
1606# define undo_queue_commit() ((void)0)
1607# endif
1608
1609static void flush_undo_data(void)
1610{
1611 struct undo_object *undo_entry;
1612
1613 while (undo_stack_tail) {
1614 undo_entry = undo_stack_tail;
1615 undo_stack_tail = undo_entry->prev;
1616 free(undo_entry);
1617 }
1618}
1619
1620// Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
1621// Add to the undo stack
Ron Yorston9b2a3892021-04-15 12:00:50 +01001622static void undo_push(char *src, unsigned length, int u_type)
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001623{
1624 struct undo_object *undo_entry;
Ron Yorston9b2a3892021-04-15 12:00:50 +01001625# if ENABLE_FEATURE_VI_UNDO_QUEUE
1626 int use_spos = u_type & UNDO_USE_SPOS;
1627# endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001628
1629 // "u_type" values
1630 // UNDO_INS: insertion, undo will remove from buffer
1631 // UNDO_DEL: deleted text, undo will restore to buffer
1632 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
1633 // The CHAIN operations are for handling multiple operations that the user
1634 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
1635 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
Ron Yorston9b2a3892021-04-15 12:00:50 +01001636 // for the INS/DEL operation.
1637 // UNDO_{INS,DEL} ORed with UNDO_USE_SPOS: commit the undo queue
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001638
1639# if ENABLE_FEATURE_VI_UNDO_QUEUE
1640 // This undo queuing functionality groups multiple character typing or backspaces
1641 // into a single large undo object. This greatly reduces calls to malloc() for
1642 // single-character operations while typing and has the side benefit of letting
1643 // an undo operation remove chunks of text rather than a single character.
1644 switch (u_type) {
1645 case UNDO_EMPTY: // Just in case this ever happens...
1646 return;
1647 case UNDO_DEL_QUEUED:
1648 if (length != 1)
1649 return; // Only queue single characters
1650 switch (undo_queue_state) {
1651 case UNDO_EMPTY:
1652 undo_queue_state = UNDO_DEL;
1653 case UNDO_DEL:
1654 undo_queue_spos = src;
1655 undo_q++;
1656 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
1657 // If queue is full, dump it into an object
1658 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1659 undo_queue_commit();
1660 return;
1661 case UNDO_INS:
1662 // Switch from storing inserted text to deleted text
1663 undo_queue_commit();
1664 undo_push(src, length, UNDO_DEL_QUEUED);
1665 return;
1666 }
1667 break;
1668 case UNDO_INS_QUEUED:
1669 if (length < 1)
1670 return;
1671 switch (undo_queue_state) {
1672 case UNDO_EMPTY:
1673 undo_queue_state = UNDO_INS;
1674 undo_queue_spos = src;
1675 case UNDO_INS:
1676 while (length--) {
1677 undo_q++; // Don't need to save any data for insertions
1678 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1679 undo_queue_commit();
1680 }
1681 return;
1682 case UNDO_DEL:
1683 // Switch from storing deleted text to inserted text
1684 undo_queue_commit();
1685 undo_push(src, length, UNDO_INS_QUEUED);
1686 return;
1687 }
1688 break;
1689 }
Ron Yorston9b2a3892021-04-15 12:00:50 +01001690 u_type &= ~UNDO_USE_SPOS;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001691# endif
1692
1693 // Allocate a new undo object
1694 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
1695 // For UNDO_DEL objects, save deleted text
1696 if ((text + length) == end)
1697 length--;
1698 // If this deletion empties text[], strip the newline. When the buffer becomes
1699 // zero-length, a newline is added back, which requires this to compensate.
1700 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
1701 memcpy(undo_entry->undo_text, src, length);
1702 } else {
1703 undo_entry = xzalloc(sizeof(*undo_entry));
1704 }
1705 undo_entry->length = length;
1706# if ENABLE_FEATURE_VI_UNDO_QUEUE
Ron Yorston9b2a3892021-04-15 12:00:50 +01001707 if (use_spos) {
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001708 undo_entry->start = undo_queue_spos - text; // use start position from queue
1709 } else {
1710 undo_entry->start = src - text; // use offset from start of text buffer
1711 }
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001712# else
1713 undo_entry->start = src - text;
1714# endif
1715 undo_entry->u_type = u_type;
1716
1717 // Push it on undo stack
1718 undo_entry->prev = undo_stack_tail;
1719 undo_stack_tail = undo_entry;
1720 modified_count++;
1721}
1722
1723static void undo_push_insert(char *p, int len, int undo)
1724{
1725 switch (undo) {
1726 case ALLOW_UNDO:
1727 undo_push(p, len, UNDO_INS);
1728 break;
1729 case ALLOW_UNDO_CHAIN:
1730 undo_push(p, len, UNDO_INS_CHAIN);
1731 break;
1732# if ENABLE_FEATURE_VI_UNDO_QUEUE
1733 case ALLOW_UNDO_QUEUED:
1734 undo_push(p, len, UNDO_INS_QUEUED);
1735 break;
1736# endif
1737 }
1738}
1739
1740// Undo the last operation
1741static void undo_pop(void)
1742{
1743 int repeat;
1744 char *u_start, *u_end;
1745 struct undo_object *undo_entry;
1746
1747 // Commit pending undo queue before popping (should be unnecessary)
1748 undo_queue_commit();
1749
1750 undo_entry = undo_stack_tail;
1751 // Check for an empty undo stack
1752 if (!undo_entry) {
1753 status_line("Already at oldest change");
1754 return;
1755 }
1756
1757 switch (undo_entry->u_type) {
1758 case UNDO_DEL:
1759 case UNDO_DEL_CHAIN:
1760 // make hole and put in text that was deleted; deallocate text
1761 u_start = text + undo_entry->start;
1762 text_hole_make(u_start, undo_entry->length);
1763 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
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, "restored",
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 case UNDO_INS:
1772 case UNDO_INS_CHAIN:
1773 // delete what was inserted
1774 u_start = undo_entry->start + text;
1775 u_end = u_start - 1 + undo_entry->length;
1776 text_hole_delete(u_start, u_end, NO_UNDO);
Ron Yorstonf4a99082021-04-06 13:44:05 +01001777# if ENABLE_FEATURE_VI_VERBOSE_STATUS
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001778 status_line("Undo [%d] %s %d chars at position %d",
1779 modified_count, "deleted",
1780 undo_entry->length, undo_entry->start
1781 );
Ron Yorstonf4a99082021-04-06 13:44:05 +01001782# endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001783 break;
1784 }
1785 repeat = 0;
1786 switch (undo_entry->u_type) {
1787 // If this is the end of a chain, lower modification count and refresh display
1788 case UNDO_DEL:
1789 case UNDO_INS:
1790 dot = (text + undo_entry->start);
1791 refresh(FALSE);
1792 break;
1793 case UNDO_DEL_CHAIN:
1794 case UNDO_INS_CHAIN:
1795 repeat = 1;
1796 break;
1797 }
1798 // Deallocate the undo object we just processed
1799 undo_stack_tail = undo_entry->prev;
1800 free(undo_entry);
1801 modified_count--;
1802 // For chained operations, continue popping all the way down the chain.
1803 if (repeat) {
1804 undo_pop(); // Follow the undo chain if one exists
1805 }
1806}
1807
1808#else
1809# define flush_undo_data() ((void)0)
1810# define undo_queue_commit() ((void)0)
1811#endif /* ENABLE_FEATURE_VI_UNDO */
1812
1813//----- Dot Movement Routines ----------------------------------
1814static void dot_left(void)
1815{
1816 undo_queue_commit();
1817 if (dot > text && dot[-1] != '\n')
1818 dot--;
1819}
1820
1821static void dot_right(void)
1822{
1823 undo_queue_commit();
1824 if (dot < end - 1 && *dot != '\n')
1825 dot++;
1826}
1827
1828static void dot_begin(void)
1829{
1830 undo_queue_commit();
1831 dot = begin_line(dot); // return pointer to first char cur line
1832}
1833
1834static void dot_end(void)
1835{
1836 undo_queue_commit();
1837 dot = end_line(dot); // return pointer to last char cur line
1838}
1839
1840static char *move_to_col(char *p, int l)
1841{
1842 int co;
1843
1844 p = begin_line(p);
1845 co = 0;
Ron Yorstond9d19892021-04-15 12:01:34 +01001846 do {
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001847 if (*p == '\n') //vda || *p == '\0')
1848 break;
Ron Yorston310ef232021-04-17 09:25:11 +01001849 co = next_column(*p, co);
1850 } while (co <= l && p++ < end);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001851 return p;
1852}
1853
1854static void dot_next(void)
1855{
1856 undo_queue_commit();
1857 dot = next_line(dot);
1858}
1859
1860static void dot_prev(void)
1861{
1862 undo_queue_commit();
1863 dot = prev_line(dot);
1864}
1865
1866static void dot_skip_over_ws(void)
1867{
1868 // skip WS
1869 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1870 dot++;
1871}
1872
Ron Yorston15f4ac32021-03-28 17:15:30 +01001873static void dot_to_char(int cmd)
1874{
1875 char *q = dot;
1876 int dir = islower(cmd) ? FORWARD : BACK;
1877
1878 if (last_search_char == 0)
1879 return;
1880
1881 do {
1882 do {
1883 q += dir;
Ron Yorston99fb5f22021-04-06 13:44:36 +01001884 if ((dir == FORWARD ? q > end - 1 : q < text) || *q == '\n') {
1885 indicate_error();
Ron Yorston15f4ac32021-03-28 17:15:30 +01001886 return;
Ron Yorston99fb5f22021-04-06 13:44:36 +01001887 }
Ron Yorston15f4ac32021-03-28 17:15:30 +01001888 } while (*q != last_search_char);
1889 } while (--cmdcnt > 0);
1890
1891 dot = q;
1892
1893 // place cursor before/after char as required
1894 if (cmd == 't')
1895 dot_left();
1896 else if (cmd == 'T')
1897 dot_right();
1898}
1899
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001900static void dot_scroll(int cnt, int dir)
1901{
1902 char *q;
1903
1904 undo_queue_commit();
1905 for (; cnt > 0; cnt--) {
1906 if (dir < 0) {
1907 // scroll Backwards
1908 // ctrl-Y scroll up one line
1909 screenbegin = prev_line(screenbegin);
1910 } else {
1911 // scroll Forwards
1912 // ctrl-E scroll down one line
1913 screenbegin = next_line(screenbegin);
1914 }
1915 }
1916 // make sure "dot" stays on the screen so we dont scroll off
1917 if (dot < screenbegin)
1918 dot = screenbegin;
1919 q = end_screen(); // find new bottom line
1920 if (dot > q)
1921 dot = begin_line(q); // is dot is below bottom line?
1922 dot_skip_over_ws();
1923}
1924
1925static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1926{
1927 if (p >= end && end > text) {
1928 p = end - 1;
1929 indicate_error();
1930 }
1931 if (p < text) {
1932 p = text;
1933 indicate_error();
1934 }
1935 return p;
1936}
1937
1938#if ENABLE_FEATURE_VI_DOT_CMD
1939static void start_new_cmd_q(char c)
1940{
1941 // get buffer for new cmd
Ron Yorstona5445022021-04-06 16:48:07 +01001942 dotcnt = cmdcnt ?: 1;
1943 last_modifying_cmd[0] = c;
1944 lmc_len = 1;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001945 adding2q = 1;
1946}
1947static void end_cmd_q(void)
1948{
1949# if ENABLE_FEATURE_VI_YANKMARK
1950 YDreg = 26; // go back to default Yank/Delete reg
1951# endif
1952 adding2q = 0;
1953}
1954#else
1955# define end_cmd_q() ((void)0)
1956#endif /* FEATURE_VI_DOT_CMD */
1957
1958// copy text into register, then delete text.
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001959//
1960#if !ENABLE_FEATURE_VI_UNDO
1961#define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
1962#endif
Ron Yorston25d25922021-03-25 14:23:36 +00001963static char *yank_delete(char *start, char *stop, int buftype, int yf, int undo)
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001964{
1965 char *p;
1966
1967 // make sure start <= stop
1968 if (start > stop) {
1969 // they are backwards, reverse them
1970 p = start;
1971 start = stop;
1972 stop = p;
1973 }
Ron Yorston25d25922021-03-25 14:23:36 +00001974 if (buftype == PARTIAL && *start == '\n')
1975 return start;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001976 p = start;
1977#if ENABLE_FEATURE_VI_YANKMARK
Ron Yorston25d25922021-03-25 14:23:36 +00001978 text_yank(start, stop, YDreg, buftype);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02001979#endif
1980 if (yf == YANKDEL) {
1981 p = text_hole_delete(start, stop, undo);
1982 } // delete lines
1983 return p;
1984}
1985
1986// might reallocate text[]!
1987static int file_insert(const char *fn, char *p, int initial)
1988{
1989 int cnt = -1;
1990 int fd, size;
1991 struct stat statbuf;
1992
1993 if (p < text)
1994 p = text;
1995 if (p > end)
1996 p = end;
1997
1998 fd = open(fn, O_RDONLY);
1999 if (fd < 0) {
2000 if (!initial)
2001 status_line_bold_errno(fn);
2002 return cnt;
2003 }
2004
2005 // Validate file
2006 if (fstat(fd, &statbuf) < 0) {
2007 status_line_bold_errno(fn);
2008 goto fi;
2009 }
2010 if (!S_ISREG(statbuf.st_mode)) {
2011 status_line_bold("'%s' is not a regular file", fn);
2012 goto fi;
2013 }
2014 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2015 p += text_hole_make(p, size);
2016 cnt = full_read(fd, p, size);
2017 if (cnt < 0) {
2018 status_line_bold_errno(fn);
2019 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2020 } else if (cnt < size) {
2021 // There was a partial read, shrink unused space
2022 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2023 status_line_bold("can't read '%s'", fn);
2024 }
Ron Yorstondadd9092021-04-25 11:54:24 +01002025# if ENABLE_FEATURE_VI_UNDO
2026 else {
2027 undo_push_insert(p, size, ALLOW_UNDO);
2028 }
2029# endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002030 fi:
2031 close(fd);
2032
2033#if ENABLE_FEATURE_VI_READONLY
2034 if (initial
2035 && ((access(fn, W_OK) < 0) ||
2036 // root will always have access()
2037 // so we check fileperms too
2038 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2039 )
2040 ) {
2041 SET_READONLY_FILE(readonly_mode);
2042 }
2043#endif
2044 return cnt;
2045}
2046
2047// find matching char of pair () [] {}
2048// will crash if c is not one of these
2049static char *find_pair(char *p, const char c)
2050{
2051 const char *braces = "()[]{}";
2052 char match;
2053 int dir, level;
2054
2055 dir = strchr(braces, c) - braces;
2056 dir ^= 1;
2057 match = braces[dir];
2058 dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\}
2059
2060 // look for match, count levels of pairs (( ))
2061 level = 1;
2062 for (;;) {
2063 p += dir;
2064 if (p < text || p >= end)
2065 return NULL;
2066 if (*p == c)
2067 level++; // increase pair levels
2068 if (*p == match) {
2069 level--; // reduce pair level
2070 if (level == 0)
2071 return p; // found matching pair
2072 }
2073 }
2074}
2075
2076#if ENABLE_FEATURE_VI_SETOPTS
2077// show the matching char of a pair, () [] {}
2078static void showmatching(char *p)
2079{
2080 char *q, *save_dot;
2081
2082 // we found half of a pair
2083 q = find_pair(p, *p); // get loc of matching char
2084 if (q == NULL) {
2085 indicate_error(); // no matching char
2086 } else {
2087 // "q" now points to matching pair
2088 save_dot = dot; // remember where we are
2089 dot = q; // go to new loc
2090 refresh(FALSE); // let the user see it
2091 mysleep(40); // give user some time
2092 dot = save_dot; // go back to old loc
2093 refresh(FALSE);
2094 }
2095}
2096#endif /* FEATURE_VI_SETOPTS */
2097
2098// might reallocate text[]! use p += stupid_insert(p, ...),
2099// and be careful to not use pointers into potentially freed text[]!
2100static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2101{
2102 uintptr_t bias;
2103 bias = text_hole_make(p, 1);
2104 p += bias;
2105 *p = c;
2106 return bias;
2107}
2108
Ron Yorston9659a8d2021-05-20 08:27:48 +01002109// find number of characters in indent, p must be at beginning of line
2110static size_t indent_len(char *p)
2111{
2112 char *r = p;
2113
2114 while (r < (end - 1) && isblank(*r))
2115 r++;
2116 return r - p;
2117}
2118
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002119#if !ENABLE_FEATURE_VI_UNDO
2120#define char_insert(a,b,c) char_insert(a,b)
2121#endif
2122static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
2123{
Ron Yorstonac6495f2021-04-15 12:03:58 +01002124#if ENABLE_FEATURE_VI_SETOPTS
Denys Vlasenkob65e7f62021-04-15 23:17:01 +02002125 size_t len;
Ron Yorston16e2fa92021-05-20 08:27:19 +01002126 int col, ntab, nspc;
Ron Yorstonac6495f2021-04-15 12:03:58 +01002127#endif
Ron Yorston9659a8d2021-05-20 08:27:48 +01002128 char *bol = begin_line(p);
Ron Yorstonac6495f2021-04-15 12:03:58 +01002129
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002130 if (c == 22) { // Is this an ctrl-V?
2131 p += stupid_insert(p, '^'); // use ^ to indicate literal next
2132 refresh(FALSE); // show the ^
2133 c = get_one_char();
2134 *p = c;
2135#if ENABLE_FEATURE_VI_UNDO
2136 undo_push_insert(p, 1, undo);
2137#else
2138 modified_count++;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02002139#endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002140 p++;
2141 } else if (c == 27) { // Is this an ESC?
2142 cmd_mode = 0;
2143 undo_queue_commit();
2144 cmdcnt = 0;
2145 end_cmd_q(); // stop adding to q
2146 last_status_cksum = 0; // force status update
S Harris5c89e5a2021-06-21 11:04:49 +01002147 if ((dot > text) && (p[-1] != '\n')) {
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002148 p--;
2149 }
Ron Yorston9659a8d2021-05-20 08:27:48 +01002150#if ENABLE_FEATURE_VI_SETOPTS
2151 if (autoindent) {
2152 len = indent_len(bol);
Ron Yorstonf1d21b72021-06-16 14:45:10 +01002153 if (len && get_column(bol + len) == indentcol && bol[len] == '\n') {
Ron Yorston9659a8d2021-05-20 08:27:48 +01002154 // remove autoindent from otherwise empty line
2155 text_hole_delete(bol, bol + len - 1, undo);
2156 p = bol;
2157 }
Ron Yorstonac6495f2021-04-15 12:03:58 +01002158 }
Ron Yorston9659a8d2021-05-20 08:27:48 +01002159#endif
2160 } else if (c == 4) { // ctrl-D reduces indentation
2161 char *r = bol + indent_len(bol);
2162 int prev = prev_tabstop(get_column(r));
Ron Yorstonf277c9e2021-04-17 09:25:47 +01002163 while (r > bol && get_column(r) > prev) {
2164 if (p > bol)
2165 p--;
2166 r--;
2167 r = text_hole_delete(r, r, ALLOW_UNDO_QUEUED);
2168 }
Ron Yorston9659a8d2021-05-20 08:27:48 +01002169
2170#if ENABLE_FEATURE_VI_SETOPTS
2171 if (autoindent && indentcol && r == end_line(p)) {
2172 // record changed size of autoindent
2173 indentcol = get_column(p);
2174 return p;
2175 }
2176#endif
Ron Yorstonf277c9e2021-04-17 09:25:47 +01002177#if ENABLE_FEATURE_VI_SETOPTS
Ron Yorston310ef232021-04-17 09:25:11 +01002178 } else if (c == '\t' && expandtab) { // expand tab
Ron Yorston16e2fa92021-05-20 08:27:19 +01002179 col = get_column(p);
Ron Yorston310ef232021-04-17 09:25:11 +01002180 col = next_tabstop(col) - col + 1;
2181 while (col--) {
2182# if ENABLE_FEATURE_VI_UNDO
2183 undo_push_insert(p, 1, undo);
2184# else
2185 modified_count++;
2186# endif
2187 p += 1 + stupid_insert(p, ' ');
2188 }
Ron Yorstonac6495f2021-04-15 12:03:58 +01002189#endif
Denys Vlasenkob29dce42019-04-01 17:17:02 +02002190 } else if (c == term_orig.c_cc[VERASE] || c == 8 || c == 127) { // Is this a BS
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002191 if (p > text) {
2192 p--;
2193 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2194 }
2195 } else {
2196 // insert a char into text[]
2197 if (c == 13)
2198 c = '\n'; // translate \r to \n
2199#if ENABLE_FEATURE_VI_UNDO
2200# if ENABLE_FEATURE_VI_UNDO_QUEUE
2201 if (c == '\n')
2202 undo_queue_commit();
2203# endif
2204 undo_push_insert(p, 1, undo);
2205#else
2206 modified_count++;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02002207#endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002208 p += 1 + stupid_insert(p, c); // insert the char
2209#if ENABLE_FEATURE_VI_SETOPTS
2210 if (showmatch && strchr(")]}", c) != NULL) {
2211 showmatching(p - 1);
2212 }
2213 if (autoindent && c == '\n') { // auto indent the new line
Ron Yorston16e2fa92021-05-20 08:27:19 +01002214 // use indent of current/previous line
Ron Yorston9659a8d2021-05-20 08:27:48 +01002215 bol = indentcol < 0 ? p : prev_line(p);
2216 len = indent_len(bol);
2217 col = get_column(bol + len);
2218
2219 if (len && col == indentcol) {
2220 // previous line was empty except for autoindent
2221 // move the indent to the current line
2222 memmove(bol + 1, bol, len);
2223 *bol = '\n';
2224 return p;
2225 }
2226
2227 if (indentcol < 0)
2228 p--; // open above, indent before newly inserted NL
2229
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002230 if (len) {
Ron Yorston9659a8d2021-05-20 08:27:48 +01002231 indentcol = col;
Ron Yorston16e2fa92021-05-20 08:27:19 +01002232 if (expandtab) {
2233 ntab = 0;
2234 nspc = col;
2235 } else {
2236 ntab = col / tabstop;
2237 nspc = col % tabstop;
2238 }
2239 p += text_hole_make(p, ntab + nspc);
Denys Vlasenkob65e7f62021-04-15 23:17:01 +02002240# if ENABLE_FEATURE_VI_UNDO
Ron Yorston16e2fa92021-05-20 08:27:19 +01002241 undo_push_insert(p, ntab + nspc, undo);
Denys Vlasenkob65e7f62021-04-15 23:17:01 +02002242# endif
Ron Yorston16e2fa92021-05-20 08:27:19 +01002243 memset(p, '\t', ntab);
2244 p += ntab;
2245 memset(p, ' ', nspc);
Ron Yorston9659a8d2021-05-20 08:27:48 +01002246 return p + nspc;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002247 }
2248 }
2249#endif
2250 }
Ron Yorston9659a8d2021-05-20 08:27:48 +01002251#if ENABLE_FEATURE_VI_SETOPTS
2252 indentcol = 0;
2253#endif
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002254 return p;
2255}
2256
Ron Yorstonacd30792021-04-25 11:55:42 +01002257#if ENABLE_FEATURE_VI_COLON_EXPAND
2258static void init_filename(char *fn)
2259{
2260 char *copy = xstrdup(fn);
2261
2262 if (current_filename == NULL) {
2263 current_filename = copy;
2264 } else {
2265 free(alt_filename);
2266 alt_filename = copy;
2267 }
2268}
2269#else
2270# define init_filename(f) ((void)(0))
2271#endif
2272
2273static void update_filename(char *fn)
2274{
2275#if ENABLE_FEATURE_VI_COLON_EXPAND
2276 if (fn == NULL)
2277 return;
2278
2279 if (current_filename == NULL || strcmp(fn, current_filename) != 0) {
2280 free(alt_filename);
2281 alt_filename = current_filename;
2282 current_filename = xstrdup(fn);
2283 }
2284#else
2285 if (fn != current_filename) {
2286 free(current_filename);
2287 current_filename = xstrdup(fn);
2288 }
2289#endif
2290}
2291
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002292// read text from file or create an empty buf
2293// will also update current_filename
2294static int init_text_buffer(char *fn)
2295{
2296 int rc;
2297
2298 // allocate/reallocate text buffer
2299 free(text);
2300 text_size = 10240;
2301 screenbegin = dot = end = text = xzalloc(text_size);
2302
Ron Yorstonacd30792021-04-25 11:55:42 +01002303 update_filename(fn);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002304 rc = file_insert(fn, text, 1);
2305 if (rc < 0) {
2306 // file doesnt exist. Start empty buf with dummy line
2307 char_insert(text, '\n', NO_UNDO);
2308 }
2309
2310 flush_undo_data();
2311 modified_count = 0;
2312 last_modified_count = -1;
2313#if ENABLE_FEATURE_VI_YANKMARK
2314 // init the marks
2315 memset(mark, 0, sizeof(mark));
2316#endif
2317 return rc;
2318}
2319
2320#if ENABLE_FEATURE_VI_YANKMARK \
2321 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2322 || ENABLE_FEATURE_VI_CRASHME
2323// might reallocate text[]! use p += string_insert(p, ...),
2324// and be careful to not use pointers into potentially freed text[]!
2325# if !ENABLE_FEATURE_VI_UNDO
2326# define string_insert(a,b,c) string_insert(a,b)
2327# endif
2328static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2329{
2330 uintptr_t bias;
2331 int i;
2332
2333 i = strlen(s);
2334#if ENABLE_FEATURE_VI_UNDO
2335 undo_push_insert(p, i, undo);
2336#endif
2337 bias = text_hole_make(p, i);
2338 p += bias;
2339 memcpy(p, s, i);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002340 return bias;
2341}
2342#endif
2343
2344static int file_write(char *fn, char *first, char *last)
2345{
2346 int fd, cnt, charcnt;
2347
2348 if (fn == 0) {
2349 status_line_bold("No current filename");
2350 return -2;
2351 }
2352 // By popular request we do not open file with O_TRUNC,
2353 // but instead ftruncate() it _after_ successful write.
2354 // Might reduce amount of data lost on power fail etc.
2355 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2356 if (fd < 0)
2357 return -1;
2358 cnt = last - first + 1;
2359 charcnt = full_write(fd, first, cnt);
2360 ftruncate(fd, charcnt);
2361 if (charcnt == cnt) {
2362 // good write
2363 //modified_count = FALSE;
2364 } else {
2365 charcnt = 0;
2366 }
2367 close(fd);
2368 return charcnt;
2369}
2370
2371#if ENABLE_FEATURE_VI_SEARCH
2372# if ENABLE_FEATURE_VI_REGEX_SEARCH
2373// search for pattern starting at p
2374static char *char_search(char *p, const char *pat, int dir_and_range)
2375{
2376 struct re_pattern_buffer preg;
2377 const char *err;
2378 char *q;
Ron Yorston51358752021-06-21 11:30:39 +01002379 int i, size, range, start;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002380
2381 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
2382 if (ignorecase)
2383 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
2384
2385 memset(&preg, 0, sizeof(preg));
2386 err = re_compile_pattern(pat, strlen(pat), &preg);
2387 if (err != NULL) {
2388 status_line_bold("bad search pattern '%s': %s", pat, err);
2389 return p;
2390 }
2391
2392 range = (dir_and_range & 1);
2393 q = end - 1; // if FULL
2394 if (range == LIMITED)
2395 q = next_line(p);
2396 if (dir_and_range < 0) { // BACK?
2397 q = text;
2398 if (range == LIMITED)
2399 q = prev_line(p);
2400 }
2401
2402 // RANGE could be negative if we are searching backwards
2403 range = q - p;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002404 if (range < 0) {
Ron Yorston51358752021-06-21 11:30:39 +01002405 size = -range;
2406 start = size;
2407 } else {
2408 size = range;
2409 start = 0;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002410 }
Ron Yorston51358752021-06-21 11:30:39 +01002411 q = p - start;
2412 if (q < text)
2413 q = text;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002414 // search for the compiled pattern, preg, in p[]
Ron Yorston51358752021-06-21 11:30:39 +01002415 // range < 0, start == size: search backward
2416 // range > 0, start == 0: search forward
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002417 // re_search() < 0: not found or error
2418 // re_search() >= 0: index of found pattern
2419 // struct pattern char int int int struct reg
2420 // re_search(*pattern_buffer, *string, size, start, range, *regs)
Ron Yorston51358752021-06-21 11:30:39 +01002421 i = re_search(&preg, q, size, start, range, /*struct re_registers*:*/ NULL);
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002422 regfree(&preg);
Ron Yorston51358752021-06-21 11:30:39 +01002423 return i < 0 ? NULL : q + i;
Denys Vlasenkod4f2e7f2019-04-01 14:18:02 +02002424}
2425# else
2426# if ENABLE_FEATURE_VI_SETOPTS
2427static int mycmp(const char *s1, const char *s2, int len)
2428{
2429 if (ignorecase) {
2430 return strncasecmp(s1, s2, len);
2431 }
2432 return strncmp(s1, s2, len);
2433}
2434# else
2435# define mycmp strncmp
2436# endif
2437static char *char_search(char *p, const char *pat, int dir_and_range)
2438{
2439 char *start, *stop;
2440 int len;
2441 int range;
2442
2443 len = strlen(pat);
2444 range = (dir_and_range & 1);
2445 if (dir_and_range > 0) { //FORWARD?
2446 stop = end - 1; // assume range is p..end-1
2447 if (range == LIMITED)
2448 stop = next_line(p); // range is to next line
2449 for (start = p; start < stop; start++) {
2450 if (mycmp(start, pat, len) == 0) {
2451 return start;
2452 }
2453 }
2454 } else { //BACK
2455 stop = text; // assume range is text..p
2456 if (range == LIMITED)
2457 stop = prev_line(p); // range is to prev line
2458 for (start = p - len; start >= stop; start--) {
2459 if (mycmp(start, pat, len) == 0) {
2460 return start;
2461 }
2462 }
2463 }
2464 // pattern not found
2465 return NULL;
2466}
2467# endif
2468#endif /* FEATURE_VI_SEARCH */
2469
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002470//----- The Colon commands -------------------------------------
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00002471#if ENABLE_FEATURE_VI_COLON
Ron Yorstonf2277262021-04-15 12:06:51 +01002472static char *get_one_address(char *p, int *result) // get colon addr, if present
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002473{
Ron Yorstonf2277262021-04-15 12:06:51 +01002474 int st, num, sign, addr, new_addr;
Ron Yorston5d1bb582021-04-15 12:05:14 +01002475# if ENABLE_FEATURE_VI_YANKMARK || ENABLE_FEATURE_VI_SEARCH
Ron Yorston47f78912021-04-15 12:06:11 +01002476 char *q, c;
Ron Yorston5d1bb582021-04-15 12:05:14 +01002477# endif
Ron Yorston47f78912021-04-15 12:06:11 +01002478 IF_FEATURE_VI_SEARCH(int dir;)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002479
Ron Yorstonf2277262021-04-15 12:06:51 +01002480 addr = -1; // assume no addr
2481 sign = 0;
2482 for (;;) {
2483 new_addr = -1;
2484 if (isblank(*p)) {
2485 p++;
2486 } else if (*p == '.') { // the current line
2487 p++;
2488 new_addr = count_lines(text, dot);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002489 }
Ron Yorstonf2277262021-04-15 12:06:51 +01002490# if ENABLE_FEATURE_VI_YANKMARK
2491 else if (*p == '\'') { // is this a mark addr
2492 p++;
2493 c = tolower(*p);
2494 p++;
2495 q = NULL;
2496 if (c >= 'a' && c <= 'z') {
2497 // we have a mark
2498 c = c - 'a';
2499 q = mark[(unsigned char) c];
2500 }
2501 if (q == NULL) // is mark valid
2502 return NULL;
2503 new_addr = count_lines(text, q);
2504 }
Denys Vlasenko70ee2332021-03-01 14:41:39 +01002505# endif
2506# if ENABLE_FEATURE_VI_SEARCH
Ron Yorstonf2277262021-04-15 12:06:51 +01002507 else if (*p == '/' || *p == '?') { // a search pattern
2508 c = *p;
2509 q = strchrnul(p + 1, c);
2510 if (p + 1 != q) {
2511 // save copy of new pattern
2512 free(last_search_pattern);
2513 last_search_pattern = xstrndup(p, q - p);
2514 }
2515 p = q;
2516 if (*p == c)
2517 p++;
2518 if (c == '/') {
2519 q = next_line(dot);
2520 dir = (FORWARD << 1) | FULL;
2521 } else {
2522 q = begin_line(dot);
2523 dir = ((unsigned)BACK << 1) | FULL;
2524 }
2525 q = char_search(q, last_search_pattern + 1, dir);
2526 if (q == NULL)
2527 return NULL;
2528 new_addr = count_lines(text, q);
Ron Yorston16bcd502020-01-23 15:31:19 +00002529 }
Denys Vlasenko70ee2332021-03-01 14:41:39 +01002530# endif
Ron Yorstonf2277262021-04-15 12:06:51 +01002531 else if (*p == '$') { // the last line in file
2532 p++;
2533 new_addr = count_lines(text, end - 1);
2534 } else if (isdigit(*p)) {
2535 sscanf(p, "%d%n", &num, &st);
2536 p += st;
2537 if (addr < 0) { // specific line number
2538 addr = num;
2539 } else { // offset from current addr
2540 addr += sign >= 0 ? num : -num;
2541 }
2542 sign = 0;
2543 } else if (*p == '-' || *p == '+') {
2544 sign = *p++ == '-' ? -1 : 1;
2545 if (addr < 0) { // default address is dot
2546 addr = count_lines(text, dot);
2547 }
2548 } else {
2549 addr += sign; // consume unused trailing sign
2550 break;
2551 }
2552 if (new_addr >= 0) {
2553 if (addr >= 0) // only one new address per expression
2554 return NULL;
2555 addr = new_addr;
2556 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002557 }
Ron Yorstonf2277262021-04-15 12:06:51 +01002558 *result = addr;
Denis Vlasenko079f8af2006-11-27 16:49:31 +00002559 return p;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002560}
2561
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002562# define GET_ADDRESS 0
2563# define GET_SEPARATOR 1
Ron Yorston5d1bb582021-04-15 12:05:14 +01002564
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002565// Read line addresses for a colon command. The user can enter as
2566// many as they like but only the last two will be used.
2567static char *get_address(char *p, int *b, int *e)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002568{
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002569 int state = GET_ADDRESS;
2570 char *save_dot = dot;
Ron Yorston5d1bb582021-04-15 12:05:14 +01002571
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002572 //----- get the address' i.e., 1,3 'a,'b -----
Ron Yorston5d1bb582021-04-15 12:05:14 +01002573 for (;;) {
2574 if (isblank(*p)) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002575 p++;
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002576 } else if (*p == '%' && state == GET_ADDRESS) { // alias for 1,$
Ron Yorston5d1bb582021-04-15 12:05:14 +01002577 p++;
2578 *b = 1;
2579 *e = count_lines(text, end-1);
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002580 state = GET_SEPARATOR;
2581 } else if (state == GET_SEPARATOR && (*p == ',' || *p == ';')) {
2582 if (*p == ';')
2583 dot = find_line(*e);
Ron Yorston5d1bb582021-04-15 12:05:14 +01002584 p++;
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002585 *b = *e;
2586 state = GET_ADDRESS;
2587 } else if (state == GET_ADDRESS) {
2588 p = get_one_address(p, e);
Ron Yorstond488def2021-04-15 12:05:45 +01002589 if (p == NULL)
2590 break;
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002591 state = GET_SEPARATOR;
Ron Yorston5d1bb582021-04-15 12:05:14 +01002592 } else {
Ron Yorston0c42a6b2021-04-29 08:36:21 +01002593 if (state == GET_SEPARATOR && *b >= 0 && *e < 0)
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002594 *e = count_lines(text, dot);
Ron Yorston5d1bb582021-04-15 12:05:14 +01002595 break;
2596 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002597 }
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002598 dot = save_dot;
Denis Vlasenko079f8af2006-11-27 16:49:31 +00002599 return p;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002600}
2601
Denys Vlasenko70ee2332021-03-01 14:41:39 +01002602# if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
Ron Yorston9f017d92021-04-06 22:11:21 +01002603static void setops(char *args, int flg_no)
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002604{
Ron Yorston9f017d92021-04-06 22:11:21 +01002605 char *eq;
2606 int index;
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002607
Ron Yorston9f017d92021-04-06 22:11:21 +01002608 eq = strchr(args, '=');
2609 if (eq) *eq = '\0';
2610 index = index_in_strings(OPTS_STR, args + flg_no);
2611 if (eq) *eq = '=';
2612 if (index < 0) {
2613 bad:
2614 status_line_bold("bad option: %s", args);
2615 return;
2616 }
2617
2618 index = 1 << (index >> 1); // convert to VI_bit
2619
2620 if (index & VI_TABSTOP) {
2621 int t;
2622 if (!eq || flg_no) // no "=NNN" or it is "notabstop"?
2623 goto bad;
2624 t = bb_strtou(eq + 1, NULL, 10);
2625 if (t <= 0 || t > MAX_TABSTOP)
2626 goto bad;
2627 tabstop = t;
2628 return;
2629 }
2630 if (eq) goto bad; // boolean option has "="?
2631 if (flg_no) {
2632 vi_setops &= ~index;
2633 } else {
2634 vi_setops |= index;
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002635 }
2636}
Denys Vlasenko70ee2332021-03-01 14:41:39 +01002637# endif
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002638
Ron Yorstonacd30792021-04-25 11:55:42 +01002639# if ENABLE_FEATURE_VI_COLON_EXPAND
2640static char *expand_args(char *args)
2641{
2642 char *s, *t;
2643 const char *replace;
2644
2645 args = xstrdup(args);
2646 for (s = args; *s; s++) {
2647 if (*s == '%') {
2648 replace = current_filename;
2649 } else if (*s == '#') {
2650 replace = alt_filename;
2651 } else {
2652 if (*s == '\\' && s[1] != '\0') {
2653 for (t = s++; *t; t++)
2654 *t = t[1];
2655 }
2656 continue;
2657 }
2658
2659 if (replace == NULL) {
2660 free(args);
2661 status_line_bold("No previous filename");
2662 return NULL;
2663 }
2664
2665 *s = '\0';
2666 t = xasprintf("%s%s%s", args, replace, s+1);
2667 s = t + (s - args) + strlen(replace);
2668 free(args);
2669 args = t;
2670 }
2671 return args;
2672}
2673# else
2674# define expand_args(a) (a)
2675# endif
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002676#endif /* FEATURE_VI_COLON */
2677
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002678// buf must be no longer than MAX_INPUT_LEN!
2679static void colon(char *buf)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002680{
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002681#if !ENABLE_FEATURE_VI_COLON
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02002682 // Simple ":cmd" handler with minimal set of commands
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002683 char *p = buf;
2684 int cnt;
2685
2686 if (*p == ':')
2687 p++;
2688 cnt = strlen(p);
2689 if (cnt == 0)
2690 return;
2691 if (strncmp(p, "quit", cnt) == 0
Ron Yorston852ffbe2021-04-25 11:55:01 +01002692 || strcmp(p, "q!") == 0
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002693 ) {
2694 if (modified_count && p[1] != '!') {
2695 status_line_bold("No write since last change (:%s! overrides)", p);
2696 } else {
2697 editing = 0;
2698 }
2699 return;
2700 }
2701 if (strncmp(p, "write", cnt) == 0
Ron Yorston852ffbe2021-04-25 11:55:01 +01002702 || strcmp(p, "wq") == 0
2703 || strcmp(p, "wn") == 0
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002704 || (p[0] == 'x' && !p[1])
2705 ) {
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01002706 if (modified_count != 0 || p[0] != 'x') {
2707 cnt = file_write(current_filename, text, end - 1);
2708 }
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002709 if (cnt < 0) {
2710 if (cnt == -1)
Denys Vlasenko6f97b302017-09-29 18:17:25 +02002711 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002712 } else {
2713 modified_count = 0;
2714 last_modified_count = -1;
Denys Vlasenko89393592019-04-02 12:45:30 +02002715 status_line("'%s' %uL, %uC",
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002716 current_filename,
2717 count_lines(text, end - 1), cnt
2718 );
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01002719 if (p[0] == 'x'
2720 || p[1] == 'q' || p[1] == 'n'
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002721 ) {
2722 editing = 0;
2723 }
2724 }
2725 return;
2726 }
2727 if (strncmp(p, "file", cnt) == 0) {
2728 last_status_cksum = 0; // force status update
2729 return;
2730 }
2731 if (sscanf(p, "%d", &cnt) > 0) {
2732 dot = find_line(cnt);
2733 dot_skip_over_ws();
2734 return;
2735 }
2736 not_implemented(p);
2737#else
2738
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002739 char c, *buf1, *q, *r;
Ron Yorstonacd30792021-04-25 11:55:42 +01002740 char *fn, cmd[MAX_INPUT_LEN], *cmdend, *args, *exp = NULL;
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002741 int i, l, li, b, e;
2742 int useforce;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002743
2744 // :3154 // if (-e line 3154) goto it else stay put
2745 // :4,33w! foo // write a portion of buffer to file "foo"
2746 // :w // write all of buffer to current file
2747 // :q // quit
2748 // :q! // quit- dont care about modified file
2749 // :'a,'z!sort -u // filter block through sort
2750 // :'f // goto mark "f"
2751 // :'fl // list literal the mark "f" line
2752 // :.r bar // read file "bar" into buffer before dot
2753 // :/123/,/abc/d // delete lines from "123" line to "abc" line
2754 // :/xyz/ // goto the "xyz" line
2755 // :s/find/replace/ // substitute pattern "find" with "replace"
2756 // :!<cmd> // run <cmd> then return
2757 //
Eric Andersen165e8cb2004-07-20 06:44:46 +00002758
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002759 if (!buf[0])
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002760 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002761 if (*buf == ':')
2762 buf++; // move past the ':'
2763
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002764 li = i = 0;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002765 b = e = -1;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002766 li = count_lines(text, end - 1);
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002767 fn = current_filename;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002768
2769 // look for optional address(es) :. :1 :1,9 :'q,'a :%
Ron Yorston852ffbe2021-04-25 11:55:01 +01002770 buf1 = buf;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002771 buf = get_address(buf, &b, &e);
Ron Yorstond488def2021-04-15 12:05:45 +01002772 if (buf == NULL) {
Ron Yorston852ffbe2021-04-25 11:55:01 +01002773 status_line_bold("Bad address: %s", buf1);
Ron Yorstond488def2021-04-15 12:05:45 +01002774 goto ret;
2775 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002776
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002777 // get the COMMAND into cmd[]
Ron Yorston852ffbe2021-04-25 11:55:01 +01002778 strcpy(cmd, buf);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002779 buf1 = cmd;
Ron Yorston852ffbe2021-04-25 11:55:01 +01002780 while (!isspace(*buf1) && *buf1 != '\0') {
2781 buf1++;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002782 }
Ron Yorston852ffbe2021-04-25 11:55:01 +01002783 cmdend = buf1;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002784 // get any ARGuments
Ron Yorston852ffbe2021-04-25 11:55:01 +01002785 while (isblank(*buf1))
2786 buf1++;
2787 args = buf1;
2788 *cmdend = '\0';
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002789 useforce = FALSE;
Ron Yorston852ffbe2021-04-25 11:55:01 +01002790 if (cmdend > cmd && cmdend[-1] == '!') {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002791 useforce = TRUE;
Ron Yorston852ffbe2021-04-25 11:55:01 +01002792 cmdend[-1] = '\0'; // get rid of !
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002793 }
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002794 // assume the command will want a range, certain commands
2795 // (read, substitute) need to adjust these assumptions
2796 if (e < 0) {
2797 q = text; // no addr, use 1,$ for the range
2798 r = end - 1;
2799 } else {
2800 // at least one addr was given, get its details
2801 q = r = find_line(e);
2802 if (b < 0) {
2803 // if there is only one addr, then it's the line
2804 // number of the single line the user wants.
2805 // Reset the end pointer to the end of that line.
2806 r = end_line(q);
2807 li = 1;
2808 } else {
2809 // we were given two addrs. change the
2810 // start pointer to the addr given by user.
2811 q = find_line(b); // what line is #b
2812 r = end_line(r);
2813 li = e - b + 1;
2814 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002815 }
2816 // ------------ now look for the command ------------
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002817 i = strlen(cmd);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002818 if (i == 0) { // :123CR goto line #123
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002819 if (e >= 0) {
2820 dot = find_line(e); // what line is #e
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002821 dot_skip_over_ws();
2822 }
Denis Vlasenko249fabf2006-12-19 00:29:22 +00002823 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002824# if ENABLE_FEATURE_ALLOW_EXEC
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002825 else if (cmd[0] == '!') { // run a cmd
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002826 int retcode;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002827 // :!ls run the <cmd>
Ron Yorstonacd30792021-04-25 11:55:42 +01002828 exp = expand_args(buf + 1);
2829 if (exp == NULL)
2830 goto ret;
Denis Vlasenko267e16c2008-10-14 10:34:41 +00002831 go_bottom_and_clear_to_eol();
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002832 cookmode();
Ron Yorstonacd30792021-04-25 11:55:42 +01002833 retcode = system(exp); // run the cmd
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002834 if (retcode)
2835 printf("\nshell returned %i\n\n", retcode);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002836 rawmode();
2837 Hit_Return(); // let user see results
Denis Vlasenko249fabf2006-12-19 00:29:22 +00002838 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002839# endif
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002840 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002841 if (e < 0) { // no addr given- use defaults
2842 e = count_lines(text, dot);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002843 }
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002844 status_line("%d", e);
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002845 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002846 if (e < 0) { // no addr given- use defaults
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002847 q = begin_line(dot); // assume .,. for the range
2848 r = end_line(dot);
2849 }
Ron Yorston25d25922021-03-25 14:23:36 +00002850 dot = yank_delete(q, r, WHOLE, YANKDEL, ALLOW_UNDO); // save, then delete lines
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002851 dot_skip_over_ws();
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002852 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002853 int size;
2854
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002855 // don't edit, if the current file has been modified
Denys Vlasenkoe7430862014-04-03 12:47:48 +02002856 if (modified_count && !useforce) {
Dennis Groenenc0657e02012-01-31 14:12:38 +01002857 status_line_bold("No write since last change (:%s! overrides)", cmd);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002858 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002859 }
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002860 if (args[0]) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002861 // the user supplied a file name
Ron Yorstonacd30792021-04-25 11:55:42 +01002862 fn = exp = expand_args(args);
2863 if (exp == NULL)
2864 goto ret;
Ron Yorstondadd9092021-04-25 11:54:24 +01002865 } else if (current_filename == NULL) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002866 // no user file name, no current name- punt
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002867 status_line_bold("No current filename");
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002868 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002869 }
2870
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002871 size = init_text_buffer(fn);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002872
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002873# if ENABLE_FEATURE_VI_YANKMARK
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002874 if (Ureg >= 0 && Ureg < 28) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002875 free(reg[Ureg]); // free orig line reg- for 'U'
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002876 reg[Ureg] = NULL;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002877 }
Denys Vlasenkofa182a32019-06-08 12:57:07 +02002878 /*if (YDreg < 28) - always true*/ {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002879 free(reg[YDreg]); // free default yank/delete register
Denys Vlasenko800a9a02012-01-31 14:10:26 +01002880 reg[YDreg] = NULL;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002881 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02002882# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002883 // how many lines in text[]?
2884 li = count_lines(text, end - 1);
Denys Vlasenko778794d2013-01-22 10:13:52 +01002885 status_line("'%s'%s"
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00002886 IF_FEATURE_VI_READONLY("%s")
Denys Vlasenko89393592019-04-02 12:45:30 +02002887 " %uL, %uC",
Ron Yorstonacd30792021-04-25 11:55:42 +01002888 fn,
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002889 (size < 0 ? " [New file]" : ""),
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00002890 IF_FEATURE_VI_READONLY(
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00002891 ((readonly_mode) ? " [Readonly]" : ""),
2892 )
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002893 li, (int)(end - text)
2894 );
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002895 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002896 if (e >= 0) {
Denys Vlasenkoc2704542009-11-20 19:14:19 +01002897 status_line_bold("No address allowed on this command");
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002898 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002899 }
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002900 if (args[0]) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002901 // user wants a new filename
Ron Yorstonacd30792021-04-25 11:55:42 +01002902 exp = expand_args(args);
2903 if (exp == NULL)
2904 goto ret;
2905 update_filename(exp);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002906 } else {
2907 // user wants file status info
Paul Fox8552aec2005-09-16 12:20:05 +00002908 last_status_cksum = 0; // force status update
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002909 }
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002910 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002911 // print out values of all features
Denis Vlasenko267e16c2008-10-14 10:34:41 +00002912 go_bottom_and_clear_to_eol();
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002913 cookmode();
2914 show_help();
2915 rawmode();
2916 Hit_Return();
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002917 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002918 if (e < 0) { // no addr given- use defaults
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002919 q = begin_line(dot); // assume .,. for the range
2920 r = end_line(dot);
2921 }
Denis Vlasenko267e16c2008-10-14 10:34:41 +00002922 go_bottom_and_clear_to_eol();
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002923 puts("\r");
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002924 for (; q <= r; q++) {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002925 int c_is_no_print;
2926
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002927 c = *q;
Denis Vlasenko2a51af22007-03-21 22:31:24 +00002928 c_is_no_print = (c & 0x80) && !Isprint(c);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002929 if (c_is_no_print) {
2930 c = '.';
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002931 standout_start();
Denis Vlasenkod3c042f2007-12-30 01:59:53 +00002932 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002933 if (c == '\n') {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002934 write1("$\r");
2935 } else if (c < ' ' || c == 127) {
Denis Vlasenko4daad902007-09-27 10:20:47 +00002936 bb_putchar('^');
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002937 if (c == 127)
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002938 c = '?';
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002939 else
2940 c += '@';
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002941 }
Denis Vlasenko4daad902007-09-27 10:20:47 +00002942 bb_putchar(c);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00002943 if (c_is_no_print)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002944 standout_end();
2945 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002946 Hit_Return();
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002947 } else if (strncmp(cmd, "quit", i) == 0 // quit
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002948 || strncmp(cmd, "next", i) == 0 // edit next file
Dennis Groenenc0657e02012-01-31 14:12:38 +01002949 || strncmp(cmd, "prev", i) == 0 // edit previous file
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00002950 ) {
Denys Vlasenko04cecd52010-04-16 22:13:55 -07002951 int n;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002952 if (useforce) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002953 if (*cmd == 'q') {
Dennis Groenenc0657e02012-01-31 14:12:38 +01002954 // force end of argv list
Denys Vlasenkoa3ce1612019-04-03 16:35:23 +02002955 optind = cmdline_filecnt;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002956 }
2957 editing = 0;
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002958 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002959 }
2960 // don't exit if the file been modified
Denys Vlasenkoe7430862014-04-03 12:47:48 +02002961 if (modified_count) {
Dennis Groenenc0657e02012-01-31 14:12:38 +01002962 status_line_bold("No write since last change (:%s! overrides)", cmd);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002963 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002964 }
2965 // are there other file to edit
Denys Vlasenko89393592019-04-02 12:45:30 +02002966 n = cmdline_filecnt - optind - 1;
Denys Vlasenko04cecd52010-04-16 22:13:55 -07002967 if (*cmd == 'q' && n > 0) {
Denys Vlasenko89393592019-04-02 12:45:30 +02002968 status_line_bold("%u more file(s) to edit", n);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002969 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002970 }
Denys Vlasenko04cecd52010-04-16 22:13:55 -07002971 if (*cmd == 'n' && n <= 0) {
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00002972 status_line_bold("No more files to edit");
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002973 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002974 }
Dennis Groenenc0657e02012-01-31 14:12:38 +01002975 if (*cmd == 'p') {
2976 // are there previous files to edit
2977 if (optind < 1) {
2978 status_line_bold("No previous files to edit");
2979 goto ret;
2980 }
2981 optind -= 2;
2982 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002983 editing = 0;
Denys Vlasenko6548edd2009-06-15 12:44:11 +02002984 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
Ron Yorstondadd9092021-04-25 11:54:24 +01002985 int size, num;
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02002986
Ron Yorstondadd9092021-04-25 11:54:24 +01002987 if (args[0]) {
2988 // the user supplied a file name
Ron Yorstonacd30792021-04-25 11:55:42 +01002989 fn = exp = expand_args(args);
2990 if (exp == NULL)
2991 goto ret;
2992 init_filename(fn);
Ron Yorstondadd9092021-04-25 11:54:24 +01002993 } else if (current_filename == NULL) {
2994 // no user file name, no current name- punt
2995 status_line_bold("No current filename");
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02002996 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00002997 }
Ron Yorstonf7ed0e82021-06-09 16:11:03 +01002998 if (e == 0) { // user said ":0r foo"
Ron Yorston7a8ceb42021-04-25 11:51:55 +01002999 q = text;
Ron Yorstonf7ed0e82021-06-09 16:11:03 +01003000 } else { // read after given line or current line if none given
3001 q = next_line(e > 0 ? find_line(e) : dot);
Ron Yorston70f43202014-11-30 20:39:53 +00003002 // read after last line
3003 if (q == end-1)
3004 ++q;
3005 }
Ron Yorstondadd9092021-04-25 11:54:24 +01003006 num = count_lines(text, q);
3007 if (q == end)
3008 num++;
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00003009 { // dance around potentially-reallocated text[]
3010 uintptr_t ofs = q - text;
Ron Yorstone5213ce2014-11-30 20:39:25 +00003011 size = file_insert(fn, q, 0);
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00003012 q = text + ofs;
3013 }
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003014 if (size < 0)
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003015 goto ret; // nothing was inserted
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003016 // how many lines in text[]?
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003017 li = count_lines(q, q + size - 1);
Denys Vlasenko778794d2013-01-22 10:13:52 +01003018 status_line("'%s'"
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00003019 IF_FEATURE_VI_READONLY("%s")
Denys Vlasenko89393592019-04-02 12:45:30 +02003020 " %uL, %uC",
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003021 fn,
Denis Vlasenko5e34ff22009-04-21 11:09:40 +00003022 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003023 li, size
3024 );
Ron Yorstondadd9092021-04-25 11:54:24 +01003025 dot = find_line(num);
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003026 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
Denys Vlasenkoe7430862014-04-03 12:47:48 +02003027 if (modified_count && !useforce) {
Dennis Groenenc0657e02012-01-31 14:12:38 +01003028 status_line_bold("No write since last change (:%s! overrides)", cmd);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003029 } else {
3030 // reset the filenames to edit
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02003031 optind = -1; // start from 0th file
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003032 editing = 0;
3033 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003034# if ENABLE_FEATURE_VI_SET
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003035 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003036# if ENABLE_FEATURE_VI_SETOPTS
Ron Yorston9f017d92021-04-06 22:11:21 +01003037 char *argp, *argn, oldch;
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003038# endif
Denys Vlasenko605f2642012-06-11 01:53:33 +02003039 // only blank is regarded as args delimiter. What about tab '\t'?
Ron Yorston9f017d92021-04-06 22:11:21 +01003040 if (!args[0] || strcmp(args, "all") == 0) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003041 // print out values of all options
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003042# if ENABLE_FEATURE_VI_SETOPTS
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003043 status_line_bold(
3044 "%sautoindent "
Ron Yorston310ef232021-04-17 09:25:11 +01003045 "%sexpandtab "
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003046 "%sflash "
3047 "%signorecase "
3048 "%sshowmatch "
3049 "tabstop=%u",
3050 autoindent ? "" : "no",
Ron Yorston310ef232021-04-17 09:25:11 +01003051 expandtab ? "" : "no",
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003052 err_method ? "" : "no",
3053 ignorecase ? "" : "no",
3054 showmatch ? "" : "no",
3055 tabstop
3056 );
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003057# endif
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003058 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003059 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003060# if ENABLE_FEATURE_VI_SETOPTS
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003061 argp = args;
Denis Vlasenkoba2fb712007-04-01 09:39:03 +00003062 while (*argp) {
Alison Winters63d9da32021-02-27 15:18:45 -08003063 i = 0;
3064 if (argp[0] == 'n' && argp[1] == 'o') // "noXXX"
3065 i = 2;
Ron Yorston9f017d92021-04-06 22:11:21 +01003066 argn = skip_non_whitespace(argp);
3067 oldch = *argn;
3068 *argn = '\0';
3069 setops(argp, i);
3070 *argn = oldch;
3071 argp = skip_whitespace(argn);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003072 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003073# endif /* FEATURE_VI_SETOPTS */
3074# endif /* FEATURE_VI_SET */
3075
3076# if ENABLE_FEATURE_VI_SEARCH
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003077 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003078 char *F, *R, *flags;
3079 size_t len_F, len_R;
Ron Yorston6220b4d2021-04-06 13:43:32 +01003080 int gflag = 0; // global replace flag
3081 int subs = 0; // number of substitutions
Ron Yorstonf4a99082021-04-06 13:44:05 +01003082# if ENABLE_FEATURE_VI_VERBOSE_STATUS
3083 int last_line = 0, lines = 0;
3084# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003085
3086 // F points to the "find" pattern
3087 // R points to the "replace" pattern
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003088 // replace the cmd line delimiters "/" with NULs
Ron Yorston852ffbe2021-04-25 11:55:01 +01003089 c = buf[1]; // what is the delimiter
3090 F = buf + 2; // start of "find"
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003091 R = strchr(F, c); // middle delimiter
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00003092 if (!R)
3093 goto colon_s_fail;
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003094 len_F = R - F;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003095 *R++ = '\0'; // terminate "find"
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003096 flags = strchr(R, c);
Ron Yorston6220b4d2021-04-06 13:43:32 +01003097 if (flags) {
3098 *flags++ = '\0'; // terminate "replace"
3099 gflag = *flags;
3100 }
3101 len_R = strlen(R);
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003102
Ron Yorston7a8ceb42021-04-25 11:51:55 +01003103 if (e < 0) { // no addr given
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003104 q = begin_line(dot); // start with cur line
Ron Yorston7a8ceb42021-04-25 11:51:55 +01003105 r = end_line(dot);
3106 b = e = count_lines(text, q); // cur line number
3107 } else if (b < 0) { // one addr given
3108 b = e;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003109 }
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003110
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003111 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003112 char *ls = q; // orig line start
3113 char *found;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003114 vc4:
Denys Vlasenkob7330462018-11-29 14:39:52 +01003115 found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003116 if (found) {
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00003117 uintptr_t bias;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003118 // we found the "find" pattern - delete it
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003119 // For undo support, the first item should not be chained
Ron Yorston6220b4d2021-04-06 13:43:32 +01003120 text_hole_delete(found, found + len_F - 1,
3121 subs ? ALLOW_UNDO_CHAIN: ALLOW_UNDO);
3122 // can't do this above, no undo => no third argument
3123 subs++;
Ron Yorstonf4a99082021-04-06 13:44:05 +01003124# if ENABLE_FEATURE_VI_VERBOSE_STATUS
3125 if (last_line != i) {
3126 last_line = i;
3127 ++lines;
3128 }
3129# endif
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003130 // insert the "replace" patern
3131 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003132 found += bias;
Denis Vlasenko4ae1e132008-11-19 13:25:14 +00003133 ls += bias;
Ron Yorston6220b4d2021-04-06 13:43:32 +01003134 dot = ls;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02003135 //q += bias; - recalculated anyway
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003136 // check for "global" :s/foo/bar/g
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003137 if (gflag == 'g') {
3138 if ((found + len_R) < end_line(ls)) {
3139 q = found + len_R;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003140 goto vc4; // don't let q move past cur line
3141 }
3142 }
3143 }
3144 q = next_line(ls);
3145 }
Ron Yorston6220b4d2021-04-06 13:43:32 +01003146 if (subs == 0) {
3147 status_line_bold("No match");
3148 } else {
3149 dot_skip_over_ws();
Ron Yorstonf4a99082021-04-06 13:44:05 +01003150# if ENABLE_FEATURE_VI_VERBOSE_STATUS
3151 if (subs > 1)
3152 status_line("%d substitutions on %d lines", subs, lines);
3153# endif
Ron Yorston6220b4d2021-04-06 13:43:32 +01003154 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003155# endif /* FEATURE_VI_SEARCH */
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003156 } else if (strncmp(cmd, "version", i) == 0) { // show software version
Denys Vlasenkoeba7fe62017-01-29 19:14:26 +01003157 status_line(BB_VER);
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003158 } else if (strncmp(cmd, "write", i) == 0 // write text to file
Ron Yorston852ffbe2021-04-25 11:55:01 +01003159 || strcmp(cmd, "wq") == 0
3160 || strcmp(cmd, "wn") == 0
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003161 || (cmd[0] == 'x' && !cmd[1])
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003162 ) {
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003163 int size;
3164 //int forced = FALSE;
3165
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003166 // is there a file name to write to?
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003167 if (args[0]) {
Ron Yorston5ae25f42021-03-28 13:18:40 +01003168 struct stat statbuf;
3169
Ron Yorstonacd30792021-04-25 11:55:42 +01003170 exp = expand_args(args);
3171 if (exp == NULL)
3172 goto ret;
3173 if (!useforce && (fn == NULL || strcmp(fn, exp) != 0) &&
3174 stat(exp, &statbuf) == 0) {
Ron Yorston5ae25f42021-03-28 13:18:40 +01003175 status_line_bold("File exists (:w! overrides)");
3176 goto ret;
3177 }
Ron Yorstonacd30792021-04-25 11:55:42 +01003178 fn = exp;
3179 init_filename(fn);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003180 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003181# if ENABLE_FEATURE_VI_READONLY
Ron Yorston09172582021-04-25 11:52:55 +01003182 else if (readonly_mode && !useforce && fn) {
Denys Vlasenko778794d2013-01-22 10:13:52 +01003183 status_line_bold("'%s' is read only", fn);
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003184 goto ret;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003185 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003186# endif
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003187 //if (useforce) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003188 // if "fn" is not write-able, chmod u+w
3189 // sprintf(syscmd, "chmod u+w %s", fn);
3190 // system(syscmd);
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003191 // forced = TRUE;
3192 //}
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01003193 if (modified_count != 0 || cmd[0] != 'x') {
3194 size = r - q + 1;
3195 l = file_write(fn, q, r);
3196 } else {
3197 size = 0;
3198 l = 0;
3199 }
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003200 //if (useforce && forced) {
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003201 // chmod u-w
3202 // sprintf(syscmd, "chmod u-w %s", fn);
3203 // system(syscmd);
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003204 // forced = FALSE;
3205 //}
Paul Fox61e45db2005-10-09 14:43:22 +00003206 if (l < 0) {
3207 if (l == -1)
Denys Vlasenko9e7c0022013-03-15 02:17:29 +01003208 status_line_bold_errno(fn);
Paul Fox61e45db2005-10-09 14:43:22 +00003209 } else {
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01003210 // how many lines written
3211 li = count_lines(q, q + l - 1);
Denys Vlasenko89393592019-04-02 12:45:30 +02003212 status_line("'%s' %uL, %uC", fn, li, l);
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01003213 if (l == size) {
3214 if (q == text && q + l == end) {
3215 modified_count = 0;
3216 last_modified_count = -1;
3217 }
Ron Yorston8e71f2a2021-05-01 13:15:30 +01003218 if (cmd[1] == 'n') {
3219 editing = 0;
3220 } else if (cmd[0] == 'x' || cmd[1] == 'q') {
3221 // are there other files to edit?
3222 int n = cmdline_filecnt - optind - 1;
3223 if (n > 0) {
3224 if (useforce) {
3225 // force end of argv list
3226 optind = cmdline_filecnt;
3227 } else {
3228 status_line_bold("%u more file(s) to edit", n);
3229 goto ret;
3230 }
3231 }
Denys Vlasenkoe88608e2017-03-13 20:50:42 +01003232 editing = 0;
3233 }
Paul Fox61e45db2005-10-09 14:43:22 +00003234 }
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003235 }
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003236# if ENABLE_FEATURE_VI_YANKMARK
Denys Vlasenko6548edd2009-06-15 12:44:11 +02003237 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003238 if (b < 0) { // no addr given- use defaults
3239 q = begin_line(dot); // assume .,. for the range
3240 r = end_line(dot);
3241 }
Ron Yorston25d25922021-03-25 14:23:36 +00003242 text_yank(q, r, YDreg, WHOLE);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003243 li = count_lines(q, r);
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003244 status_line("Yank %d lines (%d chars) into [%c]",
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003245 li, strlen(reg[YDreg]), what_reg());
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003246# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003247 } else {
3248 // cmd unknown
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003249 not_implemented(cmd);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003250 }
Denys Vlasenko9f82d0b2010-05-19 01:54:37 +02003251 ret:
Ron Yorstonacd30792021-04-25 11:55:42 +01003252# if ENABLE_FEATURE_VI_COLON_EXPAND
3253 free(exp);
3254# endif
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003255 dot = bound_dot(dot); // make sure "dot" is valid
3256 return;
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003257# if ENABLE_FEATURE_VI_SEARCH
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003258 colon_s_fail:
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003259 status_line(":s expression missing delimiters");
Denys Vlasenko8825cb62018-06-27 15:11:36 +02003260# endif
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003261#endif /* FEATURE_VI_COLON */
Denys Vlasenko32afd3a2014-04-05 22:57:46 +02003262}
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003263
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003264//----- Char Routines --------------------------------------------
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02003265// Chars that are part of a word-
3266// 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
3267// Chars that are Not part of a word (stoppers)
3268// !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
3269// Chars that are WhiteSpace
3270// TAB NEWLINE VT FF RETURN SPACE
3271// DO NOT COUNT NEWLINE AS WHITESPACE
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003272
Denis Vlasenko33875382008-06-21 20:31:50 +00003273static int st_test(char *p, int type, int dir, char *tested)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003274{
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003275 char c, c0, ci;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003276 int test, inc;
3277
3278 inc = dir;
3279 c = c0 = p[0];
3280 ci = p[inc];
3281 test = 0;
3282
3283 if (type == S_BEFORE_WS) {
3284 c = ci;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02003285 test = (!isspace(c) || c == '\n');
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003286 }
3287 if (type == S_TO_WS) {
3288 c = c0;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02003289 test = (!isspace(c) || c == '\n');
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003290 }
3291 if (type == S_OVER_WS) {
3292 c = c0;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02003293 test = isspace(c);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003294 }
3295 if (type == S_END_PUNCT) {
3296 c = ci;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02003297 test = ispunct(c);
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003298 }
3299 if (type == S_END_ALNUM) {
3300 c = ci;
Denys Vlasenkoc0dab372009-10-22 22:28:08 +02003301 test = (isalnum(c) || c == '_');
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003302 }
3303 *tested = c;
Denis Vlasenkod9e15f22006-11-27 16:49:55 +00003304 return test;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003305}
3306
Denis Vlasenko33875382008-06-21 20:31:50 +00003307static char *skip_thing(char *p, int linecnt, int dir, int type)
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003308{
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003309 char c;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003310
3311 while (st_test(p, type, dir, &c)) {
3312 // make sure we limit search to correct number of lines
3313 if (c == '\n' && --linecnt < 1)
3314 break;
3315 if (dir >= 0 && p >= end - 1)
3316 break;
3317 if (dir < 0 && p <= text)
3318 break;
3319 p += dir; // move to next char
3320 }
Denis Vlasenko079f8af2006-11-27 16:49:31 +00003321 return p;
Aaron Lehmann6fdacc72002-08-21 13:02:24 +00003322}
3323
Denys Vlasenko616e4692019-04-01 14:02:37 +02003324#if ENABLE_FEATURE_VI_USE_SIGNALS
3325static void winch_handler(int sig UNUSED_PARAM)
3326{
3327 int save_errno = errno;
3328 // FIXME: do it in main loop!!!
3329 signal(SIGWINCH, winch_handler);
3330 query_screen_dimensions();
3331 new_screen(rows, columns); // get memory for virtual screen
3332 redraw(TRUE); // re-draw the screen
3333 errno = save_errno;
3334}
3335static void tstp_handler(int sig UNUSED_PARAM)
3336{
3337 int save_errno = errno;
3338
3339 // ioctl inside cookmode() was seen to generate SIGTTOU,
3340 // stopping us too early. Prevent that:
3341 signal(SIGTTOU, SIG_IGN);
3342
3343 go_bottom_and_clear_to_eol();
3344 cookmode(); // terminal to "cooked"
3345
3346 // stop now
3347 //signal(SIGTSTP, SIG_DFL);
3348 //raise(SIGTSTP);
3349 raise(SIGSTOP); // avoid "dance" with TSTP handler - use SIGSTOP instead
3350 //signal(SIGTSTP, tstp_handler);
3351
3352 // we have been "continued" with SIGCONT, restore screen and termios
3353 rawmode(); // terminal to "raw"
3354 last_status_cksum = 0; // force status update
3355 redraw(TRUE); // re-draw the screen
3356
3357 errno = save_errno;
3358}
3359static void int_handler(int sig)
3360{
3361 signal(SIGINT, int_handler);
3362 siglongjmp(restart, sig);
3363}
3364#endif /* FEATURE_VI_USE_SIGNALS */
3365
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003366static void do_cmd(int c);
3367
Ron Yorstond56da682021-03-28 13:23:12 +01003368static int at_eof(const char *s)
3369{
3370 // does 's' point to end of file, even with no terminating newline?
3371 return ((s == end - 2 && s[1] == '\n') || s == end - 1);
3372}
3373
Ron Yorstonb7b11192021-04-06 13:40:23 +01003374static int find_range(char **start, char **stop, int cmd)
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003375{
Ron Yorston24effc72021-04-30 12:56:12 +01003376 char *p, *q, *t;
Ron Yorston25d25922021-03-25 14:23:36 +00003377 int buftype = -1;
Ron Yorstonb7b11192021-04-06 13:40:23 +01003378 int c;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003379
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003380 p = q = dot;
3381
Ron Yorstonb7b11192021-04-06 13:40:23 +01003382#if ENABLE_FEATURE_VI_YANKMARK
3383 if (cmd == 'Y') {
3384 c = 'y';
3385 } else
3386#endif
3387 {
3388 c = get_motion_char();
3389 }
3390
3391#if ENABLE_FEATURE_VI_YANKMARK
3392 if ((cmd == 'Y' || cmd == c) && strchr("cdy><", c)) {
3393#else
3394 if (cmd == c && strchr("cd><", c)) {
3395#endif
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003396 // these cmds operate on whole lines
Ron Yorston25d25922021-03-25 14:23:36 +00003397 buftype = WHOLE;
Ron Yorston038d4002021-06-16 14:47:00 +01003398 if (--cmdcnt > 0) {
Ron Yorston25d25922021-03-25 14:23:36 +00003399 do_cmd('j');
Ron Yorston038d4002021-06-16 14:47:00 +01003400 if (cmd_error)
3401 buftype = -1;
3402 }
Ron Yorstonb7b11192021-04-06 13:40:23 +01003403 } else if (strchr("^%$0bBeEfFtThnN/?|{}\b\177", c)) {
3404 // Most operate on char positions within a line. Of those that
3405 // don't '%' needs no special treatment, search commands are
3406 // marked as MULTI and "{}" are handled below.
3407 buftype = strchr("nN/?", c) ? MULTI : PARTIAL;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003408 do_cmd(c); // execute movement cmd
Ron Yorston25d25922021-03-25 14:23:36 +00003409 if (p == dot) // no movement is an error
3410 buftype = -1;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003411 } else if (strchr("wW", c)) {
Ron Yorston25d25922021-03-25 14:23:36 +00003412 buftype = MULTI;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003413 do_cmd(c); // execute movement cmd
Ron Yorstone577afc2021-04-06 13:41:01 +01003414 // step back one char, but not if we're at end of file,
3415 // or if we are at EOF and search was for 'w' and we're at
3416 // the start of a 'W' word.
3417 if (dot > p && (!at_eof(dot) || (c == 'w' && ispunct(*dot))))
Ron Yorston776b56d2021-03-25 14:21:49 +00003418 dot--;
Ron Yorstonb7b11192021-04-06 13:40:23 +01003419 t = dot;
3420 // don't include trailing WS as part of word
3421 while (dot > p && isspace(*dot)) {
3422 if (*dot-- == '\n')
3423 t = dot;
3424 }
3425 // for non-change operations WS after NL is not part of word
Ron Yorstone577afc2021-04-06 13:41:01 +01003426 if (cmd != 'c' && dot != t && *dot != '\n')
Ron Yorstonb7b11192021-04-06 13:40:23 +01003427 dot = t;
3428 } else if (strchr("GHL+-jk'\r\n", c)) {
Ron Yorston25d25922021-03-25 14:23:36 +00003429 // these operate on whole lines
3430 buftype = WHOLE;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003431 do_cmd(c); // execute movement cmd
Ron Yorston038d4002021-06-16 14:47:00 +01003432 if (cmd_error)
3433 buftype = -1;
Ron Yorston25d25922021-03-25 14:23:36 +00003434 } else if (c == ' ' || c == 'l') {
Ron Yorston5bef6782021-02-01 11:54:15 +00003435 // forward motion by character
3436 int tmpcnt = (cmdcnt ?: 1);
Ron Yorston25d25922021-03-25 14:23:36 +00003437 buftype = PARTIAL;
Ron Yorston5bef6782021-02-01 11:54:15 +00003438 do_cmd(c); // execute movement cmd
3439 // exclude last char unless range isn't what we expected
3440 // this indicates we've hit EOL
3441 if (tmpcnt == dot - p)
3442 dot--;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003443 }
Ron Yorston25d25922021-03-25 14:23:36 +00003444
Ron Yorstonb7b11192021-04-06 13:40:23 +01003445 if (buftype == -1) {
3446 if (c != 27)
3447 indicate_error();
Ron Yorston25d25922021-03-25 14:23:36 +00003448 return buftype;
Ron Yorstonb7b11192021-04-06 13:40:23 +01003449 }
Ron Yorston25d25922021-03-25 14:23:36 +00003450
3451 q = dot;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003452 if (q < p) {
3453 t = q;
3454 q = p;
3455 p = t;
3456 }
3457
Ron Yorstond56da682021-03-28 13:23:12 +01003458 // movements which don't include end of range
3459 if (q > p) {
Ron Yorstonb7b11192021-04-06 13:40:23 +01003460 if (strchr("^0bBFThnN/?|\b\177", c)) {
Ron Yorstond56da682021-03-28 13:23:12 +01003461 q--;
3462 } else if (strchr("{}", c)) {
3463 buftype = (p == begin_line(p) && (*q == '\n' || at_eof(q))) ?
3464 WHOLE : MULTI;
3465 if (!at_eof(q)) {
3466 q--;
3467 if (q > p && p != begin_line(p))
3468 q--;
3469 }
3470 }
3471 }
3472
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003473 *start = p;
3474 *stop = q;
Ron Yorston25d25922021-03-25 14:23:36 +00003475 return buftype;
Denys Vlasenko363a2bc2019-04-01 13:59:38 +02003476}
3477
Eric Andersen3f980402001-04-04 17:31:15 +00003478//---------------------------------------------------------------------
3479//----- the Ascii Chart -----------------------------------------------
Eric Andersen3f980402001-04-04 17:31:15 +00003480// 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3481// 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3482// 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3483// 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3484// 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3485// 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3486// 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3487// 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3488// 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3489// 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3490// 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3491// 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3492// 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3493// 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3494// 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3495// 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3496//---------------------------------------------------------------------
3497
3498//----- Execute a Vi Command -----------------------------------
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003499static void do_cmd(int c)
Eric Andersen3f980402001-04-04 17:31:15 +00003500{
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003501 char *p, *q, *save_dot;
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003502 char buf[12];
Denis Vlasenkoc3a9dc82008-10-29 00:58:04 +00003503 int dir;
Paul Foxc51fc7b2008-03-06 01:34:23 +00003504 int cnt, i, j;
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003505 int c1;
Ron Yorston74d565f2021-04-15 12:04:45 +01003506#if ENABLE_FEATURE_VI_YANKMARK
3507 char *orig_dot = dot;
3508#endif
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01003509#if ENABLE_FEATURE_VI_UNDO
3510 int allow_undo = ALLOW_UNDO;
3511 int undo_del = UNDO_DEL;
3512#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003513
Denis Vlasenkoe3cbfb92007-12-22 17:00:11 +00003514// c1 = c; // quiet the compiler
3515// cnt = yf = 0; // quiet the compiler
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003516// p = q = save_dot = buf; // quiet the compiler
3517 memset(buf, '\0', sizeof(buf));
Ron Yorston50a2db72021-03-28 13:20:01 +01003518 keep_index = FALSE;
Ron Yorston038d4002021-06-16 14:47:00 +01003519 cmd_error = FALSE;
Eric Andersenbff7a602001-11-17 07:15:43 +00003520
Paul Fox8552aec2005-09-16 12:20:05 +00003521 show_status_line();
3522
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02003523 // if this is a cursor key, skip these checks
Eric Andersenbff7a602001-11-17 07:15:43 +00003524 switch (c) {
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003525 case KEYCODE_UP:
3526 case KEYCODE_DOWN:
3527 case KEYCODE_LEFT:
3528 case KEYCODE_RIGHT:
3529 case KEYCODE_HOME:
3530 case KEYCODE_END:
3531 case KEYCODE_PAGEUP:
3532 case KEYCODE_PAGEDOWN:
3533 case KEYCODE_DELETE:
Eric Andersenbff7a602001-11-17 07:15:43 +00003534 goto key_cmd_mode;
3535 }
3536
Eric Andersen3f980402001-04-04 17:31:15 +00003537 if (cmd_mode == 2) {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003538 // flip-flop Insert/Replace mode
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003539 if (c == KEYCODE_INSERT)
Denis Vlasenko2a51af22007-03-21 22:31:24 +00003540 goto dc_i;
Eric Andersen3f980402001-04-04 17:31:15 +00003541 // we are 'R'eplacing the current *dot with new char
3542 if (*dot == '\n') {
3543 // don't Replace past E-o-l
3544 cmd_mode = 1; // convert to insert
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003545 undo_queue_commit();
Eric Andersen3f980402001-04-04 17:31:15 +00003546 } else {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003547 if (1 <= c || Isprint(c)) {
Eric Andersen3f980402001-04-04 17:31:15 +00003548 if (c != 27)
Ron Yorston25d25922021-03-25 14:23:36 +00003549 dot = yank_delete(dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO); // delete char
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003550 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
Eric Andersen3f980402001-04-04 17:31:15 +00003551 }
3552 goto dc1;
3553 }
3554 }
3555 if (cmd_mode == 1) {
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02003556 // hitting "Insert" twice means "R" replace mode
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003557 if (c == KEYCODE_INSERT) goto dc5;
Eric Andersen3f980402001-04-04 17:31:15 +00003558 // insert the char c at "dot"
Glenn L McGrath09adaca2002-12-02 21:18:10 +00003559 if (1 <= c || Isprint(c)) {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003560 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
Eric Andersen3f980402001-04-04 17:31:15 +00003561 }
3562 goto dc1;
3563 }
3564
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003565 key_cmd_mode:
Eric Andersen3f980402001-04-04 17:31:15 +00003566 switch (c) {
Eric Andersen822c3832001-05-07 17:37:43 +00003567 //case 0x01: // soh
3568 //case 0x09: // ht
3569 //case 0x0b: // vt
3570 //case 0x0e: // so
3571 //case 0x0f: // si
3572 //case 0x10: // dle
3573 //case 0x11: // dc1
3574 //case 0x13: // dc3
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003575#if ENABLE_FEATURE_VI_CRASHME
Eric Andersen1c0d3112001-04-16 15:46:44 +00003576 case 0x14: // dc4 ctrl-T
Eric Andersen3f980402001-04-04 17:31:15 +00003577 crashme = (crashme == 0) ? 1 : 0;
Eric Andersen3f980402001-04-04 17:31:15 +00003578 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003579#endif
Eric Andersen822c3832001-05-07 17:37:43 +00003580 //case 0x16: // syn
3581 //case 0x17: // etb
3582 //case 0x18: // can
3583 //case 0x1c: // fs
3584 //case 0x1d: // gs
3585 //case 0x1e: // rs
3586 //case 0x1f: // us
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003587 //case '!': // !-
3588 //case '#': // #-
3589 //case '&': // &-
3590 //case '(': // (-
3591 //case ')': // )-
3592 //case '*': // *-
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003593 //case '=': // =-
3594 //case '@': // @-
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003595 //case 'K': // K-
3596 //case 'Q': // Q-
3597 //case 'S': // S-
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003598 //case 'V': // V-
3599 //case '[': // [-
3600 //case '\\': // \-
3601 //case ']': // ]-
3602 //case '_': // _-
3603 //case '`': // `-
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003604 //case 'v': // v-
Denys Vlasenkob22bbff2009-07-04 16:50:43 +02003605 default: // unrecognized command
Eric Andersen3f980402001-04-04 17:31:15 +00003606 buf[0] = c;
3607 buf[1] = '\0';
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003608 not_implemented(buf);
Eric Andersen3f980402001-04-04 17:31:15 +00003609 end_cmd_q(); // stop adding to q
3610 case 0x00: // nul- ignore
3611 break;
3612 case 2: // ctrl-B scroll up full screen
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003613 case KEYCODE_PAGEUP: // Cursor Key Page Up
Eric Andersen3f980402001-04-04 17:31:15 +00003614 dot_scroll(rows - 2, -1);
3615 break;
Eric Andersen3f980402001-04-04 17:31:15 +00003616 case 4: // ctrl-D scroll down half screen
3617 dot_scroll((rows - 2) / 2, 1);
3618 break;
3619 case 5: // ctrl-E scroll down one line
3620 dot_scroll(1, 1);
3621 break;
3622 case 6: // ctrl-F scroll down full screen
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003623 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
Eric Andersen3f980402001-04-04 17:31:15 +00003624 dot_scroll(rows - 2, 1);
3625 break;
3626 case 7: // ctrl-G show current status
Paul Fox8552aec2005-09-16 12:20:05 +00003627 last_status_cksum = 0; // force status update
Eric Andersen3f980402001-04-04 17:31:15 +00003628 break;
3629 case 'h': // h- move left
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003630 case KEYCODE_LEFT: // cursor key Left
Paul Foxd13b90b2005-07-18 22:17:25 +00003631 case 8: // ctrl-H- move left (This may be ERASE char)
Denis Vlasenko2a51af22007-03-21 22:31:24 +00003632 case 0x7f: // DEL- move left (This may be ERASE char)
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003633 do {
3634 dot_left();
3635 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003636 break;
3637 case 10: // Newline ^J
3638 case 'j': // j- goto next line, same col
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003639 case KEYCODE_DOWN: // cursor key Down
Ron Yorstonac04eb32021-06-16 14:46:01 +01003640 case 13: // Carriage Return ^M
3641 case '+': // +- goto next line
3642 q = dot;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003643 do {
Ron Yorstonac04eb32021-06-16 14:46:01 +01003644 p = next_line(q);
3645 if (p == end_line(q)) {
3646 indicate_error();
3647 goto dc1;
3648 }
3649 q = p;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003650 } while (--cmdcnt > 0);
Ron Yorstonac04eb32021-06-16 14:46:01 +01003651 dot = q;
3652 if (c == 13 || c == '+') {
3653 dot_skip_over_ws();
3654 } else {
3655 // try to stay in saved column
3656 dot = cindex == C_END ? end_line(dot) : move_to_col(dot, cindex);
3657 keep_index = TRUE;
3658 }
Eric Andersen3f980402001-04-04 17:31:15 +00003659 break;
3660 case 12: // ctrl-L force redraw whole screen
Eric Andersen1c0d3112001-04-16 15:46:44 +00003661 case 18: // ctrl-R force redraw
Ron Yorston55279672016-04-26 15:23:38 +01003662 redraw(TRUE); // this will redraw the entire display
Eric Andersen3f980402001-04-04 17:31:15 +00003663 break;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02003664 case 21: // ctrl-U scroll up half screen
Eric Andersen3f980402001-04-04 17:31:15 +00003665 dot_scroll((rows - 2) / 2, -1);
3666 break;
3667 case 25: // ctrl-Y scroll up one line
3668 dot_scroll(1, -1);
3669 break;
Eric Andersen822c3832001-05-07 17:37:43 +00003670 case 27: // esc
Eric Andersen3f980402001-04-04 17:31:15 +00003671 if (cmd_mode == 0)
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003672 indicate_error();
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02003673 cmd_mode = 0; // stop inserting
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003674 undo_queue_commit();
Eric Andersen3f980402001-04-04 17:31:15 +00003675 end_cmd_q();
Paul Fox8552aec2005-09-16 12:20:05 +00003676 last_status_cksum = 0; // force status update
Eric Andersen3f980402001-04-04 17:31:15 +00003677 break;
3678 case ' ': // move right
3679 case 'l': // move right
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003680 case KEYCODE_RIGHT: // Cursor Key Right
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003681 do {
3682 dot_right();
3683 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003684 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003685#if ENABLE_FEATURE_VI_YANKMARK
Eric Andersen3f980402001-04-04 17:31:15 +00003686 case '"': // "- name a register to use for Delete/Yank
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003687 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3688 if ((unsigned)c1 <= 25) { // a-z?
3689 YDreg = c1;
Eric Andersen3f980402001-04-04 17:31:15 +00003690 } else {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003691 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003692 }
3693 break;
3694 case '\'': // '- goto a specific mark
Denys Vlasenko61fcc8c2016-09-28 16:23:05 +02003695 c1 = (get_one_char() | 0x20);
3696 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3697 c1 = (c1 - 'a');
Eric Andersen3f980402001-04-04 17:31:15 +00003698 // get the b-o-l
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003699 q = mark[c1];
Eric Andersen3f980402001-04-04 17:31:15 +00003700 if (text <= q && q < end) {
3701 dot = q;
3702 dot_begin(); // go to B-o-l
3703 dot_skip_over_ws();
Ron Yorston038d4002021-06-16 14:47:00 +01003704 } else {
3705 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003706 }
3707 } else if (c1 == '\'') { // goto previous context
3708 dot = swap_context(dot); // swap current and previous context
3709 dot_begin(); // go to B-o-l
3710 dot_skip_over_ws();
Ron Yorston74d565f2021-04-15 12:04:45 +01003711#if ENABLE_FEATURE_VI_YANKMARK
3712 orig_dot = dot; // this doesn't update stored contexts
3713#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003714 } else {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003715 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003716 }
3717 break;
3718 case 'm': // m- Mark a line
3719 // this is really stupid. If there are any inserts or deletes
3720 // between text[0] and dot then this mark will not point to the
3721 // correct location! It could be off by many lines!
3722 // Well..., at least its quick and dirty.
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003723 c1 = (get_one_char() | 0x20) - 'a';
3724 if ((unsigned)c1 <= 25) { // a-z?
Eric Andersen3f980402001-04-04 17:31:15 +00003725 // remember the line
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00003726 mark[c1] = dot;
Eric Andersen3f980402001-04-04 17:31:15 +00003727 } else {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003728 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003729 }
3730 break;
3731 case 'P': // P- Put register before
3732 case 'p': // p- put register after
3733 p = reg[YDreg];
Denis Vlasenko00d84172008-11-24 07:34:42 +00003734 if (p == NULL) {
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00003735 status_line_bold("Nothing in register %c", what_reg());
Eric Andersen3f980402001-04-04 17:31:15 +00003736 break;
3737 }
Ron Yorston951c6de2021-04-10 11:17:38 +01003738 cnt = 0;
Ron Yorstonf4a99082021-04-06 13:44:05 +01003739 i = cmdcnt ?: 1;
3740 // are we putting whole lines or strings
Ron Yorston25d25922021-03-25 14:23:36 +00003741 if (regtype[YDreg] == WHOLE) {
Eric Andersen3f980402001-04-04 17:31:15 +00003742 if (c == 'P') {
3743 dot_begin(); // putting lines- Put above
3744 }
Ron Yorston951c6de2021-04-10 11:17:38 +01003745 else /* if ( c == 'p') */ {
Eric Andersen3f980402001-04-04 17:31:15 +00003746 // are we putting after very last line?
3747 if (end_line(dot) == (end - 1)) {
3748 dot = end; // force dot to end of text[]
3749 } else {
3750 dot_next(); // next line, then put before
3751 }
3752 }
3753 } else {
3754 if (c == 'p')
3755 dot_right(); // move to right, can move to NL
Ron Yorston951c6de2021-04-10 11:17:38 +01003756 // how far to move cursor if register doesn't have a NL
3757 if (strchr(p, '\n') == NULL)
Ron Yorstonf4a99082021-04-06 13:44:05 +01003758 cnt = i * strlen(p) - 1;
Eric Andersen3f980402001-04-04 17:31:15 +00003759 }
Ron Yorston951c6de2021-04-10 11:17:38 +01003760 do {
3761 // dot is adjusted if text[] is reallocated so we don't have to
3762 string_insert(dot, p, allow_undo); // insert the string
3763# if ENABLE_FEATURE_VI_UNDO
3764 allow_undo = ALLOW_UNDO_CHAIN;
3765# endif
3766 } while (--cmdcnt > 0);
3767 dot += cnt;
Ron Yorstond95f89e2021-05-20 08:26:47 +01003768 dot_skip_over_ws();
Ron Yorstonf4a99082021-04-06 13:44:05 +01003769# if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS
3770 yank_status("Put", p, i);
3771# endif
Eric Andersen3f980402001-04-04 17:31:15 +00003772 end_cmd_q(); // stop adding to q
3773 break;
Eric Andersen3f980402001-04-04 17:31:15 +00003774 case 'U': // U- Undo; replace current line with original version
Denys Vlasenko800a9a02012-01-31 14:10:26 +01003775 if (reg[Ureg] != NULL) {
Eric Andersen3f980402001-04-04 17:31:15 +00003776 p = begin_line(dot);
3777 q = end_line(dot);
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02003778 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3779 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
Eric Andersen3f980402001-04-04 17:31:15 +00003780 dot = p;
3781 dot_skip_over_ws();
Ron Yorstonf4a99082021-04-06 13:44:05 +01003782# if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS
3783 yank_status("Undo", reg[Ureg], 1);
3784# endif
Eric Andersen3f980402001-04-04 17:31:15 +00003785 }
3786 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003787#endif /* FEATURE_VI_YANKMARK */
Andrew Fuller4d8ddb82015-05-03 18:18:25 +02003788#if ENABLE_FEATURE_VI_UNDO
3789 case 'u': // u- undo last operation
3790 undo_pop();
3791 break;
3792#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003793 case '$': // $- goto end of line
Denis Vlasenko0112ff52008-10-25 23:23:00 +00003794 case KEYCODE_END: // Cursor Key End
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003795 for (;;) {
3796 dot = end_line(dot);
Denys Vlasenko1fd71292011-11-28 04:55:48 +01003797 if (--cmdcnt <= 0)
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003798 break;
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01003799 dot_next();
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01003800 }
Ron Yorston50a2db72021-03-28 13:20:01 +01003801 cindex = C_END;
3802 keep_index = TRUE;
Eric Andersen3f980402001-04-04 17:31:15 +00003803 break;
3804 case '%': // %- find matching char of pair () [] {}
3805 for (q = dot; q < end && *q != '\n'; q++) {
3806 if (strchr("()[]{}", *q) != NULL) {
3807 // we found half of a pair
3808 p = find_pair(q, *q);
3809 if (p == NULL) {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003810 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003811 } else {
3812 dot = p;
3813 }
3814 break;
3815 }
3816 }
3817 if (*q == '\n')
Denys Vlasenko05399fc2014-09-15 17:06:10 +02003818 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00003819 break;
3820 case 'f': // f- forward to a user specified char
Ron Yorston15f4ac32021-03-28 17:15:30 +01003821 case 'F': // F- backward to a user specified char
3822 case 't': // t- move to char prior to next x
3823 case 'T': // T- move to char after previous x
3824 last_search_char = get_one_char(); // get the search char
3825 last_search_cmd = c;
3826 // fall through
3827 case ';': // ;- look at rest of line for last search char
3828 case ',': // ,- repeat latest search in opposite direction
3829 dot_to_char(c != ',' ? last_search_cmd : last_search_cmd ^ 0x20);
Eric Andersen3f980402001-04-04 17:31:15 +00003830 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003831#if ENABLE_FEATURE_VI_DOT_CMD
Eric Andersen3f980402001-04-04 17:31:15 +00003832 case '.': // .- repeat the last modifying command
3833 // Stuff the last_modifying_cmd back into stdin
3834 // and let it be re-executed.
Denys Vlasenko2a576082019-04-01 16:15:51 +02003835 if (lmc_len != 0) {
Ron Yorstona5445022021-04-06 16:48:07 +01003836 if (cmdcnt) // update saved count if current count is non-zero
3837 dotcnt = cmdcnt;
3838 last_modifying_cmd[lmc_len] = '\0';
3839 ioq = ioq_start = xasprintf("%u%s", dotcnt, last_modifying_cmd);
Eric Andersen3f980402001-04-04 17:31:15 +00003840 }
3841 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003842#endif
3843#if ENABLE_FEATURE_VI_SEARCH
Ron Yorston7ce0e752021-04-06 13:39:48 +01003844 case 'N': // N- backward search for last pattern
3845 dir = last_search_pattern[0] == '/' ? BACK : FORWARD;
3846 goto dc4; // now search for pattern
3847 break;
3848 case '?': // ?- backward search for a pattern
3849 case '/': // /- forward search for a pattern
Eric Andersen3f980402001-04-04 17:31:15 +00003850 buf[0] = c;
3851 buf[1] = '\0';
3852 q = get_input_line(buf); // get input line- use "status line"
Ron Yorston7ce0e752021-04-06 13:39:48 +01003853 if (!q[0]) // user changed mind and erased the "/"- do nothing
3854 break;
3855 if (!q[1]) { // if no pat re-use old pat
Paul Fox4917c112008-03-05 16:44:02 +00003856 if (last_search_pattern[0])
Denis Vlasenkoc3a9dc82008-10-29 00:58:04 +00003857 last_search_pattern[0] = c;
Ron Yorston7ce0e752021-04-06 13:39:48 +01003858 } else { // strlen(q) > 1: new pat- save it and find
Aaron Lehmanna170e1c2002-11-28 11:27:31 +00003859 free(last_search_pattern);
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003860 last_search_pattern = xstrdup(q);
Eric Andersen3f980402001-04-04 17:31:15 +00003861 }
Ron Yorston7ce0e752021-04-06 13:39:48 +01003862 // fall through
Eric Andersen3f980402001-04-04 17:31:15 +00003863 case 'n': // n- repeat search for last pattern
3864 // search rest of text[] starting at next char
Ron Yorston7ce0e752021-04-06 13:39:48 +01003865 // if search fails "dot" is unchanged
3866 dir = last_search_pattern[0] == '/' ? FORWARD : BACK;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003867 dc4:
Ron Yorston7ce0e752021-04-06 13:39:48 +01003868 if (last_search_pattern[1] == '\0') {
3869 status_line_bold("No previous search");
3870 break;
3871 }
3872 do {
3873 q = char_search(dot + dir, last_search_pattern + 1,
3874 (dir << 1) | FULL);
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003875 if (q != NULL) {
3876 dot = q; // good search, update "dot"
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003877 } else {
Ron Yorston7ce0e752021-04-06 13:39:48 +01003878 // no pattern found between "dot" and top/bottom of file
3879 // continue from other end of file
3880 const char *msg;
3881 q = char_search(dir == FORWARD ? text : end - 1,
3882 last_search_pattern + 1, (dir << 1) | FULL);
3883 if (q != NULL) { // found something
3884 dot = q; // found new pattern- goto it
3885 msg = "search hit %s, continuing at %s";
3886 } else { // pattern is nowhere in file
3887 cmdcnt = 0; // force exit from loop
3888 msg = "Pattern not found";
3889 }
3890 if (dir == FORWARD)
3891 status_line_bold(msg, "BOTTOM", "TOP");
3892 else
3893 status_line_bold(msg, "TOP", "BOTTOM");
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003894 }
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003895 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003896 break;
3897 case '{': // {- move backward paragraph
Eric Andersen3f980402001-04-04 17:31:15 +00003898 case '}': // }- move forward paragraph
Ron Yorston033fa3d2021-04-15 12:02:11 +01003899 dir = c == '}' ? FORWARD : BACK;
Ron Yorstond3b74822021-03-28 13:22:11 +01003900 do {
Ron Yorston033fa3d2021-04-15 12:02:11 +01003901 int skip = TRUE; // initially skip consecutive empty lines
3902 while (dir == FORWARD ? dot < end - 1 : dot > text) {
3903 if (*dot == '\n' && dot[dir] == '\n') {
3904 if (!skip) {
3905 if (dir == FORWARD)
3906 ++dot; // move to next blank line
3907 goto dc2;
3908 }
3909 }
3910 else {
3911 skip = FALSE;
3912 }
Ron Yorston8b571bd2021-03-28 13:22:43 +01003913 dot += dir;
3914 }
Ron Yorston033fa3d2021-04-15 12:02:11 +01003915 goto dc6; // end of file
3916 dc2: continue;
Ron Yorstond3b74822021-03-28 13:22:11 +01003917 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003918 break;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00003919#endif /* FEATURE_VI_SEARCH */
Maninder Singh97c64912015-05-25 13:46:36 +02003920 case '0': // 0- goto beginning of line
Eric Andersenc7bda1c2004-03-15 08:29:22 +00003921 case '1': // 1-
3922 case '2': // 2-
3923 case '3': // 3-
3924 case '4': // 4-
3925 case '5': // 5-
3926 case '6': // 6-
3927 case '7': // 7-
3928 case '8': // 8-
3929 case '9': // 9-
Eric Andersen3f980402001-04-04 17:31:15 +00003930 if (c == '0' && cmdcnt < 1) {
3931 dot_begin(); // this was a standalone zero
3932 } else {
3933 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3934 }
3935 break;
3936 case ':': // :- the colon mode commands
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00003937 p = get_input_line(":"); // get input line- use "status line"
Eric Andersen3f980402001-04-04 17:31:15 +00003938 colon(p); // execute the command
Eric Andersen3f980402001-04-04 17:31:15 +00003939 break;
3940 case '<': // <- Left shift something
3941 case '>': // >- Right shift something
3942 cnt = count_lines(text, dot); // remember what line we are on
Ron Yorstonb7b11192021-04-06 13:40:23 +01003943 if (find_range(&p, &q, c) == -1)
Ron Yorston24198f62021-03-30 13:02:32 +01003944 goto dc6;
Eric Andersen3f980402001-04-04 17:31:15 +00003945 i = count_lines(p, q); // # of lines we are shifting
Ron Yorston24effc72021-04-30 12:56:12 +01003946 for (p = begin_line(p); i > 0; i--, p = next_line(p)) {
Eric Andersen3f980402001-04-04 17:31:15 +00003947 if (c == '<') {
Ron Yorston310ef232021-04-17 09:25:11 +01003948 // shift left- remove tab or tabstop spaces
Eric Andersen3f980402001-04-04 17:31:15 +00003949 if (*p == '\t') {
3950 // shrink buffer 1 char
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01003951 text_hole_delete(p, p, allow_undo);
Eric Andersen3f980402001-04-04 17:31:15 +00003952 } else if (*p == ' ') {
3953 // we should be calculating columns, not just SPACE
3954 for (j = 0; *p == ' ' && j < tabstop; j++) {
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01003955 text_hole_delete(p, p, allow_undo);
3956#if ENABLE_FEATURE_VI_UNDO
3957 allow_undo = ALLOW_UNDO_CHAIN;
3958#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003959 }
3960 }
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01003961 } else /* if (c == '>') */ {
Ron Yorston310ef232021-04-17 09:25:11 +01003962 // shift right -- add tab or tabstop spaces
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01003963 char_insert(p, '\t', allow_undo);
Eric Andersen3f980402001-04-04 17:31:15 +00003964 }
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01003965#if ENABLE_FEATURE_VI_UNDO
3966 allow_undo = ALLOW_UNDO_CHAIN;
3967#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003968 }
3969 dot = find_line(cnt); // what line were we on
3970 dot_skip_over_ws();
3971 end_cmd_q(); // stop adding to q
3972 break;
3973 case 'A': // A- append at e-o-l
3974 dot_end(); // go to e-o-l
Bernhard Reutner-Fischera985d302008-02-11 11:44:38 +00003975 //**** fall through to ... 'a'
Eric Andersen3f980402001-04-04 17:31:15 +00003976 case 'a': // a- append after current char
3977 if (*dot != '\n')
3978 dot++;
3979 goto dc_i;
3980 break;
3981 case 'B': // B- back a blank-delimited Word
3982 case 'E': // E- end of a blank-delimited word
3983 case 'W': // W- forward a blank-delimited word
Eric Andersen3f980402001-04-04 17:31:15 +00003984 dir = FORWARD;
3985 if (c == 'B')
3986 dir = BACK;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02003987 do {
3988 if (c == 'W' || isspace(dot[dir])) {
3989 dot = skip_thing(dot, 1, dir, S_TO_WS);
3990 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3991 }
3992 if (c != 'W')
3993 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3994 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00003995 break;
3996 case 'C': // C- Change to e-o-l
3997 case 'D': // D- delete to e-o-l
3998 save_dot = dot;
3999 dot = dollar_line(dot); // move to before NL
4000 // copy text into a register and delete
Ron Yorston25d25922021-03-25 14:23:36 +00004001 dot = yank_delete(save_dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO); // delete to e-o-l
Eric Andersen3f980402001-04-04 17:31:15 +00004002 if (c == 'C')
4003 goto dc_i; // start inserting
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004004#if ENABLE_FEATURE_VI_DOT_CMD
Eric Andersen3f980402001-04-04 17:31:15 +00004005 if (c == 'D')
4006 end_cmd_q(); // stop adding to q
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004007#endif
Eric Andersen3f980402001-04-04 17:31:15 +00004008 break;
Denis Vlasenko4c9e9c42008-10-20 08:59:03 +00004009 case 'g': // 'gg' goto a line number (vim) (default: very first line)
Paul Foxb5ee8db2008-02-14 01:17:01 +00004010 c1 = get_one_char();
4011 if (c1 != 'g') {
4012 buf[0] = 'g';
Denys Vlasenkode1996d2016-09-15 13:53:42 +02004013 // c1 < 0 if the key was special. Try "g<up-arrow>"
4014 // TODO: if Unicode?
4015 buf[1] = (c1 >= 0 ? c1 : '*');
Paul Foxb5ee8db2008-02-14 01:17:01 +00004016 buf[2] = '\0';
4017 not_implemented(buf);
4018 break;
4019 }
4020 if (cmdcnt == 0)
4021 cmdcnt = 1;
Denys Vlasenko6ed94aa2019-04-01 11:58:11 +02004022 // fall through
Eric Andersen822c3832001-05-07 17:37:43 +00004023 case 'G': // G- goto to a line number (default= E-O-F)
4024 dot = end - 1; // assume E-O-F
Eric Andersen1c0d3112001-04-16 15:46:44 +00004025 if (cmdcnt > 0) {
Eric Andersen822c3832001-05-07 17:37:43 +00004026 dot = find_line(cmdcnt); // what line is #cmdcnt
Eric Andersen1c0d3112001-04-16 15:46:44 +00004027 }
Ron Yorston18871c32021-03-28 13:19:26 +01004028 dot_begin();
Eric Andersen1c0d3112001-04-16 15:46:44 +00004029 dot_skip_over_ws();
4030 break;
Eric Andersen3f980402001-04-04 17:31:15 +00004031 case 'H': // H- goto top line on screen
4032 dot = screenbegin;
4033 if (cmdcnt > (rows - 1)) {
4034 cmdcnt = (rows - 1);
4035 }
Ron Yorstonac04eb32021-06-16 14:46:01 +01004036 while (--cmdcnt > 0) {
4037 dot_next();
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01004038 }
Ron Yorstonac04eb32021-06-16 14:46:01 +01004039 dot_begin();
Eric Andersen3f980402001-04-04 17:31:15 +00004040 dot_skip_over_ws();
4041 break;
4042 case 'I': // I- insert before first non-blank
4043 dot_begin(); // 0
4044 dot_skip_over_ws();
Bernhard Reutner-Fischera985d302008-02-11 11:44:38 +00004045 //**** fall through to ... 'i'
Eric Andersen3f980402001-04-04 17:31:15 +00004046 case 'i': // i- insert before current char
Denis Vlasenko0112ff52008-10-25 23:23:00 +00004047 case KEYCODE_INSERT: // Cursor Key Insert
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004048 dc_i:
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004049 cmd_mode = 1; // start inserting
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004050 undo_queue_commit(); // commit queue when cmd_mode changes
Eric Andersen3f980402001-04-04 17:31:15 +00004051 break;
4052 case 'J': // J- join current and next lines together
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004053 do {
4054 dot_end(); // move to NL
4055 if (dot < end - 1) { // make sure not last char in text[]
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004056#if ENABLE_FEATURE_VI_UNDO
4057 undo_push(dot, 1, UNDO_DEL);
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004058 *dot++ = ' '; // replace NL with space
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004059 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
4060#else
4061 *dot++ = ' ';
Denys Vlasenkoe7430862014-04-03 12:47:48 +02004062 modified_count++;
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004063#endif
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004064 while (isblank(*dot)) { // delete leading WS
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004065 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004066 }
Eric Andersen3f980402001-04-04 17:31:15 +00004067 }
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004068 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00004069 end_cmd_q(); // stop adding to q
4070 break;
4071 case 'L': // L- goto bottom line on screen
4072 dot = end_screen();
4073 if (cmdcnt > (rows - 1)) {
4074 cmdcnt = (rows - 1);
4075 }
Ron Yorstonac04eb32021-06-16 14:46:01 +01004076 while (--cmdcnt > 0) {
4077 dot_prev();
Denys Vlasenko35fdb1b2010-03-26 16:10:14 +01004078 }
Eric Andersen3f980402001-04-04 17:31:15 +00004079 dot_begin();
4080 dot_skip_over_ws();
4081 break;
Eric Andersen822c3832001-05-07 17:37:43 +00004082 case 'M': // M- goto middle line on screen
Eric Andersen1c0d3112001-04-16 15:46:44 +00004083 dot = screenbegin;
4084 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
4085 dot = next_line(dot);
Ron Yorston18871c32021-03-28 13:19:26 +01004086 dot_skip_over_ws();
Eric Andersen1c0d3112001-04-16 15:46:44 +00004087 break;
Ron Yorston47c3eaa2021-04-15 12:04:22 +01004088 case 'O': // O- open an empty line above
4089 dot_begin();
Ron Yorston9659a8d2021-05-20 08:27:48 +01004090#if ENABLE_FEATURE_VI_SETOPTS
4091 indentcol = -1;
4092#endif
Ron Yorston47c3eaa2021-04-15 12:04:22 +01004093 goto dc3;
4094 case 'o': // o- open an empty line below
4095 dot_end();
4096 dc3:
4097 dot = char_insert(dot, '\n', ALLOW_UNDO);
4098 if (c == 'O' && !autoindent) {
Ron Yorston9659a8d2021-05-20 08:27:48 +01004099 // done in char_insert() for 'O'+autoindent
Eric Andersen3f980402001-04-04 17:31:15 +00004100 dot_prev();
Eric Andersen3f980402001-04-04 17:31:15 +00004101 }
4102 goto dc_i;
4103 break;
4104 case 'R': // R- continuous Replace char
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004105 dc5:
Eric Andersen3f980402001-04-04 17:31:15 +00004106 cmd_mode = 2;
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004107 undo_queue_commit();
Eric Andersen3f980402001-04-04 17:31:15 +00004108 break;
Denis Vlasenko0112ff52008-10-25 23:23:00 +00004109 case KEYCODE_DELETE:
Denys Vlasenko49acc1a2015-03-12 21:15:34 +01004110 if (dot < end - 1)
Ron Yorston25d25922021-03-25 14:23:36 +00004111 dot = yank_delete(dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO);
Denys Vlasenko49acc1a2015-03-12 21:15:34 +01004112 break;
Eric Andersen3f980402001-04-04 17:31:15 +00004113 case 'X': // X- delete char before dot
4114 case 'x': // x- delete the current char
4115 case 's': // s- substitute the current char
Eric Andersen3f980402001-04-04 17:31:15 +00004116 dir = 0;
4117 if (c == 'X')
4118 dir = -1;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004119 do {
4120 if (dot[dir] != '\n') {
4121 if (c == 'X')
4122 dot--; // delete prev char
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01004123 dot = yank_delete(dot, dot, PARTIAL, YANKDEL, allow_undo); // delete char
4124#if ENABLE_FEATURE_VI_UNDO
4125 allow_undo = ALLOW_UNDO_CHAIN;
4126#endif
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004127 }
4128 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00004129 end_cmd_q(); // stop adding to q
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004130 if (c == 's')
4131 goto dc_i; // start inserting
Eric Andersen3f980402001-04-04 17:31:15 +00004132 break;
4133 case 'Z': // Z- if modified, {write}; exit
4134 // ZZ means to save file (if necessary), then exit
4135 c1 = get_one_char();
4136 if (c1 != 'Z') {
Denys Vlasenko05399fc2014-09-15 17:06:10 +02004137 indicate_error();
Eric Andersen3f980402001-04-04 17:31:15 +00004138 break;
4139 }
Denys Vlasenkoe7430862014-04-03 12:47:48 +02004140 if (modified_count) {
Ron Yorston09172582021-04-25 11:52:55 +01004141 if (ENABLE_FEATURE_VI_READONLY && readonly_mode && current_filename) {
Denys Vlasenko778794d2013-01-22 10:13:52 +01004142 status_line_bold("'%s' is read only", current_filename);
Denis Vlasenko92758142006-10-03 19:56:34 +00004143 break;
Paul Foxf0305b72006-03-28 14:18:21 +00004144 }
Denis Vlasenkoeaabf062007-07-17 23:14:07 +00004145 cnt = file_write(current_filename, text, end - 1);
Paul Fox61e45db2005-10-09 14:43:22 +00004146 if (cnt < 0) {
4147 if (cnt == -1)
Denys Vlasenko6f97b302017-09-29 18:17:25 +02004148 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
Paul Fox61e45db2005-10-09 14:43:22 +00004149 } else if (cnt == (end - 1 - text + 1)) {
Eric Andersen3f980402001-04-04 17:31:15 +00004150 editing = 0;
4151 }
4152 } else {
4153 editing = 0;
4154 }
Ron Yorstonb9aaa372021-04-25 11:53:23 +01004155 // are there other files to edit?
4156 j = cmdline_filecnt - optind - 1;
4157 if (editing == 0 && j > 0) {
4158 editing = 1;
4159 modified_count = 0;
4160 last_modified_count = -1;
4161 status_line_bold("%u more file(s) to edit", j);
4162 }
Eric Andersen3f980402001-04-04 17:31:15 +00004163 break;
4164 case '^': // ^- move to first non-blank on line
4165 dot_begin();
4166 dot_skip_over_ws();
4167 break;
4168 case 'b': // b- back a word
4169 case 'e': // e- end of word
Eric Andersen3f980402001-04-04 17:31:15 +00004170 dir = FORWARD;
4171 if (c == 'b')
4172 dir = BACK;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004173 do {
4174 if ((dot + dir) < text || (dot + dir) > end - 1)
4175 break;
4176 dot += dir;
4177 if (isspace(*dot)) {
4178 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
4179 }
4180 if (isalnum(*dot) || *dot == '_') {
4181 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
4182 } else if (ispunct(*dot)) {
4183 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
4184 }
4185 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00004186 break;
4187 case 'c': // c- change something
4188 case 'd': // d- delete something
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004189#if ENABLE_FEATURE_VI_YANKMARK
Eric Andersen3f980402001-04-04 17:31:15 +00004190 case 'y': // y- yank something
4191 case 'Y': // Y- Yank a line
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004192#endif
Denis Vlasenkod44c1532008-10-14 12:59:42 +00004193 {
Ron Yorstonb7b11192021-04-06 13:40:23 +01004194 int yf = YANKDEL; // assume either "c" or "d"
4195 int buftype;
Ron Yorston25d25922021-03-25 14:23:36 +00004196#if ENABLE_FEATURE_VI_YANKMARK
Ron Yorstonf4a99082021-04-06 13:44:05 +01004197# if ENABLE_FEATURE_VI_VERBOSE_STATUS
Ron Yorston25d25922021-03-25 14:23:36 +00004198 char *savereg = reg[YDreg];
Ron Yorstonf4a99082021-04-06 13:44:05 +01004199# endif
Eric Andersen3f980402001-04-04 17:31:15 +00004200 if (c == 'y' || c == 'Y')
4201 yf = YANKONLY;
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004202#endif
Paul Foxc51fc7b2008-03-06 01:34:23 +00004203 // determine range, and whether it spans lines
Ron Yorstonb7b11192021-04-06 13:40:23 +01004204 buftype = find_range(&p, &q, c);
4205 if (buftype == -1) // invalid range
Ron Yorston25d25922021-03-25 14:23:36 +00004206 goto dc6;
Ron Yorston24effc72021-04-30 12:56:12 +01004207 if (buftype == WHOLE) {
4208 save_dot = p; // final cursor position is start of range
4209 p = begin_line(p);
4210 q = end_line(q);
4211 }
Ron Yorston25d25922021-03-25 14:23:36 +00004212 dot = yank_delete(p, q, buftype, yf, ALLOW_UNDO); // delete word
4213 if (buftype == WHOLE) {
Eric Andersen1c0d3112001-04-16 15:46:44 +00004214 if (c == 'c') {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004215 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
Eric Andersen1c0d3112001-04-16 15:46:44 +00004216 // on the last line of file don't move to prev line
Ron Yorston25d25922021-03-25 14:23:36 +00004217 if (dot != (end-1)) {
Eric Andersen1c0d3112001-04-16 15:46:44 +00004218 dot_prev();
4219 }
Ron Yorstond95f89e2021-05-20 08:26:47 +01004220 } else if (c == 'd') {
4221 dot_begin();
4222 dot_skip_over_ws();
Ron Yorston24effc72021-04-30 12:56:12 +01004223 } else {
Ron Yorstond6e653d2021-04-15 12:03:22 +01004224 dot = save_dot;
4225 }
Eric Andersen3f980402001-04-04 17:31:15 +00004226 }
Ron Yorston25d25922021-03-25 14:23:36 +00004227 // if CHANGING, not deleting, start inserting after the delete
4228 if (c == 'c') {
Ron Yorston25d25922021-03-25 14:23:36 +00004229 goto dc_i; // start inserting
4230 }
Ron Yorstonf4a99082021-04-06 13:44:05 +01004231#if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS
Ron Yorston25d25922021-03-25 14:23:36 +00004232 // only update status if a yank has actually happened
Ron Yorstonf4a99082021-04-06 13:44:05 +01004233 if (reg[YDreg] != savereg)
4234 yank_status(c == 'd' ? "Delete" : "Yank", reg[YDreg], 1);
Ron Yorston25d25922021-03-25 14:23:36 +00004235#endif
4236 dc6:
4237 end_cmd_q(); // stop adding to q
Eric Andersen3f980402001-04-04 17:31:15 +00004238 break;
Denis Vlasenkod44c1532008-10-14 12:59:42 +00004239 }
Eric Andersen3f980402001-04-04 17:31:15 +00004240 case 'k': // k- goto prev line, same col
Denis Vlasenko0112ff52008-10-25 23:23:00 +00004241 case KEYCODE_UP: // cursor key Up
Ron Yorstonac04eb32021-06-16 14:46:01 +01004242 case '-': // -- goto prev line
4243 q = dot;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004244 do {
Ron Yorstonac04eb32021-06-16 14:46:01 +01004245 p = prev_line(q);
4246 if (p == begin_line(q)) {
4247 indicate_error();
4248 goto dc1;
4249 }
4250 q = p;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004251 } while (--cmdcnt > 0);
Ron Yorstonac04eb32021-06-16 14:46:01 +01004252 dot = q;
4253 if (c == '-') {
4254 dot_skip_over_ws();
4255 } else {
4256 // try to stay in saved column
4257 dot = cindex == C_END ? end_line(dot) : move_to_col(dot, cindex);
4258 keep_index = TRUE;
4259 }
Eric Andersen3f980402001-04-04 17:31:15 +00004260 break;
4261 case 'r': // r- replace the current char with user input
4262 c1 = get_one_char(); // get the replacement char
Ron Yorstonfe765692021-04-10 11:16:47 +01004263 if (c1 != 27) {
4264 if (end_line(dot) - dot < (cmdcnt ?: 1)) {
4265 indicate_error();
4266 goto dc6;
4267 }
4268 do {
4269 dot = text_hole_delete(dot, dot, allow_undo);
4270#if ENABLE_FEATURE_VI_UNDO
4271 allow_undo = ALLOW_UNDO_CHAIN;
4272#endif
4273 dot = char_insert(dot, c1, allow_undo);
4274 } while (--cmdcnt > 0);
Ron Yorstondf4e3af2019-02-03 14:01:58 +00004275 dot_left();
Eric Andersen3f980402001-04-04 17:31:15 +00004276 }
4277 end_cmd_q(); // stop adding to q
4278 break;
4279 case 'w': // w- forward a word
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004280 do {
4281 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
4282 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
4283 } else if (ispunct(*dot)) { // we are on PUNCT
4284 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
4285 }
4286 if (dot < end - 1)
4287 dot++; // move over word
4288 if (isspace(*dot)) {
4289 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
4290 }
4291 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00004292 break;
4293 case 'z': // z-
4294 c1 = get_one_char(); // get the replacement char
4295 cnt = 0;
4296 if (c1 == '.')
4297 cnt = (rows - 2) / 2; // put dot at center
4298 if (c1 == '-')
4299 cnt = rows - 2; // put dot at bottom
4300 screenbegin = begin_line(dot); // start dot at top
4301 dot_scroll(cnt, -1);
4302 break;
4303 case '|': // |- move to column "cmdcnt"
4304 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
4305 break;
4306 case '~': // ~- flip the case of letters a-z -> A-Z
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004307 do {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004308#if ENABLE_FEATURE_VI_UNDO
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01004309 if (isalpha(*dot)) {
4310 undo_push(dot, 1, undo_del);
4311 *dot = islower(*dot) ? toupper(*dot) : tolower(*dot);
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004312 undo_push(dot, 1, UNDO_INS_CHAIN);
Ron Yorstonb18c7bf2021-04-10 10:20:31 +01004313 undo_del = UNDO_DEL_CHAIN;
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004314 }
4315#else
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004316 if (islower(*dot)) {
4317 *dot = toupper(*dot);
Denys Vlasenkoe7430862014-04-03 12:47:48 +02004318 modified_count++;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004319 } else if (isupper(*dot)) {
4320 *dot = tolower(*dot);
Denys Vlasenkoe7430862014-04-03 12:47:48 +02004321 modified_count++;
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004322 }
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004323#endif
Denys Vlasenko12e154f2011-09-09 12:35:49 +02004324 dot_right();
4325 } while (--cmdcnt > 0);
Eric Andersen3f980402001-04-04 17:31:15 +00004326 end_cmd_q(); // stop adding to q
4327 break;
4328 //----- The Cursor and Function Keys -----------------------------
Denis Vlasenko0112ff52008-10-25 23:23:00 +00004329 case KEYCODE_HOME: // Cursor Key Home
Eric Andersen3f980402001-04-04 17:31:15 +00004330 dot_begin();
4331 break;
4332 // The Fn keys could point to do_macro which could translate them
Denis Vlasenko0112ff52008-10-25 23:23:00 +00004333#if 0
4334 case KEYCODE_FUN1: // Function Key F1
4335 case KEYCODE_FUN2: // Function Key F2
4336 case KEYCODE_FUN3: // Function Key F3
4337 case KEYCODE_FUN4: // Function Key F4
4338 case KEYCODE_FUN5: // Function Key F5
4339 case KEYCODE_FUN6: // Function Key F6
4340 case KEYCODE_FUN7: // Function Key F7
4341 case KEYCODE_FUN8: // Function Key F8
4342 case KEYCODE_FUN9: // Function Key F9
4343 case KEYCODE_FUN10: // Function Key F10
4344 case KEYCODE_FUN11: // Function Key F11
4345 case KEYCODE_FUN12: // Function Key F12
Eric Andersen3f980402001-04-04 17:31:15 +00004346 break;
Denis Vlasenko0112ff52008-10-25 23:23:00 +00004347#endif
Eric Andersen3f980402001-04-04 17:31:15 +00004348 }
4349
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004350 dc1:
Eric Andersen3f980402001-04-04 17:31:15 +00004351 // if text[] just became empty, add back an empty line
4352 if (end == text) {
Jody Bruchona8d6f9b2014-04-02 13:49:26 +02004353 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
Eric Andersen3f980402001-04-04 17:31:15 +00004354 dot = text;
4355 }
4356 // it is OK for dot to exactly equal to end, otherwise check dot validity
4357 if (dot != end) {
4358 dot = bound_dot(dot); // make sure "dot" is valid
4359 }
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004360#if ENABLE_FEATURE_VI_YANKMARK
Ron Yorston74d565f2021-04-15 12:04:45 +01004361 if (dot != orig_dot)
4362 check_context(c); // update the current context
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004363#endif
Eric Andersen3f980402001-04-04 17:31:15 +00004364
4365 if (!isdigit(c))
4366 cmdcnt = 0; // cmd was not a number, reset cmdcnt
4367 cnt = dot - begin_line(dot);
4368 // Try to stay off of the Newline
4369 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4370 dot--;
4371}
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004372
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004373// NB! the CRASHME code is unmaintained, and doesn't currently build
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004374#if ENABLE_FEATURE_VI_CRASHME
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004375static int totalcmds = 0;
4376static int Mp = 85; // Movement command Probability
4377static int Np = 90; // Non-movement command Probability
4378static int Dp = 96; // Delete command Probability
4379static int Ip = 97; // Insert command Probability
4380static int Yp = 98; // Yank command Probability
4381static int Pp = 99; // Put command Probability
4382static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004383static const char chars[20] = "\t012345 abcdABCD-=.$";
4384static const char *const words[20] = {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004385 "this", "is", "a", "test",
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004386 "broadcast", "the", "emergency", "of",
4387 "system", "quick", "brown", "fox",
4388 "jumped", "over", "lazy", "dogs",
4389 "back", "January", "Febuary", "March"
4390};
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004391static const char *const lines[20] = {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004392 "You should have received a copy of the GNU General Public License\n",
4393 "char c, cm, *cmd, *cmd1;\n",
4394 "generate a command by percentages\n",
4395 "Numbers may be typed as a prefix to some commands.\n",
4396 "Quit, discarding changes!\n",
4397 "Forced write, if permission originally not valid.\n",
4398 "In general, any ex or ed command (such as substitute or delete).\n",
4399 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4400 "Please get w/ me and I will go over it with you.\n",
4401 "The following is a list of scheduled, committed changes.\n",
4402 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4403 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4404 "Any question about transactions please contact Sterling Huxley.\n",
4405 "I will try to get back to you by Friday, December 31.\n",
4406 "This Change will be implemented on Friday.\n",
4407 "Let me know if you have problems accessing this;\n",
4408 "Sterling Huxley recently added you to the access list.\n",
4409 "Would you like to go to lunch?\n",
4410 "The last command will be automatically run.\n",
4411 "This is too much english for a computer geek.\n",
4412};
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004413static char *multilines[20] = {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004414 "You should have received a copy of the GNU General Public License\n",
4415 "char c, cm, *cmd, *cmd1;\n",
4416 "generate a command by percentages\n",
4417 "Numbers may be typed as a prefix to some commands.\n",
4418 "Quit, discarding changes!\n",
4419 "Forced write, if permission originally not valid.\n",
4420 "In general, any ex or ed command (such as substitute or delete).\n",
4421 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4422 "Please get w/ me and I will go over it with you.\n",
4423 "The following is a list of scheduled, committed changes.\n",
4424 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4425 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4426 "Any question about transactions please contact Sterling Huxley.\n",
4427 "I will try to get back to you by Friday, December 31.\n",
4428 "This Change will be implemented on Friday.\n",
4429 "Let me know if you have problems accessing this;\n",
4430 "Sterling Huxley recently added you to the access list.\n",
4431 "Would you like to go to lunch?\n",
4432 "The last command will be automatically run.\n",
4433 "This is too much english for a computer geek.\n",
4434};
4435
4436// create a random command to execute
4437static void crash_dummy()
4438{
4439 static int sleeptime; // how long to pause between commands
4440 char c, cm, *cmd, *cmd1;
4441 int i, cnt, thing, rbi, startrbi, percent;
4442
4443 // "dot" movement commands
4444 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4445
4446 // is there already a command running?
Denys Vlasenko020f4062009-05-17 16:44:54 +02004447 if (readbuffer[0] > 0)
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004448 goto cd1;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004449 cd0:
Denys Vlasenko020f4062009-05-17 16:44:54 +02004450 readbuffer[0] = 'X';
4451 startrbi = rbi = 1;
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004452 sleeptime = 0; // how long to pause between commands
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004453 memset(readbuffer, '\0', sizeof(readbuffer));
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004454 // generate a command by percentages
4455 percent = (int) lrand48() % 100; // get a number from 0-99
4456 if (percent < Mp) { // Movement commands
4457 // available commands
4458 cmd = cmd1;
4459 M++;
4460 } else if (percent < Np) { // non-movement commands
4461 cmd = "mz<>\'\""; // available commands
4462 N++;
4463 } else if (percent < Dp) { // Delete commands
4464 cmd = "dx"; // available commands
4465 D++;
4466 } else if (percent < Ip) { // Inset commands
4467 cmd = "iIaAsrJ"; // available commands
4468 I++;
4469 } else if (percent < Yp) { // Yank commands
4470 cmd = "yY"; // available commands
4471 Y++;
4472 } else if (percent < Pp) { // Put commands
4473 cmd = "pP"; // available commands
4474 P++;
4475 } else {
4476 // We do not know how to handle this command, try again
4477 U++;
4478 goto cd0;
4479 }
4480 // randomly pick one of the available cmds from "cmd[]"
4481 i = (int) lrand48() % strlen(cmd);
4482 cm = cmd[i];
4483 if (strchr(":\024", cm))
4484 goto cd0; // dont allow colon or ctrl-T commands
4485 readbuffer[rbi++] = cm; // put cmd into input buffer
4486
4487 // now we have the command-
4488 // there are 1, 2, and multi char commands
4489 // find out which and generate the rest of command as necessary
4490 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4491 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4492 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4493 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4494 }
4495 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4496 c = cmd1[thing];
4497 readbuffer[rbi++] = c; // add movement to input buffer
4498 }
4499 if (strchr("iIaAsc", cm)) { // multi-char commands
4500 if (cm == 'c') {
4501 // change some thing
4502 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4503 c = cmd1[thing];
4504 readbuffer[rbi++] = c; // add movement to input buffer
4505 }
4506 thing = (int) lrand48() % 4; // what thing to insert
4507 cnt = (int) lrand48() % 10; // how many to insert
4508 for (i = 0; i < cnt; i++) {
4509 if (thing == 0) { // insert chars
4510 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4511 } else if (thing == 1) { // insert words
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004512 strcat(readbuffer, words[(int) lrand48() % 20]);
4513 strcat(readbuffer, " ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004514 sleeptime = 0; // how fast to type
4515 } else if (thing == 2) { // insert lines
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004516 strcat(readbuffer, lines[(int) lrand48() % 20]);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004517 sleeptime = 0; // how fast to type
4518 } else { // insert multi-lines
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004519 strcat(readbuffer, multilines[(int) lrand48() % 20]);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004520 sleeptime = 0; // how fast to type
4521 }
4522 }
Denys Vlasenko8187e012017-09-13 22:48:30 +02004523 strcat(readbuffer, ESC);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004524 }
Denys Vlasenko020f4062009-05-17 16:44:54 +02004525 readbuffer[0] = strlen(readbuffer + 1);
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004526 cd1:
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004527 totalcmds++;
4528 if (sleeptime > 0)
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004529 mysleep(sleeptime); // sleep 1/100 sec
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004530}
4531
4532// test to see if there are any errors
4533static void crash_test()
4534{
4535 static time_t oldtim;
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004536
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004537 time_t tim;
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004538 char d[2], msg[80];
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004539
4540 msg[0] = '\0';
4541 if (end < text) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004542 strcat(msg, "end<text ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004543 }
4544 if (end > textend) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004545 strcat(msg, "end>textend ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004546 }
4547 if (dot < text) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004548 strcat(msg, "dot<text ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004549 }
4550 if (dot > end) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004551 strcat(msg, "dot>end ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004552 }
4553 if (screenbegin < text) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004554 strcat(msg, "screenbegin<text ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004555 }
4556 if (screenbegin > end - 1) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004557 strcat(msg, "screenbegin>end-1 ");
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004558 }
4559
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004560 if (msg[0]) {
Glenn L McGrath7127b582002-12-03 21:48:15 +00004561 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
Denys Vlasenko04b52892012-06-11 13:51:38 +02004562 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
Denys Vlasenko8131eea2009-11-02 14:19:51 +01004563 fflush_all();
Bernhard Reutner-Fischer5e25ddb2008-05-19 09:48:17 +00004564 while (safe_read(STDIN_FILENO, d, 1) > 0) {
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004565 if (d[0] == '\n' || d[0] == '\r')
4566 break;
4567 }
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004568 }
Denis Vlasenko88adfcd2007-12-22 15:40:13 +00004569 tim = time(NULL);
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004570 if (tim >= (oldtim + 3)) {
Denis Vlasenkoafa37cf2007-03-21 00:05:35 +00004571 sprintf(status_buffer,
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004572 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4573 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4574 oldtim = tim;
4575 }
Glenn L McGrath09adaca2002-12-02 21:18:10 +00004576}
Denis Vlasenko6a5dc5d2006-12-30 18:42:29 +00004577#endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004578
4579static void edit_file(char *fn)
4580{
4581#if ENABLE_FEATURE_VI_YANKMARK
4582#define cur_line edit_file__cur_line
4583#endif
4584 int c;
4585#if ENABLE_FEATURE_VI_USE_SIGNALS
4586 int sig;
4587#endif
4588
4589 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
4590 rawmode();
4591 rows = 24;
4592 columns = 80;
4593 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
4594#if ENABLE_FEATURE_VI_ASK_TERMINAL
4595 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
4596 uint64_t k;
4597 write1(ESC"[999;999H" ESC"[6n");
4598 fflush_all();
4599 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
4600 if ((int32_t)k == KEYCODE_CURSOR_POS) {
4601 uint32_t rc = (k >> 32);
4602 columns = (rc & 0x7fff);
4603 if (columns > MAX_SCR_COLS)
4604 columns = MAX_SCR_COLS;
4605 rows = ((rc >> 16) & 0x7fff);
4606 if (rows > MAX_SCR_ROWS)
4607 rows = MAX_SCR_ROWS;
4608 }
4609 }
4610#endif
4611 new_screen(rows, columns); // get memory for virtual screen
4612 init_text_buffer(fn);
4613
4614#if ENABLE_FEATURE_VI_YANKMARK
4615 YDreg = 26; // default Yank/Delete reg
4616// Ureg = 27; - const // hold orig line for "U" cmd
4617 mark[26] = mark[27] = text; // init "previous context"
4618#endif
4619
Denys Vlasenkob29dce42019-04-01 17:17:02 +02004620#if ENABLE_FEATURE_VI_CRASHME
4621 last_input_char = '\0';
4622#endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004623 crow = 0;
4624 ccol = 0;
4625
4626#if ENABLE_FEATURE_VI_USE_SIGNALS
4627 signal(SIGWINCH, winch_handler);
4628 signal(SIGTSTP, tstp_handler);
4629 sig = sigsetjmp(restart, 1);
4630 if (sig != 0) {
4631 screenbegin = dot = text;
4632 }
4633 // int_handler() can jump to "restart",
4634 // must install handler *after* initializing "restart"
4635 signal(SIGINT, int_handler);
4636#endif
4637
4638 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
4639 cmdcnt = 0;
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004640 offset = 0; // no horizontal offset
4641 c = '\0';
4642#if ENABLE_FEATURE_VI_DOT_CMD
4643 free(ioq_start);
Denys Vlasenko2a576082019-04-01 16:15:51 +02004644 ioq_start = NULL;
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004645 adding2q = 0;
4646#endif
4647
4648#if ENABLE_FEATURE_VI_COLON
4649 {
4650 char *p, *q;
4651 int n = 0;
4652
4653 while ((p = initial_cmds[n]) != NULL) {
4654 do {
4655 q = p;
4656 p = strchr(q, '\n');
4657 if (p)
4658 while (*p == '\n')
4659 *p++ = '\0';
4660 if (*q)
4661 colon(q);
4662 } while (p);
4663 free(initial_cmds[n]);
4664 initial_cmds[n] = NULL;
4665 n++;
4666 }
4667 }
4668#endif
4669 redraw(FALSE); // dont force every col re-draw
4670 //------This is the main Vi cmd handling loop -----------------------
4671 while (editing > 0) {
4672#if ENABLE_FEATURE_VI_CRASHME
4673 if (crashme > 0) {
4674 if ((end - text) > 1) {
4675 crash_dummy(); // generate a random command
4676 } else {
4677 crashme = 0;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004678 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO);
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004679 dot = text;
4680 refresh(FALSE);
4681 }
4682 }
4683#endif
Denys Vlasenkob29dce42019-04-01 17:17:02 +02004684 c = get_one_char(); // get a cmd from user
4685#if ENABLE_FEATURE_VI_CRASHME
4686 last_input_char = c;
4687#endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004688#if ENABLE_FEATURE_VI_YANKMARK
4689 // save a copy of the current line- for the 'U" command
4690 if (begin_line(dot) != cur_line) {
4691 cur_line = begin_line(dot);
Ron Yorston25d25922021-03-25 14:23:36 +00004692 text_yank(begin_line(dot), end_line(dot), Ureg, PARTIAL);
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004693 }
4694#endif
4695#if ENABLE_FEATURE_VI_DOT_CMD
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004696 // If c is a command that changes text[],
4697 // (re)start remembering the input for the "." command.
Denys Vlasenko2a576082019-04-01 16:15:51 +02004698 if (!adding2q
4699 && ioq_start == NULL
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004700 && cmd_mode == 0 // command mode
4701 && c > '\0' // exclude NUL and non-ASCII chars
4702 && c < 0x7f // (Unicode and such)
4703 && strchr(modifying_cmds, c)
4704 ) {
4705 start_new_cmd_q(c);
4706 }
4707#endif
4708 do_cmd(c); // execute the user command
4709
4710 // poll to see if there is input already waiting. if we are
4711 // not able to display output fast enough to keep up, skip
4712 // the display update until we catch up with input.
4713 if (!readbuffer[0] && mysleep(0) == 0) {
4714 // no input pending - so update output
4715 refresh(FALSE);
4716 show_status_line();
4717 }
4718#if ENABLE_FEATURE_VI_CRASHME
4719 if (crashme > 0)
4720 crash_test(); // test editor variables
4721#endif
4722 }
4723 //-------------------------------------------------------------------
4724
4725 go_bottom_and_clear_to_eol();
4726 cookmode();
4727#undef cur_line
4728}
4729
4730int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
4731int vi_main(int argc, char **argv)
4732{
4733 int c;
4734
4735 INIT_G();
4736
4737#if ENABLE_FEATURE_VI_UNDO
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004738 //undo_stack_tail = NULL; - already is
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02004739# if ENABLE_FEATURE_VI_UNDO_QUEUE
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004740 undo_queue_state = UNDO_EMPTY;
Denys Vlasenko26f5e9d2019-04-01 16:38:06 +02004741 //undo_q = 0; - already is
Denys Vlasenko6ce60b92019-04-01 15:41:05 +02004742# endif
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004743#endif
4744
4745#if ENABLE_FEATURE_VI_CRASHME
4746 srand((long) getpid());
4747#endif
4748#ifdef NO_SUCH_APPLET_YET
4749 // if we aren't "vi", we are "view"
4750 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
4751 SET_READONLY_MODE(readonly_mode);
4752 }
4753#endif
4754
Ron Yorston9f017d92021-04-06 22:11:21 +01004755 // 0: all of our options are disabled by default in vim
4756 //vi_setops = 0;
4757 // 1- process EXINIT variable from environment
4758 // 2- if EXINIT is unset process $HOME/.exrc file (not inplemented yet)
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004759 // 3- process command line args
4760#if ENABLE_FEATURE_VI_COLON
4761 {
4762 char *p = getenv("EXINIT");
4763 if (p && *p)
4764 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
4765 }
4766#endif
Denys Vlasenko6b6826f2021-06-13 01:08:48 +02004767 while ((c = getopt(argc, argv,
4768#if ENABLE_FEATURE_VI_CRASHME
4769 "C"
4770#endif
4771 "RHh" IF_FEATURE_VI_COLON("c:"))) != -1) {
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004772 switch (c) {
4773#if ENABLE_FEATURE_VI_CRASHME
4774 case 'C':
4775 crashme = 1;
4776 break;
4777#endif
4778#if ENABLE_FEATURE_VI_READONLY
4779 case 'R': // Read-only flag
4780 SET_READONLY_MODE(readonly_mode);
4781 break;
4782#endif
4783#if ENABLE_FEATURE_VI_COLON
4784 case 'c': // cmd line vi command
4785 if (*optarg)
4786 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
4787 break;
4788#endif
4789 case 'H':
4790 show_help();
4791 // fall through
4792 default:
4793 bb_show_usage();
4794 return 1;
4795 }
4796 }
4797
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004798 argv += optind;
Denys Vlasenko89393592019-04-02 12:45:30 +02004799 cmdline_filecnt = argc - optind;
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004800
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004801 // "Save cursor, use alternate screen buffer, clear screen"
4802 write1(ESC"[?1049h");
Denys Vlasenko89393592019-04-02 12:45:30 +02004803 // This is the main file handling loop
4804 optind = 0;
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004805 while (1) {
Denys Vlasenko89393592019-04-02 12:45:30 +02004806 edit_file(argv[optind]); // might be NULL on 1st iteration
4807 // NB: optind can be changed by ":next" and ":rewind" commands
4808 optind++;
Denys Vlasenkoa3ce1612019-04-03 16:35:23 +02004809 if (optind >= cmdline_filecnt)
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004810 break;
4811 }
4812 // "Use normal screen buffer, restore cursor"
4813 write1(ESC"[?1049l");
Denys Vlasenko24bd3502019-04-01 13:55:27 +02004814
4815 return 0;
4816}