Skip to content

Commit ec2bb24

Browse files
committed
vscode: implement proposed.editorHoverVerbosityLevel API
Resolves GH-16397 - add proposed API definitions for editorHoverVerbosityLevel to support verbose hover (e.g. for typescript uses this in most cases now) - update plugin HoverAdapter that provides/releases hover - Remark: increase/decrease level will most likely work then with updated monaco version (GH-16401) Contributed on behalf of STMicroelectronics
1 parent bbfb7e5 commit ec2bb24

File tree

11 files changed

+260
-60
lines changed

11 files changed

+260
-60
lines changed

packages/monaco/src/browser/monaco-editor-content-menu.ts

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -70,42 +70,44 @@ export class MonacoEditorContentMenuContribution implements FrontendApplicationC
7070
);
7171
return ObservableUtils.autorunWithDisposables(({ toDispose }) => {
7272
const menuNodes = menuNodesObservable.get();
73-
const firstMatchObservable = ObservableFromEvent.create(contextKeyService.onDidChangeContext, () => this.withContext(context,
74-
() => menuNodes.find(menuNode => menuNode.isVisible(EDITOR_CONTENT_MENU, this.contextKeyService, undefined, editorWidget))
75-
));
76-
// eslint-disable-next-line @typescript-eslint/no-shadow
77-
toDispose.push(ObservableUtils.autorunWithDisposables(({ toDispose }) => {
78-
const firstMatch = firstMatchObservable.get();
79-
if (firstMatch) {
80-
const button = new MonacoEditorOverlayButton(editor, firstMatch.label);
81-
toDispose.push(button);
82-
toDispose.push(button.onClick(() =>
83-
this.withContext(context, () => firstMatch.run(EDITOR_CONTENT_MENU, editorWidget))
84-
));
73+
if (menuNodes) {
74+
const firstMatchObservable = ObservableFromEvent.create(contextKeyService.onDidChangeContext, () => this.withContext(context,
75+
() => menuNodes.find(menuNode => menuNode.isVisible(EDITOR_CONTENT_MENU, this.contextKeyService, undefined, editorWidget))
76+
));
77+
// eslint-disable-next-line @typescript-eslint/no-shadow
78+
toDispose.push(ObservableUtils.autorunWithDisposables(({ toDispose }) => {
79+
const firstMatch = firstMatchObservable.get();
80+
if (firstMatch) {
81+
const button = new MonacoEditorOverlayButton(editor, firstMatch.label);
82+
toDispose.push(button);
83+
toDispose.push(button.onClick(() =>
84+
this.withContext(context, () => firstMatch.run(EDITOR_CONTENT_MENU, editorWidget))
85+
));
8586

86-
const handlersObservable = ObservableFromEvent.create(this.commands.onCommandsChanged,
87-
() => this.commands.getAllHandlers(firstMatch.id),
88-
{ isEqual: (a, b) => ArrayUtils.equals(a, b) }
89-
);
90-
// eslint-disable-next-line @typescript-eslint/no-shadow
91-
toDispose.push(ObservableUtils.autorunWithDisposables(({ toDispose }) => {
92-
this.withContext(context, () => {
93-
button.enabled = firstMatch.isEnabled(EDITOR_CONTENT_MENU, editorWidget);
94-
const handlers = handlersObservable.get();
95-
for (const handler of handlers) {
96-
const { onDidChangeEnabled } = handler;
97-
if (onDidChangeEnabled) {
98-
// for handlers with declarative enablement such as those originating from `PluginContributionHandler.registerCommand`,
99-
// the onDidChangeEnabled event is context-dependent, so we need to ensure the subscription is made within `withContext`
100-
toDispose.push(onDidChangeEnabled(() => this.withContext(context, () =>
101-
button.enabled = firstMatch.isEnabled(EDITOR_CONTENT_MENU, editorWidget)
102-
)));
87+
const handlersObservable = ObservableFromEvent.create(this.commands.onCommandsChanged,
88+
() => this.commands.getAllHandlers(firstMatch.id),
89+
{ isEqual: (a, b) => ArrayUtils.equals(a, b) }
90+
);
91+
// eslint-disable-next-line @typescript-eslint/no-shadow
92+
toDispose.push(ObservableUtils.autorunWithDisposables(({ toDispose }) => {
93+
this.withContext(context, () => {
94+
button.enabled = firstMatch.isEnabled(EDITOR_CONTENT_MENU, editorWidget);
95+
const handlers = handlersObservable.get();
96+
for (const handler of handlers) {
97+
const { onDidChangeEnabled } = handler;
98+
if (onDidChangeEnabled) {
99+
// for handlers with declarative enablement such as those originating from `PluginContributionHandler.registerCommand`,
100+
// the onDidChangeEnabled event is context-dependent, so we need to ensure the subscription is made within `withContext`
101+
toDispose.push(onDidChangeEnabled(() => this.withContext(context, () =>
102+
button.enabled = firstMatch.isEnabled(EDITOR_CONTENT_MENU, editorWidget)
103+
)));
104+
}
103105
}
104-
}
105-
});
106-
}));
107-
}
108-
}));
106+
});
107+
}));
108+
}
109+
}));
110+
}
109111
});
110112
}
111113

