diff --git a/CHANGELOG.md b/CHANGELOG.md index fb0ff26..515e94f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,16 @@ 処理からも見えます。設定を足すツールはこの変数の指す先へ 1 ファイル置けば、`~/.bashrc` を 書き換えずに済みます。zsh と `lfm` は対象外です。 **反映には `devbase build base --no-cache` と、使っている派生イメージの建て直しが要ります。** +- **`prefix S` のセッションの一覧とメニューを、コマンド `tmux-menu` からも開けるようにしました + (PLAN71 / #270)。** tmux の中では今の pane に一覧を出し、tmux の外では attach して一覧を出します + (attach 先は tmux の既定。サーバが無ければ何も作らず終了コード 1)。本体は `tmux-session menu` + で、`tmux-session menu -c 端末 <セッション>` の形は変わりません。`prefix S` の割り当ては + `tmux-menu` を呼ぶ形になりました(開く一覧とメニューは同じ)。ホストの tmux で使う手順 + (symlink の 5 つ目の `tmux-menu` と `~/.tmux.conf` の新しい行)は + `docs/user/environment-variables.md` の「セッションを名指しで扱う」にあります。 + **反映には `devbase build base --no-cache` と、使っている派生イメージの建て直し、 + コンテナの作り直し(`devbase down` → `devbase up`)が要ります。`devbase up` だけでは + 反映されません。** ### Changed - **スナップショットの世代を、アカウントグループ(対象ボリュームの組)ごとの系列で持つように diff --git a/containers/base/Dockerfile b/containers/base/Dockerfile index 5386a61..e7cf2c7 100644 --- a/containers/base/Dockerfile +++ b/containers/base/Dockerfile @@ -260,7 +260,7 @@ COPY --chmod=0644 tmux.conf /etc/tmux.conf # tmux セッションの整理コマンド。tmux-first は居座りクライアントを切断して # 一番若い番号のセッションへ切り替え、tmux-clean は置き去りのセッションを削除する。 # tmux-session は 1 つのセッションを名指しで移る・調べる・落とす (#234)。prefix S の -# メニュー (tmux.conf) からも呼ばれる。 +# 一覧とメニュー (tmux.conf) からも呼ばれる。一覧は tmux-menu でも開く (#270)。 # 背景と使い方は各スクリプト冒頭のコメントと docs/user/environment-variables.md を参照。 COPY --chmod=0755 tmux-first /usr/local/bin/tmux-first COPY --chmod=0755 tmux-clean /usr/local/bin/tmux-clean @@ -268,12 +268,13 @@ COPY --chmod=0755 tmux-session /usr/local/bin/tmux-session # 短縮名は alias ではなく symlink で用意する。alias は bash の対話シェルにしか効かず、 # zsh などの他シェルや非対話実行 (docker exec など) では使えないため。 -# tmux-go / tmux-peek / tmux-kill は呼ばれた名前で tmux-session のサブコマンドになる。 +# tmux-go / tmux-peek / tmux-kill / tmux-menu は呼ばれた名前で tmux-session のサブコマンドになる。 RUN sudo ln -sf tmux-first /usr/local/bin/tmux1 \ && sudo ln -sf tmux-clean /usr/local/bin/tmuxc \ && sudo ln -sf tmux-session /usr/local/bin/tmux-go \ && sudo ln -sf tmux-session /usr/local/bin/tmux-peek \ - && sudo ln -sf tmux-session /usr/local/bin/tmux-kill + && sudo ln -sf tmux-session /usr/local/bin/tmux-kill \ + && sudo ln -sf tmux-session /usr/local/bin/tmux-menu # フォントの既定。素の fontconfig は sans-serif を中国語フェイス (WenQuanYi Zen Hei) へ # 向けるため、日本語を描くと中国語の字形で写る (#161)。/etc/fonts/local.conf は diff --git a/containers/base/tmux-session b/containers/base/tmux-session index 188cf88..24821be 100755 --- a/containers/base/tmux-session +++ b/containers/base/tmux-session @@ -8,9 +8,10 @@ # tmux-session go [-c 端末] <セッション> 移る。そのセッションの他の端末は外す # tmux-session peek [-n 行数] <セッション> attach せずに端末・プロセス・画面を見る # tmux-session kill [-n] [-f] [-c 端末] <セッション>... attach・実行中を問わず落とす +# tmux-session menu セッションの一覧を開く (prefix S と同じ, #270) # tmux-session menu -c 端末 <セッション> prefix S の一覧から呼ばれるメニュー # -# tmux-go / tmux-peek / tmux-kill という名前 (symlink) で呼ぶと、そのサブコマンドになる。 +# tmux-go / tmux-peek / tmux-kill / tmux-menu という名前 (symlink) で呼ぶと、そのサブコマンドになる。 # 短縮名を alias にしないのは、alias が bash の対話シェルにしか効かないため。 # # セッションの指し方: `$数字` は ID、それ以外は名前の完全一致 (devbase-1 は devbase-10 に @@ -25,6 +26,7 @@ case "$PROG" in tmux-go) SUB="go" ;; tmux-peek) SUB="peek" ;; tmux-kill) SUB="kill" ;; +tmux-menu) SUB="menu" ;; *) PROG=tmux-session SUB="" @@ -44,10 +46,13 @@ usage() { tmux-session kill [-n] [-f] [-c 端末] <セッション>... attach・実行中を問わず落とす。-n は落とさずに予定を出す。 自分の pane があるセッションは -f が無ければ落とさない。 + tmux-session menu + セッションの一覧を開く (prefix S と同じ)。選ぶと下のメニューが出る。 + tmux の外では attach して一覧を出す。 tmux-session menu -c 端末 <セッション> 移る・中身を見る・落とすのメニューを出す (prefix S の一覧から呼ばれる)。 - 短縮名: tmux-go = go / tmux-peek = peek / tmux-kill = kill + 短縮名: tmux-go = go / tmux-peek = peek / tmux-kill = kill / tmux-menu = menu <セッション>: `$数字` は ID、それ以外は名前の完全一致。 -c 端末: この端末の代わりに操作する。知らせは端末の状態行へ出す。 @@ -124,14 +129,23 @@ case "$LINES_N" in '' | *[!0-9]*) usage_error "-n は 0 以上の整数で指定してください: $LINES_N" ;; esac +# menu はセッションも -c も受け取らないときに限り一覧を開く (#270)。どちらか一方でも +# あればメニューを出す形として読む。 +LIST=0 +[ "$SUB" != menu ] || [ $# -ne 0 ] || [ "$HAVE_CLIENT" = 1 ] || LIST=1 + case "$SUB" in kill) [ $# -ge 1 ] || usage_error "セッションを指定してください" ;; +menu) [ "$LIST" = 1 ] || { + [ $# -ge 1 ] || usage_error "セッションを指定してください" + [ $# -eq 1 ] || usage_error "セッションは 1 つだけ指定してください" +} ;; *) [ $# -ge 1 ] || usage_error "セッションを指定してください" [ $# -eq 1 ] || usage_error "セッションは 1 つだけ指定してください" ;; esac -[ "$SUB" != menu ] || [ "$HAVE_CLIENT" = 1 ] || usage_error "menu には -c が要ります" +[ "$SUB" != menu ] || [ "$LIST" = 1 ] || [ "$HAVE_CLIENT" = 1 ] || usage_error "menu には -c が要ります" # 知らせの出し先。-c のときは端末の状態行へ出し、標準出力には何も出さない # (UI から背景の run-shell で呼ばれるため、出力は誰にも見えない)。 @@ -157,6 +171,26 @@ command -v tmux >/dev/null 2>&1 || fail "tmux が見つかりません" SESSIONS=$(tmux list-sessions -F '#{session_id} #{session_name}' 2>/dev/null) || fail "tmux サーバが起動していません" +# 一覧を開く (prefix S と tmux-menu の共通の定義。/etc/tmux.conf の prefix S はここを呼ぶ)。 +# "%%%" は選んだセッションの =名前: に、" \ $ ; ~ を \ で守って置き換わる。'%%' だと ' を +# 含む名前で壊れる。#{q:...} は選んだセッションの ID と、Enter を押した端末の名前をシェル +# 向けに引用する。template を run-shell の文字列へ直接書くと #{...} が先に展開されて壊れる +# ため、定義はこのスクリプトに置く。 +# 出す pane は TMUX_PANE で決める。prefix S は run-shell に TMUX_PANE=#{pane_id} を渡す +# (run-shell の中には TMUX_PANE が無く、-t が無いと直近に操作された別の端末に出ることがある)。 +if [ "$LIST" = 1 ]; then + TREE_TEMPLATE='run-shell -t "%%%" "tmux-session menu -c #{q:client_name} #{q:session_id}"' + if [ -n "${TMUX:-}" ]; then + if [ -n "${TMUX_PANE:-}" ]; then + exec tmux choose-tree -t "$TMUX_PANE" -Zs -O name "$TREE_TEMPLATE" + fi + exec tmux choose-tree -Zs -O name "$TREE_TEMPLATE" + fi + # tmux の外: attach 先は tmux の既定 (端末の繋がっていないセッションを優先し、 + # その中で直近のもの)。attach した画面に一覧を出す。 + exec tmux attach-session \; choose-tree -Zs -O name "$TREE_TEMPLATE" +fi + if [ "$HAVE_CLIENT" = 1 ]; then tmux list-clients -F '#{client_name}' | grep -Fqx -- "$CLIENT" || fail "端末がありません: $CLIENT" diff --git a/containers/base/tmux.conf b/containers/base/tmux.conf index 8760e69..539a164 100644 --- a/containers/base/tmux.conf +++ b/containers/base/tmux.conf @@ -46,7 +46,8 @@ set -g focus-events on # 含まれないため、宣言しないと Claude Code などが出す URL がクリックできない。 set -ga terminal-features ",xterm-256color:hyperlinks" -# prefix S: セッションを選び、移る・中身を見る・落とすのメニューを出す (#234)。 -# "%%%" は選んだセッションの =名前: に、" \ $ ; ~ を \ で守って置き換わる。'%%' だと ' を -# 含む名前で壊れる。#{q:...} は選んだセッションの ID と、押した端末の名前をシェル向けに引用する。 -bind-key S choose-tree -Zs -O name "run-shell -t \"%%%\" \"tmux-session menu -c #{q:client_name} #{q:session_id}\"" +# prefix S: セッションを選び、移る・中身を見る・落とすのメニューを出す (#234 / #270)。 +# 一覧の定義は tmux-session にある (tmux-menu と同じ)。run-shell は渡した文字列の #{…} を +# 先に展開するため、template をここへ書かずにコマンドを呼ぶ。run-shell の中には TMUX_PANE が +# 無いため、押した pane の ID を渡す (無いと直近に操作された別の端末に出ることがある)。 +bind-key S run-shell "TMUX_PANE=#{pane_id} tmux-menu" diff --git a/docs/specifications/tmux-named-session.md b/docs/specifications/tmux-named-session.md index 0c67067..15677e1 100644 --- a/docs/specifications/tmux-named-session.md +++ b/docs/specifications/tmux-named-session.md @@ -10,8 +10,9 @@ base イメージは、1 つの tmux セッションを名前か ID で指して - 落とす(`kill`): attach 中・実行中を問わずセッションを終わらせる tmux の中では `prefix S` でセッションの一覧(`choose-tree`)を出し、選んだセッションに同じ -3 つをメニューから行える。UI は tmux 組み込みの `choose-tree` / `display-menu` / -`display-popup` だけで組み、パッケージを足さない。 +3 つをメニューから行える。コマンド `tmux-menu`(`tmux-session menu`)からも同じ一覧とメニューを +開ける。tmux の外で打つと attach して、attach した画面に一覧を出す。UI は tmux 組み込みの +`choose-tree` / `display-menu` / `display-popup` だけで組み、パッケージを足さない。 `tmux-first` / `tmux-clean` は「同じベース名のセッション群」を、操作中の端末と実行中の セッションを守りながら整理する道具である。`tmux-session` は 1 つを狙って強制的に効かせる道具で、 @@ -24,14 +25,15 @@ tmux の中では `prefix S` でセッションの一覧(`choose-tree`)を ## 対象範囲 -- コマンド `tmux-session` と短縮名 `tmux-go` / `tmux-peek` / `tmux-kill` +- コマンド `tmux-session` と短縮名 `tmux-go` / `tmux-peek` / `tmux-kill` / `tmux-menu` - `/etc/tmux.conf` の `prefix S` の割り当て - base と、base を継ぐ派生イメージへの伝播の規則 - `containers/lfm` と `containers/snapshot` は base を継がないため対象に含まない - ホストの `~/.local/bin` と `~/.tmux.conf` へ配る仕組みは持たない。devbase はホストの利用者の ファイルへ書かず(`~/.tmux.conf` を書き換えると、消したつもりの行が戻るなどの食い違いを 生む)、手順を利用者向け文書に書いて利用者が置く -- tmux の外で一覧から選ぶ UI は持たない。名指しの 3 操作は tmux の外からもコマンドで使える +- tmux の外で、attach せずに一覧から選ぶ UI は持たない。`tmux-menu` は tmux の外では attach して + 一覧を出す。名指しの 3 操作は tmux の外からもコマンドで使える - 読み取り専用の attach(`tmux attach -r`)の操作は持たない。`choose-tree` のプレビューが 同じ用途を満たす @@ -40,9 +42,9 @@ tmux の中では `prefix S` でセッションの一覧(`choose-tree`)を | 要素 | 置き場所 | 責務 | | --- | --- | --- | | 本体 | `containers/base/tmux-session` → `/usr/local/bin/tmux-session` | サブコマンド `go` / `peek` / `kill` / `menu` を持つ POSIX sh(`#!/bin/sh`、`set -eu`)の 1 ファイル | -| 短縮名 | `/usr/local/bin/tmux-go` / `tmux-peek` / `tmux-kill` | `tmux-session` への symlink。呼ばれた名前でサブコマンドが決まる | -| 導入 | `containers/base/Dockerfile` の「tmux セッションの整理コマンド」の節 | `COPY --chmod=0755 tmux-session /usr/local/bin/tmux-session` と、`tmux1` / `tmuxc` と同じ `RUN` の `ln -sf` 3 つ | -| キーの割り当て | `containers/base/tmux.conf` の末尾 → `/etc/tmux.conf` | `bind-key S choose-tree … "tmux-session menu …"` の 1 行 | +| 短縮名 | `/usr/local/bin/tmux-go` / `tmux-peek` / `tmux-kill` / `tmux-menu` | `tmux-session` への symlink。呼ばれた名前でサブコマンドが決まる | +| 導入 | `containers/base/Dockerfile` の「tmux セッションの整理コマンド」の節 | `COPY --chmod=0755 tmux-session /usr/local/bin/tmux-session` と、`tmux1` / `tmuxc` と同じ `RUN` の `ln -sf` 4 つ | +| キーの割り当て | `containers/base/tmux.conf` の末尾 → `/etc/tmux.conf` | `bind-key S run-shell "TMUX_PANE=#{pane_id} tmux-menu"` の 1 行。一覧の定義(`choose-tree` と template)は `tmux-session` が持つ | | 静的検査 | `.github/workflows/ci.yml` の `shellcheck` ジョブ | `containers/base/tmux-first` / `tmux-clean` / `tmux-session` を既定の severity(style まで)で検査する | | 回帰テスト | `tests/containers/test_tmux_session.py`、`tests/containers/test_tmux_conf.py` | 専用の tmux サーバでの振る舞いと、Dockerfile・tmux.conf の形を固定する | @@ -62,21 +64,29 @@ tmux の中では `prefix S` でセッションの一覧(`choose-tree`)を tmux-session go [-c 端末] <セッション> tmux-session peek [-n 行数] <セッション> tmux-session kill [-n] [-f] [-c 端末] <セッション>... +tmux-session menu tmux-session menu -c 端末 <セッション> tmux-go … = tmux-session go … tmux-peek … = tmux-session peek … tmux-kill … = tmux-session kill … +tmux-menu … = tmux-session menu … ``` -- `basename "$0"` が `tmux-go` / `tmux-peek` / `tmux-kill` なら第 1 引数をサブコマンドとして - 読まない。それ以外の名前で呼ばれたときは第 1 引数をサブコマンドとして読む +- `basename "$0"` が `tmux-go` / `tmux-peek` / `tmux-kill` / `tmux-menu` なら第 1 引数を + サブコマンドとして読まない。それ以外の名前で呼ばれたときは第 1 引数をサブコマンドとして読む - `-h` / `--help` はどのサブコマンドでも使い方を標準出力へ出して終了コード 0 で終わる - `kill` の `-n` は `--dry-run`、`-f` は `--force` とも書ける。`--` でオプションの終わりを示せる - オプションはサブコマンドごとに受け付けるものが決まっている。`peek` は `-c` を、`go` は `-n` を 受け取らない(知らないオプションとして終了コード 2) -- `go` / `peek` / `menu` はセッションをちょうど 1 つ、`kill` は 1 つ以上取る +- `go` / `peek` / `menu` はセッションをちょうど 1 つ、`kill` は 1 つ以上取る。ただし `menu` は + セッションも `-c` も無いときに限り一覧を開く形になる。どちらか一方でもあればメニューを出す形として読む - `-n 行数` は 0 以上の整数だけを受け付ける。既定は 20 +`tmux-menu` を `menu` の短縮名にし、一覧を開く動きを「セッションも `-c` も無い形」に割り当てるのは、 +`tmux-go` = `go` と同じ規則で名前から動きが読め、メニューを出す形(`menu -c 端末 <セッション>`)の +呼び出し元を何も変えずに済むためである。メニューを出す形は、`/etc/tmux.conf` の一覧の template と、 +利用者がホストの `~/.tmux.conf` へ写した以前の割り当ての行が呼ぶため、形を変えない。 + ### セッションの指し方 | 引数の形 | 解決 | @@ -182,9 +192,31 @@ screen (0.0 の直近 20 行) `/etc/tmux.conf` の割り当ては次の 1 行である。 ```tmux -bind-key S choose-tree -Zs -O name "run-shell -t \"%%%\" \"tmux-session menu -c #{q:client_name} #{q:session_id}\"" +bind-key S run-shell "TMUX_PANE=#{pane_id} tmux-menu" ``` +一覧の定義は `tmux-session` の 1 か所にあり、`prefix S` とコマンド `tmux-menu`(`tmux-session menu`)が +共有する。`run-shell` は渡した文字列の `#{…}` を先に展開するため、template を割り当てへ直接書かずに +コマンドを呼ぶ。一覧を開く形の `tmux-session` は次を実行する。 + +```sh +TREE_TEMPLATE='run-shell -t "%%%" "tmux-session menu -c #{q:client_name} #{q:session_id}"' +tmux choose-tree -t "$TMUX_PANE" -Zs -O name "$TREE_TEMPLATE" # TMUX と TMUX_PANE がある +tmux choose-tree -Zs -O name "$TREE_TEMPLATE" # TMUX があり TMUX_PANE が空 +tmux attach-session \; choose-tree -Zs -O name "$TREE_TEMPLATE" # tmux の外 +``` + +- 一覧を出す pane は `TMUX_PANE` で決める。`run-shell` の中には `TMUX_PANE` が無く、`-t` が無いと + 直近に操作された別の端末に出ることがあるため、`prefix S` は押した pane の ID を `TMUX_PANE` で渡す + (`run-shell` は `#{pane_id}` を先に展開し、`%3` の形の引用の要らない ID が渡る) +- tmux は、端末を持たないクライアントから来たコマンドの現在の pane を、その環境の `TMUX_PANE` で + 決める。そのため `-t` が無くても `TMUX_PANE` の pane に出る。それでも `-t "$TMUX_PANE"` を付けるのは、 + 出す pane をコマンドの行に書いて読めるようにし、tmux が環境から現在の pane を引く規則に頼らない + ためである。`-t` の有無で振る舞いは変わらない +- tmux の外での attach 先は tmux の既定(端末の繋がっていないセッションを優先し、その中で直近のもの。 + すべてに端末が繋がっていれば全体で直近のもの)に従い、attach した画面に一覧を出す。attach 先を + 引数で取らないのは、名指しで移るなら `tmux-go` があり、一覧からどのセッションへも移れるためである。 + tmux のサーバが動いていなければ、サーバもセッションも作らずに終了コード 1 で終わる - `choose-tree -Zs -O name` は、名前順のセッションの一覧を全画面で出す。tmux の既定の操作 (`v` でプレビューの切り替え、`f` で絞り込み、`x` で 1 つ落とす、`t` で印を付けて `X` で まとめて落とす)はそのまま使える @@ -235,9 +267,12 @@ tmux display-menu -c 端末 -t '$ID' -T '#[align=centre]#{session_name}' … sequenceDiagram participant U as 利用者の端末 participant T as tmux サーバ + participant L as tmux-menu(一覧) participant M as tmux-session menu participant G as tmux-session go U->>T: prefix S + T->>L: run-shell
TMUX_PANE=pane tmux-menu + L->>T: choose-tree -t pane T->>U: choose-tree(一覧とプレビュー) U->>T: セッションを選んで Enter T->>M: run-shell -t "%%%"
menu -c 端末 $ID @@ -260,7 +295,7 @@ sequenceDiagram | --- | --- | | 0 | 成功。`-h` / `--help` | | 1 | tmux が無い・サーバが無い・対象のセッションが無い・`-c` の端末が無い・実行元を特定できない・自分のセッションを `-f` なしで `kill` しようとした・tmux のコマンドが失敗した | -| 2 | 使い方の誤り(サブコマンドが無い・知らないサブコマンドとオプション・オプションの値が無い・セッションの数が合わない・`-n` が 0 以上の整数でない・`-c` の値の形が外れた・`menu` に `-c` が無い) | +| 2 | 使い方の誤り(サブコマンドが無い・知らないサブコマンドとオプション・オプションの値が無い・セッションの数が合わない・`-n` が 0 以上の整数でない・`-c` の値の形が外れた・メニューを出す形の `menu` に `-c` が無い) | 誤りは理由を標準エラーへ出す。終了コード 2 のときは `使い方は <呼ばれた名前> -h` を添える。 @@ -292,8 +327,11 @@ sequenceDiagram (コマンドが `PATH` に無ければメニューは動かない) - `containers/lfm` と `containers/snapshot` は base を継がないため入らない - ホストの tmux で使うときは、利用者が devbase の checkout の `containers/base/tmux-session` を - 指す symlink を `~/.local/bin` へ 4 つ張り、`~/.tmux.conf` へ上の 1 行を足す。複写ではなく - symlink にすると `git pull` で更新が届く + 指す symlink を `~/.local/bin` へ 5 つ(`tmux-session` と短縮名 4 つ)張り、`~/.tmux.conf` へ上の + 1 行を足す。複写ではなく symlink にすると `git pull` で更新が届く +- ホストの `~/.tmux.conf` に以前の割り当て(`bind-key S choose-tree …` で template を直接書く行)を + 残していても、`prefix S` はメニューを出す形を呼ぶためそのまま動く。`tmux-menu` を使うには symlink の + `tmux-menu` を足す - 動作を確かめてある tmux は、コンテナの 3.6(Ubuntu 26.04)とホストの 3.7b。使う機能 (`choose-tree` の template・`display-menu`(3.0 以降)・`display-popup`(3.2 以降)・ 書式の `q:`)は両方にある。`/bin/sh` は Ubuntu の dash と macOS の bash 3.2(POSIX モード)で @@ -333,26 +371,49 @@ sequenceDiagram あること - `prefix S` - `containers/base/tmux.conf` を読んだ tmux の `list-keys -T prefix` に `S` の割り当てがちょうど - 1 つあり、`choose-tree` を呼ぶこと(`test_tmux_conf.py`) + 1 つあり、`run-shell "TMUX_PANE=#{pane_id} tmux-menu"` であること(`test_tmux_conf.py`) - 引数を書き出すだけの偽の `tmux-session` を `PATH` の先頭に置き、`pty` から `C-b S` → 選択 → Enter を送ると、選んだセッションの ID と押した端末の名前が渡ること。特殊文字の名前でも同じこと - 本物の `tmux-session` で、メニューの「移る」「中身を見る」「落とす」(確認の `y`)がそれぞれ 効き、今いるセッションも同意すれば落ちること。背景の `run-shell` からの知らせが端末へ届くこと +- `tmux-menu`(一覧を開く形) + - tmux の中で `tmux-menu` と `tmux-session menu` のどちらをプロンプトから打っても、その pane に + 一覧(`tree-mode`)が出ること + - `TMUX_PANE` の pane に一覧が出て、後から attach して直近に操作された端末の pane には出ないこと。 + `TMUX` があり `TMUX_PANE` を消した環境でも、端末が 1 つだけなら、その端末の pane に一覧が出て + 終了コード 0 であること + - tmux の外で打つと、端末の繋がっていないセッションへ attach して一覧を出すこと + - 偽の `tmux-session` で、中・外のどちらで開いた一覧でも、選んで Enter を押すと + `menu -c <選んだ ID>` が渡ること。特殊文字の名前でも同じこと + - 本物の `tmux-session` で、一覧から出したメニューの「移る」で Enter を押した端末が移り、対象に + 繋がっていた他の端末が外れること + - メニューを出す形(`-c 端末 `)は、`tmux-menu` と `tmux-session menu` のどちらでもメニューを + 出すこと + - サーバが無いと、`tmux-menu` と `tmux-session menu` のどちらも終了コード 1 で標準エラーへ理由を + 出し、セッションを作らないこと + - `tmux-menu -h` が終了コード 0 で、`tmux-session -h` の使い方に `tmux-menu` が載ること。`-c` だけで + セッションが無い・`-c` が無い・余分な引数・知らないオプションは終了コード 2 であること - 配布 - Dockerfile に `COPY --chmod=0755 tmux-session /usr/local/bin/tmux-session` が 1 行あり、 - 短縮名 3 つの `ln -sf tmux-session /usr/local/bin/<名前>` があること + 短縮名 4 つの `ln -sf tmux-session /usr/local/bin/<名前>` があること - `containers/base/tmux-session` が `#!/bin/sh` で始まり実行権を持つこと +一覧を開いて選ぶテストは、コマンド行とカーソルの移動を `send-keys` で pane へ送り、Enter だけは +繋いだ端末から送る。`send-keys` で送った Enter では、template の `#{client_name}` が押した端末ではなく +直近に操作された端末に展開され、渡った端末名を確かめられないためである。後から attach した端末は、 +打たなくても直近に操作された端末になる。 + `test_tmux_conf.py` の copy-mode の割り当ての比較は `-T copy-mode` / `-T copy-mode-vi` の行だけを 対象にし、`prefix S` の行が混ざっても崩れない。 CI はイメージを建てないため、次は建てたイメージで手で確かめる。 - `docker run --rm --entrypoint /bin/bash devbase-base:latest -c 'ls -l /usr/local/bin/tmux-*; tmux-go -h; echo exit=$?'` - で 4 つのコマンドがあり、`tmux-go -h` が終了コード 0 で終わること + で `tmux-session` と短縮名 4 つのコマンドがあり、`tmux-go -h` が終了コード 0 で終わること - 建てた base の `shellcheck` で `containers/base/tmux-session` を既定の severity で検査すると、 指摘が 0 件であること - 建て直した base のコンテナの tmux で、`prefix S` のメニューの 3 つの操作が効くこと +- 建て直した base のコンテナで `tmux-menu` を打つと、セッションの一覧が開くこと CI の ShellCheck ジョブは runner の shellcheck で `tmux-first` / `tmux-clean` / `tmux-session` を 検査する。 diff --git a/docs/user/environment-variables.md b/docs/user/environment-variables.md index d00f93d..a73c499 100644 --- a/docs/user/environment-variables.md +++ b/docs/user/environment-variables.md @@ -611,22 +611,34 @@ tmux の中では **`prefix S`**(既定では `Ctrl+b` → `Shift+s`)で、 | 中身を見る | `p` | `tmux-peek` の出力を浮いた窓に出す。`Enter` で閉じる | | 落とす | `k` | 確認(`y/n`)を挟んで落とす。今いるセッションも、同意すれば落とす | +キーを覚えていなくても、**`tmux-menu`** を打てば `prefix S` と同じ一覧とメニューが開きます(`tmux-session menu` と同じ)。 + +```bash +tmux-menu # tmux の中: 今の pane に一覧を出す + # tmux の外: attach して、attach した画面に一覧を出す +``` + +- tmux の外での attach 先は tmux の既定に従います(端末の繋がっていないセッションを優先し、その中で直近に使ったもの)。一覧からどのセッションへも移れます。名前の決まったセッションへ直接行くなら `tmux-go` を使います +- tmux のサーバが動いていないときは、サーバもセッションも作らずに理由を出して終了コード 1 で終わります + `prefix S` の割り当ては `/etc/tmux.conf` にあります。`~/.tmux.conf` で `S` を別の操作に割り当てていれば、後から読むそちらが勝ちます(コマンドはそのまま使えます)。 **ホストの tmux で使う場合**は、devbase の checkout の中のファイルへ symlink を張り、`~/.tmux.conf` へ 1 行足します(devbase はホストのこれらのファイルへ書き込みません)。複写ではなく symlink にすると、devbase を `git pull` するだけで更新が届きます。`~/.local/bin` が `PATH` に入っている必要があります。 ```bash DEVBASE_DIR=~/devbase # devbase を clone した場所 -for n in tmux-session tmux-go tmux-peek tmux-kill; do +for n in tmux-session tmux-go tmux-peek tmux-kill tmux-menu; do ln -sf "$DEVBASE_DIR/containers/base/tmux-session" ~/.local/bin/$n done ``` ```tmux # ~/.tmux.conf に足す (反映は tmux source-file ~/.tmux.conf か tmux kill-server) -bind-key S choose-tree -Zs -O name "run-shell -t \"%%%\" \"tmux-session menu -c #{q:client_name} #{q:session_id}\"" +bind-key S run-shell "TMUX_PANE=#{pane_id} tmux-menu" ``` +以前の手順で `bind-key S choose-tree …` の行を足した人は、そのままでも `prefix S` は動きます。`tmux-menu` を使うには symlink の 5 つ目(`tmux-menu`)を足します。行は上の新しい形へ差し替えると、一覧の定義が `tmux-session` の 1 か所にそろいます。 + dev コンテナで使うには、base イメージの建て直し(`devbase build base --no-cache`)と、使っている派生イメージの建て直し、コンテナの作り直し(`devbase down` → `devbase up`)が要ります。 サブコマンドごとの引数・終了コード・`peek` の出力・`prefix S` のメニューの仕様は diff --git a/issues/PLAN71_tmux-menu-design.md b/issues/PLAN71_tmux-menu-design.md deleted file mode 100644 index 010a7cb..0000000 --- a/issues/PLAN71_tmux-menu-design.md +++ /dev/null @@ -1,354 +0,0 @@ -# PLAN71: prefix S のセッションの一覧とメニューをコマンド tmux-menu からも開く の設計 - -要求と受け入れ条件は [PLAN71_tmux-menu.md](PLAN71_tmux-menu.md) にある。この文書は -「どう作るか」だけを扱う。土台の `tmux-session` の設計は PLAN69 にある。確定仕様は -[docs/specifications/tmux-named-session.md](../docs/specifications/tmux-named-session.md) である。 - -## 機能一覧 - -| # | 機能 | 誰が使うか | -| --- | --- | --- | -| F1 | tmux の中で `tmux-menu` を打つと、今の pane にセッションの一覧を出す。選ぶと `prefix S` と同じメニューが出る | tmux の中にいる利用者 | -| F2 | tmux の外で `tmux-menu` を打つと、attach と同時に一覧を出す | tmux の外にいる利用者 | -| F3 | `prefix S` を F1 と同じ定義で開く(一覧を開く定義を 1 か所にする) | tmux の中にいる利用者 | -| F4 | F1〜F3 を base イメージへ入れ、ホストで使う手順を示す | base を建てる利用者と、ホストの tmux の利用者 | - -## 構成要素 - -| 要素 | 変更 | 責務 | -| --- | --- | --- | -| `containers/base/tmux-session` | 変える | 呼ばれた名前 `tmux-menu` を `menu` へ振り分ける。`menu` がセッションを受け取らないときは一覧を開く(決定 1)。一覧を開く `choose-tree` の定義をこのファイルだけに持つ(決定 2) | -| `containers/base/tmux.conf` | 変える | `prefix S` の行を、キーを押した pane の ID を渡して `run-shell` で `tmux-menu` を呼ぶ形に変える(決定 2・決定 4) | -| `containers/base/Dockerfile` の「tmux セッションの整理コマンド」の節 | 変える | symlink の `RUN` に `tmux-menu` を 1 つ足す | -| `tests/containers/test_tmux_session.py` | 変える | F1・F2・F3 と、`menu -c 端末 <セッション>` の形が変わらないことを確かめる。`SHORT_NAMES` に `tmux-menu` を足す(下の「テスト基盤の更新」) | -| `tests/containers/test_tmux_conf.py` | 変える | 既存の `test_prefix_s_opens_session_chooser` を書き換え、`prefix S` の割り当てが `TMUX_PANE=#{pane_id}` を付けて `tmux-menu` を呼ぶことを `list-keys` で確かめる | -| `docs/user/environment-variables.md` の「セッションを名指しで扱う」 | 変える | `tmux-menu` の使い方を足す。ホストの手順の symlink を 5 つにし、`~/.tmux.conf` の行を新しい割り当てへ差し替える | -| `docs/specifications/tmux-named-session.md` | 変える(確定仕様化で) | `menu` の 2 つの形と `tmux-menu`、`prefix S` の新しい行 | -| `issues/PLAN71_tmux-menu-measure.py` | 足す(この設計で) | 「実測」の再現用スクリプト。確定仕様化のときに消す | -| `CHANGELOG.md` | 変える | `[Unreleased]` の `### Added` に足す。反映に `devbase build base --no-cache` が要ることを書く | - -次のものは変えない。 - -- `tmux-session` の `go` / `peek` / `kill` と、`menu -c 端末 <セッション>` が出すメニューの中身 -- `containers/base/tmux-first` / `containers/base/tmux-clean` -- `prefix s`(tmux の既定の `choose-tree -Zs`) - -## 決定の記録 - -### 決定 1: `tmux-menu` は `menu` サブコマンドの短縮名にし、セッションを受け取らない形を一覧を開く動きにする - -利用者が決めた名前 `tmux-menu` と、サブコマンドの名前 `menu` がそろう。`tmux-go` = `go` と同じ -規則で読める。今の `menu -c 端末 <セッション>` はセッションを必ず受け取るため、受け取らない -形は空いている。空いた形に一覧を割り当てれば、今の形の呼び出し元(今の `/etc/tmux.conf` と -ホストへ写した行)は何も変えずに動く。 - -新しいサブコマンド(`ui` など)を足して `tmux-menu` をそちらへ振り分ける案は採らない。 -`tmux-menu` と `menu` が別の動きになり、名前から動きが読めない。今の `menu` を別名へ改名する -案も採らない。ホストへ写した `~/.tmux.conf` の行が壊れる。 - -### 決定 2: 一覧を開く定義は `tmux-session` だけに持ち、`prefix S` は `run-shell` で `tmux-menu` を呼ぶ - -`prefix S` と `tmux-menu` が同じ一覧を開くことを、定義を 1 つにして保つ。2 か所に同じ template を -持つと、片方だけを直したときに 2 つの入口の動きが分かれる。実測のとおり、template を -`run-shell` の文字列へ直接書く形は `#{…}` の先の展開で壊れる。スクリプトの中に置けば -`run-shell` の展開を通らない。ホストの手順も `~/.tmux.conf` の 1 行が短くなる。 - -`prefix S` の行を今のまま残し、`tmux-session` にも同じ template を書く案は採らない。一致を -テストで縛ることはできるが、同じ文字列を 2 か所で持つこと自体が残る。 - -### 決定 3: tmux の外では attach 先を引数で取らない - -名前の決まったセッションへ行くなら `tmux-go` がある。`tmux-menu` は「まず一覧を見る」入口で、 -attach 先は tmux の既定(**繋がっている端末の無いセッションを優先し、その中で直近に使ったもの**。 -すべてに端末が繋がっていれば全体で直近に使ったもの)で足りる。一覧からどのセッションへも移れる。 -セッションを引数で取ると、決定 1 の「セッションも `-c` も受け取らない形だけが一覧を開く」規則が崩れる。 - -### 決定 4: `prefix S` はキーを押した pane の ID を `TMUX_PANE` で渡し、`choose-tree` の `-t` に使う - -`run-shell` から起動したシェルには `TMUX_PANE` が無い。`-t` の無い `choose-tree` は、呼び出し元の -pane を継がず、全セッションから直近に操作されたセッションを選び直す。`prefix S` を押してから -`choose-tree` が走るまでの間に別のセッションの端末が操作されると、一覧がその端末に出る(実測の 5)。 -`run-shell` は渡された文字列の `#{…}` を先に展開するため、`TMUX_PANE=#{pane_id} tmux-menu` と -書けば押した pane の ID(`%3` の形。引用の要らない文字だけ)が渡る(実測の 6)。 - -一覧を出す pane を決めるのは `TMUX_PANE` そのものである。tmux は、端末を持たないクライアントから -来たコマンドの現在の pane を、そのクライアントの環境の `TMUX_PANE` で決める(tmux の `cmd-find.c` -の `cmd_find_inside_pane`)。`-t` の無い `choose-tree` でも、`TMUX_PANE` があればその pane に出る -(実測の 7a)。それでも `tmux-menu` は `TMUX_PANE` を `-t` にも渡す。どの pane に出すかをコマンドの -行に書いて意図を読めるようにし、tmux が環境から現在の pane を引く規則に頼らないためである。 -`-t` は保険であり、`-t` の有無で振る舞いは変わらない(実測の 7a・7b)。 - -`tmux-menu` に `-t` のオプションを足して ID を渡す案は採らない。`menu` の受け付ける形が増え、 -tmux の中でコマンドを打つ形(`TMUX_PANE` がシェルにある)と同じ入口に揃わない。 - -## 実測(2026-09-24、ホストの tmux 3.7b) - -再現用のスクリプトは [PLAN71_tmux-menu-measure.py](PLAN71_tmux-menu-measure.py) にある -(`python3 issues/PLAN71_tmux-menu-measure.py`。一時ディレクトリのソケットでサーバを立て、利用者の -サーバに触れない)。`python3` の `pty` で端末を繋ぎ、`tmux-session` の代わりに引数を書き出すだけの -偽物を置く。一覧を開くスクリプト `open-list` は `tmux-menu` の代わりで、`TMUX_PANE` があれば -`-t "$TMUX_PANE"` を付けて次の `