From c6e91f9c6fca42305e3740234a15405e979ce0bd Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 17 Feb 2026 17:40:43 +0530 Subject: [PATCH 01/44] feat: tabbed bottom panel initial architecture --- .../NoDistractions/main.js | 19 +- src/styles/Extn-BottomPanelTabs.less | 155 +++++++++++ src/styles/brackets.less | 1 + src/utils/Resizer.js | 8 +- src/view/WorkspaceManager.js | 256 +++++++++++++++++- 5 files changed, 425 insertions(+), 14 deletions(-) create mode 100644 src/styles/Extn-BottomPanelTabs.less diff --git a/src/extensionsIntegrated/NoDistractions/main.js b/src/extensionsIntegrated/NoDistractions/main.js index e0da1645eb..17894b8c54 100644 --- a/src/extensionsIntegrated/NoDistractions/main.js +++ b/src/extensionsIntegrated/NoDistractions/main.js @@ -89,13 +89,20 @@ define(function (require, exports, module) { function _hidePanelsIfRequired() { var panelIDs = WorkspaceManager.getAllPanelIDs(); _previouslyOpenPanelIDs = []; - panelIDs.forEach(function (panelID) { - var panel = WorkspaceManager.getPanelForID(panelID); - if (panel && panel.isVisible()) { - panel.hide(); - _previouslyOpenPanelIDs.push(panelID); + // Loop until no visible panels remain. In a tabbed system, hiding the + // active tab may reveal the next tab, so we must iterate. + let hiddenSomething = true; + while (hiddenSomething) { + hiddenSomething = false; + for (let i = 0; i < panelIDs.length; i++) { + let panel = WorkspaceManager.getPanelForID(panelIDs[i]); + if (panel && panel.isVisible()) { + panel.hide(); + _previouslyOpenPanelIDs.push(panelIDs[i]); + hiddenSomething = true; + } } - }); + } } /** diff --git a/src/styles/Extn-BottomPanelTabs.less b/src/styles/Extn-BottomPanelTabs.less new file mode 100644 index 0000000000..f9475ecbca --- /dev/null +++ b/src/styles/Extn-BottomPanelTabs.less @@ -0,0 +1,155 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2021 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/* Bottom panel tab bar — switches between tabbed bottom panels */ + +#bottom-panel-container { + background-color: @bc-panel-bg; + border-top: 1px solid @bc-panel-border; + display: flex; + flex-direction: column; + + .dark & { + background-color: @dark-bc-panel-bg; + border-top: 1px solid @dark-bc-panel-border; + } + + .bottom-panel { + display: none !important; + flex: 1; + min-height: 0; + border-top: none; + height: auto !important; + + &.active-bottom-panel { + display: flex !important; + flex-direction: column; + } + } +} + +#bottom-panel-tab-bar { + display: flex; + align-items: center; + height: 28px; + min-height: 28px; + background-color: @bc-panel-bg-promoted; + border-bottom: @bc-panel-separator; + overflow-x: auto; + overflow-y: hidden; + user-select: none; + box-shadow: inset 0 1px 0 @bc-highlight-hard, 0 -1px 3px @bc-shadow-small; + + .dark & { + background-color: @dark-bc-panel-bg-promoted; + border-bottom: @dark-bc-panel-separator; + box-shadow: inset 0 1px 0 @dark-bc-highlight, 0 -1px 3px @dark-bc-shadow-small; + } + + /* Hide scrollbar but allow scrolling */ + &::-webkit-scrollbar { + display: none; + } + -ms-overflow-style: none; + scrollbar-width: none; +} + +.bottom-panel-tab { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 0 10px; + height: 100%; + cursor: pointer; + position: relative; + color: @bc-text-quiet; + font-size: 12px; + white-space: nowrap; + flex-shrink: 0; + transition: color 0.15s ease, background-color 0.15s ease; + + .dark & { + color: @dark-bc-text-quiet; + } + + &:hover { + color: @bc-text; + background-color: rgba(0, 0, 0, 0.04); + + .dark & { + color: @dark-bc-text; + background-color: rgba(255, 255, 255, 0.06); + } + } + + &.active { + color: @bc-text; + background-color: @bc-panel-bg; + + .dark & { + color: @dark-bc-text; + background-color: @dark-bc-panel-bg; + } + + &::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 2px; + background-color: @bc-primary-btn-bg; + + .dark & { + background-color: @dark-bc-primary-btn-bg; + } + } + } +} + +.bottom-panel-tab-title { + pointer-events: none; +} + +.bottom-panel-tab-close { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + font-size: 14px; + line-height: 1; + border-radius: 3px; + opacity: 0; + transition: opacity 0.15s ease, background-color 0.15s ease; + + .bottom-panel-tab:hover & { + opacity: 0.6; + } + + &:hover { + opacity: 1 !important; + background-color: rgba(0, 0, 0, 0.1); + + .dark & { + background-color: rgba(255, 255, 255, 0.15); + } + } +} diff --git a/src/styles/brackets.less b/src/styles/brackets.less index a0f0c46faa..7cd694023f 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -47,6 +47,7 @@ @import "Extn-CustomSnippets.less"; @import "Extn-CollapseFolders.less"; @import "Extn-SidebarTabs.less"; +@import "Extn-BottomPanelTabs.less"; @import "Extn-AIChatPanel.less"; @import "UserProfile.less"; @import "phoenix-pro.less"; diff --git a/src/utils/Resizer.js b/src/utils/Resizer.js index 9ae4e29886..88caab5411 100644 --- a/src/utils/Resizer.js +++ b/src/utils/Resizer.js @@ -411,8 +411,9 @@ define(function (require, exports, module) { if(initialSize){ elementSize = elementPrefs.size || initialSize; } - if(elementSize minSize for bottom panels */ + let _panelMinSizes = {}; + + /** Fallback title map for panels that don't have a .toolbar .title element */ + const _KNOWN_PANEL_TITLES = { + "errors": "Problems", + "problems-panel": "Problems", + "search-results": "Search Results", + "find-in-files-results": "Search Results", + "references-panel": "References", + "git-panel": "Git", + "keyboard-shortcuts-panel": "Keyboard Shortcuts", + "custom-snippets-panel": "Custom Snippets", + "test-builder-panel": "Test Builder" + }; + /** * Calculates the available height for the full-size Editor (or the no-editor placeholder), @@ -226,6 +256,104 @@ define(function (require, exports, module) { }); } + // --- Bottom panel tab helpers --- + + /** + * Resolve the display title for a bottom panel tab. + * First looks for a .toolbar .title element within the panel DOM, + * then falls back to the _KNOWN_PANEL_TITLES map using the panel's DOM id. + * @param {string} id The panel registration ID + * @param {jQueryObject} $panel The panel's jQuery element + * @return {string} + * @private + */ + function _getPanelTitle(id, $panel) { + let $titleEl = $panel.find(".toolbar .title"); + if ($titleEl.length && $.trim($titleEl.text())) { + return $.trim($titleEl.text()); + } + let domId = $panel.attr("id") || ""; + if (_KNOWN_PANEL_TITLES[domId]) { + return _KNOWN_PANEL_TITLES[domId]; + } + if (_KNOWN_PANEL_TITLES[id]) { + return _KNOWN_PANEL_TITLES[id]; + } + // Last resort: humanize the id + let label = (domId || id).replace(/[-_]/g, " "); + return label.charAt(0).toUpperCase() + label.slice(1); + } + + /** + * Rebuild the tab bar DOM from _openBottomPanelIds. + * @private + */ + function _updateBottomPanelTabBar() { + if (!$bottomPanelTabBar) { + return; + } + $bottomPanelTabBar.empty(); + _openBottomPanelIds.forEach(function (panelId) { + let panel = panelIDMap[panelId]; + if (!panel) { + return; + } + let title = panel._tabTitle || _getPanelTitle(panelId, panel.$panel); + let isActive = (panelId === _activeBottomPanelId); + let $tab = $('
' + + '' + $("").text(title).html() + '' + + '×' + + '
'); + $bottomPanelTabBar.append($tab); + }); + } + + /** + * Update the container's dynamic minSize to match the active panel's minSize. + * @private + */ + function _updateContainerMinSize() { + if (!$bottomPanelContainer) { + return; + } + let activeMinSize = _panelMinSizes[_activeBottomPanelId] || 100; + // Add tab bar height (28px) to the panel's minSize + $bottomPanelContainer.data("currentMinSize", activeMinSize + 28); + } + + /** + * Switch the active tab to the given panel. Does not show/hide the container. + * @param {string} panelId + * @private + */ + function _switchToTab(panelId) { + if (_activeBottomPanelId === panelId) { + return; + } + // Remove active class from current + if (_activeBottomPanelId) { + let prevPanel = panelIDMap[_activeBottomPanelId]; + if (prevPanel) { + prevPanel.$panel.removeClass("active-bottom-panel"); + } + } + // Set new active + _activeBottomPanelId = panelId; + let newPanel = panelIDMap[panelId]; + if (newPanel) { + newPanel.$panel.addClass("active-bottom-panel"); + } + _updateContainerMinSize(); + _updateBottomPanelTabBar(); + } + + /** + * Returns a copy of the currently open bottom panel IDs in tab order. + * @return {string[]} + */ + function getOpenBottomPanelIDs() { + return _openBottomPanelIds.slice(); + } /** * Creates a new resizable panel beneath the editor area and above the status bar footer. Panel is initially invisible. @@ -238,16 +366,102 @@ define(function (require, exports, module) { * @return {!Panel} */ function createBottomPanel(id, $panel, minSize) { - $panel.insertBefore("#status-bar"); + // Insert panel into the tabbed container instead of before #status-bar + $bottomPanelContainer.append($panel); $panel.hide(); - updateResizeLimits(); // initialize panel's max size + updateResizeLimits(); + + // Store minSize for dynamic container minSize + _panelMinSizes[id] = minSize || 100; let bottomPanel = new PanelView.Panel($panel, id); panelIDMap[id] = bottomPanel; - Resizer.makeResizable($panel[0], Resizer.DIRECTION_VERTICAL, Resizer.POSITION_TOP, minSize, - false, undefined, true); - listenToResize($panel); + // Cache the tab title at creation time + bottomPanel._tabTitle = _getPanelTitle(id, $panel); + + // Do NOT call Resizer.makeResizable on individual panels. + // The container handles resizing. + + // --- Override show/hide/isVisible on the Panel instance --- + + bottomPanel.show = function () { + if (!this.canBeShown()) { + return; + } + let panelId = this.panelID; + let isOpen = _openBottomPanelIds.indexOf(panelId) !== -1; + let isActive = (_activeBottomPanelId === panelId); + + if (isOpen && isActive) { + // Already open and active - no-op + return; + } + if (isOpen && !isActive) { + // Open but not active - just switch tab + _switchToTab(panelId); + PanelView.trigger(PanelView.EVENT_PANEL_SHOWN, panelId); + triggerUpdateLayout(); + return; + } + // Not open: add to open set + _openBottomPanelIds.push(panelId); + + // Show container if it was hidden + if (!$bottomPanelContainer.is(":visible")) { + Resizer.show($bottomPanelContainer[0]); + } + + _switchToTab(panelId); + PanelView.trigger(PanelView.EVENT_PANEL_SHOWN, panelId); + triggerUpdateLayout(); + }; + + bottomPanel.hide = function () { + let panelId = this.panelID; + let idx = _openBottomPanelIds.indexOf(panelId); + if (idx === -1) { + // Not open - no-op + return; + } + + // Remove from open set + _openBottomPanelIds.splice(idx, 1); + this.$panel.removeClass("active-bottom-panel"); + + let wasActive = (_activeBottomPanelId === panelId); + + if (wasActive) { + if (_openBottomPanelIds.length > 0) { + // Activate the next tab (or previous if this was the last) + let nextIdx = Math.min(idx, _openBottomPanelIds.length - 1); + let nextId = _openBottomPanelIds[nextIdx]; + _activeBottomPanelId = null; // clear so _switchToTab runs + _switchToTab(nextId); + PanelView.trigger(PanelView.EVENT_PANEL_SHOWN, nextId); + } else { + // No more tabs - hide the container + _activeBottomPanelId = null; + Resizer.hide($bottomPanelContainer[0]); + _updateBottomPanelTabBar(); + } + } else { + _updateBottomPanelTabBar(); + } + + PanelView.trigger(PanelView.EVENT_PANEL_HIDDEN, panelId); + triggerUpdateLayout(); + }; + + bottomPanel.isVisible = function () { + return (_activeBottomPanelId === this.panelID) && + $bottomPanelContainer.is(":visible"); + }; + + bottomPanel.setTitle = function (newTitle) { + this._tabTitle = newTitle; + _updateBottomPanelTabBar(); + }; return bottomPanel; } @@ -324,6 +538,37 @@ define(function (require, exports, module) { $mainPluginPanel = $("#main-plugin-panel"); $pluginIconsBar = $("#plugin-icons-bar"); + // --- Create the bottom panel tabbed container --- + $bottomPanelContainer = $('
'); + $bottomPanelTabBar = $('
'); + $bottomPanelContainer.append($bottomPanelTabBar); + $bottomPanelContainer.insertBefore("#status-bar"); + $bottomPanelContainer.hide(); + + // Make the container resizable (not individual panels) + Resizer.makeResizable($bottomPanelContainer[0], Resizer.DIRECTION_VERTICAL, Resizer.POSITION_TOP, + 100, false, undefined, true); + listenToResize($bottomPanelContainer); + + // Tab bar click handlers + $bottomPanelTabBar.on("click", ".bottom-panel-tab-close", function (e) { + e.stopPropagation(); + let panelId = $(this).closest(".bottom-panel-tab").data("panel-id"); + let panel = panelIDMap[panelId]; + if (panel) { + panel.hide(); + } + }); + + $bottomPanelTabBar.on("click", ".bottom-panel-tab", function (e) { + let panelId = $(this).data("panel-id"); + if (panelId && panelId !== _activeBottomPanelId) { + _switchToTab(panelId); + PanelView.trigger(PanelView.EVENT_PANEL_SHOWN, panelId); + triggerUpdateLayout(); + } + }); + // Sidebar is a special case: it isn't a Panel, and is not created dynamically. Need to explicitly // listen for resize here. listenToResize($("#sidebar")); @@ -572,6 +817,7 @@ define(function (require, exports, module) { exports.recomputeLayout = recomputeLayout; exports.getAllPanelIDs = getAllPanelIDs; exports.getPanelForID = getPanelForID; + exports.getOpenBottomPanelIDs = getOpenBottomPanelIDs; exports.addEscapeKeyEventHandler = addEscapeKeyEventHandler; exports.removeEscapeKeyEventHandler = removeEscapeKeyEventHandler; exports._setMockDOM = _setMockDOM; From 411b9a6946e14851e079b148487e9b50da5685fe Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 17 Feb 2026 18:33:43 +0530 Subject: [PATCH 02/44] feat: improve bottom panel tab bar styling --- src/styles/Extn-BottomPanelTabs.less | 112 +++++++++++++++++---------- src/view/WorkspaceManager.js | 23 ++++-- 2 files changed, 85 insertions(+), 50 deletions(-) diff --git a/src/styles/Extn-BottomPanelTabs.less b/src/styles/Extn-BottomPanelTabs.less index f9475ecbca..5d48e5123d 100644 --- a/src/styles/Extn-BottomPanelTabs.less +++ b/src/styles/Extn-BottomPanelTabs.less @@ -18,7 +18,8 @@ * */ -/* Bottom panel tab bar — switches between tabbed bottom panels */ +/* Bottom panel tab bar — switches between tabbed bottom panels. + * Visual style mirrors the file tab bar (Extn-TabBar.less) for consistency. */ #bottom-panel-container { background-color: @bc-panel-bg; @@ -48,20 +49,24 @@ #bottom-panel-tab-bar { display: flex; align-items: center; - height: 28px; - min-height: 28px; - background-color: @bc-panel-bg-promoted; - border-bottom: @bc-panel-separator; - overflow-x: auto; - overflow-y: hidden; + height: 1.5rem; + min-height: 1.5rem; + background-color: #f5f5f5; + border-bottom: none; + overflow: hidden; user-select: none; - box-shadow: inset 0 1px 0 @bc-highlight-hard, 0 -1px 3px @bc-shadow-small; .dark & { - background-color: @dark-bc-panel-bg-promoted; - border-bottom: @dark-bc-panel-separator; - box-shadow: inset 0 1px 0 @dark-bc-highlight, 0 -1px 3px @dark-bc-shadow-small; + background-color: #1E1E1E; } +} + +.bottom-panel-tabs-overflow { + flex: 1; + display: flex; + overflow-x: auto; + overflow-y: hidden; + height: 100%; /* Hide scrollbar but allow scrolling */ &::-webkit-scrollbar { @@ -74,51 +79,54 @@ .bottom-panel-tab { display: inline-flex; align-items: center; - gap: 4px; - padding: 0 10px; + padding: 0 0.4rem 0 0.8rem; height: 100%; cursor: pointer; position: relative; - color: @bc-text-quiet; - font-size: 12px; + flex: 0 0 auto; + min-width: fit-content; + color: #555; + background-color: #f1f1f1; + border-right: 1px solid rgba(0, 0, 0, 0.05); + font-size: 0.8rem; + letter-spacing: 0.4px; white-space: nowrap; - flex-shrink: 0; - transition: color 0.15s ease, background-color 0.15s ease; + transition: color 0.12s ease-out, background-color 0.12s ease-out; .dark & { - color: @dark-bc-text-quiet; + color: #aaa; + background-color: #292929; + border-right: 1px solid rgba(255, 255, 255, 0.05); } &:hover { - color: @bc-text; - background-color: rgba(0, 0, 0, 0.04); + background-color: #e0e0e0; .dark & { - color: @dark-bc-text; - background-color: rgba(255, 255, 255, 0.06); + background-color: #3b3a3a; } } &.active { - color: @bc-text; - background-color: @bc-panel-bg; + color: #333; + background-color: #fff; .dark & { - color: @dark-bc-text; - background-color: @dark-bc-panel-bg; + color: #dedede; + background-color: #1D1F21; } &::after { content: ""; position: absolute; - bottom: 0; + top: 0; left: 0; right: 0; - height: 2px; - background-color: @bc-primary-btn-bg; + height: 0.1rem; + background-color: #0078D7; .dark & { - background-color: @dark-bc-primary-btn-bg; + background-color: #75BEFF; } } } @@ -128,28 +136,48 @@ pointer-events: none; } -.bottom-panel-tab-close { - display: inline-flex; - align-items: center; - justify-content: center; - width: 16px; - height: 16px; - font-size: 14px; - line-height: 1; +.bottom-panel-tab-close-btn { + margin-left: 0.4rem; border-radius: 3px; + cursor: pointer; + color: #999; + font-size: 1rem; + font-weight: 500; + padding: 0 3px; + line-height: 1; opacity: 0; - transition: opacity 0.15s ease, background-color 0.15s ease; + transition: opacity 0.12s ease, color 0.12s ease, background-color 0.12s ease; + + .dark & { + color: #666; + } .bottom-panel-tab:hover & { - opacity: 0.6; + opacity: 1; + color: #666; + + .dark & { + color: #888; + } + } + + .bottom-panel-tab.active & { + opacity: 1; + color: #666; + + .dark & { + color: #888; + } } &:hover { - opacity: 1 !important; + opacity: 1; + color: #333; background-color: rgba(0, 0, 0, 0.1); .dark & { - background-color: rgba(255, 255, 255, 0.15); + color: #fff; + background-color: rgba(255, 255, 255, 0.12); } } } diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index 204312a4f2..f57ff50109 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -293,6 +293,9 @@ define(function (require, exports, module) { return; } $bottomPanelTabBar.empty(); + + // Scrollable tabs area + let $tabsOverflow = $('
'); _openBottomPanelIds.forEach(function (panelId) { let panel = panelIDMap[panelId]; if (!panel) { @@ -302,10 +305,11 @@ define(function (require, exports, module) { let isActive = (panelId === _activeBottomPanelId); let $tab = $('
' + '' + $("").text(title).html() + '' + - '×' + + '×' + '
'); - $bottomPanelTabBar.append($tab); + $tabsOverflow.append($tab); }); + $bottomPanelTabBar.append($tabsOverflow); } /** @@ -317,8 +321,9 @@ define(function (require, exports, module) { return; } let activeMinSize = _panelMinSizes[_activeBottomPanelId] || 100; - // Add tab bar height (28px) to the panel's minSize - $bottomPanelContainer.data("currentMinSize", activeMinSize + 28); + // Add tab bar height to the panel's minSize + let tabBarHeight = $bottomPanelTabBar ? $bottomPanelTabBar.outerHeight() : 34; + $bottomPanelContainer.data("currentMinSize", activeMinSize + tabBarHeight); } /** @@ -551,12 +556,14 @@ define(function (require, exports, module) { listenToResize($bottomPanelContainer); // Tab bar click handlers - $bottomPanelTabBar.on("click", ".bottom-panel-tab-close", function (e) { + $bottomPanelTabBar.on("click", ".bottom-panel-tab-close-btn", function (e) { e.stopPropagation(); let panelId = $(this).closest(".bottom-panel-tab").data("panel-id"); - let panel = panelIDMap[panelId]; - if (panel) { - panel.hide(); + if (panelId) { + let panel = panelIDMap[panelId]; + if (panel) { + panel.hide(); + } } }); From 44abd0e38b842727f54c7ba765cfcace8fc71a91 Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 17 Feb 2026 18:45:33 +0530 Subject: [PATCH 03/44] feat: remove redundant box shadow color between panel and tabbar --- src/styles/Extn-BottomPanelTabs.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/styles/Extn-BottomPanelTabs.less b/src/styles/Extn-BottomPanelTabs.less index 5d48e5123d..19552c6fff 100644 --- a/src/styles/Extn-BottomPanelTabs.less +++ b/src/styles/Extn-BottomPanelTabs.less @@ -43,6 +43,10 @@ display: flex !important; flex-direction: column; } + + .toolbar { + box-shadow: none; + } } } From 445d560548f28eb85d26373cd7d92af552450484 Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 17 Feb 2026 18:54:26 +0530 Subject: [PATCH 04/44] refactor: reduce height of active tab styling --- src/styles/Extn-BottomPanelTabs.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/Extn-BottomPanelTabs.less b/src/styles/Extn-BottomPanelTabs.less index 19552c6fff..8aab496a7d 100644 --- a/src/styles/Extn-BottomPanelTabs.less +++ b/src/styles/Extn-BottomPanelTabs.less @@ -126,7 +126,7 @@ top: 0; left: 0; right: 0; - height: 0.1rem; + height: 0.07rem; background-color: #0078D7; .dark & { From 6f947a59d1a911acf235dd8c4cc4bcd9bcd59f3b Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 17 Feb 2026 21:36:59 +0530 Subject: [PATCH 05/44] feat: better support for bottom panel tab bar title --- .../default/DebugCommands/testBuilder.js | 4 +- src/extensions/default/Git/src/Panel.js | 2 +- .../CustomSnippets/main.js | 3 +- .../DisplayShortcuts/main.js | 3 +- src/features/FindReferencesManager.js | 3 +- src/language/CodeInspection.js | 2 +- src/nls/root/strings.js | 3 ++ src/search/FindInFilesUI.js | 3 +- src/search/SearchResultsView.js | 5 ++- src/view/WorkspaceManager.js | 41 +++++-------------- 10 files changed, 28 insertions(+), 41 deletions(-) diff --git a/src/extensions/default/DebugCommands/testBuilder.js b/src/extensions/default/DebugCommands/testBuilder.js index 38e58ddf0e..bfc9b434af 100644 --- a/src/extensions/default/DebugCommands/testBuilder.js +++ b/src/extensions/default/DebugCommands/testBuilder.js @@ -38,7 +38,7 @@ define(function (require, exports, module) { function toggleTestBuilder() { if(!$panel){ $panel = $(panelHTML); - builderPanel = WorkspaceManager.createBottomPanel("phcode-test-builder-panel", $panel, 100); + builderPanel = WorkspaceManager.createBottomPanel("phcode-test-builder-panel", $panel, 100, "Test Builder"); builderPanel.hide(); _setupPanel().then(()=>{ builderPanel.setVisible(!builderPanel.isVisible()); @@ -177,7 +177,7 @@ define(function (require, exports, module) { return; } $panel = $(panelHTML); - builderPanel = WorkspaceManager.createBottomPanel("phcode-test-builder-panel", $panel, 100); + builderPanel = WorkspaceManager.createBottomPanel("phcode-test-builder-panel", $panel, 100, "Test Builder"); builderPanel.hide(); _setupPanel(); }); diff --git a/src/extensions/default/Git/src/Panel.js b/src/extensions/default/Git/src/Panel.js index 02a34cfcdc..cc4aac6dcb 100644 --- a/src/extensions/default/Git/src/Panel.js +++ b/src/extensions/default/Git/src/Panel.js @@ -1240,7 +1240,7 @@ define(function (require, exports) { var $panelHtml = $(panelHtml); $panelHtml.find(".git-available, .git-not-available").hide(); - gitPanel = WorkspaceManager.createBottomPanel("main-git.panel", $panelHtml, 100); + gitPanel = WorkspaceManager.createBottomPanel("main-git.panel", $panelHtml, 100, Strings.GIT_PANEL_TITLE); $gitPanel = gitPanel.$panel; const resizeObserver = new ResizeObserver(_panelResized); resizeObserver.observe($gitPanel[0]); diff --git a/src/extensionsIntegrated/CustomSnippets/main.js b/src/extensionsIntegrated/CustomSnippets/main.js index 5e2b1b3d0f..6ca00fbfa2 100644 --- a/src/extensionsIntegrated/CustomSnippets/main.js +++ b/src/extensionsIntegrated/CustomSnippets/main.js @@ -58,7 +58,8 @@ define(function (require, exports, module) { * @private */ function _createPanel() { - customSnippetsPanel = WorkspaceManager.createBottomPanel(PANEL_ID, $snippetsPanel, PANEL_MIN_SIZE); + customSnippetsPanel = WorkspaceManager.createBottomPanel(PANEL_ID, $snippetsPanel, PANEL_MIN_SIZE, + Strings.CUSTOM_SNIPPETS_PANEL_TITLE); customSnippetsPanel.show(); // also register the handlers diff --git a/src/extensionsIntegrated/DisplayShortcuts/main.js b/src/extensionsIntegrated/DisplayShortcuts/main.js index 857ddf79ce..96b41cd769 100644 --- a/src/extensionsIntegrated/DisplayShortcuts/main.js +++ b/src/extensionsIntegrated/DisplayShortcuts/main.js @@ -478,7 +478,8 @@ define(function (require, exports, module) { // AppInit.htmlReady() has already executed before extensions are loaded // so, for now, we need to call this ourself - panel = WorkspaceManager.createBottomPanel(TOGGLE_SHORTCUTS_ID, $(s), 300); + panel = WorkspaceManager.createBottomPanel(TOGGLE_SHORTCUTS_ID, $(s), 300, + Strings.KEYBOARD_SHORTCUT_PANEL_TITLE); panel.hide(); $shortcutsPanel = $("#shortcuts-panel"); diff --git a/src/features/FindReferencesManager.js b/src/features/FindReferencesManager.js index 0c71269278..2cb213c871 100644 --- a/src/features/FindReferencesManager.js +++ b/src/features/FindReferencesManager.js @@ -194,7 +194,8 @@ define(function (require, exports, module) { searchModel, "reference-in-files-results", "reference-in-files.results", - "reference" + "reference", + Strings.REFERENCES_PANEL_TITLE ); if(_resultsView) { _resultsView diff --git a/src/language/CodeInspection.js b/src/language/CodeInspection.js index 8cbb15d89f..01a8352bda 100644 --- a/src/language/CodeInspection.js +++ b/src/language/CodeInspection.js @@ -1263,7 +1263,7 @@ define(function (require, exports, module) { Editor.registerGutter(CODE_INSPECTION_GUTTER, CODE_INSPECTION_GUTTER_PRIORITY); // Create bottom panel to list error details var panelHtml = Mustache.render(PanelTemplate, Strings); - problemsPanel = WorkspaceManager.createBottomPanel("errors", $(panelHtml), 100); + problemsPanel = WorkspaceManager.createBottomPanel("errors", $(panelHtml), 100, Strings.CMD_VIEW_TOGGLE_PROBLEMS); $problemsPanel = $("#problems-panel"); $fixAllBtn = $problemsPanel.find(".problems-fix-all-btn"); $fixAllBtn.click(()=>{ diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index cde5aeabfb..650a58bb7c 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1249,6 +1249,8 @@ define({ "REFERENCES_IN_FILES": "references", "REFERENCE_IN_FILES": "reference", "REFERENCES_NO_RESULTS": "No References available for current cursor position", + "REFERENCES_PANEL_TITLE": "References", + "SEARCH_RESULTS_PANEL_TITLE": "Search Results", "CMD_FIND_DOCUMENT_SYMBOLS": "Find Document Symbols", "CMD_FIND_PROJECT_SYMBOLS": "Find Project Symbols", @@ -1396,6 +1398,7 @@ define({ "BUTTON_CANCEL": "Cancel", "CHECKOUT_COMMIT": "Checkout", "CHECKOUT_COMMIT_DETAIL": "Commit Message: {0}
Commit hash: {1}", + "GIT_PANEL_TITLE": "Git", "GIT_CLONE": "Clone", "BUTTON_CLOSE": "Close", "BUTTON_COMMIT": "Commit", diff --git a/src/search/FindInFilesUI.js b/src/search/FindInFilesUI.js index 6a02cd7e19..0aea171f61 100644 --- a/src/search/FindInFilesUI.js +++ b/src/search/FindInFilesUI.js @@ -536,7 +536,8 @@ define(function (require, exports, module) { // Initialize items dependent on HTML DOM AppInit.htmlReady(function () { var model = FindInFiles.searchModel; - _resultsView = new SearchResultsView(model, "find-in-files-results", "find-in-files.results"); + _resultsView = new SearchResultsView(model, "find-in-files-results", "find-in-files.results", + undefined, Strings.SEARCH_RESULTS_PANEL_TITLE); _resultsView .on("replaceBatch", function () { _finishReplaceBatch(model); diff --git a/src/search/SearchResultsView.js b/src/search/SearchResultsView.js index 54751a67e3..5557cf96d9 100644 --- a/src/search/SearchResultsView.js +++ b/src/search/SearchResultsView.js @@ -76,12 +76,13 @@ define(function (require, exports, module) { * @param {string} panelID The CSS ID to use for the panel. * @param {string} panelName The name to use for the panel, as passed to WorkspaceManager.createBottomPanel(). * @param {string} type type to identify if it is reference search or string match serach + * @param {string=} title Display title for the panel tab. */ - function SearchResultsView(model, panelID, panelName, type) { + function SearchResultsView(model, panelID, panelName, type, title) { const self = this; let panelHtml = Mustache.render(searchPanelTemplate, {panelID: panelID}); - this._panel = WorkspaceManager.createBottomPanel(panelName, $(panelHtml), 100); + this._panel = WorkspaceManager.createBottomPanel(panelName, $(panelHtml), 100, title); this._$summary = this._panel.$panel.find(".title"); this._$table = this._panel.$panel.find(".table-container"); this._$previewEditor = this._panel.$panel.find(".search-editor-preview"); diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index f57ff50109..e16282e837 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -140,18 +140,6 @@ define(function (require, exports, module) { /** @type {Object} Map of panelID -> minSize for bottom panels */ let _panelMinSizes = {}; - /** Fallback title map for panels that don't have a .toolbar .title element */ - const _KNOWN_PANEL_TITLES = { - "errors": "Problems", - "problems-panel": "Problems", - "search-results": "Search Results", - "find-in-files-results": "Search Results", - "references-panel": "References", - "git-panel": "Git", - "keyboard-shortcuts-panel": "Keyboard Shortcuts", - "custom-snippets-panel": "Custom Snippets", - "test-builder-panel": "Test Builder" - }; /** @@ -260,27 +248,17 @@ define(function (require, exports, module) { /** * Resolve the display title for a bottom panel tab. - * First looks for a .toolbar .title element within the panel DOM, - * then falls back to the _KNOWN_PANEL_TITLES map using the panel's DOM id. + * Uses the explicit title if provided, otherwise humanizes the panel id. * @param {string} id The panel registration ID - * @param {jQueryObject} $panel The panel's jQuery element + * @param {string=} title Explicit title passed to createBottomPanel * @return {string} * @private */ - function _getPanelTitle(id, $panel) { - let $titleEl = $panel.find(".toolbar .title"); - if ($titleEl.length && $.trim($titleEl.text())) { - return $.trim($titleEl.text()); + function _getPanelTitle(id, title) { + if (title) { + return title; } - let domId = $panel.attr("id") || ""; - if (_KNOWN_PANEL_TITLES[domId]) { - return _KNOWN_PANEL_TITLES[domId]; - } - if (_KNOWN_PANEL_TITLES[id]) { - return _KNOWN_PANEL_TITLES[id]; - } - // Last resort: humanize the id - let label = (domId || id).replace(/[-_]/g, " "); + let label = id.replace(new RegExp("[-_.]", "g"), " ").split(" ")[0]; return label.charAt(0).toUpperCase() + label.slice(1); } @@ -301,7 +279,7 @@ define(function (require, exports, module) { if (!panel) { return; } - let title = panel._tabTitle || _getPanelTitle(panelId, panel.$panel); + let title = panel._tabTitle || _getPanelTitle(panelId); let isActive = (panelId === _activeBottomPanelId); let $tab = $('
' + '' + $("").text(title).html() + '' + @@ -368,9 +346,10 @@ define(function (require, exports, module) { * @param {!jQueryObject} $panel DOM content to use as the panel. Need not be in the document yet. Must have an id * attribute, for use as a preferences key. * @param {number=} minSize Minimum height of panel in px. + * @param {string=} title Display title shown in the bottom panel tab bar. * @return {!Panel} */ - function createBottomPanel(id, $panel, minSize) { + function createBottomPanel(id, $panel, minSize, title) { // Insert panel into the tabbed container instead of before #status-bar $bottomPanelContainer.append($panel); $panel.hide(); @@ -383,7 +362,7 @@ define(function (require, exports, module) { panelIDMap[id] = bottomPanel; // Cache the tab title at creation time - bottomPanel._tabTitle = _getPanelTitle(id, $panel); + bottomPanel._tabTitle = _getPanelTitle(id, title); // Do NOT call Resizer.makeResizable on individual panels. // The container handles resizing. From e5396ce5bc4bb918c0e9525579b6a99ebe1e32b9 Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 17 Feb 2026 21:51:26 +0530 Subject: [PATCH 06/44] feat: better support for tab naming using dom lookup --- src/view/WorkspaceManager.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index e16282e837..823631cf23 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -248,16 +248,22 @@ define(function (require, exports, module) { /** * Resolve the display title for a bottom panel tab. - * Uses the explicit title if provided, otherwise humanizes the panel id. + * Uses the explicit title if provided, then checks for a .toolbar .title + * DOM element in the panel, and finally derives a name from the panel id. * @param {string} id The panel registration ID + * @param {jQueryObject} $panel The panel's jQuery element * @param {string=} title Explicit title passed to createBottomPanel * @return {string} * @private */ - function _getPanelTitle(id, title) { + function _getPanelTitle(id, $panel, title) { if (title) { return title; } + let $titleEl = $panel.find(".toolbar .title"); + if ($titleEl.length && $.trim($titleEl.text())) { + return $.trim($titleEl.text()); + } let label = id.replace(new RegExp("[-_.]", "g"), " ").split(" ")[0]; return label.charAt(0).toUpperCase() + label.slice(1); } @@ -279,7 +285,7 @@ define(function (require, exports, module) { if (!panel) { return; } - let title = panel._tabTitle || _getPanelTitle(panelId); + let title = panel._tabTitle || _getPanelTitle(panelId, panel.$panel); let isActive = (panelId === _activeBottomPanelId); let $tab = $('
' + '' + $("").text(title).html() + '' + @@ -362,7 +368,7 @@ define(function (require, exports, module) { panelIDMap[id] = bottomPanel; // Cache the tab title at creation time - bottomPanel._tabTitle = _getPanelTitle(id, title); + bottomPanel._tabTitle = _getPanelTitle(id, $panel, title); // Do NOT call Resizer.makeResizable on individual panels. // The container handles resizing. From e3187160a82f36aad00a1a1321ab9855049c4b5f Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 17 Feb 2026 22:19:23 +0530 Subject: [PATCH 07/44] feat: prevent full tab bar dom rebuild on panel switch --- src/view/WorkspaceManager.js | 54 ++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index 823631cf23..fbc771eace 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -272,6 +272,11 @@ define(function (require, exports, module) { * Rebuild the tab bar DOM from _openBottomPanelIds. * @private */ + /** + * Full rebuild of the tab bar DOM from _openBottomPanelIds. + * Call this when tabs are added, removed, or renamed. + * @private + */ function _updateBottomPanelTabBar() { if (!$bottomPanelTabBar) { return; @@ -296,6 +301,24 @@ define(function (require, exports, module) { $bottomPanelTabBar.append($tabsOverflow); } + /** + * Swap the .active class on the tab bar without rebuilding the DOM. + * @private + */ + function _updateActiveTabHighlight() { + if (!$bottomPanelTabBar) { + return; + } + $bottomPanelTabBar.find(".bottom-panel-tab").each(function () { + let $tab = $(this); + if ($tab.data("panel-id") === _activeBottomPanelId) { + $tab.addClass("active"); + } else { + $tab.removeClass("active"); + } + }); + } + /** * Update the container's dynamic minSize to match the active panel's minSize. * @private @@ -333,7 +356,7 @@ define(function (require, exports, module) { newPanel.$panel.addClass("active-bottom-panel"); } _updateContainerMinSize(); - _updateBottomPanelTabBar(); + _updateActiveTabHighlight(); } /** @@ -403,6 +426,7 @@ define(function (require, exports, module) { } _switchToTab(panelId); + _updateBottomPanelTabBar(); PanelView.trigger(PanelView.EVENT_PANEL_SHOWN, panelId); triggerUpdateLayout(); }; @@ -421,23 +445,19 @@ define(function (require, exports, module) { let wasActive = (_activeBottomPanelId === panelId); - if (wasActive) { - if (_openBottomPanelIds.length > 0) { - // Activate the next tab (or previous if this was the last) - let nextIdx = Math.min(idx, _openBottomPanelIds.length - 1); - let nextId = _openBottomPanelIds[nextIdx]; - _activeBottomPanelId = null; // clear so _switchToTab runs - _switchToTab(nextId); - PanelView.trigger(PanelView.EVENT_PANEL_SHOWN, nextId); - } else { - // No more tabs - hide the container - _activeBottomPanelId = null; - Resizer.hide($bottomPanelContainer[0]); - _updateBottomPanelTabBar(); - } - } else { - _updateBottomPanelTabBar(); + // Tab was removed — rebuild tab bar, then activate next if needed + if (wasActive && _openBottomPanelIds.length > 0) { + let nextIdx = Math.min(idx, _openBottomPanelIds.length - 1); + let nextId = _openBottomPanelIds[nextIdx]; + _activeBottomPanelId = null; // clear so _switchToTab runs + _switchToTab(nextId); + PanelView.trigger(PanelView.EVENT_PANEL_SHOWN, nextId); + } else if (wasActive) { + // No more tabs - hide the container + _activeBottomPanelId = null; + Resizer.hide($bottomPanelContainer[0]); } + _updateBottomPanelTabBar(); PanelView.trigger(PanelView.EVENT_PANEL_HIDDEN, panelId); triggerUpdateLayout(); From 3f86e69d201b4804e3740087cc3cab0f80fbc59e Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 17 Feb 2026 23:19:13 +0530 Subject: [PATCH 08/44] feat: deprecate min size param from bottom panel --- src/utils/Resizer.js | 8 +++----- src/view/WorkspaceManager.js | 27 ++------------------------- 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/src/utils/Resizer.js b/src/utils/Resizer.js index 88caab5411..9ae4e29886 100644 --- a/src/utils/Resizer.js +++ b/src/utils/Resizer.js @@ -411,9 +411,8 @@ define(function (require, exports, module) { if(initialSize){ elementSize = elementPrefs.size || initialSize; } - let effectiveMinSize = $element.data("currentMinSize") || minSize; - if(elementSize minSize for bottom panels */ - let _panelMinSizes = {}; - - - /** * Calculates the available height for the full-size Editor (or the no-editor placeholder), * accounting for the current size of all visible panels, toolbar, & status bar. @@ -319,20 +314,6 @@ define(function (require, exports, module) { }); } - /** - * Update the container's dynamic minSize to match the active panel's minSize. - * @private - */ - function _updateContainerMinSize() { - if (!$bottomPanelContainer) { - return; - } - let activeMinSize = _panelMinSizes[_activeBottomPanelId] || 100; - // Add tab bar height to the panel's minSize - let tabBarHeight = $bottomPanelTabBar ? $bottomPanelTabBar.outerHeight() : 34; - $bottomPanelContainer.data("currentMinSize", activeMinSize + tabBarHeight); - } - /** * Switch the active tab to the given panel. Does not show/hide the container. * @param {string} panelId @@ -355,7 +336,6 @@ define(function (require, exports, module) { if (newPanel) { newPanel.$panel.addClass("active-bottom-panel"); } - _updateContainerMinSize(); _updateActiveTabHighlight(); } @@ -374,7 +354,7 @@ define(function (require, exports, module) { * @param {!string} id Unique id for this panel. Use package-style naming, e.g. "myextension.feature.panelname" * @param {!jQueryObject} $panel DOM content to use as the panel. Need not be in the document yet. Must have an id * attribute, for use as a preferences key. - * @param {number=} minSize Minimum height of panel in px. + * @param {number=} minSize @deprecated No longer used. Pass `undefined`. * @param {string=} title Display title shown in the bottom panel tab bar. * @return {!Panel} */ @@ -384,9 +364,6 @@ define(function (require, exports, module) { $panel.hide(); updateResizeLimits(); - // Store minSize for dynamic container minSize - _panelMinSizes[id] = minSize || 100; - let bottomPanel = new PanelView.Panel($panel, id); panelIDMap[id] = bottomPanel; @@ -557,7 +534,7 @@ define(function (require, exports, module) { // Make the container resizable (not individual panels) Resizer.makeResizable($bottomPanelContainer[0], Resizer.DIRECTION_VERTICAL, Resizer.POSITION_TOP, - 100, false, undefined, true); + 200, false, undefined, true); listenToResize($bottomPanelContainer); // Tab bar click handlers From 9d37302d06a0e423430a745835472ef0c5a3e4dc Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 17 Feb 2026 23:41:21 +0530 Subject: [PATCH 09/44] refactor: improve bottom panel tab bar styling --- src/styles/Extn-BottomPanelTabs.less | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/styles/Extn-BottomPanelTabs.less b/src/styles/Extn-BottomPanelTabs.less index 8aab496a7d..996cf096b0 100644 --- a/src/styles/Extn-BottomPanelTabs.less +++ b/src/styles/Extn-BottomPanelTabs.less @@ -53,8 +53,8 @@ #bottom-panel-tab-bar { display: flex; align-items: center; - height: 1.5rem; - min-height: 1.5rem; + height: 2rem; + min-height: 2rem; background-color: #f5f5f5; border-bottom: none; overflow: hidden; @@ -92,8 +92,9 @@ color: #555; background-color: #f1f1f1; border-right: 1px solid rgba(0, 0, 0, 0.05); - font-size: 0.8rem; - letter-spacing: 0.4px; + font-size: 1rem; + font-weight: 500; + letter-spacing: 0.5px; white-space: nowrap; transition: color 0.12s ease-out, background-color 0.12s ease-out; @@ -126,7 +127,7 @@ top: 0; left: 0; right: 0; - height: 0.07rem; + height: 0.1rem; background-color: #0078D7; .dark & { @@ -141,13 +142,13 @@ } .bottom-panel-tab-close-btn { - margin-left: 0.4rem; + margin-left: 0.55rem; border-radius: 3px; cursor: pointer; color: #999; - font-size: 1rem; + font-size: 1.25rem; font-weight: 500; - padding: 0 3px; + padding: 0 4px; line-height: 1; opacity: 0; transition: opacity 0.12s ease, color 0.12s ease, background-color 0.12s ease; From d5c3b2f660b315e585a6bdf4cccd8072bba287c9 Mon Sep 17 00:00:00 2001 From: Pluto Date: Wed, 18 Feb 2026 14:10:24 +0530 Subject: [PATCH 10/44] fix: redundant duplicated jsdoc --- src/view/WorkspaceManager.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index 54a5f7c33c..515a0bd91a 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -263,10 +263,6 @@ define(function (require, exports, module) { return label.charAt(0).toUpperCase() + label.slice(1); } - /** - * Rebuild the tab bar DOM from _openBottomPanelIds. - * @private - */ /** * Full rebuild of the tab bar DOM from _openBottomPanelIds. * Call this when tabs are added, removed, or renamed. From 51dd276c7482c4c7b5a4073c6c4bd22b4d3ea808 Mon Sep 17 00:00:00 2001 From: Pluto Date: Wed, 18 Feb 2026 14:39:03 +0530 Subject: [PATCH 11/44] feat: add single close button that hides panel --- src/styles/Extn-BottomPanelTabs.less | 43 +++++++++++++++++++++++++++- src/view/WorkspaceManager.js | 39 +++++++++++++++++++------ 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/styles/Extn-BottomPanelTabs.less b/src/styles/Extn-BottomPanelTabs.less index 996cf096b0..d2843e22b7 100644 --- a/src/styles/Extn-BottomPanelTabs.less +++ b/src/styles/Extn-BottomPanelTabs.less @@ -46,6 +46,10 @@ .toolbar { box-shadow: none; + + .close { + display: none; + } } } } @@ -61,7 +65,7 @@ user-select: none; .dark & { - background-color: #1E1E1E; + background-color: #222222; } } @@ -186,3 +190,40 @@ } } } + +.bottom-panel-tab-bar-actions { + display: flex; + align-items: center; + height: 100%; + margin-left: auto; + padding: 0 0.25rem; + flex: 0 0 auto; +} + +.bottom-panel-close-all-btn { + display: flex; + align-items: center; + justify-content: center; + width: 1.6rem; + height: 1.6rem; + border-radius: 3px; + cursor: pointer; + color: #777; + font-size: 1.3rem; + line-height: 1; + transition: color 0.12s ease, background-color 0.12s ease; + + .dark & { + color: #777; + } + + &:hover { + color: #333; + background-color: rgba(0, 0, 0, 0.1); + + .dark & { + color: #fff; + background-color: rgba(255, 255, 255, 0.12); + } + } +} diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index 515a0bd91a..5683f4f84f 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -131,6 +131,9 @@ define(function (require, exports, module) { /** @type {jQueryObject} The tab bar inside the container */ let $bottomPanelTabBar; + /** @type {jQueryObject} Scrollable area holding the tab elements */ + let $bottomPanelTabsOverflow; + /** @type {string[]} Ordered list of currently open (tabbed) panel IDs */ let _openBottomPanelIds = []; @@ -269,13 +272,11 @@ define(function (require, exports, module) { * @private */ function _updateBottomPanelTabBar() { - if (!$bottomPanelTabBar) { + if (!$bottomPanelTabsOverflow) { return; } - $bottomPanelTabBar.empty(); + $bottomPanelTabsOverflow.empty(); - // Scrollable tabs area - let $tabsOverflow = $('
'); _openBottomPanelIds.forEach(function (panelId) { let panel = panelIDMap[panelId]; if (!panel) { @@ -287,9 +288,8 @@ define(function (require, exports, module) { '' + $("").text(title).html() + '' + '×' + '
'); - $tabsOverflow.append($tab); + $bottomPanelTabsOverflow.append($tab); }); - $bottomPanelTabBar.append($tabsOverflow); } /** @@ -380,12 +380,19 @@ define(function (require, exports, module) { let isActive = (_activeBottomPanelId === panelId); if (isOpen && isActive) { - // Already open and active - no-op + // Already open and active — just ensure container is visible + if (!$bottomPanelContainer.is(":visible")) { + Resizer.show($bottomPanelContainer[0]); + triggerUpdateLayout(); + } return; } if (isOpen && !isActive) { - // Open but not active - just switch tab + // Open but not active - switch tab and ensure container is visible _switchToTab(panelId); + if (!$bottomPanelContainer.is(":visible")) { + Resizer.show($bottomPanelContainer[0]); + } PanelView.trigger(PanelView.EVENT_PANEL_SHOWN, panelId); triggerUpdateLayout(); return; @@ -524,6 +531,13 @@ define(function (require, exports, module) { // --- Create the bottom panel tabbed container --- $bottomPanelContainer = $('
'); $bottomPanelTabBar = $('
'); + $bottomPanelTabsOverflow = $('
'); + let $tabBarActions = $('
'); + $tabBarActions.append( + '×' + ); + $bottomPanelTabBar.append($bottomPanelTabsOverflow); + $bottomPanelTabBar.append($tabBarActions); $bottomPanelContainer.append($bottomPanelTabBar); $bottomPanelContainer.insertBefore("#status-bar"); $bottomPanelContainer.hide(); @@ -554,6 +568,15 @@ define(function (require, exports, module) { } }); + // Close-panel button collapses the container but keeps tabs intact + $bottomPanelTabBar.on("click", ".bottom-panel-close-all-btn", function (e) { + e.stopPropagation(); + if ($bottomPanelContainer.is(":visible")) { + Resizer.hide($bottomPanelContainer[0]); + triggerUpdateLayout(); + } + }); + // Sidebar is a special case: it isn't a Panel, and is not created dynamically. Need to explicitly // listen for resize here. listenToResize($("#sidebar")); From 40de12a8f99af0e385c816e51536f25c336831f6 Mon Sep 17 00:00:00 2001 From: Pluto Date: Wed, 18 Feb 2026 16:50:36 +0530 Subject: [PATCH 12/44] =?UTF-8?q?feat:=20hide=20the=20bottom=20panel=20and?= =?UTF-8?q?=20don=E2=80=99t=20destroy=20any=20active=20ones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/styles/Extn-BottomPanelTabs.less | 18 +++++++----------- src/view/WorkspaceManager.js | 6 +++--- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/styles/Extn-BottomPanelTabs.less b/src/styles/Extn-BottomPanelTabs.less index d2843e22b7..c5405f35ba 100644 --- a/src/styles/Extn-BottomPanelTabs.less +++ b/src/styles/Extn-BottomPanelTabs.less @@ -46,10 +46,6 @@ .toolbar { box-shadow: none; - - .close { - display: none; - } } } } @@ -200,29 +196,29 @@ flex: 0 0 auto; } -.bottom-panel-close-all-btn { +.bottom-panel-hide-btn { display: flex; align-items: center; justify-content: center; width: 1.6rem; height: 1.6rem; + margin-right: 0.3rem; border-radius: 3px; cursor: pointer; - color: #777; - font-size: 1.3rem; + color: #333; + font-size: 0.8rem; + -webkit-text-stroke: 0.4px; line-height: 1; - transition: color 0.12s ease, background-color 0.12s ease; + transition: background-color 0.12s ease; .dark & { - color: #777; + color: #fff; } &:hover { - color: #333; background-color: rgba(0, 0, 0, 0.1); .dark & { - color: #fff; background-color: rgba(255, 255, 255, 0.12); } } diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index 5683f4f84f..c2f75d4110 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -534,7 +534,7 @@ define(function (require, exports, module) { $bottomPanelTabsOverflow = $('
'); let $tabBarActions = $('
'); $tabBarActions.append( - '×' + '' ); $bottomPanelTabBar.append($bottomPanelTabsOverflow); $bottomPanelTabBar.append($tabBarActions); @@ -568,8 +568,8 @@ define(function (require, exports, module) { } }); - // Close-panel button collapses the container but keeps tabs intact - $bottomPanelTabBar.on("click", ".bottom-panel-close-all-btn", function (e) { + // Hide-panel button collapses the container but keeps tabs intact + $bottomPanelTabBar.on("click", ".bottom-panel-hide-btn", function (e) { e.stopPropagation(); if ($bottomPanelContainer.is(":visible")) { Resizer.hide($bottomPanelContainer[0]); From 9727d4c20680db83baf5783afb98c45ef8ac254f Mon Sep 17 00:00:00 2001 From: Pluto Date: Wed, 18 Feb 2026 20:48:20 +0530 Subject: [PATCH 13/44] feat: add button to toggle the bottom panel via status bar --- src/styles/brackets.less | 8 ++++++++ src/view/WorkspaceManager.js | 37 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 7cd694023f..fd0ed18932 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -648,6 +648,14 @@ a, img { animation: brightenFade 2s ease-in-out; } +#status-panel-toggle { + cursor: pointer; +} + +#status-panel-toggle.flash { + animation: brightenFade 2s ease-in-out; +} + @keyframes brightenFade { 0% { background-color: transparent; diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index c2f75d4110..fbd863d22c 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -40,6 +40,7 @@ define(function (require, exports, module) { EventDispatcher = require("utils/EventDispatcher"), KeyBindingManager = require("command/KeyBindingManager"), Resizer = require("utils/Resizer"), + AnimationUtils = require("utils/AnimationUtils"), PluginPanelView = require("view/PluginPanelView"), PanelView = require("view/PanelView"), EditorManager = require("editor/EditorManager"), @@ -134,6 +135,9 @@ define(function (require, exports, module) { /** @type {jQueryObject} Scrollable area holding the tab elements */ let $bottomPanelTabsOverflow; + /** @type {jQueryObject} Chevron toggle in the status bar */ + let $statusBarPanelToggle; + /** @type {string[]} Ordered list of currently open (tabbed) panel IDs */ let _openBottomPanelIds = []; @@ -542,11 +546,44 @@ define(function (require, exports, module) { $bottomPanelContainer.insertBefore("#status-bar"); $bottomPanelContainer.hide(); + // Create status bar chevron toggle for bottom panel + $statusBarPanelToggle = $( + '
' + + '' + + '
' + ); + $("#status-indicators").append($statusBarPanelToggle); + + $statusBarPanelToggle.on("click", function () { + if ($bottomPanelContainer.is(":visible")) { + Resizer.hide($bottomPanelContainer[0]); + triggerUpdateLayout(); + } else if (_openBottomPanelIds.length > 0) { + Resizer.show($bottomPanelContainer[0]); + triggerUpdateLayout(); + } else { + _showLastHiddenPanelIfPossible(); + } + }); + // Make the container resizable (not individual panels) Resizer.makeResizable($bottomPanelContainer[0], Resizer.DIRECTION_VERTICAL, Resizer.POSITION_TOP, 200, false, undefined, true); listenToResize($bottomPanelContainer); + $bottomPanelContainer.on("panelCollapsed", function () { + $statusBarPanelToggle.find("i") + .removeClass("fa-chevron-down") + .addClass("fa-chevron-up"); + AnimationUtils.animateUsingClass($statusBarPanelToggle[0], "flash", 1500); + }); + + $bottomPanelContainer.on("panelExpanded", function () { + $statusBarPanelToggle.find("i") + .removeClass("fa-chevron-up") + .addClass("fa-chevron-down"); + }); + // Tab bar click handlers $bottomPanelTabBar.on("click", ".bottom-panel-tab-close-btn", function (e) { e.stopPropagation(); From 02bde647c8e8640198bd96529a076e36f1d8130f Mon Sep 17 00:00:00 2001 From: Pluto Date: Wed, 18 Feb 2026 21:33:29 +0530 Subject: [PATCH 14/44] feat: animate buttons when panel toggles its state via non status bar click --- src/styles/Extn-BottomPanelTabs.less | 6 +++--- src/styles/brackets.less | 2 +- src/view/WorkspaceManager.js | 18 +++++++++++++++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/styles/Extn-BottomPanelTabs.less b/src/styles/Extn-BottomPanelTabs.less index c5405f35ba..6ec4edc81e 100644 --- a/src/styles/Extn-BottomPanelTabs.less +++ b/src/styles/Extn-BottomPanelTabs.less @@ -205,14 +205,14 @@ margin-right: 0.3rem; border-radius: 3px; cursor: pointer; - color: #333; - font-size: 0.8rem; + color: #555; + font-size: 0.7rem; -webkit-text-stroke: 0.4px; line-height: 1; transition: background-color 0.12s ease; .dark & { - color: #fff; + color: #ccc; } &:hover { diff --git a/src/styles/brackets.less b/src/styles/brackets.less index fd0ed18932..500a374a68 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -653,7 +653,7 @@ a, img { } #status-panel-toggle.flash { - animation: brightenFade 2s ease-in-out; + animation: brightenFade 800ms ease-in-out; } @keyframes brightenFade { diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index fbd863d22c..ebdcdaa355 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -144,6 +144,9 @@ define(function (require, exports, module) { /** @type {string|null} The panel ID of the currently visible (active) tab */ let _activeBottomPanelId = null; + /** @type {boolean} True while the status bar toggle button is handling a click */ + let _statusBarToggleInProgress = false; + /** * Calculates the available height for the full-size Editor (or the no-editor placeholder), * accounting for the current size of all visible panels, toolbar, & status bar. @@ -548,13 +551,14 @@ define(function (require, exports, module) { // Create status bar chevron toggle for bottom panel $statusBarPanelToggle = $( - '
' + + '
' + '' + '
' ); - $("#status-indicators").append($statusBarPanelToggle); + $("#status-indicators").prepend($statusBarPanelToggle); $statusBarPanelToggle.on("click", function () { + _statusBarToggleInProgress = true; if ($bottomPanelContainer.is(":visible")) { Resizer.hide($bottomPanelContainer[0]); triggerUpdateLayout(); @@ -564,6 +568,7 @@ define(function (require, exports, module) { } else { _showLastHiddenPanelIfPossible(); } + _statusBarToggleInProgress = false; }); // Make the container resizable (not individual panels) @@ -575,13 +580,20 @@ define(function (require, exports, module) { $statusBarPanelToggle.find("i") .removeClass("fa-chevron-down") .addClass("fa-chevron-up"); - AnimationUtils.animateUsingClass($statusBarPanelToggle[0], "flash", 1500); + $statusBarPanelToggle.attr("title", "Show Bottom Panel"); + if (!_statusBarToggleInProgress) { + AnimationUtils.animateUsingClass($statusBarPanelToggle[0], "flash", 800); + } }); $bottomPanelContainer.on("panelExpanded", function () { $statusBarPanelToggle.find("i") .removeClass("fa-chevron-up") .addClass("fa-chevron-down"); + $statusBarPanelToggle.attr("title", "Hide Bottom Panel"); + if (!_statusBarToggleInProgress) { + AnimationUtils.animateUsingClass($statusBarPanelToggle[0], "flash", 800); + } }); // Tab bar click handlers From 34073881e2428ee9dc1ae691f0c6aa4c3f79c283 Mon Sep 17 00:00:00 2001 From: Pluto Date: Wed, 18 Feb 2026 21:40:38 +0530 Subject: [PATCH 15/44] refactor: better styling for the hide panel button in bottom panel tab bar --- src/styles/Extn-BottomPanelTabs.less | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/styles/Extn-BottomPanelTabs.less b/src/styles/Extn-BottomPanelTabs.less index 6ec4edc81e..327f2d62e3 100644 --- a/src/styles/Extn-BottomPanelTabs.less +++ b/src/styles/Extn-BottomPanelTabs.less @@ -201,25 +201,27 @@ align-items: center; justify-content: center; width: 1.6rem; - height: 1.6rem; - margin-right: 0.3rem; + height: 1.4rem; + margin-right: 0.4rem; border-radius: 3px; cursor: pointer; - color: #555; + color: #666; font-size: 0.7rem; -webkit-text-stroke: 0.4px; line-height: 1; - transition: background-color 0.12s ease; + transition: color 0.12s ease, background-color 0.12s ease; .dark & { - color: #ccc; + color: #aaa; } &:hover { background-color: rgba(0, 0, 0, 0.1); + color: #333; .dark & { background-color: rgba(255, 255, 255, 0.12); + color: #eee; } } } From 762fea23bb455610eb2011706766221141bdc636 Mon Sep 17 00:00:00 2001 From: Pluto Date: Wed, 18 Feb 2026 21:53:24 +0530 Subject: [PATCH 16/44] chore: localize the strings --- src/nls/root/strings.js | 3 +++ src/view/WorkspaceManager.js | 11 ++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 650a58bb7c..d1fd4bd702 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1251,6 +1251,9 @@ define({ "REFERENCES_NO_RESULTS": "No References available for current cursor position", "REFERENCES_PANEL_TITLE": "References", "SEARCH_RESULTS_PANEL_TITLE": "Search Results", + "BOTTOM_PANEL_HIDE": "Hide Panel", + "BOTTOM_PANEL_SHOW": "Show Bottom Panel", + "BOTTOM_PANEL_HIDE_TOGGLE": "Hide Bottom Panel", "CMD_FIND_DOCUMENT_SYMBOLS": "Find Document Symbols", "CMD_FIND_PROJECT_SYMBOLS": "Find Project Symbols", diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index ebdcdaa355..8f2c594dcf 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -41,6 +41,7 @@ define(function (require, exports, module) { KeyBindingManager = require("command/KeyBindingManager"), Resizer = require("utils/Resizer"), AnimationUtils = require("utils/AnimationUtils"), + Strings = require("strings"), PluginPanelView = require("view/PluginPanelView"), PanelView = require("view/PanelView"), EditorManager = require("editor/EditorManager"), @@ -293,7 +294,7 @@ define(function (require, exports, module) { let isActive = (panelId === _activeBottomPanelId); let $tab = $('
' + '' + $("").text(title).html() + '' + - '×' + + '×' + '
'); $bottomPanelTabsOverflow.append($tab); }); @@ -541,7 +542,7 @@ define(function (require, exports, module) { $bottomPanelTabsOverflow = $('
'); let $tabBarActions = $('
'); $tabBarActions.append( - '' + '' ); $bottomPanelTabBar.append($bottomPanelTabsOverflow); $bottomPanelTabBar.append($tabBarActions); @@ -551,7 +552,7 @@ define(function (require, exports, module) { // Create status bar chevron toggle for bottom panel $statusBarPanelToggle = $( - '
' + + '
' + '' + '
' ); @@ -580,7 +581,7 @@ define(function (require, exports, module) { $statusBarPanelToggle.find("i") .removeClass("fa-chevron-down") .addClass("fa-chevron-up"); - $statusBarPanelToggle.attr("title", "Show Bottom Panel"); + $statusBarPanelToggle.attr("title", Strings.BOTTOM_PANEL_SHOW); if (!_statusBarToggleInProgress) { AnimationUtils.animateUsingClass($statusBarPanelToggle[0], "flash", 800); } @@ -590,7 +591,7 @@ define(function (require, exports, module) { $statusBarPanelToggle.find("i") .removeClass("fa-chevron-up") .addClass("fa-chevron-down"); - $statusBarPanelToggle.attr("title", "Hide Bottom Panel"); + $statusBarPanelToggle.attr("title", Strings.BOTTOM_PANEL_HIDE_TOGGLE); if (!_statusBarToggleInProgress) { AnimationUtils.animateUsingClass($statusBarPanelToggle[0], "flash", 800); } From 1a7023e7a07344fee3653fb31cf1a36735c56047 Mon Sep 17 00:00:00 2001 From: Pluto Date: Wed, 18 Feb 2026 23:53:51 +0530 Subject: [PATCH 17/44] fix: some panel content getting cut off and not taking full available height --- src/styles/Extn-BottomPanelTabs.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/styles/Extn-BottomPanelTabs.less b/src/styles/Extn-BottomPanelTabs.less index 327f2d62e3..076a1f5f9d 100644 --- a/src/styles/Extn-BottomPanelTabs.less +++ b/src/styles/Extn-BottomPanelTabs.less @@ -47,6 +47,12 @@ .toolbar { box-shadow: none; } + + .resizable-content { + flex: 1; + min-height: 0; + height: auto !important; + } } } From 11378e0853c197be304b3de6e765703d38361f92 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 00:12:22 +0530 Subject: [PATCH 18/44] refactor: update shortcuts panel styles to make it consistent with the new tab bar architecture --- src/extensionsIntegrated/DisplayShortcuts/main.js | 4 ---- .../DisplayShortcuts/templates/bottom-panel.html | 2 -- src/styles/Extn-BottomPanelTabs.less | 3 ++- src/styles/Extn-DisplayShortcuts.less | 8 ++++---- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/extensionsIntegrated/DisplayShortcuts/main.js b/src/extensionsIntegrated/DisplayShortcuts/main.js index 96b41cd769..6b1281e9e9 100644 --- a/src/extensionsIntegrated/DisplayShortcuts/main.js +++ b/src/extensionsIntegrated/DisplayShortcuts/main.js @@ -506,10 +506,6 @@ define(function (require, exports, module) { } }); - $shortcutsPanel.find(".close").click(function () { - CommandManager.execute(TOGGLE_SHORTCUTS_ID); - }); - $shortcutsPanel.find(".reset-to-default").click(function () { Dialogs.showConfirmDialog( Strings.KEYBOARD_SHORTCUT_RESET_DIALOG_TITLE, diff --git a/src/extensionsIntegrated/DisplayShortcuts/templates/bottom-panel.html b/src/extensionsIntegrated/DisplayShortcuts/templates/bottom-panel.html index 4762ac7851..a9f10e7fda 100644 --- a/src/extensionsIntegrated/DisplayShortcuts/templates/bottom-panel.html +++ b/src/extensionsIntegrated/DisplayShortcuts/templates/bottom-panel.html @@ -1,7 +1,5 @@
- {{KEYBOARD_SHORTCUT_PANEL_TITLE}} - × diff --git a/src/styles/Extn-BottomPanelTabs.less b/src/styles/Extn-BottomPanelTabs.less index 076a1f5f9d..71bfdfb111 100644 --- a/src/styles/Extn-BottomPanelTabs.less +++ b/src/styles/Extn-BottomPanelTabs.less @@ -62,12 +62,13 @@ height: 2rem; min-height: 2rem; background-color: #f5f5f5; - border-bottom: none; + border-bottom: 1px solid #d6d6d6; overflow: hidden; user-select: none; .dark & { background-color: #222222; + border-bottom: 1px solid #333; } } diff --git a/src/styles/Extn-DisplayShortcuts.less b/src/styles/Extn-DisplayShortcuts.less index 77765ffbf0..2163bd5c19 100644 --- a/src/styles/Extn-DisplayShortcuts.less +++ b/src/styles/Extn-DisplayShortcuts.less @@ -106,15 +106,15 @@ } #shortcuts-panel .toolbar { - display: block; - padding-right: 28px; + display: flex; + justify-content: flex-end; + padding-right: 4px; } #shortcuts-panel .toolbar button.reset-to-default, #shortcuts-panel .toolbar button.presetPicker, #shortcuts-panel .toolbar .filter { - float: right; - margin: 4px 10px; + margin: 1px 6px; padding-top: 2px; padding-bottom: 2px; } From 348edc9938767de8452f0ccff52924b96bb11034 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 00:46:19 +0530 Subject: [PATCH 19/44] refactor: update custom snippets style as per new tabbed panel architecture --- .../CustomSnippets/UIHelper.js | 55 ++++++++++++------- .../htmlContent/snippets-panel.html | 3 +- .../CustomSnippets/main.js | 6 +- src/nls/root/strings.js | 1 + src/styles/Extn-CustomSnippets.less | 43 +++------------ src/styles/Extn-DisplayShortcuts.less | 2 +- 6 files changed, 47 insertions(+), 63 deletions(-) diff --git a/src/extensionsIntegrated/CustomSnippets/UIHelper.js b/src/extensionsIntegrated/CustomSnippets/UIHelper.js index 545214da0b..49370097a7 100644 --- a/src/extensionsIntegrated/CustomSnippets/UIHelper.js +++ b/src/extensionsIntegrated/CustomSnippets/UIHelper.js @@ -24,6 +24,9 @@ define(function (require, exports, module) { const Global = require("./global"); const Strings = require("strings"); + /** @type {Object} Reference to the panel instance, set via init() */ + let _panel; + /** * this is a generic function to show error messages for input fields * @@ -113,7 +116,6 @@ define(function (require, exports, module) { const $backToListMenuBtn = $("#back-to-list-menu-btn"); const $addNewSnippetBtn = $("#add-new-snippet-btn"); const $filterSnippetsPanel = $("#filter-snippets-panel"); - const $toolbarTitle = $(".toolbar-title"); $addSnippetMenu.removeClass("hidden"); $snippetListMenu.addClass("hidden"); @@ -122,7 +124,9 @@ define(function (require, exports, module) { $addNewSnippetBtn.addClass("hidden"); $filterSnippetsPanel.addClass("hidden"); - $toolbarTitle.html(`${Strings.CUSTOM_SNIPPETS_ADD_PANEL_TITLE} `); + if (_panel) { + _panel.setTitle(Strings.CUSTOM_SNIPPETS_ADD_PANEL_TITLE); + } } /** @@ -137,7 +141,6 @@ define(function (require, exports, module) { const $backToListMenuBtn = $("#back-to-list-menu-btn"); const $addNewSnippetBtn = $("#add-new-snippet-btn"); const $filterSnippetsPanel = $("#filter-snippets-panel"); - const $toolbarTitle = $(".toolbar-title"); $addSnippetMenu.addClass("hidden"); $editSnippetMenu.addClass("hidden"); @@ -147,12 +150,7 @@ define(function (require, exports, module) { $addNewSnippetBtn.removeClass("hidden"); $filterSnippetsPanel.removeClass("hidden"); - // add the snippet count in the toolbar (the no. of snippets added) - const snippetCount = Global.SnippetHintsList.length; - const countText = snippetCount > 0 ? `(${snippetCount})` : ""; - $toolbarTitle.html( - `${Strings.CUSTOM_SNIPPETS_PANEL_TITLE} ${countText}` - ); + _updateListTabTitle(); $("#filter-snippets-input").val(""); } @@ -167,7 +165,6 @@ define(function (require, exports, module) { const $backToListMenuBtn = $("#back-to-list-menu-btn"); const $addNewSnippetBtn = $("#add-new-snippet-btn"); const $filterSnippetsPanel = $("#filter-snippets-panel"); - const $toolbarTitle = $(".toolbar-title"); $editSnippetMenu.removeClass("hidden"); $snippetListMenu.addClass("hidden"); @@ -176,8 +173,9 @@ define(function (require, exports, module) { $addNewSnippetBtn.addClass("hidden"); $filterSnippetsPanel.addClass("hidden"); - // Update toolbar title - $toolbarTitle.html(`${Strings.CUSTOM_SNIPPETS_EDIT_PANEL_TITLE} `); + if (_panel) { + _panel.setTitle(Strings.CUSTOM_SNIPPETS_EDIT_PANEL_TITLE); + } } /** @@ -213,18 +211,35 @@ define(function (require, exports, module) { } /** - * Initializes the toolbar title for the list view - * This is called when the panel is first opened to ensure the snippet count is displayed + * Updates the tab title to show the list view format: "Custom Snippets (N)" + * @private */ - function initializeListViewToolbarTitle() { - const $toolbarTitle = $(".toolbar-title"); + function _updateListTabTitle() { + if (!_panel) { + return; + } const snippetCount = Global.SnippetHintsList.length; - const countText = snippetCount > 0 ? `(${snippetCount})` : ""; - $toolbarTitle.html( - `${Strings.CUSTOM_SNIPPETS_PANEL_TITLE} ${countText}` - ); + const countText = snippetCount > 0 ? ` (${snippetCount})` : ""; + _panel.setTitle(Strings.CUSTOM_SNIPPETS_PANEL_TITLE + countText); + } + + /** + * Initializes the tab title for the list view. + * This is called when the panel is first opened to ensure the snippet count is displayed. + */ + function initializeListViewToolbarTitle() { + _updateListTabTitle(); + } + + /** + * Sets the panel reference so UIHelper can update the tab title. + * @param {Object} panel The Panel instance returned by WorkspaceManager.createBottomPanel + */ + function init(panel) { + _panel = panel; } + exports.init = init; exports.showEmptySnippetMessage = showEmptySnippetMessage; exports.showSnippetsList = showSnippetsList; exports.clearSnippetsList = clearSnippetsList; diff --git a/src/extensionsIntegrated/CustomSnippets/htmlContent/snippets-panel.html b/src/extensionsIntegrated/CustomSnippets/htmlContent/snippets-panel.html index f1b5e3c0e2..b6312b8c4d 100644 --- a/src/extensionsIntegrated/CustomSnippets/htmlContent/snippets-panel.html +++ b/src/extensionsIntegrated/CustomSnippets/htmlContent/snippets-panel.html @@ -1,10 +1,10 @@
- {{Strings.CUSTOM_SNIPPETS_PANEL_TITLE}}
diff --git a/src/extensionsIntegrated/CustomSnippets/main.js b/src/extensionsIntegrated/CustomSnippets/main.js index 6ca00fbfa2..d7f0beaf68 100644 --- a/src/extensionsIntegrated/CustomSnippets/main.js +++ b/src/extensionsIntegrated/CustomSnippets/main.js @@ -60,6 +60,7 @@ define(function (require, exports, module) { function _createPanel() { customSnippetsPanel = WorkspaceManager.createBottomPanel(PANEL_ID, $snippetsPanel, PANEL_MIN_SIZE, Strings.CUSTOM_SNIPPETS_PANEL_TITLE); + UIHelper.init(customSnippetsPanel); customSnippetsPanel.show(); // also register the handlers @@ -130,7 +131,6 @@ define(function (require, exports, module) { * @private */ function _registerHandlers() { - const $closePanelBtn = $("#close-custom-snippets-panel-btn"); const $saveCustomSnippetBtn = $("#save-custom-snippet-btn"); const $cancelCustomSnippetBtn = $("#cancel-custom-snippet-btn"); const $abbrInput = $("#abbr-box"); @@ -162,10 +162,6 @@ define(function (require, exports, module) { SnippetsList.showSnippetsList(); }); - $closePanelBtn.on("click", function () { - _hidePanel(); - }); - $saveCustomSnippetBtn.on("click", function () { Driver.handleSaveBtnClick(); }); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index d1fd4bd702..52c7548ac5 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1702,6 +1702,7 @@ define({ "CUSTOM_SNIPPETS_ADD_PANEL_TITLE": "Add Snippet", "CUSTOM_SNIPPETS_EDIT_PANEL_TITLE": "Edit Snippet", "CUSTOM_SNIPPETS_ADD_NEW_TITLE": "Add new snippet", + "CUSTOM_SNIPPETS_ADD_BTN_LABEL": "Add", "CUSTOM_SNIPPETS_BACK_TO_LIST_TITLE": "Back to snippets list", "CUSTOM_SNIPPETS_BACK": "Back", "CUSTOM_SNIPPETS_FILTER_PLACEHOLDER": "Filter...", diff --git a/src/styles/Extn-CustomSnippets.less b/src/styles/Extn-CustomSnippets.less index 57e6b32966..dc41d16784 100644 --- a/src/styles/Extn-CustomSnippets.less +++ b/src/styles/Extn-CustomSnippets.less @@ -33,36 +33,23 @@ gap: 4px; flex-wrap: wrap; align-items: center; + margin: 0; } } -.toolbar-title { - color: @bc-text; - font-size: 15px; - font-weight: 500; - - .dark & { - color: @dark-bc-text; - } -} - -.snippets-count { - color: @bc-text; - font-size: 15px; - font-weight: 500; - - .dark & { - color: @dark-bc-text; - } -} .custom-snippet-btn button { - height: 26px; + height: 21px; border-radius: 4px; background-color: @bc-btn-bg; color: @bc-text; border: 1px solid @bc-btn-border; box-shadow: inset 0 1px @bc-highlight; + display: flex; + align-items: center; + gap: 4px; + padding: 0 8px; + font-size: 12px; .dark & { background-color: @dark-bc-btn-bg; @@ -72,11 +59,6 @@ } } -.custom-snippet-btn button { - display: flex; - align-items: center; -} - .custom-snippet-btn .back-btn-left-icon { position: relative; top: 0.6px; @@ -93,14 +75,6 @@ top: 0.6px; } -#custom-snippets-panel .close { - padding-top: 2.5px; -} - -#custom-snippets-panel .close:hover { - cursor: pointer; -} - .filter-snippets-panel { display: inline-block; } @@ -113,8 +87,7 @@ height: 14px; min-width: 120px; margin-bottom: 0; - margin-top: -4px; - margin-right: 30px; + margin-right: 4px; } #custom-snippets-list.hidden { diff --git a/src/styles/Extn-DisplayShortcuts.less b/src/styles/Extn-DisplayShortcuts.less index 2163bd5c19..a326162b06 100644 --- a/src/styles/Extn-DisplayShortcuts.less +++ b/src/styles/Extn-DisplayShortcuts.less @@ -114,7 +114,7 @@ #shortcuts-panel .toolbar button.reset-to-default, #shortcuts-panel .toolbar button.presetPicker, #shortcuts-panel .toolbar .filter { - margin: 1px 6px; + margin: 1px 8px 1px 6px; padding-top: 2px; padding-bottom: 2px; } From 93902390d8065f527d4bf74cb373bd02b8345803 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 01:07:49 +0530 Subject: [PATCH 20/44] feat: update git panel styles to fit new architecture --- src/extensions/default/Git/src/Panel.js | 3 +-- .../default/Git/styles/git-styles.less | 18 ++++++++---------- .../default/Git/templates/git-panel.html | 3 +-- src/styles/Extn-CustomSnippets.less | 1 + 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/extensions/default/Git/src/Panel.js b/src/extensions/default/Git/src/Panel.js index cc4aac6dcb..f30c78e411 100644 --- a/src/extensions/default/Git/src/Panel.js +++ b/src/extensions/default/Git/src/Panel.js @@ -1212,7 +1212,7 @@ define(function (require, exports) { return; } const mainToolbarWidth = $mainToolbar.width(); - let overFlowWidth = 565; + let overFlowWidth = 540; const breakpoints = [ { width: overFlowWidth, className: "hide-when-small" }, { width: 400, className: "hide-when-x-small" } @@ -1246,7 +1246,6 @@ define(function (require, exports) { resizeObserver.observe($gitPanel[0]); $mainToolbar = $gitPanel.find(".mainToolbar"); $gitPanel - .on("click", ".close", toggle) .on("click", ".check-all", function () { if ($(this).is(":checked")) { return Git.stageAll().then(function () { diff --git a/src/extensions/default/Git/styles/git-styles.less b/src/extensions/default/Git/styles/git-styles.less index 8075032b8b..b9b3260f20 100644 --- a/src/extensions/default/Git/styles/git-styles.less +++ b/src/extensions/default/Git/styles/git-styles.less @@ -1013,17 +1013,11 @@ .toolbar { overflow: visible; - .close { - position: absolute; - top: 22px; - margin-top: -10px; - } } .git-more-options-btn { position: absolute; - right: 25px; - top: 8px; - padding: 4px 8px 2px 8px; + right: 8px; + padding: 4px 9px 2px 8px; opacity: .7; .dark & { opacity: .5; @@ -1070,7 +1064,11 @@ .btn-group { line-height: 1; button { - height: 26px; + height: 22px; + margin-top: 2px; + margin-bottom: 2px; + padding-top: 2px; + padding-bottom: 2px; } } } @@ -1086,7 +1084,7 @@ } .git-right-icons { position:absolute; - right: 55px; + right: 32px; top: 5px; } .octicon:not(:only-child) { diff --git a/src/extensions/default/Git/templates/git-panel.html b/src/extensions/default/Git/templates/git-panel.html index 94f2d4099a..b0133c2c87 100644 --- a/src/extensions/default/Git/templates/git-panel.html +++ b/src/extensions/default/Git/templates/git-panel.html @@ -69,8 +69,7 @@
- ×
-
+
diff --git a/src/styles/Extn-CustomSnippets.less b/src/styles/Extn-CustomSnippets.less index dc41d16784..9f14524cf2 100644 --- a/src/styles/Extn-CustomSnippets.less +++ b/src/styles/Extn-CustomSnippets.less @@ -27,6 +27,7 @@ align-items: center; gap: 10px; flex-wrap: wrap; + margin-left: 4px; } .buttons { From 25dcfb0891257a90343a16b164f989180290a661 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 01:20:51 +0530 Subject: [PATCH 21/44] feat: update problems panel style to match new architecture --- src/htmlContent/problems-panel.html | 3 +-- src/language/CodeInspection.js | 5 ----- src/styles/brackets.less | 6 ++++++ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/htmlContent/problems-panel.html b/src/htmlContent/problems-panel.html index 7ad8e75ddf..34ca3fa58e 100644 --- a/src/htmlContent/problems-panel.html +++ b/src/htmlContent/problems-panel.html @@ -2,7 +2,6 @@
- ×
-
+
diff --git a/src/language/CodeInspection.js b/src/language/CodeInspection.js index 01a8352bda..217f4f5a74 100644 --- a/src/language/CodeInspection.js +++ b/src/language/CodeInspection.js @@ -1354,11 +1354,6 @@ define(function (require, exports, module) { } }); - $("#problems-panel .close").click(function () { - toggleCollapsed(true); - MainViewManager.focusActivePane(); - }); - // Status bar indicator - icon & tooltip updated by run() var statusIconHtml = Mustache.render("
 
", Strings); StatusBar.addIndicator(INDICATOR_ID, $(statusIconHtml), true, "", "", "status-indent"); diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 500a374a68..5a4a41edb2 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -3041,6 +3041,12 @@ textarea.exclusions-editor { #problems-panel { .user-select(text); // allow selecting error messages for easy web searching + .toolbar { + padding: 9px 8px; + } + .title { + margin-left: 4px; + } .line { text-align: right; // make line number line up with editor line numbers } From 1e000c3a8049f3d21d3b16e4f6a862ce0ddd7e9b Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 01:34:15 +0530 Subject: [PATCH 22/44] feat: update test builder styles --- .../default/DebugCommands/testBuilder.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/extensions/default/DebugCommands/testBuilder.js b/src/extensions/default/DebugCommands/testBuilder.js index bfc9b434af..0bf8508f57 100644 --- a/src/extensions/default/DebugCommands/testBuilder.js +++ b/src/extensions/default/DebugCommands/testBuilder.js @@ -49,23 +49,19 @@ define(function (require, exports, module) { } const panelHTML = `
-
-
-
Test Builder
+
+
-
+
- - × -
+ +
-
`; @@ -161,7 +157,6 @@ define(function (require, exports, module) { builderEditor && builderEditor.updateLayout(); }).observe($panel[0]); - $panel.find(".close").click(toggleTestBuilder); $panel.find(".save-test-builder").click(saveFile); $panel.find(".run-test-builder").click(()=>{ runTests(); From 52b1ff983eaddae2f9664ac167ef493a16ee4c26 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 01:52:01 +0530 Subject: [PATCH 23/44] feat: update search panel styles --- src/htmlContent/search-panel.html | 4 +--- src/search/SearchResultsView.js | 6 ------ src/styles/brackets.less | 7 +++++-- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/htmlContent/search-panel.html b/src/htmlContent/search-panel.html index e297478baa..4f81e7af5b 100644 --- a/src/htmlContent/search-panel.html +++ b/src/htmlContent/search-panel.html @@ -1,10 +1,8 @@
- ×
-
- +
diff --git a/src/search/SearchResultsView.js b/src/search/SearchResultsView.js index 5557cf96d9..3f5178c29f 100644 --- a/src/search/SearchResultsView.js +++ b/src/search/SearchResultsView.js @@ -329,12 +329,6 @@ define(function (require, exports, module) { var self = this; this._panel.$panel .off(".searchResults") // Remove the old events - .on("dblclick.searchResults", ".toolbar", function() { - self._panel.hide(); - }) - .on("click.searchResults", ".close", function () { - self._panel.hide(); - }) // The link to go the first page .on("click.searchResults", ".first-page:not(.disabled)", function () { self._currentStart = 0; diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 5a4a41edb2..0b942fee4c 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -1908,11 +1908,14 @@ a, img { /* Find in Files results panel - temporary UI, to be replaced with a richer search feature later */ +.search-results .toolbar { + padding: 5px 8px; +} .search-results .title { .sane-box-model; - padding-right: 20px; + margin-left: 4px; width: 100%; - line-height: 25px; + line-height: 26px; .flex-box; .contracting-col { From f2e24da3123f4a47fdc645ce315bea636984510a Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 14:13:28 +0530 Subject: [PATCH 24/44] feat: move the tabbed bottom panel logic to panel view --- src/view/PanelView.js | 254 +++++++++++++++++++++++++++++++++-- src/view/WorkspaceManager.js | 253 ++-------------------------------- 2 files changed, 254 insertions(+), 253 deletions(-) diff --git a/src/view/PanelView.js b/src/view/PanelView.js index d3c6e266fa..d18682def4 100644 --- a/src/view/PanelView.js +++ b/src/view/PanelView.js @@ -26,8 +26,9 @@ define(function (require, exports, module) { const EventDispatcher = require("utils/EventDispatcher"), - Resizer = require("utils/Resizer"); - + Resizer = require("utils/Resizer"), + Strings = require("strings"); + /** * Event when panel is hidden * @type {string} @@ -49,15 +50,132 @@ define(function (require, exports, module) { */ const PANEL_TYPE_BOTTOM_PANEL = 'bottomPanel'; + // --- Module-level tab state --- + + /** @type {Object.} Maps panel ID to Panel instance */ + let _panelMap = {}; + + /** @type {jQueryObject} The single container wrapping all bottom panels */ + let _$container; + + /** @type {jQueryObject} The tab bar inside the container */ + let _$tabBar; + + /** @type {jQueryObject} Scrollable area holding the tab elements */ + let _$tabsOverflow; + + /** @type {string[]} Ordered list of currently open (tabbed) panel IDs */ + let _openIds = []; + + /** @type {string|null} The panel ID of the currently visible (active) tab */ + let _activeId = null; + + // --- Tab helper functions --- + + /** + * Resolve the display title for a bottom panel tab. + * Uses the explicit title if provided, then checks for a .toolbar .title + * DOM element in the panel, and finally derives a name from the panel id. + * @param {string} id The panel registration ID + * @param {jQueryObject} $panel The panel's jQuery element + * @param {string=} title Explicit title passed to createBottomPanel + * @return {string} + * @private + */ + function _getPanelTitle(id, $panel, title) { + if (title) { + return title; + } + let $titleEl = $panel.find(".toolbar .title"); + if ($titleEl.length && $.trim($titleEl.text())) { + return $.trim($titleEl.text()); + } + let label = id.replace(new RegExp("[-_.]", "g"), " ").split(" ")[0]; + return label.charAt(0).toUpperCase() + label.slice(1); + } + + /** + * Full rebuild of the tab bar DOM from _openIds. + * Call this when tabs are added, removed, or renamed. + * @private + */ + function _updateBottomPanelTabBar() { + if (!_$tabsOverflow) { + return; + } + _$tabsOverflow.empty(); + + _openIds.forEach(function (panelId) { + let panel = _panelMap[panelId]; + if (!panel) { + return; + } + let title = panel._tabTitle || _getPanelTitle(panelId, panel.$panel); + let isActive = (panelId === _activeId); + let $tab = $('
' + + '' + $("").text(title).html() + '' + + '×' + + '
'); + _$tabsOverflow.append($tab); + }); + } + + /** + * Swap the .active class on the tab bar without rebuilding the DOM. + * @private + */ + function _updateActiveTabHighlight() { + if (!_$tabBar) { + return; + } + _$tabBar.find(".bottom-panel-tab").each(function () { + let $tab = $(this); + if ($tab.data("panel-id") === _activeId) { + $tab.addClass("active"); + } else { + $tab.removeClass("active"); + } + }); + } + + /** + * Switch the active tab to the given panel. Does not show/hide the container. + * @param {string} panelId + * @private + */ + function _switchToTab(panelId) { + if (_activeId === panelId) { + return; + } + // Remove active class from current + if (_activeId) { + let prevPanel = _panelMap[_activeId]; + if (prevPanel) { + prevPanel.$panel.removeClass("active-bottom-panel"); + } + } + // Set new active + _activeId = panelId; + let newPanel = _panelMap[panelId]; + if (newPanel) { + newPanel.$panel.addClass("active-bottom-panel"); + } + _updateActiveTabHighlight(); + } + /** * Represents a panel below the editor area (a child of ".content"). * @constructor * @param {!jQueryObject} $panel The entire panel, including any chrome, already in the DOM. + * @param {string} id Unique panel identifier. + * @param {string=} title Optional display title for the tab bar. */ - function Panel($panel, id) { + function Panel($panel, id, title) { this.$panel = $panel; this.panelID = id; + this._tabTitle = _getPanelTitle(id, $panel, title); + _panelMap[id] = this; } /** @@ -71,7 +189,7 @@ define(function (require, exports, module) { * @return {boolean} true if visible, false if not */ Panel.prototype.isVisible = function () { - return this.$panel.is(":visible"); + return (_activeId === this.panelID) && _$container && _$container.is(":visible"); }; /** @@ -104,20 +222,74 @@ define(function (require, exports, module) { * Shows the panel */ Panel.prototype.show = function () { - if(!this.isVisible() && this.canBeShown()){ - Resizer.show(this.$panel[0]); - exports.trigger(EVENT_PANEL_SHOWN, this.panelID); + if (!this.canBeShown()) { + return; } + let panelId = this.panelID; + let isOpen = _openIds.indexOf(panelId) !== -1; + let isActive = (_activeId === panelId); + + if (isOpen && isActive) { + // Already open and active — just ensure container is visible + if (!_$container.is(":visible")) { + Resizer.show(_$container[0]); + } + return; + } + if (isOpen && !isActive) { + // Open but not active - switch tab and ensure container is visible + _switchToTab(panelId); + if (!_$container.is(":visible")) { + Resizer.show(_$container[0]); + } + exports.trigger(EVENT_PANEL_SHOWN, panelId); + return; + } + // Not open: add to open set + _openIds.push(panelId); + + // Show container if it was hidden + if (!_$container.is(":visible")) { + Resizer.show(_$container[0]); + } + + _switchToTab(panelId); + _updateBottomPanelTabBar(); + exports.trigger(EVENT_PANEL_SHOWN, panelId); }; /** * Hides the panel */ Panel.prototype.hide = function () { - if(this.isVisible()){ - Resizer.hide(this.$panel[0]); - exports.trigger(EVENT_PANEL_HIDDEN, this.panelID); + let panelId = this.panelID; + let idx = _openIds.indexOf(panelId); + if (idx === -1) { + // Not open - no-op + return; } + + // Remove from open set + _openIds.splice(idx, 1); + this.$panel.removeClass("active-bottom-panel"); + + let wasActive = (_activeId === panelId); + + // Tab was removed — rebuild tab bar, then activate next if needed + if (wasActive && _openIds.length > 0) { + let nextIdx = Math.min(idx, _openIds.length - 1); + let nextId = _openIds[nextIdx]; + _activeId = null; // clear so _switchToTab runs + _switchToTab(nextId); + exports.trigger(EVENT_PANEL_SHOWN, nextId); + } else if (wasActive) { + // No more tabs - hide the container + _activeId = null; + Resizer.hide(_$container[0]); + } + _updateBottomPanelTabBar(); + + exports.trigger(EVENT_PANEL_HIDDEN, panelId); }; /** @@ -132,6 +304,15 @@ define(function (require, exports, module) { } }; + /** + * Updates the display title shown in the tab bar for this panel. + * @param {string} newTitle The new title to display. + */ + Panel.prototype.setTitle = function (newTitle) { + this._tabTitle = newTitle; + _updateBottomPanelTabBar(); + }; + /** * gets the Panel's type * @return {string} @@ -140,10 +321,63 @@ define(function (require, exports, module) { return PANEL_TYPE_BOTTOM_PANEL; }; + /** + * Initializes the PanelView module with references to the bottom panel container DOM elements. + * Called by WorkspaceManager during htmlReady. + * @param {jQueryObject} $container The bottom panel container element. + * @param {jQueryObject} $tabBar The tab bar element inside the container. + * @param {jQueryObject} $tabsOverflow The scrollable area holding tab elements. + */ + function init($container, $tabBar, $tabsOverflow) { + _$container = $container; + _$tabBar = $tabBar; + _$tabsOverflow = $tabsOverflow; + + // Tab bar click handlers + _$tabBar.on("click", ".bottom-panel-tab-close-btn", function (e) { + e.stopPropagation(); + let panelId = $(this).closest(".bottom-panel-tab").data("panel-id"); + if (panelId) { + let panel = _panelMap[panelId]; + if (panel) { + panel.hide(); + } + } + }); + + _$tabBar.on("click", ".bottom-panel-tab", function (e) { + let panelId = $(this).data("panel-id"); + if (panelId && panelId !== _activeId) { + let panel = _panelMap[panelId]; + if (panel) { + panel.show(); + } + } + }); + + // Hide-panel button collapses the container but keeps tabs intact + _$tabBar.on("click", ".bottom-panel-hide-btn", function (e) { + e.stopPropagation(); + if (_$container.is(":visible")) { + Resizer.hide(_$container[0]); + } + }); + } + + /** + * Returns a copy of the currently open bottom panel IDs in tab order. + * @return {string[]} + */ + function getOpenBottomPanelIDs() { + return _openIds.slice(); + } + EventDispatcher.makeEventDispatcher(exports); // Public API exports.Panel = Panel; + exports.init = init; + exports.getOpenBottomPanelIDs = getOpenBottomPanelIDs; //events exports.EVENT_PANEL_HIDDEN = EVENT_PANEL_HIDDEN; exports.EVENT_PANEL_SHOWN = EVENT_PANEL_SHOWN; diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index 8f2c594dcf..232150ae99 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -125,26 +125,12 @@ define(function (require, exports, module) { let lastHiddenBottomPanelStack = [], lastShownBottomPanelStack = []; - // --- Bottom panel tabbed container state --- - /** @type {jQueryObject} The single container wrapping all bottom panels */ let $bottomPanelContainer; - /** @type {jQueryObject} The tab bar inside the container */ - let $bottomPanelTabBar; - - /** @type {jQueryObject} Scrollable area holding the tab elements */ - let $bottomPanelTabsOverflow; - /** @type {jQueryObject} Chevron toggle in the status bar */ let $statusBarPanelToggle; - /** @type {string[]} Ordered list of currently open (tabbed) panel IDs */ - let _openBottomPanelIds = []; - - /** @type {string|null} The panel ID of the currently visible (active) tab */ - let _activeBottomPanelId = null; - /** @type {boolean} True while the status bar toggle button is handling a click */ let _statusBarToggleInProgress = false; @@ -250,107 +236,6 @@ define(function (require, exports, module) { }); } - // --- Bottom panel tab helpers --- - - /** - * Resolve the display title for a bottom panel tab. - * Uses the explicit title if provided, then checks for a .toolbar .title - * DOM element in the panel, and finally derives a name from the panel id. - * @param {string} id The panel registration ID - * @param {jQueryObject} $panel The panel's jQuery element - * @param {string=} title Explicit title passed to createBottomPanel - * @return {string} - * @private - */ - function _getPanelTitle(id, $panel, title) { - if (title) { - return title; - } - let $titleEl = $panel.find(".toolbar .title"); - if ($titleEl.length && $.trim($titleEl.text())) { - return $.trim($titleEl.text()); - } - let label = id.replace(new RegExp("[-_.]", "g"), " ").split(" ")[0]; - return label.charAt(0).toUpperCase() + label.slice(1); - } - - /** - * Full rebuild of the tab bar DOM from _openBottomPanelIds. - * Call this when tabs are added, removed, or renamed. - * @private - */ - function _updateBottomPanelTabBar() { - if (!$bottomPanelTabsOverflow) { - return; - } - $bottomPanelTabsOverflow.empty(); - - _openBottomPanelIds.forEach(function (panelId) { - let panel = panelIDMap[panelId]; - if (!panel) { - return; - } - let title = panel._tabTitle || _getPanelTitle(panelId, panel.$panel); - let isActive = (panelId === _activeBottomPanelId); - let $tab = $('
' + - '' + $("").text(title).html() + '' + - '×' + - '
'); - $bottomPanelTabsOverflow.append($tab); - }); - } - - /** - * Swap the .active class on the tab bar without rebuilding the DOM. - * @private - */ - function _updateActiveTabHighlight() { - if (!$bottomPanelTabBar) { - return; - } - $bottomPanelTabBar.find(".bottom-panel-tab").each(function () { - let $tab = $(this); - if ($tab.data("panel-id") === _activeBottomPanelId) { - $tab.addClass("active"); - } else { - $tab.removeClass("active"); - } - }); - } - - /** - * Switch the active tab to the given panel. Does not show/hide the container. - * @param {string} panelId - * @private - */ - function _switchToTab(panelId) { - if (_activeBottomPanelId === panelId) { - return; - } - // Remove active class from current - if (_activeBottomPanelId) { - let prevPanel = panelIDMap[_activeBottomPanelId]; - if (prevPanel) { - prevPanel.$panel.removeClass("active-bottom-panel"); - } - } - // Set new active - _activeBottomPanelId = panelId; - let newPanel = panelIDMap[panelId]; - if (newPanel) { - newPanel.$panel.addClass("active-bottom-panel"); - } - _updateActiveTabHighlight(); - } - - /** - * Returns a copy of the currently open bottom panel IDs in tab order. - * @return {string[]} - */ - function getOpenBottomPanelIDs() { - return _openBottomPanelIds.slice(); - } - /** * Creates a new resizable panel beneath the editor area and above the status bar footer. Panel is initially invisible. * The panel's size & visibility are automatically saved & restored as a view-state preference. @@ -363,104 +248,11 @@ define(function (require, exports, module) { * @return {!Panel} */ function createBottomPanel(id, $panel, minSize, title) { - // Insert panel into the tabbed container instead of before #status-bar $bottomPanelContainer.append($panel); $panel.hide(); updateResizeLimits(); - - let bottomPanel = new PanelView.Panel($panel, id); + let bottomPanel = new PanelView.Panel($panel, id, title); panelIDMap[id] = bottomPanel; - - // Cache the tab title at creation time - bottomPanel._tabTitle = _getPanelTitle(id, $panel, title); - - // Do NOT call Resizer.makeResizable on individual panels. - // The container handles resizing. - - // --- Override show/hide/isVisible on the Panel instance --- - - bottomPanel.show = function () { - if (!this.canBeShown()) { - return; - } - let panelId = this.panelID; - let isOpen = _openBottomPanelIds.indexOf(panelId) !== -1; - let isActive = (_activeBottomPanelId === panelId); - - if (isOpen && isActive) { - // Already open and active — just ensure container is visible - if (!$bottomPanelContainer.is(":visible")) { - Resizer.show($bottomPanelContainer[0]); - triggerUpdateLayout(); - } - return; - } - if (isOpen && !isActive) { - // Open but not active - switch tab and ensure container is visible - _switchToTab(panelId); - if (!$bottomPanelContainer.is(":visible")) { - Resizer.show($bottomPanelContainer[0]); - } - PanelView.trigger(PanelView.EVENT_PANEL_SHOWN, panelId); - triggerUpdateLayout(); - return; - } - // Not open: add to open set - _openBottomPanelIds.push(panelId); - - // Show container if it was hidden - if (!$bottomPanelContainer.is(":visible")) { - Resizer.show($bottomPanelContainer[0]); - } - - _switchToTab(panelId); - _updateBottomPanelTabBar(); - PanelView.trigger(PanelView.EVENT_PANEL_SHOWN, panelId); - triggerUpdateLayout(); - }; - - bottomPanel.hide = function () { - let panelId = this.panelID; - let idx = _openBottomPanelIds.indexOf(panelId); - if (idx === -1) { - // Not open - no-op - return; - } - - // Remove from open set - _openBottomPanelIds.splice(idx, 1); - this.$panel.removeClass("active-bottom-panel"); - - let wasActive = (_activeBottomPanelId === panelId); - - // Tab was removed — rebuild tab bar, then activate next if needed - if (wasActive && _openBottomPanelIds.length > 0) { - let nextIdx = Math.min(idx, _openBottomPanelIds.length - 1); - let nextId = _openBottomPanelIds[nextIdx]; - _activeBottomPanelId = null; // clear so _switchToTab runs - _switchToTab(nextId); - PanelView.trigger(PanelView.EVENT_PANEL_SHOWN, nextId); - } else if (wasActive) { - // No more tabs - hide the container - _activeBottomPanelId = null; - Resizer.hide($bottomPanelContainer[0]); - } - _updateBottomPanelTabBar(); - - PanelView.trigger(PanelView.EVENT_PANEL_HIDDEN, panelId); - triggerUpdateLayout(); - }; - - bottomPanel.isVisible = function () { - return (_activeBottomPanelId === this.panelID) && - $bottomPanelContainer.is(":visible"); - }; - - bottomPanel.setTitle = function (newTitle) { - this._tabTitle = newTitle; - _updateBottomPanelTabBar(); - }; - return bottomPanel; } @@ -538,8 +330,8 @@ define(function (require, exports, module) { // --- Create the bottom panel tabbed container --- $bottomPanelContainer = $('
'); - $bottomPanelTabBar = $('
'); - $bottomPanelTabsOverflow = $('
'); + let $bottomPanelTabBar = $('
'); + let $bottomPanelTabsOverflow = $('
'); let $tabBarActions = $('
'); $tabBarActions.append( '' @@ -550,6 +342,9 @@ define(function (require, exports, module) { $bottomPanelContainer.insertBefore("#status-bar"); $bottomPanelContainer.hide(); + // Initialize PanelView with container DOM references and tab bar click handlers + PanelView.init($bottomPanelContainer, $bottomPanelTabBar, $bottomPanelTabsOverflow); + // Create status bar chevron toggle for bottom panel $statusBarPanelToggle = $( '
' + @@ -563,7 +358,7 @@ define(function (require, exports, module) { if ($bottomPanelContainer.is(":visible")) { Resizer.hide($bottomPanelContainer[0]); triggerUpdateLayout(); - } else if (_openBottomPanelIds.length > 0) { + } else if (PanelView.getOpenBottomPanelIDs().length > 0) { Resizer.show($bottomPanelContainer[0]); triggerUpdateLayout(); } else { @@ -597,36 +392,6 @@ define(function (require, exports, module) { } }); - // Tab bar click handlers - $bottomPanelTabBar.on("click", ".bottom-panel-tab-close-btn", function (e) { - e.stopPropagation(); - let panelId = $(this).closest(".bottom-panel-tab").data("panel-id"); - if (panelId) { - let panel = panelIDMap[panelId]; - if (panel) { - panel.hide(); - } - } - }); - - $bottomPanelTabBar.on("click", ".bottom-panel-tab", function (e) { - let panelId = $(this).data("panel-id"); - if (panelId && panelId !== _activeBottomPanelId) { - _switchToTab(panelId); - PanelView.trigger(PanelView.EVENT_PANEL_SHOWN, panelId); - triggerUpdateLayout(); - } - }); - - // Hide-panel button collapses the container but keeps tabs intact - $bottomPanelTabBar.on("click", ".bottom-panel-hide-btn", function (e) { - e.stopPropagation(); - if ($bottomPanelContainer.is(":visible")) { - Resizer.hide($bottomPanelContainer[0]); - triggerUpdateLayout(); - } - }); - // Sidebar is a special case: it isn't a Panel, and is not created dynamically. Need to explicitly // listen for resize here. listenToResize($("#sidebar")); @@ -658,12 +423,14 @@ define(function (require, exports, module) { lastShownBottomPanelStack = lastShownBottomPanelStack.filter(item => item !== panelID); lastShownBottomPanelStack.push(panelID); exports.trigger(EVENT_WORKSPACE_PANEL_SHOWN, panelID); + triggerUpdateLayout(); }); PanelView.on(PanelView.EVENT_PANEL_HIDDEN, (event, panelID)=>{ lastHiddenBottomPanelStack = lastHiddenBottomPanelStack.filter(item => item !== panelID); lastHiddenBottomPanelStack.push(panelID); lastShownBottomPanelStack = lastShownBottomPanelStack.filter(item => item !== panelID); exports.trigger(EVENT_WORKSPACE_PANEL_HIDDEN, panelID); + triggerUpdateLayout(); }); let currentlyShownPanel = null, @@ -875,7 +642,7 @@ define(function (require, exports, module) { exports.recomputeLayout = recomputeLayout; exports.getAllPanelIDs = getAllPanelIDs; exports.getPanelForID = getPanelForID; - exports.getOpenBottomPanelIDs = getOpenBottomPanelIDs; + exports.getOpenBottomPanelIDs = PanelView.getOpenBottomPanelIDs; exports.addEscapeKeyEventHandler = addEscapeKeyEventHandler; exports.removeEscapeKeyEventHandler = removeEscapeKeyEventHandler; exports._setMockDOM = _setMockDOM; From 02af27ce4ad36cc5a97ddc454c5939bb542a16c8 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 14:16:16 +0530 Subject: [PATCH 25/44] feat: set title and attribute using the functions --- src/view/PanelView.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/view/PanelView.js b/src/view/PanelView.js index d18682def4..3fe0f53067 100644 --- a/src/view/PanelView.js +++ b/src/view/PanelView.js @@ -112,10 +112,11 @@ define(function (require, exports, module) { } let title = panel._tabTitle || _getPanelTitle(panelId, panel.$panel); let isActive = (panelId === _activeId); - let $tab = $('
' + - '' + $("").text(title).html() + '' + - '×' + - '
'); + let $tab = $('
') + .toggleClass('active', isActive) + .attr('data-panel-id', panelId); + $tab.append($('').text(title)); + $tab.append($('×').attr('title', Strings.CLOSE)); _$tabsOverflow.append($tab); }); } From d67e40fb7a6b1f85ea361199ddd7f0e70d743b9d Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 14:27:32 +0530 Subject: [PATCH 26/44] refactor: set title and attr using functions --- src/view/WorkspaceManager.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index 232150ae99..3df16d7313 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -334,7 +334,8 @@ define(function (require, exports, module) { let $bottomPanelTabsOverflow = $('
'); let $tabBarActions = $('
'); $tabBarActions.append( - '' + $('') + .attr('title', Strings.BOTTOM_PANEL_HIDE) ); $bottomPanelTabBar.append($bottomPanelTabsOverflow); $bottomPanelTabBar.append($tabBarActions); @@ -346,11 +347,8 @@ define(function (require, exports, module) { PanelView.init($bottomPanelContainer, $bottomPanelTabBar, $bottomPanelTabsOverflow); // Create status bar chevron toggle for bottom panel - $statusBarPanelToggle = $( - '
' + - '' + - '
' - ); + $statusBarPanelToggle = $('
') + .attr('title', Strings.BOTTOM_PANEL_SHOW); $("#status-indicators").prepend($statusBarPanelToggle); $statusBarPanelToggle.on("click", function () { From b5ed8b4cad7a824eb48adc047de97401f7f512a7 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 15:12:19 +0530 Subject: [PATCH 27/44] fix: escape key destroying non-visible panels --- src/view/WorkspaceManager.js | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index 3df16d7313..8371a160dd 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -378,6 +378,16 @@ define(function (require, exports, module) { if (!_statusBarToggleInProgress) { AnimationUtils.animateUsingClass($statusBarPanelToggle[0], "flash", 800); } + // When the container collapses while tabs are still open (e.g. chevron + // click or status-bar toggle), move those panels from the "shown" stack + // to the "hidden" stack so the Escape-key handler doesn't silently close + // tabs that the user can't even see. + let openIds = PanelView.getOpenBottomPanelIDs(); + for (let i = 0; i < openIds.length; i++) { + lastShownBottomPanelStack = lastShownBottomPanelStack.filter(item => item !== openIds[i]); + lastHiddenBottomPanelStack = lastHiddenBottomPanelStack.filter(item => item !== openIds[i]); + lastHiddenBottomPanelStack.push(openIds[i]); + } }); $bottomPanelContainer.on("panelExpanded", function () { @@ -388,6 +398,14 @@ define(function (require, exports, module) { if (!_statusBarToggleInProgress) { AnimationUtils.animateUsingClass($statusBarPanelToggle[0], "flash", 800); } + // When the container re-expands, move the open panels back from + // the "hidden" stack to the "shown" stack. + let openIds = PanelView.getOpenBottomPanelIDs(); + for (let i = 0; i < openIds.length; i++) { + lastHiddenBottomPanelStack = lastHiddenBottomPanelStack.filter(item => item !== openIds[i]); + lastShownBottomPanelStack = lastShownBottomPanelStack.filter(item => item !== openIds[i]); + lastShownBottomPanelStack.push(openIds[i]); + } }); // Sidebar is a special case: it isn't a Panel, and is not created dynamically. Need to explicitly @@ -574,10 +592,15 @@ define(function (require, exports, module) { function _handleEscapeKey() { let allPanelsIDs = getAllPanelIDs(); // first we see if there is any least recently shown panel - if(lastShownBottomPanelStack.length > 0){ + if (lastShownBottomPanelStack.length > 0) { let panelToHide = getPanelForID(lastShownBottomPanelStack.pop()); - panelToHide.hide(); - return true; + // Guard: only hide if the panel is actually visible. When the + // container is collapsed the stacks are updated via panelCollapsed, + // but this acts as a safety net against stale entries. + if (panelToHide && panelToHide.isVisible()) { + panelToHide.hide(); + return true; + } } // if not, see if there is any open panels that are not yet tracked in the least recently used stacks. for(let panelID of allPanelsIDs){ From c0de072ef95c9a06c8031ee5949b592488808a94 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 15:16:24 +0530 Subject: [PATCH 28/44] feat: update the title only on setTitle instead of full dom rebuild --- src/view/PanelView.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/view/PanelView.js b/src/view/PanelView.js index 3fe0f53067..8bdf0157b0 100644 --- a/src/view/PanelView.js +++ b/src/view/PanelView.js @@ -311,7 +311,10 @@ define(function (require, exports, module) { */ Panel.prototype.setTitle = function (newTitle) { this._tabTitle = newTitle; - _updateBottomPanelTabBar(); + if (_$tabsOverflow) { + _$tabsOverflow.find('.bottom-panel-tab[data-panel-id="' + this.panelID + '"] .bottom-panel-tab-title') + .text(newTitle); + } }; /** From 9f2e063ab8049c9986e68ee5841517d7e692896e Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 15:21:23 +0530 Subject: [PATCH 29/44] feat: hide all open bottom panels at once instead of closing them individually --- .../NoDistractions/main.js | 26 ++++++------ src/view/PanelView.js | 40 +++++++++++++++++++ src/view/WorkspaceManager.js | 1 + 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/extensionsIntegrated/NoDistractions/main.js b/src/extensionsIntegrated/NoDistractions/main.js index 17894b8c54..55f8d27a94 100644 --- a/src/extensionsIntegrated/NoDistractions/main.js +++ b/src/extensionsIntegrated/NoDistractions/main.js @@ -87,20 +87,20 @@ define(function (require, exports, module) { * hide all open panels */ function _hidePanelsIfRequired() { - var panelIDs = WorkspaceManager.getAllPanelIDs(); _previouslyOpenPanelIDs = []; - // Loop until no visible panels remain. In a tabbed system, hiding the - // active tab may reveal the next tab, so we must iterate. - let hiddenSomething = true; - while (hiddenSomething) { - hiddenSomething = false; - for (let i = 0; i < panelIDs.length; i++) { - let panel = WorkspaceManager.getPanelForID(panelIDs[i]); - if (panel && panel.isVisible()) { - panel.hide(); - _previouslyOpenPanelIDs.push(panelIDs[i]); - hiddenSomething = true; - } + + // Batch-hide all open bottom panel tabs in one pass, avoiding O(n) + // intermediate tab activations and layout recalcs. + let hiddenBottomPanels = WorkspaceManager.hideAllOpenBottomPanels(); + _previouslyOpenPanelIDs = hiddenBottomPanels; + + // Hide any remaining visible panels (e.g. plugin side-panels) + let panelIDs = WorkspaceManager.getAllPanelIDs(); + for (let i = 0; i < panelIDs.length; i++) { + let panel = WorkspaceManager.getPanelForID(panelIDs[i]); + if (panel && panel.isVisible()) { + panel.hide(); + _previouslyOpenPanelIDs.push(panelIDs[i]); } } } diff --git a/src/view/PanelView.js b/src/view/PanelView.js index 8bdf0157b0..549d0b5340 100644 --- a/src/view/PanelView.js +++ b/src/view/PanelView.js @@ -376,12 +376,52 @@ define(function (require, exports, module) { return _openIds.slice(); } + /** + * Hides every open bottom panel tab in a single batch + * @return {string[]} The IDs of panels that were open (useful for restoring later). + */ + function hideAllOpenPanels() { + if (_openIds.length === 0) { + return []; + } + let closedIds = _openIds.slice(); + + // Remove visual active state from every panel + for (let i = 0; i < closedIds.length; i++) { + let panel = _panelMap[closedIds[i]]; + if (panel) { + panel.$panel.removeClass("active-bottom-panel"); + } + } + + // Clear internal state BEFORE hiding the container so the + // panelCollapsed handler sees an empty _openIds and doesn't + // redundantly update the stacks. + _openIds = []; + _activeId = null; + + if (_$container && _$container.is(":visible")) { + Resizer.hide(_$container[0]); + } + + _updateBottomPanelTabBar(); + + // Fire one EVENT_PANEL_HIDDEN per panel for stack tracking. + // No intermediate EVENT_PANEL_SHOWN events are emitted. + for (let i = 0; i < closedIds.length; i++) { + exports.trigger(EVENT_PANEL_HIDDEN, closedIds[i]); + } + + return closedIds; + } + EventDispatcher.makeEventDispatcher(exports); // Public API exports.Panel = Panel; exports.init = init; exports.getOpenBottomPanelIDs = getOpenBottomPanelIDs; + exports.hideAllOpenPanels = hideAllOpenPanels; //events exports.EVENT_PANEL_HIDDEN = EVENT_PANEL_HIDDEN; exports.EVENT_PANEL_SHOWN = EVENT_PANEL_SHOWN; diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index 8371a160dd..08cbfe6f1b 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -664,6 +664,7 @@ define(function (require, exports, module) { exports.getAllPanelIDs = getAllPanelIDs; exports.getPanelForID = getPanelForID; exports.getOpenBottomPanelIDs = PanelView.getOpenBottomPanelIDs; + exports.hideAllOpenBottomPanels = PanelView.hideAllOpenPanels; exports.addEscapeKeyEventHandler = addEscapeKeyEventHandler; exports.removeEscapeKeyEventHandler = removeEscapeKeyEventHandler; exports._setMockDOM = _setMockDOM; From 2b36844d3268dd96e34998e1ef18eba364f1e1e0 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 15:24:30 +0530 Subject: [PATCH 30/44] fix: event shown event not firing --- src/view/PanelView.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/view/PanelView.js b/src/view/PanelView.js index 549d0b5340..a857972fc1 100644 --- a/src/view/PanelView.js +++ b/src/view/PanelView.js @@ -234,6 +234,7 @@ define(function (require, exports, module) { // Already open and active — just ensure container is visible if (!_$container.is(":visible")) { Resizer.show(_$container[0]); + exports.trigger(EVENT_PANEL_SHOWN, panelId); } return; } From 86d37542e89ab584727a9380f84d98a5bcb13665 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 15:35:36 +0530 Subject: [PATCH 31/44] feat: remove snippets count tracking from the title --- .../CustomSnippets/UIHelper.js | 26 ++++++------------- .../CustomSnippets/helper.js | 15 ----------- .../CustomSnippets/snippetsList.js | 2 -- 3 files changed, 8 insertions(+), 35 deletions(-) diff --git a/src/extensionsIntegrated/CustomSnippets/UIHelper.js b/src/extensionsIntegrated/CustomSnippets/UIHelper.js index 49370097a7..8a4f59b6f6 100644 --- a/src/extensionsIntegrated/CustomSnippets/UIHelper.js +++ b/src/extensionsIntegrated/CustomSnippets/UIHelper.js @@ -21,7 +21,6 @@ /* eslint-disable no-invalid-this */ define(function (require, exports, module) { const StringUtils = require("utils/StringUtils"); - const Global = require("./global"); const Strings = require("strings"); /** @type {Object} Reference to the panel instance, set via init() */ @@ -150,7 +149,9 @@ define(function (require, exports, module) { $addNewSnippetBtn.removeClass("hidden"); $filterSnippetsPanel.removeClass("hidden"); - _updateListTabTitle(); + if (_panel) { + _panel.setTitle(Strings.CUSTOM_SNIPPETS_PANEL_TITLE); + } $("#filter-snippets-input").val(""); } @@ -211,24 +212,13 @@ define(function (require, exports, module) { } /** - * Updates the tab title to show the list view format: "Custom Snippets (N)" - * @private - */ - function _updateListTabTitle() { - if (!_panel) { - return; - } - const snippetCount = Global.SnippetHintsList.length; - const countText = snippetCount > 0 ? ` (${snippetCount})` : ""; - _panel.setTitle(Strings.CUSTOM_SNIPPETS_PANEL_TITLE + countText); - } - - /** - * Initializes the tab title for the list view. - * This is called when the panel is first opened to ensure the snippet count is displayed. + * Resets the tab title back to the default list view title. + * Called when the panel is first opened or toggled visible. */ function initializeListViewToolbarTitle() { - _updateListTabTitle(); + if (_panel) { + _panel.setTitle(Strings.CUSTOM_SNIPPETS_PANEL_TITLE); + } } /** diff --git a/src/extensionsIntegrated/CustomSnippets/helper.js b/src/extensionsIntegrated/CustomSnippets/helper.js index f68dfe6294..43713dcda5 100644 --- a/src/extensionsIntegrated/CustomSnippets/helper.js +++ b/src/extensionsIntegrated/CustomSnippets/helper.js @@ -569,20 +569,6 @@ define(function (require, exports, module) { $("#edit-file-extn-box").val(""); } - /** - * Updates the snippets count which is displayed in the toolbar at the left side - * @private - */ - function updateSnippetsCount() { - const count = Global.SnippetHintsList.length; - const $countSpan = $("#snippets-count"); - if (count > 0) { - $countSpan.text(`(${count})`); - } else { - $countSpan.text(""); - } - } - /** * validates and sanitizes file extension input * @@ -932,7 +918,6 @@ define(function (require, exports, module) { exports.isSnippetSupportedInFile = isSnippetSupportedInFile; exports.hasExactMatchingSnippet = hasExactMatchingSnippet; exports.getMatchingSnippets = getMatchingSnippets; - exports.updateSnippetsCount = updateSnippetsCount; exports.sanitizeFileExtensionInput = sanitizeFileExtensionInput; exports.handleFileExtensionInput = handleFileExtensionInput; exports.handleFileExtensionKeypress = handleFileExtensionKeypress; diff --git a/src/extensionsIntegrated/CustomSnippets/snippetsList.js b/src/extensionsIntegrated/CustomSnippets/snippetsList.js index 6215842986..de45f3d95f 100644 --- a/src/extensionsIntegrated/CustomSnippets/snippetsList.js +++ b/src/extensionsIntegrated/CustomSnippets/snippetsList.js @@ -180,8 +180,6 @@ define(function (require, exports, module) { console.error("failed to delete custom snippet correctly:", error); }); - // update the snippets count in toolbar - Helper.updateSnippetsCount(); // Refresh the entire list to properly handle filtering showSnippetsList(); } From 7478ab030c1fa3279b83839da6964e6b4010e020 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 16:39:23 +0530 Subject: [PATCH 32/44] fix: git more options button appearing at incorrect position due to race condition --- src/extensions/default/Git/styles/git-styles.less | 1 + 1 file changed, 1 insertion(+) diff --git a/src/extensions/default/Git/styles/git-styles.less b/src/extensions/default/Git/styles/git-styles.less index b9b3260f20..0cf885384b 100644 --- a/src/extensions/default/Git/styles/git-styles.less +++ b/src/extensions/default/Git/styles/git-styles.less @@ -1017,6 +1017,7 @@ .git-more-options-btn { position: absolute; right: 8px; + top: 5px; padding: 4px 9px 2px 8px; opacity: .7; .dark & { From 99fd03173b274f97f232947af8768d66797b5752 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 17:32:49 +0530 Subject: [PATCH 33/44] feat: open quick access panel when on other panel is opened --- src/brackets.js | 1 + src/nls/root/strings.js | 3 + src/styles/Extn-BottomPanelTabs.less | 94 +++++++++++++++++ src/view/DefaultPanelView.js | 145 +++++++++++++++++++++++++++ src/view/WorkspaceManager.js | 72 ++++++------- 5 files changed, 273 insertions(+), 42 deletions(-) create mode 100644 src/view/DefaultPanelView.js diff --git a/src/brackets.js b/src/brackets.js index b14f4e1b99..4071ad0d25 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -135,6 +135,7 @@ define(function (require, exports, module) { require("utils/NodeUtils"); require("utils/ColorUtils"); require("view/ThemeManager"); + require("view/DefaultPanelView"); require("thirdparty/lodash"); require("language/XMLUtils"); require("language/JSONUtils"); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 52c7548ac5..4eb9876e55 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1254,6 +1254,9 @@ define({ "BOTTOM_PANEL_HIDE": "Hide Panel", "BOTTOM_PANEL_SHOW": "Show Bottom Panel", "BOTTOM_PANEL_HIDE_TOGGLE": "Hide Bottom Panel", + "BOTTOM_PANEL_DEFAULT_TITLE": "Quick Access", + "BOTTOM_PANEL_DEFAULT_HEADING": "Open a Panel", + "BOTTOM_PANEL_DEFAULT_READ_MORE": "Read More", "CMD_FIND_DOCUMENT_SYMBOLS": "Find Document Symbols", "CMD_FIND_PROJECT_SYMBOLS": "Find Project Symbols", diff --git a/src/styles/Extn-BottomPanelTabs.less b/src/styles/Extn-BottomPanelTabs.less index 71bfdfb111..dd8005d0b8 100644 --- a/src/styles/Extn-BottomPanelTabs.less +++ b/src/styles/Extn-BottomPanelTabs.less @@ -232,3 +232,97 @@ } } } + +.default-panel-content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + padding: 16px; + gap: 12px; + user-select: none; +} + +.default-panel-heading { + font-size: 13px; + font-weight: 600; + color: #555; + margin-bottom: 4px; + + .dark & { + color: #bbb; + } +} + +.default-panel-buttons { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 8px; +} + +.default-panel-btn { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 6px; + width: 120px; + height: 72px; + border: 1px solid rgba(0, 0, 0, 0.12); + border-radius: 6px; + background: rgba(0, 0, 0, 0.03); + color: #444; + cursor: pointer; + transition: background-color 0.15s, border-color 0.15s; + + i { + font-size: 20px; + } + + .default-panel-btn-label { + font-size: 11px; + text-align: center; + line-height: 1.2; + max-width: 110px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &:hover { + background: rgba(0, 0, 0, 0.07); + border-color: rgba(0, 0, 0, 0.25); + } + + &:active { + background: rgba(0, 0, 0, 0.12); + } + + .dark & { + border-color: rgba(255, 255, 255, 0.15); + background: rgba(255, 255, 255, 0.05); + color: #ccc; + + &:hover { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.3); + } + + &:active { + background: rgba(255, 255, 255, 0.15); + } + } +} + +.default-panel-read-more { + font-size: 12px; + color: @bc-text-link; + text-decoration: none; + margin-top: 4px; + + &:hover { + text-decoration: underline; + } +} diff --git a/src/view/DefaultPanelView.js b/src/view/DefaultPanelView.js new file mode 100644 index 0000000000..b54da92c8e --- /dev/null +++ b/src/view/DefaultPanelView.js @@ -0,0 +1,145 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2021 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/** + * DefaultPanelView - A launcher panel shown in the bottom panel area when no + * other panels are open. Provides quick-access buttons for common panels + * (Problems, Find in Files, Git, Custom Snippets, Keyboard Shortcuts) and a + * link to the documentation. + * + * @module view/DefaultPanelView + */ +define(function (require, exports, module) { + + const AppInit = require("utils/AppInit"), + Commands = require("command/Commands"), + CommandManager = require("command/CommandManager"), + Strings = require("strings"), + WorkspaceManager = require("view/WorkspaceManager"), + PanelView = require("view/PanelView"); + + const DOCS_URL = "https://docs.phcode.dev"; + + /** + * Descriptors for each launcher button. + * `commandID` may be undefined if the command is registered later (e.g. Git). + */ + const _panelButtons = [ + { + id: "problems", + icon: "fa-solid fa-triangle-exclamation", + label: Strings.CMD_VIEW_TOGGLE_PROBLEMS || "Problems", + commandID: Commands.VIEW_TOGGLE_PROBLEMS + }, + { + id: "search", + icon: "fa-solid fa-magnifying-glass", + label: Strings.CMD_FIND_IN_FILES || "Find in Files", + commandID: Commands.CMD_FIND_IN_FILES + }, + { + id: "git", + icon: "fa-solid fa-code-branch", + label: Strings.GIT_PANEL_TITLE || "Git", + commandID: Commands.CMD_GIT_TOGGLE_PANEL + }, + { + id: "snippets", + icon: "fa-solid fa-code", + label: Strings.CUSTOM_SNIPPETS_PANEL_TITLE || "Custom Snippets", + commandID: "custom_snippets" + }, + { + id: "shortcuts", + icon: "fa-solid fa-keyboard", + label: Strings.KEYBOARD_SHORTCUT_PANEL_TITLE || "Keyboard Shortcuts", + commandID: Commands.HELP_TOGGLE_SHORTCUTS_PANEL + } + ]; + + /** @type {Panel} The default panel instance */ + let _panel; + + /** + * Build the panel DOM. + * @return {jQueryObject} + * @private + */ + function _buildPanelHTML() { + let $panel = $('
'); + let $content = $('
'); + let $heading = $('
') + .text(Strings.BOTTOM_PANEL_DEFAULT_HEADING); + $content.append($heading); + + let $buttonsRow = $('
'); + + _panelButtons.forEach(function (btn) { + let $button = $('') + .attr("data-command", btn.commandID) + .attr("title", btn.label); + let $icon = $('').addClass(btn.icon); + let $label = $('').text(btn.label); + $button.append($icon).append($label); + $buttonsRow.append($button); + }); + + $content.append($buttonsRow); + + let $readMore = $('') + .attr("href", DOCS_URL) + .text(Strings.BOTTOM_PANEL_DEFAULT_READ_MORE + " \u2192"); + $content.append($readMore); + + $panel.append($content); + return $panel; + } + + /** + * Initialise the default panel. Called once at appReady. + * @private + */ + function _init() { + let $panel = _buildPanelHTML(); + _panel = WorkspaceManager.createBottomPanel( + WorkspaceManager.DEFAULT_PANEL_ID, + $panel, + undefined, + Strings.BOTTOM_PANEL_DEFAULT_TITLE + ); + + // Button click handler: execute the command to open the target panel. + // The auto-hide listener (EVENT_PANEL_SHOWN) will close the default panel. + $panel.on("click", ".default-panel-btn", function () { + let commandID = $(this).attr("data-command"); + if (commandID) { + CommandManager.execute(commandID); + } + }); + + // Auto-hide when any other panel is shown. + // hide() is a no-op if the panel is already closed, so no guard needed. + PanelView.on(PanelView.EVENT_PANEL_SHOWN, function (event, panelID) { + if (panelID !== WorkspaceManager.DEFAULT_PANEL_ID) { + _panel.hide(); + } + }); + } + + AppInit.appReady(_init); +}); diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index 08cbfe6f1b..9ce586a687 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -47,6 +47,13 @@ define(function (require, exports, module) { EditorManager = require("editor/EditorManager"), KeyEvent = require("utils/KeyEvent"); + /** + * Panel ID for the default launcher panel shown when no other panels are open. + * @const + * @private + */ + const DEFAULT_PANEL_ID = "workspace.defaultPanel"; + /** * Event triggered when the workspace layout updates. @@ -122,8 +129,7 @@ define(function (require, exports, module) { */ var windowResizing = false; - let lastHiddenBottomPanelStack = [], - lastShownBottomPanelStack = []; + let lastShownBottomPanelStack = []; /** @type {jQueryObject} The single container wrapping all bottom panels */ let $bottomPanelContainer; @@ -360,7 +366,7 @@ define(function (require, exports, module) { Resizer.show($bottomPanelContainer[0]); triggerUpdateLayout(); } else { - _showLastHiddenPanelIfPossible(); + _showDefaultPanel(); } _statusBarToggleInProgress = false; }); @@ -378,15 +384,12 @@ define(function (require, exports, module) { if (!_statusBarToggleInProgress) { AnimationUtils.animateUsingClass($statusBarPanelToggle[0], "flash", 800); } - // When the container collapses while tabs are still open (e.g. chevron - // click or status-bar toggle), move those panels from the "shown" stack - // to the "hidden" stack so the Escape-key handler doesn't silently close - // tabs that the user can't even see. + // When the container collapses while tabs are still open, clear the + // shown stack so the Escape-key handler doesn't silently close tabs + // that the user can't even see. let openIds = PanelView.getOpenBottomPanelIDs(); for (let i = 0; i < openIds.length; i++) { lastShownBottomPanelStack = lastShownBottomPanelStack.filter(item => item !== openIds[i]); - lastHiddenBottomPanelStack = lastHiddenBottomPanelStack.filter(item => item !== openIds[i]); - lastHiddenBottomPanelStack.push(openIds[i]); } }); @@ -398,11 +401,9 @@ define(function (require, exports, module) { if (!_statusBarToggleInProgress) { AnimationUtils.animateUsingClass($statusBarPanelToggle[0], "flash", 800); } - // When the container re-expands, move the open panels back from - // the "hidden" stack to the "shown" stack. + // When the container re-expands, add the open panels to the shown stack. let openIds = PanelView.getOpenBottomPanelIDs(); for (let i = 0; i < openIds.length; i++) { - lastHiddenBottomPanelStack = lastHiddenBottomPanelStack.filter(item => item !== openIds[i]); lastShownBottomPanelStack = lastShownBottomPanelStack.filter(item => item !== openIds[i]); lastShownBottomPanelStack.push(openIds[i]); } @@ -435,15 +436,12 @@ define(function (require, exports, module) { EventDispatcher.makeEventDispatcher(exports); PanelView.on(PanelView.EVENT_PANEL_SHOWN, (event, panelID)=>{ - lastHiddenBottomPanelStack = lastHiddenBottomPanelStack.filter(item => item !== panelID); lastShownBottomPanelStack = lastShownBottomPanelStack.filter(item => item !== panelID); lastShownBottomPanelStack.push(panelID); exports.trigger(EVENT_WORKSPACE_PANEL_SHOWN, panelID); triggerUpdateLayout(); }); PanelView.on(PanelView.EVENT_PANEL_HIDDEN, (event, panelID)=>{ - lastHiddenBottomPanelStack = lastHiddenBottomPanelStack.filter(item => item !== panelID); - lastHiddenBottomPanelStack.push(panelID); lastShownBottomPanelStack = lastShownBottomPanelStack.filter(item => item !== panelID); exports.trigger(EVENT_WORKSPACE_PANEL_HIDDEN, panelID); triggerUpdateLayout(); @@ -578,46 +576,37 @@ define(function (require, exports, module) { return false; } - function _showLastHiddenPanelIfPossible() { - while(lastHiddenBottomPanelStack.length > 0){ - let panelToShow = getPanelForID(lastHiddenBottomPanelStack.pop()); - if(panelToShow.canBeShown()){ - panelToShow.show(); - return true; - } + /** + * Shows the default launcher panel when no other panels are open. + * @private + */ + function _showDefaultPanel() { + let defaultPanel = panelIDMap[DEFAULT_PANEL_ID]; + if (defaultPanel) { + defaultPanel.show(); } - return false; } function _handleEscapeKey() { - let allPanelsIDs = getAllPanelIDs(); - // first we see if there is any least recently shown panel + // Hide the most recently shown bottom panel if (lastShownBottomPanelStack.length > 0) { let panelToHide = getPanelForID(lastShownBottomPanelStack.pop()); - // Guard: only hide if the panel is actually visible. When the - // container is collapsed the stacks are updated via panelCollapsed, - // but this acts as a safety net against stale entries. + // Guard: only hide if the panel is actually visible. if (panelToHide && panelToHide.isVisible()) { panelToHide.hide(); return true; } } - // if not, see if there is any open panels that are not yet tracked in the least recently used stacks. - for(let panelID of allPanelsIDs){ + // Fallback: hide any visible bottom panel not tracked in the stack. + let allPanelsIDs = getAllPanelIDs(); + for (let panelID of allPanelsIDs) { let panel = getPanelForID(panelID); - if(panel.getPanelType() === PanelView.PANEL_TYPE_BOTTOM_PANEL && panel.isVisible()){ + if (panel.getPanelType() === PanelView.PANEL_TYPE_BOTTOM_PANEL && panel.isVisible()) { panel.hide(); - lastHiddenBottomPanelStack.push(panelID); return true; } } - // no panels hidden, we will toggle the last hidden panel with succeeding escape key presses - return _showLastHiddenPanelIfPossible(); - } - - function _handleShiftEscapeKey() { - // show hidden panels one by one - return _showLastHiddenPanelIfPossible(); + return false; } // pressing escape when focused on editor will toggle the last opened bottom panel @@ -644,9 +633,7 @@ define(function (require, exports, module) { return; } - if (event.keyCode === KeyEvent.DOM_VK_ESCAPE && event.shiftKey) { - _handleShiftEscapeKey(); - } else if (event.keyCode === KeyEvent.DOM_VK_ESCAPE) { + if (event.keyCode === KeyEvent.DOM_VK_ESCAPE) { _handleEscapeKey(); } @@ -671,6 +658,7 @@ define(function (require, exports, module) { exports.EVENT_WORKSPACE_UPDATE_LAYOUT = EVENT_WORKSPACE_UPDATE_LAYOUT; exports.EVENT_WORKSPACE_PANEL_SHOWN = EVENT_WORKSPACE_PANEL_SHOWN; exports.EVENT_WORKSPACE_PANEL_HIDDEN = EVENT_WORKSPACE_PANEL_HIDDEN; + exports.DEFAULT_PANEL_ID = DEFAULT_PANEL_ID; /** * Constant representing the type of bottom panel From e4d5495ac65856c8fba93732c51e9a9597214119 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 17:47:08 +0530 Subject: [PATCH 34/44] feat: remove read more from the quick access panel for better accessibility --- src/nls/root/strings.js | 1 - src/styles/Extn-BottomPanelTabs.less | 17 ++++------------- src/view/DefaultPanelView.js | 8 -------- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 4eb9876e55..71aaa7bf13 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1256,7 +1256,6 @@ define({ "BOTTOM_PANEL_HIDE_TOGGLE": "Hide Bottom Panel", "BOTTOM_PANEL_DEFAULT_TITLE": "Quick Access", "BOTTOM_PANEL_DEFAULT_HEADING": "Open a Panel", - "BOTTOM_PANEL_DEFAULT_READ_MORE": "Read More", "CMD_FIND_DOCUMENT_SYMBOLS": "Find Document Symbols", "CMD_FIND_PROJECT_SYMBOLS": "Find Project Symbols", diff --git a/src/styles/Extn-BottomPanelTabs.less b/src/styles/Extn-BottomPanelTabs.less index dd8005d0b8..d2bb588270 100644 --- a/src/styles/Extn-BottomPanelTabs.less +++ b/src/styles/Extn-BottomPanelTabs.less @@ -245,8 +245,10 @@ } .default-panel-heading { - font-size: 13px; - font-weight: 600; + font-size: 14px; + letter-spacing: 0.6px; + word-spacing: 1px; + font-weight: 500; color: #555; margin-bottom: 4px; @@ -315,14 +317,3 @@ } } } - -.default-panel-read-more { - font-size: 12px; - color: @bc-text-link; - text-decoration: none; - margin-top: 4px; - - &:hover { - text-decoration: underline; - } -} diff --git a/src/view/DefaultPanelView.js b/src/view/DefaultPanelView.js index b54da92c8e..23ff14ecdf 100644 --- a/src/view/DefaultPanelView.js +++ b/src/view/DefaultPanelView.js @@ -33,11 +33,8 @@ define(function (require, exports, module) { WorkspaceManager = require("view/WorkspaceManager"), PanelView = require("view/PanelView"); - const DOCS_URL = "https://docs.phcode.dev"; - /** * Descriptors for each launcher button. - * `commandID` may be undefined if the command is registered later (e.g. Git). */ const _panelButtons = [ { @@ -101,11 +98,6 @@ define(function (require, exports, module) { $content.append($buttonsRow); - let $readMore = $('') - .attr("href", DOCS_URL) - .text(Strings.BOTTOM_PANEL_DEFAULT_READ_MORE + " \u2192"); - $content.append($readMore); - $panel.append($content); return $panel; } From a75e17f8affad2b2fdb4a7472438d09a55f89507 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 21:56:00 +0530 Subject: [PATCH 35/44] feat: show no results page when no results match in find in files --- src/search/FindInFilesUI.js | 2 +- src/search/SearchResultsView.js | 51 ++++++++++++++++++++++++++------- src/styles/brackets.less | 25 ++++++++++++++++ 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/src/search/FindInFilesUI.js b/src/search/FindInFilesUI.js index 0aea171f61..8ce87f25a4 100644 --- a/src/search/FindInFilesUI.js +++ b/src/search/FindInFilesUI.js @@ -111,7 +111,7 @@ define(function (require, exports, module) { } } else { - _resultsView.close(); + _resultsView.showNoResults(); if (_findBar) { var showMessage = false; diff --git a/src/search/SearchResultsView.js b/src/search/SearchResultsView.js index 3f5178c29f..22a6a31c1a 100644 --- a/src/search/SearchResultsView.js +++ b/src/search/SearchResultsView.js @@ -97,12 +97,10 @@ define(function (require, exports, module) { }).observe(this._panel.$panel[0]); function _showPanelIfResultsAvailable(_e, shownPanelID) { - if(self._model.numMatches === 0){ - self._panel.hide(); - } - if(shownPanelID === self._panel.panelID && !self._model.isReplace){ - // If it is replace, _handleModelChange will close the find bar as we dont - // do replace if there is a model change. So we wont enter this flow if it is a replace operation + if (shownPanelID === self._panel.panelID && self._model.numMatches > 0 && !self._model.isReplace) { + // Refresh results when the tab is re-activated (they may have changed + // while the panel was in a background tab). Skip when numMatches is 0 + // so the "no results" state isn't disturbed. self._handleModelChange(); } } @@ -525,18 +523,18 @@ define(function (require, exports, module) { let self = this; let count = self._model.countFilesMatches(), lastIndex = self._getLastIndex(count.matches), - typeStr = (count.matches > 1) ? Strings.FIND_IN_FILES_MATCHES : Strings.FIND_IN_FILES_MATCH, + typeStr = (count.matches !== 1) ? Strings.FIND_IN_FILES_MATCHES : Strings.FIND_IN_FILES_MATCH, filesStr, summary; if(this._searchResultsType === "reference") { - typeStr = (count.matches > 1) ? Strings.REFERENCES_IN_FILES : Strings.REFERENCE_IN_FILES; + typeStr = (count.matches !== 1) ? Strings.REFERENCES_IN_FILES : Strings.REFERENCE_IN_FILES; } filesStr = StringUtils.format( Strings.FIND_NUM_FILES, count.files, - (count.files > 1 ? Strings.FIND_IN_FILES_FILES : Strings.FIND_IN_FILES_FILE) + (count.files !== 1 ? Strings.FIND_IN_FILES_FILES : Strings.FIND_IN_FILES_FILE) ); // This text contains some formatting, so all the strings are assumed to be already escaped @@ -571,8 +569,15 @@ define(function (require, exports, module) { * Shows the current set of results. */ SearchResultsView.prototype._render = function () { + let count = this._model.countFilesMatches(); + if (count.matches === 0) { + this.showNoResults(); + return; + } + + this._panel.$panel.removeClass("search-no-results"); + let searchItems, match, i, item, multiLine, - count = this._model.countFilesMatches(), searchFiles = this._model.prioritizeOpenFile(this._initialFilePath), lastIndex = this._getLastIndex(count.matches), matchesCounter = 0, @@ -818,12 +823,38 @@ define(function (require, exports, module) { this._model.on("change.SearchResultsView", this._handleModelChange.bind(this)); }; + /** + * Opens the panel and displays a "no results" message instead of closing it. + * Keeps the tab visible so the user gets clear feedback without jarring tab switches. + * @param {string=} message Optional message to display. Defaults to Strings.FIND_NO_RESULTS. + */ + SearchResultsView.prototype.showNoResults = function (message) { + this._currentStart = 0; + this._$selectedRow = null; + this._allChecked = false; + + this._$table.empty(); + this._closePreviewEditor(); + + this._panel.$panel.addClass("search-no-results"); + this._showSummary(); + this._$table.append( + $('
').text(message || Strings.FIND_NO_RESULTS) + ); + + this._panel.$panel.off(".searchResults"); + this._model.off("change.SearchResultsView"); + + this._panel.show(); + }; + /** * Hides the Search Results Panel and unregisters listeners. */ SearchResultsView.prototype.close = function () { if (this._panel && this._panel.isVisible()) { this._$table.empty(); + this._panel.$panel.removeClass("search-no-results"); this._panel.hide(); this._panel.$panel.off(".searchResults"); this._model.off("change.SearchResultsView"); diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 0b942fee4c..8fe7f9a501 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -1981,6 +1981,31 @@ a, img { } } +.search-results.search-no-results { + .table-container { + width: 100% !important; + } + .search-editor-preview { + display: none !important; + } +} + +.search-no-results-message { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: #888; + font-size: 15px; + letter-spacing: 0.5px; + word-spacing: 1px; + user-select: none; + + .dark & { + color: #999; + } +} + .search-results .disclosure-triangle, #problems-panel .disclosure-triangle { .expand-collapse-triangle(); From 977d9d13f3155f79dcdef9fc21e740795b767f6d Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 22:54:52 +0530 Subject: [PATCH 36/44] feat: only show no results page when modal has valid query --- src/search/SearchResultsView.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/search/SearchResultsView.js b/src/search/SearchResultsView.js index 22a6a31c1a..e79a86116b 100644 --- a/src/search/SearchResultsView.js +++ b/src/search/SearchResultsView.js @@ -570,7 +570,9 @@ define(function (require, exports, module) { */ SearchResultsView.prototype._render = function () { let count = this._model.countFilesMatches(); - if (count.matches === 0) { + if (count.matches === 0 && this._model.queryInfo) { + // Only redirect to showNoResults() when the model has a valid query + // (i.e. this is a real "no results" state, not a transient clear). this.showNoResults(); return; } @@ -833,6 +835,11 @@ define(function (require, exports, module) { this._$selectedRow = null; this._allChecked = false; + if (this._timeoutID) { + window.clearTimeout(this._timeoutID); + this._timeoutID = null; + } + this._$table.empty(); this._closePreviewEditor(); From 9ee56b24d375ff652609eeda1c6be8fb366a297e Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 23:09:06 +0530 Subject: [PATCH 37/44] feat: escape key hides the bottom panel instead of destroying a panel --- src/view/WorkspaceManager.js | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index 9ce586a687..45412239c0 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -588,28 +588,16 @@ define(function (require, exports, module) { } function _handleEscapeKey() { - // Hide the most recently shown bottom panel - if (lastShownBottomPanelStack.length > 0) { - let panelToHide = getPanelForID(lastShownBottomPanelStack.pop()); - // Guard: only hide if the panel is actually visible. - if (panelToHide && panelToHide.isVisible()) { - panelToHide.hide(); - return true; - } - } - // Fallback: hide any visible bottom panel not tracked in the stack. - let allPanelsIDs = getAllPanelIDs(); - for (let panelID of allPanelsIDs) { - let panel = getPanelForID(panelID); - if (panel.getPanelType() === PanelView.PANEL_TYPE_BOTTOM_PANEL && panel.isVisible()) { - panel.hide(); - return true; - } + // Collapse the entire bottom panel container, keeping all tabs intact + if ($bottomPanelContainer && $bottomPanelContainer.is(":visible")) { + Resizer.hide($bottomPanelContainer[0]); + triggerUpdateLayout(); + return true; } return false; } - // pressing escape when focused on editor will toggle the last opened bottom panel + // pressing escape when focused on editor will hide the bottom panel container function _handleKeydown(event) { if(event.keyCode !== KeyEvent.DOM_VK_ESCAPE || KeyBindingManager.isInOverlayMode()){ return; From b0fe77f925c249f0a9ac59da175f0ec156ee56b6 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 19 Feb 2026 23:25:31 +0530 Subject: [PATCH 38/44] fix: checkmark/active indicators not getting removed when tab is closed --- src/extensions/default/Git/src/Panel.js | 11 +++++++++++ src/extensionsIntegrated/CustomSnippets/main.js | 8 ++++++++ src/extensionsIntegrated/DisplayShortcuts/main.js | 10 ++++++++++ src/language/CodeInspection.js | 8 ++++++++ 4 files changed, 37 insertions(+) diff --git a/src/extensions/default/Git/src/Panel.js b/src/extensions/default/Git/src/Panel.js index f30c78e411..3a90cac8e9 100644 --- a/src/extensions/default/Git/src/Panel.js +++ b/src/extensions/default/Git/src/Panel.js @@ -1501,6 +1501,17 @@ define(function (require, exports) { handleGitCommit(lastCommitMessage[ProjectManager.getProjectRoot().fullPath], false, COMMIT_MODE.DEFAULT); }); + // When the panel tab is closed externally (e.g. via the × button), + // update the toolbar icon and menu checked state to stay in sync. + WorkspaceManager.on(WorkspaceManager.EVENT_WORKSPACE_PANEL_HIDDEN, function (event, panelID) { + if (panelID === "main-git.panel" && gitPanel) { + Main.$icon.toggleClass("on", false); + Main.$icon.toggleClass("selected-button", false); + CommandManager.get(Constants.CMD_GIT_TOGGLE_PANEL).setChecked(false); + Preferences.set("panelEnabled", false); + } + }); + exports.init = init; exports.refresh = refresh; exports.toggle = toggle; diff --git a/src/extensionsIntegrated/CustomSnippets/main.js b/src/extensionsIntegrated/CustomSnippets/main.js index d7f0beaf68..b66ed5df63 100644 --- a/src/extensionsIntegrated/CustomSnippets/main.js +++ b/src/extensionsIntegrated/CustomSnippets/main.js @@ -255,6 +255,14 @@ define(function (require, exports, module) { }); } + // When the panel tab is closed externally (e.g. via the × button), + // update the menu checked state to stay in sync. + WorkspaceManager.on(WorkspaceManager.EVENT_WORKSPACE_PANEL_HIDDEN, function (event, panelID) { + if (panelID === PANEL_ID && customSnippetsPanel) { + CommandManager.get(MY_COMMAND_ID).setChecked(false); + } + }); + AppInit.appReady(function () { CommandManager.register(MENU_ITEM_NAME, MY_COMMAND_ID, showCustomSnippetsPanel); // Render template with localized strings diff --git a/src/extensionsIntegrated/DisplayShortcuts/main.js b/src/extensionsIntegrated/DisplayShortcuts/main.js index 6b1281e9e9..0b8f49678c 100644 --- a/src/extensionsIntegrated/DisplayShortcuts/main.js +++ b/src/extensionsIntegrated/DisplayShortcuts/main.js @@ -538,5 +538,15 @@ define(function (require, exports, module) { KeyBindingManager.on(KeyBindingManager.EVENT_KEY_BINDING_REMOVED, _updateKeyBindings); KeyBindingManager.on(KeyBindingManager.EVENT_NEW_PRESET, _updatePresets); KeyBindingManager.on(KeyBindingManager.EVENT_PRESET_CHANGED, _updatePresets); + + // When the panel tab is closed externally (e.g. via the × button), + // update the menu checked state and clean up resources. + WorkspaceManager.on(WorkspaceManager.EVENT_WORKSPACE_PANEL_HIDDEN, function (event, panelID) { + if (panelID === TOGGLE_SHORTCUTS_ID && panel) { + destroyKeyList(); + _clearSortingEventHandlers(); + CommandManager.get(TOGGLE_SHORTCUTS_ID).setChecked(false); + } + }); }); }); diff --git a/src/language/CodeInspection.js b/src/language/CodeInspection.js index 217f4f5a74..6e5103bcdc 100644 --- a/src/language/CodeInspection.js +++ b/src/language/CodeInspection.js @@ -1365,6 +1365,14 @@ define(function (require, exports, module) { } }); + // When the panel tab is closed externally (e.g. via the × button), + // update the collapsed state so the panel doesn't auto-reopen. + WorkspaceManager.on(WorkspaceManager.EVENT_WORKSPACE_PANEL_HIDDEN, function (event, panelID) { + if (panelID === "errors") { + _collapsed = true; + } + }); + // Set initial UI state toggleEnabled(prefs.get(PREF_ENABLED), true); toggleCollapsed(prefs.get(PREF_COLLAPSED), true); From 4ce6e45a031ede1c5cc6cd962bb0f758c38e77f1 Mon Sep 17 00:00:00 2001 From: Pluto Date: Fri, 20 Feb 2026 00:05:40 +0530 Subject: [PATCH 39/44] feat: only show the buttons available in the quick access panel --- src/view/DefaultPanelView.js | 73 ++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/src/view/DefaultPanelView.js b/src/view/DefaultPanelView.js index 23ff14ecdf..b342d24c9c 100644 --- a/src/view/DefaultPanelView.js +++ b/src/view/DefaultPanelView.js @@ -72,6 +72,9 @@ define(function (require, exports, module) { /** @type {Panel} The default panel instance */ let _panel; + /** @type {jQueryObject} The panel DOM element */ + let _$panel; + /** * Build the panel DOM. * @return {jQueryObject} @@ -89,6 +92,7 @@ define(function (require, exports, module) { _panelButtons.forEach(function (btn) { let $button = $('') .attr("data-command", btn.commandID) + .attr("data-btn-id", btn.id) .attr("title", btn.label); let $icon = $('').addClass(btn.icon); let $label = $('').text(btn.label); @@ -102,22 +106,78 @@ define(function (require, exports, module) { return $panel; } + /** + * Check whether Git is available for the current project. + * The Git extension hides its toolbar icon with the "forced-hidden" class + * when Git is not available (no binary, not a repo, extension disabled, etc.). + * @return {boolean} + * @private + */ + function _isGitAvailable() { + const $gitIcon = $("#git-toolbar-icon"); + return $gitIcon.length > 0 && !$gitIcon.hasClass("forced-hidden"); + } + + /** + * Check whether there are currently lint errors/warnings. + * The status bar indicator gets the "inspection-errors" class when problems exist. + * @return {boolean} + * @private + */ + function _hasProblems() { + const $indicator = $("#status-inspection"); + return $indicator.length > 0 && $indicator.hasClass("inspection-errors"); + } + + /** + * Show or hide the Git and Problems buttons based on current state. + * @private + */ + function _updateButtonVisibility() { + if (!_$panel) { + return; + } + _$panel.find('.default-panel-btn[data-btn-id="git"]').toggle(_isGitAvailable()); + _$panel.find('.default-panel-btn[data-btn-id="problems"]').toggle(_hasProblems()); + } + + /** + * Set up MutationObservers on the Git toolbar icon and status-inspection + * indicator so that button visibility updates live. + * @private + */ + function _observeStateChanges() { + // Watch Git toolbar icon for class changes (forced-hidden added/removed) + const gitIcon = document.getElementById("git-toolbar-icon"); + if (gitIcon) { + const gitObserver = new MutationObserver(_updateButtonVisibility); + gitObserver.observe(gitIcon, {attributes: true, attributeFilter: ["class"]}); + } + + // Watch status-inspection indicator for class changes (inspection-errors) + const statusInspection = document.getElementById("status-inspection"); + if (statusInspection) { + const inspectionObserver = new MutationObserver(_updateButtonVisibility); + inspectionObserver.observe(statusInspection, {attributes: true, attributeFilter: ["class"]}); + } + } + /** * Initialise the default panel. Called once at appReady. * @private */ function _init() { - let $panel = _buildPanelHTML(); + _$panel = _buildPanelHTML(); _panel = WorkspaceManager.createBottomPanel( WorkspaceManager.DEFAULT_PANEL_ID, - $panel, + _$panel, undefined, Strings.BOTTOM_PANEL_DEFAULT_TITLE ); // Button click handler: execute the command to open the target panel. // The auto-hide listener (EVENT_PANEL_SHOWN) will close the default panel. - $panel.on("click", ".default-panel-btn", function () { + _$panel.on("click", ".default-panel-btn", function () { let commandID = $(this).attr("data-command"); if (commandID) { CommandManager.execute(commandID); @@ -130,7 +190,14 @@ define(function (require, exports, module) { if (panelID !== WorkspaceManager.DEFAULT_PANEL_ID) { _panel.hide(); } + if (panelID === WorkspaceManager.DEFAULT_PANEL_ID) { + _updateButtonVisibility(); + } }); + + // Initial visibility update and set up live observers + _updateButtonVisibility(); + _observeStateChanges(); } AppInit.appReady(_init); From 04ada7e3222da1aaf4e36c1e85699bfb04888bda Mon Sep 17 00:00:00 2001 From: Pluto Date: Fri, 20 Feb 2026 15:22:16 +0530 Subject: [PATCH 40/44] fix: find in files buttons getting misplaced with empty input boxes --- src/styles/brackets.less | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 8fe7f9a501..72fbe70317 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -2402,6 +2402,7 @@ a, img { .search-input-container { display: inline-flex; + vertical-align: top; } .filter-container{ margin-left: -11px; From 26bf8508b4b718d059ce8035ad50d9496f9332bc Mon Sep 17 00:00:00 2001 From: Pluto Date: Fri, 20 Feb 2026 20:22:10 +0530 Subject: [PATCH 41/44] feat: add singular add/destroy panel functions to prevent whole DOM rebuilds --- src/command/Commands.js | 3 ++ src/view/DefaultPanelView.js | 5 +-- src/view/PanelView.js | 73 ++++++++++++++++++++++++++++++++---- src/view/WorkspaceManager.js | 18 +++++++++ 4 files changed, 88 insertions(+), 11 deletions(-) diff --git a/src/command/Commands.js b/src/command/Commands.js index dbd94cb219..95a47fcf71 100644 --- a/src/command/Commands.js +++ b/src/command/Commands.js @@ -541,6 +541,9 @@ define(function (require, exports, module) { /** Toggles the git panel */ exports.CMD_GIT_TOGGLE_PANEL = "git-toggle-panel"; + /** Toggles the custom snippets panel */ + exports.CMD_CUSTOM_SNIPPETS_PANEL = "custom_snippets"; + /** Goes to next git change */ exports.CMD_GIT_GOTO_NEXT_CHANGE = "git-gotoNextChange"; diff --git a/src/view/DefaultPanelView.js b/src/view/DefaultPanelView.js index b342d24c9c..ff0006db35 100644 --- a/src/view/DefaultPanelView.js +++ b/src/view/DefaultPanelView.js @@ -59,7 +59,7 @@ define(function (require, exports, module) { id: "snippets", icon: "fa-solid fa-code", label: Strings.CUSTOM_SNIPPETS_PANEL_TITLE || "Custom Snippets", - commandID: "custom_snippets" + commandID: Commands.CMD_CUSTOM_SNIPPETS_PANEL }, { id: "shortcuts", @@ -189,8 +189,7 @@ define(function (require, exports, module) { PanelView.on(PanelView.EVENT_PANEL_SHOWN, function (event, panelID) { if (panelID !== WorkspaceManager.DEFAULT_PANEL_ID) { _panel.hide(); - } - if (panelID === WorkspaceManager.DEFAULT_PANEL_ID) { + } else { _updateButtonVisibility(); } }); diff --git a/src/view/PanelView.js b/src/view/PanelView.js index a857972fc1..a5d643afe6 100644 --- a/src/view/PanelView.js +++ b/src/view/PanelView.js @@ -139,6 +139,43 @@ define(function (require, exports, module) { }); } + /** + * Append a single tab to the tab bar for the given panel. + * Use instead of _updateBottomPanelTabBar() when adding one tab. + * @param {string} panelId + * @private + */ + function _addTabToBar(panelId) { + if (!_$tabsOverflow) { + return; + } + let panel = _panelMap[panelId]; + if (!panel) { + return; + } + let title = panel._tabTitle || _getPanelTitle(panelId, panel.$panel); + let isActive = (panelId === _activeId); + let $tab = $('
') + .toggleClass('active', isActive) + .attr('data-panel-id', panelId); + $tab.append($('').text(title)); + $tab.append($('×').attr('title', Strings.CLOSE)); + _$tabsOverflow.append($tab); + } + + /** + * Remove a single tab from the tab bar by panel ID. + * Use instead of _updateBottomPanelTabBar() when removing one tab. + * @param {string} panelId + * @private + */ + function _removeTabFromBar(panelId) { + if (!_$tabsOverflow) { + return; + } + _$tabsOverflow.find('.bottom-panel-tab[data-panel-id="' + panelId + '"]').remove(); + } + /** * Switch the active tab to the given panel. Does not show/hide the container. * @param {string} panelId @@ -223,7 +260,7 @@ define(function (require, exports, module) { * Shows the panel */ Panel.prototype.show = function () { - if (!this.canBeShown()) { + if (!this.canBeShown() || !_$container) { return; } let panelId = this.panelID; @@ -256,7 +293,7 @@ define(function (require, exports, module) { } _switchToTab(panelId); - _updateBottomPanelTabBar(); + _addTabToBar(panelId); exports.trigger(EVENT_PANEL_SHOWN, panelId); }; @@ -276,22 +313,30 @@ define(function (require, exports, module) { this.$panel.removeClass("active-bottom-panel"); let wasActive = (_activeId === panelId); + let activatedId = null; - // Tab was removed — rebuild tab bar, then activate next if needed if (wasActive && _openIds.length > 0) { let nextIdx = Math.min(idx, _openIds.length - 1); - let nextId = _openIds[nextIdx]; + activatedId = _openIds[nextIdx]; _activeId = null; // clear so _switchToTab runs - _switchToTab(nextId); - exports.trigger(EVENT_PANEL_SHOWN, nextId); + _switchToTab(activatedId); } else if (wasActive) { // No more tabs - hide the container _activeId = null; - Resizer.hide(_$container[0]); + if (_$container) { + Resizer.hide(_$container[0]); + } } - _updateBottomPanelTabBar(); + _removeTabFromBar(panelId); + + // Always fire HIDDEN for the closed panel first exports.trigger(EVENT_PANEL_HIDDEN, panelId); + + // Then fire SHOWN for the newly activated tab, if any + if (activatedId) { + exports.trigger(EVENT_PANEL_SHOWN, activatedId); + } }; /** @@ -318,6 +363,18 @@ define(function (require, exports, module) { } }; + /** + * Destroys the panel, removing it from the tab bar, internal maps, and the DOM. + * After calling this, the Panel instance should not be reused. + */ + Panel.prototype.destroy = function () { + if (_openIds.indexOf(this.panelID) !== -1) { + this.hide(); + } + delete _panelMap[this.panelID]; + this.$panel.remove(); + }; + /** * gets the Panel's type * @return {string} diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index 45412239c0..77b8936635 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -262,6 +262,23 @@ define(function (require, exports, module) { return bottomPanel; } + /** + * Destroys a bottom panel, removing it from internal registries, the tab bar, and the DOM. + * After calling this, the panel ID is no longer valid and the Panel instance should not be reused. + * + * @param {!string} id The panel ID that was passed to createBottomPanel. + */ + function destroyBottomPanel(id) { + let panel = panelIDMap[id]; + if (!panel) { + return; + } + if (typeof panel.destroy === 'function') { + panel.destroy(); + } + delete panelIDMap[id]; + } + /** * Creates a new resizable plugin panel associated with the given toolbar icon. Panel is initially invisible. * The panel's size & visibility are automatically saved & restored. Only one panel can be associated with a @@ -632,6 +649,7 @@ define(function (require, exports, module) { // Define public API exports.createBottomPanel = createBottomPanel; + exports.destroyBottomPanel = destroyBottomPanel; exports.createPluginPanel = createPluginPanel; exports.isPanelVisible = isPanelVisible; exports.setPluginPanelWidth = setPluginPanelWidth; From 6dd8b97ac53ea1bb88f6855f7694ad7bd6f912ba Mon Sep 17 00:00:00 2001 From: Pluto Date: Fri, 20 Feb 2026 20:53:07 +0530 Subject: [PATCH 42/44] refactor: show the add snippets button on the centre of the panel --- src/styles/Extn-CustomSnippets.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/styles/Extn-CustomSnippets.less b/src/styles/Extn-CustomSnippets.less index 9f14524cf2..78aadd400e 100644 --- a/src/styles/Extn-CustomSnippets.less +++ b/src/styles/Extn-CustomSnippets.less @@ -91,6 +91,10 @@ margin-right: 4px; } +#custom-snippets-list { + height: 100%; +} + #custom-snippets-list.hidden { display: none; } From 3d08924ada78928826f6f53e5b4c511ed5ec8fb8 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 21 Feb 2026 00:58:49 +0530 Subject: [PATCH 43/44] chore: auto update API docs --- docs/API-Reference/command/Commands.md | 6 ++ .../API-Reference/search/SearchResultsView.md | 3 +- docs/API-Reference/view/PanelView.md | 89 ++++++++++++++++++- docs/API-Reference/view/WorkspaceManager.md | 41 ++++++++- 4 files changed, 133 insertions(+), 6 deletions(-) diff --git a/docs/API-Reference/command/Commands.md b/docs/API-Reference/command/Commands.md index e5b864be48..e3a0e2ce91 100644 --- a/docs/API-Reference/command/Commands.md +++ b/docs/API-Reference/command/Commands.md @@ -992,6 +992,12 @@ Performs a mixed reset ## CMD\_GIT\_TOGGLE\_PANEL Toggles the git panel +**Kind**: global variable + + +## CMD\_CUSTOM\_SNIPPETS\_PANEL +Toggles the custom snippets panel + **Kind**: global variable diff --git a/docs/API-Reference/search/SearchResultsView.md b/docs/API-Reference/search/SearchResultsView.md index d78dcac237..a26874c555 100644 --- a/docs/API-Reference/search/SearchResultsView.md +++ b/docs/API-Reference/search/SearchResultsView.md @@ -21,7 +21,7 @@ Dispatches the following events_ ### new Handles the search results panel. Dispatches the following events: replaceBatch - when the Replace button is clicked. - close - when the panel is closed.(model, panelID, panelName, type) + close - when the panel is closed.(model, panelID, panelName, type, [title]) | Param | Type | Description | | --- | --- | --- | @@ -29,4 +29,5 @@ Dispatches the following events: | panelID | string | The CSS ID to use for the panel. | | panelName | string | The name to use for the panel, as passed to WorkspaceManager.createBottomPanel(). | | type | string | type to identify if it is reference search or string match serach | +| [title] | string | Display title for the panel tab. | diff --git a/docs/API-Reference/view/PanelView.md b/docs/API-Reference/view/PanelView.md index c4663e07c7..4bce903cdc 100644 --- a/docs/API-Reference/view/PanelView.md +++ b/docs/API-Reference/view/PanelView.md @@ -9,7 +9,7 @@ const PanelView = brackets.getModule("view/PanelView") **Kind**: global class * [Panel](#Panel) - * [new Panel($panel)](#new_Panel_new) + * [new Panel($panel, id, [title])](#new_Panel_new) * [.$panel](#Panel+$panel) : jQueryObject * [.isVisible()](#Panel+isVisible) ⇒ boolean * [.registerCanBeShownHandler(canShowHandlerFn)](#Panel+registerCanBeShownHandler) ⇒ boolean @@ -17,17 +17,21 @@ const PanelView = brackets.getModule("view/PanelView") * [.show()](#Panel+show) * [.hide()](#Panel+hide) * [.setVisible(visible)](#Panel+setVisible) + * [.setTitle(newTitle)](#Panel+setTitle) + * [.destroy()](#Panel+destroy) * [.getPanelType()](#Panel+getPanelType) ⇒ string -### new Panel($panel) +### new Panel($panel, id, [title]) Represents a panel below the editor area (a child of ".content"). | Param | Type | Description | | --- | --- | --- | | $panel | jQueryObject | The entire panel, including any chrome, already in the DOM. | +| id | string | Unique panel identifier. | +| [title] | string | Optional display title for the tab bar. | @@ -84,12 +88,66 @@ Sets the panel's visibility state | --- | --- | --- | | visible | boolean | true to show, false to hide | + + +### panel.setTitle(newTitle) +Updates the display title shown in the tab bar for this panel. + +**Kind**: instance method of [Panel](#Panel) + +| Param | Type | Description | +| --- | --- | --- | +| newTitle | string | The new title to display. | + + + +### panel.destroy() +Destroys the panel, removing it from the tab bar, internal maps, and the DOM. +After calling this, the Panel instance should not be reused. + +**Kind**: instance method of [Panel](#Panel) ### panel.getPanelType() ⇒ string gets the Panel's type **Kind**: instance method of [Panel](#Panel) + + +## \_panelMap : Object.<string, Panel> +Maps panel ID to Panel instance + +**Kind**: global variable + + +## \_$container : jQueryObject +The single container wrapping all bottom panels + +**Kind**: global variable + + +## \_$tabBar : jQueryObject +The tab bar inside the container + +**Kind**: global variable + + +## \_$tabsOverflow : jQueryObject +Scrollable area holding the tab elements + +**Kind**: global variable + + +## \_openIds : Array.<string> +Ordered list of currently open (tabbed) panel IDs + +**Kind**: global variable + + +## \_activeId : string \| null +The panel ID of the currently visible (active) tab + +**Kind**: global variable ## EVENT\_PANEL\_HIDDEN : string @@ -108,3 +166,30 @@ Event when panel is shown type for bottom panel **Kind**: global constant + + +## init($container, $tabBar, $tabsOverflow) +Initializes the PanelView module with references to the bottom panel container DOM elements. +Called by WorkspaceManager during htmlReady. + +**Kind**: global function + +| Param | Type | Description | +| --- | --- | --- | +| $container | jQueryObject | The bottom panel container element. | +| $tabBar | jQueryObject | The tab bar element inside the container. | +| $tabsOverflow | jQueryObject | The scrollable area holding tab elements. | + + + +## getOpenBottomPanelIDs() ⇒ Array.<string> +Returns a copy of the currently open bottom panel IDs in tab order. + +**Kind**: global function + + +## hideAllOpenPanels() ⇒ Array.<string> +Hides every open bottom panel tab in a single batch + +**Kind**: global function +**Returns**: Array.<string> - The IDs of panels that were open (useful for restoring later). diff --git a/docs/API-Reference/view/WorkspaceManager.md b/docs/API-Reference/view/WorkspaceManager.md index 8d39dbbaaf..57d4d627e2 100644 --- a/docs/API-Reference/view/WorkspaceManager.md +++ b/docs/API-Reference/view/WorkspaceManager.md @@ -22,10 +22,14 @@ Events: * [.PANEL_TYPE_BOTTOM_PANEL](#module_view/WorkspaceManager.PANEL_TYPE_BOTTOM_PANEL) : string * [.PANEL_TYPE_PLUGIN_PANEL](#module_view/WorkspaceManager.PANEL_TYPE_PLUGIN_PANEL) : string * _inner_ + * [.$bottomPanelContainer](#module_view/WorkspaceManager..$bottomPanelContainer) : jQueryObject + * [.$statusBarPanelToggle](#module_view/WorkspaceManager..$statusBarPanelToggle) : jQueryObject + * [._statusBarToggleInProgress](#module_view/WorkspaceManager.._statusBarToggleInProgress) : boolean * [.EVENT_WORKSPACE_UPDATE_LAYOUT](#module_view/WorkspaceManager..EVENT_WORKSPACE_UPDATE_LAYOUT) * [.EVENT_WORKSPACE_PANEL_SHOWN](#module_view/WorkspaceManager..EVENT_WORKSPACE_PANEL_SHOWN) * [.EVENT_WORKSPACE_PANEL_HIDDEN](#module_view/WorkspaceManager..EVENT_WORKSPACE_PANEL_HIDDEN) - * [.createBottomPanel(id, $panel, [minSize])](#module_view/WorkspaceManager..createBottomPanel) ⇒ Panel + * [.createBottomPanel(id, $panel, [minSize], [title])](#module_view/WorkspaceManager..createBottomPanel) ⇒ Panel + * [.destroyBottomPanel(id)](#module_view/WorkspaceManager..destroyBottomPanel) * [.createPluginPanel(id, $panel, [minSize], $toolbarIcon, [initialSize])](#module_view/WorkspaceManager..createPluginPanel) ⇒ Panel * [.getAllPanelIDs()](#module_view/WorkspaceManager..getAllPanelIDs) ⇒ Array * [.getPanelForID(panelID)](#module_view/WorkspaceManager..getPanelForID) ⇒ Object @@ -47,6 +51,24 @@ Constant representing the type of bottom panel Constant representing the type of plugin panel **Kind**: static property of [view/WorkspaceManager](#module_view/WorkspaceManager) + + +### view/WorkspaceManager.$bottomPanelContainer : jQueryObject +The single container wrapping all bottom panels + +**Kind**: inner property of [view/WorkspaceManager](#module_view/WorkspaceManager) + + +### view/WorkspaceManager.$statusBarPanelToggle : jQueryObject +Chevron toggle in the status bar + +**Kind**: inner property of [view/WorkspaceManager](#module_view/WorkspaceManager) + + +### view/WorkspaceManager.\_statusBarToggleInProgress : boolean +True while the status bar toggle button is handling a click + +**Kind**: inner property of [view/WorkspaceManager](#module_view/WorkspaceManager) ### view/WorkspaceManager.EVENT\_WORKSPACE\_UPDATE\_LAYOUT @@ -67,7 +89,7 @@ Event triggered when a panel is hidden. **Kind**: inner constant of [view/WorkspaceManager](#module_view/WorkspaceManager) -### view/WorkspaceManager.createBottomPanel(id, $panel, [minSize]) ⇒ Panel +### view/WorkspaceManager.createBottomPanel(id, $panel, [minSize], [title]) ⇒ Panel Creates a new resizable panel beneath the editor area and above the status bar footer. Panel is initially invisible. The panel's size & visibility are automatically saved & restored as a view-state preference. @@ -77,7 +99,20 @@ The panel's size & visibility are automatically saved & restored as a view-state | --- | --- | --- | | id | string | Unique id for this panel. Use package-style naming, e.g. "myextension.feature.panelname" | | $panel | jQueryObject | DOM content to use as the panel. Need not be in the document yet. Must have an id attribute, for use as a preferences key. | -| [minSize] | number | Minimum height of panel in px. | +| [minSize] | number | @deprecated No longer used. Pass `undefined`. | +| [title] | string | Display title shown in the bottom panel tab bar. | + + + +### view/WorkspaceManager.destroyBottomPanel(id) +Destroys a bottom panel, removing it from internal registries, the tab bar, and the DOM. +After calling this, the panel ID is no longer valid and the Panel instance should not be reused. + +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) + +| Param | Type | Description | +| --- | --- | --- | +| id | string | The panel ID that was passed to createBottomPanel. | From bc1602a3d8bfcd7767d8fa71b451d97a20c75dbd Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 21 Feb 2026 00:59:37 +0530 Subject: [PATCH 44/44] fix: file filters integ tests failing --- test/spec/FileFilters-integ-test.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/spec/FileFilters-integ-test.js b/test/spec/FileFilters-integ-test.js index c86f6c9f17..34ba8017d6 100644 --- a/test/spec/FileFilters-integ-test.js +++ b/test/spec/FileFilters-integ-test.js @@ -86,9 +86,12 @@ define(function (require, exports, module) { }, "search bar open"); } - function closeSearchBar() { + async function closeSearchBar() { let $searchField = $(".modal-bar #find-group textarea"); SpecRunnerUtils.simulateKeyEvent(KeyEvent.DOM_VK_ESCAPE, "keydown", $searchField[0]); + await awaitsFor(function () { + return $(".modal-bar").length === 0; + }, "search bar close"); } async function executeSearch(searchString) { @@ -250,12 +253,15 @@ define(function (require, exports, module) { // Error message displayed expect($modalBar.find(".scope-group div.error-filter").is(":visible")).toBeTruthy(); - // Search panel not showing - expect($("#find-in-files-results").is(":visible")).toBeFalsy(); + // Search panel shows "no results" state + expect($("#find-in-files-results").is(":visible")).toBeTruthy(); // Close search bar let $searchField = $modalBar.find("#find-group textarea"); - await SpecRunnerUtils.simulateKeyEvent(KeyEvent.DOM_VK_ESCAPE, "keydown", $searchField[0]); + SpecRunnerUtils.simulateKeyEvent(KeyEvent.DOM_VK_ESCAPE, "keydown", $searchField[0]); + await awaitsFor(function () { + return $(".modal-bar").length === 0; + }, "search bar close"); }, 30000); it("should respect filter when editing code", async function () {