packages/plugin-ext/src/common/plugin-api-rpc-model.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,28 @@ export interface SignatureHelpContext {
261261
export interface Hover {
262262
contents: MarkdownStringDTO[];
263263
range?: Range;
264+
canIncreaseVerbosity?: boolean;
265+
canDecreaseVerbosity?: boolean;
264266
}
265267

266268
export interface HoverProvider {
267269
provideHover(model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken): Hover | undefined | Thenable<Hover | undefined>;
268270
}
269271

272+
export interface HoverContext<THover = Hover> {
273+
verbosityRequest?: HoverVerbosityRequest<THover>;
274+
}
275+
276+
export interface HoverVerbosityRequest<THover = Hover> {
277+
verbosityDelta: number;
278+
previousHover: THover;
279+
}
280+
281+
export enum HoverVerbosityAction {
282+
Increase,
283+
Decrease
284+
}
285+
270286
export interface EvaluatableExpression {
271287
range: Range;
272288
expression?: string;

packages/plugin-ext/src/common/plugin-api-rpc.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ import {
9191
DataTransferDTO,
9292
DocumentDropEditProviderMetadata,
9393
DebugStackFrameDTO,
94-
DebugThreadDTO
94+
DebugThreadDTO,
95+
HoverContext
9596
} from './plugin-api-rpc-model';
9697
import { ExtPluginApi } from './plugin-ext-api-contribution';
9798
import { KeysToAnyValues, KeysToKeysToAnyValue } from './types';
@@ -1681,6 +1682,10 @@ export interface PluginInfo {
16811682
displayName?: string;
16821683
}
16831684

1685+
export interface HoverWithId extends Hover {
1686+
id: number;
1687+
}
1688+
16841689
export interface LanguageStatus {
16851690
readonly id: string;
16861691
readonly name: string;
@@ -1708,7 +1713,8 @@ export interface LanguagesExt {
17081713
handle: number, resource: UriComponents, position: Position, context: SignatureHelpContext, token: CancellationToken
17091714
): Promise<SignatureHelp | undefined>;
17101715
$releaseSignatureHelp(handle: number, id: number): void;
1711-
$provideHover(handle: number, resource: UriComponents, position: Position, token: CancellationToken): Promise<Hover | undefined>;
1716+
$provideHover(handle: number, resource: UriComponents, position: Position, context: HoverContext<{ id: number }> | undefined, token: CancellationToken): Promise<HoverWithId | undefined>;
1717+
$releaseHover(handle: number, id: number): void;
17121718
$provideEvaluatableExpression(handle: number, resource: UriComponents, position: Position, token: CancellationToken): Promise<EvaluatableExpression | undefined>;
17131719
$provideInlineValues(handle: number, resource: UriComponents, range: Range, context: InlineValueContext, token: CancellationToken): Promise<InlineValue[] | undefined>;
17141720
$provideDocumentHighlights(handle: number, resource: UriComponents, position: Position, token: CancellationToken): Promise<DocumentHighlight[] | undefined>;

packages/plugin-ext/src/main/browser/languages-main.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ import {
3636
PluginInfo,
3737
LanguageStatus as LanguageStatusDTO,
3838
InlayHintDto,
39-
IdentifiableInlineCompletions
39+
IdentifiableInlineCompletions,
40+
HoverWithId
4041
} from '../../common/plugin-api-rpc';
4142
import { injectable, inject } from '@theia/core/shared/inversify';
4243
import {
@@ -360,8 +361,14 @@ export class LanguagesMainImpl implements LanguagesMain, Disposable {
360361
}
361362

362363
protected provideHover(handle: number, model: monaco.editor.ITextModel, position: monaco.Position,
363-
token: monaco.CancellationToken): monaco.languages.ProviderResult<monaco.languages.Hover> {
364-
return this.proxy.$provideHover(handle, model.uri, position, token);
364+
token: monaco.CancellationToken, context?: monaco.languages.HoverContext<HoverWithId>): monaco.languages.ProviderResult<monaco.languages.Hover> {
365+
const serializedContext: monaco.languages.HoverContext<{ id: number }> = {
366+
verbosityRequest: context?.verbosityRequest ? {
367+
verbosityDelta: context.verbosityRequest.verbosityDelta,
368+
previousHover: { id: context.verbosityRequest.previousHover.id }
369+
} : undefined,
370+
};
371+
return this.proxy.$provideHover(handle, model.uri, position, serializedContext, token);
365372
}
366373

367374
$registerEvaluatableExpressionProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void {

packages/plugin-ext/src/plugin/languages.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
InlayHintsDto,
2929
InlayHintDto,
3030
IdentifiableInlineCompletions,
31+
HoverWithId,
3132
} from '../common/plugin-api-rpc';
3233
import { RPCProtocol } from '../common/rpc-protocol';
3334
import * as theia from '@theia/plugin';
@@ -42,7 +43,6 @@ import {
4243
Completion,
4344
SerializedDocumentFilter,
4445
SignatureHelp,
45-
Hover,
4646
DocumentHighlight,
4747
Range,
4848
TextEdit,
@@ -71,7 +71,8 @@ import {
7171
TypeHierarchyItem,
7272
InlineCompletionContext,
7373
DocumentDropEdit,
74-
DataTransferDTO
74+
DataTransferDTO,
75+
HoverContext
7576
} from '../common/plugin-api-rpc-model';
7677
import { CompletionAdapter } from './languages/completion';
7778
import { Diagnostics } from './languages/diagnostics';
@@ -412,8 +413,13 @@ export class LanguagesExtImpl implements LanguagesExt {
412413
return this.createDisposable(callId);
413414
}
414415

415-
$provideHover(handle: number, resource: UriComponents, position: Position, token: theia.CancellationToken): Promise<Hover | undefined> {
416-
return this.withAdapter(handle, HoverAdapter, adapter => adapter.provideHover(URI.revive(resource), position, token), undefined);
416+
$provideHover(handle: number, resource: UriComponents, position: Position, context: HoverContext<{ id: number }> | undefined,
417+
token: theia.CancellationToken): Promise<HoverWithId | undefined> {
418+
return this.withAdapter(handle, HoverAdapter, adapter => adapter.provideHover(URI.revive(resource), position, context, token), undefined);
419+
}
420+
421+
$releaseHover(handle: number, id: number): void {
422+
this.withAdapter(handle, HoverAdapter, adapter => Promise.resolve(adapter.releaseHover(id)), undefined);
417423
}
418424
// ### Hover Provider end
419425

packages/plugin-ext/src/plugin/languages/hover.ts

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,30 @@
1818
import { URI } from '@theia/core/shared/vscode-uri';
1919
import * as theia from '@theia/plugin';
2020
import { DocumentsExtImpl } from '../documents';
21-
import { Hover } from '../../common/plugin-api-rpc-model';
21+
import { Hover, HoverContext } from '../../common/plugin-api-rpc-model';
2222
import * as Converter from '../type-converters';
2323
import { Range } from '../types-impl';
24-
import { Position } from '../../common/plugin-api-rpc';
24+
import { HoverWithId, Position } from '../../common/plugin-api-rpc';
2525

2626
export class HoverAdapter {
2727

28+
private _hoverCounter: number = 0;
29+
private _hoverMap: Map<number, theia.Hover> = new Map<number, theia.Hover>();
30+
31+
private static HOVER_MAP_MAX_SIZE = 10;
32+
2833
constructor(
2934
private readonly provider: theia.HoverProvider,
3035
private readonly documents: DocumentsExtImpl
3136
) { }
3237

33-
public provideHover(resource: URI, position: Position, token: theia.CancellationToken): Promise<Hover | undefined> {
38+
async provideHover(
39+
resource: URI,
40+
position: Position,
41+
context: HoverContext<{ id: number }> | undefined,
42+
token: theia.CancellationToken
43+
): Promise<HoverWithId | undefined> {
44+
3445
const document = this.documents.getDocumentData(resource);
3546
if (!document) {
3647
return Promise.reject(new Error(`There are no document for ${resource}`));
@@ -39,20 +50,48 @@ export class HoverAdapter {
3950
const doc = document.document;
4051
const pos = Converter.toPosition(position);
4152

42-
return Promise.resolve(this.provider.provideHover(doc, pos, token)).then(value => {
43-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
44-
if (!value || !Array.isArray(value.contents) || (value.contents as Array<any>).length === 0) {
45-
return undefined;
46-
}
47-
if (!value.range) {
48-
value.range = doc.getWordRangeAtPosition(pos);
49-
}
50-
if (!value.range) {
51-
value.range = new Range(pos, pos);
53+
let value: theia.Hover | null | undefined;
54+
if (context && context.verbosityRequest) {
55+
const previousHoverId = context.verbosityRequest.previousHover.id;
56+
const previousHover = this._hoverMap.get(previousHoverId);
57+
if (!previousHover) {
58+
throw new Error(`Hover with id ${previousHoverId} not found`);
5259
}
60+
const hoverContext: theia.HoverContext = { verbosityDelta: context.verbosityRequest.verbosityDelta, previousHover };
61+
value = await this.provider.provideHover(doc, pos, token, hoverContext);
62+
} else {
63+
value = await this.provider.provideHover(doc, pos, token);
64+
}
65+
66+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
67+
if (!value || !Array.isArray(value.contents) || (value.contents as Array<any>).length === 0) {
68+
return undefined;
69+
}
70+
if (!value.range) {
71+
value.range = doc.getWordRangeAtPosition(pos);
72+
}
73+
if (!value.range) {
74+
value.range = new Range(pos, pos);
75+
}
76+
77+
const convertedHover: Hover = Converter.fromHover(value);
78+
const id = this._hoverCounter;
79+
// Check if hover map has more than 10 elements and if yes, remove oldest from the map
80+
if (this._hoverMap.size === HoverAdapter.HOVER_MAP_MAX_SIZE) {
81+
const minimumId = Math.min(...this._hoverMap.keys());
82+
this._hoverMap.delete(minimumId);
83+
}
84+
this._hoverMap.set(id, value);
85+
this._hoverCounter += 1;
86+
const hover: HoverWithId = {
87+
...convertedHover,
88+
id
89+
};
90+
return hover;
91+
}
5392

54-
return Converter.fromHover(value);
55-
});
93+
releaseHover(id: number): void {
94+
this._hoverMap.delete(id);
5695
}
5796

5897
}

packages/plugin-ext/src/plugin/plugin-context.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ import {
8080
SignatureHelp,
8181
SignatureHelpTriggerKind,
8282
Hover,
83+
VerboseHover,
84+
HoverVerbosityAction,
8385
EvaluatableExpression,
8486
InlineValueEvaluatableExpression,
8587
InlineValueText,
@@ -1461,6 +1463,8 @@ export function createAPIFactory(
14611463
SignatureHelp,
14621464
SignatureHelpTriggerKind,
14631465
Hover,
1466+
VerboseHover,
1467+
HoverVerbosityAction,
14641468
EvaluatableExpression,
14651469
InlineValueEvaluatableExpression,
14661470
InlineValueText,

packages/plugin-ext/src/plugin/type-converters.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,10 +424,12 @@ function convertTags(tags: types.DiagnosticTag[] | undefined): types.MarkerTag[]
424424
return markerTags;
425425
}
426426

427-
export function fromHover(hover: theia.Hover): model.Hover {
427+
export function fromHover(hover: theia.VerboseHover): model.Hover {
428428
return <model.Hover>{
429429
range: fromRange(hover.range),
430-
contents: fromManyMarkdown(hover.contents)
430+
contents: fromManyMarkdown(hover.contents),
431+
canIncreaseVerbosity: hover.canIncreaseVerbosity,
432+
canDecreaseVerbosity: hover.canDecreaseVerbosity,
431433
};
432434
}
433435

packages/plugin-ext/src/plugin/types-impl.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,6 +1491,29 @@ export class Hover {
14911491
}
14921492
}
14931493

1494+
@es5ClassCompat
1495+
export class VerboseHover extends Hover {
1496+
1497+
public canIncreaseVerbosity: boolean | undefined;
1498+
public canDecreaseVerbosity: boolean | undefined;
1499+
1500+
constructor(
1501+
contents: theia.MarkdownString | theia.MarkedString | (theia.MarkdownString | theia.MarkedString)[],
1502+
range?: Range,
1503+
canIncreaseVerbosity?: boolean,
1504+
canDecreaseVerbosity?: boolean,
1505+
) {
1506+
super(contents, range);
1507+
this.canIncreaseVerbosity = canIncreaseVerbosity;
1508+
this.canDecreaseVerbosity = canDecreaseVerbosity;
1509+
}
1510+
}
1511+
1512+
export enum HoverVerbosityAction {
1513+
Increase = 0,
1514+
Decrease = 1
1515+
}
1516+
14941517
@es5ClassCompat
14951518
export class EvaluatableExpression {
14961519

packages/plugin/src/theia.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import './theia.proposed.customEditorMove';
2727
import './theia.proposed.debugVisualization';
2828
import './theia.proposed.diffCommand';
2929
import './theia.proposed.editSessionIdentityProvider';
30+
import './theia.proposed.editorHoverVerbosityLevel';
3031
import './theia.proposed.extensionsAny';
3132
import './theia.proposed.externalUriOpener';
3233
import './theia.proposed.findTextInFiles';

0 commit comments

Comments
 (0)