How to use env variable in a tmux bind-key map? - tmux

I want my bind-key command to make use of a variable.
Here is my .tmux.conf file:
# .tmux.conf
bind-key r rename-session $MY_VARIABLE
How can I set MY_VARIABLE on a session-by-session basis?
Things I have tried that did not work:
Run export MY_VARIABLE=my_value in bash before pressing C-b r.
Run tmux setenv MY_VARIABLE my_value in bash before pressing C-b r.
(C-b is my prefix in tmux)
The following (based on this answer) DOES WORK:
Add a line to to .tmux.conf, like this:
# .tmux.conf
MY_VARIABLE=my_value
bind-key r rename-session $MY_VARIABLE
Running C-b r the successfully renames the session. But this is less than ideal, because MY_VARIABLE=my_value is hard-coded into the .tmux.conf file; I want a way to change MY_VARIABLE on an ad-hoc basis.

Typically, the way round this is to go through the shell again, eg:
bind-key r run-shell 'tmux rename-session "$MY_VARIABLE"'
The single quotes stops the variable from being expanding whilst parsing the config file. If you then later say
tmux setenv MY_VARIABLE my_value
it will set the session environment.
When you then type prefix-r the shell forked by run-shell will inherit these session variables, and the shell will be able to replace the variable by the current value for the session.

Related

How to set and use variable in tmux.conf depending on whether an environment variable is set

