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
-<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.
set-register c %val{cursor_column}
# 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.
execute-keys Gk
}
set-register a %arg{@}
set-register | %{
[ -n "$kak_reg_a" ] && eval set -- "$kak_quoted_reg_a"
cmd=$(column=$kak_reg_c perl -we '
sub quote {
$SQ = "'\''";
$token = shift;
$token =~ s/$SQ/$SQ$SQ/g;
return "$SQ$token$SQ";
diff-parse BEGIN %{
my $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: $_";
}
sub fail {
$reason = shift;
print "fail " . quote("diff-jump: $reason");
exit;
}
$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;
}
} END %exp{
my $file_column;
if (not defined $file_line) {
$file_line = "";
$file_column = "";
} else {
my $diff_column = %reg{c};
$file_column = $diff_column - 1; # Account for [ +-] diff prefix.
# If the cursor was on a hunk header, go to the section header if possible.
if ($diff_line_text =~ m{^(@@ -\d+(?:,\d+)? \+\d+(?:,\d+) @@ )([^\n]*)}) {
my $hunk_header_prefix = $1;
my $hunk_header_from_userdiff = $2;
open FILE, "<", $file or fail "failed to open file: $!: $file";
my @lines = <FILE>;
for (my $i = $file_line - 1; $i >= 0 and $i < scalar @lines; $i--) {
if ($lines[$i] !~ m{\Q$hunk_header_from_userdiff}) {
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;
$file_line = $i + 1;
# Re-add 1 because the @@ line does not have a [ +-] diff prefix.
$file_column = $diff_column + 1 - length $hunk_header_prefix;
last;
}
}
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;
}
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>
}
printf "set-register c %%s $file_line $file_column\n", quote($file);
} -- %arg{@}
evaluate-commands -client %val{client} %{
evaluate-commands -try-client %opt{jumpclient} %{
%reg{c}
edit -existing -- %reg{c}
}
}
}
}
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 \