diff --git a/.vscode/launch.json b/.vscode/launch.json index 15487642dae12..573753853bb5e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -61,7 +61,8 @@ "--no-app-auto-install" ], "env": { - "NODE_ENV": "development" + "NODE_ENV": "development", + "THEIA_DEFAULT_PLUGINS": "local-dir:plugins" }, "sourceMaps": true, "outFiles": [ diff --git a/examples/browser/package.json b/examples/browser/package.json index 6596b1343b8b8..d46df94521090 100644 --- a/examples/browser/package.json +++ b/examples/browser/package.json @@ -59,7 +59,7 @@ "clean": "theia clean && rimraf errorShots", "build": "theia build --mode development", "watch": "yarn build --watch", - "start": "theia start", + "start": "export THEIA_DEFAULT_PLUGINS=local-dir:../../plugins && theia start", "start:debug": "yarn start --log-level=debug", "test": "wdio wdio.conf.js", "test-non-headless": "wdio wdio-non-headless.conf.js", diff --git a/packages/monaco/package.json b/packages/monaco/package.json index beea1cda544f9..ef7ef36ba4c3c 100644 --- a/packages/monaco/package.json +++ b/packages/monaco/package.json @@ -10,6 +10,7 @@ "@theia/markers": "^0.3.18", "@theia/outline-view": "^0.3.18", "@theia/workspace": "^0.3.18", + "jsonc-parser": "^2.0.2", "monaco-css": "^2.0.1", "monaco-html": "^2.0.2", "onigasm": "^2.1.0", diff --git a/packages/monaco/src/browser/monaco-frontend-application-contribution.ts b/packages/monaco/src/browser/monaco-frontend-application-contribution.ts index 1b218fee56026..419294f9d5fb6 100644 --- a/packages/monaco/src/browser/monaco-frontend-application-contribution.ts +++ b/packages/monaco/src/browser/monaco-frontend-application-contribution.ts @@ -17,6 +17,7 @@ import { injectable, inject } from 'inversify'; import { FrontendApplicationContribution } from '@theia/core/lib/browser'; import { ThemeService } from '@theia/core/lib/browser/theming'; +import { MonacoSnippetSuggestProvider } from './monaco-snippet-suggest-provider'; @injectable() export class MonacoFrontendApplicationContribution implements FrontendApplicationContribution { @@ -24,10 +25,15 @@ export class MonacoFrontendApplicationContribution implements FrontendApplicatio @inject(ThemeService) protected readonly themeService: ThemeService; + @inject(MonacoSnippetSuggestProvider) + protected readonly snippetSuggestProvider: MonacoSnippetSuggestProvider; + async initialize() { const currentTheme = this.themeService.getCurrentTheme(); this.changeTheme(currentTheme.editorTheme); this.themeService.onThemeChange(event => this.changeTheme(event.newTheme.editorTheme)); + + monaco.suggest.setSnippetSuggestSupport(this.snippetSuggestProvider); } protected changeTheme(editorTheme: string | undefined) { diff --git a/packages/monaco/src/browser/monaco-frontend-module.ts b/packages/monaco/src/browser/monaco-frontend-module.ts index ad54af6d277f2..ecabd08e36a64 100644 --- a/packages/monaco/src/browser/monaco-frontend-module.ts +++ b/packages/monaco/src/browser/monaco-frontend-module.ts @@ -14,6 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import '../../src/browser/style/index.css'; +import '../../src/browser/style/symbol-sprite.svg'; +import '../../src/browser/style/symbol-icons.css'; + import { ContainerModule, decorate, injectable } from 'inversify'; import { MenuContribution, CommandContribution } from '@theia/core/lib/common'; import { QuickOpenService, FrontendApplicationContribution, KeybindingContribution } from '@theia/core/lib/browser'; @@ -43,17 +47,15 @@ import MonacoTextmateModuleBinder from './textmate/monaco-textmate-frontend-bind import { MonacoSemanticHighlightingService } from './monaco-semantic-highlighting-service'; import { SemanticHighlightingService } from '@theia/editor/lib/browser/semantic-highlight/semantic-highlighting-service'; import { MonacoBulkEditService } from './monaco-bulk-edit-service'; +import { MonacoOutlineDecorator } from './monaco-outline-decorator'; +import { OutlineTreeDecorator } from '@theia/outline-view/lib/browser/outline-decorator-service'; +import { MonacoSnippetSuggestProvider } from './monaco-snippet-suggest-provider'; decorate(injectable(), MonacoToProtocolConverter); decorate(injectable(), ProtocolToMonacoConverter); -import '../../src/browser/style/index.css'; -import '../../src/browser/style/symbol-sprite.svg'; -import '../../src/browser/style/symbol-icons.css'; -import { MonacoOutlineDecorator } from './monaco-outline-decorator'; -import { OutlineTreeDecorator } from '@theia/outline-view/lib/browser/outline-decorator-service'; - export default new ContainerModule((bind, unbind, isBound, rebind) => { + bind(MonacoSnippetSuggestProvider).toSelf().inSingletonScope(); bind(FrontendApplicationContribution).to(MonacoFrontendApplicationContribution).inSingletonScope(); bind(MonacoToProtocolConverter).toSelf().inSingletonScope(); diff --git a/packages/monaco/src/browser/monaco-snippet-suggest-provider.ts b/packages/monaco/src/browser/monaco-snippet-suggest-provider.ts new file mode 100644 index 0000000000000..04f9e8abfe75c --- /dev/null +++ b/packages/monaco/src/browser/monaco-snippet-suggest-provider.ts @@ -0,0 +1,195 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import * as jsoncparser from 'jsonc-parser'; +import { injectable, inject } from 'inversify'; +import URI from '@theia/core/lib/common/uri'; +import { FileSystem, FileSystemError } from '@theia/filesystem/lib/common'; + +@injectable() +export class MonacoSnippetSuggestProvider implements monaco.modes.ISuggestSupport { + + @inject(FileSystem) + protected readonly filesystem: FileSystem; + + protected readonly snippets = new Map(); + protected readonly pendingSnippets = new Map[]>(); + + async provideCompletionItems(model: monaco.editor.ITextModel): Promise { + const languageId = model.getModeId(); // TODO: look up a language id at the position + await this.loadSnippets(languageId); + const suggestions = this.snippets.get(languageId) || []; + return { suggestions }; + } + + resolveCompletionItem(_: monaco.editor.ITextModel, __: monaco.Position, item: monaco.modes.ISuggestion): monaco.modes.ISuggestion { + return item instanceof MonacoSnippetSuggestion ? item.resolve() : item; + } + + protected async loadSnippets(scope: string): Promise { + const pending: Promise[] = []; + pending.push(...(this.pendingSnippets.get(scope) || [])); + pending.push(...(this.pendingSnippets.get('*') || [])); + if (pending.length) { + await Promise.all(pending); + } + } + + fromURI(uri: string | URI, options: SnippetLoadOptions): Promise { + const pending = this.loadURI(uri, options); + const { language } = options; + const scopes = Array.isArray(language) ? language : !!language ? [language] : ['*']; + for (const scope of scopes) { + const pendingSnippets = this.pendingSnippets.get(scope) || []; + pendingSnippets.push(pending); + this.pendingSnippets.set(scope, pendingSnippets); + } + return pending; + } + /** + * should NOT throw to prevent load erros on suggest + */ + protected async loadURI(uri: string | URI, options: SnippetLoadOptions): Promise { + try { + const { content } = await this.filesystem.resolveContent(uri.toString(), { encoding: 'utf-8' }); + const snippets = content && jsoncparser.parse(content, undefined, { disallowComments: false }); + this.fromJSON(snippets, options); + } catch (e) { + if (!FileSystemError.FileNotFound.is(e) && !FileSystemError.FileIsDirectory.is(e)) { + console.error(e); + } + } + } + + fromJSON(snippets: JsonSerializedSnippets | undefined, { language, source }: SnippetLoadOptions): void { + this.parseSnippets(snippets, (name, snippet) => { + let { prefix, body, description } = snippet; + if (Array.isArray(body)) { + body = body.join('\n'); + } + if (typeof prefix !== 'string' || typeof body !== 'string') { + return; + } + const scopes: string[] = []; + if (language) { + if (Array.isArray(language)) { + scopes.push(...language); + } else { + scopes.push(language); + } + } else if (typeof snippet.scope === 'string') { + for (const rawScope of snippet.scope.split(',')) { + const scope = rawScope.trim(); + if (scope) { + scopes.push(scope); + } + } + } + this.push({ + scopes, + name, + prefix, + description, + body, + source + }); + }); + } + protected parseSnippets(snippets: JsonSerializedSnippets | undefined, accept: (name: string, snippet: JsonSerializedSnippet) => void): void { + if (typeof snippets === 'object') { + // tslint:disable-next-line:forin + for (const name in snippets) { + const scopeOrTemplate = snippets[name]; + if (JsonSerializedSnippet.is(scopeOrTemplate)) { + accept(name, scopeOrTemplate); + } else { + this.parseSnippets(scopeOrTemplate, accept); + } + } + } + } + + push(...snippets: Snippet[]): void { + for (const snippet of snippets) { + for (const scope of snippet.scopes) { + const languageSnippets = this.snippets.get(scope) || []; + languageSnippets.push(new MonacoSnippetSuggestion(snippet)); + this.snippets.set(scope, languageSnippets); + } + } + } + +} + +export interface SnippetLoadOptions { + language?: string | string[] + source: string +} + +export interface JsonSerializedSnippets { + [name: string]: JsonSerializedSnippet | { [name: string]: JsonSerializedSnippet }; +} +export interface JsonSerializedSnippet { + body: string | string[]; + scope: string; + prefix: string; + description: string; +} +export namespace JsonSerializedSnippet { + export function is(obj: Object | undefined): obj is JsonSerializedSnippet { + return typeof obj === 'object' && 'body' in obj && 'prefix' in obj; + } +} + +export interface Snippet { + readonly scopes: string[] + readonly name: string + readonly prefix: string + readonly description: string + readonly body: string + readonly source: string +} + +export class MonacoSnippetSuggestion implements monaco.modes.ISuggestion { + + readonly label: string; + readonly detail: string; + readonly sortText: string; + readonly noAutoAccept = true; + readonly type: 'snippet' = 'snippet'; + readonly snippetType: 'textmate' = 'textmate'; + + insertText: string; + documentation?: monaco.IMarkdownString; + + constructor(protected readonly snippet: Snippet) { + this.label = snippet.prefix; + this.detail = `${snippet.description || snippet.name} (${snippet.source})`; + this.insertText = snippet.body; + this.sortText = `z-${snippet.prefix}`; + } + + protected resolved = false; + resolve(): MonacoSnippetSuggestion { + if (!this.resolved) { + const codeSnippet = new monaco.snippetParser.SnippetParser().parse(this.snippet.body).toString(); + this.insertText = codeSnippet; + this.documentation = { value: '```\n' + codeSnippet + '```' }; + this.resolved = true; + } + return this; + } + +} diff --git a/packages/monaco/src/browser/textmate/textmate-snippet-completion-provider.ts b/packages/monaco/src/browser/textmate/textmate-snippet-completion-provider.ts index a0afc3a994a26..a675197aabb07 100644 --- a/packages/monaco/src/browser/textmate/textmate-snippet-completion-provider.ts +++ b/packages/monaco/src/browser/textmate/textmate-snippet-completion-provider.ts @@ -14,6 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/** + * @deprecated use MonacoSnippetSuggestProvider instead + */ export class TextmateSnippetCompletionProvider implements monaco.languages.CompletionItemProvider { private items: monaco.languages.CompletionItem[]; @@ -42,17 +45,23 @@ export class TextmateSnippetCompletionProvider implements monaco.languages.Compl } provideCompletionItems(document: monaco.editor.ITextModel, - position: monaco.Position, - token: monaco.CancellationToken, - context: monaco.languages.CompletionContext): monaco.languages.CompletionItem[] { + position: monaco.Position, + token: monaco.CancellationToken, + context: monaco.languages.CompletionContext): monaco.languages.CompletionItem[] { return this.items; } } +/** + * @deprecated use JsonSerializedSnippets & MonacoSnippetSuggestProvider instead + */ export interface TextmateSnippets { [name: string]: TextmateSnippet; } +/** + * @deprecated use JsonSerializedSnippet & MonacoSnippetSuggestProvider instead + */ export interface TextmateSnippet { readonly prefix: string, readonly body: string[], diff --git a/packages/monaco/src/typings/monaco/index.d.ts b/packages/monaco/src/typings/monaco/index.d.ts index a70158708f662..e1ef121f89abf 100644 --- a/packages/monaco/src/typings/monaco/index.d.ts +++ b/packages/monaco/src/typings/monaco/index.d.ts @@ -903,6 +903,8 @@ declare module monaco.suggest { token?: monaco.CancellationToken ): Promise; + export function setSnippetSuggestSupport(support: monaco.modes.ISuggestSupport): monaco.modes.ISuggestSupport; + } declare module monaco.suggestController { diff --git a/packages/plugin-ext-vscode/src/node/plugin-vscode-directory-handler.ts b/packages/plugin-ext-vscode/src/node/plugin-vscode-directory-handler.ts index 540bef30f69ff..d4b36e0904c76 100644 --- a/packages/plugin-ext-vscode/src/node/plugin-vscode-directory-handler.ts +++ b/packages/plugin-ext-vscode/src/node/plugin-vscode-directory-handler.ts @@ -14,73 +14,66 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import * as fs from 'fs'; +import * as path from 'path'; +import { injectable } from 'inversify'; +import { RecursivePartial } from '@theia/core'; import { PluginDeployerDirectoryHandler, - PluginDeployerEntry, PluginPackage, PluginDeployerDirectoryHandlerContext, - PluginDeployerEntryType + PluginDeployerEntry, PluginDeployerDirectoryHandlerContext, + PluginDeployerEntryType, PluginPackage } from '@theia/plugin-ext'; -import { injectable } from 'inversify'; -import * as fs from 'fs'; -import * as path from 'path'; @injectable() export class PluginVsCodeDirectoryHandler implements PluginDeployerDirectoryHandler { - accept(resolvedPlugin: PluginDeployerEntry): boolean { - - console.log('PluginVsCodeDirectoryHandler: accepting plugin with path', resolvedPlugin.path()); + accept(plugin: PluginDeployerEntry): boolean { + console.debug(`Resolving "${plugin.id()}" as a VS Code extension...`); + return this.resolvePackage(plugin) || this.resolveFromSources(plugin) || this.resolveFromVSIX(plugin); + } - // handle only directories - if (resolvedPlugin.isFile()) { - return false; - } + async handle(context: PluginDeployerDirectoryHandlerContext): Promise { + context.pluginEntry().accept(PluginDeployerEntryType.BACKEND); + } - // is there a extension.vsixmanifest and extension folder - const extensionVsixManifestPath = path.resolve(resolvedPlugin.path(), 'extension.vsixmanifest'); - const existsExtensionVsixManifest: boolean = fs.existsSync(extensionVsixManifestPath); - if (!existsExtensionVsixManifest) { - return false; - } + protected resolveFromSources(plugin: PluginDeployerEntry): boolean { + const pluginPath = plugin.path(); + return this.resolvePackage(plugin, { pluginPath, pck: this.requirePackage(pluginPath) }); + } - const extensionPath = path.resolve(resolvedPlugin.path(), 'extension'); - const existsExtension: boolean = fs.existsSync(extensionPath); - if (!existsExtension) { + protected resolveFromVSIX(plugin: PluginDeployerEntry): boolean { + if (!fs.existsSync(path.join(plugin.path(), 'extension.vsixmanifest'))) { return false; } + const pluginPath = path.join(plugin.path(), 'extension'); + return this.resolvePackage(plugin, { pluginPath, pck: this.requirePackage(pluginPath) }); + } - // is there a package.json ? - const packageJsonPath = path.resolve(extensionPath, 'package.json'); - const existsPackageJson: boolean = fs.existsSync(packageJsonPath); - if (!existsPackageJson) { + protected resolvePackage(plugin: PluginDeployerEntry, options?: { + pluginPath: string + pck?: RecursivePartial + }): boolean { + const { pluginPath, pck } = options || { + pluginPath: plugin.path(), + pck: plugin.getValue('package.json') + }; + if (!pck || !pck.name || !pck.version || !pck.engines || !pck.engines.vscode) { return false; } - - let packageJson: PluginPackage = resolvedPlugin.getValue('package.json'); - if (!packageJson) { - packageJson = require(packageJsonPath); - resolvedPlugin.storeValue('package.json', packageJson); - } - - if (!packageJson.engines) { - return false; + if (options) { + plugin.storeValue('package.json', pck); + plugin.updatePath(pluginPath); } + console.log(`Resolved "${plugin.id()}" to a VS Code extension "${pck.name}@${pck.version}" with engines:`, pck.engines); + return true; + } - if (packageJson.engines && packageJson.engines.vscode) { - console.log('accepting packagejson with engines', packageJson.engines); - return true; + protected requirePackage(pluginPath: string): PluginPackage | undefined { + try { + return require(path.join(pluginPath, 'package.json')); + } catch { + return undefined; } - - return false; - } - // tslint:disable-next-line:no-any - handle(context: PluginDeployerDirectoryHandlerContext): Promise { - context.pluginEntry().accept(PluginDeployerEntryType.BACKEND); - - const extensionPath = path.resolve(context.pluginEntry().path(), 'extension'); - context.pluginEntry().updatePath(extensionPath); - - return Promise.resolve(true); - } } diff --git a/packages/plugin-ext/src/common/plugin-protocol.ts b/packages/plugin-ext/src/common/plugin-protocol.ts index c43d027ee2681..41268bd786674 100644 --- a/packages/plugin-ext/src/common/plugin-protocol.ts +++ b/packages/plugin-ext/src/common/plugin-protocol.ts @@ -62,6 +62,7 @@ export interface PluginPackageContribution { menus?: { [location: string]: PluginPackageMenu[] }; keybindings?: PluginPackageKeybinding[]; debuggers?: PluginPackageDebuggersContribution[]; + snippets: PluginPackageSnippetsContribution[]; } export interface PluginPackageViewContainer { @@ -100,6 +101,11 @@ export interface ScopeMap { [scopeName: string]: string; } +export interface PluginPackageSnippetsContribution { + language?: string; + path?: string; +} + export interface PlatformSpecificAdapterContribution { program?: string; args?: string[]; @@ -337,6 +343,13 @@ export interface PluginContribution { menus?: { [location: string]: Menu[] }; keybindings?: Keybinding[]; debuggers?: DebuggerContribution[]; + snippets?: SnippetContribution[]; +} + +export interface SnippetContribution { + uri: string + source: string + language?: string } export interface GrammarsContribution { diff --git a/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts b/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts index e4593709adafb..453a8a1d15d7e 100644 --- a/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts +++ b/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts @@ -38,7 +38,8 @@ import { Menu, PluginPackageMenu, PluginPackageDebuggersContribution, - DebuggerContribution + DebuggerContribution, + SnippetContribution } from '../../../common/plugin-protocol'; import * as fs from 'fs'; import * as path from 'path'; @@ -48,6 +49,7 @@ import { CharacterPair } from '../../../api/plugin-api'; import * as jsoncparser from 'jsonc-parser'; import { IJSONSchema } from '@theia/core/lib/common/json-schema'; import { deepClone } from '@theia/core/lib/common/objects'; +import { FileUri } from '@theia/core/lib/node/file-uri'; namespace nls { export function localize(key: string, _default: string) { @@ -168,9 +170,40 @@ export class TheiaPluginScanner implements PluginScanner { contributions.debuggers = debuggers; } + contributions.snippets = this.readSnippets(rawPlugin); return contributions; } + protected readSnippets(pck: PluginPackage): SnippetContribution[] | undefined { + if (!pck.contributes || !pck.contributes.snippets) { + return undefined; + } + const result: SnippetContribution[] = []; + for (const contribution of pck.contributes.snippets) { + if (contribution.path) { + result.push({ + language: contribution.language, + source: pck.displayName || pck.name, + uri: FileUri.create(path.join(pck.packagePath, contribution.path)).toString() + }); + } + } + return result; + } + + protected readJson(filePath: string): T | undefined { + const content = this.readFileSync(filePath); + return content ? jsoncparser.parse(content, undefined, { disallowComments: false }) : undefined; + } + protected readFileSync(filePath: string): string { + try { + return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : ''; + } catch (e) { + console.error(e); + return ''; + } + } + // tslint:disable-next-line:no-any private readConfiguration(rawConfiguration: any, pluginPath: string): any { return { @@ -244,11 +277,8 @@ export class TheiaPluginScanner implements PluginScanner { mimetypes: rawLang.mimetypes }; if (rawLang.configuration) { - const conf = fs.readFileSync(path.resolve(pluginPath, rawLang.configuration), 'utf8'); - if (conf) { - const strippedContent = jsoncparser.stripComments(conf); - const rawConfiguration: PluginPackageLanguageContributionConfiguration = jsoncparser.parse(strippedContent); - + const rawConfiguration = this.readJson(path.resolve(pluginPath, rawLang.configuration)); + if (rawConfiguration) { const configuration: LanguageConfiguration = { brackets: rawConfiguration.brackets, comments: rawConfiguration.comments, diff --git a/packages/plugin-ext/src/main/browser/plugin-contribution-handler.ts b/packages/plugin-ext/src/main/browser/plugin-contribution-handler.ts index 9cffb1e743f86..0a86d0eb635cd 100644 --- a/packages/plugin-ext/src/main/browser/plugin-contribution-handler.ts +++ b/packages/plugin-ext/src/main/browser/plugin-contribution-handler.ts @@ -23,6 +23,7 @@ import { PluginContribution, IndentationRules, FoldingRules, ScopeMap } from '.. import { PreferenceSchemaProvider } from '@theia/core/lib/browser'; import { PreferenceSchema } from '@theia/core/lib/browser/preferences'; import { KeybindingsContributionPointHandler } from './keybindings/keybindings-contribution-handler'; +import { MonacoSnippetSuggestProvider } from '@theia/monaco/lib/browser/monaco-snippet-suggest-provider'; @injectable() export class PluginContributionHandler { @@ -47,6 +48,9 @@ export class PluginContributionHandler { @inject(KeybindingsContributionPointHandler) private readonly keybindingsContributionHandler: KeybindingsContributionPointHandler; + @inject(MonacoSnippetSuggestProvider) + protected readonly snippetSuggestProvider: MonacoSnippetSuggestProvider; + handleContributions(contributions: PluginContribution): void { if (contributions.configuration) { this.updateConfigurationSchema(contributions.configuration); @@ -131,6 +135,14 @@ export class PluginContributionHandler { this.menusContributionHandler.handle(contributions); this.keybindingsContributionHandler.handle(contributions); + if (contributions.snippets) { + for (const snippet of contributions.snippets) { + this.snippetSuggestProvider.fromURI(snippet.uri, { + language: snippet.language, + source: snippet.source + }); + } + } } private updateConfigurationSchema(schema: PreferenceSchema): void { diff --git a/packages/plugin-ext/src/main/node/resolvers/plugin-local-dir-resolver.ts b/packages/plugin-ext/src/main/node/resolvers/plugin-local-dir-resolver.ts index 05bee15ea17fb..d20de1a3cbb11 100644 --- a/packages/plugin-ext/src/main/node/resolvers/plugin-local-dir-resolver.ts +++ b/packages/plugin-ext/src/main/node/resolvers/plugin-local-dir-resolver.ts @@ -35,7 +35,10 @@ export class LocalDirectoryPluginDeployerResolver implements PluginDeployerResol return; } // remove prefix - const dirPath = localDirSetting.substring('local-dir'.length + 1); + let dirPath = localDirSetting.substring('local-dir'.length + 1); + if (!path.isAbsolute(dirPath)) { + dirPath = path.resolve(process.cwd(), dirPath); + } // check directory exists if (!fs.existsSync(dirPath)) { diff --git a/packages/typescript/src/browser/typescript-language-config.ts b/packages/typescript/src/browser/typescript-language-config.ts index 2697866c5404d..b957f57832b90 100644 --- a/packages/typescript/src/browser/typescript-language-config.ts +++ b/packages/typescript/src/browser/typescript-language-config.ts @@ -15,13 +15,16 @@ ********************************************************************************/ import { TYPESCRIPT_LANGUAGE_ID, TYPESCRIPT_REACT_LANGUAGE_ID, TYPESCRIPT_LANGUAGE_NAME, TYPESCRIPT_REACT_LANGUAGE_NAME } from '../common'; -import { injectable } from 'inversify'; +import { injectable, inject } from 'inversify'; import { LanguageGrammarDefinitionContribution, TextmateRegistry } from '@theia/monaco/lib/browser/textmate'; -import { TextmateSnippetCompletionProvider } from '@theia/monaco/lib/browser/textmate/textmate-snippet-completion-provider'; +import { MonacoSnippetSuggestProvider } from '@theia/monaco/lib/browser/monaco-snippet-suggest-provider'; @injectable() export class TypescriptGrammarContribution implements LanguageGrammarDefinitionContribution { + @inject(MonacoSnippetSuggestProvider) + protected readonly snippetSuggestProvider: MonacoSnippetSuggestProvider; + registerTextmateLanguage(registry: TextmateRegistry) { this.registerTypeScript(); this.registerSnippets(); @@ -58,10 +61,12 @@ export class TypescriptGrammarContribution implements LanguageGrammarDefinitionC registry.mapLanguageIdToTextmateGrammar(TYPESCRIPT_REACT_LANGUAGE_ID, 'source.tsx'); } - protected registerSnippets() { + protected registerSnippets(): void { const snippets = require('../../data/snippets/typescript.json'); - monaco.languages.registerCompletionItemProvider(TYPESCRIPT_LANGUAGE_ID, new TextmateSnippetCompletionProvider(snippets, 'ts')); - monaco.languages.registerCompletionItemProvider(TYPESCRIPT_REACT_LANGUAGE_ID, new TextmateSnippetCompletionProvider(snippets, 'ts')); + this.snippetSuggestProvider.fromJSON(snippets, { + language: [TYPESCRIPT_LANGUAGE_ID, TYPESCRIPT_REACT_LANGUAGE_ID], + source: 'TypeScript Language' + }); } protected registerTypeScript() { diff --git a/yarn.lock b/yarn.lock index abaef30ef3769..8eaa5f8b3338f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5569,6 +5569,7 @@ jsonc-parser@^2.0.0-next.1, jsonc-parser@^2.0.1: jsonc-parser@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.0.2.tgz#42fcf56d70852a043fadafde51ddb4a85649978d" + integrity sha512-TSU435K5tEKh3g7bam1AFf+uZrISheoDsLlpmAo6wWZYqjsnd09lHYK1Qo+moK4Ikifev1Gdpa69g4NELKnCrQ== jsonfile@^2.1.0: version "2.4.0"