import { CompletionHandler } from '@jupyterlab/completer'; import { LabIcon } from '@jupyterlab/ui-components'; import * as lsProtocol from 'vscode-languageserver-types'; import { until_ready } from '../../utils'; import { LSPConnector } from './completion_handler'; /** * To be upstreamed */ export interface ICompletionsSource { /** * The name displayed in the GUI */ name: string; /** * The higher the number the higher the priority */ priority: number; /** * The icon to be displayed if no type icon is present */ fallbackIcon?: LabIcon; } /** * To be upstreamed */ export interface IExtendedCompletionItem extends CompletionHandler.ICompletionItem { insertText: string; sortText: string; source?: ICompletionsSource; } export class LazyCompletionItem implements IExtendedCompletionItem { private _detail: string | undefined; private _documentation: string | undefined; private _is_documentation_markdown: boolean; private _requested_resolution: boolean; private _resolved: boolean; /** * Self-reference to make sure that the instance for will remain accessible * after any copy operation (whether via spread syntax or Object.assign) * performed by the JupyterLab completer internals. */ public self: LazyCompletionItem; public element: HTMLLIElement; private _currentInsertText: string; get isDocumentationMarkdown(): boolean { return this._is_documentation_markdown; } /** * User facing completion. * If insertText is not set, this will be inserted. */ public label: string; public source: ICompletionsSource; constructor( /** * Type of this completion item. */ public type: string, /** * LabIcon object for icon to be rendered with completion type. */ public icon: LabIcon, private match: lsProtocol.CompletionItem, private connector: LSPConnector, private uri: string ) { this.label = match.label; this._setDocumentation(match.documentation); this._requested_resolution = false; this._resolved = false; this._detail = match.detail; this.self = this; } private _setDocumentation( documentation: string | lsProtocol.MarkupContent | undefined ) { if (lsProtocol.MarkupContent.is(documentation)) { this._documentation = documentation.value; this._is_documentation_markdown = documentation.kind === 'markdown'; } else { this._documentation = documentation; this._is_documentation_markdown = false; } } /** * Completion to be inserted. */ get insertText(): string { return this._currentInsertText || this.match.insertText || this.match.label; } set insertText(text: string) { this._currentInsertText = text; } get sortText(): string { return this.match.sortText || this.match.label; } get filterText(): string | undefined { return this.match.filterText; } public supportsResolution() { const connection = this.connector.get_connection(this.uri); return connection != null && connection.isCompletionResolveProvider(); } get detail(): string | undefined { return this._detail; } public needsResolution(): boolean { if (this.documentation) { return false; } if (this._resolved) { return false; } if (this._requested_resolution) { return false; } return this.supportsResolution(); } public isResolved() { return this._resolved; } /** * Resolve (fetch) details such as documentation. */ public resolve(): Promise { if (this._resolved) { return Promise.resolve(this); } if (!this.supportsResolution()) { return Promise.resolve(this); } if (this._requested_resolution) { return until_ready(() => this._resolved, 100, 50).then(() => this); } const connection = this.connector.get_connection(this.uri)!; this._requested_resolution = true; return connection .getCompletionResolve(this.match) .then(resolvedCompletionItem => { if (resolvedCompletionItem === null) { return resolvedCompletionItem; } this._setDocumentation(resolvedCompletionItem?.documentation); this._detail = resolvedCompletionItem?.detail; // TODO: implement in pyls and enable with proper LSP communication // this.label = resolvedCompletionItem.label; this._resolved = true; return this; }); } /** * A human-readable string with additional information * about this item, like type or symbol information. */ get documentation(): string | undefined { if (!this.connector.should_show_documentation) { return undefined; } if (this._documentation) { return this._documentation; } return undefined; } /** * Indicates if the item is deprecated. */ get deprecated(): boolean { if (this.match.deprecated) { return this.match.deprecated; } return ( this.match.tags != null && this.match.tags.some( tag => tag == lsProtocol.CompletionItemTag.Deprecated ) ); } }