(Disclaimer: I am fully aware that there are solutions to the problem I describe below that involve writing and calling shell scripts that interact with a running tmux server, or set the necessary environment variables before starting the tmux server. I am specifically posting this questions to see if it possible to solve this problem without the use of such scripts.)
Problem Summary
In my .tmux.conf file, I am trying to set a local variable VALUE to different values depending on whether an environment variable FOO has been set before invoking tmux or not. I then want to use VALUE in other tmux commands. Unfortunately, I either cannot set VALUE correctly or access it after it has been set.
Previous Attempts
According to what I have found in the manpage and in other Q&A posts that contain sample tmux code, there are several ways to implement the above.
Attempt 1
I first tried using the if-shell command. I attempted using this command both with and without the -b flag; the result was the same in either case.
I have seen from examples that I can assign variables with the syntax VALUE=bar. Given that, here is a minimal example of my configuration:
if-shell '[ -z "$FOO" ]' \
'VALUE=bar' \
'VALUE=baz'
set-environment -g RESULT $VALUE
Terminal session:
$ echo $FOO
$ tmux
[detached (from session 0)]
$ tmux showenv -g VALUE
VALUE=bar
$ tmux showenv -g RESULT
RESULT=
$ killall tmux
$ export FOO=foo
$ echo $FOO
foo
$ tmux
[detached (from session 0)]
$ tmux showenv -g VALUE
VALUE=baz
$ tmux showenv -g RESULT
RESULT=
So while VALUE seems to have been set correctly, RESULT does not seem to able to access VALUE.
Attempt 2
The manpage also mentions that commands can be conditionally executed using %if statements. Using this format, I tried the following configuration:
%if #{#(if [ -z "$FOO" ]; then echo 1; else echo 0)}
VALUE=bar
%else
VALUE=baz
%endif
set-environment -g RESULT $VALUE
For the expression in the %if statement, I tried several variations, such as
#{#([ -z "$FOO" ])} (I believe this shouldn't work since this command does not produce any output, but it was worth a try.)
#{==:#(if [-z "$FOO" ]; then echo 1; else echo 0),1} (Just in case an explicit comparison would work)
Even with these variations, regardless of whether FOO was set or not, I got the following:
$ tmux
[detached (from session 0)]
$ tmux showenv -g VALUE
VALUE=baz
$ tmux showenv -g RESULT
RESULT=baz
Thus while VALUE was accessible, it was always baz.
Unfortunately, I have been able to find no useful examples regarding the formats used in conditional statements. The manpage describes how to access tmux variables and some formatting hints; however, regarding accessing environment variables, all I could find was a way to use shell commands:
In addition, the first line of a shell command's output may be inserted using #(). For example, #(uptime) will insert the system's uptime. When constructing formats, tmux does not wait for #() commands to finish; instead, the previous result from running the same command is used, or a placeholder if the command has not been run before.
I am unsure of whether this means I need to call commands in #() twice to avoid using a placeholder value, which may be a possible error on my part.
I was also unable to find a way to print the result of #{} commands easily to debug this part of the statement.
Summary of Questions
While I would appreciate any pointers to information that may help me solve this problem, the most pressing questions for me are:
Why is VALUE being set correctly, yet not accessible to RESULT in Attempt 1?
How should my conditional be written in Attempt 2 to ensure that VALUE is set correctly?
The way tmux runs the config is by parsing the config file into a set of commands, and then executing them (there is a command queue, so the config file is parsed and appended to the queue and then executed from the queue). So there are distinct parse and execution steps.
The problem you are running into with attempt 1, is that the if-shell is run at execution time, but the $VALUE expansion happens at parse time. VALUE is not set when the set-environment command is parsed.
In attempt 2, #() is not processed inside %if so that won't work. However, you can use the variable directly in formats (if it is set). %if happens at parse time.
So you need to make sure assignment and expansion happen in the right order. You have a couple of choices.
You could make tmux expand the variable at command execution time rather than parse time. You can do this by wrapping the setenv inside run-shell, so something like:
if-shell '[ -z "$FOO" ]' \
'VALUE=bar' \
'VALUE=baz'
run 'tmux setenv -g RESULT $VALUE'
Or you could do the assignment at parse time like you tried in attempt 2, but you can't use #() - you need to use a format instead:
%if #{==:#{FOO},}
VALUE=bar
%else
VALUE=baz
%endif
setenv -g RESULT $VALUE
(Note that X=Y in the config file is equivalent to setenv -g X=Y except it happens when parsing rather than executing - both set the global environment. So you could get rid of VALUE and do either RESULT=bar or setenv -g RESULT bar inside the %if.)
Also you can use display -p to print formats. In master and 2.9 you can add -v to see how they are parsed:
$ tmux setenv -g FOO bar
$ tmux display -pv '#{==:#{FOO},baz}'
# expanding format: #{==:#{FOO},baz}
# found #{}: ==:#{FOO},baz
# modifier 0 is ==
# expanding format: #{FOO}
# found #{}: FOO
# format 'FOO' found: bar
# replaced 'FOO' with 'bar'
# result is: bar
# expanding format: baz
# result is: baz
# compare == left is: bar
# compare == right is: baz
# replaced '==:#{FOO},baz' with '0'
# result is: 0
0

can I override next session binding <prefix>-) in tmux?

I want to bind this to something more convenient, like M-] without the prefix:
bind -n M-] send-prefix \; send-keys )
in my tmux.conf doesn't work.
What's my mistake?... I also tried C-] and some others. When I'm at a prompt, it just writes the ) character, so the prefix isn't being captured by tmux.
There doesn't seem to be a command for "next session", just the predefined binding.
I put these lines in my .tmux.conf:
bind j switch-client -n
bind k switch-client -p
As you can see the -n and -p arguments are next and previous. Enjoy!

binding the shift key in .tmux.conf doesn't work

This is my .tmux.conf file, placed in the home directory.
unbind C-b
set -g prefix S-a
bind S-a send-prefix
bind r source-file ~/.tmux.conf
I am properly loading it with tmux source-file ~/.tmux.conf. However, doing shift+a to initiate the prefix doesn't work.
However if I replace S-a with C-a, it'll work (ctrl+a).
What am I doing wrong?
You can't and it's not a good idea, the shift key is by no way meant for that. Take a look in the man tmux, section KEY BINDINGS for the list of available keys. More info are available here https://unix.stackexchange.com/a/140010
Solution 1
Run: (WARN: Save your work in all sessions first)
tmux kill-server
Solution 2
In your .zshrc/.bashrc file, add the following line:
[ -z "${TMUX}" ] || tmux YOUR_COMMAND
e.g. YOUR_COMMAND = set -g prefix S-a.

