Add a complete-command command to configure command completion

This makes it possible to change command completion in hooks and
paves the way to more flexibility in how custom commands can be
completed
This commit is contained in:
Maxime Coste 2022-02-22 20:14:47 +11:00
parent b915e4e11b
commit 7061001728
22 changed files with 276 additions and 125 deletions

View File

@ -3,6 +3,10 @@
This changelog contains major and/or breaking changes to Kakoune between
released versions.
== Development version
* `complete-command` (See <<commands#configuring-command-completion,`:doc commands configuring-command-completion`>>
== Kakoune 2021.11.07
* Support for curly and separately colored underlines (undocumented in 2021.10.28)

View File

@ -180,6 +180,11 @@ of the file onto the filesystem
define a new alias named *name* in *scope*
(See <<aliases,Using aliases>> and <<scopes#,`:doc scopes`>>)
*complete-command* [<switches>] <name> <type> [<param>]::
*alias* compl +
configure how a command completion works
(See <<configuring-command-completion,Configuring command completion>>)
*unalias* <scope> <name> [<command>]::
remove an alias if its current value is the same as the one passed
as an optional parameter, remove it unconditionally otherwise
@ -473,25 +478,59 @@ New commands can be defined using the *define-command* command:
define the documentation string for the command
*-menu*:::
the suggestions generated by the completion options are the only
permitted parameters.
*-file-completion*:::
try file completion on any parameter passed to this command
*-client-completion*:::
try client name completion on any parameter passed to this command
*-buffer-completion*:::
try buffer name completion on any parameter passed to this command
*-command-completion*:::
try command completion on any parameter passed to this command
*-shell-completion*:::
try shell command completion on any parameter passed to this command
*-shell-script-completion*:::
*-shell-script-candidates*:::
old-style command completion specification, function as-if
the switch and its eventual parameter was passed to the
*complete-command* command
(See <<configuring-command-completion,Configuring command completion>>)
The use of those switches is discouraged in favor of the
*complete-command* command.
Using shell expansion allows defining complex commands or accessing
Kakoune's state:
---------------------------------------------------------------------
# create a directory for current buffer if it does not exist
define-command mkdir %{ nop %sh{ mkdir -p $(dirname $kak_buffile) } }
---------------------------------------------------------------------
== Configuring command completion
Command completion can be configured with the *complete-command* command:
*complete-command* [<switches>] <command_name> <completion_type> [<parameter>]::
*switches* can be:
*-menu*:::
the suggestions generated by the completion options are the only
permitted parameters. Kakoune will autoselect the best completion
candidate on command validation.
*completion_type* can be:
*file-completion*:::
try file completion on any parameter passed to the command
*client-completion*:::
try client name completion on any parameter passed to the command
*buffer-completion*:::
try buffer name completion on any parameter passed to the command
*command-completion*:::
try command completion on any parameter passed to the command
*shell-completion*:::
try shell command completion on any parameter passed to the command
*shell-script-completion*:::
following string is a shell command which takes parameters as
positional params and outputs one completion candidate per line.
The provided shell command will run after each keypress.
@ -507,14 +546,14 @@ New commands can be defined using the *define-command* command:
Position of the cursor inside the token being completed, in bytes
from token start.
*-shell-script-candidates*:::
following string is a shell command which takes parameters as
*shell-script-candidates*:::
following string is a shell script which takes parameters as
positional params and outputs one completion candidate per line.
The provided shell command will run once at the beginning of each
The provided shell script will run once at the beginning of each
completion session, candidates are cached and then used by kakoune
internal fuzzy engine.
During the execution of the shell command, the following env vars are
During the execution of the shell script, the following env vars are
available:
*$kak_token_to_complete*::::
@ -522,14 +561,6 @@ New commands can be defined using the *define-command* command:
Note that unlike the Unix `argv` tradition,
0 is the first argument, not the command name itself.
Using shell expansion allows defining complex commands or accessing
Kakoune's state:
---------------------------------------------------------------------
# create a directory for current buffer if it does not exist
define-command mkdir %{ nop %sh{ mkdir -p $(dirname $kak_buffile) } }
---------------------------------------------------------------------
== Aliases
With `:alias`, commands can be given additional names.

View File

@ -19,14 +19,14 @@ add-highlighter shared/diff/ regex "^\+[^\n]*\n" 0:green,default
add-highlighter shared/diff/ regex "^-[^\n]*\n" 0:red,default
add-highlighter shared/diff/ regex "^@@[^\n]*@@" 0:cyan,default
define-command diff-jump \
-docstring %{diff-jump [<switches>] [<directory>]: edit the diff's source file at the cursor position.
Paths are resolved relative to <directory>, or the current working directory if unspecified.
define-command diff-jump -params .. -docstring %{
diff-jump [<switches>] [<directory>]: edit the diff's source file at the cursor position.
Paths are resolved relative to <directory>, or the current working directory if unspecified.
Switches:
- jump to the old file instead of the new file
-<num> strip <num> leading directory components, like -p<num> in patch(1). Defaults to 1 if there is a 'diff' line (as printed by 'diff -r'), or 0 otherwise.} \
-params .. -file-completion %{
Switches:
- jump to the old file instead of the new file
-<num> strip <num> leading directory components, like -p<num> in patch(1). Defaults to 1 if there is a 'diff' line (as printed by 'diff -r'), or 0 otherwise.
} %{
evaluate-commands -draft -save-regs c %{
# Save the column because we will move the cursor.
set-register c %val{cursor_column}
@ -146,6 +146,7 @@ Switches:
}
}
}
complete-command diff-jump file
§

View File

@ -56,7 +56,7 @@ add-highlighter shared/kakrc/shell8 region -recurse '<' '(^|\h)\K-shell-script-
evaluate-commands %sh{
# Grammar
keywords="add-highlighter alias arrange-buffers buffer buffer-next buffer-previous catch
change-directory colorscheme debug declare-option declare-user-mode define-command
change-directory colorscheme debug declare-option declare-user-mode define-command complete-command
delete-buffer delete-buffer! echo edit edit! enter-user-mode evaluate-commands execute-keys
fail hook info kill kill! map menu nop on-key prompt provide-module quit quit!
remove-highlighter remove-hooks rename-buffer rename-client rename-session require-module

View File

@ -4,7 +4,7 @@ declare-option -docstring "name of the client in which utilities display informa
str toolsclient
declare-option -hidden int grep_current_line 0
define-command -params .. -file-completion -docstring %{
define-command -params .. -docstring %{
grep [<arguments>]: grep utility wrapper
All optional arguments are forwarded to the grep utility
} grep %{ evaluate-commands %sh{
@ -23,6 +23,7 @@ define-command -params .. -file-completion -docstring %{
hook -always -once buffer BufCloseFifo .* %{ nop %sh{ rm -r $(dirname ${output}) } }
}"
}}
complete-command grep file
hook -group grep-highlight global WinSetOption filetype=grep %{
add-highlighter window/grep group

View File

@ -36,22 +36,25 @@ define-command -hidden -params 2.. iterm-terminal-split-impl %{
}
}
define-command iterm-terminal-vertical -params 1.. -shell-completion -docstring '
define-command iterm-terminal-vertical -params 1.. -docstring '
iterm-terminal-vertical <program> [<arguments>]: create a new terminal as an iterm pane
The current pane is split into two, left and right
The program passed as argument will be executed in the new terminal'\
%{
iterm-terminal-split-impl 'vertically' %arg{@}
}
define-command iterm-terminal-horizontal -params 1.. -shell-completion -docstring '
complete-command iterm-terminal-vertical shell
define-command iterm-terminal-horizontal -params 1.. -docstring '
iterm-terminal-horizontal <program> [<arguments>]: create a new terminal as an iterm pane
The current pane is split into two, top and bottom
The program passed as argument will be executed in the new terminal'\
%{
iterm-terminal-split-impl 'horizontally' %arg{@}
}
complete-command iterm-terminal-horizontal shell
define-command iterm-terminal-tab -params 1.. -shell-completion -docstring '
define-command iterm-terminal-tab -params 1.. -docstring '
iterm-terminal-tab <program> [<arguments>]: create a new terminal as an iterm tab
The program passed as argument will be executed in the new terminal'\
%{
@ -76,8 +79,9 @@ The program passed as argument will be executed in the new terminal'\
-e "end tell" >/dev/null
}
}
complete-command iterm-terminal-tab shell
define-command iterm-terminal-window -params 1.. -shell-completion -docstring '
define-command iterm-terminal-window -params 1.. -docstring '
iterm-terminal-window <program> [<arguments>]: create a new terminal as an iterm window
The program passed as argument will be executed in the new terminal'\
%{
@ -100,8 +104,9 @@ The program passed as argument will be executed in the new terminal'\
-e "end tell" >/dev/null
}
}
complete-command iterm-terminal-window shell
define-command iterm-focus -params ..1 -client-completion -docstring '
define-command iterm-focus -params ..1 -docstring '
iterm-focus [<client>]: focus the given client
If no client is passed then the current one is used' \
%{
@ -131,6 +136,7 @@ If no client is passed then the current one is used' \
fi
}
}
complete-command iterm-focus client
alias global focus iterm-focus
alias global terminal iterm-terminal-vertical

