ATL-988: Aligned the hover size to the expression.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
Akos Kitta
2021-03-31 13:37:37 +02:00
committed by Akos Kitta
parent 8a692d0ce5
commit 80bddc238d
6 changed files with 242 additions and 0 deletions

View File

@@ -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<void> {
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);
}

View File

@@ -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<ExpressionItem | DebugVariable | undefined> {
const evaluated = await this.doEvaluate(expression);
const elements = evaluated && await evaluated.getElements();
this._expression = evaluated;
this.elements = elements ? [...elements] : [];
this.fireDidChange();
return evaluated;
}
}

View File

@@ -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<void> {
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<void>(resolve => {
setTimeout(() => window.requestAnimationFrame(() => {
this.editor.getControl().layoutContentWidget(this);
resolve();
}), 0);
});
}
}