fix(tmux): prefer split-or-defer with FIFO deferred attach
This commit is contained in:
@@ -1,13 +1,40 @@
|
||||
import type { SplitDirection, TmuxPaneInfo, WindowState } from "./types"
|
||||
import type { CapacityConfig, SplitDirection, TmuxPaneInfo, WindowState } from "./types"
|
||||
import { computeMainPaneWidth } from "./tmux-grid-constants"
|
||||
import { computeGridPlan, mapPaneToSlot } from "./grid-planning"
|
||||
import { canSplitPane, getBestSplitDirection } from "./pane-split-availability"
|
||||
import { MIN_PANE_WIDTH } from "./types"
|
||||
import { canSplitPane } from "./pane-split-availability"
|
||||
|
||||
export interface SpawnTarget {
|
||||
targetPaneId: string
|
||||
splitDirection: SplitDirection
|
||||
}
|
||||
|
||||
function isStrictMainVertical(config: CapacityConfig): boolean {
|
||||
return config.layout === "main-vertical"
|
||||
}
|
||||
|
||||
function isStrictMainHorizontal(config: CapacityConfig): boolean {
|
||||
return config.layout === "main-horizontal"
|
||||
}
|
||||
|
||||
function isStrictMainLayout(config: CapacityConfig): boolean {
|
||||
return isStrictMainVertical(config) || isStrictMainHorizontal(config)
|
||||
}
|
||||
|
||||
function getInitialSplitDirection(config: CapacityConfig): SplitDirection {
|
||||
return isStrictMainHorizontal(config) ? "-v" : "-h"
|
||||
}
|
||||
|
||||
function getStrictFollowupSplitDirection(config: CapacityConfig): SplitDirection {
|
||||
return isStrictMainHorizontal(config) ? "-h" : "-v"
|
||||
}
|
||||
|
||||
function sortPanesForStrictLayout(panes: TmuxPaneInfo[], config: CapacityConfig): TmuxPaneInfo[] {
|
||||
if (isStrictMainHorizontal(config)) {
|
||||
return [...panes].sort((a, b) => a.left - b.left || a.top - b.top)
|
||||
}
|
||||
return [...panes].sort((a, b) => a.top - b.top || a.left - b.left)
|
||||
}
|
||||
|
||||
function buildOccupancy(
|
||||
agentPanes: TmuxPaneInfo[],
|
||||
plan: ReturnType<typeof computeGridPlan>,
|
||||
@@ -37,16 +64,29 @@ function findFirstEmptySlot(
|
||||
|
||||
function findSplittableTarget(
|
||||
state: WindowState,
|
||||
minPaneWidth: number,
|
||||
config: CapacityConfig,
|
||||
_preferredDirection?: SplitDirection,
|
||||
): SpawnTarget | null {
|
||||
if (!state.mainPane) return null
|
||||
const existingCount = state.agentPanes.length
|
||||
const minAgentPaneWidth = config.agentPaneWidth
|
||||
const initialDirection = getInitialSplitDirection(config)
|
||||
|
||||
if (existingCount === 0) {
|
||||
const virtualMainPane: TmuxPaneInfo = { ...state.mainPane, width: state.windowWidth }
|
||||
if (canSplitPane(virtualMainPane, "-h", minPaneWidth)) {
|
||||
return { targetPaneId: state.mainPane.paneId, splitDirection: "-h" }
|
||||
if (canSplitPane(virtualMainPane, initialDirection, minAgentPaneWidth)) {
|
||||
return { targetPaneId: state.mainPane.paneId, splitDirection: initialDirection }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
if (isStrictMainLayout(config)) {
|
||||
const followupDirection = getStrictFollowupSplitDirection(config)
|
||||
const panesByPriority = sortPanesForStrictLayout(state.agentPanes, config)
|
||||
for (const pane of panesByPriority) {
|
||||
if (canSplitPane(pane, followupDirection, minAgentPaneWidth)) {
|
||||
return { targetPaneId: pane.paneId, splitDirection: followupDirection }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -55,34 +95,44 @@ function findSplittableTarget(
|
||||
state.windowWidth,
|
||||
state.windowHeight,
|
||||
existingCount + 1,
|
||||
state.mainPane.width,
|
||||
minPaneWidth,
|
||||
config,
|
||||
)
|
||||
const mainPaneWidth = state.mainPane.width
|
||||
const mainPaneWidth = computeMainPaneWidth(state.windowWidth, config)
|
||||
const occupancy = buildOccupancy(state.agentPanes, plan, mainPaneWidth)
|
||||
const targetSlot = findFirstEmptySlot(occupancy, plan)
|
||||
|
||||
const leftPane = occupancy.get(`${targetSlot.row}:${targetSlot.col - 1}`)
|
||||
if (leftPane && canSplitPane(leftPane, "-h", minPaneWidth)) {
|
||||
if (
|
||||
!isStrictMainVertical(config) &&
|
||||
leftPane &&
|
||||
canSplitPane(leftPane, "-h", minAgentPaneWidth)
|
||||
) {
|
||||
return { targetPaneId: leftPane.paneId, splitDirection: "-h" }
|
||||
}
|
||||
|
||||
const abovePane = occupancy.get(`${targetSlot.row - 1}:${targetSlot.col}`)
|
||||
if (abovePane && canSplitPane(abovePane, "-v", minPaneWidth)) {
|
||||
if (abovePane && canSplitPane(abovePane, "-v", minAgentPaneWidth)) {
|
||||
return { targetPaneId: abovePane.paneId, splitDirection: "-v" }
|
||||
}
|
||||
|
||||
const splittablePanes = state.agentPanes
|
||||
.map((pane) => ({ pane, direction: getBestSplitDirection(pane, minPaneWidth) }))
|
||||
.filter(
|
||||
(item): item is { pane: TmuxPaneInfo; direction: SplitDirection } =>
|
||||
item.direction !== null,
|
||||
)
|
||||
.sort((a, b) => b.pane.width * b.pane.height - a.pane.width * a.pane.height)
|
||||
const panesByPosition = [...state.agentPanes].sort(
|
||||
(a, b) => a.left - b.left || a.top - b.top,
|
||||
)
|
||||
|
||||
const best = splittablePanes[0]
|
||||
if (best) {
|
||||
return { targetPaneId: best.pane.paneId, splitDirection: best.direction }
|
||||
for (const pane of panesByPosition) {
|
||||
if (canSplitPane(pane, "-v", minAgentPaneWidth)) {
|
||||
return { targetPaneId: pane.paneId, splitDirection: "-v" }
|
||||
}
|
||||
}
|
||||
|
||||
if (isStrictMainVertical(config)) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (const pane of panesByPosition) {
|
||||
if (canSplitPane(pane, "-h", minAgentPaneWidth)) {
|
||||
return { targetPaneId: pane.paneId, splitDirection: "-h" }
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
@@ -90,7 +140,7 @@ function findSplittableTarget(
|
||||
|
||||
export function findSpawnTarget(
|
||||
state: WindowState,
|
||||
minPaneWidth: number = MIN_PANE_WIDTH,
|
||||
config: CapacityConfig,
|
||||
): SpawnTarget | null {
|
||||
return findSplittableTarget(state, minPaneWidth)
|
||||
return findSplittableTarget(state, config)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user