From d03bd792fd50f7c01c989c997559e36a85f35e8f Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Mon, 12 Jun 2023 19:40:56 +1000 Subject: [PATCH] mergeview/wiggle: improvements for space-only conflicts. 1/ change how the "wiggle" is reported. It is no longer reported by "set-wiggle". Instead a new "get-result" function returns various results including the "wiggle". 2/ count how many conflicts only involve white space. If that is all the conflicts, then still calculate the wiggle. 3/ If a merge only has space conflicts, use blue for the merge markers rather the red (or green). 4/ mark the green merge markers bold so they stand out more 5/ When adding attrs, annotate conflicts which are space only, and respond to that with underlines instead of inverse red. Signed-off-by: NeilBrown --- DOC/TODO.md | 7 ++- lib-wiggle.c | 96 ++++++++++++++++++++++++++++++++++++----- python/lib-mergeview.py | 41 ++++++++++-------- 3 files changed, 116 insertions(+), 28 deletions(-) diff --git a/DOC/TODO.md b/DOC/TODO.md index 73eca513..69c02af7 100644 --- a/DOC/TODO.md +++ b/DOC/TODO.md @@ -195,6 +195,11 @@ Module features ### popup +### lib-wiggle + +- [ ] maybe find matches considering only alphanum first, then + refine the gaps considering punctuation. + ### lib-diff - [ ] When viewing diff or merge can get into infinite loop. Possibly due @@ -221,7 +226,7 @@ Module features if there is a selection, paste in patch with selection as "orig" if no selection - just paste it in with empty orig -- [ ] merge-mode to highlight markers with "space-only" or "no-diff" state +- [X] merge-mode to highlight markers with "space-only" or "no-diff" state Also have green for "no conflicts", but it doesn't stand out. It would be nice if space-only differences didn't stand out so much. That would require different mark-up, or moving a mark around while diff --git a/lib-wiggle.c b/lib-wiggle.c index fd3feb99..0133dce7 100644 --- a/lib-wiggle.c +++ b/lib-wiggle.c @@ -195,6 +195,9 @@ struct wiggle_data { */ } texts[3]; struct command c; + /* After set-wiggle is called, these are set */ + int space_conflicts, conflicts, wiggles; + char *wiggle; }; DEF_CMD(notify_close) @@ -227,6 +230,7 @@ DEF_CMD(wiggle_close) wd->texts[i].end = NULL; wd->texts[i].text = NULL; } + free(wd->wiggle); return 1; } @@ -378,6 +382,38 @@ static const char *typenames[] = { [AlreadyApplied] = "AlreadyApplied", }; +static bool merge_has_nonspace(struct file f, int pos, int len) +{ + char *cp; + char *endcp; + + if (len == 0) + return False; + if (!f.list) + return True; + endcp = f.list[pos+len-1].start + f.list[pos+len-1].len; + cp = f.list[pos].start; + return has_nonspace(cp, endcp-cp); +} + +static int count_space_conflicts(struct merge *merge safe, + struct file a, struct file b, struct file c) +{ + int cnt = 0; + struct merge *m; + + for (m = merge; m->type != End; m++) { + + if (m->type != Conflict) + continue; + if (!merge_has_nonspace(a, m->a, m->al) && + !merge_has_nonspace(b, m->b, m->bl) && + !merge_has_nonspace(c, m->c, m->cl)) + cnt += 1; + } + return cnt; +} + static void add_merge_markup(struct pane *p safe, struct mark *st, int skip, int choose, @@ -395,7 +431,10 @@ static void add_merge_markup(struct pane *p safe, for (m = merge; m->type != End; m++) { int len; const char *cp, *endcp; + wint_t wch; + bool non_space; int chars; + char *suffix = ""; char buf[30]; switch (which) { @@ -425,10 +464,17 @@ static void add_merge_markup(struct pane *p safe, endcp = f.list[pos+len-1].start + f.list[pos+len-1].len; pos += len; chars = 0; - while (get_utf8(&cp, endcp) < WERR) + non_space = False; + while ((wch = get_utf8(&cp, endcp)) < WERR) { chars += 1; + if (!iswspace(wch)) + non_space = True; + } - snprintf(buf, sizeof(buf), "%d %s", chars, typenames[m->type]); + if (m->type == Conflict && !non_space) + suffix = " spaces"; + snprintf(buf, sizeof(buf), "%d %s%s", + chars, typenames[m->type], suffix); call("doc:set-attr", p, 0, st, attr, 0, NULL, buf); while (chars > 0) { wint_t ch = doc_next(p, st); @@ -439,8 +485,8 @@ static void add_merge_markup(struct pane *p safe, doskip(p, st, NULL, skip, choose); chars -= 1; if (is_eol(ch) && chars > 0) { - snprintf(buf, sizeof(buf), "%d %s", chars, - typenames[m->type]); + snprintf(buf, sizeof(buf), "%d %s%s", chars, + typenames[m->type], suffix); call("doc:set-attr", p, 0, st, attr, 0, NULL, buf); } @@ -481,6 +527,7 @@ static char *collect_merge(struct merge *merge safe, for (m = merge; m->type != End; m++) { if (m->type == Unmatched || m->type == AlreadyApplied || + m->type == Conflict || m->type == Unchanged) l += copy_words(NULL, &of, m->a, m->al); else if (m->type == Changed) @@ -492,6 +539,7 @@ static char *collect_merge(struct merge *merge safe, for (m = merge; m->type != End; m++) { if (m->type == Unmatched || m->type == AlreadyApplied || + m->type == Conflict || m->type == Unchanged) l += copy_words(str+l, &of, m->a, m->al); else if (m->type == Changed) @@ -532,12 +580,14 @@ DEF_CMD(wiggle_set_wiggle) csl2 = wiggle_diff(bf, af, 1); info = wiggle_make_merger(of, bf, af, csl1, csl2, 1, 1, 0); if (info.merger) { - if (ci->comm2 && info.conflicts == 0) { - char *str = collect_merge(info.merger, of, bf, af); - if (str) - comm_call(ci->comm2, "cb", ci->focus, 0, NULL, str); - free(str); - } + free(wd->wiggle); + wd->wiggle = NULL; + wd->conflicts = info.conflicts; + wd->wiggles = info.wiggles; + wd->space_conflicts = count_space_conflicts(info.merger, + of, bf, af); + if (info.conflicts == wd->space_conflicts) + wd->wiggle = collect_merge(info.merger, of, bf, af); if (*attr) { add_merge_markup(ci->focus, wd->texts[0].start, @@ -668,6 +718,30 @@ DEF_CMD(wiggle_find) return ret > 0 ? fuzz + 1 : Efail; } +DEF_CMD(wiggle_get) +{ + struct wiggle_data *wd = ci->home->data; + + if (wd->conflicts < 0) + return Einval; + if (!ci->str) + return Enoarg; + if (strcmp(ci->str, "wiggle") == 0) { + if (wd->wiggle) + return comm_call(ci->comm2, "cb", ci->focus, 0, NULL, + wd->wiggle); + else + return Efalse; + } + if (strcmp(ci->str, "space-conflicts") == 0) + return wd->space_conflicts + 1; + if (strcmp(ci->str, "conflicts") == 0) + return wd->conflicts + 1; + if (strcmp(ci->str, "wiggles") == 0) + return wd->wiggles + 1; + return Einval; +} + DEF_CMD(wiggle_find_best) { return 0; } static struct map *wiggle_map; @@ -690,11 +764,13 @@ DEF_CMD(make_wiggle) key_add(wiggle_map, "set-wiggle", &wiggle_set_wiggle); key_add(wiggle_map, "find", &wiggle_find); key_add(wiggle_map, "find-best", &wiggle_find_best); + key_add(wiggle_map, "get-result", &wiggle_get); } alloc(wd, pane); wd->c = do_wiggle; wd->c.free = wiggle_free; + wd->conflicts = -1; p = pane_register(pane_root(ci->focus), 0, &wiggle_pane.c, wd); if (!p) { diff --git a/python/lib-mergeview.py b/python/lib-mergeview.py index 229bfa63..507c3c46 100644 --- a/python/lib-mergeview.py +++ b/python/lib-mergeview.py @@ -20,6 +20,7 @@ class MergePane(edlib.Pane): self.marks = None self.wig = None self.conflicts = 0 + self.space_conflicts = 0 self.call("doc:request:doc:replaced") def fore(self, m, end, ptn): @@ -63,12 +64,12 @@ class MergePane(edlib.Pane): self.call("doc:EOL", 1, t, 1) cmd("after", self, t, m3) - ret = cmd("set-wiggle", self, "render:merge-same", ret='str') - if type(ret) == str: - self.wig = ret - self.conflicts = 0 - else: - self.conflicts = 1 + ret = cmd("set-wiggle", self, "render:merge-same") + self.conflicts = ret-1 + self.space_conflicts = cmd("get-result", self, "space-conflicts") - 1 + if self.conflicts == self.space_conflicts: + self.wig = cmd("get-result", self, "wiggle", ret='str') + del cmd self.marks = [start, m1, m2, m3] @@ -96,7 +97,7 @@ class MergePane(edlib.Pane): return 1 if num == 0: # if no conflicts remain, wiggle the merge - if self.conflicts or self.wig is None: + if self.wig is None: focus.call("Message", "Cannot complete merge while conflicts remain") return 1 focus.call("doc:set-attr", "render:merge-same", @@ -241,34 +242,40 @@ class MergePane(edlib.Pane): if str == "start-of-line": if mark == o or mark == b or mark == a or mark == e: - if self.conflicts: + if self.conflicts > self.space_conflicts: comm2("attr:cb", focus, mark, "fg:red-40", 0, 102) + elif self.conflicts: + comm2("attr:cb", focus, mark, "fg:blue-80", + 0, 102) else: - comm2("attr:cb", focus, mark, "fg:green-40", + comm2("attr:cb", focus, mark, "fg:green-60,bold", 0, 102) return edlib.Efallthrough if str == "render:merge-same": w = str2.split() - len = int(w[0]) + alen = int(w[0]) if w[1] == "Unmatched": - comm2("attr:cb", focus, mark, "fg:blue-80,bg:cyan+20", len, 103) + comm2("attr:cb", focus, mark, "fg:blue-80,bg:cyan+20", alen, 103) if w[1] == "Extraneous": - comm2("attr:cb", focus, mark, "fg:cyan-60,bg:yellow", len, 103) + comm2("attr:cb", focus, mark, "fg:cyan-60,bg:yellow", alen, 103) if w[1] == "Changed": if mark < a: - comm2("attr:cb", focus, mark, "fg:red-60", len, 103) + comm2("attr:cb", focus, mark, "fg:red-60", alen, 103) else: - comm2("attr:cb", focus, mark, "fg:green-60", len, 103) + comm2("attr:cb", focus, mark, "fg:green-60", alen, 103) if w[1] == "Conflict": - comm2("attr:cb", focus, mark, "fg:red-60,inverse", len, 103) + if len(w) >= 3 and w[2] == "spaces": + comm2("attr:cb", focus, mark, "fg:red-60,underline", alen, 103) + else: + comm2("attr:cb", focus, mark, "fg:red-60,inverse", alen, 103) if w[1] == "AlreadyApplied": if mark > b and mark < a: # This part is 'before' - mosly irrelevant - comm2("attr:cb", focus, mark, "fg:cyan-60", len, 103) + comm2("attr:cb", focus, mark, "fg:cyan-60", alen, 103) else: - comm2("attr:cb", focus, mark, "fg:cyan-60,inverse", len, 103) + comm2("attr:cb", focus, mark, "fg:cyan-60,inverse", alen, 103) return edlib.Efallthrough -- 2.39.5