Skip to content

Commit

Permalink
Merge pull request #966 from dvalter/word-diff
Browse files Browse the repository at this point in the history
Add --word-diff=plain colorizing support.

Closes #221
  • Loading branch information
koutcher committed Apr 4, 2020
2 parents efcfc20 + 9b9e6f2 commit 41763ae
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 1 deletion.
1 change: 1 addition & 0 deletions NEWS.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Improvements:
- The blame view requires a working tree.
- Fix use of deprecated vwprintw() function.
- Update utf8proc to v2.5.0.
- Add --word-diff=plain colorizing support. (#221)

Bug fixes:

Expand Down
5 changes: 5 additions & 0 deletions doc/tig.1.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ Show references (branches, remotes and tags):
$ tig refs
-----------------------------------------------------------------------------
Use word diff in the diff view:
-----------------------------------------------------------------------------
$ tig --word-diff=plain
-----------------------------------------------------------------------------
ENVIRONMENT VARIABLES
---------------------

Expand Down
1 change: 1 addition & 0 deletions include/tig/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ extern iconv_t opt_iconv_out;
extern char opt_editor[SIZEOF_STR];
extern const char **opt_cmdline_args;
extern bool opt_log_follow;
extern bool opt_word_diff;

/*
* Mapping between options and command argument mapping.
Expand Down
80 changes: 79 additions & 1 deletion src/diff.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ diff_open(struct view *view, enum open_flags flags)
enum status_code
diff_init_highlight(struct view *view, struct diff_state *state)
{
if (!opt_diff_highlight || !*opt_diff_highlight)
if (!opt_diff_highlight || !*opt_diff_highlight || opt_word_diff)
return SUCCESS;

struct app_external *app = app_diff_highlight_load(opt_diff_highlight);
Expand Down Expand Up @@ -211,6 +211,77 @@ diff_common_add_diff_stat(struct view *view, const char *text, size_t offset)
return NULL;
}

static bool
diff_common_read_diff_wdiff_group(struct diff_stat_context *context)
{
const char *sep_add = strstr(context->text, "{+");
const char *sep_del = strstr(context->text, "[-");
const char *sep;
enum line_type next_type;
const char *end_delimiter;
const char *end_sep;
size_t len;

if (sep_add == NULL && sep_del == NULL)
return false;

if (sep_del == NULL || (sep_add != NULL && sep_add < sep_del)) {
sep = sep_add;
next_type = LINE_DIFF_ADD;
end_delimiter = "+}";
} else {
sep = sep_del;
next_type = LINE_DIFF_DEL;
end_delimiter = "-]";
}

diff_common_add_cell(context, sep - context->text, false);

context->type = next_type;
context->text = sep;

// workaround for a single }/] change
end_sep = strstr(context->text + sizeof("{+") - 1, end_delimiter);

if (end_sep == NULL) {
// diff is not terminated
len = strlen(context->text);
} else {
// separators are included in the add/del highlight
len = end_sep - context->text + sizeof("+}") - 1;
}

diff_common_add_cell(context, len, false);

if (end_sep == NULL) {
context->text += len;
} else {
context->text = end_sep + sizeof("+}") - 1;
}
context->type = LINE_DEFAULT;

return true;
}

static bool
diff_common_read_diff_wdiff(struct view *view, const char *text)
{
struct diff_stat_context context = { text, LINE_DEFAULT };

/* Detect remaining part of a word diff line:
*
* added {+new +} text
* removed[- something -] from the file
* replaced [-some-]{+same+} text
* there could be [-one-] diff part{+s+} in the {+any +} line
*/
while (diff_common_read_diff_wdiff_group(&context))
;

diff_common_add_cell(&context, strlen(context.text), true);
return diff_common_add_line(view, text, LINE_DEFAULT, &context);
}

