diff options
Diffstat (limited to 'src')
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 ¤t_funccal->l_vars.dv_hashtab; /* l: variable */ + *d = current_funccal ? ¤t_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 ¤t_funccal->l_avars.dv_hashtab; - if (*name == 'l' && current_funccal != NULL) /* local function variable */ - return ¤t_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 = ¤t_funccal->l_avars; + } else if (*name == 'l' && current_funccal != NULL) { // local variable + *d = ¤t_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(®match, 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(¶ms); /* @@ -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> |