Skip to content

Commit 7d3a373

Browse files
committed
Draw ruler and box around headers
Rulers are drawn around the first level of headers, and boxes around the second level of headers, if any. The `diff-so-fancy.rulerWidth` config still applies, but only to the rulers. Boxes have just the size of the text inside them. `git show` and `git log` have two levels of headers: (1) commit, and (2) meta (e.g. `modified: diff-so-fancy`). `git diff` only has one level of headers: meta. This allows for a better hierarchical view of commits specifically, with commit lines having a configurable wider ruler around them and meta lines ("children" of commits) having a shorter box around them.
1 parent a417b0f commit 7d3a373

File tree

2 files changed

+76
-66
lines changed

2 files changed

+76
-66
lines changed

diff-so-fancy

Lines changed: 70 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,37 @@ my $ruler_width = git_config("diff-so-fancy.rulerWidth", undef);
2727
my $git_strip_prefix = git_config_boolean("diff.noprefix","false");
2828
my $has_stdin = has_stdin();
2929

30+
my $box_horizontal;
31+
my $box_vertical;
32+
my $box_down;
33+
my $box_up;
34+
# BOX DRAWINGS LIGHT HORIZONTAL http://www.fileformat.info/info/unicode/char/2500/index.htm
35+
# BOX DRAWINGS LIGHT VERTICAL https://www.fileformat.info/info/unicode/char/2502/index.htm
36+
# BOX DRAWINGS LIGHT DOWN AND LEFT https://www.fileformat.info/info/unicode/char/2510/index.htm
37+
# BOX DRAWINGS LIGHT UP AND LEFT https://www.fileformat.info/info/unicode/char/2518/index.htm
38+
if ($use_unicode_dash_for_ruler && should_print_unicode()) {
39+
# $box_horizontal = Encode::encode('UTF-8', "\x{2500}");
40+
$box_horizontal = "\xE2\x94\x80";
41+
# $box_vertical = Encode::encode('UTF-8', "\x{2502}");
42+
$box_vertical = "\xE2\x94\x82";
43+
# $box_down = Encode::encode('UTF-8', "\x{2510}");
44+
$box_down = "\xE2\x94\x90";
45+
# $box_up = Encode::encode('UTF-8', "\x{2518}");
46+
$box_up = "\xE2\x94\x98";
47+
} else {
48+
$box_horizontal = "-";
49+
$box_vertical = "|";
50+
$box_down = ".";
51+
$box_up = "'";
52+
}
53+
3054
my $ansi_regex = qr/\e\[([0-9]{1,3}(;[0-9]{1,3}){0,10})[mK]/;
3155
my $ansi_color_regex = qr/(${ansi_regex})?/;
3256
my $reset_color = color("reset");
3357
my $bold = color("bold");
3458
my $meta_color = "";
59+
my $commit_color = "";
60+
my $min_header_level = 2;
3561

3662
# Set the diff highlight colors from the config
3763
init_diff_highlight_colors();
@@ -159,13 +185,17 @@ sub do_dsf_stuff {
159185

160186
#######################################################################
161187

162-
####################################################################
163-
# Look for git index and replace it horizontal line (header later) #
164-
####################################################################
165-
if ($line =~ /^${ansi_color_regex}index /) {
166-
# Print the line color and then the actual line
188+
########################
189+
# Look for commit line #
190+
########################
191+
if ($line =~ /^${ansi_color_regex}(commit [0-9a-f]{40}.*)$/) {
192+
$commit_color = $1 || get_config_color("commit");
193+
print_header(1,$commit_color,$4);
194+
######################
195+
# Look for git index #
196+
######################
197+
} elsif ($line =~ /^${ansi_color_regex}index /) {
167198
$meta_color = $1 || get_config_color("meta");
168-
169199
# Get the next line without incrementing counter while loop
170200
my $next = $input->[0] || "";
171201
my ($file_1,$file_2);
@@ -185,9 +215,7 @@ sub do_dsf_stuff {
185215
}
186216

187217
if ($file_1 && $file_2) {
188-
print horizontal_rule($meta_color);
189-
print $meta_color . file_change_string($file_1,$file_2) . "\n";
190-
print horizontal_rule($meta_color);
218+
print_header(2,$meta_color,file_change_string($file_1,$file_2));
191219
}
192220
#########################
193221
# Look for the filename #
@@ -198,7 +226,6 @@ sub do_dsf_stuff {
198226
# Mercurial looks like: diff -r 82e55d328c8c hello.c
199227
if ($4 eq "-r") {
200228
$is_mercurial = 1;
201-
$meta_color = get_config_color("meta");
202229
# Git looks like: diff --git a/diff-so-fancy b/diff-so-fancy
203230
} else {
204231
$last_file_seen = $5;
@@ -214,8 +241,6 @@ sub do_dsf_stuff {
214241
# Find the first file: --- a/README.md #
215242
########################################
216243
} elsif (!$in_hunk && $line =~ /^$ansi_color_regex--- (\w\/)?(.+?)(\e|\t|$)/) {
217-
$meta_color = get_config_color("meta");
218-
219244
if ($git_strip_prefix) {
220245
my $file_dir = $4 || "";
221246
$file_1 = $file_dir . $5;
@@ -240,20 +265,8 @@ sub do_dsf_stuff {
240265
$last_file_seen = $file_2;
241266
}
242267

243-
# Print out the top horizontal line of the header
244-
print $reset_color;
245-
print horizontal_rule($meta_color);
246-
247-
# Mercurial coloring is slightly different so we need to hard reset colors
248-
if ($is_mercurial) {
249-
print $reset_color;
250-
}
251-
252-
print $meta_color;
253-
print file_change_string($file_1,$file_2) . "\n";
254-
255-
# Print out the bottom horizontal line of the header
256-
print horizontal_rule($meta_color);
268+
$meta_color ||= get_config_color("meta");
269+
print_header(2,$meta_color,file_change_string($file_1,$file_2));
257270
########################################
258271
# Check for "@@ -3,41 +3,63 @@" syntax #
259272
########################################
@@ -323,10 +336,9 @@ sub do_dsf_stuff {
323336
# Look for binary file changes #
324337
################################
325338
} elsif ($line =~ /^Binary files (\w\/)?(.+?) and (\w\/)?(.+?) differ/) {
326-
my $change = file_change_string($2,$4);
327-
print horizontal_rule($meta_color);
328-
print "$meta_color$change (binary)\n";
329-
print horizontal_rule($meta_color);
339+
my ($change,$change_length) = file_change_string($2,$4);
340+
$meta_color ||= get_config_color("meta");
341+
print_header(2,$meta_color,$change . " (binary)", $change_length + 9);
330342
#####################################################
331343
# Check if we're changing the permissions of a file #
332344
#####################################################
@@ -365,14 +377,9 @@ sub do_dsf_stuff {
365377
my ($file2) = $next =~ /rename to (.+?)(\e|\t|$)/;
366378

367379
if ($file1 && $file2) {
368-
# We may not have extracted this yet, so we pull from the config if not
369-
$meta_color = get_config_color("meta");
380+
$meta_color ||= get_config_color("meta");
381+
print_header(2,$meta_color,file_change_string($file1,$file2));
370382

371-
my $change = file_change_string($file1,$file2);
372-
373-
print horizontal_rule($meta_color);
374-
print $meta_color . $change . "\n";
375-
print horizontal_rule($meta_color);
376383
}
377384

378385
$i += 3; # We've consumed three lines
@@ -613,26 +620,25 @@ sub trim {
613620
return $s;
614621
}
615622

616-
# Print a line of em-dash or line-drawing chars the full width of the screen
617-
sub horizontal_rule {
618-
my $color = $_[0] || "";
619-
my $width = get_terminal_width();
620-
621-
# em-dash http://www.fileformat.info/info/unicode/char/2014/index.htm
622-
#my $dash = "\x{2014}";
623-
# BOX DRAWINGS LIGHT HORIZONTAL http://www.fileformat.info/info/unicode/char/2500/index.htm
624-
my $dash;
625-
if ($use_unicode_dash_for_ruler && should_print_unicode()) {
626-
#$dash = Encode::encode('UTF-8', "\x{2500}");
627-
$dash = "\xE2\x94\x80";
623+
sub print_header {
624+
my $level = shift();
625+
my $color = shift();
626+
my $line = shift();
627+
if ($level < $min_header_level) {
628+
$min_header_level = $level
629+
}
630+
if ($level > $min_header_level) {
631+
my $line_length = shift();
632+
my $ruler = $box_horizontal x ($line_length + 1);
633+
print $color.$ruler.$box_down."\n";
634+
print $color.$line." ".$color.$box_vertical."\n";
635+
print $color.$ruler.$box_up."\n";
628636
} else {
629-
$dash = "-";
637+
my $ruler = $box_horizontal x get_terminal_width();
638+
print $color.$ruler."\n";
639+
print $color.$line."\n";
640+
print $color.$ruler."\n";
630641
}
631-
632-
# Draw the line
633-
my $ret = $color . ($dash x $width) . "$reset_color\n";
634-
635-
return $ret;
636642
}
637643

638644
sub file_change_string {
@@ -641,25 +647,26 @@ sub file_change_string {
641647

642648
# If they're the same it's a modify
643649
if ($file_1 eq $file_2) {
644-
return "modified: $file_1";
650+
return ("modified: $file_1", 10 + length($file_1));
645651
# If the first is /dev/null it's a new file
646652
} elsif ($file_1 eq "/dev/null") {
647653
my $add_color = $DiffHighlight::NEW_HIGHLIGHT[1];
648-
return "added: $add_color$file_2$reset_color";
654+
return ("added: $add_color$file_2$reset_color", 7 + length($file_2));
649655
# If the second is /dev/null it's a deletion
650656
} elsif ($file_2 eq "/dev/null") {
651657
my $del_color = $DiffHighlight::OLD_HIGHLIGHT[1];
652-
return "deleted: $del_color$file_1$reset_color";
658+
return ("deleted: $del_color$file_1$reset_color", 9 + length($file_1));
653659
# If the files aren't the same it's a rename
654660
} elsif ($file_1 ne $file_2) {
655661
my ($old, $new) = DiffHighlight::highlight_pair($file_1,$file_2,{only_diff => 1});
656662
# highlight_pair already includes reset_color, but adds newline characters that need to be trimmed off
657663
$old = trim($old);
658664
$new = trim($new);
659-
return "renamed: $old$meta_color to $new"
665+
$meta_color ||= get_config_color("meta");
666+
return ("renamed: $old$meta_color to $new", 13 + length($file_1) + length($file_2));
660667
# Something we haven't thought of yet
661668
} else {
662-
return "$file_1 -> $file_2";
669+
return ("$file_1 -> $file_2", 4 + length($file_1) + length($file_2));
663670
}
664671
}
665672

@@ -861,6 +868,9 @@ sub color {
861868
if ($str eq "meta") {
862869
# Default ANSI yellow
863870
$ret = git_ansi_color(git_config('color.diff.meta')) || color(11);
871+
} elsif ($str eq "commit") {
872+
# Default ANSI yellow bold
873+
$ret = git_ansi_color(git_config('color.diff.commit')) || color('11_bold');
864874
} elsif ($str eq "reset") {
865875
$ret = color("reset");
866876
} elsif ($str eq "add_line") {

test/diff-so-fancy.bats

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ teardown_file() {
7878
@test "Should not show unicode bytes in hex if missing LC_*/LANG _and_ piping the output" {
7979
unset LESSCHARSET LESSCHARDEF LC_ALL LC_CTYPE LANG
8080
# pipe to cat(1) so we don't open stdout
81-
header=$( printf "%s" "$(load_fixture "ls-function" | $diff_so_fancy | cat)" | head -n3 )
81+
header=$( load_fixture "ls-function" | $diff_so_fancy | cat | head -n3 )
8282
run printf "%s" "$header"
8383
assert_line --index 0 --partial "-----"
8484
assert_line --index 1 --partial "modified: fish/functions/ls.fish"
@@ -121,13 +121,13 @@ teardown_file() {
121121
@test "Empty file add" {
122122
output=$( load_fixture "add_empty_file" | $diff_so_fancy )
123123
run printf "%s" "$output"
124-
assert_line --index 5 --regexp "added:.*empty_file.txt"
124+
assert_line --index 7 --regexp "added:.*empty_file.txt"
125125
}
126126

127127
@test "Empty file delete" {
128128
output=$( load_fixture "remove_empty_file" | $diff_so_fancy )
129129
run printf "%s" "$output"
130-
assert_line --index 5 --regexp "deleted:.*empty_file.txt"
130+
assert_line --index 7 --regexp "deleted:.*empty_file.txt"
131131
}
132132

133133
@test "Move with content change" {
@@ -209,8 +209,8 @@ teardown_file() {
209209
output=$( load_fixture "complex-hunks" | $diff_so_fancy 2>&1 )
210210
run printf "%s" "$output"
211211

212-
assert_line --index 4 --partial "@ libs/header_clean/header_clean.pl:107 @"
213-
refute_output --partial 'Use of uninitialized value'
212+
assert_line --index 6 --partial "@ libs/header_clean/header_clean.pl:107 @"
213+
refute_output --partial 'Use of uninitialized value'
214214
}
215215

216216
@test "Hunk formatting: @@ -1,6 +1,6 @@" {
@@ -223,7 +223,7 @@ teardown_file() {
223223
# stderr forced into output
224224
output=$( load_fixture "single-line-remove" | $diff_so_fancy )
225225
run printf "%s" "$output"
226-
assert_line --index 4 --regexp 'var delayedMessage = "It worked";'
226+
assert_line --index 4 --partial 'var delayedMessage = "It worked";'
227227
}
228228

229229
@test "Three way merge" {

0 commit comments

Comments
 (0)