Skip to content

Commit d3094ae

Browse files
authored
refactor!: use run-shell -C to execute hooks (#60)
BREAKING CHANGE: 1. Hooks are now directly forwarded to tmux using `run-shell -C <hook>`. 2. Escape sequences are handled by tmux.
1 parent 99e083b commit d3094ae

19 files changed

Lines changed: 112 additions & 135 deletions

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,20 @@ follows <https://www.conventionalcommits.org/en/v1.0.0/> to track changes.
4545
- Add `@popup-proxy` to run tmux commands in the caller session ([#59])
4646
- Add `@popup-sync-buffer` to sync tmux buffers before pasting ([#59])
4747

48+
### Changed
49+
50+
- (**breaking**) Use `run-shell -C` to execute hooks. Escape sequences are now
51+
handled by tmux, so `\;` can be used directly ([#60])
52+
4853
### Removed
4954

5055
- (**breaking**) Remove placeholder `{popup_caller_path}` and
5156
`{popup_caller_pane_path}`. Use `##{session_path}` and `##{pane_current_path}`
5257
instead ([#58])
5358

5459
[#58]: https://github.com/loichyan/tmux-toggle-popup/pull/58
55-
[#{59}]: https://github.com/loichyan/tmux-toggle-popup/pull/59
60+
[#59]: https://github.com/loichyan/tmux-toggle-popup/pull/59
61+
[#60]: https://github.com/loichyan/tmux-toggle-popup/pull/60
5662

5763
## [0.4.4] - 2025-08-30
5864

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ A handy plugin to create toggleable popups.
1313
- tmux >= **3.4** (not tested on earlier versions)
1414
- Bash >= **3.2.57**
1515

16-
> [!NOTE]
16+
> [!Note]
1717
>
1818
> This plugin has been tested on macOS's built-in Bash through GitHub Actions.
1919
> However, if you are experiencing issues on macOS, please try upgrading your
@@ -60,8 +60,8 @@ config.programs.tmux = {
6060
extraConfig = ''
6161
...
6262
# popups
63-
bind C-t run "#{@popup-toggle} -Ed'#{pane_current_path}' -w75% -h75%"
64-
bind C-g run "#{@popup-toggle} -Ed'#{pane_current_path}' -w90% -h90% --name=lazygit lazygit"
63+
bind C-t run "#{@popup-toggle} -Ed'##{pane_current_path}' -w75% -h75%"
64+
bind C-g run "#{@popup-toggle} -Ed'##{pane_current_path}' -w90% -h90% --name=lazygit lazygit"
6565
...
6666
'';
6767
};
@@ -92,7 +92,7 @@ set -g @plugin "tmux-plugins/tmux-continuum"
9292
set -g @plugin "loichyan/tmux-toggle-popup"
9393
```
9494

95-
> [!TIP]
95+
> [!Tip]
9696
>
9797
> Whenever you update the *.tmux.conf*, remember to reload it in both your
9898
> working session and the popup session; otherwise, keybinding or style changes

USAGE.md

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ intended as a workaround for [tmux/tmux#3991], which has been fixed in
111111
[tmux/tmux#3991]: https://github.com/tmux/tmux/issues/3991
112112
[tmux/tmux@a869693]: https://github.com/tmux/tmux/commit/a869693405f99c8ca8e2da32a08534489ce165f2
113113

114-
> [!NOTE]
114+
> [!Note]
115115
>
116116
> The fix has been included in tmux v3.6, so this command is no longer
117117
> necessary.
@@ -216,38 +216,29 @@ affect subsequent calls.
216216

217217
## 🪝 Hooks
218218

219-
A hook is used to run tmux commands on certain events. Each hook is preprocessed
220-
by *xargs(1)*, which splits it into a token stream. The token stream is
221-
collected and passed to tmux when the corresponding event fires. When writing
222-
hooks, keep the following rules in mind:
223-
224-
1. Two commands must be delimited by a semicolon (`;`).
225-
2. Line breaks are replaced with spaces, therefore, as rule *(1)* states,
226-
remember to put a semicolon after each command.
227-
3. Tokens can be protected by quotes (either `'` or `"`) from being split.
228-
4. Any character preceded by a backslash (`\`) is treated as a literal escape.
229-
For instance, `\n` is interpreted as `n` rather than a line break.
230-
5. For commands that take a command sequence as an argument, each command in
231-
that sequence must be delimited by `\;`, just as you would do in
232-
*.tmux.conf*. You can use either `\\;` or `'\;'` to input a `\;`.
233-
Additionally, you may also put the entire sequence in a pair of quotes, as
234-
the following example shows.
235-
236-
To disable a hook, you should set it to `nop` instead of an empty string.
219+
A hook is used to run tmux commands on certain events. Each hook is sent to tmux
220+
using `run-shell -C <hook>`. Here are some general tips for writing hooks:
221+
222+
1. Separate two commands with a semicolon (`;`).
223+
2. For commands that accept a command sequence as an argument, like `bind-key`,
224+
separate each command in the sequence with `\;`.
225+
3. To disable a hook, set it to `nop` or an empty string.
237226

238227
**Example**:
239228

240229
```tmux
241-
# Hide the statusline and setup additional key bindings
242-
set -g @popup-on-init 'set status off'
243-
# In single quotes, no escape sequence will be recognized.
244-
set -ga @popup-on-init '
245-
; bind -n M-1 confirm -p"inside a popup?" "run true" \\; display -d3000 "of course!"
230+
# tmux transforms everything inside braces into a single string that it can
231+
# parse. This string is then sent to tmux through `run-shell -C` right after we
232+
# we enter the popup session.
233+
set -g @popup-on-init {
234+
set status off
235+
bind -n M-t confirm -p "inside a popup?" "run true" \; display -d3000 "of course!"
236+
}
237+
# Outside tmux.conf, we need to manually escape and separate each command.
238+
set -g @popup-on-init '
239+
set status off;
240+
bind -n M-t confirm -p "inside a popup?" "run true" \; display -d3000 "of course!";
246241
'
247-
# While in double quotes, escape sequences work as usual.
248-
set -ga @popup-on-init "
249-
; bind -n M-2 \"confirm -p'inside a popup?' 'run true' ; display -d3000 'of course!'\"
250-
"
251242
```
252243

253244
### `@popup-on-init`
@@ -331,7 +322,7 @@ if -F "#{!:#{@popup_did_init}}" {
331322
}
332323
```
333324
334-
> [!NOTE]
325+
> [!Note]
335326
>
336327
> Whenever you need to interact with the popup session, do it through the
337328
> `on-init` hook rather than `#{@popup-toggle} some_script.sh`. This is because,

src/helpers.sh

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,11 @@ escape_session_name() {
7878
print "${1//[.:]/_}"
7979
}
8080

81-
# Parses tmux commands, assigning the tokens to an array named `cmds`.
82-
#
83-
# It returns 1 if the given string does not contain any valid tmux commands.
84-
declare cmds
85-
parse_cmds() {
81+
# Returns whether a hook is enabled.
82+
check_hook() {
8683
if [[ -z $1 || $1 == "nop" ]]; then
8784
return 1
8885
fi
89-
# shellcheck disable=SC2034
90-
IFS=$'\n' read -d '' -ra cmds < <(print "$1" | xargs printf "%s\n") || true
9186
}
9287

9388
# Expands the provided tmux FORMAT string.

src/helpers_tests.sh

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,61 +9,6 @@ CURRENT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
99
# shellcheck source=./helpers.sh
1010
source "$CURRENT_DIR/helpers.sh"
1111

12-
#-- test:parse_cmds ------------------------------------------------------------
13-
14-
test_parse_commands() {
15-
parse_cmds "$1"
16-
shift
17-
18-
if [[ $# -ne ${#cmds[@]} ]]; then
19-
failf "expected $# tokens to be parsed, got ${#cmds[@]}:%s" "$(printf "\n\t%s" "${cmds[@]}")"
20-
fi
21-
22-
local i=0
23-
while [[ $# -gt 0 ]]; do
24-
if [[ $1 != "${cmds[$i]}" ]]; then
25-
git diff <(print "${cmds[i]}") <(print "$1")
26-
failf "unexpected token at $((i + 1))"
27-
fi
28-
shift
29-
i=$((i + 1))
30-
done
31-
}
32-
33-
(
34-
begin_test "delimited_by_semis" || exit 0
35-
test_parse_commands \
36-
'set status off ; set exit-empty off' \
37-
'set' 'status' 'off' ';' \
38-
'set' 'exit-empty' 'off'
39-
) || exit 1 || exit 1
40-
41-
(
42-
begin_test "delimited_by_line_breaks" || exit 0
43-
test_parse_commands \
44-
'set status off
45-
set exit-empty off' \
46-
'set' 'status' 'off' \
47-
'set' 'exit-empty' 'off'
48-
) || exit 1 || exit 1
49-
50-
(
51-
begin_test "escaped_multiple_commands" || exit 0
52-
test_parse_commands \
53-
'bind -n M-1 display random\ text \\; display and\ more' \
54-
'bind' '-n' 'M-1' \
55-
'display' 'random text' '\;' \
56-
'display' 'and more'
57-
) || exit 1
58-
59-
(
60-
begin_test "quoted_multiple_commands" || exit 0
61-
test_parse_commands \
62-
"bind -n M-2 \"display 'random text' ; display 'and more'\"" \
63-
'bind' '-n' 'M-2' \
64-
"display 'random text' ; display 'and more'"
65-
) || exit 1
66-
6712
#-- test:interpolate -----------------------------------------------------------
6813

6914
declare expected input

src/toggle.sh

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,8 @@ prepare_init() {
7676
on_cleanup+=(unbind $k \;)
7777
done
7878

79-
if parse_cmds "$on_init"; then
80-
init_cmds+=("${cmds[@]}")
81-
fi
79+
# Handle hook: on-init
80+
if check_hook "$on_init"; then init_cmds+=(run -C "$on_init" \;); fi
8281
}
8382

8483
declare name id id_format toggle_keys=() init_args=() display_args=()
@@ -197,26 +196,27 @@ main() {
197196
open_script=""
198197

199198
# Handle hook: before-open
200-
if parse_cmds "$before_open"; then open_cmds+=("${cmds[@]}" \;); fi
199+
if check_hook "$before_open"; then open_cmds+=(run -C "$before_open" \;); fi
201200

202201
# Starting from version 3.5, tmux uses the user's `default-shell` to execute
203202
# shell commands. However, our scripts require sh(1) and may not be parsed
204203
# correctly by some incompatible shells. In this case, we change the default
205204
# shell to `/bin/sh` and then revert it immediately.
206-
open_script+="tmux set default-shell '$default_shell' ;"
205+
open_script+="tmux set default-shell '$default_shell';"
207206
open_cmds+=(set default-shell "/bin/sh" \;)
208207

209208
# Set $TMUX_POPUP_SERVER to identify the popup server.
210-
open_script+="export TMUX_POPUP_SERVER='$popup_server' SHELL='$default_shell' ;"
209+
open_script+="export TMUX_POPUP_SERVER='$popup_server' SHELL='$default_shell';"
211210

212211
# Suppress stdout to hide the `[detached] ...` message
213-
open_script+="exec tmux $(escape "${popup_socket[@]}" "${init_cmds[@]}") >/dev/null ;"
212+
open_script+="exec $(escape tmux "${popup_socket[@]}" "${init_cmds[@]}")>/dev/null;"
214213
open_cmds+=(display-popup "${display_args[@]}" "$open_script" \;)
215214

216215
# Handle hook: after-close
217-
if parse_cmds "$after_close"; then open_cmds+=("${cmds[@]}" \;); fi
216+
if check_hook "$after_close"; then open_cmds+=(run -C "$after_close" \;); fi
218217

219218
# Do open the popup window
219+
# printf '%s\n'
220220
tmux "${open_cmds[@]}"
221221

222222
# Undo temporary changes on the popup server

src/toggle_tests/escape_session_name.stdout

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ TMUX:BEGIN[1] {
44
SHELL=/system/shell
55
}
66
run
7-
#{@popup_before_open}
7+
-C
8+
run '#{@popup_before_open}'
89
;
910
set
1011
default-shell
@@ -38,10 +39,13 @@ TMUX:BEGIN[1] {
3839
%current_pane_id:tmux-test
3940
;
4041
run
41-
#{@popup_on_init}
42+
-C
43+
run '#{@popup_on_init}'
44+
;
4245
TMUX:END[3]
4346
;
4447
run
45-
#{@popup_after_close}
48+
-C
49+
run '#{@popup_after_close}'
4650
;
4751
TMUX:END[1]

src/toggle_tests/open_nested_popup.stdout

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ TMUX:BEGIN[1] {
44
SHELL=/system/shell
55
}
66
run
7-
#{@popup_before_open}
7+
-C
8+
run '#{@popup_before_open}'
89
;
910
set
1011
default-shell
@@ -30,6 +31,7 @@ TMUX:BEGIN[1] {
3031
TMUX:END[3]
3132
;
3233
run
33-
#{@popup_after_close}
34+
-C
35+
run '#{@popup_after_close}'
3436
;
3537
TMUX:END[1]

src/toggle_tests/open_nested_with_id.stdout

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ TMUX:BEGIN[1] {
44
SHELL=/system/shell
55
}
66
run
7-
#{@popup_before_open}
7+
-C
8+
run '#{@popup_before_open}'
89
;
910
set
1011
default-shell
@@ -30,6 +31,7 @@ TMUX:BEGIN[1] {
3031
TMUX:END[3]
3132
;
3233
run
33-
#{@popup_after_close}
34+
-C
35+
run '#{@popup_after_close}'
3436
;
3537
TMUX:END[1]

src/toggle_tests/open_nested_with_toggle_key.stdout

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ TMUX:BEGIN[1] {
44
SHELL=/system/shell
55
}
66
run
7-
#{@popup_before_open}
7+
-C
8+
run '#{@popup_before_open}'
89
;
910
set
1011
default-shell
@@ -30,6 +31,7 @@ TMUX:BEGIN[1] {
3031
TMUX:END[3]
3132
;
3233
run
33-
#{@popup_after_close}
34+
-C
35+
run '#{@popup_after_close}'
3436
;
3537
TMUX:END[1]

0 commit comments

Comments
 (0)