Compare commits

...

28 Commits

Author SHA1 Message Date
Jonathon
493804eece
Merge 931b3f0693 into deac7df80a 2024-09-12 12:17:00 -04:00
jonathon
931b3f0693 fix: memory leak and out of bounds access fixes 2024-08-29 10:20:06 -04:00
Jonathon
e78ed6f347 remove redundant cast to int 2024-08-07 23:05:45 -04:00
Jonathon
66df7c3413 changes from make format 2024-08-07 23:05:04 -04:00
Jonathon
66715e9ce3 explicit cast to size_t 2024-08-03 08:29:07 -04:00
Jonathon
9ebcc1131b remove int to size t comparison 2024-08-03 08:29:07 -04:00
Jonathon
8985e9292b explicit type conversion 2024-08-03 08:29:07 -04:00
Jonathon
0e110d180f remove warning 2024-08-03 08:29:07 -04:00
Jonathon
7d07185d99 remove trailing white space from lua test 2024-08-03 08:29:06 -04:00
Jonathon
729fa8ea7d refractoring, and commenting 2024-08-03 08:29:06 -04:00
Jonathon
eae703b044 free iwhite_index_offset, and remove initialization because it's unecessary 2024-08-03 08:29:06 -04:00
Jonathon
867ee3a892 free decisions (fix memory leak) 2024-08-03 08:29:06 -04:00
Jonathon
65f6a6b76b include a test for linematch, include ignore white processing for all
algorithms in same place
2024-08-03 08:29:06 -04:00
Jonathon
209fc88c8f add functional tests for utf-8 characters 2024-08-03 08:29:06 -04:00
Jonathon
9247062387 fix type errors 2024-08-03 08:29:06 -04:00
Jonathon
d9a3363896 handle utf-8 characters as tokens consisting of multiple characters 2024-08-03 08:29:06 -04:00
Jonathon
0371a006d0 fix linter errors 2024-08-03 08:29:06 -04:00
Jonathon
34cf5a3fee test fix 2024-08-03 08:29:06 -04:00
Jonathon
f40e576ebc build fix 2024-08-03 08:29:06 -04:00
Jonathon
e76b0f2990 remove false from ml_get_buf 2024-08-03 08:29:06 -04:00
Jonathon
d21977db34 move the -1 to outside function call 2024-08-03 08:29:06 -04:00
Jonathon
b3119cba2b tests with ignore white 2024-08-03 08:29:06 -04:00
Jonathon
027ee9ebed remove beforeeach 2024-08-03 08:29:06 -04:00
Jonathon
17e1d68529 3 tests working for word match 2024-08-03 08:29:06 -04:00
Jonathon
95c2958324 add test for ignore whitespace 2024-08-03 08:29:06 -04:00
Jonathon
bcd5311279 test for iwhite 2024-08-03 08:29:06 -04:00
Jonathon
7e63013bd3 add unit tests for charmatch 2024-08-03 08:29:06 -04:00
Jonathon
0db7e59325 charmatch / wordmatch - cross line character-wise diff highlighting
and comparison grouping optimization
2024-08-03 08:28:47 -04:00
7 changed files with 1015 additions and 84 deletions

View File

@ -763,6 +763,8 @@ 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?
size_t n_charmatch;
int *charmatchp; // values for charmatch
};
#define SNAP_HELP_IDX 0

View File

