aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/README.md190
-rw-r--r--src/nvim/api/buffer.c1
-rw-r--r--src/nvim/ascii.h7
-rw-r--r--src/nvim/auevents.lua9
-rw-r--r--src/nvim/buffer.c1019
-rw-r--r--src/nvim/buffer_defs.h1
-rw-r--r--src/nvim/diff.c1
-rw-r--r--src/nvim/edit.c1814
-rw-r--r--src/nvim/eval.c578
-rw-r--r--src/nvim/eval.h44
-rw-r--r--src/nvim/eval_defs.h4
-rw-r--r--src/nvim/event/socket.c2
-rw-r--r--src/nvim/event/stream.c2
-rw-r--r--src/nvim/ex_cmds.c16
-rw-r--r--src/nvim/ex_cmds.lua34
-rw-r--r--src/nvim/ex_cmds2.c104
-rw-r--r--src/nvim/ex_cmds_defs.h6
-rw-r--r--src/nvim/ex_docmd.c75
-rw-r--r--src/nvim/ex_eval.c8
-rw-r--r--src/nvim/ex_getln.c2581
-rw-r--r--src/nvim/file_search.c1
-rw-r--r--src/nvim/fileio.c59
-rw-r--r--src/nvim/fold.c9
-rw-r--r--src/nvim/getchar.c10
-rw-r--r--src/nvim/globals.h62
-rw-r--r--src/nvim/hardcopy.c9
-rw-r--r--src/nvim/if_cscope.c3
-rw-r--r--src/nvim/keymap.c5
-rw-r--r--src/nvim/keymap.h9
-rw-r--r--src/nvim/log.c2
-rw-r--r--src/nvim/log.h2
-rw-r--r--src/nvim/macros.h11
-rw-r--r--src/nvim/main.c448
-rw-r--r--src/nvim/mark.c38
-rw-r--r--src/nvim/mbyte.c10
-rw-r--r--src/nvim/memfile.c9
-rw-r--r--src/nvim/memline.c139
-rw-r--r--src/nvim/memline_defs.h2
-rw-r--r--src/nvim/memory.c3
-rw-r--r--src/nvim/menu.c10
-rw-r--r--src/nvim/message.c9
-rw-r--r--src/nvim/misc1.c10
-rw-r--r--src/nvim/misc2.c11
-rw-r--r--src/nvim/move.c7
-rw-r--r--src/nvim/normal.c1500
-rw-r--r--src/nvim/normal.h4
-rw-r--r--src/nvim/ops.c12
-rw-r--r--src/nvim/option.c452
-rw-r--r--src/nvim/option_defs.h1
-rw-r--r--src/nvim/options.lua27
-rw-r--r--src/nvim/os/env.c101
-rw-r--r--src/nvim/os/fs.c44
-rw-r--r--src/nvim/os/fs_defs.h4
-rw-r--r--src/nvim/os/input.c42
-rw-r--r--src/nvim/os/os.h2
-rw-r--r--src/nvim/os/os_defs.h30
-rw-r--r--src/nvim/os/shell.c3
-rw-r--r--src/nvim/os/signal.c12
-rw-r--r--src/nvim/os/stdpaths.c108
-rw-r--r--src/nvim/os/stdpaths_defs.h14
-rw-r--r--src/nvim/os/unix_defs.h53
-rw-r--r--src/nvim/os/win_defs.h17
-rw-r--r--src/nvim/os_unix.c8
-rw-r--r--src/nvim/path.c64
-rw-r--r--src/nvim/po/CMakeLists.txt2
-rw-r--r--src/nvim/popupmnu.c2
-rw-r--r--src/nvim/quickfix.c9
-rw-r--r--src/nvim/regexp_nfa.c9
-rw-r--r--src/nvim/screen.c9
-rw-r--r--src/nvim/search.c28
-rw-r--r--src/nvim/shada.c374
-rw-r--r--src/nvim/spell.c7
-rw-r--r--src/nvim/state.c62
-rw-r--r--src/nvim/state.h20
-rw-r--r--src/nvim/strings.c2
-rw-r--r--src/nvim/syntax.c9
-rw-r--r--src/nvim/tag.c9
-rw-r--r--src/nvim/terminal.c168
-rw-r--r--src/nvim/terminal.h9
-rw-r--r--src/nvim/testdir/test49.vim2
-rw-r--r--src/nvim/testdir/test53.in4
-rw-r--r--src/nvim/testdir/test53.ok1
-rw-r--r--src/nvim/testdir/unix.vim3
-rw-r--r--src/nvim/tui/input.c32
-rw-r--r--src/nvim/tui/tui.c35
-rw-r--r--src/nvim/types.h7
-rw-r--r--src/nvim/ui_bridge.c19
-rw-r--r--src/nvim/ui_bridge.h4
-rw-r--r--src/nvim/undo.c247
-rw-r--r--src/nvim/version.c194
-rw-r--r--src/nvim/version.h1
-rw-r--r--src/nvim/vim.h7
-rw-r--r--src/nvim/window.c9
93 files changed, 6532 insertions, 4624 deletions
diff --git a/src/nvim/README.md b/src/nvim/README.md
new file mode 100644
index 0000000000..e4939d94fd
--- /dev/null
+++ b/src/nvim/README.md
@@ -0,0 +1,190 @@
+## Source code overview
+
+Since Neovim has inherited most code from Vim, some information in [its
+README](https://raw.githubusercontent.com/vim/vim/master/src/README.txt) still
+applies.
+
+This document aims to give a high level overview of how Neovim works internally,
+focusing on parts that are different from Vim. Currently this is still a work in
+progress, especially because I have avoided adding too many details about parts
+that are constantly changing. As the code becomes more organized and stable,
+this document will be updated to reflect the changes.
+
+If you are looking for module-specific details, it is best to read the source
+code. Some files are extensively commented at the top(eg: terminal.c,
+screen.c).
+
+### Top-level program loops
+
+First let's understand what a Vim-like program does by analyzing the workflow of
+a typical editing session:
+
+01. Vim dispays the welcome screen
+02. User types: `:`
+03. Vim enters command-line mode
+04. User types: `edit README.txt<CR>`
+05. Vim opens the file and returns to normal mode
+06. User types: `G`
+07. Vim navigates to the end of the file
+09. User types: `5`
+10. Vim enters count-pending mode
+11. User types: `d`
+12. Vim enters operator-pending mode
+13. User types: `w`
+14. Vim deletes 5 words
+15. User types: `g`
+16. Vim enters the "g command mode"
+17. User types: `g`
+18. Vim goes to the beginning of the file
+19. User types: `i`
+20. Vim enters insert mode
+21. User types: `word<ESC>`
+22. Vim inserts "word" at the beginning and returns to normal mode
+
+Note that we have split user actions into sequences of inputs that change the
+state of the editor. While there's no documentation about a "g command
+mode"(step 16), internally it is implemented similarly to "operator-pending
+mode".
+
+From this we can see that Vim has the behavior of a input-driven state
+machine(more specifically, a pushdown automaton since it requires a stack for
+transitioning back from states). Assuming each state has a callback responsible
+for handling keys, this pseudocode(a python-like language) shows a good
+representation of the main program loop:
+
+```py
+def state_enter(state_callback, data):
+ do
+ key = readkey() # read a key from the user
+ while state_callback(data, key) # invoke the callback for the current state
+```
+
+That is, each state is entered by calling `state_enter` and passing a
+state-specific callback and data. Here is a high-level pseudocode for a program
+that implements something like the workflow described above:
+
+```py
+def main()
+ state_enter(normal_state, {}):
+
+def normal_state(data, key):
+ if key == ':':
+ state_enter(command_line_state, {})
+ elif key == 'i':
+ state_enter(insert_state, {})
+ elif key == 'd':
+ state_enter(delete_operator_state, {})
+ elif key == 'g':
+ state_enter(g_command_state, {})
+ elif is_number(key):
+ state_enter(get_operator_count_state, {'count': key})
+ elif key == 'G'
+ jump_to_eof()
+ return true
+
+def command_line_state(data, key):
+ if key == '<cr>':
+ if data['input']:
+ execute_ex_command(data['input'])
+ return false
+ elif key == '<esc>'
+ return false
+
+ if not data['input']:
+ data['input'] = ''
+
+ data['input'] += key
+ return true
+
+def delete_operator_state(data, key):
+ count = data['count'] or 1
+ if key == 'w':
+ delete_word(count)
+ elif key == '$':
+ delete_to_eol(count)
+ return false # return to normal mode
+
+def g_command_state(data, key):
+ if key == 'g':
+ go_top()
+ elif key == 'v':
+ reselect()
+ return false # return to normal mode
+
+def get_operator_count_state(data, key):
+ if is_number(key):
+ data['count'] += key
+ return true
+ unshift_key(key) # return key to the input buffer
+ state_enter(delete_operator_state, data)
+ return false
+
+def insert_state(data, key):
+ if key == '<esc>':
+ return false # exit insert mode
+ self_insert(key)
+ return true
+```
+
+While the actual code is much more complicated, the above gives an idea of how
+Neovim is organized internally. Some states like the `g_command_state` or
+`get_operator_count_state` do not have a dedicated `state_enter` callback, but
+are implicitly embedded into other states(this will change later as we continue
+the refactoring effort). To start reading the actual code, here's the
+recommended order:
+
+1. `state_enter()` function(state.c). This is the actual program loop,
+ note that a `VimState` structure is used, which contains function pointers
+ for the callback and state data.
+2. `main()` function(main.c). After all startup, `normal_enter` is called
+ at the end of function to enter normal mode.
+3. `normal_enter()` function(normal.c) is a small wrapper for setting
+ up the NormalState structure and calling `state_enter`.
+4. `normal_check()` function(normal.c) is called before each iteration of
+ normal mode.
+5. `normal_execute()` function(normal.c) is called when a key is read in normal
+ mode.
+
+The basic structure described for normal mode in 3, 4 and 5 is used for other
+modes managed by the `state_enter` loop:
+
+- command-line mode: `command_line_{enter,check,execute}()`(`ex_getln.c`)
+- insert mode: `insert_{enter,check,execute}()`(`edit.c`)
+- terminal mode: `terminal_{enter,execute}()`(`terminal.c`)
+
+### Async event support
+
+One of the features Neovim added is the support for handling arbitrary
+asynchronous events, which can include:
+
+- msgpack-rpc requests
+- job control callbacks
+- timers(not implemented yet but the support code is already there)
+
+Neovim implements this functionality by entering another event loop while
+waiting for characters, so instead of:
+
+```py
+def state_enter(state_callback, data):
+ do
+ key = readkey() # read a key from the user
+ while state_callback(data, key) # invoke the callback for the current state
+```
+
+Neovim program loop is more like:
+
+```py
+def state_enter(state_callback, data):
+ do
+ event = read_next_event() # read an event from the operating system
+ while state_callback(data, event) # invoke the callback for the current state
+```
+
+where `event` is something the operating system delivers to us, including(but
+not limited to) user input. The `read_next_event()` part is internally
+implemented by libuv, the platform layer used by Neovim.
+
+Since Neovim inherited its code from Vim, the states are not prepared to receive
+"arbitrary events", so we use a special key to represent those(When a state
+receives an "arbitrary event", it normally doesn't do anything other update the
+screen).
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index a8446265d0..b7a86af134 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -1,6 +1,5 @@
// Much of this code was adapted from 'if_py_both.h' from the original
// vim source
-#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h
index cce52c5250..2b3e94d5a0 100644
--- a/src/nvim/ascii.h
+++ b/src/nvim/ascii.h
@@ -1,10 +1,3 @@
-/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- */
-
#ifndef NVIM_ASCII_H
#define NVIM_ASCII_H
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index 3d8a75febd..aa4a8d8332 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -57,6 +57,7 @@ return {
'InsertLeave', -- when leaving Insert mode
'JobActivity', -- when job sent some data
'MenuPopup', -- just before popup menu is displayed
+ 'OptionSet', -- after setting any option
'QuickFixCmdPost', -- after :make, :grep etc.
'QuickFixCmdPre', -- before :make, :grep etc.
'QuitPre', -- before :quit
@@ -77,8 +78,9 @@ return {
'TabNew', -- when creating a new tab
'TabNewEntered', -- after entering a new tab
'TermChanged', -- after changing 'term'
- 'TermResponse', -- after setting "v:termresponse"
+ 'TermClose', -- after the processs exits
'TermOpen', -- after opening a terminal buffer
+ 'TermResponse', -- after setting "v:termresponse"
'TextChanged', -- text was modified
'TextChangedI', -- text was modified in Insert mode
'User', -- user defined autocommand
@@ -98,9 +100,10 @@ return {
-- List of neovim-specific events or aliases for the purpose of generating
-- syntax file
neovim_specific = {
+ TabClosed=true,
TabNew=true,
TabNewEntered=true,
- TabClosed=true,
- TermEnter=true,
+ TermClose=true,
+ TermOpen=true,
},
}
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index b3eba4f5f6..762cd3efd3 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -1,12 +1,4 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* buffer.c: functions for dealing with the buffer structure
*/
@@ -25,7 +17,6 @@
*/
#include <stdbool.h>
-#include <errno.h>
#include <string.h>
#include <inttypes.h>
@@ -1423,7 +1414,6 @@ buflist_new (
return NULL;
if (aborting()) /* autocmds may abort script processing */
return NULL;
- /* buf->b_nwindows = 0; why was this here? */
free_buffer_stuff(buf, FALSE); /* delete local variables et al. */
/* Init the options. */
@@ -1484,6 +1474,9 @@ buflist_new (
fmarks_check_names(buf); /* check file marks for this file */
buf->b_p_bl = (flags & BLN_LISTED) ? TRUE : FALSE; /* init 'buflisted' */
if (!(flags & BLN_DUMMY)) {
+ // Tricky: these autocommands may change the buffer list. They could also
+ // split the window with re-using the one empty buffer. This may result in
+ // unexpectedly losing the empty buffer.
apply_autocmds(EVENT_BUFNEW, NULL, NULL, FALSE, buf);
if (!buf_valid(buf)) {
return NULL;
@@ -2159,9 +2152,23 @@ void buflist_list(exarg_T *eap)
int i;
for (buf = firstbuf; buf != NULL && !got_int; buf = buf->b_next) {
- /* skip unlisted buffers, unless ! was used */
- if (!buf->b_p_bl && !eap->forceit)
+ // skip unspecified buffers
+ if ((!buf->b_p_bl && !eap->forceit && !strchr((char *)eap->arg, 'u'))
+ || (strchr((char *)eap->arg, 'u') && buf->b_p_bl)
+ || (strchr((char *)eap->arg, '+')
+ && ((buf->b_flags & BF_READERR) || !bufIsChanged(buf)))
+ || (strchr((char *)eap->arg, 'a')
+ && (buf->b_ml.ml_mfp == NULL || buf->b_nwindows == 0))
+ || (strchr((char *)eap->arg, 'h')
+ && (buf->b_ml.ml_mfp == NULL || buf->b_nwindows != 0))
+ || (strchr((char *)eap->arg, '-') && buf->b_p_ma)
+ || (strchr((char *)eap->arg, '=') && !buf->b_p_ro)
+ || (strchr((char *)eap->arg, 'x') && !(buf->b_flags & BF_READERR))
+ || (strchr((char *)eap->arg, '%') && buf != curbuf)
+ || (strchr((char *)eap->arg, '#')
+ && (buf == curbuf || curwin->w_alt_fnum != buf->b_fnum))) {
continue;
+ }
msg_putchar('\n');
if (buf_spname(buf) != NULL)
STRLCPY(NameBuff, buf_spname(buf), MAXPATHL);
@@ -2789,58 +2796,59 @@ void free_titles(void)
# endif
+/// Enumeration specifying the valid numeric bases that can
+/// be used when printing numbers in the status line.
+typedef enum {
+ kNumBaseDecimal = 10,
+ kNumBaseOctal = 8,
+ kNumBaseHexadecimal = 16
+} NumberBase;
-/*
- * Build a string from the status line items in "fmt".
- * Return length of string in screen cells.
- *
- * Normally works for window "wp", except when working for 'tabline' then it
- * is "curwin".
- *
- * Items are drawn interspersed with the text that surrounds it
- * Specials: %-<wid>(xxx%) => group, %= => middle marker, %< => truncation
- * Item: %-<minwid>.<maxwid><itemch> All but <itemch> are optional
- *
- * If maxwidth is not zero, the string will be filled at any middle marker
- * or truncated if too long, fillchar is used for all whitespace.
- */
-int
-build_stl_str_hl (
+
+/// Build a string from the status line items in "fmt".
+/// Return length of string in screen cells.
+///
+/// Normally works for window "wp", except when working for 'tabline' then it
+/// is "curwin".
+///
+/// Items are drawn interspersed with the text that surrounds it
+/// Specials: %-<wid>(xxx%) => group, %= => middle marker, %< => truncation
+/// Item: %-<minwid>.<maxwid><itemch> All but <itemch> are optional
+///
+/// If maxwidth is not zero, the string will be filled at any middle marker
+/// or truncated if too long, fillchar is used for all whitespace.
+///
+/// @param wp The window to build a statusline for
+/// @param out The output buffer to write the statusline to
+/// Note: This should not be NameBuff
+/// @param outlen The length of the output buffer
+/// @param fmt The statusline format string
+/// @param use_sandbox Use a sandboxed environment when evaluating fmt
+/// @param fillchar Character to use when filling empty space in the statusline
+/// @param maxwidth The maximum width to make the statusline
+/// @param hltab HL attributes (can be NULL)
+/// @param tabtab tab page nrs (can be NULL)
+///
+/// @return The final width of the statusline
+int build_stl_str_hl(
win_T *wp,
- char_u *out, /* buffer to write into != NameBuff */
- size_t outlen, /* length of out[] */
+ char_u *out,
+ size_t outlen,
char_u *fmt,
- int use_sandbox, /* "fmt" was set insecurely, use sandbox */
+ int use_sandbox,
int fillchar,
int maxwidth,
- struct stl_hlrec *hltab, /* return: HL attributes (can be NULL) */
- struct stl_hlrec *tabtab /* return: tab page nrs (can be NULL) */
+ struct stl_hlrec *hltab,
+ struct stl_hlrec *tabtab
)
{
- char_u *p;
- char_u *s;
- char_u *t;
- int byteval;
- win_T *o_curwin;
- buf_T *o_curbuf;
- int empty_line;
- colnr_T virtcol;
- long l;
- long n;
- int prevchar_isflag;
- int prevchar_isitem;
- int itemisflag;
- int fillable;
- char_u *str;
- long num;
- int width;
- int itemcnt;
- int curitem;
int groupitem[STL_MAX_ITEM];
- int groupdepth;
struct stl_item {
+ // Where the item starts in the status line output buffer
char_u *start;
+ // The minimum width of the item
int minwid;
+ // The maximum width of the item
int maxwid;
enum {
Normal,
@@ -2852,20 +2860,13 @@ build_stl_str_hl (
Trunc
} type;
} item[STL_MAX_ITEM];
- int minwid;
- int maxwid;
- int zeropad;
- char_u base;
- char_u opt;
+
#define TMPLEN 70
char_u tmp[TMPLEN];
char_u *usefmt = fmt;
- struct stl_hlrec *sp;
- /*
- * When the format starts with "%!" then evaluate it as an expression and
- * use the result as the actual format string.
- */
+ // When the format starts with "%!" then evaluate it as an expression and
+ // use the result as the actual format string.
if (fmt[0] == '%' && fmt[1] == '!') {
usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox);
if (usefmt == NULL)
@@ -2874,175 +2875,280 @@ build_stl_str_hl (
if (fillchar == 0)
fillchar = ' ';
- /* Can't handle a multi-byte fill character yet. */
+ // Can't handle a multi-byte fill character yet.
else if (mb_char2len(fillchar) > 1)
fillchar = '-';
- /* Get line & check if empty (cursorpos will show "0-1"). Note that
- * p will become invalid when getting another buffer line. */
- p = ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, FALSE);
- empty_line = (*p == NUL);
+ // Get line & check if empty (cursorpos will show "0-1").
+ char_u *line_ptr = ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, false);
+ bool empty_line = (*line_ptr == NUL);
- /* Get the byte value now, in case we need it below. This is more
- * efficient than making a copy of the line. */
- if (wp->w_cursor.col > (colnr_T)STRLEN(p))
+ // Get the byte value now, in case we need it below. This is more
+ // efficient than making a copy of the line.
+ int byteval;
+ if (wp->w_cursor.col > (colnr_T)STRLEN(line_ptr))
byteval = 0;
else
- byteval = (*mb_ptr2char)(p + wp->w_cursor.col);
-
- groupdepth = 0;
- p = out;
- curitem = 0;
- prevchar_isflag = TRUE;
- prevchar_isitem = FALSE;
- for (s = usefmt; *s; ) {
+ byteval = (*mb_ptr2char)(line_ptr + wp->w_cursor.col);
+
+ int groupdepth = 0;
+
+ int curitem = 0;
+ bool prevchar_isflag = true;
+ bool prevchar_isitem = false;
+
+ // out_p is the current position in the output buffer
+ char_u *out_p = out;
+
+ // out_end_p is the last valid character in the output buffer
+ // Note: The null termination character must occur here or earlier,
+ // so any user-visible characters must occur before here.
+ char_u *out_end_p = (out + outlen) - 1;
+
+
+ // Proceed character by character through the statusline format string
+ // fmt_p is the current positon in the input buffer
+ for (char_u *fmt_p = usefmt; *fmt_p; ) {
if (curitem == STL_MAX_ITEM) {
- /* There are too many items. Add the error code to the statusline
- * to give the user a hint about what went wrong. */
- if (p + 6 < out + outlen) {
- memmove(p, " E541", (size_t)5);
- p += 5;
+ // There are too many items. Add the error code to the statusline
+ // to give the user a hint about what went wrong.
+ if (out_p + 5 < out_end_p) {
+ memmove(out_p, " E541", (size_t)5);
+ out_p += 5;
}
break;
}
- if (*s != NUL && *s != '%')
- prevchar_isflag = prevchar_isitem = FALSE;
+ if (*fmt_p != NUL && *fmt_p != '%') {
+ prevchar_isflag = prevchar_isitem = false;
+ }
+
+ // Copy the formatting verbatim until we reach the end of the string
+ // or find a formatting item (denoted by `%`)
+ // or run out of room in our output buffer.
+ while (*fmt_p != NUL && *fmt_p != '%' && out_p < out_end_p)
+ *out_p++ = *fmt_p++;
- /*
- * Handle up to the next '%' or the end.
- */
- while (*s != NUL && *s != '%' && p + 1 < out + outlen)
- *p++ = *s++;
- if (*s == NUL || p + 1 >= out + outlen)
+ // If we have processed the entire format string or run out of
+ // room in our output buffer, exit the loop.
+ if (*fmt_p == NUL || out_p >= out_end_p)
break;
- /*
- * Handle one '%' item.
- */
- s++;
- if (*s == NUL) /* ignore trailing % */
+ // The rest of this loop will handle a single `%` item.
+ // Note: We increment here to skip over the `%` character we are currently
+ // on so we can process the item's contents.
+ fmt_p++;
+
+ // Ignore `%` at the end of the format string
+ if (*fmt_p == NUL) {
break;
- if (*s == '%') {
- if (p + 1 >= out + outlen)
+ }
+
+ // Two `%` in a row is the escape sequence to print a
+ // single `%` in the output buffer.
+ if (*fmt_p == '%') {
+ // Ignore the character if we're out of room in the output buffer.
+ if (out_p >= out_end_p)
break;
- *p++ = *s++;
- prevchar_isflag = prevchar_isitem = FALSE;
+ *out_p++ = *fmt_p++;
+ prevchar_isflag = prevchar_isitem = false;
continue;
}
- if (*s == STL_MIDDLEMARK) {
- s++;
- if (groupdepth > 0)
+
+ // STL_MIDDLEMARK: Separation place between left and right aligned items.
+ if (*fmt_p == STL_MIDDLEMARK) {
+ fmt_p++;
+ // Ignored when we are inside of a grouping
+ if (groupdepth > 0) {
continue;
+ }
item[curitem].type = Middle;
- item[curitem++].start = p;
+ item[curitem++].start = out_p;
continue;
}
- if (*s == STL_TRUNCMARK) {
- s++;
+
+ // STL_TRUNCMARK: Where to begin truncating if the statusline is too long.
+ if (*fmt_p == STL_TRUNCMARK) {
+ fmt_p++;
item[curitem].type = Trunc;
- item[curitem++].start = p;
+ item[curitem++].start = out_p;
continue;
}
- if (*s == ')') {
- s++;
- if (groupdepth < 1)
+
+ // The end of a grouping
+ if (*fmt_p == ')') {
+ fmt_p++;
+ // Ignore if we are not actually inside a group currently
+ if (groupdepth < 1) {
continue;
+ }
groupdepth--;
- t = item[groupitem[groupdepth]].start;
- *p = NUL;
- l = vim_strsize(t);
+ // Determine how long the group is.
+ // Note: We set the current output position to null
+ // so `vim_strsize` will work.
+ char_u *t = item[groupitem[groupdepth]].start;
+ *out_p = NUL;
+ long group_len = vim_strsize(t);
+
+ // If the group contained internal items
+ // and the group did not have a minimum width,
+ // and if there were no normal items in the group,
+ // move the output pointer back to where the group started.
+ // Note: This erases any non-item characters that were in the group.
+ // Otherwise there would be no reason to do this step.
if (curitem > groupitem[groupdepth] + 1
&& item[groupitem[groupdepth]].minwid == 0) {
- /* remove group if all items are empty */
- for (n = groupitem[groupdepth] + 1; n < curitem; n++)
- if (item[n].type == Normal)
+ bool has_normal_items = false;
+ for (long n = groupitem[groupdepth] + 1; n < curitem; n++) {
+ if (item[n].type == Normal) {
+ has_normal_items = true;
break;
- if (n == curitem) {
- p = t;
- l = 0;
+ }
+ }
+
+ if (!has_normal_items) {
+ out_p = t;
+ group_len = 0;
}
}
- if (l > item[groupitem[groupdepth]].maxwid) {
- /* truncate, remove n bytes of text at the start */
+
+ // If the group is longer than it is allowed to be
+ // truncate by removing bytes from the start of the group text.
+ if (group_len > item[groupitem[groupdepth]].maxwid) {
+ // { Determine the number of bytes to remove
+ long n;
if (has_mbyte) {
/* Find the first character that should be included. */
n = 0;
- while (l >= item[groupitem[groupdepth]].maxwid) {
- l -= ptr2cells(t + n);
+ while (group_len >= item[groupitem[groupdepth]].maxwid) {
+ group_len -= ptr2cells(t + n);
n += (*mb_ptr2len)(t + n);
}
- } else
- n = (long)(p - t) - item[groupitem[groupdepth]].maxwid + 1;
+ } else {
+ n = (long)(out_p - t) - item[groupitem[groupdepth]].maxwid + 1;
+ }
+ // }
+ // Prepend the `<` to indicate that the output was truncated.
*t = '<';
- memmove(t + 1, t + n, (size_t)(p - (t + n)));
- p = p - n + 1;
+
+ // { Move the truncated output
+ memmove(t + 1, t + n, (size_t)(out_p - (t + n)));
+ out_p = out_p - n + 1;
/* Fill up space left over by half a double-wide char. */
- while (++l < item[groupitem[groupdepth]].minwid)
- *p++ = fillchar;
+ while (++group_len < item[groupitem[groupdepth]].minwid)
+ *out_p++ = fillchar;
+ // }
/* correct the start of the items for the truncation */
- for (l = groupitem[groupdepth] + 1; l < curitem; l++) {
- item[l].start -= n;
- if (item[l].start < t)
- item[l].start = t;
+ for (int idx = groupitem[groupdepth] + 1; idx < curitem; idx++) {
+ // Shift everything back by the number of removed bytes
+ item[idx].start -= n;
+
+ // If the item was partially or completely truncated, set its
+ // start to the start of the group
+ if (item[idx].start < t) {
+ item[idx].start = t;
+ }
}
- } else if (abs(item[groupitem[groupdepth]].minwid) > l) {
- /* fill */
- n = item[groupitem[groupdepth]].minwid;
- if (n < 0) {
- /* fill by appending characters */
- n = 0 - n;
- while (l++ < n && p + 1 < out + outlen)
- *p++ = fillchar;
+ // If the group is shorter than the minimum width, add padding characters.
+ } else if (abs(item[groupitem[groupdepth]].minwid) > group_len) {
+ long min_group_width = item[groupitem[groupdepth]].minwid;
+ // If the group is left-aligned, add characters to the right.
+ if (min_group_width < 0) {
+ min_group_width = 0 - min_group_width;
+ while (group_len++ < min_group_width && out_p < out_end_p)
+ *out_p++ = fillchar;
+ // If the group is right-aligned, shift everything to the right and
+ // prepend with filler characters.
} else {
- /* fill by inserting characters */
- memmove(t + n - l, t, (size_t)(p - t));
- l = n - l;
- if (p + l >= out + outlen)
- l = (long)((out + outlen) - p - 1);
- p += l;
- for (n = groupitem[groupdepth] + 1; n < curitem; n++)
- item[n].start += l;
- for (; l > 0; l--)
+ // { Move the group to the right
+ memmove(t + min_group_width - group_len, t, (size_t)(out_p - t));
+ group_len = min_group_width - group_len;
+ if (out_p + group_len >= (out_end_p + 1)) {
+ group_len = (long)(out_end_p - out_p);
+ }
+ out_p += group_len;
+ // }
+
+ // Adjust item start positions
+ for (int n = groupitem[groupdepth] + 1; n < curitem; n++) {
+ item[n].start += group_len;
+ }
+
+ // Prepend the fill characters
+ for (; group_len > 0; group_len--) {
*t++ = fillchar;
+ }
}
}
continue;
}
- minwid = 0;
- maxwid = 9999;
- zeropad = FALSE;
- l = 1;
- if (*s == '0') {
- s++;
- zeropad = TRUE;
+ int minwid = 0;
+ int maxwid = 9999;
+ bool left_align = false;
+
+ // Denotes that numbers should be left-padded with zeros
+ bool zeropad = (*fmt_p == '0');
+ if (zeropad) {
+ fmt_p++;
}
- if (*s == '-') {
- s++;
- l = -1;
+
+ // Denotes that the item should be left-aligned.
+ // This is tracked by using a negative length.
+ if (*fmt_p == '-') {
+ fmt_p++;
+ left_align = true;
}
- if (ascii_isdigit(*s)) {
- minwid = getdigits_int(&s);
+
+ // The first digit group is the item's min width
+ if (ascii_isdigit(*fmt_p)) {
+ minwid = getdigits_int(&fmt_p);
if (minwid < 0) /* overflow */
minwid = 0;
}
- if (*s == STL_USER_HL) {
+
+ // User highlight groups override the min width field
+ // to denote the styling to use.
+ if (*fmt_p == STL_USER_HL) {
item[curitem].type = Highlight;
- item[curitem].start = p;
+ item[curitem].start = out_p;
item[curitem].minwid = minwid > 9 ? 1 : minwid;
- s++;
+ fmt_p++;
curitem++;
continue;
}
- if (*s == STL_TABPAGENR || *s == STL_TABCLOSENR) {
- if (*s == STL_TABCLOSENR) {
+
+ // TABPAGE pairs are used to denote a region that when clicked will
+ // either switch to or close a tab.
+ //
+ // Ex: tabline=%0Ttab\ zero%X
+ // This tabline has a TABPAGENR item with minwid `0`,
+ // which is then closed with a TABCLOSENR item.
+ // Clicking on this region with mouse enabled will switch to tab 0.
+ // Setting the minwid to a different value will switch
+ // to that tab, if it exists
+ //
+ // Ex: tabline=%1Xtab\ one%X
+ // This tabline has a TABCLOSENR item with minwid `1`,
+ // which is then closed with a TABCLOSENR item.
+ // Clicking on this region with mouse enabled will close tab 0.
+ // This is determined by the following formula:
+ // tab to close = (1 - minwid)
+ // This is because for TABPAGENR we use `minwid` = `tab number`.
+ // For TABCLOSENR we store the tab number as a negative value.
+ // Because 0 is a valid TABPAGENR value, we have to
+ // start our numbering at `-1`.
+ // So, `-1` corresponds to us wanting to close tab `0`
+ //
+ // Note: These options are only valid when creating a tabline.
+ if (*fmt_p == STL_TABPAGENR || *fmt_p == STL_TABCLOSENR) {
+ if (*fmt_p == STL_TABCLOSENR) {
if (minwid == 0) {
/* %X ends the close label, go back to the previously
* define tab label nr. */
- for (n = curitem - 1; n >= 0; --n)
+ for (long n = curitem - 1; n >= 0; --n)
if (item[n].type == TabPage && item[n].minwid >= 0) {
minwid = item[n].minwid;
break;
@@ -3052,54 +3158,70 @@ build_stl_str_hl (
minwid = -minwid;
}
item[curitem].type = TabPage;
- item[curitem].start = p;
+ item[curitem].start = out_p;
item[curitem].minwid = minwid;
- s++;
+ fmt_p++;
curitem++;
continue;
}
- if (*s == '.') {
- s++;
- if (ascii_isdigit(*s)) {
- maxwid = getdigits_int(&s);
+
+ // Denotes the end of the minwid
+ // the maxwid may follow immediately after
+ if (*fmt_p == '.') {
+ fmt_p++;
+ if (ascii_isdigit(*fmt_p)) {
+ maxwid = getdigits_int(&fmt_p);
if (maxwid <= 0) /* overflow */
maxwid = 50;
}
}
- minwid = (minwid > 50 ? 50 : minwid) * l;
- if (*s == '(') {
+
+ // Bound the minimum width at 50.
+ // Make the number negative to denote left alignment of the item
+ minwid = (minwid > 50 ? 50 : minwid) * (left_align ? -1 : 1);
+
+ // Denotes the start of a new group
+ if (*fmt_p == '(') {
groupitem[groupdepth++] = curitem;
item[curitem].type = Group;
- item[curitem].start = p;
+ item[curitem].start = out_p;
item[curitem].minwid = minwid;
item[curitem].maxwid = maxwid;
- s++;
+ fmt_p++;
curitem++;
continue;
}
- if (vim_strchr(STL_ALL, *s) == NULL) {
- s++;
+
+ // An invalid item was specified.
+ // Continue processing on the next character of the format string.
+ if (vim_strchr(STL_ALL, *fmt_p) == NULL) {
+ fmt_p++;
continue;
}
- opt = *s++;
-
- /* OK - now for the real work */
- base = 'D';
- itemisflag = FALSE;
- fillable = TRUE;
- num = -1;
- str = NULL;
+
+ // The status line item type
+ char_u opt = *fmt_p++;
+
+ // OK - now for the real work
+ NumberBase base = kNumBaseDecimal;
+ bool itemisflag = false;
+ bool fillable = true;
+ long num = -1;
+ char_u *str = NULL;
switch (opt) {
case STL_FILEPATH:
case STL_FULLPATH:
case STL_FILENAME:
- fillable = FALSE; /* don't change ' ' to fillchar */
- if (buf_spname(wp->w_buffer) != NULL)
+ {
+ // Set fillable to false to that ' ' in the filename will not
+ // get replaced with the fillchar
+ fillable = false;
+ if (buf_spname(wp->w_buffer) != NULL) {
STRLCPY(NameBuff, buf_spname(wp->w_buffer), MAXPATHL);
- else {
- t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname
- : wp->w_buffer->b_fname;
- home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, TRUE);
+ } else {
+ char_u *t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname
+ : wp->w_buffer->b_fname;
+ home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, true);
}
trans_characters(NameBuff, MAXPATHL);
if (opt != STL_FILENAME)
@@ -3107,42 +3229,59 @@ build_stl_str_hl (
else
str = path_tail(NameBuff);
break;
-
+ }
case STL_VIM_EXPR: /* '{' */
- itemisflag = TRUE;
- t = p;
- while (*s != '}' && *s != NUL && p + 1 < out + outlen)
- *p++ = *s++;
- if (*s != '}') /* missing '}' or out of space */
+ {
+ itemisflag = true;
+
+ // Attempt to copy the expression to evaluate into
+ // the output buffer as a null-terminated string.
+ char_u *t = out_p;
+ while (*fmt_p != '}' && *fmt_p != NUL && out_p < out_end_p)
+ *out_p++ = *fmt_p++;
+ if (*fmt_p != '}') /* missing '}' or out of space */
break;
- s++;
- *p = 0;
- p = t;
+ fmt_p++;
+ *out_p = 0;
+ // Move our position in the output buffer
+ // to the beginning of the expression
+ out_p = t;
+
+ // { Evaluate the expression
+
+ // Store the current buffer number as a string variable
vim_snprintf((char *)tmp, sizeof(tmp), "%d", curbuf->b_fnum);
set_internal_string_var((char_u *)"actual_curbuf", tmp);
- o_curbuf = curbuf;
- o_curwin = curwin;
+ buf_T *o_curbuf = curbuf;
+ win_T *o_curwin = curwin;
curwin = wp;
curbuf = wp->w_buffer;
- str = eval_to_string_safe(p, &t, use_sandbox);
+ // Note: The result stored in `t` is unused.
+ str = eval_to_string_safe(out_p, &t, use_sandbox);
curwin = o_curwin;
curbuf = o_curbuf;
- do_unlet((char_u *)"g:actual_curbuf", TRUE);
+ // Remove the variable we just stored
+ do_unlet((char_u *)"g:actual_curbuf", true);
+
+ // }
+
+ // Check if the evaluated result is a number.
+ // If so, convert the number to an int and free the string.
if (str != NULL && *str != 0) {
if (*skipdigits(str) == NUL) {
num = atoi((char *)str);
xfree(str);
str = NULL;
- itemisflag = FALSE;
+ itemisflag = false;
}
}
break;
-
+ }
case STL_LINE:
num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY)
? 0L : (long)(wp->w_cursor.lnum);
@@ -3159,21 +3298,23 @@ build_stl_str_hl (
case STL_VIRTCOL:
case STL_VIRTCOL_ALT:
- /* In list mode virtcol needs to be recomputed */
- virtcol = wp->w_virtcol;
+ {
+ // In list mode virtcol needs to be recomputed
+ colnr_T virtcol = wp->w_virtcol;
if (wp->w_p_list && lcs_tab1 == NUL) {
wp->w_p_list = FALSE;
getvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL);
wp->w_p_list = TRUE;
}
++virtcol;
- /* Don't display %V if it's the same as %c. */
+ // Don't display %V if it's the same as %c.
if (opt == STL_VIRTCOL_ALT
&& (virtcol == (colnr_T)(!(State & INSERT) && empty_line
? 0 : (int)wp->w_cursor.col + 1)))
break;
num = (long)virtcol;
break;
+ }
case STL_PERCENTAGE:
num = (int)(((long)wp->w_cursor.lnum * 100L) /
@@ -3181,19 +3322,31 @@ build_stl_str_hl (
break;
case STL_ALTPERCENT:
+ // Store the position percentage in our temporary buffer.
+ // Note: We cannot store the value in `num` because
+ // `get_rel_pos` can return a named position. Ex: "Top"
+ get_rel_pos(wp, tmp, TMPLEN);
str = tmp;
- get_rel_pos(wp, str, TMPLEN);
break;
case STL_ARGLISTSTAT:
- fillable = FALSE;
+ fillable = false;
+
+ // Note: This is important because `append_arg_number` starts appending
+ // at the end of the null-terminated string.
+ // Setting the first byte to null means it will place the argument
+ // number string at the beginning of the buffer.
tmp[0] = 0;
- if (append_arg_number(wp, tmp, (int)sizeof(tmp), FALSE))
+
+ // Note: The call will only return true if it actually
+ // appended data to the `tmp` buffer.
+ if (append_arg_number(wp, tmp, (int)sizeof(tmp), false)) {
str = tmp;
+ }
break;
case STL_KEYMAP:
- fillable = FALSE;
+ fillable = false;
if (get_keymap_str(wp, tmp, TMPLEN))
str = tmp;
break;
@@ -3206,16 +3359,17 @@ build_stl_str_hl (
break;
case STL_OFFSET_X:
- base = 'X';
+ base = kNumBaseHexadecimal;
case STL_OFFSET:
- l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL);
+ {
+ long l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL);
num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 ?
0L : l + 1 + (!(State & INSERT) && empty_line ?
0 : (int)wp->w_cursor.col);
break;
-
+ }
case STL_BYTEVAL_X:
- base = 'X';
+ base = kNumBaseHexadecimal;
case STL_BYTEVAL:
num = byteval;
if (num == NL)
@@ -3226,20 +3380,23 @@ build_stl_str_hl (
case STL_ROFLAG:
case STL_ROFLAG_ALT:
- itemisflag = TRUE;
+ itemisflag = true;
if (wp->w_buffer->b_p_ro)
str = (char_u *)((opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"));
break;
case STL_HELPFLAG:
case STL_HELPFLAG_ALT:
- itemisflag = TRUE;
+ itemisflag = true;
if (wp->w_buffer->b_help)
str = (char_u *)((opt == STL_HELPFLAG_ALT) ? ",HLP"
: _("[Help]"));
break;
case STL_FILETYPE:
+ // Copy the filetype if it is not null and the formatted string will fit
+ // in the temporary buffer
+ // (including the brackets and null terminating character)
if (*wp->w_buffer->b_p_ft != NUL
&& STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3) {
vim_snprintf((char *)tmp, sizeof(tmp), "[%s]",
@@ -3249,20 +3406,26 @@ build_stl_str_hl (
break;
case STL_FILETYPE_ALT:
- itemisflag = TRUE;
+ {
+ itemisflag = true;
+ // Copy the filetype if it is not null and the formatted string will fit
+ // in the temporary buffer
+ // (including the comma and null terminating character)
if (*wp->w_buffer->b_p_ft != NUL
&& STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2) {
vim_snprintf((char *)tmp, sizeof(tmp), ",%s",
wp->w_buffer->b_p_ft);
- for (t = tmp; *t != 0; t++)
+ // Uppercase the file extension
+ for (char_u *t = tmp; *t != 0; t++) {
*t = TOUPPER_LOC(*t);
+ }
str = tmp;
}
break;
-
+ }
case STL_PREVIEWFLAG:
case STL_PREVIEWFLAG_ALT:
- itemisflag = TRUE;
+ itemisflag = true;
if (wp->w_p_pvw)
str = (char_u *)((opt == STL_PREVIEWFLAG_ALT) ? ",PRV"
: _("[Preview]"));
@@ -3277,7 +3440,7 @@ build_stl_str_hl (
case STL_MODIFIED:
case STL_MODIFIED_ALT:
- itemisflag = TRUE;
+ itemisflag = true;
switch ((opt == STL_MODIFIED_ALT)
+ bufIsChanged(wp->w_buffer) * 2
+ (!MODIFIABLE(wp->w_buffer)) * 4) {
@@ -3291,212 +3454,362 @@ build_stl_str_hl (
break;
case STL_HIGHLIGHT:
- t = s;
- while (*s != '#' && *s != NUL)
- ++s;
- if (*s == '#') {
+ {
+ // { The name of the highlight is surrounded by `#`
+ char_u *t = fmt_p;
+ while (*fmt_p != '#' && *fmt_p != NUL) {
+ ++fmt_p;
+ }
+ // }
+
+ // Create a highlight item based on the name
+ if (*fmt_p == '#') {
item[curitem].type = Highlight;
- item[curitem].start = p;
- item[curitem].minwid = -syn_namen2id(t, (int)(s - t));
+ item[curitem].start = out_p;
+ item[curitem].minwid = -syn_namen2id(t, (int)(fmt_p - t));
curitem++;
+ fmt_p++;
}
- if (*s != NUL)
- ++s;
continue;
}
+ }
- item[curitem].start = p;
+ // If we made it this far, the item is normal and starts at
+ // our current position in the output buffer.
+ // Non-normal items would have `continued`.
+ item[curitem].start = out_p;
item[curitem].type = Normal;
+
+ // Copy the item string into the output buffer
if (str != NULL && *str) {
- t = str;
+ // { Skip the leading `,` or ` ` if the item is a flag
+ // and the proper conditions are met
+ char_u *t = str;
if (itemisflag) {
if ((t[0] && t[1])
&& ((!prevchar_isitem && *t == ',')
|| (prevchar_isflag && *t == ' ')))
t++;
- prevchar_isflag = TRUE;
+ prevchar_isflag = true;
}
- l = vim_strsize(t);
- if (l > 0)
- prevchar_isitem = TRUE;
+ // }
+
+ long l = vim_strsize(t);
+
+ // If this item is non-empty, record that the last thing
+ // we put in the output buffer was an item
+ if (l > 0) {
+ prevchar_isitem = true;
+ }
+
+ // If the item is too wide, truncate it from the beginning
if (l > maxwid) {
while (l >= maxwid)
if (has_mbyte) {
l -= ptr2cells(t);
t += (*mb_ptr2len)(t);
- } else
+ } else {
l -= byte2cells(*t++);
- if (p + 1 >= out + outlen)
+ }
+
+ // Early out if there isn't enough room for the truncation marker
+ if (out_p >= out_end_p)
break;
- *p++ = '<';
+
+ // Add the truncation marker
+ *out_p++ = '<';
}
+
+ // If the item is right aligned and not wide enough,
+ // pad with fill characters.
if (minwid > 0) {
- for (; l < minwid && p + 1 < out + outlen; l++) {
- /* Don't put a "-" in front of a digit. */
+ for (; l < minwid && out_p < out_end_p; l++) {
+ // Don't put a "-" in front of a digit.
if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t))
- *p++ = ' ';
+ *out_p++ = ' ';
else
- *p++ = fillchar;
+ *out_p++ = fillchar;
}
minwid = 0;
- } else
+ } else {
+ // Note: The negative value denotes a left aligned item.
+ // Here we switch the minimum width back to a positive value.
minwid *= -1;
- while (*t && p + 1 < out + outlen) {
- *p++ = *t++;
- /* Change a space by fillchar, unless fillchar is '-' and a
- * digit follows. */
- if (fillable && p[-1] == ' '
+ }
+
+ // { Copy the string text into the output buffer
+ while (*t && out_p < out_end_p) {
+ *out_p++ = *t++;
+ // Change a space by fillchar, unless fillchar is '-' and a
+ // digit follows.
+ if (fillable && out_p[-1] == ' '
&& (!ascii_isdigit(*t) || fillchar != '-'))
- p[-1] = fillchar;
+ out_p[-1] = fillchar;
}
- for (; l < minwid && p + 1 < out + outlen; l++)
- *p++ = fillchar;
- } else if (num >= 0) {
- int nbase = (base == 'D' ? 10 : (base == 'O' ? 8 : 16));
- char_u nstr[20];
+ // }
- if (p + 20 >= out + outlen)
+ // For left-aligned items, fill any remaining space with the fillchar
+ for (; l < minwid && out_p < out_end_p; l++) {
+ *out_p++ = fillchar;
+ }
+
+ // Otherwise if the item is a number, copy that to the output buffer.
+ } else if (num >= 0) {
+ if (out_p + 20 > out_end_p)
break; /* not sufficient space */
- prevchar_isitem = TRUE;
- t = nstr;
+ prevchar_isitem = true;
+
+ // { Build the formatting string
+ char_u nstr[20];
+ char_u *t = nstr;
if (opt == STL_VIRTCOL_ALT) {
*t++ = '-';
minwid--;
}
*t++ = '%';
- if (zeropad)
+ if (zeropad) {
*t++ = '0';
+ }
+
+ // Note: The `*` means we take the width as one of the arguments
*t++ = '*';
- *t++ = nbase == 16 ? base : (char_u)(nbase == 8 ? 'o' : 'd');
+ *t++ = (char_u) (base == kNumBaseHexadecimal ? 'X'
+ : (base == kNumBaseOctal ? 'o'
+ : 'd'));
*t = 0;
+ // }
+
+ // { Determine how many characters the number will take up when printed
+ // Note: We have to cast the base because the compiler uses
+ // unsigned ints for the enum values.
+ long num_chars = 1;
+ for (long n = num; n >= (int) base; n /= (int) base) {
+ num_chars++;
+ }
- for (n = num, l = 1; n >= nbase; n /= nbase)
- l++;
- if (opt == STL_VIRTCOL_ALT)
- l++;
- if (l > maxwid) {
- l += 2;
- n = l - maxwid;
- while (l-- > maxwid)
- num /= nbase;
+ // VIRTCOL_ALT takes up an extra character because
+ // of the `-` we added above.
+ if (opt == STL_VIRTCOL_ALT) {
+ num_chars++;
+ }
+ // }
+
+ size_t remaining_buf_len = (out_end_p - out_p) + 1;
+
+ // If the number is going to take up too much room
+ // Figure out the approximate number in "scientific" type notation.
+ // Ex: 14532 with maxwid of 4 -> '14>3'
+ if (num_chars > maxwid) {
+ // Add two to the width because the power piece will take
+ // two extra characters
+ num_chars += 2;
+
+ // How many extra characters there are
+ long n = num_chars - maxwid;
+
+ // { Reduce the number by base^n
+ while (num_chars-- > maxwid) {
+ num /= base;
+ }
+ // }
+
+ // { Add the format string for the exponent bit
*t++ = '>';
*t++ = '%';
+ // Use the same base as the first number
*t = t[-3];
*++t = 0;
- vim_snprintf((char *)p, outlen - (p - out), (char *)nstr,
+ // }
+
+ vim_snprintf((char *)out_p, remaining_buf_len, (char *)nstr,
0, num, n);
- } else
- vim_snprintf((char *)p, outlen - (p - out), (char *)nstr,
+ } else {
+ vim_snprintf((char *)out_p, remaining_buf_len, (char *)nstr,
minwid, num);
- p += STRLEN(p);
- } else
+ }
+
+ // Advance the output buffer position to the end of the
+ // number we just printed
+ out_p += STRLEN(out_p);
+
+ // Otherwise, there was nothing to print so mark the item as empty
+ } else {
item[curitem].type = Empty;
+ }
- if (opt == STL_VIM_EXPR)
+ // Only free the string buffer if we allocated it.
+ // Note: This is not needed if `str` is pointing at `tmp`
+ if (opt == STL_VIM_EXPR) {
xfree(str);
+ }
if (num >= 0 || (!itemisflag && str && *str))
- prevchar_isflag = FALSE; /* Item not NULL, but not a flag */
+ prevchar_isflag = false; /* Item not NULL, but not a flag */
+
+ // Item processed, move to the next
curitem++;
}
- *p = NUL;
- itemcnt = curitem;
- if (usefmt != fmt)
+ *out_p = NUL;
+ int itemcnt = curitem;
+
+ // Free the format buffer if we allocated it internally
+ if (usefmt != fmt) {
xfree(usefmt);
+ }
+
+ // We have now processed the entire statusline format string.
+ // What follows is post-processing to handle alignment and
+ // highlighting factors.
- width = vim_strsize(out);
+ int width = vim_strsize(out);
if (maxwidth > 0 && width > maxwidth) {
- /* Result is too long, must truncate somewhere. */
- l = 0;
- if (itemcnt == 0)
- s = out;
- else {
- for (; l < itemcnt; l++)
- if (item[l].type == Trunc) {
- /* Truncate at %< item. */
- s = item[l].start;
+ // Result is too long, must truncate somewhere.
+ int item_idx = 0;
+ char_u *trunc_p;
+
+ // If there are no items, truncate from beginning
+ if (itemcnt == 0) {
+ trunc_p = out;
+
+ // Otherwise, look for the truncation item
+ } else {
+ // Default to truncating at the first item
+ trunc_p = item[0].start;
+ item_idx = 0;
+
+ for (int i = 0; i < itemcnt; i++)
+ if (item[i].type == Trunc) {
+ // Truncate at %< item.
+ trunc_p = item[i].start;
+ item_idx = i;
break;
}
- if (l == itemcnt) {
- /* No %< item, truncate first item. */
- s = item[0].start;
- l = 0;
- }
}
- if (width - vim_strsize(s) >= maxwidth) {
- /* Truncation mark is beyond max length */
+ // If the truncation point we found is beyond the maximum
+ // length of the string, truncate the end of the string.
+ if (width - vim_strsize(trunc_p) >= maxwidth) {
+ // If we are using a multi-byte encoding, walk from the beginning of the
+ // string to find the last character that will fit.
if (has_mbyte) {
- s = out;
+ trunc_p = out;
width = 0;
for (;; ) {
- width += ptr2cells(s);
+ width += ptr2cells(trunc_p);
if (width >= maxwidth)
break;
- s += (*mb_ptr2len)(s);
+
+ // Note: Only advance the pointer if the next
+ // character will fit in the available output space
+ trunc_p += (*mb_ptr2len)(trunc_p);
}
- /* Fill up for half a double-wide character. */
- while (++width < maxwidth)
- *s++ = fillchar;
- } else
- s = out + maxwidth - 1;
- for (l = 0; l < itemcnt; l++)
- if (item[l].start > s)
+
+ // Otherwise put the truncation point at the end, leaving enough room
+ // for a single-character truncation marker
+ } else {
+ trunc_p = out + maxwidth - 1;
+ }
+
+ // Ignore any items in the statusline that occur after
+ // the truncation point
+ for (int i = 0; i < itemcnt; i++) {
+ if (item[i].start > trunc_p) {
+ itemcnt = i;
break;
- itemcnt = l;
- *s++ = '>';
- *s = 0;
+ }
+ }
+
+ // Truncate the output
+ *trunc_p++ = '>';
+ *trunc_p = 0;
+
+ // Truncate at the truncation point we found
} else {
+ // { Determine how many bytes to remove
+ long trunc_len;
if (has_mbyte) {
- n = 0;
+ trunc_len = 0;
while (width >= maxwidth) {
- width -= ptr2cells(s + n);
- n += (*mb_ptr2len)(s + n);
+ width -= ptr2cells(trunc_p + trunc_len);
+ trunc_len += (*mb_ptr2len)(trunc_p + trunc_len);
}
- } else
- n = width - maxwidth + 1;
- p = s + n;
- STRMOVE(s + 1, p);
- *s = '<';
+ } else {
+ // Truncate an extra character so we can insert our `<`.
+ trunc_len = (width - maxwidth) + 1;
+ }
+ // }
- /* Fill up for half a double-wide character. */
- while (++width < maxwidth) {
- s = s + STRLEN(s);
- *s++ = fillchar;
- *s = NUL;
+ // { Truncate the string
+ char_u *trunc_end_p = trunc_p + trunc_len;
+ STRMOVE(trunc_p + 1, trunc_end_p);
+
+ // Put a `<` to mark where we truncated at
+ *trunc_p = '<';
+
+ if (width + 1 < maxwidth) {
+ // Advance the pointer to the end of the string
+ trunc_p = trunc_p + STRLEN(trunc_p);
}
- --n; /* count the '<' */
- for (; l < itemcnt; l++) {
- if (item[l].start - n >= s)
- item[l].start -= n;
- else
- item[l].start = s;
+ // Fill up for half a double-wide character.
+ while (++width < maxwidth) {
+ *trunc_p++ = fillchar;
+ *trunc_p = NUL;
}
+ // }
+
+ // { Change the start point for items based on
+ // their position relative to our truncation point
+
+ // Note: The offset is one less than the truncation length because
+ // the truncation marker `<` is not counted.
+ long item_offset = trunc_len - 1;
+
+ for (int i = item_idx; i < itemcnt; i++) {
+ // Items starting at or after the end of the truncated section need
+ // to be moved backwards.
+ if (item[i].start >= trunc_end_p) {
+ item[i].start -= item_offset;
+ // Anything inside the truncated area is set to start
+ // at the `<` truncation character.
+ } else {
+ item[i].start = trunc_p;
+ }
+ }
+ // }
}
width = maxwidth;
- } else if (width < maxwidth && STRLEN(out) + maxwidth - width + 1 <
- outlen) {
- /* Apply STL_MIDDLE if any */
- for (l = 0; l < itemcnt; l++)
- if (item[l].type == Middle)
+
+ // If there is room left in our statusline, and room left in our buffer,
+ // add characters at the middle marker (if there is one) to
+ // fill up the available space.
+ } else if (width < maxwidth
+ && STRLEN(out) + maxwidth - width + 1 < outlen) {
+ for (int item_idx = 0; item_idx < itemcnt; item_idx++) {
+ if (item[item_idx].type == Middle) {
+ // Move the statusline to make room for the middle characters
+ char_u *middle_end = item[item_idx].start + (maxwidth - width);
+ STRMOVE(middle_end, item[item_idx].start);
+
+ // Fill the middle section with our fill character
+ for (char_u *s = item[item_idx].start; s < middle_end; s++)
+ *s = fillchar;
+
+ // Adjust the offset of any items after the middle
+ for (item_idx++; item_idx < itemcnt; item_idx++)
+ item[item_idx].start += maxwidth - width;
+
+ width = maxwidth;
break;
- if (l < itemcnt) {
- p = item[l].start + maxwidth - width;
- STRMOVE(p, item[l].start);
- for (s = item[l].start; s < p; s++)
- *s = fillchar;
- for (l++; l < itemcnt; l++)
- item[l].start += maxwidth - width;
- width = maxwidth;
+ }
}
}
- /* Store the info about highlighting. */
+ // Store the info about highlighting.
if (hltab != NULL) {
- sp = hltab;
- for (l = 0; l < itemcnt; l++) {
+ struct stl_hlrec *sp = hltab;
+ for (long l = 0; l < itemcnt; l++) {
if (item[l].type == Highlight) {
sp->start = item[l].start;
sp->userhl = item[l].minwid;
@@ -3507,10 +3820,10 @@ build_stl_str_hl (
sp->userhl = 0;
}
- /* Store the info about tab pages labels. */
+ // Store the info about tab pages labels.
if (tabtab != NULL) {
- sp = tabtab;
- for (l = 0; l < itemcnt; l++) {
+ struct stl_hlrec *sp = tabtab;
+ for (long l = 0; l < itemcnt; l++) {
if (item[l].type == TabPage) {
sp->start = item[l].start;
sp->userhl = item[l].minwid;
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 3eabb7ee43..6b5bbe3b00 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -612,6 +612,7 @@ struct file_buffer {
char_u *b_p_cfu; /* 'completefunc' */
char_u *b_p_ofu; /* 'omnifunc' */
int b_p_eol; /* 'endofline' */
+ int b_p_fixeol; /* 'fixendofline' */
int b_p_et; /* 'expandtab' */
int b_p_et_nobin; /* b_p_et saved for binary mode */
char_u *b_p_fenc; /* 'fileencoding' */
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 6e2b3056e4..d311588ab4 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -2,7 +2,6 @@
///
/// Code for diff'ing two, three or four buffers.
-#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 2d6dcf4f80..9ba5d96e16 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -1,17 +1,8 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* edit.c: functions for Insert mode
*/
#include <assert.h>
-#include <errno.h>
#include <string.h>
#include <inttypes.h>
#include <stdbool.h>
@@ -52,6 +43,7 @@
#include "nvim/search.h"
#include "nvim/spell.h"
#include "nvim/strings.h"
+#include "nvim/state.h"
#include "nvim/syntax.h"
#include "nvim/tag.h"
#include "nvim/ui.h"
@@ -192,6 +184,28 @@ static expand_T compl_xp;
static int compl_opt_refresh_always = FALSE;
+typedef struct insert_state {
+ VimState state;
+ cmdarg_T *ca;
+ int mincol;
+ int cmdchar;
+ int startln;
+ long count;
+ int c;
+ int lastc;
+ int i;
+ bool did_backspace; // previous char was backspace
+ bool line_is_white; // line is empty before insert
+ linenr_T old_topline; // topline before insertion
+ int old_topfill;
+ int inserted_space; // just inserted a space
+ int replaceState;
+ int did_restart_edit; // remember if insert mode was restarted
+ // after a ctrl+o
+ bool nomove;
+ uint8_t *ptr;
+} InsertState;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "edit.c.generated.h"
@@ -228,111 +242,48 @@ static int ins_need_undo; /* call u_save() before inserting a
static int did_add_space = FALSE; /* auto_format() added an extra space
under the cursor */
+static int dont_sync_undo = false; // CTRL-G U prevents syncing undo
+ // for the next left/right cursor
-/*
- * edit(): Start inserting text.
- *
- * "cmdchar" can be:
- * 'i' normal insert command
- * 'a' normal append command
- * 'R' replace command
- * 'r' "r<CR>" command: insert one <CR>. Note: count can be > 1, for redo,
- * but still only one <CR> is inserted. The <Esc> is not used for redo.
- * 'g' "gI" command.
- * 'V' "gR" command for Virtual Replace mode.
- * 'v' "gr" command for single character Virtual Replace mode.
- *
- * This function is not called recursively. For CTRL-O commands, it returns
- * and lets the caller handle the Normal-mode command.
- *
- * Return TRUE if a CTRL-O command caused the return (insert mode pending).
- */
-int
-edit (
- int cmdchar,
- int startln, /* if set, insert at start of line */
- long count
-)
-{
- if (curbuf->terminal) {
- if (ex_normal_busy) {
- // don't enter terminal mode from `ex_normal`, which can result in all
- // kinds of havoc(such as terminal mode recursiveness). Instead, set a
- // flag that allow us to force-set the value of `restart_edit` before
- // `ex_normal` returns
- restart_edit = 'i';
- force_restart_edit = true;
- } else {
- terminal_enter();
- }
- return false;
- }
+static linenr_T o_lnum = 0;
- int c = 0;
- char_u *ptr;
- int lastc;
- int mincol;
- static linenr_T o_lnum = 0;
- int i;
- int did_backspace = TRUE; /* previous char was backspace */
- int line_is_white = FALSE; /* line is empty before insert */
- linenr_T old_topline = 0; /* topline before insertion */
- int old_topfill = -1;
- int inserted_space = FALSE; /* just inserted a space */
- int replaceState = REPLACE;
- int nomove = FALSE; /* don't move cursor on return */
-
- /* Remember whether editing was restarted after CTRL-O. */
+static void insert_enter(InsertState *s)
+{
+ s->did_backspace = true;
+ s->old_topfill = -1;
+ s->replaceState = REPLACE;
+ // Remember whether editing was restarted after CTRL-O
did_restart_edit = restart_edit;
-
- /* sleep before redrawing, needed for "CTRL-O :" that results in an
- * error message */
- check_for_delay(TRUE);
-
+ // sleep before redrawing, needed for "CTRL-O :" that results in an
+ // error message
+ check_for_delay(true);
// set Insstart_orig to Insstart
update_Insstart_orig = true;
- // Don't allow inserting in the sandbox.
- if (sandbox != 0) {
- EMSG(_(e_sandbox));
- return FALSE;
- }
- /* Don't allow changes in the buffer while editing the cmdline. The
- * caller of getcmdline() may get confused. */
- if (textlock != 0) {
- EMSG(_(e_secure));
- return FALSE;
- }
-
- /* Don't allow recursive insert mode when busy with completion. */
- if (compl_started || compl_busy || pum_visible()) {
- EMSG(_(e_secure));
- return FALSE;
- }
- ins_compl_clear(); /* clear stuff for CTRL-X mode */
+ ins_compl_clear(); // clear stuff for CTRL-X mode
- /*
- * Trigger InsertEnter autocommands. Do not do this for "r<CR>" or "grx".
- */
- if (cmdchar != 'r' && cmdchar != 'v') {
+ // Trigger InsertEnter autocommands. Do not do this for "r<CR>" or "grx".
+ if (s->cmdchar != 'r' && s->cmdchar != 'v') {
pos_T save_cursor = curwin->w_cursor;
- if (cmdchar == 'R')
- ptr = (char_u *)"r";
- else if (cmdchar == 'V')
- ptr = (char_u *)"v";
- else
- ptr = (char_u *)"i";
- set_vim_var_string(VV_INSERTMODE, ptr, 1);
+ if (s->cmdchar == 'R') {
+ s->ptr = (char_u *)"r";
+ } else if (s->cmdchar == 'V') {
+ s->ptr = (char_u *)"v";
+ } else {
+ s->ptr = (char_u *)"i";
+ }
+
+ set_vim_var_string(VV_INSERTMODE, s->ptr, 1);
set_vim_var_string(VV_CHAR, NULL, -1); /* clear v:char */
- apply_autocmds(EVENT_INSERTENTER, NULL, NULL, FALSE, curbuf);
-
- /* Make sure the cursor didn't move. Do call check_cursor_col() in
- * case the text was modified. Since Insert mode was not started yet
- * a call to check_cursor_col() may move the cursor, especially with
- * the "A" command, thus set State to avoid that. Also check that the
- * line number is still valid (lines may have been deleted).
- * Do not restore if v:char was set to a non-empty string. */
+ apply_autocmds(EVENT_INSERTENTER, NULL, NULL, false, curbuf);
+
+ // Make sure the cursor didn't move. Do call check_cursor_col() in
+ // case the text was modified. Since Insert mode was not started yet
+ // a call to check_cursor_col() may move the cursor, especially with
+ // the "A" command, thus set State to avoid that. Also check that the
+ // line number is still valid (lines may have been deleted).
+ // Do not restore if v:char was set to a non-empty string.
if (!equalpos(curwin->w_cursor, save_cursor)
&& *get_vim_var_str(VV_CHAR) == NUL
&& save_cursor.lnum <= curbuf->b_ml.ml_line_count) {
@@ -345,899 +296,1041 @@ edit (
}
}
- /* Check if the cursor line needs redrawing before changing State. If
- * 'concealcursor' is "n" it needs to be redrawn without concealing. */
+ // Check if the cursor line needs redrawing before changing State. If
+ // 'concealcursor' is "n" it needs to be redrawn without concealing.
conceal_check_cursur_line();
- /*
- * When doing a paste with the middle mouse button, Insstart is set to
- * where the paste started.
- */
- if (where_paste_started.lnum != 0)
+ // When doing a paste with the middle mouse button, Insstart is set to
+ // where the paste started.
+ if (where_paste_started.lnum != 0) {
Insstart = where_paste_started;
- else {
+ } else {
Insstart = curwin->w_cursor;
- if (startln)
+ if (s->startln) {
Insstart.col = 0;
+ }
}
+
Insstart_textlen = (colnr_T)linetabsize(get_cursor_line_ptr());
Insstart_blank_vcol = MAXCOL;
- if (!did_ai)
+
+ if (!did_ai) {
ai_col = 0;
+ }
- if (cmdchar != NUL && restart_edit == 0) {
+ if (s->cmdchar != NUL && restart_edit == 0) {
ResetRedobuff();
- AppendNumberToRedobuff(count);
- if (cmdchar == 'V' || cmdchar == 'v') {
- /* "gR" or "gr" command */
+ AppendNumberToRedobuff(s->count);
+ if (s->cmdchar == 'V' || s->cmdchar == 'v') {
+ // "gR" or "gr" command
AppendCharToRedobuff('g');
- AppendCharToRedobuff((cmdchar == 'v') ? 'r' : 'R');
+ AppendCharToRedobuff((s->cmdchar == 'v') ? 'r' : 'R');
} else {
- AppendCharToRedobuff(cmdchar);
- if (cmdchar == 'g') /* "gI" command */
+ AppendCharToRedobuff(s->cmdchar);
+ if (s->cmdchar == 'g') { // "gI" command
AppendCharToRedobuff('I');
- else if (cmdchar == 'r') /* "r<CR>" command */
- count = 1; /* insert only one <CR> */
+ } else if (s->cmdchar == 'r') { // "r<CR>" command
+ s->count = 1; // insert only one <CR>
+ }
}
}
- if (cmdchar == 'R') {
+ if (s->cmdchar == 'R') {
if (p_fkmap && p_ri) {
beep_flush();
- EMSG(farsi_text_3); /* encoded in Farsi */
+ EMSG(farsi_text_3); // encoded in Farsi
State = INSERT;
- } else
+ } else {
State = REPLACE;
- } else if (cmdchar == 'V' || cmdchar == 'v') {
+ }
+ } else if (s->cmdchar == 'V' || s->cmdchar == 'v') {
State = VREPLACE;
- replaceState = VREPLACE;
+ s->replaceState = VREPLACE;
orig_line_count = curbuf->b_ml.ml_line_count;
vr_lines_changed = 1;
- } else
+ } else {
State = INSERT;
+ }
- stop_insert_mode = FALSE;
+ stop_insert_mode = false;
- /*
- * Need to recompute the cursor position, it might move when the cursor is
- * on a TAB or special character.
- */
- curs_columns(TRUE);
+ // Need to recompute the cursor position, it might move when the cursor is
+ // on a TAB or special character.
+ curs_columns(true);
- /*
- * Enable langmap or IME, indicated by 'iminsert'.
- * Note that IME may enabled/disabled without us noticing here, thus the
- * 'iminsert' value may not reflect what is actually used. It is updated
- * when hitting <Esc>.
- */
- if (curbuf->b_p_iminsert == B_IMODE_LMAP)
+ // Enable langmap or IME, indicated by 'iminsert'.
+ // Note that IME may enabled/disabled without us noticing here, thus the
+ // 'iminsert' value may not reflect what is actually used. It is updated
+ // when hitting <Esc>.
+ if (curbuf->b_p_iminsert == B_IMODE_LMAP) {
State |= LANGMAP;
+ }
setmouse();
clear_showcmd();
- /* there is no reverse replace mode */
+ // there is no reverse replace mode
revins_on = (State == INSERT && p_ri);
- if (revins_on)
+ if (revins_on) {
undisplay_dollar();
+ }
revins_chars = 0;
revins_legal = 0;
revins_scol = -1;
- /*
- * Handle restarting Insert mode.
- * Don't do this for "CTRL-O ." (repeat an insert): we get here with
- * restart_edit non-zero, and something in the stuff buffer.
- */
+ // Handle restarting Insert mode.
+ // Don't do this for "CTRL-O ." (repeat an insert): we get here with
+ // restart_edit non-zero, and something in the stuff buffer.
if (restart_edit != 0 && stuff_empty()) {
- /*
- * After a paste we consider text typed to be part of the insert for
- * the pasted text. You can backspace over the pasted text too.
- */
- if (where_paste_started.lnum)
- arrow_used = FALSE;
- else
- arrow_used = TRUE;
+ // After a paste we consider text typed to be part of the insert for
+ // the pasted text. You can backspace over the pasted text too.
+ if (where_paste_started.lnum) {
+ arrow_used = false;
+ } else {
+ arrow_used = true;
+ }
restart_edit = 0;
- /*
- * If the cursor was after the end-of-line before the CTRL-O and it is
- * now at the end-of-line, put it after the end-of-line (this is not
- * correct in very rare cases).
- * Also do this if curswant is greater than the current virtual
- * column. Eg after "^O$" or "^O80|".
- */
+ // If the cursor was after the end-of-line before the CTRL-O and it is
+ // now at the end-of-line, put it after the end-of-line (this is not
+ // correct in very rare cases).
+ // Also do this if curswant is greater than the current virtual
+ // column. Eg after "^O$" or "^O80|".
validate_virtcol();
update_curswant();
if (((ins_at_eol && curwin->w_cursor.lnum == o_lnum)
|| curwin->w_curswant > curwin->w_virtcol)
- && *(ptr = get_cursor_line_ptr() + curwin->w_cursor.col) != NUL) {
- if (ptr[1] == NUL)
+ && *(s->ptr = get_cursor_line_ptr() + curwin->w_cursor.col) != NUL) {
+ if (s->ptr[1] == NUL) {
++curwin->w_cursor.col;
- else if (has_mbyte) {
- i = (*mb_ptr2len)(ptr);
- if (ptr[i] == NUL)
- curwin->w_cursor.col += i;
+ } else if (has_mbyte) {
+ s->i = (*mb_ptr2len)(s->ptr);
+ if (s->ptr[s->i] == NUL) {
+ curwin->w_cursor.col += s->i;
+ }
}
}
- ins_at_eol = FALSE;
- } else
- arrow_used = FALSE;
+ ins_at_eol = false;
+ } else {
+ arrow_used = false;
+ }
- /* we are in insert mode now, don't need to start it anymore */
- need_start_insertmode = FALSE;
+ // we are in insert mode now, don't need to start it anymore
+ need_start_insertmode = false;
- /* Need to save the line for undo before inserting the first char. */
- ins_need_undo = TRUE;
+ // Need to save the line for undo before inserting the first char.
+ ins_need_undo = true;
where_paste_started.lnum = 0;
- can_cindent = TRUE;
- /* The cursor line is not in a closed fold, unless 'insertmode' is set or
- * restarting. */
- if (!p_im && did_restart_edit == 0)
+ can_cindent = true;
+ // The cursor line is not in a closed fold, unless 'insertmode' is set or
+ // restarting.
+ if (!p_im && did_restart_edit == 0) {
foldOpenCursor();
+ }
- /*
- * If 'showmode' is set, show the current (insert/replace/..) mode.
- * A warning message for changing a readonly file is given here, before
- * actually changing anything. It's put after the mode, if any.
- */
- i = 0;
- if (p_smd && msg_silent == 0)
- i = showmode();
+ // If 'showmode' is set, show the current (insert/replace/..) mode.
+ // A warning message for changing a readonly file is given here, before
+ // actually changing anything. It's put after the mode, if any.
+ s->i = 0;
+ if (p_smd && msg_silent == 0) {
+ s->i = showmode();
+ }
- if (!p_im && did_restart_edit == 0)
- change_warning(i == 0 ? 0 : i + 1);
+ if (!p_im && did_restart_edit == 0) {
+ change_warning(s->i == 0 ? 0 : s->i + 1);
+ }
ui_cursor_shape(); /* may show different cursor shape */
do_digraph(-1); /* clear digraphs */
- /*
- * Get the current length of the redo buffer, those characters have to be
- * skipped if we want to get to the inserted characters.
- */
- ptr = get_inserted();
- if (ptr == NULL)
+ // Get the current length of the redo buffer, those characters have to be
+ // skipped if we want to get to the inserted characters.
+ s->ptr = get_inserted();
+ if (s->ptr == NULL) {
new_insert_skip = 0;
- else {
- new_insert_skip = (int)STRLEN(ptr);
- xfree(ptr);
+ } else {
+ new_insert_skip = (int)STRLEN(s->ptr);
+ xfree(s->ptr);
}
old_indent = 0;
- /*
- * Main loop in Insert mode: repeat until Insert mode is left.
- */
- for (;; ) {
- if (!revins_legal)
- revins_scol = -1; /* reset on illegal motions */
- else
- revins_legal = 0;
- if (arrow_used) /* don't repeat insert when arrow key used */
- count = 0;
+ do {
+ state_enter(&s->state);
+ // If s->count != 0, `ins_esc` will prepare the redo buffer for reprocessing
+ // and return false, causing `state_enter` to be called again.
+ } while (!ins_esc(&s->count, s->cmdchar, s->nomove));
- if (update_Insstart_orig) {
- Insstart_orig = Insstart;
- }
+ // Always update o_lnum, so that a "CTRL-O ." that adds a line
+ // still puts the cursor back after the inserted text.
+ if (ins_at_eol && gchar_cursor() == NUL) {
+ o_lnum = curwin->w_cursor.lnum;
+ }
- if (stop_insert_mode) {
- /* ":stopinsert" used or 'insertmode' reset */
- count = 0;
- goto doESCkey;
- }
+ if (s->cmdchar != 'r' && s->cmdchar != 'v') {
+ apply_autocmds(EVENT_INSERTLEAVE, NULL, NULL, false, curbuf);
+ }
+ did_cursorhold = false;
+}
- /* set curwin->w_curswant for next K_DOWN or K_UP */
- if (!arrow_used)
- curwin->w_set_curswant = TRUE;
+static int insert_check(VimState *state)
+{
+ InsertState *s = (InsertState *)state;
+
+ // If typed something may trigger CursorHoldI again.
+ if (s->c != K_EVENT) {
+ did_cursorhold = false;
+ }
+
+ // If the cursor was moved we didn't just insert a space */
+ if (arrow_used) {
+ s->inserted_space = false;
+ }
+
+ if (can_cindent && cindent_on() && ctrl_x_mode == 0) {
+ insert_do_cindent(s);
+ }
+
+ if (!revins_legal) {
+ revins_scol = -1; // reset on illegal motions
+ } else {
+ revins_legal = 0;
+ }
+
+ if (arrow_used) { // don't repeat insert when arrow key used
+ s->count = 0;
+ }
+
+ if (update_Insstart_orig) {
+ Insstart_orig = Insstart;
+ }
+
+ if (stop_insert_mode) {
+ // ":stopinsert" used or 'insertmode' reset
+ s->count = 0;
+ return 0; // exit insert mode
+ }
- /* If there is no typeahead may check for timestamps (e.g., for when a
- * menu invoked a shell command). */
- if (stuff_empty()) {
- did_check_timestamps = FALSE;
- if (need_check_timestamps)
- check_timestamps(FALSE);
+ // set curwin->w_curswant for next K_DOWN or K_UP
+ if (!arrow_used) {
+ curwin->w_set_curswant = true;
+ }
+
+ // If there is no typeahead may check for timestamps (e.g., for when a
+ // menu invoked a shell command).
+ if (stuff_empty()) {
+ did_check_timestamps = false;
+ if (need_check_timestamps) {
+ check_timestamps(false);
}
+ }
- /*
- * When emsg() was called msg_scroll will have been set.
- */
- msg_scroll = FALSE;
+ // When emsg() was called msg_scroll will have been set.
+ msg_scroll = false;
- /* Open fold at the cursor line, according to 'foldopen'. */
- if (fdo_flags & FDO_INSERT)
- foldOpenCursor();
- /* Close folds where the cursor isn't, according to 'foldclose' */
- if (!char_avail())
- foldCheckClose();
+ // Open fold at the cursor line, according to 'foldopen'.
+ if (fdo_flags & FDO_INSERT) {
+ foldOpenCursor();
+ }
- /*
- * If we inserted a character at the last position of the last line in
- * the window, scroll the window one line up. This avoids an extra
- * redraw.
- * This is detected when the cursor column is smaller after inserting
- * something.
- * Don't do this when the topline changed already, it has
- * already been adjusted (by insertchar() calling open_line())).
- */
- if (curbuf->b_mod_set
- && curwin->w_p_wrap
- && !did_backspace
- && curwin->w_topline == old_topline
- && curwin->w_topfill == old_topfill
- ) {
- mincol = curwin->w_wcol;
- validate_cursor_col();
-
- if (curwin->w_wcol < mincol - curbuf->b_p_ts
- && curwin->w_wrow == curwin->w_winrow
- + curwin->w_height - 1 - p_so
- && (curwin->w_cursor.lnum != curwin->w_topline
- || curwin->w_topfill > 0
- )) {
- if (curwin->w_topfill > 0)
- --curwin->w_topfill;
- else if (hasFolding(curwin->w_topline, NULL, &old_topline))
- set_topline(curwin, old_topline + 1);
- else
- set_topline(curwin, curwin->w_topline + 1);
+ // Close folds where the cursor isn't, according to 'foldclose'
+ if (!char_avail()) {
+ foldCheckClose();
+ }
+
+ // If we inserted a character at the last position of the last line in the
+ // window, scroll the window one line up. This avoids an extra redraw. This
+ // is detected when the cursor column is smaller after inserting something.
+ // Don't do this when the topline changed already, it has already been
+ // adjusted (by insertchar() calling open_line())).
+ if (curbuf->b_mod_set
+ && curwin->w_p_wrap
+ && !s->did_backspace
+ && curwin->w_topline == s->old_topline
+ && curwin->w_topfill == s->old_topfill) {
+ s->mincol = curwin->w_wcol;
+ validate_cursor_col();
+
+ if (curwin->w_wcol < s->mincol - curbuf->b_p_ts
+ && curwin->w_wrow == curwin->w_winrow
+ + curwin->w_height - 1 - p_so
+ && (curwin->w_cursor.lnum != curwin->w_topline
+ || curwin->w_topfill > 0)) {
+ if (curwin->w_topfill > 0) {
+ --curwin->w_topfill;
+ } else if (hasFolding(curwin->w_topline, NULL, &s->old_topline)) {
+ set_topline(curwin, s->old_topline + 1);
+ } else {
+ set_topline(curwin, curwin->w_topline + 1);
}
}
+ }
- /* May need to adjust w_topline to show the cursor. */
- update_topline();
+ // May need to adjust w_topline to show the cursor.
+ update_topline();
- did_backspace = FALSE;
+ s->did_backspace = false;
- validate_cursor(); /* may set must_redraw */
+ validate_cursor(); // may set must_redraw
- /*
- * Redraw the display when no characters are waiting.
- * Also shows mode, ruler and positions cursor.
- */
- ins_redraw(TRUE);
+ // Redraw the display when no characters are waiting.
+ // Also shows mode, ruler and positions cursor.
+ ins_redraw(true);
- if (curwin->w_p_scb)
- do_check_scrollbind(TRUE);
+ if (curwin->w_p_scb) {
+ do_check_scrollbind(true);
+ }
- if (curwin->w_p_crb)
- do_check_cursorbind();
- update_curswant();
- old_topline = curwin->w_topline;
- old_topfill = curwin->w_topfill;
+ if (curwin->w_p_crb) {
+ do_check_cursorbind();
+ }
+ update_curswant();
+ s->old_topline = curwin->w_topline;
+ s->old_topfill = curwin->w_topfill;
+ s->lastc = s->c; // remember previous char for CTRL-D
- /*
- * Get a character for Insert mode. Ignore K_IGNORE.
- */
- lastc = c; /* remember previous char for CTRL-D */
- input_enable_events();
- do {
- c = safe_vgetc();
- } while (c == K_IGNORE);
- input_disable_events();
+ // After using CTRL-G U the next cursor key will not break undo.
+ if (dont_sync_undo == MAYBE) {
+ dont_sync_undo = true;
+ } else {
+ dont_sync_undo = false;
+ }
- if (c == K_EVENT) {
- c = lastc;
- queue_process_events(loop.events);
- continue;
- }
+ return 1;
+}
- /* Don't want K_CURSORHOLD for the second key, e.g., after CTRL-V. */
- did_cursorhold = TRUE;
+static int insert_execute(VimState *state, int key)
+{
+ if (key == K_IGNORE) {
+ return -1; // get another key
+ }
+ InsertState *s = (InsertState *)state;
+ s->c = key;
- if (p_hkmap && KeyTyped)
- c = hkmap(c); /* Hebrew mode mapping */
- if (p_fkmap && KeyTyped)
- c = fkmap(c); /* Farsi mode mapping */
+ // Don't want K_EVENT with cursorhold for the second key, e.g., after CTRL-V.
+ did_cursorhold = true;
- /*
- * Special handling of keys while the popup menu is visible or wanted
- * and the cursor is still in the completed word. Only when there is
- * a match, skip this when no matches were found.
- */
- if (compl_started
- && pum_wanted()
- && curwin->w_cursor.col >= compl_col
- && (compl_shown_match == NULL
- || compl_shown_match != compl_shown_match->cp_next)) {
- /* BS: Delete one character from "compl_leader". */
- if ((c == K_BS || c == Ctrl_H)
- && curwin->w_cursor.col > compl_col
- && (c = ins_compl_bs()) == NUL)
- continue;
-
- /* When no match was selected or it was edited. */
- if (!compl_used_match) {
- /* CTRL-L: Add one character from the current match to
- * "compl_leader". Except when at the original match and
- * there is nothing to add, CTRL-L works like CTRL-P then. */
- if (c == Ctrl_L
- && (!CTRL_X_MODE_LINE_OR_EVAL(ctrl_x_mode)
- || (int)STRLEN(compl_shown_match->cp_str)
- > curwin->w_cursor.col - compl_col)) {
- ins_compl_addfrommatch();
- continue;
- }
+ if (p_hkmap && KeyTyped) {
+ s->c = hkmap(s->c); // Hebrew mode mapping
+ }
- /* A non-white character that fits in with the current
- * completion: Add to "compl_leader". */
- if (ins_compl_accept_char(c)) {
- /* Trigger InsertCharPre. */
- char_u *str = do_insert_char_pre(c);
- char_u *p;
-
- if (str != NULL) {
- for (p = str; *p != NUL; mb_ptr_adv(p))
- ins_compl_addleader(PTR2CHAR(p));
- xfree(str);
- } else
- ins_compl_addleader(c);
- continue;
- }
+ if (p_fkmap && KeyTyped) {
+ s->c = fkmap(s->c); // Farsi mode mapping
+ }
- /* Pressing CTRL-Y selects the current match. When
- * compl_enter_selects is set the Enter key does the same. */
- if (c == Ctrl_Y || (compl_enter_selects
- && (c == CAR || c == K_KENTER || c == NL))) {
- ins_compl_delete();
- ins_compl_insert();
- }
- }
+ // Special handling of keys while the popup menu is visible or wanted
+ // and the cursor is still in the completed word. Only when there is
+ // a match, skip this when no matches were found.
+ if (compl_started
+ && pum_wanted()
+ && curwin->w_cursor.col >= compl_col
+ && (compl_shown_match == NULL
+ || compl_shown_match != compl_shown_match->cp_next)) {
+ // BS: Delete one character from "compl_leader".
+ if ((s->c == K_BS || s->c == Ctrl_H)
+ && curwin->w_cursor.col > compl_col
+ && (s->c = ins_compl_bs()) == NUL) {
+ return 1; // continue
}
- /* Prepare for or stop CTRL-X mode. This doesn't do completion, but
- * it does fix up the text when finishing completion. */
- compl_get_longest = FALSE;
- if (ins_compl_prep(c))
- continue;
+ // When no match was selected or it was edited.
+ if (!compl_used_match) {
+ // CTRL-L: Add one character from the current match to
+ // "compl_leader". Except when at the original match and
+ // there is nothing to add, CTRL-L works like CTRL-P then.
+ if (s->c == Ctrl_L
+ && (!CTRL_X_MODE_LINE_OR_EVAL(ctrl_x_mode)
+ || (int)STRLEN(compl_shown_match->cp_str)
+ > curwin->w_cursor.col - compl_col)) {
+ ins_compl_addfrommatch();
+ return 1; // continue
+ }
- /* CTRL-\ CTRL-N goes to Normal mode,
- * CTRL-\ CTRL-G goes to mode selected with 'insertmode',
- * CTRL-\ CTRL-O is like CTRL-O but without moving the cursor. */
- if (c == Ctrl_BSL) {
- /* may need to redraw when no more chars available now */
- ins_redraw(FALSE);
- ++no_mapping;
- ++allow_keys;
- c = plain_vgetc();
- --no_mapping;
- --allow_keys;
- if (c != Ctrl_N && c != Ctrl_G && c != Ctrl_O) {
- /* it's something else */
- vungetc(c);
- c = Ctrl_BSL;
- } else if (c == Ctrl_G && p_im)
- continue;
- else {
- if (c == Ctrl_O) {
- ins_ctrl_o();
- ins_at_eol = FALSE; /* cursor keeps its column */
- nomove = TRUE;
+ // A non-white character that fits in with the current
+ // completion: Add to "compl_leader".
+ if (ins_compl_accept_char(s->c)) {
+ // Trigger InsertCharPre.
+ char_u *str = do_insert_char_pre(s->c);
+ char_u *p;
+
+ if (str != NULL) {
+ for (p = str; *p != NUL; mb_ptr_adv(p)) {
+ ins_compl_addleader(PTR2CHAR(p));
+ }
+ xfree(str);
+ } else {
+ ins_compl_addleader(s->c);
}
- count = 0;
- goto doESCkey;
+ return 1; // continue
+ }
+
+ // Pressing CTRL-Y selects the current match. When
+ // compl_enter_selects is set the Enter key does the same.
+ if (s->c == Ctrl_Y
+ || (compl_enter_selects
+ && (s->c == CAR || s->c == K_KENTER || s->c == NL))) {
+ ins_compl_delete();
+ ins_compl_insert();
}
}
+ }
- c = do_digraph(c);
+ // Prepare for or stop CTRL-X mode. This doesn't do completion, but it does
+ // fix up the text when finishing completion.
+ compl_get_longest = false;
+ if (ins_compl_prep(s->c)) {
+ return 1; // continue
+ }
- if ((c == Ctrl_V || c == Ctrl_Q) && ctrl_x_mode == CTRL_X_CMDLINE)
- goto docomplete;
- if (c == Ctrl_V || c == Ctrl_Q) {
- ins_ctrl_v();
- c = Ctrl_V; /* pretend CTRL-V is last typed character */
- continue;
+ // CTRL-\ CTRL-N goes to Normal mode,
+ // CTRL-\ CTRL-G goes to mode selected with 'insertmode',
+ // CTRL-\ CTRL-O is like CTRL-O but without moving the cursor
+ if (s->c == Ctrl_BSL) {
+ // may need to redraw when no more chars available now
+ ins_redraw(false);
+ ++no_mapping;
+ ++allow_keys;
+ s->c = plain_vgetc();
+ --no_mapping;
+ --allow_keys;
+ if (s->c != Ctrl_N && s->c != Ctrl_G && s->c != Ctrl_O) {
+ // it's something else
+ vungetc(s->c);
+ s->c = Ctrl_BSL;
+ } else if (s->c == Ctrl_G && p_im) {
+ return 1; // continue
+ } else {
+ if (s->c == Ctrl_O) {
+ ins_ctrl_o();
+ ins_at_eol = false; // cursor keeps its column
+ s->nomove = true;
+ }
+ s->count = 0;
+ return 0;
}
+ }
- if (cindent_on()
- && ctrl_x_mode == 0
- ) {
- /* A key name preceded by a bang means this key is not to be
- * inserted. Skip ahead to the re-indenting below.
- * A key name preceded by a star means that indenting has to be
- * done before inserting the key. */
- line_is_white = inindent(0);
- if (in_cinkeys(c, '!', line_is_white))
- goto force_cindent;
- if (can_cindent && in_cinkeys(c, '*', line_is_white)
- && stop_arrow() == OK)
- do_c_expr_indent();
- }
+ s->c = do_digraph(s->c);
- if (curwin->w_p_rl)
- switch (c) {
- case K_LEFT: c = K_RIGHT; break;
- case K_S_LEFT: c = K_S_RIGHT; break;
- case K_C_LEFT: c = K_C_RIGHT; break;
- case K_RIGHT: c = K_LEFT; break;
- case K_S_RIGHT: c = K_S_LEFT; break;
- case K_C_RIGHT: c = K_C_LEFT; break;
- }
+ if ((s->c == Ctrl_V || s->c == Ctrl_Q) && ctrl_x_mode == CTRL_X_CMDLINE) {
+ insert_do_complete(s);
+ return 1;
+ }
- /*
- * If 'keymodel' contains "startsel", may start selection. If it
- * does, a CTRL-O and c will be stuffed, we need to get these
- * characters.
- */
- if (ins_start_select(c))
- continue;
+ if (s->c == Ctrl_V || s->c == Ctrl_Q) {
+ ins_ctrl_v();
+ s->c = Ctrl_V; // pretend CTRL-V is last typed character
+ return 1; // continue
+ }
- /*
- * The big switch to handle a character in insert mode.
- */
- switch (c) {
- case ESC: /* End input mode */
- if (echeck_abbr(ESC + ABBR_OFF))
- break;
- /*FALLTHROUGH*/
-
- case Ctrl_C: /* End input mode */
- if (c == Ctrl_C && cmdwin_type != 0) {
- /* Close the cmdline window. */
- cmdwin_result = K_IGNORE;
- got_int = FALSE; /* don't stop executing autocommands et al. */
- nomove = TRUE;
- goto doESCkey;
- }
+ if (cindent_on()
+ && ctrl_x_mode == 0) {
+ // A key name preceded by a bang means this key is not to be
+ // inserted. Skip ahead to the re-indenting below.
+ // A key name preceded by a star means that indenting has to be
+ // done before inserting the key.
+ s->line_is_white = inindent(0);
+ if (in_cinkeys(s->c, '!', s->line_is_white)) {
+ insert_do_cindent(s);
+ return 1; // continue
+ }
-#ifdef UNIX
-do_intr:
-#endif
- // when 'insertmode' set, and not halfway through a mapping, don't leave
- // Insert mode
- if (goto_im()) {
- if (got_int) {
- (void)vgetc(); /* flush all buffers */
- got_int = false;
- } else {
- vim_beep(BO_IM);
- }
- break;
- }
-doESCkey:
- /*
- * This is the ONLY return from edit()!
- */
- /* Always update o_lnum, so that a "CTRL-O ." that adds a line
- * still puts the cursor back after the inserted text. */
- if (ins_at_eol && gchar_cursor() == NUL)
- o_lnum = curwin->w_cursor.lnum;
-
- if (ins_esc(&count, cmdchar, nomove)) {
- if (cmdchar != 'r' && cmdchar != 'v')
- apply_autocmds(EVENT_INSERTLEAVE, NULL, NULL,
- FALSE, curbuf);
- did_cursorhold = FALSE;
- return c == Ctrl_O;
- }
- continue;
+ if (can_cindent && in_cinkeys(s->c, '*', s->line_is_white)
+ && stop_arrow() == OK) {
+ do_c_expr_indent();
+ }
+ }
- case Ctrl_Z: /* suspend when 'insertmode' set */
- if (!p_im)
- goto normalchar; /* insert CTRL-Z as normal char */
- stuffReadbuff((char_u *)":st\r");
- c = Ctrl_O;
- /*FALLTHROUGH*/
-
- case Ctrl_O: /* execute one command */
- if (ctrl_x_mode == CTRL_X_OMNI)
- goto docomplete;
- if (echeck_abbr(Ctrl_O + ABBR_OFF))
- break;
- ins_ctrl_o();
+ if (curwin->w_p_rl)
+ switch (s->c) {
+ case K_LEFT: s->c = K_RIGHT; break;
+ case K_S_LEFT: s->c = K_S_RIGHT; break;
+ case K_C_LEFT: s->c = K_C_RIGHT; break;
+ case K_RIGHT: s->c = K_LEFT; break;
+ case K_S_RIGHT: s->c = K_S_LEFT; break;
+ case K_C_RIGHT: s->c = K_C_LEFT; break;
+ }
- /* don't move the cursor left when 'virtualedit' has "onemore". */
- if (ve_flags & VE_ONEMORE) {
- ins_at_eol = FALSE;
- nomove = TRUE;
- }
- count = 0;
- goto doESCkey;
+ // If 'keymodel' contains "startsel", may start selection. If it
+ // does, a CTRL-O and c will be stuffed, we need to get these
+ // characters.
+ if (ins_start_select(s->c)) {
+ return 1; // continue
+ }
- case K_INS: /* toggle insert/replace mode */
- case K_KINS:
- ins_insert(replaceState);
+ return insert_handle_key(s);
+}
+
+static int insert_handle_key(InsertState *s)
+{
+ // The big switch to handle a character in insert mode.
+ // TODO(tarruda): This could look better if a lookup table is used.
+ // (similar to normal mode `nv_cmds[]`)
+ switch (s->c) {
+ case ESC: // End input mode
+ if (echeck_abbr(ESC + ABBR_OFF)) {
break;
+ }
+ // FALLTHROUGH
+
+ case Ctrl_C: // End input mode
+ if (s->c == Ctrl_C && cmdwin_type != 0) {
+ // Close the cmdline window. */
+ cmdwin_result = K_IGNORE;
+ got_int = false; // don't stop executing autocommands et al
+ s->nomove = true;
+ return 0; // exit insert mode
+ }
- case K_SELECT: /* end of Select mode mapping - ignore */
+ // when 'insertmode' set, and not halfway through a mapping, don't leave
+ // Insert mode
+ if (goto_im()) {
+ if (got_int) {
+ (void)vgetc(); // flush all buffers
+ got_int = false;
+ } else {
+ vim_beep(BO_IM);
+ }
break;
+ }
+ return 0; // exit insert mode
+ case Ctrl_Z: // suspend when 'insertmode' set
+ if (!p_im) {
+ goto normalchar; // insert CTRL-Z as normal char
+ }
+ stuffReadbuff((char_u *)":st\r");
+ s->c = Ctrl_O;
+ // FALLTHROUGH
- case K_HELP: /* Help key works like <ESC> <Help> */
- case K_F1:
- case K_XF1:
- stuffcharReadbuff(K_HELP);
- if (p_im)
- need_start_insertmode = TRUE;
- goto doESCkey;
-
-
- case K_ZERO: /* Insert the previously inserted text. */
- case NUL:
- case Ctrl_A:
- /* For ^@ the trailing ESC will end the insert, unless there is an
- * error. */
- if (stuff_inserted(NUL, 1L, (c == Ctrl_A)) == FAIL
- && c != Ctrl_A && !p_im)
- goto doESCkey; /* quit insert mode */
- inserted_space = FALSE;
+ case Ctrl_O: // execute one command
+ if (ctrl_x_mode == CTRL_X_OMNI) {
+ insert_do_complete(s);
break;
+ }
- case Ctrl_R: /* insert the contents of a register */
- ins_reg();
- auto_format(FALSE, TRUE);
- inserted_space = FALSE;
+ if (echeck_abbr(Ctrl_O + ABBR_OFF)) {
break;
+ }
- case Ctrl_G: /* commands starting with CTRL-G */
- ins_ctrl_g();
- break;
+ ins_ctrl_o();
- case Ctrl_HAT: /* switch input mode and/or langmap */
- ins_ctrl_hat();
- break;
+ // don't move the cursor left when 'virtualedit' has "onemore".
+ if (ve_flags & VE_ONEMORE) {
+ ins_at_eol = false;
+ s->nomove = true;
+ }
- case Ctrl__: /* switch between languages */
- if (!p_ari)
- goto normalchar;
- ins_ctrl_();
- break;
+ s->count = 0;
+ return 0; // exit insert mode
- case Ctrl_D: /* Make indent one shiftwidth smaller. */
- if (ctrl_x_mode == CTRL_X_PATH_DEFINES)
- goto docomplete;
- /* FALLTHROUGH */
+ case K_INS: // toggle insert/replace mode
+ case K_KINS:
+ ins_insert(s->replaceState);
+ break;
- case Ctrl_T: /* Make indent one shiftwidth greater. */
- if (c == Ctrl_T && ctrl_x_mode == CTRL_X_THESAURUS) {
- if (has_compl_option(FALSE))
- goto docomplete;
- break;
- }
- ins_shift(c, lastc);
- auto_format(FALSE, TRUE);
- inserted_space = FALSE;
- break;
+ case K_SELECT: // end of Select mode mapping - ignore
+ break;
- case K_DEL: /* delete character under the cursor */
- case K_KDEL:
- ins_del();
- auto_format(FALSE, TRUE);
- break;
- case K_BS: /* delete character before the cursor */
- case Ctrl_H:
- did_backspace = ins_bs(c, BACKSPACE_CHAR, &inserted_space);
- auto_format(FALSE, TRUE);
- break;
+ case K_HELP: // Help key works like <ESC> <Help>
+ case K_F1:
+ case K_XF1:
+ stuffcharReadbuff(K_HELP);
+ if (p_im) {
+ need_start_insertmode = true;
+ }
+ return 0; // exit insert mode
- case Ctrl_W: /* delete word before the cursor */
- did_backspace = ins_bs(c, BACKSPACE_WORD, &inserted_space);
- auto_format(FALSE, TRUE);
- break;
- case Ctrl_U: /* delete all inserted text in current line */
- /* CTRL-X CTRL-U completes with 'completefunc'. */
- if (ctrl_x_mode == CTRL_X_FUNCTION)
- goto docomplete;
- did_backspace = ins_bs(c, BACKSPACE_LINE, &inserted_space);
- auto_format(FALSE, TRUE);
- inserted_space = FALSE;
- break;
+ case K_ZERO: // Insert the previously inserted text.
+ case NUL:
+ case Ctrl_A:
+ // For ^@ the trailing ESC will end the insert, unless there is an
+ // error.
+ if (stuff_inserted(NUL, 1L, (s->c == Ctrl_A)) == FAIL
+ && s->c != Ctrl_A && !p_im) {
+ return 0; // exit insert mode
+ }
+ s->inserted_space = false;
+ break;
- case K_LEFTMOUSE: /* mouse keys */
- case K_LEFTMOUSE_NM:
- case K_LEFTDRAG:
- case K_LEFTRELEASE:
- case K_LEFTRELEASE_NM:
- case K_MIDDLEMOUSE:
- case K_MIDDLEDRAG:
- case K_MIDDLERELEASE:
- case K_RIGHTMOUSE:
- case K_RIGHTDRAG:
- case K_RIGHTRELEASE:
- case K_X1MOUSE:
- case K_X1DRAG:
- case K_X1RELEASE:
- case K_X2MOUSE:
- case K_X2DRAG:
- case K_X2RELEASE:
- ins_mouse(c);
- break;
+ case Ctrl_R: // insert the contents of a register
+ ins_reg();
+ auto_format(false, true);
+ s->inserted_space = false;
+ break;
- case K_MOUSEDOWN: /* Default action for scroll wheel up: scroll up */
- ins_mousescroll(MSCR_DOWN);
- break;
+ case Ctrl_G: // commands starting with CTRL-G
+ ins_ctrl_g();
+ break;
- case K_MOUSEUP: /* Default action for scroll wheel down: scroll down */
- ins_mousescroll(MSCR_UP);
- break;
+ case Ctrl_HAT: // switch input mode and/or langmap
+ ins_ctrl_hat();
+ break;
- case K_MOUSELEFT: /* Scroll wheel left */
- ins_mousescroll(MSCR_LEFT);
- break;
+ case Ctrl__: // switch between languages
+ if (!p_ari) {
+ goto normalchar;
+ }
+ ins_ctrl_();
+ break;
- case K_MOUSERIGHT: /* Scroll wheel right */
- ins_mousescroll(MSCR_RIGHT);
+ case Ctrl_D: // Make indent one shiftwidth smaller.
+ if (ctrl_x_mode == CTRL_X_PATH_DEFINES) {
+ insert_do_complete(s);
break;
+ }
+ // FALLTHROUGH
- case K_IGNORE: /* Something mapped to nothing */
+ case Ctrl_T: // Make indent one shiftwidth greater.
+ if (s->c == Ctrl_T && ctrl_x_mode == CTRL_X_THESAURUS) {
+ if (has_compl_option(false)) {
+ insert_do_complete(s);
+ }
break;
+ }
+ ins_shift(s->c, s->lastc);
+ auto_format(false, true);
+ s->inserted_space = false;
+ break;
- case K_CURSORHOLD: /* Didn't type something for a while. */
- apply_autocmds(EVENT_CURSORHOLDI, NULL, NULL, FALSE, curbuf);
- did_cursorhold = TRUE;
- break;
+ case K_DEL: // delete character under the cursor
+ case K_KDEL:
+ ins_del();
+ auto_format(false, true);
+ break;
- case K_HOME: /* <Home> */
- case K_KHOME:
- case K_S_HOME:
- case K_C_HOME:
- ins_home(c);
- break;
+ case K_BS: // delete character before the cursor
+ case Ctrl_H:
+ s->did_backspace = ins_bs(s->c, BACKSPACE_CHAR, &s->inserted_space);
+ auto_format(false, true);
+ break;
- case K_END: /* <End> */
- case K_KEND:
- case K_S_END:
- case K_C_END:
- ins_end(c);
- break;
+ case Ctrl_W: // delete word before the cursor
+ s->did_backspace = ins_bs(s->c, BACKSPACE_WORD, &s->inserted_space);
+ auto_format(false, true);
+ break;
- case K_LEFT: /* <Left> */
- if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))
- ins_s_left();
- else
- ins_left();
- break;
+ case Ctrl_U: // delete all inserted text in current line
+ // CTRL-X CTRL-U completes with 'completefunc'.
+ if (ctrl_x_mode == CTRL_X_FUNCTION) {
+ insert_do_complete(s);
+ } else {
+ s->did_backspace = ins_bs(s->c, BACKSPACE_LINE, &s->inserted_space);
+ auto_format(false, true);
+ s->inserted_space = false;
+ }
+ break;
+
+ case K_LEFTMOUSE: // mouse keys
+ case K_LEFTMOUSE_NM:
+ case K_LEFTDRAG:
+ case K_LEFTRELEASE:
+ case K_LEFTRELEASE_NM:
+ case K_MIDDLEMOUSE:
+ case K_MIDDLEDRAG:
+ case K_MIDDLERELEASE:
+ case K_RIGHTMOUSE:
+ case K_RIGHTDRAG:
+ case K_RIGHTRELEASE:
+ case K_X1MOUSE:
+ case K_X1DRAG:
+ case K_X1RELEASE:
+ case K_X2MOUSE:
+ case K_X2DRAG:
+ case K_X2RELEASE:
+ ins_mouse(s->c);
+ break;
+
+ case K_MOUSEDOWN: // Default action for scroll wheel up: scroll up
+ ins_mousescroll(MSCR_DOWN);
+ break;
+
+ case K_MOUSEUP: // Default action for scroll wheel down: scroll down
+ ins_mousescroll(MSCR_UP);
+ break;
+
+ case K_MOUSELEFT: // Scroll wheel left
+ ins_mousescroll(MSCR_LEFT);
+ break;
+
+ case K_MOUSERIGHT: // Scroll wheel right
+ ins_mousescroll(MSCR_RIGHT);
+ break;
+
+ case K_IGNORE: // Something mapped to nothing
+ break;
+
+ case K_EVENT: // some event
+ queue_process_events(loop.events);
+ break;
+
+ case K_FOCUSGAINED: // Neovim has been given focus
+ apply_autocmds(EVENT_FOCUSGAINED, NULL, NULL, false, curbuf);
+ break;
+
+ case K_FOCUSLOST: // Neovim has lost focus
+ apply_autocmds(EVENT_FOCUSLOST, NULL, NULL, false, curbuf);
+ break;
+
+ case K_HOME: // <Home>
+ case K_KHOME:
+ case K_S_HOME:
+ case K_C_HOME:
+ ins_home(s->c);
+ break;
+
+ case K_END: // <End>
+ case K_KEND:
+ case K_S_END:
+ case K_C_END:
+ ins_end(s->c);
+ break;
- case K_S_LEFT: /* <S-Left> */
- case K_C_LEFT:
+ case K_LEFT: // <Left>
+ if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) {
ins_s_left();
- break;
+ } else {
+ ins_left(dont_sync_undo == false);
+ }
+ break;
- case K_RIGHT: /* <Right> */
- if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))
- ins_s_right();
- else
- ins_right();
- break;
+ case K_S_LEFT: // <S-Left>
+ case K_C_LEFT:
+ ins_s_left();
+ break;
- case K_S_RIGHT: /* <S-Right> */
- case K_C_RIGHT:
+ case K_RIGHT: // <Right>
+ if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) {
ins_s_right();
- break;
+ } else {
+ ins_right(dont_sync_undo == false);
+ }
+ break;
- case K_UP: /* <Up> */
- if (pum_visible())
- goto docomplete;
- if (mod_mask & MOD_MASK_SHIFT)
- ins_pageup();
- else
- ins_up(FALSE);
- break;
+ case K_S_RIGHT: // <S-Right>
+ case K_C_RIGHT:
+ ins_s_right();
+ break;
- case K_S_UP: /* <S-Up> */
- case K_PAGEUP:
- case K_KPAGEUP:
- if (pum_visible())
- goto docomplete;
+ case K_UP: // <Up>
+ if (pum_visible()) {
+ insert_do_complete(s);
+ } else if (mod_mask & MOD_MASK_SHIFT) {
ins_pageup();
- break;
+ } else {
+ ins_up(false);
+ }
+ break;
- case K_DOWN: /* <Down> */
- if (pum_visible())
- goto docomplete;
- if (mod_mask & MOD_MASK_SHIFT)
- ins_pagedown();
- else
- ins_down(FALSE);
- break;
+ case K_S_UP: // <S-Up>
+ case K_PAGEUP:
+ case K_KPAGEUP:
+ if (pum_visible()) {
+ insert_do_complete(s);
+ } else {
+ ins_pageup();
+ }
+ break;
- case K_S_DOWN: /* <S-Down> */
- case K_PAGEDOWN:
- case K_KPAGEDOWN:
- if (pum_visible())
- goto docomplete;
+ case K_DOWN: // <Down>
+ if (pum_visible()) {
+ insert_do_complete(s);
+ } else if (mod_mask & MOD_MASK_SHIFT) {
ins_pagedown();
- break;
+ } else {
+ ins_down(false);
+ }
+ break;
+ case K_S_DOWN: // <S-Down>
+ case K_PAGEDOWN:
+ case K_KPAGEDOWN:
+ if (pum_visible()) {
+ insert_do_complete(s);
+ } else {
+ ins_pagedown();
+ }
+ break;
- case K_S_TAB: /* When not mapped, use like a normal TAB */
- c = TAB;
- /* FALLTHROUGH */
- case TAB: /* TAB or Complete patterns along path */
- if (ctrl_x_mode == CTRL_X_PATH_PATTERNS)
- goto docomplete;
- inserted_space = FALSE;
- if (ins_tab())
- goto normalchar; /* insert TAB as a normal char */
- auto_format(FALSE, TRUE);
+ case K_S_TAB: // When not mapped, use like a normal TAB
+ s->c = TAB;
+ // FALLTHROUGH
+
+ case TAB: // TAB or Complete patterns along path
+ if (ctrl_x_mode == CTRL_X_PATH_PATTERNS) {
+ insert_do_complete(s);
break;
+ }
+ s->inserted_space = false;
+ if (ins_tab()) {
+ goto normalchar; // insert TAB as a normal char
+ }
+ auto_format(false, true);
+ break;
- case K_KENTER: /* <Enter> */
- c = CAR;
- /* FALLTHROUGH */
- case CAR:
- case NL:
- /* In a quickfix window a <CR> jumps to the error under the
- * cursor. */
- if (bt_quickfix(curbuf) && c == CAR) {
- if (curwin->w_llist_ref == NULL) /* quickfix window */
- do_cmdline_cmd(".cc");
- else /* location list window */
- do_cmdline_cmd(".ll");
- break;
+ case K_KENTER: // <Enter>
+ s->c = CAR;
+ // FALLTHROUGH
+ case CAR:
+ case NL:
+ // In a quickfix window a <CR> jumps to the error under the
+ // cursor.
+ if (bt_quickfix(curbuf) && s->c == CAR) {
+ if (curwin->w_llist_ref == NULL) { // quickfix window
+ do_cmdline_cmd(".cc");
+ } else { // location list window
+ do_cmdline_cmd(".ll");
}
- if (cmdwin_type != 0) {
- /* Execute the command in the cmdline window. */
- cmdwin_result = CAR;
- goto doESCkey;
- }
- if (ins_eol(c) && !p_im)
- goto doESCkey; /* out of memory */
- auto_format(FALSE, FALSE);
- inserted_space = FALSE;
break;
+ }
+ if (cmdwin_type != 0) {
+ // Execute the command in the cmdline window.
+ cmdwin_result = CAR;
+ return 0;
+ }
+ if (ins_eol(s->c) && !p_im) {
+ return 0; // out of memory
+ }
+ auto_format(false, false);
+ s->inserted_space = false;
+ break;
- case Ctrl_K: /* digraph or keyword completion */
- if (ctrl_x_mode == CTRL_X_DICTIONARY) {
- if (has_compl_option(TRUE))
- goto docomplete;
- break;
+ case Ctrl_K: // digraph or keyword completion
+ if (ctrl_x_mode == CTRL_X_DICTIONARY) {
+ if (has_compl_option(true)) {
+ insert_do_complete(s);
}
- c = ins_digraph();
- if (c == NUL)
- break;
- goto normalchar;
+ break;
+ }
- case Ctrl_X: /* Enter CTRL-X mode */
- ins_ctrl_x();
+ s->c = ins_digraph();
+ if (s->c == NUL) {
break;
+ }
+ goto normalchar;
- case Ctrl_RSB: /* Tag name completion after ^X */
- if (ctrl_x_mode != CTRL_X_TAGS)
- goto normalchar;
- goto docomplete;
+ case Ctrl_X: // Enter CTRL-X mode
+ ins_ctrl_x();
+ break;
- case Ctrl_F: /* File name completion after ^X */
- if (ctrl_x_mode != CTRL_X_FILES)
- goto normalchar;
- goto docomplete;
+ case Ctrl_RSB: // Tag name completion after ^X
+ if (ctrl_x_mode != CTRL_X_TAGS) {
+ goto normalchar;
+ } else {
+ insert_do_complete(s);
+ }
+ break;
- case 's': /* Spelling completion after ^X */
- case Ctrl_S:
- if (ctrl_x_mode != CTRL_X_SPELL)
- goto normalchar;
- goto docomplete;
-
- case Ctrl_L: /* Whole line completion after ^X */
- if (ctrl_x_mode != CTRL_X_WHOLE_LINE) {
- /* CTRL-L with 'insertmode' set: Leave Insert mode */
- if (p_im) {
- if (echeck_abbr(Ctrl_L + ABBR_OFF))
- break;
- goto doESCkey;
+ case Ctrl_F: // File name completion after ^X
+ if (ctrl_x_mode != CTRL_X_FILES) {
+ goto normalchar;
+ } else {
+ insert_do_complete(s);
+ }
+ break;
+
+ case 's': // Spelling completion after ^X
+ case Ctrl_S:
+ if (ctrl_x_mode != CTRL_X_SPELL) {
+ goto normalchar;
+ } else {
+ insert_do_complete(s);
+ }
+ break;
+
+ case Ctrl_L: // Whole line completion after ^X
+ if (ctrl_x_mode != CTRL_X_WHOLE_LINE) {
+ // CTRL-L with 'insertmode' set: Leave Insert mode
+ if (p_im) {
+ if (echeck_abbr(Ctrl_L + ABBR_OFF)) {
+ break;
}
- goto normalchar;
+ return 0; // exit insert mode
}
- /* FALLTHROUGH */
+ goto normalchar;
+ }
+ // FALLTHROUGH
+
+ case Ctrl_P: // Do previous/next pattern completion
+ case Ctrl_N:
+ // if 'complete' is empty then plain ^P is no longer special,
+ // but it is under other ^X modes
+ if (*curbuf->b_p_cpt == NUL
+ && ctrl_x_mode != 0
+ && !(compl_cont_status & CONT_LOCAL)) {
+ goto normalchar;
+ }
- case Ctrl_P: /* Do previous/next pattern completion */
- case Ctrl_N:
- /* if 'complete' is empty then plain ^P is no longer special,
- * but it is under other ^X modes */
- if (*curbuf->b_p_cpt == NUL
- && ctrl_x_mode != 0
- && !(compl_cont_status & CONT_LOCAL))
- goto normalchar;
-
-docomplete:
- compl_busy = TRUE;
- if (ins_complete(c) == FAIL)
- compl_cont_status = 0;
- compl_busy = FALSE;
- break;
+ insert_do_complete(s);
+ break;
- case Ctrl_Y: /* copy from previous line or scroll down */
- case Ctrl_E: /* copy from next line or scroll up */
- c = ins_ctrl_ey(c);
- break;
+ case Ctrl_Y: // copy from previous line or scroll down
+ case Ctrl_E: // copy from next line or scroll up
+ s->c = ins_ctrl_ey(s->c);
+ break;
- default:
-#ifdef UNIX
- if (c == intr_char) /* special interrupt char */
- goto do_intr;
-#endif
+ default:
normalchar:
- /*
- * Insert a normal character.
- */
- if (!p_paste) {
- /* Trigger InsertCharPre. */
- char_u *str = do_insert_char_pre(c);
- char_u *p;
-
- if (str != NULL) {
- if (*str != NUL && stop_arrow() != FAIL) {
- /* Insert the new value of v:char literally. */
- for (p = str; *p != NUL; mb_ptr_adv(p)) {
- c = PTR2CHAR(p);
- if (c == CAR || c == K_KENTER || c == NL)
- ins_eol(c);
- else
- ins_char(c);
+ // Insert a normal character.
+ if (!p_paste) {
+ // Trigger InsertCharPre.
+ char_u *str = do_insert_char_pre(s->c);
+ char_u *p;
+
+ if (str != NULL) {
+ if (*str != NUL && stop_arrow() != FAIL) {
+ // Insert the new value of v:char literally.
+ for (p = str; *p != NUL; mb_ptr_adv(p)) {
+ s->c = PTR2CHAR(p);
+ if (s->c == CAR || s->c == K_KENTER || s->c == NL) {
+ ins_eol(s->c);
+ } else {
+ ins_char(s->c);
}
- AppendToRedobuffLit(str, -1);
}
- xfree(str);
- c = NUL;
+ AppendToRedobuffLit(str, -1);
}
+ xfree(str);
+ s->c = NUL;
+ }
- /* If the new value is already inserted or an empty string
- * then don't insert any character. */
- if (c == NUL)
- break;
+ // If the new value is already inserted or an empty string
+ // then don't insert any character.
+ if (s->c == NUL)
+ break;
+ }
+ // Try to perform smart-indenting.
+ ins_try_si(s->c);
+
+ if (s->c == ' ') {
+ s->inserted_space = true;
+ if (inindent(0)) {
+ can_cindent = false;
}
- /* Try to perform smart-indenting. */
- ins_try_si(c);
-
- if (c == ' ') {
- inserted_space = TRUE;
- if (inindent(0))
- can_cindent = FALSE;
- if (Insstart_blank_vcol == MAXCOL
- && curwin->w_cursor.lnum == Insstart.lnum)
- Insstart_blank_vcol = get_nolist_virtcol();
+ if (Insstart_blank_vcol == MAXCOL
+ && curwin->w_cursor.lnum == Insstart.lnum) {
+ Insstart_blank_vcol = get_nolist_virtcol();
}
+ }
- /* Insert a normal character and check for abbreviations on a
- * special character. Let CTRL-] expand abbreviations without
- * inserting it. */
- if (vim_iswordc(c) || (!echeck_abbr(
- /* Add ABBR_OFF for characters above 0x100, this is
- * what check_abbr() expects. */
- (has_mbyte && c >= 0x100) ? (c + ABBR_OFF) :
- c) && c != Ctrl_RSB)) {
- insert_special(c, FALSE, FALSE);
- revins_legal++;
- revins_chars++;
- }
+ // Insert a normal character and check for abbreviations on a
+ // special character. Let CTRL-] expand abbreviations without
+ // inserting it.
+ if (vim_iswordc(s->c)
+ || (!echeck_abbr(
+ // Add ABBR_OFF for characters above 0x100, this is
+ // what check_abbr() expects.
+ (has_mbyte && s->c >= 0x100) ? (s->c + ABBR_OFF) : s->c)
+ && s->c != Ctrl_RSB)) {
+ insert_special(s->c, false, false);
+ revins_legal++;
+ revins_chars++;
+ }
- auto_format(FALSE, TRUE);
+ auto_format(false, true);
- /* When inserting a character the cursor line must never be in a
- * closed fold. */
- foldOpenCursor();
- break;
- } /* end of switch (c) */
+ // When inserting a character the cursor line must never be in a
+ // closed fold.
+ foldOpenCursor();
+ break;
+ } // end of switch (s->c)
- /* If typed something may trigger CursorHoldI again. */
- if (c != K_CURSORHOLD)
- did_cursorhold = FALSE;
+ return 1; // continue
+}
- /* If the cursor was moved we didn't just insert a space */
- if (arrow_used)
- inserted_space = FALSE;
+static void insert_do_complete(InsertState *s)
+{
+ compl_busy = true;
+ if (ins_complete(s->c) == FAIL) {
+ compl_cont_status = 0;
+ }
+ compl_busy = false;
+}
- if (can_cindent && cindent_on()
- && ctrl_x_mode == 0
- ) {
-force_cindent:
- /*
- * Indent now if a key was typed that is in 'cinkeys'.
- */
- if (in_cinkeys(c, ' ', line_is_white)) {
- if (stop_arrow() == OK)
- /* re-indent the current line */
- do_c_expr_indent();
- }
+static void insert_do_cindent(InsertState *s)
+{
+ // Indent now if a key was typed that is in 'cinkeys'.
+ if (in_cinkeys(s->c, ' ', s->line_is_white)) {
+ if (stop_arrow() == OK) {
+ // re-indent the current line
+ do_c_expr_indent();
}
+ }
+}
- } /* for (;;) */
- /* NOTREACHED */
+/*
+ * edit(): Start inserting text.
+ *
+ * "cmdchar" can be:
+ * 'i' normal insert command
+ * 'a' normal append command
+ * 'R' replace command
+ * 'r' "r<CR>" command: insert one <CR>. Note: count can be > 1, for redo,
+ * but still only one <CR> is inserted. The <Esc> is not used for redo.
+ * 'g' "gI" command.
+ * 'V' "gR" command for Virtual Replace mode.
+ * 'v' "gr" command for single character Virtual Replace mode.
+ *
+ * This function is not called recursively. For CTRL-O commands, it returns
+ * and lets the caller handle the Normal-mode command.
+ *
+ * Return TRUE if a CTRL-O command caused the return (insert mode pending).
+ */
+int
+edit (
+ int cmdchar,
+ int startln, /* if set, insert at start of line */
+ long count
+)
+{
+ if (curbuf->terminal) {
+ if (ex_normal_busy) {
+ // don't enter terminal mode from `ex_normal`, which can result in all
+ // kinds of havoc(such as terminal mode recursiveness). Instead, set a
+ // flag that allow us to force-set the value of `restart_edit` before
+ // `ex_normal` returns
+ restart_edit = 'i';
+ force_restart_edit = true;
+ } else {
+ terminal_enter();
+ }
+ return false;
+ }
+
+ // Don't allow inserting in the sandbox.
+ if (sandbox != 0) {
+ EMSG(_(e_sandbox));
+ return false;
+ }
+
+ // Don't allow changes in the buffer while editing the cmdline. The
+ // caller of getcmdline() may get confused.
+ if (textlock != 0) {
+ EMSG(_(e_secure));
+ return false;
+ }
+
+ // Don't allow recursive insert mode when busy with completion.
+ if (compl_started || compl_busy || pum_visible()) {
+ EMSG(_(e_secure));
+ return false;
+ }
+
+ InsertState state, *s = &state;
+ memset(s, 0, sizeof(InsertState));
+ s->state.execute = insert_execute;
+ s->state.check = insert_check;
+ s->cmdchar = cmdchar;
+ s->startln = startln;
+ s->count = count;
+ insert_enter(s);
+ return s->c == Ctrl_O;
}
/*
@@ -4924,8 +5017,9 @@ insertchar (
int textwidth;
char_u *p;
int fo_ins_blank;
+ int force_format = flags & INSCHAR_FORMAT;
- textwidth = comp_textwidth(flags & INSCHAR_FORMAT);
+ textwidth = comp_textwidth(force_format);
fo_ins_blank = has_format_option(FO_INS_BLANK);
/*
@@ -4944,7 +5038,7 @@ insertchar (
* before 'textwidth'
*/
if (textwidth > 0
- && ((flags & INSCHAR_FORMAT)
+ && (force_format
|| (!ascii_iswhite(c)
&& !((State & REPLACE_FLAG)
&& !(State & VREPLACE_FLAG)
@@ -4958,8 +5052,11 @@ insertchar (
/* Format with 'formatexpr' when it's set. Use internal formatting
* when 'formatexpr' isn't set or it returns non-zero. */
int do_internal = TRUE;
+ colnr_T virtcol = get_nolist_virtcol()
+ + char2cells(c != NUL ? c : gchar_cursor());
- if (*curbuf->b_p_fex != NUL && (flags & INSCHAR_NO_FEX) == 0) {
+ if (*curbuf->b_p_fex != NUL && (flags & INSCHAR_NO_FEX) == 0
+ && (force_format || virtcol > (colnr_T)textwidth)) {
do_internal = (fex_format(curwin->w_cursor.lnum, 1L, c) != 0);
/* It may be required to save for undo again, e.g. when setline()
* was called. */
@@ -5627,16 +5724,31 @@ static void redo_literal(int c)
AppendCharToRedobuff(c);
}
-/*
- * start_arrow() is called when an arrow key is used in insert mode.
- * For undo/redo it resembles hitting the <ESC> key.
- */
-static void
-start_arrow (
- pos_T *end_insert_pos /* can be NULL */
-)
+// start_arrow() is called when an arrow key is used in insert mode.
+// For undo/redo it resembles hitting the <ESC> key.
+static void start_arrow(pos_T *end_insert_pos /* can be NULL */)
+{
+ start_arrow_common(end_insert_pos, true);
+}
+
+/// Like start_arrow() but with end_change argument.
+/// Will prepare for redo of CTRL-G U if "end_change" is FALSE.
+/// @param end_insert_pos can be NULL
+/// @param end_change end undoable change
+static void start_arrow_with_change(pos_T *end_insert_pos, bool end_change)
{
- if (!arrow_used) { /* something has been inserted */
+ start_arrow_common(end_insert_pos, end_change);
+ if (!end_change) {
+ AppendCharToRedobuff(Ctrl_G);
+ AppendCharToRedobuff('U');
+ }
+}
+
+/// @param end_insert_pos can be NULL
+/// @param end_change end undoable change
+static void start_arrow_common(pos_T *end_insert_pos, bool end_change)
+{
+ if (!arrow_used && end_change) { // something has been inserted
AppendToRedobuff(ESC_STR);
stop_insert(end_insert_pos, FALSE, FALSE);
arrow_used = TRUE; /* this means we stopped the current insert */
@@ -6903,6 +7015,13 @@ static void ins_ctrl_g(void)
Insstart = curwin->w_cursor;
break;
+ // CTRL-G U: do not break undo with the next char.
+ case 'U':
+ // Allow one left/right cursor movement with the next char,
+ // without breaking undo.
+ dont_sync_undo = MAYBE;
+ break;
+
/* Unknown CTRL-G command, reserved for future expansion. */
default: vim_beep(BO_CTRLG);
}
@@ -7312,15 +7431,14 @@ static int ins_bs(int c, int mode, int *inserted_space_p)
* delete newline!
*/
if (curwin->w_cursor.col == 0) {
- lnum = Insstart_orig.lnum;
+ lnum = Insstart.lnum;
if (curwin->w_cursor.lnum == lnum || revins_on) {
if (u_save((linenr_T)(curwin->w_cursor.lnum - 2),
(linenr_T)(curwin->w_cursor.lnum + 1)) == FAIL) {
return FALSE;
}
- --Insstart_orig.lnum;
- Insstart_orig.col = MAXCOL;
- Insstart = Insstart_orig;
+ --Insstart.lnum;
+ Insstart.col = MAXCOL;
}
/*
* In replace mode:
@@ -7649,7 +7767,7 @@ static void ins_mousescroll(int dir)
-static void ins_left(void)
+static void ins_left(bool end_change)
{
pos_T tpos;
@@ -7658,17 +7776,17 @@ static void ins_left(void)
undisplay_dollar();
tpos = curwin->w_cursor;
if (oneleft() == OK) {
- start_arrow(&tpos);
+ start_arrow_with_change(&tpos, end_change);
+ if (!end_change) {
+ AppendCharToRedobuff(K_LEFT);
+ }
/* If exit reversed string, position is fixed */
if (revins_scol != -1 && (int)curwin->w_cursor.col >= revins_scol)
revins_legal++;
revins_chars++;
- }
- /*
- * if 'whichwrap' set for cursor in insert mode may go to
- * previous line
- */
- else if (vim_strchr(p_ww, '[') != NULL && curwin->w_cursor.lnum > 1) {
+ } else if (vim_strchr(p_ww, '[') != NULL && curwin->w_cursor.lnum > 1) {
+ // if 'whichwrap' set for cursor in insert mode may go to previous line.
+ // always break undo when moving upwards/downwards, else undo may break
start_arrow(&tpos);
--(curwin->w_cursor.lnum);
coladvance((colnr_T)MAXCOL);
@@ -7676,6 +7794,7 @@ static void ins_left(void)
} else {
vim_beep(BO_CRSR);
}
+ dont_sync_undo = false;
}
static void ins_home(int c)
@@ -7724,16 +7843,18 @@ static void ins_s_left(void)
}
}
-static void ins_right(void)
+/// @param end_change end undoable change
+static void ins_right(bool end_change)
{
if ((fdo_flags & FDO_HOR) && KeyTyped)
foldOpenCursor();
undisplay_dollar();
- if (gchar_cursor() != NUL
- || virtual_active()
- ) {
- start_arrow(&curwin->w_cursor);
- curwin->w_set_curswant = TRUE;
+ if (gchar_cursor() != NUL || virtual_active()) {
+ start_arrow_with_change(&curwin->w_cursor, end_change);
+ if (!end_change) {
+ AppendCharToRedobuff(K_RIGHT);
+ }
+ curwin->w_set_curswant = true;
if (virtual_active())
oneright();
else {
@@ -7758,6 +7879,7 @@ static void ins_right(void)
} else {
vim_beep(BO_CRSR);
}
+ dont_sync_undo = false;
}
static void ins_s_right(void)
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index a1ac713a75..b60886704e 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -1,18 +1,8 @@
/*
- *
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* eval.c: Expression evaluation.
*/
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <stdarg.h>
#include <string.h>
@@ -98,6 +88,7 @@
#include "nvim/os/input.h"
#include "nvim/event/loop.h"
#include "nvim/lib/kvec.h"
+#include "nvim/lib/queue.h"
#define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */
@@ -228,50 +219,11 @@ static int echo_attr = 0; /* attributes used for ":echo" */
#define GLV_QUIET TFN_QUIET /* no error messages */
#define GLV_NO_AUTOLOAD TFN_NO_AUTOLOAD /* do not use script autoloading */
-/*
- * Structure to hold info for a user function.
- */
-typedef struct ufunc ufunc_T;
-
-struct ufunc {
- int uf_varargs; /* variable nr of arguments */
- int uf_flags;
- int uf_calls; /* nr of active calls */
- garray_T uf_args; /* arguments */
- garray_T uf_lines; /* function lines */
- int uf_profiling; /* TRUE when func is being profiled */
- /* profiling the function as a whole */
- int uf_tm_count; /* nr of calls */
- proftime_T uf_tm_total; /* time spent in function + children */
- proftime_T uf_tm_self; /* time spent in function itself */
- proftime_T uf_tm_children; /* time spent in children this call */
- /* profiling the function per line */
- int *uf_tml_count; /* nr of times line was executed */
- proftime_T *uf_tml_total; /* time spent in a line + children */
- proftime_T *uf_tml_self; /* time spent in a line itself */
- proftime_T uf_tml_start; /* start time for current line */
- proftime_T uf_tml_children; /* time spent in children for this line */
- proftime_T uf_tml_wait; /* start wait time for current line */
- int uf_tml_idx; /* index of line being timed; -1 if none */
- int uf_tml_execed; /* line being timed was executed */
- scid_T uf_script_ID; /* ID of script where function was defined,
- used for s: variables */
- int uf_refcount; /* for numbered function: reference count */
- char_u uf_name[1]; /* name of function (actually longer); can
- start with <SNR>123_ (<SNR> is K_SPECIAL
- KS_EXTRA KE_SNR) */
-};
-
/* function flags */
#define FC_ABORT 1 /* abort function on error */
#define FC_RANGE 2 /* function accepts range */
#define FC_DICT 4 /* Dict function, uses "self" */
-/*
- * All user-defined functions are found in this hashtable.
- */
-static hashtab_T func_hashtab;
-
/* The names of packages that once were loaded are remembered. */
static garray_T ga_loaded = {0, 0, sizeof(char_u *), 4, NULL};
@@ -279,12 +231,6 @@ static garray_T ga_loaded = {0, 0, sizeof(char_u *), 4, NULL};
static dict_T *first_dict = NULL; /* list of all dicts */
static list_T *first_list = NULL; /* list of all lists */
-/* From user function to hashitem and back. */
-static ufunc_T dumuf;
-#define UF2HIKEY(fp) ((fp)->uf_name)
-#define HIKEY2UF(p) ((ufunc_T *)(p - (dumuf.uf_name - (char_u *)&dumuf)))
-#define HI2UF(hi) HIKEY2UF((hi)->hi_key)
-
#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j]
#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j]
@@ -427,6 +373,9 @@ static struct vimvar {
{VV_NAME("progpath", VAR_STRING), VV_RO},
{VV_NAME("command_output", VAR_STRING), 0},
{VV_NAME("completed_item", VAR_DICT), VV_RO},
+ {VV_NAME("option_new", VAR_STRING), VV_RO},
+ {VV_NAME("option_old", VAR_STRING), VV_RO},
+ {VV_NAME("option_type", VAR_STRING), VV_RO},
{VV_NAME("msgpack_types", VAR_DICT), VV_RO},
};
@@ -459,6 +408,13 @@ typedef struct {
Queue *events;
} TerminalJobData;
+typedef struct dict_watcher {
+ ufunc_T *callback;
+ char *key_pattern;
+ QUEUE node;
+ bool busy; // prevent recursion if the dict is changed in the callback
+} DictWatcher;
+
/// Structure representing current VimL to messagepack conversion state
typedef struct {
enum {
@@ -2376,6 +2332,14 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, ch
: lp->ll_n1 != lp->ll_n2)
EMSG(_("E711: List value has not enough items"));
} else {
+ typval_T oldtv;
+ dict_T *dict = lp->ll_dict;
+ bool watched = is_watched(dict);
+
+ if (watched) {
+ init_tv(&oldtv);
+ }
+
/*
* Assign to a List or Dictionary item.
*/
@@ -2392,22 +2356,38 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, ch
return;
}
lp->ll_tv = &di->di_tv;
- } else if (op != NULL && *op != '=') {
- tv_op(lp->ll_tv, rettv, op);
- return;
- } else
- clear_tv(lp->ll_tv);
+ } else {
+ if (watched) {
+ copy_tv(lp->ll_tv, &oldtv);
+ }
- /*
- * Assign the value to the variable or list item.
- */
- if (copy)
+ if (op != NULL && *op != '=') {
+ tv_op(lp->ll_tv, rettv, op);
+ goto notify;
+ } else {
+ clear_tv(lp->ll_tv);
+ }
+ }
+
+ // Assign the value to the variable or list item.
+ if (copy) {
copy_tv(rettv, lp->ll_tv);
- else {
+ } else {
*lp->ll_tv = *rettv;
lp->ll_tv->v_lock = 0;
init_tv(rettv);
}
+
+notify:
+ if (watched) {
+ if (oldtv.v_type == VAR_UNKNOWN) {
+ dictwatcher_notify(dict, (char *)lp->ll_newkey, lp->ll_tv, NULL);
+ } else {
+ dictitem_T *di = lp->ll_di;
+ dictwatcher_notify(dict, (char *)di->di_key, lp->ll_tv, &oldtv);
+ clear_tv(&oldtv);
+ }
+ }
}
}
@@ -2932,12 +2912,31 @@ static int do_unlet_var(lval_T *lp, char_u *name_end, int forceit)
++lp->ll_n1;
}
} else {
- if (lp->ll_list != NULL)
- /* unlet a List item. */
+ if (lp->ll_list != NULL) {
+ // unlet a List item.
listitem_remove(lp->ll_list, lp->ll_li);
- else
- /* unlet a Dictionary item. */
- dictitem_remove(lp->ll_dict, lp->ll_di);
+ } else {
+ // unlet a Dictionary item.
+ dict_T *d = lp->ll_dict;
+ dictitem_T *di = lp->ll_di;
+ bool watched = is_watched(d);
+ char *key = NULL;
+ typval_T oldtv;
+
+ if (watched) {
+ copy_tv(&di->di_tv, &oldtv);
+ // need to save key because dictitem_remove will free it
+ key = xstrdup((char *)di->di_key);
+ }
+
+ dictitem_remove(d, di);
+
+ if (watched) {
+ dictwatcher_notify(d, key, NULL, &oldtv);
+ clear_tv(&oldtv);
+ xfree(key);
+ }
+ }
}
return ret;
@@ -2953,8 +2952,9 @@ int do_unlet(char_u *name, int forceit)
hashitem_T *hi;
char_u *varname;
dictitem_T *di;
+ dict_T *dict;
+ ht = find_var_ht_dict(name, &varname, &dict);
- ht = find_var_ht(name, &varname);
if (ht != NULL && *varname != NUL) {
hi = hash_find(ht, varname);
if (!HASHITEM_EMPTY(hi)) {
@@ -2962,7 +2962,19 @@ int do_unlet(char_u *name, int forceit)
if (var_check_fixed(di->di_flags, name)
|| var_check_ro(di->di_flags, name))
return FAIL;
+ typval_T oldtv;
+ bool watched = is_watched(dict);
+
+ if (watched) {
+ copy_tv(&di->di_tv, &oldtv);
+ }
+
delete_var(ht, hi);
+
+ if (watched) {
+ dictwatcher_notify(dict, (char *)varname, NULL, &oldtv);
+ clear_tv(&oldtv);
+ }
return OK;
}
}
@@ -5639,6 +5651,14 @@ bool garbage_collect(void)
ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID, NULL);
}
+ // Jobs
+ {
+ TerminalJobData *data;
+ map_foreach_value(jobs, data, {
+ ABORTING(set_ref_dict)(data->self, copyID);
+ })
+ }
+
// v: vars
ABORTING(set_ref_in_ht)(&vimvarht, copyID, NULL);
@@ -5716,8 +5736,7 @@ static int free_unref_items(int copyID)
// Go through the list of dicts and free items without the copyID.
// Don't free dicts that are referenced internally.
for (dict_T *dd = first_dict; dd != NULL; ) {
- if ((dd->dv_copyID & COPYID_MASK) != (copyID & COPYID_MASK)
- && !dd->internal_refcount) {
+ if ((dd->dv_copyID & COPYID_MASK) != (copyID & COPYID_MASK)) {
// Free the Dictionary and ordinary items it contains, but don't
// recurse into Lists and Dictionaries, they will be in the list
// of dicts or list of lists. */
@@ -5958,7 +5977,7 @@ dict_T *dict_alloc(void) FUNC_ATTR_NONNULL_RET
d->dv_scope = 0;
d->dv_refcount = 0;
d->dv_copyID = 0;
- d->internal_refcount = 0;
+ QUEUE_INIT(&d->watchers);
return d;
}
@@ -6025,6 +6044,14 @@ dict_free (
--todo;
}
}
+
+ while (!QUEUE_EMPTY(&d->watchers)) {
+ QUEUE *w = QUEUE_HEAD(&d->watchers);
+ DictWatcher *watcher = dictwatcher_node_data(w);
+ dictwatcher_free(watcher);
+ QUEUE_REMOVE(w);
+ }
+
hash_clear(&d->dv_hashtab);
xfree(d);
}
@@ -6066,10 +6093,11 @@ static void dictitem_remove(dict_T *dict, dictitem_T *item)
hashitem_T *hi;
hi = hash_find(&dict->dv_hashtab, item->di_key);
- if (HASHITEM_EMPTY(hi))
+ if (HASHITEM_EMPTY(hi)) {
EMSG2(_(e_intern2), "dictitem_remove()");
- else
+ } else {
hash_remove(&dict->dv_hashtab, hi);
+ }
dictitem_free(item);
}
@@ -6264,7 +6292,16 @@ static bool get_dict_callback(dict_T *d, char *key, ufunc_T **result)
return false;
}
- uint8_t *name = di->di_tv.vval.v_string;
+ if ((*result = find_ufunc(di->di_tv.vval.v_string)) == NULL) {
+ return false;
+ }
+
+ (*result)->uf_refcount++;
+ return true;
+}
+
+static ufunc_T *find_ufunc(uint8_t *name)
+{
uint8_t *n = name;
ufunc_T *rv = NULL;
if (*n > '9' || *n < '0') {
@@ -6279,13 +6316,10 @@ static bool get_dict_callback(dict_T *d, char *key, ufunc_T **result)
if (!rv) {
EMSG2(_("Function %s doesn't exist"), name);
- *result = NULL;
- return false;
+ return NULL;
}
- rv->uf_refcount++;
- *result = rv;
- return true;
+ return rv;
}
/*
@@ -7098,6 +7132,8 @@ static struct fst {
{"cursor", 1, 3, f_cursor},
{"deepcopy", 1, 2, f_deepcopy},
{"delete", 1, 1, f_delete},
+ {"dictwatcheradd", 3, 3, f_dictwatcheradd},
+ {"dictwatcherdel", 3, 3, f_dictwatcherdel},
{"did_filetype", 0, 0, f_did_filetype},
{"diff_filler", 1, 1, f_diff_filler},
{"diff_hlID", 2, 2, f_diff_hlID},
@@ -8666,6 +8702,110 @@ static void f_delete(typval_T *argvars, typval_T *rettv)
rettv->vval.v_number = os_remove((char *)get_tv_string(&argvars[0]));
}
+// dictwatcheradd(dict, key, funcref) function
+static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv)
+{
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_DICT) {
+ EMSG2(e_invarg2, "dict");
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_STRING && argvars[1].v_type != VAR_NUMBER) {
+ EMSG2(e_invarg2, "key");
+ return;
+ }
+
+ if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) {
+ EMSG2(e_invarg2, "funcref");
+ return;
+ }
+
+ char *key_pattern = (char *)get_tv_string_chk(argvars + 1);
+ assert(key_pattern);
+ const size_t key_len = STRLEN(argvars[1].vval.v_string);
+
+ if (key_len == 0) {
+ EMSG(_(e_emptykey));
+ return;
+ }
+
+ ufunc_T *func = find_ufunc(argvars[2].vval.v_string);
+ if (!func) {
+ // Invalid function name. Error already reported by `find_ufunc`.
+ return;
+ }
+
+ func->uf_refcount++;
+ DictWatcher *watcher = xmalloc(sizeof(DictWatcher));
+ watcher->key_pattern = xmemdupz(key_pattern, key_len);
+ watcher->callback = func;
+ watcher->busy = false;
+ QUEUE_INSERT_TAIL(&argvars[0].vval.v_dict->watchers, &watcher->node);
+}
+
+// dictwatcherdel(dict, key, funcref) function
+static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv)
+{
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_DICT) {
+ EMSG2(e_invarg2, "dict");
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_STRING && argvars[1].v_type != VAR_NUMBER) {
+ EMSG2(e_invarg2, "key");
+ return;
+ }
+
+ if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) {
+ EMSG2(e_invarg2, "funcref");
+ return;
+ }
+
+ char *key_pattern = (char *)get_tv_string_chk(argvars + 1);
+ assert(key_pattern);
+ const size_t key_len = STRLEN(argvars[1].vval.v_string);
+
+ if (key_len == 0) {
+ EMSG(_(e_emptykey));
+ return;
+ }
+
+ ufunc_T *func = find_ufunc(argvars[2].vval.v_string);
+ if (!func) {
+ // Invalid function name. Error already reported by `find_ufunc`.
+ return;
+ }
+
+ dict_T *dict = argvars[0].vval.v_dict;
+ QUEUE *w = NULL;
+ DictWatcher *watcher = NULL;
+ bool matched = false;
+ QUEUE_FOREACH(w, &dict->watchers) {
+ watcher = dictwatcher_node_data(w);
+ if (func == watcher->callback
+ && !strcmp(watcher->key_pattern, key_pattern)) {
+ matched = true;
+ break;
+ }
+ }
+
+ if (!matched) {
+ EMSG("Couldn't find a watcher matching key and callback");
+ return;
+ }
+
+ QUEUE_REMOVE(w);
+ dictwatcher_free(watcher);
+}
+
/*
* "did_filetype()" function
*/
@@ -8972,6 +9112,7 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action)
dictitem_T *di1;
hashitem_T *hi2;
int todo;
+ bool watched = is_watched(d1);
todo = (int)d2->dv_hashtab.ht_used;
for (hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2) {
@@ -8992,14 +9133,30 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action)
}
if (di1 == NULL) {
di1 = dictitem_copy(HI2DI(hi2));
- if (dict_add(d1, di1) == FAIL)
+ if (dict_add(d1, di1) == FAIL) {
dictitem_free(di1);
+ }
+
+ if (watched) {
+ dictwatcher_notify(d1, (char *)di1->di_key, &di1->di_tv, NULL);
+ }
} else if (*action == 'e') {
EMSG2(_("E737: Key already exists: %s"), hi2->hi_key);
break;
} else if (*action == 'f' && HI2DI(hi2) != di1) {
+ typval_T oldtv;
+
+ if (watched) {
+ copy_tv(&di1->di_tv, &oldtv);
+ }
+
clear_tv(&di1->di_tv);
copy_tv(&HI2DI(hi2)->di_tv, &di1->di_tv);
+
+ if (watched) {
+ dictwatcher_notify(d1, (char *)di1->di_key, &di1->di_tv, &oldtv);
+ clear_tv(&oldtv);
+ }
}
}
}
@@ -10556,9 +10713,7 @@ static void f_has(typval_T *argvars, typval_T *rettv)
#endif
"arabic",
"autocmd",
-#ifdef FEAT_BROWSE
"browsefilter",
-#endif
"byte_offset",
"cindent",
"cmdline_compl",
@@ -13519,6 +13674,9 @@ static void f_remove(typval_T *argvars, typval_T *rettv)
*rettv = di->di_tv;
init_tv(&di->di_tv);
dictitem_remove(d, di);
+ if (is_watched(d)) {
+ dictwatcher_notify(d, (char *)key, NULL, rettv);
+ }
}
}
}
@@ -16678,10 +16836,11 @@ static void f_undofile(typval_T *argvars, typval_T *rettv)
/* If there is no file name there will be no undo file. */
rettv->vval.v_string = NULL;
} else {
- char_u *ffname = (char_u *)FullName_save((char *)fname, FALSE);
+ char *ffname = FullName_save((char *)fname, false);
- if (ffname != NULL)
- rettv->vval.v_string = u_get_undo_file_name(ffname, FALSE);
+ if (ffname != NULL) {
+ rettv->vval.v_string = (char_u *)u_get_undo_file_name(ffname, false);
+ }
xfree(ffname);
}
}
@@ -18110,53 +18269,67 @@ static dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, char_u *varname, in
return HI2DI(hi);
}
-/*
- * Find the hashtab used for a variable name.
- * Set "varname" to the start of name without ':'.
- */
-static hashtab_T *find_var_ht(char_u *name, char_u **varname)
+// Find the dict and hashtable used for a variable name. Set "varname" to the
+// start of name without ':'.
+static hashtab_T *find_var_ht_dict(char_u *name, uint8_t **varname, dict_T **d)
{
hashitem_T *hi;
+ *d = NULL;
if (name[1] != ':') {
- /* The name must not start with a colon or #. */
- if (name[0] == ':' || name[0] == AUTOLOAD_CHAR)
+ // name has implicit scope
+ if (name[0] == ':' || name[0] == AUTOLOAD_CHAR) {
+ // The name must not start with a colon or #.
return NULL;
+ }
*varname = name;
- /* "version" is "v:version" in all scopes */
+ // "version" is "v:version" in all scopes
hi = hash_find(&compat_hashtab, name);
- if (!HASHITEM_EMPTY(hi))
+ if (!HASHITEM_EMPTY(hi)) {
return &compat_hashtab;
+ }
- if (current_funccal == NULL)
- return &globvarht; /* global variable */
- return &current_funccal->l_vars.dv_hashtab; /* l: variable */
+ *d = current_funccal ? &current_funccal->l_vars : &globvardict;
+ goto end;
}
+
*varname = name + 2;
- if (*name == 'g') /* global variable */
- return &globvarht;
- /* There must be no ':' or '#' in the rest of the name, unless g: is used
- */
- if (vim_strchr(name + 2, ':') != NULL
- || vim_strchr(name + 2, AUTOLOAD_CHAR) != NULL)
+ if (*name == 'g') { // global variable
+ *d = &globvardict;
+ } else if (vim_strchr(name + 2, ':') != NULL
+ || vim_strchr(name + 2, AUTOLOAD_CHAR) != NULL) {
+ // There must be no ':' or '#' in the rest of the name if g: was not used
return NULL;
- if (*name == 'b') /* buffer variable */
- return &curbuf->b_vars->dv_hashtab;
- if (*name == 'w') /* window variable */
- return &curwin->w_vars->dv_hashtab;
- if (*name == 't') /* tab page variable */
- return &curtab->tp_vars->dv_hashtab;
- if (*name == 'v') /* v: variable */
- return &vimvarht;
- if (*name == 'a' && current_funccal != NULL) /* function argument */
- return &current_funccal->l_avars.dv_hashtab;
- if (*name == 'l' && current_funccal != NULL) /* local function variable */
- return &current_funccal->l_vars.dv_hashtab;
- if (*name == 's' /* script variable */
- && current_SID > 0 && current_SID <= ga_scripts.ga_len)
- return &SCRIPT_VARS(current_SID);
- return NULL;
+ }
+
+ if (*name == 'b') { // buffer variable
+ *d = curbuf->b_vars;
+ } else if (*name == 'w') { // window variable
+ *d = curwin->w_vars;
+ } else if (*name == 't') { // tab page variable
+ *d = curtab->tp_vars;
+ } else if (*name == 'v') { // v: variable
+ *d = &vimvardict;
+ } else if (*name == 'a' && current_funccal != NULL) { // function argument
+ *d = &current_funccal->l_avars;
+ } else if (*name == 'l' && current_funccal != NULL) { // local variable
+ *d = &current_funccal->l_vars;
+ } else if (*name == 's' // script variable
+ && current_SID > 0 && current_SID <= ga_scripts.ga_len) {
+ *d = &SCRIPT_SV(current_SID)->sv_dict;
+ }
+
+end:
+ return *d ? &(*d)->dv_hashtab : NULL;
+}
+
+// Find the hashtab used for a variable name.
+// Set "varname" to the start of name without ':'.
+static hashtab_T *find_var_ht(uint8_t *name, uint8_t **varname)
+{
+ dict_T *d;
+ return find_var_ht_dict(name, varname, &d);
}
/*
@@ -18220,6 +18393,7 @@ void init_var_dict(dict_T *dict, dictitem_T *dict_var, int scope)
dict_var->di_tv.v_lock = VAR_FIXED;
dict_var->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
dict_var->di_key[0] = NUL;
+ QUEUE_INIT(&dict->watchers);
}
/*
@@ -18352,8 +18526,16 @@ set_var (
dictitem_T *v;
char_u *varname;
hashtab_T *ht;
+ typval_T oldtv;
+ dict_T *dict;
+
+ ht = find_var_ht_dict(name, &varname, &dict);
+ bool watched = is_watched(dict);
+
+ if (watched) {
+ init_tv(&oldtv);
+ }
- ht = find_var_ht(name, &varname);
if (ht == NULL || *varname == NUL) {
EMSG2(_(e_illvar), name);
return;
@@ -18410,6 +18592,9 @@ set_var (
return;
}
+ if (watched) {
+ copy_tv(&v->di_tv, &oldtv);
+ }
clear_tv(&v->di_tv);
} else { /* add a new variable */
/* Can't add "v:" variable. */
@@ -18431,13 +18616,22 @@ set_var (
v->di_flags = 0;
}
- if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT)
+ if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) {
copy_tv(tv, &v->di_tv);
- else {
+ } else {
v->di_tv = *tv;
v->di_tv.v_lock = 0;
init_tv(tv);
}
+
+ if (watched) {
+ if (oldtv.v_type == VAR_UNKNOWN) {
+ dictwatcher_notify(dict, (char *)v->di_key, &v->di_tv, NULL);
+ } else {
+ dictwatcher_notify(dict, (char *)v->di_key, &v->di_tv, &oldtv);
+ clear_tv(&oldtv);
+ }
+ }
}
/*
@@ -21013,9 +21207,9 @@ void last_set_msg(scid_T scriptID)
*/
void ex_oldfiles(exarg_T *eap)
{
- list_T *l = vimvars[VV_OLDFILES].vv_list;
+ list_T *l = get_vim_var_list(VV_OLDFILES);
listitem_T *li;
- int nr = 0;
+ long nr = 0;
if (l == NULL)
msg((char_u *)_("No old files"));
@@ -21023,7 +21217,7 @@ void ex_oldfiles(exarg_T *eap)
msg_start();
msg_scroll = TRUE;
for (li = l->lv_first; li != NULL && !got_int; li = li->li_next) {
- msg_outnum((long)++nr);
+ msg_outnum(++nr);
MSG_PUTS(": ");
msg_outtrans(get_tv_string(&li->li_tv));
msg_putchar('\n');
@@ -21033,12 +21227,33 @@ void ex_oldfiles(exarg_T *eap)
/* Assume "got_int" was set to truncate the listing. */
got_int = FALSE;
+ // File selection prompt on ":oldfiles!"
+ if (eap->forceit) {
+ quit_more = false;
+ nr = prompt_for_number(false);
+ msg_starthere();
+ if (nr > 0 && nr <= l->lv_len) {
+ char_u *p = list_find_str(l, nr);
+ if (p == NULL) {
+ return;
+ }
+ p = expand_env_save(p);
+ eap->arg = p;
+ eap->cmdidx = CMD_edit;
+ do_exedit(eap, NULL);
+ xfree(p);
+ }
+ }
}
}
-
-
-
+// reset v:option_new, v:option_old and v:option_type
+void reset_v_option_vars(void)
+{
+ set_vim_var_string(VV_OPTION_NEW, NULL, -1);
+ set_vim_var_string(VV_OPTION_OLD, NULL, -1);
+ set_vim_var_string(VV_OPTION_TYPE, NULL, -1);
+}
/*
* Adjust a filename, according to a string of modifiers.
@@ -21411,7 +21626,6 @@ static inline bool common_job_callbacks(dict_T *vopts, ufunc_T **on_stdout,
if (get_dict_callback(vopts, "on_stdout", on_stdout)
&& get_dict_callback(vopts, "on_stderr", on_stderr)
&& get_dict_callback(vopts, "on_exit", on_exit)) {
- vopts->internal_refcount++;
vopts->dv_refcount++;
return true;
}
@@ -21473,7 +21687,6 @@ static inline void free_term_job_data_event(void **argv)
}
if (data->self) {
- data->self->internal_refcount--;
dict_unref(data->self);
}
queue_free(data->events);
@@ -21569,8 +21782,10 @@ static void on_process_exit(Process *proc, int status, void *d)
TerminalJobData *data = d;
if (data->term && !data->exited) {
data->exited = true;
- terminal_close(data->term,
- _("\r\n[Program exited, press any key to close]"));
+ char msg[22];
+ snprintf(msg, sizeof msg, "\r\n[Process exited %d]", proc->status);
+ terminal_close(data->term, msg);
+ apply_autocmds(EVENT_TERMCLOSE, NULL, NULL, false, curbuf);
}
if (data->status_ptr) {
@@ -21722,7 +21937,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
true,
NULL);
- arguments->lv_refcount--;
+ list_unref(arguments);
// Restore caller scope information
restore_funccal(provider_caller_scope.funccalp);
provider_caller_scope = saved_provider_caller_scope;
@@ -21758,3 +21973,94 @@ bool eval_has_provider(char *name)
return false;
}
+
+// Compute the `DictWatcher` address from a QUEUE node. This only exists because
+// ASAN doesn't handle `QUEUE_DATA` pointer arithmetic, and we blacklist this
+// function on .asan-blacklist.
+static DictWatcher *dictwatcher_node_data(QUEUE *q)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ return QUEUE_DATA(q, DictWatcher, node);
+}
+
+// Send a change notification to all `dict` watchers that match `key`.
+static void dictwatcher_notify(dict_T *dict, const char *key, typval_T *newtv,
+ typval_T *oldtv)
+ FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(2)
+{
+ typval_T argv[3];
+ for (size_t i = 0; i < ARRAY_SIZE(argv); i++) {
+ init_tv(argv + i);
+ }
+
+ argv[0].v_type = VAR_DICT;
+ argv[0].vval.v_dict = dict;
+ argv[1].v_type = VAR_STRING;
+ argv[1].vval.v_string = (char_u *)xstrdup(key);
+ argv[2].v_type = VAR_DICT;
+ argv[2].vval.v_dict = dict_alloc();
+ argv[2].vval.v_dict->dv_refcount++;
+
+ if (newtv) {
+ dictitem_T *v = dictitem_alloc((char_u *)"new");
+ copy_tv(newtv, &v->di_tv);
+ dict_add(argv[2].vval.v_dict, v);
+ }
+
+ if (oldtv) {
+ dictitem_T *v = dictitem_alloc((char_u *)"old");
+ copy_tv(oldtv, &v->di_tv);
+ dict_add(argv[2].vval.v_dict, v);
+ }
+
+ typval_T rettv;
+
+ QUEUE *w;
+ QUEUE_FOREACH(w, &dict->watchers) {
+ DictWatcher *watcher = dictwatcher_node_data(w);
+ if (!watcher->busy && dictwatcher_matches(watcher, key)) {
+ init_tv(&rettv);
+ watcher->busy = true;
+ call_user_func(watcher->callback, ARRAY_SIZE(argv), argv, &rettv,
+ curwin->w_cursor.lnum, curwin->w_cursor.lnum, NULL);
+ watcher->busy = false;
+ clear_tv(&rettv);
+ }
+ }
+
+ for (size_t i = 1; i < ARRAY_SIZE(argv); i++) {
+ clear_tv(argv + i);
+ }
+}
+
+// Test if `key` matches with with `watcher->key_pattern`
+static bool dictwatcher_matches(DictWatcher *watcher, const char *key)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // For now only allow very simple globbing in key patterns: a '*' at the end
+ // of the string means it should match everything up to the '*' instead of the
+ // whole string.
+ char *nul = strchr(watcher->key_pattern, NUL);
+ size_t len = nul - watcher->key_pattern;
+ if (*(nul - 1) == '*') {
+ return !strncmp(key, watcher->key_pattern, len - 1);
+ } else {
+ return !strcmp(key, watcher->key_pattern);
+ }
+}
+
+// Perform all necessary cleanup for a `DictWatcher` instance.
+static void dictwatcher_free(DictWatcher *watcher)
+ FUNC_ATTR_NONNULL_ALL
+{
+ user_func_unref(watcher->callback);
+ xfree(watcher->key_pattern);
+ xfree(watcher);
+}
+
+// Check if `d` has at least one watcher.
+static bool is_watched(dict_T *d)
+{
+ return d && !QUEUE_EMPTY(&d->watchers);
+}
+
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 864daed716..19a1bbb083 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -5,6 +5,47 @@
#include "nvim/profile.h"
+// All user-defined functions are found in this hashtable.
+EXTERN hashtab_T func_hashtab;
+
+// Structure to hold info for a user function.
+typedef struct ufunc ufunc_T;
+
+struct ufunc {
+ int uf_varargs; ///< variable nr of arguments
+ int uf_flags;
+ int uf_calls; ///< nr of active calls
+ garray_T uf_args; ///< arguments
+ garray_T uf_lines; ///< function lines
+ int uf_profiling; ///< true when func is being profiled
+ // Profiling the function as a whole.
+ int uf_tm_count; ///< nr of calls
+ proftime_T uf_tm_total; ///< time spent in function + children
+ proftime_T uf_tm_self; ///< time spent in function itself
+ proftime_T uf_tm_children; ///< time spent in children this call
+ // Profiling the function per line.
+ int *uf_tml_count; ///< nr of times line was executed
+ proftime_T *uf_tml_total; ///< time spent in a line + children
+ proftime_T *uf_tml_self; ///< time spent in a line itself
+ proftime_T uf_tml_start; ///< start time for current line
+ proftime_T uf_tml_children; ///< time spent in children for this line
+ proftime_T uf_tml_wait; ///< start wait time for current line
+ int uf_tml_idx; ///< index of line being timed; -1 if none
+ int uf_tml_execed; ///< line being timed was executed
+ scid_T uf_script_ID; ///< ID of script where function was defined,
+ // used for s: variables
+ int uf_refcount; ///< for numbered function: reference count
+ char_u uf_name[1]; ///< name of function (actually longer); can
+ // start with <SNR>123_ (<SNR> is K_SPECIAL
+ // KS_EXTRA KE_SNR)
+};
+
+// From user function to hashitem and back.
+EXTERN ufunc_T dumuf;
+#define UF2HIKEY(fp) ((fp)->uf_name)
+#define HIKEY2UF(p) ((ufunc_T *)(p - (dumuf.uf_name - (char_u *)&dumuf)))
+#define HI2UF(hi) HIKEY2UF((hi)->hi_key)
+
/* Defines for Vim variables. These must match vimvars[] in eval.c! */
enum {
VV_COUNT,
@@ -67,6 +108,9 @@ enum {
VV_PROGPATH,
VV_COMMAND_OUTPUT,
VV_COMPLETED_ITEM,
+ VV_OPTION_NEW,
+ VV_OPTION_OLD,
+ VV_OPTION_TYPE,
VV_MSGPACK_TYPES,
VV_LEN, /* number of v: vars */
};
diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h
index 373f1e6278..ed419268d2 100644
--- a/src/nvim/eval_defs.h
+++ b/src/nvim/eval_defs.h
@@ -5,6 +5,7 @@
#include <stddef.h>
#include "nvim/hashtab.h"
+#include "nvim/lib/queue.h"
typedef int varnumber_T;
typedef double float_T;
@@ -117,8 +118,7 @@ struct dictvar_S {
dict_T *dv_copydict; /* copied dict used by deepcopy() */
dict_T *dv_used_next; /* next dict in used dicts list */
dict_T *dv_used_prev; /* previous dict in used dicts list */
- int internal_refcount; // number of internal references to
- // prevent garbage collection
+ QUEUE watchers; // dictionary key watchers set by user code
};
// structure used for explicit stack while garbage collecting hash tables
diff --git a/src/nvim/event/socket.c b/src/nvim/event/socket.c
index 347e464d25..93cc592683 100644
--- a/src/nvim/event/socket.c
+++ b/src/nvim/event/socket.c
@@ -97,7 +97,7 @@ int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb)
result = uv_listen(watcher->stream, backlog, connection_cb);
}
- assert(result <= 0); // libuv should have returned -errno or zero.
+ assert(result <= 0); // libuv should return negative error code or zero.
if (result < 0) {
if (result == -EACCES) {
// Libuv converts ENOENT to EACCES for Windows compatibility, but if
diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c
index 376eb9fce7..71582ab357 100644
--- a/src/nvim/event/stream.c
+++ b/src/nvim/event/stream.c
@@ -13,7 +13,7 @@
/// Sets the stream associated with `fd` to "blocking" mode.
///
-/// @return `0` on success, or `-errno` on failure.
+/// @return `0` on success, or libuv error code on failure.
int stream_set_blocking(int fd, bool blocking)
{
// Private loop to avoid conflict with existing watcher(s):
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 5db3880026..d902234ef7 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -1,17 +1,8 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* ex_cmds.c: some functions for command line commands
*/
#include <assert.h>
-#include <errno.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
@@ -62,7 +53,6 @@
#include "nvim/tempfile.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/version.h"
#include "nvim/window.h"
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
@@ -2122,7 +2112,6 @@ do_ecmd (
goto theend;
if (buf->b_ml.ml_mfp == NULL) { /* no memfile yet */
oldbuf = FALSE;
- buf->b_nwindows = 0;
} else { /* existing memfile */
oldbuf = TRUE;
(void)buf_check_timestamp(buf, FALSE);
@@ -2148,7 +2137,7 @@ do_ecmd (
* Make the (new) buffer the one used by the current window.
* If the old buffer becomes unused, free it if ECMD_HIDE is FALSE.
* If the current buffer was empty and has no file name, curbuf
- * is returned by buflist_new().
+ * is returned by buflist_new(), nothing to do here.
*/
if (buf != curbuf) {
/*
@@ -2235,8 +2224,7 @@ do_ecmd (
}
xfree(new_name);
au_new_curbuf = NULL;
- } else
- ++curbuf->b_nwindows;
+ }
curwin->w_pcmark.lnum = 1;
curwin->w_pcmark.col = 0;
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 77f7dba81b..b7a3505c99 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -267,7 +267,7 @@ return {
},
{
command='buffers',
- flags=bit.bor(BANG, TRLBAR, CMDWIN),
+ flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
func='buflist_list',
},
@@ -885,7 +885,7 @@ return {
},
{
command='files',
- flags=bit.bor(BANG, TRLBAR, CMDWIN),
+ flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
func='buflist_list',
},
@@ -989,13 +989,13 @@ return {
command='gui',
flags=bit.bor(BANG, FILES, EDITCMD, ARGOPT, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_gui',
+ func='ex_nogui',
},
{
command='gvim',
flags=bit.bor(BANG, FILES, EDITCMD, ARGOPT, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_gui',
+ func='ex_nogui',
},
{
command='help',
@@ -1013,7 +1013,7 @@ return {
command='helpfind',
flags=bit.bor(EXTRA, NOTRLCOM),
addr_type=ADDR_LINES,
- func='ex_helpfind',
+ func='ex_ni',
},
{
command='helpgrep',
@@ -1521,7 +1521,7 @@ return {
},
{
command='ls',
- flags=bit.bor(BANG, TRLBAR, CMDWIN),
+ flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
func='buflist_list',
},
@@ -1643,19 +1643,19 @@ return {
command='nbkey',
flags=bit.bor(EXTRA, NOTADR, NEEDARG),
addr_type=ADDR_LINES,
- func='ex_nbkey',
+ func='ex_ni',
},
{
command='nbclose',
flags=bit.bor(TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_nbclose',
+ func='ex_ni',
},
{
command='nbstart',
flags=bit.bor(WORD1, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_nbstart',
+ func='ex_ni',
},
{
command='new',
@@ -1754,12 +1754,6 @@ return {
func='ex_menu',
},
{
- command='open',
- flags=bit.bor(RANGE, BANG, EXTRA),
- addr_type=ADDR_LINES,
- func='ex_open',
- },
- {
command='oldfiles',
flags=bit.bor(BANG, TRLBAR, SBOXOK, CMDWIN),
addr_type=ADDR_LINES,
@@ -1865,7 +1859,7 @@ return {
command='popup',
flags=bit.bor(NEEDARG, EXTRA, BANG, TRLBAR, NOTRLCOM, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_popup',
+ func='ex_ni',
},
{
command='ppop',
@@ -1889,13 +1883,13 @@ return {
command='promptfind',
flags=bit.bor(EXTRA, NOTRLCOM, CMDWIN),
addr_type=ADDR_LINES,
- func='gui_mch_find_dialog',
+ func='ex_ni',
},
{
command='promptrepl',
flags=bit.bor(EXTRA, NOTRLCOM, CMDWIN),
addr_type=ADDR_LINES,
- func='gui_mch_replace_dialog',
+ func='ex_ni',
},
{
command='profile',
@@ -2309,7 +2303,7 @@ return {
command='simalt',
flags=bit.bor(NEEDARG, WORD1, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_simalt',
+ func='ex_ni',
},
{
command='sign',
@@ -3035,7 +3029,7 @@ return {
command='wsverb',
flags=bit.bor(EXTRA, NOTADR, NEEDARG),
addr_type=ADDR_LINES,
- func='ex_wsverb',
+ func='ex_ni',
},
{
command='wshada',
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index f172ea54c2..87a6283310 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -1,17 +1,8 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* ex_cmds2.c: some more functions for command line commands
*/
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
@@ -21,7 +12,6 @@
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
-#include "nvim/version.h"
#include "nvim/ex_cmds2.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
@@ -762,9 +752,14 @@ void ex_profile(exarg_T *eap)
do_profiling = PROF_YES;
profile_set_wait(profile_zero());
set_vim_var_nr(VV_PROFILING, 1L);
- } else if (do_profiling == PROF_NONE)
+ } else if (do_profiling == PROF_NONE) {
EMSG(_("E750: First use \":profile start {fname}\""));
- else if (STRCMP(eap->arg, "pause") == 0) {
+ } else if (STRCMP(eap->arg, "stop") == 0) {
+ profile_dump();
+ do_profiling = PROF_NONE;
+ set_vim_var_nr(VV_PROFILING, 0L);
+ profile_reset();
+ } else if (STRCMP(eap->arg, "pause") == 0) {
if (do_profiling == PROF_YES)
pause_time = profile_start();
do_profiling = PROF_PAUSED;
@@ -774,6 +769,8 @@ void ex_profile(exarg_T *eap)
profile_set_wait(profile_add(profile_get_wait(), pause_time));
}
do_profiling = PROF_YES;
+ } else if (STRCMP(eap->arg, "dump") == 0) {
+ profile_dump();
} else {
/* The rest is similar to ":breakadd". */
ex_breakadd(eap);
@@ -817,18 +814,14 @@ static enum {
} pexpand_what;
static char *pexpand_cmds[] = {
- "start",
-#define PROFCMD_START 0
- "pause",
-#define PROFCMD_PAUSE 1
"continue",
-#define PROFCMD_CONTINUE 2
- "func",
-#define PROFCMD_FUNC 3
+ "dump",
"file",
-#define PROFCMD_FILE 4
+ "func",
+ "pause",
+ "start",
+ "stop",
NULL
-#define PROFCMD_LAST 5
};
/*
@@ -891,10 +884,63 @@ void profile_dump(void)
}
}
-/*
- * Start profiling script "fp".
- */
-static void script_do_profile(scriptitem_T *si)
+/// Reset all profiling information.
+static void profile_reset(void)
+{
+ // Reset sourced files.
+ for (int id = 1; id <= script_items.ga_len; id++) {
+ scriptitem_T *si = &SCRIPT_ITEM(id);
+ if (si->sn_prof_on) {
+ si->sn_prof_on = 0;
+ si->sn_pr_force = 0;
+ si->sn_pr_child = profile_zero();
+ si->sn_pr_nest = 0;
+ si->sn_pr_count = 0;
+ si->sn_pr_total = profile_zero();
+ si->sn_pr_self = profile_zero();
+ si->sn_pr_start = profile_zero();
+ si->sn_pr_children = profile_zero();
+ ga_clear(&si->sn_prl_ga);
+ si->sn_prl_start = profile_zero();
+ si->sn_prl_children = profile_zero();
+ si->sn_prl_wait = profile_zero();
+ si->sn_prl_idx = -1;
+ si->sn_prl_execed = 0;
+ }
+ }
+
+ // Reset functions.
+ size_t n = func_hashtab.ht_used;
+ hashitem_T *hi = func_hashtab.ht_array;
+
+ for (; n > (size_t)0; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ n--;
+ ufunc_T *uf = HI2UF(hi);
+ if (uf->uf_profiling) {
+ uf->uf_profiling = 0;
+ uf->uf_tm_count = 0;
+ uf->uf_tm_total = profile_zero();
+ uf->uf_tm_self = profile_zero();
+ uf->uf_tm_children = profile_zero();
+ uf->uf_tml_count = NULL;
+ uf->uf_tml_total = NULL;
+ uf->uf_tml_self = NULL;
+ uf->uf_tml_start = profile_zero();
+ uf->uf_tml_children = profile_zero();
+ uf->uf_tml_wait = profile_zero();
+ uf->uf_tml_idx = -1;
+ uf->uf_tml_execed = 0;
+ }
+ }
+ }
+
+ xfree(profile_fname);
+ profile_fname = NULL;
+}
+
+/// Start profiling a script.
+static void profile_init(scriptitem_T *si)
{
si->sn_pr_count = 0;
si->sn_pr_total = profile_zero();
@@ -2357,9 +2403,7 @@ do_source (
*/
p = path_tail(fname_exp);
if ((*p == '.' || *p == '_')
- && (STRICMP(p + 1, "nvimrc") == 0
- || STRICMP(p + 1, "ngvimrc") == 0
- || STRICMP(p + 1, "exrc") == 0)) {
+ && (STRICMP(p + 1, "nvimrc") == 0 || STRICMP(p + 1, "exrc") == 0)) {
if (*p == '_')
*p = '.';
else
@@ -2510,8 +2554,8 @@ do_source (
int forceit;
/* Check if we do profiling for this script. */
- if (!si->sn_prof_on && has_profiling(TRUE, si->sn_name, &forceit)) {
- script_do_profile(si);
+ if (!si->sn_prof_on && has_profiling(true, si->sn_name, &forceit)) {
+ profile_init(si);
si->sn_pr_force = forceit;
}
if (si->sn_prof_on) {
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index 4065cc2fdc..10d2eb688e 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -1,9 +1,3 @@
-/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- */
#ifndef NVIM_EX_CMDS_DEFS_H
#define NVIM_EX_CMDS_DEFS_H
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index a9262ca6ea..59bda9345e 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -1,12 +1,4 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* ex_docmd.c: functions for executing an Ex command line.
*/
@@ -14,7 +6,6 @@
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
-#include <errno.h>
#include <inttypes.h>
#include "nvim/vim.h"
@@ -80,6 +71,9 @@
static int quitmore = 0;
static int ex_pressedreturn = FALSE;
+/* whether ":lcd" was produced for a session */
+static int did_lcd;
+
typedef struct ucmd {
char_u *uc_name; /* The command name */
uint32_t uc_argt; /* The argument type */
@@ -144,23 +138,9 @@ struct dbg_stuff {
# include "ex_docmd.c.generated.h"
#endif
-# define ex_gui ex_nogui
-# define ex_popup ex_ni
-# define ex_simalt ex_ni
-# define gui_mch_find_dialog ex_ni
-# define gui_mch_replace_dialog ex_ni
-# define ex_helpfind ex_ni
-static int did_lcd; /* whether ":lcd" was produced for a session */
#ifndef HAVE_WORKING_LIBINTL
# define ex_language ex_ni
#endif
-# define ex_wsverb ex_ni
-# define ex_nbclose ex_ni
-# define ex_nbkey ex_ni
-# define ex_nbstart ex_ni
-
-
-
/*
* Declare cmdnames[].
@@ -3466,6 +3446,7 @@ static linenr_T get_address(char_u **ptr,
}
if (addr_type != ADDR_LINES) {
EMSG(_(e_invaddr));
+ cmd = NULL;
goto error;
}
if (skip)
@@ -3493,6 +3474,7 @@ static linenr_T get_address(char_u **ptr,
c = *cmd++;
if (addr_type != ADDR_LINES) {
EMSG(_(e_invaddr));
+ cmd = NULL;
goto error;
}
if (skip) { /* skip "/pat/" */
@@ -3536,6 +3518,7 @@ static linenr_T get_address(char_u **ptr,
++cmd;
if (addr_type != ADDR_LINES) {
EMSG(_(e_invaddr));
+ cmd = NULL;
goto error;
}
if (*cmd == '&')
@@ -3607,7 +3590,8 @@ static linenr_T get_address(char_u **ptr,
else
n = getdigits(&cmd);
if (addr_type == ADDR_LOADED_BUFFERS || addr_type == ADDR_BUFFERS)
- lnum = compute_buffer_local_count(addr_type, lnum, (i == '-') ? -1 * n : n);
+ lnum = compute_buffer_local_count(
+ addr_type, lnum, (i == '-') ? -1 * n : n);
else if (i == '-')
lnum -= n;
else
@@ -3675,7 +3659,8 @@ static char_u *invalid_range(exarg_T *eap)
}
break;
case ADDR_ARGUMENTS:
- if (eap->line2 > ARGCOUNT + (!ARGCOUNT)) { // add 1 if ARGCOUNT is 0
+ // add 1 if ARGCOUNT is 0
+ if (eap->line2 > ARGCOUNT + (!ARGCOUNT)) {
return (char_u *)_(e_invrange);
}
break;
@@ -6467,40 +6452,6 @@ static void ex_find(exarg_T *eap)
}
/*
- * ":open" simulation: for now just work like ":visual".
- */
-static void ex_open(exarg_T *eap)
-{
- regmatch_T regmatch;
- char_u *p;
-
- curwin->w_cursor.lnum = eap->line2;
- beginline(BL_SOL | BL_FIX);
- if (*eap->arg == '/') {
- /* ":open /pattern/": put cursor in column found with pattern */
- ++eap->arg;
- p = skip_regexp(eap->arg, '/', p_magic, NULL);
- *p = NUL;
- regmatch.regprog = vim_regcomp(eap->arg, p_magic ? RE_MAGIC : 0);
- if (regmatch.regprog != NULL) {
- regmatch.rm_ic = p_ic;
- p = get_cursor_line_ptr();
- if (vim_regexec(&regmatch, p, (colnr_T)0))
- curwin->w_cursor.col = (colnr_T)(regmatch.startp[0] - p);
- else
- EMSG(_(e_nomatch));
- vim_regfree(regmatch.regprog);
- }
- /* Move to the NUL, ignore any other arguments. */
- eap->arg += STRLEN(eap->arg);
- }
- check_cursor();
-
- eap->cmdidx = CMD_visual;
- do_exedit(eap, NULL);
-}
-
-/*
* ":edit", ":badd", ":visual".
*/
static void ex_edit(exarg_T *eap)
@@ -6544,7 +6495,7 @@ do_exedit (
msg_scroll = 0;
must_redraw = CLEAR;
- main_loop(FALSE, TRUE);
+ normal_enter(false, true);
RedrawingDisabled = rd;
no_wait_return = nwr;
@@ -7193,7 +7144,7 @@ static void ex_wundo(exarg_T *eap)
char_u hash[UNDO_HASH_SIZE];
u_compute_hash(hash);
- u_write_undo(eap->arg, eap->forceit, curbuf, hash);
+ u_write_undo((char *) eap->arg, eap->forceit, curbuf, hash);
}
static void ex_rundo(exarg_T *eap)
@@ -7201,7 +7152,7 @@ static void ex_rundo(exarg_T *eap)
char_u hash[UNDO_HASH_SIZE];
u_compute_hash(hash);
- u_read_undo(eap->arg, hash, NULL);
+ u_read_undo((char *) eap->arg, hash, NULL);
}
/*
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index bea1aecb58..bf67047ae8 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -1,12 +1,4 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* ex_eval.c: functions for Ex command line for the +eval feature.
*/
#include <assert.h>
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 50e9ce7c17..6d81f3680a 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -1,17 +1,8 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* ex_getln.c: Functions for entering and editing an Ex command line.
*/
#include <assert.h>
-#include <errno.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
@@ -58,6 +49,7 @@
#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/strings.h"
+#include "nvim/state.h"
#include "nvim/syntax.h"
#include "nvim/tag.h"
#include "nvim/window.h"
@@ -91,6 +83,45 @@ struct cmdline_info {
int input_fn; /* when TRUE Invoked for input() function */
};
+typedef struct command_line_state {
+ VimState state;
+ int firstc;
+ long count;
+ int indent;
+ int c;
+ int i;
+ int j;
+ int gotesc; // TRUE when <ESC> just typed
+ int do_abbr; // when TRUE check for abbr.
+ char_u *lookfor; // string to match
+ int hiscnt; // current history line in use
+ int histype; // history type to be used
+ pos_T old_cursor;
+ colnr_T old_curswant;
+ colnr_T old_leftcol;
+ linenr_T old_topline;
+ int old_topfill;
+ linenr_T old_botline;
+ int did_incsearch;
+ int incsearch_postponed;
+ int did_wild_list; // did wild_list() recently
+ int wim_index; // index in wim_flags[]
+ int res;
+ int save_msg_scroll;
+ int save_State; // remember State when called
+ int some_key_typed; // one of the keys was typed
+ // mouse drag and release events are ignored, unless they are
+ // preceded with a mouse down event
+ int ignore_drag_release;
+ int break_ctrl_c;
+ expand_T xpc;
+ long *b_im_ptr;
+ // Everything that may work recursively should save and restore the
+ // current command line in save_ccline. That includes update_screen(), a
+ // custom status line may invoke ":normal".
+ struct cmdline_info save_ccline;
+} CommandLineState;
+
/* The current cmdline_info. It is initialized in getcmdline() and after that
* used by other functions. When invoking getcmdline() recursively it needs
* to be saved with save_cmdline() and restored with restore_cmdline().
@@ -120,1400 +151,1486 @@ static int hislen = 0; /* actual length of history tables */
static int cmd_hkmap = 0; /* Hebrew mapping during command line */
static int cmd_fkmap = 0; /* Farsi mapping during command line */
-
-/*
- * getcmdline() - accept a command line starting with firstc.
- *
- * firstc == ':' get ":" command line.
- * firstc == '/' or '?' get search pattern
- * firstc == '=' get expression
- * firstc == '@' get text for input() function
- * firstc == '>' get text for debug mode
- * firstc == NUL get text for :insert command
- * firstc == -1 like NUL, and break on CTRL-C
- *
- * The line is collected in ccline.cmdbuff, which is reallocated to fit the
- * command line.
- *
- * Careful: getcmdline() can be called recursively!
- *
- * Return pointer to allocated string if there is a commandline, NULL
- * otherwise.
- */
-char_u *
-getcmdline (
- int firstc,
- long count, /* only used for incremental search */
- int indent /* indent for inside conditionals */
-)
+static uint8_t *command_line_enter(int firstc, long count, int indent)
{
- int c;
- int i;
- int j;
- int gotesc = FALSE; /* TRUE when <ESC> just typed */
- int do_abbr; /* when TRUE check for abbr. */
- char_u *lookfor = NULL; /* string to match */
- int hiscnt; /* current history line in use */
- int histype; /* history type to be used */
- pos_T old_cursor;
- colnr_T old_curswant;
- colnr_T old_leftcol;
- linenr_T old_topline;
- int old_topfill;
- linenr_T old_botline;
- int did_incsearch = FALSE;
- int incsearch_postponed = FALSE;
- int did_wild_list = FALSE; /* did wild_list() recently */
- int wim_index = 0; /* index in wim_flags[] */
- int res;
- int save_msg_scroll = msg_scroll;
- int save_State = State; /* remember State when called */
- int some_key_typed = FALSE; /* one of the keys was typed */
- /* mouse drag and release events are ignored, unless they are
- * preceded with a mouse down event */
- int ignore_drag_release = TRUE;
- int break_ctrl_c = FALSE;
- expand_T xpc;
- long *b_im_ptr = NULL;
- /* Everything that may work recursively should save and restore the
- * current command line in save_ccline. That includes update_screen(), a
- * custom status line may invoke ":normal". */
- struct cmdline_info save_ccline;
-
- if (firstc == -1) {
- firstc = NUL;
- break_ctrl_c = TRUE;
+ CommandLineState state, *s = &state;
+ memset(s, 0, sizeof(CommandLineState));
+ s->firstc = firstc;
+ s->count = count;
+ s->indent = indent;
+ s->save_msg_scroll = msg_scroll;
+ s->save_State = State;
+ s->ignore_drag_release = true;
+
+ if (s->firstc == -1) {
+ s->firstc = NUL;
+ s->break_ctrl_c = true;
}
- /* start without Hebrew mapping for a command line */
- if (firstc == ':' || firstc == '=' || firstc == '>')
+
+ // start without Hebrew mapping for a command line
+ if (s->firstc == ':' || s->firstc == '=' || s->firstc == '>') {
cmd_hkmap = 0;
+ }
- ccline.overstrike = FALSE; /* always start in insert mode */
- old_cursor = curwin->w_cursor; /* needs to be restored later */
- old_curswant = curwin->w_curswant;
- old_leftcol = curwin->w_leftcol;
- old_topline = curwin->w_topline;
- old_topfill = curwin->w_topfill;
- old_botline = curwin->w_botline;
+ ccline.overstrike = false; // always start in insert mode
+ s->old_cursor = curwin->w_cursor; // needs to be restored later
+ s->old_curswant = curwin->w_curswant;
+ s->old_leftcol = curwin->w_leftcol;
+ s->old_topline = curwin->w_topline;
+ s->old_topfill = curwin->w_topfill;
+ s->old_botline = curwin->w_botline;
- /*
- * set some variables for redrawcmd()
- */
- ccline.cmdfirstc = (firstc == '@' ? 0 : firstc);
- ccline.cmdindent = (firstc > 0 ? indent : 0);
+ // set some variables for redrawcmd()
+ ccline.cmdfirstc = (s->firstc == '@' ? 0 : s->firstc);
+ ccline.cmdindent = (s->firstc > 0 ? s->indent : 0);
- /* alloc initial ccline.cmdbuff */
- alloc_cmdbuff(exmode_active ? 250 : indent + 1);
+ // alloc initial ccline.cmdbuff
+ alloc_cmdbuff(exmode_active ? 250 : s->indent + 1);
ccline.cmdlen = ccline.cmdpos = 0;
ccline.cmdbuff[0] = NUL;
- /* autoindent for :insert and :append */
- if (firstc <= 0) {
- memset(ccline.cmdbuff, ' ', indent);
- ccline.cmdbuff[indent] = NUL;
- ccline.cmdpos = indent;
- ccline.cmdspos = indent;
- ccline.cmdlen = indent;
+ // autoindent for :insert and :append
+ if (s->firstc <= 0) {
+ memset(ccline.cmdbuff, ' ', s->indent);
+ ccline.cmdbuff[s->indent] = NUL;
+ ccline.cmdpos = s->indent;
+ ccline.cmdspos = s->indent;
+ ccline.cmdlen = s->indent;
}
- ExpandInit(&xpc);
- ccline.xpc = &xpc;
+ ExpandInit(&s->xpc);
+ ccline.xpc = &s->xpc;
if (curwin->w_p_rl && *curwin->w_p_rlc == 's'
- && (firstc == '/' || firstc == '?'))
- cmdmsg_rl = TRUE;
- else
- cmdmsg_rl = FALSE;
+ && (s->firstc == '/' || s->firstc == '?')) {
+ cmdmsg_rl = true;
+ } else {
+ cmdmsg_rl = false;
+ }
- redir_off = TRUE; /* don't redirect the typed command */
+ redir_off = true; // don't redirect the typed command
if (!cmd_silent) {
- i = msg_scrolled;
- msg_scrolled = 0; /* avoid wait_return message */
- gotocmdline(TRUE);
- msg_scrolled += i;
- redrawcmdprompt(); /* draw prompt or indent */
+ s->i = msg_scrolled;
+ msg_scrolled = 0; // avoid wait_return message
+ gotocmdline(true);
+ msg_scrolled += s->i;
+ redrawcmdprompt(); // draw prompt or indent
set_cmdspos();
}
- xpc.xp_context = EXPAND_NOTHING;
- xpc.xp_backslash = XP_BS_NONE;
+ s->xpc.xp_context = EXPAND_NOTHING;
+ s->xpc.xp_backslash = XP_BS_NONE;
#ifndef BACKSLASH_IN_FILENAME
- xpc.xp_shell = FALSE;
+ s->xpc.xp_shell = false;
#endif
if (ccline.input_fn) {
- xpc.xp_context = ccline.xp_context;
- xpc.xp_pattern = ccline.cmdbuff;
- xpc.xp_arg = ccline.xp_arg;
+ s->xpc.xp_context = ccline.xp_context;
+ s->xpc.xp_pattern = ccline.cmdbuff;
+ s->xpc.xp_arg = ccline.xp_arg;
}
- /*
- * Avoid scrolling when called by a recursive do_cmdline(), e.g. when
- * doing ":@0" when register 0 doesn't contain a CR.
- */
- msg_scroll = FALSE;
+ // Avoid scrolling when called by a recursive do_cmdline(), e.g. when
+ // doing ":@0" when register 0 doesn't contain a CR.
+ msg_scroll = false;
State = CMDLINE;
- if (firstc == '/' || firstc == '?' || firstc == '@') {
- /* Use ":lmap" mappings for search pattern and input(). */
- if (curbuf->b_p_imsearch == B_IMODE_USE_INSERT)
- b_im_ptr = &curbuf->b_p_iminsert;
- else
- b_im_ptr = &curbuf->b_p_imsearch;
- if (*b_im_ptr == B_IMODE_LMAP)
+ if (s->firstc == '/' || s->firstc == '?' || s->firstc == '@') {
+ // Use ":lmap" mappings for search pattern and input().
+ if (curbuf->b_p_imsearch == B_IMODE_USE_INSERT) {
+ s->b_im_ptr = &curbuf->b_p_iminsert;
+ } else {
+ s->b_im_ptr = &curbuf->b_p_imsearch;
+ }
+
+ if (*s->b_im_ptr == B_IMODE_LMAP) {
State |= LANGMAP;
+ }
}
setmouse();
- ui_cursor_shape(); /* may show different cursor shape */
+ ui_cursor_shape(); // may show different cursor shape
init_history();
- hiscnt = hislen; /* set hiscnt to impossible history value */
- histype = hist_char2type(firstc);
-
- do_digraph(-1); /* init digraph typeahead */
+ s->hiscnt = hislen; // set hiscnt to impossible history value
+ s->histype = hist_char2type(s->firstc);
+ do_digraph(-1); // init digraph typeahead
// If something above caused an error, reset the flags, we do want to type
// and execute commands. Display may be messed up a bit.
if (did_emsg) {
redrawcmd();
}
- did_emsg = FALSE;
- got_int = FALSE;
- /*
- * Collect the command string, handling editing keys.
- */
- for (;; ) {
- redir_off = TRUE; /* Don't redirect the typed command.
- Repeated, because a ":redir" inside
- completion may switch it on. */
- quit_more = FALSE; /* reset after CTRL-D which had a more-prompt */
+ did_emsg = false;
+ got_int = false;
+ s->state.check = command_line_check;
+ s->state.execute = command_line_execute;
+ state_enter(&s->state);
- cursorcmd(); /* set the cursor on the right spot */
+ cmdmsg_rl = false;
- /* Get a character. Ignore K_IGNORE, it should not do anything, such
- * as stop completion. */
- input_enable_events();
- do {
- c = safe_vgetc();
- } while (c == K_IGNORE || c == K_PASTE);
- input_disable_events();
+ cmd_fkmap = 0;
- if (c == K_EVENT) {
- queue_process_events(loop.events);
- continue;
+ ExpandCleanup(&s->xpc);
+ ccline.xpc = NULL;
+
+ if (s->did_incsearch) {
+ curwin->w_cursor = s->old_cursor;
+ curwin->w_curswant = s->old_curswant;
+ curwin->w_leftcol = s->old_leftcol;
+ curwin->w_topline = s->old_topline;
+ curwin->w_topfill = s->old_topfill;
+ curwin->w_botline = s->old_botline;
+ highlight_match = false;
+ validate_cursor(); // needed for TAB
+ redraw_later(SOME_VALID);
+ }
+
+ if (ccline.cmdbuff != NULL) {
+ // Put line in history buffer (":" and "=" only when it was typed).
+ if (ccline.cmdlen && s->firstc != NUL
+ && (s->some_key_typed || s->histype == HIST_SEARCH)) {
+ add_to_history(s->histype, ccline.cmdbuff, true,
+ s->histype == HIST_SEARCH ? s->firstc : NUL);
+ if (s->firstc == ':') {
+ xfree(new_last_cmdline);
+ new_last_cmdline = vim_strsave(ccline.cmdbuff);
+ }
}
- if (KeyTyped) {
- some_key_typed = TRUE;
- if (cmd_hkmap)
- c = hkmap(c);
- if (cmd_fkmap)
- c = cmdl_fkmap(c);
- if (cmdmsg_rl && !KeyStuffed) {
- /* Invert horizontal movements and operations. Only when
- * typed by the user directly, not when the result of a
- * mapping. */
- switch (c) {
- case K_RIGHT: c = K_LEFT; break;
- case K_S_RIGHT: c = K_S_LEFT; break;
- case K_C_RIGHT: c = K_C_LEFT; break;
- case K_LEFT: c = K_RIGHT; break;
- case K_S_LEFT: c = K_S_RIGHT; break;
- case K_C_LEFT: c = K_C_RIGHT; break;
- }
+ if (s->gotesc) { // abandon command line
+ xfree(ccline.cmdbuff);
+ ccline.cmdbuff = NULL;
+ if (msg_scrolled == 0) {
+ compute_cmdrow();
}
+ MSG("");
+ redraw_cmdline = true;
}
+ }
- /*
- * Ignore got_int when CTRL-C was typed here.
- * Don't ignore it in :global, we really need to break then, e.g., for
- * ":g/pat/normal /pat" (without the <CR>).
- * Don't ignore it for the input() function.
- */
- if ((c == Ctrl_C
-#ifdef UNIX
- || c == intr_char
-#endif
- )
- && firstc != '@'
- && !break_ctrl_c
- && !global_busy)
- got_int = FALSE;
-
- /* free old command line when finished moving around in the history
- * list */
- if (lookfor != NULL
- && c != K_S_DOWN && c != K_S_UP
- && c != K_DOWN && c != K_UP
- && c != K_PAGEDOWN && c != K_PAGEUP
- && c != K_KPAGEDOWN && c != K_KPAGEUP
- && c != K_LEFT && c != K_RIGHT
- && (xpc.xp_numfiles > 0 || (c != Ctrl_P && c != Ctrl_N))) {
- xfree(lookfor);
- lookfor = NULL;
+ // If the screen was shifted up, redraw the whole screen (later).
+ // If the line is too long, clear it, so ruler and shown command do
+ // not get printed in the middle of it.
+ msg_check();
+ msg_scroll = s->save_msg_scroll;
+ redir_off = false;
+
+ // When the command line was typed, no need for a wait-return prompt.
+ if (s->some_key_typed) {
+ need_wait_return = false;
+ }
+
+ State = s->save_State;
+ setmouse();
+ ui_cursor_shape(); // may show different cursor shape
+
+ {
+ char_u *p = ccline.cmdbuff;
+
+ // Make ccline empty, getcmdline() may try to use it.
+ ccline.cmdbuff = NULL;
+ return p;
+ }
+}
+
+static int command_line_check(VimState *state)
+{
+ redir_off = true; // Don't redirect the typed command.
+ // Repeated, because a ":redir" inside
+ // completion may switch it on.
+ quit_more = false; // reset after CTRL-D which had a more-prompt
+
+ cursorcmd(); // set the cursor on the right spot
+ return 1;
+}
+
+static int command_line_execute(VimState *state, int key)
+{
+ if (key == K_IGNORE || key == K_PASTE) {
+ return -1; // get another key
+ }
+
+ CommandLineState *s = (CommandLineState *)state;
+ s->c = key;
+
+ if (s->c == K_EVENT) {
+ queue_process_events(loop.events);
+ return 1;
+ }
+
+ if (KeyTyped) {
+ s->some_key_typed = true;
+ if (cmd_hkmap) {
+ s->c = hkmap(s->c);
}
- /*
- * When there are matching completions to select <S-Tab> works like
- * CTRL-P (unless 'wc' is <S-Tab>).
- */
- if (c != p_wc && c == K_S_TAB && xpc.xp_numfiles > 0)
- c = Ctrl_P;
-
- /* Special translations for 'wildmenu' */
- if (did_wild_list && p_wmnu) {
- if (c == K_LEFT)
- c = Ctrl_P;
- else if (c == K_RIGHT)
- c = Ctrl_N;
+ if (cmd_fkmap) {
+ s->c = cmdl_fkmap(s->c);
}
- /* Hitting CR after "emenu Name.": complete submenu */
- if (xpc.xp_context == EXPAND_MENUNAMES && p_wmnu
- && ccline.cmdpos > 1
- && ccline.cmdbuff[ccline.cmdpos - 1] == '.'
- && ccline.cmdbuff[ccline.cmdpos - 2] != '\\'
- && (c == '\n' || c == '\r' || c == K_KENTER))
- c = K_DOWN;
-
- /* free expanded names when finished walking through matches */
- if (xpc.xp_numfiles != -1
- && !(c == p_wc && KeyTyped) && c != p_wcm
- && c != Ctrl_N && c != Ctrl_P && c != Ctrl_A
- && c != Ctrl_L) {
- (void)ExpandOne(&xpc, NULL, NULL, 0, WILD_FREE);
- did_wild_list = FALSE;
- if (!p_wmnu || (c != K_UP && c != K_DOWN))
- xpc.xp_context = EXPAND_NOTHING;
- wim_index = 0;
- if (p_wmnu && wild_menu_showing != 0) {
- int skt = KeyTyped;
- int old_RedrawingDisabled = RedrawingDisabled;
-
- if (ccline.input_fn)
- RedrawingDisabled = 0;
-
- if (wild_menu_showing == WM_SCROLLED) {
- /* Entered command line, move it up */
- cmdline_row--;
- redrawcmd();
- } else if (save_p_ls != -1) {
- /* restore 'laststatus' and 'winminheight' */
- p_ls = save_p_ls;
- p_wmh = save_p_wmh;
- last_status(FALSE);
- save_cmdline(&save_ccline);
- update_screen(VALID); /* redraw the screen NOW */
- restore_cmdline(&save_ccline);
- redrawcmd();
- save_p_ls = -1;
- } else {
- win_redraw_last_status(topframe);
- redraw_statuslines();
- }
- KeyTyped = skt;
- wild_menu_showing = 0;
- if (ccline.input_fn)
- RedrawingDisabled = old_RedrawingDisabled;
+
+ if (cmdmsg_rl && !KeyStuffed) {
+ // Invert horizontal movements and operations. Only when
+ // typed by the user directly, not when the result of a
+ // mapping.
+ switch (s->c) {
+ case K_RIGHT: s->c = K_LEFT; break;
+ case K_S_RIGHT: s->c = K_S_LEFT; break;
+ case K_C_RIGHT: s->c = K_C_LEFT; break;
+ case K_LEFT: s->c = K_RIGHT; break;
+ case K_S_LEFT: s->c = K_S_RIGHT; break;
+ case K_C_LEFT: s->c = K_C_RIGHT; break;
}
}
+ }
- /* Special translations for 'wildmenu' */
- if (xpc.xp_context == EXPAND_MENUNAMES && p_wmnu) {
- /* Hitting <Down> after "emenu Name.": complete submenu */
- if (c == K_DOWN && ccline.cmdpos > 0
- && ccline.cmdbuff[ccline.cmdpos - 1] == '.')
- c = p_wc;
- else if (c == K_UP) {
- /* Hitting <Up>: Remove one submenu name in front of the
- * cursor */
- int found = FALSE;
-
- j = (int)(xpc.xp_pattern - ccline.cmdbuff);
- i = 0;
- while (--j > 0) {
- /* check for start of menu name */
- if (ccline.cmdbuff[j] == ' '
- && ccline.cmdbuff[j - 1] != '\\') {
- i = j + 1;
+ // Ignore got_int when CTRL-C was typed here.
+ // Don't ignore it in :global, we really need to break then, e.g., for
+ // ":g/pat/normal /pat" (without the <CR>).
+ // Don't ignore it for the input() function.
+ if ((s->c == Ctrl_C)
+ && s->firstc != '@'
+ && !s->break_ctrl_c
+ && !global_busy) {
+ got_int = false;
+ }
+
+ // free old command line when finished moving around in the history
+ // list
+ if (s->lookfor != NULL
+ && s->c != K_S_DOWN && s->c != K_S_UP
+ && s->c != K_DOWN && s->c != K_UP
+ && s->c != K_PAGEDOWN && s->c != K_PAGEUP
+ && s->c != K_KPAGEDOWN && s->c != K_KPAGEUP
+ && s->c != K_LEFT && s->c != K_RIGHT
+ && (s->xpc.xp_numfiles > 0 || (s->c != Ctrl_P && s->c != Ctrl_N))) {
+ xfree(s->lookfor);
+ s->lookfor = NULL;
+ }
+
+ // When there are matching completions to select <S-Tab> works like
+ // CTRL-P (unless 'wc' is <S-Tab>).
+ if (s->c != p_wc && s->c == K_S_TAB && s->xpc.xp_numfiles > 0) {
+ s->c = Ctrl_P;
+ }
+
+ // Special translations for 'wildmenu'
+ if (s->did_wild_list && p_wmnu) {
+ if (s->c == K_LEFT) {
+ s->c = Ctrl_P;
+ } else if (s->c == K_RIGHT) {
+ s->c = Ctrl_N;
+ }
+ }
+
+ // Hitting CR after "emenu Name.": complete submenu
+ if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu
+ && ccline.cmdpos > 1
+ && ccline.cmdbuff[ccline.cmdpos - 1] == '.'
+ && ccline.cmdbuff[ccline.cmdpos - 2] != '\\'
+ && (s->c == '\n' || s->c == '\r' || s->c == K_KENTER)) {
+ s->c = K_DOWN;
+ }
+
+ // free expanded names when finished walking through matches
+ if (s->xpc.xp_numfiles != -1
+ && !(s->c == p_wc && KeyTyped) && s->c != p_wcm
+ && s->c != Ctrl_N && s->c != Ctrl_P && s->c != Ctrl_A
+ && s->c != Ctrl_L) {
+ (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE);
+ s->did_wild_list = false;
+ if (!p_wmnu || (s->c != K_UP && s->c != K_DOWN)) {
+ s->xpc.xp_context = EXPAND_NOTHING;
+ }
+ s->wim_index = 0;
+ if (p_wmnu && wild_menu_showing != 0) {
+ int skt = KeyTyped;
+ int old_RedrawingDisabled = RedrawingDisabled;
+
+ if (ccline.input_fn) {
+ RedrawingDisabled = 0;
+ }
+
+ if (wild_menu_showing == WM_SCROLLED) {
+ // Entered command line, move it up
+ cmdline_row--;
+ redrawcmd();
+ } else if (save_p_ls != -1) {
+ // restore 'laststatus' and 'winminheight'
+ p_ls = save_p_ls;
+ p_wmh = save_p_wmh;
+ last_status(false);
+ save_cmdline(&s->save_ccline);
+ update_screen(VALID); // redraw the screen NOW
+ restore_cmdline(&s->save_ccline);
+ redrawcmd();
+ save_p_ls = -1;
+ } else {
+ win_redraw_last_status(topframe);
+ redraw_statuslines();
+ }
+ KeyTyped = skt;
+ wild_menu_showing = 0;
+ if (ccline.input_fn) {
+ RedrawingDisabled = old_RedrawingDisabled;
+ }
+ }
+ }
+
+ // Special translations for 'wildmenu'
+ if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu) {
+ // Hitting <Down> after "emenu Name.": complete submenu
+ if (s->c == K_DOWN && ccline.cmdpos > 0
+ && ccline.cmdbuff[ccline.cmdpos - 1] == '.') {
+ s->c = p_wc;
+ } else if (s->c == K_UP) {
+ // Hitting <Up>: Remove one submenu name in front of the
+ // cursor
+ int found = false;
+
+ s->j = (int)(s->xpc.xp_pattern - ccline.cmdbuff);
+ s->i = 0;
+ while (--s->j > 0) {
+ // check for start of menu name
+ if (ccline.cmdbuff[s->j] == ' '
+ && ccline.cmdbuff[s->j - 1] != '\\') {
+ s->i = s->j + 1;
+ break;
+ }
+
+ // check for start of submenu name
+ if (ccline.cmdbuff[s->j] == '.'
+ && ccline.cmdbuff[s->j - 1] != '\\') {
+ if (found) {
+ s->i = s->j + 1;
break;
- }
- /* check for start of submenu name */
- if (ccline.cmdbuff[j] == '.'
- && ccline.cmdbuff[j - 1] != '\\') {
- if (found) {
- i = j + 1;
- break;
- } else
- found = TRUE;
+ } else {
+ found = true;
}
}
- if (i > 0)
- cmdline_del(i);
- c = p_wc;
- xpc.xp_context = EXPAND_NOTHING;
}
+ if (s->i > 0) {
+ cmdline_del(s->i);
+ }
+ s->c = p_wc;
+ s->xpc.xp_context = EXPAND_NOTHING;
}
- if ((xpc.xp_context == EXPAND_FILES
- || xpc.xp_context == EXPAND_DIRECTORIES
- || xpc.xp_context == EXPAND_SHELLCMD) && p_wmnu) {
- char_u upseg[5];
-
- upseg[0] = PATHSEP;
- upseg[1] = '.';
- upseg[2] = '.';
- upseg[3] = PATHSEP;
- upseg[4] = NUL;
-
- if (c == K_DOWN
- && ccline.cmdpos > 0
- && ccline.cmdbuff[ccline.cmdpos - 1] == PATHSEP
- && (ccline.cmdpos < 3
- || ccline.cmdbuff[ccline.cmdpos - 2] != '.'
- || ccline.cmdbuff[ccline.cmdpos - 3] != '.')) {
- /* go down a directory */
- c = p_wc;
- } else if (STRNCMP(xpc.xp_pattern, upseg + 1, 3) == 0 && c == K_DOWN) {
- /* If in a direct ancestor, strip off one ../ to go down */
- int found = FALSE;
-
- j = ccline.cmdpos;
- i = (int)(xpc.xp_pattern - ccline.cmdbuff);
- while (--j > i) {
- if (has_mbyte)
- j -= (*mb_head_off)(ccline.cmdbuff, ccline.cmdbuff + j);
- if (vim_ispathsep(ccline.cmdbuff[j])) {
- found = TRUE;
- break;
- }
+ }
+ if ((s->xpc.xp_context == EXPAND_FILES
+ || s->xpc.xp_context == EXPAND_DIRECTORIES
+ || s->xpc.xp_context == EXPAND_SHELLCMD) && p_wmnu) {
+ char_u upseg[5];
+
+ upseg[0] = PATHSEP;
+ upseg[1] = '.';
+ upseg[2] = '.';
+ upseg[3] = PATHSEP;
+ upseg[4] = NUL;
+
+ if (s->c == K_DOWN
+ && ccline.cmdpos > 0
+ && ccline.cmdbuff[ccline.cmdpos - 1] == PATHSEP
+ && (ccline.cmdpos < 3
+ || ccline.cmdbuff[ccline.cmdpos - 2] != '.'
+ || ccline.cmdbuff[ccline.cmdpos - 3] != '.')) {
+ // go down a directory
+ s->c = p_wc;
+ } else if (STRNCMP(s->xpc.xp_pattern, upseg + 1, 3) == 0
+ && s->c == K_DOWN) {
+ // If in a direct ancestor, strip off one ../ to go down
+ int found = false;
+
+ s->j = ccline.cmdpos;
+ s->i = (int)(s->xpc.xp_pattern - ccline.cmdbuff);
+ while (--s->j > s->i) {
+ if (has_mbyte) {
+ s->j -= (*mb_head_off)(ccline.cmdbuff, ccline.cmdbuff + s->j);
}
- if (found
- && ccline.cmdbuff[j - 1] == '.'
- && ccline.cmdbuff[j - 2] == '.'
- && (vim_ispathsep(ccline.cmdbuff[j - 3]) || j == i + 2)) {
- cmdline_del(j - 2);
- c = p_wc;
+ if (vim_ispathsep(ccline.cmdbuff[s->j])) {
+ found = true;
+ break;
+ }
+ }
+ if (found
+ && ccline.cmdbuff[s->j - 1] == '.'
+ && ccline.cmdbuff[s->j - 2] == '.'
+ && (vim_ispathsep(ccline.cmdbuff[s->j - 3]) || s->j == s->i + 2)) {
+ cmdline_del(s->j - 2);
+ s->c = p_wc;
+ }
+ } else if (s->c == K_UP) {
+ // go up a directory
+ int found = false;
+
+ s->j = ccline.cmdpos - 1;
+ s->i = (int)(s->xpc.xp_pattern - ccline.cmdbuff);
+ while (--s->j > s->i) {
+ if (has_mbyte) {
+ s->j -= (*mb_head_off)(ccline.cmdbuff, ccline.cmdbuff + s->j);
}
- } else if (c == K_UP) {
- /* go up a directory */
- int found = FALSE;
-
- j = ccline.cmdpos - 1;
- i = (int)(xpc.xp_pattern - ccline.cmdbuff);
- while (--j > i) {
- if (has_mbyte)
- j -= (*mb_head_off)(ccline.cmdbuff, ccline.cmdbuff + j);
- if (vim_ispathsep(ccline.cmdbuff[j])
+ if (vim_ispathsep(ccline.cmdbuff[s->j])
#ifdef BACKSLASH_IN_FILENAME
- && vim_strchr(" *?[{`$%#", ccline.cmdbuff[j + 1])
- == NULL
+ && vim_strchr(" *?[{`$%#", ccline.cmdbuff[s->j + 1])
+ == NULL
#endif
- ) {
- if (found) {
- i = j + 1;
- break;
- } else
- found = TRUE;
+ ) {
+ if (found) {
+ s->i = s->j + 1;
+ break;
+ } else {
+ found = true;
}
}
+ }
- if (!found)
- j = i;
- else if (STRNCMP(ccline.cmdbuff + j, upseg, 4) == 0)
- j += 4;
- else if (STRNCMP(ccline.cmdbuff + j, upseg + 1, 3) == 0
- && j == i)
- j += 3;
- else
- j = 0;
- if (j > 0) {
- /* TODO this is only for DOS/UNIX systems - need to put in
- * machine-specific stuff here and in upseg init */
- cmdline_del(j);
- put_on_cmdline(upseg + 1, 3, FALSE);
- } else if (ccline.cmdpos > i)
- cmdline_del(i);
-
- /* Now complete in the new directory. Set KeyTyped in case the
- * Up key came from a mapping. */
- c = p_wc;
- KeyTyped = TRUE;
+ if (!found) {
+ s->j = s->i;
+ } else if (STRNCMP(ccline.cmdbuff + s->j, upseg, 4) == 0) {
+ s->j += 4;
+ } else if (STRNCMP(ccline.cmdbuff + s->j, upseg + 1, 3) == 0
+ && s->j == s->i) {
+ s->j += 3;
+ } else {
+ s->j = 0;
+ }
+
+ if (s->j > 0) {
+ // TODO(tarruda): this is only for DOS/UNIX systems - need to put in
+ // machine-specific stuff here and in upseg init
+ cmdline_del(s->j);
+ put_on_cmdline(upseg + 1, 3, false);
+ } else if (ccline.cmdpos > s->i) {
+ cmdline_del(s->i);
}
+
+ // Now complete in the new directory. Set KeyTyped in case the
+ // Up key came from a mapping.
+ s->c = p_wc;
+ KeyTyped = true;
}
+ }
+ // CTRL-\ CTRL-N goes to Normal mode, CTRL-\ CTRL-G goes to Insert
+ // mode when 'insertmode' is set, CTRL-\ e prompts for an expression.
+ if (s->c == Ctrl_BSL) {
+ ++no_mapping;
+ ++allow_keys;
+ s->c = plain_vgetc();
+ --no_mapping;
+ --allow_keys;
+ // CTRL-\ e doesn't work when obtaining an expression, unless it
+ // is in a mapping.
+ if (s->c != Ctrl_N && s->c != Ctrl_G && (s->c != 'e'
+ || (ccline.cmdfirstc == '=' &&
+ KeyTyped))) {
+ vungetc(s->c);
+ s->c = Ctrl_BSL;
+ } else if (s->c == 'e') {
+ char_u *p = NULL;
+ int len;
- /* CTRL-\ CTRL-N goes to Normal mode, CTRL-\ CTRL-G goes to Insert
- * mode when 'insertmode' is set, CTRL-\ e prompts for an expression. */
- if (c == Ctrl_BSL) {
- ++no_mapping;
- ++allow_keys;
- c = plain_vgetc();
- --no_mapping;
- --allow_keys;
- /* CTRL-\ e doesn't work when obtaining an expression, unless it
- * is in a mapping. */
- if (c != Ctrl_N && c != Ctrl_G && (c != 'e'
- || (ccline.cmdfirstc == '=' &&
- KeyTyped))) {
- vungetc(c);
- c = Ctrl_BSL;
- } else if (c == 'e') {
- char_u *p = NULL;
- int len;
-
- /*
- * Replace the command line with the result of an expression.
- * Need to save and restore the current command line, to be
- * able to enter a new one...
- */
- if (ccline.cmdpos == ccline.cmdlen)
- new_cmdpos = 99999; /* keep it at the end */
- else
- new_cmdpos = ccline.cmdpos;
-
- save_cmdline(&save_ccline);
- c = get_expr_register();
- restore_cmdline(&save_ccline);
- if (c == '=') {
- /* Need to save and restore ccline. And set "textlock"
- * to avoid nasty things like going to another buffer when
- * evaluating an expression. */
- save_cmdline(&save_ccline);
- ++textlock;
- p = get_expr_line();
- --textlock;
- restore_cmdline(&save_ccline);
-
- if (p != NULL) {
- len = (int)STRLEN(p);
- realloc_cmdbuff(len + 1);
- ccline.cmdlen = len;
- STRCPY(ccline.cmdbuff, p);
- xfree(p);
-
- /* Restore the cursor or use the position set with
- * set_cmdline_pos(). */
- if (new_cmdpos > ccline.cmdlen)
- ccline.cmdpos = ccline.cmdlen;
- else
- ccline.cmdpos = new_cmdpos;
-
- KeyTyped = FALSE; /* Don't do p_wc completion. */
- redrawcmd();
- goto cmdline_changed;
+ // Replace the command line with the result of an expression.
+ // Need to save and restore the current command line, to be
+ // able to enter a new one...
+ if (ccline.cmdpos == ccline.cmdlen) {
+ new_cmdpos = 99999; // keep it at the end
+ } else {
+ new_cmdpos = ccline.cmdpos;
+ }
+
+ save_cmdline(&s->save_ccline);
+ s->c = get_expr_register();
+ restore_cmdline(&s->save_ccline);
+ if (s->c == '=') {
+ // Need to save and restore ccline. And set "textlock"
+ // to avoid nasty things like going to another buffer when
+ // evaluating an expression.
+ save_cmdline(&s->save_ccline);
+ ++textlock;
+ p = get_expr_line();
+ --textlock;
+ restore_cmdline(&s->save_ccline);
+
+ if (p != NULL) {
+ len = (int)STRLEN(p);
+ realloc_cmdbuff(len + 1);
+ ccline.cmdlen = len;
+ STRCPY(ccline.cmdbuff, p);
+ xfree(p);
+
+ // Restore the cursor or use the position set with
+ // set_cmdline_pos().
+ if (new_cmdpos > ccline.cmdlen) {
+ ccline.cmdpos = ccline.cmdlen;
+ } else {
+ ccline.cmdpos = new_cmdpos;
}
+
+ KeyTyped = false; // Don't do p_wc completion.
+ redrawcmd();
+ return command_line_changed(s);
}
- beep_flush();
- got_int = FALSE; /* don't abandon the command line */
- did_emsg = FALSE;
- emsg_on_display = FALSE;
- redrawcmd();
- goto cmdline_not_changed;
- } else {
- if (c == Ctrl_G && p_im && restart_edit == 0)
- restart_edit = 'a';
- gotesc = TRUE; /* will free ccline.cmdbuff after putting it
- in history */
- goto returncmd; /* back to Normal mode */
}
+ beep_flush();
+ got_int = false; // don't abandon the command line
+ did_emsg = false;
+ emsg_on_display = false;
+ redrawcmd();
+ return command_line_not_changed(s);
+ } else {
+ if (s->c == Ctrl_G && p_im && restart_edit == 0) {
+ restart_edit = 'a';
+ }
+ s->gotesc = true; // will free ccline.cmdbuff after putting it
+ // in history
+ return 0; // back to Normal mode
}
+ }
- if (c == cedit_key || c == K_CMDWIN) {
- if (ex_normal_busy == 0 && got_int == FALSE) {
- /*
- * Open a window to edit the command line (and history).
- */
- c = ex_window();
- some_key_typed = TRUE;
+ if (s->c == cedit_key || s->c == K_CMDWIN) {
+ if (ex_normal_busy == 0 && got_int == false) {
+ // Open a window to edit the command line (and history).
+ s->c = ex_window();
+ s->some_key_typed = true;
+ }
+ } else {
+ s->c = do_digraph(s->c);
+ }
+
+ if (s->c == '\n'
+ || s->c == '\r'
+ || s->c == K_KENTER
+ || (s->c == ESC
+ && (!KeyTyped || vim_strchr(p_cpo, CPO_ESC) != NULL))) {
+ // In Ex mode a backslash escapes a newline.
+ if (exmode_active
+ && s->c != ESC
+ && ccline.cmdpos == ccline.cmdlen
+ && ccline.cmdpos > 0
+ && ccline.cmdbuff[ccline.cmdpos - 1] == '\\') {
+ if (s->c == K_KENTER) {
+ s->c = '\n';
}
- } else
- c = do_digraph(c);
-
- if (c == '\n' || c == '\r' || c == K_KENTER || (c == ESC
- && (!KeyTyped ||
- vim_strchr(p_cpo,
- CPO_ESC) !=
- NULL))) {
- /* In Ex mode a backslash escapes a newline. */
- if (exmode_active
- && c != ESC
- && ccline.cmdpos == ccline.cmdlen
- && ccline.cmdpos > 0
- && ccline.cmdbuff[ccline.cmdpos - 1] == '\\') {
- if (c == K_KENTER)
- c = '\n';
- } else {
- gotesc = FALSE; /* Might have typed ESC previously, don't
- truncate the cmdline now. */
- if (ccheck_abbr(c + ABBR_OFF))
- goto cmdline_changed;
- if (!cmd_silent) {
- ui_cursor_goto(msg_row, 0);
- ui_flush();
- }
- break;
+ } else {
+ s->gotesc = false; // Might have typed ESC previously, don't
+ // truncate the cmdline now.
+ if (ccheck_abbr(s->c + ABBR_OFF)) {
+ return command_line_changed(s);
+ }
+
+ if (!cmd_silent) {
+ ui_cursor_goto(msg_row, 0);
+ ui_flush();
}
+ return 0;
}
+ }
- /*
- * Completion for 'wildchar' or 'wildcharm' key.
- * - hitting <ESC> twice means: abandon command line.
- * - wildcard expansion is only done when the 'wildchar' key is really
- * typed, not when it comes from a macro
- */
- if ((c == p_wc && !gotesc && KeyTyped) || c == p_wcm) {
- if (xpc.xp_numfiles > 0) { /* typed p_wc at least twice */
- /* if 'wildmode' contains "list" may still need to list */
- if (xpc.xp_numfiles > 1
- && !did_wild_list
- && (wim_flags[wim_index] & WIM_LIST)) {
- (void)showmatches(&xpc, FALSE);
- redrawcmd();
- did_wild_list = TRUE;
- }
- if (wim_flags[wim_index] & WIM_LONGEST)
- res = nextwild(&xpc, WILD_LONGEST, WILD_NO_BEEP,
- firstc != '@');
- else if (wim_flags[wim_index] & WIM_FULL)
- res = nextwild(&xpc, WILD_NEXT, WILD_NO_BEEP,
- firstc != '@');
- else
- res = OK; /* don't insert 'wildchar' now */
- } else { /* typed p_wc first time */
- wim_index = 0;
- j = ccline.cmdpos;
- /* if 'wildmode' first contains "longest", get longest
- * common part */
- if (wim_flags[0] & WIM_LONGEST)
- res = nextwild(&xpc, WILD_LONGEST, WILD_NO_BEEP,
- firstc != '@');
- else
- res = nextwild(&xpc, WILD_EXPAND_KEEP, WILD_NO_BEEP,
- firstc != '@');
-
- /* if interrupted while completing, behave like it failed */
- if (got_int) {
- (void)vpeekc(); /* remove <C-C> from input stream */
- got_int = FALSE; /* don't abandon the command line */
- (void)ExpandOne(&xpc, NULL, NULL, 0, WILD_FREE);
- xpc.xp_context = EXPAND_NOTHING;
- goto cmdline_changed;
+ // Completion for 'wildchar' or 'wildcharm' key.
+ // - hitting <ESC> twice means: abandon command line.
+ // - wildcard expansion is only done when the 'wildchar' key is really
+ // typed, not when it comes from a macro
+ if ((s->c == p_wc && !s->gotesc && KeyTyped) || s->c == p_wcm) {
+ if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice
+ // if 'wildmode' contains "list" may still need to list
+ if (s->xpc.xp_numfiles > 1
+ && !s->did_wild_list
+ && (wim_flags[s->wim_index] & WIM_LIST)) {
+ (void)showmatches(&s->xpc, false);
+ redrawcmd();
+ s->did_wild_list = true;
+ }
+
+ if (wim_flags[s->wim_index] & WIM_LONGEST) {
+ s->res = nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP,
+ s->firstc != '@');
+ } else if (wim_flags[s->wim_index] & WIM_FULL) {
+ s->res = nextwild(&s->xpc, WILD_NEXT, WILD_NO_BEEP,
+ s->firstc != '@');
+ } else {
+ s->res = OK; // don't insert 'wildchar' now
+ }
+ } else { // typed p_wc first time
+ s->wim_index = 0;
+ s->j = ccline.cmdpos;
+
+ // if 'wildmode' first contains "longest", get longest
+ // common part
+ if (wim_flags[0] & WIM_LONGEST) {
+ s->res = nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP,
+ s->firstc != '@');
+ } else {
+ s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, WILD_NO_BEEP,
+ s->firstc != '@');
+ }
+
+ // if interrupted while completing, behave like it failed
+ if (got_int) {
+ (void)vpeekc(); // remove <C-C> from input stream
+ got_int = false; // don't abandon the command line
+ (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE);
+ s->xpc.xp_context = EXPAND_NOTHING;
+ return command_line_changed(s);
+ }
+
+ // when more than one match, and 'wildmode' first contains
+ // "list", or no change and 'wildmode' contains "longest,list",
+ // list all matches
+ if (s->res == OK && s->xpc.xp_numfiles > 1) {
+ // a "longest" that didn't do anything is skipped (but not
+ // "list:longest")
+ if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == s->j) {
+ s->wim_index = 1;
}
+ if ((wim_flags[s->wim_index] & WIM_LIST)
+ || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0)) {
+ if (!(wim_flags[0] & WIM_LONGEST)) {
+ int p_wmnu_save = p_wmnu;
+ p_wmnu = 0;
+ // remove match
+ nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@');
+ p_wmnu = p_wmnu_save;
+ }
- /* when more than one match, and 'wildmode' first contains
- * "list", or no change and 'wildmode' contains "longest,list",
- * list all matches */
- if (res == OK && xpc.xp_numfiles > 1) {
- /* a "longest" that didn't do anything is skipped (but not
- * "list:longest") */
- if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == j)
- wim_index = 1;
- if ((wim_flags[wim_index] & WIM_LIST)
- || (p_wmnu && (wim_flags[wim_index] & WIM_FULL) != 0)
- ) {
- if (!(wim_flags[0] & WIM_LONGEST)) {
- int p_wmnu_save = p_wmnu;
- p_wmnu = 0;
- /* remove match */
- nextwild(&xpc, WILD_PREV, 0, firstc != '@');
- p_wmnu = p_wmnu_save;
- }
- (void)showmatches(&xpc, p_wmnu
- && ((wim_flags[wim_index] & WIM_LIST) == 0));
- redrawcmd();
- did_wild_list = TRUE;
- if (wim_flags[wim_index] & WIM_LONGEST)
- nextwild(&xpc, WILD_LONGEST, WILD_NO_BEEP,
- firstc != '@');
- else if (wim_flags[wim_index] & WIM_FULL)
- nextwild(&xpc, WILD_NEXT, WILD_NO_BEEP,
- firstc != '@');
- } else {
- vim_beep(BO_WILD);
+ (void)showmatches(&s->xpc, p_wmnu
+ && ((wim_flags[s->wim_index] & WIM_LIST) == 0));
+ redrawcmd();
+ s->did_wild_list = true;
+
+ if (wim_flags[s->wim_index] & WIM_LONGEST) {
+ nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP,
+ s->firstc != '@');
+ } else if (wim_flags[s->wim_index] & WIM_FULL) {
+ nextwild(&s->xpc, WILD_NEXT, WILD_NO_BEEP,
+ s->firstc != '@');
}
- } else if (xpc.xp_numfiles == -1) {
- xpc.xp_context = EXPAND_NOTHING;
+ } else {
+ vim_beep(BO_WILD);
}
+ } else if (s->xpc.xp_numfiles == -1) {
+ s->xpc.xp_context = EXPAND_NOTHING;
}
- if (wim_index < 3)
- ++wim_index;
- if (c == ESC)
- gotesc = TRUE;
- if (res == OK)
- goto cmdline_changed;
}
- gotesc = FALSE;
+ if (s->wim_index < 3) {
+ ++s->wim_index;
+ }
- /* <S-Tab> goes to last match, in a clumsy way */
- if (c == K_S_TAB && KeyTyped) {
- if (nextwild(&xpc, WILD_EXPAND_KEEP, 0, firstc != '@') == OK
- && nextwild(&xpc, WILD_PREV, 0, firstc != '@') == OK
- && nextwild(&xpc, WILD_PREV, 0, firstc != '@') == OK)
- goto cmdline_changed;
+ if (s->c == ESC) {
+ s->gotesc = true;
}
- if (c == NUL || c == K_ZERO) /* NUL is stored as NL */
- c = NL;
+ if (s->res == OK) {
+ return command_line_changed(s);
+ }
+ }
- do_abbr = TRUE; /* default: check for abbreviation */
+ s->gotesc = false;
- /*
- * Big switch for a typed command line character.
- */
- switch (c) {
- case K_BS:
- case Ctrl_H:
- case K_DEL:
- case K_KDEL:
- case Ctrl_W:
- if (cmd_fkmap && c == K_BS)
- c = K_DEL;
- if (c == K_KDEL)
- c = K_DEL;
+ // <S-Tab> goes to last match, in a clumsy way
+ if (s->c == K_S_TAB && KeyTyped) {
+ if (nextwild(&s->xpc, WILD_EXPAND_KEEP, 0, s->firstc != '@') == OK
+ && nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@') == OK
+ && nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@') == OK) {
+ return command_line_changed(s);
+ }
+ }
- /*
- * delete current character is the same as backspace on next
- * character, except at end of line
- */
- if (c == K_DEL && ccline.cmdpos != ccline.cmdlen)
- ++ccline.cmdpos;
- if (has_mbyte && c == K_DEL)
- ccline.cmdpos += mb_off_next(ccline.cmdbuff,
- ccline.cmdbuff + ccline.cmdpos);
- if (ccline.cmdpos > 0) {
- char_u *p;
-
- j = ccline.cmdpos;
- p = ccline.cmdbuff + j;
- if (has_mbyte) {
- p = mb_prevptr(ccline.cmdbuff, p);
- if (c == Ctrl_W) {
- while (p > ccline.cmdbuff && ascii_isspace(*p))
- p = mb_prevptr(ccline.cmdbuff, p);
- i = mb_get_class(p);
- while (p > ccline.cmdbuff && mb_get_class(p) == i)
- p = mb_prevptr(ccline.cmdbuff, p);
- if (mb_get_class(p) != i)
- p += (*mb_ptr2len)(p);
+ if (s->c == NUL || s->c == K_ZERO) {
+ // NUL is stored as NL
+ s->c = NL;
+ }
+
+ s->do_abbr = true; // default: check for abbreviation
+ return command_line_handle_key(s);
+}
+
+static int command_line_handle_key(CommandLineState *s)
+{
+ // Big switch for a typed command line character.
+ switch (s->c) {
+ case K_BS:
+ case Ctrl_H:
+ case K_DEL:
+ case K_KDEL:
+ case Ctrl_W:
+ if (cmd_fkmap && s->c == K_BS) {
+ s->c = K_DEL;
+ }
+
+ if (s->c == K_KDEL) {
+ s->c = K_DEL;
+ }
+
+ // delete current character is the same as backspace on next
+ // character, except at end of line
+ if (s->c == K_DEL && ccline.cmdpos != ccline.cmdlen) {
+ ++ccline.cmdpos;
+ }
+
+ if (has_mbyte && s->c == K_DEL) {
+ ccline.cmdpos += mb_off_next(ccline.cmdbuff,
+ ccline.cmdbuff + ccline.cmdpos);
+ }
+
+ if (ccline.cmdpos > 0) {
+ char_u *p;
+
+ s->j = ccline.cmdpos;
+ p = ccline.cmdbuff + s->j;
+ if (has_mbyte) {
+ p = mb_prevptr(ccline.cmdbuff, p);
+
+ if (s->c == Ctrl_W) {
+ while (p > ccline.cmdbuff && ascii_isspace(*p)) {
+ p = mb_prevptr(ccline.cmdbuff, p);
}
- } else if (c == Ctrl_W) {
- while (p > ccline.cmdbuff && ascii_isspace(p[-1]))
- --p;
- i = vim_iswordc(p[-1]);
- while (p > ccline.cmdbuff && !ascii_isspace(p[-1])
- && vim_iswordc(p[-1]) == i)
- --p;
- } else
+
+ s->i = mb_get_class(p);
+ while (p > ccline.cmdbuff && mb_get_class(p) == s->i)
+ p = mb_prevptr(ccline.cmdbuff, p);
+
+ if (mb_get_class(p) != s->i) {
+ p += (*mb_ptr2len)(p);
+ }
+ }
+ } else if (s->c == Ctrl_W) {
+ while (p > ccline.cmdbuff && ascii_isspace(p[-1])) {
--p;
- ccline.cmdpos = (int)(p - ccline.cmdbuff);
- ccline.cmdlen -= j - ccline.cmdpos;
- i = ccline.cmdpos;
- while (i < ccline.cmdlen)
- ccline.cmdbuff[i++] = ccline.cmdbuff[j++];
-
- /* Truncate at the end, required for multi-byte chars. */
- ccline.cmdbuff[ccline.cmdlen] = NUL;
- redrawcmd();
- } else if (ccline.cmdlen == 0 && c != Ctrl_W
- && ccline.cmdprompt == NULL && indent == 0) {
- /* In ex and debug mode it doesn't make sense to return. */
- if (exmode_active
- || ccline.cmdfirstc == '>'
- )
- goto cmdline_not_changed;
-
- xfree(ccline.cmdbuff); /* no commandline to return */
- ccline.cmdbuff = NULL;
- if (!cmd_silent) {
- if (cmdmsg_rl)
- msg_col = Columns;
- else
- msg_col = 0;
- msg_putchar(' '); /* delete ':' */
}
- redraw_cmdline = TRUE;
- goto returncmd; /* back to cmd mode */
- }
- goto cmdline_changed;
- case K_INS:
- case K_KINS:
- /* if Farsi mode set, we are in reverse insert mode -
- Do not change the mode */
- if (cmd_fkmap)
- beep_flush();
- else
- ccline.overstrike = !ccline.overstrike;
-
- ui_cursor_shape(); /* may show different cursor shape */
- goto cmdline_not_changed;
-
- case Ctrl_HAT:
- if (map_to_exists_mode((char_u *)"", LANGMAP, FALSE)) {
- /* ":lmap" mappings exists, toggle use of mappings. */
- State ^= LANGMAP;
- if (b_im_ptr != NULL) {
- if (State & LANGMAP)
- *b_im_ptr = B_IMODE_LMAP;
- else
- *b_im_ptr = B_IMODE_NONE;
- }
+ s->i = vim_iswordc(p[-1]);
+ while (p > ccline.cmdbuff && !ascii_isspace(p[-1])
+ && vim_iswordc(p[-1]) == s->i)
+ --p;
+ } else {
+ --p;
}
- if (b_im_ptr != NULL) {
- if (b_im_ptr == &curbuf->b_p_iminsert)
- set_iminsert_global();
- else
- set_imsearch_global();
+
+ ccline.cmdpos = (int)(p - ccline.cmdbuff);
+ ccline.cmdlen -= s->j - ccline.cmdpos;
+ s->i = ccline.cmdpos;
+
+ while (s->i < ccline.cmdlen) {
+ ccline.cmdbuff[s->i++] = ccline.cmdbuff[s->j++];
}
- ui_cursor_shape(); /* may show different cursor shape */
- /* Show/unshow value of 'keymap' in status lines later. */
- status_redraw_curbuf();
- goto cmdline_not_changed;
-
- /* case '@': only in very old vi */
- case Ctrl_U:
- /* delete all characters left of the cursor */
- j = ccline.cmdpos;
- ccline.cmdlen -= j;
- i = ccline.cmdpos = 0;
- while (i < ccline.cmdlen)
- ccline.cmdbuff[i++] = ccline.cmdbuff[j++];
- /* Truncate at the end, required for multi-byte chars. */
+
+ // Truncate at the end, required for multi-byte chars.
ccline.cmdbuff[ccline.cmdlen] = NUL;
redrawcmd();
- goto cmdline_changed;
-
-
- case ESC: /* get here if p_wc != ESC or when ESC typed twice */
- case Ctrl_C:
- /* In exmode it doesn't make sense to return. Except when
- * ":normal" runs out of characters. */
- if (exmode_active
- && (ex_normal_busy == 0 || typebuf.tb_len > 0)
- )
- goto cmdline_not_changed;
-
- gotesc = TRUE; /* will free ccline.cmdbuff after
- putting it in history */
- goto returncmd; /* back to cmd mode */
-
- case Ctrl_R: /* insert register */
- putcmdline('"', TRUE);
- ++no_mapping;
- i = c = plain_vgetc(); /* CTRL-R <char> */
- if (i == Ctrl_O)
- i = Ctrl_R; /* CTRL-R CTRL-O == CTRL-R CTRL-R */
- if (i == Ctrl_R)
- c = plain_vgetc(); /* CTRL-R CTRL-R <char> */
- --no_mapping;
- /*
- * Insert the result of an expression.
- * Need to save the current command line, to be able to enter
- * a new one...
- */
- new_cmdpos = -1;
- if (c == '=') {
- if (ccline.cmdfirstc == '=') { /* can't do this recursively */
- beep_flush();
- c = ESC;
+ } else if (ccline.cmdlen == 0 && s->c != Ctrl_W
+ && ccline.cmdprompt == NULL && s->indent == 0) {
+ // In ex and debug mode it doesn't make sense to return.
+ if (exmode_active || ccline.cmdfirstc == '>') {
+ return command_line_not_changed(s);
+ }
+
+ xfree(ccline.cmdbuff); // no commandline to return
+ ccline.cmdbuff = NULL;
+ if (!cmd_silent) {
+ if (cmdmsg_rl) {
+ msg_col = Columns;
} else {
- save_cmdline(&save_ccline);
- c = get_expr_register();
- restore_cmdline(&save_ccline);
+ msg_col = 0;
}
+ msg_putchar(' '); // delete ':'
}
- if (c != ESC) { /* use ESC to cancel inserting register */
- cmdline_paste(c, i == Ctrl_R, FALSE);
-
- /* When there was a serious error abort getting the
- * command line. */
- if (aborting()) {
- gotesc = TRUE; /* will free ccline.cmdbuff after
- putting it in history */
- goto returncmd; /* back to cmd mode */
- }
- KeyTyped = FALSE; /* Don't do p_wc completion. */
- if (new_cmdpos >= 0) {
- /* set_cmdline_pos() was used */
- if (new_cmdpos > ccline.cmdlen)
- ccline.cmdpos = ccline.cmdlen;
- else
- ccline.cmdpos = new_cmdpos;
+ redraw_cmdline = true;
+ return 0; // back to cmd mode
+ }
+ return command_line_changed(s);
+
+ case K_INS:
+ case K_KINS:
+ // if Farsi mode set, we are in reverse insert mode -
+ // Do not change the mode
+ if (cmd_fkmap) {
+ beep_flush();
+ } else {
+ ccline.overstrike = !ccline.overstrike;
+ }
+
+ ui_cursor_shape(); // may show different cursor shape
+ return command_line_not_changed(s);
+
+ case Ctrl_HAT:
+ if (map_to_exists_mode((char_u *)"", LANGMAP, false)) {
+ // ":lmap" mappings exists, toggle use of mappings.
+ State ^= LANGMAP;
+ if (s->b_im_ptr != NULL) {
+ if (State & LANGMAP) {
+ *s->b_im_ptr = B_IMODE_LMAP;
+ } else {
+ *s->b_im_ptr = B_IMODE_NONE;
}
}
- redrawcmd();
- goto cmdline_changed;
+ }
- case Ctrl_D:
- if (showmatches(&xpc, FALSE) == EXPAND_NOTHING)
- break; /* Use ^D as normal char instead */
+ if (s->b_im_ptr != NULL) {
+ if (s->b_im_ptr == &curbuf->b_p_iminsert) {
+ set_iminsert_global();
+ } else {
+ set_imsearch_global();
+ }
+ }
+ ui_cursor_shape(); // may show different cursor shape
+ // Show/unshow value of 'keymap' in status lines later.
+ status_redraw_curbuf();
+ return command_line_not_changed(s);
+
+ // case '@': only in very old vi
+ case Ctrl_U:
+ // delete all characters left of the cursor
+ s->j = ccline.cmdpos;
+ ccline.cmdlen -= s->j;
+ s->i = ccline.cmdpos = 0;
+ while (s->i < ccline.cmdlen) {
+ ccline.cmdbuff[s->i++] = ccline.cmdbuff[s->j++];
+ }
- redrawcmd();
- continue; /* don't do incremental search now */
+ // Truncate at the end, required for multi-byte chars.
+ ccline.cmdbuff[ccline.cmdlen] = NUL;
+ redrawcmd();
+ return command_line_changed(s);
- case K_RIGHT:
- case K_S_RIGHT:
- case K_C_RIGHT:
- do {
- if (ccline.cmdpos >= ccline.cmdlen)
- break;
- i = cmdline_charsize(ccline.cmdpos);
- if (KeyTyped && ccline.cmdspos + i >= Columns * Rows)
- break;
- ccline.cmdspos += i;
- if (has_mbyte)
- ccline.cmdpos += (*mb_ptr2len)(ccline.cmdbuff
- + ccline.cmdpos);
- else
- ++ccline.cmdpos;
- } while ((c == K_S_RIGHT || c == K_C_RIGHT
- || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)))
- && ccline.cmdbuff[ccline.cmdpos] != ' ');
- if (has_mbyte)
- set_cmdspos_cursor();
- goto cmdline_not_changed;
- case K_LEFT:
- case K_S_LEFT:
- case K_C_LEFT:
- if (ccline.cmdpos == 0)
- goto cmdline_not_changed;
- do {
- --ccline.cmdpos;
- if (has_mbyte) /* move to first byte of char */
- ccline.cmdpos -= (*mb_head_off)(ccline.cmdbuff,
- ccline.cmdbuff + ccline.cmdpos);
- ccline.cmdspos -= cmdline_charsize(ccline.cmdpos);
- } while (ccline.cmdpos > 0
- && (c == K_S_LEFT || c == K_C_LEFT
- || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)))
- && ccline.cmdbuff[ccline.cmdpos - 1] != ' ');
- if (has_mbyte)
- set_cmdspos_cursor();
- goto cmdline_not_changed;
+ case ESC: // get here if p_wc != ESC or when ESC typed twice
+ case Ctrl_C:
+ // In exmode it doesn't make sense to return. Except when
+ // ":normal" runs out of characters.
+ if (exmode_active && (ex_normal_busy == 0 || typebuf.tb_len > 0)) {
+ return command_line_not_changed(s);
+ }
- case K_IGNORE:
- /* Ignore mouse event or ex_window() result. */
- goto cmdline_not_changed;
+ s->gotesc = true; // will free ccline.cmdbuff after
+ // putting it in history
+ return 0; // back to cmd mode
+
+ case Ctrl_R: // insert register
+ putcmdline('"', true);
+ ++no_mapping;
+ s->i = s->c = plain_vgetc(); // CTRL-R <char>
+ if (s->i == Ctrl_O) {
+ s->i = Ctrl_R; // CTRL-R CTRL-O == CTRL-R CTRL-R
+ }
+ if (s->i == Ctrl_R) {
+ s->c = plain_vgetc(); // CTRL-R CTRL-R <char>
+ }
+ --no_mapping;
+ // Insert the result of an expression.
+ // Need to save the current command line, to be able to enter
+ // a new one...
+ new_cmdpos = -1;
+ if (s->c == '=') {
+ if (ccline.cmdfirstc == '=') { // can't do this recursively
+ beep_flush();
+ s->c = ESC;
+ } else {
+ save_cmdline(&s->save_ccline);
+ s->c = get_expr_register();
+ restore_cmdline(&s->save_ccline);
+ }
+ }
- case K_MIDDLEDRAG:
- case K_MIDDLERELEASE:
- goto cmdline_not_changed; /* Ignore mouse */
+ if (s->c != ESC) { // use ESC to cancel inserting register
+ cmdline_paste(s->c, s->i == Ctrl_R, false);
- case K_MIDDLEMOUSE:
- if (!mouse_has(MOUSE_COMMAND))
- goto cmdline_not_changed; /* Ignore mouse */
- cmdline_paste(0, TRUE, TRUE);
- redrawcmd();
- goto cmdline_changed;
-
-
- case K_LEFTDRAG:
- case K_LEFTRELEASE:
- case K_RIGHTDRAG:
- case K_RIGHTRELEASE:
- /* Ignore drag and release events when the button-down wasn't
- * seen before. */
- if (ignore_drag_release)
- goto cmdline_not_changed;
- /* FALLTHROUGH */
- case K_LEFTMOUSE:
- case K_RIGHTMOUSE:
- if (c == K_LEFTRELEASE || c == K_RIGHTRELEASE)
- ignore_drag_release = TRUE;
- else
- ignore_drag_release = FALSE;
- if (!mouse_has(MOUSE_COMMAND))
- goto cmdline_not_changed; /* Ignore mouse */
-
- set_cmdspos();
- for (ccline.cmdpos = 0; ccline.cmdpos < ccline.cmdlen;
- ++ccline.cmdpos) {
- i = cmdline_charsize(ccline.cmdpos);
- if (mouse_row <= cmdline_row + ccline.cmdspos / Columns
- && mouse_col < ccline.cmdspos % Columns + i)
- break;
- if (has_mbyte) {
- /* Count ">" for double-wide char that doesn't fit. */
- correct_cmdspos(ccline.cmdpos, i);
- ccline.cmdpos += (*mb_ptr2len)(ccline.cmdbuff
- + ccline.cmdpos) - 1;
+ // When there was a serious error abort getting the
+ // command line.
+ if (aborting()) {
+ s->gotesc = true; // will free ccline.cmdbuff after
+ // putting it in history
+ return 0; // back to cmd mode
+ }
+ KeyTyped = false; // Don't do p_wc completion.
+ if (new_cmdpos >= 0) {
+ // set_cmdline_pos() was used
+ if (new_cmdpos > ccline.cmdlen) {
+ ccline.cmdpos = ccline.cmdlen;
+ } else {
+ ccline.cmdpos = new_cmdpos;
}
- ccline.cmdspos += i;
}
- goto cmdline_not_changed;
-
- /* Mouse scroll wheel: ignored here */
- case K_MOUSEDOWN:
- case K_MOUSEUP:
- case K_MOUSELEFT:
- case K_MOUSERIGHT:
- /* Alternate buttons ignored here */
- case K_X1MOUSE:
- case K_X1DRAG:
- case K_X1RELEASE:
- case K_X2MOUSE:
- case K_X2DRAG:
- case K_X2RELEASE:
- goto cmdline_not_changed;
-
-
-
- case K_SELECT: /* end of Select mode mapping - ignore */
- goto cmdline_not_changed;
-
- case Ctrl_B: /* begin of command line */
- case K_HOME:
- case K_KHOME:
- case K_S_HOME:
- case K_C_HOME:
- ccline.cmdpos = 0;
- set_cmdspos();
- goto cmdline_not_changed;
-
- case Ctrl_E: /* end of command line */
- case K_END:
- case K_KEND:
- case K_S_END:
- case K_C_END:
- ccline.cmdpos = ccline.cmdlen;
- set_cmdspos_cursor();
- goto cmdline_not_changed;
+ }
+ redrawcmd();
+ return command_line_changed(s);
+
+ case Ctrl_D:
+ if (showmatches(&s->xpc, false) == EXPAND_NOTHING) {
+ break; // Use ^D as normal char instead
+ }
+
+ redrawcmd();
+ return 1; // don't do incremental search now
- case Ctrl_A: /* all matches */
- if (nextwild(&xpc, WILD_ALL, 0, firstc != '@') == FAIL)
+ case K_RIGHT:
+ case K_S_RIGHT:
+ case K_C_RIGHT:
+ do {
+ if (ccline.cmdpos >= ccline.cmdlen) {
break;
- goto cmdline_changed;
-
- case Ctrl_L:
- if (p_is && !cmd_silent && (firstc == '/' || firstc == '?')) {
- /* Add a character from under the cursor for 'incsearch' */
- if (did_incsearch
- && !equalpos(curwin->w_cursor, old_cursor)) {
- c = gchar_cursor();
- /* If 'ignorecase' and 'smartcase' are set and the
- * command line has no uppercase characters, convert
- * the character to lowercase */
- if (p_ic && p_scs && !pat_has_uppercase(ccline.cmdbuff))
- c = vim_tolower(c);
- if (c != NUL) {
- if (c == firstc || vim_strchr((char_u *)(
- p_magic ? "\\^$.*[" : "\\^$"), c)
- != NULL) {
- /* put a backslash before special characters */
- stuffcharReadbuff(c);
- c = '\\';
- }
- break;
- }
- }
- goto cmdline_not_changed;
}
- /* completion: longest common part */
- if (nextwild(&xpc, WILD_LONGEST, 0, firstc != '@') == FAIL)
+ s->i = cmdline_charsize(ccline.cmdpos);
+ if (KeyTyped && ccline.cmdspos + s->i >= Columns * Rows) {
break;
- goto cmdline_changed;
+ }
- case Ctrl_N: /* next match */
- case Ctrl_P: /* previous match */
- if (xpc.xp_numfiles > 0) {
- if (nextwild(&xpc, (c == Ctrl_P) ? WILD_PREV : WILD_NEXT,
- 0, firstc != '@') == FAIL)
- break;
- goto cmdline_changed;
+ ccline.cmdspos += s->i;
+ if (has_mbyte) {
+ ccline.cmdpos += (*mb_ptr2len)(ccline.cmdbuff
+ + ccline.cmdpos);
+ } else {
+ ++ccline.cmdpos;
}
+ } while ((s->c == K_S_RIGHT || s->c == K_C_RIGHT
+ || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)))
+ && ccline.cmdbuff[ccline.cmdpos] != ' ');
+ if (has_mbyte) {
+ set_cmdspos_cursor();
+ }
+ return command_line_not_changed(s);
- case K_UP:
- case K_DOWN:
- case K_S_UP:
- case K_S_DOWN:
- case K_PAGEUP:
- case K_KPAGEUP:
- case K_PAGEDOWN:
- case K_KPAGEDOWN:
- if (hislen == 0 || firstc == NUL) /* no history */
- goto cmdline_not_changed;
-
- i = hiscnt;
-
- /* save current command string so it can be restored later */
- if (lookfor == NULL) {
- lookfor = vim_strsave(ccline.cmdbuff);
- lookfor[ccline.cmdpos] = NUL;
+ case K_LEFT:
+ case K_S_LEFT:
+ case K_C_LEFT:
+ if (ccline.cmdpos == 0) {
+ return command_line_not_changed(s);
+ }
+ do {
+ --ccline.cmdpos;
+ if (has_mbyte) { // move to first byte of char
+ ccline.cmdpos -= (*mb_head_off)(ccline.cmdbuff,
+ ccline.cmdbuff + ccline.cmdpos);
}
+ ccline.cmdspos -= cmdline_charsize(ccline.cmdpos);
+ } while (ccline.cmdpos > 0
+ && (s->c == K_S_LEFT || s->c == K_C_LEFT
+ || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)))
+ && ccline.cmdbuff[ccline.cmdpos - 1] != ' ');
- j = (int)STRLEN(lookfor);
- for (;; ) {
- /* one step backwards */
- if (c == K_UP|| c == K_S_UP || c == Ctrl_P
- || c == K_PAGEUP || c == K_KPAGEUP) {
- if (hiscnt == hislen) /* first time */
- hiscnt = hisidx[histype];
- else if (hiscnt == 0 && hisidx[histype] != hislen - 1)
- hiscnt = hislen - 1;
- else if (hiscnt != hisidx[histype] + 1)
- --hiscnt;
- else { /* at top of list */
- hiscnt = i;
- break;
- }
- } else { /* one step forwards */
- /* on last entry, clear the line */
- if (hiscnt == hisidx[histype]) {
- hiscnt = hislen;
- break;
- }
+ if (has_mbyte) {
+ set_cmdspos_cursor();
+ }
- /* not on a history line, nothing to do */
- if (hiscnt == hislen)
- break;
- if (hiscnt == hislen - 1) /* wrap around */
- hiscnt = 0;
- else
- ++hiscnt;
+ return command_line_not_changed(s);
+
+ case K_IGNORE:
+ // Ignore mouse event or ex_window() result.
+ return command_line_not_changed(s);
+
+
+ case K_MIDDLEDRAG:
+ case K_MIDDLERELEASE:
+ return command_line_not_changed(s); // Ignore mouse
+
+ case K_MIDDLEMOUSE:
+ if (!mouse_has(MOUSE_COMMAND)) {
+ return command_line_not_changed(s); // Ignore mouse
+ }
+ cmdline_paste(0, true, true);
+ redrawcmd();
+ return command_line_changed(s);
+
+
+ case K_LEFTDRAG:
+ case K_LEFTRELEASE:
+ case K_RIGHTDRAG:
+ case K_RIGHTRELEASE:
+ // Ignore drag and release events when the button-down wasn't
+ // seen before.
+ if (s->ignore_drag_release) {
+ return command_line_not_changed(s);
+ }
+ // FALLTHROUGH
+ case K_LEFTMOUSE:
+ case K_RIGHTMOUSE:
+ if (s->c == K_LEFTRELEASE || s->c == K_RIGHTRELEASE) {
+ s->ignore_drag_release = true;
+ } else {
+ s->ignore_drag_release = false;
+ }
+
+ if (!mouse_has(MOUSE_COMMAND)) {
+ return command_line_not_changed(s); // Ignore mouse
+ }
+
+ set_cmdspos();
+ for (ccline.cmdpos = 0; ccline.cmdpos < ccline.cmdlen;
+ ++ccline.cmdpos) {
+ s->i = cmdline_charsize(ccline.cmdpos);
+ if (mouse_row <= cmdline_row + ccline.cmdspos / Columns
+ && mouse_col < ccline.cmdspos % Columns + s->i) {
+ break;
+ }
+
+ if (has_mbyte) {
+ // Count ">" for double-wide char that doesn't fit.
+ correct_cmdspos(ccline.cmdpos, s->i);
+ ccline.cmdpos += (*mb_ptr2len)(ccline.cmdbuff
+ + ccline.cmdpos) - 1;
+ }
+ ccline.cmdspos += s->i;
+ }
+ return command_line_not_changed(s);
+
+ // Mouse scroll wheel: ignored here
+ case K_MOUSEDOWN:
+ case K_MOUSEUP:
+ case K_MOUSELEFT:
+ case K_MOUSERIGHT:
+ // Alternate buttons ignored here
+ case K_X1MOUSE:
+ case K_X1DRAG:
+ case K_X1RELEASE:
+ case K_X2MOUSE:
+ case K_X2DRAG:
+ case K_X2RELEASE:
+ return command_line_not_changed(s);
+
+
+
+ case K_SELECT: // end of Select mode mapping - ignore
+ return command_line_not_changed(s);
+
+ case Ctrl_B: // begin of command line
+ case K_HOME:
+ case K_KHOME:
+ case K_S_HOME:
+ case K_C_HOME:
+ ccline.cmdpos = 0;
+ set_cmdspos();
+ return command_line_not_changed(s);
+
+ case Ctrl_E: // end of command line
+ case K_END:
+ case K_KEND:
+ case K_S_END:
+ case K_C_END:
+ ccline.cmdpos = ccline.cmdlen;
+ set_cmdspos_cursor();
+ return command_line_not_changed(s);
+
+ case Ctrl_A: // all matches
+ if (nextwild(&s->xpc, WILD_ALL, 0, s->firstc != '@') == FAIL)
+ break;
+ return command_line_changed(s);
+
+ case Ctrl_L:
+ if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) {
+ // Add a character from under the cursor for 'incsearch'
+ if (s->did_incsearch && !equalpos(curwin->w_cursor, s->old_cursor)) {
+ s->c = gchar_cursor();
+ // If 'ignorecase' and 'smartcase' are set and the
+ // command line has no uppercase characters, convert
+ // the character to lowercase
+ if (p_ic && p_scs && !pat_has_uppercase(ccline.cmdbuff)) {
+ s->c = vim_tolower(s->c);
}
- if (hiscnt < 0 || history[histype][hiscnt].hisstr == NULL) {
- hiscnt = i;
+
+ if (s->c != NUL) {
+ if (s->c == s->firstc
+ || vim_strchr((char_u *)(p_magic ? "\\^$.*[" : "\\^$"), s->c)
+ != NULL) {
+ // put a backslash before special characters
+ stuffcharReadbuff(s->c);
+ s->c = '\\';
+ }
break;
}
- if ((c != K_UP && c != K_DOWN)
- || hiscnt == i
- || STRNCMP(history[histype][hiscnt].hisstr,
- lookfor, (size_t)j) == 0)
- break;
}
+ return command_line_not_changed(s);
+ }
- if (hiscnt != i) { /* jumped to other entry */
- char_u *p;
- int len;
- int old_firstc;
+ // completion: longest common part
+ if (nextwild(&s->xpc, WILD_LONGEST, 0, s->firstc != '@') == FAIL) {
+ break;
+ }
+ return command_line_changed(s);
- xfree(ccline.cmdbuff);
- xpc.xp_context = EXPAND_NOTHING;
- if (hiscnt == hislen)
- p = lookfor; /* back to the old one */
- else
- p = history[histype][hiscnt].hisstr;
-
- if (histype == HIST_SEARCH
- && p != lookfor
- && (old_firstc = p[STRLEN(p) + 1]) != firstc) {
- /* Correct for the separator character used when
- * adding the history entry vs the one used now.
- * First loop: count length.
- * Second loop: copy the characters. */
- for (i = 0; i <= 1; ++i) {
- len = 0;
- for (j = 0; p[j] != NUL; ++j) {
- /* Replace old sep with new sep, unless it is
- * escaped. */
- if (p[j] == old_firstc
- && (j == 0 || p[j - 1] != '\\')) {
- if (i > 0)
- ccline.cmdbuff[len] = firstc;
- } else {
- /* Escape new sep, unless it is already
- * escaped. */
- if (p[j] == firstc
- && (j == 0 || p[j - 1] != '\\')) {
- if (i > 0)
- ccline.cmdbuff[len] = '\\';
- ++len;
- }
- if (i > 0)
- ccline.cmdbuff[len] = p[j];
- }
- ++len;
- }
- if (i == 0) {
- alloc_cmdbuff(len);
- }
- }
- ccline.cmdbuff[len] = NUL;
+ case Ctrl_N: // next match
+ case Ctrl_P: // previous match
+ if (s->xpc.xp_numfiles > 0) {
+ if (nextwild(&s->xpc, (s->c == Ctrl_P) ? WILD_PREV : WILD_NEXT,
+ 0, s->firstc != '@') == FAIL) {
+ break;
+ }
+ return command_line_changed(s);
+ }
+
+ case K_UP:
+ case K_DOWN:
+ case K_S_UP:
+ case K_S_DOWN:
+ case K_PAGEUP:
+ case K_KPAGEUP:
+ case K_PAGEDOWN:
+ case K_KPAGEDOWN:
+ if (hislen == 0 || s->firstc == NUL) {
+ // no history
+ return command_line_not_changed(s);
+ }
+
+ s->i = s->hiscnt;
+
+ // save current command string so it can be restored later
+ if (s->lookfor == NULL) {
+ s->lookfor = vim_strsave(ccline.cmdbuff);
+ s->lookfor[ccline.cmdpos] = NUL;
+ }
+
+ s->j = (int)STRLEN(s->lookfor);
+ for (;; ) {
+ // one step backwards
+ if (s->c == K_UP|| s->c == K_S_UP || s->c == Ctrl_P
+ || s->c == K_PAGEUP || s->c == K_KPAGEUP) {
+ if (s->hiscnt == hislen) {
+ // first time
+ s->hiscnt = hisidx[s->histype];
+ } else if (s->hiscnt == 0 && hisidx[s->histype] != hislen - 1) {
+ s->hiscnt = hislen - 1;
+ } else if (s->hiscnt != hisidx[s->histype] + 1) {
+ --s->hiscnt;
} else {
- alloc_cmdbuff((int)STRLEN(p));
- STRCPY(ccline.cmdbuff, p);
+ // at top of list
+ s->hiscnt = s->i;
+ break;
+ }
+ } else { // one step forwards
+ // on last entry, clear the line
+ if (s->hiscnt == hisidx[s->histype]) {
+ s->hiscnt = hislen;
+ break;
}
- ccline.cmdpos = ccline.cmdlen = (int)STRLEN(ccline.cmdbuff);
- redrawcmd();
- goto cmdline_changed;
- }
- beep_flush();
- goto cmdline_not_changed;
-
- case Ctrl_V:
- case Ctrl_Q:
- ignore_drag_release = TRUE;
- putcmdline('^', TRUE);
- c = get_literal(); /* get next (two) character(s) */
- do_abbr = FALSE; /* don't do abbreviation now */
- /* may need to remove ^ when composing char was typed */
- if (enc_utf8 && utf_iscomposing(c) && !cmd_silent) {
- draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos);
- msg_putchar(' ');
- cursorcmd();
+ // not on a history line, nothing to do
+ if (s->hiscnt == hislen) {
+ break;
+ }
+
+ if (s->hiscnt == hislen - 1) {
+ // wrap around
+ s->hiscnt = 0;
+ } else {
+ ++s->hiscnt;
+ }
}
- break;
- case Ctrl_K:
- ignore_drag_release = TRUE;
- putcmdline('?', TRUE);
- c = get_digraph(TRUE);
- if (c != NUL)
+ if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) {
+ s->hiscnt = s->i;
break;
+ }
- redrawcmd();
- goto cmdline_not_changed;
-
- case Ctrl__: /* CTRL-_: switch language mode */
- if (!p_ari)
+ if ((s->c != K_UP && s->c != K_DOWN)
+ || s->hiscnt == s->i
+ || STRNCMP(history[s->histype][s->hiscnt].hisstr,
+ s->lookfor, (size_t)s->j) == 0) {
break;
- if (p_altkeymap) {
- cmd_fkmap = !cmd_fkmap;
- if (cmd_fkmap) /* in Farsi always in Insert mode */
- ccline.overstrike = FALSE;
- } else /* Hebrew is default */
- cmd_hkmap = !cmd_hkmap;
- goto cmdline_not_changed;
-
- default:
-#ifdef UNIX
- if (c == intr_char) {
- gotesc = TRUE; /* will free ccline.cmdbuff after
- putting it in history */
- goto returncmd; /* back to Normal mode */
}
-#endif
- /*
- * Normal character with no special meaning. Just set mod_mask
- * to 0x0 so that typing Shift-Space in the GUI doesn't enter
- * the string <S-Space>. This should only happen after ^V.
- */
- if (!IS_SPECIAL(c))
- mod_mask = 0x0;
- break;
}
- /*
- * End of switch on command line character.
- * We come here if we have a normal character.
- */
- if (do_abbr && (IS_SPECIAL(c) || !vim_iswordc(c)) && (ccheck_abbr(
- /* Add ABBR_OFF for characters above 0x100, this is
- * what check_abbr() expects. */
- (has_mbyte &&
- c >=
- 0x100) ? (c +
- ABBR_OFF) :
- c) || c ==
- Ctrl_RSB))
- goto cmdline_changed;
+ if (s->hiscnt != s->i) {
+ // jumped to other entry
+ char_u *p;
+ int len;
+ int old_firstc;
- /*
- * put the character in the command line
- */
- if (IS_SPECIAL(c) || mod_mask != 0)
- put_on_cmdline(get_special_key_name(c, mod_mask), -1, TRUE);
- else {
- if (has_mbyte) {
- j = (*mb_char2bytes)(c, IObuff);
- IObuff[j] = NUL; /* exclude composing chars */
- put_on_cmdline(IObuff, j, TRUE);
+ xfree(ccline.cmdbuff);
+ s->xpc.xp_context = EXPAND_NOTHING;
+ if (s->hiscnt == hislen) {
+ p = s->lookfor; // back to the old one
} else {
- IObuff[0] = c;
- put_on_cmdline(IObuff, 1, TRUE);
+ p = history[s->histype][s->hiscnt].hisstr;
}
- }
- goto cmdline_changed;
- /*
- * This part implements incremental searches for "/" and "?"
- * Jump to cmdline_not_changed when a character has been read but the command
- * line did not change. Then we only search and redraw if something changed in
- * the past.
- * Jump to cmdline_changed when the command line did change.
- * (Sorry for the goto's, I know it is ugly).
- */
-cmdline_not_changed:
- if (!incsearch_postponed)
- continue;
+ if (s->histype == HIST_SEARCH
+ && p != s->lookfor
+ && (old_firstc = p[STRLEN(p) + 1]) != s->firstc) {
+ // Correct for the separator character used when
+ // adding the history entry vs the one used now.
+ // First loop: count length.
+ // Second loop: copy the characters.
+ for (s->i = 0; s->i <= 1; ++s->i) {
+ len = 0;
+ for (s->j = 0; p[s->j] != NUL; ++s->j) {
+ // Replace old sep with new sep, unless it is
+ // escaped.
+ if (p[s->j] == old_firstc
+ && (s->j == 0 || p[s->j - 1] != '\\')) {
+ if (s->i > 0) {
+ ccline.cmdbuff[len] = s->firstc;
+ }
+ } else {
+ // Escape new sep, unless it is already
+ // escaped.
+ if (p[s->j] == s->firstc
+ && (s->j == 0 || p[s->j - 1] != '\\')) {
+ if (s->i > 0) {
+ ccline.cmdbuff[len] = '\\';
+ }
+ ++len;
+ }
-cmdline_changed:
- /*
- * 'incsearch' highlighting.
- */
- if (p_is && !cmd_silent && (firstc == '/' || firstc == '?')) {
- pos_T end_pos;
- proftime_T tm;
+ if (s->i > 0) {
+ ccline.cmdbuff[len] = p[s->j];
+ }
+ }
+ ++len;
+ }
- /* if there is a character waiting, search and redraw later */
- if (char_avail()) {
- incsearch_postponed = TRUE;
- continue;
+ if (s->i == 0) {
+ alloc_cmdbuff(len);
+ }
+ }
+ ccline.cmdbuff[len] = NUL;
+ } else {
+ alloc_cmdbuff((int)STRLEN(p));
+ STRCPY(ccline.cmdbuff, p);
}
- incsearch_postponed = FALSE;
- curwin->w_cursor = old_cursor; /* start at old position */
- /* If there is no command line, don't do anything */
- if (ccline.cmdlen == 0)
- i = 0;
- else {
- ui_busy_start();
- ui_flush();
- ++emsg_off; /* So it doesn't beep if bad expr */
- /* Set the time limit to half a second. */
- tm = profile_setlimit(500L);
- i = do_search(NULL, firstc, ccline.cmdbuff, count,
- SEARCH_KEEP + SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK,
- &tm
- );
- --emsg_off;
- /* if interrupted while searching, behave like it failed */
- if (got_int) {
- (void)vpeekc(); /* remove <C-C> from input stream */
- got_int = FALSE; /* don't abandon the command line */
- i = 0;
- } else if (char_avail())
- /* cancelled searching because a char was typed */
- incsearch_postponed = TRUE;
- ui_busy_stop();
- }
- if (i != 0)
- highlight_match = TRUE; /* highlight position */
- else
- highlight_match = FALSE; /* remove highlight */
-
- /* first restore the old curwin values, so the screen is
- * positioned in the same way as the actual search command */
- curwin->w_leftcol = old_leftcol;
- curwin->w_topline = old_topline;
- curwin->w_topfill = old_topfill;
- curwin->w_botline = old_botline;
- changed_cline_bef_curs();
- update_topline();
-
- if (i != 0) {
- pos_T save_pos = curwin->w_cursor;
-
- /*
- * First move cursor to end of match, then to the start. This
- * moves the whole match onto the screen when 'nowrap' is set.
- */
- curwin->w_cursor.lnum += search_match_lines;
- curwin->w_cursor.col = search_match_endcol;
- if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
- curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
- coladvance((colnr_T)MAXCOL);
- }
- validate_cursor();
- end_pos = curwin->w_cursor;
- curwin->w_cursor = save_pos;
- } else
- end_pos = curwin->w_cursor; /* shutup gcc 4 */
+ ccline.cmdpos = ccline.cmdlen = (int)STRLEN(ccline.cmdbuff);
+ redrawcmd();
+ return command_line_changed(s);
+ }
+ beep_flush();
+ return command_line_not_changed(s);
+
+ case Ctrl_V:
+ case Ctrl_Q:
+ s->ignore_drag_release = true;
+ putcmdline('^', true);
+ s->c = get_literal(); // get next (two) character(s)
+ s->do_abbr = false; // don't do abbreviation now
+ // may need to remove ^ when composing char was typed
+ if (enc_utf8 && utf_iscomposing(s->c) && !cmd_silent) {
+ draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos);
+ msg_putchar(' ');
+ cursorcmd();
+ }
+ break;
- validate_cursor();
- /* May redraw the status line to show the cursor position. */
- if (p_ru && curwin->w_status_height > 0)
- curwin->w_redr_status = TRUE;
+ case Ctrl_K:
+ s->ignore_drag_release = true;
+ putcmdline('?', true);
+ s->c = get_digraph(true);
- save_cmdline(&save_ccline);
- update_screen(SOME_VALID);
- restore_cmdline(&save_ccline);
+ if (s->c != NUL) {
+ break;
+ }
- /* Leave it at the end to make CTRL-R CTRL-W work. */
- if (i != 0)
- curwin->w_cursor = end_pos;
+ redrawcmd();
+ return command_line_not_changed(s);
- msg_starthere();
- redrawcmdline();
- did_incsearch = TRUE;
+ case Ctrl__: // CTRL-_: switch language mode
+ if (!p_ari) {
+ break;
}
-
- if (cmdmsg_rl
- || (p_arshape && !p_tbidi && enc_utf8)
- )
- /* Always redraw the whole command line to fix shaping and
- * right-left typing. Not efficient, but it works.
- * Do it only when there are no characters left to read
- * to avoid useless intermediate redraws. */
- if (vpeekc() == NUL)
- redrawcmd();
+ if (p_altkeymap) {
+ cmd_fkmap = !cmd_fkmap;
+ if (cmd_fkmap) {
+ // in Farsi always in Insert mode
+ ccline.overstrike = false;
+ }
+ } else {
+ // Hebrew is default
+ cmd_hkmap = !cmd_hkmap;
+ }
+ return command_line_not_changed(s);
+
+ case K_FOCUSGAINED: // Neovim has been given focus
+ apply_autocmds(EVENT_FOCUSGAINED, NULL, NULL, false, curbuf);
+ return command_line_not_changed(s);
+
+ case K_FOCUSLOST: // Neovim has lost focus
+ apply_autocmds(EVENT_FOCUSLOST, NULL, NULL, false, curbuf);
+ return command_line_not_changed(s);
+
+ default:
+ // Normal character with no special meaning. Just set mod_mask
+ // to 0x0 so that typing Shift-Space in the GUI doesn't enter
+ // the string <S-Space>. This should only happen after ^V.
+ if (!IS_SPECIAL(s->c)) {
+ mod_mask = 0x0;
+ }
+ break;
}
-returncmd:
-
- cmdmsg_rl = FALSE;
+ // End of switch on command line character.
+ // We come here if we have a normal character.
+ if (s->do_abbr && (IS_SPECIAL(s->c) || !vim_iswordc(s->c))
+ // Add ABBR_OFF for characters above 0x100, this is
+ // what check_abbr() expects.
+ && (ccheck_abbr((has_mbyte && s->c >= 0x100) ?
+ (s->c + ABBR_OFF) : s->c)
+ || s->c == Ctrl_RSB)) {
+ return command_line_changed(s);
+ }
- cmd_fkmap = 0;
+ // put the character in the command line
+ if (IS_SPECIAL(s->c) || mod_mask != 0) {
+ put_on_cmdline(get_special_key_name(s->c, mod_mask), -1, true);
+ } else {
+ if (has_mbyte) {
+ s->j = (*mb_char2bytes)(s->c, IObuff);
+ IObuff[s->j] = NUL; // exclude composing chars
+ put_on_cmdline(IObuff, s->j, true);
+ } else {
+ IObuff[0] = s->c;
+ put_on_cmdline(IObuff, 1, true);
+ }
+ }
+ return command_line_changed(s);
+}
- ExpandCleanup(&xpc);
- ccline.xpc = NULL;
- if (did_incsearch) {
- curwin->w_cursor = old_cursor;
- curwin->w_curswant = old_curswant;
- curwin->w_leftcol = old_leftcol;
- curwin->w_topline = old_topline;
- curwin->w_topfill = old_topfill;
- curwin->w_botline = old_botline;
- highlight_match = FALSE;
- validate_cursor(); /* needed for TAB */
- redraw_later(SOME_VALID);
+static int command_line_not_changed(CommandLineState *s)
+{
+ // This part implements incremental searches for "/" and "?" Jump to
+ // cmdline_not_changed when a character has been read but the command line
+ // did not change. Then we only search and redraw if something changed in
+ // the past. Jump to cmdline_changed when the command line did change.
+ // (Sorry for the goto's, I know it is ugly).
+ if (!s->incsearch_postponed) {
+ return 1;
}
+ return command_line_changed(s);
+}
- if (ccline.cmdbuff != NULL) {
- /*
- * Put line in history buffer (":" and "=" only when it was typed).
- */
- if (ccline.cmdlen && firstc != NUL
- && (some_key_typed || histype == HIST_SEARCH)) {
- add_to_history(histype, ccline.cmdbuff, TRUE,
- histype == HIST_SEARCH ? firstc : NUL);
- if (firstc == ':') {
- xfree(new_last_cmdline);
- new_last_cmdline = vim_strsave(ccline.cmdbuff);
+static int command_line_changed(CommandLineState *s)
+{
+ // 'incsearch' highlighting.
+ if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) {
+ pos_T end_pos;
+ proftime_T tm;
+
+ // if there is a character waiting, search and redraw later
+ if (char_avail()) {
+ s->incsearch_postponed = true;
+ return 1;
+ }
+ s->incsearch_postponed = false;
+ curwin->w_cursor = s->old_cursor; // start at old position
+
+ // If there is no command line, don't do anything
+ if (ccline.cmdlen == 0) {
+ s->i = 0;
+ } else {
+ ui_busy_start();
+ ui_flush();
+ ++emsg_off; // So it doesn't beep if bad expr
+ // Set the time limit to half a second.
+ tm = profile_setlimit(500L);
+ s->i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count,
+ SEARCH_KEEP + SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK,
+ &tm);
+ --emsg_off;
+ // if interrupted while searching, behave like it failed
+ if (got_int) {
+ (void)vpeekc(); // remove <C-C> from input stream
+ got_int = false; // don't abandon the command line
+ s->i = 0;
+ } else if (char_avail()) {
+ // cancelled searching because a char was typed
+ s->incsearch_postponed = true;
}
+ ui_busy_stop();
}
- if (gotesc) { /* abandon command line */
- xfree(ccline.cmdbuff);
- ccline.cmdbuff = NULL;
- if (msg_scrolled == 0)
- compute_cmdrow();
- MSG("");
- redraw_cmdline = TRUE;
+ if (s->i != 0) {
+ highlight_match = true; // highlight position
+ } else {
+ highlight_match = false; // remove highlight
}
- }
- /*
- * If the screen was shifted up, redraw the whole screen (later).
- * If the line is too long, clear it, so ruler and shown command do
- * not get printed in the middle of it.
- */
- msg_check();
- msg_scroll = save_msg_scroll;
- redir_off = FALSE;
+ // first restore the old curwin values, so the screen is
+ // positioned in the same way as the actual search command
+ curwin->w_leftcol = s->old_leftcol;
+ curwin->w_topline = s->old_topline;
+ curwin->w_topfill = s->old_topfill;
+ curwin->w_botline = s->old_botline;
+ changed_cline_bef_curs();
+ update_topline();
+
+ if (s->i != 0) {
+ pos_T save_pos = curwin->w_cursor;
+
+ // First move cursor to end of match, then to the start. This
+ // moves the whole match onto the screen when 'nowrap' is set.
+ curwin->w_cursor.lnum += search_match_lines;
+ curwin->w_cursor.col = search_match_endcol;
+ if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
+ curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
+ coladvance((colnr_T)MAXCOL);
+ }
+ validate_cursor();
+ end_pos = curwin->w_cursor;
+ curwin->w_cursor = save_pos;
+ } else {
+ end_pos = curwin->w_cursor; // shutup gcc 4
+ }
- /* When the command line was typed, no need for a wait-return prompt. */
- if (some_key_typed)
- need_wait_return = FALSE;
+ validate_cursor();
+ // May redraw the status line to show the cursor position.
+ if (p_ru && curwin->w_status_height > 0) {
+ curwin->w_redr_status = true;
+ }
- State = save_State;
- setmouse();
- ui_cursor_shape(); /* may show different cursor shape */
+ save_cmdline(&s->save_ccline);
+ update_screen(SOME_VALID);
+ restore_cmdline(&s->save_ccline);
- {
- char_u *p = ccline.cmdbuff;
+ // Leave it at the end to make CTRL-R CTRL-W work.
+ if (s->i != 0) {
+ curwin->w_cursor = end_pos;
+ }
- /* Make ccline empty, getcmdline() may try to use it. */
- ccline.cmdbuff = NULL;
- return p;
+ msg_starthere();
+ redrawcmdline();
+ s->did_incsearch = true;
}
+
+ if (cmdmsg_rl || (p_arshape && !p_tbidi && enc_utf8)) {
+ // Always redraw the whole command line to fix shaping and
+ // right-left typing. Not efficient, but it works.
+ // Do it only when there are no characters left to read
+ // to avoid useless intermediate redraws.
+ if (vpeekc() == NUL) {
+ redrawcmd();
+ }
+ }
+
+ return 1;
+}
+
+/*
+ * getcmdline() - accept a command line starting with firstc.
+ *
+ * firstc == ':' get ":" command line.
+ * firstc == '/' or '?' get search pattern
+ * firstc == '=' get expression
+ * firstc == '@' get text for input() function
+ * firstc == '>' get text for debug mode
+ * firstc == NUL get text for :insert command
+ * firstc == -1 like NUL, and break on CTRL-C
+ *
+ * The line is collected in ccline.cmdbuff, which is reallocated to fit the
+ * command line.
+ *
+ * Careful: getcmdline() can be called recursively!
+ *
+ * Return pointer to allocated string if there is a commandline, NULL
+ * otherwise.
+ */
+char_u *
+getcmdline (
+ int firstc,
+ long count, // only used for incremental search
+ int indent // indent for inside conditionals
+)
+{
+ return command_line_enter(firstc, count, indent);
}
/*
@@ -4943,7 +5060,7 @@ static int ex_window(void)
* Call the main loop until <CR> or CTRL-C is typed.
*/
cmdwin_result = 0;
- main_loop(TRUE, FALSE);
+ normal_enter(true, false);
RedrawingDisabled = i;
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index fbff7d2417..4f345158cf 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -45,7 +45,6 @@
*/
#include <assert.h>
-#include <errno.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index a7472b40e2..1a6c85abaa 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -1,12 +1,4 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* fileio.c: read from and write to a file
*/
@@ -535,11 +527,7 @@ readfile (
if (!newfile) {
return FAIL;
}
- if (perm < 0
-#ifdef ENOENT
- && errno == ENOENT
-#endif
- ) {
+ if (perm == UV_ENOENT) {
/*
* Set the 'new-file' flag, so that when the file has
* been created by someone else, a ":w" will complain.
@@ -582,11 +570,11 @@ readfile (
return OK; /* a new file is not an error */
} else {
filemess(curbuf, sfname, (char_u *)(
-# ifdef EFBIG
- (errno == EFBIG) ? _("[File too big]") :
-# endif
-# ifdef EOVERFLOW
- (errno == EOVERFLOW) ? _("[File too big]") :
+ (fd == UV_EFBIG) ? _("[File too big]") :
+# if defined(UNIX) && defined(EOVERFLOW)
+ // libuv only returns -errno in Unix and in Windows open() does not
+ // set EOVERFLOW
+ (fd == -EOVERFLOW) ? _("[File too big]") :
# endif
_("[Permission Denied]")), 0);
curbuf->b_p_ro = TRUE; /* must use "w!" now */
@@ -1556,6 +1544,11 @@ rewind_retry:
/* First try finding a NL, for Dos and Unix */
if (try_dos || try_unix) {
for (p = ptr; p < ptr + size; ++p) {
+ // Reset the carriage return counter.
+ if (try_mac) {
+ try_mac = 1;
+ }
+
if (*p == NL) {
if (!try_unix
|| (try_dos && p > ptr && p[-1] == CAR))
@@ -1563,6 +1556,8 @@ rewind_retry:
else
fileformat = EOL_UNIX;
break;
+ } else if (*p == CAR && try_mac) {
+ try_mac++;
}
}
@@ -1583,6 +1578,10 @@ rewind_retry:
if (try_mac > try_unix)
fileformat = EOL_MAC;
}
+ } else if (fileformat == EOL_UNKNOWN && try_mac == 1) {
+ // Looking for CR but found no end-of-line markers at all:
+ // use the default format.
+ fileformat = default_fileformat();
}
}
@@ -1934,10 +1933,10 @@ failed:
check_marks_read();
/*
- * Trick: We remember if the last line of the read didn't have
- * an eol even when 'binary' is off, for when writing it again with
- * 'binary' on. This is required for
- * ":autocmd FileReadPost *.gz set bin|'[,']!gunzip" to work.
+ * We remember if the last line of the read didn't have
+ * an eol even when 'binary' is off, to support turning 'fixeol' off,
+ * or writing the read again with 'binary' on. The latter is required
+ * for ":autocmd FileReadPost *.gz set bin|'[,']!gunzip" to work.
*/
curbuf->b_no_eol_lnum = read_no_eol_lnum;
@@ -3322,7 +3321,7 @@ restore_backup:
/* write failed or last line has no EOL: stop here */
if (end == 0
|| (lnum == end
- && write_bin
+ && (write_bin || !buf->b_p_fixeol)
&& (lnum == buf->b_no_eol_lnum
|| (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol)))) {
++lnum; /* written the line, count it */
@@ -4343,8 +4342,6 @@ void shorten_fnames(int force)
/// @return [allocated] - A new filename, made up from:
/// * fname + ext, if fname not NULL.
/// * current dir + ext, if fname is NULL.
-/// On Windows, and if ext starts with ".", a "_" is
-/// preprended to ext (for filename to be valid).
/// Result is guaranteed to:
/// * be ended by <ext>.
/// * have a basename with at most BASENAMELEN chars:
@@ -4398,15 +4395,6 @@ char *modname(const char *fname, const char *ext, bool prepend_dot)
char *s;
s = ptr + strlen(ptr);
-#if defined(WIN3264)
- // If there is no file name, and the extension starts with '.', put a
- // '_' before the dot, because just ".ext" may be invalid if it's on a
- // FAT partition, and on HPFS it doesn't matter.
- else if ((fname == NULL || *fname == NUL) && *ext == '.') {
- *s++ = '_';
- }
-#endif
-
// Append the extension.
// ext can start with '.' and cannot exceed 3 more characters.
strcpy(s, ext);
@@ -6430,7 +6418,7 @@ apply_autocmds_group (
* invalid.
*/
if (fname_io == NULL) {
- if (event == EVENT_COLORSCHEME)
+ if (event == EVENT_COLORSCHEME || event == EVENT_OPTIONSET)
autocmd_fname = NULL;
else if (fname != NULL && *fname != NUL)
autocmd_fname = fname;
@@ -6480,6 +6468,7 @@ apply_autocmds_group (
if (event == EVENT_COLORSCHEME
|| event == EVENT_FILETYPE
|| event == EVENT_FUNCUNDEFINED
+ || event == EVENT_OPTIONSET
|| event == EVENT_QUICKFIXCMDPOST
|| event == EVENT_QUICKFIXCMDPRE
|| event == EVENT_REMOTEREPLY
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index b52938075c..2e32e78062 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -1,11 +1,4 @@
-/* vim: set fdm=marker fdl=1 fdc=3
- *
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
+// vim: set fdm=marker fdl=1 fdc=3
/*
* fold.c: code for folding
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 990d0fb8e2..44d6c086e7 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -1,12 +1,4 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* getchar.c
*
* functions related with getting a character from the user/mapping/redo/...
@@ -1131,7 +1123,7 @@ static void gotchars(char_u *chars, int len)
* - While reading a script file.
* - When no_u_sync is non-zero.
*/
-static void may_sync_undo(void)
+void may_sync_undo(void)
{
if ((!(State & (INSERT + CMDLINE)) || arrow_used)
&& scriptin[curscript] == NULL)
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 0ef0a12889..52eebebf41 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -1,10 +1,3 @@
-/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- */
-
#ifndef NVIM_GLOBALS_H
#define NVIM_GLOBALS_H
@@ -55,6 +48,57 @@
# endif
#endif
+#ifdef WIN32
+# define _PATHSEPSTR "\\"
+#else
+# define _PATHSEPSTR "/"
+#endif
+
+#ifndef FILETYPE_FILE
+# define FILETYPE_FILE "filetype.vim"
+#endif
+
+#ifndef FTPLUGIN_FILE
+# define FTPLUGIN_FILE "ftplugin.vim"
+#endif
+
+#ifndef INDENT_FILE
+# define INDENT_FILE "indent.vim"
+#endif
+
+#ifndef FTOFF_FILE
+# define FTOFF_FILE "ftoff.vim"
+#endif
+
+#ifndef FTPLUGOF_FILE
+# define FTPLUGOF_FILE "ftplugof.vim"
+#endif
+
+#ifndef INDOFF_FILE
+# define INDOFF_FILE "indoff.vim"
+#endif
+
+#define DFLT_ERRORFILE "errors.err"
+
+#ifndef SYS_VIMRC_FILE
+# define SYS_VIMRC_FILE "$VIM" _PATHSEPSTR "sysinit.vim"
+#endif
+
+#ifndef DFLT_HELPFILE
+# define DFLT_HELPFILE "$VIMRUNTIME" _PATHSEPSTR "doc" _PATHSEPSTR "help.txt"
+#endif
+
+#ifndef SYNTAX_FNAME
+# define SYNTAX_FNAME "$VIMRUNTIME" _PATHSEPSTR "syntax" _PATHSEPSTR "%s.vim"
+#endif
+
+#ifndef EXRC_FILE
+# define EXRC_FILE ".exrc"
+#endif
+
+#ifndef VIMRC_FILE
+# define VIMRC_FILE ".nvimrc"
+#endif
/* Values for "starting" */
#define NO_SCREEN 2 /* no screen updating yet */
@@ -633,7 +677,7 @@ EXTERN int silent_mode INIT(= FALSE);
/* set to TRUE when "-s" commandline argument
* used for ex */
-// Set to true when sourcing of startup scripts (nvimrc) is done.
+// Set to true when sourcing of startup scripts (init.vim) is done.
// Used for options that cannot be changed after startup scripts.
EXTERN bool did_source_startup_scripts INIT(= false);
@@ -998,7 +1042,7 @@ EXTERN int lcs_space INIT(= NUL);
EXTERN int lcs_tab1 INIT(= NUL);
EXTERN int lcs_tab2 INIT(= NUL);
EXTERN int lcs_trail INIT(= NUL);
-EXTERN int lcs_conceal INIT(= '-');
+EXTERN int lcs_conceal INIT(= ' ');
/* Characters from 'fillchars' option */
EXTERN int fill_stl INIT(= ' ');
diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c
index 819015a85d..ab8959239b 100644
--- a/src/nvim/hardcopy.c
+++ b/src/nvim/hardcopy.c
@@ -1,17 +1,8 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* hardcopy.c: printing to paper
*/
#include <assert.h>
-#include <errno.h>
#include <string.h>
#include <inttypes.h>
#include <stdint.h>
diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c
index e5cbb58608..3585c26ca2 100644
--- a/src/nvim/if_cscope.c
+++ b/src/nvim/if_cscope.c
@@ -4,9 +4,8 @@
*
* The basic idea/structure of cscope for Vim was borrowed from Nvi. There
* might be a few lines of code that look similar to what Nvi has.
- *
- * See README.txt for an overview of the Vim source code.
*/
+
#include <stdbool.h>
#include <assert.h>
diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c
index bf4f5e8c4d..04d6cbf5e3 100644
--- a/src/nvim/keymap.c
+++ b/src/nvim/keymap.c
@@ -1,4 +1,4 @@
-/************************************************************************
+/*
* functions that use lookup tables for various things, generally to do with
* special key codes.
*/
@@ -283,8 +283,9 @@ static struct key_name_entry {
{K_ZERO, (char_u *)"Nul"},
{K_SNR, (char_u *)"SNR"},
{K_PLUG, (char_u *)"Plug"},
- {K_CURSORHOLD, (char_u *)"CursorHold"},
{K_PASTE, (char_u *)"Paste"},
+ {K_FOCUSGAINED, (char_u *)"FocusGained"},
+ {K_FOCUSLOST, (char_u *)"FocusLost"},
{0, NULL}
};
diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h
index 119bff943a..766362d145 100644
--- a/src/nvim/keymap.h
+++ b/src/nvim/keymap.h
@@ -1,10 +1,3 @@
-/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- */
-
#ifndef NVIM_KEYMAP_H
#define NVIM_KEYMAP_H
@@ -242,7 +235,6 @@ enum key_extra {
, KE_X2RELEASE
, KE_DROP /* DnD data is available */
- , KE_CURSORHOLD /* CursorHold event */
, KE_NOP /* doesn't do something */
, KE_FOCUSGAINED /* focus gained */
, KE_FOCUSLOST /* focus lost */
@@ -437,7 +429,6 @@ enum key_extra {
#define K_FOCUSGAINED TERMCAP2KEY(KS_EXTRA, KE_FOCUSGAINED)
#define K_FOCUSLOST TERMCAP2KEY(KS_EXTRA, KE_FOCUSLOST)
-#define K_CURSORHOLD TERMCAP2KEY(KS_EXTRA, KE_CURSORHOLD)
#define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT)
#define K_PASTE TERMCAP2KEY(KS_EXTRA, KE_PASTE)
diff --git a/src/nvim/log.c b/src/nvim/log.c
index 08b6d0483e..5767da03af 100644
--- a/src/nvim/log.c
+++ b/src/nvim/log.c
@@ -14,7 +14,7 @@
# include <unistd.h>
#endif
-#define USR_LOG_FILE "$HOME/.nvimlog"
+#define USR_LOG_FILE "$HOME" _PATHSEPSTR ".nvimlog"
static uv_mutex_t mutex;
diff --git a/src/nvim/log.h b/src/nvim/log.h
index 152e90760e..32b7276f14 100644
--- a/src/nvim/log.h
+++ b/src/nvim/log.h
@@ -19,7 +19,7 @@
#define ELOGN(...)
// Logging is disabled if NDEBUG or DISABLE_LOG is defined.
-#ifdef NDEBUG
+#if !defined(DISABLE_LOG) && defined(NDEBUG)
# define DISABLE_LOG
#endif
diff --git a/src/nvim/macros.h b/src/nvim/macros.h
index d42997650c..26ab5a7de7 100644
--- a/src/nvim/macros.h
+++ b/src/nvim/macros.h
@@ -1,17 +1,6 @@
#ifndef NVIM_MACROS_H
#define NVIM_MACROS_H
-/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- */
-
-/*
- * macros.h: macro definitions for often used code
- */
-
#ifndef MIN
# define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
#endif
diff --git a/src/nvim/main.c b/src/nvim/main.c
index d865260295..cef10d12d5 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -1,14 +1,5 @@
-/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
#define EXTERN
#include <assert.h>
-#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
@@ -54,6 +45,7 @@
#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/screen.h"
+#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/ui.h"
@@ -287,8 +279,8 @@ int main(int argc, char **argv)
* Set the default values for the options that use Rows and Columns.
*/
win_init_size();
- /* Set the 'diff' option now, so that it can be checked for in a .vimrc
- * file. There is no buffer yet though. */
+ // Set the 'diff' option now, so that it can be checked for in a vimrc
+ // file. There is no buffer yet though.
if (params.diff_mode)
diff_win_options(firstwin, FALSE);
@@ -345,7 +337,7 @@ int main(int argc, char **argv)
*/
load_plugins();
- /* Decide about window layout for diff mode after reading vimrc. */
+ // Decide about window layout for diff mode after reading vimrc.
set_window_layout(&params);
/*
@@ -358,10 +350,8 @@ int main(int argc, char **argv)
mch_exit(0);
}
- /*
- * Set a few option defaults after reading .vimrc files:
- * 'title' and 'icon', Unix: 'shellpipe' and 'shellredir'.
- */
+ // Set a few option defaults after reading vimrc files:
+ // 'title' and 'icon', Unix: 'shellpipe' and 'shellredir'.
set_init_3();
TIME_MSG("inits 3");
@@ -530,228 +520,16 @@ int main(int argc, char **argv)
}
TIME_MSG("before starting main loop");
+ ILOG("Starting Neovim main loop.");
/*
* Call the main command loop. This never returns.
*/
- main_loop(FALSE, FALSE);
+ normal_enter(false, false);
return 0;
}
-/*
- * Main loop: Execute Normal mode commands until exiting Vim.
- * Also used to handle commands in the command-line window, until the window
- * is closed.
- * Also used to handle ":visual" command after ":global": execute Normal mode
- * commands, return when entering Ex mode. "noexmode" is TRUE then.
- */
-void
-main_loop (
- int cmdwin, /* TRUE when working in the command-line window */
- int noexmode /* TRUE when return on entering Ex mode */
-)
-{
- oparg_T oa; /* operator arguments */
- int previous_got_int = FALSE; /* "got_int" was TRUE */
- linenr_T conceal_old_cursor_line = 0;
- linenr_T conceal_new_cursor_line = 0;
- int conceal_update_lines = FALSE;
-
- ILOG("Starting Neovim main loop.");
-
- clear_oparg(&oa);
- while (!cmdwin
- || cmdwin_result == 0
- ) {
- if (stuff_empty()) {
- did_check_timestamps = FALSE;
- if (need_check_timestamps)
- check_timestamps(FALSE);
- if (need_wait_return) /* if wait_return still needed ... */
- wait_return(FALSE); /* ... call it now */
- if (need_start_insertmode && goto_im()
- && !VIsual_active
- ) {
- need_start_insertmode = FALSE;
- stuffReadbuff((char_u *)"i"); /* start insert mode next */
- /* skip the fileinfo message now, because it would be shown
- * after insert mode finishes! */
- need_fileinfo = FALSE;
- }
- }
-
- /* Reset "got_int" now that we got back to the main loop. Except when
- * inside a ":g/pat/cmd" command, then the "got_int" needs to abort
- * the ":g" command.
- * For ":g/pat/vi" we reset "got_int" when used once. When used
- * a second time we go back to Ex mode and abort the ":g" command. */
- if (got_int) {
- if (noexmode && global_busy && !exmode_active && previous_got_int) {
- /* Typed two CTRL-C in a row: go back to ex mode as if "Q" was
- * used and keep "got_int" set, so that it aborts ":g". */
- exmode_active = EXMODE_NORMAL;
- State = NORMAL;
- } else if (!global_busy || !exmode_active) {
- if (!quit_more)
- (void)vgetc(); /* flush all buffers */
- got_int = FALSE;
- }
- previous_got_int = TRUE;
- } else
- previous_got_int = FALSE;
-
- if (!exmode_active)
- msg_scroll = FALSE;
- quit_more = FALSE;
-
- /*
- * If skip redraw is set (for ":" in wait_return()), don't redraw now.
- * If there is nothing in the stuff_buffer or do_redraw is TRUE,
- * update cursor and redraw.
- */
- if (skip_redraw || exmode_active)
- skip_redraw = FALSE;
- else if (do_redraw || stuff_empty()) {
- /* Trigger CursorMoved if the cursor moved. */
- if (!finish_op && (
- has_cursormoved()
- ||
- curwin->w_p_cole > 0
- )
- && !equalpos(last_cursormoved, curwin->w_cursor)) {
- if (has_cursormoved())
- apply_autocmds(EVENT_CURSORMOVED, NULL, NULL,
- FALSE, curbuf);
- if (curwin->w_p_cole > 0) {
- conceal_old_cursor_line = last_cursormoved.lnum;
- conceal_new_cursor_line = curwin->w_cursor.lnum;
- conceal_update_lines = TRUE;
- }
- last_cursormoved = curwin->w_cursor;
- }
-
- /* Trigger TextChanged if b_changedtick differs. */
- if (!finish_op && has_textchanged()
- && last_changedtick != curbuf->b_changedtick) {
- if (last_changedtick_buf == curbuf)
- apply_autocmds(EVENT_TEXTCHANGED, NULL, NULL,
- FALSE, curbuf);
- last_changedtick_buf = curbuf;
- last_changedtick = curbuf->b_changedtick;
- }
-
- /* Scroll-binding for diff mode may have been postponed until
- * here. Avoids doing it for every change. */
- if (diff_need_scrollbind) {
- check_scrollbind((linenr_T)0, 0L);
- diff_need_scrollbind = FALSE;
- }
- /* Include a closed fold completely in the Visual area. */
- foldAdjustVisual();
- /*
- * When 'foldclose' is set, apply 'foldlevel' to folds that don't
- * contain the cursor.
- * When 'foldopen' is "all", open the fold(s) under the cursor.
- * This may mark the window for redrawing.
- */
- if (hasAnyFolding(curwin) && !char_avail()) {
- foldCheckClose();
- if (fdo_flags & FDO_ALL)
- foldOpenCursor();
- }
-
- /*
- * Before redrawing, make sure w_topline is correct, and w_leftcol
- * if lines don't wrap, and w_skipcol if lines wrap.
- */
- update_topline();
- validate_cursor();
-
- if (VIsual_active)
- update_curbuf(INVERTED); /* update inverted part */
- else if (must_redraw)
- update_screen(0);
- else if (redraw_cmdline || clear_cmdline)
- showmode();
- redraw_statuslines();
- if (need_maketitle)
- maketitle();
- /* display message after redraw */
- if (keep_msg != NULL) {
- char_u *p;
-
- // msg_attr_keep() will set keep_msg to NULL, must free the string
- // here. Don't reset keep_msg, msg_attr_keep() uses it to check for
- // duplicates.
- p = keep_msg;
- msg_attr(p, keep_msg_attr);
- xfree(p);
- }
- if (need_fileinfo) { /* show file info after redraw */
- fileinfo(FALSE, TRUE, FALSE);
- need_fileinfo = FALSE;
- }
-
- emsg_on_display = FALSE; /* can delete error message now */
- did_emsg = FALSE;
- msg_didany = FALSE; /* reset lines_left in msg_start() */
- may_clear_sb_text(); /* clear scroll-back text on next msg */
- showruler(FALSE);
-
- if (conceal_update_lines
- && (conceal_old_cursor_line != conceal_new_cursor_line
- || conceal_cursor_line(curwin)
- || need_cursor_line_redraw)) {
- if (conceal_old_cursor_line != conceal_new_cursor_line
- && conceal_old_cursor_line
- <= curbuf->b_ml.ml_line_count)
- update_single_line(curwin, conceal_old_cursor_line);
- update_single_line(curwin, conceal_new_cursor_line);
- curwin->w_valid &= ~VALID_CROW;
- }
- setcursor();
-
- do_redraw = FALSE;
-
- /* Now that we have drawn the first screen all the startup stuff
- * has been done, close any file for startup messages. */
- if (time_fd != NULL) {
- TIME_MSG("first screen update");
- TIME_MSG("--- NVIM STARTED ---");
- fclose(time_fd);
- time_fd = NULL;
- }
- }
-
- /*
- * Update w_curswant if w_set_curswant has been set.
- * Postponed until here to avoid computing w_virtcol too often.
- */
- update_curswant();
-
- /*
- * May perform garbage collection when waiting for a character, but
- * only at the very toplevel. Otherwise we may be using a List or
- * Dict internally somewhere.
- * "may_garbage_collect" is reset in vgetc() which is invoked through
- * do_exmode() and normal_cmd().
- */
- may_garbage_collect = (!cmdwin && !noexmode);
- /*
- * If we're invoked as ex, do a round of ex commands.
- * Otherwise, get and execute a normal mode command.
- */
- if (exmode_active) {
- if (noexmode) /* End of ":global/path/visual" commands */
- return;
- do_exmode(exmode_active == EXMODE_VIM);
- } else
- normal_cmd(&oa, TRUE);
- }
-}
-
-
/* Exit properly */
void getout(int exitval)
{
@@ -1148,9 +926,6 @@ static void command_line_scan(mparm_T *parmp)
want_argument = TRUE;
break;
- case 'X': /* "-X" don't connect to X server */
- break;
-
case 'Z': /* "-Z" restricted mode */
restricted = TRUE;
break;
@@ -1551,8 +1326,8 @@ static void create_windows(mparm_T *parmp)
if (parmp->window_count == 0)
parmp->window_count = GARGCOUNT;
if (parmp->window_count > 1) {
- /* Don't change the windows if there was a command in .vimrc that
- * already split some windows */
+ // Don't change the windows if there was a command in vimrc that
+ // already split some windows
if (parmp->window_layout == 0)
parmp->window_layout = WIN_HOR;
if (parmp->window_layout == WIN_TABS) {
@@ -1574,14 +1349,11 @@ static void create_windows(mparm_T *parmp)
getout(1);
do_modelines(0); /* do modelines */
} else {
- /*
- * Open a buffer for windows that don't have one yet.
- * Commands in the .vimrc might have loaded a file or split the window.
- * Watch out for autocommands that delete a window.
- */
- /*
- * Don't execute Win/Buf Enter/Leave autocommands here
- */
+ // Open a buffer for windows that don't have one yet.
+ // Commands in the vimrc might have loaded a file or split the window.
+ // Watch out for autocommands that delete a window.
+ //
+ // Don't execute Win/Buf Enter/Leave autocommands here
++autocmd_no_enter;
++autocmd_no_leave;
dorewind = TRUE;
@@ -1691,8 +1463,8 @@ static void edit_buffers(mparm_T *parmp)
}
advance = TRUE;
- /* Only open the file if there is no file in this window yet (that can
- * happen when .vimrc contains ":sall"). */
+ // Only open the file if there is no file in this window yet (that can
+ // happen when vimrc contains ":sall").
if (curbuf == firstwin->w_buffer || curbuf->b_ffname == NULL) {
curwin->w_arg_idx = arg_idx;
/* Edit file from arg list, if there is one. When "Quit" selected
@@ -1801,117 +1573,125 @@ static void exe_commands(mparm_T *parmp)
TIME_MSG("executing command arguments");
}
-/*
- * Source startup scripts.
- */
-static void source_startup_scripts(mparm_T *parmp)
+/// Source vimrc or do other user initialization
+///
+/// Does one of the following things, stops after whichever succeeds:
+///
+/// 1. Execution of VIMINIT environment variable.
+/// 2. Sourcing user vimrc file ($XDG_CONFIG_HOME/nvim/init.vim).
+/// 3. Sourcing other vimrc files ($XDG_CONFIG_DIRS[1]/nvim/init.vim, …).
+/// 4. Execution of EXINIT environment variable.
+///
+/// @return True if it is needed to attempt to source exrc file according to
+/// 'exrc' option definition.
+static bool do_user_initialization(void)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
- int i;
+ bool do_exrc = p_exrc;
+ if (process_env("VIMINIT", true) == OK) {
+ do_exrc = p_exrc;
+ return do_exrc;
+ }
+ char_u *user_vimrc = (char_u *)stdpaths_user_conf_subpath("init.vim");
+ if (do_source(user_vimrc, true, DOSO_VIMRC) != FAIL) {
+ do_exrc = p_exrc;
+ if (do_exrc) {
+ do_exrc = (path_full_compare((char_u *)VIMRC_FILE, user_vimrc, false)
+ != kEqualFiles);
+ }
+ xfree(user_vimrc);
+ return do_exrc;
+ }
+ xfree(user_vimrc);
+ char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs);
+ if (config_dirs != NULL) {
+ const void *iter = NULL;
+ do {
+ const char *dir;
+ size_t dir_len;
+ iter = vim_colon_env_iter(config_dirs, iter, &dir, &dir_len);
+ if (dir == NULL || dir_len == 0) {
+ break;
+ }
+ const char path_tail[] = { 'n', 'v', 'i', 'm', PATHSEP,
+ 'i', 'n', 'i', 't', '.', 'v', 'i', 'm', NUL };
+ char *vimrc = xmalloc(dir_len + sizeof(path_tail) + 1);
+ memmove(vimrc, dir, dir_len);
+ vimrc[dir_len] = PATHSEP;
+ memmove(vimrc + dir_len + 1, path_tail, sizeof(path_tail));
+ if (do_source((char_u *) vimrc, true, DOSO_VIMRC) != FAIL) {
+ do_exrc = p_exrc;
+ if (do_exrc) {
+ do_exrc = (path_full_compare((char_u *)VIMRC_FILE, (char_u *)vimrc,
+ false) != kEqualFiles);
+ }
+ xfree(vimrc);
+ xfree(config_dirs);
+ return do_exrc;
+ }
+ xfree(vimrc);
+ } while (iter != NULL);
+ xfree(config_dirs);
+ }
+ if (process_env("EXINIT", false) == OK) {
+ do_exrc = p_exrc;
+ return do_exrc;
+ }
+ return do_exrc;
+}
- /*
- * If -u argument given, use only the initializations from that file and
- * nothing else.
- */
+/// Source startup scripts
+///
+/// @param[in]
+static void source_startup_scripts(const mparm_T *const parmp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // If -u argument given, use only the initializations from that file and
+ // nothing else.
if (parmp->use_vimrc != NULL) {
if (strcmp(parmp->use_vimrc, "NONE") == 0
|| strcmp(parmp->use_vimrc, "NORC") == 0) {
if (parmp->use_vimrc[2] == 'N')
- p_lpl = FALSE; // don't load plugins either
+ p_lpl = false; // don't load plugins either
} else {
if (do_source((char_u *)parmp->use_vimrc, FALSE, DOSO_NONE) != OK)
EMSG2(_("E282: Cannot read from \"%s\""), parmp->use_vimrc);
}
} else if (!silent_mode) {
-
- /*
- * Get system wide defaults, if the file name is defined.
- */
#ifdef SYS_VIMRC_FILE
- (void)do_source((char_u *)SYS_VIMRC_FILE, FALSE, DOSO_NONE);
-#endif
-
- /*
- * Try to read initialization commands from the following places:
- * - environment variable VIMINIT
- * - user vimrc file (~/.vimrc)
- * - second user vimrc file ($VIM/.vimrc for Dos)
- * - environment variable EXINIT
- * - user exrc file (~/.exrc)
- * - second user exrc file ($VIM/.exrc for Dos)
- * The first that exists is used, the rest is ignored.
- */
- if (process_env("VIMINIT", true) != OK) {
- if (do_source((char_u *)USR_VIMRC_FILE, TRUE, DOSO_VIMRC) == FAIL
-#ifdef USR_VIMRC_FILE2
- && do_source((char_u *)USR_VIMRC_FILE2, TRUE,
- DOSO_VIMRC) == FAIL
-#endif
-#ifdef USR_VIMRC_FILE3
- && do_source((char_u *)USR_VIMRC_FILE3, TRUE,
- DOSO_VIMRC) == FAIL
-#endif
- && process_env("EXINIT", FALSE) == FAIL
- && do_source((char_u *)USR_EXRC_FILE, FALSE, DOSO_NONE) == FAIL) {
-#ifdef USR_EXRC_FILE2
- (void)do_source((char_u *)USR_EXRC_FILE2, FALSE, DOSO_NONE);
+ // Get system wide defaults, if the file name is defined.
+ (void) do_source((char_u *)SYS_VIMRC_FILE, false, DOSO_NONE);
#endif
- }
- }
- /*
- * Read initialization commands from ".vimrc" or ".exrc" in current
- * directory. This is only done if the 'exrc' option is set.
- * Because of security reasons we disallow shell and write commands
- * now, except for unix if the file is owned by the user or 'secure'
- * option has been reset in environment of global ".exrc" or ".vimrc".
- * Only do this if VIMRC_FILE is not the same as USR_VIMRC_FILE or
- * SYS_VIMRC_FILE.
- */
- if (p_exrc) {
+ if (do_user_initialization()) {
+ // Read initialization commands from ".vimrc" or ".exrc" in current
+ // directory. This is only done if the 'exrc' option is set.
+ // Because of security reasons we disallow shell and write commands
+ // now, except for unix if the file is owned by the user or 'secure'
+ // option has been reset in environment of global "exrc" or "vimrc".
+ // Only do this if VIMRC_FILE is not the same as vimrc file sourced in
+ // do_user_initialization.
#if defined(UNIX)
- /* If ".vimrc" file is not owned by user, set 'secure' mode. */
+ // If vimrc file is not owned by user, set 'secure' mode.
if (!file_owned(VIMRC_FILE))
#endif
secure = p_secure;
- i = FAIL;
- if (path_full_compare((char_u *)USR_VIMRC_FILE,
- (char_u *)VIMRC_FILE, FALSE) != kEqualFiles
-#ifdef USR_VIMRC_FILE2
- && path_full_compare((char_u *)USR_VIMRC_FILE2,
- (char_u *)VIMRC_FILE, FALSE) != kEqualFiles
-#endif
-#ifdef USR_VIMRC_FILE3
- && path_full_compare((char_u *)USR_VIMRC_FILE3,
- (char_u *)VIMRC_FILE, FALSE) != kEqualFiles
-#endif
-#ifdef SYS_VIMRC_FILE
- && path_full_compare((char_u *)SYS_VIMRC_FILE,
- (char_u *)VIMRC_FILE, FALSE) != kEqualFiles
-#endif
- )
- i = do_source((char_u *)VIMRC_FILE, TRUE, DOSO_VIMRC);
-
- if (i == FAIL) {
+ if (do_source((char_u *)VIMRC_FILE, true, DOSO_VIMRC) == FAIL) {
#if defined(UNIX)
- /* if ".exrc" is not owned by user set 'secure' mode */
- if (!file_owned(EXRC_FILE))
+ // if ".exrc" is not owned by user set 'secure' mode
+ if (!file_owned(EXRC_FILE)) {
secure = p_secure;
- else
+ } else {
secure = 0;
+ }
#endif
- if ( path_full_compare((char_u *)USR_EXRC_FILE,
- (char_u *)EXRC_FILE, FALSE) != kEqualFiles
-#ifdef USR_EXRC_FILE2
- && path_full_compare((char_u *)USR_EXRC_FILE2,
- (char_u *)EXRC_FILE, FALSE) != kEqualFiles
-#endif
- )
- (void)do_source((char_u *)EXRC_FILE, FALSE, DOSO_NONE);
+ (void)do_source((char_u *)EXRC_FILE, false, DOSO_NONE);
}
}
- if (secure == 2)
- need_wait_return = TRUE;
+ if (secure == 2) {
+ need_wait_return = true;
+ }
secure = 0;
}
did_source_startup_scripts = true;
@@ -2041,8 +1821,8 @@ static void usage(void)
mch_msg(_(" -n No swap file, use memory only\n"));
mch_msg(_(" -r, -L List swap files and exit\n"));
mch_msg(_(" -r <file> Recover crashed session\n"));
- mch_msg(_(" -u <nvimrc> Use <nvimrc> instead of the default\n"));
- mch_msg(_(" -i <shada> Use <shada> instead of the default " SHADA_FILE "\n")); // NOLINT(whitespace/line_length)
+ mch_msg(_(" -u <vimrc> Use <vimrc> instead of the default\n"));
+ mch_msg(_(" -i <shada> Use <shada> instead of the default\n"));
mch_msg(_(" --noplugin Don't load plugin scripts\n"));
mch_msg(_(" -o[N] Open N windows (default: one for each file)\n"));
mch_msg(_(" -O[N] Like -o but split vertically\n"));
@@ -2050,7 +1830,7 @@ static void usage(void)
mch_msg(_(" + Start at end of file\n"));
mch_msg(_(" +<linenum> Start at line <linenum>\n"));
mch_msg(_(" +/<pattern> Start at first occurrence of <pattern>\n"));
- mch_msg(_(" --cmd <command> Execute <command> before loading any nvimrc\n"));
+ mch_msg(_(" --cmd <command> Execute <command> before loading any vimrc\n"));
mch_msg(_(" -c <command> Execute <command> after loading the first file\n"));
mch_msg(_(" -S <session> Source <session> after loading the first file\n"));
mch_msg(_(" -s <scriptin> Read Normal mode commands from <scriptin>\n"));
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index dd49b311d3..e2f212340c 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -1,17 +1,8 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* mark.c: functions for setting marks and jumping to them
*/
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <string.h>
#include <limits.h>
@@ -546,19 +537,26 @@ int check_mark(pos_T *pos)
return OK;
}
-/*
- * clrallmarks() - clear all marks in the buffer 'buf'
- *
- * Used mainly when trashing the entire buffer during ":e" type commands
- */
-void clrallmarks(buf_T *buf)
+/// Clear all marks and change list in the given buffer
+///
+/// Used mainly when trashing the entire buffer during ":e" type commands.
+///
+/// @param[out] buf Buffer to clear marks in.
+void clrallmarks(buf_T *const buf)
+ FUNC_ATTR_NONNULL_ALL
{
- memset(&(buf->b_namedm[0]), 0, sizeof(buf->b_namedm));
- buf->b_op_start.lnum = 0; /* start/end op mark cleared */
+ for (size_t i = 0; i < NMARKS; i++) {
+ clear_fmark(&buf->b_namedm[i]);
+ }
+ clear_fmark(&buf->b_last_cursor);
+ buf->b_last_cursor.mark.lnum = 1;
+ clear_fmark(&buf->b_last_insert);
+ clear_fmark(&buf->b_last_change);
+ buf->b_op_start.lnum = 0; // start/end op mark cleared
buf->b_op_end.lnum = 0;
- RESET_FMARK(&buf->b_last_cursor, ((pos_T) {1, 0, 0}), 0); // '" mark
- CLEAR_FMARK(&buf->b_last_insert); // '^ mark
- CLEAR_FMARK(&buf->b_last_change); // '. mark
+ for (int i = 0; i < buf->b_changelistlen; i++) {
+ clear_fmark(&buf->b_changelist[i]);
+ }
buf->b_changelistlen = 0;
}
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 087d2e677c..b02c18c53b 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -1,13 +1,6 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- * Multibyte extensions partly by Sung-Hoon Baek
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-/*
* mbyte.c: Code specifically for handling multi-byte characters.
+ * Multibyte extensions partly by Sung-Hoon Baek
*
* The encoding used in the core is set with 'encoding'. When 'encoding' is
* changed, the following four variables are set (for speed).
@@ -71,7 +64,6 @@
* some commands, like ":menutrans"
*/
-#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c
index b6a341d5e8..3df4cbc4a3 100644
--- a/src/nvim/memfile.c
+++ b/src/nvim/memfile.c
@@ -1,11 +1,3 @@
-/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
/// An abstraction to handle blocks of memory which can be stored in a file.
/// This is the implementation of a sort of virtual memory.
///
@@ -44,7 +36,6 @@
/// mf_fullname() make file name full path (use before first :cd)
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <string.h>
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index d90e91be5d..c91a25df6e 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -1,11 +1,3 @@
-/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
/* for debugging */
/* #define CHECK(c, s) if (c) EMSG(s) */
#define CHECK(c, s)
@@ -406,10 +398,12 @@ void ml_setname(buf_T *buf)
* Try all directories in the 'directory' option.
*/
dirp = p_dir;
+ bool found_existing_dir = false;
for (;; ) {
if (*dirp == NUL) /* tried all directories, fail */
break;
- fname = findswapname(buf, &dirp, mfp->mf_fname);
+ fname = (char_u *)findswapname(buf, (char **)&dirp, (char *)mfp->mf_fname,
+ &found_existing_dir);
/* alloc's fname */
if (dirp == NULL) /* out of memory */
break;
@@ -504,13 +498,15 @@ void ml_open_file(buf_T *buf)
* Try all directories in 'directory' option.
*/
dirp = p_dir;
+ bool found_existing_dir = false;
for (;; ) {
if (*dirp == NUL)
break;
- /* There is a small chance that between choosing the swap file name
- * and creating it, another Vim creates the file. In that case the
- * creation will fail and we will use another directory. */
- fname = findswapname(buf, &dirp, NULL); /* allocates fname */
+ // There is a small chance that between choosing the swap file name
+ // and creating it, another Vim creates the file. In that case the
+ // creation will fail and we will use another directory.
+ fname = (char_u *)findswapname(buf, (char **)&dirp, NULL,
+ &found_existing_dir);
if (dirp == NULL)
break; /* out of memory */
if (fname == NULL)
@@ -2986,7 +2982,7 @@ static void ml_lineadd(buf_T *buf, int count)
* If it worked returns OK and the resolved link in "buf[MAXPATHL]".
* Otherwise returns FAIL.
*/
-int resolve_symlink(char_u *fname, char_u *buf)
+int resolve_symlink(const char_u *fname, char_u *buf)
{
char_u tmp[MAXPATHL];
int ret;
@@ -3222,45 +3218,56 @@ static int do_swapexists(buf_T *buf, char_u *fname)
return 0;
}
-/*
- * Find out what name to use for the swap file for buffer 'buf'.
- *
- * Several names are tried to find one that does not exist
- * Returns the name in allocated memory or NULL.
- * When out of memory "dirp" is set to NULL.
- *
- * Note: If BASENAMELEN is not correct, you will get error messages for
- * not being able to open the swap or undo file
- * Note: May trigger SwapExists autocmd, pointers may change!
- */
-static char_u *
-findswapname (
- buf_T *buf,
- char_u **dirp, /* pointer to list of directories */
- char_u *old_fname /* don't give warning for this file name */
-)
+/// Find out what name to use for the swap file for buffer 'buf'.
+///
+/// Several names are tried to find one that does not exist. Last directory in
+/// option is automatically created.
+///
+/// @note If BASENAMELEN is not correct, you will get error messages for
+/// not being able to open the swap or undo file.
+/// @note May trigger SwapExists autocmd, pointers may change!
+///
+/// @param[in] buf Buffer for which swap file names needs to be found.
+/// @param[in,out] dirp Pointer to a list of directories. When out of memory,
+/// is set to NULL. Is advanced to the next directory in
+/// the list otherwise.
+/// @param[in] old_fname Allowed existing swap file name. Except for this
+/// case, name of the non-existing file is used.
+/// @param[in,out] found_existing_dir If points to true, then new directory
+/// for swap file is not created. At first
+/// findswapname() call this argument must
+/// point to false. This parameter may only
+/// be set to true by this function, it is
+/// never set to false.
+///
+/// @return [allocated] Name of the swap file.
+static char *findswapname(buf_T *buf, char **dirp, char *old_fname,
+ bool *found_existing_dir)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1, 2, 4)
{
- char_u *fname;
- int n;
- char_u *dir_name;
- char_u *buf_fname = buf->b_fname;
+ char *fname;
+ size_t n;
+ char *dir_name;
+ char *buf_fname = (char *) buf->b_fname;
/*
* Isolate a directory name from *dirp and put it in dir_name.
* First allocate some memory to put the directory name in.
*/
- dir_name = xmalloc(STRLEN(*dirp) + 1);
- (void)copy_option_part(dirp, dir_name, 31000, ",");
+ const size_t dir_len = strlen(*dirp) + 1;
+ dir_name = xmalloc(dir_len);
+ (void)copy_option_part((char_u **) dirp, (char_u *) dir_name, dir_len, ",");
/*
* we try different names until we find one that does not exist yet
*/
- fname = makeswapname(buf_fname, buf->b_ffname, buf, dir_name);
+ fname = (char *)makeswapname((char_u *)buf_fname, buf->b_ffname, buf,
+ (char_u *)dir_name);
for (;; ) {
if (fname == NULL) /* must be out of memory */
break;
- if ((n = (int)STRLEN(fname)) == 0) { /* safety check */
+ if ((n = strlen(fname)) == 0) { /* safety check */
xfree(fname);
fname = NULL;
break;
@@ -3269,7 +3276,7 @@ findswapname (
// Extra security check: When a swap file is a symbolic link, this
// is most likely a symlink attack.
FileInfo file_info;
- bool file_or_link_found = os_fileinfo_link((char *)fname, &file_info);
+ bool file_or_link_found = os_fileinfo_link(fname, &file_info);
if (!file_or_link_found) {
break;
}
@@ -3300,7 +3307,7 @@ findswapname (
* Try to read block 0 from the swap file to get the original
* file name (and inode number).
*/
- fd = os_open((char *)fname, O_RDONLY, 0);
+ fd = os_open(fname, O_RDONLY, 0);
if (fd >= 0) {
if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) {
/*
@@ -3311,7 +3318,7 @@ findswapname (
if (b0.b0_flags & B0_SAME_DIR) {
if (fnamecmp(path_tail(buf->b_ffname),
path_tail(b0.b0_fname)) != 0
- || !same_directory(fname, buf->b_ffname)) {
+ || !same_directory((char_u *) fname, buf->b_ffname)) {
/* Symlinks may point to the same file even
* when the name differs, need to check the
* inode too. */
@@ -3351,12 +3358,12 @@ findswapname (
* user anyway.
*/
if (swap_exists_action != SEA_NONE
- && has_autocmd(EVENT_SWAPEXISTS, buf_fname, buf))
- choice = do_swapexists(buf, fname);
+ && has_autocmd(EVENT_SWAPEXISTS, (char_u *) buf_fname, buf))
+ choice = do_swapexists(buf, (char_u *) fname);
if (choice == 0) {
/* Show info about the existing swap file. */
- attention_message(buf, fname);
+ attention_message(buf, (char_u *) fname);
/* We don't want a 'q' typed at the more-prompt
* interrupt loading a file. */
@@ -3364,20 +3371,21 @@ findswapname (
}
if (swap_exists_action != SEA_NONE && choice == 0) {
- char_u *name;
+ char *name;
- name = xmalloc(STRLEN(fname)
- + STRLEN(_("Swap file \""))
- + STRLEN(_("\" already exists!")) + 5);
+ const size_t fname_len = strlen(fname);
+ name = xmalloc(fname_len
+ + strlen(_("Swap file \""))
+ + strlen(_("\" already exists!")) + 5);
STRCPY(name, _("Swap file \""));
- home_replace(NULL, fname, name + STRLEN(name),
- 1000, TRUE);
+ home_replace(NULL, (char_u *) fname, (char_u *)&name[strlen(name)],
+ fname_len, true);
STRCAT(name, _("\" already exists!"));
choice = do_dialog(VIM_WARNING,
(char_u *)_("VIM - ATTENTION"),
- name == NULL
- ? (char_u *)_("Swap file already exists!")
- : name,
+ (char_u *)(name == NULL
+ ? _("Swap file already exists!")
+ : name),
# if defined(UNIX)
process_still_running
? (char_u *)_(
@@ -3409,7 +3417,7 @@ findswapname (
swap_exists_action = SEA_RECOVER;
break;
case 4:
- os_remove((char *)fname);
+ os_remove(fname);
break;
case 5:
swap_exists_action = SEA_QUIT;
@@ -3421,7 +3429,7 @@ findswapname (
}
/* If the file was deleted this fname can be used. */
- if (!os_file_exists(fname))
+ if (!os_file_exists((char_u *) fname))
break;
} else
{
@@ -3454,6 +3462,19 @@ findswapname (
--fname[n - 1]; /* ".swo", ".swn", etc. */
}
+ if (os_isdir((char_u *) dir_name)) {
+ *found_existing_dir = true;
+ } else if (!*found_existing_dir && **dirp == NUL) {
+ int ret;
+ char *failed_dir;
+ if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir)) != 0) {
+ EMSG3(_("E303: Unable to create directory \"%s\" for swap file, "
+ "recovery impossible: %s"),
+ failed_dir, os_strerror(ret));
+ xfree(failed_dir);
+ }
+ }
+
xfree(dir_name);
return fname;
}
@@ -3933,8 +3954,10 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp)
if (ffdos)
size += lnum - 1;
- /* Don't count the last line break if 'bin' and 'noeol'. */
- if (buf->b_p_bin && !buf->b_p_eol && buf->b_ml.ml_line_count == lnum) {
+ /* Don't count the last line break if 'noeol' and ('bin' or
+ * 'nofixeol'). */
+ if ((!buf->b_p_fixeol || buf->b_p_bin) && !buf->b_p_eol
+ && buf->b_ml.ml_line_count == lnum) {
size -= ffdos + 1;
}
}
diff --git a/src/nvim/memline_defs.h b/src/nvim/memline_defs.h
index bcc1c673d2..34a002af5d 100644
--- a/src/nvim/memline_defs.h
+++ b/src/nvim/memline_defs.h
@@ -41,7 +41,7 @@ typedef struct memline {
int ml_flags;
infoptr_T *ml_stack; /* stack of pointer blocks (array of IPTRs) */
- int ml_stack_top; /* current top if ml_stack */
+ int ml_stack_top; /* current top of ml_stack */
int ml_stack_size; /* total number of entries in ml_stack */
linenr_T ml_line_lnum; /* line number of cached line, 0 if not valid */
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index d25dc7c941..8db47b79c1 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -1,7 +1,6 @@
// Various routines dealing with allocation and deallocation of memory.
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <string.h>
#include <stdbool.h>
@@ -42,8 +41,6 @@ void try_to_free_memory(void)
clear_sb_text();
// Try to save all buffers and release as many blocks as possible
mf_release_all();
- // cleanup recursive lists/dicts
- garbage_collect();
trying_to_free = false;
}
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index 9857b8a778..91a72abfc5 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -1,14 +1,6 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- * GUI/Motif support by Robert Webb
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* Code for menus. Used for the GUI and 'wildmenu'.
+ * GUI/Motif support by Robert Webb
*/
#include <assert.h>
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 5f06506a31..66b8b9b5d2 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -1,17 +1,8 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* message.c: functions for displaying messages on the command line
*/
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdarg.h>
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index 6829e4988c..96ef6cbaef 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -1,17 +1,8 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* misc1.c: functions that didn't seem to fit elsewhere
*/
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
@@ -19,7 +10,6 @@
#include "nvim/vim.h"
#include "nvim/ascii.h"
-#include "nvim/version.h"
#include "nvim/misc1.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
diff --git a/src/nvim/misc2.c b/src/nvim/misc2.c
index d9bc35470f..3c0a1414a6 100644
--- a/src/nvim/misc2.c
+++ b/src/nvim/misc2.c
@@ -1,16 +1,7 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* misc2.c: Various functions.
*/
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <string.h>
@@ -479,7 +470,7 @@ void put_time(FILE *fd, time_t time_)
{
uint8_t buf[8];
time_to_bytes(time_, buf);
- fwrite(buf, sizeof(uint8_t), ARRAY_SIZE(buf), fd);
+ (void)fwrite(buf, sizeof(uint8_t), ARRAY_SIZE(buf), fd);
}
/// Writes time_t to "buf[8]".
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 3831004703..eb55397511 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -1,11 +1,4 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-/*
* move.c: Functions for moving the cursor and scrolling text.
*
* There are two ways to move the cursor:
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 713aa55500..a2e473fcb8 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1,18 +1,10 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-/*
* normal.c: Contains the main routine for processing characters in command
* mode. Communicates closely with the code in ops.c to handle
* the operators.
*/
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <string.h>
#include <stdbool.h>
@@ -61,10 +53,35 @@
#include "nvim/mouse.h"
#include "nvim/undo.h"
#include "nvim/window.h"
+#include "nvim/state.h"
#include "nvim/event/loop.h"
#include "nvim/os/time.h"
#include "nvim/os/input.h"
+typedef struct normal_state {
+ VimState state;
+ linenr_T conceal_old_cursor_line;
+ linenr_T conceal_new_cursor_line;
+ bool command_finished;
+ bool ctrl_w;
+ bool need_flushbuf;
+ bool conceal_update_lines;
+ bool set_prevcount;
+ bool previous_got_int; // `got_int` was true
+ bool cmdwin; // command-line window normal mode
+ bool noexmode; // true if the normal mode was pushed from
+ // ex mode(:global or :visual for example)
+ bool toplevel; // top-level normal mode
+ oparg_T oa; // operator arguments
+ cmdarg_T ca; // command arguments
+ int mapped_len;
+ int old_mapped_len;
+ int idx;
+ int c;
+ int old_col;
+ pos_T old_pos;
+} NormalState;
+
/*
* The Visual area is remembered for reselection.
*/
@@ -79,6 +96,14 @@ static int restart_VIsual_select = 0;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "normal.c.generated.h"
#endif
+
+static inline void normal_state_init(NormalState *s)
+{
+ memset(s, 0, sizeof(NormalState));
+ s->state.check = normal_check;
+ s->state.execute = normal_execute;
+}
+
/*
* nv_*(): functions called to handle Normal and Visual mode commands.
* n_*(): functions called to handle Normal mode commands.
@@ -315,7 +340,9 @@ static const struct nv_cmd {
{K_SELECT, nv_select, 0, 0},
{K_F8, farsi_fkey, 0, 0},
{K_F9, farsi_fkey, 0, 0},
- {K_CURSORHOLD, nv_cursorhold, NV_KEEPREG, 0},
+ {K_EVENT, nv_event, NV_KEEPREG, 0},
+ {K_FOCUSGAINED, nv_focusgained, NV_KEEPREG, 0},
+ {K_FOCUSLOST, nv_focuslost, NV_KEEPREG, 0},
};
/* Number of commands in nv_cmds[]. */
@@ -418,650 +445,946 @@ static int find_command(int cmdchar)
return idx;
}
-/*
- * Execute a command in Normal mode.
- */
-void
-normal_cmd (
- oparg_T *oap,
- bool toplevel /* true when called from main() */
-)
-{
- cmdarg_T ca; /* command arguments */
- int c;
- bool ctrl_w = false; /* got CTRL-W command */
- int old_col = curwin->w_curswant;
- bool need_flushbuf; /* need to call ui_flush() */
- pos_T old_pos; /* cursor position before command */
- int mapped_len;
- static int old_mapped_len = 0;
- int idx;
- bool set_prevcount = false;
+// Normal state entry point. This is called on:
+//
+// - Startup, In this case the function never returns.
+// - The command-line window is opened(`q:`). Returns when `cmdwin_result` != 0.
+// - The :visual command is called from :global in ex mode, `:global/PAT/visual`
+// for example. Returns when re-entering ex mode(because ex mode recursion is
+// not allowed)
+//
+// This used to be called main_loop on main.c
+void normal_enter(bool cmdwin, bool noexmode)
+{
+ NormalState state;
+ normal_state_init(&state);
+ state.cmdwin = cmdwin;
+ state.noexmode = noexmode;
+ state.toplevel = !cmdwin && !noexmode;
+ state_enter(&state.state);
+}
+
+static void normal_prepare(NormalState *s)
+{
+ memset(&s->ca, 0, sizeof(s->ca)); // also resets ca.retval
+ s->ca.oap = &s->oa;
+
+ // Use a count remembered from before entering an operator. After typing "3d"
+ // we return from normal_cmd() and come back here, the "3" is remembered in
+ // "opcount".
+ s->ca.opcount = opcount;
+
+ // If there is an operator pending, then the command we take this time will
+ // terminate it. Finish_op tells us to finish the operation before returning
+ // this time (unless the operation was cancelled).
+ int c = finish_op;
+ finish_op = (s->oa.op_type != OP_NOP);
+ if (finish_op != c) {
+ ui_cursor_shape(); // may show different cursor shape
+ }
- memset(&ca, 0, sizeof(ca)); /* also resets ca.retval */
- ca.oap = oap;
+ // When not finishing an operator and no register name typed, reset the count.
+ if (!finish_op && !s->oa.regname) {
+ s->ca.opcount = 0;
+ s->set_prevcount = true;
+ }
- /* Use a count remembered from before entering an operator. After typing
- * "3d" we return from normal_cmd() and come back here, the "3" is
- * remembered in "opcount". */
- ca.opcount = opcount;
+ // Restore counts from before receiving K_EVENT. This means after
+ // typing "3", handling K_EVENT and then typing "2" we get "32", not
+ // "3 * 2".
+ if (s->oa.prev_opcount > 0 || s->oa.prev_count0 > 0) {
+ s->ca.opcount = s->oa.prev_opcount;
+ s->ca.count0 = s->oa.prev_count0;
+ s->oa.prev_opcount = 0;
+ s->oa.prev_count0 = 0;
+ }
+ s->mapped_len = typebuf_maplen();
+ State = NORMAL_BUSY;
- /*
- * If there is an operator pending, then the command we take this time
- * will terminate it. Finish_op tells us to finish the operation before
- * returning this time (unless the operation was cancelled).
- */
- c = finish_op;
- finish_op = (oap->op_type != OP_NOP);
- if (finish_op != c) {
- ui_cursor_shape(); /* may show different cursor shape */
+ // Set v:count here, when called from main() and not a stuffed command, so
+ // that v:count can be used in an expression mapping when there is no count.
+ // Do set it for redo
+ if (s->toplevel && readbuf1_empty()) {
+ set_vcount_ca(&s->ca, &s->set_prevcount);
}
+}
- /* When not finishing an operator and no register name typed, reset the
- * count. */
- if (!finish_op && !oap->regname) {
- ca.opcount = 0;
- set_prevcount = true;
+static bool normal_handle_special_visual_command(NormalState *s)
+{
+ // when 'keymodel' contains "stopsel" may stop Select/Visual mode
+ if (km_stopsel
+ && (nv_cmds[s->idx].cmd_flags & NV_STS)
+ && !(mod_mask & MOD_MASK_SHIFT)) {
+ end_visual_mode();
+ redraw_curbuf_later(INVERTED);
}
- /* Restore counts from before receiving K_CURSORHOLD. This means after
- * typing "3", handling K_CURSORHOLD and then typing "2" we get "32", not
- * "3 * 2". */
- if (oap->prev_opcount > 0 || oap->prev_count0 > 0) {
- ca.opcount = oap->prev_opcount;
- ca.count0 = oap->prev_count0;
- oap->prev_opcount = 0;
- oap->prev_count0 = 0;
+ // Keys that work different when 'keymodel' contains "startsel"
+ if (km_startsel) {
+ if (nv_cmds[s->idx].cmd_flags & NV_SS) {
+ unshift_special(&s->ca);
+ s->idx = find_command(s->ca.cmdchar);
+ if (s->idx < 0) {
+ // Just in case
+ clearopbeep(&s->oa);
+ return true;
+ }
+ } else if ((nv_cmds[s->idx].cmd_flags & NV_SSS)
+ && (mod_mask & MOD_MASK_SHIFT)) {
+ mod_mask &= ~MOD_MASK_SHIFT;
+ }
}
+ return false;
+}
- mapped_len = typebuf_maplen();
+static bool normal_need_aditional_char(NormalState *s)
+{
+ int flags = nv_cmds[s->idx].cmd_flags;
+ bool pending_op = s->oa.op_type != OP_NOP;
+ int cmdchar = s->ca.cmdchar;
+ return
+ // without NV_NCH we never need to check for an additional char
+ flags & NV_NCH && (
+ // NV_NCH_NOP is set and no operator is pending, get a second char
+ ((flags & NV_NCH_NOP) == NV_NCH_NOP && !pending_op)
+ // NV_NCH_ALW is set, always get a second char
+ || (flags & NV_NCH_ALW) == NV_NCH_ALW
+ // 'q' without a pending operator, recording or executing a register,
+ // needs to be followed by a second char, examples:
+ // - qc => record using register c
+ // - q: => open command-line window
+ || (cmdchar == 'q' && !pending_op && !Recording && !Exec_reg)
+ // 'a' or 'i' after an operator is a text object, examples:
+ // - ciw => change inside word
+ // - da( => delete parenthesis and everything inside.
+ // Also, don't do anything when these keys are received in visual mode
+ // so just get another char.
+ //
+ // TODO(tarruda): Visual state needs to be refactored into a
+ // separate state that "inherits" from normal state.
+ || ((cmdchar == 'a' || cmdchar == 'i') && (pending_op || VIsual_active)));
+}
+
+static bool normal_need_redraw_mode_message(NormalState *s)
+{
+ return (
+ (
+ // 'showmode' is set and messages can be printed
+ p_smd && msg_silent == 0
+ // must restart insert mode(ctrl+o or ctrl+l) or we just entered visual
+ // mode
+ && (restart_edit != 0 || (VIsual_active
+ && s->old_pos.lnum == curwin->w_cursor.lnum
+ && s->old_pos.col == curwin->w_cursor.col))
+ // command-line must be cleared or redrawn
+ && (clear_cmdline || redraw_cmdline)
+ // some message was printed or scrolled
+ && (msg_didout || (msg_didany && msg_scroll))
+ // it is fine to remove the current message
+ && !msg_nowait
+ // the command was the result of direct user input and not a mapping
+ && KeyTyped
+ )
+ ||
+ // must restart insert mode, not in visual mode and error message is
+ // being shown
+ (restart_edit != 0 && !VIsual_active && (msg_scroll && emsg_on_display))
+ )
+ // no register was used
+ && s->oa.regname == 0
+ && !(s->ca.retval & CA_COMMAND_BUSY)
+ && stuff_empty()
+ && typebuf_typed()
+ && emsg_silent == 0
+ && !did_wait_return
+ && s->oa.op_type == OP_NOP;
+}
+
+static void normal_redraw_mode_message(NormalState *s)
+{
+ int save_State = State;
+
+ // Draw the cursor with the right shape here
+ if (restart_edit != 0) {
+ State = INSERT;
+ }
+
+ // If need to redraw, and there is a "keep_msg", redraw before the
+ // delay
+ if (must_redraw && keep_msg != NULL && !emsg_on_display) {
+ char_u *kmsg;
+
+ kmsg = keep_msg;
+ keep_msg = NULL;
+ // showmode() will clear keep_msg, but we want to use it anyway
+ update_screen(0);
+ // now reset it, otherwise it's put in the history again
+ keep_msg = kmsg;
+ msg_attr(kmsg, keep_msg_attr);
+ xfree(kmsg);
+ }
+ setcursor();
+ ui_flush();
+ if (msg_scroll || emsg_on_display) {
+ os_delay(1000L, true); // wait at least one second
+ }
+ os_delay(3000L, false); // wait up to three seconds
+ State = save_State;
+
+ msg_scroll = false;
+ emsg_on_display = false;
+}
+
+// TODO(tarruda): Split into a "normal pending" state that can handle K_EVENT
+static void normal_get_additional_char(NormalState *s)
+{
+ int *cp;
+ bool repl = false; // get character for replace mode
+ bool lit = false; // get extra character literally
+ bool langmap_active = false; // using :lmap mappings
+ int lang; // getting a text character
+
+ ++no_mapping;
+ ++allow_keys; // no mapping for nchar, but allow key codes
+ // Don't generate a CursorHold event here, most commands can't handle
+ // it, e.g., nv_replace(), nv_csearch().
+ did_cursorhold = true;
+ if (s->ca.cmdchar == 'g') {
+ // For 'g' get the next character now, so that we can check for
+ // "gr", "g'" and "g`".
+ s->ca.nchar = plain_vgetc();
+ LANGMAP_ADJUST(s->ca.nchar, true);
+ s->need_flushbuf |= add_to_showcmd(s->ca.nchar);
+ if (s->ca.nchar == 'r' || s->ca.nchar == '\'' || s->ca.nchar == '`'
+ || s->ca.nchar == Ctrl_BSL) {
+ cp = &s->ca.extra_char; // need to get a third character
+ if (s->ca.nchar != 'r') {
+ lit = true; // get it literally
+ } else {
+ repl = true; // get it in replace mode
+ }
+ } else {
+ cp = NULL; // no third character needed
+ }
+ } else {
+ if (s->ca.cmdchar == 'r') {
+ // get it in replace mode
+ repl = true;
+ }
+ cp = &s->ca.nchar;
+ }
+ lang = (repl || (nv_cmds[s->idx].cmd_flags & NV_LANG));
- State = NORMAL_BUSY;
+ // Get a second or third character.
+ if (cp != NULL) {
+ if (repl) {
+ State = REPLACE; // pretend Replace mode
+ ui_cursor_shape(); // show different cursor shape
+ }
+ if (lang && curbuf->b_p_iminsert == B_IMODE_LMAP) {
+ // Allow mappings defined with ":lmap".
+ --no_mapping;
+ --allow_keys;
+ if (repl) {
+ State = LREPLACE;
+ } else {
+ State = LANGMAP;
+ }
+ langmap_active = true;
+ }
- /* Set v:count here, when called from main() and not a stuffed
- * command, so that v:count can be used in an expression mapping
- * when there is no count. Do set it for redo. */
- if (toplevel && readbuf1_empty())
- set_vcount_ca(&ca, &set_prevcount);
+ *cp = plain_vgetc();
- /*
- * Get the command character from the user.
- */
- input_enable_events();
- c = safe_vgetc();
- input_disable_events();
+ if (langmap_active) {
+ // Undo the decrement done above
+ ++no_mapping;
+ ++allow_keys;
+ State = NORMAL_BUSY;
+ }
+ State = NORMAL_BUSY;
+ s->need_flushbuf |= add_to_showcmd(*cp);
+
+ if (!lit) {
+ // Typing CTRL-K gets a digraph.
+ if (*cp == Ctrl_K && ((nv_cmds[s->idx].cmd_flags & NV_LANG)
+ || cp == &s->ca.extra_char)
+ && vim_strchr(p_cpo, CPO_DIGRAPH) == NULL) {
+ s->c = get_digraph(false);
+ if (s->c > 0) {
+ *cp = s->c;
+ // Guessing how to update showcmd here...
+ del_from_showcmd(3);
+ s->need_flushbuf |= add_to_showcmd(*cp);
+ }
+ }
- if (c == K_EVENT) {
- queue_process_events(loop.events);
- return;
- }
+ // adjust chars > 127, except after "tTfFr" commands
+ LANGMAP_ADJUST(*cp, !lang);
+ // adjust Hebrew mapped char
+ if (p_hkmap && lang && KeyTyped) {
+ *cp = hkmap(*cp);
+ }
+ // adjust Farsi mapped char
+ if (p_fkmap && lang && KeyTyped) {
+ *cp = fkmap(*cp);
+ }
+ }
- LANGMAP_ADJUST(c, true);
+ // When the next character is CTRL-\ a following CTRL-N means the
+ // command is aborted and we go to Normal mode.
+ if (cp == &s->ca.extra_char
+ && s->ca.nchar == Ctrl_BSL
+ && (s->ca.extra_char == Ctrl_N || s->ca.extra_char == Ctrl_G)) {
+ s->ca.cmdchar = Ctrl_BSL;
+ s->ca.nchar = s->ca.extra_char;
+ s->idx = find_command(s->ca.cmdchar);
+ } else if ((s->ca.nchar == 'n' || s->ca.nchar == 'N')
+ && s->ca.cmdchar == 'g') {
+ s->ca.oap->op_type = get_op_type(*cp, NUL);
+ } else if (*cp == Ctrl_BSL) {
+ long towait = (p_ttm >= 0 ? p_ttm : p_tm);
+
+ // There is a busy wait here when typing "f<C-\>" and then
+ // something different from CTRL-N. Can't be avoided.
+ while ((s->c = vpeekc()) <= 0 && towait > 0L) {
+ do_sleep(towait > 50L ? 50L : towait);
+ towait -= 50L;
+ }
+ if (s->c > 0) {
+ s->c = plain_vgetc();
+ if (s->c != Ctrl_N && s->c != Ctrl_G) {
+ vungetc(s->c);
+ } else {
+ s->ca.cmdchar = Ctrl_BSL;
+ s->ca.nchar = s->c;
+ s->idx = find_command(s->ca.cmdchar);
+ assert(s->idx >= 0);
+ }
+ }
+ }
- /*
- * If a mapping was started in Visual or Select mode, remember the length
- * of the mapping. This is used below to not return to Insert mode for as
- * long as the mapping is being executed.
- */
- if (restart_edit == 0)
- old_mapped_len = 0;
- else if (old_mapped_len
- || (VIsual_active && mapped_len == 0 && typebuf_maplen() > 0))
- old_mapped_len = typebuf_maplen();
+ // When getting a text character and the next character is a
+ // multi-byte character, it could be a composing character.
+ // However, don't wait for it to arrive. Also, do enable mapping,
+ // because if it's put back with vungetc() it's too late to apply
+ // mapping.
+ no_mapping--;
+ while (enc_utf8 && lang && (s->c = vpeekc()) > 0
+ && (s->c >= 0x100 || MB_BYTE2LEN(vpeekc()) > 1)) {
+ s->c = plain_vgetc();
+ if (!utf_iscomposing(s->c)) {
+ vungetc(s->c); /* it wasn't, put it back */
+ break;
+ } else if (s->ca.ncharC1 == 0) {
+ s->ca.ncharC1 = s->c;
+ } else {
+ s->ca.ncharC2 = s->c;
+ }
+ }
+ no_mapping++;
+ }
+ --no_mapping;
+ --allow_keys;
+}
- if (c == NUL)
- c = K_ZERO;
+static void normal_invert_horizontal(NormalState *s)
+{
+ switch (s->ca.cmdchar) {
+ case 'l': s->ca.cmdchar = 'h'; break;
+ case K_RIGHT: s->ca.cmdchar = K_LEFT; break;
+ case K_S_RIGHT: s->ca.cmdchar = K_S_LEFT; break;
+ case K_C_RIGHT: s->ca.cmdchar = K_C_LEFT; break;
+ case 'h': s->ca.cmdchar = 'l'; break;
+ case K_LEFT: s->ca.cmdchar = K_RIGHT; break;
+ case K_S_LEFT: s->ca.cmdchar = K_S_RIGHT; break;
+ case K_C_LEFT: s->ca.cmdchar = K_C_RIGHT; break;
+ case '>': s->ca.cmdchar = '<'; break;
+ case '<': s->ca.cmdchar = '>'; break;
+ }
+ s->idx = find_command(s->ca.cmdchar);
+}
- /*
- * In Select mode, typed text replaces the selection.
- */
- if (VIsual_active
- && VIsual_select
- && (vim_isprintc(c) || c == NL || c == CAR || c == K_KENTER)) {
- /* Fake a "c"hange command. When "restart_edit" is set (e.g., because
- * 'insertmode' is set) fake a "d"elete command, Insert mode will
- * restart automatically.
- * Insert the typed character in the typeahead buffer, so that it can
- * be mapped in Insert mode. Required for ":lmap" to work. */
- ins_char_typebuf(c);
- if (restart_edit != 0)
- c = 'd';
- else
- c = 'c';
- msg_nowait = true; /* don't delay going to insert mode */
- old_mapped_len = 0; /* do go to Insert mode */
+static bool normal_get_command_count(NormalState *s)
+{
+ if (VIsual_active && VIsual_select) {
+ return false;
}
+ // Handle a count before a command and compute ca.count0.
+ // Note that '0' is a command and not the start of a count, but it's
+ // part of a count after other digits.
+ while ((s->c >= '1' && s->c <= '9') || (s->ca.count0 != 0
+ && (s->c == K_DEL || s->c == K_KDEL || s->c == '0'))) {
+ if (s->c == K_DEL || s->c == K_KDEL) {
+ s->ca.count0 /= 10;
+ del_from_showcmd(4); // delete the digit and ~@%
+ } else {
+ s->ca.count0 = s->ca.count0 * 10 + (s->c - '0');
+ }
- need_flushbuf = add_to_showcmd(c);
+ if (s->ca.count0 < 0) {
+ // got too large!
+ s->ca.count0 = 999999999L;
+ }
-getcount:
- if (!(VIsual_active && VIsual_select)) {
- /*
- * Handle a count before a command and compute ca.count0.
- * Note that '0' is a command and not the start of a count, but it's
- * part of a count after other digits.
- */
- while ( (c >= '1' && c <= '9')
- || (ca.count0 != 0 &&
- (c == K_DEL || c == K_KDEL || c == '0'))) {
- if (c == K_DEL || c == K_KDEL) {
- ca.count0 /= 10;
- del_from_showcmd(4); /* delete the digit and ~@% */
- } else
- ca.count0 = ca.count0 * 10 + (c - '0');
- if (ca.count0 < 0) /* got too large! */
- ca.count0 = 999999999L;
- /* Set v:count here, when called from main() and not a stuffed
- * command, so that v:count can be used in an expression mapping
- * right after the count. Do set it for redo. */
- if (toplevel && readbuf1_empty())
- set_vcount_ca(&ca, &set_prevcount);
- if (ctrl_w) {
- ++no_mapping;
- ++allow_keys; /* no mapping for nchar, but keys */
- }
- ++no_zero_mapping; /* don't map zero here */
- c = plain_vgetc();
- LANGMAP_ADJUST(c, true);
- --no_zero_mapping;
- if (ctrl_w) {
- --no_mapping;
- --allow_keys;
- }
- need_flushbuf |= add_to_showcmd(c);
+ // Set v:count here, when called from main() and not a stuffed
+ // command, so that v:count can be used in an expression mapping
+ // right after the count. Do set it for redo.
+ if (s->toplevel && readbuf1_empty()) {
+ set_vcount_ca(&s->ca, &s->set_prevcount);
}
- /*
- * If we got CTRL-W there may be a/another count
- */
- if (c == Ctrl_W && !ctrl_w && oap->op_type == OP_NOP) {
- ctrl_w = true;
- ca.opcount = ca.count0; /* remember first count */
- ca.count0 = 0;
+ if (s->ctrl_w) {
++no_mapping;
- ++allow_keys; /* no mapping for nchar, but keys */
- c = plain_vgetc(); /* get next character */
- LANGMAP_ADJUST(c, true);
+ ++allow_keys; // no mapping for nchar, but keys
+ }
+
+ ++no_zero_mapping; // don't map zero here
+ s->c = plain_vgetc();
+ LANGMAP_ADJUST(s->c, true);
+ --no_zero_mapping;
+ if (s->ctrl_w) {
--no_mapping;
--allow_keys;
- need_flushbuf |= add_to_showcmd(c);
- goto getcount; /* jump back */
}
+ s->need_flushbuf |= add_to_showcmd(s->c);
}
- if (c == K_CURSORHOLD) {
- /* Save the count values so that ca.opcount and ca.count0 are exactly
- * the same when coming back here after handling K_CURSORHOLD. */
- oap->prev_opcount = ca.opcount;
- oap->prev_count0 = ca.count0;
- } else if (ca.opcount != 0) {
- /*
- * If we're in the middle of an operator (including after entering a
- * yank buffer with '"') AND we had a count before the operator, then
- * that count overrides the current value of ca.count0.
- * What this means effectively, is that commands like "3dw" get turned
- * into "d3w" which makes things fall into place pretty neatly.
- * If you give a count before AND after the operator, they are
- * multiplied.
- */
- if (ca.count0)
- ca.count0 *= ca.opcount;
- else
- ca.count0 = ca.opcount;
+ // If we got CTRL-W there may be a/another count
+ if (s->c == Ctrl_W && !s->ctrl_w && s->oa.op_type == OP_NOP) {
+ s->ctrl_w = true;
+ s->ca.opcount = s->ca.count0; // remember first count
+ s->ca.count0 = 0;
+ ++no_mapping;
+ ++allow_keys; // no mapping for nchar, but keys
+ s->c = plain_vgetc(); // get next character
+ LANGMAP_ADJUST(s->c, true);
+ --no_mapping;
+ --allow_keys;
+ s->need_flushbuf |= add_to_showcmd(s->c);
+ return true;
}
- /*
- * Always remember the count. It will be set to zero (on the next call,
- * above) when there is no pending operator.
- * When called from main(), save the count for use by the "count" built-in
- * variable.
- */
- ca.opcount = ca.count0;
- ca.count1 = (ca.count0 == 0 ? 1 : ca.count0);
-
- /*
- * Only set v:count when called from main() and not a stuffed command.
- * Do set it for redo.
- */
- if (toplevel && readbuf1_empty())
- set_vcount(ca.count0, ca.count1, set_prevcount);
+ return false;
+}
- /*
- * Find the command character in the table of commands.
- * For CTRL-W we already got nchar when looking for a count.
- */
- if (ctrl_w) {
- ca.nchar = c;
- ca.cmdchar = Ctrl_W;
- } else
- ca.cmdchar = c;
- idx = find_command(ca.cmdchar);
- if (idx < 0) {
- /* Not a known command: beep. */
- clearopbeep(oap);
+static void normal_finish_command(NormalState *s)
+{
+ if (s->command_finished) {
goto normal_end;
}
- if (text_locked() && (nv_cmds[idx].cmd_flags & NV_NCW)) {
- // This command is not allowed while editing a cmdline: beep.
- clearopbeep(oap);
- text_locked_msg();
- goto normal_end;
+ // If we didn't start or finish an operator, reset oap->regname, unless we
+ // need it later.
+ if (!finish_op
+ && !s->oa.op_type
+ && (s->idx < 0 || !(nv_cmds[s->idx].cmd_flags & NV_KEEPREG))) {
+ clearop(&s->oa);
+ set_reg_var(get_default_register_name());
}
- if ((nv_cmds[idx].cmd_flags & NV_NCW) && curbuf_locked())
- goto normal_end;
- /*
- * In Visual/Select mode, a few keys are handled in a special way.
- */
- if (VIsual_active) {
- /* when 'keymodel' contains "stopsel" may stop Select/Visual mode */
- if (km_stopsel
- && (nv_cmds[idx].cmd_flags & NV_STS)
- && !(mod_mask & MOD_MASK_SHIFT)) {
- end_visual_mode();
- redraw_curbuf_later(INVERTED);
- }
+ // Get the length of mapped chars again after typing a count, second
+ // character or "z333<cr>".
+ if (s->old_mapped_len > 0) {
+ s->old_mapped_len = typebuf_maplen();
+ }
- /* Keys that work different when 'keymodel' contains "startsel" */
- if (km_startsel) {
- if (nv_cmds[idx].cmd_flags & NV_SS) {
- unshift_special(&ca);
- idx = find_command(ca.cmdchar);
- if (idx < 0) {
- /* Just in case */
- clearopbeep(oap);
- goto normal_end;
- }
- } else if ((nv_cmds[idx].cmd_flags & NV_SSS)
- && (mod_mask & MOD_MASK_SHIFT)) {
- mod_mask &= ~MOD_MASK_SHIFT;
- }
+ // If an operation is pending, handle it...
+ do_pending_operator(&s->ca, s->old_col, false);
+
+ // Wait for a moment when a message is displayed that will be overwritten
+ // by the mode message.
+ // In Visual mode and with "^O" in Insert mode, a short message will be
+ // overwritten by the mode message. Wait a bit, until a key is hit.
+ // In Visual mode, it's more important to keep the Visual area updated
+ // than keeping a message (e.g. from a /pat search).
+ // Only do this if the command was typed, not from a mapping.
+ // Don't wait when emsg_silent is non-zero.
+ // Also wait a bit after an error message, e.g. for "^O:".
+ // Don't redraw the screen, it would remove the message.
+ if (normal_need_redraw_mode_message(s)) {
+ normal_redraw_mode_message(s);
+ }
+
+ // Finish up after executing a Normal mode command.
+normal_end:
+
+ msg_nowait = false;
+
+ // Reset finish_op, in case it was set
+ s->c = finish_op;
+ finish_op = false;
+ // Redraw the cursor with another shape, if we were in Operator-pending
+ // mode or did a replace command.
+ if (s->c || s->ca.cmdchar == 'r') {
+ ui_cursor_shape(); // may show different cursor shape
+ }
+
+ if (s->oa.op_type == OP_NOP && s->oa.regname == 0
+ && s->ca.cmdchar != K_EVENT) {
+ clear_showcmd();
+ }
+
+ checkpcmark(); // check if we moved since setting pcmark
+ xfree(s->ca.searchbuf);
+
+ if (has_mbyte) {
+ mb_adjust_cursor();
+ }
+
+ if (curwin->w_p_scb && s->toplevel) {
+ validate_cursor(); // may need to update w_leftcol
+ do_check_scrollbind(true);
+ }
+
+ if (curwin->w_p_crb && s->toplevel) {
+ validate_cursor(); // may need to update w_leftcol
+ do_check_cursorbind();
+ }
+
+ // May restart edit(), if we got here with CTRL-O in Insert mode (but not
+ // if still inside a mapping that started in Visual mode).
+ // May switch from Visual to Select mode after CTRL-O command.
+ if (s->oa.op_type == OP_NOP
+ && ((restart_edit != 0 && !VIsual_active && s->old_mapped_len == 0)
+ || restart_VIsual_select == 1)
+ && !(s->ca.retval & CA_COMMAND_BUSY)
+ && stuff_empty()
+ && s->oa.regname == 0) {
+ if (restart_VIsual_select == 1) {
+ VIsual_select = true;
+ showmode();
+ restart_VIsual_select = 0;
+ }
+ if (restart_edit != 0 && !VIsual_active && s->old_mapped_len == 0) {
+ (void)edit(restart_edit, false, 1L);
}
}
- if (curwin->w_p_rl && KeyTyped && !KeyStuffed
- && (nv_cmds[idx].cmd_flags & NV_RL)) {
- /* Invert horizontal movements and operations. Only when typed by the
- * user directly, not when the result of a mapping or "x" translated
- * to "dl". */
- switch (ca.cmdchar) {
- case 'l': ca.cmdchar = 'h'; break;
- case K_RIGHT: ca.cmdchar = K_LEFT; break;
- case K_S_RIGHT: ca.cmdchar = K_S_LEFT; break;
- case K_C_RIGHT: ca.cmdchar = K_C_LEFT; break;
- case 'h': ca.cmdchar = 'l'; break;
- case K_LEFT: ca.cmdchar = K_RIGHT; break;
- case K_S_LEFT: ca.cmdchar = K_S_RIGHT; break;
- case K_C_LEFT: ca.cmdchar = K_C_RIGHT; break;
- case '>': ca.cmdchar = '<'; break;
- case '<': ca.cmdchar = '>'; break;
- }
- idx = find_command(ca.cmdchar);
+ if (restart_VIsual_select == 2) {
+ restart_VIsual_select = 1;
}
- /*
- * Get an additional character if we need one.
- */
- if ((nv_cmds[idx].cmd_flags & NV_NCH)
- && (((nv_cmds[idx].cmd_flags & NV_NCH_NOP) == NV_NCH_NOP
- && oap->op_type == OP_NOP)
- || (nv_cmds[idx].cmd_flags & NV_NCH_ALW) == NV_NCH_ALW
- || (ca.cmdchar == 'q'
- && oap->op_type == OP_NOP
- && !Recording
- && !Exec_reg)
- || ((ca.cmdchar == 'a' || ca.cmdchar == 'i')
- && (oap->op_type != OP_NOP
- || VIsual_active
- )))) {
- int *cp;
- bool repl = false; /* get character for replace mode */
- bool lit = false; /* get extra character literally */
- bool langmap_active = false; /* using :lmap mappings */
- int lang; /* getting a text character */
+ // Save count before an operator for next time
+ opcount = s->ca.opcount;
+}
- ++no_mapping;
- ++allow_keys; /* no mapping for nchar, but allow key codes */
- /* Don't generate a CursorHold event here, most commands can't handle
- * it, e.g., nv_replace(), nv_csearch(). */
- did_cursorhold = true;
- if (ca.cmdchar == 'g') {
- /*
- * For 'g' get the next character now, so that we can check for
- * "gr", "g'" and "g`".
- */
- ca.nchar = plain_vgetc();
- LANGMAP_ADJUST(ca.nchar, true);
- need_flushbuf |= add_to_showcmd(ca.nchar);
- if (ca.nchar == 'r' || ca.nchar == '\'' || ca.nchar == '`'
- || ca.nchar == Ctrl_BSL) {
- cp = &ca.extra_char; /* need to get a third character */
- if (ca.nchar != 'r')
- lit = true; /* get it literally */
- else
- repl = true; /* get it in replace mode */
- } else
- cp = NULL; /* no third character needed */
+static int normal_execute(VimState *state, int key)
+{
+ NormalState *s = (NormalState *)state;
+ s->command_finished = false;
+ s->ctrl_w = false; /* got CTRL-W command */
+ s->old_col = curwin->w_curswant;
+ s->c = key;
+
+ LANGMAP_ADJUST(s->c, true);
+
+ // If a mapping was started in Visual or Select mode, remember the length
+ // of the mapping. This is used below to not return to Insert mode for as
+ // long as the mapping is being executed.
+ if (restart_edit == 0) {
+ s->old_mapped_len = 0;
+ } else if (s->old_mapped_len || (VIsual_active && s->mapped_len == 0
+ && typebuf_maplen() > 0)) {
+ s->old_mapped_len = typebuf_maplen();
+ }
+
+ if (s->c == NUL) {
+ s->c = K_ZERO;
+ }
+
+ // In Select mode, typed text replaces the selection.
+ if (VIsual_active && VIsual_select && (vim_isprintc(s->c)
+ || s->c == NL || s->c == CAR || s->c == K_KENTER)) {
+ // Fake a "c"hange command. When "restart_edit" is set (e.g., because
+ // 'insertmode' is set) fake a "d"elete command, Insert mode will
+ // restart automatically.
+ // Insert the typed character in the typeahead buffer, so that it can
+ // be mapped in Insert mode. Required for ":lmap" to work.
+ ins_char_typebuf(s->c);
+ if (restart_edit != 0) {
+ s->c = 'd';
} else {
- if (ca.cmdchar == 'r') /* get it in replace mode */
- repl = true;
- cp = &ca.nchar;
+ s->c = 'c';
}
- lang = (repl || (nv_cmds[idx].cmd_flags & NV_LANG));
+ msg_nowait = true; // don't delay going to insert mode
+ s->old_mapped_len = 0; // do go to Insert mode
+ }
+
+ s->need_flushbuf = add_to_showcmd(s->c);
+
+ while (normal_get_command_count(s)) continue;
+
+ if (s->c == K_EVENT) {
+ // Save the count values so that ca.opcount and ca.count0 are exactly
+ // the same when coming back here after handling K_EVENT.
+ s->oa.prev_opcount = s->ca.opcount;
+ s->oa.prev_count0 = s->ca.count0;
+ } else if (s->ca.opcount != 0) {
+ // If we're in the middle of an operator (including after entering a
+ // yank buffer with '"') AND we had a count before the operator, then
+ // that count overrides the current value of ca.count0.
+ // What this means effectively, is that commands like "3dw" get turned
+ // into "d3w" which makes things fall into place pretty neatly.
+ // If you give a count before AND after the operator, they are
+ // multiplied.
+ if (s->ca.count0) {
+ s->ca.count0 *= s->ca.opcount;
+ } else {
+ s->ca.count0 = s->ca.opcount;
+ }
+ }
- /*
- * Get a second or third character.
- */
- if (cp != NULL) {
- if (repl) {
- State = REPLACE; /* pretend Replace mode */
- ui_cursor_shape(); /* show different cursor shape */
- }
- if (lang && curbuf->b_p_iminsert == B_IMODE_LMAP) {
- /* Allow mappings defined with ":lmap". */
- --no_mapping;
- --allow_keys;
- if (repl)
- State = LREPLACE;
- else
- State = LANGMAP;
- langmap_active = true;
- }
+ // Always remember the count. It will be set to zero (on the next call,
+ // above) when there is no pending operator.
+ // When called from main(), save the count for use by the "count" built-in
+ // variable.
+ s->ca.opcount = s->ca.count0;
+ s->ca.count1 = (s->ca.count0 == 0 ? 1 : s->ca.count0);
- *cp = plain_vgetc();
+ // Only set v:count when called from main() and not a stuffed command.
+ // Do set it for redo.
+ if (s->toplevel && readbuf1_empty()) {
+ set_vcount(s->ca.count0, s->ca.count1, s->set_prevcount);
+ }
- if (langmap_active) {
- /* Undo the decrement done above */
- ++no_mapping;
- ++allow_keys;
- State = NORMAL_BUSY;
- }
- State = NORMAL_BUSY;
- need_flushbuf |= add_to_showcmd(*cp);
-
- if (!lit) {
- /* Typing CTRL-K gets a digraph. */
- if (*cp == Ctrl_K
- && ((nv_cmds[idx].cmd_flags & NV_LANG)
- || cp == &ca.extra_char)
- && vim_strchr(p_cpo, CPO_DIGRAPH) == NULL) {
- c = get_digraph(false);
- if (c > 0) {
- *cp = c;
- /* Guessing how to update showcmd here... */
- del_from_showcmd(3);
- need_flushbuf |= add_to_showcmd(*cp);
- }
- }
+ // Find the command character in the table of commands.
+ // For CTRL-W we already got nchar when looking for a count.
+ if (s->ctrl_w) {
+ s->ca.nchar = s->c;
+ s->ca.cmdchar = Ctrl_W;
+ } else {
+ s->ca.cmdchar = s->c;
+ }
- /* adjust chars > 127, except after "tTfFr" commands */
- LANGMAP_ADJUST(*cp, !lang);
- /* adjust Hebrew mapped char */
- if (p_hkmap && lang && KeyTyped)
- *cp = hkmap(*cp);
- /* adjust Farsi mapped char */
- if (p_fkmap && lang && KeyTyped)
- *cp = fkmap(*cp);
- }
+ s->idx = find_command(s->ca.cmdchar);
- /*
- * When the next character is CTRL-\ a following CTRL-N means the
- * command is aborted and we go to Normal mode.
- */
- if (cp == &ca.extra_char
- && ca.nchar == Ctrl_BSL
- && (ca.extra_char == Ctrl_N || ca.extra_char == Ctrl_G)) {
- ca.cmdchar = Ctrl_BSL;
- ca.nchar = ca.extra_char;
- idx = find_command(ca.cmdchar);
- } else if ((ca.nchar == 'n' || ca.nchar == 'N') && ca.cmdchar == 'g')
- ca.oap->op_type = get_op_type(*cp, NUL);
- else if (*cp == Ctrl_BSL) {
- long towait = (p_ttm >= 0 ? p_ttm : p_tm);
-
- /* There is a busy wait here when typing "f<C-\>" and then
- * something different from CTRL-N. Can't be avoided. */
- while ((c = vpeekc()) <= 0 && towait > 0L) {
- do_sleep(towait > 50L ? 50L : towait);
- towait -= 50L;
- }
- if (c > 0) {
- c = plain_vgetc();
- if (c != Ctrl_N && c != Ctrl_G)
- vungetc(c);
- else {
- ca.cmdchar = Ctrl_BSL;
- ca.nchar = c;
- idx = find_command(ca.cmdchar);
- assert(idx >= 0);
- }
- }
- }
+ if (s->idx < 0) {
+ // Not a known command: beep.
+ clearopbeep(&s->oa);
+ s->command_finished = true;
+ goto finish;
+ }
- /* When getting a text character and the next character is a
- * multi-byte character, it could be a composing character.
- * However, don't wait for it to arrive. Also, do enable mapping,
- * because if it's put back with vungetc() it's too late to apply
- * mapping. */
- no_mapping--;
- while (enc_utf8 && lang && (c = vpeekc()) > 0
- && (c >= 0x100 || MB_BYTE2LEN(vpeekc()) > 1)) {
- c = plain_vgetc();
- if (!utf_iscomposing(c)) {
- vungetc(c); /* it wasn't, put it back */
- break;
- } else if (ca.ncharC1 == 0)
- ca.ncharC1 = c;
- else
- ca.ncharC2 = c;
- }
- no_mapping++;
- }
- --no_mapping;
- --allow_keys;
+ if (text_locked() && (nv_cmds[s->idx].cmd_flags & NV_NCW)) {
+ // This command is not allowed while editing a cmdline: beep.
+ clearopbeep(&s->oa);
+ text_locked_msg();
+ s->command_finished = true;
+ goto finish;
}
- /*
- * Flush the showcmd characters onto the screen so we can see them while
- * the command is being executed. Only do this when the shown command was
- * actually displayed, otherwise this will slow down a lot when executing
- * mappings.
- */
- if (need_flushbuf)
+ if ((nv_cmds[s->idx].cmd_flags & NV_NCW) && curbuf_locked()) {
+ s->command_finished = true;
+ goto finish;
+ }
+
+ // In Visual/Select mode, a few keys are handled in a special way.
+ if (VIsual_active && normal_handle_special_visual_command(s)) {
+ s->command_finished = true;
+ goto finish;
+ }
+
+ if (curwin->w_p_rl && KeyTyped && !KeyStuffed
+ && (nv_cmds[s->idx].cmd_flags & NV_RL)) {
+ // Invert horizontal movements and operations. Only when typed by the
+ // user directly, not when the result of a mapping or "x" translated
+ // to "dl".
+ normal_invert_horizontal(s);
+ }
+
+ // Get an additional character if we need one.
+ if (normal_need_aditional_char(s)) {
+ normal_get_additional_char(s);
+ }
+
+ // Flush the showcmd characters onto the screen so we can see them while
+ // the command is being executed. Only do this when the shown command was
+ // actually displayed, otherwise this will slow down a lot when executing
+ // mappings.
+ if (s->need_flushbuf) {
ui_flush();
- if (ca.cmdchar != K_IGNORE)
+ }
+ if (s->ca.cmdchar != K_IGNORE && s->ca.cmdchar != K_EVENT) {
did_cursorhold = false;
+ }
State = NORMAL;
- if (ca.nchar == ESC) {
- clearop(oap);
- if (restart_edit == 0 && goto_im())
+ if (s->ca.nchar == ESC) {
+ clearop(&s->oa);
+ if (restart_edit == 0 && goto_im()) {
restart_edit = 'a';
- goto normal_end;
+ }
+ s->command_finished = true;
+ goto finish;
}
- if (ca.cmdchar != K_IGNORE) {
- msg_didout = false; /* don't scroll screen up for normal command */
+ if (s->ca.cmdchar != K_IGNORE) {
+ msg_didout = false; // don't scroll screen up for normal command
msg_col = 0;
}
- old_pos = curwin->w_cursor; /* remember where cursor was */
+ s->old_pos = curwin->w_cursor; // remember where cursor was
- /* When 'keymodel' contains "startsel" some keys start Select/Visual
- * mode. */
+ // When 'keymodel' contains "startsel" some keys start Select/Visual
+ // mode.
if (!VIsual_active && km_startsel) {
- if (nv_cmds[idx].cmd_flags & NV_SS) {
+ if (nv_cmds[s->idx].cmd_flags & NV_SS) {
start_selection();
- unshift_special(&ca);
- idx = find_command(ca.cmdchar);
- } else if ((nv_cmds[idx].cmd_flags & NV_SSS)
+ unshift_special(&s->ca);
+ s->idx = find_command(s->ca.cmdchar);
+ } else if ((nv_cmds[s->idx].cmd_flags & NV_SSS)
&& (mod_mask & MOD_MASK_SHIFT)) {
start_selection();
mod_mask &= ~MOD_MASK_SHIFT;
}
}
- /*
- * Execute the command!
- * Call the command function found in the commands table.
- */
- ca.arg = nv_cmds[idx].cmd_arg;
- (nv_cmds[idx].cmd_func)(&ca);
+ // Execute the command!
+ // Call the command function found in the commands table.
+ s->ca.arg = nv_cmds[s->idx].cmd_arg;
+ (nv_cmds[s->idx].cmd_func)(&s->ca);
- /*
- * If we didn't start or finish an operator, reset oap->regname, unless we
- * need it later.
- */
- if (!finish_op
- && !oap->op_type
- && (idx < 0 || !(nv_cmds[idx].cmd_flags & NV_KEEPREG))) {
- clearop(oap);
- set_reg_var(get_default_register_name());
+finish:
+ normal_finish_command(s);
+ return 1;
+}
+
+static void normal_check_stuff_buffer(NormalState *s)
+{
+ if (stuff_empty()) {
+ did_check_timestamps = false;
+
+ if (need_check_timestamps) {
+ check_timestamps(false);
+ }
+
+ if (need_wait_return) {
+ // if wait_return still needed call it now
+ wait_return(false);
+ }
+
+ if (need_start_insertmode && goto_im() && !VIsual_active) {
+ need_start_insertmode = false;
+ stuffReadbuff((uint8_t *)"i"); // start insert mode next
+ // skip the fileinfo message now, because it would be shown
+ // after insert mode finishes!
+ need_fileinfo = false;
+ }
}
+}
- /* Get the length of mapped chars again after typing a count, second
- * character or "z333<cr>". */
- if (old_mapped_len > 0)
- old_mapped_len = typebuf_maplen();
+static void normal_check_interrupt(NormalState *s)
+{
+ // Reset "got_int" now that we got back to the main loop. Except when
+ // inside a ":g/pat/cmd" command, then the "got_int" needs to abort
+ // the ":g" command.
+ // For ":g/pat/vi" we reset "got_int" when used once. When used
+ // a second time we go back to Ex mode and abort the ":g" command.
+ if (got_int) {
+ if (s->noexmode && global_busy && !exmode_active
+ && s->previous_got_int) {
+ // Typed two CTRL-C in a row: go back to ex mode as if "Q" was
+ // used and keep "got_int" set, so that it aborts ":g".
+ exmode_active = EXMODE_NORMAL;
+ State = NORMAL;
+ } else if (!global_busy || !exmode_active) {
+ if (!quit_more) {
+ // flush all buffers
+ (void)vgetc();
+ }
+ got_int = false;
+ }
+ s->previous_got_int = true;
+ } else {
+ s->previous_got_int = false;
+ }
+}
- /*
- * If an operation is pending, handle it...
- */
- do_pending_operator(&ca, old_col, false);
+static void normal_check_cursor_moved(NormalState *s)
+{
+ // Trigger CursorMoved if the cursor moved.
+ if (!finish_op && (has_cursormoved() || curwin->w_p_cole > 0)
+ && !equalpos(last_cursormoved, curwin->w_cursor)) {
+ if (has_cursormoved()) {
+ apply_autocmds(EVENT_CURSORMOVED, NULL, NULL, false, curbuf);
+ }
- /*
- * Wait for a moment when a message is displayed that will be overwritten
- * by the mode message.
- * In Visual mode and with "^O" in Insert mode, a short message will be
- * overwritten by the mode message. Wait a bit, until a key is hit.
- * In Visual mode, it's more important to keep the Visual area updated
- * than keeping a message (e.g. from a /pat search).
- * Only do this if the command was typed, not from a mapping.
- * Don't wait when emsg_silent is non-zero.
- * Also wait a bit after an error message, e.g. for "^O:".
- * Don't redraw the screen, it would remove the message.
- */
- if ( ((p_smd
- && msg_silent == 0
- && (restart_edit != 0
- || (VIsual_active
- && old_pos.lnum == curwin->w_cursor.lnum
- && old_pos.col == curwin->w_cursor.col)
- )
- && (clear_cmdline
- || redraw_cmdline)
- && (msg_didout || (msg_didany && msg_scroll))
- && !msg_nowait
- && KeyTyped)
- || (restart_edit != 0
- && !VIsual_active
- && (msg_scroll
- || emsg_on_display)))
- && oap->regname == 0
- && !(ca.retval & CA_COMMAND_BUSY)
- && stuff_empty()
- && typebuf_typed()
- && emsg_silent == 0
- && !did_wait_return
- && oap->op_type == OP_NOP) {
- int save_State = State;
+ if (curwin->w_p_cole > 0) {
+ s->conceal_old_cursor_line = last_cursormoved.lnum;
+ s->conceal_new_cursor_line = curwin->w_cursor.lnum;
+ s->conceal_update_lines = true;
+ }
- /* Draw the cursor with the right shape here */
- if (restart_edit != 0)
- State = INSERT;
+ last_cursormoved = curwin->w_cursor;
+ }
+}
+
+static void normal_check_text_changed(NormalState *s)
+{
+ // Trigger TextChanged if b_changedtick differs.
+ if (!finish_op && has_textchanged()
+ && last_changedtick != curbuf->b_changedtick) {
+ if (last_changedtick_buf == curbuf) {
+ apply_autocmds(EVENT_TEXTCHANGED, NULL, NULL, false, curbuf);
+ }
- /* If need to redraw, and there is a "keep_msg", redraw before the
- * delay */
- if (must_redraw && keep_msg != NULL && !emsg_on_display) {
- char_u *kmsg;
+ last_changedtick_buf = curbuf;
+ last_changedtick = curbuf->b_changedtick;
+ }
+}
- kmsg = keep_msg;
- keep_msg = NULL;
- /* showmode() will clear keep_msg, but we want to use it anyway */
- update_screen(0);
- /* now reset it, otherwise it's put in the history again */
- keep_msg = kmsg;
- msg_attr(kmsg, keep_msg_attr);
- xfree(kmsg);
+static void normal_check_folds(NormalState *s)
+{
+ // Include a closed fold completely in the Visual area.
+ foldAdjustVisual();
+
+ // When 'foldclose' is set, apply 'foldlevel' to folds that don't
+ // contain the cursor.
+ // When 'foldopen' is "all", open the fold(s) under the cursor.
+ // This may mark the window for redrawing.
+ if (hasAnyFolding(curwin) && !char_avail()) {
+ foldCheckClose();
+
+ if (fdo_flags & FDO_ALL) {
+ foldOpenCursor();
}
- setcursor();
- ui_flush();
- if (msg_scroll || emsg_on_display)
- os_delay(1000L, true); /* wait at least one second */
- os_delay(3000L, false); /* wait up to three seconds */
- State = save_State;
+ }
+}
- msg_scroll = false;
- emsg_on_display = false;
+static void normal_redraw(NormalState *s)
+{
+ // Before redrawing, make sure w_topline is correct, and w_leftcol
+ // if lines don't wrap, and w_skipcol if lines wrap.
+ update_topline();
+ validate_cursor();
+
+ if (VIsual_active) {
+ update_curbuf(INVERTED); // update inverted part
+ } else if (must_redraw) {
+ update_screen(0);
+ } else if (redraw_cmdline || clear_cmdline) {
+ showmode();
}
- /*
- * Finish up after executing a Normal mode command.
- */
-normal_end:
+ redraw_statuslines();
- msg_nowait = false;
+ if (need_maketitle) {
+ maketitle();
+ }
- /* Reset finish_op, in case it was set */
- c = finish_op;
- finish_op = false;
- /* Redraw the cursor with another shape, if we were in Operator-pending
- * mode or did a replace command. */
- if (c || ca.cmdchar == 'r') {
- ui_cursor_shape(); /* may show different cursor shape */
+ // display message after redraw
+ if (keep_msg != NULL) {
+ // msg_attr_keep() will set keep_msg to NULL, must free the string here.
+ // Don't reset keep_msg, msg_attr_keep() uses it to check for duplicates.
+ char *p = (char *)keep_msg;
+ msg_attr((uint8_t *)p, keep_msg_attr);
+ xfree(p);
}
- if (oap->op_type == OP_NOP && oap->regname == 0
- && ca.cmdchar != K_CURSORHOLD
- )
- clear_showcmd();
+ if (need_fileinfo) { // show file info after redraw
+ fileinfo(false, true, false);
+ need_fileinfo = false;
+ }
- checkpcmark(); /* check if we moved since setting pcmark */
- xfree(ca.searchbuf);
+ emsg_on_display = false; // can delete error message now
+ did_emsg = false;
+ msg_didany = false; // reset lines_left in msg_start()
+ may_clear_sb_text(); // clear scroll-back text on next msg
+ showruler(false);
- if (has_mbyte)
- mb_adjust_cursor();
+ if (s->conceal_update_lines
+ && (s->conceal_old_cursor_line !=
+ s->conceal_new_cursor_line
+ || conceal_cursor_line(curwin)
+ || need_cursor_line_redraw)) {
+ if (s->conceal_old_cursor_line !=
+ s->conceal_new_cursor_line
+ && s->conceal_old_cursor_line <=
+ curbuf->b_ml.ml_line_count) {
+ update_single_line(curwin, s->conceal_old_cursor_line);
+ }
- if (curwin->w_p_scb && toplevel) {
- validate_cursor(); /* may need to update w_leftcol */
- do_check_scrollbind(true);
+ update_single_line(curwin, s->conceal_new_cursor_line);
+ curwin->w_valid &= ~VALID_CROW;
}
- if (curwin->w_p_crb && toplevel) {
- validate_cursor(); /* may need to update w_leftcol */
- do_check_cursorbind();
+ setcursor();
+}
+
+// Function executed before each iteration of normal mode.
+// Return:
+// 1 if the iteration should continue normally
+// -1 if the iteration should be skipped
+// 0 if the main loop must exit
+static int normal_check(VimState *state)
+{
+ NormalState *s = (NormalState *)state;
+ normal_check_stuff_buffer(s);
+ normal_check_interrupt(s);
+
+ if (!exmode_active) {
+ msg_scroll = false;
}
+ quit_more = false;
+
+ // If skip redraw is set (for ":" in wait_return()), don't redraw now.
+ // If there is nothing in the stuff_buffer or do_redraw is TRUE,
+ // update cursor and redraw.
+ if (skip_redraw || exmode_active) {
+ skip_redraw = false;
+ } else if (do_redraw || stuff_empty()) {
+ normal_check_cursor_moved(s);
+ normal_check_text_changed(s);
+
+ // Scroll-binding for diff mode may have been postponed until
+ // here. Avoids doing it for every change.
+ if (diff_need_scrollbind) {
+ check_scrollbind((linenr_T)0, 0L);
+ diff_need_scrollbind = false;
+ }
- /*
- * May restart edit(), if we got here with CTRL-O in Insert mode (but not
- * if still inside a mapping that started in Visual mode).
- * May switch from Visual to Select mode after CTRL-O command.
- */
- if ( oap->op_type == OP_NOP
- && ((restart_edit != 0 && !VIsual_active && old_mapped_len == 0)
- || restart_VIsual_select == 1)
- && !(ca.retval & CA_COMMAND_BUSY)
- && stuff_empty()
- && oap->regname == 0) {
- if (restart_VIsual_select == 1) {
- VIsual_select = true;
- showmode();
- restart_VIsual_select = 0;
+ normal_check_folds(s);
+ normal_redraw(s);
+ do_redraw = false;
+
+ // Now that we have drawn the first screen all the startup stuff
+ // has been done, close any file for startup messages.
+ if (time_fd != NULL) {
+ TIME_MSG("first screen update");
+ TIME_MSG("--- NVIM STARTED ---");
+ fclose(time_fd);
+ time_fd = NULL;
}
- if (restart_edit != 0
- && !VIsual_active && old_mapped_len == 0
- )
- (void)edit(restart_edit, false, 1L);
}
- if (restart_VIsual_select == 2)
- restart_VIsual_select = 1;
+ // May perform garbage collection when waiting for a character, but
+ // only at the very toplevel. Otherwise we may be using a List or
+ // Dict internally somewhere.
+ // "may_garbage_collect" is reset in vgetc() which is invoked through
+ // do_exmode() and normal_cmd().
+ may_garbage_collect = s->toplevel;
+
+ // Update w_curswant if w_set_curswant has been set.
+ // Postponed until here to avoid computing w_virtcol too often.
+ update_curswant();
- /* Save count before an operator for next time. */
- opcount = ca.opcount;
+ if (exmode_active) {
+ if (s->noexmode) {
+ return 0;
+ }
+ do_exmode(exmode_active == EXMODE_VIM);
+ return -1;
+ }
+
+ if (s->cmdwin && cmdwin_result != 0) {
+ // command-line window and cmdwin_result is set
+ return 0;
+ }
+
+ normal_prepare(s);
+ return 1;
}
/*
@@ -2937,7 +3260,7 @@ bool add_to_showcmd(int c)
K_RIGHTMOUSE, K_RIGHTDRAG, K_RIGHTRELEASE,
K_MOUSEDOWN, K_MOUSEUP, K_MOUSELEFT, K_MOUSERIGHT,
K_X1MOUSE, K_X1DRAG, K_X1RELEASE, K_X2MOUSE, K_X2DRAG, K_X2RELEASE,
- K_CURSORHOLD,
+ K_EVENT,
0
};
@@ -3924,8 +4247,13 @@ dozet:
break;
/* "zm": fold more */
- case 'm': if (curwin->w_p_fdl > 0)
- --curwin->w_p_fdl;
+ case 'm':
+ if (curwin->w_p_fdl > 0) {
+ curwin->w_p_fdl -= cap->count1;
+ if (curwin->w_p_fdl < 0) {
+ curwin->w_p_fdl = 0;
+ }
+ }
old_fdl = -1; /* force an update */
curwin->w_p_fen = true;
break;
@@ -3937,7 +4265,14 @@ dozet:
break;
/* "zr": reduce folding */
- case 'r': ++curwin->w_p_fdl;
+ case 'r':
+ curwin->w_p_fdl += cap->count1;
+ {
+ int d = getDeepestNesting();
+ if (curwin->w_p_fdl >= d) {
+ curwin->w_p_fdl = d;
+ }
+ }
break;
/* "zR": open all folds */
@@ -7131,6 +7466,13 @@ static void nv_object(cmdarg_T *cap)
flag = current_block(cap->oap, cap->count1, include, '<', '>');
break;
case 't': /* "at" = a tag block (xml and html) */
+ // Do not adjust oap->end in do_pending_operator()
+ // otherwise there are different results for 'dit'
+ // (note leading whitespace in last line):
+ // 1) <b> 2) <b>
+ // foobar foobar
+ // </b> </b>
+ cap->retval |= CA_NO_ADJ_OP_END;
flag = current_tagblock(cap->oap, cap->count1, include);
break;
case 'p': /* "ap" = a paragraph */
@@ -7357,16 +7699,33 @@ static void nv_open(cmdarg_T *cap)
n_opencmd(cap);
}
-/*
- * Trigger CursorHold event.
- * When waiting for a character for 'updatetime' K_CURSORHOLD is put in the
- * input buffer. "did_cursorhold" is set to avoid retriggering.
- */
-static void nv_cursorhold(cmdarg_T *cap)
+// Handle an arbitrary event in normal mode
+static void nv_event(cmdarg_T *cap)
{
- apply_autocmds(EVENT_CURSORHOLD, NULL, NULL, false, curbuf);
- did_cursorhold = true;
- cap->retval |= CA_COMMAND_BUSY; /* don't call edit() now */
+ // Garbage collection should have been executed before blocking for events in
+ // the `os_inchar` in `state_enter`, but we also disable it here in case the
+ // `os_inchar` branch was not executed(!queue_empty(loop.events), which could
+ // have `may_garbage_collect` set to true in `normal_check`).
+ //
+ // That is because here we may run code that calls `os_inchar`
+ // later(`f_confirm` or `get_keystroke` for example), but in these cases it is
+ // not safe to perform garbage collection because there could be unreferenced
+ // lists or dicts being used.
+ may_garbage_collect = false;
+ queue_process_events(loop.events);
+ cap->retval |= CA_COMMAND_BUSY; // don't call edit() now
+}
+
+/// Trigger FocusGained event.
+static void nv_focusgained(cmdarg_T *cap)
+{
+ apply_autocmds(EVENT_FOCUSGAINED, NULL, NULL, false, curbuf);
+}
+
+/// Trigger FocusLost event.
+static void nv_focuslost(cmdarg_T *cap)
+{
+ apply_autocmds(EVENT_FOCUSLOST, NULL, NULL, false, curbuf);
}
/*
@@ -7376,3 +7735,14 @@ static int mouse_model_popup(void)
{
return p_mousem[0] == 'p';
}
+
+void normal_cmd(oparg_T *oap, bool toplevel)
+{
+ NormalState s;
+ normal_state_init(&s);
+ s.toplevel = toplevel;
+ s.oa = *oap;
+ normal_prepare(&s);
+ (void)normal_execute(&s.state, safe_vgetc());
+ *oap = s.oa;
+}
diff --git a/src/nvim/normal.h b/src/nvim/normal.h
index b71487c30c..01259de6cd 100644
--- a/src/nvim/normal.h
+++ b/src/nvim/normal.h
@@ -36,8 +36,8 @@ typedef struct oparg_S {
bool block_mode; /* current operator is Visual block mode */
colnr_T start_vcol; /* start col for block mode operator */
colnr_T end_vcol; /* end col for block mode operator */
- long prev_opcount; /* ca.opcount saved for K_CURSORHOLD */
- long prev_count0; /* ca.count0 saved for K_CURSORHOLD */
+ long prev_opcount; // ca.opcount saved for K_EVENT
+ long prev_count0; // ca.count0 saved for K_EVENT
} oparg_T;
/*
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 3fd2c0b773..bef0ebaeed 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -1,12 +1,4 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* ops.c: implementation of various operators: op_shift, op_delete, op_tilde,
* op_change, op_yank, do_put, do_join
*/
@@ -4972,7 +4964,7 @@ void cursor_pos_info(void)
&char_count_cursor, len, eol_size);
if (lnum == curbuf->b_ml.ml_line_count
&& !curbuf->b_p_eol
- && curbuf->b_p_bin
+ && (curbuf->b_p_bin || !curbuf->b_p_fixeol)
&& (long)STRLEN(s) < len)
byte_count_cursor -= eol_size;
}
@@ -4993,7 +4985,7 @@ void cursor_pos_info(void)
}
/* Correction for when last line doesn't have an EOL. */
- if (!curbuf->b_p_eol && curbuf->b_p_bin)
+ if (!curbuf->b_p_eol && (curbuf->b_p_bin || !curbuf->b_p_fixeol))
byte_count -= eol_size;
if (l_VIsual_active) {
diff --git a/src/nvim/option.c b/src/nvim/option.c
index a578f2bb01..486f2083a6 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -1,12 +1,4 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* Code to handle user-settable options. This is all pretty much table-
* driven. Checklist for adding a new option:
* - Put it in the options array below (copy an existing entry).
@@ -30,7 +22,6 @@
#define IN_OPTION_C
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
@@ -131,6 +122,7 @@ static char_u *p_cpt;
static char_u *p_cfu;
static char_u *p_ofu;
static int p_eol;
+static int p_fixeol;
static int p_et;
static char_u *p_fenc;
static char_u *p_ff;
@@ -301,6 +293,243 @@ static char *(p_cot_values[]) = {"menu", "menuone", "longest", "preview",
# include "option.c.generated.h"
#endif
+/// Append string with escaped commas
+static char *strcpy_comma_escaped(char *dest, const char *src, const size_t len)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ size_t shift = 0;
+ for (size_t i = 0; i < len; i++) {
+ if (src[i] == ',') {
+ dest[i + shift++] = '\\';
+ }
+ dest[i + shift] = src[i];
+ }
+ return &dest[len + shift];
+}
+
+/// Compute length of a colon-separated value, doubled and with some suffixes
+///
+/// @param[in] val Colon-separated array value.
+/// @param[in] common_suf_len Length of the common suffix which is appended to
+/// each item in the array, twice.
+/// @param[in] single_suf_len Length of the suffix which is appended to each
+/// item in the array once.
+///
+/// @return Length of the comma-separated string array that contains each item
+/// in the original array twice with suffixes with given length
+/// (common_suf is present after each new item, single_suf is present
+/// after half of the new items) and with commas after each item, commas
+/// inside the values are escaped.
+static inline size_t compute_double_colon_len(const char *const val,
+ const size_t common_suf_len,
+ const size_t single_suf_len)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (val == NULL || *val == NUL) {
+ return 0;
+ }
+ size_t ret = 0;
+ const void *iter = NULL;
+ do {
+ size_t dir_len;
+ const char *dir;
+ iter = vim_colon_env_iter(val, iter, &dir, &dir_len);
+ if (dir != NULL && dir_len > 0) {
+ ret += ((dir_len + memcnt(dir, ',', dir_len) + common_suf_len
+ + !after_pathsep(dir, dir + dir_len)) * 2
+ + single_suf_len);
+ }
+ } while (iter != NULL);
+ return ret;
+}
+
+#define NVIM_SIZE (sizeof("nvim") - 1)
+
+/// Add directories to a comma-separated array from a colon-separated one
+///
+/// Commas are escaped in process. To each item PATHSEP "nvim" is appended in
+/// addition to suf1 and suf2.
+///
+/// @param[in,out] dest Destination comma-separated array.
+/// @param[in] val Source colon-separated array.
+/// @param[in] suf1 If not NULL, suffix appended to destination. Prior to it
+/// directory separator is appended. Suffix must not contain
+/// commas.
+/// @param[in] len1 Length of the suf1.
+/// @param[in] suf2 If not NULL, another suffix appended to destination. Again
+/// with directory separator behind. Suffix must not contain
+/// commas.
+/// @param[in] len2 Length of the suf2.
+/// @param[in] forward If true, iterate over val in forward direction.
+/// Otherwise in reverse.
+///
+/// @return (dest + appended_characters_length)
+static inline char *add_colon_dirs(char *dest, const char *const val,
+ const char *const suf1, const size_t len1,
+ const char *const suf2, const size_t len2,
+ const bool forward)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (val == NULL || *val == NUL) {
+ return dest;
+ }
+ const void *iter = NULL;
+ do {
+ size_t dir_len;
+ const char *dir;
+ iter = (forward ? vim_colon_env_iter : vim_colon_env_iter_rev)(
+ val, iter, &dir, &dir_len);
+ if (dir != NULL && dir_len > 0) {
+ dest = strcpy_comma_escaped(dest, dir, dir_len);
+ if (!after_pathsep(dest - 1, dest)) {
+ *dest++ = PATHSEP;
+ }
+ memmove(dest, "nvim", NVIM_SIZE);
+ dest += NVIM_SIZE;
+ if (suf1 != NULL) {
+ *dest++ = PATHSEP;
+ memmove(dest, suf1, len1);
+ dest += len1;
+ if (suf2 != NULL) {
+ *dest++ = PATHSEP;
+ memmove(dest, suf2, len2);
+ dest += len2;
+ }
+ }
+ *dest++ = ',';
+ }
+ } while (iter != NULL);
+ return dest;
+}
+
+/// Add directory to a comma-separated list of directories
+///
+/// In the added directory comma is escaped.
+///
+/// @param[in,out] dest Destination comma-separated array.
+/// @param[in] dir Directory to append.
+/// @param[in] append_nvim If true, append "nvim" as the very first suffix.
+/// @param[in] suf1 If not NULL, suffix appended to destination. Prior to it
+/// directory separator is appended. Suffix must not contain
+/// commas.
+/// @param[in] len1 Length of the suf1.
+/// @param[in] suf2 If not NULL, another suffix appended to destination. Again
+/// with directory separator behind. Suffix must not contain
+/// commas.
+/// @param[in] len2 Length of the suf2.
+/// @param[in] forward If true, iterate over val in forward direction.
+/// Otherwise in reverse.
+///
+/// @return (dest + appended_characters_length)
+static inline char *add_dir(char *dest, const char *const dir,
+ const size_t dir_len, const bool append_nvim,
+ const char *const suf1, const size_t len1,
+ const char *const suf2, const size_t len2)
+ FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (dir == NULL || dir_len == 0) {
+ return dest;
+ }
+ dest = strcpy_comma_escaped(dest, dir, dir_len);
+ if (append_nvim) {
+ if (!after_pathsep(dest - 1, dest)) {
+ *dest++ = PATHSEP;
+ }
+ memmove(dest, "nvim", NVIM_SIZE);
+ dest += NVIM_SIZE;
+ if (suf1 != NULL) {
+ *dest++ = PATHSEP;
+ memmove(dest, suf1, len1);
+ dest += len1;
+ if (suf2 != NULL) {
+ *dest++ = PATHSEP;
+ memmove(dest, suf2, len2);
+ dest += len2;
+ }
+ }
+ }
+ *dest++ = ',';
+ return dest;
+}
+
+/// Set &runtimepath to default value
+static void set_runtimepath_default(void)
+{
+ size_t rtp_size = 0;
+ char *const data_home = stdpaths_get_xdg_var(kXDGDataHome);
+ char *const config_home = stdpaths_get_xdg_var(kXDGConfigHome);
+ char *const vimruntime = vim_getenv("VIMRUNTIME");
+ char *const data_dirs = stdpaths_get_xdg_var(kXDGDataDirs);
+ char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs);
+#define SITE_SIZE (sizeof("site") - 1)
+#define AFTER_SIZE (sizeof("after") - 1)
+ size_t data_len = 0;
+ size_t config_len = 0;
+ size_t vimruntime_len = 0;
+ if (data_home != NULL) {
+ data_len = strlen(data_home);
+ if (data_len != 0) {
+ rtp_size += ((data_len + memcnt(data_home, ',', data_len)
+ + NVIM_SIZE + 1 + SITE_SIZE + 1
+ + !after_pathsep(data_home, data_home + data_len)) * 2
+ + AFTER_SIZE + 1);
+ }
+ }
+ if (config_home != NULL) {
+ config_len = strlen(config_home);
+ if (config_len != 0) {
+ rtp_size += ((config_len + memcnt(config_home, ',', config_len)
+ + NVIM_SIZE + 1
+ + !after_pathsep(config_home, config_home + config_len)) * 2
+ + AFTER_SIZE + 1);
+ }
+ }
+ if (vimruntime != NULL) {
+ vimruntime_len = strlen(vimruntime);
+ if (vimruntime_len != 0) {
+ rtp_size += vimruntime_len + memcnt(vimruntime, ',', vimruntime_len) + 1;
+ }
+ }
+ rtp_size += compute_double_colon_len(data_dirs, NVIM_SIZE + 1 + SITE_SIZE + 1,
+ AFTER_SIZE + 1);
+ rtp_size += compute_double_colon_len(config_dirs, NVIM_SIZE + 1,
+ AFTER_SIZE + 1);
+ if (rtp_size == 0) {
+ return;
+ }
+ char *const rtp = xmalloc(rtp_size);
+ char *rtp_cur = rtp;
+ rtp_cur = add_dir(rtp_cur, config_home, config_len, true, NULL, 0, NULL, 0);
+ rtp_cur = add_colon_dirs(rtp_cur, config_dirs, NULL, 0, NULL, 0, true);
+ rtp_cur = add_dir(rtp_cur, data_home, data_len, true, "site", SITE_SIZE,
+ NULL, 0);
+ rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, NULL, 0,
+ true);
+ rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, false, NULL, 0,
+ NULL, 0);
+ rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE,
+ "after", AFTER_SIZE, false);
+ rtp_cur = add_dir(rtp_cur, data_home, data_len, true, "site", SITE_SIZE,
+ "after", AFTER_SIZE);
+ rtp_cur = add_colon_dirs(rtp_cur, config_dirs, "after", AFTER_SIZE, NULL, 0,
+ false);
+ rtp_cur = add_dir(rtp_cur, config_home, config_len, true,
+ "after", AFTER_SIZE, NULL, 0);
+ // Strip trailing comma.
+ rtp_cur[-1] = NUL;
+ assert((size_t) (rtp_cur - rtp) == rtp_size);
+#undef SITE_SIZE
+#undef AFTER_SIZE
+ set_string_default("runtimepath", rtp, true);
+ xfree(data_dirs);
+ xfree(config_dirs);
+ xfree(data_home);
+ xfree(config_home);
+ xfree(vimruntime);
+}
+
+#undef NVIM_SIZE
+
/*
* Initialize the options, first part.
*
@@ -308,7 +537,6 @@ static char *(p_cot_values[]) = {"menu", "menuone", "longest", "preview",
*/
void set_init_1(void)
{
- char_u *p;
int opt_idx;
langmap_init();
@@ -320,8 +548,12 @@ void set_init_1(void)
* Find default value for 'shell' option.
* Don't use it if it is empty.
*/
- if ((p = (char_u *)os_getenv("SHELL")) != NULL)
- set_string_default("sh", p);
+ {
+ const char *shell = os_getenv("SHELL");
+ if (shell != NULL) {
+ set_string_default("sh", (char *) shell, false);
+ }
+ }
/*
* Set the default for 'backupskip' to include environment variables for
@@ -339,17 +571,18 @@ void set_init_1(void)
ga_init(&ga, 1, 100);
for (size_t n = 0; n < ARRAY_SIZE(names); ++n) {
bool mustfree = true;
+ char *p;
# ifdef UNIX
if (*names[n] == NUL) {
- p = (char_u *)"/tmp";
+ p = "/tmp";
mustfree = false;
}
else
# endif
- p = (char_u *)vim_getenv(names[n]);
+ p = vim_getenv(names[n]);
if (p != NULL && *p != NUL) {
// First time count the NUL, otherwise count the ','.
- len = (int)STRLEN(p) + 3;
+ len = (int)strlen(p) + 3;
ga_grow(&ga, len);
if (!GA_EMPTY(&ga))
STRCAT(ga.ga_data, ",");
@@ -363,8 +596,7 @@ void set_init_1(void)
}
}
if (ga.ga_data != NULL) {
- set_string_default("bsk", ga.ga_data);
- xfree(ga.ga_data);
+ set_string_default("bsk", ga.ga_data, true);
}
}
@@ -425,17 +657,38 @@ void set_init_1(void)
#if defined(MSWIN) || defined(MAC)
/* Set print encoding on platforms that don't default to latin1 */
- set_string_default("penc",
- (char_u *)"hp-roman8"
- );
+ set_string_default("printencoding", "hp-roman8", false);
#endif
- /* 'printexpr' must be allocated to be able to evaluate it. */
- set_string_default(
- "pexpr",
- (char_u *)
- "system('lpr' . (&printdevice == '' ? '' : ' -P' . &printdevice) . ' ' . v:fname_in) . delete(v:fname_in) + v:shell_error"
- );
+ // 'printexpr' must be allocated to be able to evaluate it.
+ set_string_default("printexpr",
+#ifdef UNIX
+ "system(['lpr'] "
+ "+ (empty(&printdevice)?[]:['-P', &printdevice]) "
+ "+ [v:fname_in])"
+ ". delete(v:fname_in)"
+ "+ v:shell_error",
+#elif defined(MSWIN)
+ "system(['copy', v:fname_in, "
+ "empty(&printdevice)?'LPT1':&printdevice])"
+ ". delete(v:fname_in)",
+#else
+ "",
+#endif
+ false);
+
+ char *backupdir = stdpaths_user_data_subpath("backup", 0);
+ const size_t backupdir_len = strlen(backupdir);
+ backupdir = xrealloc(backupdir, backupdir_len + 3);
+ memmove(backupdir + 2, backupdir, backupdir_len + 1);
+ memmove(backupdir, ".,", 2);
+ set_string_default("viewdir", stdpaths_user_data_subpath("view", 0), true);
+ set_string_default("backupdir", backupdir, true);
+ set_string_default("directory", stdpaths_user_data_subpath("swap", 2), true);
+ set_string_default("undodir", stdpaths_user_data_subpath("undo", 0), true);
+ // Set default for &runtimepath. All necessary expansions are performed in
+ // this function.
+ set_runtimepath_default();
/*
* Set all the options (except the terminal options) to their default
@@ -478,14 +731,16 @@ void set_init_1(void)
* default.
*/
for (opt_idx = 0; options[opt_idx].fullname; opt_idx++) {
+ char *p;
if ((options[opt_idx].flags & P_GETTEXT)
- && options[opt_idx].var != NULL)
- p = (char_u *)_(*(char **)options[opt_idx].var);
- else
- p = option_expand(opt_idx, NULL);
+ && options[opt_idx].var != NULL) {
+ p = _(*(char **)options[opt_idx].var);
+ } else {
+ p = (char *) option_expand(opt_idx, NULL);
+ }
if (p != NULL) {
- p = vim_strsave(p);
- *(char_u **)options[opt_idx].var = p;
+ p = xstrdup(p);
+ *(char **)options[opt_idx].var = p;
/* VIMEXP
* Defaults for all expanded options are currently the same for Vi
* and Vim. When this changes, add some code here! Also need to
@@ -493,7 +748,7 @@ void set_init_1(void)
*/
if (options[opt_idx].flags & P_DEF_ALLOCED)
xfree(options[opt_idx].def_val[VI_DEFAULT]);
- options[opt_idx].def_val[VI_DEFAULT] = p;
+ options[opt_idx].def_val[VI_DEFAULT] = (char_u *) p;
options[opt_idx].flags |= P_DEF_ALLOCED;
}
}
@@ -522,14 +777,14 @@ void set_init_1(void)
(void)set_chars_option(&p_lcs);
/* enc_locale() will try to find the encoding of the current locale. */
- p = enc_locale();
+ char_u *p = enc_locale();
if (p != NULL) {
char_u *save_enc;
/* Try setting 'encoding' and check if the value is valid.
* If not, go back to the default "utf-8". */
save_enc = p_enc;
- p_enc = p;
+ p_enc = (char_u *) p;
if (STRCMP(p_enc, "gb18030") == 0) {
/* We don't support "gb18030", but "cp936" is a good substitute
* for practical purposes, thus use that. It's not an alias to
@@ -674,7 +929,9 @@ set_options_default (
///
/// @param name The name of the option
/// @param val The value of the option
-void set_string_default(const char *name, const char_u *val)
+/// @param allocated If true, do not copy default as it was already allocated.
+static void set_string_default(const char *name, char *val, bool allocated)
+ FUNC_ATTR_NONNULL_ALL
{
int opt_idx = findoption((char_u *)name);
if (opt_idx >= 0) {
@@ -682,7 +939,10 @@ void set_string_default(const char *name, const char_u *val)
xfree(options[opt_idx].def_val[VI_DEFAULT]);
}
- options[opt_idx].def_val[VI_DEFAULT] = (char_u *) xstrdup((char *) val);
+ options[opt_idx].def_val[VI_DEFAULT] = (char_u *) (
+ allocated
+ ? (char_u *) val
+ : (char_u *) xstrdup(val));
options[opt_idx].flags |= P_DEF_ALLOCED;
}
}
@@ -759,12 +1019,9 @@ void set_init_2(void)
*/
void set_init_3(void)
{
-#if defined(UNIX) || defined(WIN3264)
- /*
- * Set 'shellpipe' and 'shellredir', depending on the 'shell' option.
- * This is done after other initializations, where 'shell' might have been
- * set, but only if they have not been set before.
- */
+ // Set 'shellpipe' and 'shellredir', depending on the 'shell' option.
+ // This is done after other initializations, where 'shell' might have been
+ // set, but only if they have not been set before.
int idx_srr;
int do_srr;
int idx_sp;
@@ -821,8 +1078,6 @@ void set_init_3(void)
}
xfree(p);
}
-#endif
-
set_title_defaults();
}
@@ -1244,9 +1499,10 @@ do_set (
} else if (opt_idx >= 0) { /* string */
char_u *save_arg = NULL;
char_u *s = NULL;
- char_u *oldval; /* previous value if *varp */
+ char_u *oldval = NULL; // previous value if *varp
char_u *newval;
- char_u *origval;
+ char_u *origval = NULL;
+ char_u *saved_origval = NULL;
unsigned newlen;
int comma;
int bs;
@@ -1513,14 +1769,37 @@ do_set (
/* Set the new value. */
*(char_u **)(varp) = newval;
+ if (!starting && origval != NULL) {
+ // origval may be freed by
+ // did_set_string_option(), make a copy.
+ saved_origval = vim_strsave(origval);
+ }
+
/* Handle side effects, and set the global value for
* ":set" on local options. */
errmsg = did_set_string_option(opt_idx, (char_u **)varp,
new_value_alloced, oldval, errbuf, opt_flags);
- /* If error detected, print the error message. */
- if (errmsg != NULL)
+ // If error detected, print the error message.
+ if (errmsg != NULL) {
+ xfree(saved_origval);
goto skip;
+ }
+
+ if (saved_origval != NULL) {
+ char_u buf_type[7];
+ vim_snprintf((char *)buf_type, ARRAY_SIZE(buf_type), "%s",
+ (opt_flags & OPT_LOCAL) ? "local" : "global");
+ set_vim_var_string(VV_OPTION_NEW,
+ *(char_u **)varp, -1);
+ set_vim_var_string(VV_OPTION_OLD, saved_origval, -1);
+ set_vim_var_string(VV_OPTION_TYPE, buf_type, -1);
+ apply_autocmds(EVENT_OPTIONSET,
+ (char_u *)options[opt_idx].fullname,
+ NULL, false, NULL);
+ reset_v_option_vars();
+ xfree(saved_origval);
+ }
} else {
// key code option(FIXME(tarruda): Show a warning or something
// similar)
@@ -2070,6 +2349,7 @@ set_string_option (
char_u *s;
char_u **varp;
char_u *oldval;
+ char_u *saved_oldval = NULL;
char_u *r = NULL;
if (options[opt_idx].var == NULL) /* don't set hidden option */
@@ -2083,10 +2363,30 @@ set_string_option (
: opt_flags);
oldval = *varp;
*varp = s;
- if ((r = did_set_string_option(opt_idx, varp, TRUE, oldval, NULL,
+
+ if (!starting) {
+ saved_oldval = vim_strsave(oldval);
+ }
+
+ if ((r = did_set_string_option(opt_idx, varp, (int)true, oldval, NULL,
opt_flags)) == NULL)
did_set_option(opt_idx, opt_flags, TRUE);
+ // call autocommand after handling side effects
+ if (saved_oldval != NULL) {
+ char_u buf_type[7];
+ vim_snprintf((char *)buf_type, ARRAY_SIZE(buf_type), "%s",
+ (opt_flags & OPT_LOCAL) ? "local" : "global");
+ set_vim_var_string(VV_OPTION_NEW, *varp, -1);
+ set_vim_var_string(VV_OPTION_OLD, saved_oldval, -1);
+ set_vim_var_string(VV_OPTION_TYPE, buf_type, -1);
+ apply_autocmds(EVENT_OPTIONSET,
+ (char_u *)options[opt_idx].fullname,
+ NULL, false, NULL);
+ reset_v_option_vars();
+ xfree(saved_oldval);
+ }
+
return r;
}
@@ -3288,6 +3588,9 @@ set_bool_option (
/* when 'endofline' is changed, redraw the window title */
else if ((int *)varp == &curbuf->b_p_eol) {
redraw_titles();
+ } else if ((int *)varp == &curbuf->b_p_fixeol) {
+ // when 'fixeol' is changed, redraw the window title
+ redraw_titles();
}
/* when 'bomb' is changed, redraw the window title and tab page text */
else if ((int *)varp == &curbuf->b_p_bomb) {
@@ -3555,8 +3858,29 @@ set_bool_option (
* End of handling side effects for bool options.
*/
+ // after handling side effects, call autocommand
+
options[opt_idx].flags |= P_WAS_SET;
+ if (!starting) {
+ char_u buf_old[2];
+ char_u buf_new[2];
+ char_u buf_type[7];
+ vim_snprintf((char *)buf_old, ARRAY_SIZE(buf_old), "%d",
+ old_value ? true: false);
+ vim_snprintf((char *)buf_new, ARRAY_SIZE(buf_new), "%d",
+ value ? true: false);
+ vim_snprintf((char *)buf_type, ARRAY_SIZE(buf_type), "%s",
+ (opt_flags & OPT_LOCAL) ? "local" : "global");
+ set_vim_var_string(VV_OPTION_NEW, buf_new, -1);
+ set_vim_var_string(VV_OPTION_OLD, buf_old, -1);
+ set_vim_var_string(VV_OPTION_TYPE, buf_type, -1);
+ apply_autocmds(EVENT_OPTIONSET,
+ (char_u *) options[opt_idx].fullname,
+ NULL, false, NULL);
+ reset_v_option_vars();
+ }
+
comp_col(); /* in case 'ruler' or 'showcmd' changed */
if (curwin->w_curswant != MAXCOL
&& (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0)
@@ -3928,6 +4252,23 @@ set_num_option (
options[opt_idx].flags |= P_WAS_SET;
+ if (!starting && errmsg == NULL) {
+ char_u buf_old[NUMBUFLEN];
+ char_u buf_new[NUMBUFLEN];
+ char_u buf_type[7];
+ vim_snprintf((char *)buf_old, ARRAY_SIZE(buf_old), "%ld", old_value);
+ vim_snprintf((char *)buf_new, ARRAY_SIZE(buf_new), "%ld", value);
+ vim_snprintf((char *)buf_type, ARRAY_SIZE(buf_type), "%s",
+ (opt_flags & OPT_LOCAL) ? "local" : "global");
+ set_vim_var_string(VV_OPTION_NEW, buf_new, -1);
+ set_vim_var_string(VV_OPTION_OLD, buf_old, -1);
+ set_vim_var_string(VV_OPTION_TYPE, buf_type, -1);
+ apply_autocmds(EVENT_OPTIONSET,
+ (char_u *) options[opt_idx].fullname,
+ NULL, false, NULL);
+ reset_v_option_vars();
+ }
+
comp_col(); /* in case 'columns' or 'ls' changed */
if (curwin->w_curswant != MAXCOL
&& (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0)
@@ -4962,6 +5303,7 @@ static char_u *get_varp(vimoption_T *p)
case PV_CFU: return (char_u *)&(curbuf->b_p_cfu);
case PV_OFU: return (char_u *)&(curbuf->b_p_ofu);
case PV_EOL: return (char_u *)&(curbuf->b_p_eol);
+ case PV_FIXEOL: return (char_u *)&(curbuf->b_p_fixeol);
case PV_ET: return (char_u *)&(curbuf->b_p_et);
case PV_FENC: return (char_u *)&(curbuf->b_p_fenc);
case PV_FF: return (char_u *)&(curbuf->b_p_ff);
@@ -5206,6 +5548,7 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_bin = p_bin;
buf->b_p_bomb = p_bomb;
buf->b_p_et = p_et;
+ buf->b_p_fixeol = p_fixeol;
buf->b_p_et_nobin = p_et_nobin;
buf->b_p_ml = p_ml;
buf->b_p_ml_nobin = p_ml_nobin;
@@ -5948,7 +6291,7 @@ static void paste_option_changed(void)
old_p_paste = p_paste;
}
-/// vimrc_found() - Called when a ".vimrc" or "VIMINIT" has been found.
+/// vimrc_found() - Called when a vimrc or "VIMINIT" has been found.
///
/// Set the values for options that didn't get set yet to the Vim defaults.
/// When "fname" is not NULL, use it to set $"envname" when it wasn't set yet.
@@ -6141,6 +6484,7 @@ void save_file_ff(buf_T *buf)
* from when editing started (save_file_ff() called).
* Also when 'endofline' was changed and 'binary' is set, or when 'bomb' was
* changed and 'binary' is not set.
+ * Also when 'endofline' was changed and 'fixeol' is not set.
* When "ignore_empty" is true don't consider a new, empty buffer to be
* changed.
*/
@@ -6155,9 +6499,9 @@ bool file_ff_differs(buf_T *buf, bool ignore_empty)
&& *ml_get_buf(buf, (linenr_T)1, FALSE) == NUL)
return FALSE;
if (buf->b_start_ffc != *buf->b_p_ff)
- return TRUE;
- if (buf->b_p_bin && buf->b_start_eol != buf->b_p_eol)
- return TRUE;
+ return true;
+ if ((buf->b_p_bin || !buf->b_p_fixeol) && buf->b_start_eol != buf->b_p_eol)
+ return true;
if (!buf->b_p_bin && buf->b_start_bomb != buf->b_p_bomb)
return TRUE;
if (buf->b_start_fenc == NULL)
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index d4d3410d5c..c72e1cf0bb 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -665,6 +665,7 @@ enum {
, BV_DEF
, BV_INC
, BV_EOL
+ , BV_FIXEOL
, BV_EP
, BV_ET
, BV_FENC
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 842b0a7c82..5187340629 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -51,15 +51,6 @@ return {
defaults={if_true={vi=224}}
},
{
- full_name='antialias', abbreviation='anti',
- type='bool', scope={'global'},
- vi_def=true,
- vim=true,
- redraw={'everything'},
- enable_if=false,
- defaults={if_true={vi=false, vim=false}}
- },
- {
full_name='arabic', abbreviation='arab',
type='bool', scope={'window'},
vi_def=true,
@@ -176,7 +167,7 @@ return {
vi_def=true,
expand=true,
varname='p_bdir',
- defaults={if_true={vi=macros('DFLT_BDIR')}}
+ defaults={if_true={vi=''}}
},
{
full_name='backupext', abbreviation='bex',
@@ -627,7 +618,7 @@ return {
vi_def=true,
expand=true,
varname='p_dir',
- defaults={if_true={vi=macros('DFLT_DIR')}}
+ defaults={if_true={vi=''}}
},
{
full_name='display', abbreviation='dy',
@@ -808,6 +799,14 @@ return {
defaults={if_true={vi="vert:|,fold:-"}}
},
{
+ full_name='fixendofline', abbreviation='fixeol',
+ type='bool', scope={'buffer'},
+ vi_def=true,
+ redraw={'statuslines'},
+ varname='p_fixeol',
+ defaults={if_true={vi=true}}
+ },
+ {
full_name='fkmap', abbreviation='fk',
type='bool', scope={'global'},
vi_def=true,
@@ -1916,7 +1915,7 @@ return {
vi_def=true,
expand=true,
varname='p_rtp',
- defaults={if_true={vi=macros('DFLT_RUNTIMEPATH')}}
+ defaults={if_true={vi=''}}
},
{
full_name='scroll', abbreviation='scr',
@@ -2524,7 +2523,7 @@ return {
vi_def=true,
expand=true,
varname='p_udir',
- defaults={if_true={vi="."}}
+ defaults={if_true={vi=''}}
},
{
full_name='undofile', abbreviation='udf',
@@ -2585,7 +2584,7 @@ return {
vi_def=true,
expand=true,
varname='p_vdir',
- defaults={if_true={vi=macros('DFLT_VDIR')}}
+ defaults={if_true={vi=''}}
},
{
full_name='viewoptions', abbreviation='vop',
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index 7be8a868bd..a791dca39c 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -46,7 +46,19 @@ bool os_env_exists(const char *name)
int os_setenv(const char *name, const char *value, int overwrite)
FUNC_ATTR_NONNULL_ALL
{
+#ifdef HAVE_SETENV
return setenv(name, value, overwrite);
+#elif defined(HAVE_PUTENV_S)
+ if (!overwrite && os_getenv(name) != NULL) {
+ return 0;
+ }
+ if (_putenv_s(name, value) == 0) {
+ return 0;
+ }
+ return -1;
+#else
+# error "This system has no implementation available for os_setenv()"
+#endif
}
/// Unset environment variable
@@ -141,6 +153,27 @@ void init_homedir(void)
char_u *var = (char_u *)os_getenv("HOME");
+#ifdef WIN32
+ // Typically, $HOME is not defined on Windows, unless the user has
+ // specifically defined it for Vim's sake. However, on Windows NT
+ // platforms, $HOMEDRIVE and $HOMEPATH are automatically defined for
+ // each user. Try constructing $HOME from these.
+ if (var == NULL) {
+ const char *homedrive = os_getenv("HOMEDRIVE");
+ const char *homepath = os_getenv("HOMEPATH");
+ if (homepath == NULL) {
+ homepath = "\\";
+ }
+ if (homedrive != NULL && strlen(homedrive) + strlen(homepath) < MAXPATHL) {
+ snprintf((char *)NameBuff, MAXPATHL, "%s%s", homedrive, homepath);
+ if (NameBuff[0] != NUL) {
+ var = NameBuff;
+ vim_setenv("HOME", (char *)NameBuff);
+ }
+ }
+ }
+#endif
+
if (var != NULL) {
#ifdef UNIX
/*
@@ -415,6 +448,74 @@ static char *remove_tail(char *p, char *pend, char *name)
return pend;
}
+/// Iterate over colon-separated list
+///
+/// @note Environment variables must not be modified during iteration.
+///
+/// @param[in] val Value of the environment variable to iterate over.
+/// @param[in] iter Pointer used for iteration. Must be NULL on first
+/// iteration.
+/// @param[out] dir Location where pointer to the start of the current
+/// directory name should be saved. May be set to NULL.
+/// @param[out] len Location where current directory length should be saved.
+///
+/// @return Next iter argument value or NULL when iteration should stop.
+const void *vim_colon_env_iter(const char *const val,
+ const void *const iter,
+ const char **const dir,
+ size_t *const len)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const char *varval = (const char *) iter;
+ if (varval == NULL) {
+ varval = val;
+ }
+ *dir = varval;
+ const char *const dirend = strchr(varval, ':');
+ if (dirend == NULL) {
+ *len = strlen(varval);
+ return NULL;
+ } else {
+ *len = (size_t) (dirend - varval);
+ return dirend + 1;
+ }
+}
+
+/// Iterate over colon-separated list in reverse order
+///
+/// @note Environment variables must not be modified during iteration.
+///
+/// @param[in] val Value of the environment variable to iterate over.
+/// @param[in] iter Pointer used for iteration. Must be NULL on first
+/// iteration.
+/// @param[out] dir Location where pointer to the start of the current
+/// directory name should be saved. May be set to NULL.
+/// @param[out] len Location where current directory length should be saved.
+///
+/// @return Next iter argument value or NULL when iteration should stop.
+const void *vim_colon_env_iter_rev(const char *const val,
+ const void *const iter,
+ const char **const dir,
+ size_t *const len)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const char *varend = (const char *) iter;
+ if (varend == NULL) {
+ varend = val + strlen(val) - 1;
+ }
+ const size_t varlen = (size_t) (varend - val) + 1;
+ const char *const colon = xmemrchr(val, ':', varlen);
+ if (colon == NULL) {
+ *len = varlen;
+ *dir = val;
+ return NULL;
+ } else {
+ *dir = colon + 1;
+ *len = (size_t) (varend - colon);
+ return colon - 1;
+ }
+}
+
/// Vim's version of getenv().
/// Special handling of $HOME, $VIM and $VIMRUNTIME, allowing the user to
/// override the vim runtime directory at runtime. Also does ACP to 'enc'
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 522e49950c..d59b66e773 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -140,9 +140,8 @@ static bool is_executable(const char_u *name)
static bool is_executable_in_path(const char_u *name, char_u **abspath)
FUNC_ATTR_NONNULL_ARG(1)
{
- const char *path = getenv("PATH");
- // PATH environment variable does not exist or is empty.
- if (path == NULL || *path == NUL) {
+ const char *path = os_getenv("PATH");
+ if (path == NULL) {
return false;
}
@@ -186,13 +185,13 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath)
/// Opens or creates a file and returns a non-negative integer representing
/// the lowest-numbered unused file descriptor, for use in subsequent system
-/// calls (read, write, lseek, fcntl, etc.). If the operation fails, `-errno`
-/// is returned, and no file is created or modified.
+/// calls (read, write, lseek, fcntl, etc.). If the operation fails, a libuv
+/// error code is returned, and no file is created or modified.
///
/// @param flags Bitwise OR of flags defined in <fcntl.h>
/// @param mode Permissions for the newly-created file (IGNORED if 'flags' is
/// not `O_CREAT` or `O_TMPFILE`), subject to the current umask
-/// @return file descriptor, or negative `errno` on failure
+/// @return file descriptor, or libuv error code on failure
int os_open(const char* path, int flags, int mode)
FUNC_ATTR_NONNULL_ALL
{
@@ -205,28 +204,29 @@ int os_open(const char* path, int flags, int mode)
/// Get stat information for a file.
///
-/// @return OK on success, FAIL if a failure occurred.
-static bool os_stat(const char *name, uv_stat_t *statbuf)
+/// @return libuv return code.
+static int os_stat(const char *name, uv_stat_t *statbuf)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
int result = uv_fs_stat(&fs_loop, &request, name, NULL);
*statbuf = request.statbuf;
uv_fs_req_cleanup(&request);
- return (result == kLibuvSuccess);
+ return result;
}
/// Get the file permissions for a given file.
///
-/// @return `-1` when `name` doesn't exist.
+/// @return libuv error code on error.
int32_t os_getperm(const char_u *name)
FUNC_ATTR_NONNULL_ALL
{
uv_stat_t statbuf;
- if (os_stat((char *)name, &statbuf)) {
+ int stat_result = os_stat((char *)name, &statbuf);
+ if (stat_result == kLibuvSuccess) {
return (int32_t)statbuf.st_mode;
} else {
- return -1;
+ return stat_result;
}
}
@@ -271,7 +271,7 @@ bool os_file_exists(const char_u *name)
FUNC_ATTR_NONNULL_ALL
{
uv_stat_t statbuf;
- return os_stat((char *)name, &statbuf);
+ return os_stat((char *)name, &statbuf) == kLibuvSuccess;
}
/// Check if a file is readable.
@@ -323,7 +323,7 @@ int os_rename(const char_u *path, const char_u *new_path)
/// Make a directory.
///
-/// @return `0` for success, -errno for failure.
+/// @return `0` for success, libuv error code for failure.
int os_mkdir(const char *path, int32_t mode)
FUNC_ATTR_NONNULL_ALL
{
@@ -343,7 +343,7 @@ int os_mkdir(const char *path, int32_t mode)
/// failed to create. I.e. it will contain dir or any
/// of the higher level directories.
///
-/// @return `0` for success, -errno for failure.
+/// @return `0` for success, libuv error code for failure.
int os_mkdir_recurse(const char *const dir, int32_t mode,
char **const failed_dir)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
@@ -366,11 +366,17 @@ int os_mkdir_recurse(const char *const dir, int32_t mode,
}
while (e != real_end) {
if (e > past_head) {
- *e = '/';
+ *e = PATHSEP;
} else {
*past_head = past_head_save;
}
- e += strlen(e);
+ const size_t component_len = strlen(e);
+ e += component_len;
+ if (e == real_end
+ && memcnt(e - component_len, PATHSEP, component_len) == component_len) {
+ // Path ends with something like "////". Ignore this.
+ break;
+ }
int ret;
if ((ret = os_mkdir(curdir, mode)) != 0) {
*failed_dir = curdir;
@@ -465,7 +471,7 @@ int os_remove(const char *path)
bool os_fileinfo(const char *path, FileInfo *file_info)
FUNC_ATTR_NONNULL_ALL
{
- return os_stat(path, &(file_info->stat));
+ return os_stat(path, &(file_info->stat)) == kLibuvSuccess;
}
/// Get the file information for a given path without following links
@@ -567,7 +573,7 @@ bool os_fileid(const char *path, FileID *file_id)
FUNC_ATTR_NONNULL_ALL
{
uv_stat_t statbuf;
- if (os_stat(path, &statbuf)) {
+ if (os_stat(path, &statbuf) == kLibuvSuccess) {
file_id->inode = statbuf.st_ino;
file_id->device_id = statbuf.st_dev;
return true;
diff --git a/src/nvim/os/fs_defs.h b/src/nvim/os/fs_defs.h
index df1031b721..52b2841514 100644
--- a/src/nvim/os/fs_defs.h
+++ b/src/nvim/os/fs_defs.h
@@ -21,9 +21,9 @@ typedef struct {
uv_dirent_t ent; ///< @private The entry information.
} Directory;
-/// Function to convert -errno error to char * error description
+/// Function to convert libuv error to char * error description
///
-/// -errno errors are returned by a number of os functions.
+/// negative libuv error codes are returned by a number of os functions.
#define os_strerror uv_strerror
#endif // NVIM_OS_FS_DEFS_H
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index e2cff2f9c0..ef6b5ff6f5 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -73,10 +73,27 @@ void input_stop(void)
stream_close(&read_stream, NULL);
}
+static void cursorhold_event(void **argv)
+{
+ event_T event = State & INSERT ? EVENT_CURSORHOLDI : EVENT_CURSORHOLD;
+ apply_autocmds(event, NULL, NULL, false, curbuf);
+ did_cursorhold = true;
+}
+
+static void create_cursorhold_event(void)
+{
+ // If events are enabled and the queue has any items, this function should not
+ // have been called(inbuf_poll would return kInputAvail)
+ // TODO(tarruda): Cursorhold should be implemented as a timer set during the
+ // `state_check` callback for the states where it can be triggered.
+ assert(!events_enabled || queue_empty(loop.events));
+ queue_put(loop.events, cursorhold_event, 0);
+}
+
// Low level input function
int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt)
{
- if (rbuffer_size(input_buffer)) {
+ if (maxlen && rbuffer_size(input_buffer)) {
return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen);
}
@@ -87,16 +104,12 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt)
}
} else {
if ((result = inbuf_poll((int)p_ut)) == kInputNone) {
- if (trigger_cursorhold() && maxlen >= 3
- && !typebuf_changed(tb_change_cnt)) {
- buf[0] = K_SPECIAL;
- buf[1] = KS_EXTRA;
- buf[2] = KE_CURSORHOLD;
- return 3;
+ if (trigger_cursorhold() && !typebuf_changed(tb_change_cnt)) {
+ create_cursorhold_event();
+ } else {
+ before_blocking();
+ result = inbuf_poll(-1);
}
-
- before_blocking();
- result = inbuf_poll(-1);
}
}
@@ -105,14 +118,14 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt)
return 0;
}
- if (rbuffer_size(input_buffer)) {
+ if (maxlen && rbuffer_size(input_buffer)) {
// Safe to convert rbuffer_read to int, it will never overflow since we use
// relatively small buffers.
return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen);
}
// If there are events, return the keys directly
- if (pending_events()) {
+ if (maxlen && pending_events()) {
return push_event_key(buf, maxlen);
}
@@ -313,6 +326,11 @@ void input_done(void)
input_eof = true;
}
+bool input_available(void)
+{
+ return rbuffer_size(input_buffer) != 0;
+}
+
// This is a replacement for the old `WaitForChar` function in os_unix.c
static InbufPollResult inbuf_poll(int ms)
{
diff --git a/src/nvim/os/os.h b/src/nvim/os/os.h
index 69bd1ff4fd..3e89e5a94a 100644
--- a/src/nvim/os/os.h
+++ b/src/nvim/os/os.h
@@ -5,6 +5,7 @@
#include <uv.h>
#include "nvim/os/fs_defs.h"
+#include "nvim/os/stdpaths_defs.h"
#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -12,6 +13,7 @@
# include "os/mem.h.generated.h"
# include "os/env.h.generated.h"
# include "os/users.h.generated.h"
+# include "os/stdpaths.h.generated.h"
#endif
#endif // NVIM_OS_OS_H
diff --git a/src/nvim/os/os_defs.h b/src/nvim/os/os_defs.h
index 3d56115401..7d77899287 100644
--- a/src/nvim/os/os_defs.h
+++ b/src/nvim/os/os_defs.h
@@ -39,32 +39,6 @@
# define MAXPATHL 1024
#endif
-#ifndef FILETYPE_FILE
-# define FILETYPE_FILE "filetype.vim"
-#endif
-
-#ifndef FTPLUGIN_FILE
-# define FTPLUGIN_FILE "ftplugin.vim"
-#endif
-
-#ifndef INDENT_FILE
-# define INDENT_FILE "indent.vim"
-#endif
-
-#ifndef FTOFF_FILE
-# define FTOFF_FILE "ftoff.vim"
-#endif
-
-#ifndef FTPLUGOF_FILE
-# define FTPLUGOF_FILE "ftplugof.vim"
-#endif
-
-#ifndef INDOFF_FILE
-# define INDOFF_FILE "indoff.vim"
-#endif
-
-#define DFLT_ERRORFILE "errors.err"
-
// Command-processing buffer. Use large buffers for all platforms.
#define CMDBUFFSIZE 1024
@@ -103,9 +77,9 @@
# include <strings.h>
#endif
-/// Function to convert -errno error to char * error description
+/// Function to convert libuv error to char * error description
///
-/// -errno errors are returned by a number of os functions.
+/// negative libuv error codes are returned by a number of os functions.
#define os_strerror uv_strerror
#endif // NVIM_OS_OS_DEFS_H
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index 57e25560de..3813c45726 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -418,7 +418,8 @@ static void read_input(DynamicBuffer *buf)
// Finished a line, add a NL, unless this line should not have one.
// FIXME need to make this more readable
if (lnum != curbuf->b_op_end.lnum
- || !curbuf->b_p_bin
+ || (!curbuf->b_p_bin
+ && curbuf->b_p_fixeol)
|| (lnum != curbuf->b_no_eol_lnum
&& (lnum !=
curbuf->b_ml.ml_line_count
diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c
index 7158721433..0ff6016e32 100644
--- a/src/nvim/os/signal.c
+++ b/src/nvim/os/signal.c
@@ -32,9 +32,13 @@ void signal_init(void)
signal_watcher_init(&loop, &shup, NULL);
signal_watcher_init(&loop, &squit, NULL);
signal_watcher_init(&loop, &sterm, NULL);
+#ifdef SIGPIPE
signal_watcher_start(&spipe, on_signal, SIGPIPE);
+#endif
signal_watcher_start(&shup, on_signal, SIGHUP);
+#ifdef SIGQUIT
signal_watcher_start(&squit, on_signal, SIGQUIT);
+#endif
signal_watcher_start(&sterm, on_signal, SIGTERM);
#ifdef SIGPWR
signal_watcher_init(&loop, &spwr, NULL);
@@ -82,12 +86,16 @@ static char * signal_name(int signum)
case SIGPWR:
return "SIGPWR";
#endif
+#ifdef SIGPIPE
case SIGPIPE:
return "SIGPIPE";
+#endif
case SIGTERM:
return "SIGTERM";
+#ifdef SIGQUIT
case SIGQUIT:
return "SIGQUIT";
+#endif
case SIGHUP:
return "SIGHUP";
default:
@@ -123,11 +131,15 @@ static void on_signal(SignalWatcher *handle, int signum, void *data)
ml_sync_all(false, false);
break;
#endif
+#ifdef SIGPIPE
case SIGPIPE:
// Ignore
break;
+#endif
case SIGTERM:
+#ifdef SIGQUIT
case SIGQUIT:
+#endif
case SIGHUP:
if (!rejecting_deadly) {
deadly_signal(signum);
diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c
new file mode 100644
index 0000000000..c9631a434c
--- /dev/null
+++ b/src/nvim/os/stdpaths.c
@@ -0,0 +1,108 @@
+#include <stdbool.h>
+
+#include "nvim/os/stdpaths_defs.h"
+#include "nvim/os/os.h"
+#include "nvim/path.h"
+#include "nvim/memory.h"
+#include "nvim/ascii.h"
+
+/// Names of the environment variables, mapped to XDGVarType values
+static const char *xdg_env_vars[] = {
+ [kXDGConfigHome] = "XDG_CONFIG_HOME",
+ [kXDGDataHome] = "XDG_DATA_HOME",
+ [kXDGCacheHome] = "XDG_CACHE_HOME",
+ [kXDGRuntimeDir] = "XDG_RUNTIME_DIR",
+ [kXDGConfigDirs] = "XDG_CONFIG_DIRS",
+ [kXDGDataDirs] = "XDG_DATA_DIRS",
+};
+
+/// Defaults for XDGVarType values
+///
+/// Used in case environment variables contain nothing. Need to be expanded.
+static const char *const xdg_defaults[] = {
+#ifdef WIN32
+ // Windows
+ [kXDGConfigHome] = "$LOCALAPPDATA\\nvim\\config",
+ [kXDGDataHome] = "$LOCALAPPDATA\\nvim\\data",
+ [kXDGCacheHome] = "$LOCALAPPDATA\\nvim\\cache",
+ [kXDGRuntimeDir] = NULL,
+ [kXDGConfigDirs] = NULL,
+ [kXDGDataDirs] = NULL,
+#else
+ // Linux, BSD, CYGWIN, Apple
+ [kXDGConfigHome] = "~/.config",
+ [kXDGDataHome] = "~/.local/share",
+ [kXDGCacheHome] = "~/.cache",
+ [kXDGRuntimeDir] = NULL,
+ [kXDGConfigDirs] = "/etc/xdg/",
+ [kXDGDataDirs] = "/usr/local/share/:/usr/share/",
+#endif
+};
+
+/// Return XDG variable value
+///
+/// @param[in] idx XDG variable to use.
+///
+/// @return [allocated] variable value.
+char *stdpaths_get_xdg_var(const XDGVarType idx)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const char *const env = xdg_env_vars[idx];
+ const char *const fallback = xdg_defaults[idx];
+
+ const char *const env_val = os_getenv(env);
+ char *ret = NULL;
+ if (env_val != NULL) {
+ ret = xstrdup(env_val);
+ } else if (fallback) {
+ ret = (char *) expand_env_save((char_u *)fallback);
+ }
+
+ return ret;
+}
+
+/// Return nvim-specific XDG directory subpath
+///
+/// @param[in] idx XDG directory to use.
+///
+/// @return [allocated] `{xdg_directory}/nvim`
+static char *get_xdg_home(const XDGVarType idx)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ char *dir = stdpaths_get_xdg_var(idx);
+ if (dir) {
+ dir = concat_fnames_realloc(dir, "nvim", true);
+ }
+ return dir;
+}
+
+/// Return subpath of $XDG_CONFIG_HOME
+///
+/// @param[in] fname New component of the path.
+///
+/// @return [allocated] `$XDG_CONFIG_HOME/nvim/{fname}`
+char *stdpaths_user_conf_subpath(const char *fname)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ return concat_fnames_realloc(get_xdg_home(kXDGConfigHome), fname, true);
+}
+
+/// Return subpath of $XDG_DATA_HOME
+///
+/// @param[in] fname New component of the path.
+/// @param[in] trailing_pathseps Amount of trailing path separators to add.
+///
+/// @return [allocated] `$XDG_DATA_HOME/nvim/{fname}`
+char *stdpaths_user_data_subpath(const char *fname,
+ const size_t trailing_pathseps)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ char *ret = concat_fnames_realloc(get_xdg_home(kXDGDataHome), fname, true);
+ if (trailing_pathseps) {
+ const size_t len = strlen(ret);
+ ret = xrealloc(ret, len + trailing_pathseps + 1);
+ memset(ret + len, PATHSEP, trailing_pathseps);
+ ret[len + trailing_pathseps] = NUL;
+ }
+ return ret;
+}
diff --git a/src/nvim/os/stdpaths_defs.h b/src/nvim/os/stdpaths_defs.h
new file mode 100644
index 0000000000..1433e0bc62
--- /dev/null
+++ b/src/nvim/os/stdpaths_defs.h
@@ -0,0 +1,14 @@
+#ifndef NVIM_OS_STDPATHS_DEFS_H
+#define NVIM_OS_STDPATHS_DEFS_H
+
+/// List of possible XDG variables
+typedef enum {
+ kXDGConfigHome, ///< XDG_CONFIG_HOME
+ kXDGDataHome, ///< XDG_DATA_HOME
+ kXDGCacheHome, ///< XDG_CACHE_HOME
+ kXDGRuntimeDir, ///< XDG_RUNTIME_DIR
+ kXDGConfigDirs, ///< XDG_CONFIG_DIRS
+ kXDGDataDirs, ///< XDG_DATA_DIRS
+} XDGVarType;
+
+#endif // NVIM_OS_STDPATHS_DEFS_H
diff --git a/src/nvim/os/unix_defs.h b/src/nvim/os/unix_defs.h
index 949973bf40..e3ba3262f4 100644
--- a/src/nvim/os/unix_defs.h
+++ b/src/nvim/os/unix_defs.h
@@ -9,7 +9,6 @@
# include <sys/param.h>
#endif
-
#define TEMP_DIR_NAMES {"$TMPDIR", "/tmp", ".", "~"}
#define TEMP_FILE_PATH_MAXLEN 256
@@ -18,56 +17,4 @@
// Special wildcards that need to be handled by the shell.
#define SPECIAL_WILDCHAR "`'{"
-// Unix system-dependent file names
-#ifndef SYS_VIMRC_FILE
-# define SYS_VIMRC_FILE "$VIM/nvimrc"
-#endif
-#ifndef DFLT_HELPFILE
-# define DFLT_HELPFILE "$VIMRUNTIME/doc/help.txt"
-#endif
-#ifndef SYNTAX_FNAME
-# define SYNTAX_FNAME "$VIMRUNTIME/syntax/%s.vim"
-#endif
-#ifndef USR_EXRC_FILE
-# define USR_EXRC_FILE "~/.exrc"
-#endif
-#ifndef USR_VIMRC_FILE
-# define USR_VIMRC_FILE "~/.nvimrc"
-#endif
-#ifndef USR_VIMRC_FILE2
-# define USR_VIMRC_FILE2 "~/.nvim/nvimrc"
-#endif
-#ifndef EXRC_FILE
-# define EXRC_FILE ".exrc"
-#endif
-#ifndef VIMRC_FILE
-# define VIMRC_FILE ".nvimrc"
-#endif
-#ifndef SHADA_FILE
-# define SHADA_FILE "~/.nvim/shada/main.shada"
-#endif
-
-// Default for 'backupdir'.
-#ifndef DFLT_BDIR
-# define DFLT_BDIR ".,~/tmp,~/"
-#endif
-
-// Default for 'directory'.
-#ifndef DFLT_DIR
-# define DFLT_DIR ".,~/tmp,/var/tmp,/tmp"
-#endif
-
-// Default for 'viewdir'.
-#ifndef DFLT_VDIR
-# define DFLT_VDIR "~/.nvim/view"
-#endif
-
-#ifdef RUNTIME_GLOBAL
-# define DFLT_RUNTIMEPATH "~/.nvim," RUNTIME_GLOBAL ",$VIMRUNTIME," \
- RUNTIME_GLOBAL "/after,~/.nvim/after"
-#else
-# define DFLT_RUNTIMEPATH \
- "~/.nvim,$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after,~/.nvim/after"
-#endif
-
#endif // NVIM_OS_UNIX_DEFS_H
diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h
index b7ec50a109..32960dfbe9 100644
--- a/src/nvim/os/win_defs.h
+++ b/src/nvim/os/win_defs.h
@@ -6,21 +6,6 @@
#define TEMP_DIR_NAMES {"$TMP", "$TEMP", "$USERPROFILE", ""}
#define TEMP_FILE_PATH_MAXLEN _MAX_PATH
-// Defines needed to fix the build on Windows:
-// - USR_EXRC_FILE
-// - USR_VIMRC_FILE
-// - SHADA_FILE
-// - DFLT_DIR
-// - DFLT_BDIR
-// - DFLT_VDIR
-// - DFLT_RUNTIMEPATH
-// - EXRC_FILE
-// - VIMRC_FILE
-// - SYNTAX_FNAME
-// - DFLT_HELPFILE
-// - SYS_VIMRC_FILE
-// - SPECIAL_WILDCHAR
-
#define USE_CRNL
#ifdef _MSC_VER
@@ -32,7 +17,9 @@
# endif
#endif
+#ifdef _MSC_VER
typedef SSIZE_T ssize_t;
+#endif
#ifndef SSIZE_MAX
# ifdef _WIN64
diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c
index 828ccd556d..62b264046c 100644
--- a/src/nvim/os_unix.c
+++ b/src/nvim/os_unix.c
@@ -1,12 +1,4 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* os_unix.c -- code for all flavors of Unix (BSD, SYSV, SVR4, POSIX, ...)
*
* A lot of this file was originally written by Juergen Weigert and later
diff --git a/src/nvim/path.c b/src/nvim/path.c
index a9d1d052d4..253035ed99 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -1,6 +1,4 @@
-
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
@@ -329,6 +327,31 @@ int vim_fnamencmp(char_u *x, char_u *y, size_t len)
#endif
}
+/// Append fname2 to fname1
+///
+/// @param[in] fname1 First fname to append to.
+/// @param[in] len1 Length of fname1.
+/// @param[in] fname2 Secord part of the file name.
+/// @param[in] len2 Length of fname2.
+/// @param[in] sep If true and fname1 does not end with a path separator,
+/// add a path separator before fname2.
+///
+/// @return fname1
+static inline char *do_concat_fnames(char *fname1, const size_t len1,
+ const char *fname2, const size_t len2,
+ const bool sep)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ if (sep && *fname1 && !after_pathsep(fname1, fname1 + len1)) {
+ fname1[len1] = PATHSEP;
+ memmove(fname1 + len1 + 1, fname2, len2 + 1);
+ } else {
+ memmove(fname1 + len1, fname2, len2 + 1);
+ }
+
+ return fname1;
+}
+
/// Concatenate file names fname1 and fname2 into allocated memory.
///
/// Only add a '/' or '\\' when 'sep' is true and it is necessary.
@@ -339,17 +362,33 @@ int vim_fnamencmp(char_u *x, char_u *y, size_t len)
/// if necessary
/// @return [allocated] Concatenation of fname1 and fname2.
char *concat_fnames(const char *fname1, const char *fname2, bool sep)
- FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_NONNULL_RET
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
- char *dest = xmalloc(strlen(fname1) + strlen(fname2) + 3);
-
- strcpy(dest, fname1);
- if (sep) {
- add_pathsep(dest);
- }
- strcat(dest, fname2);
+ const size_t len1 = strlen(fname1);
+ const size_t len2 = strlen(fname2);
+ char *dest = xmalloc(len1 + len2 + 3);
+ memmove(dest, fname1, len1 + 1);
+ return do_concat_fnames(dest, len1, fname2, len2, sep);
+}
- return dest;
+/// Concatenate file names fname1 and fname2
+///
+/// Like concat_fnames(), but in place of allocating new memory it reallocates
+/// fname1. For this reason fname1 must be allocated with xmalloc, and can no
+/// longer be used after running concat_fnames_realloc.
+///
+/// @param fname1 is the first part of the path or filename
+/// @param fname2 is the second half of the path or filename
+/// @param sep is a flag to indicate a path separator should be added
+/// if necessary
+/// @return [allocated] Concatenation of fname1 and fname2.
+char *concat_fnames_realloc(char *fname1, const char *fname2, bool sep)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ const size_t len1 = strlen(fname1);
+ const size_t len2 = strlen(fname2);
+ return do_concat_fnames(xrealloc(fname1, len1 + len2 + 3), len1,
+ fname2, len2, sep);
}
/*
@@ -428,16 +467,13 @@ bool path_has_wildcard(const char_u *p)
return false;
}
-#if defined(UNIX)
/*
* Unix style wildcard expansion code.
- * It's here because it's used both for Unix and Mac.
*/
static int pstrcmp(const void *a, const void *b)
{
return pathcmp(*(char **)a, *(char **)b, -1);
}
-#endif
/// Checks if a path has a character path_expand can expand.
/// @param p The path to expand.
diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt
index 243ac19b33..6687918df4 100644
--- a/src/nvim/po/CMakeLists.txt
+++ b/src/nvim/po/CMakeLists.txt
@@ -82,7 +82,7 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG)
set(poFile ${CMAKE_CURRENT_SOURCE_DIR}/${name}.po)
add_custom_target(check-po-${name}
- COMMAND $<TARGET_FILE:nvim> -u NONE -n -e -X
+ COMMAND $<TARGET_FILE:nvim> -u NONE -n -e
-S ${CMAKE_CURRENT_SOURCE_DIR}/check.vim
-c "if error == 0 | q | endif" -c cq ${poFile} ||
${CMAKE_COMMAND} -E echo "${name}.po failed the check."
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index 10012a9775..001740943f 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -1,7 +1,7 @@
/// @file popupmnu.c
///
/// Popup menu (PUM)
-//
+
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 4d53238381..8e6ae46a3b 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -1,17 +1,8 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* quickfix.c: functions for quickfix mode, using a file with error messages
*/
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 4cd422400f..b96dcc66b3 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -1897,9 +1897,10 @@ static int nfa_regpiece(void)
return OK;
}
- // The engine is very inefficient (uses too many states) when the maximum is
- // much larger than the minimum. Bail out if we can use the other engine.
- if ((nfa_re_flags & RE_AUTO) && maxval > minval + 200) {
+ // The engine is very inefficient (uses too many states) when the maximum
+ // is much larger than the minimum and when the maximum is large. Bail out
+ // if we can use the other engine.
+ if ((nfa_re_flags & RE_AUTO) && (maxval > minval + 200 || maxval > 500)) {
return FAIL;
}
@@ -5761,7 +5762,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
// Bail out quickly when there can't be a match, avoid the overhead of
// win_linetabsize() on long lines.
- if (op != 1 && col > t->state->val) {
+ if (op != 1 && col > t->state->val * (has_mbyte ? MB_MAXBYTES : 1)) {
break;
}
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 0c4cf30602..9fdb476748 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -1,12 +1,4 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* screen.c: code for displaying on the screen
*
* Output to the screen (console, terminal emulator or GUI window) is minimized
@@ -87,7 +79,6 @@
*/
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
diff --git a/src/nvim/search.c b/src/nvim/search.c
index a44b0e00c7..fb7dfa350b 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -1,16 +1,8 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-/*
* search.c: code for normal mode searching commands
*/
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
@@ -926,7 +918,7 @@ static int first_submatch(regmmatch_T *rp)
* Careful: If spats[0].off.line == TRUE and spats[0].off.off == 0 this
* makes the movement linewise without moving the match position.
*
- * return 0 for failure, 1 for found, 2 for found and line offset added
+ * Return 0 for failure, 1 for found, 2 for found and line offset added.
*/
int do_search(
oparg_T *oap, /* can be NULL */
@@ -3201,6 +3193,7 @@ current_tagblock (
int do_include = include;
bool save_p_ws = p_ws;
int retval = FAIL;
+ int is_inclusive = true;
p_ws = false;
@@ -3295,9 +3288,16 @@ again:
if (inc_cursor() < 0)
break;
} else {
- /* Exclude the '<' of the end tag. */
- if (*get_cursor_pos_ptr() == '<')
+ char_u *c = get_cursor_pos_ptr();
+ // Exclude the '<' of the end tag.
+ // If the closing tag is on new line, do not decrement cursor, but make
+ // operation exclusive, so that the linefeed will be selected
+ if (*c == '<' && !VIsual_active && curwin->w_cursor.col == 0) {
+ // do not decrement cursor
+ is_inclusive = false;
+ } else if (*c == '<') {
dec_cursor();
+ }
}
end_pos = curwin->w_cursor;
@@ -3342,8 +3342,9 @@ again:
* on an empty area. */
curwin->w_cursor = start_pos;
oap->inclusive = false;
- } else
- oap->inclusive = true;
+ } else {
+ oap->inclusive = is_inclusive;
+ }
}
retval = OK;
@@ -4623,6 +4624,7 @@ void set_search_pattern(const SearchPattern pat)
{
free_spat(&spats[0]);
memcpy(&(spats[0]), &pat, sizeof(spats[0]));
+ set_vv_searchforward();
}
/// Set last substitute pattern
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 523f8db6f0..340c14066a 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -10,7 +10,9 @@
#include <stdint.h>
#include <inttypes.h>
#include <errno.h>
-#include <unistd.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
#include <assert.h>
#include <msgpack.h>
@@ -43,6 +45,7 @@
#include "nvim/path.h"
#include "nvim/fileio.h"
#include "nvim/strings.h"
+#include "nvim/quickfix.h"
#include "nvim/lib/khash.h"
#include "nvim/lib/kvec.h"
@@ -98,6 +101,7 @@ KHASH_SET_INIT_STR(strset)
#define SEARCH_KEY_HIGHLIGHTED "sh"
#define SEARCH_KEY_OFFSET "so"
#define SEARCH_KEY_PAT "sp"
+#define SEARCH_KEY_BACKWARD "sb"
#define REG_KEY_TYPE "rt"
#define REG_KEY_WIDTH "rw"
@@ -201,11 +205,11 @@ enum SRNIFlags {
kSDReadHeader = (1 << kSDItemHeader), ///< Determines whether header should
///< be read (it is usually ignored).
kSDReadUndisableableData = (
- (1 << kSDItemSearchPattern)
- | (1 << kSDItemSubString)
- | (1 << kSDItemJump)), ///< Data reading which cannot be disabled by &shada
- ///< or other options except for disabling reading
- ///< ShaDa as a whole.
+ (1 << kSDItemSearchPattern)
+ | (1 << kSDItemSubString)
+ | (1 << kSDItemJump)), ///< Data reading which cannot be disabled by
+ ///< &shada or other options except for disabling
+ ///< reading ShaDa as a whole.
kSDReadRegisters = (1 << kSDItemRegister), ///< Determines whether registers
///< should be read (may only be
///< disabled when writing, but
@@ -263,6 +267,7 @@ typedef struct {
bool is_last_used;
bool is_substitute_pattern;
bool highlighted;
+ bool search_backward;
char *pat;
dict_T *additional_data;
} search_pattern;
@@ -441,7 +446,7 @@ typedef struct sd_write_def {
.attr = { __VA_ARGS__ } \
} \
}
-#define DEFAULT_POS {1, 0, 0}
+#define DEFAULT_POS { 1, 0, 0 }
static const pos_T default_pos = DEFAULT_POS;
static const ShadaEntry sd_default_values[] = {
[kSDItemMissing] = { .type = kSDItemMissing, .timestamp = 0 },
@@ -455,6 +460,7 @@ static const ShadaEntry sd_default_values[] = {
.is_last_used = true,
.is_substitute_pattern = false,
.highlighted = false,
+ .search_backward = false,
.pat = NULL,
.additional_data = NULL),
DEF_SDE(SubString, sub_string, .sub = NULL, .additional_elements = NULL),
@@ -527,11 +533,14 @@ static inline void hmll_init(HMLList *const hmll, const size_t size)
///
/// @param hmll Pointer to the list.
/// @param cur_entry Name of the variable to iterate over.
+/// @param code Code to execute on each iteration.
///
/// @return `for` cycle header (use `HMLL_FORALL(hmll, cur_entry) {body}`).
-#define HMLL_FORALL(hmll, cur_entry) \
+#define HMLL_FORALL(hmll, cur_entry, code) \
for (HMLListEntry *cur_entry = (hmll)->first; cur_entry != NULL; \
- cur_entry = cur_entry->next)
+ cur_entry = cur_entry->next) { \
+ code \
+ } \
/// Remove entry from the linked list
///
@@ -627,11 +636,14 @@ static inline void hmll_insert(HMLList *const hmll,
/// @param hmll Pointer to the list.
/// @param cur_entry Name of the variable to iterate over, must be already
/// defined.
+/// @param code Code to execute on each iteration.
///
/// @return `for` cycle header (use `HMLL_FORALL(hmll, cur_entry) {body}`).
-#define HMLL_ITER_BACK(hmll, cur_entry) \
+#define HMLL_ITER_BACK(hmll, cur_entry, code) \
for (cur_entry = (hmll)->last; cur_entry != NULL; \
- cur_entry = cur_entry->prev)
+ cur_entry = cur_entry->prev) { \
+ code \
+ }
/// Free linked list
///
@@ -812,7 +824,7 @@ static ShaDaReadResult sd_reader_skip(ShaDaReadDef *const sd_reader,
///
/// All arguments are passed to os_open().
///
-/// @return file descriptor or -errno on failure.
+/// @return file descriptor or libuv error on failure.
static int open_file(const char *const fname, const int flags, const int mode)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
@@ -822,15 +834,15 @@ open_file_start:
fd = os_open(fname, flags, mode);
if (fd < 0) {
- if (-fd == ENOENT) {
+ if (fd == UV_ENOENT) {
return fd;
}
- if (-fd == ENOMEM && !did_try_to_free) {
+ if (fd == UV_ENOMEM && !did_try_to_free) {
try_to_free_memory();
did_try_to_free = true;
goto open_file_start;
}
- if (-fd != EEXIST) {
+ if (fd != UV_EEXIST) {
emsg3(_(SERR "System error while opening ShaDa file %s: %s"),
fname, os_strerror(fd));
}
@@ -844,7 +856,7 @@ open_file_start:
/// @param[in] fname File name to open.
/// @param[out] sd_reader Location where reader structure will be saved.
///
-/// @return -errno in case of error, 0 otherwise.
+/// @return libuv error in case of error, 0 otherwise.
static int open_shada_file_for_reading(const char *const fname,
ShaDaReadDef *sd_reader)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
@@ -953,16 +965,16 @@ static int shada_read_file(const char *const file, const int flags)
if (p_verbose > 0) {
verbose_enter();
smsg(_("Reading ShaDa file \"%s\"%s%s%s"),
- fname,
- (flags & kShaDaWantInfo) ? _(" info") : "",
- (flags & kShaDaWantMarks) ? _(" marks") : "",
- (flags & kShaDaGetOldfiles) ? _(" oldfiles") : "",
- of_ret != 0 ? _(" FAILED") : "");
+ fname,
+ (flags & kShaDaWantInfo) ? _(" info") : "",
+ (flags & kShaDaWantMarks) ? _(" marks") : "",
+ (flags & kShaDaGetOldfiles) ? _(" oldfiles") : "",
+ of_ret != 0 ? _(" FAILED") : "");
verbose_leave();
}
if (of_ret != 0) {
- if (-of_ret == ENOENT && (flags & kShaDaMissingError)) {
+ if (of_ret == UV_ENOENT && (flags & kShaDaMissingError)) {
emsg3(_(SERR "System error while opening ShaDa file %s for reading: %s"),
fname, os_strerror(of_ret));
}
@@ -1005,8 +1017,8 @@ static const void *shada_hist_iter(const void *const iter,
.histtype = history_type,
.string = (char *) hist_he.hisstr,
.sep = (char) (history_type == HIST_SEARCH
- ? (char) hist_he.hisstr[STRLEN(hist_he.hisstr) + 1]
- : 0),
+ ? (char) hist_he.hisstr[STRLEN(hist_he.hisstr) + 1]
+ : 0),
.additional_elements = hist_he.additional_elements,
}
}
@@ -1068,11 +1080,11 @@ static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry,
}
}
HMLListEntry *insert_after;
- HMLL_ITER_BACK(hmll, insert_after) {
+ HMLL_ITER_BACK(hmll, insert_after, {
if (insert_after->data.timestamp <= entry.timestamp) {
break;
}
- }
+ })
hmll_insert(hmll, insert_after, entry, can_free_entry);
}
@@ -1130,14 +1142,14 @@ static inline void hms_to_he_array(const HistoryMergerState *const hms_p,
FUNC_ATTR_NONNULL_ALL
{
histentry_T *hist = hist_array;
- HMLL_FORALL(&hms_p->hmll, cur_entry) {
+ HMLL_FORALL(&hms_p->hmll, cur_entry, {
hist->timestamp = cur_entry->data.timestamp;
hist->hisnum = (int) (hist - hist_array) + 1;
hist->hisstr = (char_u *) cur_entry->data.data.history_item.string;
hist->additional_elements =
cur_entry->data.data.history_item.additional_elements;
hist++;
- }
+ })
*new_hisnum = (int) (hist - hist_array);
*new_hisidx = *new_hisnum - 1;
}
@@ -1155,10 +1167,11 @@ static inline void hms_dealloc(HistoryMergerState *const hms_p)
///
/// @param[in] hms_p Merger structure to iterate over.
/// @param[out] cur_entry Name of the iterator variable.
+/// @param code Code to execute on each iteration.
///
/// @return for cycle header. Use `HMS_ITER(hms_p, cur_entry) {body}`.
-#define HMS_ITER(hms_p, cur_entry) \
- HMLL_FORALL(&((hms_p)->hmll), cur_entry)
+#define HMS_ITER(hms_p, cur_entry, code) \
+ HMLL_FORALL(&((hms_p)->hmll), cur_entry, code)
/// Find buffer for given buffer name (cached)
///
@@ -1335,17 +1348,18 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
(cur_entry.data.search_pattern.is_substitute_pattern
? &set_substitute_pattern
: &set_search_pattern)((SearchPattern) {
- .magic = cur_entry.data.search_pattern.magic,
- .no_scs = !cur_entry.data.search_pattern.smartcase,
- .off = {
- .line = cur_entry.data.search_pattern.has_line_offset,
- .end = cur_entry.data.search_pattern.place_cursor_at_end,
- .off = cur_entry.data.search_pattern.offset,
- },
- .pat = (char_u *) cur_entry.data.search_pattern.pat,
- .additional_data = cur_entry.data.search_pattern.additional_data,
- .timestamp = cur_entry.timestamp,
- });
+ .magic = cur_entry.data.search_pattern.magic,
+ .no_scs = !cur_entry.data.search_pattern.smartcase,
+ .off = {
+ .dir = cur_entry.data.search_pattern.search_backward ? '?' : '/',
+ .line = cur_entry.data.search_pattern.has_line_offset,
+ .end = cur_entry.data.search_pattern.place_cursor_at_end,
+ .off = cur_entry.data.search_pattern.offset,
+ },
+ .pat = (char_u *) cur_entry.data.search_pattern.pat,
+ .additional_data = cur_entry.data.search_pattern.additional_data,
+ .timestamp = cur_entry.timestamp,
+ });
if (cur_entry.data.search_pattern.is_last_used) {
set_last_used_pattern(
cur_entry.data.search_pattern.is_substitute_pattern);
@@ -1583,6 +1597,20 @@ shada_read_main_cycle_end:
kh_dealloc(strset, &oldfiles_set);
}
+/// Default shada file location: cached path
+static char *default_shada_file = NULL;
+
+/// Get the default ShaDa file
+static const char *shada_get_default_file(void)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (default_shada_file == NULL) {
+ char *shada_dir = stdpaths_user_data_subpath("shada", 0);
+ default_shada_file = concat_fnames_realloc(shada_dir, "main.shada", true);
+ }
+ return default_shada_file;
+}
+
/// Get the ShaDa file name to use
///
/// If "file" is given and not empty, use it (has already been expanded by
@@ -1600,22 +1628,7 @@ static char *shada_filename(const char *file)
file = used_shada_file;
} else {
if ((file = find_shada_parameter('n')) == NULL || *file == NUL) {
-#ifdef SHADA_FILE2
- // don't use $HOME when not defined (turned into "c:/"!).
- if (os_getenv((char_u *)"HOME") == NULL) {
- // don't use $VIM when not available.
- expand_env((char_u *)"$VIM", NameBuff, MAXPATHL);
- if (STRCMP("$VIM", NameBuff) != 0) { // $VIM was expanded
- file = SHADA_FILE2;
- } else {
- file = SHADA_FILE;
- }
- } else {
-#endif
- file = SHADA_FILE;
-#ifdef SHADA_FILE2
- }
-#endif
+ file = shada_get_default_file();
}
// XXX It used to be one level lower, so that whatever is in
// `used_shada_file` was expanded. I intentionally moved it here
@@ -1755,6 +1768,7 @@ static bool shada_pack_entry(msgpack_packer *const packer,
+ ONE_IF_NOT_DEFAULT(entry, search_pattern.is_substitute_pattern)
+ ONE_IF_NOT_DEFAULT(entry, search_pattern.highlighted)
+ ONE_IF_NOT_DEFAULT(entry, search_pattern.offset)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.search_backward)
// finally, additional data:
+ (size_t) (
entry.data.search_pattern.additional_data
@@ -1781,6 +1795,7 @@ static bool shada_pack_entry(msgpack_packer *const packer,
PACK_BOOL(entry, SEARCH_KEY_PLACE_CURSOR_AT_END, place_cursor_at_end);
PACK_BOOL(entry, SEARCH_KEY_IS_SUBSTITUTE_PATTERN, is_substitute_pattern);
PACK_BOOL(entry, SEARCH_KEY_HIGHLIGHTED, highlighted);
+ PACK_BOOL(entry, SEARCH_KEY_BACKWARD, search_backward);
if (!CHECK_DEFAULT(entry, search_pattern.offset)) {
PACK_STATIC_STR(SEARCH_KEY_OFFSET);
msgpack_pack_int64(spacker, entry.data.search_pattern.offset);
@@ -2422,17 +2437,26 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
}
const unsigned srni_flags = (unsigned) (
- kSDReadUndisableableData
- | kSDReadUnknown
- | (dump_history ? kSDReadHistory : 0)
- | (dump_registers ? kSDReadRegisters : 0)
- | (dump_global_vars ? kSDReadVariables : 0)
- | (dump_global_marks ? kSDReadGlobalMarks : 0)
- | (num_marked_files ? kSDReadLocalMarks | kSDReadChanges : 0));
+ kSDReadUndisableableData
+ | kSDReadUnknown
+ | (dump_history ? kSDReadHistory : 0)
+ | (dump_registers ? kSDReadRegisters : 0)
+ | (dump_global_vars ? kSDReadVariables : 0)
+ | (dump_global_marks ? kSDReadGlobalMarks : 0)
+ | (num_marked_files ? kSDReadLocalMarks | kSDReadChanges : 0));
msgpack_packer *const packer = msgpack_packer_new(sd_writer,
&msgpack_sd_writer_write);
+ // Set b_last_cursor for all the buffers that have a window.
+ //
+ // It is needed to correctly save '"' mark on exit. Has a side effect of
+ // setting '"' mark in all windows on :wshada to the current cursor
+ // position (basically what :wviminfo used to do).
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ set_last_cursor(wp);
+ }
+
FOR_ALL_BUFFERS(buf) {
if (buf->b_ffname != NULL && shada_removable((char *) buf->b_ffname)) {
int kh_ret;
@@ -2470,8 +2494,11 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
// Write buffer list
if (find_shada_parameter('%') != NULL) {
size_t buf_count = 0;
+#define IGNORE_BUF(buf)\
+ (buf->b_ffname == NULL || !buf->b_p_bl || bt_quickfix(buf) \
+ || in_bufset(&removable_bufs, buf))
FOR_ALL_BUFFERS(buf) {
- if (buf->b_ffname != NULL && !in_bufset(&removable_bufs, buf)) {
+ if (!IGNORE_BUF(buf)) {
buf_count++;
}
}
@@ -2489,7 +2516,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
};
size_t i = 0;
FOR_ALL_BUFFERS(buf) {
- if (buf->b_ffname == NULL || in_bufset(&removable_bufs, buf)) {
+ if (IGNORE_BUF(buf)) {
continue;
}
buflist_entry.data.buffer_list.buffers[i] = (struct buffer_list_buffer) {
@@ -2505,6 +2532,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
goto shada_write_exit;
}
xfree(buflist_entry.data.buffer_list.buffers);
+#undef IGNORE_BUF
}
// Write some of the variables
@@ -2573,6 +2601,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
&& search_highlighted), \
.pat = (char *) pat.pat, \
.additional_data = pat.additional_data, \
+ .search_backward = (!is_sub && pat.off.dir == '?'), \
} \
} \
} \
@@ -2873,16 +2902,16 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
for (size_t i = 0; i < HIST_COUNT; i++) {
if (dump_one_history[i]) {
hms_insert_whole_neovim_history(&wms->hms[i]);
- HMS_ITER(&wms->hms[i], cur_entry) {
+ HMS_ITER(&wms->hms[i], cur_entry, {
if (!shada_pack_encoded_entry(
- packer, &sd_writer->sd_conv, (PossiblyFreedShadaEntry) {
- .data = cur_entry->data,
- .can_free_entry = cur_entry->can_free_entry,
- }, max_kbyte)) {
+ packer, &sd_writer->sd_conv, (PossiblyFreedShadaEntry) {
+ .data = cur_entry->data,
+ .can_free_entry = cur_entry->can_free_entry,
+ }, max_kbyte)) {
ret = kSDWriteFailed;
break;
}
- }
+ })
hms_dealloc(&wms->hms[i]);
if (ret == kSDWriteFailed) {
goto shada_write_exit;
@@ -2949,9 +2978,9 @@ shada_write_file_open:
fd = (intptr_t) open_file(tempname, O_CREAT|O_WRONLY|O_NOFOLLOW|O_EXCL,
perm);
if (fd < 0) {
- if (-fd == EEXIST
+ if (fd == UV_EEXIST
#ifdef ELOOP
- || -fd == ELOOP
+ || fd == UV_ELOOP
#endif
) {
// File already exists, try another name
@@ -3333,8 +3362,8 @@ static inline char *get_converted_string(const vimconv_T *const sd_conv,
entry_name " entry at position %" PRIu64 " " \
error_desc
#define CHECK_KEY(key, expected) ( \
- key.via.str.size == sizeof(expected) - 1 \
- && STRNCMP(key.via.str.ptr, expected, sizeof(expected) - 1) == 0)
+ key.via.str.size == sizeof(expected) - 1 \
+ && STRNCMP(key.via.str.ptr, expected, sizeof(expected) - 1) == 0)
#define CLEAR_GA_AND_ERROR_OUT(ga) \
do { \
ga_clear(&ga); \
@@ -3357,18 +3386,17 @@ static inline char *get_converted_string(const vimconv_T *const sd_conv,
tgt = proc(obj.via.attr); \
} while (0)
#define CHECK_KEY_IS_STR(entry_name) \
- do { \
- if (unpacked.data.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR) { \
- emsgu(_(READERR(entry_name, "has key which is not a string")), \
- initial_fpos); \
- CLEAR_GA_AND_ERROR_OUT(ad_ga); \
- } else if (unpacked.data.via.map.ptr[i].key.via.str.size == 0) { \
- emsgu(_(READERR(entry_name, "has empty key")), initial_fpos); \
- CLEAR_GA_AND_ERROR_OUT(ad_ga); \
- } \
- } while (0)
+ if (unpacked.data.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR) { \
+ emsgu(_(READERR(entry_name, "has key which is not a string")), \
+ initial_fpos); \
+ CLEAR_GA_AND_ERROR_OUT(ad_ga); \
+ } else if (unpacked.data.via.map.ptr[i].key.via.str.size == 0) { \
+ emsgu(_(READERR(entry_name, "has empty key")), initial_fpos); \
+ CLEAR_GA_AND_ERROR_OUT(ad_ga); \
+ }
#define CHECKED_KEY(entry_name, name, error_desc, tgt, condition, attr, proc) \
- if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, name)) { \
+ else if (CHECK_KEY( /* NOLINT(readability/braces) */ \
+ unpacked.data.via.map.ptr[i].key, name)) { \
CHECKED_ENTRY( \
condition, "has " name " key value " error_desc, \
entry_name, unpacked.data.via.map.ptr[i].val, \
@@ -3388,17 +3416,17 @@ static inline char *get_converted_string(const vimconv_T *const sd_conv,
#define INT_KEY(entry_name, name, tgt, proc) \
CHECKED_KEY( \
entry_name, name, "which is not an integer", tgt, \
- (unpacked.data.via.map.ptr[i].val.type \
- == MSGPACK_OBJECT_POSITIVE_INTEGER \
- || unpacked.data.via.map.ptr[i].val.type \
- == MSGPACK_OBJECT_NEGATIVE_INTEGER), \
+ ((unpacked.data.via.map.ptr[i].val.type \
+ == MSGPACK_OBJECT_POSITIVE_INTEGER) \
+ || (unpacked.data.via.map.ptr[i].val.type \
+ == MSGPACK_OBJECT_NEGATIVE_INTEGER)), \
i64, proc)
#define INTEGER_KEY(entry_name, name, tgt) \
INT_KEY(entry_name, name, tgt, TOINT)
#define LONG_KEY(entry_name, name, tgt) \
INT_KEY(entry_name, name, tgt, TOLONG)
#define ADDITIONAL_KEY \
- { \
+ else { /* NOLINT(readability/braces) */ \
ga_grow(&ad_ga, 1); \
memcpy(((char *)ad_ga.ga_data) + ((size_t) ad_ga.ga_len \
* sizeof(*unpacked.data.via.map.ptr)), \
@@ -3407,9 +3435,9 @@ static inline char *get_converted_string(const vimconv_T *const sd_conv,
ad_ga.ga_len++; \
}
#define CONVERTED(str, len) ( \
- sd_reader->sd_conv.vc_type != CONV_NONE \
- ? get_converted_string(&sd_reader->sd_conv, (str), (len)) \
- : xmemdupz((str), (len)))
+ sd_reader->sd_conv.vc_type != CONV_NONE \
+ ? get_converted_string(&sd_reader->sd_conv, (str), (len)) \
+ : xmemdupz((str), (len)))
#define BIN_CONVERTED(b) CONVERTED(b.ptr, b.size)
#define SET_ADDITIONAL_DATA(tgt, name) \
do { \
@@ -3603,35 +3631,28 @@ shada_read_next_item_start:
garray_T ad_ga;
ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1);
for (size_t i = 0; i < unpacked.data.via.map.size; i++) {
- CHECK_KEY_IS_STR("search pattern");
+ CHECK_KEY_IS_STR("search pattern")
BOOLEAN_KEY("search pattern", SEARCH_KEY_MAGIC,
entry->data.search_pattern.magic)
- else
- BOOLEAN_KEY("search pattern", SEARCH_KEY_SMARTCASE,
- entry->data.search_pattern.smartcase)
- else
- BOOLEAN_KEY("search pattern", SEARCH_KEY_HAS_LINE_OFFSET,
- entry->data.search_pattern.has_line_offset)
- else
- BOOLEAN_KEY("search pattern", SEARCH_KEY_PLACE_CURSOR_AT_END,
- entry->data.search_pattern.place_cursor_at_end)
- else
- BOOLEAN_KEY("search pattern", SEARCH_KEY_IS_LAST_USED,
- entry->data.search_pattern.is_last_used)
- else
- BOOLEAN_KEY("search pattern", SEARCH_KEY_IS_SUBSTITUTE_PATTERN,
- entry->data.search_pattern.is_substitute_pattern)
- else
- BOOLEAN_KEY("search pattern", SEARCH_KEY_HIGHLIGHTED,
- entry->data.search_pattern.highlighted)
- else
- INTEGER_KEY("search pattern", SEARCH_KEY_OFFSET,
- entry->data.search_pattern.offset)
- else
- CONVERTED_STRING_KEY("search pattern", SEARCH_KEY_PAT,
- entry->data.search_pattern.pat)
- else
- ADDITIONAL_KEY
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_SMARTCASE,
+ entry->data.search_pattern.smartcase)
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_HAS_LINE_OFFSET,
+ entry->data.search_pattern.has_line_offset)
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_PLACE_CURSOR_AT_END,
+ entry->data.search_pattern.place_cursor_at_end)
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_IS_LAST_USED,
+ entry->data.search_pattern.is_last_used)
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_IS_SUBSTITUTE_PATTERN,
+ entry->data.search_pattern.is_substitute_pattern)
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_HIGHLIGHTED,
+ entry->data.search_pattern.highlighted)
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_BACKWARD,
+ entry->data.search_pattern.search_backward)
+ INTEGER_KEY("search pattern", SEARCH_KEY_OFFSET,
+ entry->data.search_pattern.offset)
+ CONVERTED_STRING_KEY("search pattern", SEARCH_KEY_PAT,
+ entry->data.search_pattern.pat)
+ ADDITIONAL_KEY
}
if (entry->data.search_pattern.pat == NULL) {
emsgu(_(READERR("search pattern", "has no pattern")), initial_fpos);
@@ -3652,7 +3673,7 @@ shada_read_next_item_start:
garray_T ad_ga;
ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1);
for (size_t i = 0; i < unpacked.data.via.map.size; i++) {
- CHECK_KEY_IS_STR("mark");
+ CHECK_KEY_IS_STR("mark")
if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, KEY_NAME_CHAR)) {
if (type_u64 == kSDItemJump || type_u64 == kSDItemChange) {
emsgu(_(READERR("mark", "has n key which is only valid for "
@@ -3665,15 +3686,11 @@ shada_read_next_item_start:
"has n key value which is not an unsigned integer",
"mark", unpacked.data.via.map.ptr[i].val,
entry->data.filemark.name, u64, TOCHAR);
- } else {
- LONG_KEY("mark", KEY_LNUM, entry->data.filemark.mark.lnum)
- else
- INTEGER_KEY("mark", KEY_COL, entry->data.filemark.mark.col)
- else
- STRING_KEY("mark", KEY_FILE, entry->data.filemark.fname)
- else
- ADDITIONAL_KEY
}
+ LONG_KEY("mark", KEY_LNUM, entry->data.filemark.mark.lnum)
+ INTEGER_KEY("mark", KEY_COL, entry->data.filemark.mark.col)
+ STRING_KEY("mark", KEY_FILE, entry->data.filemark.fname)
+ ADDITIONAL_KEY
}
if (entry->data.filemark.fname == NULL) {
emsgu(_(READERR("mark", "is missing file name")), initial_fpos);
@@ -3698,48 +3715,44 @@ shada_read_next_item_start:
garray_T ad_ga;
ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1);
for (size_t i = 0; i < unpacked.data.via.map.size; i++) {
- CHECK_KEY_IS_STR("register");
- TYPED_KEY("register", REG_KEY_TYPE, "an unsigned integer",
- entry->data.reg.type, POSITIVE_INTEGER, u64, TOU8)
- else
- TYPED_KEY("register", KEY_NAME_CHAR, "an unsigned integer",
- entry->data.reg.name, POSITIVE_INTEGER, u64, TOCHAR)
- else
- TYPED_KEY("register", REG_KEY_WIDTH, "an unsigned integer",
- entry->data.reg.width, POSITIVE_INTEGER, u64, TOSIZE)
- else
- if (CHECK_KEY(unpacked.data.via.map.ptr[i].key,
- REG_KEY_CONTENTS)) {
- if (unpacked.data.via.map.ptr[i].val.type != MSGPACK_OBJECT_ARRAY) {
- emsgu(_(READERR(
- "register",
- "has " REG_KEY_CONTENTS " key with non-array value")),
- initial_fpos);
- CLEAR_GA_AND_ERROR_OUT(ad_ga);
- }
- if (unpacked.data.via.map.ptr[i].val.via.array.size == 0) {
- emsgu(_(READERR("register",
- "has " REG_KEY_CONTENTS " key with empty array")),
- initial_fpos);
+ CHECK_KEY_IS_STR("register")
+ if (CHECK_KEY(unpacked.data.via.map.ptr[i].key,
+ REG_KEY_CONTENTS)) {
+ if (unpacked.data.via.map.ptr[i].val.type != MSGPACK_OBJECT_ARRAY) {
+ emsgu(_(READERR("register",
+ "has " REG_KEY_CONTENTS
+ " key with non-array value")),
+ initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ if (unpacked.data.via.map.ptr[i].val.via.array.size == 0) {
+ emsgu(_(READERR("register",
+ "has " REG_KEY_CONTENTS " key with empty array")),
+ initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ const msgpack_object_array arr =
+ unpacked.data.via.map.ptr[i].val.via.array;
+ for (size_t i = 0; i < arr.size; i++) {
+ if (arr.ptr[i].type != MSGPACK_OBJECT_BIN) {
+ emsgu(_(READERR("register", "has " REG_KEY_CONTENTS " array "
+ "with non-binary value")), initial_fpos);
CLEAR_GA_AND_ERROR_OUT(ad_ga);
}
- const msgpack_object_array arr =
- unpacked.data.via.map.ptr[i].val.via.array;
- for (size_t i = 0; i < arr.size; i++) {
- if (arr.ptr[i].type != MSGPACK_OBJECT_BIN) {
- emsgu(_(READERR("register", "has " REG_KEY_CONTENTS " array "
- "with non-binary value")), initial_fpos);
- CLEAR_GA_AND_ERROR_OUT(ad_ga);
- }
- }
- entry->data.reg.contents_size = arr.size;
- entry->data.reg.contents = xmalloc(arr.size * sizeof(char *));
- for (size_t i = 0; i < arr.size; i++) {
- entry->data.reg.contents[i] = BIN_CONVERTED(arr.ptr[i].via.bin);
- }
- } else {
- ADDITIONAL_KEY
}
+ entry->data.reg.contents_size = arr.size;
+ entry->data.reg.contents = xmalloc(arr.size * sizeof(char *));
+ for (size_t i = 0; i < arr.size; i++) {
+ entry->data.reg.contents[i] = BIN_CONVERTED(arr.ptr[i].via.bin);
+ }
+ }
+ TYPED_KEY("register", REG_KEY_TYPE, "an unsigned integer",
+ entry->data.reg.type, POSITIVE_INTEGER, u64, TOU8)
+ TYPED_KEY("register", KEY_NAME_CHAR, "an unsigned integer",
+ entry->data.reg.name, POSITIVE_INTEGER, u64, TOCHAR)
+ TYPED_KEY("register", REG_KEY_WIDTH, "an unsigned integer",
+ entry->data.reg.width, POSITIVE_INTEGER, u64, TOSIZE)
+ ADDITIONAL_KEY
}
if (entry->data.reg.contents == NULL) {
emsgu(_(READERR("register", "has missing " REG_KEY_CONTENTS " array")),
@@ -3807,8 +3820,8 @@ shada_read_next_item_hist_no_conv:
+ 1); // Separator character
entry->data.history_item.string = xmalloc(strsize);
memcpy(entry->data.history_item.string,
- unpacked.data.via.array.ptr[1].via.bin.ptr,
- unpacked.data.via.array.ptr[1].via.bin.size);
+ unpacked.data.via.array.ptr[1].via.bin.ptr,
+ unpacked.data.via.array.ptr[1].via.bin.size);
} else {
size_t len = unpacked.data.via.array.ptr[1].via.bin.size;
char *const converted = string_convert(
@@ -3928,17 +3941,14 @@ shada_read_next_item_hist_no_conv:
const size_t j = i;
{
for (size_t i = 0; i < unpacked.data.via.map.size; i++) {
- CHECK_KEY_IS_STR("buffer list entry");
+ CHECK_KEY_IS_STR("buffer list entry")
LONG_KEY("buffer list entry", KEY_LNUM,
- entry->data.buffer_list.buffers[j].pos.lnum)
- else
- INTEGER_KEY("buffer list entry", KEY_COL,
- entry->data.buffer_list.buffers[j].pos.col)
- else
- STRING_KEY("buffer list entry", KEY_FILE,
- entry->data.buffer_list.buffers[j].fname)
- else
- ADDITIONAL_KEY
+ entry->data.buffer_list.buffers[j].pos.lnum)
+ INTEGER_KEY("buffer list entry", KEY_COL,
+ entry->data.buffer_list.buffers[j].pos.col)
+ STRING_KEY("buffer list entry", KEY_FILE,
+ entry->data.buffer_list.buffers[j].fname)
+ ADDITIONAL_KEY
}
}
}
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 7d9257141a..420e8e2b70 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -1,9 +1,3 @@
-// VIM - Vi IMproved by Bram Moolenaar
-//
-// Do ":help uganda" in Vim to read copying and usage conditions.
-// Do ":help credits" in Vim to see a list of people who contributed.
-// See README.txt for an overview of the Vim source code.
-
// spell.c: code for spell checking
//
// The spell checking mechanism uses a tree (aka trie). Each node in the tree
@@ -285,7 +279,6 @@
// few bytes as possible, see offset2bytes())
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
diff --git a/src/nvim/state.c b/src/nvim/state.c
new file mode 100644
index 0000000000..b2f3f0bebe
--- /dev/null
+++ b/src/nvim/state.c
@@ -0,0 +1,62 @@
+#include <assert.h>
+
+#include "nvim/lib/kvec.h"
+
+#include "nvim/state.h"
+#include "nvim/vim.h"
+#include "nvim/getchar.h"
+#include "nvim/ui.h"
+#include "nvim/os/input.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "state.c.generated.h"
+#endif
+
+
+void state_enter(VimState *s)
+{
+ for (;;) {
+ int check_result = s->check ? s->check(s) : 1;
+
+ if (!check_result) {
+ break;
+ } else if (check_result == -1) {
+ continue;
+ }
+
+ int key;
+
+getkey:
+ if (char_avail() || using_script() || input_available()) {
+ // Don't block for events if there's a character already available for
+ // processing. Characters can come from mappings, scripts and other
+ // sources, so this scenario is very common.
+ key = safe_vgetc();
+ } else if (!queue_empty(loop.events)) {
+ // Event was made available after the last queue_process_events call
+ key = K_EVENT;
+ } else {
+ input_enable_events();
+ // Flush screen updates before blocking
+ ui_flush();
+ // Call `os_inchar` directly to block for events or user input without
+ // consuming anything from `input_buffer`(os/input.c) or calling the
+ // mapping engine. If an event was put into the queue, we send K_EVENT
+ // directly.
+ (void)os_inchar(NULL, 0, -1, 0);
+ input_disable_events();
+ key = !queue_empty(loop.events) ? K_EVENT : safe_vgetc();
+ }
+
+ if (key == K_EVENT) {
+ may_sync_undo();
+ }
+
+ int execute_result = s->execute(s, key);
+ if (!execute_result) {
+ break;
+ } else if (execute_result == -1) {
+ goto getkey;
+ }
+ }
+}
diff --git a/src/nvim/state.h b/src/nvim/state.h
new file mode 100644
index 0000000000..8027514148
--- /dev/null
+++ b/src/nvim/state.h
@@ -0,0 +1,20 @@
+#ifndef NVIM_STATE_H
+#define NVIM_STATE_H
+
+#include <stddef.h>
+
+typedef struct vim_state VimState;
+
+typedef int(*state_check_callback)(VimState *state);
+typedef int(*state_execute_callback)(VimState *state, int key);
+
+struct vim_state {
+ state_check_callback check;
+ state_execute_callback execute;
+};
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "state.h.generated.h"
+#endif
+
+#endif // NVIM_STATE_H
diff --git a/src/nvim/strings.c b/src/nvim/strings.c
index 9ffa5c6a76..00dcf3cf46 100644
--- a/src/nvim/strings.c
+++ b/src/nvim/strings.c
@@ -1,5 +1,3 @@
-
-#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index d0491ab42b..24422c71fb 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -1,18 +1,9 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* syntax.c: code for syntax highlighting
*/
#include <assert.h>
#include <ctype.h>
-#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index b0d1a17c89..d832924efd 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -1,17 +1,8 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* Code to handle tags and the tag stack
*/
#include <assert.h>
-#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 82b9599051..adf3f725a2 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -63,6 +63,7 @@
#include "nvim/map.h"
#include "nvim/misc1.h"
#include "nvim/move.h"
+#include "nvim/state.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_cmds.h"
#include "nvim/window.h"
@@ -73,6 +74,16 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/private/handle.h"
+typedef struct terminal_state {
+ VimState state;
+ Terminal *term;
+ int save_state; // saved value of State
+ int save_rd; // saved value of RedrawingDisabled
+ bool save_mapped_ctrl_c; // saved value of mapped_ctrl_c;
+ bool close;
+ bool got_bs; // if the last input was <C-\>
+} TerminalState;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "terminal.c.generated.h"
#endif
@@ -341,105 +352,114 @@ void terminal_resize(Terminal *term, uint16_t width, uint16_t height)
void terminal_enter(void)
{
buf_T *buf = curbuf;
- Terminal *term = buf->terminal;
- assert(term && "should only be called when curbuf has a terminal");
+ TerminalState state, *s = &state;
+ memset(s, 0, sizeof(TerminalState));
+ s->term = buf->terminal;
+ assert(s->term && "should only be called when curbuf has a terminal");
// Ensure the terminal is properly sized.
- terminal_resize(term, 0, 0);
+ terminal_resize(s->term, 0, 0);
checkpcmark();
setpcmark();
- int save_state = State;
- int save_rd = RedrawingDisabled;
+ s->save_state = State;
+ s->save_rd = RedrawingDisabled;
State = TERM_FOCUS;
RedrawingDisabled = false;
- bool save_mapped_ctrl_c = mapped_ctrl_c;
+ s->save_mapped_ctrl_c = mapped_ctrl_c;
mapped_ctrl_c = true;
// go to the bottom when the terminal is focused
- adjust_topline(term, buf, false);
+ adjust_topline(s->term, buf, false);
// erase the unfocused cursor
- invalidate_terminal(term, term->cursor.row, term->cursor.row + 1);
+ invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1);
showmode();
ui_busy_start();
redraw(false);
- int c;
- bool close = false;
-
- bool got_bs = false; // True if the last input was <C-\>
-
- while (curbuf->handle == term->buf_handle) {
- input_enable_events();
- c = safe_vgetc();
- input_disable_events();
-
- switch (c) {
- case K_LEFTMOUSE:
- case K_LEFTDRAG:
- case K_LEFTRELEASE:
- case K_MIDDLEMOUSE:
- case K_MIDDLEDRAG:
- case K_MIDDLERELEASE:
- case K_RIGHTMOUSE:
- case K_RIGHTDRAG:
- case K_RIGHTRELEASE:
- case K_MOUSEDOWN:
- case K_MOUSEUP:
- if (send_mouse_event(term, c)) {
- goto end;
- }
- break;
-
- case K_EVENT:
- // We cannot let an event free the terminal yet. It is still needed.
- term->refcount++;
- queue_process_events(loop.events);
- term->refcount--;
- if (term->buf_handle == 0) {
- close = true;
- goto end;
- }
- break;
- case Ctrl_N:
- if (got_bs) {
- goto end;
- }
- // FALLTHROUGH
-
- default:
- if (c == Ctrl_BSL && !got_bs) {
- got_bs = true;
- break;
- }
- if (term->closed) {
- close = true;
- goto end;
- }
-
- got_bs = false;
- terminal_send_key(term, c);
- }
- }
+ s->state.execute = terminal_execute;
+ state_enter(&s->state);
-end:
restart_edit = 0;
- State = save_state;
- RedrawingDisabled = save_rd;
+ State = s->save_state;
+ RedrawingDisabled = s->save_rd;
// draw the unfocused cursor
- invalidate_terminal(term, term->cursor.row, term->cursor.row + 1);
- mapped_ctrl_c = save_mapped_ctrl_c;
+ invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1);
+ mapped_ctrl_c = s->save_mapped_ctrl_c;
unshowmode(true);
- redraw(buf != curbuf);
+ redraw(curbuf->handle != s->term->buf_handle);
ui_busy_stop();
- if (close) {
- bool wipe = term->buf_handle != 0;
- term->opts.close_cb(term->opts.data);
+ if (s->close) {
+ bool wipe = s->term->buf_handle != 0;
+ s->term->opts.close_cb(s->term->opts.data);
if (wipe) {
do_cmdline_cmd("bwipeout!");
}
}
}
+static int terminal_execute(VimState *state, int key)
+{
+ TerminalState *s = (TerminalState *)state;
+
+ switch (key) {
+ case K_FOCUSGAINED: // Neovim has been given focus
+ apply_autocmds(EVENT_FOCUSGAINED, NULL, NULL, false, curbuf);
+ break;
+
+ case K_FOCUSLOST: // Neovim has lost focus
+ apply_autocmds(EVENT_FOCUSLOST, NULL, NULL, false, curbuf);
+ break;
+
+ case K_LEFTMOUSE:
+ case K_LEFTDRAG:
+ case K_LEFTRELEASE:
+ case K_MIDDLEMOUSE:
+ case K_MIDDLEDRAG:
+ case K_MIDDLERELEASE:
+ case K_RIGHTMOUSE:
+ case K_RIGHTDRAG:
+ case K_RIGHTRELEASE:
+ case K_MOUSEDOWN:
+ case K_MOUSEUP:
+ if (send_mouse_event(s->term, key)) {
+ return 0;
+ }
+ break;
+
+ case K_EVENT:
+ // We cannot let an event free the terminal yet. It is still needed.
+ s->term->refcount++;
+ queue_process_events(loop.events);
+ s->term->refcount--;
+ if (s->term->buf_handle == 0) {
+ s->close = true;
+ return 0;
+ }
+ break;
+
+ case Ctrl_N:
+ if (s->got_bs) {
+ return 0;
+ }
+ // FALLTHROUGH
+
+ default:
+ if (key == Ctrl_BSL && !s->got_bs) {
+ s->got_bs = true;
+ break;
+ }
+ if (s->term->closed) {
+ s->close = true;
+ return 0;
+ }
+
+ s->got_bs = false;
+ terminal_send_key(s->term, key);
+ }
+
+ return curbuf->handle == s->term->buf_handle;
+}
+
void terminal_destroy(Terminal *term)
{
buf_T *buf = handle_get_buffer(term->buf_handle);
diff --git a/src/nvim/terminal.h b/src/nvim/terminal.h
index 6e0b062fbd..25e609fb68 100644
--- a/src/nvim/terminal.h
+++ b/src/nvim/terminal.h
@@ -18,15 +18,6 @@ typedef struct {
terminal_close_cb close_cb;
} TerminalOptions;
-#define TERMINAL_OPTIONS_INIT ((TerminalOptions) { \
- .data = NULL, \
- .width = 80, \
- .height = 24, \
- .write_cb = NULL, \
- .resize_cb = NULL, \
- .close_cb = NULL \
- })
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "terminal.h.generated.h"
#endif
diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim
index 4cb500292d..afee9d882c 100644
--- a/src/nvim/testdir/test49.vim
+++ b/src/nvim/testdir/test49.vim
@@ -456,7 +456,7 @@ function! ExtraVim(...)
" messing up the user's viminfo file.
let redirect = a:0 ?
\ " -c 'au VimLeave * redir END' -c 'redir\\! >" . a:1 . "'" : ""
- exec "!echo '" . debug_quits . "q' | ../../../build/bin/nvim -u NONE -N -Xes" . redirect .
+ exec "!echo '" . debug_quits . "q' | ../../../build/bin/nvim -u NONE -N -es" . redirect .
\ " -c 'debuggreedy|set viminfo+=nviminfo'" .
\ " -c 'let ExtraVimBegin = " . extra_begin . "'" .
\ " -c 'let ExtraVimResult = \"" . resultfile . "\"'" . breakpoints .
diff --git a/src/nvim/testdir/test53.in b/src/nvim/testdir/test53.in
index 8ca9c9ed29..7c35b2e853 100644
--- a/src/nvim/testdir/test53.in
+++ b/src/nvim/testdir/test53.in
@@ -23,6 +23,7 @@ jfXdit
0fXdit
fXdat
0fXdat
+dit
:"
:put =matchstr(\"abcd\", \".\", 0, 2) " b
:put =matchstr(\"abcd\", \"..\", 0, 2) " bc
@@ -97,6 +98,9 @@ voo "nah" sdf " asdf" sdf " sdf" sd
-<b>asdX<i>a<i />sdf</i>asdf</b>-
-<b>asdf<i>Xasdf</i>asdf</b>-
-<b>asdX<i>as<b />df</i>asdf</b>-
+-<b>
+innertext object
+</b>
</begin>
SEARCH:
foobar
diff --git a/src/nvim/testdir/test53.ok b/src/nvim/testdir/test53.ok
index 0c0b9ded16..05206972a4 100644
--- a/src/nvim/testdir/test53.ok
+++ b/src/nvim/testdir/test53.ok
@@ -11,6 +11,7 @@ voo "zzzzzzzzzzzzzzzzzzzzzzzzzzzzsd
-<b></b>-
-<b>asdfasdf</b>-
--
+-<b></b>
</begin>
b
bc
diff --git a/src/nvim/testdir/unix.vim b/src/nvim/testdir/unix.vim
index aa1f6a92bc..a7daacf8cf 100644
--- a/src/nvim/testdir/unix.vim
+++ b/src/nvim/testdir/unix.vim
@@ -4,3 +4,6 @@ set shell=sh
" Don't depend on system locale, always use utf-8
set encoding=utf-8
+
+" Use safer defaults for various directories
+set backupdir=. directory=. undodir=. viewdir=.
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 654b857301..b41e4d2fba 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -10,6 +10,8 @@
#include "nvim/event/rstream.h"
#define PASTETOGGLE_KEY "<Paste>"
+#define FOCUSGAINED_KEY "<FocusGained>"
+#define FOCUSLOST_KEY "<FocusLost>"
#define KEY_BUFFER_SIZE 0xfff
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -252,6 +254,32 @@ static void timer_cb(TimeWatcher *watcher, void *data)
flush_input(data, true);
}
+/// Handle focus events.
+///
+/// If the upcoming sequence of bytes in the input stream matches either the
+/// escape code for focus gained `<ESC>[I` or focus lost `<ESC>[O` then consume
+/// that sequence and push the appropriate event into the input queue
+///
+/// @param input the input stream
+/// @return true iff handle_focus_event consumed some input
+static bool handle_focus_event(TermInput *input)
+{
+ if (rbuffer_size(input->read_stream.buffer) > 2
+ && (!rbuffer_cmp(input->read_stream.buffer, "\x1b[I", 3)
+ || !rbuffer_cmp(input->read_stream.buffer, "\x1b[O", 3))) {
+ // Advance past the sequence
+ bool focus_gained = *rbuffer_get(input->read_stream.buffer, 2) == 'I';
+ rbuffer_consumed(input->read_stream.buffer, 3);
+ if (focus_gained) {
+ enqueue_input(input, FOCUSGAINED_KEY, sizeof(FOCUSGAINED_KEY) - 1);
+ } else {
+ enqueue_input(input, FOCUSLOST_KEY, sizeof(FOCUSLOST_KEY) - 1);
+ }
+ return true;
+ }
+ return false;
+}
+
static bool handle_bracketed_paste(TermInput *input)
{
if (rbuffer_size(input->read_stream.buffer) > 5 &&
@@ -314,7 +342,9 @@ static void read_cb(Stream *stream, RBuffer *buf, size_t c, void *data,
}
do {
- if (handle_bracketed_paste(input) || handle_forced_escape(input)) {
+ if (handle_focus_event(input)
+ || handle_bracketed_paste(input)
+ || handle_forced_escape(input)) {
continue;
}
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index c87f6d331b..7f7d138358 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -43,7 +43,11 @@ typedef struct {
TermInput input;
uv_loop_t write_loop;
unibi_term *ut;
- uv_tty_t output_handle;
+ union {
+ uv_tty_t tty;
+ uv_pipe_t pipe;
+ } output_handle;
+ bool out_isatty;
SignalWatcher winch_handle, cont_handle;
bool cont_received;
// Event scheduled by the ui bridge. Since the main thread suspends until
@@ -62,6 +66,7 @@ typedef struct {
int enable_bracketed_paste, disable_bracketed_paste;
int enter_insert_mode, enter_replace_mode, exit_insert_mode;
int set_rgb_foreground, set_rgb_background;
+ int enable_focus_reporting, disable_focus_reporting;
} unibi_ext;
} TUIData;
@@ -116,8 +121,10 @@ static void terminfo_start(UI *ui)
data->unibi_ext.enter_insert_mode = -1;
data->unibi_ext.enter_replace_mode = -1;
data->unibi_ext.exit_insert_mode = -1;
- // write output to stderr if stdout is not a tty
- data->out_fd = os_isatty(1) ? 1 : (os_isatty(2) ? 2 : 1);
+ data->unibi_ext.enable_focus_reporting = -1;
+ data->unibi_ext.disable_focus_reporting = -1;
+ data->out_fd = 1;
+ data->out_isatty = os_isatty(data->out_fd);
// setup unibilium
data->ut = unibi_from_env();
if (!data->ut) {
@@ -131,9 +138,16 @@ static void terminfo_start(UI *ui)
unibi_out(ui, unibi_clear_screen);
// Enable bracketed paste
unibi_out(ui, data->unibi_ext.enable_bracketed_paste);
+ // Enable focus reporting
+ unibi_out(ui, data->unibi_ext.enable_focus_reporting);
uv_loop_init(&data->write_loop);
- uv_tty_init(&data->write_loop, &data->output_handle, data->out_fd, 0);
- uv_tty_set_mode(&data->output_handle, UV_TTY_MODE_RAW);
+ if (data->out_isatty) {
+ uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0);
+ uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_RAW);
+ } else {
+ uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0);
+ uv_pipe_open(&data->output_handle.pipe, data->out_fd);
+ }
}
static void terminfo_stop(UI *ui)
@@ -148,6 +162,8 @@ static void terminfo_stop(UI *ui)
unibi_out(ui, unibi_exit_ca_mode);
// Disable bracketed paste
unibi_out(ui, data->unibi_ext.disable_bracketed_paste);
+ // Disable focus reporting
+ unibi_out(ui, data->unibi_ext.disable_focus_reporting);
flush_buf(ui);
uv_tty_reset_mode();
uv_close((uv_handle_t *)&data->output_handle, NULL);
@@ -210,6 +226,7 @@ static void tui_main(UIBridgeData *bridge, UI *ui)
loop_poll_events(&tui_loop, -1);
}
+ ui_bridge_stopped(bridge);
term_input_destroy(&data->input);
signal_watcher_stop(&data->cont_handle);
signal_watcher_close(&data->cont_handle, NULL);
@@ -677,7 +694,8 @@ static void update_size(UI *ui)
}
// 2 - try from a system call(ioctl/TIOCGWINSZ on unix)
- if (!uv_tty_get_winsize(&data->output_handle, &width, &height)) {
+ if (data->out_isatty &&
+ !uv_tty_get_winsize(&data->output_handle.tty, &width, &height)) {
goto end;
}
@@ -796,6 +814,11 @@ static void fix_terminfo(TUIData *data)
data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, NULL,
"\x1b[?2004l");
+ data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, NULL,
+ "\x1b[?1004h");
+ data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, NULL,
+ "\x1b[?1004l");
+
#define XTERM_SETAF \
"\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m"
#define XTERM_SETAB \
diff --git a/src/nvim/types.h b/src/nvim/types.h
index afd684925a..bfe8be2091 100644
--- a/src/nvim/types.h
+++ b/src/nvim/types.h
@@ -1,10 +1,3 @@
-/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- */
-
#ifndef NVIM_TYPES_H
#define NVIM_TYPES_H
diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c
index 836339a887..359fffe3bf 100644
--- a/src/nvim/ui_bridge.c
+++ b/src/nvim/ui_bridge.c
@@ -74,6 +74,13 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler)
return &rv->bridge;
}
+void ui_bridge_stopped(UIBridgeData *bridge)
+{
+ uv_mutex_lock(&bridge->mutex);
+ bridge->stopped = true;
+ uv_mutex_unlock(&bridge->mutex);
+}
+
static void ui_thread_run(void *data)
{
UIBridgeData *bridge = data;
@@ -82,8 +89,18 @@ static void ui_thread_run(void *data)
static void ui_bridge_stop(UI *b)
{
- UI_CALL(b, stop, 1, b);
UIBridgeData *bridge = (UIBridgeData *)b;
+ bool stopped = bridge->stopped = false;
+ UI_CALL(b, stop, 1, b);
+ for (;;) {
+ uv_mutex_lock(&bridge->mutex);
+ stopped = bridge->stopped;
+ uv_mutex_unlock(&bridge->mutex);
+ if (stopped) {
+ break;
+ }
+ loop_poll_events(&loop, 10);
+ }
uv_thread_join(&bridge->ui_thread);
uv_mutex_destroy(&bridge->mutex);
uv_cond_destroy(&bridge->cond);
diff --git a/src/nvim/ui_bridge.h b/src/nvim/ui_bridge.h
index 76e9e27989..31b9a69216 100644
--- a/src/nvim/ui_bridge.h
+++ b/src/nvim/ui_bridge.h
@@ -22,6 +22,10 @@ struct ui_bridge_data {
// the call returns. This flag is used as a condition for the main
// thread to continue.
bool ready;
+ // When a stop request is sent from the main thread, it must wait until the UI
+ // thread finishes handling all events. This flag is set by the UI thread as a
+ // signal that it will no longer send messages to the main thread.
+ bool stopped;
};
#define CONTINUE(b) \
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 2b0ffefa7e..69ac18ad54 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -1,12 +1,4 @@
/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
* undo.c: multi level undo facility
*
* The saved lines are stored in a list of lists (one for each buffer):
@@ -83,7 +75,6 @@
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
-#include <errno.h>
#include <stdbool.h>
#include <string.h>
@@ -284,32 +275,32 @@ int u_savedel(linenr_T lnum, long nlines)
nlines == curbuf->b_ml.ml_line_count ? 2 : lnum, FALSE);
}
-/*
- * Return TRUE when undo is allowed. Otherwise give an error message and
- * return FALSE.
- */
-int undo_allowed(void)
+/// Return true when undo is allowed. Otherwise print an error message and
+/// return false.
+///
+/// @return true if undo is allowed.
+bool undo_allowed(void)
{
/* Don't allow changes when 'modifiable' is off. */
if (!MODIFIABLE(curbuf)) {
EMSG(_(e_modifiable));
- return FALSE;
+ return false;
}
// In the sandbox it's not allowed to change the text.
if (sandbox != 0) {
EMSG(_(e_sandbox));
- return FALSE;
+ return false;
}
/* Don't allow changes in the buffer while editing the cmdline. The
* caller of getcmdline() may get confused. */
if (textlock != 0) {
EMSG(_(e_secure));
- return FALSE;
+ return false;
}
- return TRUE;
+ return true;
}
/*
@@ -638,64 +629,89 @@ void u_compute_hash(char_u *hash)
sha256_finish(&ctx, hash);
}
-/*
- * Return an allocated string of the full path of the target undofile.
- * When "reading" is TRUE find the file to read, go over all directories in
- * 'undodir'.
- * When "reading" is FALSE use the first name where the directory exists.
- * Returns NULL when there is no place to write or no file to read.
- */
-char_u *u_get_undo_file_name(char_u *buf_ffname, int reading)
+/// Return an allocated string of the full path of the target undofile.
+///
+/// @param[in] buf_ffname Full file name for which undo file location should
+/// be found.
+/// @param[in] reading If true, find the file to read by traversing all of the
+/// directories in &undodir. If false use the first
+/// existing directory. If none of the directories in
+/// &undodir option exist then last directory in the list
+/// will be automatically created.
+///
+/// @return [allocated] File name to read from/write to or NULL.
+char *u_get_undo_file_name(const char *const buf_ffname, const bool reading)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
- char_u *dirp;
- char_u dir_name[IOSIZE + 1];
- char_u *munged_name = NULL;
- char_u *undo_file_name = NULL;
- char_u *p;
- char_u *ffname = buf_ffname;
+ char *dirp;
+ char dir_name[MAXPATHL + 1];
+ char *munged_name = NULL;
+ char *undo_file_name = NULL;
+ const char *ffname = buf_ffname;
#ifdef HAVE_READLINK
- char_u fname_buf[MAXPATHL];
+ char fname_buf[MAXPATHL];
#endif
- if (ffname == NULL)
+ if (ffname == NULL) {
return NULL;
+ }
#ifdef HAVE_READLINK
- /* Expand symlink in the file name, so that we put the undo file with the
- * actual file instead of with the symlink. */
- if (resolve_symlink(ffname, fname_buf) == OK)
+ // Expand symlink in the file name, so that we put the undo file with the
+ // actual file instead of with the symlink.
+ if (resolve_symlink((const char_u *)ffname, (char_u *)fname_buf) == OK) {
ffname = fname_buf;
+ }
#endif
- /* Loop over 'undodir'. When reading find the first file that exists.
- * When not reading use the first directory that exists or ".". */
- dirp = p_udir;
+ // Loop over 'undodir'. When reading find the first file that exists.
+ // When not reading use the first directory that exists or ".".
+ dirp = (char *) p_udir;
while (*dirp != NUL) {
- size_t dir_len = copy_option_part(&dirp, dir_name, IOSIZE, ",");
+ size_t dir_len = copy_option_part((char_u **)&dirp, (char_u *)dir_name,
+ MAXPATHL, ",");
if (dir_len == 1 && dir_name[0] == '.') {
- /* Use same directory as the ffname,
- * "dir/name" -> "dir/.name.un~" */
- undo_file_name = vim_strnsave(ffname, STRLEN(ffname) + 5);
- p = path_tail(undo_file_name);
- memmove(p + 1, p, STRLEN(p) + 1);
- *p = '.';
- STRCAT(p, ".un~");
+ // Use same directory as the ffname,
+ // "dir/name" -> "dir/.name.un~"
+ const size_t ffname_len = strlen(ffname);
+ undo_file_name = xmalloc(ffname_len + 6);
+ memmove(undo_file_name, ffname, ffname_len + 1);
+ char *const tail = (char *) path_tail((char_u *) undo_file_name);
+ const size_t tail_len = strlen(tail);
+ memmove(tail + 1, tail, tail_len + 1);
+ *tail = '.';
+ memmove(tail + tail_len + 1, ".un~", sizeof(".un~"));
} else {
dir_name[dir_len] = NUL;
- if (os_isdir(dir_name)) {
+ bool has_directory = os_isdir((char_u *)dir_name);
+ if (!has_directory && *dirp == NUL && !reading) {
+ // Last directory in the list does not exist, create it.
+ int ret;
+ char *failed_dir;
+ if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir)) != 0) {
+ EMSG3(_("E926: Unable to create directory \"%s\" for undo file: %s"),
+ failed_dir, os_strerror(ret));
+ xfree(failed_dir);
+ } else {
+ has_directory = true;
+ }
+ }
+ if (has_directory) {
if (munged_name == NULL) {
- munged_name = vim_strsave(ffname);
- for (p = munged_name; *p != NUL; mb_ptr_adv(p))
- if (vim_ispathsep(*p))
+ munged_name = xstrdup(ffname);
+ for (char *p = munged_name; *p != NUL; mb_ptr_adv(p)) {
+ if (vim_ispathsep(*p)) {
*p = '%';
+ }
+ }
}
- undo_file_name = (char_u *)concat_fnames((char *)dir_name, (char *)munged_name, TRUE);
+ undo_file_name = concat_fnames(dir_name, munged_name, true);
}
}
// When reading check if the file exists.
- if (undo_file_name != NULL &&
- (!reading || os_file_exists(undo_file_name))) {
+ if (undo_file_name != NULL
+ && (!reading || os_file_exists((char_u *)undo_file_name))) {
break;
}
xfree(undo_file_name);
@@ -706,7 +722,13 @@ char_u *u_get_undo_file_name(char_u *buf_ffname, int reading)
return undo_file_name;
}
-static void corruption_error(char *mesg, char_u *file_name)
+/// Display an error for corrupted undo file
+///
+/// @param[in] mesg Identifier of the corruption kind.
+/// @param[in] file_name File in which error occurred.
+static void corruption_error(const char *const mesg,
+ const char *const file_name)
+ FUNC_ATTR_NONNULL_ALL
{
EMSG3(_("E825: Corrupted undo file (%s): %s"), mesg, file_name);
}
@@ -725,7 +747,11 @@ static void u_free_uhp(u_header_T *uhp)
xfree(uhp);
}
-/// Writes the header.
+/// Writes the undofile header.
+///
+/// @param bi The buffer information
+/// @param hash The hash of the buffer contents
+//
/// @returns false in case of an error.
static bool serialize_header(bufinfo_T *bi, char_u *hash)
FUNC_ATTR_NONNULL_ALL
@@ -779,6 +805,12 @@ static bool serialize_header(bufinfo_T *bi, char_u *hash)
return true;
}
+/// Writes an undo header.
+///
+/// @param bi The buffer information
+/// @param uhp The undo header to write
+//
+/// @returns false in case of an error.
static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp)
{
if (!undo_write_bytes(bi, (uintmax_t)UF_HEADER_MAGIC, 2)) {
@@ -821,7 +853,8 @@ static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp)
return true;
}
-static u_header_T *unserialize_uhp(bufinfo_T *bi, char_u *file_name)
+static u_header_T *unserialize_uhp(bufinfo_T *bi,
+ const char *file_name)
{
u_header_T *uhp = xmalloc(sizeof(u_header_T));
memset(uhp, 0, sizeof(u_header_T));
@@ -898,6 +931,9 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi, char_u *file_name)
/// Serializes "uep".
///
+/// @param bi The buffer information
+/// @param uep The undo entry to write
+//
/// @returns false in case of an error.
static bool serialize_uep(bufinfo_T *bi, u_entry_T *uep)
{
@@ -918,7 +954,8 @@ static bool serialize_uep(bufinfo_T *bi, u_entry_T *uep)
return true;
}
-static u_entry_T *unserialize_uep(bufinfo_T * bi, bool *error, char_u *file_name)
+static u_entry_T *unserialize_uep(bufinfo_T * bi, bool *error,
+ const char *file_name)
{
u_entry_T *uep = xmalloc(sizeof(u_entry_T));
memset(uep, 0, sizeof(u_entry_T));
@@ -1000,19 +1037,20 @@ static void unserialize_visualinfo(bufinfo_T *bi, visualinfo_T *info)
info->vi_curswant = undo_read_4c(bi);
}
-/*
- * Write the undo tree in an undo file.
- * When "name" is not NULL, use it as the name of the undo file.
- * Otherwise use buf->b_ffname to generate the undo file name.
- * "buf" must never be null, buf->b_ffname is used to obtain the original file
- * permissions.
- * "forceit" is TRUE for ":wundo!", FALSE otherwise.
- * "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text.
- */
-void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash)
+/// Write the undo tree in an undo file.
+///
+/// @param[in] name Name of the undo file or NULL if this function needs to
+/// generate the undo file name based on buf->b_ffname.
+/// @param[in] forceit True for `:wundo!`, false otherwise.
+/// @param[in] buf Buffer for which undo file is written.
+/// @param[in] hash Hash value of the buffer text. Must have #UNDO_HASH_SIZE
+/// size.
+void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
+ char_u *const hash)
+ FUNC_ATTR_NONNULL_ARG(3, 4)
{
u_header_T *uhp;
- char_u *file_name;
+ char *file_name;
int mark;
#ifdef U_DEBUG
int headers_written = 0;
@@ -1024,7 +1062,7 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash)
bufinfo_T bi;
if (name == NULL) {
- file_name = u_get_undo_file_name(buf->b_ffname, FALSE);
+ file_name = u_get_undo_file_name((char *) buf->b_ffname, false);
if (file_name == NULL) {
if (p_verbose > 0) {
verbose_enter();
@@ -1033,8 +1071,9 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash)
}
return;
}
- } else
- file_name = name;
+ } else {
+ file_name = (char *) name;
+ }
/*
* Decide about the permission to use for the undo file. If the buffer
@@ -1054,10 +1093,10 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash)
/* If the undo file already exists, verify that it actually is an undo
* file, and delete it. */
- if (os_file_exists(file_name)) {
+ if (os_file_exists((char_u *)file_name)) {
if (name == NULL || !forceit) {
/* Check we can read it and it's an undo file. */
- fd = os_open((char *)file_name, O_RDONLY, 0);
+ fd = os_open(file_name, O_RDONLY, 0);
if (fd < 0) {
if (name != NULL || p_verbose > 0) {
if (name == NULL)
@@ -1086,7 +1125,7 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash)
}
}
}
- os_remove((char *)file_name);
+ os_remove(file_name);
}
/* If there is no undo information at all, quit here after deleting any
@@ -1097,13 +1136,12 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash)
goto theend;
}
- fd = os_open((char *)file_name,
- O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm);
+ fd = os_open(file_name, O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm);
if (fd < 0) {
EMSG2(_(e_not_open), file_name);
goto theend;
}
- (void)os_setperm(file_name, perm);
+ (void)os_setperm((char_u *)file_name, perm);
if (p_verbose > 0) {
verbose_enter();
smsg(_("Writing undo file: %s"), file_name);
@@ -1125,10 +1163,10 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash)
FileInfo file_info_new;
if (buf->b_ffname != NULL
&& os_fileinfo((char *)buf->b_ffname, &file_info_old)
- && os_fileinfo((char *)file_name, &file_info_new)
+ && os_fileinfo(file_name, &file_info_new)
&& file_info_old.stat.st_gid != file_info_new.stat.st_gid
&& os_fchown(fd, (uv_uid_t)-1, (uv_gid_t)file_info_old.stat.st_gid)) {
- os_setperm(file_name, (perm & 0707) | ((perm & 07) << 3));
+ os_setperm((char_u *)file_name, (perm & 0707) | ((perm & 07) << 3));
}
# ifdef HAVE_SELINUX
if (buf->b_ffname != NULL)
@@ -1140,7 +1178,7 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash)
if (fp == NULL) {
EMSG2(_(e_not_open), file_name);
close(fd);
- os_remove((char *)file_name);
+ os_remove(file_name);
goto theend;
}
@@ -1209,7 +1247,7 @@ write_error:
/* For systems that support ACL: get the ACL from the original file. */
acl = mch_get_acl(buf->b_ffname);
- mch_set_acl(file_name, acl);
+ mch_set_acl((char_u *)file_name, acl);
mch_free_acl(acl);
}
#endif
@@ -1224,15 +1262,15 @@ theend:
/// a bit more verbose.
/// Otherwise use curbuf->b_ffname to generate the undo file name.
/// "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text.
-void u_read_undo(char_u *name, char_u *hash, char_u *orig_name)
+void u_read_undo(char *name, char_u *hash, char_u *orig_name)
FUNC_ATTR_NONNULL_ARG(2)
{
u_header_T **uhp_table = NULL;
char_u *line_ptr = NULL;
- char_u *file_name;
+ char *file_name;
if (name == NULL) {
- file_name = u_get_undo_file_name(curbuf->b_ffname, TRUE);
+ file_name = u_get_undo_file_name((char *) curbuf->b_ffname, true);
if (file_name == NULL) {
return;
}
@@ -1256,7 +1294,7 @@ void u_read_undo(char_u *name, char_u *hash, char_u *orig_name)
}
#endif
} else {
- file_name = name;
+ file_name = (char *) name;
}
if (p_verbose > 0) {
@@ -1265,7 +1303,7 @@ void u_read_undo(char_u *name, char_u *hash, char_u *orig_name)
verbose_leave();
}
- FILE *fp = mch_fopen((char *)file_name, "r");
+ FILE *fp = mch_fopen(file_name, "r");
if (fp == NULL) {
if (name != NULL || p_verbose > 0) {
EMSG2(_("E822: Cannot open undo file for reading: %s"), file_name);
@@ -1520,6 +1558,10 @@ theend:
/// Writes a sequence of bytes to the undo file.
///
+/// @param bi The buffer info
+/// @param ptr The byte buffer to write
+/// @param len The number of bytes to write
+///
/// @returns false in case of an error.
static bool undo_write(bufinfo_T *bi, uint8_t *ptr, size_t len)
FUNC_ATTR_NONNULL_ARG(1)
@@ -1531,6 +1573,10 @@ static bool undo_write(bufinfo_T *bi, uint8_t *ptr, size_t len)
///
/// Must match with undo_read_?c() functions.
///
+/// @param bi The buffer info
+/// @param nr The number to write
+/// @param len The number of bytes to use when writing the number.
+///
/// @returns false in case of an error.
static bool undo_write_bytes(bufinfo_T *bi, uintmax_t nr, size_t len)
{
@@ -1575,6 +1621,10 @@ static time_t undo_read_time(bufinfo_T *bi)
/// Reads "buffer[size]" from the undo file.
///
+/// @param bi The buffer info
+/// @param buffer Character buffer to read data into
+/// @param size The size of the character buffer
+///
/// @returns false in case of an error.
static bool undo_read(bufinfo_T *bi, uint8_t *buffer, size_t size)
FUNC_ATTR_NONNULL_ARG(1)
@@ -2805,19 +2855,26 @@ static char_u *u_save_line(linenr_T lnum)
return vim_strsave(ml_get(lnum));
}
-/*
- * Check if the 'modified' flag is set, or 'ff' has changed (only need to
- * check the first character, because it can only be "dos", "unix" or "mac").
- * "nofile" and "scratch" type buffers are considered to always be unchanged.
- */
-int bufIsChanged(buf_T *buf)
+/// Check if the 'modified' flag is set, or 'ff' has changed (only need to
+/// check the first character, because it can only be "dos", "unix" or "mac").
+/// "nofile" and "scratch" type buffers are considered to always be unchanged.
+///
+/// @param buf The buffer to check
+///
+/// @return true if the buffer has changed
+bool bufIsChanged(buf_T *buf)
{
return
!bt_dontwrite(buf) &&
(buf->b_changed || file_ff_differs(buf, true));
}
-int curbufIsChanged(void)
+/// Check if the 'modified' flag is set, or 'ff' has changed (only need to
+/// check the first character, because it can only be "dos", "unix" or "mac").
+/// "nofile" and "scratch" type buffers are considered to always be unchanged.
+///
+/// @return true if the current buffer has changed
+bool curbufIsChanged(void)
{
return
!bt_dontwrite(curbuf) &&
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 961c017bd5..e0f378b156 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -25,18 +25,21 @@
#define STR_(x) #x
#define STR(x) STR_(x)
-// for the startup-screen ( ":intro" command )
-#define NVIM_VERSION_MEDIUM STR(NVIM_VERSION_MAJOR) "." STR(NVIM_VERSION_MINOR)
-
-// for the ":version" command and "nvim --version"
-#define NVIM_VERSION_LONG "NVIM " NVIM_VERSION_MEDIUM "." STR(NVIM_VERSION_PATCH) NVIM_VERSION_PRERELEASE NVIM_VERSION_BUILD
+// for ":version", ":intro", and "nvim --version"
+#ifndef NVIM_VERSION_MEDIUM
+#define NVIM_VERSION_MEDIUM STR(NVIM_VERSION_MAJOR) "." STR(NVIM_VERSION_MINOR)\
+ "." STR(NVIM_VERSION_PATCH) NVIM_VERSION_PRERELEASE
+#endif
+#define NVIM_VERSION_LONG "NVIM " NVIM_VERSION_MEDIUM
char *Version = VIM_VERSION_SHORT;
char *longVersion = NVIM_VERSION_LONG;
-char *longVersionWithDate = NVIM_VERSION_LONG " (compiled " __DATE__ " " __TIME__ ")";
-char *mediumVersion = NVIM_VERSION_MEDIUM;
+char *longVersionWithDate = NVIM_VERSION_LONG \
+ " (compiled " __DATE__ " " __TIME__ ")";
+#ifdef NVIM_VERSION_COMMIT
char *version_commit = "Commit: " NVIM_VERSION_COMMIT;
+#endif
char *version_buildtype = "Build type: " NVIM_VERSION_BUILD_TYPE;
char *version_cflags = "Compilation: " NVIM_VERSION_CFLAGS;
@@ -71,20 +74,92 @@ static char *features[] = {
// clang-format off
static int included_patches[] = {
- // 850,
- // 849,
+ // 922,
+ // 921 NA
+ // 920 NA
+ // 919 NA
+ // 918 NA
+ // 917 NA
+ 916,
+ // 915,
+ // 914,
+ // 913 NA
+ // 912,
+ // 911 NA
+ // 910 NA
+ // 909,
+ // 908 NA
+ // 907 NA
+ // 906 NA
+ // 905,
+ // 904,
+ // 903,
+ // 902 NA
+ // 901,
+ // 900 NA
+ // 899 NA
+ 898,
+ // 897,
+ // 896,
+ // 895,
+ // 894 NA
+ // 893,
+ // 892,
+ // 891,
+ // 890 NA
+ // 889,
+ // 888,
+ // 887,
+ // 886 NA
+ // 885,
+ // 884 NA
+ // 883,
+ // 882,
+ // 881,
+ // 880 NA
+ // 879,
+ // 878,
+ // 877,
+ // 876 NA
+ // 875 NA
+ // 874 NA
+ // 873,
+ // 872 NA
+ // 871,
+ // 870,
+ // 869 NA
+ // 868,
+ // 867 NA
+ // 866,
+ // 865,
+ // 864,
+ // 863,
+ // 862 NA
+ // 861 NA
+ // 860,
+ // 859,
+ // 858,
+ // 857,
+ // 856,
+ // 855 NA
+ // 854,
+ // 853,
+ // 852 NA
+ // 851 NA
+ // 850 NA
+ 849,
// 848,
// 847,
- // 846,
+ // 846 NA
// 845,
// 844,
// 843,
// 842,
// 841,
- // 840,
+ // 840 NA
// 839,
// 838,
- // 837,
+ // 837 NA
836,
// 835,
// 834,
@@ -92,7 +167,7 @@ static int included_patches[] = {
// 832,
// 831,
// 830,
- // 829,
+ // 829 NA
// 828,
// 827,
826,
@@ -112,31 +187,31 @@ static int included_patches[] = {
// 812,
// 811,
// 810,
- // 809,
+ 809,
// 808,
// 807,
// 806,
// 805,
// 804,
// 803,
- // 802,
+ 802,
// 801,
// 800,
799,
// 798,
// 797,
// 796 NA
- // 795,
+ 795,
// 794 NA
793,
// 792,
- // 791,
- // 790,
- // 789,
+ 791,
+ 790,
+ 789,
// 788 NA
- // 787,
- // 786,
- // 785,
+ 787,
+ 786,
+ 785,
784,
// 783 NA
// 782,
@@ -149,35 +224,35 @@ static int included_patches[] = {
775,
774,
773,
- // 772,
+ // 772 NA
// 771,
- // 770,
+ // 770 NA
// 769,
// 768,
// 767,
- // 766,
+ // 766 NA
// 765,
// 764,
- // 763,
- // 762,
- // 761,
+ // 763 NA
+ // 762 NA
+ // 761 NA
// 760,
- // 759,
+ // 759 NA
// 758,
- // 757,
- // 756,
+ // 757 NA
+ // 756 NA
// 755,
// 754,
// 753,
// 752,
- // 751,
- // 750,
+ // 751 NA
+ // 750 NA
// 749,
// 748,
// 747,
// 746,
// 745,
- // 744,
+ // 744 NA
// 743,
// 742,
// 741,
@@ -235,7 +310,7 @@ static int included_patches[] = {
// 689,
// 688,
// 687 NA
- // 686,
+ 686,
// 685,
// 684,
// 683 NA
@@ -263,34 +338,34 @@ static int included_patches[] = {
// 661,
660,
659,
- // 658,
+ 658,
// 657 NA
// 656,
- // 655,
+ 655,
// 654,
653,
- // 652,
- // 651,
+ // 652 NA
+ 651,
// 650 NA
- // 649,
+ 649,
// 648 NA
// 647 NA
646,
- // 645,
+ 645,
// 644 NA
// 643,
// 642,
// 641,
- // 640,
+ 640,
// 639,
// 638 NA
637,
636,
- // 635,
+ 635,
// 634,
633,
// 632 NA
- // 631,
+ 631,
630,
629,
// 628,
@@ -298,13 +373,13 @@ static int included_patches[] = {
// 626 NA
// 625 NA
// 624,
- // 623,
+ 623,
// 622 NA
// 621 NA
// 620,
// 619 NA
// 618 NA
- // 617,
+ 617,
// 616,
615,
// 614,
@@ -1024,7 +1099,9 @@ void list_version(void)
// When adding features here, don't forget to update the list of
// internal variables in eval.c!
MSG(longVersionWithDate);
+#ifdef NVIM_VERSION_COMMIT
MSG(version_commit);
+#endif
MSG(version_buildtype);
MSG(version_cflags);
@@ -1067,31 +1144,6 @@ void list_version(void)
version_msg(SYS_VIMRC_FILE);
version_msg("\"\n");
#endif // ifdef SYS_VIMRC_FILE
-#ifdef USR_VIMRC_FILE
- version_msg(_(" user vimrc file: \""));
- version_msg(USR_VIMRC_FILE);
- version_msg("\"\n");
-#endif // ifdef USR_VIMRC_FILE
-#ifdef USR_VIMRC_FILE2
- version_msg(_(" 2nd user vimrc file: \""));
- version_msg(USR_VIMRC_FILE2);
- version_msg("\"\n");
-#endif // ifdef USR_VIMRC_FILE2
-#ifdef USR_VIMRC_FILE3
- version_msg(_(" 3rd user vimrc file: \""));
- version_msg(USR_VIMRC_FILE3);
- version_msg("\"\n");
-#endif // ifdef USR_VIMRC_FILE3
-#ifdef USR_EXRC_FILE
- version_msg(_(" user exrc file: \""));
- version_msg(USR_EXRC_FILE);
- version_msg("\"\n");
-#endif // ifdef USR_EXRC_FILE
-#ifdef USR_EXRC_FILE2
- version_msg(_(" 2nd user exrc file: \""));
- version_msg(USR_EXRC_FILE2);
- version_msg("\"\n");
-#endif // ifdef USR_EXRC_FILE2
#ifdef HAVE_PATHDEF
if (*default_vim_dir != NUL) {
diff --git a/src/nvim/version.h b/src/nvim/version.h
index c1881250f1..1de809e539 100644
--- a/src/nvim/version.h
+++ b/src/nvim/version.h
@@ -3,7 +3,6 @@
// defined in version.c
extern char* Version;
-extern char* mediumVersion;
extern char* longVersion;
//
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
index 48d53369de..fa00d9efcf 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -1,10 +1,3 @@
-/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- */
-
#ifndef NVIM_VIM_H
#define NVIM_VIM_H
diff --git a/src/nvim/window.c b/src/nvim/window.c
index b71b2cb603..16ff7dfb14 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -1,12 +1,3 @@
-/*
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read a list of people who contributed.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-#include <errno.h>
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>