From 36efbf4cbfe70c7fb8ca8ac6171cd5d8b134fdc0 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Sat, 3 Feb 2024 00:26:55 +0100 Subject: [PATCH] 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. --- rc/filetype/diff-parse.pl | 127 +++++++++++++++++++++++++++++++ rc/filetype/diff.kak | 154 +++++++++++++------------------------- 2 files changed, 179 insertions(+), 102 deletions(-) create mode 100755 rc/filetype/diff-parse.pl diff --git a/rc/filetype/diff-parse.pl b/rc/filetype/diff-parse.pl new file mode 100755 index 000000000..f7858eb7a --- /dev/null +++ b/rc/filetype/diff-parse.pl @@ -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 () { + 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; diff --git a/rc/filetype/diff.kak b/rc/filetype/diff.kak index 6d7ba695c..530bf3088 100644 --- a/rc/filetype/diff.kak +++ b/rc/filetype/diff.kak @@ -29,7 +29,7 @@ define-command diff-jump -params .. -docstring %{ - jump to the old file instead of the new file - strip leading directory components, like -p 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 () { - 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 = ; + 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 = ; - 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 + } + 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 + %reg{e} + } +} + ยง define-command \