static bool
diff_common_highlight(struct view *view, const char *text, enum line_type type)
{
Expand Down Expand Up @@ -300,6 +371,13 @@ diff_common_read(struct view *view, const char *data, struct diff_state *state)
} else if (state->highlight && strchr(data, 0x1b)) {
return diff_common_highlight(view, data, type);

} else if (opt_word_diff && state->reading_diff_chunk &&
/* combined diff format is not using word diff */
!state->combined_diff &&
(type = LINE_DEFAULT ||
/* ADD and DEL are only valid in regular diff hunks */
type == LINE_DIFF_ADD || type == LINE_DIFF_DEL)) {
return diff_common_read_diff_wdiff(view, data);
}

return pager_common_read(view, data, type, NULL);
Expand Down
11 changes: 11 additions & 0 deletions src/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ iconv_t opt_iconv_out = ICONV_NONE;
char opt_editor[SIZEOF_STR] = "";
const char **opt_cmdline_args = NULL;
bool opt_log_follow = false;
bool opt_word_diff = false;

/*
* Mapping between options and command argument mapping.
Expand Down Expand Up @@ -253,6 +254,11 @@ update_options_from_argv(const char *argv[])
continue;
}

if (!strcmp(flag, "--word-diff") ||
!strcmp(flag, "--word-diff=plain")) {
opt_word_diff = true;
}

argv[flags_pos++] = flag;
}

Expand Down Expand Up @@ -1065,6 +1071,11 @@ load_options(void)
return error("Failed to format TIG_DIFF_OPTS arguments");
}

if (argv_contains(opt_diff_options, "--word-diff") ||
argv_contains(opt_diff_options, "--word-diff=plain")) {
opt_word_diff = true;
}

return SUCCESS;
}

Expand Down
213 changes: 213 additions & 0 deletions test/diff/diff-wdiff-context-test
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
#!/bin/sh

. libtest.sh
. libgit.sh

steps '
:save-display diff-default.screen
:21
]
:save-display diff-u4.screen
]
:save-display diff-u5.screen
:toggle diff-context +5
:save-display diff-u10.screen
[
[
:save-display diff-u8.screen
:0
:set diff-context = 3
:view-main
:view-diff
:save-display diff-default-from-main.screen
:21
]
:save-display diff-u4-from-main.screen
]
:save-display diff-u5-from-main.screen
:toggle diff-context +5
:save-display diff-u10-from-main.screen
[
[
:save-display diff-u8-from-main.screen
'

in_work_dir create_repo_from_tgz "$base_dir/files/scala-js-benchmarks.tgz"

test_tig show master^ --word-diff

assert_equals 'diff-default.screen' <<EOF
commit a1dcf1aaa11470978db1d5d8bcf9e16201eb70ff
Author: Jonas Fonseca <[email protected]>
AuthorDate: Sat Mar 1 15:59:02 2014 -0500
Commit: Jonas Fonseca <[email protected]>
CommitDate: Sat Mar 1 15:59:02 2014 -0500
Add type parameter for js.Dynamic
---
common/src/main/scala/org/scalajs/benchmark/Benchmark.scala | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala b/commo
index 65f914a..3aa4320 100644
--- a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
+++ b/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
@@ -15,7 +15,7 @@ object Benchmark {
val benchmarks = js.Array[Benchmark]()
val benchmarkApps = js.Array[BenchmarkApp]()
val global = [-js.Dynamic.global.asInstanceOf[js.Dictionary]-]{+js.Dynamic.glo
global("runScalaJSBenchmarks") = runBenchmarks _
global("initScalaJSBenchmarkApps") = initBenchmarkApps _
[diff] a1dcf1aaa11470978db1d5d8bcf9e16201eb70ff - line 1 of 23 100%
EOF

assert_equals 'diff-u4.screen' <<EOF
Author: Jonas Fonseca <[email protected]>
AuthorDate: Sat Mar 1 15:59:02 2014 -0500
Commit: Jonas Fonseca <[email protected]>
CommitDate: Sat Mar 1 15:59:02 2014 -0500
Add type parameter for js.Dynamic
---
common/src/main/scala/org/scalajs/benchmark/Benchmark.scala | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala b/commo
index 65f914a..3aa4320 100644
--- a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
+++ b/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
@@ -14,9 +14,9 @@ import scala.scalajs.js
object Benchmark {
val benchmarks = js.Array[Benchmark]()
val benchmarkApps = js.Array[BenchmarkApp]()
val global = [-js.Dynamic.global.asInstanceOf[js.Dictionary]-]{+js.Dynamic.glo
global("runScalaJSBenchmarks") = runBenchmarks _
global("initScalaJSBenchmarkApps") = initBenchmarkApps _
def add(benchmark: Benchmark) {
[diff] Changes to 'common/src/main/scala/org/scalajs/benchmark/Benchmark.sca100%
EOF

assert_equals 'diff-u5.screen' <<EOF
AuthorDate: Sat Mar 1 15:59:02 2014 -0500
Commit: Jonas Fonseca <[email protected]>
CommitDate: Sat Mar 1 15:59:02 2014 -0500
Add type parameter for js.Dynamic
---
common/src/main/scala/org/scalajs/benchmark/Benchmark.scala | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala b/commo
index 65f914a..3aa4320 100644
--- a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
+++ b/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
@@ -13,11 +13,11 @@ import scala.scalajs.js
object Benchmark {
val benchmarks = js.Array[Benchmark]()
val benchmarkApps = js.Array[BenchmarkApp]()
val global = [-js.Dynamic.global.asInstanceOf[js.Dictionary]-]{+js.Dynamic.glo
global("runScalaJSBenchmarks") = runBenchmarks _
global("initScalaJSBenchmarkApps") = initBenchmarkApps _
def add(benchmark: Benchmark) {
benchmarks.push(benchmark)
[diff] Changes to 'common/src/main/scala/org/scalajs/benchmark/Benchmark.sca100%
EOF

assert_equals 'diff-u10.screen' <<EOF
---
common/src/main/scala/org/scalajs/benchmark/Benchmark.scala | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala b/commo
index 65f914a..3aa4320 100644
--- a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
+++ b/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
@@ -8,21 +8,21 @@
package org.scalajs.benchmark
import scala.compat.Platform
import scala.scalajs.js
object Benchmark {
val benchmarks = js.Array[Benchmark]()
val benchmarkApps = js.Array[BenchmarkApp]()
val global = [-js.Dynamic.global.asInstanceOf[js.Dictionary]-]{+js.Dynamic.glo
global("runScalaJSBenchmarks") = runBenchmarks _
global("initScalaJSBenchmarkApps") = initBenchmarkApps _
def add(benchmark: Benchmark) {
benchmarks.push(benchmark)
if (benchmark.isInstanceOf[BenchmarkApp]) {
benchmarkApps.push(benchmark.asInstanceOf[BenchmarkApp])
}
[diff] Changes to 'common/src/main/scala/org/scalajs/benchmark/Benchmark.scal94%
EOF

assert_equals 'diff-u8.screen' <<EOF
Add type parameter for js.Dynamic
---
common/src/main/scala/org/scalajs/benchmark/Benchmark.scala | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala b/commo
index 65f914a..3aa4320 100644
--- a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
+++ b/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
@@ -10,17 +10,17 @@ package org.scalajs.benchmark
import scala.compat.Platform
import scala.scalajs.js
object Benchmark {
val benchmarks = js.Array[Benchmark]()
val benchmarkApps = js.Array[BenchmarkApp]()
val global = [-js.Dynamic.global.asInstanceOf[js.Dictionary]-]{+js.Dynamic.glo
global("runScalaJSBenchmarks") = runBenchmarks _
global("initScalaJSBenchmarkApps") = initBenchmarkApps _
def add(benchmark: Benchmark) {
benchmarks.push(benchmark)
if (benchmark.isInstanceOf[BenchmarkApp]) {
benchmarkApps.push(benchmark.asInstanceOf[BenchmarkApp])
}
[diff] Changes to 'common/src/main/scala/org/scalajs/benchmark/Benchmark.sca100%
EOF

for i in expected/*.screen; do
diff_file="$(basename -- "$i" | sed 's/[.]screen/-from-main.screen/')"
assert_equals "$diff_file" <<EOF
$(cat < "$i")
EOF
done

0 comments on commit 41763ae

Please sign in to comment.