138 lines
3.3 KiB
TypeScript

import { MIN_PANE_HEIGHT, MIN_PANE_WIDTH } from "./types"
import type { CapacityConfig, TmuxPaneInfo } from "./types"
import {
DIVIDER_SIZE,
MAX_GRID_SIZE,
computeAgentAreaWidth,
} from "./tmux-grid-constants"
export interface GridCapacity {
cols: number
rows: number
total: number
}
export interface GridSlot {
row: number
col: number
}
export interface GridPlan {
cols: number
rows: number
slotWidth: number
slotHeight: number
}
type CapacityOptions = CapacityConfig | number | undefined
function resolveMinPaneWidth(options?: CapacityOptions): number {
if (typeof options === "number") {
return Math.max(1, options)
}
if (options && typeof options.agentPaneWidth === "number") {
return Math.max(1, options.agentPaneWidth)
}
return MIN_PANE_WIDTH
}
function resolveAgentAreaWidth(windowWidth: number, options?: CapacityOptions): number {
if (typeof options === "number") {
return computeAgentAreaWidth(windowWidth)
}
return computeAgentAreaWidth(windowWidth, options)
}
export function calculateCapacity(
windowWidth: number,
windowHeight: number,
options?: CapacityOptions,
mainPaneWidth?: number,
): GridCapacity {
const availableWidth =
typeof mainPaneWidth === "number"
? Math.max(0, windowWidth - mainPaneWidth - DIVIDER_SIZE)
: resolveAgentAreaWidth(windowWidth, options)
const minPaneWidth = resolveMinPaneWidth(options)
const cols = Math.min(
MAX_GRID_SIZE,
Math.max(
0,
Math.floor(
(availableWidth + DIVIDER_SIZE) / (minPaneWidth + DIVIDER_SIZE),
),
),
)
const rows = Math.min(
MAX_GRID_SIZE,
Math.max(
0,
Math.floor(
(windowHeight + DIVIDER_SIZE) / (MIN_PANE_HEIGHT + DIVIDER_SIZE),
),
),
)
return { cols, rows, total: cols * rows }
}
export function computeGridPlan(
windowWidth: number,
windowHeight: number,
paneCount: number,
options?: CapacityOptions,
mainPaneWidth?: number,
): GridPlan {
const capacity = calculateCapacity(windowWidth, windowHeight, options, mainPaneWidth)
const { cols: maxCols, rows: maxRows } = capacity
if (maxCols === 0 || maxRows === 0 || paneCount === 0) {
return { cols: 1, rows: 1, slotWidth: 0, slotHeight: 0 }
}
let bestCols = 1
let bestRows = 1
let bestArea = Infinity
for (let rows = 1; rows <= maxRows; rows++) {
for (let cols = 1; cols <= maxCols; cols++) {
if (cols * rows < paneCount) continue
const area = cols * rows
if (area < bestArea || (area === bestArea && rows < bestRows)) {
bestCols = cols
bestRows = rows
bestArea = area
}
}
}
const availableWidth =
typeof mainPaneWidth === "number"
? Math.max(0, windowWidth - mainPaneWidth - DIVIDER_SIZE)
: resolveAgentAreaWidth(windowWidth, options)
const slotWidth = Math.floor(availableWidth / bestCols)
const slotHeight = Math.floor(windowHeight / bestRows)
return { cols: bestCols, rows: bestRows, slotWidth, slotHeight }
}
export function mapPaneToSlot(
pane: TmuxPaneInfo,
plan: GridPlan,
mainPaneWidth: number,
): GridSlot {
const rightAreaX = mainPaneWidth
const relativeX = Math.max(0, pane.left - rightAreaX)
const relativeY = pane.top
const col =
plan.slotWidth > 0
? Math.min(plan.cols - 1, Math.floor(relativeX / plan.slotWidth))
: 0
const row =
plan.slotHeight > 0
? Math.min(plan.rows - 1, Math.floor(relativeY / plan.slotHeight))
: 0
return { row, col }
}