From 829cf47c4ab084be068058e9f7ed7137ef381128 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Tue, 4 Feb 2025 17:13:21 +0100 Subject: [PATCH 01/10] fix: `ContentHoverWidget` respects Theia styles Customize the default `ContentHoverWidget` behavior to ensure it uses Theia styles when calculating available space for the hover widget. VS Code uses 30 pixel for top height, and 24 pixels for bottom height, but Theia uses 32 pixel for the top and 22 for the bottom. Closes eclipse-theia/theia#14826 --- packages/monaco/src/browser/monaco-init.ts | 39 ++++++++++++++++++- ...application-shell-with-toolbar-override.ts | 11 ++++-- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/packages/monaco/src/browser/monaco-init.ts b/packages/monaco/src/browser/monaco-init.ts index 8906e681b2c3b..6f2427ef95394 100644 --- a/packages/monaco/src/browser/monaco-init.ts +++ b/packages/monaco/src/browser/monaco-init.ts @@ -16,8 +16,8 @@ /* * The code in this file is responsible for overriding service implementations in the Monaco editor with our own Theia-based implementations. - * Since we only get a single chance to call `StandaloneServies.initialize()` with our overrides, we need to make sure that intialize is called before the first call to - * `StandaloneServices.get()` or `StandaloneServies.initialize()`. As we do not control the mechanics of Inversify instance constructions, the approach here is to call + * Since we only get a single chance to call `StandaloneServices.initialize()` with our overrides, we need to make sure that initialize is called before the first call to + * `StandaloneServices.get()` or `StandaloneServices.initialize()`. As we do not control the mechanics of Inversify instance constructions, the approach here is to call * `MonacoInit.init()` from the `index.js` file after all container modules are loaded, but before the first object is fetched from it. * `StandaloneServices.initialize()` is called with service descriptors, not service instances. This lets us finish all overrides before any inversify object is constructed and * might call `initialize()` while being constructed. @@ -50,6 +50,41 @@ import { MonacoQuickInputImplementation } from './monaco-quick-input-service'; import { IQuickInputService } from '@theia/monaco-editor-core/esm/vs/platform/quickinput/common/quickInput'; import { IStandaloneThemeService } from '@theia/monaco-editor-core/esm/vs/editor/standalone/common/standaloneTheme'; import { MonacoStandaloneThemeService } from './monaco-standalone-theme-service'; +import { ContentHoverWidget } from '@theia/monaco-editor-core/esm/vs/editor/contrib/hover/browser/contentHoverWidget'; +import { IPosition } from '@theia/monaco-editor-core/esm/vs/editor/common/core/position'; + +// VS Code uses 30 pixel for top height, and 24 pixels for bottom height, but Theia uses 32 pixel for the top and 22 for the bottom. +// https://github.com/microsoft/vscode/blob/1430e1845cbf5ec29a2fc265f12c7fb5c3d685c3/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts#L13-L14 +// https://github.com/eclipse-theia/theia/blob/b752ea690bdc4e7c5d9ab98a138504ead05be0d1/packages/core/src/browser/style/menus.css#L22 +// https://github.com/eclipse-theia/theia/blob/b752ea690bdc4e7c5d9ab98a138504ead05be0d1/packages/core/src/browser/style/status-bar.css#L18 +// https://github.com/eclipse-theia/theia/issues/14826 +function patchContentHoverWidget(actualTopHeight = 32): { setActualTopHeightForContentHoverWidget: (value: number) => void } { + const vscodeTopHeight = 30; + let _actualTopHeight = actualTopHeight; + function topHeightDiff(): number { + return _actualTopHeight - vscodeTopHeight; + } + + const originalAvailableVerticalSpaceAbove = ContentHoverWidget.prototype['_availableVerticalSpaceAbove']; + ContentHoverWidget.prototype['_availableVerticalSpaceAbove'] = function (position: IPosition): number | undefined { + const value = originalAvailableVerticalSpaceAbove.call(this, position); + return typeof value === 'number' ? value - topHeightDiff() : undefined; + }; + + const originalAvailableVerticalSpaceBelow = ContentHoverWidget.prototype['_availableVerticalSpaceBelow']; + ContentHoverWidget.prototype['_availableVerticalSpaceBelow'] = function (position: IPosition): number | undefined { + const value = originalAvailableVerticalSpaceBelow.call(this, position); + return typeof value === 'number' ? value + 2 : undefined; + }; + + return { + setActualTopHeightForContentHoverWidget: (value: number) => { + _actualTopHeight = value; + } + }; +} + +export const { setActualTopHeightForContentHoverWidget } = patchContentHoverWidget(); class MonacoEditorServiceConstructor { /** diff --git a/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts b/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts index 207fd54447fa6..c4e79eb10e151 100644 --- a/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts +++ b/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts @@ -22,6 +22,7 @@ import { } from '@theia/core/lib/browser'; import { inject, injectable, interfaces, postConstruct } from '@theia/core/shared/inversify'; import { MAXIMIZED_CLASS } from '@theia/core/lib/browser/shell/theia-dock-panel'; +import { setActualTopHeightForContentHoverWidget } from '@theia/monaco/lib/browser/monaco-init'; import { Toolbar, ToolbarFactory } from './toolbar-interfaces'; import { ToolbarPreferences, TOOLBAR_ENABLE_PREFERENCE_ID } from './toolbar-preference-contribution'; @@ -60,12 +61,14 @@ export class ApplicationShellWithToolbarOverride extends ApplicationShell { protected tryShowToolbar(): boolean { const doShowToolbarFromPreference = this.toolbarPreferences[TOOLBAR_ENABLE_PREFERENCE_ID]; const isShellMaximized = this.mainPanel.hasClass(MAXIMIZED_CLASS) || this.bottomPanel.hasClass(MAXIMIZED_CLASS); - if (doShowToolbarFromPreference && !isShellMaximized) { + const show = doShowToolbarFromPreference && !isShellMaximized; + if (show) { this.toolbar.show(); - return true; + } else { + this.toolbar.hide(); } - this.toolbar.hide(); - return false; + setActualTopHeightForContentHoverWidget(show ? 32 * 2 : 32); + return show; } protected override createLayout(): Layout { From 12156fc35527b811cc952a21299eff3a3e6b8ab1 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Wed, 5 Feb 2025 11:25:39 +0100 Subject: [PATCH 02/10] fix: correct actual top height when toolbar is on - use 30px instead of 32px - change api from set to adjust to remove top panel dependency from the toolbar code --- packages/monaco/src/browser/monaco-init.ts | 8 ++++---- .../browser/application-shell-with-toolbar-override.ts | 8 ++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/monaco/src/browser/monaco-init.ts b/packages/monaco/src/browser/monaco-init.ts index 6f2427ef95394..0b0f028aa145a 100644 --- a/packages/monaco/src/browser/monaco-init.ts +++ b/packages/monaco/src/browser/monaco-init.ts @@ -58,7 +58,7 @@ import { IPosition } from '@theia/monaco-editor-core/esm/vs/editor/common/core/p // https://github.com/eclipse-theia/theia/blob/b752ea690bdc4e7c5d9ab98a138504ead05be0d1/packages/core/src/browser/style/menus.css#L22 // https://github.com/eclipse-theia/theia/blob/b752ea690bdc4e7c5d9ab98a138504ead05be0d1/packages/core/src/browser/style/status-bar.css#L18 // https://github.com/eclipse-theia/theia/issues/14826 -function patchContentHoverWidget(actualTopHeight = 32): { setActualTopHeightForContentHoverWidget: (value: number) => void } { +function patchContentHoverWidget(actualTopHeight = 32): { adjustActualTopHeightForContentHoverWidget: (value: number) => void } { const vscodeTopHeight = 30; let _actualTopHeight = actualTopHeight; function topHeightDiff(): number { @@ -78,13 +78,13 @@ function patchContentHoverWidget(actualTopHeight = 32): { setActualTopHeightForC }; return { - setActualTopHeightForContentHoverWidget: (value: number) => { - _actualTopHeight = value; + adjustActualTopHeightForContentHoverWidget: (pixelsToAdjustWith: number) => { + _actualTopHeight += pixelsToAdjustWith; } }; } -export const { setActualTopHeightForContentHoverWidget } = patchContentHoverWidget(); +export const { adjustActualTopHeightForContentHoverWidget } = patchContentHoverWidget(); class MonacoEditorServiceConstructor { /** diff --git a/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts b/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts index c4e79eb10e151..a3526a242ec7f 100644 --- a/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts +++ b/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts @@ -22,10 +22,14 @@ import { } from '@theia/core/lib/browser'; import { inject, injectable, interfaces, postConstruct } from '@theia/core/shared/inversify'; import { MAXIMIZED_CLASS } from '@theia/core/lib/browser/shell/theia-dock-panel'; -import { setActualTopHeightForContentHoverWidget } from '@theia/monaco/lib/browser/monaco-init'; +import { adjustActualTopHeightForContentHoverWidget } from '@theia/monaco/lib/browser/monaco-init'; import { Toolbar, ToolbarFactory } from './toolbar-interfaces'; import { ToolbarPreferences, TOOLBAR_ENABLE_PREFERENCE_ID } from './toolbar-preference-contribution'; +// The toolbar is 2px shorter than the height of the top panel. +// https://github.com/eclipse-theia/theia/blob/451464e6ea3d4aaf9cdbffd3d17dbb117787fc4e/packages/toolbar/src/browser/style/toolbar.css#L18 +const TOOLBAR_HEIGHT_PX = 30; + @injectable() export class ApplicationShellWithToolbarOverride extends ApplicationShell { @inject(ToolbarPreferences) protected toolbarPreferences: ToolbarPreferences; @@ -67,7 +71,7 @@ export class ApplicationShellWithToolbarOverride extends ApplicationShell { } else { this.toolbar.hide(); } - setActualTopHeightForContentHoverWidget(show ? 32 * 2 : 32); + adjustActualTopHeightForContentHoverWidget(show ? TOOLBAR_HEIGHT_PX : -TOOLBAR_HEIGHT_PX); return show; } From 6ee3bdfcd67dde24f7034e6384b081bbfe4be529 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Wed, 5 Feb 2025 11:37:59 +0100 Subject: [PATCH 03/10] fix: improve dev docs about the pixel adjustments --- packages/monaco/src/browser/monaco-init.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/monaco/src/browser/monaco-init.ts b/packages/monaco/src/browser/monaco-init.ts index 0b0f028aa145a..99c79ba9f6554 100644 --- a/packages/monaco/src/browser/monaco-init.ts +++ b/packages/monaco/src/browser/monaco-init.ts @@ -68,12 +68,20 @@ function patchContentHoverWidget(actualTopHeight = 32): { adjustActualTopHeightF const originalAvailableVerticalSpaceAbove = ContentHoverWidget.prototype['_availableVerticalSpaceAbove']; ContentHoverWidget.prototype['_availableVerticalSpaceAbove'] = function (position: IPosition): number | undefined { const value = originalAvailableVerticalSpaceAbove.call(this, position); + // The original implementation deducts the height of the top panel from the total available space. + // https://github.com/microsoft/vscode/blob/1430e1845cbf5ec29a2fc265f12c7fb5c3d685c3/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts#L71 + // However, in Theia, the top panel is generally 2 pixels taller (or more, depending on the visibility of the toolbar). + // This additional height must be further subtracted from the computed height for accurate positioning. return typeof value === 'number' ? value - topHeightDiff() : undefined; }; const originalAvailableVerticalSpaceBelow = ContentHoverWidget.prototype['_availableVerticalSpaceBelow']; ContentHoverWidget.prototype['_availableVerticalSpaceBelow'] = function (position: IPosition): number | undefined { const value = originalAvailableVerticalSpaceBelow.call(this, position); + // The original method subtracts the height of the bottom panel from the overall available height. + // https://github.com/microsoft/vscode/blob/1430e1845cbf5ec29a2fc265f12c7fb5c3d685c3/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts#L83 + // In Theia, the status bar is 2 pixels shorter than in VS Code, which means this difference + // should be added back to ensure the calculated available space is accurate. return typeof value === 'number' ? value + 2 : undefined; }; From 3138f4c542d3f1cc02ac3bf71246a07a0b9a5ff8 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Wed, 5 Feb 2025 11:49:23 +0100 Subject: [PATCH 04/10] fix: measure toolbar instead of using magic number --- .../src/browser/application-shell-with-toolbar-override.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts b/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts index a3526a242ec7f..282214bb7889b 100644 --- a/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts +++ b/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts @@ -26,9 +26,6 @@ import { adjustActualTopHeightForContentHoverWidget } from '@theia/monaco/lib/br import { Toolbar, ToolbarFactory } from './toolbar-interfaces'; import { ToolbarPreferences, TOOLBAR_ENABLE_PREFERENCE_ID } from './toolbar-preference-contribution'; -// The toolbar is 2px shorter than the height of the top panel. -// https://github.com/eclipse-theia/theia/blob/451464e6ea3d4aaf9cdbffd3d17dbb117787fc4e/packages/toolbar/src/browser/style/toolbar.css#L18 -const TOOLBAR_HEIGHT_PX = 30; @injectable() export class ApplicationShellWithToolbarOverride extends ApplicationShell { @@ -71,7 +68,8 @@ export class ApplicationShellWithToolbarOverride extends ApplicationShell { } else { this.toolbar.hide(); } - adjustActualTopHeightForContentHoverWidget(show ? TOOLBAR_HEIGHT_PX : -TOOLBAR_HEIGHT_PX); + const toolbarHeight = this.toolbar.node.getBoundingClientRect().height; + adjustActualTopHeightForContentHoverWidget(show ? toolbarHeight : -toolbarHeight); return show; } From 5e0f660b3a3af25d5ed2a2f129e93663f87e344f Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Wed, 5 Feb 2025 12:08:17 +0100 Subject: [PATCH 05/10] fix: remove additional magic numbers --- packages/monaco/src/browser/monaco-init.ts | 35 +++++++++++-------- ...application-shell-with-toolbar-override.ts | 7 ++-- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/packages/monaco/src/browser/monaco-init.ts b/packages/monaco/src/browser/monaco-init.ts index 99c79ba9f6554..dbfe60417f166 100644 --- a/packages/monaco/src/browser/monaco-init.ts +++ b/packages/monaco/src/browser/monaco-init.ts @@ -53,26 +53,32 @@ import { MonacoStandaloneThemeService } from './monaco-standalone-theme-service' import { ContentHoverWidget } from '@theia/monaco-editor-core/esm/vs/editor/contrib/hover/browser/contentHoverWidget'; import { IPosition } from '@theia/monaco-editor-core/esm/vs/editor/common/core/position'; -// VS Code uses 30 pixel for top height, and 24 pixels for bottom height, but Theia uses 32 pixel for the top and 22 for the bottom. // https://github.com/microsoft/vscode/blob/1430e1845cbf5ec29a2fc265f12c7fb5c3d685c3/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts#L13-L14 +const VSCODE_TOP_HEIGHT = 30; +const VSCODE_BOTTOM_HEIGHT = 24; + +// VS Code uses 30 pixel for top height, and 24 pixels for bottom height, but Theia uses 32 pixel for the top and 22 for the bottom. // https://github.com/eclipse-theia/theia/blob/b752ea690bdc4e7c5d9ab98a138504ead05be0d1/packages/core/src/browser/style/menus.css#L22 // https://github.com/eclipse-theia/theia/blob/b752ea690bdc4e7c5d9ab98a138504ead05be0d1/packages/core/src/browser/style/status-bar.css#L18 // https://github.com/eclipse-theia/theia/issues/14826 -function patchContentHoverWidget(actualTopHeight = 32): { adjustActualTopHeightForContentHoverWidget: (value: number) => void } { - const vscodeTopHeight = 30; - let _actualTopHeight = actualTopHeight; - function topHeightDiff(): number { - return _actualTopHeight - vscodeTopHeight; +function patchContentHoverWidget(topPanelHeight = 32): { setActualTopHeightForContentHoverWidget: (value: number) => void } { + let _actualTopHeight = topPanelHeight; + function getTopHeightDiff(): number { + return _actualTopHeight - VSCODE_TOP_HEIGHT; } + const actualBottomHeight = 22; // Theia's status bar height + const bottomHeightDiff = actualBottomHeight - VSCODE_BOTTOM_HEIGHT; + const originalAvailableVerticalSpaceAbove = ContentHoverWidget.prototype['_availableVerticalSpaceAbove']; ContentHoverWidget.prototype['_availableVerticalSpaceAbove'] = function (position: IPosition): number | undefined { const value = originalAvailableVerticalSpaceAbove.call(this, position); // The original implementation deducts the height of the top panel from the total available space. // https://github.com/microsoft/vscode/blob/1430e1845cbf5ec29a2fc265f12c7fb5c3d685c3/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts#L71 - // However, in Theia, the top panel is generally 2 pixels taller (or more, depending on the visibility of the toolbar). + // However, in Theia, the top panel has generally different size (especially when the toolbar is visible). // This additional height must be further subtracted from the computed height for accurate positioning. - return typeof value === 'number' ? value - topHeightDiff() : undefined; + const topHeightDiff = getTopHeightDiff(); + return typeof value === 'number' ? value - topHeightDiff : undefined; }; const originalAvailableVerticalSpaceBelow = ContentHoverWidget.prototype['_availableVerticalSpaceBelow']; @@ -80,19 +86,20 @@ function patchContentHoverWidget(actualTopHeight = 32): { adjustActualTopHeightF const value = originalAvailableVerticalSpaceBelow.call(this, position); // The original method subtracts the height of the bottom panel from the overall available height. // https://github.com/microsoft/vscode/blob/1430e1845cbf5ec29a2fc265f12c7fb5c3d685c3/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts#L83 - // In Theia, the status bar is 2 pixels shorter than in VS Code, which means this difference - // should be added back to ensure the calculated available space is accurate. - return typeof value === 'number' ? value + 2 : undefined; + // In Theia, the status bar has different height than in VS Code, which means this difference + // should be also removed to ensure the calculated available space is accurate. + // Note that removing negative value will increase the available space. + return typeof value === 'number' ? value - bottomHeightDiff : undefined; }; return { - adjustActualTopHeightForContentHoverWidget: (pixelsToAdjustWith: number) => { - _actualTopHeight += pixelsToAdjustWith; + setActualTopHeightForContentHoverWidget: (value: number) => { + _actualTopHeight = value; } }; } -export const { adjustActualTopHeightForContentHoverWidget } = patchContentHoverWidget(); +export const { setActualTopHeightForContentHoverWidget } = patchContentHoverWidget(); class MonacoEditorServiceConstructor { /** diff --git a/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts b/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts index 282214bb7889b..89111b3f14f67 100644 --- a/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts +++ b/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts @@ -22,7 +22,7 @@ import { } from '@theia/core/lib/browser'; import { inject, injectable, interfaces, postConstruct } from '@theia/core/shared/inversify'; import { MAXIMIZED_CLASS } from '@theia/core/lib/browser/shell/theia-dock-panel'; -import { adjustActualTopHeightForContentHoverWidget } from '@theia/monaco/lib/browser/monaco-init'; +import { setActualTopHeightForContentHoverWidget } from '@theia/monaco/lib/browser/monaco-init'; import { Toolbar, ToolbarFactory } from './toolbar-interfaces'; import { ToolbarPreferences, TOOLBAR_ENABLE_PREFERENCE_ID } from './toolbar-preference-contribution'; @@ -68,8 +68,9 @@ export class ApplicationShellWithToolbarOverride extends ApplicationShell { } else { this.toolbar.hide(); } - const toolbarHeight = this.toolbar.node.getBoundingClientRect().height; - adjustActualTopHeightForContentHoverWidget(show ? toolbarHeight : -toolbarHeight); + const topPanelHeight = this.topPanel.node.getBoundingClientRect().height; + const toolbarHeight = this.toolbar.node.getBoundingClientRect().height; // 0 if hidden + setActualTopHeightForContentHoverWidget(topPanelHeight + toolbarHeight); return show; } From 10df216a25215dfa146771a231007b742988f90a Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Wed, 5 Feb 2025 12:26:31 +0100 Subject: [PATCH 06/10] fix(lint): no double empty lines --- .../src/browser/application-shell-with-toolbar-override.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts b/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts index 89111b3f14f67..4bc3a46f45836 100644 --- a/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts +++ b/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts @@ -26,7 +26,6 @@ import { setActualTopHeightForContentHoverWidget } from '@theia/monaco/lib/brows import { Toolbar, ToolbarFactory } from './toolbar-interfaces'; import { ToolbarPreferences, TOOLBAR_ENABLE_PREFERENCE_ID } from './toolbar-preference-contribution'; - @injectable() export class ApplicationShellWithToolbarOverride extends ApplicationShell { @inject(ToolbarPreferences) protected toolbarPreferences: ToolbarPreferences; From c1980286fd9bb39c4b28f326dc2997bfbab5897b Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Wed, 5 Feb 2025 16:00:55 +0100 Subject: [PATCH 07/10] fix: use no magic pixel numbers --- .../browser/content-hover-widget-patcher.ts | 74 +++++++++++++++++++ .../default-content-hover-widget-patcher.ts | 50 +++++++++++++ .../src/browser/monaco-frontend-module.ts | 4 + packages/monaco/src/browser/monaco-init.ts | 51 +------------ ...application-shell-with-toolbar-override.ts | 13 +--- .../toolbar-content-hover-widget-patcher.ts | 51 +++++++++++++ .../src/browser/toolbar-frontend-module.ts | 2 + 7 files changed, 187 insertions(+), 58 deletions(-) create mode 100644 packages/monaco/src/browser/content-hover-widget-patcher.ts create mode 100644 packages/monaco/src/browser/default-content-hover-widget-patcher.ts create mode 100644 packages/toolbar/src/browser/toolbar-content-hover-widget-patcher.ts diff --git a/packages/monaco/src/browser/content-hover-widget-patcher.ts b/packages/monaco/src/browser/content-hover-widget-patcher.ts new file mode 100644 index 0000000000000..4af197f032a3d --- /dev/null +++ b/packages/monaco/src/browser/content-hover-widget-patcher.ts @@ -0,0 +1,74 @@ +// ***************************************************************************** +// Copyright (C) 2025 and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { IPosition } from '@theia/monaco-editor-core/esm/vs/editor/common/core/position'; +import { ContentHoverWidget } from '@theia/monaco-editor-core/esm/vs/editor/contrib/hover/browser/contentHoverWidget'; + +// https://github.com/microsoft/vscode/blob/1430e1845cbf5ec29a2fc265f12c7fb5c3d685c3/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts#L13-L14 +const VSCODE_TOP_HEIGHT = 30; +const VSCODE_BOTTOM_HEIGHT = 24; + +export interface SetActualHeightForContentHoverWidgetParams { + topHeight?: number; + bottomHeight?: number; +} + +export interface ContentHoverWidgetPatcher { + setActualHeightForContentHoverWidget(params: SetActualHeightForContentHoverWidgetParams): void; +} + +export function createContentWidgetPatcher(): ContentHoverWidgetPatcher { + let actualTopDiff: number | undefined; + let actualBottomDiff: number | undefined; + + const originalAvailableVerticalSpaceAbove = ContentHoverWidget.prototype['_availableVerticalSpaceAbove']; + ContentHoverWidget.prototype['_availableVerticalSpaceAbove'] = function (position: IPosition): number | undefined { + const originalValue = originalAvailableVerticalSpaceAbove.call(this, position); + if (typeof originalValue !== 'number' || !actualTopDiff) { + return originalValue; + } + // The original implementation deducts the height of the top panel from the total available space. + // https://github.com/microsoft/vscode/blob/1430e1845cbf5ec29a2fc265f12c7fb5c3d685c3/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts#L71 + // However, in Theia, the top panel has generally different size (especially when the toolbar is visible). + // This additional height must be further subtracted from the computed height for accurate positioning. + return originalValue - actualTopDiff; + }; + + const originalAvailableVerticalSpaceBelow = ContentHoverWidget.prototype['_availableVerticalSpaceBelow']; + ContentHoverWidget.prototype['_availableVerticalSpaceBelow'] = function (position: IPosition): number | undefined { + const originalValue = originalAvailableVerticalSpaceBelow.call(this, position); + if (typeof originalValue !== 'number' || !actualBottomDiff) { + return originalValue; + } + // The original method subtracts the height of the bottom panel from the overall available height. + // https://github.com/microsoft/vscode/blob/1430e1845cbf5ec29a2fc265f12c7fb5c3d685c3/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts#L83 + // In Theia, the status bar has different height than in VS Code, which means this difference + // should be also removed to ensure the calculated available space is accurate. + // Note that removing negative value will increase the available space. + return originalValue - actualBottomDiff; + }; + + return { + setActualHeightForContentHoverWidget(params): void { + if (typeof params.topHeight === 'number') { + actualTopDiff = params.topHeight - VSCODE_TOP_HEIGHT; + } + if (typeof params.bottomHeight === 'number') { + actualBottomDiff = params.bottomHeight - VSCODE_BOTTOM_HEIGHT; + } + }, + }; +} diff --git a/packages/monaco/src/browser/default-content-hover-widget-patcher.ts b/packages/monaco/src/browser/default-content-hover-widget-patcher.ts new file mode 100644 index 0000000000000..5faac5fb5e71c --- /dev/null +++ b/packages/monaco/src/browser/default-content-hover-widget-patcher.ts @@ -0,0 +1,50 @@ +// ***************************************************************************** +// Copyright (C) 2025 and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { injectable } from '@theia/core/shared/inversify'; +import { ApplicationShell, FrontendApplication, FrontendApplicationContribution } from '@theia/core/src/browser'; +import { setActualHeightForContentHoverWidget } from './monaco-init'; +import { SetActualHeightForContentHoverWidgetParams } from './content-hover-widget-patcher'; + +@injectable() +export class DefaultContentHoverWidgetPatcher implements FrontendApplicationContribution { + onStart(app: FrontendApplication): void { + const shell = app.shell; + + this.updateContentHoverWidgetHeight({ + topHeight: this.getTopPanelHeight(shell), + bottomHeight: this.getStatusBarHeight(shell) + }); + + shell['statusBar'].onDidChangeVisibility(() => { + this.updateContentHoverWidgetHeight({ + bottomHeight: this.getStatusBarHeight(shell) + }); + }); + } + + protected updateContentHoverWidgetHeight(params: SetActualHeightForContentHoverWidgetParams): void { + setActualHeightForContentHoverWidget(params); + } + + protected getTopPanelHeight(shell: ApplicationShell): number { + return shell.topPanel.node.getBoundingClientRect().height; + } + + protected getStatusBarHeight(shell: ApplicationShell): number { + return shell['statusBar'].node.getBoundingClientRect().height; + } +} diff --git a/packages/monaco/src/browser/monaco-frontend-module.ts b/packages/monaco/src/browser/monaco-frontend-module.ts index 2bfb2daf5b3f3..1fc1b35af64c8 100644 --- a/packages/monaco/src/browser/monaco-frontend-module.ts +++ b/packages/monaco/src/browser/monaco-frontend-module.ts @@ -77,6 +77,7 @@ import { IContextKeyService } from '@theia/monaco-editor-core/esm/vs/platform/co import { IThemeService } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/themeService.js'; import { ActiveMonacoUndoRedoHandler, FocusedMonacoUndoRedoHandler } from './monaco-undo-redo-handler'; import { ILogService } from '@theia/monaco-editor-core/esm/vs/platform/log/common/log'; +import { DefaultContentHoverWidgetPatcher } from './default-content-hover-widget-patcher'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(MonacoThemingService).toSelf().inSingletonScope(); @@ -184,6 +185,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ActiveMonacoUndoRedoHandler).toSelf().inSingletonScope(); bind(UndoRedoHandler).toService(FocusedMonacoUndoRedoHandler); bind(UndoRedoHandler).toService(ActiveMonacoUndoRedoHandler); + + bind(DefaultContentHoverWidgetPatcher).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(DefaultContentHoverWidgetPatcher); }); export const MonacoConfigurationService = Symbol('MonacoConfigurationService'); diff --git a/packages/monaco/src/browser/monaco-init.ts b/packages/monaco/src/browser/monaco-init.ts index dbfe60417f166..25ab038b9b057 100644 --- a/packages/monaco/src/browser/monaco-init.ts +++ b/packages/monaco/src/browser/monaco-init.ts @@ -50,56 +50,9 @@ import { MonacoQuickInputImplementation } from './monaco-quick-input-service'; import { IQuickInputService } from '@theia/monaco-editor-core/esm/vs/platform/quickinput/common/quickInput'; import { IStandaloneThemeService } from '@theia/monaco-editor-core/esm/vs/editor/standalone/common/standaloneTheme'; import { MonacoStandaloneThemeService } from './monaco-standalone-theme-service'; -import { ContentHoverWidget } from '@theia/monaco-editor-core/esm/vs/editor/contrib/hover/browser/contentHoverWidget'; -import { IPosition } from '@theia/monaco-editor-core/esm/vs/editor/common/core/position'; +import { createContentWidgetPatcher } from './content-hover-widget-patcher'; -// https://github.com/microsoft/vscode/blob/1430e1845cbf5ec29a2fc265f12c7fb5c3d685c3/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts#L13-L14 -const VSCODE_TOP_HEIGHT = 30; -const VSCODE_BOTTOM_HEIGHT = 24; - -// VS Code uses 30 pixel for top height, and 24 pixels for bottom height, but Theia uses 32 pixel for the top and 22 for the bottom. -// https://github.com/eclipse-theia/theia/blob/b752ea690bdc4e7c5d9ab98a138504ead05be0d1/packages/core/src/browser/style/menus.css#L22 -// https://github.com/eclipse-theia/theia/blob/b752ea690bdc4e7c5d9ab98a138504ead05be0d1/packages/core/src/browser/style/status-bar.css#L18 -// https://github.com/eclipse-theia/theia/issues/14826 -function patchContentHoverWidget(topPanelHeight = 32): { setActualTopHeightForContentHoverWidget: (value: number) => void } { - let _actualTopHeight = topPanelHeight; - function getTopHeightDiff(): number { - return _actualTopHeight - VSCODE_TOP_HEIGHT; - } - - const actualBottomHeight = 22; // Theia's status bar height - const bottomHeightDiff = actualBottomHeight - VSCODE_BOTTOM_HEIGHT; - - const originalAvailableVerticalSpaceAbove = ContentHoverWidget.prototype['_availableVerticalSpaceAbove']; - ContentHoverWidget.prototype['_availableVerticalSpaceAbove'] = function (position: IPosition): number | undefined { - const value = originalAvailableVerticalSpaceAbove.call(this, position); - // The original implementation deducts the height of the top panel from the total available space. - // https://github.com/microsoft/vscode/blob/1430e1845cbf5ec29a2fc265f12c7fb5c3d685c3/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts#L71 - // However, in Theia, the top panel has generally different size (especially when the toolbar is visible). - // This additional height must be further subtracted from the computed height for accurate positioning. - const topHeightDiff = getTopHeightDiff(); - return typeof value === 'number' ? value - topHeightDiff : undefined; - }; - - const originalAvailableVerticalSpaceBelow = ContentHoverWidget.prototype['_availableVerticalSpaceBelow']; - ContentHoverWidget.prototype['_availableVerticalSpaceBelow'] = function (position: IPosition): number | undefined { - const value = originalAvailableVerticalSpaceBelow.call(this, position); - // The original method subtracts the height of the bottom panel from the overall available height. - // https://github.com/microsoft/vscode/blob/1430e1845cbf5ec29a2fc265f12c7fb5c3d685c3/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts#L83 - // In Theia, the status bar has different height than in VS Code, which means this difference - // should be also removed to ensure the calculated available space is accurate. - // Note that removing negative value will increase the available space. - return typeof value === 'number' ? value - bottomHeightDiff : undefined; - }; - - return { - setActualTopHeightForContentHoverWidget: (value: number) => { - _actualTopHeight = value; - } - }; -} - -export const { setActualTopHeightForContentHoverWidget } = patchContentHoverWidget(); +export const { setActualHeightForContentHoverWidget } = createContentWidgetPatcher(); class MonacoEditorServiceConstructor { /** diff --git a/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts b/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts index 4bc3a46f45836..e2a61ad8d61a7 100644 --- a/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts +++ b/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts @@ -22,7 +22,6 @@ import { } from '@theia/core/lib/browser'; import { inject, injectable, interfaces, postConstruct } from '@theia/core/shared/inversify'; import { MAXIMIZED_CLASS } from '@theia/core/lib/browser/shell/theia-dock-panel'; -import { setActualTopHeightForContentHoverWidget } from '@theia/monaco/lib/browser/monaco-init'; import { Toolbar, ToolbarFactory } from './toolbar-interfaces'; import { ToolbarPreferences, TOOLBAR_ENABLE_PREFERENCE_ID } from './toolbar-preference-contribution'; @@ -61,16 +60,12 @@ export class ApplicationShellWithToolbarOverride extends ApplicationShell { protected tryShowToolbar(): boolean { const doShowToolbarFromPreference = this.toolbarPreferences[TOOLBAR_ENABLE_PREFERENCE_ID]; const isShellMaximized = this.mainPanel.hasClass(MAXIMIZED_CLASS) || this.bottomPanel.hasClass(MAXIMIZED_CLASS); - const show = doShowToolbarFromPreference && !isShellMaximized; - if (show) { + if (doShowToolbarFromPreference && !isShellMaximized) { this.toolbar.show(); - } else { - this.toolbar.hide(); + return true } - const topPanelHeight = this.topPanel.node.getBoundingClientRect().height; - const toolbarHeight = this.toolbar.node.getBoundingClientRect().height; // 0 if hidden - setActualTopHeightForContentHoverWidget(topPanelHeight + toolbarHeight); - return show; + this.toolbar.hide(); + return false } protected override createLayout(): Layout { diff --git a/packages/toolbar/src/browser/toolbar-content-hover-widget-patcher.ts b/packages/toolbar/src/browser/toolbar-content-hover-widget-patcher.ts new file mode 100644 index 0000000000000..b95e890863785 --- /dev/null +++ b/packages/toolbar/src/browser/toolbar-content-hover-widget-patcher.ts @@ -0,0 +1,51 @@ +// ***************************************************************************** +// Copyright (C) 2025 and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { ApplicationShell, FrontendApplication } from '@theia/core/lib/browser'; +import { injectable, interfaces } from '@theia/core/shared/inversify'; +import { DefaultContentHoverWidgetPatcher } from '@theia/monaco/lib/browser/default-content-hover-widget-patcher'; +import { ApplicationShellWithToolbarOverride } from './application-shell-with-toolbar-override'; + +@injectable() +export class ToolbarContentHoverWidgetPatcher extends DefaultContentHoverWidgetPatcher { + + override onStart(app: FrontendApplication): void { + super.onStart(app); + const shell = app.shell; + if (shell instanceof ApplicationShellWithToolbarOverride) { + shell['toolbar'].onDidChangeVisibility(() => { + this.updateContentHoverWidgetHeight({ + topHeight: this.getTopPanelHeight(shell) + }); + }); + } + } + + protected override getTopPanelHeight(shell: ApplicationShell): number { + const defaultHeight = shell.topPanel.node.getBoundingClientRect().height; + if (shell instanceof ApplicationShellWithToolbarOverride) { + const toolbarHeight = shell['toolbar'].node.getBoundingClientRect().height; + return defaultHeight + toolbarHeight; + } + return defaultHeight; + } +} + +export const bindToolbarContentHoverWidgetPatcher = (bind: interfaces.Bind, rebind: interfaces.Rebind, unbind: interfaces.Unbind): void => { + bind(ToolbarContentHoverWidgetPatcher).toSelf().inSingletonScope(); + rebind(DefaultContentHoverWidgetPatcher).toService(ToolbarContentHoverWidgetPatcher); +}; + diff --git a/packages/toolbar/src/browser/toolbar-frontend-module.ts b/packages/toolbar/src/browser/toolbar-frontend-module.ts index e7895098657a3..dd66ba98f2d00 100644 --- a/packages/toolbar/src/browser/toolbar-frontend-module.ts +++ b/packages/toolbar/src/browser/toolbar-frontend-module.ts @@ -18,6 +18,7 @@ import '../../src/browser/style/toolbar.css'; import { ContainerModule, interfaces } from '@theia/core/shared/inversify'; import { bindToolbarApplicationShell } from './application-shell-with-toolbar-override'; import { bindToolbar } from './toolbar-command-contribution'; +import { bindToolbarContentHoverWidgetPatcher } from './toolbar-content-hover-widget-patcher'; export default new ContainerModule(( bind: interfaces.Bind, @@ -27,4 +28,5 @@ export default new ContainerModule(( ) => { bindToolbarApplicationShell(bind, rebind, unbind); bindToolbar(bind); + bindToolbarContentHoverWidgetPatcher(bind, rebind, unbind); }); From d3079a98229f0e4a4dd88950d05f1b2af99fe311 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Wed, 5 Feb 2025 16:03:36 +0100 Subject: [PATCH 08/10] fix: add missing hover to symbol name --- packages/monaco/src/browser/content-hover-widget-patcher.ts | 2 +- .../src/browser/default-content-hover-widget-patcher.ts | 4 ++-- packages/monaco/src/browser/monaco-init.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/monaco/src/browser/content-hover-widget-patcher.ts b/packages/monaco/src/browser/content-hover-widget-patcher.ts index 4af197f032a3d..e8150a71e1621 100644 --- a/packages/monaco/src/browser/content-hover-widget-patcher.ts +++ b/packages/monaco/src/browser/content-hover-widget-patcher.ts @@ -30,7 +30,7 @@ export interface ContentHoverWidgetPatcher { setActualHeightForContentHoverWidget(params: SetActualHeightForContentHoverWidgetParams): void; } -export function createContentWidgetPatcher(): ContentHoverWidgetPatcher { +export function createContentHoverWidgetPatcher(): ContentHoverWidgetPatcher { let actualTopDiff: number | undefined; let actualBottomDiff: number | undefined; diff --git a/packages/monaco/src/browser/default-content-hover-widget-patcher.ts b/packages/monaco/src/browser/default-content-hover-widget-patcher.ts index 5faac5fb5e71c..2dcb667668f28 100644 --- a/packages/monaco/src/browser/default-content-hover-widget-patcher.ts +++ b/packages/monaco/src/browser/default-content-hover-widget-patcher.ts @@ -16,8 +16,8 @@ import { injectable } from '@theia/core/shared/inversify'; import { ApplicationShell, FrontendApplication, FrontendApplicationContribution } from '@theia/core/src/browser'; -import { setActualHeightForContentHoverWidget } from './monaco-init'; import { SetActualHeightForContentHoverWidgetParams } from './content-hover-widget-patcher'; +import { contentHoverWidgetPatcher } from './monaco-init'; @injectable() export class DefaultContentHoverWidgetPatcher implements FrontendApplicationContribution { @@ -37,7 +37,7 @@ export class DefaultContentHoverWidgetPatcher implements FrontendApplicationCont } protected updateContentHoverWidgetHeight(params: SetActualHeightForContentHoverWidgetParams): void { - setActualHeightForContentHoverWidget(params); + contentHoverWidgetPatcher.setActualHeightForContentHoverWidget(params); } protected getTopPanelHeight(shell: ApplicationShell): number { diff --git a/packages/monaco/src/browser/monaco-init.ts b/packages/monaco/src/browser/monaco-init.ts index 25ab038b9b057..2a5260318e879 100644 --- a/packages/monaco/src/browser/monaco-init.ts +++ b/packages/monaco/src/browser/monaco-init.ts @@ -50,9 +50,9 @@ import { MonacoQuickInputImplementation } from './monaco-quick-input-service'; import { IQuickInputService } from '@theia/monaco-editor-core/esm/vs/platform/quickinput/common/quickInput'; import { IStandaloneThemeService } from '@theia/monaco-editor-core/esm/vs/editor/standalone/common/standaloneTheme'; import { MonacoStandaloneThemeService } from './monaco-standalone-theme-service'; -import { createContentWidgetPatcher } from './content-hover-widget-patcher'; +import { createContentHoverWidgetPatcher } from './content-hover-widget-patcher'; -export const { setActualHeightForContentHoverWidget } = createContentWidgetPatcher(); +export const contentHoverWidgetPatcher = createContentHoverWidgetPatcher(); class MonacoEditorServiceConstructor { /** From 380a1553d09170e3b4f1eae0e731d1b0622cfab7 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Wed, 5 Feb 2025 16:05:02 +0100 Subject: [PATCH 09/10] fix(lint): missing semi --- .../src/browser/application-shell-with-toolbar-override.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts b/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts index e2a61ad8d61a7..207fd54447fa6 100644 --- a/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts +++ b/packages/toolbar/src/browser/application-shell-with-toolbar-override.ts @@ -62,10 +62,10 @@ export class ApplicationShellWithToolbarOverride extends ApplicationShell { const isShellMaximized = this.mainPanel.hasClass(MAXIMIZED_CLASS) || this.bottomPanel.hasClass(MAXIMIZED_CLASS); if (doShowToolbarFromPreference && !isShellMaximized) { this.toolbar.show(); - return true + return true; } this.toolbar.hide(); - return false + return false; } protected override createLayout(): Layout { From 723f0ac31236fc91c25286881710db2d1c85881f Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Wed, 5 Feb 2025 16:10:03 +0100 Subject: [PATCH 10/10] fix(lint): no src import --- .../monaco/src/browser/default-content-hover-widget-patcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/monaco/src/browser/default-content-hover-widget-patcher.ts b/packages/monaco/src/browser/default-content-hover-widget-patcher.ts index 2dcb667668f28..38f9e200e8c54 100644 --- a/packages/monaco/src/browser/default-content-hover-widget-patcher.ts +++ b/packages/monaco/src/browser/default-content-hover-widget-patcher.ts @@ -15,7 +15,7 @@ // ***************************************************************************** import { injectable } from '@theia/core/shared/inversify'; -import { ApplicationShell, FrontendApplication, FrontendApplicationContribution } from '@theia/core/src/browser'; +import { ApplicationShell, FrontendApplication, FrontendApplicationContribution } from '@theia/core/lib/browser'; import { SetActualHeightForContentHoverWidgetParams } from './content-hover-widget-patcher'; import { contentHoverWidgetPatcher } from './monaco-init';