Skip to content

Commit 0a04a77

Browse files
committed
Implement completionItem/resolve and icon substitution
1 parent 9dde59c commit 0a04a77

File tree

8 files changed

+383
-40
lines changed

8 files changed

+383
-40
lines changed

packages/completion-theme/src/index.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,24 @@ import {
1212
ILSPCompletionThemeManager,
1313
PLUGIN_ID,
1414
COMPLETER_THEME_PREFIX,
15-
KernelKind
15+
KernelKind,
16+
CompletionItemKindStrings
1617
} from './types';
1718
import { render_themes_list } from './about';
1819
import '../style/index.css';
20+
import { ILSPLogConsole } from '@krassowski/jupyterlab-lsp';
1921

2022
export class CompletionThemeManager implements ILSPCompletionThemeManager {
2123
protected current_icons: Map<string, LabIcon>;
2224
protected themes: Map<string, ICompletionTheme>;
2325
private current_theme_id: string;
2426
private icons_cache: Map<string, LabIcon>;
27+
private icon_overrides: Record<string, CompletionItemKindStrings>;
2528

2629
constructor(protected themeManager: IThemeManager) {
2730
this.themes = new Map();
2831
this.icons_cache = new Map();
32+
this.icon_overrides = {};
2933
themeManager.themeChanged.connect(this.update_icons_set, this);
3034
}
3135

@@ -78,12 +82,16 @@ export class CompletionThemeManager implements ILSPCompletionThemeManager {
7882
}
7983
let options = this.current_theme.icons.options || {};
8084
if (type) {
85+
if (type in this.icon_overrides) {
86+
type = this.icon_overrides[type];
87+
}
8188
type =
8289
type.substring(0, 1).toUpperCase() + type.substring(1).toLowerCase();
8390
}
8491
if (this.current_icons.has(type)) {
8592
return this.current_icons.get(type).bindprops(options);
8693
}
94+
8795
if (type === KernelKind) {
8896
return kernelIcon;
8997
}
@@ -144,13 +152,19 @@ export class CompletionThemeManager implements ILSPCompletionThemeManager {
144152
buttons: [Dialog.okButton()]
145153
}).catch(console.warn);
146154
}
155+
156+
set_icons_overrides(
157+
iconOverrides: Record<string, CompletionItemKindStrings>
158+
) {
159+
this.icon_overrides = iconOverrides;
160+
}
147161
}
148162

149163
const LSP_CATEGORY = 'Language server protocol';
150164

151165
export const COMPLETION_THEME_MANAGER: JupyterFrontEndPlugin<ILSPCompletionThemeManager> = {
152166
id: PLUGIN_ID,
153-
requires: [IThemeManager, ICommandPalette],
167+
requires: [IThemeManager, ICommandPalette, ILSPLogConsole],
154168
activate: (
155169
app,
156170
themeManager: IThemeManager,

packages/completion-theme/src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ export interface ILSPCompletionThemeManager {
116116
get_iconset(
117117
theme: ICompletionTheme
118118
): Map<keyof ICompletionIconSet, LabIcon.ILabIcon>;
119+
120+
set_icons_overrides(
121+
map: Record<string, CompletionItemKindStrings | 'Kernel'>
122+
): void;
119123
}
120124

121125
export const ILSPCompletionThemeManager = new Token<ILSPCompletionThemeManager>(

packages/jupyterlab-lsp/schema/completion.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,54 @@
3131
"type": ["string", "null"],
3232
"default": "vscode",
3333
"description": "The identifier of a completer theme with icons which indicate the kind of completion. Set to null to disable icons."
34+
},
35+
"typesMap": {
36+
"title": "Mapping of custom kernel types to valid completion kind names",
37+
"description": "Mapping used for icon selection. Accepted values are the names of CompletionItemKind and 'Kernel' literal. The defaults aim to provide good initial experience for Julia, Python and R kernels.",
38+
"type": "object",
39+
"default": {
40+
"<unknown>": "Kernel",
41+
"instance": "Value",
42+
"path": "File",
43+
"param": "Variable",
44+
"missing": "Constant",
45+
"nothing": "Constant",
46+
"undefinitializer": "Constant",
47+
"base.devnull": "Constant"
48+
},
49+
"patternProperties": {
50+
"^.*$": {
51+
"type": "string",
52+
"enum": [
53+
"Kernel",
54+
"Text",
55+
"Method",
56+
"Function",
57+
"Constructor",
58+
"Field",
59+
"Variable",
60+
"Class",
61+
"Interface",
62+
"Module",
63+
"Property",
64+
"Unit",
65+
"Value",
66+
"Enum",
67+
"Keyword",
68+
"Snippet",
69+
"Color",
70+
"File",
71+
"Reference",
72+
"Folder",
73+
"EnumMember",
74+
"Constant",
75+
"Struct",
76+
"Event",
77+
"Operator",
78+
"TypeParameter"
79+
]
80+
}
81+
}
3482
}
3583
},
3684
"jupyter.lab.shortcuts": []

packages/jupyterlab-lsp/src/connection.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
// Introduced modifications are BSD licenced, copyright JupyterLab development team.
55
import * as lsProtocol from 'vscode-languageserver-protocol';
66
import {
7+
IDocumentInfo,
78
ILspOptions,
89
IPosition,
9-
LspWsConnection,
10-
IDocumentInfo
10+
LspWsConnection
1111
} from 'lsp-ws-connection';
1212
import { until_ready } from './utils';
1313

@@ -138,4 +138,21 @@ export class LSPConnection extends LspWsConnection {
138138
);
139139
documentInfo.version++;
140140
}
141+
142+
async getCompletionResolve(completionItem: lsProtocol.CompletionItem) {
143+
if (!this.isReady || !this.isCompletionResolveProvider()) {
144+
return;
145+
}
146+
return this.connection.sendRequest<lsProtocol.CompletionItem>(
147+
'completionItem/resolve',
148+
completionItem
149+
);
150+
}
151+
152+
/**
153+
* Does support completionItem/resolve?.
154+
*/
155+
public isCompletionResolveProvider(): boolean {
156+
return this.serverCapabilities?.completionProvider?.resolveProvider;
157+
}
141158
}

