occivink / kakoune-snippets

Snippet support for kakoune
The Unlicense
46 stars 6 forks source link

[Feature Request] Expand snippet on special keypress #9

Closed andreyorst closed 5 years ago

andreyorst commented 5 years ago

It would be handy to have an option to expand snippets when some user-definde key is pressed. Automatic expansion is good feature, but not always wanted. I like to expand snippets with Tab, and jump betwen placeholders with Tab too. This involves some problems when you actually want to insert tab character. When I've designed SimpleSnippets.vim, I've implemented a function, that checked if typed text is a trigger, and if it is it inserted a snippet, otherwise it acted as tab key was pressed. That allowed me to use tab to completions and snippets.

occivink commented 5 years ago

I made some changes to allow this, you should now be able to do something like:

try %{
   snippets-expand-trigger
} catch %{
    exec <tab>
}

Note that the commit also comes with some breaking changes in the option names.

andreyorst commented 5 years ago

I can't get it to work. I've defined a snippet like this:

set global snippets 'for' %{
snippets-insert %{for (int ${1:i}; $1 < $2; $1++) {
${indent}$0
}}}

After that I've tried to:

    hook global InsertKey <ret> %{ try %{ snippets-expand-trigger } }

and every time I type for I get not a valid trigger error

occivink commented 5 years ago

Right some of this stuff is still a bit underdocumented. I still rely on the two options for (maybe it's worth merging all this stuff into one at some point) for defining the snippet and its trigger, so currently you would have to do something like that

set global snippets_auto_expand false
set global snippets 'For loop' %{
snippets-insert %{for (int ${1:i}; $1 < $2; $1++) {
${indent}$0
}}}
set global snippets_triggers 'for' 'For loop'

And for using <ret>, I'd suggest using a mapping instead of a hook, so that we don't have to remove the inserted newline:

def snippet-or-newline %{ try %{ snippets-expand-trigger b } catch %{ exec <ret> } }
map global insert <ret> "<a-;>: snippet-or-newline<ret>"

... I guess there is still a way to go before this can be considered user-friendly

andreyorst commented 5 years ago

maybe it's worth merging all this stuff into one at some point

Consider implementing this as part of #6. I think it would be much better to have triggers defined within snippet syntax, and not Kakoune configuration.

For example, yasnippet snippet definition:

# -*- mode: C -*-
# name: for
# key: for
# --
for (${1:int} ${2:i} = 0; $2 ${3:< 10}; $2${4:++}) {
    $0
}

key is the thigger here, and snippet body is defined after the header.

Here's how it is done in UltiSnips:

snippet for "for loop (for)"
for (${2:i} = 0; $2 < ${1:count}; ${3:++$2}) {
    ${VISUAL}$0
}
endsnippet

Yeah, your example works just fine. I think such command should be part of snippet, so user just could map it on some key, without explicitly defining it.

andreyorst commented 5 years ago

By the way. Do you somehow track that we are currently within active snippet? If yes,

define-command snippets-expand-or-jump %{
    try %{
        snippets-expand-trigger b
    } catch %{
        snippets-jump-forward-or-key
    }
}

define-command snippets-jump-forward-or-key %{
    try %{
        snippets-jump-forward
    catch %{
        execute-keys some_user_set_key
    }
}

map global insert some_user_set_key "<a-;>: snippet-expand-or-jump<ret>"

This way we could use the same key for both expanding and jumping.

occivink commented 5 years ago

Do you somehow track that we are currently within active snippet?

Not in the sense of the cursor position, but a cheap workaround to that is to keep track of whether there are placeholders remaining. So:

define-command snippets-expand-or-jump %{
    try %{
        snippets-expand-trigger b
    } catch %{
        snippets-jump-forward
    } catch %{
        exec <key>
    }   
}
andreyorst commented 5 years ago

It would be cool to have such function inside plugin core, except it should be ready to use user's keys. Something like so:

declare-option str snippets_user_mapping "<tab>"

map global insert %opt{snippets_user_mapping} "<a-;>:<space>snippets-expand-or-jump<ret>"

define-command snippets-expand-or-jump %{
    try %{
        snippets-expand-trigger b
    } catch %{
        snippets-jump-forward
    } catch %{
        exec %opt{snippets_user_mapping}
    }   
}

Except I'm not sure if this map will respect user config at all...


Although I'm completely fine if there will be just functions, and mapping those would be on user's side.

andreyorst commented 5 years ago

With such config:

map global insert '<tab>' "z<a-;>: snippets-expand-or-jump 'tab'<ret>"

hook global InsertCompletionShow .* %{
    try %{
        execute-keys -draft 'h<a-K>\h<ret>'
        map window insert '<ret>' "z<a-;>: snippets-expand-or-jump 'ret'<ret>"
    }
}

hook global InsertCompletionHide .* %{
    unmap window insert '<ret>' "z<a-;>: snippets-expand-or-jump 'ret'<ret>"
}

define-command snippets-expand-or-jump -params 1 %{
    execute-keys <backspace>
    try %{
        snippets-expand-trigger
    } catch %{
        snippets-select-next-placeholders
    } catch %sh{
        case $1 in
            ret|tab)
                printf "%s\n" "execute-keys -with-hooks <$1>" ;;
            *)
                printf "%s\n" "execute-keys -with-hooks $1" ;;
        esac
    }
}

It works like so:

vaiv

And that's just amazing.

Basically I've made it work just like it worked in my plugin: if popup completion is opened, use return to jump or expand, and tab to cycle. If no popup is opened use tab for expand and jump.

andreyorst commented 5 years ago

@occivink I've updated my configuration in the comment above. I think that it can be added to the Wiki and this issue can be closed.

occivink commented 5 years ago

I don't really want to start wiki pages yet, but feel free to create them if you wish, I'll update them in case of breaking changes.