From 80bddc238dbb17bacf0051ac6a74c38560785a46 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Wed, 31 Mar 2021 13:37:37 +0200 Subject: [PATCH] ATL-988: Aligned the hover size to the expression. Signed-off-by: Akos Kitta --- .../browser/arduino-ide-frontend-module.ts | 7 ++ .../src/browser/style/debug.css | 36 +++++++ .../src/browser/style/index.css | 1 + .../browser/theia/debug/debug-editor-model.ts | 84 ++++++++++++++++ .../browser/theia/debug/debug-hover-source.ts | 18 ++++ .../browser/theia/debug/debug-hover-widget.ts | 96 +++++++++++++++++++ 6 files changed, 242 insertions(+) create mode 100644 arduino-ide-extension/src/browser/style/debug.css create mode 100644 arduino-ide-extension/src/browser/theia/debug/debug-editor-model.ts create mode 100644 arduino-ide-extension/src/browser/theia/debug/debug-hover-source.ts create mode 100644 arduino-ide-extension/src/browser/theia/debug/debug-hover-widget.ts diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index ec8c7f62..95021fde 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -156,6 +156,8 @@ import { SearchInWorkspaceResultTreeWidget as TheiaSearchInWorkspaceResultTreeWi import { SearchInWorkspaceResultTreeWidget } from './theia/search-in-workspace/search-in-workspace-result-tree-widget'; import { MonacoEditorProvider } from './theia/monaco/monaco-editor-provider'; import { MonacoEditorProvider as TheiaMonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider'; +import { DebugEditorModel } from './theia/debug/debug-editor-model'; +import { DebugEditorModelFactory } from '@theia/debug/lib/browser/editor/debug-editor-model'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -417,6 +419,11 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(DebugConfigurationManager).toSelf().inSingletonScope(); rebind(TheiaDebugConfigurationManager).toService(DebugConfigurationManager); + // Patch for the debug hover: https://github.com/eclipse-theia/theia/pull/9256/ + rebind(DebugEditorModelFactory).toDynamicValue(({ container }) => (editor => + DebugEditorModel.createModel(container, editor) + )).inSingletonScope(); + // Preferences bindArduinoPreferences(bind); diff --git a/arduino-ide-extension/src/browser/style/debug.css b/arduino-ide-extension/src/browser/style/debug.css new file mode 100644 index 00000000..62f2a07f --- /dev/null +++ b/arduino-ide-extension/src/browser/style/debug.css @@ -0,0 +1,36 @@ +/* TODO: remove after https://github.com/eclipse-theia/theia/pull/9256/ */ + +/* To fix colors in Theia. */ +.theia-debug-hover-title.number, +.theia-debug-console-variable.number { + color: var(--theia-variable-number-variable-color); +} +.theia-debug-hover-title.boolean, +.theia-debug-console-variable.boolean { + color: var(--theia-variable-boolean-variable-color); +} +.theia-debug-hover-title.string, +.theia-debug-console-variable.string { + color: var(--theia-variable-string-variable-color); +} + +/* To unset the default debug hover dimension. */ +.theia-debug-hover { + min-width: unset; + min-height: unset; + width: unset; + height: unset; +} + +/* To adjust the left padding in the hover title. */ +.theia-debug-hover-title { + padding-left: 5px; +} + +/* Use the default Theia dimensions only iff the expression is complex (`!!expression.hasChildren~) */ +.theia-debug-hover.complex-value { + min-width: 324px; + min-height: 324px; + width: 324px; + height: 324px; +} diff --git a/arduino-ide-extension/src/browser/style/index.css b/arduino-ide-extension/src/browser/style/index.css index 0d8fd79c..44dab940 100644 --- a/arduino-ide-extension/src/browser/style/index.css +++ b/arduino-ide-extension/src/browser/style/index.css @@ -7,6 +7,7 @@ @import './terminal.css'; @import './editor.css'; @import './settings-dialog.css'; +@import './debug.css'; .theia-input.warning:focus { outline-width: 1px; diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-editor-model.ts b/arduino-ide-extension/src/browser/theia/debug/debug-editor-model.ts new file mode 100644 index 00000000..ecd0dab5 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/debug/debug-editor-model.ts @@ -0,0 +1,84 @@ +import debounce from 'p-debounce'; +import { inject, injectable, postConstruct, interfaces, Container } from 'inversify'; +import URI from '@theia/core/lib/common/uri'; +import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; +import { MonacoConfigurationService } from '@theia/monaco/lib/browser/monaco-frontend-module'; +import { INLINE_VALUE_DECORATION_KEY } from '@theia/debug/lib/browser/editor//debug-inline-value-decorator'; +import { DebugEditor } from '@theia/debug/lib/browser/editor/debug-editor'; +import { DebugExceptionWidget } from '@theia/debug/lib/browser/editor/debug-exception-widget'; +import { DebugBreakpointWidget } from '@theia/debug/lib/browser/editor/debug-breakpoint-widget'; +import { DebugEditorModel as TheiaDebugEditorModel } from '@theia/debug/lib/browser/editor/debug-editor-model'; +import { createDebugHoverWidgetContainer } from './debug-hover-widget' + +// TODO: Remove after https://github.com/eclipse-theia/theia/pull/9256/ +@injectable() +export class DebugEditorModel extends TheiaDebugEditorModel { + + static createContainer(parent: interfaces.Container, editor: DebugEditor): Container { + const child = createDebugHoverWidgetContainer(parent, editor); + child.bind(DebugEditorModel).toSelf(); + child.bind(DebugBreakpointWidget).toSelf(); + child.bind(DebugExceptionWidget).toSelf(); + return child; + } + + static createModel(parent: interfaces.Container, editor: DebugEditor): DebugEditorModel { + return DebugEditorModel.createContainer(parent, editor).get(DebugEditorModel); + } + + @inject(MonacoConfigurationService) + readonly configurationService: monaco.services.IConfigurationService; + + protected readonly toDisposeOnRenderFrames = new DisposableCollection(); + + @postConstruct() + protected init(): void { + this.toDispose.push(this.toDisposeOnRenderFrames); + super.init(); + } + + protected async updateEditorHover(): Promise { + if (this.isCurrentEditorFrame(this.uri)) { + const codeEditor = this.editor.getControl(); + codeEditor.updateOptions({ hover: { enabled: false } }); + this.toDisposeOnRenderFrames.push(Disposable.create(() => { + const model = codeEditor.getModel()!; + const overrides = { + resource: model.uri, + overrideIdentifier: (model as any).getLanguageIdentifier().language, + }; + const { enabled, delay, sticky } = this.configurationService._configuration.getValue('editor.hover', overrides, undefined); + codeEditor.updateOptions({ + hover: { + enabled, + delay, + sticky + } + }); + })); + } + } + + private isCurrentEditorFrame(uri: URI): boolean { + return this.sessions.currentFrame?.source?.uri.toString() === uri.toString(); + } + + protected readonly renderFrames = debounce(async () => { + if (this.toDispose.disposed) { + return; + } + this.toDisposeOnRenderFrames.dispose(); + + this.toggleExceptionWidget(); + const [newFrameDecorations, inlineValueDecorations] = await Promise.all([ + this.createFrameDecorations(), + this.createInlineValueDecorations() + ]); + const codeEditor = this.editor.getControl(); + codeEditor.removeDecorations(INLINE_VALUE_DECORATION_KEY); + codeEditor.setDecorations(INLINE_VALUE_DECORATION_KEY, inlineValueDecorations); + this.frameDecorations = this.deltaDecorations(this.frameDecorations, newFrameDecorations); + this.updateEditorHover(); + }, 100); + +} diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-hover-source.ts b/arduino-ide-extension/src/browser/theia/debug/debug-hover-source.ts new file mode 100644 index 00000000..cf1fd43a --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/debug/debug-hover-source.ts @@ -0,0 +1,18 @@ +import { injectable } from 'inversify'; +import { ExpressionItem, DebugVariable } from '@theia/debug/lib/browser/console/debug-console-items'; +import { DebugHoverSource as TheiaDebugHoverSource } from '@theia/debug/lib/browser/editor/debug-hover-source'; + +// TODO: remove after https://github.com/eclipse-theia/theia/pull/9256/. +@injectable() +export class DebugHoverSource extends TheiaDebugHoverSource { + + async evaluate2(expression: string): Promise { + const evaluated = await this.doEvaluate(expression); + const elements = evaluated && await evaluated.getElements(); + this._expression = evaluated; + this.elements = elements ? [...elements] : []; + this.fireDidChange(); + return evaluated; + } + +} diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-hover-widget.ts b/arduino-ide-extension/src/browser/theia/debug/debug-hover-widget.ts new file mode 100644 index 00000000..c69ce0a9 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/debug/debug-hover-widget.ts @@ -0,0 +1,96 @@ +import { injectable, interfaces, Container } from 'inversify'; +import { Widget } from '@phosphor/widgets'; +import { SourceTreeWidget } from '@theia/core/lib/browser/source-tree'; +import { DisposableCollection } from '@theia/core/lib/common/disposable'; +import { DebugEditor } from '@theia/debug/lib/browser/editor/debug-editor'; +import { DebugVariable } from '@theia/debug/lib/browser/console/debug-console-items'; +import { DebugExpressionProvider } from '@theia/debug/lib/browser/editor/debug-expression-provider'; +import { DebugHoverSource as TheiaDebugHoverSource } from '@theia/debug/lib/browser/editor/debug-hover-source'; +import { DebugHoverWidget as TheiaDebugHoverWidget, ShowDebugHoverOptions } from '@theia/debug/lib/browser/editor/debug-hover-widget'; +import { DebugHoverSource } from './debug-hover-source'; + +export function createDebugHoverWidgetContainer(parent: interfaces.Container, editor: DebugEditor): Container { + const child = SourceTreeWidget.createContainer(parent, { + virtualized: false + }); + child.bind(DebugEditor).toConstantValue(editor); + child.bind(TheiaDebugHoverSource).toSelf(); + child.bind(DebugHoverSource).toSelf(); + child.rebind(TheiaDebugHoverSource).to(DebugHoverSource); + child.unbind(SourceTreeWidget); + child.bind(DebugExpressionProvider).toSelf(); + child.bind(TheiaDebugHoverWidget).toSelf(); + child.bind(DebugHoverWidget).toSelf(); + child.rebind(TheiaDebugHoverWidget).to(DebugHoverWidget); + return child; +} + +// TODO: remove patch after https://github.com/eclipse-theia/theia/pull/9256/ +@injectable() +export class DebugHoverWidget extends TheiaDebugHoverWidget { + + protected async doShow(options: ShowDebugHoverOptions | undefined = this.options): Promise { + if (!this.isEditorFrame()) { + this.hide(); + return; + } + if (!options) { + this.hide(); + return; + } + if (this.options && this.options.selection.equalsRange(options.selection)) { + return; + } + if (!this.isAttached) { + Widget.attach(this, this.contentNode); + } + + this.options = options; + const matchingExpression = this.expressionProvider.get(this.editor.getControl().getModel()!, options.selection); + if (!matchingExpression) { + this.hide(); + return; + } + const toFocus = new DisposableCollection(); + if (this.options.focus === true) { + toFocus.push(this.model.onNodeRefreshed(() => { + toFocus.dispose(); + this.activate(); + })); + } + const expression = await (this.hoverSource as DebugHoverSource).evaluate2(matchingExpression); + if (!expression || !expression.value) { + toFocus.dispose(); + this.hide(); + return; + } + + this.contentNode.hidden = false; + ['number', 'boolean', 'string'].forEach(token => this.titleNode.classList.remove(token)); + this.domNode.classList.remove('complex-value'); + if (expression.hasElements) { + this.domNode.classList.add('complex-value'); + } else { + this.contentNode.hidden = true; + if (expression.type === 'number' || expression.type === 'boolean' || expression.type === 'string') { + this.titleNode.classList.add(expression.type); + } else if (!isNaN(+expression.value)) { + this.titleNode.classList.add('number'); + } else if (DebugVariable.booleanRegex.test(expression.value)) { + this.titleNode.classList.add('boolean'); + } else if (DebugVariable.stringRegex.test(expression.value)) { + this.titleNode.classList.add('string'); + } + } + + // super.show(); // Here we cannot call `super.show()` but have to call `show` on the `Widget` prototype. + Widget.prototype.show.call(this); + await new Promise(resolve => { + setTimeout(() => window.requestAnimationFrame(() => { + this.editor.getControl().layoutContentWidget(this); + resolve(); + }), 0); + }); + } + +}