From 1dac91c0158051bf9ec49acdda11eab9416629aa Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 7 Apr 2026 08:01:01 +0530 Subject: [PATCH 1/5] feat(terminal): replace hover flyout with static inline tab bar Replace the animated hover-expand flyout with a static tab sidebar integrated into the panel layout. Below 750px width shows collapsed icon-only tabs; at 750px+ shows expanded tabs with labels. Close button appears on hover without layout shift. No animation. --- src/extensionsIntegrated/Terminal/main.js | 13 ++- src/styles/Extn-Terminal.less | 104 +++++++++++----------- 2 files changed, 63 insertions(+), 54 deletions(-) diff --git a/src/extensionsIntegrated/Terminal/main.js b/src/extensionsIntegrated/Terminal/main.js index 2a5bcd554..9c6c3c816 100644 --- a/src/extensionsIntegrated/Terminal/main.js +++ b/src/extensionsIntegrated/Terminal/main.js @@ -145,9 +145,6 @@ define(function (require, exports, module) { // Dropdown chevron button toggles shell selector $panel.find(".terminal-flyout-dropdown-btn").on("click", _onDropdownButtonClick); - // Refresh process info when user hovers over the flyout - $panel.find(".terminal-tab-flyout").on("mouseenter", _refreshAllProcesses); - // Listen for panel resize WorkspaceManager.on("workspaceUpdateLayout", _handleResize); @@ -155,6 +152,7 @@ define(function (require, exports, module) { const PanelView = require("view/PanelView"); PanelView.on(PanelView.EVENT_PANEL_SHOWN, function (_event, panelId) { if (panelId === PANEL_ID) { + _updateTabBarMode(); const active = _getActiveTerminal(); if (active) { active.handleResize(); @@ -586,14 +584,23 @@ define(function (require, exports, module) { } } + /** + * Update the expanded/collapsed tab bar class based on panel width + */ + function _updateTabBarMode() { + $panel.toggleClass("terminal-tabs-expanded", $panel.width() >= 750); + } + /** * Handle workspace resize */ function _handleResize() { + _updateTabBarMode(); const active = _getActiveTerminal(); if (active) { active.handleResize(); } + _refreshAllProcesses(); } /** diff --git a/src/styles/Extn-Terminal.less b/src/styles/Extn-Terminal.less index ac253797c..07e72cdf7 100644 --- a/src/styles/Extn-Terminal.less +++ b/src/styles/Extn-Terminal.less @@ -96,35 +96,44 @@ position: relative; } -/* Tab bar: 30px flex spacer, flyout sits inside absolutely */ +/* Tab bar: static sidebar, width switches via .terminal-tabs-expanded on container */ .terminal-tab-bar { position: relative; - width: 30px; - min-width: 30px; - overflow: visible; + width: 60px; + min-width: 60px; +} + +.terminal-tabs-expanded .terminal-tab-bar { + width: 170px; + min-width: 170px; } -/* ─── Unified flyout: collapses to 30px, expands on hover ─── */ +/* ─── Tab sidebar: static, no flyout/hover ─── */ .terminal-tab-flyout { display: flex; - position: absolute; - top: 0; - right: 0; - bottom: 0; - width: 30px; + position: relative; + width: 100%; + height: 100%; background: var(--terminal-tab-bg); border-left: 1px solid var(--terminal-border); - z-index: 20; flex-direction: column; - transition: width 0.08s ease; + overflow: hidden; } -.terminal-tab-flyout:hover { - width: 170px; - box-shadow: -2px 0 8px rgba(0, 0, 0, 0.3); +/* Fade gradient at bottom of tab list to hint overflow */ +.terminal-tab-flyout::after { + content: ''; + position: absolute; + bottom: 29px; /* sits above the actions row */ + left: 0; + right: 0; + height: 20px; + background: linear-gradient(to bottom, transparent, var(--terminal-tab-bg)); + pointer-events: none; + z-index: 1; } -/* Flyout list: scrollable, scrollbar hidden in collapsed mode */ +/* Tab list: scrollable, scrollbar hidden, scroll via wheel/trackpad */ .terminal-tab-flyout .terminal-flyout-list { flex: 1; min-height: 0; @@ -136,13 +145,6 @@ } } -.terminal-tab-flyout:hover .terminal-flyout-list { - scrollbar-width: auto; - &::-webkit-scrollbar { - display: block; - } -} - /* Flyout item */ .terminal-flyout-item { position: relative; @@ -167,8 +169,7 @@ box-shadow: inset 2px 0 0 #007acc; } -/* Icon column: fixed 30px slot, always visible in collapsed mode. - In expanded mode, CSS order moves it to the right end. */ +/* Icon column: fixed 30px slot */ .terminal-flyout-icon { width: 30px; min-width: 30px; @@ -179,15 +180,15 @@ flex-shrink: 0; } -/* Title: fills remaining space */ +/* Title & CWD: hidden in collapsed, shown in expanded */ .terminal-flyout-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; + display: none; } -/* CWD basename shown on the right */ .terminal-flyout-cwd { font-size: 11px; color: var(--terminal-tab-text); @@ -199,26 +200,16 @@ opacity: 0.6; overflow: hidden; text-overflow: ellipsis; + display: none; } -/* Expanded mode: reorder so icon is at the right end, - add left padding for close button area */ -.terminal-tab-flyout:hover .terminal-flyout-item { - padding-left: 30px; -} - -.terminal-tab-flyout:hover .terminal-flyout-icon { - order: 99; -} - -/* Close button: overlays left padding area, visible only when - flyout is expanded AND the specific item is hovered */ +/* Close button: absolutely positioned on the right, visible on item hover only */ .terminal-flyout-close { position: absolute; - left: 0; + right: 0; top: 0; bottom: 0; - width: 30px; + width: 20px; display: none; align-items: center; justify-content: center; @@ -228,7 +219,7 @@ z-index: 1; } -.terminal-tab-flyout:hover .terminal-flyout-item:hover .terminal-flyout-close { +.terminal-flyout-item:hover .terminal-flyout-close { display: flex; } @@ -237,6 +228,21 @@ background: rgba(255, 255, 255, 0.1); } +/* ─── Expanded mode ─── */ +.terminal-tabs-expanded .terminal-flyout-title { + display: block; +} + +.terminal-tabs-expanded .terminal-flyout-cwd { + display: block; +} + +/* Reserve space so cwd text doesn't sit under the close button */ +.terminal-tabs-expanded .terminal-flyout-cwd { + margin-right: 20px; +} + + /* ─── Flyout bottom actions ─── */ .terminal-flyout-actions { border-top: 1px solid var(--terminal-border); @@ -272,9 +278,9 @@ color: var(--terminal-tab-active-text); } -/* Dropdown chevron: hidden in collapsed mode, shown inline when expanded */ +/* Dropdown chevron: visible in both modes */ .terminal-flyout-dropdown-btn { - display: none; + display: flex; align-items: center; justify-content: center; width: 30px; @@ -289,10 +295,6 @@ flex-shrink: 0; } -.terminal-tab-flyout:hover .terminal-flyout-dropdown-btn { - display: flex; -} - .terminal-flyout-dropdown-btn:hover { background: rgba(255, 255, 255, 0.05); color: var(--terminal-tab-active-text); @@ -304,12 +306,12 @@ flex-shrink: 0; } -/* Hide button labels in collapsed mode; show when expanded */ -.terminal-tab-flyout .terminal-btn-label { +/* Button labels: hidden in collapsed, shown in expanded */ +.terminal-btn-label { display: none; } -.terminal-tab-flyout:hover .terminal-btn-label { +.terminal-tabs-expanded .terminal-btn-label { display: inline; } From 175521d05c8d8d2c853d3543039de6184dca78d9 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 7 Apr 2026 08:18:48 +0530 Subject: [PATCH 2/5] feat(terminal): add Ctrl+L passthrough and Ctrl+K clear buffer shortcut Ctrl+L was intercepted by Phoenix's edit.selectLine binding. Now it passes through to the shell when the terminal is focused. Ctrl+K (Cmd+K on mac) clears the terminal scrollback buffer. A one-time tip toast is shown on first Ctrl+L press. --- src/extensionsIntegrated/Terminal/main.js | 54 ++++++++++++++++++++++- src/nls/root/strings.js | 1 + 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/extensionsIntegrated/Terminal/main.js b/src/extensionsIntegrated/Terminal/main.js index 9c6c3c816..80b0ea80e 100644 --- a/src/extensionsIntegrated/Terminal/main.js +++ b/src/extensionsIntegrated/Terminal/main.js @@ -42,6 +42,7 @@ define(function (require, exports, module) { const Menus = require("command/Menus"); const Commands = require("command/Commands"); + const KeyBindingManager = require("command/KeyBindingManager"); const NotificationUI = require("widgets/NotificationUI"); const TerminalInstance = require("./TerminalInstance"); const ShellProfiles = require("./ShellProfiles"); @@ -90,6 +91,7 @@ define(function (require, exports, module) { let processInfo = {}; // id -> processName from PTY let originalDefaultShellName = null; // System-detected default shell name let _focusToastShown = false; // Show focus hint toast only once per session + let _clearHintShown = false; // Show clear buffer hint toast only once per session let $panel, $contentArea, $shellDropdown, $flyoutList; /** @@ -145,6 +147,38 @@ define(function (require, exports, module) { // Dropdown chevron button toggles shell selector $panel.find(".terminal-flyout-dropdown-btn").on("click", _onDropdownButtonClick); + // When the terminal is focused, prevent Phoenix keybindings from + // stealing keys that should go to the shell (e.g. Ctrl+L for clear). + // The EDITOR_SHORTCUTS list in TerminalInstance.js already defines which + // Ctrl combos should pass through to Phoenix; everything else should + // reach xterm/the PTY. + KeyBindingManager.addGlobalKeydownHook(function (event) { + if (event.type !== "keydown") { + return false; + } + // Only intercept when a terminal textarea is focused + const el = document.activeElement; + if (!el || !$contentArea[0].contains(el)) { + return false; + } + // Let the terminal handle Ctrl/Cmd key combos that aren't + // reserved for the editor (those are handled by TerminalInstance's + // _customKeyHandler which returns false for them). + const ctrlOrMeta = event.ctrlKey || event.metaKey; + const key = event.key.toLowerCase(); + if (ctrlOrMeta && !event.shiftKey && key === "l") { + _showClearBufferHintToast(); + return true; // Block Phoenix, let xterm handle Ctrl+L + } + // Ctrl+K (Cmd+K on mac): clear terminal scrollback + if (ctrlOrMeta && !event.shiftKey && key === "k") { + event.preventDefault(); + _clearActiveTerminal(); + return true; + } + return false; + }); + // Listen for panel resize WorkspaceManager.on("workspaceUpdateLayout", _handleResize); @@ -588,7 +622,7 @@ define(function (require, exports, module) { * Update the expanded/collapsed tab bar class based on panel width */ function _updateTabBarMode() { - $panel.toggleClass("terminal-tabs-expanded", $panel.width() >= 750); + $panel.toggleClass("terminal-tabs-expanded", $panel.width() >= 840); } /** @@ -629,6 +663,24 @@ define(function (require, exports, module) { }); } + /** + * Show a one-time toast hint about Ctrl/Cmd+K to clear terminal buffer + */ + function _showClearBufferHintToast() { + if (_clearHintShown) { + return; + } + _clearHintShown = true; + + const isMac = brackets.platform === "mac"; + const shortcutKey = isMac ? 'Cmd+K' : 'Ctrl+K'; + const message = StringUtils.format(Strings.TERMINAL_CLEAR_BUFFER_HINT, shortcutKey); + NotificationUI.showToastOn($contentArea[0], message, { + autoCloseTimeS: 5, + dismissOnClick: true + }); + } + /** * Escape HTML special characters */ diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index aaa44a8fb..0b8ac5a1b 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1784,6 +1784,7 @@ define({ "TERMINAL_CLOSE_ALL_STOP_BTN": "Close All & Stop Processes", "TERMINAL_FOCUS_HINT": "Press {0} to switch between editor and terminal", "TERMINAL_CLEAR": "Clear Terminal", + "TERMINAL_CLEAR_BUFFER_HINT": "💡 Press {0} to clear terminal buffer", "EXTENDED_COMMIT_MESSAGE": "EXTENDED", "GETTING_STAGED_DIFF_PROGRESS": "Getting diff of staged files\u2026", "GIT_COMMIT": "Git commit\u2026", From 62cbd254de48d6ada39909899bd4974d63f8b1f7 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 7 Apr 2026 08:29:20 +0530 Subject: [PATCH 3/5] fix(terminal): improve tab bar font size and contrast Bump font size from 11px to 12px for tab items, icons, cwd, and action buttons. Use active text color for better readability in both light and dark themes. Increase cwd opacity from 0.6 to 0.8. --- src/styles/Extn-Terminal.less | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/styles/Extn-Terminal.less b/src/styles/Extn-Terminal.less index 07e72cdf7..60aada8e2 100644 --- a/src/styles/Extn-Terminal.less +++ b/src/styles/Extn-Terminal.less @@ -153,8 +153,8 @@ height: 28px; min-height: 28px; cursor: pointer; - color: var(--terminal-tab-text); - font-size: 11px; + color: var(--terminal-tab-active-text); + font-size: 12px; white-space: nowrap; overflow: hidden; } @@ -176,7 +176,7 @@ display: flex; align-items: center; justify-content: center; - font-size: 11px; + font-size: 12px; flex-shrink: 0; } @@ -190,14 +190,14 @@ } .terminal-flyout-cwd { - font-size: 11px; + font-size: 12px; color: var(--terminal-tab-text); flex-shrink: 1; min-width: 0; max-width: 50%; margin-left: 4px; margin-right: 2px; - opacity: 0.6; + opacity: 0.8; overflow: hidden; text-overflow: ellipsis; display: none; @@ -265,8 +265,8 @@ height: 100%; padding: 0; cursor: pointer; - color: var(--terminal-tab-text); - font-size: 11px; + color: var(--terminal-tab-active-text); + font-size: 12px; background: transparent; border: none; overflow: hidden; @@ -275,7 +275,6 @@ .terminal-flyout-new-btn:hover { background: rgba(255, 255, 255, 0.05); - color: var(--terminal-tab-active-text); } /* Dropdown chevron: visible in both modes */ From 7e76eb4dfa38d274b1702f1e54fe7100b4a27146 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 7 Apr 2026 08:41:54 +0530 Subject: [PATCH 4/5] fix(terminal): refresh process info on tab bar hover/focus and fix tests Refresh process info when the tab bar is hovered or gains focus, and when a terminal is activated. Update integration tests to use the exported _refreshAllProcesses instead of the removed mouseenter trigger. Bump tab bar expand threshold to 840px. Improve tab text visibility with larger font and better contrast. --- src/extensionsIntegrated/Terminal/main.js | 6 ++++++ test/spec/Terminal-integ-test.js | 8 +++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/extensionsIntegrated/Terminal/main.js b/src/extensionsIntegrated/Terminal/main.js index 80b0ea80e..fda71c4aa 100644 --- a/src/extensionsIntegrated/Terminal/main.js +++ b/src/extensionsIntegrated/Terminal/main.js @@ -179,6 +179,10 @@ define(function (require, exports, module) { return false; }); + // Refresh process info when the tab bar gains focus or mouse enters + $panel.find(".terminal-tab-bar").on("mouseenter", _refreshAllProcesses); + $panel.find(".terminal-tab-bar").on("focusin", _refreshAllProcesses); + // Listen for panel resize WorkspaceManager.on("workspaceUpdateLayout", _handleResize); @@ -365,6 +369,7 @@ define(function (require, exports, module) { } _updateFlyout(); + _refreshAllProcesses(); } /** @@ -873,6 +878,7 @@ define(function (require, exports, module) { if (Phoenix.isTestWindow) { exports._getActiveTerminal = _getActiveTerminal; + exports._refreshAllProcesses = _refreshAllProcesses; /** * Write data to the active terminal's PTY. Test-only helper. diff --git a/test/spec/Terminal-integ-test.js b/test/spec/Terminal-integ-test.js index 9cc256f62..3066abd28 100644 --- a/test/spec/Terminal-integ-test.js +++ b/test/spec/Terminal-integ-test.js @@ -138,12 +138,14 @@ define(function (require, exports, module) { } /** - * Trigger a flyout process refresh so tab titles + * Trigger a process refresh so tab titles * reflect the current foreground process. */ function triggerFlyoutRefresh() { - testWindow.$(".terminal-tab-flyout") - .trigger("mouseenter"); + const termModule = testWindow.brackets.getModule( + "extensionsIntegrated/Terminal/main" + ); + termModule._refreshAllProcesses(); } /** From 10fda10c0cc7b9e28c3b5a35cfa282f98ede5d6d Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 7 Apr 2026 08:51:15 +0530 Subject: [PATCH 5/5] chore: tabs in terminal lessen width --- src/styles/Extn-Terminal.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/Extn-Terminal.less b/src/styles/Extn-Terminal.less index 60aada8e2..3d2a1033b 100644 --- a/src/styles/Extn-Terminal.less +++ b/src/styles/Extn-Terminal.less @@ -104,8 +104,8 @@ } .terminal-tabs-expanded .terminal-tab-bar { - width: 170px; - min-width: 170px; + width: 159px; + min-width: 159px; } /* ─── Tab sidebar: static, no flyout/hover ─── */