View File

@ -10,7 +10,7 @@ evaluate-commands %sh{
declare-option -docstring %{window type that kitty creates on new and repl calls (window|os-window)} str kitty_window_type window
define-command kitty-terminal -params 1.. -shell-completion -docstring '
define-command kitty-terminal -params 1.. -docstring '
kitty-terminal <program> [<arguments>]: create a new terminal as a kitty window
The program passed as argument will be executed in the new terminal' \
%{
@ -28,8 +28,9 @@ The program passed as argument will be executed in the new terminal' \
kitty @ $listen launch --no-response --type="$kak_opt_kitty_window_type" --cwd="$PWD" $match "$@"
}
}
complete-command kitty-terminal shell
define-command kitty-terminal-tab -params 1.. -shell-completion -docstring '
define-command kitty-terminal-tab -params 1.. -docstring '
kitty-terminal-tab <program> [<arguments>]: create a new terminal as kitty tab
The program passed as argument will be executed in the new terminal' \
%{
@ -47,8 +48,9 @@ The program passed as argument will be executed in the new terminal' \
kitty @ $listen launch --no-response --type=tab --cwd="$PWD" $match "$@"
}
}
complete-command kitty-terminal-tab shell
define-command kitty-focus -params ..1 -client-completion -docstring '
define-command kitty-focus -params ..1 -docstring '
kitty-focus [<client>]: focus the given client
If no client is passed then the current one is used' \
%{
@ -70,6 +72,7 @@ If no client is passed then the current one is used' \
fi
}
}
complete-command kitty-focus client
alias global terminal kitty-terminal
alias global terminal-tab kitty-terminal-tab

