When viewing :git diff or :git show within kakoune, this commit

adds the ability to press <ret> within a hunk and navigate to the original
source code. This can be useful because one often needs to go back and forth
between the diff and the full source code.

- You can press <ret> anywhere _within_ a hunk i.e. lines that start with
  ` `, `+`, `-`. You will be taken to the exact line in the source that corresponds
  to where you pressed <ret> in the hunk. It actually does not make sense
  to press <ret> on a `-` line because that does not exist anymore but
  in that case you are taken to a nearby line in the hope this is still useful.

- You can also press <ret> on a range line (lines that
  look like @@ ... @@). If you press <ret> on anywhere on a range line e.g.

  ```
  @@ -120,3 +123,4 @@ fn some_function {
  ```
  The code will try to navigate to the section heading "fn some_function {"
  Note that the section heading is _not_ necessarily located at the
  range line (in the above example the range line is 123).

- You can press <ret> on a +++ line also and you will be taken the first
   line of the file

Caveats:
- Navigation to the original source file will be accurate only if any edits to
  the original source file have been saved to disk, because otherwise
  they will not be detected by the `:git diff` or `:git show` commands
- This feature should work well for most typical uses e.g. `:git diff`, `:git diff HEAD^`
  `:git diff <some-sha1>`. In fact this feature should work in all scenarios when
  the *current files* on disk are being compared _with_ some arbitrary git revision/staging.
  It will be less useful in other scenarios when two arbitrary revisions are being
  compared to each other or when you are trying to compare staging to some revision.
  For example when you invoke `:git diff --staged` you are trying to compare staging
  with HEAD but are navigating to what is currently on disk (which may be different
  from staging).

Co-authored-by: Johannes Altmanninger <aclopte@gmail.com>
This commit is contained in:
Sidharth Kshatriya 2021-07-14 02:18:56 +05:30
parent 860b20ef0a
commit 2825ca5f73

View File

