aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/buffer_defs.h52
-rw-r--r--src/nvim/change.c1
-rw-r--r--src/nvim/diff.c719
-rw-r--r--src/nvim/drawline.c71
-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/option_vars.h10
-rw-r--r--src/nvim/options.lua18
-rw-r--r--src/nvim/optionstr.c10
10 files changed, 788 insertions, 96 deletions
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index c4241eed45..be28e08675 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 paste 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 4c5b86adc4..585a937558 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -87,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;
@@ -137,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
@@ -523,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;
@@ -533,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;
@@ -764,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;
}
@@ -944,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);
@@ -1640,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
@@ -1662,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;
}
@@ -1683,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 {
@@ -1717,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;
@@ -1735,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) {
@@ -1789,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;
}
@@ -2532,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);
@@ -2604,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;
}
+}
- if ((dp == NULL) || (diff_check_sanity(curtab, dp) == FAIL)) {
- xfree(line_org);
+/// used for simple inline diff algorithm
+static diffline_change_T simple_diffline_change;
+
+/// 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;
@@ -2660,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
@@ -2738,6 +2840,470 @@ 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;
+ }
+
+ if (lnum - dp->df_lnum[idx] > INT_MAX) {
+ // Integer overflow protection
+ 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.
+ idx = diff_buf_idx(wp->w_buffer, curtab);
+ 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.
///
@@ -3499,20 +4065,29 @@ void f_diff_filler(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "diff_hlID()" function
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 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 (lnum != prev_lnum
+ if (!cache_results
+ || lnum != prev_lnum
|| changedtick != buf_get_changedtick(curbuf)
- || fnum != curbuf->b_fnum) {
+ || 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);
@@ -3520,10 +4095,14 @@ void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (filler_lines == -1 || linestatus == -1) {
change_start = MAXCOL;
change_end = -1;
- if (diff_find_change(curwin, lnum, &change_start, &change_end)) {
+ 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
@@ -3531,17 +4110,37 @@ void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
} else {
hlID = (hlf_T)0;
}
- prev_lnum = lnum;
- changedtick = buf_get_changedtick(curbuf);
- fnum = curbuf->b_fnum;
+
+ 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 (col >= change_start && col <= change_end) {
- hlID = HLF_TXD; // Changed text.
+ 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; // Changed line.
+ 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/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/option_vars.h b/src/nvim/option_vars.h
index 2e5698870f..0b5d0a45b4 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," \
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index f566582c0c..4a11078b80 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 nothing
+ 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
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;
}