diff --git a/README.md b/README.md index 13ae4c30..4dcb1672 100644 --- a/README.md +++ b/README.md @@ -253,7 +253,7 @@ Configuration is stored in the platform-specific config directory: | `agent` | string | `""` | Pass `--agent ` to Copilot CLI | | `model` | string | `""` | Pass `--model ` to Copilot CLI | | `launch_mode` | string | `"tab"` | How to open sessions: `in-place`, `tab`, `window`, `pane` | -| `pane_direction` | string | `"auto"` | Split direction for pane mode: `auto`, `right`, `down`, `left`, `up` | +| `pane_direction` | string | `"auto"` | Split direction for pane mode: `auto`, `right`, `down`, `left`, `up` (see note below) | | `custom_command` | string | `""` | Custom launch command (`{sessionId}` is replaced) | | `excluded_dirs` | array | `[]` | Directory paths to hide from session list | | `theme` | string | `"auto"` | Color scheme: `auto` or a named scheme | @@ -262,6 +262,20 @@ Configuration is stored in the platform-specific config directory: | `hiddenSessions` | array | `[]` | Session IDs hidden from the main list | | `favoriteSessions` | array | `[]` | Session IDs starred as favorites | +#### Pane Direction Semantics + +When `launch_mode` is `"pane"`, the `pane_direction` value maps to Windows Terminal's `-H` / `-V` split-pane flags: + +| Direction | WT Flag | Meaning | +|-----------|---------|---------| +| `down` | `-H` | Horizontal split — divider runs horizontally, new pane below | +| `up` | `-H` | Horizontal split — WT controls actual placement (closest available) | +| `right` | `-V` | Vertical split — divider runs vertically, new pane to the right | +| `left` | `-V` | Vertical split — WT controls actual placement (closest available) | +| `auto` | *(none)* | Windows Terminal decides automatically | + +> **Note:** `-H` and `-V` control split *orientation* only (the direction the divider runs). Windows Terminal decides actual pane placement based on available space. + ### Example config.json ```json diff --git a/internal/platform/coverage_test.go b/internal/platform/coverage_test.go index fd3acef4..e1c2155c 100644 --- a/internal/platform/coverage_test.go +++ b/internal/platform/coverage_test.go @@ -155,6 +155,57 @@ func TestLaunchStyleConstants(t *testing.T) { } } +// --------------------------------------------------------------------------- +// appendWTPaneDirFlags +// --------------------------------------------------------------------------- + +func TestAppendWTPaneDirFlags(t *testing.T) { + tests := []struct { + name string + dir string + want []string + }{ + {"down", "down", []string{"-H"}}, + {"up", "up", []string{"-H"}}, + {"right", "right", []string{"-V"}}, + {"left", "left", []string{"-V"}}, + {"auto", "auto", nil}, + {"empty", "", nil}, + {"unknown", "diagonal", nil}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + base := []string{"-w", "0", "split-pane"} + got := appendWTPaneDirFlags(base, tc.dir) + + // base should never be modified in place. + extra := got[len(base):] + if tc.want == nil { + if len(extra) != 0 { + t.Errorf("dir=%q: got extra args %v, want none", tc.dir, extra) + } + return + } + if len(extra) != len(tc.want) { + t.Fatalf("dir=%q: got %v extra args, want %v", tc.dir, extra, tc.want) + } + for i, w := range tc.want { + if extra[i] != w { + t.Errorf("dir=%q: extra[%d]=%q, want %q", tc.dir, i, extra[i], w) + } + } + }) + } +} + +func TestAppendWTPaneDirFlags_PreservesExistingArgs(t *testing.T) { + base := []string{"existing", "args"} + got := appendWTPaneDirFlags(base, "down") + if len(got) != 3 || got[0] != "existing" || got[1] != "args" || got[2] != "-H" { + t.Errorf("got %v, want [existing args -H]", got) + } +} + // --------------------------------------------------------------------------- // TerminalInfo and ShellInfo // --------------------------------------------------------------------------- diff --git a/internal/platform/shell.go b/internal/platform/shell.go index 074fbcd8..a03f67a6 100644 --- a/internal/platform/shell.go +++ b/internal/platform/shell.go @@ -537,6 +537,34 @@ func defaultWindowsShell() ShellInfo { return ShellInfo{Name: "Command Prompt", Path: p} } +// appendWTPaneDirFlags translates a dispatch pane direction string into +// the correct wt.exe split-pane flags. +// +// Windows Terminal's -H and -V flags control the split *orientation* (the +// direction the divider runs), not which side the new pane appears on. +// WT itself decides actual pane placement based on available space. +// +// Mapping: +// +// "down" → -H horizontal split — divider runs horizontally, new pane below +// "up" → -H horizontal split — WT picks closest available placement +// "right" → -V vertical split — divider runs vertically, new pane to the right +// "left" → -V vertical split — WT picks closest available placement +// "auto" → (no flag) WT default behavior +// "" → (no flag) WT default behavior +// unknown → (no flag) WT default behavior +func appendWTPaneDirFlags(args []string, dir string) []string { + switch dir { + case "down", "up": + return append(args, "-H") + case "right", "left": + return append(args, "-V") + default: + // "auto" or empty — let Windows Terminal choose. + return args + } +} + func launchWindowsSession(shell ShellInfo, resumeCmd string, terminal string, cwd string, launchStyle string, paneDirection string) error { // Use Windows Terminal when configured (or defaulted by LaunchSession). if terminal == termWindowsTerminal { @@ -549,9 +577,7 @@ func launchWindowsSession(shell ShellInfo, resumeCmd string, terminal string, cw case LaunchStylePane: // Open a split pane in the current tab. args = append(args, "-w", "0", "split-pane") - if paneDirection != "" && paneDirection != "auto" { - args = append(args, "--direction", paneDirection) - } + args = appendWTPaneDirFlags(args, paneDirection) default: // Open a new tab in the most recently used window. // Without -w 0, wt.exe opens a new window by default. @@ -879,9 +905,7 @@ func buildWSLWTArgs(shell ShellInfo, resumeCmd, winCwd, distro, launchStyle, pan args = append(args, "-w", "new", "new-tab") case LaunchStylePane: args = append(args, "-w", "0", "split-pane") - if paneDirection != "" && paneDirection != "auto" { - args = append(args, "--direction", paneDirection) - } + args = appendWTPaneDirFlags(args, paneDirection) default: args = append(args, "-w", "0", "new-tab") } diff --git a/internal/platform/wsl_test.go b/internal/platform/wsl_test.go index 2b060652..3834bfa4 100644 --- a/internal/platform/wsl_test.go +++ b/internal/platform/wsl_test.go @@ -454,8 +454,11 @@ func TestBuildWSLWTArgs_LaunchStyles(t *testing.T) { {"explicit_tab", LaunchStyleTab, "", []string{"-w", "0", "new-tab"}}, {"window", LaunchStyleWindow, "", []string{"-w", "new", "new-tab"}}, {"pane_auto", LaunchStylePane, "auto", []string{"-w", "0", "split-pane"}}, - {"pane_right", LaunchStylePane, "right", []string{"-w", "0", "split-pane", "--direction", "right"}}, - {"pane_down", LaunchStylePane, "down", []string{"-w", "0", "split-pane", "--direction", "down"}}, + {"pane_right", LaunchStylePane, "right", []string{"-w", "0", "split-pane", "-V"}}, + {"pane_down", LaunchStylePane, "down", []string{"-w", "0", "split-pane", "-H"}}, + {"pane_left", LaunchStylePane, "left", []string{"-w", "0", "split-pane", "-V"}}, + {"pane_up", LaunchStylePane, "up", []string{"-w", "0", "split-pane", "-H"}}, + {"pane_empty_dir", LaunchStylePane, "", []string{"-w", "0", "split-pane"}}, } for _, tc := range tests { diff --git a/web/src/pages/config.astro b/web/src/pages/config.astro index 42820fe8..9fe9c4d9 100644 --- a/web/src/pages/config.astro +++ b/web/src/pages/config.astro @@ -37,7 +37,7 @@ import ConfigTable from '../components/ConfigTable.astro'; { key: 'agent', type: 'string', default: '""', description: 'Pass --agent to Copilot CLI.' }, { key: 'model', type: 'string', default: '""', description: 'Pass --model to Copilot CLI.' }, { key: 'launch_mode', type: 'string', default: '"tab"', description: 'How to open sessions: in-place, tab, window, pane.' }, - { key: 'pane_direction', type: 'string', default: '"auto"', description: 'Split direction for pane mode: auto, right, down, left, up.' }, + { key: 'pane_direction', type: 'string', default: '"auto"', description: 'Split direction for pane mode: auto, right, down, left, up (see Pane Direction Semantics below).' }, { key: 'custom_command', type: 'string', default: '""', description: 'Custom launch command. {sessionId} is replaced with the session ID.' }, { key: 'excluded_dirs', type: 'array', default: '[]', description: 'Directory paths to hide from the session list.' }, { key: 'favoriteSessions', type: 'array', default: '[]', description: 'Session IDs starred as favorites.' }, @@ -113,6 +113,29 @@ import ConfigTable from '../components/ConfigTable.astro';
"custom_command": "my-tool resume {'{sessionId}'}"
+ +
+
+

Pane Direction Semantics

+

+ When launch_mode is "pane", the + pane_direction value maps to Windows Terminal's + -H / -V split-pane flags: +

+
    +
  • down → -H — horizontal split (divider runs horizontally, new pane below)
  • +
  • up → -H — horizontal split (WT controls actual placement; closest available)
  • +
  • right → -V — vertical split (divider runs vertically, new pane to the right)
  • +
  • left → -V — vertical split (WT controls actual placement; closest available)
  • +
  • auto / empty — no flag; Windows Terminal decides automatically
  • +
+

+ Note: -H and -V control + split orientation only (the direction the divider runs). + Windows Terminal decides actual pane placement based on available space. +

+
+