View File

@ -1,4 +1,4 @@
define-command new -params .. -command-completion -docstring '
define-command new -params .. -docstring '
new [<commands>]: create a new Kakoune client
The ''terminal'' alias is being used to determine the user''s preferred terminal emulator
The optional arguments are passed as commands to the new client' \
@ -6,3 +6,4 @@ The optional arguments are passed as commands to the new client' \
terminal kak -c %val{session} -e "%arg{@}"
}
complete-command new command

View File

@ -12,7 +12,6 @@ define-command -docstring %{
All optional parameters are forwarded to the new terminal window
} \
-params .. \
-shell-completion \
dtach-repl %{ terminal sh -c %{
file="$(mktemp -u -t kak_dtach_repl.XXXXX)"
trap 'rm -f "${file}"' EXIT
@ -22,6 +21,7 @@ define-command -docstring %{
dtach -c "${file}" -E sh -c "${@:-$SHELL}" || "${@:-$SHELL}"
} -- %val{client} %val{session} %arg{@}
}
complete-command dtach-repl shell
define-command dtach-send-text -params 0..1 -docstring %{
dtach-send-text [text]: Send text to the REPL.

View File

@ -4,7 +4,7 @@ hook global ModuleLoaded kitty %{
provide-module kitty-repl %{
define-command -params .. -shell-completion \
define-command -params .. \
-docstring %{
kitty-repl [<arguments>]: Create a new window for repl interaction.
@ -31,6 +31,7 @@ define-command -params .. -shell-completion \
kitty @ $listen launch --no-response --keep-focus --type="$kak_opt_kitty_window_type" --title=kak_repl_window --cwd="$PWD" $match $cmd
}
}
complete-command kitty-repl shell
define-command -hidden -params 0..1 \
-docstring %{

View File

@ -23,17 +23,20 @@ define-command -hidden -params 1.. tmux-repl-impl %{
}
}
define-command tmux-repl-vertical -params 0.. -command-completion -docstring "Create a new vertical pane for repl interaction" %{
define-command tmux-repl-vertical -params 0.. -docstring "Create a new vertical pane for repl interaction" %{
tmux-repl-impl 'split-window -v' %arg{@}
}
complete-command tmux-repl-vertical command
define-command tmux-repl-horizontal -params 0.. -command-completion -docstring "Create a new horizontal pane for repl interaction" %{
define-command tmux-repl-horizontal -params 0.. -docstring "Create a new horizontal pane for repl interaction" %{
tmux-repl-impl 'split-window -h' %arg{@}
}
complete-command tmux-repl-horizontal command
define-command tmux-repl-window -params 0.. -command-completion -docstring "Create a new window for repl interaction" %{
define-command tmux-repl-window -params 0.. -docstring "Create a new window for repl interaction" %{
tmux-repl-impl 'new-window' %arg{@}
}
complete-command tmux-repl-window command
define-command -params 0..1 tmux-repl-set-pane -docstring %{
tmux-repl-set-pane [pane number]: Set an existing tmux pane for repl interaction

View File

@ -11,7 +11,6 @@ define-command -docstring %{
All optional parameters are forwarded to the new window
} \
-params .. \
-shell-completion \
x11-repl %{ x11-terminal sh -c %{
winid="${WINDOWID:-$(xdotool search --pid ${PPID} | tail -1)}"
printf "evaluate-commands -try-client $1 \
@ -20,6 +19,7 @@ define-command -docstring %{
[ "$1" ] && "$@" || "$SHELL"
} -- %val{client} %val{session} %arg{@}
}
complete-command x11-repl shell
define-command x11-send-text -params 0..1 -docstring %{
x11-send-text [text]: Send text to the REPL window.

View File

@ -28,21 +28,25 @@ define-command screen-terminal-impl -hidden -params 3.. %{
}
}
define-command screen-terminal-vertical -params 1.. -shell-completion -docstring '
define-command screen-terminal-vertical -params 1.. -docstring '
screen-terminal-vertical <program> [<arguments>] [<arguments>]: create a new terminal as a screen pane
The current pane is split into two, left and right
The program passed as argument will be executed in the new terminal' \
%{
screen-terminal-impl 'split -v' 'focus right' %arg{@}
}
define-command screen-terminal-horizontal -params 1.. -shell-completion -docstring '
complete-command screen-terminal-vertical shell
define-command screen-terminal-horizontal -params 1.. -docstring '
screen-terminal-horizontal <program> [<arguments>]: create a new terminal as a screen pane
The current pane is split into two, top and bottom
The program passed as argument will be executed in the new terminal' \
%{
screen-terminal-impl 'split -h' 'focus down' %arg{@}
}
define-command screen-terminal-window -params 1.. -shell-completion -docstring '
complete-command screen-terminal-horizontal shell
define-command screen-terminal-window -params 1.. -docstring '
screen-terminal-window <program> [<arguments>]: create a new terminal as a screen window
The program passed as argument will be executed in the new terminal' \
%{
@ -51,8 +55,9 @@ The program passed as argument will be executed in the new terminal' \
screen -X screen "$@" < "/dev/$tty"
}
}
complete-command screen-terminal-window shell
define-command screen-focus -params ..1 -client-completion -docstring '
define-command screen-focus -params ..1 -docstring '
screen-focus [<client>]: focus the given client
If no client is passed then the current one is used' \
%{
@ -67,6 +72,7 @@ If no client is passed then the current one is used' \
fi
}
}
complete-command screen-focus client
alias global focus screen-focus
alias global terminal screen-terminal-vertical

View File

@ -30,28 +30,33 @@ define-command -hidden -params 2.. tmux-terminal-impl %{
}
}
define-command tmux-terminal-vertical -params 1.. -shell-completion -docstring '
define-command tmux-terminal-vertical -params 1.. -docstring '
tmux-terminal-vertical <program> [<arguments>]: create a new terminal as a tmux pane
The current pane is split into two, top and bottom
The program passed as argument will be executed in the new terminal' \
%{
tmux-terminal-impl 'split-window -v' %arg{@}
}
define-command tmux-terminal-horizontal -params 1.. -shell-completion -docstring '
complete-command tmux-terminal-vertical shell
define-command tmux-terminal-horizontal -params 1.. -docstring '
tmux-terminal-horizontal <program> [<arguments>]: create a new terminal as a tmux pane
The current pane is split into two, left and right
The program passed as argument will be executed in the new terminal' \
%{
tmux-terminal-impl 'split-window -h' %arg{@}
}
define-command tmux-terminal-window -params 1.. -shell-completion -docstring '
complete-command tmux-terminal-horizontal shell
define-command tmux-terminal-window -params 1.. -docstring '
tmux-terminal-window <program> [<arguments>] [<arguments>]: create a new terminal as a tmux window
The program passed as argument will be executed in the new terminal' \
%{
tmux-terminal-impl 'new-window' %arg{@}
}
complete-command tmux-terminal-window shell
define-command tmux-focus -params ..1 -client-completion -docstring '
define-command tmux-focus -params ..1 -docstring '
tmux-focus [<client>]: focus the given client
If no client is passed then the current one is used' \
%{
@ -66,6 +71,7 @@ If no client is passed then the current one is used' \
fi
}
}
complete-command tmux-focus client
## The default behaviour for the `new` command is to open an horizontal pane in a tmux session
alias global focus tmux-focus

