Skip to content

Commit e9481f1

Browse files
Support resolveCompletionItem for Jedi docstrings in data science (microsoft#9718)
1 parent 0a52490 commit e9481f1

File tree

9 files changed

+204
-12
lines changed

9 files changed

+204
-12
lines changed

news/2 Fixes/8706.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support resolveCompletionItem so that we can get Jedi docstrings in Notebook Editor and Interactive Window.

src/client/activation/jedi.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,13 @@ export class JediExtensionActivator implements ILanguageServerActivator {
168168
return this.completionProvider.provideCompletionItems(document, position, token);
169169
}
170170
}
171+
172+
public resolveCompletionItem(item: CompletionItem, token: CancellationToken): ProviderResult<CompletionItem> {
173+
if (this.completionProvider) {
174+
return this.completionProvider.resolveCompletionItem(item, token);
175+
}
176+
}
177+
171178
public get onDidChangeCodeLenses(): Event<void> | undefined {
172179
return this.codeLensProvider ? this.codeLensProvider.onDidChangeCodeLenses : undefined;
173180
}

src/client/activation/refCountedLanguageServer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ export class RefCountedLanguageServer implements ILanguageServerActivator {
8989
): ProviderResult<CompletionItem[] | CompletionList> {
9090
return this.impl.provideCompletionItems(document, position, token, context);
9191
}
92+
public resolveCompletionItem(item: CompletionItem, token: CancellationToken): ProviderResult<CompletionItem> {
93+
if (this.impl.resolveCompletionItem) {
94+
return this.impl.resolveCompletionItem(item, token);
95+
}
96+
}
9297
public provideCodeLenses(document: TextDocument, token: CancellationToken): ProviderResult<CodeLens[]> {
9398
return this.impl.provideCodeLenses(document, token);
9499
}

src/client/datascience/interactive-common/intellisense/conversion.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,35 @@ const mapCompletionItemKind: Map<number, number> = new Map<number, number>([
6868
[vscode.CompletionItemKind.TypeParameter, monacoCompletionItemKind.TypeParameter] // TypeParameter
6969
]);
7070

71+
// Left side is the monaco value.
72+
const reverseMapCompletionItemKind: Map<number, vscode.CompletionItemKind> = new Map<number, vscode.CompletionItemKind>([
73+
[monacoCompletionItemKind.Text, vscode.CompletionItemKind.Text], // Text
74+
[monacoCompletionItemKind.Method, vscode.CompletionItemKind.Method], // Method
75+
[monacoCompletionItemKind.Function, vscode.CompletionItemKind.Function], // Function
76+
[monacoCompletionItemKind.Constructor, vscode.CompletionItemKind.Constructor], // Constructor
77+
[monacoCompletionItemKind.Field, vscode.CompletionItemKind.Field], // Field
78+
[monacoCompletionItemKind.Variable, vscode.CompletionItemKind.Variable], // Variable
79+
[monacoCompletionItemKind.Class, vscode.CompletionItemKind.Class], // Class
80+
[monacoCompletionItemKind.Interface, vscode.CompletionItemKind.Interface], // Interface
81+
[monacoCompletionItemKind.Module, vscode.CompletionItemKind.Module], // Module
82+
[monacoCompletionItemKind.Property, vscode.CompletionItemKind.Property], // Property
83+
[monacoCompletionItemKind.Unit, vscode.CompletionItemKind.Unit], // Unit
84+
[monacoCompletionItemKind.Value, vscode.CompletionItemKind.Value], // Value
85+
[monacoCompletionItemKind.Enum, vscode.CompletionItemKind.Enum], // Enum
86+
[monacoCompletionItemKind.Keyword, vscode.CompletionItemKind.Keyword], // Keyword
87+
[monacoCompletionItemKind.Snippet, vscode.CompletionItemKind.Snippet], // Snippet
88+
[monacoCompletionItemKind.Color, vscode.CompletionItemKind.Color], // Color
89+
[monacoCompletionItemKind.File, vscode.CompletionItemKind.File], // File
90+
[monacoCompletionItemKind.Reference, vscode.CompletionItemKind.Reference], // Reference
91+
[monacoCompletionItemKind.Folder, vscode.CompletionItemKind.Folder], // Folder
92+
[monacoCompletionItemKind.EnumMember, vscode.CompletionItemKind.EnumMember], // EnumMember
93+
[monacoCompletionItemKind.Constant, vscode.CompletionItemKind.Constant], // Constant
94+
[monacoCompletionItemKind.Struct, vscode.CompletionItemKind.Struct], // Struct
95+
[monacoCompletionItemKind.Event, vscode.CompletionItemKind.Event], // Event
96+
[monacoCompletionItemKind.Operator, vscode.CompletionItemKind.Operator], // Operator
97+
[monacoCompletionItemKind.TypeParameter, vscode.CompletionItemKind.TypeParameter] // TypeParameter
98+
]);
99+
71100
const mapJupyterKind: Map<string, number> = new Map<string, number>([
72101
['method', monacoCompletionItemKind.Method],
73102
['function', monacoCompletionItemKind.Function],
@@ -109,6 +138,12 @@ function convertToMonacoRange(range: vscodeLanguageClient.Range | undefined): mo
109138
}
110139
}
111140

