141 lines
3.6 KiB
JavaScript
141 lines
3.6 KiB
JavaScript
import { TerminalView } from './terminal-view.js';
|
|
|
|
export class TabSplitTree {
|
|
constructor(options = {}) {
|
|
this.container = options.container;
|
|
this.tabId = options.tabId;
|
|
this.onActiveTerminalChange = options.onActiveTerminalChange || (() => {});
|
|
this.onEmpty = options.onEmpty || (() => {});
|
|
this.onTitleChange = options.onTitleChange || (() => {});
|
|
|
|
this.terminals = new Map(); // id -> TerminalView
|
|
this.activeTerminal = null;
|
|
|
|
this.rootElement = document.createElement('div');
|
|
this.rootElement.className = 'split-container horizontal';
|
|
this.container.appendChild(this.rootElement);
|
|
|
|
// 创建初始窗格
|
|
this.addPane(this.rootElement);
|
|
}
|
|
|
|
addPane(parentElement, orientation = 'horizontal') {
|
|
const paneElement = document.createElement('div');
|
|
paneElement.className = 'split-pane';
|
|
|
|
parentElement.appendChild(paneElement);
|
|
|
|
const termView = new TerminalView({
|
|
container: paneElement,
|
|
onFocus: (term) => {
|
|
this.setActiveTerminal(term);
|
|
},
|
|
onTitleChange: (title) => {
|
|
if (this.activeTerminal === termView) {
|
|
this.onTitleChange(title);
|
|
}
|
|
},
|
|
onExit: () => {
|
|
this.closeTerminal(termView.id);
|
|
},
|
|
});
|
|
|
|
this.terminals.set(termView.id, {
|
|
termView,
|
|
paneElement,
|
|
parentElement,
|
|
});
|
|
|
|
paneElement.addEventListener('click', () => {
|
|
termView.focus();
|
|
});
|
|
|
|
this.setActiveTerminal(termView);
|
|
return termView;
|
|
}
|
|
|
|
splitCurrent(direction = 'horizontal') {
|
|
if (!this.activeTerminal) return;
|
|
const currentInfo = this.terminals.get(this.activeTerminal.id);
|
|
if (!currentInfo) return;
|
|
|
|
const currentPane = currentInfo.paneElement;
|
|
const parent = currentPane.parentElement;
|
|
|
|
// 创建一个新的 split-container 容器替换原有 pane,实现嵌套分屏
|
|
const splitWrapper = document.createElement('div');
|
|
splitWrapper.className = `split-container ${direction}`;
|
|
|
|
parent.replaceChild(splitWrapper, currentPane);
|
|
splitWrapper.appendChild(currentPane);
|
|
|
|
// 添加分割线
|
|
const divider = document.createElement('div');
|
|
divider.className = 'split-divider';
|
|
splitWrapper.appendChild(divider);
|
|
|
|
// 添加新窗格
|
|
const newTerm = this.addPane(splitWrapper, direction);
|
|
|
|
// 刷新自适应尺寸
|
|
setTimeout(() => {
|
|
this.fitAll();
|
|
newTerm.focus();
|
|
}, 50);
|
|
}
|
|
|
|
setActiveTerminal(termView) {
|
|
this.activeTerminal = termView;
|
|
|
|
// 清除所有聚焦样式
|
|
for (const info of this.terminals.values()) {
|
|
info.paneElement.classList.remove('focused');
|
|
}
|
|
|
|
const currentInfo = this.terminals.get(termView.id);
|
|
if (currentInfo) {
|
|
currentInfo.paneElement.classList.add('focused');
|
|
}
|
|
|
|
this.onActiveTerminalChange(termView);
|
|
this.onTitleChange(termView.title);
|
|
}
|
|
|
|
closeTerminal(termId) {
|
|
const info = this.terminals.get(termId);
|
|
if (!info) return;
|
|
|
|
info.termView.destroy();
|
|
const pane = info.paneElement;
|
|
const parent = pane.parentElement;
|
|
|
|
this.terminals.delete(termId);
|
|
pane.remove();
|
|
|
|
if (this.terminals.size === 0) {
|
|
this.onEmpty();
|
|
return;
|
|
}
|
|
|
|
// 焦点转移给另一个终端
|
|
const nextTerm = this.terminals.values().next().value.termView;
|
|
this.setActiveTerminal(nextTerm);
|
|
nextTerm.focus();
|
|
this.fitAll();
|
|
}
|
|
|
|
fitAll() {
|
|
for (const info of this.terminals.values()) {
|
|
info.termView.fit();
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
for (const info of this.terminals.values()) {
|
|
info.termView.destroy();
|
|
}
|
|
this.terminals.clear();
|
|
this.rootElement.remove();
|
|
}
|
|
}
|