Run InsertCompletionHide hook before insertions that close completion menu

Insert mode completions are accepted by typing any key.  For example,
if there is a completion "somefunction()", then typing

	some<c-n>;

will insert

	somefunction();

and then the InsertCompletionHide hook will fire.  The hook parameter
is a range that contains the entire thing: the actual completion plus
the trailing semicolon that closed the completion menu.

The [original motivation] for the hook parameter was to support
removing text inserted by completion, so we can apply text edits
or expand snippets instead. One problem is that we don't want to
remove the semicolon. Another problem came up in a discussion
about [snippets]: let's say we have a snippet "add" that expands to

	add(?, ?)

where ? are placeholders. After snippet expansion the cursor replaces
the first placeholder. If I type "ad<c-n>1" I expect to get "add(1, ?)".
If the InsertCompletionHide hook only runs after processing the "1"
keystroke, this is not possible without evil hacks.

Fix these problems by running InsertCompletionHide when a completion is
accepted _before_ inserting anything else into the buffer.  This should
make it much easier to fully implement [LSP text edits]. I doubt
that anyone besides kak-lsp is using the hook parameter today so this
should be a low-risk fix.

[original motivation]: https://github.com/mawww/kakoune/issues/2898
[snippets]: https://github.com/kak-lsp/kak-lsp/pull/616#discussion_r883208858
[LSP text edits]: https://github.com/kak-lsp/kak-lsp/issues/40
This commit is contained in:
Johannes Altmanninger 2022-05-29 08:06:48 +02:00
parent d9ea62666b
commit 66078243ec
7 changed files with 45 additions and 0 deletions

View File

@ -1345,7 +1345,10 @@ public:
selections.sort_and_merge_overlapping();
}
else if (auto cp = key.codepoint())
{
m_completer.try_accept();
insert(*cp);
}
else if (key == ctrl('r'))
{
on_next_key_with_autoinfo(context(), "register", KeymapMode::None,
@ -1353,6 +1356,7 @@ public:
auto cp = key.codepoint();
if (not cp or key == Key::Escape)
return;
m_completer.try_accept();
insert(RegisterManager::instance()[*cp].get(context()));
}, "enter register name", register_doc.str());
update_completions = false;
@ -1415,6 +1419,7 @@ public:
[this, transient](Key key, Context&) {
if (auto cp = get_raw_codepoint(key))
{
m_completer.try_accept();
insert(*cp);
context().hooks().run_hook(Hook::InsertKey, key_to_str(key), context());
if (enabled() and not transient)

View File

@ -474,6 +474,15 @@ void InsertCompleter::update(bool allow_implicit)
auto& get_first(BufferRange& range) { return range.begin; }
auto& get_last(BufferRange& range) { return range.end; }
void InsertCompleter::try_accept()
{
if (m_completions.is_valid() and m_context.has_client()
and m_current_candidate >= 0 and m_current_candidate < m_completions.candidates.size() - 1)
{
reset();
}
}
void InsertCompleter::reset()
{
if (m_explicit_completer or m_completions.is_valid())

View File

@ -84,6 +84,7 @@ public:
void select(int index, bool relative, Vector<Key>& keystrokes);
void update(bool allow_implicit);
void try_accept();
void reset();
void explicit_file_complete();

View File

@ -0,0 +1,4 @@
:update-completions<ret>
i<c-n>a<ret><esc>
:update-completions<ret>
i<c-n><c-p>b<esc>

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,2 @@
accepted completion: <foo()>a
rejected completion b

View File

@ -0,0 +1,23 @@
declare-option completions line1
declare-option completions line2
set-option global completers \
option=line1 \
option=line2 \
define-command update-completions %{
set-option global line1 "1.1@%val{timestamp}" foo()||
set-option global line2 "2.1@%val{timestamp}" foo()||
}
hook global InsertCompletionHide .+ %{
evaluate-commands -draft %{
select %val{hook_param}
execute-keys i<lt><esc>a<gt><esc>
execute-keys <a-h>i "accepted completion: "
}
}
hook global InsertCompletionHide '' %{
evaluate-commands -draft %{
execute-keys <a-h>i "rejected completion "
}
}