Context.vim (I want this!)

This is just a random post of something I am jealous of… https://github.com/wellle/context.vim – that is great!

6 Likes

oh my dog yes please !

I wonder what would it take to do that in kakoune.
I’m think of use a tmux pane but it doesn’t feel right.
I guess who have to hack with the json RPC to do it properly.

With the relatively new replace-ranges highlighter it’s possibly to implement this. Currently there a few bugs in the implementation (3644, 3689). The first is a straight up visual bug that needs to be fixed but the second bug just makes it impossible to have a new line in the replaced text so the implementation has to work around that by using the new lines in the buffer (hence <a-:>H or (?=\n) before making the range-spec) and by having multiple replace-ranges to handle nesting. Anyway, python is an easy language to show an example in since it’s white space sensitive.

declare-option range-specs context_replace_range
declare-option str context_replace_range_text
add-highlighter window/context_replace_range_function replace-ranges context_replace_range
define-command -override range-replace %{
    try %{
        exec -draft '<a-:><a-;>K<a-x>s^[^\h]<ret>'
        set window context_replace_range %val{timestamp}
    } catch %{ try %{
        eval -no-hooks -draft %{
            exec [ i k <a-x> s ^\h*(def\h|for\h|if\h)[^\n]+:$ <ret>
            set window context_replace_range_text "%val{selection} --context"
        }
        eval -no-hooks -draft %{
            exec 'k[iK<a-x><a-:>H'
            set window context_replace_range %val{timestamp} "%val{selection_desc}|%opt{context_replace_range_text}"
        }
    }}
}
remove-hooks window context
hook -group context window NormalKey .* range-replace
hook -group context window InsertKey .* range-replace

def my_python_func():
    this()
    that()
    them()

if 'a' in ['a', 'b', 'c']:
    print('hello')
    x = 'a' * 4
    print(x)
    print(x * 4)
    print('a')


for character in ['a', 'b', 'c']:
    print(character)
    print(character * 3)
    print(character * 6)

With some shell scripting to make nesting work and some logic to figure out when to fold (probably should only do it for when %val{selection_desc} is outside %val{window_range}) it doesn’t seem like too too much work to make an equivalent kakoune plugin. The real PITA will be doing it for each file type.

EDIT:
I prefer this version of the function, much simpler.

define-command -override range-replace %{
    try %{
        # fails if the line or the line above aren't in a nested block
        exec -draft '<a-:><a-;>K<a-x>s^[^\h]<ret>'
        set window context_replace_range %val{timestamp}
    } catch %{
        eval -draft %{
            # select the text to replace. Everything between the block start and the current line.
            exec 'k[iK<a-x>s^\h*(def\h|for\h|if\h)[^\n]+:\n\K.*(?=\n)<ret>'
            set window context_replace_range %val{timestamp} "%val{selection_desc}|------ CONTEXT ------"
        }
    } catch %{}
}
7 Likes

@prion Kudos for this nice example.

1 Like

I realized that you can handle nesting using split, eval -itersel and set -add. I haven’t quite figured out how to make sure what’s visible in the current window using pure kakscript but I’m sure it’s do-able. It would probably be a lot easier if %val{window_range} was formatted the same as a selection_desc because window based commands do not seem to work in in -draft contexts.

Anyway if we have some text like

class MyClass():
   my_const = 'fdsfdsf'
   my_other_const = 'fdsfdsf'
   def __init__(self):
       self.value = my_const
       self.value += my_other_const
       for a in [1,2,3,4,5,6]:
           print('------------------')
           print(self.value + ' ' + a)
           print('------------------')

selected then we can do something like

# reset ranges
set window context_replace_range %val{timestamp}
# split into all of our blocks. Currently also includes unrelated blocks but that's probably fixable
exec 'S^\h*(def\h|class\h|if\h|for\h|where\h|else\h|elif\g)[^\n]+:\n<ret>'
# get rid of that annoying selection that's always at the start of every split selection
exec ')<a-space>'
# add each selection to the range spec
eval -itersel %{ set -add window context_replace_range "%val{selection_desc}|" }

to collapse example text down to

class MyClass():
   def __init__(self):
       for a in [1,2,3,4,5,6]:

You can think of a draft context as a separate, temporary window, with its own input queue and set of selections and all that stuff. As a result, anything window or view-related isn’t much use in a draft context, since it’s describing/affecting the temporary window, not the actual window the user is looking at.

@Screwtapello I think @prion wants to pass values to the draft context.

Something like:

evaluate-commands -draft %{
  execute-keys %arg{2} g %arg{4} J <a-x>
  ...
} -- %val{window_range}

could be nice.

I wrote a very simple proof of concept bash script that scans up through the given lines on stdin and prints all lines where the indentation decreases with respect to the previous printed line.

The output of this program could be shown in a new window.

#!/bin/bash

function get_indent_level {
    printf "%s" "$1" | grep -Po '^\s+' | tr -d '\n' | wc -c
}

prev_indent=0
IFS=''
tac /dev/stdin | while read line; do
    indent_level=$(get_indent_level "$line")
    if [ $prev_indent -eq 0 ] || [ $indent_level -lt $prev_indent ]; then
        prev_indent=$indent_level
        printf "%s\n" "$line"
    fi
    if [ $indent_level -eq 0 ]; then
        exit
    fi
done | tac

From kakoune you could do something like

exec -draft Gk<a-x><a-|>context.sh<ret>

A related config in git to specify different strategies to extract the hunk header (similar to the notion of “context” in this thread) depending of the language: https://tekin.co.uk/2020/10/better-git-diff-output-for-ruby-python-elixir-and-more

1 Like
1 Like

I was about to chip in and ask if ul/kak-tree can provide detection for this feature with better semantic accuracy. Looks like @alexherbo2 found a proof of concept for vim, so it should be even easier to reimplement.

Using your code I get the following error:

*** This is the debug buffer, where debug info will be written ***
/home/joe/.config/kak/kakrc:16:1: 'add-highlighter' no window in context
/home/joe/.local/share/kak/kakrc:28:1: 'evaluate-commands' 134:1: 'source' 16:1: 'add-highlighter' no window in context
error while parsing kakrc:
    1:1: 'source' 28:1: 'evaluate-commands' 134:1: 'source' 16:1: 'add-highlighter' no window in context

@JJK96 this seems like a very decent and robust idea. I’ve had some problems with the fifo code — it doesn’t appear to be in a working state (e.g. mktemp creates a file that mkfifo can’t override).

Anyway, I’ve picked this up and rewritten it. fast-context.kak, which I will post separately, up to commit context.kak: truncate to window_width (1ec482b4) · Commits · kstr0k / fast-context.kak · GitLab, is derived from your code, and should have a working fifo-based implementation (after that, I’ve rewritten it to use an infobox and implement other features).

As for the context script posted above, a non-bash, less-external command-hungry version is

#!/bin/sh
set -u

get_indent_level() {
    local trimmed; trimmed=$(echo "$1" | { read -r trimmed; echo "$trimmed"; })
    set -- "${1%"$trimmed"*}"
    echo ${#1}
}

prev_indent=99999
tac |
    while IFS= read -r line; do
        case "$line" in *[^[:space:]]*) ;; *) continue ;; esac
        indent_level=$(get_indent_level "$line")
        if [ $indent_level -lt $prev_indent ]; then
            prev_indent=$indent_level
            printf '%s\n' "$line"
        fi
        [ $indent_level -ne 0 ] || exit
done | tac
2 Likes

Nice work, and thx for the credits!