Skip to content

Commit 0547bf8

Browse files
committed
fix: make tenkai switch robust with shell hook gating
1 parent cfcec9c commit 0547bf8

5 files changed

Lines changed: 53 additions & 7 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,17 @@ ryoiki tenkai
9090
| `d` | purpose(用途)を設定 |
9191
| `f` | workspaceを削除 |
9292
| `a` | workspaceを追加 |
93+
| `s` | 選択workspaceへ切り替え(`cd`|
9394
| `r` | リフレッシュ |
9495
| `q` | 終了 |
9596

97+
`s` で切り替えるには最新のシェル統合が必要です。動かない場合は再設定してください。
98+
99+
```bash
100+
ryoiki init zsh
101+
source ~/.zshrc
102+
```
103+
96104
## Requirements
97105

98106
- [jj (Jujutsu)](https://github.com/jj-vcs/jj) がインストール済みであること

cmd/tenkai.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"github.com/kosuke9809/ryoiki/internal/tui"
1111
)
1212

13+
const tenkaiSwitchCaptureEnv = "RYOIKI_TENKAI_SWITCH_CAPTURE"
14+
1315
var tenkaiCmd = &cobra.Command{
1416
Use: "tenkai",
1517
Short: "Launch interactive TUI",
@@ -31,12 +33,21 @@ var tenkaiCmd = &cobra.Command{
3133
return err
3234
}
3335
if finalApp, ok := finalModel.(tui.App); ok && finalApp.SwitchPath != "" {
36+
if !switchCaptureEnabled() {
37+
return fmt.Errorf(
38+
"tenkai switch requires updated shell integration. Re-run `ryoiki init <shell>` and reload your shell (e.g. `source ~/.zshrc`)",
39+
)
40+
}
3441
fmt.Print(finalApp.SwitchPath)
3542
}
3643
return nil
3744
},
3845
}
3946

47+
func switchCaptureEnabled() bool {
48+
return os.Getenv(tenkaiSwitchCaptureEnv) == "1"
49+
}
50+
4051
func requireInteractiveTerminal(stdin, output *os.File, isTerminal func(*os.File) bool) error {
4152
if !isTerminal(stdin) || !isTerminal(output) {
4253
return fmt.Errorf(

cmd/tenkai_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,15 @@ func TestIsInteractiveTerminalNilFile(t *testing.T) {
7070
t.Fatal("expected nil file to be non-interactive")
7171
}
7272
}
73+
74+
func TestSwitchCaptureEnabled(t *testing.T) {
75+
t.Setenv(tenkaiSwitchCaptureEnv, "1")
76+
if !switchCaptureEnabled() {
77+
t.Fatal("expected switch capture enabled when env=1")
78+
}
79+
80+
t.Setenv(tenkaiSwitchCaptureEnv, "0")
81+
if switchCaptureEnabled() {
82+
t.Fatal("expected switch capture disabled when env!=1")
83+
}
84+
}

internal/shell/init.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,43 @@ import (
88
)
99

1010
const zshScript = `ryoiki() {
11+
export RYOIKI_SHELL_HOOK_VERSION=2
1112
if [[ "$1" == "switch" ]]; then
1213
local dir
1314
dir="$(\command ryoiki "$@")" && [[ -n "$dir" ]] && builtin cd -- "$dir"
15+
elif [[ "$1" == "tenkai" ]]; then
16+
local dir
17+
dir="$(RYOIKI_TENKAI_SWITCH_CAPTURE=1 \command ryoiki "$@")" && [[ -n "$dir" ]] && builtin cd -- "$dir"
1418
else
1519
\command ryoiki "$@"
1620
fi
1721
}
1822
`
1923

2024
const bashScript = `ryoiki() {
25+
export RYOIKI_SHELL_HOOK_VERSION=2
2126
if [[ "$1" == "switch" ]]; then
2227
local dir
2328
dir="$(command ryoiki "$@")" && [[ -n "$dir" ]] && builtin cd -- "$dir"
29+
elif [[ "$1" == "tenkai" ]]; then
30+
local dir
31+
dir="$(RYOIKI_TENKAI_SWITCH_CAPTURE=1 command ryoiki "$@")" && [[ -n "$dir" ]] && builtin cd -- "$dir"
2432
else
2533
command ryoiki "$@"
2634
fi
2735
}
2836
`
2937

3038
const fishScript = `function ryoiki
39+
set -gx RYOIKI_SHELL_HOOK_VERSION 2
3140
if test "$argv[1]" = "switch"
3241
set -l dir (command ryoiki $argv)
3342
and test -n "$dir"
3443
and builtin cd -- $dir
44+
else if test "$argv[1]" = "tenkai"
45+
set -l dir (env RYOIKI_TENKAI_SWITCH_CAPTURE=1 command ryoiki $argv)
46+
and test -n "$dir"
47+
and builtin cd -- $dir
3548
else
3649
command ryoiki $argv
3750
end

internal/shell/init_test.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -124,14 +124,14 @@ func TestEvalLine(t *testing.T) {
124124
}
125125
}
126126

127-
func TestInitScriptDoesNotInterceptTenkai(t *testing.T) {
127+
func TestInitScriptInterceptsTenkai(t *testing.T) {
128128
tests := []struct {
129129
shell string
130-
contain string
130+
contain []string
131131
}{
132-
{"zsh", `"tenkai"`},
133-
{"bash", `"tenkai"`},
134-
{"fish", `"tenkai"`},
132+
{"zsh", []string{`"switch"`, `"tenkai"`, "RYOIKI_TENKAI_SWITCH_CAPTURE=1", "RYOIKI_SHELL_HOOK_VERSION=2"}},
133+
{"bash", []string{`"switch"`, `"tenkai"`, "RYOIKI_TENKAI_SWITCH_CAPTURE=1", "RYOIKI_SHELL_HOOK_VERSION=2"}},
134+
{"fish", []string{`"switch"`, `"tenkai"`, "RYOIKI_TENKAI_SWITCH_CAPTURE=1", "RYOIKI_SHELL_HOOK_VERSION 2"}},
135135
}
136136

137137
for _, tt := range tests {
@@ -140,8 +140,10 @@ func TestInitScriptDoesNotInterceptTenkai(t *testing.T) {
140140
if err != nil {
141141
t.Fatalf("unexpected error: %v", err)
142142
}
143-
if strings.Contains(script, tt.contain) {
144-
t.Errorf("script should not intercept tenkai (found %q):\n%s", tt.contain, script)
143+
for _, contain := range tt.contain {
144+
if !strings.Contains(script, contain) {
145+
t.Errorf("script missing %q:\n%s", contain, script)
146+
}
145147
}
146148
})
147149
}

0 commit comments

Comments
 (0)