mirror of
https://github.com/stablyai/orca.git
synced 2026-10-14 00:02:42 +00:00
Fix macro splits to use tab groups
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -162,8 +162,8 @@ export function ExperimentalPane({
|
||||
<div className="min-w-0 shrink space-y-1.5">
|
||||
<Label>Terminal Macros</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Define named terminal launch presets that open a new tab, optionally create a split
|
||||
right or down, and seed the new terminal with startup text. This remains
|
||||
Define named terminal launch presets that open a new tab in the current tab group or
|
||||
a new tab split, then seed the new terminal with startup text. This remains
|
||||
experimental and appears in the tab-bar new-tab menu only when enabled.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -21,10 +21,10 @@ type EditorState =
|
||||
|
||||
function layoutLabel(layout: TerminalMacro['layout']): string {
|
||||
if (layout === 'split-right') {
|
||||
return 'Split Right'
|
||||
return 'Tab Split Right'
|
||||
}
|
||||
if (layout === 'split-down') {
|
||||
return 'Split Down'
|
||||
return 'Tab Split Down'
|
||||
}
|
||||
return 'Tab'
|
||||
}
|
||||
@@ -54,7 +54,7 @@ export function TerminalMacrosSection({
|
||||
<div className="space-y-1">
|
||||
<Label>Saved Macros</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Each macro opens a named tab and can seed one startup split.
|
||||
Each macro opens a named tab in the current tab group or a new tab split.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
|
||||
@@ -76,7 +76,7 @@ export function TerminalMacroDialog({
|
||||
{mode === 'edit' ? 'Edit Macro' : 'Add Macro'}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs">
|
||||
Macros open a named terminal tab and optionally create one startup split.
|
||||
Macros open a named terminal tab and can place it in a tab split.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -108,8 +108,8 @@ export function TerminalMacroDialog({
|
||||
className="justify-start"
|
||||
>
|
||||
<ToggleGroupItem value="tab">Tab</ToggleGroupItem>
|
||||
<ToggleGroupItem value="split-right">Split Right</ToggleGroupItem>
|
||||
<ToggleGroupItem value="split-down">Split Down</ToggleGroupItem>
|
||||
<ToggleGroupItem value="split-right">Tab Split Right</ToggleGroupItem>
|
||||
<ToggleGroupItem value="split-down">Tab Split Down</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
|
||||
@@ -124,24 +124,15 @@ export function TerminalMacroDialog({
|
||||
rows={5}
|
||||
className="min-h-28 w-full resize-y rounded-md border border-input bg-transparent px-3 py-2 text-sm font-mono shadow-xs outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
||||
/>
|
||||
{targetsSplitPane ? (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
This startup text runs only in the newly created split pane. The original pane is
|
||||
left alone.
|
||||
</p>
|
||||
) : (
|
||||
{!targetsSplitPane && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Optional startup text for the new tab.
|
||||
</p>
|
||||
)}
|
||||
<MacroAppendEnterToggle
|
||||
checked={draft.appendEnter !== false}
|
||||
label={targetsSplitPane ? 'Run new pane startup text' : 'Run tab startup text'}
|
||||
description={
|
||||
targetsSplitPane
|
||||
? 'Press Enter after sending the new split pane text.'
|
||||
: 'Press Enter after sending the tab startup text.'
|
||||
}
|
||||
label="Run startup command"
|
||||
description="Press Enter after this text is in the terminal."
|
||||
onToggle={() =>
|
||||
setDraft((current) => ({ ...current, appendEnter: !current.appendEnter }))
|
||||
}
|
||||
|
||||
@@ -2,11 +2,18 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const state = {
|
||||
createTab: vi.fn(),
|
||||
createEmptySplitGroup: vi.fn(),
|
||||
setTabCustomTitle: vi.fn(),
|
||||
queueTabStartupCommand: vi.fn(),
|
||||
queueTabSetupSplit: vi.fn(),
|
||||
setActiveTabType: vi.fn(),
|
||||
setTabBarOrder: vi.fn(),
|
||||
activeGroupIdByWorktree: {
|
||||
'wt-1': 'group-1'
|
||||
},
|
||||
groupsByWorktree: {
|
||||
'wt-1': [{ id: 'group-1' }]
|
||||
},
|
||||
tabsByWorktree: {
|
||||
'wt-1': [{ id: 'existing-tab' }, { id: 'tab-1' }]
|
||||
},
|
||||
@@ -29,6 +36,7 @@ describe('launchTerminalMacro', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
state.createTab.mockReturnValue({ id: 'tab-1', title: 'Terminal 1' })
|
||||
state.createEmptySplitGroup.mockReturnValue('group-2')
|
||||
})
|
||||
|
||||
it('creates a named tab and queues startup for tab macros', async () => {
|
||||
@@ -62,7 +70,7 @@ describe('launchTerminalMacro', () => {
|
||||
expect(result).toEqual({ tabId: 'tab-1' })
|
||||
})
|
||||
|
||||
it('queues an initial split and sends startup only to the new split pane', async () => {
|
||||
it('creates a tab group split and queues startup on the new tab', async () => {
|
||||
const { launchTerminalMacro } = await import('./launch-terminal-macro')
|
||||
|
||||
launchTerminalMacro({
|
||||
@@ -76,11 +84,12 @@ describe('launchTerminalMacro', () => {
|
||||
worktreeId: 'wt-1'
|
||||
})
|
||||
|
||||
expect(state.queueTabStartupCommand).not.toHaveBeenCalled()
|
||||
expect(state.queueTabSetupSplit).toHaveBeenCalledWith('tab-1', {
|
||||
direction: 'vertical',
|
||||
expect(state.createEmptySplitGroup).toHaveBeenCalledWith('wt-1', 'group-1', 'right')
|
||||
expect(state.createTab).toHaveBeenCalledWith('wt-1', 'group-2')
|
||||
expect(state.queueTabStartupCommand).toHaveBeenCalledWith('tab-1', {
|
||||
command: 'npm run dev\r'
|
||||
})
|
||||
expect(state.queueTabSetupSplit).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('returns null for blank names', async () => {
|
||||
|
||||
@@ -20,27 +20,25 @@ export function launchTerminalMacro({
|
||||
return null
|
||||
}
|
||||
|
||||
const tab = store.createTab(worktreeId, groupId)
|
||||
const targetGroupId =
|
||||
macro.layout === 'split-right' || macro.layout === 'split-down'
|
||||
? resolveTerminalMacroSplitGroupId({
|
||||
worktreeId,
|
||||
groupId,
|
||||
direction: macro.layout === 'split-right' ? 'right' : 'down'
|
||||
})
|
||||
: groupId
|
||||
|
||||
const tab = store.createTab(worktreeId, targetGroupId)
|
||||
store.setTabCustomTitle(tab.id, name)
|
||||
|
||||
const command = macro.command.trimEnd()
|
||||
if (command && macro.layout === 'tab') {
|
||||
if (command) {
|
||||
store.queueTabStartupCommand(tab.id, {
|
||||
command: buildTerminalMacroInput(command, macro.appendEnter !== false)
|
||||
})
|
||||
}
|
||||
|
||||
if (macro.layout === 'split-right' || macro.layout === 'split-down') {
|
||||
store.queueTabSetupSplit(tab.id, {
|
||||
direction: macro.layout === 'split-right' ? 'vertical' : 'horizontal',
|
||||
...(command
|
||||
? {
|
||||
command: buildTerminalMacroInput(command, macro.appendEnter !== false)
|
||||
}
|
||||
: {})
|
||||
})
|
||||
}
|
||||
|
||||
// Why: macro launches should surface the new terminal immediately even when
|
||||
// the user currently has an editor/browser active in the same worktree.
|
||||
store.setActiveTabType('terminal')
|
||||
@@ -63,3 +61,26 @@ export function launchTerminalMacro({
|
||||
|
||||
return { tabId: tab.id }
|
||||
}
|
||||
|
||||
function resolveTerminalMacroSplitGroupId({
|
||||
worktreeId,
|
||||
groupId,
|
||||
direction
|
||||
}: {
|
||||
worktreeId: string
|
||||
groupId?: string
|
||||
direction: 'right' | 'down'
|
||||
}): string | undefined {
|
||||
const store = useAppStore.getState()
|
||||
const sourceGroupId =
|
||||
groupId ??
|
||||
store.activeGroupIdByWorktree[worktreeId] ??
|
||||
store.groupsByWorktree[worktreeId]?.[0]?.id
|
||||
if (!sourceGroupId) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Why: macro "split right/down" means an Orca tab-group split, not an
|
||||
// xterm pane split. Keeping it here avoids remounting the source terminal.
|
||||
return store.createEmptySplitGroup(worktreeId, sourceGroupId, direction) ?? undefined
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user