@termaxjs/web-core 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/block/blockRange.d.ts +11 -0
- package/dist/block/blockRange.d.ts.map +1 -0
- package/dist/block/blockRange.js +16 -0
- package/dist/block/blockRange.js.map +1 -0
- package/dist/block/modeMachine.d.ts +18 -0
- package/dist/block/modeMachine.d.ts.map +1 -0
- package/dist/block/modeMachine.js +21 -0
- package/dist/block/modeMachine.js.map +1 -0
- package/dist/cursorBlink.d.ts +2 -0
- package/dist/cursorBlink.d.ts.map +1 -0
- package/dist/cursorBlink.js +4 -0
- package/dist/cursorBlink.js.map +1 -0
- package/dist/explorer/HeadlessFileTree.d.ts +27 -0
- package/dist/explorer/HeadlessFileTree.d.ts.map +1 -0
- package/dist/explorer/HeadlessFileTree.js +147 -0
- package/dist/explorer/HeadlessFileTree.js.map +1 -0
- package/dist/explorer/expansionCache.d.ts +4 -0
- package/dist/explorer/expansionCache.d.ts.map +1 -0
- package/dist/explorer/expansionCache.js +26 -0
- package/dist/explorer/expansionCache.js.map +1 -0
- package/dist/explorer/index.d.ts +5 -0
- package/dist/explorer/index.d.ts.map +1 -0
- package/dist/explorer/index.js +5 -0
- package/dist/explorer/index.js.map +1 -0
- package/dist/explorer/pathUtils.d.ts +5 -0
- package/dist/explorer/pathUtils.d.ts.map +1 -0
- package/dist/explorer/pathUtils.js +19 -0
- package/dist/explorer/pathUtils.js.map +1 -0
- package/dist/explorer/types.d.ts +65 -0
- package/dist/explorer/types.d.ts.map +1 -0
- package/dist/explorer/types.js +2 -0
- package/dist/explorer/types.js.map +1 -0
- package/dist/git/HeadlessGitGraph.d.ts +39 -0
- package/dist/git/HeadlessGitGraph.d.ts.map +1 -0
- package/dist/git/HeadlessGitGraph.js +81 -0
- package/dist/git/HeadlessGitGraph.js.map +1 -0
- package/dist/images/KittyGraphicsParser.d.ts +24 -0
- package/dist/images/KittyGraphicsParser.d.ts.map +1 -0
- package/dist/images/KittyGraphicsParser.js +87 -0
- package/dist/images/KittyGraphicsParser.js.map +1 -0
- package/dist/images/SixelDecoder.d.ts +9 -0
- package/dist/images/SixelDecoder.d.ts.map +1 -0
- package/dist/images/SixelDecoder.js +110 -0
- package/dist/images/SixelDecoder.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/keymap.d.ts +9 -0
- package/dist/keymap.d.ts.map +1 -0
- package/dist/keymap.js +62 -0
- package/dist/keymap.js.map +1 -0
- package/dist/layout/HeadlessFloatingManager.d.ts +18 -0
- package/dist/layout/HeadlessFloatingManager.d.ts.map +1 -0
- package/dist/layout/HeadlessFloatingManager.js +41 -0
- package/dist/layout/HeadlessFloatingManager.js.map +1 -0
- package/dist/layout/HeadlessPaneTree.d.ts +27 -0
- package/dist/layout/HeadlessPaneTree.d.ts.map +1 -0
- package/dist/layout/HeadlessPaneTree.js +117 -0
- package/dist/layout/HeadlessPaneTree.js.map +1 -0
- package/dist/layout/PaneGeometry.d.ts +17 -0
- package/dist/layout/PaneGeometry.d.ts.map +1 -0
- package/dist/layout/PaneGeometry.js +27 -0
- package/dist/layout/PaneGeometry.js.map +1 -0
- package/dist/layout/index.d.ts +9 -0
- package/dist/layout/index.d.ts.map +1 -0
- package/dist/layout/index.js +12 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/layout/paneTree.d.ts +39 -0
- package/dist/layout/paneTree.d.ts.map +1 -0
- package/dist/layout/paneTree.js +215 -0
- package/dist/layout/paneTree.js.map +1 -0
- package/dist/layout/storeUtils.d.ts +23 -0
- package/dist/layout/storeUtils.d.ts.map +1 -0
- package/dist/layout/storeUtils.js +9 -0
- package/dist/layout/storeUtils.js.map +1 -0
- package/dist/layout/tabsActions.d.ts +72 -0
- package/dist/layout/tabsActions.d.ts.map +1 -0
- package/dist/layout/tabsActions.js +975 -0
- package/dist/layout/tabsActions.js.map +1 -0
- package/dist/layout/tabsStore.d.ts +43 -0
- package/dist/layout/tabsStore.d.ts.map +1 -0
- package/dist/layout/tabsStore.js +69 -0
- package/dist/layout/tabsStore.js.map +1 -0
- package/dist/layout/types.d.ts +114 -0
- package/dist/layout/types.d.ts.map +1 -0
- package/dist/layout/types.js +2 -0
- package/dist/layout/types.js.map +1 -0
- package/dist/layout/useTabs.d.ts +55 -0
- package/dist/layout/useTabs.d.ts.map +1 -0
- package/dist/layout/useTabs.js +116 -0
- package/dist/layout/useTabs.js.map +1 -0
- package/dist/lsp/client.d.ts +16 -0
- package/dist/lsp/client.d.ts.map +1 -0
- package/dist/lsp/client.js +125 -0
- package/dist/lsp/client.js.map +1 -0
- package/dist/lsp/index.d.ts +3 -0
- package/dist/lsp/index.d.ts.map +1 -0
- package/dist/lsp/index.js +3 -0
- package/dist/lsp/index.js.map +1 -0
- package/dist/lsp/types.d.ts +44 -0
- package/dist/lsp/types.d.ts.map +1 -0
- package/dist/lsp/types.js +2 -0
- package/dist/lsp/types.js.map +1 -0
- package/dist/palette/HeadlessCommandPalette.d.ts +21 -0
- package/dist/palette/HeadlessCommandPalette.d.ts.map +1 -0
- package/dist/palette/HeadlessCommandPalette.js +48 -0
- package/dist/palette/HeadlessCommandPalette.js.map +1 -0
- package/dist/parser/VtEngine.d.ts +87 -0
- package/dist/parser/VtEngine.d.ts.map +1 -0
- package/dist/parser/VtEngine.js +1206 -0
- package/dist/parser/VtEngine.js.map +1 -0
- package/dist/parser/index.d.ts +2 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +2 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/protocol.d.ts +3 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +94 -0
- package/dist/protocol.js.map +1 -0
- package/dist/settings/EditorSchema.d.ts +3 -0
- package/dist/settings/EditorSchema.d.ts.map +1 -0
- package/dist/settings/EditorSchema.js +78 -0
- package/dist/settings/EditorSchema.js.map +1 -0
- package/dist/settings/GeneralSchema.d.ts +3 -0
- package/dist/settings/GeneralSchema.d.ts.map +1 -0
- package/dist/settings/GeneralSchema.js +87 -0
- package/dist/settings/GeneralSchema.js.map +1 -0
- package/dist/settings/TerminalSchema.d.ts +3 -0
- package/dist/settings/TerminalSchema.d.ts.map +1 -0
- package/dist/settings/TerminalSchema.js +196 -0
- package/dist/settings/TerminalSchema.js.map +1 -0
- package/dist/settings/UiCustomizationSchema.d.ts +3 -0
- package/dist/settings/UiCustomizationSchema.d.ts.map +1 -0
- package/dist/settings/UiCustomizationSchema.js +85 -0
- package/dist/settings/UiCustomizationSchema.js.map +1 -0
- package/dist/settings/constants.d.ts +6 -0
- package/dist/settings/constants.d.ts.map +1 -0
- package/dist/settings/constants.js +27 -0
- package/dist/settings/constants.js.map +1 -0
- package/dist/settings/index.d.ts +7 -0
- package/dist/settings/index.d.ts.map +1 -0
- package/dist/settings/index.js +7 -0
- package/dist/settings/index.js.map +1 -0
- package/dist/settings/types.d.ts +34 -0
- package/dist/settings/types.d.ts.map +1 -0
- package/dist/settings/types.js +2 -0
- package/dist/settings/types.js.map +1 -0
- package/dist/spaces/HeadlessSpaces.d.ts +31 -0
- package/dist/spaces/HeadlessSpaces.d.ts.map +1 -0
- package/dist/spaces/HeadlessSpaces.js +48 -0
- package/dist/spaces/HeadlessSpaces.js.map +1 -0
- package/dist/theme/applyTheme.d.ts +4 -0
- package/dist/theme/applyTheme.d.ts.map +1 -0
- package/dist/theme/applyTheme.js +130 -0
- package/dist/theme/applyTheme.js.map +1 -0
- package/dist/theme/index.d.ts +3 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +3 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/validateTheme.d.ts +10 -0
- package/dist/theme/validateTheme.d.ts.map +1 -0
- package/dist/theme/validateTheme.js +158 -0
- package/dist/theme/validateTheme.js.map +1 -0
- package/dist/theme.d.ts +12 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +32 -0
- package/dist/theme.js.map +1 -0
- package/dist/themes/caffeine.d.ts +3 -0
- package/dist/themes/caffeine.d.ts.map +1 -0
- package/dist/themes/caffeine.js +121 -0
- package/dist/themes/caffeine.js.map +1 -0
- package/dist/themes/catppuccin.d.ts +3 -0
- package/dist/themes/catppuccin.d.ts.map +1 -0
- package/dist/themes/catppuccin.js +119 -0
- package/dist/themes/catppuccin.js.map +1 -0
- package/dist/themes/claude.d.ts +3 -0
- package/dist/themes/claude.d.ts.map +1 -0
- package/dist/themes/claude.js +121 -0
- package/dist/themes/claude.js.map +1 -0
- package/dist/themes/gruvbox.d.ts +3 -0
- package/dist/themes/gruvbox.d.ts.map +1 -0
- package/dist/themes/gruvbox.js +119 -0
- package/dist/themes/gruvbox.js.map +1 -0
- package/dist/themes/index.d.ts +16 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +45 -0
- package/dist/themes/index.js.map +1 -0
- package/dist/themes/nord.d.ts +3 -0
- package/dist/themes/nord.d.ts.map +1 -0
- package/dist/themes/nord.js +103 -0
- package/dist/themes/nord.js.map +1 -0
- package/dist/themes/rose-pine.d.ts +3 -0
- package/dist/themes/rose-pine.d.ts.map +1 -0
- package/dist/themes/rose-pine.js +119 -0
- package/dist/themes/rose-pine.js.map +1 -0
- package/dist/themes/sage.d.ts +3 -0
- package/dist/themes/sage.d.ts.map +1 -0
- package/dist/themes/sage.js +121 -0
- package/dist/themes/sage.js.map +1 -0
- package/dist/themes/termax-default.d.ts +3 -0
- package/dist/themes/termax-default.d.ts.map +1 -0
- package/dist/themes/termax-default.js +119 -0
- package/dist/themes/termax-default.js.map +1 -0
- package/dist/themes/tide.d.ts +3 -0
- package/dist/themes/tide.d.ts.map +1 -0
- package/dist/themes/tide.js +121 -0
- package/dist/themes/tide.js.map +1 -0
- package/dist/themes/tokyo-night.d.ts +3 -0
- package/dist/themes/tokyo-night.d.ts.map +1 -0
- package/dist/themes/tokyo-night.js +64 -0
- package/dist/themes/tokyo-night.js.map +1 -0
- package/dist/themes/types.d.ts +75 -0
- package/dist/themes/types.d.ts.map +1 -0
- package/dist/themes/types.js +2 -0
- package/dist/themes/types.js.map +1 -0
- package/dist/types.d.ts +217 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/shellQuote.d.ts +3 -0
- package/dist/utils/shellQuote.d.ts.map +1 -0
- package/dist/utils/shellQuote.js +12 -0
- package/dist/utils/shellQuote.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,975 @@
|
|
|
1
|
+
export let layoutAdapter = { disposeSession: () => { } };
|
|
2
|
+
export const setLayoutAdapter = (a) => layoutAdapter = a;
|
|
3
|
+
import { findLeafCwd, hasLeaf, leafIds, moveLeaf, nextLeafId, removeLeaf, setLeafCwd as setLeafCwdInTree, siblingLeafOf, splitLeaf, swapLeaves, } from "./paneTree.js";
|
|
4
|
+
import { MAX_PANES_PER_TAB, nextActiveInSpace } from "./useTabs.js";
|
|
5
|
+
import { useTabsStore } from "./tabsStore.js";
|
|
6
|
+
function basename(path) {
|
|
7
|
+
const parts = path.split(/[\\/]/).filter(Boolean);
|
|
8
|
+
return parts.length ? parts[parts.length - 1] : path;
|
|
9
|
+
}
|
|
10
|
+
function titleFromUrl(url) {
|
|
11
|
+
try {
|
|
12
|
+
const u = new URL(url);
|
|
13
|
+
return u.host || url;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return url || "preview";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function splitActivePane(tabId, dir) {
|
|
20
|
+
let newLeafId = null;
|
|
21
|
+
useTabsStore.getState().setTabs((curr) => curr.map((t) => {
|
|
22
|
+
if (t.id !== tabId || t.kind !== "terminal" || t.blocks)
|
|
23
|
+
return t;
|
|
24
|
+
if (leafIds(t.paneTree).length >= MAX_PANES_PER_TAB)
|
|
25
|
+
return t;
|
|
26
|
+
const splitId = useTabsStore.getState().allocId();
|
|
27
|
+
const leafId = useTabsStore.getState().allocId();
|
|
28
|
+
newLeafId = leafId;
|
|
29
|
+
const paneTree = splitLeaf(t.paneTree, t.activeLeafId, splitId, leafId, dir, t.cwd);
|
|
30
|
+
return { ...t, paneTree, activeLeafId: leafId };
|
|
31
|
+
}));
|
|
32
|
+
return newLeafId;
|
|
33
|
+
}
|
|
34
|
+
export function splitPaneDirectly(tabId, targetLeafId, dir, edge) {
|
|
35
|
+
let newLeafId = null;
|
|
36
|
+
useTabsStore.getState().setTabs((curr) => curr.map((t) => {
|
|
37
|
+
if (t.id !== tabId || t.kind !== "terminal" || t.blocks)
|
|
38
|
+
return t;
|
|
39
|
+
if (leafIds(t.paneTree).length >= MAX_PANES_PER_TAB)
|
|
40
|
+
return t;
|
|
41
|
+
const splitId = useTabsStore.getState().allocId();
|
|
42
|
+
const leafId = useTabsStore.getState().allocId();
|
|
43
|
+
newLeafId = leafId;
|
|
44
|
+
const paneTree = splitLeaf(t.paneTree, targetLeafId, splitId, leafId, dir, t.cwd, undefined, edge);
|
|
45
|
+
return { ...t, paneTree, activeLeafId: leafId };
|
|
46
|
+
}));
|
|
47
|
+
return newLeafId;
|
|
48
|
+
}
|
|
49
|
+
export function splitPaneWithEditor(tabId, leafIdToSplit, path, dir, edge = "after") {
|
|
50
|
+
let newLeafId = null;
|
|
51
|
+
useTabsStore.getState().setTabs((curr) => curr.map((t) => {
|
|
52
|
+
if (t.id !== tabId || t.kind !== "terminal" || t.blocks)
|
|
53
|
+
return t;
|
|
54
|
+
if (leafIds(t.paneTree).length >= MAX_PANES_PER_TAB)
|
|
55
|
+
return t;
|
|
56
|
+
const splitId = useTabsStore.getState().allocId();
|
|
57
|
+
const leafId = useTabsStore.getState().allocId();
|
|
58
|
+
newLeafId = leafId;
|
|
59
|
+
const paneTree = splitLeaf(t.paneTree, leafIdToSplit, splitId, leafId, dir, t.cwd, path, edge);
|
|
60
|
+
return { ...t, paneTree, activeLeafId: leafId };
|
|
61
|
+
}));
|
|
62
|
+
return newLeafId;
|
|
63
|
+
}
|
|
64
|
+
export function swapPanes(tabId, leafA, leafB) {
|
|
65
|
+
useTabsStore.getState().setTabs((curr) => curr.map((t) => {
|
|
66
|
+
if (t.id !== tabId || t.kind !== "terminal")
|
|
67
|
+
return t;
|
|
68
|
+
const paneTree = swapLeaves(t.paneTree, leafA, leafB);
|
|
69
|
+
return { ...t, paneTree };
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
export function movePane(tabId, sourceId, targetId, dir, edge) {
|
|
73
|
+
useTabsStore.getState().setTabs((curr) => curr.map((t) => {
|
|
74
|
+
if (t.id !== tabId || t.kind !== "terminal")
|
|
75
|
+
return t;
|
|
76
|
+
const newSplitId = useTabsStore.getState().allocId();
|
|
77
|
+
const paneTree = moveLeaf(t.paneTree, sourceId, targetId, newSplitId, dir, edge);
|
|
78
|
+
return { ...t, paneTree };
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
export function closePaneByLeaf(leafId) {
|
|
82
|
+
let didRemove = false;
|
|
83
|
+
useTabsStore.getState().setTabs((curr) => {
|
|
84
|
+
const tab = curr.find((t) => t.kind === "terminal" && hasLeaf(t.paneTree, leafId));
|
|
85
|
+
if (tab?.kind !== "terminal")
|
|
86
|
+
return curr;
|
|
87
|
+
const newTree = removeLeaf(tab.paneTree, leafId);
|
|
88
|
+
if (newTree === null) {
|
|
89
|
+
const fallback = nextActiveInSpace(curr, tab.id);
|
|
90
|
+
const next = curr.filter((x) => x.id !== tab.id);
|
|
91
|
+
const newActive = fallback !== null ? fallback : -1;
|
|
92
|
+
useTabsStore
|
|
93
|
+
.getState()
|
|
94
|
+
.setActiveId(useTabsStore.getState().activeId === tab.id
|
|
95
|
+
? newActive
|
|
96
|
+
: useTabsStore.getState().activeId);
|
|
97
|
+
didRemove = true;
|
|
98
|
+
return next;
|
|
99
|
+
}
|
|
100
|
+
const remaining = leafIds(newTree);
|
|
101
|
+
let newActive = tab.activeLeafId;
|
|
102
|
+
if (tab.activeLeafId === leafId) {
|
|
103
|
+
const sib = siblingLeafOf(tab.paneTree, leafId);
|
|
104
|
+
newActive = sib && remaining.includes(sib) ? sib : remaining[0];
|
|
105
|
+
}
|
|
106
|
+
didRemove = true;
|
|
107
|
+
return curr.map((x) => x.id === tab.id
|
|
108
|
+
? { ...x, paneTree: newTree, activeLeafId: newActive }
|
|
109
|
+
: x);
|
|
110
|
+
});
|
|
111
|
+
if (didRemove) {
|
|
112
|
+
layoutAdapter.disposeSession(leafId);
|
|
113
|
+
window.dispatchEvent(new Event("resize"));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export function closeActivePane(tabId) {
|
|
117
|
+
let closedTab = false;
|
|
118
|
+
let removedLeaf = null;
|
|
119
|
+
useTabsStore.getState().setTabs((curr) => {
|
|
120
|
+
const t = curr.find((x) => x.id === tabId);
|
|
121
|
+
if (t?.kind !== "terminal")
|
|
122
|
+
return curr;
|
|
123
|
+
const target = t.activeLeafId;
|
|
124
|
+
const newTree = removeLeaf(t.paneTree, target);
|
|
125
|
+
if (newTree === null) {
|
|
126
|
+
const fallback = nextActiveInSpace(curr, tabId);
|
|
127
|
+
const next = curr.filter((x) => x.id !== tabId);
|
|
128
|
+
const newActive = fallback !== null ? fallback : -1;
|
|
129
|
+
useTabsStore
|
|
130
|
+
.getState()
|
|
131
|
+
.setActiveId(useTabsStore.getState().activeId === tabId
|
|
132
|
+
? newActive
|
|
133
|
+
: useTabsStore.getState().activeId);
|
|
134
|
+
closedTab = true;
|
|
135
|
+
removedLeaf = target;
|
|
136
|
+
return next;
|
|
137
|
+
}
|
|
138
|
+
const remaining = leafIds(newTree);
|
|
139
|
+
const sib = siblingLeafOf(t.paneTree, target);
|
|
140
|
+
const newActive = sib && remaining.includes(sib) ? sib : remaining[0];
|
|
141
|
+
removedLeaf = target;
|
|
142
|
+
return curr.map((x) => x.id === tabId ? { ...x, paneTree: newTree, activeLeafId: newActive } : x);
|
|
143
|
+
});
|
|
144
|
+
if (removedLeaf !== null) {
|
|
145
|
+
layoutAdapter.disposeSession(removedLeaf);
|
|
146
|
+
window.dispatchEvent(new Event("resize"));
|
|
147
|
+
}
|
|
148
|
+
return closedTab;
|
|
149
|
+
}
|
|
150
|
+
export function mergeTabIntoPane(sourceTabId, activeTabId, targetPaneId, dir, edge) {
|
|
151
|
+
useTabsStore.getState().setTabs((curr) => {
|
|
152
|
+
const sourceTab = curr.find((t) => t.id === sourceTabId);
|
|
153
|
+
const activeTab = curr.find((t) => t.id === activeTabId);
|
|
154
|
+
if (!sourceTab || !activeTab || activeTab.kind !== "terminal")
|
|
155
|
+
return curr;
|
|
156
|
+
if (sourceTabId === activeTabId)
|
|
157
|
+
return curr;
|
|
158
|
+
const newSplitId = useTabsStore.getState().allocId();
|
|
159
|
+
let sourceNode;
|
|
160
|
+
if (sourceTab.kind === "terminal") {
|
|
161
|
+
const leaves = leafIds(sourceTab.paneTree);
|
|
162
|
+
if (leaves.length !== 1)
|
|
163
|
+
return curr;
|
|
164
|
+
let foundLeaf = null;
|
|
165
|
+
const findLeaf = (n) => {
|
|
166
|
+
if (n.kind === "leaf" && n.id === leaves[0])
|
|
167
|
+
foundLeaf = n;
|
|
168
|
+
else if (n.kind === "split")
|
|
169
|
+
n.children.forEach(findLeaf);
|
|
170
|
+
};
|
|
171
|
+
findLeaf(sourceTab.paneTree);
|
|
172
|
+
if (!foundLeaf)
|
|
173
|
+
return curr;
|
|
174
|
+
sourceNode = foundLeaf;
|
|
175
|
+
}
|
|
176
|
+
else if (sourceTab.kind === "editor" || sourceTab.kind === "markdown") {
|
|
177
|
+
sourceNode = {
|
|
178
|
+
kind: "leaf",
|
|
179
|
+
id: useTabsStore.getState().allocId(),
|
|
180
|
+
editorPath: sourceTab.path,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
return curr;
|
|
185
|
+
}
|
|
186
|
+
function insertNode(n) {
|
|
187
|
+
if (n.kind === "split" && n.dir === dir) {
|
|
188
|
+
const leafIdx = n.children.findIndex((c) => c.kind === "leaf" && c.id === targetPaneId);
|
|
189
|
+
if (leafIdx >= 0) {
|
|
190
|
+
const newChildren = [...n.children];
|
|
191
|
+
newChildren.splice(edge === "before" ? leafIdx : leafIdx + 1, 0, sourceNode);
|
|
192
|
+
return { ...n, children: newChildren };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (n.kind === "leaf") {
|
|
196
|
+
if (n.id !== targetPaneId)
|
|
197
|
+
return n;
|
|
198
|
+
return {
|
|
199
|
+
kind: "split",
|
|
200
|
+
id: newSplitId,
|
|
201
|
+
dir,
|
|
202
|
+
children: edge === "before" ? [sourceNode, n] : [n, sourceNode],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
let changed = false;
|
|
206
|
+
const newChildren = n.children.map((c) => {
|
|
207
|
+
const inserted = insertNode(c);
|
|
208
|
+
if (inserted !== c)
|
|
209
|
+
changed = true;
|
|
210
|
+
return inserted;
|
|
211
|
+
});
|
|
212
|
+
return changed ? { ...n, children: newChildren } : n;
|
|
213
|
+
}
|
|
214
|
+
const newActiveTree = insertNode(activeTab.paneTree);
|
|
215
|
+
return curr
|
|
216
|
+
.filter((t) => t.id !== sourceTabId)
|
|
217
|
+
.map((t) => t.id === activeTabId ? { ...t, paneTree: newActiveTree, activeLeafId: sourceNode.id } : t);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
export function setActiveSpaceForNewTabs(spaceId) {
|
|
221
|
+
useTabsStore.getState().activeSpaceId = spaceId;
|
|
222
|
+
}
|
|
223
|
+
export function replaceTabs(next, nextActiveId) {
|
|
224
|
+
if (next.length === 0)
|
|
225
|
+
return;
|
|
226
|
+
useTabsStore.getState().setTabs(next);
|
|
227
|
+
useTabsStore.getState().setActiveId(nextActiveId);
|
|
228
|
+
}
|
|
229
|
+
export function newTabInSpace(spaceId, cwd) {
|
|
230
|
+
const tabId = useTabsStore.getState().allocId();
|
|
231
|
+
const leafId = useTabsStore.getState().allocId();
|
|
232
|
+
useTabsStore.getState().setTabs((curr) => [
|
|
233
|
+
...curr,
|
|
234
|
+
{
|
|
235
|
+
id: tabId,
|
|
236
|
+
kind: "terminal",
|
|
237
|
+
spaceId,
|
|
238
|
+
cold: true,
|
|
239
|
+
title: cwd ? basename(cwd) : "shell",
|
|
240
|
+
cwd,
|
|
241
|
+
paneTree: { kind: "leaf", id: leafId, cwd },
|
|
242
|
+
activeLeafId: leafId,
|
|
243
|
+
},
|
|
244
|
+
]);
|
|
245
|
+
// Focus the new tab when it lands in the active space (e.g. opened from the
|
|
246
|
+
// empty-space landing) so the surface actually switches to it. Skip when
|
|
247
|
+
// seeding a background space — pointing the global activeId at a hidden
|
|
248
|
+
// space would blank the visible one.
|
|
249
|
+
if (useTabsStore.getState().activeSpaceId === spaceId) {
|
|
250
|
+
useTabsStore.getState().setActiveId(tabId);
|
|
251
|
+
}
|
|
252
|
+
return tabId;
|
|
253
|
+
}
|
|
254
|
+
export function moveTabToSpace(tabId, targetSpaceId) {
|
|
255
|
+
const curr = useTabsStore.getState().tabs;
|
|
256
|
+
const tab = curr.find((t) => t.id === tabId);
|
|
257
|
+
if (!tab || tab.spaceId === targetSpaceId)
|
|
258
|
+
return false;
|
|
259
|
+
useTabsStore
|
|
260
|
+
.getState()
|
|
261
|
+
.setTabs((prev) => prev.map((t) => t.id === tabId ? { ...t, spaceId: targetSpaceId } : t));
|
|
262
|
+
if (useTabsStore.getState().activeId !== tabId)
|
|
263
|
+
return false;
|
|
264
|
+
const fallback = nextActiveInSpace(curr, tabId);
|
|
265
|
+
if (fallback !== null) {
|
|
266
|
+
useTabsStore.getState().setActiveId(fallback);
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
export function copyTabToSpace(tabId, targetSpaceId) {
|
|
272
|
+
const curr = useTabsStore.getState().tabs;
|
|
273
|
+
const tab = curr.find((t) => t.id === tabId);
|
|
274
|
+
if (!tab)
|
|
275
|
+
return null;
|
|
276
|
+
const newTabId = useTabsStore.getState().allocId();
|
|
277
|
+
const newLeafId = useTabsStore.getState().allocId();
|
|
278
|
+
let cloned;
|
|
279
|
+
if (tab.kind === "terminal") {
|
|
280
|
+
cloned = {
|
|
281
|
+
...tab,
|
|
282
|
+
id: newTabId,
|
|
283
|
+
spaceId: targetSpaceId,
|
|
284
|
+
cold: true,
|
|
285
|
+
paneTree: {
|
|
286
|
+
kind: "leaf",
|
|
287
|
+
id: newLeafId,
|
|
288
|
+
...(tab.cwd && { cwd: tab.cwd }),
|
|
289
|
+
},
|
|
290
|
+
activeLeafId: newLeafId,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
cloned = {
|
|
295
|
+
...tab,
|
|
296
|
+
id: newTabId,
|
|
297
|
+
spaceId: targetSpaceId,
|
|
298
|
+
cold: true,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
useTabsStore.getState().setTabs((prev) => [...prev, cloned]);
|
|
302
|
+
return newTabId;
|
|
303
|
+
}
|
|
304
|
+
export function reorderTab(tabId, targetTabId, edge) {
|
|
305
|
+
if (tabId === targetTabId)
|
|
306
|
+
return false;
|
|
307
|
+
const curr = useTabsStore.getState().tabs;
|
|
308
|
+
const moved = curr.find((t) => t.id === tabId);
|
|
309
|
+
const target = curr.find((t) => t.id === targetTabId);
|
|
310
|
+
if (!moved || !target)
|
|
311
|
+
return false;
|
|
312
|
+
const crossSpace = moved.spaceId !== target.spaceId;
|
|
313
|
+
useTabsStore.getState().setTabs((prev) => {
|
|
314
|
+
const without = prev.filter((t) => t.id !== tabId);
|
|
315
|
+
let idx = without.findIndex((t) => t.id === targetTabId);
|
|
316
|
+
if (idx < 0)
|
|
317
|
+
return prev;
|
|
318
|
+
if (edge === "bottom")
|
|
319
|
+
idx += 1;
|
|
320
|
+
const next = crossSpace
|
|
321
|
+
? { ...moved, spaceId: target.spaceId }
|
|
322
|
+
: moved;
|
|
323
|
+
without.splice(idx, 0, next);
|
|
324
|
+
return without;
|
|
325
|
+
});
|
|
326
|
+
if (!crossSpace || useTabsStore.getState().activeId !== tabId)
|
|
327
|
+
return false;
|
|
328
|
+
const fallback = nextActiveInSpace(curr, tabId);
|
|
329
|
+
if (fallback !== null) {
|
|
330
|
+
useTabsStore.getState().setActiveId(fallback);
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
export function removeTabsForSpace(spaceId) {
|
|
336
|
+
let toDispose = [];
|
|
337
|
+
useTabsStore.getState().setTabs((curr) => {
|
|
338
|
+
const next = curr.filter((t) => t.spaceId !== spaceId);
|
|
339
|
+
if (next.length === curr.length)
|
|
340
|
+
return curr;
|
|
341
|
+
toDispose = curr
|
|
342
|
+
.filter((t) => t.spaceId === spaceId && t.kind === "terminal")
|
|
343
|
+
.flatMap((t) => leafIds(t.paneTree));
|
|
344
|
+
const activeTab = curr.find((t) => t.id === useTabsStore.getState().activeId);
|
|
345
|
+
if (activeTab?.spaceId === spaceId) {
|
|
346
|
+
useTabsStore.getState().setActiveId(next.length > 0 ? next[0].id : -1);
|
|
347
|
+
}
|
|
348
|
+
return next;
|
|
349
|
+
});
|
|
350
|
+
for (const lid of toDispose)
|
|
351
|
+
layoutAdapter.disposeSession(lid);
|
|
352
|
+
}
|
|
353
|
+
export function newTab(cwd, command, title) {
|
|
354
|
+
const tabId = useTabsStore.getState().allocId();
|
|
355
|
+
const leafId = useTabsStore.getState().allocId();
|
|
356
|
+
useTabsStore.getState().setTabs((t) => [
|
|
357
|
+
...t,
|
|
358
|
+
{
|
|
359
|
+
id: tabId,
|
|
360
|
+
kind: "terminal",
|
|
361
|
+
spaceId: useTabsStore.getState().activeSpaceId,
|
|
362
|
+
title: title || "shell",
|
|
363
|
+
cwd,
|
|
364
|
+
paneTree: { kind: "leaf", id: leafId, cwd, command },
|
|
365
|
+
activeLeafId: leafId,
|
|
366
|
+
},
|
|
367
|
+
]);
|
|
368
|
+
useTabsStore.getState().setActiveId(tabId);
|
|
369
|
+
return tabId;
|
|
370
|
+
}
|
|
371
|
+
export function newBlockTab(cwd) {
|
|
372
|
+
const tabId = useTabsStore.getState().allocId();
|
|
373
|
+
const leafId = useTabsStore.getState().allocId();
|
|
374
|
+
useTabsStore.getState().setTabs((t) => [
|
|
375
|
+
...t,
|
|
376
|
+
{
|
|
377
|
+
id: tabId,
|
|
378
|
+
kind: "terminal",
|
|
379
|
+
spaceId: useTabsStore.getState().activeSpaceId,
|
|
380
|
+
title: "blocks",
|
|
381
|
+
cwd,
|
|
382
|
+
paneTree: { kind: "leaf", id: leafId, cwd },
|
|
383
|
+
activeLeafId: leafId,
|
|
384
|
+
blocks: true,
|
|
385
|
+
},
|
|
386
|
+
]);
|
|
387
|
+
useTabsStore.getState().setActiveId(tabId);
|
|
388
|
+
return tabId;
|
|
389
|
+
}
|
|
390
|
+
export function newAgentTab(cwd, title) {
|
|
391
|
+
const tabId = useTabsStore.getState().allocId();
|
|
392
|
+
const leafId = useTabsStore.getState().allocId();
|
|
393
|
+
useTabsStore.getState().setTabs((t) => [
|
|
394
|
+
...t,
|
|
395
|
+
{
|
|
396
|
+
id: tabId,
|
|
397
|
+
kind: "terminal",
|
|
398
|
+
spaceId: useTabsStore.getState().activeSpaceId,
|
|
399
|
+
title,
|
|
400
|
+
cwd,
|
|
401
|
+
paneTree: { kind: "leaf", id: leafId, cwd },
|
|
402
|
+
activeLeafId: leafId,
|
|
403
|
+
},
|
|
404
|
+
]);
|
|
405
|
+
useTabsStore.getState().setActiveId(tabId);
|
|
406
|
+
return { tabId, leafId };
|
|
407
|
+
}
|
|
408
|
+
export function newPrivateTab(cwd) {
|
|
409
|
+
const tabId = useTabsStore.getState().allocId();
|
|
410
|
+
const leafId = useTabsStore.getState().allocId();
|
|
411
|
+
useTabsStore.getState().setTabs((t) => [
|
|
412
|
+
...t,
|
|
413
|
+
{
|
|
414
|
+
id: tabId,
|
|
415
|
+
kind: "terminal",
|
|
416
|
+
spaceId: useTabsStore.getState().activeSpaceId,
|
|
417
|
+
title: "private",
|
|
418
|
+
cwd,
|
|
419
|
+
paneTree: { kind: "leaf", id: leafId, cwd },
|
|
420
|
+
activeLeafId: leafId,
|
|
421
|
+
private: true,
|
|
422
|
+
},
|
|
423
|
+
]);
|
|
424
|
+
useTabsStore.getState().setActiveId(tabId);
|
|
425
|
+
return tabId;
|
|
426
|
+
}
|
|
427
|
+
export function openFileTab(path, pin = true, gotoLine) {
|
|
428
|
+
let targetId = null;
|
|
429
|
+
useTabsStore.getState().setTabs((curr) => {
|
|
430
|
+
if (pin) {
|
|
431
|
+
// Persistent open: find any existing editor tab, pin it if needed.
|
|
432
|
+
const existing = curr.find((t) => t.kind === "editor" && t.path === path);
|
|
433
|
+
if (existing) {
|
|
434
|
+
targetId = existing.id;
|
|
435
|
+
let needsUpdate = false;
|
|
436
|
+
if (gotoLine !== undefined &&
|
|
437
|
+
existing.gotoLine !== gotoLine) {
|
|
438
|
+
needsUpdate = true;
|
|
439
|
+
}
|
|
440
|
+
if (existing.preview) {
|
|
441
|
+
needsUpdate = true;
|
|
442
|
+
}
|
|
443
|
+
if (needsUpdate) {
|
|
444
|
+
return curr.map((t) => t.id === existing.id
|
|
445
|
+
? {
|
|
446
|
+
...t,
|
|
447
|
+
preview: false,
|
|
448
|
+
...(gotoLine !== undefined ? { gotoLine } : {}),
|
|
449
|
+
}
|
|
450
|
+
: t);
|
|
451
|
+
}
|
|
452
|
+
return curr;
|
|
453
|
+
}
|
|
454
|
+
const id = useTabsStore.getState().allocId();
|
|
455
|
+
targetId = id;
|
|
456
|
+
return [
|
|
457
|
+
...curr,
|
|
458
|
+
{
|
|
459
|
+
id,
|
|
460
|
+
kind: "editor",
|
|
461
|
+
spaceId: useTabsStore.getState().activeSpaceId,
|
|
462
|
+
title: basename(path),
|
|
463
|
+
path,
|
|
464
|
+
dirty: false,
|
|
465
|
+
preview: false,
|
|
466
|
+
},
|
|
467
|
+
];
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
// Preview open: persistent tab for this path takes priority.
|
|
471
|
+
const persistent = curr.find((t) => t.kind === "editor" && t.path === path && !t.preview);
|
|
472
|
+
if (persistent) {
|
|
473
|
+
targetId = persistent.id;
|
|
474
|
+
return curr;
|
|
475
|
+
}
|
|
476
|
+
// Reuse the slot if it already shows the same path.
|
|
477
|
+
const existingPreview = curr.find((t) => t.kind === "editor" && t.path === path && t.preview);
|
|
478
|
+
if (existingPreview) {
|
|
479
|
+
targetId = existingPreview.id;
|
|
480
|
+
return curr;
|
|
481
|
+
}
|
|
482
|
+
// Replace the current preview slot, or append a new one.
|
|
483
|
+
const previewIdx = curr.findIndex((t) => t.kind === "editor" && t.preview);
|
|
484
|
+
const id = useTabsStore.getState().allocId();
|
|
485
|
+
targetId = id;
|
|
486
|
+
const tab = {
|
|
487
|
+
id,
|
|
488
|
+
kind: "editor",
|
|
489
|
+
spaceId: useTabsStore.getState().activeSpaceId,
|
|
490
|
+
title: basename(path),
|
|
491
|
+
path,
|
|
492
|
+
dirty: false,
|
|
493
|
+
preview: true,
|
|
494
|
+
};
|
|
495
|
+
if (previewIdx === -1)
|
|
496
|
+
return [...curr, tab];
|
|
497
|
+
const next = [...curr];
|
|
498
|
+
next[previewIdx] = tab;
|
|
499
|
+
return next;
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
if (targetId !== null)
|
|
503
|
+
useTabsStore.getState().setActiveId(targetId);
|
|
504
|
+
return targetId;
|
|
505
|
+
}
|
|
506
|
+
export function openFileTabInSpace(spaceId, path, pin = false) {
|
|
507
|
+
let targetId = null;
|
|
508
|
+
useTabsStore.getState().setTabs((curr) => {
|
|
509
|
+
const existing = curr.find((t) => t.kind === "editor" && t.path === path && t.spaceId === spaceId);
|
|
510
|
+
if (existing) {
|
|
511
|
+
targetId = existing.id;
|
|
512
|
+
if (pin && existing.preview) {
|
|
513
|
+
return curr.map((t) => t.id === existing.id ? { ...t, preview: false } : t);
|
|
514
|
+
}
|
|
515
|
+
return curr;
|
|
516
|
+
}
|
|
517
|
+
const id = useTabsStore.getState().allocId();
|
|
518
|
+
targetId = id;
|
|
519
|
+
const tab = {
|
|
520
|
+
id,
|
|
521
|
+
kind: "editor",
|
|
522
|
+
spaceId,
|
|
523
|
+
title: basename(path) || "untitled",
|
|
524
|
+
path,
|
|
525
|
+
dirty: false,
|
|
526
|
+
preview: !pin,
|
|
527
|
+
};
|
|
528
|
+
return [...curr, tab];
|
|
529
|
+
});
|
|
530
|
+
if (targetId !== null)
|
|
531
|
+
useTabsStore.getState().setActiveId(targetId);
|
|
532
|
+
return targetId;
|
|
533
|
+
}
|
|
534
|
+
export function pinTab(id) {
|
|
535
|
+
useTabsStore
|
|
536
|
+
.getState()
|
|
537
|
+
.setTabs((curr) => curr.map((t) => t.id === id && t.kind === "editor" ? { ...t, preview: false } : t));
|
|
538
|
+
}
|
|
539
|
+
export function openAiDiffTab(input) {
|
|
540
|
+
let targetId = null;
|
|
541
|
+
useTabsStore.getState().setTabs((curr) => {
|
|
542
|
+
const existing = curr.find((t) => t.kind === "ai-diff" && t.approvalId === input.approvalId);
|
|
543
|
+
if (existing) {
|
|
544
|
+
targetId = existing.id;
|
|
545
|
+
return curr;
|
|
546
|
+
}
|
|
547
|
+
const id = useTabsStore.getState().allocId();
|
|
548
|
+
targetId = id;
|
|
549
|
+
const title = `${basename(input.path)} (AI diff)`;
|
|
550
|
+
return [
|
|
551
|
+
...curr,
|
|
552
|
+
{
|
|
553
|
+
id,
|
|
554
|
+
kind: "ai-diff",
|
|
555
|
+
spaceId: useTabsStore.getState().activeSpaceId,
|
|
556
|
+
title,
|
|
557
|
+
path: input.path,
|
|
558
|
+
originalContent: input.originalContent,
|
|
559
|
+
proposedContent: input.proposedContent,
|
|
560
|
+
approvalId: input.approvalId,
|
|
561
|
+
status: "pending",
|
|
562
|
+
isNewFile: input.isNewFile,
|
|
563
|
+
},
|
|
564
|
+
];
|
|
565
|
+
});
|
|
566
|
+
if (targetId !== null)
|
|
567
|
+
useTabsStore.getState().setActiveId(targetId);
|
|
568
|
+
return targetId;
|
|
569
|
+
}
|
|
570
|
+
export function setAiDiffStatus(approvalId, status) {
|
|
571
|
+
useTabsStore
|
|
572
|
+
.getState()
|
|
573
|
+
.setTabs((curr) => curr.map((t) => t.kind === "ai-diff" && t.approvalId === approvalId
|
|
574
|
+
? { ...t, status }
|
|
575
|
+
: t));
|
|
576
|
+
}
|
|
577
|
+
export function closeAiDiffTab(approvalId) {
|
|
578
|
+
useTabsStore.getState().setTabs((curr) => {
|
|
579
|
+
const target = curr.find((t) => t.kind === "ai-diff" && t.approvalId === approvalId);
|
|
580
|
+
if (!target)
|
|
581
|
+
return curr;
|
|
582
|
+
const fallback = nextActiveInSpace(curr, target.id);
|
|
583
|
+
const next = curr.filter((t) => t.id !== target.id);
|
|
584
|
+
const newActiveId = fallback !== null ? fallback : -1;
|
|
585
|
+
useTabsStore
|
|
586
|
+
.getState()
|
|
587
|
+
.setActiveId((active) => (target.id === active ? newActiveId : active));
|
|
588
|
+
return next;
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
export function newPreviewTab(url) {
|
|
592
|
+
const id = useTabsStore.getState().allocId();
|
|
593
|
+
useTabsStore.getState().setTabs((t) => [
|
|
594
|
+
...t,
|
|
595
|
+
{
|
|
596
|
+
id,
|
|
597
|
+
kind: "preview",
|
|
598
|
+
spaceId: useTabsStore.getState().activeSpaceId,
|
|
599
|
+
title: titleFromUrl(url),
|
|
600
|
+
url,
|
|
601
|
+
},
|
|
602
|
+
]);
|
|
603
|
+
useTabsStore.getState().setActiveId(id);
|
|
604
|
+
return id;
|
|
605
|
+
}
|
|
606
|
+
export function newMarkdownTab(path) {
|
|
607
|
+
let targetId = null;
|
|
608
|
+
useTabsStore.getState().setTabs((curr) => {
|
|
609
|
+
const existing = curr.find((t) => t.kind === "markdown" && t.path === path);
|
|
610
|
+
if (existing) {
|
|
611
|
+
targetId = existing.id;
|
|
612
|
+
return curr;
|
|
613
|
+
}
|
|
614
|
+
const id = useTabsStore.getState().allocId();
|
|
615
|
+
targetId = id;
|
|
616
|
+
return [
|
|
617
|
+
...curr,
|
|
618
|
+
{
|
|
619
|
+
id,
|
|
620
|
+
kind: "markdown",
|
|
621
|
+
spaceId: useTabsStore.getState().activeSpaceId,
|
|
622
|
+
title: basename(path),
|
|
623
|
+
path,
|
|
624
|
+
},
|
|
625
|
+
];
|
|
626
|
+
});
|
|
627
|
+
if (targetId !== null)
|
|
628
|
+
useTabsStore.getState().setActiveId(targetId);
|
|
629
|
+
return targetId;
|
|
630
|
+
}
|
|
631
|
+
export function openGitDiffTab(input) {
|
|
632
|
+
const curr = useTabsStore.getState().tabs;
|
|
633
|
+
const existing = curr.find((t) => t.kind === "git-diff" &&
|
|
634
|
+
t.repoRoot === input.repoRoot &&
|
|
635
|
+
t.path === input.path &&
|
|
636
|
+
t.mode === input.mode);
|
|
637
|
+
const computedTitle = input.title ?? `${basename(input.path)} (${input.mode})`;
|
|
638
|
+
const originalPath = input.originalPath ?? null;
|
|
639
|
+
if (existing) {
|
|
640
|
+
const nextTabs = curr.map((t) => t.id === existing.id ? { ...t, title: computedTitle, originalPath } : t);
|
|
641
|
+
useTabsStore.getState().tabs = nextTabs;
|
|
642
|
+
useTabsStore.getState().setTabs(nextTabs);
|
|
643
|
+
useTabsStore.getState().setActiveId(existing.id);
|
|
644
|
+
return existing.id;
|
|
645
|
+
}
|
|
646
|
+
const id = useTabsStore.getState().allocId();
|
|
647
|
+
const nextTabs = [
|
|
648
|
+
...curr,
|
|
649
|
+
{
|
|
650
|
+
id,
|
|
651
|
+
kind: "git-diff",
|
|
652
|
+
spaceId: useTabsStore.getState().activeSpaceId,
|
|
653
|
+
title: computedTitle,
|
|
654
|
+
path: input.path,
|
|
655
|
+
repoRoot: input.repoRoot,
|
|
656
|
+
mode: input.mode,
|
|
657
|
+
originalPath,
|
|
658
|
+
},
|
|
659
|
+
];
|
|
660
|
+
useTabsStore.getState().tabs = nextTabs;
|
|
661
|
+
useTabsStore.getState().setTabs(nextTabs);
|
|
662
|
+
useTabsStore.getState().setActiveId(id);
|
|
663
|
+
return id;
|
|
664
|
+
}
|
|
665
|
+
export function openCommitHistoryTab(input) {
|
|
666
|
+
const curr = useTabsStore.getState().tabs;
|
|
667
|
+
const existing = curr.find((t) => t.kind === "git-history" && t.repoRoot === input.repoRoot);
|
|
668
|
+
const title = input.branch ? `History · ${input.branch}` : "Git History";
|
|
669
|
+
if (existing) {
|
|
670
|
+
const nextTabs = curr.map((t) => t.id === existing.id ? { ...t, title } : t);
|
|
671
|
+
useTabsStore.getState().tabs = nextTabs;
|
|
672
|
+
useTabsStore.getState().setTabs(nextTabs);
|
|
673
|
+
useTabsStore.getState().setActiveId(existing.id);
|
|
674
|
+
return existing.id;
|
|
675
|
+
}
|
|
676
|
+
const id = useTabsStore.getState().allocId();
|
|
677
|
+
const nextTabs = [
|
|
678
|
+
...curr,
|
|
679
|
+
{
|
|
680
|
+
id,
|
|
681
|
+
kind: "git-history",
|
|
682
|
+
spaceId: useTabsStore.getState().activeSpaceId,
|
|
683
|
+
title,
|
|
684
|
+
repoRoot: input.repoRoot,
|
|
685
|
+
},
|
|
686
|
+
];
|
|
687
|
+
useTabsStore.getState().tabs = nextTabs;
|
|
688
|
+
useTabsStore.getState().setTabs(nextTabs);
|
|
689
|
+
useTabsStore.getState().setActiveId(id);
|
|
690
|
+
return id;
|
|
691
|
+
}
|
|
692
|
+
export function openCommitFileDiffTab(input) {
|
|
693
|
+
const curr = useTabsStore.getState().tabs;
|
|
694
|
+
const existing = curr.find((t) => t.kind === "git-commit-file" &&
|
|
695
|
+
t.repoRoot === input.repoRoot &&
|
|
696
|
+
t.sha === input.sha &&
|
|
697
|
+
t.path === input.path);
|
|
698
|
+
const title = `${basename(input.path)} @ ${input.shortSha}`;
|
|
699
|
+
if (existing) {
|
|
700
|
+
const nextTabs = curr.map((t) => t.id === existing.id
|
|
701
|
+
? {
|
|
702
|
+
...t,
|
|
703
|
+
title,
|
|
704
|
+
subject: input.subject,
|
|
705
|
+
originalPath: input.originalPath,
|
|
706
|
+
}
|
|
707
|
+
: t);
|
|
708
|
+
useTabsStore.getState().tabs = nextTabs;
|
|
709
|
+
useTabsStore.getState().setTabs(nextTabs);
|
|
710
|
+
useTabsStore.getState().setActiveId(existing.id);
|
|
711
|
+
return existing.id;
|
|
712
|
+
}
|
|
713
|
+
const id = useTabsStore.getState().allocId();
|
|
714
|
+
const nextTabs = [
|
|
715
|
+
...curr,
|
|
716
|
+
{
|
|
717
|
+
id,
|
|
718
|
+
kind: "git-commit-file",
|
|
719
|
+
spaceId: useTabsStore.getState().activeSpaceId,
|
|
720
|
+
title,
|
|
721
|
+
repoRoot: input.repoRoot,
|
|
722
|
+
sha: input.sha,
|
|
723
|
+
shortSha: input.shortSha,
|
|
724
|
+
subject: input.subject,
|
|
725
|
+
path: input.path,
|
|
726
|
+
originalPath: input.originalPath,
|
|
727
|
+
},
|
|
728
|
+
];
|
|
729
|
+
useTabsStore.getState().tabs = nextTabs;
|
|
730
|
+
useTabsStore.getState().setTabs(nextTabs);
|
|
731
|
+
useTabsStore.getState().setActiveId(id);
|
|
732
|
+
return id;
|
|
733
|
+
}
|
|
734
|
+
export function closeTab(id) {
|
|
735
|
+
let toDispose = [];
|
|
736
|
+
useTabsStore.getState().setTabs((curr) => {
|
|
737
|
+
const target = curr.find((t) => t.id === id);
|
|
738
|
+
if (!target)
|
|
739
|
+
return curr;
|
|
740
|
+
if (target.kind === "terminal") {
|
|
741
|
+
toDispose = leafIds(target.paneTree);
|
|
742
|
+
}
|
|
743
|
+
const fallback = nextActiveInSpace(curr, id);
|
|
744
|
+
const next = curr.filter((t) => t.id !== id);
|
|
745
|
+
const newActiveId = fallback !== null ? fallback : -1;
|
|
746
|
+
useTabsStore
|
|
747
|
+
.getState()
|
|
748
|
+
.setActiveId((active) => (id === active ? newActiveId : active));
|
|
749
|
+
return next;
|
|
750
|
+
});
|
|
751
|
+
for (const lid of toDispose)
|
|
752
|
+
layoutAdapter.disposeSession(lid);
|
|
753
|
+
}
|
|
754
|
+
export function updateTab(id, patch) {
|
|
755
|
+
useTabsStore.getState().setTabs((t) => t.map((x) => {
|
|
756
|
+
if (x.id !== id)
|
|
757
|
+
return x;
|
|
758
|
+
if (x.kind === "terminal") {
|
|
759
|
+
return {
|
|
760
|
+
...x,
|
|
761
|
+
...(patch.title !== undefined && { title: patch.title }),
|
|
762
|
+
...(patch.dynamicTitle !== undefined && {
|
|
763
|
+
dynamicTitle: patch.dynamicTitle,
|
|
764
|
+
}),
|
|
765
|
+
...(patch.alert !== undefined && { alert: patch.alert }),
|
|
766
|
+
...(patch.interaction !== undefined && {
|
|
767
|
+
interaction: patch.interaction,
|
|
768
|
+
}),
|
|
769
|
+
...(patch.cwd !== undefined && { cwd: patch.cwd }),
|
|
770
|
+
...(patch.customTitle !== undefined && {
|
|
771
|
+
customTitle: patch.customTitle === "" ? undefined : patch.customTitle,
|
|
772
|
+
}),
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
if (x.kind === "preview") {
|
|
776
|
+
return {
|
|
777
|
+
...x,
|
|
778
|
+
...(patch.title !== undefined && { title: patch.title }),
|
|
779
|
+
...(patch.url !== undefined && {
|
|
780
|
+
url: patch.url,
|
|
781
|
+
title: patch.title ?? titleFromUrl(patch.url),
|
|
782
|
+
}),
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
if (x.kind === "markdown") {
|
|
786
|
+
return {
|
|
787
|
+
...x,
|
|
788
|
+
...(patch.title !== undefined && { title: patch.title }),
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
// editor tab: auto-promote from preview the moment the file becomes dirty.
|
|
792
|
+
const autoPin = patch.dirty === true && x.preview
|
|
793
|
+
? { preview: false }
|
|
794
|
+
: {};
|
|
795
|
+
return {
|
|
796
|
+
...x,
|
|
797
|
+
...autoPin,
|
|
798
|
+
...(patch.title !== undefined && { title: patch.title }),
|
|
799
|
+
...(patch.dirty !== undefined && { dirty: patch.dirty }),
|
|
800
|
+
...(patch.path !== undefined && { path: patch.path }),
|
|
801
|
+
};
|
|
802
|
+
}));
|
|
803
|
+
}
|
|
804
|
+
export function selectByIndex(idx) {
|
|
805
|
+
const t = useTabsStore.getState().tabs[idx];
|
|
806
|
+
if (t)
|
|
807
|
+
useTabsStore.getState().setActiveId(t.id);
|
|
808
|
+
}
|
|
809
|
+
export function setLeafCwd(leafId, cwd) {
|
|
810
|
+
useTabsStore.getState().setTabs((curr) => {
|
|
811
|
+
let changed = false;
|
|
812
|
+
const next = curr.map((t) => {
|
|
813
|
+
if (t.kind !== "terminal" || !hasLeaf(t.paneTree, leafId))
|
|
814
|
+
return t;
|
|
815
|
+
const paneTree = setLeafCwdInTree(t.paneTree, leafId, cwd);
|
|
816
|
+
const isActive = t.activeLeafId === leafId;
|
|
817
|
+
const cwdChanged = isActive && t.cwd !== cwd;
|
|
818
|
+
if (paneTree === t.paneTree && !cwdChanged)
|
|
819
|
+
return t;
|
|
820
|
+
changed = true;
|
|
821
|
+
return {
|
|
822
|
+
...t,
|
|
823
|
+
paneTree,
|
|
824
|
+
...(cwdChanged && { cwd, title: cwd ? basename(cwd) : "shell" }),
|
|
825
|
+
};
|
|
826
|
+
});
|
|
827
|
+
return changed ? next : curr;
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
export function focusPane(tabId, leafId) {
|
|
831
|
+
useTabsStore.getState().setTabs((curr) => curr.map((t) => {
|
|
832
|
+
if (t.id !== tabId || t.kind !== "terminal")
|
|
833
|
+
return t;
|
|
834
|
+
if (!hasLeaf(t.paneTree, leafId))
|
|
835
|
+
return t;
|
|
836
|
+
if (t.activeLeafId === leafId)
|
|
837
|
+
return t;
|
|
838
|
+
const cwd = findLeafCwd(t.paneTree, leafId);
|
|
839
|
+
return {
|
|
840
|
+
...t,
|
|
841
|
+
activeLeafId: leafId,
|
|
842
|
+
...(cwd !== undefined && { cwd }),
|
|
843
|
+
};
|
|
844
|
+
}));
|
|
845
|
+
}
|
|
846
|
+
export function focusNextPaneInTab(tabId, delta) {
|
|
847
|
+
useTabsStore.getState().setTabs((curr) => curr.map((t) => {
|
|
848
|
+
if (t.id !== tabId || t.kind !== "terminal")
|
|
849
|
+
return t;
|
|
850
|
+
const next = nextLeafId(t.paneTree, t.activeLeafId, delta);
|
|
851
|
+
if (next === t.activeLeafId)
|
|
852
|
+
return t;
|
|
853
|
+
const cwd = findLeafCwd(t.paneTree, next);
|
|
854
|
+
return { ...t, activeLeafId: next, ...(cwd !== undefined && { cwd }) };
|
|
855
|
+
}));
|
|
856
|
+
}
|
|
857
|
+
export function resetWorkspace(cwd) {
|
|
858
|
+
const tabId = useTabsStore.getState().allocId();
|
|
859
|
+
const leafId = useTabsStore.getState().allocId();
|
|
860
|
+
let toDispose = [];
|
|
861
|
+
useTabsStore.getState().setTabs((curr) => {
|
|
862
|
+
toDispose = curr.flatMap((t) => t.kind === "terminal" ? leafIds(t.paneTree) : []);
|
|
863
|
+
return [
|
|
864
|
+
{
|
|
865
|
+
id: tabId,
|
|
866
|
+
kind: "terminal",
|
|
867
|
+
spaceId: useTabsStore.getState().activeSpaceId,
|
|
868
|
+
title: "shell",
|
|
869
|
+
cwd,
|
|
870
|
+
paneTree: { kind: "leaf", id: leafId, cwd },
|
|
871
|
+
activeLeafId: leafId,
|
|
872
|
+
},
|
|
873
|
+
];
|
|
874
|
+
});
|
|
875
|
+
useTabsStore.getState().setActiveId(tabId);
|
|
876
|
+
for (const lid of toDispose)
|
|
877
|
+
layoutAdapter.disposeSession(lid);
|
|
878
|
+
}
|
|
879
|
+
export function extractPaneToTab(tabId, leafId) {
|
|
880
|
+
let newTabId = null;
|
|
881
|
+
useTabsStore.getState().setTabs((curr) => {
|
|
882
|
+
const tab = curr.find((t) => t.id === tabId && t.kind === "terminal");
|
|
883
|
+
if (!tab || tab.kind !== "terminal")
|
|
884
|
+
return curr;
|
|
885
|
+
if (!hasLeaf(tab.paneTree, leafId))
|
|
886
|
+
return curr;
|
|
887
|
+
const leaves = leafIds(tab.paneTree);
|
|
888
|
+
if (leaves.length <= 1)
|
|
889
|
+
return curr;
|
|
890
|
+
const newTree = removeLeaf(tab.paneTree, leafId);
|
|
891
|
+
if (!newTree)
|
|
892
|
+
return curr;
|
|
893
|
+
const remaining = leafIds(newTree);
|
|
894
|
+
const newActive = tab.activeLeafId === leafId ? remaining[0] : tab.activeLeafId;
|
|
895
|
+
newTabId = useTabsStore.getState().allocId();
|
|
896
|
+
const createdTab = {
|
|
897
|
+
kind: "terminal",
|
|
898
|
+
id: newTabId,
|
|
899
|
+
spaceId: tab.spaceId,
|
|
900
|
+
paneTree: { kind: "leaf", id: leafId },
|
|
901
|
+
activeLeafId: leafId,
|
|
902
|
+
cwd: tab.cwd,
|
|
903
|
+
title: tab.title,
|
|
904
|
+
};
|
|
905
|
+
return curr
|
|
906
|
+
.map((x) => (x.id === tabId ? { ...x, paneTree: newTree, activeLeafId: newActive } : x))
|
|
907
|
+
.concat(createdTab);
|
|
908
|
+
});
|
|
909
|
+
if (newTabId !== null) {
|
|
910
|
+
useTabsStore.getState().setActiveId(newTabId);
|
|
911
|
+
if (typeof window !== "undefined")
|
|
912
|
+
window.dispatchEvent(new Event("resize"));
|
|
913
|
+
}
|
|
914
|
+
return newTabId;
|
|
915
|
+
}
|
|
916
|
+
export function unsplitAllPanesToTabs(tabId) {
|
|
917
|
+
let createdTabIds = [];
|
|
918
|
+
useTabsStore.getState().setTabs((curr) => {
|
|
919
|
+
const tab = curr.find((t) => t.id === tabId && t.kind === "terminal");
|
|
920
|
+
if (!tab || tab.kind !== "terminal")
|
|
921
|
+
return curr;
|
|
922
|
+
const leaves = leafIds(tab.paneTree);
|
|
923
|
+
if (leaves.length <= 1)
|
|
924
|
+
return curr;
|
|
925
|
+
const firstLeaf = leaves[0];
|
|
926
|
+
const firstTab = {
|
|
927
|
+
...tab,
|
|
928
|
+
paneTree: { kind: "leaf", id: firstLeaf },
|
|
929
|
+
activeLeafId: firstLeaf,
|
|
930
|
+
};
|
|
931
|
+
const extraTabs = leaves.slice(1).map((leafId) => {
|
|
932
|
+
const newId = useTabsStore.getState().allocId();
|
|
933
|
+
createdTabIds.push(newId);
|
|
934
|
+
return {
|
|
935
|
+
kind: "terminal",
|
|
936
|
+
id: newId,
|
|
937
|
+
spaceId: tab.spaceId,
|
|
938
|
+
paneTree: { kind: "leaf", id: leafId },
|
|
939
|
+
activeLeafId: leafId,
|
|
940
|
+
cwd: tab.cwd,
|
|
941
|
+
title: tab.title,
|
|
942
|
+
};
|
|
943
|
+
});
|
|
944
|
+
return curr.map((x) => (x.id === tabId ? firstTab : x)).concat(extraTabs);
|
|
945
|
+
});
|
|
946
|
+
if (typeof window !== "undefined")
|
|
947
|
+
window.dispatchEvent(new Event("resize"));
|
|
948
|
+
return createdTabIds;
|
|
949
|
+
}
|
|
950
|
+
export function closeOtherPanes(tabId, keepLeafId) {
|
|
951
|
+
const leavesToDispose = [];
|
|
952
|
+
useTabsStore.getState().setTabs((curr) => {
|
|
953
|
+
const tab = curr.find((t) => t.id === tabId && t.kind === "terminal");
|
|
954
|
+
if (!tab || tab.kind !== "terminal")
|
|
955
|
+
return curr;
|
|
956
|
+
const leaves = leafIds(tab.paneTree);
|
|
957
|
+
for (const l of leaves) {
|
|
958
|
+
if (l !== keepLeafId)
|
|
959
|
+
leavesToDispose.push(l);
|
|
960
|
+
}
|
|
961
|
+
return curr.map((x) => x.id === tabId
|
|
962
|
+
? {
|
|
963
|
+
...x,
|
|
964
|
+
paneTree: { kind: "leaf", id: keepLeafId },
|
|
965
|
+
activeLeafId: keepLeafId,
|
|
966
|
+
}
|
|
967
|
+
: x);
|
|
968
|
+
});
|
|
969
|
+
for (const l of leavesToDispose) {
|
|
970
|
+
layoutAdapter.disposeSession(l);
|
|
971
|
+
}
|
|
972
|
+
if (typeof window !== "undefined")
|
|
973
|
+
window.dispatchEvent(new Event("resize"));
|
|
974
|
+
}
|
|
975
|
+
//# sourceMappingURL=tabsActions.js.map
|