$COLUMNS and $LINES empty in %sh{}

Environment:
Debian 12, Kitty 0.31.0 using bash, Kakoune 2022.10.31
echo $COLUMNS works at the bash prompt
kak (to open an empty session)
:echo %sh{ echo $COLUMNS $LINES } (returns nothing)
there is a work around in that
kitty @ ls|jq ‘..tabs.windows|select(.is_self == true).columns’
will give columns when run in %sh{}

It looks to me like the kakoune shell environment is not setting $COLUMNS and $LINES
would appreciate any suggestions on how to diagnose further
should I open a bug report?

thanks,
Greg
edited to change %LINES to $LINES

This is more likely to be a shell problem than a Kakoune problem. COLUMNS and LINES are not environment variables that your shell (e.g., Bash) detects and sets correctly whenever it runs. From its man page, Bash seems to detect COLUMNS and LINES only if the checkwinsize option is on and/or when Bash runs interactively.

A more reliable way to detect COLUMNS and LINES from your shell may be to call the stty command. Thus, typing:

evaluate-commands %sh{info=$(stty size </dev/tty); echo “echo $info”}

or, more simply:

echo %sh{stty size </dev/tty}

on Kakoune’s command line does give the correct response on my machine, whereas your code only gives an empty response.

NB: here, the </dev/tty input redirection makes sure that the input to stty is from the actual, controlling terminal.

1 Like

Thanks François, that works for me. Thanks for pointing to checkwinsize. I compared set output from the kitty terminal and from %sh{}. checkwinsize is set in BASHOPTS on my terminal, but not in %sh{}. In fact there are no settings starting with BASH at all under %sh{}.

Kakoune has to deal with a lot of different shells so I suspect Kakoune is only supporting a subset of all shells functionality and for some reason $COLUMNS falls outside that subset. I appreciate how responsive this relentless focus on orthogonality makes Kakoune so am fine with using either kitty @ ls or stty size instead of $COLUMNS

%sh{} should not have $COLUMNS or $LINES set, because the shell commands in that block do not have any lines or columns - they are not connected to a terminal, so they can only output a simple byte stream.

You might say “Ah, but Kakoune runs in a terminal, so it should have access to those things!” The Kakoune client runs in a terminal, but %sh{} blocks are run by the Kakoune server which may not have access to a terminal - for example, if the server was started with kak -d, or if it was running in a terminal that got closed.

However, most people (I think) just run kak and never run :new or kak -c to connect any other clients, in which case interacting with /dev/tty should work.

2 Likes

Thanks to both François and Screwtapello for your comments. I appreciate your help in deepening my understanding of Kakoune architecture.

Yes, this is occurring under kak i.e. client+server all-in-one, as best I can tell there is only 1 kak pid.

“set” output for %sh{} includes some KITTY environment variables like KITTY_WINDOW_ID - I suppose that is a KITTY id, not a shell characteristic. It looks to me like Kakoune is actively choosing a subset of the environment variables of the shell it starts in and presumably actively filtering out environment variables with name starting with BASH, $LINES, $COLUMNS and perhaps others.

As you point out the Kakoune client appears to be using the same number of columns and lines as the underlying terminal so the Kakoune client must be aware of these dimensions.

Inside %sh{} I’m running a report on a buffer in kak and directing the output to -fifo buffers in the same kak. The report needs know how wide the terminal is to properly format. I have been running this as a standalone bash script which meant it had access to $COLUMNS. I viewed it in less. It produced error messages to the terminal. I find it convenient to have the original data, the report and error messages all in Kakoune buffers for consistent viewing/editing in one Kitty window.

I connected with kak -c and %sh{} reports the terminal size of the original window which is different than the size of the window running kak -c. The KITTY_WINDOW_ID was also for the original window. BTW this seems like an easy way to view Kakoune buffers side by side if needed. I’ve started with 1 buffer/kak but can now see stacking up more buffers/kak. It’s nice to keep stumbling over powerful new features of Kakoune.

I closed the original kak and now the stty size is null as you indicate. KITTY_WINDOW_ID still points to the window where the original kak used to be running so it’s now invalid. kitty @ ls|jq ‘..tabs.windows|select(.is_self == true).id’ incorrectly points to the original window as well. It looks like the kak server forked to the background is holding on to the kitty window it was running in because I can’t exit that bash session until I close the client. For now, I want to avoid this situation instead of trying to figure it out, but it’s good to know what can happen.

Thanks for the in-depth response.
Greg

When bash starts up, any environment variables it inherits are available with a $ prefix, like $PATH or $HOME. There are a bunch of other values that you can access with a $ prefix, like $BASH_VERSION or $COLUMNS, and set lists them all side-by-side, so it’s easy to assume they’re all the same kind of thing, but they’re not. There are “shell variables” and “environment variables”, and they’re different.

  • At startup, the shell inherits a load of environment variables, and imports them as shell variables.
  • any additional variables defined by shell commands are “shell variables”
  • the export command converts a shell variable into an environment variable
  • when the shell launches an external command, the external command receives environment variables in its environment, but does not receive shell variables at all, even if it’s another shell
  • The set builtin, when run without any arguments, lists all the shell variables defined in the current shell.
  • /usr/bin/env is an external command which, when run without any arguments, lists all the environment variables it has inherited

In a fresh terminal running bash:

$ set | wc -l
2242
$ env | wc -l
75
$ set | grep COLUMNS; echo done
COLUMNS=119
done
$ env | grep COLUMNS; echo done
done

If you want to know how wide the Kakoune buffer is, so a report can be properly formatted inside it, you might want to use the %val{window_width} expansion. It’s not completely reliable, as it doesn’t subtract the width of line-flags highlighters (like line-numbers), but it’s more reliable than trying to poke at /dev/tty from the Kakoune server.

Kitty exports its information as environment variables so they’re inherited by every descendent process, unless those variables are explicitly cleared the way that (for example) sudo or env -i do.

This is a great deep dive into %sh{}. I like the one liners like env|grep COLUMNS that illuminate what’s going on.

kitty @ ls is keying on $KITTY_WINDOW_ID in %sh{} to know what the current window is. That explains why it’s unreliable when there are separate kakoune clients and servers.

changes to KITTY_WINDOW_ID are only valid for the current invocation of %sh{}. It looks like %sh{} resets it’s environment variables from the original shell with each invocation.

$kak_window_width works and also gives the correct width for a client window even when it’s different from the original window. $kak_window_width looks like a complete solution to the original question.

The following is way above and beyond the original question. I’d be glad to start a new topic if that’s the custom on this forum.

%sh{} picks up the correct window_width per server, client and invocation. how about %sh{} also picking up the environment variables in the clients shell instead of the server shell when creating %sh{} for a client? so that it picks up the correct KITTY_WINDOW_ID for the client? If kakoune were to key off the client environment then perhaps the server could just transfer to the next client when the original kak session is closed. Or at least Kakoune wouldn’t need to hang on to the original terminal session when the kakoune running there is closed…

If I open kak in gnome-terminal, then -c connect to it from a Kitty window, the kitty client %sh{} has no $KITTY_WINDOW_ID at all.

thanks,
Greg

%val{client_pid} gives the correct client pid
kitty @ ls includes pids
Here is a rather crude jq to determine the correct kitty tab and windowid by matching on pid
echo %sh{
kitty @ ls |jq -c ‘..tabs|select(.windows.foreground_processes.pid ==’$kak_client_pid’)|{“tid”:.id}, (.windows|select(.foreground_processes.pid ==‘$kak_client_pid’)|{“wid”:.id})’
}
sample output
{“tid”:4}
{“wid”:11}
This provides reliable access to the extensive information in kitty @ ls including .columns which should be correct in all the scenarios raised so far in this topic including separate client and server pids.

It may even be possible to get kitty @ ls --match and --match-tab to work correctly in %sh{} by setting KITTY_WINDOW_ID correctly

How cool that set -x works in %sh{}!

I would still like to understand why Kakoune uses the server env variables in client sessions, but thanks to your help I’m set to bump up my kakoune integration with kitty a couple of notches.

Just as it would seem reasonable for a kakoune client to inherit it’s environment variables from the shell where kak -c launched it, wouldn’t it be logical for .dev/tty in the client %sh{} to point to the shell where kak -c launched it? I can imagine there are tradeoffs in designing a server-client system and so far everything I’ve encountered about Kakoune is well thought-out so perhaps there are reasons to point to the server shell.

There is plenty of kakoune function I can start to take advantage of. I’m just curious if there is anything more I might be able to do by better understanding the client-server design.

Just as it would seem reasonable for a kakoune client to inherit it’s environment variables from the shell where kak -c launched it,

Hmm I’m not sure. I’ve gotten used to the current behavior and it seems the simplest choice overall. The idea of using the client env is interesting. I reckon that when we explore it further, we’ll discover why it’s not a good idea.
In my experience the client env vars are only necessary for UI related decisions.
For that we can access them explicitly using %val{client_env_FOO}.
For example tmux.kak uses tmux -t ${kak_client_env_TMUX_PANE}

wouldn’t it be logical for .dev/tty in the client %sh{} to point to the shell where kak -c launched it

yeah I don’t think the current /dev/tty is useful. There are a couple approaches to change this but I’m wary of added complexity.
Today we can access the client’s TTY with /proc/$kak_client_pid/fd/0 (not POSIX).

Thank you! I’m glad you found it helpful!

When Kakoune encounters a %sh{) block, it launches a new shell process to execute it, which naturally inherits its environment variables from the Kakoune server. The shell process can set its own variables and export them to any child processes it launches, but it can’t do anything to affect the environment in the parent Kakoune process.

There’s not really any such thing as a client session; a Kakoune client just lets you interact with the server much like how a web browser lets you interact with a web server. As krobelus points out, though, if you mention the expansion client_env_FOO (%val{client_env_FOO} in Kakoune, or $kak_client_env_FOO in a shell block) then the server will try to figure out which client is involved, and ask it for the contents of its environment variables.

Again, there’s no “client %sh{}”. If you open up a Kakoune client and type :echo %sh{date}<ret> then those individual keys are sent from the client to the server, and then the server interprets them as a command and executes them. The client never executes anything, and does not know what is a command and what isn’t.

I expect the reason for this centralisation is that Kakoune scripts can affect the entire state of the server, including every buffer (eval -buffer FOO some-command) and client (eval -client FOO some-command) and it’s a lot easier to implement that if all the information is stored in one process, rather than multiple processes having to communicate and collaborate to make that happen.