View File

@ -28,7 +28,7 @@ A shell command is appended to the one set in this option at runtime} \
done
}
define-command wayland-terminal -params 1.. -shell-completion -docstring '
define-command wayland-terminal -params 1.. -docstring '
wayland-terminal <program> [<arguments>]: create a new terminal as a Wayland window
The program passed as argument will be executed in the new terminal' \
%{
@ -43,13 +43,15 @@ The program passed as argument will be executed in the new terminal' \
}
}
}
complete-command wayland-terminal shell
define-command wayland-focus -params ..1 -client-completion -docstring '
define-command wayland-focus -params ..1 -docstring '
wayland-focus [<kakoune_client>]: focus a given client''s window
If no client is passed, then the current client is used' \
%{
fail There is no way to focus another window on Wayland
}
complete-command wayland-focus client
alias global focus wayland-focus
alias global terminal wayland-terminal

View File

@ -33,7 +33,7 @@ A shell command is appended to the one set in this option at runtime} \
done
}
define-command x11-terminal -params 1.. -shell-completion -docstring '
define-command x11-terminal -params 1.. -docstring '
x11-terminal <program> [<arguments>]: create a new terminal as an X11 window
The program passed as argument will be executed in the new terminal' \
%{
@ -48,8 +48,9 @@ The program passed as argument will be executed in the new terminal' \
}
}
}
complete-command x11-terminal shell
define-command x11-focus -params ..1 -client-completion -docstring '
define-command x11-focus -params ..1 -docstring '
x11-focus [<kakoune_client>]: focus a given client''s window
If no client is passed, then the current client is used' \
%{
@ -62,6 +63,7 @@ If no client is passed, then the current client is used' \
fi
}
}
complete-command x11-focus client
alias global focus x11-focus
alias global terminal x11-terminal

