Skip to content

Commit 41763ae

Browse files
committed
Merge pull request jonas#966 from dvalter/word-diff
Add --word-diff=plain colorizing support. Closes jonas#221
2 parents efcfc20 + 9b9e6f2 commit 41763ae

File tree

6 files changed

+310
-1
lines changed

6 files changed

+310
-1
lines changed

NEWS.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Improvements:
1414
- The blame view requires a working tree.
1515
- Fix use of deprecated vwprintw() function.
1616
- Update utf8proc to v2.5.0.
17+
- Add --word-diff=plain colorizing support. (#221)
1718

1819
Bug fixes:
1920

doc/tig.1.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ Show references (branches, remotes and tags):
183183
$ tig refs
184184
-----------------------------------------------------------------------------
185185
186+
Use word diff in the diff view:
187+
-----------------------------------------------------------------------------
188+
$ tig --word-diff=plain
189+
-----------------------------------------------------------------------------
190+
186191
ENVIRONMENT VARIABLES
187192
---------------------
188193

include/tig/options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ extern iconv_t opt_iconv_out;
183183
extern char opt_editor[SIZEOF_STR];
184184
extern const char **opt_cmdline_args;
185185
extern bool opt_log_follow;
186+
extern bool opt_word_diff;
186187

187188
/*
188189
* Mapping between options and command argument mapping.

src/diff.c

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ diff_open(struct view *view, enum open_flags flags)
4646
enum status_code
4747
diff_init_highlight(struct view *view, struct diff_state *state)
4848
{
49-
if (!opt_diff_highlight || !*opt_diff_highlight)
49+
if (!opt_diff_highlight || !*opt_diff_highlight || opt_word_diff)
5050
return SUCCESS;
5151

5252
struct app_external *app = app_diff_highlight_load(opt_diff_highlight);
@@ -211,6 +211,77 @@ diff_common_add_diff_stat(struct view *view, const char *text, size_t offset)
211211
return NULL;
212212
}
213213

214+
static bool
215+
diff_common_read_diff_wdiff_group(struct diff_stat_context *context)
216+
{
217+
const char *sep_add = strstr(context->text, "{+");
218+
const char *sep_del = strstr(context->text, "[-");
219+
const char *sep;
220+
enum line_type next_type;
221+
const char *end_delimiter;
222+
const char *end_sep;
223+
size_t len;
224+
225+
if (sep_add == NULL && sep_del == NULL)
226+
return false;
227+
228+
if (sep_del == NULL || (sep_add != NULL && sep_add < sep_del)) {
229+
sep = sep_add;
230+
next_type = LINE_DIFF_ADD;
231+
end_delimiter = "+}";
232+
} else {
233+
sep = sep_del;
234+
next_type = LINE_DIFF_DEL;
235+
end_delimiter = "-]";
236+
}
237+
238+
diff_common_add_cell(context, sep - context->text, false);
239+
240+
context->type = next_type;
241+
context->text = sep;
242+
243+
// workaround for a single }/] change
244+
end_sep = strstr(context->text + sizeof("{+") - 1, end_delimiter);
245+
246+
if (end_sep == NULL) {
247+
// diff is not terminated
248+
len = strlen(context->text);
249+
} else {
250+
// separators are included in the add/del highlight
251+
len = end_sep - context->text + sizeof("+}") - 1;
252+
}
253+
254+
diff_common_add_cell(context, len, false);
255+
256+
if (end_sep == NULL) {
257+
context->text += len;
258+
} else {
259+
context->text = end_sep + sizeof("+}") - 1;
260+
}
261+
context->type = LINE_DEFAULT;
262+
263+
return true;
264+
}
265+
266+
static bool
267+
diff_common_read_diff_wdiff(struct view *view, const char *text)
268+
{
269+
struct diff_stat_context context = { text, LINE_DEFAULT };
270+
271+
/* Detect remaining part of a word diff line:
272+
*
273+
* added {+new +} text
274+
* removed[- something -] from the file
275+
* replaced [-some-]{+same+} text
276+
* there could be [-one-] diff part{+s+} in the {+any +} line
277+
*/
278+
while (diff_common_read_diff_wdiff_group(&context))
279+
;
280+
281+
diff_common_add_cell(&context, strlen(context.text), true);
282+
return diff_common_add_line(view, text, LINE_DEFAULT, &context);
283+
}
284+
214285
static bool
215286
diff_common_highlight(struct view *view, const char *text, enum line_type type)
216287
{
@@ -300,6 +371,13 @@ diff_common_read(struct view *view, const char *data, struct diff_state *state)
300371
} else if (state->highlight && strchr(data, 0x1b)) {
301372
return diff_common_highlight(view, data, type);
302373

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

305383
return pager_common_read(view, data, type, NULL);

src/options.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ iconv_t opt_iconv_out = ICONV_NONE;
123123
char opt_editor[SIZEOF_STR] = "";
124124
const char **opt_cmdline_args = NULL;
125125
bool opt_log_follow = false;
126+
bool opt_word_diff = false;
126127

127128
/*
128129
* Mapping between options and command argument mapping.
@@ -253,6 +254,11 @@ update_options_from_argv(const char *argv[])
253254
continue;
254255
}
255256

257+
if (!strcmp(flag, "--word-diff") ||
258+
!strcmp(flag, "--word-diff=plain")) {
259+
opt_word_diff = true;
260+
}
261+
256262
argv[flags_pos++] = flag;
257263
}
258264

@@ -1065,6 +1071,11 @@ load_options(void)
10651071
return error("Failed to format TIG_DIFF_OPTS arguments");
10661072
}
10671073

1074+
if (argv_contains(opt_diff_options, "--word-diff") ||
1075+
argv_contains(opt_diff_options, "--word-diff=plain")) {
1076+
opt_word_diff = true;
1077+
}
1078+
10681079
return SUCCESS;
10691080
}
10701081

test/diff/diff-wdiff-context-test

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
#!/bin/sh
2+
3+
. libtest.sh
4+
. libgit.sh
5+
6+
steps '
7+
:save-display diff-default.screen
8+
9+
:21
10+
]
11+
:save-display diff-u4.screen
12+
13+
]
14+
:save-display diff-u5.screen
15+
16+
:toggle diff-context +5
17+
:save-display diff-u10.screen
18+
19+
[
20+
[
21+
:save-display diff-u8.screen
22+
23+
:0
24+
:set diff-context = 3
25+
:view-main
26+
:view-diff
27+
:save-display diff-default-from-main.screen
28+
29+
:21
30+
]
31+
:save-display diff-u4-from-main.screen
32+
33+
]
34+
:save-display diff-u5-from-main.screen
35+
36+
:toggle diff-context +5
37+
:save-display diff-u10-from-main.screen
38+
39+
[
40+
[
41+
:save-display diff-u8-from-main.screen
42+
'
43+
44+
in_work_dir create_repo_from_tgz "$base_dir/files/scala-js-benchmarks.tgz"
45+
46+
test_tig show master^ --word-diff
47+
48+
assert_equals 'diff-default.screen' <<EOF
49+
commit a1dcf1aaa11470978db1d5d8bcf9e16201eb70ff
50+
Author: Jonas Fonseca <jonas.fonseca@gmail.com>
51+
AuthorDate: Sat Mar 1 15:59:02 2014 -0500
52+
Commit: Jonas Fonseca <jonas.fonseca@gmail.com>
53+
CommitDate: Sat Mar 1 15:59:02 2014 -0500
54+
55+
Add type parameter for js.Dynamic
56+
---
57+
common/src/main/scala/org/scalajs/benchmark/Benchmark.scala | 2 +-
58+
1 file changed, 1 insertion(+), 1 deletion(-)
59+
60+
diff --git a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala b/commo
61+
index 65f914a..3aa4320 100644
62+
--- a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
63+
+++ b/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
64+
@@ -15,7 +15,7 @@ object Benchmark {
65+
val benchmarks = js.Array[Benchmark]()
66+
val benchmarkApps = js.Array[BenchmarkApp]()
67+
68+
val global = [-js.Dynamic.global.asInstanceOf[js.Dictionary]-]{+js.Dynamic.glo
69+
global("runScalaJSBenchmarks") = runBenchmarks _
70+
global("initScalaJSBenchmarkApps") = initBenchmarkApps _
71+
72+
73+
74+
75+
76+
77+
[diff] a1dcf1aaa11470978db1d5d8bcf9e16201eb70ff - line 1 of 23 100%
78+
EOF
79+
80+
assert_equals 'diff-u4.screen' <<EOF
81+
Author: Jonas Fonseca <jonas.fonseca@gmail.com>
82+
AuthorDate: Sat Mar 1 15:59:02 2014 -0500
83+
Commit: Jonas Fonseca <jonas.fonseca@gmail.com>
84+
CommitDate: Sat Mar 1 15:59:02 2014 -0500
85+
86+
Add type parameter for js.Dynamic
87+
---
88+
common/src/main/scala/org/scalajs/benchmark/Benchmark.scala | 2 +-
89+
1 file changed, 1 insertion(+), 1 deletion(-)
90+
91+
diff --git a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala b/commo
92+
index 65f914a..3aa4320 100644
93+
--- a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
94+
+++ b/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
95+
@@ -14,9 +14,9 @@ import scala.scalajs.js
96+
object Benchmark {
97+
val benchmarks = js.Array[Benchmark]()
98+
val benchmarkApps = js.Array[BenchmarkApp]()
99+
100+
val global = [-js.Dynamic.global.asInstanceOf[js.Dictionary]-]{+js.Dynamic.glo
101+
global("runScalaJSBenchmarks") = runBenchmarks _
102+
global("initScalaJSBenchmarkApps") = initBenchmarkApps _
103+
104+
def add(benchmark: Benchmark) {
105+
106+
107+
108+
109+
[diff] Changes to 'common/src/main/scala/org/scalajs/benchmark/Benchmark.sca100%
110+
EOF
111+
112+
assert_equals 'diff-u5.screen' <<EOF
113+
AuthorDate: Sat Mar 1 15:59:02 2014 -0500
114+
Commit: Jonas Fonseca <jonas.fonseca@gmail.com>
115+
CommitDate: Sat Mar 1 15:59:02 2014 -0500
116+
117+
Add type parameter for js.Dynamic
118+
---
119+
common/src/main/scala/org/scalajs/benchmark/Benchmark.scala | 2 +-
120+
1 file changed, 1 insertion(+), 1 deletion(-)
121+
122+
diff --git a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala b/commo
123+
index 65f914a..3aa4320 100644
124+
--- a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
125+
+++ b/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
126+
@@ -13,11 +13,11 @@ import scala.scalajs.js
127+
128+
object Benchmark {
129+
val benchmarks = js.Array[Benchmark]()
130+
val benchmarkApps = js.Array[BenchmarkApp]()
131+
132+
val global = [-js.Dynamic.global.asInstanceOf[js.Dictionary]-]{+js.Dynamic.glo
133+
global("runScalaJSBenchmarks") = runBenchmarks _
134+
global("initScalaJSBenchmarkApps") = initBenchmarkApps _
135+
136+
def add(benchmark: Benchmark) {
137+
benchmarks.push(benchmark)
138+
139+
140+
141+
[diff] Changes to 'common/src/main/scala/org/scalajs/benchmark/Benchmark.sca100%
142+
EOF
143+
144+
assert_equals 'diff-u10.screen' <<EOF
145+
---
146+
common/src/main/scala/org/scalajs/benchmark/Benchmark.scala | 2 +-
147+
1 file changed, 1 insertion(+), 1 deletion(-)
148+
149+
diff --git a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala b/commo
150+
index 65f914a..3aa4320 100644
151+
--- a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
152+
+++ b/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
153+
@@ -8,21 +8,21 @@
154+
155+
package org.scalajs.benchmark
156+
157+
import scala.compat.Platform
158+
import scala.scalajs.js
159+
160+
object Benchmark {
161+
val benchmarks = js.Array[Benchmark]()
162+
val benchmarkApps = js.Array[BenchmarkApp]()
163+
164+
val global = [-js.Dynamic.global.asInstanceOf[js.Dictionary]-]{+js.Dynamic.glo
165+
global("runScalaJSBenchmarks") = runBenchmarks _
166+
global("initScalaJSBenchmarkApps") = initBenchmarkApps _
167+
168+
def add(benchmark: Benchmark) {
169+
benchmarks.push(benchmark)
170+
if (benchmark.isInstanceOf[BenchmarkApp]) {
171+
benchmarkApps.push(benchmark.asInstanceOf[BenchmarkApp])
172+
}
173+
[diff] Changes to 'common/src/main/scala/org/scalajs/benchmark/Benchmark.scal94%
174+
EOF
175+
176+
assert_equals 'diff-u8.screen' <<EOF
177+
178+
Add type parameter for js.Dynamic
179+
---
180+
common/src/main/scala/org/scalajs/benchmark/Benchmark.scala | 2 +-
181+
1 file changed, 1 insertion(+), 1 deletion(-)
182+
183+
diff --git a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala b/commo
184+
index 65f914a..3aa4320 100644
185+
--- a/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
186+
+++ b/common/src/main/scala/org/scalajs/benchmark/Benchmark.scala
187+
@@ -10,17 +10,17 @@ package org.scalajs.benchmark
188+
189+
import scala.compat.Platform
190+
import scala.scalajs.js
191+
192+
object Benchmark {
193+
val benchmarks = js.Array[Benchmark]()
194+
val benchmarkApps = js.Array[BenchmarkApp]()
195+
196+
val global = [-js.Dynamic.global.asInstanceOf[js.Dictionary]-]{+js.Dynamic.glo
197+
global("runScalaJSBenchmarks") = runBenchmarks _
198+
global("initScalaJSBenchmarkApps") = initBenchmarkApps _
199+
200+
def add(benchmark: Benchmark) {
201+
benchmarks.push(benchmark)
202+
if (benchmark.isInstanceOf[BenchmarkApp]) {
203+
benchmarkApps.push(benchmark.asInstanceOf[BenchmarkApp])
204+
}
205+
[diff] Changes to 'common/src/main/scala/org/scalajs/benchmark/Benchmark.sca100%
206+
EOF
207+
208+
for i in expected/*.screen; do
209+
diff_file="$(basename -- "$i" | sed 's/[.]screen/-from-main.screen/')"
210+
assert_equals "$diff_file" <<EOF
211+
$(cat < "$i")
212+
EOF
213+
done

0 commit comments

Comments
 (0)