diff --git a/packages/plugin-ext/src/common/plugin-protocol.ts b/packages/plugin-ext/src/common/plugin-protocol.ts index e0880ed8bea8c..42547db5ca91d 100644 --- a/packages/plugin-ext/src/common/plugin-protocol.ts +++ b/packages/plugin-ext/src/common/plugin-protocol.ts @@ -103,14 +103,14 @@ export interface PlatformSpecificAdapterContribution { * This interface describes a package.json debuggers contribution section object. */ export interface PluginPackageDebuggersContribution extends PlatformSpecificAdapterContribution { - type: string, - label?: string, - languages?: string[], - enableBreakpointsFor?: { languageIds: string[] }, - configurationAttributes: { [request: string]: IJSONSchema }, - configurationSnippets: IJSONSchemaSnippet[], - variables?: ScopeMap, - adapterExecutableCommand?: string + type: string; + label?: string; + languages?: string[]; + enableBreakpointsFor?: { languageIds: string[] }; + configurationAttributes: { [request: string]: IJSONSchema }; + configurationSnippets: IJSONSchemaSnippet[]; + variables?: ScopeMap; + adapterExecutableCommand?: string; win?: PlatformSpecificAdapterContribution; winx86?: PlatformSpecificAdapterContribution; windows?: PlatformSpecificAdapterContribution; @@ -330,6 +330,7 @@ export interface PluginContribution { viewsContainers?: { [location: string]: ViewContainer[] }; views?: { [location: string]: View[] }; menus?: { [location: string]: Menu[] }; + debuggers?: DebuggerContribution[]; } export interface GrammarsContribution { @@ -366,6 +367,27 @@ export interface LanguageConfiguration { wordPattern?: string; } +/** + * This interface describes a package.json debuggers contribution section object. + */ +export interface DebuggerContribution extends PlatformSpecificAdapterContribution { + type: string, + label?: string, + languages?: string[], + enableBreakpointsFor?: { + languageIds: string[] + }, + configurationAttributes?: IJSONSchema[], + configurationSnippets?: IJSONSchemaSnippet[], + variables?: ScopeMap, + adapterExecutableCommand?: string + win?: PlatformSpecificAdapterContribution; + winx86?: PlatformSpecificAdapterContribution; + windows?: PlatformSpecificAdapterContribution; + osx?: PlatformSpecificAdapterContribution; + linux?: PlatformSpecificAdapterContribution; +} + export interface IndentationRules { increaseIndentPattern: string; decreaseIndentPattern: string; diff --git a/packages/plugin-ext/src/hosted/node/plugin-reader.ts b/packages/plugin-ext/src/hosted/node/plugin-reader.ts index 1cbe426ab3e0e..9dc623f6662bd 100644 --- a/packages/plugin-ext/src/hosted/node/plugin-reader.ts +++ b/packages/plugin-ext/src/hosted/node/plugin-reader.ts @@ -75,7 +75,10 @@ export class HostedPluginReader implements BackendApplicationContribution { return undefined; } - const plugin: PluginPackage = require(packageJsonPath); + let rawData = fs.readFileSync(packageJsonPath).toString(); + rawData = this.localize(rawData, path); + + const plugin: PluginPackage = JSON.parse(rawData); plugin.packagePath = path; const pluginMetadata = this.scanner.getPluginMetadata(plugin); if (pluginMetadata.model.entryPoint.backend) { @@ -95,6 +98,21 @@ export class HostedPluginReader implements BackendApplicationContribution { return pluginMetadata; } + private localize(rawData: string, pluginPath: string): string { + const nlsPath = pluginPath + 'package.nls.json'; + if (fs.existsSync(nlsPath)) { + const nlsMap: { + [key: string]: string + } = require(nlsPath); + for (const key of Object.keys(nlsMap)) { + const value = nlsMap[key].replace(/\"/g, '\\"'); + rawData = rawData.split('%' + key + '%').join(value); + } + } + + return rawData; + } + getPlugin(): PluginMetadata | undefined { return this.plugin; } 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 5f7b6c0234043..1a3d22a89dc50 100644 --- a/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts +++ b/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts @@ -34,7 +34,9 @@ import { View, PluginPackageView, Menu, - PluginPackageMenu + PluginPackageMenu, + PluginPackageDebuggersContribution, + DebuggerContribution } from '../../../common/plugin-protocol'; import * as fs from 'fs'; import * as path from 'path'; @@ -42,6 +44,20 @@ import { isObject } from 'util'; import { GrammarsReader } from './grammars-reader'; 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'; + +namespace nls { + export function localize(key: string, _default: string) { + return _default; + } +} + +const INTERNAL_CONSOLE_OPTIONS_SCHEMA = { + enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart'], + default: 'openOnFirstSessionStart', + description: nls.localize('internalConsoleOptions', 'Controls when the internal debug console should open.') +}; @injectable() export class TheiaPluginScanner implements PluginScanner { @@ -141,6 +157,11 @@ export class TheiaPluginScanner implements PluginScanner { }); } + if (rawPlugin.contributes!.debuggers) { + const debuggers = this.readDebuggers(rawPlugin.contributes.debuggers!); + contributions.debuggers = debuggers; + } + return contributions; } @@ -229,6 +250,115 @@ export class TheiaPluginScanner implements PluginScanner { } + private readDebuggers(rawDebuggers: PluginPackageDebuggersContribution[]): DebuggerContribution[] { + return rawDebuggers.map(rawDebug => this.readDebugger(rawDebug)); + } + + private readDebugger(rawDebugger: PluginPackageDebuggersContribution): DebuggerContribution { + const result: DebuggerContribution = { + type: rawDebugger.type, + label: rawDebugger.label, + languages: rawDebugger.languages, + enableBreakpointsFor: rawDebugger.enableBreakpointsFor, + variables: rawDebugger.variables, + adapterExecutableCommand: rawDebugger.adapterExecutableCommand, + configurationSnippets: rawDebugger.configurationSnippets, + win: rawDebugger.win, + winx86: rawDebugger.winx86, + windows: rawDebugger.windows, + osx: rawDebugger.osx, + linux: rawDebugger.linux, + program: rawDebugger.program, + args: rawDebugger.args, + runtime: rawDebugger.runtime, + runtimeArgs: rawDebugger.runtimeArgs + }; + + result.configurationAttributes = rawDebugger.configurationAttributes + && this.resolveSchemaAttributes(rawDebugger.type, rawDebugger.configurationAttributes); + + return result; + } + + protected resolveSchemaAttributes(type: string, configurationAttributes: { [request: string]: IJSONSchema }): IJSONSchema[] { + const taskSchema = {}; + return Object.keys(configurationAttributes).map(request => { + const attributes: IJSONSchema = deepClone(configurationAttributes[request]); + const defaultRequired = ['name', 'type', 'request']; + attributes.required = attributes.required && attributes.required.length ? defaultRequired.concat(attributes.required) : defaultRequired; + attributes.additionalProperties = false; + attributes.type = 'object'; + if (!attributes.properties) { + attributes.properties = {}; + } + const properties = attributes.properties; + properties['type'] = { + enum: [type], + description: nls.localize('debugType', 'Type of configuration.'), + pattern: '^(?!node2)', + errorMessage: nls.localize('debugTypeNotRecognised', + 'The debug type is not recognized. Make sure that you have a corresponding debug extension installed and that it is enabled.'), + patternErrorMessage: nls.localize('node2NotSupported', + '"node2" is no longer supported, use "node" instead and set the "protocol" attribute to "inspector".') + }; + properties['name'] = { + type: 'string', + description: nls.localize('debugName', 'Name of configuration; appears in the launch configuration drop down menu.'), + default: 'Launch' + }; + properties['request'] = { + enum: [request], + description: nls.localize('debugRequest', 'Request type of configuration. Can be "launch" or "attach".'), + }; + properties['debugServer'] = { + type: 'number', + description: nls.localize('debugServer', + 'For debug extension development only: if a port is specified VS Code tries to connect to a debug adapter running in server mode'), + default: 4711 + }; + properties['preLaunchTask'] = { + anyOf: [taskSchema, { + type: ['string', 'null'], + }], + default: '', + description: nls.localize('debugPrelaunchTask', 'Task to run before debug session starts.') + }; + properties['postDebugTask'] = { + anyOf: [taskSchema, { + type: ['string', 'null'], + }], + default: '', + description: nls.localize('debugPostDebugTask', 'Task to run after debug session ends.') + }; + properties['internalConsoleOptions'] = INTERNAL_CONSOLE_OPTIONS_SCHEMA; + + const osProperties = Object.assign({}, properties); + properties['windows'] = { + type: 'object', + description: nls.localize('debugWindowsConfiguration', 'Windows specific launch configuration attributes.'), + properties: osProperties + }; + properties['osx'] = { + type: 'object', + description: nls.localize('debugOSXConfiguration', 'OS X specific launch configuration attributes.'), + properties: osProperties + }; + properties['linux'] = { + type: 'object', + description: nls.localize('debugLinuxConfiguration', 'Linux specific launch configuration attributes.'), + properties: osProperties + }; + Object.keys(attributes.properties).forEach(name => { + // Use schema allOf property to get independent error reporting #21113 + attributes!.properties![name].pattern = attributes!.properties![name].pattern || '^(?!.*\\$\\{(env|config|command)\\.)'; + attributes!.properties![name].patternErrorMessage = attributes!.properties![name].patternErrorMessage || + nls.localize('deprecatedVariables', "'env.', 'config.' and 'command.' are deprecated, use 'env:', 'config:' and 'command:' instead."); + }); + + return attributes; + }); + } + private extractValidAutoClosingPairs(langId: string, configuration: PluginPackageLanguageContributionConfiguration): AutoClosingPairConditional[] | undefined { const source = configuration.autoClosingPairs; if (typeof source === 'undefined') { diff --git a/packages/plugin-ext/src/plugin/node/debug/debug.ts b/packages/plugin-ext/src/plugin/node/debug/debug.ts index ffba1b0d75fa6..0d74975d0c721 100644 --- a/packages/plugin-ext/src/plugin/node/debug/debug.ts +++ b/packages/plugin-ext/src/plugin/node/debug/debug.ts @@ -31,7 +31,7 @@ import { import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema'; import { DebuggerDescription } from '@theia/debug/lib/common/debug-service'; import { DebugProtocol } from 'vscode-debugprotocol'; -import { PluginPackageDebuggersContribution } from '../../../common'; +import { DebuggerContribution } from '../../../common'; import { DebugAdapterSessionImpl } from '@theia/debug/lib/node/debug-adapter-session'; import { ChildProcess, spawn, fork } from 'child_process'; import { ConnectionExtImpl } from '../../connection-ext'; @@ -113,19 +113,19 @@ export class DebugExtImpl implements DebugExt { registerDebugConfigurationProvider( debugType: string, provider: theia.DebugConfigurationProvider, - packageContribution: PluginPackageDebuggersContribution, + debuggerContribution: DebuggerContribution, pluginPath: string): Disposable { const contributionId = uuid.v4(); - const contribution = new PluginDebugAdapterContribution( + const adapterContribution = new PluginDebugAdapterContribution( debugType, provider, - packageContribution, + debuggerContribution, this.commandRegistryExt, pluginPath); - this.contributions.set(contributionId, contribution); + this.contributions.set(contributionId, adapterContribution); - const description: DebuggerDescription = { type: debugType, label: packageContribution.label || debugType }; + const description: DebuggerDescription = { type: debugType, label: debuggerContribution.label || debugType }; this.proxy.$registerDebugConfigurationProvider(contributionId, description); return Disposable.create(() => { diff --git a/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-contribution.ts b/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-contribution.ts index 0af6235dd7574..86028ad1e5548 100644 --- a/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-contribution.ts +++ b/packages/plugin-ext/src/plugin/node/debug/plugin-debug-adapter-contribution.ts @@ -17,7 +17,7 @@ import * as theia from '@theia/plugin'; import * as path from 'path'; import { DebugConfiguration } from '@theia/debug/lib/common/debug-configuration'; -import { PluginPackageDebuggersContribution, PlatformSpecificAdapterContribution } from '../../../common'; +import { PlatformSpecificAdapterContribution, DebuggerContribution } from '../../../common'; import { DebugAdapterExecutable } from '@theia/debug/lib/node/debug-model'; import { CommandRegistryImpl } from '../../command-registry'; import { IJSONSchemaSnippet, IJSONSchema } from '@theia/core/lib/common/json-schema'; @@ -27,7 +27,7 @@ export class PluginDebugAdapterContribution { constructor( protected readonly debugType: string, protected readonly provider: theia.DebugConfigurationProvider, - protected readonly packageContribution: PluginPackageDebuggersContribution, + protected readonly debuggerContribution: DebuggerContribution, protected readonly commandRegistryExt: CommandRegistryImpl, protected readonly pluginPath: string) { } @@ -49,26 +49,26 @@ export class PluginDebugAdapterContribution { } async getSupportedLanguages(): Promise { - return this.packageContribution.languages || []; + return this.debuggerContribution.languages || []; } async provideDebugAdapterExecutable(debugConfiguration: theia.DebugConfiguration): Promise { - if (this.packageContribution.adapterExecutableCommand) { - return await this.commandRegistryExt.executeCommand(this.packageContribution.adapterExecutableCommand, []) as DebugAdapterExecutable; + if (this.debuggerContribution.adapterExecutableCommand) { + return await this.commandRegistryExt.executeCommand(this.debuggerContribution.adapterExecutableCommand, []) as DebugAdapterExecutable; } - const info = this.toPlatformInfo(this.packageContribution); - let program = (info && info.program || this.packageContribution.program); + const info = this.toPlatformInfo(this.debuggerContribution); + let program = (info && info.program || this.debuggerContribution.program); if (!program) { throw new Error('It is not possible to provide debug adapter executable. Program not found.'); } program = path.join(this.pluginPath, program); - const programArgs = info && info.args || this.packageContribution.args || []; - let runtime = info && info.runtime || this.packageContribution.runtime; + const programArgs = info && info.args || this.debuggerContribution.args || []; + let runtime = info && info.runtime || this.debuggerContribution.runtime; if (runtime && runtime.indexOf('./') === 0) { runtime = path.join(this.pluginPath, runtime); } - const runtimeArgs = info && info.runtimeArgs || this.packageContribution.runtimeArgs || []; + const runtimeArgs = info && info.runtimeArgs || this.debuggerContribution.runtimeArgs || []; const command = runtime ? runtime : program; const args = runtime ? [...runtimeArgs, program, ...programArgs] : programArgs; return { @@ -78,19 +78,14 @@ export class PluginDebugAdapterContribution { } async getSchemaAttributes(): Promise { - const configurationSnippets = this.packageContribution.configurationSnippets; - if (configurationSnippets) { - return []; - } - - return Object.keys(configurationSnippets).map(request => configurationSnippets[request]); + return this.debuggerContribution.configurationSnippets || []; } async getConfigurationSnippets(): Promise { - return this.packageContribution.configurationSnippets || []; + return this.debuggerContribution.configurationSnippets || []; } - protected toPlatformInfo(executable: PluginPackageDebuggersContribution): PlatformSpecificAdapterContribution | undefined { + protected toPlatformInfo(executable: DebuggerContribution): PlatformSpecificAdapterContribution | undefined { if (isWindows && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432')) { return executable.winx86 || executable.win || executable.windows; } diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index e0a83cc6dcd03..16a6410b198fb 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -108,7 +108,7 @@ import { ConnectionExtImpl } from './connection-ext'; import { WebviewsExtImpl } from './webviews'; import { TasksExtImpl } from './tasks/tasks'; import { DebugExtImpl } from './node/debug/debug'; -import { PluginPackageDebuggersContribution } from '../common'; +import { DebuggerContribution } from '../common'; export function createAPIFactory( rpc: RPCProtocol, @@ -510,9 +510,9 @@ export function createAPIFactory( return debugExt.onDidChangeBreakpoints; }, registerDebugConfigurationProvider(debugType: string, provider: theia.DebugConfigurationProvider): Disposable { - const debuggersContribution = plugin.rawModel.contributes && plugin.rawModel.contributes.debuggers; + const debuggersContribution = plugin.model.contributes && plugin.model.contributes.debuggers; if (debuggersContribution) { - const contribution = debuggersContribution.filter((value: PluginPackageDebuggersContribution) => value.type === debugType)[0]; + const contribution = debuggersContribution.filter((value: DebuggerContribution) => value.type === debugType)[0]; if (contribution) { console.info(`Registered debug contribution provider: '${debugType}'`); return debugExt.registerDebugConfigurationProvider(debugType, provider, contribution, plugin.pluginFolder);