aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/gen/gen_eval.lua1
-rw-r--r--src/gen/gen_help_html.lua1
-rw-r--r--src/gen/gen_keycodes.lua33
-rw-r--r--src/nvim/api/autocmd.c4
-rw-r--r--src/nvim/api/win_config.c51
-rw-r--r--src/nvim/buffer_defs.h52
-rw-r--r--src/nvim/change.c1
-rw-r--r--src/nvim/diff.c752
-rw-r--r--src/nvim/drawline.c71
-rw-r--r--src/nvim/drawscreen.c16
-rw-r--r--src/nvim/edit.c2
-rw-r--r--src/nvim/eval.c2
-rw-r--r--src/nvim/eval/funcs.c58
-rw-r--r--src/nvim/ex_docmd.c8
-rw-r--r--src/nvim/highlight.h1
-rw-r--r--src/nvim/highlight_defs.h1
-rw-r--r--src/nvim/highlight_group.c1
-rw-r--r--src/nvim/keycodes.c7
-rw-r--r--src/nvim/lua/treesitter.c57
-rw-r--r--src/nvim/mapping.c3
-rw-r--r--src/nvim/mouse.c9
-rw-r--r--src/nvim/option_vars.h11
-rw-r--r--src/nvim/options.lua33
-rw-r--r--src/nvim/optionstr.c10
-rw-r--r--src/nvim/os/shell.c5
-rw-r--r--src/nvim/popupmenu.c65
-rw-r--r--src/nvim/regexp.c17
-rw-r--r--src/nvim/vterm/state.c18
-rw-r--r--src/nvim/window.c10
29 files changed, 1033 insertions, 267 deletions
diff --git a/src/gen/gen_eval.lua b/src/gen/gen_eval.lua
index 9d2f2f7523..2f2d09485b 100644
--- a/src/gen/gen_eval.lua
+++ b/src/gen/gen_eval.lua
@@ -16,6 +16,7 @@ hashpipe:write([[
#include "nvim/arglist.h"
#include "nvim/cmdexpand.h"
#include "nvim/cmdhist.h"
+#include "nvim/diff.h"
#include "nvim/digraph.h"
#include "nvim/eval.h"
#include "nvim/eval/buffer.h"
diff --git a/src/gen/gen_help_html.lua b/src/gen/gen_help_html.lua
index 0d98d9e1b1..817811e857 100644
--- a/src/gen/gen_help_html.lua
+++ b/src/gen/gen_help_html.lua
@@ -76,6 +76,7 @@ local new_layout = {
['news.txt'] = true,
['news-0.9.txt'] = true,
['news-0.10.txt'] = true,
+ ['news-0.11.txt'] = true,
['nvim.txt'] = true,
['provider.txt'] = true,
['tui.txt'] = true,
diff --git a/src/gen/gen_keycodes.lua b/src/gen/gen_keycodes.lua
index 6fbfb5190f..584f0a3d36 100644
--- a/src/gen/gen_keycodes.lua
+++ b/src/gen/gen_keycodes.lua
@@ -38,43 +38,26 @@ hashorder, hashfun = hashy.hashy_hash('get_special_key_code', hashorder, functio
return 'key_names_table[' .. idx .. '].name.data'
end, true)
---- @type table<string,integer>
---- Maps keys to the (after hash) indexes of the entries with preferred names.
-local key_hash_idx = {}
-
-local name_orig_idx_ = vim.deepcopy(name_orig_idx)
-for i, lower_name in ipairs(hashorder) do
- local orig_idx = table.remove(name_orig_idx_[lower_name], 1)
- local keycode = keycode_names[orig_idx]
- local key = keycode[1]
- if key_orig_idx[key] == orig_idx then
- key_hash_idx[key] = i
- end
-end
-assert(vim.iter(vim.tbl_values(name_orig_idx_)):all(vim.tbl_isempty))
-
local names_tgt = assert(io.open(names_file, 'w'))
names_tgt:write([[
static const struct key_name_entry {
- int key; ///< Special key code or ascii value
- String name; ///< Name of key
- const String *pref_name; ///< Pointer to preferred key name
- ///< (may be NULL or point to the name in another entry)
+ int key; ///< Special key code or ascii value
+ bool is_alt; ///< Is an alternative name
+ String name; ///< Name of key
} key_names_table[] = {]])
-name_orig_idx_ = vim.deepcopy(name_orig_idx)
-for i, lower_name in ipairs(hashorder) do
+local name_orig_idx_ = vim.deepcopy(name_orig_idx)
+for _, lower_name in ipairs(hashorder) do
local orig_idx = table.remove(name_orig_idx_[lower_name], 1)
local keycode = keycode_names[orig_idx]
local key = keycode[1]
local name = keycode[2]
- local pref_idx = key_hash_idx[key]
names_tgt:write(
- ('\n {%s, {"%s", %u}, %s},'):format(
+ ('\n {%s, %s, {"%s", %u}},'):format(
key,
+ key_orig_idx[key] == orig_idx and 'false' or 'true',
name,
- #name,
- pref_idx == i and 'NULL' or ('&key_names_table[%u].name'):format(pref_idx - 1)
+ #name
)
)
end
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index 1e33b0b62a..7c98736289 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -369,8 +369,8 @@ cleanup:
/// - desc (string) optional: description (for documentation and troubleshooting).
/// - callback (function|string) optional: Lua function (or Vimscript function name, if
/// string) called when the event(s) is triggered. Lua callback can return a truthy
-/// value (not `false` or `nil`) to delete the autocommand. Receives one argument,
-/// a table with these keys: [event-args]()
+/// value (not `false` or `nil`) to delete the autocommand, and receives one argument, a
+/// table with these keys: [event-args]()
/// - id: (number) autocommand id
/// - event: (string) name of the triggered event |autocmd-events|
/// - group: (number|nil) autocommand group id, if any
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index ee6571fd95..4f3ecf8efe 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -158,41 +158,40 @@
/// region is hidden by setting `eob` flag of
/// 'fillchars' to a space char, and clearing the
/// |hl-EndOfBuffer| region in 'winhighlight'.
-/// - border: Style of (optional) window border. This can either be a string
-/// or an array. The string values are the same as those described in 'winborder'.
-/// If it is an array, it should have a length of eight or any divisor of
-/// eight. The array will specify the eight chars building up the border
-/// in a clockwise fashion starting with the top-left corner. As an
-/// example, the double box style could be specified as:
-/// ```
-/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ].
-/// ```
-/// If the number of chars are less than eight, they will be repeated. Thus
-/// an ASCII border could be specified as
-/// ```
-/// [ "/", "-", \"\\\\\", "|" ],
-/// ```
-/// or all chars the same as
-/// ```
-/// [ "x" ].
-/// ```
-/// An empty string can be used to turn off a specific border, for instance,
+/// - border: (`string|string[]`) (defaults to 'winborder' option) Window border. The string form
+/// accepts the same values as the 'winborder' option. The array form must have a length of
+/// eight or any divisor of eight, specifying the chars that form the border in a clockwise
+/// fashion starting from the top-left corner. For example, the double-box style can be
+/// specified as:
/// ```
-/// [ "", "", "", ">", "", "", "", "<" ]
+/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ].
/// ```
-/// will only make vertical borders but not horizontal ones.
-/// By default, `FloatBorder` highlight is used, which links to `WinSeparator`
-/// when not defined. It could also be specified by character:
+/// If fewer than eight chars are given, they will be repeated. An ASCII border could be
+/// specified as:
/// ```
-/// [ ["+", "MyCorner"], ["x", "MyBorder"] ].
+/// [ "/", "-", \"\\\\\", "|" ],
/// ```
-/// - title: Title (optional) in window border, string or list.
+/// Or one char for all sides:
+/// ```
+/// [ "x" ].
+/// ```
+/// Empty string can be used to hide a specific border. This example will show only vertical
+/// borders, not horizontal:
+/// ```
+/// [ "", "", "", ">", "", "", "", "<" ]
+/// ```
+/// By default, |hl-FloatBorder| highlight is used, which links to |hl-WinSeparator| when not
+/// defined. Each border side can specify an optional highlight:
+/// ```
+/// [ ["+", "MyCorner"], ["x", "MyBorder"] ].
+/// ```
+/// - title: (optional) Title in window border, string or list.
/// List should consist of `[text, highlight]` tuples.
/// If string, or a tuple lacks a highlight, the default highlight group is `FloatTitle`.
/// - title_pos: Title position. Must be set with `title` option.
/// Value can be one of "left", "center", or "right".
/// Default is `"left"`.
-/// - footer: Footer (optional) in window border, string or list.
+/// - footer: (optional) Footer in window border, string or list.
/// List should consist of `[text, highlight]` tuples.
/// If string, or a tuple lacks a highlight, the default highlight group is `FloatFooter`.
/// - footer_pos: Footer position. Must be set with `footer` option.
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index c4241eed45..f226ce9c42 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -743,18 +743,21 @@ struct file_buffer {
// Stuff for diff mode.
#define DB_COUNT 8 // up to four buffers can be diff'ed
-// Each diffblock defines where a block of lines starts in each of the buffers
-// and how many lines it occupies in that buffer. When the lines are missing
-// in the buffer the df_count[] is zero. This is all counted in
-// buffer lines.
-// There is always at least one unchanged line in between the diffs.
-// Otherwise it would have been included in the diff above or below it.
-// df_lnum[] + df_count[] is the lnum below the change. When in one buffer
-// lines have been inserted, in the other buffer df_lnum[] is the line below
-// the insertion and df_count[] is zero. When appending lines at the end of
-// the buffer, df_lnum[] is one beyond the end!
-// This is using a linked list, because the number of differences is expected
-// to be reasonable small. The list is sorted on lnum.
+/// Each diffblock defines where a block of lines starts in each of the buffers
+/// and how many lines it occupies in that buffer. When the lines are missing
+/// in the buffer the df_count[] is zero. This is all counted in
+/// buffer lines.
+/// There is always at least one unchanged line in between the diffs (unless
+/// linematch is used). Otherwise it would have been included in the diff above
+/// or below it.
+/// df_lnum[] + df_count[] is the lnum below the change. When in one buffer
+/// lines have been inserted, in the other buffer df_lnum[] is the line below
+/// the insertion and df_count[] is zero. When appending lines at the end of
+/// the buffer, df_lnum[] is one beyond the end!
+/// This is using a linked list, because the number of differences is expected
+/// to be reasonable small. The list is sorted on lnum.
+/// Each diffblock also contains a cached list of inline diff of changes within
+/// the block, used for highlighting.
typedef struct diffblock_S diff_T;
struct diffblock_S {
diff_T *df_next;
@@ -762,6 +765,31 @@ struct diffblock_S {
linenr_T df_count[DB_COUNT]; // nr of inserted/changed lines
bool is_linematched; // has the linematch algorithm ran on this diff hunk to divide it into
// smaller diff hunks?
+
+ bool has_changes; ///< has cached list of inline changes
+ garray_T df_changes; ///< list of inline changes (diffline_change_T)
+};
+
+/// Each entry stores a single inline change within a diff block. Line numbers
+/// are recorded as relative offsets, and columns are byte offsets, not
+/// character counts.
+/// Ranges are [start,end), with the end being exclusive.
+typedef struct diffline_change_S diffline_change_T;
+struct diffline_change_S {
+ colnr_T dc_start[DB_COUNT]; ///< byte offset of start of range in the line
+ colnr_T dc_end[DB_COUNT]; ///< 1 past byte offset of end of range in line
+ int dc_start_lnum_off[DB_COUNT]; ///< starting line offset
+ int dc_end_lnum_off[DB_COUNT]; ///< end line offset
+};
+
+/// Describes a single line's list of inline changes. Use diff_change_parse() to
+/// parse this.
+typedef struct diffline_S diffline_T;
+struct diffline_S {
+ diffline_change_T *changes;
+ int num_changes;
+ int bufidx;
+ int lineoff;
};
#define SNAP_HELP_IDX 0
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 192e0d9faa..cf7d5dfc4b 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -240,6 +240,7 @@ static void changed_common(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnum
FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
if (win->w_buffer == buf && win->w_p_diff && diff_internal()) {
curtab->tp_diff_update = true;
+ diff_update_line(lnum);
}
}
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index c9ca58c816..6309aa6c5e 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -29,6 +29,7 @@
#include "nvim/drawscreen.h"
#include "nvim/errors.h"
#include "nvim/eval.h"
+#include "nvim/eval/typval.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
@@ -86,7 +87,13 @@ static bool diff_need_update = false; // ex_diffupdate needs to be called
#define DIFF_CLOSE_OFF 0x400 // diffoff when closing window
#define DIFF_FOLLOWWRAP 0x800 // follow the wrap option
#define DIFF_LINEMATCH 0x1000 // match most similar lines within diff
+#define DIFF_INLINE_NONE 0x2000 // no inline highlight
+#define DIFF_INLINE_SIMPLE 0x4000 // inline highlight with simple algorithm
+#define DIFF_INLINE_CHAR 0x8000 // inline highlight with character diff
+#define DIFF_INLINE_WORD 0x10000 // inline highlight with word diff
#define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL)
+#define ALL_INLINE (DIFF_INLINE_NONE | DIFF_INLINE_SIMPLE | DIFF_INLINE_CHAR | DIFF_INLINE_WORD)
+#define ALL_INLINE_DIFF (DIFF_INLINE_CHAR | DIFF_INLINE_WORD)
static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF;
static int diff_algorithm = 0;
@@ -136,6 +143,15 @@ typedef enum {
# include "diff.c.generated.h"
#endif
+#define FOR_ALL_DIFFBLOCKS_IN_TAB(tp, dp) \
+ for ((dp) = (tp)->tp_first_diff; (dp) != NULL; (dp) = (dp)->df_next)
+
+static void clear_diffblock(diff_T *dp)
+{
+ ga_clear(&dp->df_changes);
+ xfree(dp);
+}
+
/// Called when deleting or unloading a buffer: No longer make a diff with it.
///
/// @param buf
@@ -522,7 +538,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T
/// @return The new diff block.
static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp)
{
- diff_T *dnew = xmalloc(sizeof(*dnew));
+ diff_T *dnew = xcalloc(1, sizeof(*dnew));
dnew->is_linematched = false;
dnew->df_next = dp;
@@ -532,13 +548,15 @@ static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp)
dprev->df_next = dnew;
}
+ dnew->has_changes = false;
+ ga_init(&dnew->df_changes, sizeof(diffline_change_T), 20);
return dnew;
}
static diff_T *diff_free(tabpage_T *tp, diff_T *dprev, diff_T *dp)
{
diff_T *ret = dp->df_next;
- xfree(dp);
+ clear_diffblock(dp);
if (dprev == NULL) {
tp->tp_first_diff = ret;
@@ -763,15 +781,32 @@ static int diff_write_buffer(buf_T *buf, mmfile_t *m, linenr_T start, linenr_T e
char *s = ml_get_buf(buf, lnum);
if (diff_flags & DIFF_ICASE) {
while (*s != NUL) {
+ int c;
+ int c_len = 1;
char cbuf[MB_MAXBYTES + 1];
- // xdiff doesn't support ignoring case, fold-case the text.
- int c = *s == NL ? NUL : utf_fold(utf_ptr2char(s));
+ if (*s == NL) {
+ c = NUL;
+ } else {
+ // xdiff doesn't support ignoring case, fold-case the text.
+ c = utf_ptr2char(s);
+ c_len = utf_char2len(c);
+ c = utf_fold(c);
+ }
const int orig_len = utfc_ptr2len(s);
- // TODO(Bram): handle byte length difference
- char *s1 = (utf_char2bytes(c, cbuf) != orig_len) ? s : cbuf;
- memmove(ptr + len, s1, (size_t)orig_len);
+ if (utf_char2bytes(c, cbuf) != c_len) {
+ // TODO(Bram): handle byte length difference
+ // One example is Å (3 bytes) and å (2 bytes).
+ memmove(ptr + len, s, (size_t)orig_len);
+ } else {
+ memmove(ptr + len, cbuf, (size_t)c_len);
+ if (orig_len > c_len) {
+ // Copy remaining composing characters
+ memmove(ptr + len + c_len, s + c_len, (size_t)(orig_len - c_len));
+ }
+ }
+
s += orig_len;
len += (size_t)orig_len;
}
@@ -943,8 +978,7 @@ void ex_diffupdate(exarg_T *eap)
}
// Only use the internal method if it did not fail for one of the buffers.
- diffio_T diffio;
- CLEAR_FIELD(diffio);
+ diffio_T diffio = { 0 };
diffio.dio_internal = diff_internal();
diff_try_update(&diffio, idx_orig, eap);
@@ -1639,11 +1673,6 @@ static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_ne
if (off > 0) {
dp->df_count[idx_new] += off;
}
- if ((dp->df_lnum[idx_new] + dp->df_count[idx_new] - 1)
- > curtab->tp_diffbuf[idx_new]->b_ml.ml_line_count) {
- dp->df_count[idx_new] = curtab->tp_diffbuf[idx_new]->b_ml.ml_line_count
- - dp->df_lnum[idx_new] + 1;
- }
}
// Adjust the size of the block to include all the lines to the
@@ -1661,11 +1690,6 @@ static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_ne
// overlap later.
dp->df_count[idx_new] += -off;
}
- if ((dp->df_lnum[idx_new] + dp->df_count[idx_new] - 1)
- > curtab->tp_diffbuf[idx_new]->b_ml.ml_line_count) {
- dp->df_count[idx_new] = curtab->tp_diffbuf[idx_new]->b_ml.ml_line_count
- - dp->df_lnum[idx_new] + 1;
- }
off = 0;
}
@@ -1682,7 +1706,7 @@ static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_ne
while (dn != dp->df_next) {
dpl = dn->df_next;
- xfree(dn);
+ clear_diffblock(dn);
dn = dpl;
}
} else {
@@ -1716,7 +1740,7 @@ static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_ne
static void diff_read(int idx_orig, int idx_new, diffio_T *dio)
{
FILE *fd = NULL;
- int line_idx = 0;
+ int line_hunk_idx = 0; // line or hunk index
diff_T *dprev = NULL;
diff_T *dp = curtab->tp_first_diff;
diffout_T *dout = &dio->dio_diff;
@@ -1734,7 +1758,7 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio)
while (true) {
diffhunk_T hunk = { 0 };
bool eof = dio->dio_internal
- ? extract_hunk_internal(dout, &hunk, &line_idx)
+ ? extract_hunk_internal(dout, &hunk, &line_hunk_idx)
: extract_hunk(fd, &hunk, &diffstyle);
if (eof) {
@@ -1788,7 +1812,7 @@ void diff_clear(tabpage_T *tp)
diff_T *next_p;
for (diff_T *p = tp->tp_first_diff; p != NULL; p = next_p) {
next_p = p->df_next;
- xfree(p);
+ clear_diffblock(p);
}
tp->tp_first_diff = NULL;
}
@@ -2531,6 +2555,28 @@ int diffopt_changed(void)
} else {
return FAIL;
}
+ } else if (strncmp(p, "inline:", 7) == 0) {
+ // Note: Keep this in sync with opt_dip_inline_values.
+ p += 7;
+ if (strncmp(p, "none", 4) == 0) {
+ p += 4;
+ diff_flags_new &= ~(ALL_INLINE);
+ diff_flags_new |= DIFF_INLINE_NONE;
+ } else if (strncmp(p, "simple", 6) == 0) {
+ p += 6;
+ diff_flags_new &= ~(ALL_INLINE);
+ diff_flags_new |= DIFF_INLINE_SIMPLE;
+ } else if (strncmp(p, "char", 4) == 0) {
+ p += 4;
+ diff_flags_new &= ~(ALL_INLINE);
+ diff_flags_new |= DIFF_INLINE_CHAR;
+ } else if (strncmp(p, "word", 4) == 0) {
+ p += 4;
+ diff_flags_new &= ~(ALL_INLINE);
+ diff_flags_new |= DIFF_INLINE_WORD;
+ } else {
+ return FAIL;
+ }
} else if ((strncmp(p, "linematch:", 10) == 0) && ascii_isdigit(p[10])) {
p += 10;
linematch_lines_new = getdigits_int(&p, false, linematch_lines_new);
@@ -2603,48 +2649,101 @@ bool diffopt_filler(void)
return (diff_flags & DIFF_FILLER) != 0;
}
-/// Find the difference within a changed line.
-///
-/// @param wp window whose current buffer to check
-/// @param lnum line number to check within the buffer
-/// @param startp first char of the change
-/// @param endp last char of the change
-///
-/// @return true if the line was added, no other buffer has it.
-bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp)
- FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+/// Called when a line has been updated. Used for updating inline diff in Insert
+/// mode without waiting for global diff update later.
+void diff_update_line(linenr_T lnum)
{
- // Make a copy of the line, the next ml_get() will invalidate it.
- char *line_org = xstrdup(ml_get_buf(wp->w_buffer, lnum));
+ if (!(diff_flags & ALL_INLINE_DIFF)) {
+ // We only care if we are doing inline-diff where we cache the diff results
+ return;
+ }
- int idx = diff_buf_idx(wp->w_buffer, curtab);
+ int idx = diff_buf_idx(curbuf, curtab);
if (idx == DB_COUNT) {
- // cannot happen
- xfree(line_org);
- return false;
+ return;
}
-
- // search for a change that includes "lnum" in the list of diffblocks.
diff_T *dp;
- for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) {
+ FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp) {
if (lnum <= dp->df_lnum[idx] + dp->df_count[idx]) {
break;
}
}
- if (dp != NULL && dp->is_linematched) {
- while (dp && dp->df_next
- && lnum == dp->df_count[idx] + dp->df_lnum[idx]
- && dp->df_next->df_lnum[idx] == lnum) {
- dp = dp->df_next;
- }
+
+ // clear the inline change cache as it's invalid
+ if (dp != NULL) {
+ dp->has_changes = false;
+ dp->df_changes.ga_len = 0;
}
+}
+
+/// used for simple inline diff algorithm
+static diffline_change_T simple_diffline_change;
- if ((dp == NULL) || (diff_check_sanity(curtab, dp) == FAIL)) {
- xfree(line_org);
+/// Parse a diffline struct and returns the [start,end] byte offsets
+///
+/// Returns true if this change was added, no other buffer has it.
+bool diff_change_parse(diffline_T *diffline, diffline_change_T *change, int *change_start,
+ int *change_end)
+{
+ if (change->dc_start_lnum_off[diffline->bufidx] < diffline->lineoff) {
+ *change_start = 0;
+ } else {
+ *change_start = change->dc_start[diffline->bufidx];
+ }
+ if (change->dc_end_lnum_off[diffline->bufidx] > diffline->lineoff) {
+ *change_end = INT_MAX;
+ } else {
+ *change_end = change->dc_end[diffline->bufidx];
+ }
+ if (change == &simple_diffline_change) {
+ // This is what we returned from simple inline diff. We always consider
+ // the range to be changed, rather than added for now.
return false;
}
+ // Find out whether this is an addition. Note that for multi buffer diff,
+ // to tell whether lines are additions we check whether all the other diff
+ // lines are identical (in diff_check_with_linestatus). If so, we mark them
+ // as add. We don't do that for inline diff here for simplicity.
+ for (int i = 0; i < DB_COUNT; i++) {
+ if (i == diffline->bufidx) {
+ continue;
+ }
+ if (change->dc_start[i] != change->dc_end[i]
+ || change->dc_end_lnum_off[i] != change->dc_start_lnum_off[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/// Find the difference within a changed line and returns [startp,endp] byte
+/// positions. Performs a simple algorithm by finding a single range in the
+/// middle.
+///
+/// If diffopt has DIFF_INLINE_NONE set, then this will only calculate the return
+/// value (added or changed), but startp/endp will not be calculated.
+///
+/// @param wp window whose current buffer to check
+/// @param lnum line number to check within the buffer
+/// @param startp first char of the change
+/// @param endp last char of the change
+///
+/// @return true if the line was added, no other buffer has it.
+static bool diff_find_change_simple(win_T *wp, linenr_T lnum, const diff_T *dp, int idx,
+ int *startp, int *endp)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ char *line_org;
+ if (diff_flags & DIFF_INLINE_NONE) {
+ // We only care about the return value, not the actual string comparisons.
+ line_org = NULL;
+ } else {
+ // Make a copy of the line, the next ml_get() will invalidate it.
+ line_org = xstrdup(ml_get_buf(wp->w_buffer, lnum));
+ }
+
int si_org;
int si_new;
int ei_org;
@@ -2659,6 +2758,10 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp)
continue;
}
added = false;
+ if (diff_flags & DIFF_INLINE_NONE) {
+ break; // early terminate as we only care about the return value
+ }
+
char *line_new = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + off);
// Search for start of difference
@@ -2737,6 +2840,465 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp)
return added;
}
+/// Mapping used for mapping from temporary mmfile created for inline diff back
+/// to original buffer's line/col.
+typedef struct {
+ colnr_T byte_start;
+ colnr_T num_bytes;
+ int lineoff;
+} linemap_entry_T;
+
+/// Refine inline character-wise diff blocks to create a more human readable
+/// highlight. Otherwise a naive diff under existing algorithms tends to create
+/// a messy output with lots of small gaps.
+/// It does this by merging adjacent long diff blocks if they are only separated
+/// by a couple characters.
+/// These are done by heuristics and can be further tuned.
+static void diff_refine_inline_char_highlight(diff_T *dp_orig, garray_T *linemap, int idx1)
+{
+ // Perform multiple passes so that newly merged blocks will now be long
+ // enough which may cause other previously unmerged gaps to be merged as
+ // well.
+ int pass = 1;
+ do {
+ bool has_unmerged_gaps = false;
+ bool has_merged_gaps = false;
+ diff_T *dp = dp_orig;
+ while (dp != NULL && dp->df_next != NULL) {
+ // Only use first buffer to calculate the gap because the gap is
+ // unchanged text, which would be the same in all buffers.
+ if (dp->df_lnum[idx1] + dp->df_count[idx1] - 1 >= linemap[idx1].ga_len
+ || dp->df_next->df_lnum[idx1] - 1 >= linemap[idx1].ga_len) {
+ dp = dp->df_next;
+ continue;
+ }
+
+ // If the gap occurs over different lines, don't consider it
+ linemap_entry_T *entry1 =
+ &((linemap_entry_T *)linemap[idx1].ga_data)[dp->df_lnum[idx1]
+ + dp->df_count[idx1] - 1];
+ linemap_entry_T *entry2 =
+ &((linemap_entry_T *)linemap[idx1].ga_data)[dp->df_next->df_lnum[idx1] - 1];
+ if (entry1->lineoff != entry2->lineoff) {
+ dp = dp->df_next;
+ continue;
+ }
+
+ linenr_T gap = dp->df_next->df_lnum[idx1] - (dp->df_lnum[idx1] + dp->df_count[idx1]);
+ if (gap <= 3) {
+ linenr_T max_df_count = 0;
+ for (int i = 0; i < DB_COUNT; i++) {
+ max_df_count = MAX(max_df_count, dp->df_count[i] + dp->df_next->df_count[i]);
+ }
+
+ if (max_df_count >= gap * 4) {
+ // Merge current block with the next one. Don't advance the
+ // pointer so we try the same merged block against the next
+ // one.
+ for (int i = 0; i < DB_COUNT; i++) {
+ dp->df_count[i] = dp->df_next->df_lnum[i]
+ + dp->df_next->df_count[i] - dp->df_lnum[i];
+ }
+ diff_T *dp_next = dp->df_next;
+ dp->df_next = dp_next->df_next;
+ clear_diffblock(dp_next);
+ has_merged_gaps = true;
+ continue;
+ } else {
+ has_unmerged_gaps = true;
+ }
+ }
+ dp = dp->df_next;
+ }
+ if (!has_unmerged_gaps || !has_merged_gaps) {
+ break;
+ }
+ } while (pass++ < 4); // use limited number of passes to avoid excessive looping
+}
+
+/// Find the inline difference within a diff block among differnt buffers. Do
+/// this by splitting each block's content into characters or words, and then
+/// use internal xdiff to calculate the per-character/word diff. The result is
+/// stored in dp instead of returned by the function.
+static void diff_find_change_inline_diff(diff_T *dp)
+{
+ const int save_diff_algorithm = diff_algorithm;
+
+ diffio_T dio = { 0 };
+ ga_init(&dio.dio_diff.dout_ga, sizeof(char *), 1000);
+
+ // inline diff only supports internal algo
+ dio.dio_internal = true;
+
+ // always use indent-heuristics to slide diff splits along
+ // whitespace
+ diff_algorithm |= XDF_INDENT_HEURISTIC;
+
+ // diff_read() has an implicit dependency on curtab->tp_first_diff
+ diff_T *orig_diff = curtab->tp_first_diff;
+ curtab->tp_first_diff = NULL;
+
+ garray_T linemap[DB_COUNT];
+ garray_T file1_str;
+ garray_T file2_str;
+
+ // Buffers to populate mmfile 1/2 that would be passed to xdiff as memory
+ // files. Use a grow array as it is not obvious how much exact space we
+ // need.
+ ga_init(&file1_str, 1, 1024);
+ ga_init(&file2_str, 1, 1024);
+
+ // Line map to map from generated mmfiles' line numbers back to original
+ // diff blocks' locations. Need this even for char diff because not all
+ // characters are 1-byte long / ASCII.
+ for (int i = 0; i < DB_COUNT; i++) {
+ ga_init(&linemap[i], sizeof(linemap_entry_T), 128);
+ }
+
+ int file1_idx = -1;
+ for (int i = 0; i < DB_COUNT; i++) {
+ dio.dio_diff.dout_ga.ga_len = 0;
+
+ buf_T *buf = curtab->tp_diffbuf[i];
+ if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
+ continue; // skip buffer that isn't loaded
+ }
+ if (dp->df_count[i] == 0) {
+ continue; // skip buffer that don't have any texts in this block
+ }
+ if (file1_idx == -1) {
+ file1_idx = i;
+ }
+
+ garray_T *curstr = (file1_idx != i) ? &file2_str : &file1_str;
+
+ linenr_T numlines = 0;
+ curstr->ga_len = 0;
+
+ // Split each line into chars/words and populate fake file buffer as
+ // newline-delimited tokens as that's what xdiff requires.
+ for (int off = 0; off < dp->df_count[i]; off++) {
+ char *curline = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + off);
+
+ bool in_keyword = false;
+
+ // iwhiteeol support vars
+ bool last_white = false;
+ int eol_ga_len = -1;
+ int eol_linemap_len = -1;
+ int eol_numlines = -1;
+
+ char *s = curline;
+ while (*s != NUL) {
+ // Always use the first buffer's 'iskeyword' to have a consistent diff
+ bool new_in_keyword = false;
+ if (diff_flags & DIFF_INLINE_WORD) {
+ new_in_keyword = vim_iswordp_buf(s, curtab->tp_diffbuf[file1_idx]);
+ }
+ if (in_keyword && !new_in_keyword) {
+ ga_append(curstr, NL);
+ numlines++;
+ }
+
+ if (ascii_iswhite(*s)) {
+ if (diff_flags & DIFF_IWHITEALL) {
+ in_keyword = false;
+ s = skipwhite(s);
+ continue;
+ } else if ((diff_flags & DIFF_IWHITEEOL) || (diff_flags & DIFF_IWHITE)) {
+ if (!last_white) {
+ eol_ga_len = curstr->ga_len;
+ eol_linemap_len = linemap[i].ga_len;
+ eol_numlines = numlines;
+ last_white = true;
+ }
+ }
+ } else {
+ if ((diff_flags & DIFF_IWHITEEOL) || (diff_flags & DIFF_IWHITE)) {
+ last_white = false;
+ eol_ga_len = -1;
+ eol_linemap_len = -1;
+ eol_numlines = -1;
+ }
+ }
+
+ int char_len = 1;
+ if (*s == NL) {
+ // NL is internal substitute for NUL
+ ga_append(curstr, NUL);
+ } else {
+ char_len = utfc_ptr2len(s);
+
+ if (ascii_iswhite(*s) && (diff_flags & DIFF_IWHITE)) {
+ // Treat the entire white space span as a single char.
+ char_len = (int)(skipwhite(s) - s);
+ }
+
+ if (diff_flags & DIFF_ICASE) {
+ // xdiff doesn't support ignoring case, fold-case the text manually.
+ int c = utf_ptr2char(s);
+ int c_len = utf_char2len(c);
+ c = utf_fold(c);
+ char cbuf[MB_MAXBYTES + 1];
+ int c_fold_len = utf_char2bytes(c, cbuf);
+ ga_concat_len(curstr, cbuf, (size_t)c_fold_len);
+ if (char_len > c_len) {
+ // There may be remaining composing characters. Write those back in.
+ // Composing characters don't need case folding.
+ ga_concat_len(curstr, s + c_len, (size_t)(char_len - c_len));
+ }
+ } else {
+ ga_concat_len(curstr, s, (size_t)char_len);
+ }
+ }
+
+ if (!new_in_keyword) {
+ ga_append(curstr, NL);
+ numlines++;
+ }
+
+ if (!new_in_keyword || (new_in_keyword && !in_keyword)) {
+ // create a new mapping entry from the xdiff mmfile back to
+ // original line/col.
+ linemap_entry_T linemap_entry = {
+ .lineoff = off,
+ .byte_start = (colnr_T)(s - curline),
+ .num_bytes = char_len,
+ };
+ GA_APPEND(linemap_entry_T, &linemap[i], linemap_entry);
+ } else {
+ // Still inside a keyword. Just increment byte count but
+ // don't make a new entry.
+ // linemap always has at least one entry here
+ ((linemap_entry_T *)linemap[i].ga_data)[linemap[i].ga_len - 1].num_bytes += char_len;
+ }
+
+ in_keyword = new_in_keyword;
+ s += char_len;
+ }
+ if (in_keyword) {
+ ga_append(curstr, NL);
+ numlines++;
+ }
+
+ if ((diff_flags & DIFF_IWHITEEOL) || (diff_flags & DIFF_IWHITE)) {
+ // Need to trim trailing whitespace. Do this simply by
+ // resetting arrays back to before we encountered them.
+ if (eol_ga_len != -1) {
+ curstr->ga_len = eol_ga_len;
+ linemap[i].ga_len = eol_linemap_len;
+ numlines = eol_numlines;
+ }
+ }
+
+ if (!(diff_flags & DIFF_IWHITEALL)) {
+ // Add an empty line token mapped to the end-of-line in the
+ // original file. This helps diff newline differences among
+ // files, which will be visualized when using 'list' as the eol
+ // listchar will be highlighted.
+ ga_append(curstr, NL);
+ numlines++;
+
+ linemap_entry_T linemap_entry = {
+ .lineoff = off,
+ .byte_start = (colnr_T)(s - curline),
+ .num_bytes = sizeof(NL),
+ };
+ GA_APPEND(linemap_entry_T, &linemap[i], linemap_entry);
+ }
+ }
+
+ if (file1_idx != i) {
+ dio.dio_new.din_mmfile.ptr = (char *)curstr->ga_data;
+ dio.dio_new.din_mmfile.size = curstr->ga_len;
+ } else {
+ dio.dio_orig.din_mmfile.ptr = (char *)curstr->ga_data;
+ dio.dio_orig.din_mmfile.size = curstr->ga_len;
+ }
+ if (file1_idx != i) {
+ // Perform diff with first file and read the results
+ int diff_status = diff_file_internal(&dio);
+ if (diff_status == FAIL) {
+ goto done;
+ }
+
+ diff_read(0, i, &dio);
+ clear_diffout(&dio.dio_diff);
+ }
+ }
+ diff_T *new_diff = curtab->tp_first_diff;
+
+ if (diff_flags & DIFF_INLINE_CHAR && file1_idx != -1) {
+ diff_refine_inline_char_highlight(new_diff, linemap, file1_idx);
+ }
+
+ // After the diff, use the linemap to obtain the original line/col of the
+ // changes and cache them in dp.
+ dp->df_changes.ga_len = 0; // this should already be zero
+ for (; new_diff != NULL; new_diff = new_diff->df_next) {
+ diffline_change_T change = { 0 };
+ for (int i = 0; i < DB_COUNT; i++) {
+ if (new_diff->df_lnum[i] == 0) {
+ continue;
+ }
+ linenr_T diff_lnum = new_diff->df_lnum[i] - 1; // use zero-index
+ linenr_T diff_lnum_end = diff_lnum + new_diff->df_count[i];
+
+ if (diff_lnum >= linemap[i].ga_len) {
+ change.dc_start[i] = MAXCOL;
+ change.dc_start_lnum_off[i] = INT_MAX;
+ } else {
+ change.dc_start[i] = ((linemap_entry_T *)linemap[i].ga_data)[diff_lnum].byte_start;
+ change.dc_start_lnum_off[i] = ((linemap_entry_T *)linemap[i].ga_data)[diff_lnum].lineoff;
+ }
+
+ if (diff_lnum == diff_lnum_end) {
+ change.dc_end[i] = change.dc_start[i];
+ change.dc_end_lnum_off[i] = change.dc_start_lnum_off[i];
+ } else if (diff_lnum_end - 1 >= linemap[i].ga_len) {
+ change.dc_end[i] = MAXCOL;
+ change.dc_end_lnum_off[i] = INT_MAX;
+ } else {
+ change.dc_end[i] = ((linemap_entry_T *)linemap[i].ga_data)[diff_lnum_end - 1].byte_start +
+ ((linemap_entry_T *)linemap[i].ga_data)[diff_lnum_end - 1].num_bytes;
+ change.dc_end_lnum_off[i] = ((linemap_entry_T *)linemap[i].ga_data)[diff_lnum_end -
+ 1].lineoff;
+ }
+ }
+ GA_APPEND(diffline_change_T, &dp->df_changes, change);
+ }
+
+done:
+ diff_algorithm = save_diff_algorithm;
+
+ dp->has_changes = true;
+
+ diff_clear(curtab);
+ curtab->tp_first_diff = orig_diff;
+
+ ga_clear(&file1_str);
+ ga_clear(&file2_str);
+ // No need to clear dio.dio_orig/dio_new because they were referencing
+ // strings that are now cleared.
+ clear_diffout(&dio.dio_diff);
+ for (int i = 0; i < DB_COUNT; i++) {
+ ga_clear(&linemap[i]);
+ }
+}
+
+/// Find the difference within a changed line.
+/// Returns true if the line was added, no other buffer has it.
+bool diff_find_change(win_T *wp, linenr_T lnum, diffline_T *diffline)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ int idx = diff_buf_idx(wp->w_buffer, curtab);
+ if (idx == DB_COUNT) { // cannot happen
+ return false;
+ }
+
+ // search for a change that includes "lnum" in the list of diffblocks.
+ diff_T *dp;
+ FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp) {
+ if (lnum <= dp->df_lnum[idx] + dp->df_count[idx]) {
+ break;
+ }
+ }
+ if (dp && dp->is_linematched) {
+ while (dp && dp->df_next
+ && lnum == dp->df_count[idx] + dp->df_lnum[idx]
+ && dp->df_next->df_lnum[idx] == lnum) {
+ dp = dp->df_next;
+ }
+ }
+ if (dp == NULL || diff_check_sanity(curtab, dp) == FAIL) {
+ return false;
+ }
+
+ int off = lnum - dp->df_lnum[idx];
+
+ if (!(diff_flags & ALL_INLINE_DIFF)) {
+ // Use simple algorithm
+ int change_start = MAXCOL; // first col of changed area
+ int change_end = -1; // last col of changed area
+
+ int ret = diff_find_change_simple(wp, lnum, dp, idx, &change_start, &change_end);
+
+ // convert from inclusive end to exclusive end per diffline's contract
+ change_end += 1;
+
+ // Create a mock diffline struct. We always only have one so no need to
+ // allocate memory.
+ CLEAR_FIELD(simple_diffline_change);
+ diffline->changes = &simple_diffline_change;
+ diffline->num_changes = 1;
+ diffline->bufidx = idx;
+ diffline->lineoff = lnum - dp->df_lnum[idx];
+
+ simple_diffline_change.dc_start[idx] = change_start;
+ simple_diffline_change.dc_end[idx] = change_end;
+ simple_diffline_change.dc_start_lnum_off[idx] = off;
+ simple_diffline_change.dc_end_lnum_off[idx] = off;
+ return ret;
+ }
+
+ // Use inline diff algorithm.
+ // The diff changes are usually cached so we check that first.
+ if (!dp->has_changes) {
+ diff_find_change_inline_diff(dp);
+ }
+
+ garray_T *changes = &dp->df_changes;
+
+ // Use linear search to find the first change for this line. We could
+ // optimize this to use binary search, but there should usually be a
+ // limited number of inline changes per diff block, and limited number of
+ // diff blocks shown on screen, so it is not necessary.
+ int num_changes = 0;
+ int change_idx = 0;
+ diffline->changes = NULL;
+ for (change_idx = 0; change_idx < changes->ga_len; change_idx++) {
+ diffline_change_T *change =
+ &((diffline_change_T *)dp->df_changes.ga_data)[change_idx];
+ if (change->dc_end_lnum_off[idx] < off) {
+ continue;
+ }
+ if (change->dc_start_lnum_off[idx] > off) {
+ break;
+ }
+ if (diffline->changes == NULL) {
+ diffline->changes = change;
+ }
+ num_changes++;
+ }
+ diffline->num_changes = num_changes;
+ diffline->bufidx = idx;
+ diffline->lineoff = off;
+
+ // Detect simple cases of added lines in the end within a diff block. This
+ // has to be the last change of this diff block, and all other buffers are
+ // considering this to be an addition past their last line. Other scenarios
+ // will be considered a changed line instead.
+ bool added = false;
+ if (num_changes == 1 && change_idx == dp->df_changes.ga_len) {
+ added = true;
+ for (int i = 0; i < DB_COUNT; i++) {
+ if (idx == i) {
+ continue;
+ }
+ if (curtab->tp_diffbuf[i] == NULL) {
+ continue;
+ }
+ diffline_change_T *change =
+ &((diffline_change_T *)dp->df_changes.ga_data)[dp->df_changes.ga_len - 1];
+ if (change->dc_start_lnum_off[i] != INT_MAX) {
+ added = false;
+ break;
+ }
+ }
+ }
+ return added;
+}
+
/// Check that line "lnum" is not close to a diff block, this line should
/// be in a fold.
///
@@ -3488,3 +4050,93 @@ static int xdiff_out(int start_a, int count_a, int start_b, int count_b, void *p
}));
return 0;
}
+
+/// "diff_filler()" function
+void f_diff_filler(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = MAX(0, diff_check(curwin, tv_get_lnum(argvars)));
+}
+
+/// "diff_hlID()" function
+void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ static linenr_T prev_lnum = 0;
+ static varnumber_T changedtick = 0;
+ static int fnum = 0;
+ static int prev_diff_flags = 0;
+ static int change_start = 0;
+ static int change_end = 0;
+ static hlf_T hlID = (hlf_T)0;
+
+ diffline_T diffline = { 0 };
+ // Remember the results if using simple since it's recalculated per
+ // call. Otherwise just call diff_find_change() every time since
+ // internally the result is cached interally.
+ const bool cache_results = !(diff_flags & ALL_INLINE_DIFF);
+
+ linenr_T lnum = tv_get_lnum(argvars);
+ if (lnum < 0) { // ignore type error in {lnum} arg
+ lnum = 0;
+ }
+ if (!cache_results
+ || lnum != prev_lnum
+ || changedtick != buf_get_changedtick(curbuf)
+ || fnum != curbuf->b_fnum
+ || diff_flags != prev_diff_flags) {
+ // New line, buffer, change: need to get the values.
+ int linestatus = 0;
+ int filler_lines = diff_check_with_linestatus(curwin, lnum, &linestatus);
+ if (filler_lines < 0 || linestatus < 0) {
+ if (filler_lines == -1 || linestatus == -1) {
+ change_start = MAXCOL;
+ change_end = -1;
+ if (diff_find_change(curwin, lnum, &diffline)) {
+ hlID = HLF_ADD; // added line
+ } else {
+ hlID = HLF_CHD; // changed line
+ if (diffline.num_changes > 0 && cache_results) {
+ change_start = diffline.changes[0].dc_start[diffline.bufidx];
+ change_end = diffline.changes[0].dc_end[diffline.bufidx];
+ }
+ }
+ } else {
+ hlID = HLF_ADD; // added line
+ }
+ } else {
+ hlID = (hlf_T)0;
+ }
+
+ if (cache_results) {
+ prev_lnum = lnum;
+ changedtick = buf_get_changedtick(curbuf);
+ fnum = curbuf->b_fnum;
+ prev_diff_flags = diff_flags;
+ }
+ }
+
+ if (hlID == HLF_CHD || hlID == HLF_TXD) {
+ int col = (int)tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}.
+ if (cache_results) {
+ if (col >= change_start && col < change_end) {
+ hlID = HLF_TXD; // Changed text.
+ } else {
+ hlID = HLF_CHD; // Changed line.
+ }
+ } else {
+ hlID = HLF_CHD;
+ for (int i = 0; i < diffline.num_changes; i++) {
+ bool added = diff_change_parse(&diffline, &diffline.changes[i],
+ &change_start, &change_end);
+ if (col >= change_start && col < change_end) {
+ hlID = added ? HLF_TXA : HLF_TXD;
+ break;
+ }
+ if (col < change_start) {
+ // the remaining changes are past this column and not relevant
+ break;
+ }
+ }
+ }
+ }
+ rettv->vval.v_number = hlID;
+}
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index 16e3a95121..43d9f67b5c 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -844,6 +844,18 @@ static void apply_cursorline_highlight(win_T *wp, winlinevars_T *wlv)
}
}
+static void set_line_attr_for_diff(win_T *wp, winlinevars_T *wlv)
+{
+ wlv->line_attr = win_hl_attr(wp, (int)wlv->diff_hlf);
+ // Overlay CursorLine onto diff-mode highlight.
+ if (wlv->cul_attr) {
+ wlv->line_attr = 0 != wlv->line_attr_lowprio // Low-priority CursorLine
+ ? hl_combine_attr(hl_combine_attr(wlv->cul_attr, wlv->line_attr),
+ hl_get_underline())
+ : hl_combine_attr(wlv->line_attr, wlv->cul_attr);
+ }
+}
+
/// Checks if there is more inline virtual text that need to be drawn.
static bool has_more_inline_virt(winlinevars_T *wlv, ptrdiff_t v)
{
@@ -1259,14 +1271,28 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
int linestatus = 0;
wlv.filler_lines = diff_check_with_linestatus(wp, lnum, &linestatus);
+ diffline_T line_changes = { 0 };
+ int change_index = -1;
if (wlv.filler_lines < 0 || linestatus < 0) {
if (wlv.filler_lines == -1 || linestatus == -1) {
- if (diff_find_change(wp, lnum, &change_start, &change_end)) {
- wlv.diff_hlf = HLF_ADD; // added line
- } else if (change_start == 0) {
- wlv.diff_hlf = HLF_TXD; // changed text
+ if (diff_find_change(wp, lnum, &line_changes)) {
+ wlv.diff_hlf = HLF_ADD; // added line
+ } else if (line_changes.num_changes > 0) {
+ bool added = diff_change_parse(&line_changes, &line_changes.changes[0],
+ &change_start, &change_end);
+ if (change_start == 0) {
+ if (added) {
+ wlv.diff_hlf = HLF_TXA; // added text on changed line
+ } else {
+ wlv.diff_hlf = HLF_TXD; // changed text on changed line
+ }
+ } else {
+ wlv.diff_hlf = HLF_CHD; // unchanged text on changed line
+ }
+ change_index = 0;
} else {
- wlv.diff_hlf = HLF_CHD; // changed line
+ wlv.diff_hlf = HLF_CHD; // changed line
+ change_index = 0;
}
} else {
wlv.diff_hlf = HLF_ADD; // added line
@@ -1846,24 +1872,32 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
}
if (wlv.diff_hlf != (hlf_T)0) {
+ if (line_changes.num_changes > 0
+ && change_index >= 0
+ && change_index < line_changes.num_changes - 1) {
+ if (ptr - line
+ >= line_changes.changes[change_index + 1].dc_start[line_changes.bufidx]) {
+ change_index += 1;
+ }
+ }
+ bool added = false;
+ if (line_changes.num_changes > 0 && change_index >= 0
+ && change_index < line_changes.num_changes) {
+ added = diff_change_parse(&line_changes, &line_changes.changes[change_index],
+ &change_start, &change_end);
+ }
// When there is extra text (eg: virtual text) it gets the
// diff highlighting for the line, but not for changed text.
if (wlv.diff_hlf == HLF_CHD && ptr - line >= change_start
&& wlv.n_extra == 0) {
- wlv.diff_hlf = HLF_TXD; // changed text
- }
- if (wlv.diff_hlf == HLF_TXD && ((ptr - line > change_end && wlv.n_extra == 0)
- || (wlv.n_extra > 0 && wlv.extra_for_extmark))) {
- wlv.diff_hlf = HLF_CHD; // changed line
+ wlv.diff_hlf = added ? HLF_TXA : HLF_TXD; // added/changed text
}
- wlv.line_attr = win_hl_attr(wp, (int)wlv.diff_hlf);
- // Overlay CursorLine onto diff-mode highlight.
- if (wlv.cul_attr) {
- wlv.line_attr = 0 != wlv.line_attr_lowprio // Low-priority CursorLine
- ? hl_combine_attr(hl_combine_attr(wlv.cul_attr, wlv.line_attr),
- hl_get_underline())
- : hl_combine_attr(wlv.line_attr, wlv.cul_attr);
+ if ((wlv.diff_hlf == HLF_TXD || wlv.diff_hlf == HLF_TXA)
+ && ((ptr - line >= change_end && wlv.n_extra == 0)
+ || (wlv.n_extra > 0 && wlv.extra_for_extmark))) {
+ wlv.diff_hlf = HLF_CHD; // changed line
}
+ set_line_attr_for_diff(wp, &wlv);
}
// Decide which of the highlight attributes to use.
@@ -2727,8 +2761,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
const int cuc_attr = win_hl_attr(wp, HLF_CUC);
const int mc_attr = win_hl_attr(wp, HLF_MC);
- if (wlv.diff_hlf == HLF_TXD) {
+ if (wlv.diff_hlf == HLF_TXD || wlv.diff_hlf == HLF_TXA) {
wlv.diff_hlf = HLF_CHD;
+ set_line_attr_for_diff(wp, &wlv);
}
const int diff_attr = wlv.diff_hlf != 0
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index 4c1b756ea1..2905d51657 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -1700,6 +1700,16 @@ static void win_update(win_T *wp)
}
}
+ // Below logic compares wp->w_topline against wp->w_lines[0].wl_lnum,
+ // which may point to a line below wp->w_topline if it is concealed;
+ // incurring scrolling even though wp->w_topline is still the same.
+ // Compare against an adjusted topline instead:
+ linenr_T topline_conceal = wp->w_topline;
+ while (decor_conceal_line(wp, topline_conceal - 1, false)) {
+ topline_conceal++;
+ hasFolding(wp, topline_conceal, NULL, &topline_conceal);
+ }
+
// If there are no changes on the screen that require a complete redraw,
// handle three cases:
// 1: we are off the top of the screen by a few lines: scroll down
@@ -1712,12 +1722,12 @@ static void win_update(win_T *wp)
if (mod_top != 0
&& wp->w_topline == mod_top
&& (!wp->w_lines[0].wl_valid
- || wp->w_topline == wp->w_lines[0].wl_lnum)) {
+ || topline_conceal == wp->w_lines[0].wl_lnum)) {
// w_topline is the first changed line and window is not scrolled,
// the scrolling from changed lines will be done further down.
} else if (wp->w_lines[0].wl_valid
- && (wp->w_topline < wp->w_lines[0].wl_lnum
- || (wp->w_topline == wp->w_lines[0].wl_lnum
+ && (topline_conceal < wp->w_lines[0].wl_lnum
+ || (topline_conceal == wp->w_lines[0].wl_lnum
&& wp->w_topfill > wp->w_old_topfill))) {
// New topline is above old topline: May scroll down.
int j;
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index f2001b6f6f..4eeba5e38d 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -2774,7 +2774,7 @@ char *get_last_insert_save(void)
char *s = xmemdupz(insert.data, insert.size);
if (insert.size > 0 && s[insert.size - 1] == ESC) { // remain trailing ESC
- s[insert.size - 1] = NUL;
+ s[--insert.size] = NUL;
}
return s;
}
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index ee5dd25e93..3f73074333 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -8530,7 +8530,7 @@ void script_host_eval(char *name, typval_T *argvars, typval_T *rettv)
typval_T eval_call_provider(char *provider, char *method, list_T *arguments, bool discard)
{
if (!eval_has_provider(provider, false)) {
- semsg("E319: No \"%s\" provider found. Run \":checkhealth provider\"",
+ semsg("E319: No \"%s\" provider found. Run \":checkhealth vim.provider\"",
provider);
return (typval_T){
.v_type = VAR_NUMBER,
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index edb77778a3..25252cdfde 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -34,7 +34,6 @@
#include "nvim/cmdexpand_defs.h"
#include "nvim/context.h"
#include "nvim/cursor.h"
-#include "nvim/diff.h"
#include "nvim/edit.h"
#include "nvim/errors.h"
#include "nvim/eval.h"
@@ -1300,63 +1299,6 @@ static void f_did_filetype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
rettv->vval.v_number = curbuf->b_did_filetype;
}
-/// "diff_filler()" function
-static void f_diff_filler(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_number = MAX(0, diff_check(curwin, tv_get_lnum(argvars)));
-}
-
-/// "diff_hlID()" function
-static void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- linenr_T lnum = tv_get_lnum(argvars);
- static linenr_T prev_lnum = 0;
- static varnumber_T changedtick = 0;
- static int fnum = 0;
- static int change_start = 0;
- static int change_end = 0;
- static hlf_T hlID = (hlf_T)0;
-
- if (lnum < 0) { // ignore type error in {lnum} arg
- lnum = 0;
- }
- if (lnum != prev_lnum
- || changedtick != buf_get_changedtick(curbuf)
- || fnum != curbuf->b_fnum) {
- // New line, buffer, change: need to get the values.
- int linestatus = 0;
- int filler_lines = diff_check_with_linestatus(curwin, lnum, &linestatus);
- if (filler_lines < 0 || linestatus < 0) {
- if (filler_lines == -1 || linestatus == -1) {
- change_start = MAXCOL;
- change_end = -1;
- if (diff_find_change(curwin, lnum, &change_start, &change_end)) {
- hlID = HLF_ADD; // added line
- } else {
- hlID = HLF_CHD; // changed line
- }
- } else {
- hlID = HLF_ADD; // added line
- }
- } else {
- hlID = (hlf_T)0;
- }
- prev_lnum = lnum;
- changedtick = buf_get_changedtick(curbuf);
- fnum = curbuf->b_fnum;
- }
-
- if (hlID == HLF_CHD || hlID == HLF_TXD) {
- int col = (int)tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}.
- if (col >= change_start && col <= change_end) {
- hlID = HLF_TXD; // Changed text.
- } else {
- hlID = HLF_CHD; // Changed line.
- }
- }
- rettv->vval.v_number = hlID;
-}
-
/// "empty({expr})" function
static void f_empty(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 6c5cca5d5c..746dc15c71 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -407,6 +407,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
int count = 0; // line number count
bool did_inc = false; // incremented RedrawingDisabled
int block_indent = -1; // indent for ext_cmdline block event
+ char *block_line = NULL; // block_line for ext_cmdline block event
int retval = OK;
cstack_T cstack = { // conditional stack
.cs_idx = -1,
@@ -577,7 +578,9 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
int indent = cstack.cs_idx < 0 ? 0 : (cstack.cs_idx + 1) * 2;
if (count >= 1 && getline_equal(fgetline, cookie, getexline)) {
if (ui_has(kUICmdline)) {
- ui_ext_cmdline_block_append((size_t)MAX(0, block_indent), last_cmdline);
+ char *line = block_line == last_cmdline ? "" : last_cmdline;
+ ui_ext_cmdline_block_append((size_t)MAX(0, block_indent), line);
+ block_line = last_cmdline;
block_indent = indent;
} else if (count == 1) {
// Need to set msg_didout for the first line after an ":if",
@@ -683,8 +686,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
// If the command was typed, remember it for the ':' register.
// Do this AFTER executing the command to make :@: work.
- if (getline_equal(fgetline, cookie, getexline)
- && new_last_cmdline != NULL) {
+ if (getline_equal(fgetline, cookie, getexline) && new_last_cmdline != NULL) {
xfree(last_cmdline);
last_cmdline = new_last_cmdline;
new_last_cmdline = NULL;
diff --git a/src/nvim/highlight.h b/src/nvim/highlight.h
index a89d778474..03853c2ddb 100644
--- a/src/nvim/highlight.h
+++ b/src/nvim/highlight.h
@@ -45,6 +45,7 @@ EXTERN const char *hlf_names[] INIT( = {
[HLF_CHD] = "DiffChange",
[HLF_DED] = "DiffDelete",
[HLF_TXD] = "DiffText",
+ [HLF_TXA] = "DiffTextAdd",
[HLF_SC] = "SignColumn",
[HLF_CONCEAL] = "Conceal",
[HLF_SPB] = "SpellBad",
diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h
index cbbc28311f..dba69fcf71 100644
--- a/src/nvim/highlight_defs.h
+++ b/src/nvim/highlight_defs.h
@@ -93,6 +93,7 @@ typedef enum {
HLF_CHD, ///< Changed diff line
HLF_DED, ///< Deleted diff line
HLF_TXD, ///< Text Changed in diff line
+ HLF_TXA, ///< Text Added in changed diff line
HLF_SC, ///< Sign column
HLF_CONCEAL, ///< Concealed text
HLF_SPB, ///< SpellBad
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index 901d2c84bc..2ecd3b9af7 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -159,6 +159,7 @@ static const char *highlight_init_both[] = {
"default link CursorIM Cursor",
"default link CursorLineFold FoldColumn",
"default link CursorLineSign SignColumn",
+ "default link DiffTextAdd DiffText",
"default link EndOfBuffer NonText",
"default link FloatBorder NormalFloat",
"default link FloatFooter FloatTitle",
diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c
index 9cb2321eee..778682a881 100644
--- a/src/nvim/keycodes.c
+++ b/src/nvim/keycodes.c
@@ -338,10 +338,7 @@ char *get_special_key_name(int c, int modifiers)
}
}
} else { // use name of special key
- const String *s = key_names_table[table_idx].pref_name != NULL
- ? key_names_table[table_idx].pref_name
- : &key_names_table[table_idx].name;
-
+ const String *s = &key_names_table[table_idx].name;
if ((int)s->size + idx + 2 <= MAX_KEY_NAME_LEN) {
STRCPY(string + idx, s->data);
idx += (int)s->size;
@@ -593,7 +590,7 @@ static int extract_modifiers(int key, int *modp, const bool simplify, bool *cons
int find_special_key_in_table(int c)
{
for (int i = 0; i < (int)ARRAY_SIZE(key_names_table); i++) {
- if (c == key_names_table[i].key) {
+ if (c == key_names_table[i].key && !key_names_table[i].is_alt) {
return i;
}
}
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index a346bf5963..5d3599920e 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -15,6 +15,8 @@
#include <tree_sitter/api.h>
#include <uv.h>
+#include "nvim/os/time.h"
+
#ifdef HAVE_WASMTIME
# include <wasm.h>
@@ -52,6 +54,11 @@ typedef struct {
TSTree *tree;
} TSLuaTree;
+typedef struct {
+ uint64_t parse_start_time;
+ uint64_t timeout_threshold_ns;
+} TSLuaParserCallbackPayload;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/treesitter.c.generated.h"
#endif
@@ -362,8 +369,6 @@ static struct luaL_Reg parser_meta[] = {
{ "reset", parser_reset },
{ "set_included_ranges", parser_set_ranges },
{ "included_ranges", parser_get_ranges },
- { "set_timeout", parser_set_timeout },
- { "timeout", parser_get_timeout },
{ "_set_logger", parser_set_logger },
{ "_logger", parser_get_logger },
{ NULL, NULL }
@@ -487,6 +492,13 @@ static void push_ranges(lua_State *L, const TSRange *ranges, const size_t length
}
}
+static bool on_parser_progress(TSParseState *state)
+{
+ TSLuaParserCallbackPayload *payload = state->payload;
+ uint64_t parse_time = os_hrtime() - payload->parse_start_time;
+ return parse_time >= payload->timeout_threshold_ns;
+}
+
static int parser_parse(lua_State *L)
{
TSParser *p = parser_check(L, 1);
@@ -524,7 +536,17 @@ static int parser_parse(lua_State *L)
}
input = (TSInput){ (void *)buf, input_cb, TSInputEncodingUTF8, NULL };
- new_tree = ts_parser_parse(p, old_tree, input);
+ if (!lua_isnil(L, 5)) {
+ uint64_t timeout_ns = (uint64_t)lua_tointeger(L, 5);
+ TSLuaParserCallbackPayload payload =
+ (TSLuaParserCallbackPayload){ .parse_start_time = os_hrtime(),
+ .timeout_threshold_ns = timeout_ns };
+ TSParseOptions parse_options = { .payload = &payload,
+ .progress_callback = on_parser_progress };
+ new_tree = ts_parser_parse_with_options(p, old_tree, input, parse_options);
+ } else {
+ new_tree = ts_parser_parse(p, old_tree, input);
+ }
break;
@@ -534,12 +556,11 @@ static int parser_parse(lua_State *L)
bool include_bytes = (lua_gettop(L) >= 4) && lua_toboolean(L, 4);
- // Sometimes parsing fails (timeout, or wrong parser ABI)
- // In those case, just return an error.
if (!new_tree) {
- if (ts_parser_timeout_micros(p) == 0) {
- // No timeout set, must have had an error
- return luaL_error(L, "An error occurred when parsing.");
+ // Sometimes parsing fails (no language was set, or it was set to one with an incompatible ABI)
+ // In those cases, just return an error.
+ if (!ts_parser_language(p)) {
+ return luaL_error(L, "Language was unset, or has an incompatible ABI.");
}
return 0;
}
@@ -670,26 +691,6 @@ static int parser_get_ranges(lua_State *L)
return 1;
}
-static int parser_set_timeout(lua_State *L)
-{
- TSParser *p = parser_check(L, 1);
-
- if (lua_gettop(L) < 2) {
- luaL_error(L, "integer expected");
- }
-
- uint32_t timeout = (uint32_t)luaL_checkinteger(L, 2);
- ts_parser_set_timeout_micros(p, timeout);
- return 0;
-}
-
-static int parser_get_timeout(lua_State *L)
-{
- TSParser *p = parser_check(L, 1);
- lua_pushinteger(L, (lua_Integer)ts_parser_timeout_micros(p));
- return 1;
-}
-
static void logger_cb(void *payload, TSLogType logtype, const char *s)
{
TSLuaLoggerOpts *opts = (TSLuaLoggerOpts *)payload;
diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c
index ab9d68f911..3dc66f7dba 100644
--- a/src/nvim/mapping.c
+++ b/src/nvim/mapping.c
@@ -2805,7 +2805,8 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
goto fail_and_free;
case 5:
api_set_error(err, kErrorTypeException,
- "E227: mapping already exists for %s", parsed_args.lhs);
+ is_abbrev ? e_abbreviation_already_exists_for_str
+ : e_mapping_already_exists_for_str, lhs.data);
goto fail_and_free;
default:
assert(false && "Unrecognized return code!");
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index b0ab235f6b..0003d88543 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -655,6 +655,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent)
bool in_winbar = (jump_flags & MOUSE_WINBAR);
bool in_statuscol = (jump_flags & MOUSE_STATUSCOL);
bool in_status_line = (jump_flags & IN_STATUS_LINE);
+ bool in_global_statusline = in_status_line && global_stl_height() > 0;
bool in_sep_line = (jump_flags & IN_SEP_LINE);
if ((in_winbar || in_status_line || in_statuscol) && is_click) {
@@ -671,7 +672,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent)
: in_winbar ? wp->w_winbar_click_defs
: wp->w_statuscol_click_defs;
- if (in_status_line && global_stl_height() > 0) {
+ if (in_global_statusline) {
// global statusline is displayed for the current window,
// and spans the whole screen.
click_defs = curwin->w_status_click_defs;
@@ -681,7 +682,11 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent)
if (in_statuscol && wp->w_p_rl) {
click_col = wp->w_width_inner - click_col - 1;
}
- if (in_statuscol && click_col >= (int)wp->w_statuscol_click_defs_size) {
+
+ if ((in_statuscol && click_col >= (int)wp->w_statuscol_click_defs_size)
+ || (in_status_line
+ && click_col >=
+ (int)(in_global_statusline ? curwin : wp)->w_status_click_defs_size)) {
return false;
}
diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h
index 2e5698870f..e624ab80ef 100644
--- a/src/nvim/option_vars.h
+++ b/src/nvim/option_vars.h
@@ -13,11 +13,11 @@
// option_vars.h: definition of global variables for settable options
#define HIGHLIGHT_INIT \
- "8:SpecialKey,~:EndOfBuffer,z:TermCursor,@:NonText,d:Directory,e:ErrorMsg," \
- "i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow," \
- "N:CursorLineNr,G:CursorLineSign,O:CursorLineFold,r:Question,s:StatusLine,S:StatusLineNC," \
- "c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg,W:WildMenu,f:Folded,F:FoldColumn," \
- "A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn,-:Conceal,B:SpellBad,P:SpellCap," \
+ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,@:NonText,d:Directory,e:ErrorMsg,i:IncSearch,l:Search," \
+ "y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow,N:CursorLineNr," \
+ "G:CursorLineSign,O:CursorLineFold,r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title," \
+ "v:Visual,V:VisualNOS,w:WarningMsg,W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange," \
+ "D:DiffDelete,T:DiffText,E:DiffTextAdd,>:SignColumn,-:Conceal,B:SpellBad,P:SpellCap," \
"R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,k:PmenuMatch,<:PmenuMatchSel,[:PmenuKind," \
"]:PmenuKindSel,{:PmenuExtra,}:PmenuExtraSel,x:PmenuSbar,X:PmenuThumb,*:TabLine,#:TabLineSel," \
"_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn,q:QuickFixLine,z:StatusLineTerm," \
@@ -306,6 +306,7 @@ EXTERN char *p_csl; ///< 'completeslash'
EXTERN OptInt p_pb; ///< 'pumblend'
EXTERN OptInt p_ph; ///< 'pumheight'
EXTERN OptInt p_pw; ///< 'pumwidth'
+EXTERN OptInt p_pmw; ///< 'pummaxwidth'
EXTERN char *p_com; ///< 'comments'
EXTERN char *p_cpo; ///< 'cpoptions'
EXTERN char *p_debug; ///< 'debug'
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index f566582c0c..f261abf30e 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -2188,7 +2188,7 @@ local options = {
{
abbreviation = 'dip',
cb = 'did_set_diffopt',
- defaults = 'internal,filler,closeoff,linematch:40',
+ defaults = 'internal,filler,closeoff,inline:simple,linematch:40',
-- Keep this in sync with diffopt_changed().
values = {
'filler',
@@ -2207,6 +2207,7 @@ local options = {
'internal',
'indent-heuristic',
{ 'algorithm:', { 'myers', 'minimal', 'patience', 'histogram' } },
+ { 'inline:', { 'none', 'simple', 'char', 'word' } },
'linematch:',
},
deny_duplicates = true,
@@ -2272,6 +2273,21 @@ local options = {
Use the indent heuristic for the internal
diff library.
+ inline:{text} Highlight inline differences within a change.
+ See |view-diffs|. Supported values are:
+
+ none Do not perform inline highlighting.
+ simple Highlight from first different
+ character to the last one in each
+ line. This is the default if no
+ `inline:` value is set.
+ char Use internal diff to perform a
+ character-wise diff and highlight the
+ difference.
+ word Use internal diff to perform a
+ |word|-wise diff and highlight the
+ difference.
+
internal Use the internal diff library. This is
ignored when 'diffexpr' is set. *E960*
When running out of memory when writing a
@@ -6410,6 +6426,21 @@ local options = {
varname = 'p_ph',
},
{
+ abbreviation = 'pmw',
+ defaults = 0,
+ desc = [=[
+ Maximum width for the popup menu (|ins-completion-menu|). When zero,
+ there is no maximum width limit, otherwise the popup menu will never be
+ wider than this value. Truncated text will be indicated by "..." at the
+ end. Takes precedence over 'pumwidth'.
+ ]=],
+ full_name = 'pummaxwidth',
+ scope = { 'global' },
+ short_desc = N_('maximum width of the popup menu'),
+ type = 'number',
+ varname = 'p_pmw',
+ },
+ {
abbreviation = 'pw',
defaults = 15,
desc = [=[
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c
index c6cc7af8cd..905656ccc9 100644
--- a/src/nvim/optionstr.c
+++ b/src/nvim/optionstr.c
@@ -1018,6 +1018,16 @@ int expand_set_diffopt(optexpand_T *args, int *numMatches, char ***matches)
numMatches,
matches);
}
+ // Within "inline:", we have a subgroup of possible options.
+ const size_t inline_len = strlen("inline:");
+ if (xp->xp_pattern - args->oe_set_arg >= (int)inline_len
+ && strncmp(xp->xp_pattern - inline_len, "inline:", inline_len) == 0) {
+ return expand_set_opt_string(args,
+ opt_dip_inline_values,
+ ARRAY_SIZE(opt_dip_inline_values) - 1,
+ numMatches,
+ matches);
+ }
return FAIL;
}
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index d60d0b3e55..e711611d67 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -1211,7 +1211,6 @@ static void read_input(StringBuilder *buf)
size_t lplen = (size_t)ml_get_len(lnum);
while (true) {
- lplen -= written;
if (lplen == 0) {
len = 0;
} else if (lp[written] == NL) {
@@ -1220,11 +1219,11 @@ static void read_input(StringBuilder *buf)
kv_push(*buf, NUL);
} else {
char *s = vim_strchr(lp + written, NL);
- len = s == NULL ? lplen : (size_t)(s - (lp + written));
+ len = s == NULL ? lplen - written : (size_t)(s - (lp + written));
kv_concat_len(*buf, lp + written, len);
}
- if (len == lplen) {
+ if (len == lplen - written) {
// Finished a line, add a NL, unless this line should not have one.
if (lnum != curbuf->b_op_end.lnum
|| (!curbuf->b_p_bin && curbuf->b_p_fixeol)
diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c
index b1c6e02449..2e5d08e5ae 100644
--- a/src/nvim/popupmenu.c
+++ b/src/nvim/popupmenu.c
@@ -199,6 +199,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
}
int def_width = (int)p_pw;
+ if (p_pmw > 0 && def_width > p_pmw) {
+ def_width = (int)p_pmw;
+ }
win_T *pvwin = NULL;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
@@ -307,6 +310,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
pum_compute_size();
int max_width = pum_base_width;
+ if (p_pmw > 0 && max_width > p_pmw) {
+ max_width = (int)p_pmw;
+ }
// if there are more items than room we need a scrollbar
if (pum_height < size) {
@@ -339,6 +345,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
if (pum_width > content_width && pum_width > p_pw) {
// Reduce width to fit item
pum_width = MAX(content_width, (int)p_pw);
+ if (p_pmw > 0 && pum_width > p_pmw) {
+ pum_width = (int)p_pmw;
+ }
} else if (((cursor_col - min_col > p_pw
|| cursor_col - min_col > max_width) && !pum_rl)
|| (pum_rl && (cursor_col < max_col - p_pw
@@ -365,6 +374,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
if (pum_width < p_pw) {
pum_width = (int)p_pw;
+ if (p_pmw > 0 && pum_width > p_pmw) {
+ pum_width = (int)p_pmw;
+ }
if (pum_rl) {
if (pum_width > pum_col - min_col) {
pum_width = pum_col - min_col;
@@ -376,6 +388,11 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
}
} else if (pum_width > content_width && pum_width > p_pw) {
pum_width = MAX(content_width, (int)p_pw);
+ if (p_pmw > 0 && pum_width > p_pmw) {
+ pum_width = (int)p_pmw;
+ }
+ } else if (p_pmw > 0 && pum_width > p_pmw) {
+ pum_width = (int)p_pmw;
}
}
} else if (max_col - min_col < def_width) {
@@ -386,11 +403,17 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
pum_col = min_col;
}
pum_width = max_col - min_col - 1;
+ if (p_pmw > 0 && pum_width > p_pmw) {
+ pum_width = (int)p_pmw;
+ }
} else {
if (max_width > p_pw) {
// truncate
max_width = (int)p_pw;
}
+ if (p_pmw > 0 && max_width > p_pmw) {
+ max_width = (int)p_pmw;
+ }
if (pum_rl) {
pum_col = min_col + max_width - 1;
} else {
@@ -611,6 +634,8 @@ void pum_redraw(void)
thumb_pos = (pum_first * (pum_height - thumb_height) + scroll_range / 2) / scroll_range;
}
+ const int ellipsis_width = 3;
+
for (int i = 0; i < pum_height; i++) {
int idx = i + pum_first;
const hlf_T *const hlfs = (idx == pum_selected) ? hlfsSel : hlfsNorm;
@@ -633,6 +658,7 @@ void pum_redraw(void)
// Do this 3 times and order from p_cia
int grid_col = col_off;
int totwidth = 0;
+ bool need_ellipsis = false;
int order[3];
int items_width_array[3] = { pum_base_width, pum_kind_width, pum_extra_width };
pum_align_order(order);
@@ -684,15 +710,19 @@ void pum_redraw(void)
if (pum_rl) {
char *rt = reverse_text(st);
char *rt_start = rt;
- int cells = vim_strsize(rt);
+ int cells = (int)mb_string2cells(rt);
+ if (p_pmw > ellipsis_width && pum_width == p_pmw
+ && grid_col - cells < col_off - pum_width) {
+ need_ellipsis = true;
+ }
- if (cells > pum_width) {
+ if (grid_col - cells < col_off - pum_width) {
do {
cells -= utf_ptr2cells(rt);
MB_PTR_ADV(rt);
- } while (cells > pum_width);
+ } while (grid_col - cells < col_off - pum_width);
- if (cells < pum_width) {
+ if (grid_col - cells > col_off - pum_width) {
// Most left character requires 2-cells but only 1 cell is available on
// screen. Put a '<' on the left of the pum item.
*(--rt) = '<';
@@ -710,10 +740,16 @@ void pum_redraw(void)
xfree(st);
grid_col -= width;
} else {
+ int cells = (int)mb_string2cells(st);
+ if (p_pmw > ellipsis_width && pum_width == p_pmw
+ && grid_col + cells > col_off + pum_width) {
+ need_ellipsis = true;
+ }
+
if (attrs == NULL) {
grid_line_puts(grid_col, st, -1, attr);
} else {
- pum_grid_puts_with_attrs(grid_col, vim_strsize(st), st, -1, attrs);
+ pum_grid_puts_with_attrs(grid_col, cells, st, -1, attrs);
}
xfree(st);
@@ -772,9 +808,24 @@ void pum_redraw(void)
}
if (pum_rl) {
- grid_line_fill(col_off - pum_width + 1, grid_col + 1, schar_from_ascii(' '), orig_attr);
+ const int lcol = col_off - pum_width + 1;
+ grid_line_fill(lcol, grid_col + 1, schar_from_ascii(' '), orig_attr);
+ if (need_ellipsis) {
+ bool over_wide = pum_width > ellipsis_width && linebuf_char[lcol + ellipsis_width] == NUL;
+ grid_line_fill(lcol, lcol + ellipsis_width, schar_from_ascii('.'), orig_attr);
+ if (over_wide) {
+ grid_line_put_schar(lcol + ellipsis_width, schar_from_ascii(' '), orig_attr);
+ }
+ }
} else {
- grid_line_fill(grid_col, col_off + pum_width, schar_from_ascii(' '), orig_attr);
+ const int rcol = col_off + pum_width;
+ grid_line_fill(grid_col, rcol, schar_from_ascii(' '), orig_attr);
+ if (need_ellipsis) {
+ if (pum_width > ellipsis_width && linebuf_char[rcol - ellipsis_width] == NUL) {
+ grid_line_put_schar(rcol - ellipsis_width - 1, schar_from_ascii(' '), orig_attr);
+ }
+ grid_line_fill(rcol - ellipsis_width, rcol, schar_from_ascii('.'), orig_attr);
+ }
}
if (pum_scrollbar > 0) {
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index de9a7e580f..7a8d963dee 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -367,6 +367,8 @@ static const char e_nfa_regexp_missing_value_in_chr[]
static const char e_atom_engine_must_be_at_start_of_pattern[]
= N_("E1281: Atom '\\%%#=%c' must be at the start of the pattern");
static const char e_substitute_nesting_too_deep[] = N_("E1290: substitute nesting too deep");
+static const char e_unicode_val_too_large[]
+ = N_("E1541: Value too large, max Unicode codepoint is U+10FFFF");
#define NOT_MULTI 0
#define MULTI_ONE 1
@@ -4796,6 +4798,11 @@ collection:
|| *regparse == 'u'
|| *regparse == 'U') {
startc = coll_get_char();
+ // max UTF-8 Codepoint is U+10FFFF,
+ // but allow values until INT_MAX
+ if (startc == INT_MAX) {
+ EMSG_RET_NULL(_(e_unicode_val_too_large));
+ }
if (startc == 0) {
regc(0x0a);
} else {
@@ -5548,12 +5555,15 @@ static int coll_get_char(void)
case 'U':
nr = gethexchrs(8); break;
}
- if (nr < 0 || nr > INT_MAX) {
+ if (nr < 0) {
// If getting the number fails be backwards compatible: the character
// is a backslash.
regparse--;
nr = '\\';
}
+ if (nr > INT_MAX) {
+ nr = INT_MAX;
+ }
return (int)nr;
}
@@ -10565,6 +10575,11 @@ collection:
|| *regparse == 'U') {
// TODO(RE): This needs more testing
startc = coll_get_char();
+ // max UTF-8 Codepoint is U+10FFFF,
+ // but allow values until INT_MAX
+ if (startc == INT_MAX) {
+ EMSG_RET_FAIL(_(e_unicode_val_too_large));
+ }
got_coll_char = true;
MB_PTR_BACK(old_regparse, regparse);
} else {
diff --git a/src/nvim/vterm/state.c b/src/nvim/vterm/state.c
index 0e43107347..3b7b5e2997 100644
--- a/src/nvim/vterm/state.c
+++ b/src/nvim/vterm/state.c
@@ -1948,28 +1948,26 @@ static int on_osc(int command, VTermStringFragment frag, void *user)
case 0:
settermprop_string(state, VTERM_PROP_ICONNAME, frag);
settermprop_string(state, VTERM_PROP_TITLE, frag);
- return 1;
+ break;
case 1:
settermprop_string(state, VTERM_PROP_ICONNAME, frag);
- return 1;
+ break;
case 2:
settermprop_string(state, VTERM_PROP_TITLE, frag);
- return 1;
+ break;
case 52:
if (state->selection.callbacks) {
osc_selection(state, frag);
}
+ break;
+ }
- return 1;
-
- default:
- if (state->fallbacks && state->fallbacks->osc) {
- if ((*state->fallbacks->osc)(command, frag, state->fbdata)) {
- return 1;
- }
+ if (state->fallbacks && state->fallbacks->osc) {
+ if ((*state->fallbacks->osc)(command, frag, state->fbdata)) {
+ return 1;
}
}
diff --git a/src/nvim/window.c b/src/nvim/window.c
index dd351a6af7..916c193469 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -196,6 +196,10 @@ win_T *swbuf_goto_win_with_buf(buf_T *buf)
return wp;
}
+// 'cmdheight' value explicitly set by the user: window commands are allowed to
+// resize the topframe to values higher than this minimum, but not lower.
+static OptInt min_set_ch = 1;
+
/// all CTRL-W window commands are handled here, called from normal_cmd().
///
/// @param xchar extra char from ":wincmd gx" or NUL
@@ -513,7 +517,7 @@ newwindow:
// set current window height
case Ctrl__:
case '_':
- win_setheight(Prenum ? Prenum : Rows - 1);
+ win_setheight(Prenum ? Prenum : Rows - (int)min_set_ch);
break;
// increase current window width
@@ -3505,10 +3509,6 @@ static bool is_bottom_win(win_T *wp)
return true;
}
-// 'cmdheight' value explicitly set by the user: window commands are allowed to
-// resize the topframe to values higher than this minimum, but not lower.
-static OptInt min_set_ch = 1;
-
/// Set a new height for a frame. Recursively sets the height for contained
/// frames and windows. Caller must take care of positions.
///