packages/jupyterlab-lsp/src/features/completion/completion.ts

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import * as CodeMirror from 'codemirror';
77
import { CodeMirrorIntegration } from '../../editor_integration/codemirror';
88
import { JupyterFrontEnd } from '@jupyterlab/application';
99
import { IEditorChangedData, WidgetAdapter } from '../../adapters/adapter';
10-
import { LSPConnector } from './completion_handler';
11-
import { ICompletionManager } from '@jupyterlab/completer';
10+
import { LazyCompletionItem, LSPConnector } from './completion_handler';
11+
import { CompletionHandler, ICompletionManager } from '@jupyterlab/completer';
1212
import { CodeEditor } from '@jupyterlab/codeeditor';
1313
import { IDocumentWidget } from '@jupyterlab/docregistry';
1414
import { NotebookPanel } from '@jupyterlab/notebook';
@@ -19,6 +19,8 @@ import { IDocumentConnectionData } from '../../connection_manager';
1919
import { ILSPAdapterManager, ILSPLogConsole } from '../../tokens';
2020
import { NotebookAdapter } from '../../adapters/notebook/notebook';
2121
import { ILSPCompletionThemeManager } from '@krassowski/completion-theme/lib/types';
22+
import { LSPCompletionRenderer } from './renderer';
23+
import { IRenderMime, IRenderMimeRegistry } from '@jupyterlab/rendermime';
2224

