vde-layout
Version:
Terminal multiplexer layout management tool for VDE (Vibe Coding Development Environment)
174 lines • 7.03 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LayoutEngine = void 0;
const executor_1 = require("../tmux/executor");
const commands_1 = require("../tmux/commands");
const ratio_1 = require("../utils/ratio");
class LayoutEngine {
executor;
commandGenerator;
constructor(options = {}) {
if (options.executor && "verifyTmuxEnvironment" in options.executor) {
this.executor = options.executor;
}
else if (options.executor) {
const executorOptions = {
verbose: options.verbose,
dryRun: options.dryRun,
executor: options.executor,
};
this.executor = new executor_1.TmuxExecutor(executorOptions);
}
else {
const executorOptions = {
verbose: options.verbose,
dryRun: options.dryRun,
};
this.executor = new executor_1.TmuxExecutor(executorOptions);
}
this.commandGenerator = options.commandGenerator || new commands_1.TmuxCommandGenerator();
}
async createLayout(preset) {
await this.executor.verifyTmuxEnvironment();
const windowName = preset.name || "vde-layout";
await this.executor.execute(this.commandGenerator.newWindow(windowName));
const initialPaneId = await this.getCurrentPaneId();
let paneInfos;
if (preset.layout) {
paneInfos = await this.createLayoutRecursive(preset.layout, initialPaneId);
}
else {
const terminalPane = {
name: preset.name,
command: preset.command,
};
paneInfos = [
{
pane: terminalPane,
paneId: initialPaneId,
},
];
}
for (const paneInfo of paneInfos) {
await this.applyPaneOptions(paneInfo);
}
const focusedPane = paneInfos.find((info) => {
const terminalPane = info.pane;
return terminalPane.focus === true;
});
if (focusedPane !== undefined) {
await this.executor.execute(this.commandGenerator.selectPane(focusedPane.paneId));
}
}
async createLayoutRecursive(layout, parentPaneId, _isFirst = true) {
const isLayout = (l) => {
return (typeof l === "object" &&
l !== null &&
"type" in l &&
"panes" in l &&
"ratio" in l &&
(l.type === "horizontal" || l.type === "vertical"));
};
if (!isLayout(layout)) {
return [
{
pane: layout,
paneId: parentPaneId,
},
];
}
const paneInfos = [];
const currentPaneId = parentPaneId;
let paneIndex = 0;
if (parentPaneId && parentPaneId.startsWith("%")) {
paneIndex = parseInt(parentPaneId.slice(1), 10) || 0;
}
const normalizedRatio = (0, ratio_1.normalizeRatio)(layout.ratio ?? []);
const paneIds = [parentPaneId];
for (let i = 1; i < layout.panes.length; i++) {
let percentage;
if (i === 1) {
const firstPaneRatio = normalizedRatio[0];
const totalRatio = normalizedRatio.reduce((sum, r) => sum + r, 0);
const remainingRatiosSum = totalRatio - firstPaneRatio;
percentage = Math.round((remainingRatiosSum / totalRatio) * 100);
}
else {
const currentSpaceRatios = normalizedRatio.slice(i - 1);
const currentSpaceTotal = currentSpaceRatios.reduce((sum, r) => sum + r, 0);
const newPaneRatios = normalizedRatio.slice(i);
const newPaneTotal = newPaneRatios.reduce((sum, r) => sum + r, 0);
percentage = Math.round((newPaneTotal / currentSpaceTotal) * 100);
}
const beforePaneIds = await this.getAllPaneIds();
const targetPaneId = i === 1 ? currentPaneId : paneIds[paneIds.length - 1];
await this.executor.execute(this.commandGenerator.splitWindow(layout.type, targetPaneId, percentage));
const afterPaneIds = await this.getAllPaneIds();
const newPaneId = afterPaneIds.find((id) => !beforePaneIds.includes(id));
if (newPaneId === undefined) {
const fallbackPaneId = `%${paneIndex + i}`;
paneIds.push(fallbackPaneId);
}
else {
paneIds.push(newPaneId);
}
paneIndex++;
}
for (let i = 0; i < layout.panes.length; i++) {
const pane = layout.panes[i];
const paneId = paneIds[i];
const childPaneInfos = await this.createLayoutRecursive(pane, paneId, false);
for (const childInfo of childPaneInfos) {
if (childInfo.paneId && childInfo.paneId.startsWith("%")) {
const childIndex = parseInt(childInfo.paneId.slice(1), 10);
if (!isNaN(childIndex) && childIndex > paneIndex) {
paneIndex = childIndex;
}
}
}
paneInfos.push(...childPaneInfos);
}
return paneInfos;
}
async applyPaneOptions(paneInfo) {
const pane = paneInfo.pane;
const paneId = paneInfo.paneId;
if (pane.cwd !== undefined) {
await this.executor.execute(this.commandGenerator.changeDirectory(paneId, pane.cwd));
}
if (pane.env !== undefined) {
const envCommands = this.commandGenerator.setEnvironment(paneId, pane.env);
for (const command of envCommands) {
await this.executor.execute(command);
}
}
if (pane.name !== undefined) {
await this.executor.execute(this.commandGenerator.setPaneTitle(paneId, pane.name));
}
if (pane.delay !== undefined && pane.delay > 0) {
await new Promise((resolve) => setTimeout(resolve, pane.delay));
}
if (pane.command !== undefined) {
await this.executor.execute(this.commandGenerator.sendKeys(paneId, pane.command));
}
}
async getCurrentPaneId() {
const result = await this.executor.execute(["display-message", "-p", "#{pane_id}"]);
if (!result || typeof result !== "string") {
return "%0";
}
return result.trim() || "%0";
}
async getAllPaneIds() {
const result = await this.executor.execute(["list-panes", "-F", "#{pane_id}"]);
if (!result || typeof result !== "string") {
return ["%0"];
}
return result
.trim()
.split("\n")
.filter((id) => id.trim() !== "");
}
}
exports.LayoutEngine = LayoutEngine;
//# sourceMappingURL=engine.js.map