141+
function convertToVSCodeRange(range: monacoEditor.IRange | undefined): vscode.Range | undefined {
142+
if (range) {
143+
return new vscode.Range(new vscode.Position(range.startLineNumber - 1, range.startColumn - 1), new vscode.Position(range.endLineNumber - 1, range.endColumn - 1));
144+
}
145+
}
146+
112147
// Something very fishy. If the monacoEditor.languages.CompletionItemKind is included here, we get this error on startup
113148
// Activating extension `ms-python.python` failed: Unexpected token {
114149
// extensionHostProcess.js:457
@@ -123,9 +158,17 @@ function convertToMonacoCompletionItemKind(kind?: number): number {
123158
return monacoCompletionItemKind.Property;
124159
}
125160

161+
function convertToVSCodeCompletionItemKind(kind?: number): vscode.CompletionItemKind {
162+
const value = kind ? reverseMapCompletionItemKind.get(kind) : vscode.CompletionItemKind.Property;
163+
if (value) {
164+
return value;
165+
}
166+
return vscode.CompletionItemKind.Property;
167+
}
168+
126169
const SnippetEscape = 4;
127170

128-
function convertToMonacoCompletionItem(item: vscodeLanguageClient.CompletionItem, requiresKindConversion: boolean): monacoEditor.languages.CompletionItem {
171+
export function convertToMonacoCompletionItem(item: vscodeLanguageClient.CompletionItem, requiresKindConversion: boolean): monacoEditor.languages.CompletionItem {
129172
// They should be pretty much identical? Except for ranges.
130173
// tslint:disable-next-line: no-object-literal-type-assertion no-any
131174
const result = ({ ...item } as any) as monacoEditor.languages.CompletionItem;
@@ -156,6 +199,21 @@ function convertToMonacoCompletionItem(item: vscodeLanguageClient.CompletionItem
156199
return result;
157200
}
158201

202+
export function convertToVSCodeCompletionItem(item: monacoEditor.languages.CompletionItem): vscode.CompletionItem {
203+
// tslint:disable-next-line: no-object-literal-type-assertion no-any
204+
const result = ({ ...item } as any) as vscode.CompletionItem;
205+
206+
if (item.kind && result.kind) {
207+
result.kind = convertToVSCodeCompletionItemKind(item.kind);
208+
}
209+
210+
if (item.range && result.range) {
211+
result.range = convertToVSCodeRange(item.range);
212+
}
213+
214+
return result;
215+
}
216+
159217
export function convertToMonacoCompletionList(
160218
result: vscodeLanguageClient.CompletionList | vscodeLanguageClient.CompletionItem[] | vscode.CompletionItem[] | vscode.CompletionList | null,
161219
requiresKindConversion: boolean

src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { inject, injectable } from 'inversify';
77
import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api';
88
import * as path from 'path';
99
import * as uuid from 'uuid/v4';
10-
import { CancellationToken, CancellationTokenSource, Event, EventEmitter, SignatureHelpContext, TextDocumentContentChangeEvent, Uri } from 'vscode';
11-
10+
import { CancellationToken, CancellationTokenSource, CompletionItem, Event, EventEmitter, SignatureHelpContext, TextDocumentContentChangeEvent, Uri } from 'vscode';
11+
import * as vscodeLanguageClient from 'vscode-languageclient';
1212
import { concatMultilineStringInput } from '../../../../datascience-ui/common';
1313
import { ILanguageServer, ILanguageServerCache } from '../../../activation/types';
1414
import { IWorkspaceService } from '../../../common/application/types';
@@ -34,9 +34,17 @@ import {
3434
IProvideHoverRequest,
3535
IProvideSignatureHelpRequest,
3636
IRemoveCell,
37+
IResolveCompletionItemRequest,
3738
ISwapCells
3839
} from '../interactiveWindowTypes';
39-
import { convertStringsToSuggestions, convertToMonacoCompletionList, convertToMonacoHover, convertToMonacoSignatureHelp } from './conversion';
40+
import {
41+
convertStringsToSuggestions,
42+
convertToMonacoCompletionItem,
43+
convertToMonacoCompletionList,
44+
convertToMonacoHover,
45+
convertToMonacoSignatureHelp,
46+
convertToVSCodeCompletionItem
47+
} from './conversion';
4048
import { IntellisenseDocument } from './intellisenseDocument';
4149

4250
// tslint:disable:no-any
@@ -95,6 +103,10 @@ export class IntellisenseProvider implements IInteractiveWindowListener {
95103
this.dispatchMessage(message, payload, this.handleSignatureHelpRequest);
96104
break;
97105

106+
case InteractiveWindowMessages.ResolveCompletionItemRequest:
107+
this.dispatchMessage(message, payload, this.handleResolveCompletionItemRequest);
108+
break;
109+
98110
case InteractiveWindowMessages.EditCell:
99111
this.dispatchMessage(message, payload, this.editCell);
100112
break;
@@ -211,8 +223,7 @@ export class IntellisenseProvider implements IInteractiveWindowListener {
211223
cellId: string,
212224
token: CancellationToken
213225
): Promise<monacoEditor.languages.CompletionList> {
214-
const languageServer = await this.getLanguageServer();
215-
const document = await this.getDocument();
226+
const [languageServer, document] = await Promise.all([this.getLanguageServer(), this.getDocument()]);
216227
if (languageServer && document) {
217228
const docPos = document.convertToDocumentPosition(cellId, position.lineNumber, position.column);
218229
const result = await languageServer.provideCompletionItems(document, docPos, token, context);
@@ -227,8 +238,7 @@ export class IntellisenseProvider implements IInteractiveWindowListener {
227238
};
228239
}
229240
protected async provideHover(position: monacoEditor.Position, cellId: string, token: CancellationToken): Promise<monacoEditor.languages.Hover> {
230-
const languageServer = await this.getLanguageServer();
231-
const document = await this.getDocument();
241+
const [languageServer, document] = await Promise.all([this.getLanguageServer(), this.getDocument()]);
232242
if (languageServer && document) {
233243
const docPos = document.convertToDocumentPosition(cellId, position.lineNumber, position.column);
234244
const result = await languageServer.provideHover(document, docPos, token);
@@ -247,8 +257,7 @@ export class IntellisenseProvider implements IInteractiveWindowListener {
247257
cellId: string,
248258
token: CancellationToken
249259
): Promise<monacoEditor.languages.SignatureHelp> {
250-
const languageServer = await this.getLanguageServer();
251-
const document = await this.getDocument();
260+
const [languageServer, document] = await Promise.all([this.getLanguageServer(), this.getDocument()]);
252261
if (languageServer && document) {
253262
const docPos = document.convertToDocumentPosition(cellId, position.lineNumber, position.column);
254263
const result = await languageServer.provideSignatureHelp(document, docPos, token, context as SignatureHelpContext);
@@ -264,6 +273,31 @@ export class IntellisenseProvider implements IInteractiveWindowListener {
264273
};
265274
}
266275

276+
protected async resolveCompletionItem(
277+
position: monacoEditor.Position,
278+
item: monacoEditor.languages.CompletionItem,
279+
cellId: string,
280+
token: CancellationToken
281+
): Promise<monacoEditor.languages.CompletionItem> {
282+
const [languageServer, document] = await Promise.all([this.getLanguageServer(), this.getDocument()]);
283+
if (languageServer && languageServer.resolveCompletionItem && document) {
284+
const vscodeCompItem: CompletionItem = convertToVSCodeCompletionItem(item);
285+
286+
// Needed by Jedi in completionSource.ts to resolve the item
287+
const docPos = document.convertToDocumentPosition(cellId, position.lineNumber, position.column);
288+
(vscodeCompItem as any)._documentPosition = { document, position: docPos };
289+
290+
const result = await languageServer.resolveCompletionItem(vscodeCompItem, token);
291+
if (result) {
292+
// Convert expects vclc completion item, but takes both vclc and vscode items so just cast here
293+
return convertToMonacoCompletionItem(result as vscodeLanguageClient.CompletionItem, true);
294+
}
295+
}
296+
297+
// If we can't fill in the extra info, just return the item
298+
return item;
299+
}
300+
267301
protected async handleChanges(document: IntellisenseDocument, changes: TextDocumentContentChangeEvent[]): Promise<void> {
268302
// For the dot net language server, we have to send extra data to the language server
269303
if (document) {
@@ -325,6 +359,25 @@ export class IntellisenseProvider implements IInteractiveWindowListener {
325359
);
326360
}
327361

362+
private handleResolveCompletionItemRequest(request: IResolveCompletionItemRequest) {
363+
// Create a cancellation source. We'll use this for our sub class request and a jupyter one
364+
const cancelSource = new CancellationTokenSource();
365+
this.cancellationSources.set(request.requestId, cancelSource);
366+
367+
// Combine all of the results together.
368+
this.postTimedResponse(
369+
[this.resolveCompletionItem(request.position, request.item, request.cellId, cancelSource.token)],
370+
InteractiveWindowMessages.ResolveCompletionItemResponse,
371+
c => {
372+
if (c && c[0]) {
373+
return { item: c[0], requestId: request.requestId };
374+
} else {
375+
return { item: request.item, requestId: request.requestId };
376+
}
377+
}
378+
);
379+
}
380+
328381
private handleHoverRequest(request: IProvideHoverRequest) {
329382
const cancelSource = new CancellationTokenSource();
330383
this.cancellationSources.set(request.requestId, cancelSource);

src/client/datascience/interactive-common/interactiveWindowTypes.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ export enum InteractiveWindowMessages {
5050
ProvideSignatureHelpRequest = 'provide_signature_help_request',
5151
CancelSignatureHelpRequest = 'cancel_signature_help_request',
5252
ProvideSignatureHelpResponse = 'provide_signature_help_response',
53+
ResolveCompletionItemRequest = 'resolve_completion_item_request',
54+
CancelResolveCompletionItemRequest = 'cancel_resolve_completion_item_request',
55+
ResolveCompletionItemResponse = 'resolve_completion_item_response',
5356
AddCell = 'add_cell',
5457
EditCell = 'edit_cell',
5558
RemoveCell = 'remove_cell',
@@ -192,6 +195,13 @@ export interface ICancelIntellisenseRequest {
192195
requestId: string;
193196
}
194197

198+
export interface IResolveCompletionItemRequest {
199+
position: monacoEditor.Position;
200+
item: monacoEditor.languages.CompletionItem;
201+
requestId: string;
202+
cellId: string;
203+
}
204+
195205
export interface IProvideCompletionItemsResponse {
196206
list: monacoEditor.languages.CompletionList;
197207
requestId: string;
@@ -207,6 +217,11 @@ export interface IProvideSignatureHelpResponse {
207217
requestId: string;
208218
}
209219

220+
export interface IResolveCompletionItemResponse {
221+
item: monacoEditor.languages.CompletionItem;
222+
requestId: string;
223+
}
224+
210225
export interface IPosition {
211226
line: number;
212227
ch: number;
@@ -325,6 +340,9 @@ export class IInteractiveWindowMapping {
325340
public [InteractiveWindowMessages.ProvideSignatureHelpRequest]: IProvideSignatureHelpRequest;
326341
public [InteractiveWindowMessages.CancelSignatureHelpRequest]: ICancelIntellisenseRequest;
327342
public [InteractiveWindowMessages.ProvideSignatureHelpResponse]: IProvideSignatureHelpResponse;
343+
public [InteractiveWindowMessages.ResolveCompletionItemRequest]: IResolveCompletionItemRequest;
344+
public [InteractiveWindowMessages.CancelResolveCompletionItemRequest]: ICancelIntellisenseRequest;
345+
public [InteractiveWindowMessages.ResolveCompletionItemResponse]: IResolveCompletionItemResponse;
328346
public [InteractiveWindowMessages.AddCell]: IAddCell;
329347
public [InteractiveWindowMessages.EditCell]: IEditCell;
330348
public [InteractiveWindowMessages.RemoveCell]: IRemoveCell;

src/datascience-ui/interactive-common/intellisenseProvider.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
InteractiveWindowMessages,
1414
IProvideCompletionItemsResponse,
1515
IProvideHoverResponse,
16-
IProvideSignatureHelpResponse
16+
IProvideSignatureHelpResponse,
17+
IResolveCompletionItemResponse
1718
} from '../../client/datascience/interactive-common/interactiveWindowTypes';
1819

1920
interface IRequestData<T> {
@@ -27,6 +28,7 @@ export class IntellisenseProvider
2728
public readonly signatureHelpTriggerCharacters?: ReadonlyArray<string> = ['(', ',', '<'];
2829
public readonly signatureHelpRetriggerCharacters?: ReadonlyArray<string> = [')'];
2930
private completionRequests: Map<string, IRequestData<monacoEditor.languages.CompletionList>> = new Map<string, IRequestData<monacoEditor.languages.CompletionList>>();
31+
private resolveCompletionRequests: Map<string, IRequestData<monacoEditor.languages.CompletionItem>> = new Map<string, IRequestData<monacoEditor.languages.CompletionItem>>();
3032
private hoverRequests: Map<string, IRequestData<monacoEditor.languages.Hover>> = new Map<string, IRequestData<monacoEditor.languages.Hover>>();
3133
private signatureHelpRequests: Map<string, IRequestData<monacoEditor.languages.SignatureHelpResult>> = new Map<
3234
string,
@@ -64,6 +66,32 @@ export class IntellisenseProvider
6466
return promise.promise;
6567
}
6668

69+
public resolveCompletionItem(
70+
model: monacoEditor.editor.ITextModel,
71+
position: monacoEditor.Position,
72+
item: monacoEditor.languages.CompletionItem,
73+
token: monacoEditor.CancellationToken
74+
): monacoEditor.languages.ProviderResult<monacoEditor.languages.CompletionItem> {
75+
// If the item has already resolved documentation (as with MS LS) we don't need to do this
76+
if (!item.documentation) {
77+
// Emit a new request
78+
const requestId = uuid();
79+
const promise = createDeferred<monacoEditor.languages.CompletionItem>();
80+
81+
const cancelDisposable = token.onCancellationRequested(() => {
82+
promise.resolve();
83+
this.sendMessage(InteractiveWindowMessages.CancelResolveCompletionItemRequest, { requestId });
84+
});
85+
86+
this.resolveCompletionRequests.set(requestId, { promise, cancelDisposable });
87+
this.sendMessage(InteractiveWindowMessages.ResolveCompletionItemRequest, { position, item, requestId, cellId: this.getCellId(model.id) });
88+
89+
return promise.promise;
90+
} else {
91+
return Promise.resolve(item);
92+
}
93+
}
94+
6795
public provideHover(
6896
model: monacoEditor.editor.ITextModel,
6997
position: monacoEditor.Position,
@@ -109,6 +137,7 @@ export class IntellisenseProvider
109137
this.disposed = true;
110138
this.registerDisposables.forEach(r => r.dispose());
111139
this.completionRequests.forEach(r => r.promise.resolve());
140+
this.resolveCompletionRequests.forEach(r => r.promise.resolve());
112141
this.hoverRequests.forEach(r => r.promise.resolve());
113142

114143
this.registerDisposables = [];
@@ -154,6 +183,15 @@ export class IntellisenseProvider
154183
}
155184
}
156185

186+
public handleResolveCompletionItemResponse(response: IResolveCompletionItemResponse) {
187+
// Resolve our waiting promise if we have one
188+
const waiting = this.resolveCompletionRequests.get(response.requestId);
189+
if (waiting) {
190+
waiting.promise.resolve(response.item);
191+
this.completionRequests.delete(response.requestId);
192+
}
193+
}
194+
157195
private getCellId(monacoId: string): string {
158196
const result = this.monacoIdToCellId.get(monacoId);
159197
if (result) {

src/datascience-ui/interactive-common/redux/postOffice.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ export enum IncomingMessageActions {
5555
PROVIDESIGNATUREHELPREQUEST = 'action.provide_signature_help_request',
5656
CANCELSIGNATUREHELPREQUEST = 'action.cancel_signature_help_request',
5757
PROVIDESIGNATUREHELPRESPONSE = 'action.provide_signature_help_response',
58+
RESOLVECOMPLETIONITEMREQUEST = 'action.resolve_completion_item_request',
59+
CANCELRESOLVECOMPLETIONITEMREQUEST = 'action.cancel_completion_items_request',
60+
RESOLVECOMPLETIONITEMRESPONSE = 'action.resolve_completion_item_response',
5861
ADDCELL = 'action.add_cell',
5962
EDITCELL = 'action.edit_cell',
6063
REMOVECELL = 'action.remove_cell',

0 commit comments

Comments
 (0)