diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-api-provider.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-api-provider.ts index 32f9aa373..6efb52df7 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-api-provider.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-api-provider.ts @@ -25,6 +25,7 @@ import { CheSideCarContentReaderMainImpl } from './che-sidecar-content-reader-ma import { CheGithubMainImpl } from './che-github-main'; import { CheOpenshiftMainImpl } from './che-openshift-main'; import { CheOauthMainImpl } from './che-oauth-main'; +import { CheLanguagesTestAPIImpl } from './che-languages-test-api'; @injectable() export class CheApiProvider implements MainPluginApiProvider { @@ -43,6 +44,7 @@ export class CheApiProvider implements MainPluginApiProvider { rpc.set(PLUGIN_RPC_CONTEXT.CHE_USER_MAIN, new CheUserMainImpl(container)); rpc.set(PLUGIN_RPC_CONTEXT.CHE_PRODUCT_MAIN, new CheProductMainImpl(container, rpc)); rpc.set(PLUGIN_RPC_CONTEXT.CHE_SIDERCAR_CONTENT_READER_MAIN, new CheSideCarContentReaderMainImpl(container, rpc)); + rpc.set(PLUGIN_RPC_CONTEXT.CHE_LANGUAGES_TEST_API_MAIN, new CheLanguagesTestAPIImpl(container)); } } diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-frontend-module.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-frontend-module.ts index d5ca2ac8d..4de13455a 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-frontend-module.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-frontend-module.ts @@ -54,6 +54,11 @@ import { CheTaskResolver } from './che-task-resolver'; import { CheTaskTerminalWidgetManager } from './che-task-terminal-widget-manager'; import { TaskTerminalWidgetManager } from '@theia/task/lib/browser/task-terminal-widget-manager'; import { ContainerPicker } from './container-picker'; +import { ChePluginHandleRegistry } from './che-plugin-handle-registry'; +import { CheLanguagesMainTestImpl } from './che-languages-test-main'; +import { RPCProtocol } from '@theia/plugin-ext/lib/common/rpc-protocol'; +import { interfaces } from 'inversify'; +import { LanguagesMainFactory } from '@theia/plugin-ext'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(CheApiProvider).toSelf().inSingletonScope(); @@ -122,4 +127,12 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(CheTaskTerminalWidgetManager).toSelf().inSingletonScope(); rebind(TaskTerminalWidgetManager).toService(CheTaskTerminalWidgetManager); + + bind(ChePluginHandleRegistry).toSelf().inSingletonScope(); + bind(CheLanguagesMainTestImpl).toSelf().inTransientScope(); + rebind(LanguagesMainFactory).toFactory((context: interfaces.Context) => (rpc: RPCProtocol) => { + const child = context.container.createChild(); + child.bind(RPCProtocol).toConstantValue(rpc); + return child.get(CheLanguagesMainTestImpl); + }); }); diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-languages-test-api.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-languages-test-api.ts new file mode 100644 index 000000000..cd592bdb8 --- /dev/null +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-languages-test-api.ts @@ -0,0 +1,412 @@ +/********************************************************************* + * Copyright (c) 2020 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +import { ChePluginHandleRegistry } from './che-plugin-handle-registry'; +import { interfaces } from 'inversify'; +import { CheLanguagesTestAPI } from '../common/che-languages-test-protocol'; +import { + CancellationToken, + FoldingContext, + CodeActionContext, + Uri, + CompletionList, + SignatureHelpContext, + ReferenceContext, + CompletionContext, + FormattingOptions, + CodeAction, + Range, + DocumentHighlight, + DocumentSymbol, + DocumentLink, + Definition, + TextEdit, + DefinitionLink, + CodeLens, + Hover, + Position, + Selection, + SymbolInformation, + FoldingRange, + ColorInformation, + WorkspaceEdit, + SignatureHelp, + Location +} from '@theia/plugin'; +import * as Converter from './type-converters'; + +/** + * This class redirects language api requests to the correct sidecars and returns the results + */ +export class CheLanguagesTestAPIImpl implements CheLanguagesTestAPI { + + private readonly pluginHandleRegistry: ChePluginHandleRegistry; + + constructor(container: interfaces.Container) { + this.pluginHandleRegistry = container.get(ChePluginHandleRegistry); + } + + async $provideCompletionItems( + pluginID: string, + resource: Uri, + position: Position, + context: CompletionContext, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'completion'); + const convertedPosition = Converter.fromPosition(Converter.revivePosition(position)); + return Promise.resolve(languagesExt.$provideCompletionItems( + handle, + resource, + convertedPosition, + context, + token + )).then(completion => { + if (!completion) { + return undefined; + } else { + return Converter.toCompletionList(completion); + } + }); + } + + async $provideDefinition( + pluginID: string, + resource: Uri, + position: Position, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'definition'); + const convertedPosition = Converter.fromPosition(Converter.revivePosition(position)); + return Promise.resolve(languagesExt.$provideDefinition(handle, resource, convertedPosition, token)).then(definition => { + if (!definition) { + return undefined; + } else { + return Converter.toDefinition(definition); + } + }); + } + + async $provideDeclaration( + pluginID: string, + resource: Uri, + position: Position, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'declaration'); + const convertedPosition = Converter.fromPosition(Converter.revivePosition(position)); + return Promise.resolve(languagesExt.$provideDeclaration(handle, resource, convertedPosition, token)).then(declaration => { + if (!declaration) { + return undefined; + } else { + return Converter.toDefinition(declaration); + } + }); + } + + async $provideSignatureHelp( + pluginID: string, + resource: Uri, + position: Position, + context: SignatureHelpContext, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'signatureHelp'); + const convertedPosition = Converter.fromPosition(Converter.revivePosition(position)); + return Promise.resolve(languagesExt.$provideSignatureHelp(handle, resource, convertedPosition, context, token)).then(signatureHelp => { + if (!signatureHelp) { + return undefined; + } else { + return Converter.toSignatureHelp(signatureHelp); + } + }); + } + + async $provideImplementation( + pluginID: string, + resource: Uri, + position: Position, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'implementation'); + const convertedPosition = Converter.fromPosition(Converter.revivePosition(position)); + return Promise.resolve(languagesExt.$provideImplementation(handle, resource, convertedPosition, token)).then(implementation => { + if (!implementation) { + return undefined; + } else { + return Converter.toDefinition(implementation); + } + }); + } + + async $provideTypeDefinition( + pluginID: string, + resource: Uri, + position: Position, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'typeDefinition'); + const convertedPosition = Converter.fromPosition(Converter.revivePosition(position)); + return Promise.resolve(languagesExt.$provideTypeDefinition(handle, resource, convertedPosition, token)).then(typeDefinition => { + if (!typeDefinition) { + return undefined; + } else { + return Converter.toDefinition(typeDefinition); + } + }); + } + + async $provideHover( + pluginID: string, + resource: Uri, + position: Position, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'hover'); + const convertedPosition = Converter.fromPosition(Converter.revivePosition(position)); + return Promise.resolve(languagesExt.$provideHover(handle, resource, convertedPosition, token)).then(hover => { + if (!hover) { + return undefined; + } else { + return Converter.toHover(hover); + } + }); + } + + async $provideDocumentHighlights( + pluginID: string, + resource: Uri, + position: Position, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'documentHighlight'); + const convertedPosition = Converter.fromPosition(Converter.revivePosition(position)); + return Promise.resolve(languagesExt.$provideDocumentHighlights(handle, resource, convertedPosition, token)).then(highlights => { + if (!highlights) { + return undefined; + } else { + return highlights.map(Converter.toDocumentHighlight); + } + }); + } + + async $provideWorkspaceSymbols( + pluginID: string, + query: string, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'workspaceSymbols'); + return Promise.resolve(languagesExt.$provideWorkspaceSymbols(handle, query, token)).then(workspaceSymbols => { + if (!workspaceSymbols) { + return []; + } else { + const newSymbols: SymbolInformation[] = []; + for (const sym of workspaceSymbols) { + const convertedSymbol = Converter.toSymbolInformation(sym); + if (convertedSymbol) { + newSymbols.push(convertedSymbol); + } + }; + return newSymbols; + } + }); + } + + async $provideDocumentFormattingEdits( + pluginID: string, + resource: Uri, + options: FormattingOptions, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'documentFormattingEdits'); + return Promise.resolve(languagesExt.$provideDocumentFormattingEdits(handle, resource, options, token)).then(edits => { + if (!edits) { + return undefined; + } else { + return edits.map(Converter.toTextEdit); + } + }); + } + + async $provideDocumentRangeFormattingEdits( + pluginID: string, + resource: Uri, + range: Range, + options: FormattingOptions, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'documentRangeFormattingEdits'); + const convertedRange = Converter.fromRange(Converter.reviveRange(range)); + return Promise.resolve(languagesExt.$provideDocumentRangeFormattingEdits( + handle, + resource, + convertedRange, + options, token + )).then(edits => { + if (!edits) { + return undefined; + } else { + return edits.map(Converter.toTextEdit); + } + }); + } + + async $provideOnTypeFormattingEdits( + pluginID: string, + resource: Uri, + position: Position, + ch: string, + options: FormattingOptions, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'onTypeFormattingEdits'); + const convertedPosition = Converter.fromPosition(Converter.revivePosition(position)); + return Promise.resolve(languagesExt.$provideOnTypeFormattingEdits(handle, resource, convertedPosition, ch, options, token)).then(edits => { + if (!edits) { + return undefined; + } else { + return edits.map(Converter.toTextEdit); + } + }); + } + + async $provideDocumentLinks( + pluginID: string, + resource: Uri, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'documentLinks'); + return Promise.resolve(languagesExt.$provideDocumentLinks(handle, resource, token)).then(links => { + if (!links) { + return undefined; + } else { + return links.map(Converter.toDocumentLink); + } + }); + } + + async $provideCodeActions( + pluginID: string, + resource: Uri, + rangeOrSelection: Range | Selection, + context: CodeActionContext, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'codeActions'); + const rangeOrSelectionParam = rangeOrSelection.hasOwnProperty('_anchor') && rangeOrSelection.hasOwnProperty('_active') ? + Converter.fromSelection(Converter.reviveSelection(rangeOrSelection)) : + Converter.fromRange(Converter.reviveRange(rangeOrSelection)); + return Promise.resolve(languagesExt.$provideCodeActions( + handle, + resource, + rangeOrSelectionParam, + Converter.fromCodeActionContext(context), + token + )).then(actions => { + if (!actions) { + return undefined; + } else { + return actions.map(Converter.toCodeAction); + } + }); + } + + async $provideCodeLenses( + pluginID: string, + resource: Uri, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'codeLenses'); + return Promise.resolve(languagesExt.$provideCodeLenses(handle, resource, token)).then(lenses => { + if (!lenses) { + return undefined; + } else { + return lenses.map(Converter.toCodeLens); + } + }); + } + + async $provideReferences( + pluginID: string, + resource: Uri, + position: Position, + context: ReferenceContext, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'references'); + const convertedPosition = Converter.fromPosition(Converter.revivePosition(position)); + return Promise.resolve(languagesExt.$provideReferences(handle, resource, convertedPosition, context, token)).then(locations => { + if (!locations) { + return undefined; + } else { + return locations.map(Converter.toLocation); + } + }); + } + + async $provideDocumentColors( + pluginID: string, + resource: Uri, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'documentColors'); + return Promise.resolve(languagesExt.$provideDocumentColors(handle, resource, token)) + .then(rawColorInfo => rawColorInfo.map(Converter.toColorInformation)); + } + + async $provideFoldingRange( + pluginID: string, + resource: Uri, + context: FoldingContext, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'foldingRange'); + return Promise.resolve(languagesExt.$provideFoldingRange(handle, resource, context, token)).then(foldingRanges => { + if (!foldingRanges) { + return undefined; + } else { + return foldingRanges.map(Converter.toFoldingRange); + } + }); + } + + async $provideRenameEdits( + pluginID: string, + resource: Uri, + position: Position, + newName: string, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'renameEdits'); + const convertedPosition = Converter.fromPosition(Converter.revivePosition(position)); + return Promise.resolve(languagesExt.$provideRenameEdits(handle, resource, convertedPosition, newName, token)).then(edits => { + if (!edits) { + return undefined; + } else { + return Converter.toWorkspaceEdit(edits); + } + }); + } + + async $provideDocumentSymbols( + pluginID: string, + resource: Uri, + token: CancellationToken + ): Promise { + const { languagesExt, handle } = await this.pluginHandleRegistry.lookupLanguagesExtForPluginAndAction(pluginID, 'symbols'); + return Promise.resolve(languagesExt.$provideDocumentSymbols(handle, resource, token)).then(documentSymbols => { + if (!documentSymbols) { + return undefined; + } else { + return documentSymbols.map(Converter.toDocumentSymbol); + } + }); + } +} diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-languages-test-main.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-languages-test-main.ts new file mode 100644 index 000000000..6fa4661d1 --- /dev/null +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-languages-test-main.ts @@ -0,0 +1,201 @@ +/********************************************************************* + * Copyright (c) 2020 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +import { injectable, inject } from 'inversify'; +import { PluginInfo, LanguagesExt, MAIN_RPC_CONTEXT } from '@theia/plugin-ext/lib/common/plugin-api-rpc'; +import { SerializedDocumentFilter, MarkerData } from '@theia/plugin-ext/lib/common/plugin-api-rpc-model'; +import { LanguagesMainImpl } from '@theia/plugin-ext/lib/main/browser/languages-main'; +import * as theia from '@theia/plugin'; +import { ChePluginHandleRegistry } from './che-plugin-handle-registry'; +import { RPCProtocol } from '@theia/plugin-ext/lib/common/rpc-protocol'; +import { LanguagesMain, SerializedLanguageConfiguration } from '@theia/plugin-ext/lib/common/plugin-api-rpc'; +import { UriComponents } from '@theia/plugin-ext/lib/common/uri-components'; + +export type LanguageServerAction = string; +export type LanguageServerActions = + 'completion' | + 'definition' | + 'declaration' | + 'signatureHelp' | + 'implementation' | + 'typeDefinition' | + 'hover' | + 'quickFix' | + 'documentHighlight' | + 'workspaceSymbols' | + 'documentFormattingEdits' | + 'documentRangeFormattingEdits' | + 'onTypeFormattingEdits' | + 'documentLinks' | + 'codeActions' | + 'codeLenses' | + 'references' | + 'symbols' | + 'documentColors' | + 'foldingRange' | + 'selectionRange' | + 'renameEdits'; + +@injectable() +export class CheLanguagesMainTestImpl implements LanguagesMain { + + @inject(LanguagesMainImpl) + private readonly languagesMainImpl: LanguagesMainImpl; + + @inject(ChePluginHandleRegistry) + private readonly pluginHandleRegistry: ChePluginHandleRegistry; + + private readonly languagesExtProxy: LanguagesExt; + + constructor(@inject(RPCProtocol) rpc: RPCProtocol) { + this.languagesExtProxy = rpc.getProxy(MAIN_RPC_CONTEXT.LANGUAGES_EXT); + } + + $getLanguages(): Promise { + return this.languagesMainImpl.$getLanguages(); + } + + $changeLanguage(resource: UriComponents, languageId: string): Promise { + return this.languagesMainImpl.$changeLanguage(resource, languageId); + } + + $setLanguageConfiguration(handle: number, languageId: string, configuration: SerializedLanguageConfiguration): void { + this.languagesMainImpl.$setLanguageConfiguration(handle, languageId, configuration); + } + + $unregister(handle: number): void { + this.languagesMainImpl.$unregister(handle); + } + + $clearDiagnostics(id: string): void { + this.languagesMainImpl.$clearDiagnostics(id); + } + + $changeDiagnostics(id: string, delta: [string, MarkerData[]][]): void { + this.languagesMainImpl.$changeDiagnostics(id, delta); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + $emitCodeLensEvent(eventHandle: number, event?: any): void { + this.languagesMainImpl.$emitCodeLensEvent(eventHandle, event); + } + + $registerCompletionSupport(handle: number, pluginInfo: PluginInfo, + selector: SerializedDocumentFilter[], triggerCharacters: string[], supportsResolveDetails: boolean): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'completion', this.languagesExtProxy); + this.languagesMainImpl.$registerCompletionSupport(handle, pluginInfo, selector, triggerCharacters, supportsResolveDetails); + } + + $registerSelectionRangeProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'selectionRange', this.languagesExtProxy); + this.languagesMainImpl.$registerSelectionRangeProvider(handle, pluginInfo, selector); + } + + $registerDefinitionProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'definition', this.languagesExtProxy); + this.languagesMainImpl.$registerDefinitionProvider(handle, pluginInfo, selector); + } + + $registerDeclarationProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'declaration', this.languagesExtProxy); + this.languagesMainImpl.$registerDeclarationProvider(handle, pluginInfo, selector); + } + + $registerReferenceProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'references', this.languagesExtProxy); + this.languagesMainImpl.$registerReferenceProvider(handle, pluginInfo, selector); + } + + $registerSignatureHelpProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[], metadata: theia.SignatureHelpProviderMetadata): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'signatureHelp', this.languagesExtProxy); + this.languagesMainImpl.$registerSignatureHelpProvider(handle, pluginInfo, selector, metadata); + } + + $registerImplementationProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'implementation', this.languagesExtProxy); + this.languagesMainImpl.$registerImplementationProvider(handle, pluginInfo, selector); + } + + $registerTypeDefinitionProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'typeDefinition', this.languagesExtProxy); + this.languagesMainImpl.$registerTypeDefinitionProvider(handle, pluginInfo, selector); + } + + $registerHoverProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'hover', this.languagesExtProxy); + this.languagesMainImpl.$registerHoverProvider(handle, pluginInfo, selector); + } + + $registerQuickFixProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[], codeActionKinds?: string[] | undefined): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'quickFix', this.languagesExtProxy); + this.languagesMainImpl.$registerQuickFixProvider(handle, pluginInfo, selector); + } + + $registerDocumentHighlightProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'documentHighlight', this.languagesExtProxy); + this.languagesMainImpl.$registerDocumentHighlightProvider(handle, pluginInfo, selector); + } + + $registerWorkspaceSymbolProvider(handle: number, pluginInfo: PluginInfo): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'workspaceSymbols', this.languagesExtProxy); + this.languagesMainImpl.$registerWorkspaceSymbolProvider(handle, pluginInfo); + } + + $registerDocumentLinkProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'documentLinks', this.languagesExtProxy); + this.languagesMainImpl.$registerDocumentLinkProvider(handle, pluginInfo, selector); + } + + $registerCodeLensSupport(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[], eventHandle: number): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'codeLenses', this.languagesExtProxy); + this.languagesMainImpl.$registerCodeLensSupport(handle, pluginInfo, selector, eventHandle); + } + + $registerOutlineSupport(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'symbols', this.languagesExtProxy); + this.languagesMainImpl.$registerOutlineSupport(handle, pluginInfo, selector); + } + + $registerDocumentFormattingSupport(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'documentFormattingEdits', this.languagesExtProxy); + this.languagesMainImpl.$registerDocumentFormattingSupport(handle, pluginInfo, selector); + } + + $registerRangeFormattingProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'documentRangeFormattingEdits', this.languagesExtProxy); + this.languagesMainImpl.$registerRangeFormattingProvider(handle, pluginInfo, selector); + } + + $registerOnTypeFormattingProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[], autoFormatTriggerCharacters: string[]): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'onTypeFormattingEdits', this.languagesExtProxy); + this.languagesMainImpl.$registerOnTypeFormattingProvider(handle, pluginInfo, selector, autoFormatTriggerCharacters); + } + + $registerFoldingRangeProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'foldingRange', this.languagesExtProxy); + this.languagesMainImpl.$registerFoldingRangeProvider(handle, pluginInfo, selector); + } + + $registerDocumentColorProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[]): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'documentColors', this.languagesExtProxy); + this.languagesMainImpl.$registerDocumentColorProvider(handle, pluginInfo, selector); + } + + $registerRenameProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[], supportsResolveLocation: boolean): void { + this.pluginHandleRegistry.registerPluginWithFeatureHandle(handle, pluginInfo.id, 'renameEdits', this.languagesExtProxy); + this.languagesMainImpl.$registerRenameProvider(handle, pluginInfo, selector, supportsResolveLocation); + } + + $registerCallHierarchyProvider(handle: number, selector: SerializedDocumentFilter[]): void { + // This doesnt have pluginInfo so it cannot register with the pluginHandleRegistry for now + this.languagesMainImpl.$registerCallHierarchyProvider(handle, selector); + } + +} diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-plugin-handle-registry.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-plugin-handle-registry.ts new file mode 100644 index 000000000..5ba8a95b5 --- /dev/null +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-plugin-handle-registry.ts @@ -0,0 +1,90 @@ +/********************************************************************* + * Copyright (c) 2020 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +import { injectable } from 'inversify'; +import { LanguageServerAction } from './che-languages-test-main'; +import { LanguagesExt } from '@theia/plugin-ext'; +import { Emitter } from '@theia/core/lib/common/event'; + +export interface LanguageFeatureRegistration { + languagesExtImpl: LanguagesExt; // The languagesExt that registered this plugin in the registry + providerHandles: Map; // A map of language actions to their handle +} + +export interface LanguagesExtHandle { + handle: number; + languagesExt: LanguagesExt; +} + +export interface LanguageRegistrationEvent extends LanguagesExtHandle { + pluginID: string; + action: LanguageServerAction; +} + +/** + * This class keeps a registry of which plugins map to which handle + */ +@injectable() +export class ChePluginHandleRegistry { + pluginRegistrationMap: Map = new Map(); + + private onRegisteredLanguageFeatureEmitter = new Emitter(); + private onRegisteredLanguageFeature = this.onRegisteredLanguageFeatureEmitter.event; + + private findRegisteredLanguagesExt(pluginID: string, languageServerAction: string): LanguagesExtHandle | undefined { + const languageFeatureRegistration = this.pluginRegistrationMap.get(pluginID); + if (languageFeatureRegistration) { + const correctLanguagesExt = languageFeatureRegistration.languagesExtImpl; + const correctLanguageServerHandle = languageFeatureRegistration.providerHandles.get(languageServerAction); + if (correctLanguageServerHandle) { + return { + handle: correctLanguageServerHandle, + languagesExt: correctLanguagesExt + }; + } + } + return undefined; + } + + lookupLanguagesExtForPluginAndAction(pluginID: string, languageServerAction: LanguageServerAction): Promise { + const registeredLanguagesExt = this.findRegisteredLanguagesExt(pluginID, languageServerAction); + if (!registeredLanguagesExt) { + return new Promise(resolve => { + this.onRegisteredLanguageFeature((newRegistration: LanguageRegistrationEvent) => { + if (newRegistration.pluginID === pluginID && newRegistration.action === languageServerAction) { + return resolve(newRegistration); + } + }); + }); + } else { + return Promise.resolve(registeredLanguagesExt); + } + } + + registerPluginWithFeatureHandle(handle: number, extensionId: string, newlyRegisteredAction: string, languagesExtProxy: LanguagesExt): void { + let potentialRegistration = this.pluginRegistrationMap.get(extensionId); + if (!potentialRegistration) { + potentialRegistration = { + providerHandles: new Map(), + languagesExtImpl: languagesExtProxy + }; + this.pluginRegistrationMap.set(extensionId, potentialRegistration); + } + potentialRegistration.providerHandles.set(newlyRegisteredAction, handle); + + this.onRegisteredLanguageFeatureEmitter.fire({ + handle, + languagesExt: languagesExtProxy, + pluginID: extensionId, + action: newlyRegisteredAction + }); + } + +} diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/type-converters.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/type-converters.ts new file mode 100644 index 000000000..a5dfcfe7e --- /dev/null +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/type-converters.ts @@ -0,0 +1,377 @@ +/********************************************************************* + * Copyright (c) 2020 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +import * as model from '@theia/plugin-ext/lib/common/plugin-api-rpc-model'; +import { SymbolInformation } from 'vscode-languageserver-types'; +import * as theia from '@theia/plugin'; +import * as types from '@theia/plugin-ext/lib/plugin/types-impl'; +import * as Converter from '@theia/plugin-ext/lib/plugin/type-converters'; +import { URI } from 'vscode-uri'; +import { + Selection, Position, RawColorInfo, ResourceFileEditDto, + ResourceTextEditDto +} from '@theia/plugin-ext/lib/common/plugin-api-rpc'; + +export function toWorkspaceEdit(workspaceEdit: model.WorkspaceEdit): theia.WorkspaceEdit { + const result: theia.WorkspaceEdit = new types.WorkspaceEdit(); + const edits = workspaceEdit.edits; + + for (const entry of edits) { + if (entry.hasOwnProperty('oldUri')) { + const fileEdit = entry as ResourceFileEditDto; + + result.renameFile(URI.revive(fileEdit.oldUri), URI.revive(fileEdit.newUri), fileEdit.options); + } else { + const textEdit = entry as ResourceTextEditDto; + result.set(URI.revive(textEdit.resource), textEdit.edits.map(toTextEdit)); + } + } + return result; +} + +export function toCompletionList(completionResultDTO: model.CompletionResultDto): theia.CompletionList { + const result: theia.CompletionList = { + items: [] + }; + if (completionResultDTO.hasOwnProperty('incomplete')) { + result.isIncomplete = completionResultDTO.incomplete; + } + result.items = completionResultDTO.completions.map(toCompletionItem); + return result; +} + +export function toCompletionItem(completionDTO: model.CompletionDto): theia.CompletionItem { + let range: theia.CompletionItem['range'] | undefined; + const itemRange = completionDTO.range; + if (!itemRange) { + range = undefined; + } else if (itemRange.hasOwnProperty('insert') && itemRange.hasOwnProperty('replace')) { + const inserting = Converter.toRange((itemRange as unknown as { + insert: model.Range; + replace: model.Range; + }).insert); + const replacing = Converter.toRange((itemRange as unknown as { + insert: model.Range; + replace: model.Range; + }).replace); + range = { + inserting: inserting, + replacing: replacing + }; + } else { + range = Converter.toRange(itemRange as model.Range); + } + + return { + label: completionDTO.label, + range: range, + additionalTextEdits: completionDTO.additionalTextEdits && completionDTO.additionalTextEdits.map(toTextEdit), + command: toCommand(completionDTO.command), + commitCharacters: completionDTO.commitCharacters, + detail: completionDTO.detail, + filterText: completionDTO.filterText, + insertText: completionDTO.insertText, + keepWhitespace: completionDTO.insertTextRules ? true : false, + kind: Converter.toCompletionItemKind(completionDTO.kind), + preselect: completionDTO.preselect, + sortText: completionDTO.sortText, + }; +} + +export function toHover(hover: model.Hover): theia.Hover { + let range = undefined; + if (hover.range) { + range = Converter.toRange(hover.range); + } + const markdown = hover.contents.map(Converter.toMarkdown); + return { + range: range, + contents: markdown + }; +} + +export function toFoldingRange(foldingRange: model.FoldingRange): theia.FoldingRange { + const range: theia.FoldingRange = { + start: foldingRange.start - 1, + end: foldingRange.end - 1 + }; + if (foldingRange.kind) { + range.kind = toFoldingRangeKind(foldingRange.kind); + } + return range; +} + +export function toDocumentLink(link: model.DocumentLink): theia.DocumentLink { + let target = undefined; + if (link.url) { + try { + target = typeof link.url === 'string' ? URI.parse(link.url, true) : URI.revive(link.url); + } catch (err) { + console.error(err); + } + } + return { + range: Converter.toRange(link.range), + target: target + }; +} + +export function toFoldingRangeKind(kind: model.FoldingRangeKind | undefined): theia.FoldingRangeKind | undefined { + if (kind) { + switch (kind) { + case model.FoldingRangeKind.Comment: + return types.FoldingRangeKind.Comment; + case model.FoldingRangeKind.Imports: + return types.FoldingRangeKind.Imports; + case model.FoldingRangeKind.Region: + return types.FoldingRangeKind.Region; + } + } + return undefined; +} + +export function toCodeLens(lenses: model.CodeLensSymbol): theia.CodeLens { + return { + range: Converter.toRange(lenses.range), + command: lenses.command, + isResolved: true + }; +} + +export function toDefinition(definition: model.Definition): theia.Definition | theia.DefinitionLink[] | undefined { + if (isLocationLinkArray(definition)) { + const definitionLinks: theia.DefinitionLink[] = []; + for (const location of definition) { + definitionLinks.push(toDefinitionLink(location)); + } + return definitionLinks; + } else if (isLocationArray(definition)) { + const locations: theia.Location[] = []; + for (const location of definition) { + locations.push(Converter.toLocation(location)); + } + return locations; + } else { + return Converter.toLocation(definition); + } +} + +export function toDefinitionLink(locationLink: model.LocationLink): theia.DefinitionLink { + return { + targetUri: Converter.toLocation(locationLink as model.Location).uri, + targetRange: Converter.toRange(locationLink.range), + originSelectionRange: locationLink.originSelectionRange ? Converter.toRange(locationLink.originSelectionRange) : undefined, + targetSelectionRange: locationLink.targetSelectionRange ? Converter.toRange(locationLink.targetSelectionRange) : undefined + }; +} + +export function toTextEdit(edit: model.TextEdit): theia.TextEdit { + return { + newText: edit.text, + range: Converter.toRange(edit.range), + newEol: edit.eol === 1 ? 2 : 1 + }; +} + +export function toSignatureHelp(signatureHelp: model.SignatureHelp): theia.SignatureHelp { + return Converter.SignatureHelp.to(signatureHelp); +} + +export function toDocumentSymbol(symbol: model.DocumentSymbol): theia.DocumentSymbol { + return Converter.toDocumentSymbol(symbol); +} + +export function toSymbolInformation(symbolInformation: SymbolInformation): theia.SymbolInformation | undefined { + return Converter.toSymbolInformation(symbolInformation); +} + +export function toLocation(location: model.Location): theia.Location { + return Converter.toLocation(location); +} + +export function toDocumentHighlight(documentHighlight: model.DocumentHighlight): theia.DocumentHighlight { + return { + range: Converter.toRange(documentHighlight.range), + kind: toDocumentHighlightKind(documentHighlight.kind) + }; +} + +export function toDocumentHighlightKind(kind: model.DocumentHighlightKind | undefined): types.DocumentHighlightKind { + switch (kind) { + case model.DocumentHighlightKind.Text: return types.DocumentHighlightKind.Text; + case model.DocumentHighlightKind.Read: return types.DocumentHighlightKind.Read; + case model.DocumentHighlightKind.Write: return types.DocumentHighlightKind.Write; + }; + return types.DocumentHighlightKind.Text; +} + +export function toCodeAction(action: model.CodeAction): theia.CodeAction { + return { + title: action.title, + command: toCommand(action.command), + diagnostics: toDiagnostics(action.diagnostics), + kind: action.kind ? new types.CodeActionKind(action.kind) : undefined, + edit: action.edit ? toWorkspaceEdit(action.edit) : undefined + }; +} + +export function toCommand(command: model.Command | undefined): theia.Command | undefined { + if (!command) { + return undefined; + } + return { + id: command.id, + title: command.title, + arguments: command.arguments, + tooltip: command.tooltip + }; +} + +export function toDiagnostics(diagnostics: model.MarkerData[] | undefined): theia.Diagnostic[] | undefined { + return diagnostics ? diagnostics.map(toDiagnostic) : undefined; +} + +export function fromRange(range: theia.Range): model.Range { + return Converter.fromRange(range); +} + +export function fromPosition(position: theia.Position): Position { + return { lineNumber: position.line + 1, column: position.character + 1 }; +} + +export function toColorInformation(rawColorInfo: RawColorInfo): theia.ColorInformation { + return { + color: Converter.toColor(rawColorInfo.color), + range: Converter.toRange(rawColorInfo.range) + }; +} + +export function fromCodeActionContext(context: theia.CodeActionContext): model.CodeActionContext { + return { + only: context.only?.value + }; +} + +export function fromSelection(selection: theia.Selection): Selection { + const { active, anchor } = selection; + return { + selectionStartLineNumber: anchor.line + 1, + selectionStartColumn: anchor.character + 1, + positionLineNumber: active.line + 1, + positionColumn: active.character + 1 + }; +} + +export function toDiagnostic(marker: model.MarkerData): theia.Diagnostic { + return { + message: marker.message, + range: Converter.toRange({ + startLineNumber: marker.startLineNumber, + startColumn: marker.startColumn, + endLineNumber: marker.endLineNumber, + endColumn: marker.endColumn + }), + severity: toDiagnosticSeverity(marker.severity), + code: marker.code, + source: marker.source, + relatedInformation: toDiagnosticRelatedInformation(marker.relatedInformation), + tags: toDiagnosticTags(marker.tags) + }; +} + +export function toDiagnosticSeverity(severity: types.MarkerSeverity): types.DiagnosticSeverity { + switch (severity) { + case types.MarkerSeverity.Error: return types.DiagnosticSeverity.Error; + case types.MarkerSeverity.Warning: return types.DiagnosticSeverity.Warning; + case types.MarkerSeverity.Info: return types.DiagnosticSeverity.Information; + case types.MarkerSeverity.Hint: return types.DiagnosticSeverity.Hint; + }; +} + +export function toDiagnosticRelatedInformation(relatedInformation: model.RelatedInformation[] | undefined): theia.DiagnosticRelatedInformation[] | undefined { + if (!relatedInformation) { + return undefined; + } + const diagnosticRelatedInformation: theia.DiagnosticRelatedInformation[] = []; + for (const item of relatedInformation) { + const location: theia.Location = { + uri: URI.parse(item.resource), + range: Converter.toRange({ + startLineNumber: item.startLineNumber, + startColumn: item.startColumn, + endLineNumber: item.endLineNumber, + endColumn: item.endColumn + }) + }; + diagnosticRelatedInformation.push({ + location: location, + message: item.message + }); + } + return diagnosticRelatedInformation; +} + +function toDiagnosticTags(tags: types.MarkerTag[] | undefined): types.DiagnosticTag[] | undefined { + if (!tags) { + return undefined; + } + + const diagnosticTags: theia.DiagnosticTag[] = []; + for (const tag of tags) { + if (tag === types.MarkerTag.Unnecessary) { + diagnosticTags.push(types.DiagnosticTag.Unnecessary); + } + } + return diagnosticTags; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function isLocationArray(array: any): array is model.Location[] { + return Array.isArray(array) && array.length > 0; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function isLocationLinkArray(array: any): array is model.LocationLink[] { + return Array.isArray(array) && array.length > 0 && array[0].hasOwnProperty('originSelectionRange') && array[0].hasOwnProperty('targetSelectionRange'); +} + +// Try to restore Position object from its serialized content +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function revivePosition(position: any): theia.Position { + if (!position || !position._line || !position._character) { + throw new Error('Not able to restore position'); + } + const result: theia.Position = new types.Position(position._line, position._character); + return result; +} + +// Try to restore Range object from its serialized content +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function reviveRange(range: any): theia.Range { + if (!range || !range._start || !range._end) { + throw new Error('Not able to restore range'); + } + const start = revivePosition(range._start); + const end = revivePosition(range._end); + const result: theia.Range = new types.Range(start.line, start.character, end.line, end.character); + return result; +} + +// Try to restore Selection object from its serialized content +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function reviveSelection(selection: any): theia.Selection { + if (!selection || !selection._anchor || !selection._active) { + throw new Error('Not able to restore selection'); + } + const anchor = revivePosition(selection._anchor); + const active = revivePosition(selection._active); + const result: theia.Selection = new types.Selection(anchor.line, anchor.character, active.line, active.character); + return result; +} diff --git a/extensions/eclipse-che-theia-plugin-ext/src/common/che-languages-test-protocol.ts b/extensions/eclipse-che-theia-plugin-ext/src/common/che-languages-test-protocol.ts new file mode 100644 index 000000000..a8ad015e9 --- /dev/null +++ b/extensions/eclipse-che-theia-plugin-ext/src/common/che-languages-test-protocol.ts @@ -0,0 +1,104 @@ +/********************************************************************* + * Copyright (c) 2020 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +import { + CancellationToken, + FormattingOptions, + CodeActionContext, + CompletionContext, + SignatureHelpContext, + FoldingContext, + ReferenceContext, + Uri, + CompletionList, + DocumentHighlight, + CodeLens, + DocumentSymbol, + DocumentLink, + ColorInformation, + TextEdit, + Range, + CodeAction, + DefinitionLink, + Definition, + Hover, + Position, + Selection, + SymbolInformation, + FoldingRange, + WorkspaceEdit, + SignatureHelp, + Location +} from '@theia/plugin'; + +// Expose additional API that allows you to know if a language server is connected and build a map of the language servers +export interface CheLanguagesTestAPI { + $provideCompletionItems( + pluginID: string, + resource: Uri, + position: Position, + context: CompletionContext, + token: CancellationToken + ): Promise; + $provideImplementation(pluginID: string, resource: Uri, position: Position, token: CancellationToken): Promise; + $provideTypeDefinition(pluginID: string, resource: Uri, position: Position, token: CancellationToken): Promise; + $provideDefinition(pluginID: string, resource: Uri, position: Position, token: CancellationToken): Promise; + $provideDeclaration(pluginID: string, resource: Uri, position: Position, token: CancellationToken): Promise; + $provideReferences(pluginID: string, resource: Uri, position: Position, context: ReferenceContext, token: CancellationToken): Promise; + $provideSignatureHelp( + pluginID: string, + resource: Uri, + position: Position, + context: SignatureHelpContext, + token: CancellationToken + ): Promise; + $provideHover(pluginID: string, resource: Uri, position: Position, token: CancellationToken): Promise; + $provideDocumentHighlights(pluginID: string, resource: Uri, position: Position, token: CancellationToken): Promise; + $provideDocumentFormattingEdits( + pluginID: string, + resource: Uri, + options: FormattingOptions, + token: CancellationToken + ): Promise; + $provideDocumentRangeFormattingEdits( + pluginID: string, + resource: Uri, + range: Range, + options: FormattingOptions, + token: CancellationToken + ): Promise; + $provideOnTypeFormattingEdits( + pluginID: string, + resource: Uri, + position: Position, + ch: string, + options: FormattingOptions, + token: CancellationToken + ): Promise; + $provideDocumentLinks(pluginID: string, resource: Uri, token: CancellationToken): Promise; + $provideCodeLenses(pluginID: string, resource: Uri, token: CancellationToken): Promise; + $provideCodeActions( + pluginID: string, + resource: Uri, + rangeOrSelection: Range | Selection, + context: CodeActionContext, + token: CancellationToken + ): Promise; + $provideDocumentSymbols(pluginID: string, resource: Uri, token: CancellationToken): Promise; + $provideWorkspaceSymbols(pluginID: string, query: string, token: CancellationToken): Promise; + $provideFoldingRange( + pluginID: string, + resource: Uri, + context: FoldingContext, + token: CancellationToken + ): Promise; + $provideDocumentColors(pluginID: string, resource: Uri, token: CancellationToken): Promise; + $provideRenameEdits(pluginID: string, resource: Uri, position: Position, newName: string, token: CancellationToken): Promise; +} diff --git a/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts b/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts index 03b4bb3e9..797308167 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts @@ -12,6 +12,7 @@ import { che as cheApi } from '@eclipse-che/api'; import * as che from '@eclipse-che/plugin'; import { Event, JsonRpcServer } from '@theia/core'; import { createProxyIdentifier } from '@theia/plugin-ext/lib/common/rpc-protocol'; +import { CheLanguagesTestAPI } from './che-languages-test-protocol'; /** * Workspace plugin API @@ -445,6 +446,8 @@ export const PLUGIN_RPC_CONTEXT = { CHE_SIDERCAR_CONTENT_READER: createProxyIdentifier('CheSideCarContentReader'), CHE_SIDERCAR_CONTENT_READER_MAIN: createProxyIdentifier('CheSideCarContentReaderMain'), + + CHE_LANGUAGES_TEST_API_MAIN: createProxyIdentifier('CheLanguagesTestAPI') }; // Theia RPC protocol diff --git a/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-api.ts b/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-api.ts index c59b3c3e8..d1b598825 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-api.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-api.ts @@ -10,6 +10,7 @@ import { che as cheApi } from '@eclipse-che/api'; import * as che from '@eclipse-che/plugin'; +import * as theia from '@theia/plugin'; import { TaskStatusOptions } from '@eclipse-che/plugin'; import { Plugin } from '@theia/plugin-ext/lib/common/plugin-api-rpc'; import { RPCProtocol } from '@theia/plugin-ext/lib/common/rpc-protocol'; @@ -49,6 +50,8 @@ export function createAPIFactory(rpc: RPCProtocol): CheApiFactory { const cheProductImpl = rpc.set(PLUGIN_RPC_CONTEXT.CHE_PRODUCT, new CheProductImpl(rpc)); const cheTelemetryImpl = rpc.set(PLUGIN_RPC_CONTEXT.CHE_TELEMETRY, new CheTelemetryImpl(rpc)); + const languageTestAPI = rpc.getProxy(PLUGIN_RPC_CONTEXT.CHE_LANGUAGES_TEST_API_MAIN); + return function (plugin: Plugin): typeof che { const workspace: typeof che.workspace = { getCurrentWorkspace(): Promise { @@ -228,6 +231,176 @@ export function createAPIFactory(rpc: RPCProtocol): CheApiFactory { } }; + const languagesTest: typeof che.languages.test = { + + completion( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + context: theia.CompletionContext, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideCompletionItems(pluginID, resource, position, context, token); + }, + implementation( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideImplementation(pluginID, resource, position, token); + }, + typeDefinition( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideTypeDefinition(pluginID, resource, position, token); + }, + definition( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideDefinition(pluginID, resource, position, token); + }, + declaration( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideDeclaration(pluginID, resource, position, token); + }, + references( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + context: theia.ReferenceContext, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideReferences(pluginID, resource, position, context, token); + }, + signatureHelp( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + context: theia.SignatureHelpContext, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideSignatureHelp(pluginID, resource, position, context, token); + }, + hover(pluginID: string, + resource: theia.Uri, + position: theia.Position, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideHover(pluginID, resource, position, token); + }, + documentHighlights( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideDocumentHighlights(pluginID, resource, position, token); + }, + documentFormattingEdits( + pluginID: string, + resource: theia.Uri, + options: theia.FormattingOptions, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideDocumentFormattingEdits(pluginID, resource, options, token); + }, + documentRangeFormattingEdits( + pluginID: string, + resource: theia.Uri, + range: theia.Range, + options: theia.FormattingOptions, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideDocumentRangeFormattingEdits(pluginID, resource, range, options, token); + }, + onTypeFormattingEdits( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + ch: string, + options: theia.FormattingOptions, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideOnTypeFormattingEdits(pluginID, resource, position, ch, options, token); + }, + documentLinks( + pluginID: string, + resource: theia.Uri, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideDocumentLinks(pluginID, resource, token); + }, + codeLenses( + pluginID: string, + resource: theia.Uri, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideCodeLenses(pluginID, resource, token); + }, + codeActions( + pluginID: string, + resource: theia.Uri, + rangeOrSelection: theia.Range | theia.Selection, + context: theia.CodeActionContext, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideCodeActions(pluginID, resource, rangeOrSelection, context, token); + }, + documentSymbols( + pluginID: string, + resource: theia.Uri, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideDocumentSymbols(pluginID, resource, token); + }, + workspaceSymbols( + pluginID: string, + query: string, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideWorkspaceSymbols(pluginID, query, token); + }, + foldingRange( + pluginID: string, + resource: theia.Uri, + context: theia.FoldingContext, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideFoldingRange(pluginID, resource, context, token); + }, + documentColors( + pluginID: string, + resource: theia.Uri, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideDocumentColors(pluginID, resource, token); + }, + renameEdits( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + newName: string, + token: theia.CancellationToken + ): Promise { + return languageTestAPI.$provideRenameEdits(pluginID, resource, position, newName, token); + } + }; + + const languages: typeof che.languages = { + test: languagesTest + }; + return { workspace, factory, @@ -242,7 +415,8 @@ export function createAPIFactory(rpc: RPCProtocol): CheApiFactory { oAuth, telemetry, TaskStatus, - TaskTerminallKind + TaskTerminallKind, + languages }; }; diff --git a/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts b/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts index c1648b429..9b2926c95 100644 --- a/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts +++ b/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts @@ -301,4 +301,132 @@ declare module '@eclipse-che/plugin' { export let links: LinkMap; } + export namespace languages { + export namespace test { + export function completion( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + context: theia.CompletionContext, + token: theia.CancellationToken + ): Promise; + export function implementation( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + token: theia.CancellationToken + ): Promise; + export function typeDefinition( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + token: theia.CancellationToken + ): Promise; + export function definition( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + token: theia.CancellationToken + ): Promise; + export function declaration( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + token: theia.CancellationToken + ): Promise; + export function references( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + context: theia.ReferenceContext, + token: theia.CancellationToken + ): Promise; + export function signatureHelp( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + context: theia.SignatureHelpContext, + token: theia.CancellationToken + ): Promise; + export function hover( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + token: theia.CancellationToken + ): Promise; + export function documentHighlights( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + token: theia.CancellationToken + ): Promise; + export function documentFormattingEdits( + pluginID: string, + resource: theia.Uri, + options: theia.FormattingOptions, + token: theia.CancellationToken + ): Promise; + export function documentRangeFormattingEdits( + pluginID: string, + resource: theia.Uri, + range: theia.Range, + options: theia.FormattingOptions, + token: theia.CancellationToken + ): Promise; + export function onTypeFormattingEdits( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + ch: string, + options: theia.FormattingOptions, + token: theia.CancellationToken + ): Promise; + export function documentLinks( + pluginID: string, + resource: theia.Uri, + token: theia.CancellationToken + ): Promise; + export function codeLenses( + pluginID: string, + resource: theia.Uri, + token: theia.CancellationToken + ): Promise; + export function codeActions( + pluginID: string, + resource: theia.Uri, + rangeOrSelection: theia.Range | theia.Selection, + context: theia.CodeActionContext, + token: theia.CancellationToken + ): Promise; + export function documentSymbols( + pluginID: string, + resource: theia.Uri, + token: theia.CancellationToken + ): Promise; + export function workspaceSymbols( + pluginID: string, + query: string, + token: theia.CancellationToken + ): Promise; + export function foldingRange( + pluginID: string, + resource: theia.Uri, + context: theia.FoldingContext, + token: theia.CancellationToken + ): Promise; + export function documentColors( + pluginID: string, + resource: theia.Uri, + token: theia.CancellationToken + ): Promise; + export function renameEdits( + pluginID: string, + resource: theia.Uri, + position: theia.Position, + newName: string, + token: theia.CancellationToken + ): Promise; + } + } + } diff --git a/plugins/task-plugin/src/machine/machines-picker.ts b/plugins/task-plugin/src/machine/machines-picker.ts index 19a3ae62b..8e1e8f474 100644 --- a/plugins/task-plugin/src/machine/machines-picker.ts +++ b/plugins/task-plugin/src/machine/machines-picker.ts @@ -47,7 +47,8 @@ export class MachinesPicker { window.showQuickPick(items, { placeHolder: CONTAINERS_PLACE_HOLDER, ignoreFocusOut: false, - onDidSelectItem: (item => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onDidSelectItem: ((item: any) => { resolve((item).label); }) });