rc filetype diff: extract diff parsing from diff-jump

Most diff consumers we've written only care about the "final" state
after parsing through a diff. Let's extract the diff parsing part,
for reuse in several new commands.

In future we should try to use this (or better, a diff-parsing library)
for patch-range.pl. We'd add a callback argument that is invoked once
perl hunk (or line). Unfortunately I haven't found that library for
Perl yet.
This commit is contained in:
Johannes Altmanninger 2024-02-03 00:26:55 +01:00 committed by Maxime Coste
parent ce7ceb1cf0
commit 36efbf4cbf
2 changed files with 179 additions and 102 deletions

127
rc/filetype/diff-parse.pl Executable file
View File

@ -0,0 +1,127 @@
#!/usr/bin/env perl
use warnings;
sub quote {
my $token = shift;
$token =~ s/'/''/g;
return "'$token'";
}
sub fail {
my $reason = shift;
print "set-register e fail " . quote("diff-parse.pl: $reason");
exit;
}
my $begin;
my $end;
while (defined $ARGV[0]) {
if ($ARGV[0] eq "--") {
shift;
last;
}
if ($ARGV[0] =~ m{^(BEGIN|END)$}) {
if (not defined $ARGV[1]) {
fail "missing argument to $ARGV[0]";
}
if ($ARGV[0] eq "BEGIN") {
$begin = $ARGV[1];
} else {
$end = $ARGV[1];
}
shift, shift;
next;
}
fail "unknown argument: $ARGV[0]";
}
# Inputs
our $directory = $ENV{PWD};
our $strip;
our $version = "+";
eval $begin if defined $begin;
# Outputs
our $file;
our $file_line;
our $diff_line_text;
my $other_version;
if ($version eq "+") {
$other_version = "-";
} else {
$other_version = "+";
}
my $is_recursive_diff = 0;
my $state = "header";
my $fallback_file;
my $other_file;
my $other_file_line;
sub strip {
my $is_recursive_diff = shift;
my $f = shift;
my $effective_strip;
if (defined $strip) {
$effective_strip = $strip;
} else {
# 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.
$effective_strip = $is_recursive_diff ? 1 : 0;
}
if ($f !~ m{^/}) {
$f =~ s,^([^/]+/+){$effective_strip},, or fail "directory prefix underflow";
$f = "$directory/$f";
}
return $f;
}
while (<STDIN>) {
s/^(> )*//g;
$diff_line_text = $_;
if (m{^diff\b}) {
$state = "header";
$is_recursive_diff = 1;
if (m{^diff -\S* (\S+) (\S+)$}) {
$fallback_file = strip $is_recursive_diff, ($version eq "+" ? $2 : $1);
}
next;
}
if ($state eq "header") {
if (m{^[$version]{3} ([^\t\n]+)}) {
$file = strip $is_recursive_diff, $1;
next;
}
if (m{^[$other_version]{3} ([^\t\n]+)}) {
$other_file = strip $is_recursive_diff, $1;
next;
}
}
if (m{^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@}) {
$state = "contents";
$file_line = ($version eq "+" ? $2 : $1) - 1;
$other_file_line = ($version eq "+" ? $1 : $2) - 1;
} else {
my $iscontext = m{^[ ]};
if (m{^[ $version]}) {
$file_line++ if defined $file_line;
}
if (m{^[ $other_version]}) {
$other_file_line++ if defined $other_file_line;
}
}
}
if (not defined $file) {
$file = ($fallback_file or $other_file);
}
if (not defined $file) {
fail "missing diff header";
}
eval $end if defined $end;

View File