tmux only shows current pane title if queried from a key binding

Suppose have 3 panes in a tmux window: vim, vom, and vam
I want to access the pane title of the first pane as part of a keyboard mapping (forward command to vim if vim's in pane 1). It wasn't working as expected and I've narrowed it down to the following...
If I run:
tmux display-message "`tmux list-panes -F '#{pane_title}'`"
the result is:
vim
vom
vam
This is what I'd expect. If I try to run the same from a key mapping:
bind -n C-h run "tmux display-message \"`tmux list-panes -F '#{pane_title}'`\""
I get the title of the current pane repeated three times:
vom
vom
vom
Does run-shell execute in a different context or something?
Thanks for your help
Run tmux list-keys in the terminal and see what C-h gets mapped to. I get something like this:
bind-key C-t run-shell "tmux display-message "name1
name2
name3""
From the above, you can see the backtick interpolation happens at the moment when the key is bound, not later when the binding is executed.
I don't know how to get around this and you must be having a lot of pain because of so many nested commands.
Personally, when hacking tmux I always apply the rule of "get to the shell as soon as possible".
For your example that would mean:
keep the key binding simple: bind -n C-h run "/path/to/script.sh"
create a script, make it executable and put the rest of what you want to do in it. This would be its content:
tmux display-message "`tmux list-panes -F '#{pane_title}'\`"
I just did it and it worked for me locally. Hope that helps!

Bindings with key sequences

Does Tmux supports key-bindings with key sequences like Vim does (e.g. bind-key ab kill-pane)? Or how can i emulate that?
I'm using tmux 2.3.
You can emulate key sequences by defining your own key tables and chaining them together.
For example, if I want <C-q>x to do something, I put the binding for 'x' into a key table "my-keys", then bind the key that activates that key table with switch-client (C-q):
bind-key -Tmy-keys x send-keys "my binding"
# Multi-key prefix for custom bindings
bind-key -Troot C-q switch-client -Tmy-keys
NOTE: I started with C-q, because it seems to conflict the least with the command line and Vim.
So, now you have every key at your disposal with a C-q prefix.
If you want more keys in your sequence, add another level of indirection:
bind-key -Tmy-keys x send-keys "my binding"
# Pane (i.e. 'W'indow commands like Vim with C-w)
bind-key -Tmy-keys-window-ctl s swap-pane
bind-key -Tmy-keys C-w switch-client -T my-keys-window-ctl
# Multi-key prefix for custom bindings
bind-key -Troot C-q switch-client -Tmy-keys
So, now I have swap-pane bound to <C-q><C-w>s.
This works because
<C-q> activates "my-keys" key table,
which has the binding <C-w>,
which activates "my-keys-window-ctl" key table
which has the binding s to call swap-pane
Tmux supports only single character key bindings (unfortunately).
So, only this:
bind-key a kill-pane
or this:
bind-key b kill-pane
Please note this is different from for example C-a (Ctrl-a) or M-a (Alt-a).
Even though we users write those with multiple characters and even have to press 2 keys to invoke them, both Ctrl-a and Alt-a are actually a single character for tmux (and in general to my knowledge).
Alternative
...might not be what you expect, but here it is:
# in .tmux.conf
bind a command-prompt -p "pressed a" "run '~/my_script %%'"
And the example my_script file:
#!/bin/bash
case "$1" in
b)
tmux kill-pane
;;
c)
tmux kill-window
;;
esac
Now after you reload your tmux.conf and press prefix + a you'll get a tmux prompt saying 'pressed a'.
Go ahead and press b and Enter. tmux kill-pane from the script will execute.
Similarly if you press prefix + a + c and Enter you'll execute another option from the script.
This kind-of mimics what you want with the addition of Enter key at the end.
Also, the provided script is extendable so you can add more "bindings" to get prefix + a + d + Enter etc..

Resources