Skip to content

Commit e702764

Browse files
committed
feat(tabs): add pin/unpin support for editor and non-editor tabs
1 parent 61aca01 commit e702764

File tree

13 files changed

+342
-137
lines changed

13 files changed

+342
-137
lines changed

src/cm/commandRegistry.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,16 @@ function registerCoreCommands() {
211211
return true;
212212
},
213213
});
214+
addCommand({
215+
name: "togglePinnedTab",
216+
description: "Pin or unpin current tab",
217+
readOnly: true,
218+
requiresView: false,
219+
run() {
220+
acode.exec("toggle-pin-tab");
221+
return true;
222+
},
223+
});
214224
addCommand({
215225
name: "newFile",
216226
description: "Create new file",

src/components/terminal/terminalManager.js

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class TerminalManager {
7373
sessions.push({
7474
pid: entry,
7575
name: `Terminal ${entry}`,
76+
pinned: false,
7677
});
7778
changed = true;
7879
continue;
@@ -88,12 +89,13 @@ class TerminalManager {
8889
typeof entry.name === "string" && entry.name.trim()
8990
? entry.name.trim()
9091
: `Terminal ${pid}`;
92+
const pinned = entry.pinned === true;
9193

92-
if (entry.pid !== pid || entry.name !== name) {
94+
if (entry.pid !== pid || entry.name !== name || entry.pinned !== pinned) {
9395
changed = true;
9496
}
9597

96-
sessions.push({ pid, name });
98+
sessions.push({ pid, name, pinned });
9799
}
98100

99101
for (const session of sessions) {
@@ -109,6 +111,7 @@ class TerminalManager {
109111
typeof session.name === "string" && session.name.trim()
110112
? session.name.trim()
111113
: `Terminal ${pid}`,
114+
pinned: session.pinned === true,
112115
});
113116
}
114117

@@ -174,7 +177,7 @@ class TerminalManager {
174177
}
175178
}
176179

177-
async persistTerminalSession(pid, name) {
180+
async persistTerminalSession(pid, name, pinned = false) {
178181
if (!pid) return;
179182

180183
const pidStr = String(pid);
@@ -185,6 +188,7 @@ class TerminalManager {
185188
const sessionData = {
186189
pid: pidStr,
187190
name: name || `Terminal ${pidStr}`,
191+
pinned: pinned === true,
188192
};
189193

190194
if (existingIndex >= 0) {
@@ -228,6 +232,7 @@ class TerminalManager {
228232
const instance = await this.createServerTerminal({
229233
pid: session.pid,
230234
name: session.name,
235+
pinned: session.pinned === true,
231236
reconnecting: true,
232237
render: false,
233238
});
@@ -266,7 +271,8 @@ class TerminalManager {
266271
*/
267272
async createTerminal(options = {}) {
268273
try {
269-
const { render, serverMode, reconnecting, ...terminalOptions } = options;
274+
const { render, serverMode, reconnecting, pinned, ...terminalOptions } =
275+
options;
270276
const shouldRender = render !== false;
271277
const isServerMode = serverMode !== false;
272278
const isReconnecting = reconnecting === true;
@@ -317,6 +323,7 @@ class TerminalManager {
317323
type: "terminal",
318324
content: terminalContainer,
319325
tabIcon: "licons terminal",
326+
pinned,
320327
render: shouldRender,
321328
});
322329

@@ -363,6 +370,7 @@ class TerminalManager {
363370
await this.persistTerminalSession(
364371
terminalComponent.pid,
365372
terminalName,
373+
terminalFile.pinned,
366374
);
367375
}
368376
resolve(instance);
@@ -382,7 +390,7 @@ class TerminalManager {
382390
try {
383391
// Force remove the tab without confirmation
384392
terminalFile._skipTerminalCloseConfirm = true;
385-
terminalFile.remove(true);
393+
terminalFile.remove(true, { ignorePinned: true });
386394
} catch (removeError) {
387395
console.error("Error removing terminal tab:", removeError);
388396
}
@@ -613,10 +621,22 @@ class TerminalManager {
613621
terminalFile.onclose = () => {
614622
this.closeTerminal(terminalId);
615623
};
624+
terminalFile.onpinstatechange = (pinned) => {
625+
if (!terminalComponent.serverMode || !terminalComponent.pid) return;
626+
void this.persistTerminalSession(
627+
terminalComponent.pid,
628+
terminalFile.filename,
629+
pinned,
630+
);
631+
};
616632

617633
terminalFile._skipTerminalCloseConfirm = false;
618634
const originalRemove = terminalFile.remove.bind(terminalFile);
619-
terminalFile.remove = async (force = false) => {
635+
terminalFile.remove = async (force = false, options = {}) => {
636+
if (terminalFile.pinned && !options?.ignorePinned) {
637+
return originalRemove(force, options);
638+
}
639+
620640
if (
621641
!terminalFile._skipTerminalCloseConfirm &&
622642
this.shouldConfirmTerminalClose()
@@ -627,7 +647,7 @@ class TerminalManager {
627647
}
628648

629649
terminalFile._skipTerminalCloseConfirm = false;
630-
return originalRemove(force);
650+
return originalRemove(force, options);
631651
};
632652

633653
// Enhanced resize handling with debouncing
@@ -717,6 +737,7 @@ class TerminalManager {
717737
await this.persistTerminalSession(
718738
terminalComponent.pid,
719739
formattedTitle,
740+
terminalFile.pinned,
720741
);
721742
}
722743

@@ -744,7 +765,7 @@ class TerminalManager {
744765

745766
this.closeTerminal(terminalId);
746767
terminalFile._skipTerminalCloseConfirm = true;
747-
terminalFile.remove(true);
768+
terminalFile.remove(true, { ignorePinned: true });
748769
toast(message);
749770
};
750771

@@ -823,7 +844,7 @@ class TerminalManager {
823844
if (removeTab && terminal.file) {
824845
try {
825846
terminal.file._skipTerminalCloseConfirm = true;
826-
terminal.file.remove(true);
847+
terminal.file.remove(true, { ignorePinned: true });
827848
} catch (removeError) {
828849
console.error("Error removing terminal tab:", removeError);
829850
}

src/handlers/editorFileTab.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ function getClientPos(e) {
283283
* @param {HTMLElement} $parent
284284
*/
285285
function updateFileList($parent) {
286+
const pinnedCount = editorManager.files.filter((file) => file.pinned).length;
286287
const children = [...$parent.children];
287288
const newFileList = [];
288289
for (let el of children) {
@@ -295,6 +296,26 @@ function updateFileList($parent) {
295296
}
296297

297298
editorManager.files = newFileList;
299+
300+
const draggedFile = newFileList.find((file) => file.tab === $tab);
301+
if (draggedFile) {
302+
const draggedIndex = newFileList.indexOf(draggedFile);
303+
let nextPinnedState;
304+
305+
if (!draggedFile.pinned && draggedIndex < pinnedCount) {
306+
nextPinnedState = true;
307+
} else if (draggedFile.pinned && draggedIndex >= pinnedCount) {
308+
nextPinnedState = false;
309+
}
310+
311+
if (nextPinnedState !== undefined) {
312+
draggedFile.setPinnedState(nextPinnedState, { reorder: false });
313+
}
314+
}
315+
316+
if (typeof editorManager.normalizePinnedTabOrder === "function") {
317+
editorManager.normalizePinnedTabOrder(editorManager.files);
318+
}
298319
}
299320

300321
/**

src/lib/commands.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,11 @@ export default {
4040
await runAllTests();
4141
},
4242
async "close-all-tabs"() {
43+
const closableFiles = editorManager.files.filter((file) => !file.pinned);
44+
if (!closableFiles.length) return;
45+
4346
let save = false;
44-
const unsavedFiles = editorManager.files.filter(
45-
(file) => file.isUnsaved,
46-
).length;
47+
const unsavedFiles = closableFiles.filter((file) => file.isUnsaved).length;
4748
if (unsavedFiles) {
4849
const confirmation = await confirm(
4950
strings["warning"],
@@ -73,15 +74,15 @@ export default {
7374
}
7475
}
7576

76-
editorManager.files.forEach(async (file) => {
77+
for (const file of [...closableFiles]) {
7778
if (save) {
7879
await file.save();
79-
file.remove();
80-
return;
80+
await file.remove(true, { silentPinned: true });
81+
continue;
8182
}
8283

83-
file.remove(true);
84-
});
84+
await file.remove(true, { silentPinned: true });
85+
}
8586
},
8687
async "save-all-changes"() {
8788
const doSave = await confirm(
@@ -97,6 +98,9 @@ export default {
9798
"close-current-tab"() {
9899
editorManager.activeFile.remove();
99100
},
101+
"toggle-pin-tab"() {
102+
editorManager.activeFile?.togglePinned?.();
103+
},
100104
console() {
101105
run(true, "inapp");
102106
},

0 commit comments

Comments
 (0)