2325
export class CompletionCM extends CodeMirrorIntegration {
2426
private _completionCharacters: string[];
@@ -75,21 +77,44 @@ export class CompletionCM extends CodeMirrorIntegration {
7577
export class CompletionLabIntegration implements IFeatureLabIntegration {
7678
// TODO: maybe instead of creating it each time, keep a hash map instead?
7779
protected current_completion_connector: LSPConnector;
78-
protected current_completion_handler: ICompletionManager.ICompletableAttributes;
80+
protected current_completion_handler: CompletionHandler;
7981
protected current_adapter: WidgetAdapter<IDocumentWidget> = null;
82+
protected renderer: LSPCompletionRenderer;
83+
private markdown_renderer: IRenderMime.IRenderer;
8084

8185
constructor(
8286
private app: JupyterFrontEnd,
8387
private completionManager: ICompletionManager,
8488
public settings: FeatureSettings<LSPCompletionSettings>,
8589
private adapterManager: ILSPAdapterManager,
8690
private completionThemeManager: ILSPCompletionThemeManager,
87-
private console: ILSPLogConsole
91+
private console: ILSPLogConsole,
92+
private renderMimeRegistry: IRenderMimeRegistry
8893
) {
94+
this.renderer = new LSPCompletionRenderer({ integrator: this });
95+
this.renderer.activeChanged.connect(this.active_completion_changed, this);
8996
adapterManager.adapterChanged.connect(this.swap_adapter, this);
9097
settings.changed.connect(() => {
9198
completionThemeManager.set_theme(this.settings.composite.theme);
99+
completionThemeManager.set_icons_overrides(
100+
this.settings.composite.typesMap
101+
);
92102
});
103+
104+
this.markdown_renderer = this.renderMimeRegistry.createRenderer(
105+
'text/markdown'
106+
);
107+
}
108+
109+
active_completion_changed(
110+
renderer: LSPCompletionRenderer,
111+
item: LazyCompletionItem
112+
) {
113+
if (item.needsResolution()) {
114+
item.fetchDocumentation();
115+
} else if (item.isResolved()) {
116+
this.refresh_doc_panel(item);
117+
}
93118
}
94119

95120
private swap_adapter(
@@ -130,11 +155,14 @@ export class CompletionLabIntegration implements IFeatureLabIntegration {
130155
return;
131156
}
132157
this.set_completion_connector(adapter, editor);
133-
this.current_completion_handler = this.completionManager.register({
134-
connector: this.current_completion_connector,
135-
editor: editor,
136-
parent: adapter.widget
137-
});
158+
this.current_completion_handler = this.completionManager.register(
159+
{
160+
connector: this.current_completion_connector,
161+
editor: editor,
162+
parent: adapter.widget
163+
},
164+
this.renderer
165+
) as CompletionHandler;
138166
}
139167

140168
invoke_completer(kind: ExtendedCompletionTriggerKind) {
@@ -162,9 +190,53 @@ export class CompletionLabIntegration implements IFeatureLabIntegration {
162190
}
163191
this.set_completion_connector(adapter, editor_changed.editor);
164192
this.current_completion_handler.editor = editor_changed.editor;
193+
// @ts-ignore
165194
this.current_completion_handler.connector = this.current_completion_connector;
166195
}
167196

197+
refresh_doc_panel(item: LazyCompletionItem) {
198+
// TODO upstream: make completer public?
199+
let completer = this.current_completion_handler.completer;
200+
201+
// TODO upstream: allow to get completionItems() without markup
202+
// (note: not trivial as _markup() does filtering too)
203+
const items = completer.model.completionItems();
204+
205+
// TODO upstream: Completer will have getActiveItem()
206+
// @ts-ignore
207+
const index = completer._activeIndex;
208+
const active: CompletionHandler.ICompletionItem = items[index];
209+
210+
if (active.insertText != item.insertText) {
211+
return;
212+
}
213+
214+
if (item.documentation) {
215+
let docPanel = completer.node.querySelector('.jp-Completer-docpanel');
216+
217+
// TODO upstream: renderer should take care of the documentation rendering
218+
// sent PR: https://github.com/jupyterlab/jupyterlab/pull/9663
219+
if (item.isDocumentationMarkdown) {
220+
this.markdown_renderer
221+
.renderModel({
222+
data: {
223+
'text/markdown': item.documentation
224+
},
225+
trusted: false,
226+
metadata: {},
227+
setData(options: IRenderMime.IMimeModel.ISetDataOptions) {}
228+
})
229+
.catch(this.console.warn);
230+
// remove all children
231+
docPanel.textContent = '';
232+
docPanel.appendChild(this.markdown_renderer.node);
233+
} else {
234+
docPanel.textContent = item.documentation;
235+
}
236+
docPanel.setAttribute('style', '');
237+
}
238+
}
239+
168240
private set_completion_connector(
169241
adapter: WidgetAdapter<IDocumentWidget>,
170242
editor: CodeEditor.IEditor
@@ -178,6 +250,7 @@ export class CompletionLabIntegration implements IFeatureLabIntegration {
178250
connections: this.current_adapter.connection_manager.connections,
179251
virtual_editor: this.current_adapter.virtual_editor,
180252
settings: this.settings,
253+
labIntegration: this,
181254
// it might or might not be a notebook panel (if it is not, the sessionContext and session will just be undefined)
182255
session: (this.current_adapter.widget as NotebookPanel)?.sessionContext
183256
?.session,

0 commit comments

Comments
 (0)