View File

@ -41,6 +41,15 @@ void CommandManager::register_command(String command_name,
std::move(completer) };
}
void CommandManager::set_command_completer(StringView command_name, CommandCompleter completer)
{
auto it = m_commands.find(command_name);
if (it == m_commands.end())
throw runtime_error(format("no such command '{}'", command_name));
it->value.completer = std::move(completer);
}
bool CommandManager::module_defined(StringView module_name) const
{
return m_modules.find(module_name) != m_modules.end();

View File

@ -112,6 +112,8 @@ public:
CommandHelper helper = CommandHelper(),
CommandCompleter completer = CommandCompleter());
void set_command_completer(StringView command_name, CommandCompleter completer);
Completions complete_command_name(const Context& context, StringView query) const;
void clear_last_complete_command() { m_last_complete_command = String{}; }

View File

@ -1159,6 +1159,82 @@ Vector<String> params_to_shell(const ParametersParser& parser)
return vars;
}
CommandCompleter make_command_completer(StringView type, StringView param, Completions::Flags completions_flags)
{
if (type == "file")
{
return [=](const Context& context, CompletionFlags flags,
CommandParameters params,
size_t token_to_complete, ByteCount pos_in_token) {
const String& prefix = params[token_to_complete];
const auto& ignored_files = context.options()["ignored_files"].get<Regex>();
return Completions{0_byte, pos_in_token,
complete_filename(prefix, ignored_files,
pos_in_token, FilenameFlags::Expand),
completions_flags};
};
}
else if (type == "client")
{
return [=](const Context& context, CompletionFlags flags,
CommandParameters params,
size_t token_to_complete, ByteCount pos_in_token)
{
const String& prefix = params[token_to_complete];
auto& cm = ClientManager::instance();
return Completions{0_byte, pos_in_token,
cm.complete_client_name(prefix, pos_in_token),
completions_flags};
};
}
else if (type == "buffer")
{
return [=](const Context& context, CompletionFlags flags,
CommandParameters params,
size_t token_to_complete, ByteCount pos_in_token)
{
return add_flags(complete_buffer_name<false>, completions_flags)(
context, flags, params[token_to_complete], pos_in_token);
};
}
else if (type == "shell-script")
{
if (param.empty())
throw runtime_error("shell-script requires a shell script parameter");
return ShellScriptCompleter{param.str(), completions_flags};
}
else if (type == "shell-script-candidates")
{
if (param.empty())
throw runtime_error("shell-script-candidates requires a shell script parameter");
return ShellCandidatesCompleter{param.str(), completions_flags};
}
else if (type == "command")
{
return [](const Context& context, CompletionFlags flags,
CommandParameters params,
size_t token_to_complete, ByteCount pos_in_token)
{
return CommandManager::instance().complete(
context, flags, params, token_to_complete, pos_in_token);
};
}
else if (type == "shell")
{
return [=](const Context& context, CompletionFlags flags,
CommandParameters params,
size_t token_to_complete, ByteCount pos_in_token)
{
return add_flags(shell_complete, completions_flags)(
context, flags, params[token_to_complete], pos_in_token);
};
}
else
throw runtime_error(format("invalid command completion type '{}'", type));
}
void define_command(const ParametersParser& parser, Context& context, const ShellContext&)
{
const String& cmd_name = parser[0];
@ -1211,75 +1287,22 @@ void define_command(const ParametersParser& parser, Context& context, const Shel
}
CommandCompleter completer;
if (parser.get_switch("file-completion"))
for (StringView completion_switch : {"file-completion", "client-completion", "buffer-completion",
"shell-script-completion", "shell-script-candidates",
"command-completion", "shell-completion"})
{
completer = [=](const Context& context, CompletionFlags flags,
CommandParameters params,
size_t token_to_complete, ByteCount pos_in_token)
if (auto param = parser.get_switch(completion_switch))
{
const String& prefix = params[token_to_complete];
const auto& ignored_files = context.options()["ignored_files"].get<Regex>();
return Completions{0_byte, pos_in_token,
complete_filename(prefix, ignored_files,
pos_in_token, FilenameFlags::Expand),
completions_flags};
};
constexpr StringView suffix = "-completion";
if (completion_switch.ends_with(suffix))
completion_switch = completion_switch.substr(0, completion_switch.length() - suffix.length());
completer = make_command_completer(completion_switch, *param, completions_flags);
break;
}
}
else if (parser.get_switch("client-completion"))
{
completer = [=](const Context& context, CompletionFlags flags,
CommandParameters params,
size_t token_to_complete, ByteCount pos_in_token)
{
const String& prefix = params[token_to_complete];
auto& cm = ClientManager::instance();
return Completions{0_byte, pos_in_token,
cm.complete_client_name(prefix, pos_in_token),
completions_flags};
};
}
else if (parser.get_switch("buffer-completion"))
{
completer = [=](const Context& context, CompletionFlags flags,
CommandParameters params,
size_t token_to_complete, ByteCount pos_in_token)
{
return add_flags(complete_buffer_name<false>, completions_flags)(
context, flags, params[token_to_complete], pos_in_token);
};
}
else if (auto shell_script = parser.get_switch("shell-script-completion"))
{
completer = ShellScriptCompleter{shell_script->str(), completions_flags};
}
else if (auto shell_script = parser.get_switch("shell-script-candidates"))
{
completer = ShellCandidatesCompleter{shell_script->str(), completions_flags};
}
else if (parser.get_switch("command-completion"))
{
completer = [](const Context& context, CompletionFlags flags,
CommandParameters params,
size_t token_to_complete, ByteCount pos_in_token)
{
return CommandManager::instance().complete(
context, flags, params, token_to_complete, pos_in_token);
};
}
else if (parser.get_switch("shell-completion"))
{
completer = [=](const Context& context, CompletionFlags flags,
CommandParameters params,
size_t token_to_complete, ByteCount pos_in_token)
{
return add_flags(shell_complete, completions_flags)(
context, flags, params[token_to_complete], pos_in_token);
};
}
auto docstring = trim_indent(parser.get_switch("docstring").value_or(StringView{}));
cm.register_command(cmd_name, cmd, docstring, desc, flags, CommandHelper{}, completer);
cm.register_command(cmd_name, cmd, docstring, desc, flags, CommandHelper{}, std::move(completer));
}
const CommandDesc define_command_cmd = {
@ -1354,6 +1377,25 @@ const CommandDesc unalias_cmd = {
}
};
const CommandDesc complete_command_cmd = {
"complete-command",
"compl",
"complete-command [<switches>] <name> <type> [<param>]\n"
"define command completion",
ParameterDesc{
{ { "menu", { false, "treat completions as the only valid inputs" } }, },
ParameterDesc::Flags::None, 2, 3},
CommandFlags::None,
CommandHelper{},
make_completer(complete_command_name),
[](const ParametersParser& parser, Context& context, const ShellContext&)
{
const Completions::Flags flags = parser.get_switch("menu") ? Completions::Flags::Menu : Completions::Flags::None;
CommandCompleter completer = make_command_completer(parser[1], parser.positional_count() >= 3 ? parser[2] : StringView{}, flags);
CommandManager::instance().set_command_completer(parser[0], std::move(completer));
}
};
const CommandDesc echo_cmd = {
"echo",
nullptr,
@ -2719,6 +2761,7 @@ void register_commands()
register_command(remove_hook_cmd);
register_command(trigger_user_hook_cmd);
register_command(define_command_cmd);
register_command(complete_command_cmd);
register_command(alias_cmd);
register_command(unalias_cmd);
register_command(echo_cmd);

View File

@ -46,6 +46,7 @@ struct {
} constexpr version_notes[] = { {
0,
"» pipe commands do not append final end-of-lines anymore\n"
"» {+u}complete-command{} to configure command completion\n"
}, {
20211107,
"» colored and curly underlines support (undocumented in 20210828)\n"

View File

@ -67,6 +67,9 @@ public:
[[gnu::always_inline]]
bool empty() const { return type().length() == 0_byte; }
bool starts_with(StringView str) const;
bool ends_with(StringView str) const;
ByteCount byte_count_to(CharCount count) const
{ return utf8::advance(begin(), end(), count) - begin(); }
@ -306,6 +309,22 @@ inline StringView StringOps<Type, CharType>::substr(ColumnCount from, ColumnCoun
return StringView{ beg, utf8::advance(beg, end(), length) };
}
template<typename Type, typename CharType>
inline bool StringOps<Type, CharType>::starts_with(StringView str) const
{
if (type().length() < str.length())
return false;
return substr(0, str.length()) == str;
}
template<typename Type, typename CharType>
inline bool StringOps<Type, CharType>::ends_with(StringView str) const
{
if (type().length() < str.length())
return false;
return substr(type().length() - str.length()) == str;
}
inline String& operator+=(String& lhs, StringView rhs)
{
lhs.append(rhs.data(), rhs.length());

View File

@ -396,6 +396,16 @@ UnitTest test_string{[]()
{
kak_assert(String("youpi ") + "matin" == "youpi matin");
kak_assert(StringView{"youpi"}.starts_with(""));
kak_assert(StringView{"youpi"}.starts_with("you"));
kak_assert(StringView{"youpi"}.starts_with("youpi"));
kak_assert(not StringView{"youpi"}.starts_with("youpi!"));
kak_assert(StringView{"youpi"}.ends_with(""));
kak_assert(StringView{"youpi"}.ends_with("pi"));
kak_assert(StringView{"youpi"}.ends_with("youpi"));
kak_assert(not StringView{"youpi"}.ends_with("oup"));
auto wrapped = "wrap this paragraph\n respecting whitespaces and much_too_long_words" | wrap_at(16) | gather<Vector>();
kak_assert(wrapped.size() == 6);
kak_assert(wrapped[0] == "wrap this");