@ -29,7 +29,7 @@ define-command diff-jump -params .. -docstring %{
- jump to the old file instead of the new file - 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. -<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 ac| %{ evaluate-commands -draft -save-regs c| %{
# Save the column because we will move the cursor. # Save the column because we will move the cursor.
set-register c %val{cursor_column} set-register c %val{cursor_column}
# If there is a "diff" line, we don't need to look further back. # If there is a "diff" line, we don't need to look further back.
@ -41,120 +41,70 @@ define-command diff-jump -params .. -docstring %{
# or content. # or content.
execute-keys Gk execute-keys Gk
} }
set-register a %arg{@} diff-parse BEGIN %{
set-register | %{ my $seen_ddash = 0;
[ -n "$kak_reg_a" ] && eval set -- "$kak_quoted_reg_a" foreach (@ARGV) {
cmd=$(column=$kak_reg_c perl -we ' if ($seen_ddash or !m{^-}) {
sub quote { $directory = $_;
$SQ = "'\''"; } elsif ($_ eq "-") {
$token = shift; $version = "-", $other_version = "+";
$token =~ s/$SQ/$SQ$SQ/g; } elsif (m{^-(\d+)$}) {
return "$SQ$token$SQ"; $strip = $1;
} elsif ($_ eq "--") {
$seen_ddash = 1;
} else {
fail "unknown option: $_";
} }
sub fail { }
$reason = shift; } END %exp{
print "fail " . quote("diff-jump: $reason"); my $file_column;
exit; if (not defined $file_line) {
} $file_line = "";
$version = "+", $other_version = "-"; $file_column = "";
$strip = undef; } else {
$directory = $ENV{PWD}; my $diff_column = %reg{c};
$seen_ddash = 0; $file_column = $diff_column - 1; # Account for [ +-] diff prefix.
foreach (@ARGV) { # If the cursor was on a hunk header, go to the section header if possible.
if ($seen_ddash or !m{^-}) { if ($diff_line_text =~ m{^(@@ -\d+(?:,\d+)? \+\d+(?:,\d+) @@ )([^\n]*)}) {
$directory = $_; my $hunk_header_prefix = $1;
} elsif ($_ eq "-") { my $hunk_header_from_userdiff = $2;
$version = "-", $other_version = "+"; open FILE, "<", $file or fail "failed to open file: $!: $file";
} elsif (m{^-(\d+)$}) { my @lines = <FILE>;
$strip = $1; for (my $i = $file_line - 1; $i >= 0 and $i < scalar @lines; $i--) {
} elsif ($_ eq "--") { if ($lines[$i] !~ m{\Q$hunk_header_from_userdiff}) {
$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; next;
} }
if (m{^[$other_version]{3} ([^\t\n]+)}) { $file_line = $i + 1;
$fallback_file = $1; # Re-add 1 because the @@ line does not have a [ +-] diff prefix.
next; $file_column = $diff_column + 1 - length $hunk_header_prefix;
} last;
}
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; printf "set-register c %%s $file_line $file_column\n", quote($file);
} } -- %arg{@}
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;
}
if ($file !~ m{^/}) {
$file =~ s,^([^/]+/+){$strip},, or fail "directory prefix underflow";
$file = "$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, "<", $file or fail "failed to open file: $!: $file";
@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 "edit -existing -- %s $line $column", quote($file);
' -- "$@")
echo "set-register c $cmd" >"$kak_command_fifo"
}
execute-keys <a-|><ret>
evaluate-commands -client %val{client} %{ evaluate-commands -client %val{client} %{
evaluate-commands -try-client %opt{jumpclient} %{ evaluate-commands -try-client %opt{jumpclient} %{
%reg{c} edit -existing -- %reg{c}
} }
} }
} }
} }
complete-command diff-jump file complete-command diff-jump file
define-command -hidden diff-parse -params 2.. %{
evaluate-commands -save-regs ae %{
set-register a %arg{@}
set-register e nop
set-register | %{
eval set -- "$kak_quoted_reg_a"
perl "${kak_runtime}/rc/filetype/diff-parse.pl" "$@" >"$kak_command_fifo"
}
execute-keys <a-|><ret>
%reg{e}
}
}
§ §
define-command \ define-command \