diff --git a/src/cm/commandRegistry.js b/src/cm/commandRegistry.js index c647c8be1..5dd59adf3 100644 --- a/src/cm/commandRegistry.js +++ b/src/cm/commandRegistry.js @@ -211,6 +211,16 @@ function registerCoreCommands() { return true; }, }); + addCommand({ + name: "togglePinnedTab", + description: "Pin or unpin current tab", + readOnly: true, + requiresView: false, + run() { + acode.exec("toggle-pin-tab"); + return true; + }, + }); addCommand({ name: "newFile", description: "Create new file", diff --git a/src/components/terminal/terminalManager.js b/src/components/terminal/terminalManager.js index 174fd9b30..c6b51e222 100644 --- a/src/components/terminal/terminalManager.js +++ b/src/components/terminal/terminalManager.js @@ -73,6 +73,7 @@ class TerminalManager { sessions.push({ pid: entry, name: `Terminal ${entry}`, + pinned: false, }); changed = true; continue; @@ -88,12 +89,13 @@ class TerminalManager { typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : `Terminal ${pid}`; + const pinned = entry.pinned === true; - if (entry.pid !== pid || entry.name !== name) { + if (entry.pid !== pid || entry.name !== name || entry.pinned !== pinned) { changed = true; } - sessions.push({ pid, name }); + sessions.push({ pid, name, pinned }); } for (const session of sessions) { @@ -109,6 +111,7 @@ class TerminalManager { typeof session.name === "string" && session.name.trim() ? session.name.trim() : `Terminal ${pid}`, + pinned: session.pinned === true, }); } @@ -174,7 +177,7 @@ class TerminalManager { } } - async persistTerminalSession(pid, name) { + async persistTerminalSession(pid, name, pinned = false) { if (!pid) return; const pidStr = String(pid); @@ -185,6 +188,7 @@ class TerminalManager { const sessionData = { pid: pidStr, name: name || `Terminal ${pidStr}`, + pinned: pinned === true, }; if (existingIndex >= 0) { @@ -228,6 +232,7 @@ class TerminalManager { const instance = await this.createServerTerminal({ pid: session.pid, name: session.name, + pinned: session.pinned === true, reconnecting: true, render: false, }); @@ -266,7 +271,8 @@ class TerminalManager { */ async createTerminal(options = {}) { try { - const { render, serverMode, reconnecting, ...terminalOptions } = options; + const { render, serverMode, reconnecting, pinned, ...terminalOptions } = + options; const shouldRender = render !== false; const isServerMode = serverMode !== false; const isReconnecting = reconnecting === true; @@ -317,6 +323,7 @@ class TerminalManager { type: "terminal", content: terminalContainer, tabIcon: "licons terminal", + pinned, render: shouldRender, }); @@ -363,6 +370,7 @@ class TerminalManager { await this.persistTerminalSession( terminalComponent.pid, terminalName, + terminalFile.pinned, ); } resolve(instance); @@ -382,7 +390,7 @@ class TerminalManager { try { // Force remove the tab without confirmation terminalFile._skipTerminalCloseConfirm = true; - terminalFile.remove(true); + terminalFile.remove(true, { ignorePinned: true }); } catch (removeError) { console.error("Error removing terminal tab:", removeError); } @@ -613,10 +621,22 @@ class TerminalManager { terminalFile.onclose = () => { this.closeTerminal(terminalId); }; + terminalFile.onpinstatechange = (pinned) => { + if (!terminalComponent.serverMode || !terminalComponent.pid) return; + void this.persistTerminalSession( + terminalComponent.pid, + terminalFile.filename, + pinned, + ); + }; terminalFile._skipTerminalCloseConfirm = false; const originalRemove = terminalFile.remove.bind(terminalFile); - terminalFile.remove = async (force = false) => { + terminalFile.remove = async (force = false, options = {}) => { + if (terminalFile.pinned && !options?.ignorePinned) { + return originalRemove(force, options); + } + if ( !terminalFile._skipTerminalCloseConfirm && this.shouldConfirmTerminalClose() @@ -627,7 +647,7 @@ class TerminalManager { } terminalFile._skipTerminalCloseConfirm = false; - return originalRemove(force); + return originalRemove(force, options); }; // Enhanced resize handling with debouncing @@ -717,6 +737,7 @@ class TerminalManager { await this.persistTerminalSession( terminalComponent.pid, formattedTitle, + terminalFile.pinned, ); } @@ -744,7 +765,7 @@ class TerminalManager { this.closeTerminal(terminalId); terminalFile._skipTerminalCloseConfirm = true; - terminalFile.remove(true); + terminalFile.remove(true, { ignorePinned: true }); toast(message); }; @@ -823,7 +844,7 @@ class TerminalManager { if (removeTab && terminal.file) { try { terminal.file._skipTerminalCloseConfirm = true; - terminal.file.remove(true); + terminal.file.remove(true, { ignorePinned: true }); } catch (removeError) { console.error("Error removing terminal tab:", removeError); } diff --git a/src/handlers/editorFileTab.js b/src/handlers/editorFileTab.js index 9fdc2b420..0b26d1059 100644 --- a/src/handlers/editorFileTab.js +++ b/src/handlers/editorFileTab.js @@ -283,6 +283,7 @@ function getClientPos(e) { * @param {HTMLElement} $parent */ function updateFileList($parent) { + const pinnedCount = editorManager.files.filter((file) => file.pinned).length; const children = [...$parent.children]; const newFileList = []; for (let el of children) { @@ -295,6 +296,25 @@ function updateFileList($parent) { } editorManager.files = newFileList; + + const draggedFile = newFileList.find((file) => file.tab === $tab); + if (draggedFile) { + const draggedIndex = newFileList.indexOf(draggedFile); + let nextPinnedState; + + if (!draggedFile.pinned && draggedIndex < pinnedCount) { + nextPinnedState = true; + } else if (draggedFile.pinned && draggedIndex >= pinnedCount) { + nextPinnedState = false; + } + + if (nextPinnedState !== undefined) { + draggedFile.setPinnedState(nextPinnedState, { reorder: false }); + if (typeof editorManager.normalizePinnedTabOrder === "function") { + editorManager.normalizePinnedTabOrder(editorManager.files); + } + } + } } /** diff --git a/src/lang/ar-ye.json b/src/lang/ar-ye.json index 372e72c9d..e3a44699e 100644 --- a/src/lang/ar-ye.json +++ b/src/lang/ar-ye.json @@ -699,5 +699,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/be-by.json b/src/lang/be-by.json index 7ca60c9e5..85d8daf5d 100644 --- a/src/lang/be-by.json +++ b/src/lang/be-by.json @@ -701,5 +701,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/bn-bd.json b/src/lang/bn-bd.json index 2bdaf826c..1a14bd20f 100644 --- a/src/lang/bn-bd.json +++ b/src/lang/bn-bd.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/cs-cz.json b/src/lang/cs-cz.json index 0d58f8e7f..cb2e38fa2 100644 --- a/src/lang/cs-cz.json +++ b/src/lang/cs-cz.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/de-de.json b/src/lang/de-de.json index b372afa1a..fb78ad977 100644 --- a/src/lang/de-de.json +++ b/src/lang/de-de.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/en-us.json b/src/lang/en-us.json index 60b01d2c8..54c215971 100644 --- a/src/lang/en-us.json +++ b/src/lang/en-us.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/es-sv.json b/src/lang/es-sv.json index 43f35aaf9..87affdfa9 100644 --- a/src/lang/es-sv.json +++ b/src/lang/es-sv.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/fr-fr.json b/src/lang/fr-fr.json index e91c7bf02..ae9caa742 100644 --- a/src/lang/fr-fr.json +++ b/src/lang/fr-fr.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/he-il.json b/src/lang/he-il.json index 372ba4fd9..e6c102149 100644 --- a/src/lang/he-il.json +++ b/src/lang/he-il.json @@ -701,5 +701,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/hi-in.json b/src/lang/hi-in.json index ed5e3a2b7..ecd15cb9a 100644 --- a/src/lang/hi-in.json +++ b/src/lang/hi-in.json @@ -701,5 +701,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/hu-hu.json b/src/lang/hu-hu.json index df187a5f2..375cba0dd 100644 --- a/src/lang/hu-hu.json +++ b/src/lang/hu-hu.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Rendeljen formázót minden nyelvhez. További lehetőségek feloldásához telepítsen formázó bővítményeket.", "settings-note-lsp-settings": "A nyelvi kiszolgálók automatikus kiegészítést, diagnosztikát, felugró részleteket és egyebeket nyújtanak. Itt telepíthet, frissíthet vagy definiálhat egyéni kiszolgálókat. A felügyelt telepítők a terminál/proot környezetben futnak.", "search result label singular": "találat", - "search result label plural": "találat" + "search result label plural": "találat", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/id-id.json b/src/lang/id-id.json index e20a2e2a7..23d6c64a6 100644 --- a/src/lang/id-id.json +++ b/src/lang/id-id.json @@ -701,5 +701,9 @@ "settings-note-formatter-settings": "Tetapkan pemformat untuk setiap bahasa. Pasang plugin pemformat untuk membuka lebih banyak opsi.", "settings-note-lsp-settings": "Server bahasa menambahkan fitur pelengkapan otomatis, diagnostik, detail saat kursor diarahkan ke teks, dan banyak lagi. Anda dapat memasang, memperbarui, atau menentukan server khusus di sini. Penginstal terkelola berjalan di dalam lingkungan terminal/proot.", "search result label singular": "hasil", - "search result label plural": "hasil" + "search result label plural": "hasil", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/ir-fa.json b/src/lang/ir-fa.json index c511ee56e..ad461280b 100644 --- a/src/lang/ir-fa.json +++ b/src/lang/ir-fa.json @@ -701,5 +701,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/it-it.json b/src/lang/it-it.json index f75bc6876..4f8fb6102 100644 --- a/src/lang/it-it.json +++ b/src/lang/it-it.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/ja-jp.json b/src/lang/ja-jp.json index 203ade0a6..e38ea3923 100644 --- a/src/lang/ja-jp.json +++ b/src/lang/ja-jp.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/ko-kr.json b/src/lang/ko-kr.json index 8d26db543..2d5b1d148 100644 --- a/src/lang/ko-kr.json +++ b/src/lang/ko-kr.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/ml-in.json b/src/lang/ml-in.json index 3c6d5a0df..77e11e892 100644 --- a/src/lang/ml-in.json +++ b/src/lang/ml-in.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/mm-unicode.json b/src/lang/mm-unicode.json index a390806bf..2cc0185d2 100644 --- a/src/lang/mm-unicode.json +++ b/src/lang/mm-unicode.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/mm-zawgyi.json b/src/lang/mm-zawgyi.json index 6de4e7239..3ad486c57 100644 --- a/src/lang/mm-zawgyi.json +++ b/src/lang/mm-zawgyi.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/pl-pl.json b/src/lang/pl-pl.json index 4943fb2bd..03a03aa44 100644 --- a/src/lang/pl-pl.json +++ b/src/lang/pl-pl.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/pt-br.json b/src/lang/pt-br.json index 28042037d..21cee3021 100644 --- a/src/lang/pt-br.json +++ b/src/lang/pt-br.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/pu-in.json b/src/lang/pu-in.json index 5351c6ec0..642c62d23 100644 --- a/src/lang/pu-in.json +++ b/src/lang/pu-in.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/ru-ru.json b/src/lang/ru-ru.json index 1144466e8..d91a576f7 100644 --- a/src/lang/ru-ru.json +++ b/src/lang/ru-ru.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/tl-ph.json b/src/lang/tl-ph.json index f282bc8f5..71c5b4d59 100644 --- a/src/lang/tl-ph.json +++ b/src/lang/tl-ph.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/tr-tr.json b/src/lang/tr-tr.json index e44b2f55e..509710e81 100644 --- a/src/lang/tr-tr.json +++ b/src/lang/tr-tr.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/uk-ua.json b/src/lang/uk-ua.json index 708b4e1ec..fb3f9844a 100644 --- a/src/lang/uk-ua.json +++ b/src/lang/uk-ua.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Призначте форматувальник для кожної мови. Встановіть плагіни форматувальників, щоб розблокувати більше опцій.", "settings-note-lsp-settings": "Мовні сервери додають автозаповнення, діагностику, деталі при наведенні курсора тощо. Тут ви можете встановлювати, оновлювати або визначати власні сервери. Керовані інсталятори працюють у середовищі терміналу/proot.", "search result label singular": "результат", - "search result label plural": "результати" + "search result label plural": "результати", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/uz-uz.json b/src/lang/uz-uz.json index 543e28ad2..4550d1b4b 100644 --- a/src/lang/uz-uz.json +++ b/src/lang/uz-uz.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/vi-vn.json b/src/lang/vi-vn.json index a050f94cf..9949d4396 100644 --- a/src/lang/vi-vn.json +++ b/src/lang/vi-vn.json @@ -701,5 +701,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/zh-cn.json b/src/lang/zh-cn.json index 345db85f1..93a16935d 100644 --- a/src/lang/zh-cn.json +++ b/src/lang/zh-cn.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "为每种语言分配格式化器。安装格式化插件可解锁更多选项。", "settings-note-lsp-settings": "语言服务器提供补全、诊断、悬停等功能。你可以在此安装、更新或定义自定义服务器。托管安装程序在终端/proot 环境中运行。", "search result label singular": "条结果", - "search result label plural": "条结果" + "search result label plural": "条结果", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/zh-hant.json b/src/lang/zh-hant.json index 9dd9d6b54..1ce79ff9d 100644 --- a/src/lang/zh-hant.json +++ b/src/lang/zh-hant.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "為每種語言分配格式化器。安裝格式化插件可解鎖更多選項。", "settings-note-lsp-settings": "語言服務器提供補全、診斷、懸停等功能。你可以在此安裝、更新或定義自定義服務器。託管安裝程序在終端/proot 環境中運行。", "search result label singular": "條結果", - "search result label plural": "條結果" + "search result label plural": "條結果", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lang/zh-tw.json b/src/lang/zh-tw.json index 346f1949f..0e53a4909 100644 --- a/src/lang/zh-tw.json +++ b/src/lang/zh-tw.json @@ -700,5 +700,9 @@ "settings-note-formatter-settings": "Assign a formatter to each language. Install formatter plugins to unlock more options.", "settings-note-lsp-settings": "Language servers add autocomplete, diagnostics, hover details, and more. You can install, update, or define custom servers here. Managed installers run inside the terminal/proot environment.", "search result label singular": "result", - "search result label plural": "results" + "search result label plural": "results", + "pin tab": "Pin tab", + "unpin tab": "Unpin tab", + "pinned tab": "Pinned tab", + "unpin tab before closing": "Unpin the tab before closing it." } diff --git a/src/lib/commands.js b/src/lib/commands.js index 960baf828..054f40f8f 100644 --- a/src/lib/commands.js +++ b/src/lib/commands.js @@ -40,10 +40,11 @@ export default { await runAllTests(); }, async "close-all-tabs"() { + const closableFiles = editorManager.files.filter((file) => !file.pinned); + if (!closableFiles.length) return; + let save = false; - const unsavedFiles = editorManager.files.filter( - (file) => file.isUnsaved, - ).length; + const unsavedFiles = closableFiles.filter((file) => file.isUnsaved).length; if (unsavedFiles) { const confirmation = await confirm( strings["warning"], @@ -73,15 +74,15 @@ export default { } } - editorManager.files.forEach(async (file) => { + for (const file of [...closableFiles]) { if (save) { await file.save(); - file.remove(); - return; + await file.remove(true, { silentPinned: true }); + continue; } - file.remove(true); - }); + await file.remove(true, { silentPinned: true }); + } }, async "save-all-changes"() { const doSave = await confirm( @@ -97,6 +98,9 @@ export default { "close-current-tab"() { editorManager.activeFile.remove(); }, + "toggle-pin-tab"() { + editorManager.activeFile?.togglePinned?.(); + }, console() { run(true, "inapp"); }, diff --git a/src/lib/editorFile.js b/src/lib/editorFile.js index cce47eb4c..a6ac24765 100644 --- a/src/lib/editorFile.js +++ b/src/lib/editorFile.js @@ -10,6 +10,7 @@ import { import { getMode, getModeForPath } from "cm/modelist"; import Sidebar from "components/sidebar"; import tile from "components/tile"; +import toast from "components/toast"; import confirm from "dialogs/confirm"; import DOMPurify from "dompurify"; import startDrag from "handlers/editorFileTab"; @@ -227,6 +228,7 @@ function createSessionProxy(state, file) { * @property {number} [scrollLeft] scroll left * @property {number} [scrollTop] scroll top * @property {Array} [folds] folds + * @property {boolean} [pinned] pin the tab to prevent accidental closing */ export default class EditorFile { @@ -332,6 +334,11 @@ export default class EditorFile { * @type {boolean} */ #editable = true; + /** + * Prevents the tab from being closed until it is unpinned. + * @type {boolean} + */ + #pinned = false; /** * contains information about cursor position, scroll left, scroll top, folds. */ @@ -378,6 +385,7 @@ export default class EditorFile { onchangemode; onrun; oncanrun; + onpinstatechange; /** * @@ -524,6 +532,7 @@ export default class EditorFile { this.#onFilePosChange(); this.#tab.addEventListener("click", tabOnclick.bind(this)); appSettings.on("update:openFileListPos", this.#onFilePosChange); + this.pinned = !!options?.pinned; addFile(this); editorManager.emit("new-file", this); @@ -759,6 +768,39 @@ export default class EditorFile { this.#updateTab(); } + get pinned() { + return this.#pinned; + } + + set pinned(value) { + this.setPinnedState(value); + } + + setPinnedState(value, options = {}) { + const { reorder = false, emit = true } = options; + value = !!value; + if (this.#pinned === value) return value; + + this.#pinned = value; + this.#updateTab(); + this.onpinstatechange?.(value); + + if (editorManager.files.includes(this) && reorder) { + editorManager.moveFileByPinnedState?.(this); + } + + if (emit) { + editorManager.onupdate("pin-tab"); + editorManager.emit("update", "pin-tab", this); + } + + return value; + } + + togglePinned() { + return this.setPinnedState(!this.pinned); + } + /** * DON'T remove, plugin need this property to get filename. */ @@ -938,18 +980,29 @@ export default class EditorFile { * Remove and closes the file. * @param {boolean} force if true, will prompt to save the file */ - async remove(force = false) { + async remove(force = false, options = {}) { + const { ignorePinned = false, silentPinned = false } = options || {}; + if ( this.id === constants.DEFAULT_FILE_SESSION && !editorManager.files.length ) - return; + return false; + if (this.pinned && !ignorePinned) { + if (!silentPinned) { + toast( + strings["unpin tab before closing"] || + "Unpin the tab before closing it.", + ); + } + return false; + } if (!force && this.isUnsaved) { const confirmation = await confirm( strings.warning.toUpperCase(), strings["unsaved file"], ); - if (!confirmation) return; + if (!confirmation) return false; } this.#destroy(); @@ -970,6 +1023,7 @@ export default class EditorFile { } editorManager.onupdate("remove-file"); editorManager.emit("remove-file", this); + return true; } /** @@ -1380,7 +1434,7 @@ export default class EditorFile { }, 0); } catch (error) { this.#emit("loaderror", createFileEvent(this)); - this.remove(); + this.remove(false, { ignorePinned: true }); toast(`Unable to load: ${this.filename}`); window.log("error", "Unable to load: " + this.filename); window.log("error", error); @@ -1418,11 +1472,16 @@ export default class EditorFile { } #updateTab() { + if (!this.#tab) return; + if (this.#isUnsaved) { this.tab.classList.add("notice"); } else { this.tab.classList.remove("notice"); } + + this.tab.classList.toggle("pinned", this.#pinned); + this.#tab.tail(this.#createTabTail()); } /** @@ -1458,6 +1517,25 @@ export default class EditorFile { toast(strings["no app found to handle this file"]); } + #createTabTail() { + if (!this.#pinned) { + return tag("span", { + className: "icon cancel", + dataset: { + action: "close-file", + }, + }); + } + + return tag("span", { + className: "licons pin", + title: strings["unpin tab"] || "Unpin tab", + dataset: { + action: "toggle-pin", + }, + }); + } + #getTitle() { // Use custom title function if provided if (this.#customTitleFn) { @@ -1506,6 +1584,10 @@ function tabOnclick(e) { this.remove(); return; } + if (action === "toggle-pin") { + this.togglePinned(); + return; + } this.makeActive(); } diff --git a/src/lib/editorManager.js b/src/lib/editorManager.js index 6fc6798ad..05810ddc6 100644 --- a/src/lib/editorManager.js +++ b/src/lib/editorManager.js @@ -1335,6 +1335,9 @@ async function EditorManager($header, $body) { readOnlyCompartment, getFile, switchFile, + moveFileByPinnedState, + normalizePinnedTabOrder, + syncOpenFileList, hasUnsavedFiles, getEditorHeight, getEditorWidth, @@ -1765,12 +1768,54 @@ async function EditorManager($header, $body) { */ function addFile(file) { if (manager.files.includes(file)) return; - manager.files.push(file); - manager.openFileList.append(file.tab); + const insertAt = file.pinned + ? getPinnedInsertIndex() + : manager.files.length; + manager.files.splice(insertAt, 0, file); + syncOpenFileList(); $header.text = file.name; toggleProblemButton(); } + function getPinnedInsertIndex(skipFile = null) { + return manager.files.reduce((count, file) => { + if (file === skipFile) return count; + return count + (file.pinned ? 1 : 0); + }, 0); + } + + function syncOpenFileList() { + const $list = manager.openFileList; + manager.files.forEach((file) => { + $list.append(file.tab); + }); + } + + function moveFileByPinnedState(file) { + if (!manager.files.includes(file)) return; + if (manager.activeFile?.id === file.id) { + file.tab.scrollIntoView(); + } + } + + function normalizePinnedTabOrder(nextFiles = manager.files) { + const pinnedFiles = []; + const regularFiles = []; + + nextFiles.forEach((file) => { + if (file.pinned) { + pinnedFiles.push(file); + return; + } + regularFiles.push(file); + }); + + manager.files = [...pinnedFiles, ...regularFiles]; + syncOpenFileList(); + + return manager.files; + } + /** * Sets up the editor with various configurations and event listeners. * @returns {Promise} A promise that resolves once the editor is set up. diff --git a/src/lib/saveState.js b/src/lib/saveState.js index 9aaa6d7d7..a6ba3c1d4 100644 --- a/src/lib/saveState.js +++ b/src/lib/saveState.js @@ -54,6 +54,7 @@ export default () => { uri: file.uri, type: file.type, filename: file.filename, + pinned: file.pinned, isUnsaved: file.isUnsaved, readOnly: file.readOnly, SAFMode: file.SAFMode, diff --git a/src/main.js b/src/main.js index 2918698f6..2b641d20f 100644 --- a/src/main.js +++ b/src/main.js @@ -757,6 +757,10 @@ function createFileMenu({ top, bottom, toggler }) { const hasSelection = !!cmEditor && !cmEditor.state.selection.main.empty; return mustache.render($_fileMenu, { ...strings, + toggle_pin_tab_text: file.pinned + ? strings["unpin tab"] || "Unpin tab" + : strings["pin tab"] || "Pin tab", + toggle_pin_tab_icon: file.pinned ? "licons pin-off" : "licons pin", // Use CodeMirror mode stored on EditorFile (set in setMode) file_mode: isEditorFile ? file.currentMode || "" : "", file_encoding: isEditorFile ? encoding : "", diff --git a/src/res/icons/li-icon.ttf b/src/res/icons/li-icon.ttf index 5b218bf3c..98da9dbe2 100644 Binary files a/src/res/icons/li-icon.ttf and b/src/res/icons/li-icon.ttf differ diff --git a/src/res/icons/style.css b/src/res/icons/style.css index c48079160..76c7aef3f 100644 --- a/src/res/icons/style.css +++ b/src/res/icons/style.css @@ -43,6 +43,14 @@ -moz-osx-font-smoothing: grayscale; } +.licons.pin-off:before { + content: "\f007"; +} + +.licons.pin:before { + content: "\f008"; +} + .licons.lightbulb:before { content: "\f004"; } diff --git a/src/views/file-menu.hbs b/src/views/file-menu.hbs index 555c008a5..e0ed79b39 100644 --- a/src/views/file-menu.hbs +++ b/src/views/file-menu.hbs @@ -1,114 +1,119 @@ -
  • - {{file properties}} - -
  • -
    -
  • - {{rename}} -
  • -{{#is_editor}} -
  • -
    - {{syntax highlighting}} - {{file_mode}} -
    -
  • -
  • -
    - {{encoding}} - {{file_encoding}} -
    -
  • -
  • -
    - {{new line mode}} - {{file_eol}} -
    -
  • -{{/is_editor}} - -{{#is_editor}} -
  • -
    - {{read only}} -
    - -
  • -
  • -
    - {{format}} -
    -
  • -{{#has_lsp_servers}} -
  • - LSP Info - -
  • -{{/has_lsp_servers}} -{{/is_editor}} -
    -{{#file_on_disk}} -
  • - {{share}} - -
  • -
  • - {{open with}} - -
  • -
  • - {{edit with}} - -
  • -
  • - {{add to home screen}} - -
  • -{{/file_on_disk}} - -{{#is_editor}} -
    -
  • - {{search}} - -
  • -
  • - {{goto}} - -
  • -
    -{{^file_read_only}} -
  • - {{insert color}} - -
  • -
    -{{#copy_text}} -
  • -
    - {{cut}} -
    -
  • -{{/copy_text}} -
  • -
    - {{paste}} -
    -
  • -{{/file_read_only}} -{{#copy_text}} -
  • -
    - {{copy}} -
    -
  • -{{/copy_text}} -
  • -
    - {{select all}} -
    -
  • -{{/is_editor}} +
  • + {{file properties}} + +
  • +
    +
  • + {{rename}} +
  • +{{#is_editor}} +
  • +
    + {{syntax highlighting}} + {{file_mode}} +
    +
  • +
  • +
    + {{encoding}} + {{file_encoding}} +
    +
  • +
  • +
    + {{new line mode}} + {{file_eol}} +
    +
  • +{{/is_editor}} + +{{#is_editor}} +
  • +
    + {{read only}} +
    + +
  • +
  • +
    + {{format}} +
    +
  • +{{#has_lsp_servers}} +
  • + LSP Info + +
  • +{{/has_lsp_servers}} +{{/is_editor}} +
    +{{#file_on_disk}} +
  • + {{share}} + +
  • +
  • + {{open with}} + +
  • +
  • + {{edit with}} + +
  • +
  • + {{add to home screen}} + +
  • +{{/file_on_disk}} + +
  • + {{toggle_pin_tab_text}} + +
  • + +{{#is_editor}} +
    +
  • + {{search}} + +
  • +
  • + {{goto}} + +
  • +
    +{{^file_read_only}} +
  • + {{insert color}} + +
  • +
    +{{#copy_text}} +
  • +
    + {{cut}} +
    +
  • +{{/copy_text}} +
  • +
    + {{paste}} +
    +
  • +{{/file_read_only}} +{{#copy_text}} +
  • +
    + {{copy}} +
    +
  • +{{/copy_text}} +
  • +
    + {{select all}} +
    +
  • +{{/is_editor}} diff --git a/utils/extra-icons/pin-off.svg b/utils/extra-icons/pin-off.svg new file mode 100644 index 000000000..b12ebae14 --- /dev/null +++ b/utils/extra-icons/pin-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/utils/extra-icons/pin.svg b/utils/extra-icons/pin.svg new file mode 100644 index 000000000..a280a7ca3 --- /dev/null +++ b/utils/extra-icons/pin.svg @@ -0,0 +1 @@ + \ No newline at end of file