Compare selections with GNU diff utility

Background:
I’m currently working on some code in which there’s an if/else statement in which I’m pretty sure the body of the if was copy-pasted into the body of the else. Since a small difference can be hard to catch with the naked eye, I thought it would be great if I could diff the 2 sections just as I might diff 2 files.

While I haven’t really used it, I’m aware that there’s a utility included on many unix systems called diff that can diff 2 files or even raw text.

Some searches yielded me to this example:

diff <(echo "$sometext") <(echo "$someothertext")

Kakoune provides great mechanisms for sending selections through shell commands. There seems a good opportunity to achieve my goal here.

Question(s):
Does anyone know or have some ideas on how to diff [let’s start with] 2 selections from within Kakoune? Is this doable without any scripting, or what would be a minimal command implementation to accomplish this?

I think there’s great plugin or rc script opportunities here (like the git integration script), but would like to know if anyone knows or could come up with a quick-n-dirty way of using diff from kak in this way.

Not so much a pointer for implementation (since Vim has its internal diff engine and windowing utilities) but in terms of ideas and interface linediff-vim is probably looking into. Very occasionally (once or twice a year) I need such a tool and I usually go back to Vim to use the above plugin. However it probably wouldn’t be hard to implement such a tool in Kakoune, at least to display a unified format diff in a scratch buffer. It would be trickier to be able to edit the snippet and reflect the changes to the original buffer though.

The literal easiest thing to do would be:

  1. Select the first piece of text

  2. Write it to a temporary file:

     <a-|>cat > /tmp/a.txt <ret>
    
  3. Select the second piece of text

  4. Write it to a different temporary file:

     <a-|>cat > /tmp/b.txt <ret>
    
  5. Create a new scratch buffer, and read in the diff of the two temporary files:

     :edit -scratch *diff* <ret>
     !diff -urw /tmp/a.txt /tmp/b.txt <ret>
    

It wouldn’t be too difficult to write a plugin that used RegisterModified hooks to automatically update a buffer with the diff of two registers. That way you could do something like :new diff-regs a b to open a new window containing a diff buffer comparing the a and b registers, then "ay one one piece of text and "by on the other to fill in the regsiters and watch the diff buffer update.

It would be more work than just using temporary files manually, though.

1 Like

Not this, but you still may like it or pull an idea from it.

# autoload/diff.kak

# refs: https://github.com/ggreer/the_silver_searcher
#       https://github.com/mawww/config/blob/master/kakrc#L101
#       https://github.com/mawww/config/blob/master/kakrc#L136
#       https://github.com/mawww/kakoune/blob/master/doc/pages/command-parsing.asciidoc
#       https://www.gnu.org/software/coreutils/manual/html_node/pr-invocation.html#pr-invocation
#
# diff --paginate (pass output through 'pr' to paginate it, a format tool for hard copy reports)
# 
define-command -docstring "compare 2 files line by line with diff" diff -menu -params 2 -shell-script-candidates %{
    ag --filename -g '' --word-regexp --ignore "$kak_opt_ignored_files"
} %{ diff-to-fifo %arg{1} %arg{2} }
# -------------------------------------------------------------------------------------------------------------------------------------- #
define-command -hidden -docstring "diff command to fifo buffer" -params 2 diff-to-fifo %{ evaluate-commands %sh{
  
  # Edited: now all good for kakoune.
  if output=$(mktemp -d "${TMPDIR:-/tmp}"/kak-fifo.XXXXXXXX)/fifo && mkfifo --mode 600 ${output};
    then ( diff --text \
                --color=never \
                --no-dereference \
                --ignore-all-space \
                --speed-large-files \
                --ignore-blank-lines \
                --ignore-file-name-case \
                --suppress-common-lines \
                --report-identical-files \
                "$1" "$2" > ${output} 2>&1 &
         ) >/dev/null 2>&1 </dev/null

         printf %s\\n "evaluate-commands -try-client '$kak_opt_toolsclient' %{
                          edit! -fifo ${output} *fifo*
                          hook -always -once buffer BufCloseFifo .* %{ nop %sh{ rm -r $(dirname ${output}) } }
                      }"

    else printf "fail \"%s\"" "diff failed on mktemp or mkfifo."
  fi
} }

Works as is. Bye :wave:

1 Like

Based on @Screwtapello’s input, I threw this together, which works well. Kakoune even has built-in syntax highlighting for diffs.

define-command -docstring 'Diff the current selections and display result in a new buffer.' \
diff-selections %{
    evaluate-commands %sh{
        eval set -- "$kak_quoted_selections"
        if [ $# -gt 1 ]; then
            echo "$1" > /tmp/a.txt
            echo "$2" > /tmp/b.txt
            diff -uw /tmp/a.txt /tmp/b.txt > /tmp/diff-result.diff
            echo 'edit -existing -readonly /tmp/diff-result.diff'
        else
            echo "echo -debug 'You must have at least 2 selections to compare.'"
        fi
    }
}

I was hoping to avoid creating tmp files, but it works, is easy, and I couldn’t figure out a more elegant way to do it my limited time and knowledge. I don’t think process substitution <(...) from the original example diff command I posted is available when executing via /bin/sh.

@duncan’s example may add some good ideas.

Incorporating a fifos and/or heredocs were some other thoughts I had. The former is in duncan’s example, but I don’t know if the latter is relevant.

3 Likes

Thank you for implementing this. I made some improvements with regards to cleanup.

define-command -docstring 'Diff the current selections and display result in a new buffer.' \
diff-selections %{
    evaluate-commands %sh{
        eval set -- "$kak_quoted_selections"
        if [ $# -gt 1 ]; then
            dir=$(mktemp -d -t "kak_diff_XXXXXXX")
            a="$dir/a"
            b="$dir/b"
            result="$dir/result"
            printf "%s" "$1" > "$a"
            printf "%s" "$2" > "$b"
            diff -uw "$a" "$b" > "$result"
            printf %s\\n "evaluate-commands -try-client '$kak_opt_toolsclient' %{
                edit -readonly ${result}
                hook -always -once buffer BufClose .* %{ nop %sh{ rm -r ${dir} } }
            }"
        else
            printf "echo -debug 'You must have at least 2 selections to compare.'"
        fi
    }
}
2 Likes