Skip to content
2 changes: 1 addition & 1 deletion src/extensions/default/Git/src/Panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, Strings.GIT_PANEL_TITLE);
gitPanel = WorkspaceManager.createBottomPanel("main-git.panel", $panelHtml, 100, Strings.GIT_PANEL_TITLE, {iconClass: "fa-brands fa-git-alt"});
$gitPanel = gitPanel.$panel;
const resizeObserver = new ResizeObserver(_panelResized);
resizeObserver.observe($gitPanel[0]);
Expand Down
2 changes: 1 addition & 1 deletion src/extensionsIntegrated/CustomSnippets/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ define(function (require, exports, module) {
*/
function _createPanel() {
customSnippetsPanel = WorkspaceManager.createBottomPanel(PANEL_ID, $snippetsPanel, PANEL_MIN_SIZE,
Strings.CUSTOM_SNIPPETS_PANEL_TITLE);
Strings.CUSTOM_SNIPPETS_PANEL_TITLE, {iconClass: "fa-solid fa-code"});
UIHelper.init(customSnippetsPanel);
customSnippetsPanel.show();

Expand Down
2 changes: 1 addition & 1 deletion src/extensionsIntegrated/DisplayShortcuts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ 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,
Strings.KEYBOARD_SHORTCUT_PANEL_TITLE);
Strings.KEYBOARD_SHORTCUT_PANEL_TITLE, {iconClass: "fa-solid fa-keyboard"});
panel.hide();

