Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 67 additions & 2 deletions src/extensionsIntegrated/Terminal/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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;

/**
Expand Down Expand Up @@ -145,8 +147,41 @@ 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);
// 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;
});

// 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);
Expand All @@ -155,6 +190,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();
Expand Down Expand Up @@ -333,6 +369,7 @@ define(function (require, exports, module) {
}

_updateFlyout();
_refreshAllProcesses();
}

/**
Expand Down Expand Up @@ -586,14 +623,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() >= 840);
}

/**
* Handle workspace resize
*/
function _handleResize() {
_updateTabBarMode();
const active = _getActiveTerminal();
if (active) {
active.handleResize();
}
_refreshAllProcesses();
}

/**
Expand Down Expand Up @@ -622,6 +668,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 ? '<kbd>Cmd+K</kbd>' : '<kbd>Ctrl+K</kbd>';
const message = StringUtils.format(Strings.TERMINAL_CLEAR_BUFFER_HINT, shortcutKey);
NotificationUI.showToastOn($contentArea[0], message, {
autoCloseTimeS: 5,
dismissOnClick: true
});
}

/**
* Escape HTML special characters
*/
Expand Down Expand Up @@ -814,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.
Expand Down
1 change: 1 addition & 0 deletions src/nls/root/strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
119 changes: 60 additions & 59 deletions src/styles/Extn-Terminal.less
Original file line number Diff line number Diff line change
Expand Up @@ -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: 159px;
min-width: 159px;
}

/* ─── 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;
Expand All @@ -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;
Expand All @@ -151,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;
}
Expand All @@ -167,58 +169,47 @@
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;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-size: 12px;
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;
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;
}

/* 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;
Expand All @@ -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;
}

Expand All @@ -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 {

Check warning on line 241 in src/styles/Extn-Terminal.less

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected duplicate selector ".terminal-tabs-expanded .terminal-flyout-cwd", first used at line 236

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ1l-Y7BZXo6OFpywUjT&open=AZ1l-Y7BZXo6OFpywUjT&pullRequest=2794
margin-right: 20px;
}


/* ─── Flyout bottom actions ─── */
.terminal-flyout-actions {
border-top: 1px solid var(--terminal-border);
Expand All @@ -259,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;
Expand All @@ -269,12 +275,11 @@

.terminal-flyout-new-btn:hover {
background: rgba(255, 255, 255, 0.05);
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;
Expand All @@ -289,10 +294,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);
Expand All @@ -304,12 +305,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;
}

Expand Down
8 changes: 5 additions & 3 deletions test/spec/Terminal-integ-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

/**
Expand Down
Loading