home/rc/filetype/diff.kak
Maxime Coste 7061001728 Add a complete-command command to configure command completion
This makes it possible to change command completion in hooks and
paves the way to more flexibility in how custom commands can be
completed
2022-03-06 10:13:14 +11:00

185 lines
7.5 KiB
Plaintext

hook global BufCreate .*\.(diff|patch) %{
set-option buffer filetype diff
map buffer normal <ret> %{: diff-jump<ret>}
}
hook global WinSetOption filetype=diff %{
require-module diff
}
hook -group diff-highlight global WinSetOption filetype=diff %{
add-highlighter window/diff ref diff
hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/diff }
}
provide-module diff %§
add-highlighter shared/diff group
add-highlighter shared/diff/ regex "^\+[^\n]*\n" 0:green,default
add-highlighter shared/diff/ regex "^-[^\n]*\n" 0:red,default
add-highlighter shared/diff/ regex "^@@[^\n]*@@" 0:cyan,default
define-command diff-jump -params .. -docstring %{
diff-jump [<switches>] [<directory>]: edit the diff's source file at the cursor position.
Paths are resolved relative to <directory>, or the current working directory if unspecified.
Switches:
- jump to the old file instead of the new file
-<num> strip <num> leading directory components, like -p<num> in patch(1). Defaults to 1 if there is a 'diff' line (as printed by 'diff -r'), or 0 otherwise.
} %{
evaluate-commands -draft -save-regs c %{
# Save the column because we will move the cursor.
set-register c %val{cursor_column}
# If there is a "diff" line, we don't need to look further back.
try %{
execute-keys %{<a-l><semicolon><a-?>^(?:> )*diff\b<ret><a-x>}
} catch %{
# A single file diff won't have a diff line. Start parsing from
# the buffer start, so we can tell if +++/--- lines are headers
# or content.
execute-keys Gk
}
evaluate-commands %sh{
printf %s "$kak_selection" |
column=$kak_reg_c perl -we '
sub quote {
$SQ = "'\''";
$token = shift;
$token =~ s/$SQ/$SQ$SQ/g;
return "$SQ$token$SQ";
}
sub fail {
$reason = shift;
print "fail " . quote("diff-jump: $reason");
exit 1;
}
$version = "+", $other_version = "-";
$strip = undef;
$directory = $ENV{PWD};
$seen_ddash = 0;
foreach (@ARGV) {
if ($seen_ddash or !m{^-}) {
$directory = $_;
} elsif ($_ eq "-") {
$version = "-", $other_version = "+";
} elsif (m{^-(\d+)$}) {
$strip = $1;
} elsif ($_ eq "--") {
$seen_ddash = 1;
} else {
fail "unknown option: $_";
}
}
$have_diff_line = 0;
$state = "header";
while (<STDIN>) {
s/^(> )*//g;
$last_line = $_;
if (m{^diff\b}) {
$state = "header";
$have_diff_line = 1;
if (m{^diff -\S* (\S+) (\S+)$}) {
$fallback_file = $version eq "+" ? $2 : $1;
}
next;
}
if ($state eq "header") {
if (m{^[$version]{3} ([^\t\n]+)}) {
$file = $1;
next;
}
if (m{^[$other_version]{3} ([^\t\n]+)}) {
$fallback_file = $1;
next;
}
}
if (m{^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@}) {
$state = "contents";
$line = ($version eq "+" ? $2 : $1) - 1;
} elsif (m{^[ $version]}) {
$line++ if defined $line;
}
}
if (not defined $file) {
$file = $fallback_file;
}
if (not defined $file) {
fail "missing diff header";
}
if (not defined $strip) {
# A "diff -r" or "git diff" adds "diff" lines to
# the output. If no such line is present, we have
# a plain diff between files (not directories), so
# there should be no need to strip the directory.
$strip = $have_diff_line ? 1 : 0;
}
$file =~ s,^([^/]+/+){$strip},, or fail "directory prefix underflow";
$filepath = "$directory/$file";
if (defined $line) {
$column = $ENV{column} - 1; # Account for [ +-] diff prefix.
# If the cursor was on a hunk header, go to the section header if possible.
if ($last_line =~ m{^(@@ -\d+(?:,\d+)? \+\d+(?:,\d+) @@ )([^\n]*)}) {
$hunk_header_prefix = $1;
$hunk_header_from_userdiff = $2;
open FILE, "<", $filepath or fail "failed to open file: $!: $filepath";
@lines = <FILE>;
for (my $i = $line - 1; $i >= 0 && $i < scalar @lines; $i--) {
if ($lines[$i] !~ m{\Q$hunk_header_from_userdiff}) {
next;
}
$line = $i + 1;
# Re-add 1 because the @@ line does not have a [ +-] diff prefix.
$column = $column + 1 - length $hunk_header_prefix;
last;
}
}
}
printf "set-register c %s $line $column", quote($filepath);
' -- "$@"
}
evaluate-commands -client %val{client} %{
evaluate-commands -try-client %opt{jumpclient} %{
edit -existing -- %reg{c}
}
}
}
}
complete-command diff-jump file
§
define-command \
-docstring %{diff-select-file: Select surrounding patch file} \
-params 0 \
diff-select-file %{
evaluate-commands -itersel -save-regs 'ose/' %{
try %{
execute-keys '"oZgl<a-?>^diff <ret>;"sZ' 'Ge"eZ'
try %{ execute-keys '"sz?\n(?=diff )<ret>"e<a-Z><lt>' }
execute-keys '"ez'
} catch %{
execute-keys '"oz'
fail 'Not in a diff file'
}
}
}
define-command \
-docstring %{diff-select-hunk: Select surrounding patch hunk} \
-params 0 \
diff-select-hunk %{
evaluate-commands -itersel -save-regs 'ose/' %{
try %{
execute-keys '"oZgl<a-?>^@@ <ret>;"sZ' 'Ge"eZ'
try %{ execute-keys '"sz?\n(?=diff )<ret>"e<a-Z><lt>' }
try %{ execute-keys '"sz?\n(?=@@ )<ret>"e<a-Z><lt>' }
execute-keys '"ez'
} catch %{
execute-keys '"oz'
fail 'Not in a diff hunk'
}
}
}