@ -10,6 +10,7 @@
#include <assert.h>
#include <ctype.h>
#include <inttypes.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@ -84,11 +85,15 @@ 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_CHARDIFF 0x2000 // character-wise matching
#define DIFF_WORDDIFF 0x4000 // character-wise matching
#define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL)
static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF;
static int diff_algorithm = 0;
static int linematch_lines = 0;
static int chardiff_chars = 0;
static int worddiff_words = 0;
#define LBUFLEN 50 // length of line in diff file
@ -130,6 +135,11 @@ typedef enum {
DIFF_NONE,
} diffstyle_T;
typedef enum {
LINEMATCH,
CHARMATCH,
WORDMATCH,
} diff_allignment_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "diff.c.generated.h"
#endif
@ -522,6 +532,7 @@ static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp)
{
diff_T *dnew = xmalloc(sizeof(*dnew));
dnew->charmatchp = NULL;
dnew->is_linematched = false;
dnew->df_next = dp;
if (dprev == NULL) {
@ -536,6 +547,7 @@ static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp)
static diff_T *diff_free(tabpage_T *tp, diff_T *dprev, diff_T *dp)
{
diff_T *ret = dp->df_next;
xfree(dp->charmatchp);
xfree(dp);
if (dprev == NULL) {
@ -1647,6 +1659,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->charmatchp);
xfree(dn);
dn = dpl;
}
@ -1753,11 +1766,18 @@ 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->charmatchp);
xfree(p);
}
tp->tp_first_diff = NULL;
}
/// Return true if char diff option is enabled.
bool chardiff(void)
{
return (diff_flags & DIFF_CHARDIFF) || (diff_flags & DIFF_WORDDIFF);
}
/// Return true if the options are set to use diff linematch.
bool diff_linematch(diff_T *dp)
{
@ -2001,13 +2021,23 @@ static void apply_linematch_results(diff_T *dp, size_t decisions_length, const i
dp->is_linematched = true;
}
static void run_linematch_algorithm(diff_T *dp)
static void run_alignment_algorithm(diff_T *dp, diff_allignment_T diff_allignment)
{
// define buffers for diff algorithm
mmfile_t diffbufs_mm[DB_COUNT];
const char *diffbufs[DB_COUNT];
int diff_length[DB_COUNT];
mmfile_t diffbufs_mm[DB_COUNT] = { 0 };
char *diffbufs[DB_COUNT] = { 0 };
int diff_length[DB_COUNT] = { 0 };
int diff_lines[DB_COUNT] = { 0 };
size_t ndiffs = 0;
size_t total_word_count = 0;
size_t total_chars_length = 0;
size_t *word_offset_size[DB_COUNT] = { 0 }; // mapping array used for charmatch
size_t *word_offset[DB_COUNT] = { 0 }; // mapping array used for charmatch
size_t word_offset_result_index[DB_COUNT] = { 0 }; // mapping array used for charmatch
size_t *iwhite_index_offset = NULL; // mapping array used for charmatch
size_t result_diff_start_pos[DB_COUNT]; // the position in the result array where this
// an array for index mapping with iwhite
const bool iwhite = (diff_flags & (DIFF_IWHITEALL | DIFF_IWHITE)) > 0;
for (int i = 0; i < DB_COUNT; i++) {
if (curtab->tp_diffbuf[i] != NULL) {
// write the contents of the entire buffer to
@ -2019,28 +2049,216 @@ static void run_linematch_algorithm(diff_T *dp)
// we add it to the array of char*, diffbufs
diffbufs[ndiffs] = diffbufs_mm[ndiffs].ptr;
// keep track of the length of this diff block to pass it to the linematch
// algorithm
diff_length[ndiffs] = dp->df_count[i];
diff_lines[ndiffs] = dp->df_count[i];
if (diff_allignment == CHARMATCH || diff_allignment == WORDMATCH) {
// before removing whitespace for charmatch
result_diff_start_pos[ndiffs] = total_chars_length;
// get the length of each of the diffs
int lines = dp->df_count[i];
const char *p = diffbufs[ndiffs];
while (lines) {
total_chars_length++; // increment the total characters counter
if (*p == '\n') {
lines--;
}
p++;
}
} else if (diff_allignment == LINEMATCH) {
// LINEMATCH
// keep track of the length of this diff block to pass it to the linematch
// algorithm
diff_length[ndiffs] = dp->df_count[i];
}
// increment the amount of diff buffers we are passing to the algorithm
ndiffs++;
}
}
// we will get the output of the linematch algorithm in the format of an array
// of integers (*decisions) and the length of that array (decisions_length)
int *decisions = NULL;
const bool iwhite = (diff_flags & (DIFF_IWHITEALL | DIFF_IWHITE)) > 0;
size_t decisions_length = linematch_nbuffers(diffbufs, diff_length, ndiffs, &decisions, iwhite);
// allocate all the memory we will need to keep track of tokens positions and their respective
// lengths. For word matching, this is the 'word' as vim defines it, for character matching, the
// token is the one or more 8 bit 'chars' that make up a utf character
if (diff_allignment == WORDMATCH || diff_allignment == CHARMATCH) {
// are we ignoring whitespace in the comparison?
if (iwhite) {
// allocate array for index mapping of result array
iwhite_index_offset = xmalloc(total_chars_length * sizeof(size_t));
}
for (size_t i = 0; i < ndiffs; i++) {
word_offset[i] = xmalloc(total_chars_length * sizeof(size_t));
word_offset_size[i] = xmalloc(total_chars_length * sizeof(size_t));
for (size_t j = 0; j < total_chars_length; j++) {
word_offset_size[i][j] = 0;
}
}
}
// calculate the token lengths and white space offset and pre process the contents of the diffs to
// remove white space if necessary
for (size_t i = 0; i < ndiffs; i++) {
XFREE_CLEAR(diffbufs_mm[i].ptr);
int cls = INT_MIN; // keep track of what type of character this is, to determine when we are
// moving to a different word
size_t j = 0; // j will iterate over each character in each of the diffs
size_t k = 0; // k represents the index of the current character if there were no white spaces,
// so we will use k and j to calculate the white space offset and use it later to
// populate the final results for drawing to the screen
// if 'iwhite' is not used, k will always be the same as j
size_t lines = (size_t)diff_lines[i]; // we iterate over each line of this part of the diff
size_t w = result_diff_start_pos[i]; // keep track of the offset of all the characters without
// any whitespace, so that we can ignore the white space
// while calculating the diff, and then use this to
// populate the results
size_t cur_char_length = 0;
while (lines > 0) {
if (iwhite && (diffbufs[i][j] == ' ' || diffbufs[i][j] == '\t')) {
// we are using 'iwhite' and this is a whitespace, so it will not be included as a token in
// the diff algorithm
// we are ignoring whitespace and this is a whitespace ' ' or '\t' reset the class definition
cls = INT_MIN;
} else {
// we have a character which is not a blank (or we are not using iwhite)
// how we determine when there is a new token depends on if this is chardiff or worddiff
if (diff_allignment == WORDMATCH) {
// WORDMATCH
if (utf_class(diffbufs[i][j]) != cls || diffbufs[i][j] == '\n') {
// this is a new token
word_offset[i][diff_length[i]] = k; // mark the offset of this without whitespace
diff_length[i]++; // this diff length has another token, so it gets longer
total_word_count++;
}
cls = utf_class(diffbufs[i][j]);
word_offset_size[i][diff_length[i] - 1]++; // still the same class (iterating over the
// same type of word), so the current word
// length is getting longer
} else if (diff_allignment == CHARMATCH) {
// CHARMATCH
if (cur_char_length == 0) {
// get the length of current character
if (diffbufs[i][j] == '\n') {
// this is the last character of the line
cur_char_length = 1;
} else {
cur_char_length = (size_t)utfc_ptr2len((const char *const)&diffbufs[i][j]);
}
word_offset[i][diff_length[i]] = k;
diff_length[i]++;
total_word_count++;
// the token size is the length of this utf character
word_offset_size[i][diff_length[i] - 1] = cur_char_length;
}
cur_char_length--;
}
// if ignoring whitespace, keep track of the white space index
if (iwhite && (diff_allignment == CHARMATCH || diff_allignment == WORDMATCH)) {
// keep track of the index offset with ignoring whitespace to use when populating the results
iwhite_index_offset[w++] = j - k;
}
diffbufs[i][k++] = diffbufs[i][j];
}
if (diffbufs[i][j++] == '\n') {
lines--;
}
}
}
apply_linematch_results(dp, decisions_length, decisions);
// we will get the output of the linematch algorithm in the format of an array
// of integers (*decisions) and the length of that array (decisions_length)
if (diff_allignment == LINEMATCH) {
int *decisions = NULL;
size_t decisions_length = linematch_nbuffers((const char **)diffbufs, diff_length, ndiffs,
&decisions, 0, NULL, NULL);
apply_linematch_results(dp, decisions_length, decisions);
xfree(decisions);
} else if (diff_allignment == CHARMATCH || diff_allignment == WORDMATCH) {
dp->charmatchp = xmalloc(total_chars_length * sizeof(int)); // will hold results
dp->n_charmatch = total_chars_length;
xfree(decisions);
bool lim_exceeded = false;
if (diff_allignment == CHARMATCH && total_chars_length > (size_t)chardiff_chars) {
lim_exceeded = true;
} else if (diff_allignment == WORDMATCH && total_word_count > (size_t)worddiff_words) {
lim_exceeded = true;
}
if (lim_exceeded == true) {
// do not run charmatch on the entire diff block
// we will attempt to run charmatch on the individual lines later
// for now, just initialize the result memory
for (size_t i = 0; i < total_chars_length; i++) {
dp->charmatchp[i] = -1; // -1 indicates that algorithm has not yet ran
}
} else {
for (size_t i = 0; i < total_chars_length; i++) {
dp->charmatchp[i] = 0; // default to not highlighted
}
// check is this a line that does not exist in other buffers?
// if so, highlight it as a 'newline', and we don't need to run the algorithm
bool newline = true;
for (size_t i = 0, c = 0; i < ndiffs; i++) {
if (diff_length[i] > 0) {
c++;
}
if (c > 1) {
newline = false;
break;
}
}
if (newline == true) {
for (size_t i = 0; i < total_chars_length; i++) {
dp->charmatchp[i] = 2;
}
} else {
int *decisions = NULL;
size_t decisions_length = linematch_nbuffers((const char **)diffbufs, diff_length, ndiffs,
&decisions, 1, word_offset, word_offset_size);
for (size_t i = 0; i < decisions_length; i++) {
if (decisions[i] == (pow(2, (double)ndiffs) - 1)) {
// it's a comparison of all the buffers (don't highlight)
for (size_t j = 0; j < ndiffs; j++) {
for (size_t k = 0;
k <
(diff_allignment ==
WORDMATCH ? word_offset_size[j][word_offset_result_index[j]] : 1); k++) {
size_t l = result_diff_start_pos[j]++;
dp->charmatchp[iwhite_index_offset ? iwhite_index_offset[l] + l : l] = 0;
}
word_offset_result_index[j]++;
}
} else {
// it's a skip in a single buffer (highlight as changed)
for (size_t j = 0; j < ndiffs; j++) {
if (decisions[i] & (1 << j)) {
for (size_t k = 0;
k <
(diff_allignment ==
WORDMATCH ? word_offset_size[j][word_offset_result_index[j]] : 1); k++) {
size_t l = result_diff_start_pos[j]++;
dp->charmatchp[iwhite_index_offset ? iwhite_index_offset[l] + l : l] = 1;
}
word_offset_result_index[j]++;
break;
}
}
}
}
xfree(decisions);
}
}
}
for (size_t i = 0; i < ndiffs; i++) {
xfree(word_offset[i]);
xfree(word_offset_size[i]);
XFREE_CLEAR(diffbufs_mm[i].ptr);
}
xfree(iwhite_index_offset);
}
/// Check diff status for line "lnum" in buffer "buf":
@ -2105,7 +2323,7 @@ int diff_check_with_linestatus(win_T *wp, linenr_T lnum, int *linestatus)
// above the screen.
if (lnum >= wp->w_topline && lnum < wp->w_botline
&& !dp->is_linematched && diff_linematch(dp)) {
run_linematch_algorithm(dp);
run_alignment_algorithm(dp, LINEMATCH);
}
if (dp->is_linematched) {
@ -2412,6 +2630,8 @@ int diffopt_changed(void)
{
int diff_context_new = 6;
int linematch_lines_new = 0;
int chardiff_chars_new = 0;
int worddiff_words_new = 0;
int diff_flags_new = 0;
int diff_foldcolumn_new = 2;
int diff_algorithm_new = 0;
@ -2487,6 +2707,14 @@ int diffopt_changed(void)
p += 10;
linematch_lines_new = getdigits_int(&p, false, linematch_lines_new);
diff_flags_new |= DIFF_LINEMATCH;
} else if ((strncmp(p, "chardiff:", 9) == 0) && ascii_isdigit(p[9])) {
p += 9;
chardiff_chars_new = getdigits_int(&p, false, chardiff_chars_new);
diff_flags_new |= DIFF_CHARDIFF;
} else if ((strncmp(p, "worddiff:", 9) == 0) && ascii_isdigit(p[9])) {
p += 9;
worddiff_words_new = getdigits_int(&p, false, worddiff_words_new);
diff_flags_new |= DIFF_WORDDIFF;
}
if ((*p != ',') && (*p != NUL)) {
@ -2516,6 +2744,8 @@ int diffopt_changed(void)
diff_flags = diff_flags_new;
diff_context = diff_context_new == 0 ? 1 : diff_context_new;
linematch_lines = linematch_lines_new;
chardiff_chars = chardiff_chars_new;
worddiff_words = worddiff_words_new;
diff_foldcolumn = diff_foldcolumn_new;
diff_algorithm = diff_algorithm_new;
@ -2563,7 +2793,8 @@ bool diffopt_filler(void)
/// @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)
bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp, int **hlresult,
bool *diffchars_lim_exceeded, size_t *diffchars_line_len)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
// Make a copy of the line, the next ml_get() will invalidate it.
@ -2604,6 +2835,97 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp)
bool added = true;
linenr_T off = lnum - dp->df_lnum[idx];
if (chardiff()) {
diff_allignment_T diff_allignment;
if (diff_flags & DIFF_CHARDIFF) {
// if both chardiff & worddiff are enabled, it will pick chardiff
diff_allignment = CHARMATCH;
} else if (diff_flags & DIFF_WORDDIFF) {
diff_allignment = WORDMATCH;
}
if (dp->charmatchp == NULL) {
// get the first buffers
// try running on the whole diff buffer first
run_alignment_algorithm(dp, diff_allignment);
}
size_t charcount = 0;
for (int i = 0; i < DB_COUNT; i++) {
// for each diff buffer
if (curtab->tp_diffbuf[i] != NULL) {
for (int j = 0; j < dp->df_count[i]; j++) {
// for each line in that buffer
// get a pointer to the line
char *diffline = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + j);
while (*diffline != '\0') {
diffline++; charcount++;
}
charcount++;
}
}
}
if (dp->n_charmatch != charcount) {
// we need to re run if the length of the diff has changed
// count the number of characters in this diff
// the line is currently being edited in insert mode, so pause highlighting until the diff is
// recalculated, then resume the charmatch highlighting
(*hlresult) = NULL;
} else {
// charmatchp is not null, is the whole thing already diffed?
// get the correct offset for hlresult
//
// if the character count is not null
size_t hlresult_line_offset = 0;
// get the offset for the highlight of this line
*diffchars_line_len = strlen(ml_get_buf(curtab->tp_diffbuf[idx], dp->df_lnum[idx] + off));
hlresult_line_offset = get_buffer_position(idx, dp, off);
if (*(dp->charmatchp + hlresult_line_offset) == -1) {
diff_T dp_tmp;
for (int i = 0; i < DB_COUNT; i++) {
if (curtab->tp_diffbuf[i] != NULL) {
dp_tmp.df_lnum[i] = dp->df_lnum[i] + off;
dp_tmp.df_count[i] = off < dp->df_count[i] ? 1 : 0;
}
}
// this line has not yet been calculated
// run charmatch on this line of the diff
// figure out how many buffers we are diffing
// what line number is this in each buffer?
run_alignment_algorithm(&dp_tmp, diff_allignment);
if (dp_tmp.n_charmatch > 0) {
for (int i = 0, p = 0; i < DB_COUNT; i++) {
if (curtab->tp_diffbuf[i] != NULL) {
// get the offset in the original charmatchp
if (off < dp->df_count[i]) {
size_t length = strlen(ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + off)) + 1;
size_t k = get_buffer_position(i, dp, off);
for (size_t m = 0; m < length; m++) {
int val = dp_tmp.charmatchp[p++];
dp->charmatchp[k + m] = val == -1 ? -2 : val; // if this individual line is still
// too long to diff, mark it as a
// -2, meaning it's been attempted
// already
}
}
}
}
// extract the results from here
}
xfree(dp_tmp.charmatchp);
}
(*hlresult) = dp->charmatchp + hlresult_line_offset;
}
if ((*hlresult) == NULL) {
xfree(line_org);
return false;
} else if ((*hlresult)[0] != -2) { // -2 indicates that we've attempted a character wise diff with the
xfree(line_org);
return false; // entire block, and with this individual line, and still exceeded
} else { // the character limit
//
*diffchars_lim_exceeded = true; // go to the default highlighting behaviour without character
} // wise matching
}
for (int i = 0; i < DB_COUNT; i++) {
if ((curtab->tp_diffbuf[i] != NULL) && (i != idx)) {
// Skip lines that are not in the other change (filler lines).
@ -3440,3 +3762,25 @@ static int xdiff_out(int start_a, int count_a, int start_b, int count_b, void *p
}));
return 0;
}
// get the position in the character diff buffer of this line
static size_t get_buffer_position(const int idx, const diff_T *dp, linenr_T offset)
{
size_t comparison_mem_offset = 0;
for (int i = 0; i < DB_COUNT; i++) {
if ((curtab->tp_diffbuf[i] != NULL)) {
for (int j = 0; j < ((i == idx) ? offset : dp->df_count[i]); j++) {
char *diffline = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + j);
while (*diffline != '\0') {
diffline++; comparison_mem_offset++;
}
comparison_mem_offset++; // count the '\0' character as the newline marker for each line
}
if (i == idx) {
break;
}
}
}
// what is the line length for this pointer?
return comparison_mem_offset;
}