@ -74,9 +74,12 @@ define-command -params 1.. \
show_git_cmd_output() {
local filetype
local map_diff_goto_source
case "$1" in
diff) filetype=diff ;;
log|show) filetype=git-log ;;
diff) map_diff_goto_source=true; filetype=diff ;;
show) map_diff_goto_source=true; filetype=git-log ;;
log) filetype=git-log ;;
status) filetype=git-status ;;
*) return 1 ;;
esac
@ -84,10 +87,17 @@ define-command -params 1.. \
mkfifo ${output}
( git "$@" > ${output} 2>&1 & ) > /dev/null 2>&1 < /dev/null
# We need to unmap in case an existing buffer changes type,
# for example if the user runs "git show" and "git status".
map_diff_goto_source=$([ -n "${map_diff_goto_source}" ] \
&& printf %s "map buffer normal <ret> %[: git-diff-goto-source<ret>] -docstring 'Jump to source from git diff'" \
|| printf %s "unmap buffer normal <ret> %[: git-diff-goto-source<ret>]")
printf %s "evaluate-commands -try-client '$kak_opt_docsclient' %{
edit! -fifo ${output} *git*
set-option buffer filetype '${filetype}'
hook -always -once buffer BufCloseFifo .* %{ nop %sh{ rm -r $(dirname ${output}) } }
${map_diff_goto_source}
}"
}
@ -326,3 +336,108 @@ define-command -params 1.. \
;;
esac
}}
# Options needed by git-diff-goto-source command
declare-option -hidden str git_diff_hunk_filename
declare-option -hidden int git_diff_hunk_line_num_start
declare-option -hidden int git_diff_go_to_line_num
declare-option -hidden str git_diff_git_dir
declare-option -hidden str git_diff_section_heading
declare-option -hidden int git_diff_cursor_column
# Works within :git diff and :git show
define-command git-diff-goto-source \
-docstring 'Navigate to source by pressing the enter key in hunks when git diff is displayed. Works within :git diff and :git show' %{
try %{
set-option global git_diff_git_dir %sh{
git rev-parse --show-toplevel
}
# We will need this later. Need to subtract 1 because a diff has an initial column
# for -,+,<space>
set-option global git_diff_cursor_column %sh{ echo $(($kak_cursor_column-1)) }
# This function works_within a hunk or in the diff header.
# - On a context line or added line, it will navigate to that line.
# - On a deleted line, it will navigate to the context line or added line just above.
# - On a @@ line (i.e. a "hunk header") this will navigate to section heading (see below).
# - A diff header contains lines starting with "diff", "index", "+++", and "---".
# Inside a diff header, this will navigate to the first line of the file.
execute-keys -draft 'x<a-k>^[@ +-]|^diff|^index<ret>'
# Find the source filename for the current hunk (reverse search)
evaluate-commands -draft %{
# First look for the "diff" line. because "+++" may be part of a diff.
execute-keys 'x<semicolon><a-/>^diff<ret></>^\+\+\+ \w([^\n]*)<ret>'
set-option global git_diff_hunk_filename %reg{1}
}
try %{
# Are we inside the diff header? If so simply go to the first line of the file.
# The diff header is everything before the first hunk header.
execute-keys -draft 'x<semicolon><a-?>^diff<ret><a-K>^@@<ret>'
edit -existing "%opt{git_diff_git_dir}%opt{git_diff_hunk_filename}" 1
} catch %{
# Find the source line at which the current hunk starts (reverse search)
evaluate-commands -draft %{
execute-keys 'x<semicolon><a-/>^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@<ret>'
set-option buffer git_diff_hunk_line_num_start %reg{1}
}
# If we're already on a hunk header (i.e. a line that starts with @@) then
# our behavior changes slightly: we need to go look for the section heading.
# For example take this hunk header: @@ -123,4 +123,4 @@ fn some_function_name_possibly
# Here the section heading is "fn some_function_name_possibly". Please note that the section
# heading is NOT necessarily at the hunk start line so we can't trivially extract that.
try %{
# First things first, are we on a hunk header? If not, head to the nearest `catch`
execute-keys -draft 'x<a-k>^@@<ret>'
evaluate-commands -try-client %opt{jumpclient} %{
# Now attempt to find the section heading!
try %{
# First extract the section heading.
evaluate-commands -draft %{
execute-keys 'xs^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@ ([^\n]*)<ret>'
set-option global git_diff_section_heading %reg{2}
}
# Go to the hunk start in the source file. The section header will be above us.
edit -existing "%opt{git_diff_git_dir}%opt{git_diff_hunk_filename}" %opt{git_diff_hunk_line_num_start}
# Search for the raw text of the section, like "fn some_function_name_possibly". That should work most of the time.
set-register / "\Q%opt{git_diff_section_heading}"
# Search backward from where the cursor is now.
# Note that the hunk line number is NOT located at the same place as the section heading.
# After we have found it, adjust the cursor and center the viewport as if we had directly jumped
# to the first character of the section header with and `edit` command.
execute-keys "<a-/><ret><a-semicolon><semicolon>vc"
} catch %{
# There is no section heading, or we can't find it in the source file,
# so just go to the hunk start line.
# NOTE that we DONT go to the saved cursor column,
# because our cursor column will be fixed to the start of the section heading
edit -existing "%opt{git_diff_git_dir}%opt{git_diff_hunk_filename}" %opt{git_diff_hunk_line_num_start}
}
}
} catch %{
# This catch deals with the typical case. We're somewhere on either:
# (a) A context line i.e. lines starting with ' '
# or (b) On a line removal i.e. lines starting with '-'
# or (c) On a line addition i.e. lines starting with '+'
# So now try to figure out a line offset + git_diff_hunk_line_num_start that we need to go to
# Ignoring any diff lines starting with `-`, how many lines from where we
# pressed <ret> till the start of the hunk?
evaluate-commands -draft %{
execute-keys '<a-?>^@@<ret>J<a-s><a-K>^-<ret>'
set-option global git_diff_go_to_line_num %sh{
set -- $kak_reg_hash
line=$(($#+$kak_opt_git_diff_hunk_line_num_start-1))
echo $line
}
}
evaluate-commands -try-client %opt{jumpclient} %{
# Open the source file at the appropriate line number and cursor column
edit -existing "%opt{git_diff_git_dir}%opt{git_diff_hunk_filename}" %opt{git_diff_go_to_line_num} %opt{git_diff_cursor_column}
}
}
}
} catch %{
fail "git-diff-goto-source: unable to navigate to source. Use only inside a diff"
}
}