$shortcutsPanel = $("#shortcuts-panel");
Expand Down
18 changes: 0 additions & 18 deletions src/extensionsIntegrated/Terminal/TerminalInstance.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,6 @@ define(function (require, exports, module) {

let _nextId = 0;

// Shortcuts that should be passed to the editor, not the terminal
const EDITOR_SHORTCUTS = [
{ctrlKey: true, shiftKey: true, key: "p"}, // Command Palette
{ctrlKey: true, key: "p"}, // Quick Open
{ctrlKey: true, key: "b"}, // Toggle sidebar
{ctrlKey: true, key: "Tab"}, // Next tab
{ctrlKey: true, shiftKey: true, key: "Tab"} // Previous tab
];

/**
* Read terminal theme colors from CSS variables
Expand Down Expand Up @@ -267,16 +259,6 @@ define(function (require, exports, module) {
return false;
}

for (const shortcut of EDITOR_SHORTCUTS) {
const ctrlMatch = shortcut.ctrlKey ? ctrlOrMeta : !ctrlOrMeta;
const shiftMatch = shortcut.shiftKey ? event.shiftKey : !event.shiftKey;
const keyMatch = event.key.toLowerCase() === shortcut.key.toLowerCase();

if (ctrlMatch && shiftMatch && keyMatch) {
return false; // Don't let xterm handle it
}
}

return true; // Let xterm handle it
};

Expand Down
113 changes: 81 additions & 32 deletions src/extensionsIntegrated/Terminal/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ define(function (require, exports, module) {
};

$panel = $(Mustache.render(panelHTML, templateVars));
panel = WorkspaceManager.createBottomPanel(PANEL_ID, $panel, PANEL_MIN_SIZE);
panel = WorkspaceManager.createBottomPanel(PANEL_ID, $panel, PANEL_MIN_SIZE, undefined, {iconClass: "fa-solid fa-terminal"});

// Override focus() so Shift+Escape can transfer focus to the terminal
panel.focus = function () {
Expand Down Expand Up @@ -147,37 +147,7 @@ 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;
});
_setupPhoenixShortcuts();

// Refresh process info when the tab bar gains focus or mouse enters
$panel.find(".terminal-tab-bar").on("mouseenter", _refreshAllProcesses);
Expand Down Expand Up @@ -251,6 +221,22 @@ define(function (require, exports, module) {
* Show/hide the shell dropdown
*/
function _showShellDropdown() {
// Move dropdown out of the flyout and append to the terminal body
// so it isn't clipped by the tab bar's overflow: hidden.
// Position it above the actions row, aligned to the right.
const $body = $panel.find(".terminal-body");
const $actions = $panel.find(".terminal-flyout-actions");
const actionsRect = $actions[0].getBoundingClientRect();
const bodyRect = $body[0].getBoundingClientRect();

$shellDropdown.appendTo($body);
$shellDropdown.css({
position: "absolute",
bottom: (bodyRect.bottom - actionsRect.top) + "px",
right: "0",
left: "auto",
top: "auto"
});
$shellDropdown.removeClass("forced-hidden");
// Close on outside click
setTimeout(function () {
Expand Down Expand Up @@ -642,6 +628,69 @@ define(function (require, exports, module) {
_refreshAllProcesses();
}

/**
* Set up keyboard shortcut routing so that when the terminal is focused,
* all keys go to the terminal except shortcuts bound to specific Phoenix
* commands (e.g. toggle terminal, keyboard nav overlay).
*/
function _setupPhoenixShortcuts() {
// Commands whose shortcuts should pass through to Phoenix
// even when the terminal is focused.
const PASSTHROUGH_COMMANDS = [
Commands.VIEW_TERMINAL,
Commands.CMD_KEYBOARD_NAV_UI_OVERLAY
];

// Build a set of shortcut strings, rebuilt when bindings change.
let passthroughShortcuts = new Set();
function rebuild() {
passthroughShortcuts = new Set();
for (const cmdId of PASSTHROUGH_COMMANDS) {
for (const binding of KeyBindingManager.getKeyBindings(cmdId)) {
if (binding.key) {
passthroughShortcuts.add(binding.key);
}
}
}
}
rebuild();
KeyBindingManager.on(KeyBindingManager.EVENT_KEY_BINDING_ADDED, rebuild);
KeyBindingManager.on(KeyBindingManager.EVENT_KEY_BINDING_REMOVED, rebuild);

KeyBindingManager.addGlobalKeydownHook(function (event, shortcut) {
if (event.type !== "keydown") {
return false;
}
const el = document.activeElement;
if (!el || !$contentArea[0].contains(el)) {
return false;
}

const ctrlOrMeta = event.ctrlKey || event.metaKey;
const key = event.key.toLowerCase();

// Ctrl+K (Cmd+K on mac): clear terminal scrollback
if (ctrlOrMeta && !event.shiftKey && key === "k") {
event.preventDefault();
_clearActiveTerminal();
return true;
}

// Show clear buffer hint on Ctrl+L
if (ctrlOrMeta && !event.shiftKey && key === "l") {
_showClearBufferHintToast();
}

// Let Phoenix handle shortcuts bound to passthrough commands
if (shortcut && passthroughShortcuts.has(shortcut)) {
return false;
}

// Block Phoenix from handling everything else — let xterm get it
return true;
});
}

/**
* Update all terminal themes (after editor theme change)
*/
Expand Down
2 changes: 1 addition & 1 deletion src/language/CodeInspection.js
Original file line number Diff line number Diff line change
Expand Up @@ -1240,7 +1240,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, Strings.CMD_VIEW_TOGGLE_PROBLEMS);
problemsPanel = WorkspaceManager.createBottomPanel("errors", $(panelHtml), 100, Strings.CMD_VIEW_TOGGLE_PROBLEMS, {iconClass: "fa-solid fa-triangle-exclamation"});
$problemsPanel = $("#problems-panel");
$fixAllBtn = $problemsPanel.find(".problems-fix-all-btn");
$fixAllBtn.click(()=>{
Expand Down
2 changes: 1 addition & 1 deletion src/search/SearchResultsView.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ define(function (require, exports, module) {
const self = this;
let panelHtml = Mustache.render(searchPanelTemplate, {panelID: panelID});

this._panel = WorkspaceManager.createBottomPanel(panelName, $(panelHtml), 100, title);
this._panel = WorkspaceManager.createBottomPanel(panelName, $(panelHtml), 100, title, {iconClass: "fa-solid fa-magnifying-glass"});
this._$summary = this._panel.$panel.find(".title");
this._$table = this._panel.$panel.find(".table-container");
this._$previewEditor = this._panel.$panel.find(".search-editor-preview");
Expand Down
56 changes: 56 additions & 0 deletions src/styles/Extn-BottomPanelTabs.less
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,68 @@
}
}

/* Tab icon: hidden by default, shown when tabs are collapsed */
.bottom-panel-tab-icon {
display: none;
font-size: 1rem;
width: 1rem;
text-align: center;
pointer-events: none;
}

/* Override any FA class specificity (e.g. fa-brands sets font-size: 1.2em) */
i.panel-titlebar-icon.panel-titlebar-icon {
font-size: 1rem;
}

img.panel-titlebar-icon {
width: 1rem;
height: 1rem;
vertical-align: middle;
}

.default-panel-btn i.panel-titlebar-icon {
font-size: 20px;
}

.default-panel-btn img.panel-titlebar-icon {
width: 20px;
height: 20px;
}


.bottom-panel-tab-title {
display: inline-flex;
align-items: center;
pointer-events: none;
}

/* Drag-and-drop tab reordering */
.bottom-panel-tab-dragging {
opacity: 0.5;
}

/* Collapsed tab bar: show icons, hide titles for tabs that have icons */
.bottom-panel-tabs-collapsed {
.bottom-panel-tab-icon {
display: inline-flex;
}
.bottom-panel-tab-icon ~ .bottom-panel-tab-title {
display: none;
}
/* Increase spacing in collapsed mode */
.bottom-panel-tab {
padding: 0 0.6rem 0 0.8rem;
}
.bottom-panel-tab-close-btn {
margin-left: 0.8rem;
}
/* Only show close button on the active tab to prevent accidental clicks */
.bottom-panel-tab:not(.active) .bottom-panel-tab-close-btn {
visibility: hidden;
}
}

.bottom-panel-tab-close-btn {
margin-left: 0.55rem;
border-radius: 3px;
Expand Down
5 changes: 1 addition & 4 deletions src/styles/Extn-Terminal.less
Original file line number Diff line number Diff line change
Expand Up @@ -314,11 +314,8 @@
display: inline;
}

/* ─── Shell dropdown (pops above the actions row) ─── */
/* ─── Shell dropdown (positioned on $panel to avoid flyout overflow clip) ─── */
.terminal-shell-dropdown {
position: absolute;
bottom: 100%;
right: 0;
min-width: 180px;
background: var(--terminal-tab-bg);
border: 1px solid var(--terminal-border);
Expand Down
39 changes: 10 additions & 29 deletions src/view/DefaultPanelView.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,12 @@ define(function (require, exports, module) {
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",
icon: "fa-brands fa-git-alt",
label: Strings.GIT_PANEL_TITLE || "Git",
commandID: Commands.CMD_GIT_TOGGLE_PANEL
commandID: Commands.CMD_GIT_TOGGLE_PANEL,
nativeOnly: true
},
{
id: "snippets",
Expand Down Expand Up @@ -104,7 +99,7 @@ define(function (require, exports, module) {
.attr("data-command", btn.commandID)
.attr("data-btn-id", btn.id)
.attr("title", btn.label);
let $icon = $('<i></i>').addClass(btn.icon);
let $icon = $('<i class="panel-titlebar-icon"></i>').addClass(btn.icon);
let $label = $('<span class="default-panel-btn-label"></span>').text(btn.label);
$button.append($icon).append($label);
$buttonsRow.append($button);
Expand All @@ -130,15 +125,16 @@ define(function (require, exports, module) {

/**
* Show or hide buttons based on current state.
* The Problems button is always shown since the panel now displays
* meaningful content regardless of error state.
* On desktop, Git is always shown. On browser, it depends on availability.
* @private
*/
function _updateButtonVisibility() {
if (!_$panel) {
return;
}
_$panel.find('.default-panel-btn[data-btn-id="git"]').toggle(_isGitAvailable());
if (!Phoenix.isNativeApp) {
_$panel.find('.default-panel-btn[data-btn-id="git"]').toggle(_isGitAvailable());
}
}

/**
Expand All @@ -165,7 +161,8 @@ define(function (require, exports, module) {
WorkspaceManager.DEFAULT_PANEL_ID,
_$panel,
undefined,
Strings.BOTTOM_PANEL_DEFAULT_TITLE
Strings.BOTTOM_PANEL_DEFAULT_TITLE,
{iconSvg: "styles/images/app-drawer.svg"}
);

// Button click handler: execute the command to open the target panel.
Expand All @@ -177,21 +174,6 @@ define(function (require, exports, module) {
}
});

const iconHTML = '<img class="app-drawer-tab-icon" src="styles/images/app-drawer.svg"'
+ ' style="width:12px;height:12px;vertical-align:middle;margin-right:4px">';

/**
* Inject the app-drawer icon into the Quick Access tab title.
* Called each time the panel is shown because the tab DOM is rebuilt.
*/
function _addTabIcon() {
const $tabTitle = $('#bottom-panel-tab-bar .bottom-panel-tab[data-panel-id="'
+ WorkspaceManager.DEFAULT_PANEL_ID + '"] .bottom-panel-tab-title');
if ($tabTitle.length && !$tabTitle.find(".app-drawer-tab-icon").length) {
$tabTitle.prepend(iconHTML);
}
}

// The app-drawer button is defined in index.html; set its title here.
const $drawerBtn = $("#app-drawer-button")
.attr("title", Strings.BOTTOM_PANEL_DEFAULT_TITLE);
Expand All @@ -210,7 +192,6 @@ define(function (require, exports, module) {
_panel.hide();
} else {
_updateButtonVisibility();
_addTabIcon();
}
$drawerBtn.toggleClass("selected-button", panelID === WorkspaceManager.DEFAULT_PANEL_ID);
});
Expand Down
Loading
Loading