View File

@ -1137,10 +1137,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
int bg_attr = win_bg_attr(wp);
int linestatus = 0;
bool diffchars_lim_exceeded = false;
size_t diffchars_line_len = 0;
int *hlresult = NULL;
wlv.filler_lines = diff_check_with_linestatus(wp, lnum, &linestatus);
if (wlv.filler_lines < 0 || linestatus < 0) {
if (wlv.filler_lines == -1 || linestatus == -1) {
if (diff_find_change(wp, lnum, &change_start, &change_end)) {
if (diff_find_change(wp, lnum, &change_start, &change_end, &hlresult,
&diffchars_lim_exceeded, &diffchars_line_len)) {
wlv.diff_hlf = HLF_ADD; // added line
} else if (change_start == 0) {
wlv.diff_hlf = HLF_TXD; // changed text
@ -1721,16 +1725,32 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
}
if (wlv.diff_hlf != (hlf_T)0) {
// 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
if (chardiff() && !diffchars_lim_exceeded) {
if (wlv.diff_hlf != HLF_ADD) {
if (hlresult == NULL) {
wlv.diff_hlf = HLF_CHD;
} else if (hlresult[0] == 2) {
wlv.diff_hlf = HLF_ADD;
} else if ((size_t)(ptr - line) < diffchars_line_len
&& (hlresult[ptr - line] == 1 || hlresult[ptr - line] == -2)) {
wlv.diff_hlf = HLF_TXD;
} else {
wlv.diff_hlf = HLF_CHD;
}
}
} else {
// 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.line_attr = win_hl_attr(wp, (int)wlv.diff_hlf);
// Overlay CursorLine onto diff-mode highlight.
if (wlv.cul_attr) {

View File

@ -1304,6 +1304,9 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
static int change_start = 0;
static int change_end = 0;
static hlf_T hlID = (hlf_T)0;
bool diffchars_lim_exceeded = false;
size_t diffchars_line_len = 0;
int *hlresult = NULL;
if (lnum < 0) { // ignore type error in {lnum} arg
lnum = 0;
@ -1318,7 +1321,8 @@ static 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, &change_start, &change_end, &hlresult,
&diffchars_lim_exceeded, &diffchars_line_len)) {
hlID = HLF_ADD; // added line
} else {
hlID = HLF_CHD; // changed line
@ -1336,10 +1340,26 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
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 (chardiff() && !diffchars_lim_exceeded) {
if (hlID != HLF_ADD) {
if (hlresult == NULL) {
hlID = HLF_CHD;
} else if (hlresult[0] == 2) {
hlID = HLF_ADD;
} else if ((size_t)col < diffchars_line_len
&& (hlresult[col] == 1 || hlresult[col] == -2)) {
hlID = HLF_TXD;
} else {
hlID = HLF_CHD;
}
}
} else {
hlID = HLF_CHD; // Changed line.
if (col >= change_start && col <= change_end) {
hlID = HLF_TXD; // Changed text.
} else {
hlID = HLF_CHD; // Changed line.
}
}
}
rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (hlID + 1);

View File

@ -38,38 +38,6 @@ static size_t line_len(const char *s)
return strlen(s);
}
/// Same as matching_chars but ignore whitespace
///
/// @param s1
/// @param s2
static int matching_chars_iwhite(const char *s1, const char *s2)
{
// the newly processed strings that will be compared
// delete the white space characters, and/or replace all upper case with lower
char *strsproc[2];
const char *strsorig[2] = { s1, s2 };
for (int k = 0; k < 2; k++) {
size_t d = 0;
size_t i = 0;
size_t slen = line_len(strsorig[k]);
strsproc[k] = xmalloc((slen + 1) * sizeof(char));
while (d + i < slen) {
char e = strsorig[k][i + d];
if (e != ' ' && e != '\t') {
strsproc[k][i] = e;
i++;
} else {
d++;
}
}
strsproc[k][i] = NUL;
}
int matching = matching_chars(strsproc[0], strsproc[1]);
xfree(strsproc[0]);
xfree(strsproc[1]);
return matching;
}
#define MATCH_CHAR_MAX_LEN 800
/// Return matching characters between "s1" and "s2" whilst respecting sequence order.
@ -119,7 +87,7 @@ static int matching_chars(const char *s1, const char *s2)
/// @param sp
/// @param fomvals
/// @param n
static int count_n_matched_chars(const char **sp, const size_t n, bool iwhite)
static int count_n_matched_chars(const char **sp, const size_t n)
{
int matched_chars = 0;
int matched = 0;
@ -127,9 +95,7 @@ static int count_n_matched_chars(const char **sp, const size_t n, bool iwhite)
for (size_t j = i + 1; j < n; j++) {
if (sp[i] != NULL && sp[j] != NULL) {
matched++;
// TODO(lewis6991): handle whitespace ignoring higher up in the stack
matched_chars += iwhite ? matching_chars_iwhite(sp[i], sp[j])
: matching_chars(sp[i], sp[j]);
matched_chars += matching_chars(sp[i], sp[j]);
}
}
}
@ -145,7 +111,7 @@ static int count_n_matched_chars(const char **sp, const size_t n, bool iwhite)
void fastforward_buf_to_lnum(const char **s, linenr_T lnum)
{
for (int i = 0; i < lnum - 1; i++) {
for (long i = 0; i < lnum; i++) {
*s = strchr(*s, '\n');
if (!*s) {
return;
@ -168,20 +134,32 @@ void fastforward_buf_to_lnum(const char **s, linenr_T lnum)
static void try_possible_paths(const int *df_iters, const size_t *paths, const int npaths,
const int path_idx, int *choice, diffcmppath_T *diffcmppath,
const int *diff_len, const size_t ndiffs, const char **diff_blk,
bool iwhite)
bool charmatch, size_t **word_offset, size_t **word_offset_size)
{
if (path_idx == npaths) {
if ((*choice) > 0) {
int from_vals[LN_MAX_BUFS] = { 0 };
const int *to_vals = df_iters;
const char *current_lines[LN_MAX_BUFS];
size_t word_len[LN_MAX_BUFS];
for (size_t k = 0; k < ndiffs; k++) {
from_vals[k] = df_iters[k];
// get the index at all of the places
if ((*choice) & (1 << k)) {
from_vals[k]--;
const char *p = diff_blk[k];
fastforward_buf_to_lnum(&p, df_iters[k]);
if (charmatch) {
if (word_offset[k] != NULL) {
// get start position
p += word_offset[k][df_iters[k] - 1];
word_len[k] = word_offset_size[k][df_iters[k] - 1];
// index of the word for comparison
} else {
p += df_iters[k] - 1; // advance by the character count
}
} else {
fastforward_buf_to_lnum(&p, df_iters[k] - 1);
}
current_lines[k] = p;
} else {
current_lines[k] = NULL;
@ -189,7 +167,42 @@ static void try_possible_paths(const int *df_iters, const size_t *paths, const i
}
size_t unwrapped_idx_from = unwrap_indexes(from_vals, diff_len, ndiffs);
size_t unwrapped_idx_to = unwrap_indexes(to_vals, diff_len, ndiffs);
int matched_chars = count_n_matched_chars(current_lines, ndiffs, iwhite);
int matched_chars = 0;
if (charmatch) {
// only two valid options for charmatch
// 1. skip of a single character
// 2. a combination of 'ndiffs' characters, when not equal to '\n'
char t[256];
t[0] = '\0';
size_t t_l = 0;
size_t compared = 0;
for (size_t i = 0; i < ndiffs; i++) {
if (current_lines[i] != NULL) {
compared++;
if ((t[0] != '\0' && !compare(current_lines[i],
(word_offset[i] != NULL ? word_len[i] : 1), t, t_l)) // if theres more than one to compare, and
// they're not matching
|| (t[0] != '\0' && current_lines[i][0] == '\n')) { // if there's more than one to compare, and
// at least one is a '\n'
return; // not a possible path
} else if (t[0] != '\0') {
matched_chars = 1; // comparison of all buffers
}
size_t this_word_length = word_offset[i] != NULL ? word_len[i] : 1;
t_l = this_word_length;
for (size_t l = 0; l < this_word_length; l++) {
t[l] = current_lines[i][l];
}
}
}
if (!(compared == ndiffs || compared == 1)) {
return;
}
} else {
matched_chars = count_n_matched_chars(current_lines, ndiffs);
}
int score = diffcmppath[unwrapped_idx_from].df_lev_score + matched_chars;
if (score > diffcmppath[unwrapped_idx_to].df_lev_score) {
diffcmppath[unwrapped_idx_to].df_path_n = 1;
@ -207,10 +220,12 @@ static void try_possible_paths(const int *df_iters, const size_t *paths, const i
size_t bit_place = paths[path_idx];
*(choice) |= (1 << bit_place); // set it to 1
try_possible_paths(df_iters, paths, npaths, path_idx + 1, choice,
diffcmppath, diff_len, ndiffs, diff_blk, iwhite);
diffcmppath, diff_len, ndiffs, diff_blk, charmatch, word_offset,
word_offset_size);
*(choice) &= ~(1 << bit_place); // set it to 0
try_possible_paths(df_iters, paths, npaths, path_idx + 1, choice,
diffcmppath, diff_len, ndiffs, diff_blk, iwhite);
diffcmppath, diff_len, ndiffs, diff_blk, charmatch, word_offset,
word_offset_size);
}
/// unwrap indexes to access n dimensional tensor
@ -245,7 +260,7 @@ static size_t unwrap_indexes(const int *values, const int *diff_len, const size_
/// @param diff_blk
static void populate_tensor(int *df_iters, const size_t ch_dim, diffcmppath_T *diffcmppath,
const int *diff_len, const size_t ndiffs, const char **diff_blk,
bool iwhite)
bool charmatch, size_t **word_offset, size_t **word_offset_size)
{
if (ch_dim == ndiffs) {
int npaths = 0;
@ -259,16 +274,19 @@ static void populate_tensor(int *df_iters, const size_t ch_dim, diffcmppath_T *d
}
int choice = 0;
size_t unwrapper_idx_to = unwrap_indexes(df_iters, diff_len, ndiffs);
diffcmppath[unwrapper_idx_to].df_lev_score = -1;
if (unwrapper_idx_to > 0) {
diffcmppath[unwrapper_idx_to].df_lev_score = -1;
}
try_possible_paths(df_iters, paths, npaths, 0, &choice, diffcmppath,
diff_len, ndiffs, diff_blk, iwhite);
diff_len, ndiffs, diff_blk, charmatch, word_offset,
word_offset_size);
return;
}
for (int i = 0; i <= diff_len[ch_dim]; i++) {
df_iters[ch_dim] = i;
populate_tensor(df_iters, ch_dim + 1, diffcmppath, diff_len,
ndiffs, diff_blk, iwhite);
ndiffs, diff_blk, charmatch, word_offset, word_offset_size);
}
}
@ -328,7 +346,8 @@ static void populate_tensor(int *df_iters, const size_t ch_dim, diffcmppath_T *d
/// @param [out] [allocated] decisions
/// @return the length of decisions
size_t linematch_nbuffers(const char **diff_blk, const int *diff_len, const size_t ndiffs,
int **decisions, bool iwhite)
int **decisions, bool charmatch, size_t **word_offset,
size_t **word_offset_size)
{
assert(ndiffs <= LN_MAX_BUFS);
@ -353,7 +372,8 @@ size_t linematch_nbuffers(const char **diff_blk, const int *diff_len, const size
// memory for avoiding repetitive calculations of score
int df_iters[LN_MAX_BUFS];
populate_tensor(df_iters, 0, diffcmppath, diff_len, ndiffs, diff_blk, iwhite);
populate_tensor(df_iters, 0, diffcmppath, diff_len, ndiffs, diff_blk, charmatch,
word_offset, word_offset_size);
const size_t u = unwrap_indexes(diff_len, diff_len, ndiffs);
diffcmppath_T *startNode = &diffcmppath[u];
@ -402,3 +422,16 @@ static size_t test_charmatch_paths(diffcmppath_T *node, int lastdecision)
}
return (size_t)node->df_choice_mem[lastdecision];
}
// return true if these two strings are equal
static bool compare(const char *s1, size_t l1, const char *s2, size_t l2)
{
if (l1 != l2) {
return false;
}
for (size_t i = 0; i < l1; i++) {
if (s1[i] != s2[i]) {
return false;
}
}
return true;
}

View File

@ -67,14 +67,29 @@ static void get_linematch_results(lua_State *lstate, mmfile_t *ma, mmfile_t *mb,
int count_a, int start_b, int count_b, bool iwhite)
{
// get the pointer to char of the start of the diff to pass it to linematch algorithm
const char *diff_begin[2] = { ma->ptr, mb->ptr };
char *diff_begin[2] = { ma->ptr, mb->ptr };
int diff_length[2] = { count_a, count_b };
fastforward_buf_to_lnum(&diff_begin[0], (linenr_T)start_a + 1);
fastforward_buf_to_lnum(&diff_begin[1], (linenr_T)start_b + 1);
fastforward_buf_to_lnum((const char **)(&diff_begin[0]), (linenr_T)start_a + 1);
fastforward_buf_to_lnum((const char **)(&diff_begin[1]), (linenr_T)start_b + 1);
if (iwhite) {
for (int i = 0; i < 2; i++) {
size_t j = 0, k = 0, lines = (size_t)diff_length[i];
while (lines > 0) {
if (diff_begin[i][j] != ' ' && diff_begin[i][j] != '\t') {
diff_begin[i][k++] = diff_begin[i][j];
}
if (diff_begin[i][j++] == '\n') {
lines--;
}
}
}
}
int *decisions = NULL;
size_t decisions_length = linematch_nbuffers(diff_begin, diff_length, 2, &decisions, iwhite);
size_t decisions_length = linematch_nbuffers((const char **)diff_begin, diff_length, 2,
&decisions, 0, NULL, NULL);
int lnuma = start_a;
int lnumb = start_b;

View File

@ -803,6 +803,73 @@ void testFunction () {
]])
end)
end)
describe('setup a diff with 2 files and set linematch:30', function()
before_each(function()
feed(':set diffopt+=linematch:30<cr>')
local f1 = [[
start
?a
]]
local f2 = [[
start
!b
!a
!c
]]
write_file(fname, f1, false)
write_file(fname_2, f2, false)
reread()
end)
it('display results', function()
screen:expect([[
{7: }{8: 1 }^start {7: }{8: 1 }start |
{7: }{8: 2 }{22:!b }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{22:!a }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 }{4: }{27:!c}{4: }{7: }{8: 2 }{4: }{27:?a}{4: }|
{7: }{8: 5 } {7: }{8: 3 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:e |
]])
end)
it('display results with ignore white', function()
feed(':set diffopt+=iwhiteall<cr>')
screen:expect([[
{7: }{8: 1 }^start {7: }{8: 1 }start |
{7: }{8: 2 }{22:!b }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{27:!}{4:a }{7: }{8: 2 }{4: }{27:?}{4:a }|
{7: }{8: 4 }{22: !c }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 5 } {7: }{8: 3 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=iwhiteall |
]])
end)
end)
describe('a diff that would result in multiple groups before grouping optimization', function()
before_each(function()
feed(':set diffopt+=linematch:30<cr>')
@ -1084,6 +1151,436 @@ something
end
)
end)
describe('show a diff with charmatch enabled', function()
before_each(function()
local f1 = [[
abbcabbcdefghijklmnop
]]
local f2 = [[
abca?bc
dfgh?ijl
mnop?
]]
write_file(fname, f1, false)
write_file(fname_2, f2, false)
reread()
end)
describe('when the entire hunk is compared, cross-line', function()
before_each(function()
feed(':set diffopt+=chardiff:100<cr>')
end)
it('display results', function()
screen:expect([[
{7: }{8: 1 }{4:^abca}{27:?}{4:bc }{7: }{8: 1 }{4:a}{27:b}{4:bca}{27:b}{4:bcd}{27:e}{4:fghij}{27:k}{4:lmnop }|
{7: }{8: 2 }{4:dfgh}{27:?}{4:ijl }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{4:mnop}{27:?}{4: }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=chardiff:100 |
]])
end)
end)
describe('when the single line is compared, cross-line', function()
before_each(function()
feed(':set diffopt+=chardiff:30<cr>')
end)
it('display results', function()
screen:expect([[
{7: }{8: 1 }{4:^abca}{27:?}{4:bc }{7: }{8: 1 }{4:a}{27:b}{4:bca}{27:b}{4:bc}{27:defghijklmnop}{4: }|
{7: }{8: 2 }{22:dfgh?ijl }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{22:mnop? }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=chardiff:30 |
]])
end)
end)
describe('when the diff hunk and the single line are too long to run chardiff', function()
before_each(function()
feed(':set diffopt+=chardiff:10<cr>')
end)
it('display results', function()
screen:expect([[
{7: }{8: 1 }{4:^ab}{27:ca?bc}{4: }{7: }{8: 1 }{4:ab}{27:bcabbcdefghijklmnop}{4: }|
{7: }{8: 2 }{22:dfgh?ijl }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{22:mnop? }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=chardiff:10 |
]])
end)
end)
end)
describe('show a diff with wordmatch enabled', function()
before_each(function()
local f1 = [[
wA w1 wB w1 w2 wC w3 w4
]]
local f2 = [[
w1 w2
w2 w3
w4 w5
]]
write_file(fname, f1, false)
write_file(fname_2, f2, false)
reread()
end)
describe('when the entire hunk is compared, cross-line', function()
it('display results', function()
feed(':set diffopt+=worddiff:30<cr>')
screen:expect([[
{7: }{8: 1 }{4:^w1 w2 }{7: }{8: 1 }{27:wA w1 wB }{4:w1 w2}{27: wC}{4: w3}{27: }{4:w4 }|
{7: }{8: 2 }{27:w2}{4: w3 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{4:w4}{27: w5}{4: }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=worddiff:30 |
]])
end)
it('display results, with ignore white', function()
feed(':set diffopt+=worddiff:20<cr>:set diffopt+=iwhiteall<cr>')
screen:expect([[
{7: }{8: 1 }{4:^w1 w2 }{7: }{8: 1 }{27:wA}{4: }{27:w1}{4: }{27:wB}{4: w1 w2 }{27:wC}{4: w3 w4 }|
{7: }{8: 2 }{27:w2}{4: w3 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{4:w4 }{27:w5}{4: }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=iwhiteall |
]])
end)
end)
describe('when the single line is compared, cross-line', function()
it('display results', function()
feed(':set diffopt+=worddiff:20<cr>')
screen:expect([[
{7: }{8: 1 }{4:^w1 w2 }{7: }{8: 1 }{27:wA w1 wB }{4:w1 w2}{27: wC w3 w4}{4: }|
{7: }{8: 2 }{22:w2 w3 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{22:w4 w5 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=worddiff:20 |
]])
end)
it('display results, with ignore white', function()
feed(':set diffopt+=worddiff:15<cr>:set diffopt+=iwhiteall<cr>')
screen:expect([[
{7: }{8: 1 }{4:^w1 w2 }{7: }{8: 1 }{27:wA}{4: }{27:w1}{4: }{27:wB}{4: w1 w2 }{27:wC}{4: }{27:w3}{4: }{27:w4}{4: }|
{7: }{8: 2 }{22:w2 w3 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{22:w4 w5 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=iwhiteall |
]])
end)
end)
describe('when the diff hunk and the single line are too long to run chardiff', function()
it('display results', function()
feed(':set diffopt+=worddiff:10<cr>')
screen:expect([[
{7: }{8: 1 }{4:^w}{27:1 w2}{4: }{7: }{8: 1 }{4:w}{27:A w1 wB w1 w2 wC w3 w4}{4: }|
{7: }{8: 2 }{22:w2 w3 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{22:w4 w5 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=worddiff:10 |
]])
end)
it('display results, with ignore white', function()
feed(':set diffopt+=worddiff:10<cr>:set diffopt+=iwhiteall<cr>')
screen:expect([[
{7: }{8: 1 }{4:^w}{27:1 w2}{4: }{7: }{8: 1 }{4:w}{27:A w1 wB w1 w2 wC w3 w4}{4: }|
{7: }{8: 2 }{22:w2 w3 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 3 }{22:w4 w5 }{7: }{8: }{23:--------------------------------------------}|
{7: }{8: 4 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=iwhiteall |
]])
end)
end)
end)
describe('show a diff with charmatch enabled, with and without ignore white', function()
before_each(function()
local f1 = [[
ababcabcdabcde
]]
local f2 = [[
abc abcd abcde abcdef
]]
write_file(fname, f1, false)
write_file(fname_2, f2, false)
reread()
end)
describe('normal comparison, including whitespace', function()
before_each(function()
feed(':set diffopt+=chardiff:100<cr>')
end)
it('display results', function()
screen:expect([[
{7: }{8: 1 }{4:^ab}{27:c }{4:abc}{27:d }{4:abcd}{27:e }{4:abcde}{27:f}{4: }{7: }{8: 1 }{4:ababcabcdabcde }|
{7: }{8: 2 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=chardiff:100 |
]])
end)
end)
describe('ignore whitespace', function()
before_each(function()
feed(':set diffopt+=chardiff:100<cr>:set diffopt+=iwhiteall<cr>')
end)
it('display results', function()
screen:expect([[
{7: }{8: 1 }{4:^ab}{27:c}{4: abc}{27:d}{4: abcd}{27:e}{4: abcde}{27:f}{4: }{7: }{8: 1 }{4:ababcabcdabcde }|
{7: }{8: 2 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=iwhiteall |
]])
end)
end)
end)
describe('show a diff with charmatch enabled, with different UTF-8 character', function()
before_each(function()
local f1 = [[
aaaहaaa
]]
local f2 = [[
aaaसaaa
]]
write_file(fname, f1, false)
write_file(fname_2, f2, false)
reread()
end)
describe('normal comparison, including whitespace', function()
before_each(function()
feed(':set diffopt+=chardiff:100<cr>')
end)
it('display results', function()
screen:expect([[
{7: }{8: 1 }{4:^aaa}{27:}{4:a}{27:a}{4:a }{7: }{8: 1 }{4:aaa}{27:}{4:a}{27:a}{4:a }|
{7: }{8: 2 } {7: }{8: 2 } |
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=chardiff:100 |
]])
end)
end)
end)
describe('show a diff with charmatch enabled, with same UTF-8 character', function()
before_each(function()
local f1 = [[
aaaहaaa
]]
local f2 = [[
aaaहaaa
]]
write_file(fname, f1, false)
write_file(fname_2, f2, false)
reread()
end)
describe('normal comparison, including whitespace', function()
before_each(function()
feed(':set diffopt+=chardiff:100<cr>')
end)
it('display results', function()
screen:expect([[
{7:+ }{8: 1 }{13:^+-- 2 lines: aaaहaaa······················}{7:+ }{8: 1 }{13:+-- 2 lines: aaaहaaa·······················}|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{1:~ }{1:~ }|
{3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }|
:set diffopt+=chardiff:100 |
]])
end)
end)
end)
end)
describe('regressions', function()