In Kakoune scripts, branching on the value of a boolean option, myvar, may be done by calling the shell inside a try ... catch construction:
try %{
evaluate-commands %sh{
if [ "$kak_opt_myvar" = false ]; then
printf %s\\n fail
fi
}
commands_1
} \
catch %{
commands_2
}
which looks bloated and contorted to me. (One could use a wrapper to make the code look simpler, but then the wrapper needs to be defined elsewhere.)
Recently I have been using a trick to avoid calling the shell and inflating the code. The trick is to change the type of the myvar option to str instead of boolean, setting myvar to nop when I want it to be true and to nay when I want it to be false.
This is Kakoune’s eval, not the shell’s. When myvar is “true”, eval %opt(myvar) goes through and we execute commands_1. When myvar is “false”, eval %opt(myvar) fails (because there is no such command as nay) and we execute commands_2.
But perhaps most of you, professional coders, are already using this sort of hack? In any case, I like the look of the resulting code. You are free to decide whether this is more clever than stupid or vice-versa !
No, at leas I’m not thought about it! Nice trick!
I was using this custom command:
require-module kak
add-highlighter shared/kakrc/code/if_else regex \b(if|else)\b 0:keyword
define-command -docstring "if <condition> <expression> [else [if <condition>] <expression>]: if statement that accepts shell-valid condition string" \
if -params 2.. %{ evaluate-commands %sh{
while [ true ]; do
condition="[ $1 ]"
if [ -n "$3" ] && [ "$3" != "else" ]; then
printf "%s\n" "fail %{if: unknown operator '$3'}"
elif [ $# -eq 3 ]; then
printf "%s\n" "fail %{if: wrong argument count}"
elif eval $condition; then
[ -n "${2##*&*}" ] && arg="$2" || arg="$(printf '%s' "$2" | sed 's/&/&&/g')"
printf "%s\n" "evaluate-commands %& $arg &"
elif [ $# -eq 4 ]; then
[ -n "${4##*&*}" ] && arg="$4" || arg="$(printf '%s' "$4" | sed 's/&/&&/g')"
printf "%s\n" "evaluate-commands %& $arg &"
elif [ $# -gt 4 ]; then
if [ "$4" = "if" ]; then
shift 4
continue
else
printf "%s\n" "fail %{if: wrong argument count}"
fi
fi
exit
done
}}
That allows me to chain ifelseif … else blocks as in C-like languages, but I mostly use it for single if else, like this, to branch my configuration depending on where I run Kakoune:
The problem is that I mostly want to use shell conditionals as those are far more versatile compared to Kakoune ones, but your example can be used to make some code that uses trycatch approach more appealing. I wonder if this can be turned into function, as in my example
I forgot to say that a similar trick can be used to define a procedure (= command) with two variants. Instead of declaring two different commands, declare a single command along these lines:
One thing that I don’t get with your trick is how do you set the option to the desired value, don’t you need a branch at that point? Let’s say, how would you check if your current selection is 5 characters long?
Not necessarily. Setting the option to “true” or “false” (nop or nay) may be done in a separate command that you may call during your editing session (e.g., “do you want the script to use feature X [yes|no]?”). The option-setting command may also be called by a hook that fires when opening a buffer of a specific filetype.
Even when setting the option to “true” or “false” requires a branch, this branch (A) may occur earlier in the script, in a part separate from the later branch or command (B). For example, you may set the option to “nop” or “nay” depending on the result of an <a-k>... test done on the current selection. Then you may need to call other commands before finally calling (B) with the value of the previously set option.
Of course, my trick is superflous if you just need to do some test and execute some action immediately, depending on the result of the test. The trick is useful only when option-setting is done separately from the final branch – and especially useful if several procedures depend on the previously set option at different points in time.
Nice! I’ve done something similar with setting option values as commands and running eval on the option.
With how many posts of this sort that have popped up, I’m becoming more and more convinced that conditional control flow should be properly included in kakscript. Though, I guess the percentage of kak files that use things like this is still pretty small.
That or, once mrsh is ready, have a compile option to integrate the shell into Kakoune itself.
Normal user configs probably don’t need conditionals very often, but a ton of plugins depend on conditionals. Adding it to kakscript would be a nice quality of life improvement, and could improve performance as well.
It is nice, but a bit verbose, and ultimately, we do not care about the buffer thing.
In a recent version of Kakoune, @mawww introduced user hooks, allowing us to create our own hooks. As a bonus, we can leverage branching without opening a shell or a scratch buffer.
define-command if -params 3 -docstring 'if <value> <regex> <commands>' %{
hook -group if global User %arg{2} %arg{3}
trigger-user-hook %arg{1}
remove-hooks global if
}
Here’s the usage in a case insensitive select command. Without it you end up selecting everything when the prompty is empty. I included the entire snippet in case anyone wants to use this but fair warning, it segfaults on the last official release, works fine if you build from latest master.
Turns out you can define true and false as functions in kakscript.
true is a function that takes two braches, and executes the first false is a function that takes two branches, and executes the seconf
if is a function that takes thre arguments, first is truef or falsef and the branches. It then passes the branches to the test function, which chooses the branch.
I gave it a bit of testing and it seems to work for complex branches.
Yeah, this is probably the right approach. The only thing that concerns me here is the eval, because I don’t fully understand it. If there’s an eval, there must be always be a related escape.
For sh, eval-escaping is simple: apostrophe-escape all strings (' -> '\'' then surround). What is the equivalent escaping rule for kakscript's eval? And can it be done programatically without %sh?
TBH I don’t understand it either. I really wanted for it to work, so perhaps that’s why it works
@occivink suggested to use --verbatim escaping to be more portable, but I’m not expert here, and I don’t know why --verbatim is needed in if but is not needed in true or false for example, and adding it there breaks stuff.
However note, that Kakoune’s eval has nothing to do with shell’s eval. It’s just a short way of writing evaluate-commands. So maybe escaping with --verbatim once is sufficient.
It’s the only way I got them to match. IOW, if you want eval ... to have the same effect as ..., each argument must be surrounded in triple quotes, and inside the surround each quote must be quadrupled.
Can this escaping be done in pure kakscript, no sh? Can I define a command that takes a list of arguments and recursively builds up a string with those rules? Because if I could, a lot of things become possible by switching to continuation-passing style.
I don’t qute understand why this is needed? You provide balanced strings to if which are evaled by kakoune, without shell (%{this is a string}), so those are already escaped by you. This escaping is preserved with --verbatim, as far as I understand this.