diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 934751f89b3..48d8b59509c 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -87,6 +87,8 @@ import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/e import { once } from 'vs/base/common/functional'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { ISignService } from 'vs/platform/sign/common/sign'; +import { IExternalTerminalMainService } from 'vs/platform/externalTerminal/common/externalTerminal'; +import { LinuxExternalTerminalService, MacExternalTerminalService, WindowsExternalTerminalService } from 'vs/platform/externalTerminal/node/externalTerminalService'; /** * The main VS Code application. There will only ever be one instance, @@ -551,6 +553,15 @@ export class CodeApplication extends Disposable { // Storage services.set(IStorageMainService, new SyncDescriptor(StorageMainService)); + // External terminal + if (isWindows) { + services.set(IExternalTerminalMainService, new SyncDescriptor(WindowsExternalTerminalService)); + } else if (isMacintosh) { + services.set(IExternalTerminalMainService, new SyncDescriptor(MacExternalTerminalService)); + } else if (isLinux) { + services.set(IExternalTerminalMainService, new SyncDescriptor(LinuxExternalTerminalService)); + } + // Backups const backupMainService = new BackupMainService(this.environmentMainService, this.configurationService, this.logService); services.set(IBackupMainService, backupMainService); @@ -637,6 +648,10 @@ export class CodeApplication extends Disposable { mainProcessElectronServer.registerChannel('storage', storageChannel); sharedProcessClient.then(client => client.registerChannel('storage', storageChannel)); + // External Terminal + const externalTerminalChannel = ProxyChannel.fromService(accessor.get(IExternalTerminalMainService)); + mainProcessElectronServer.registerChannel('externalTerminal', externalTerminalChannel); + // Log Level (main & shared process) const logLevelChannel = new LogLevelChannel(accessor.get(ILogService)); mainProcessElectronServer.registerChannel('logLevel', logLevelChannel); diff --git a/src/vs/workbench/contrib/externalTerminal/common/externalTerminal.ts b/src/vs/platform/externalTerminal/common/externalTerminal.ts similarity index 67% rename from src/vs/workbench/contrib/externalTerminal/common/externalTerminal.ts rename to src/vs/platform/externalTerminal/common/externalTerminal.ts index 3aab081a27a..72d71f88520 100644 --- a/src/vs/workbench/contrib/externalTerminal/common/externalTerminal.ts +++ b/src/vs/platform/externalTerminal/common/externalTerminal.ts @@ -6,7 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ITerminalEnvironment } from 'vs/platform/terminal/common/terminal'; -export const IExternalTerminalService = createDecorator('nativeTerminalService'); +export const IExternalTerminalService = createDecorator('externalTerminal'); export interface IExternalTerminalSettings { linuxExec?: string; @@ -14,10 +14,17 @@ export interface IExternalTerminalSettings { windowsExec?: string; } +export interface ITerminalForPlatform { + windows: string, + linux: string, + osx: string +} + export interface IExternalTerminalService { readonly _serviceBrand: undefined; - openTerminal(path: string): void; + openTerminal(path: string): Promise; runInTerminal(title: string, cwd: string, args: string[], env: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise; + getDefaultTerminalForPlatforms(): Promise; } export interface IExternalTerminalConfiguration { @@ -26,3 +33,11 @@ export interface IExternalTerminalConfiguration { external: IExternalTerminalSettings; }; } + +export const DEFAULT_TERMINAL_OSX = 'Terminal.app'; + +export const IExternalTerminalMainService = createDecorator('externalTerminal'); + +export interface IExternalTerminalMainService extends IExternalTerminalService { + readonly _serviceBrand: undefined; +} diff --git a/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.test.ts b/src/vs/platform/externalTerminal/electron-main/externalTerminalService.test.ts similarity index 95% rename from src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.test.ts rename to src/vs/platform/externalTerminal/electron-main/externalTerminalService.test.ts index 264e8a93275..a3acac5ce13 100644 --- a/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.test.ts +++ b/src/vs/platform/externalTerminal/electron-main/externalTerminalService.test.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { deepEqual, equal } from 'assert'; -import { WindowsExternalTerminalService, LinuxExternalTerminalService, MacExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/node/externalTerminalService'; -import { DEFAULT_TERMINAL_OSX } from 'vs/workbench/contrib/externalTerminal/node/externalTerminal'; +import { DEFAULT_TERMINAL_OSX } from 'vs/platform/externalTerminal/common/externalTerminal'; +import { WindowsExternalTerminalService, MacExternalTerminalService, LinuxExternalTerminalService } from 'vs/platform/externalTerminal/node/externalTerminalService'; suite('ExternalTerminalService', () => { let mockOnExit: Function; diff --git a/src/vs/platform/externalTerminal/electron-sandbox/externalTerminalMainService.ts b/src/vs/platform/externalTerminal/electron-sandbox/externalTerminalMainService.ts new file mode 100644 index 00000000000..3b1ce4c60c7 --- /dev/null +++ b/src/vs/platform/externalTerminal/electron-sandbox/externalTerminalMainService.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; +import { IExternalTerminalService } from 'vs/platform/externalTerminal/common/externalTerminal'; + +export const IExternalTerminalMainService = createDecorator('externalTerminal'); + +export interface IExternalTerminalMainService extends IExternalTerminalService { + readonly _serviceBrand: undefined; +} + +registerMainProcessRemoteService(IExternalTerminalMainService, 'externalTerminal', { supportsDelayedInstantiation: true }); diff --git a/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts b/src/vs/platform/externalTerminal/node/externalTerminalService.ts similarity index 79% rename from src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts rename to src/vs/platform/externalTerminal/node/externalTerminalService.ts index eb3b37ab569..9897e79a3b8 100644 --- a/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts +++ b/src/vs/platform/externalTerminal/node/externalTerminalService.ts @@ -9,72 +9,41 @@ import * as processes from 'vs/base/node/processes'; import * as nls from 'vs/nls'; import * as pfs from 'vs/base/node/pfs'; import * as env from 'vs/base/common/platform'; -import { IExternalTerminalService, IExternalTerminalConfiguration, IExternalTerminalSettings } from 'vs/workbench/contrib/externalTerminal/common/externalTerminal'; +import { IExternalTerminalConfiguration, IExternalTerminalSettings, DEFAULT_TERMINAL_OSX, ITerminalForPlatform, IExternalTerminalMainService } from 'vs/platform/externalTerminal/common/externalTerminal'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { optional } from 'vs/platform/instantiation/common/instantiation'; -import { DEFAULT_TERMINAL_OSX } from 'vs/workbench/contrib/externalTerminal/node/externalTerminal'; import { FileAccess } from 'vs/base/common/network'; import { ITerminalEnvironment } from 'vs/platform/terminal/common/terminal'; const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console"); -export class WindowsExternalTerminalService implements IExternalTerminalService { +abstract class ExternalTerminalService { public _serviceBrand: undefined; - private static readonly CMD = 'cmd.exe'; + async getDefaultTerminalForPlatforms(): Promise { + const linuxTerminal = await LinuxExternalTerminalService.getDefaultTerminalLinuxReady(); + return { windows: WindowsExternalTerminalService.getDefaultTerminalWindows(), linux: linuxTerminal, osx: 'xterm' }; + } +} - private readonly _configurationService?: IConfigurationService; +export class WindowsExternalTerminalService extends ExternalTerminalService implements IExternalTerminalMainService { + private static readonly CMD = 'cmd.exe'; + private static _DEFAULT_TERMINAL_WINDOWS: string; constructor( - @optional(IConfigurationService) configurationService: IConfigurationService + @optional(IConfigurationService) private readonly _configurationService: IConfigurationService ) { - this._configurationService = configurationService; + super(); } - public openTerminal(cwd?: string): void { - if (this._configurationService) { - const configuration = this._configurationService.getValue(); - this.spawnTerminal(cp, configuration, processes.getWindowsShell(), cwd); - } + public openTerminal(cwd?: string): Promise { + const configuration = this._configurationService.getValue(); + return this.spawnTerminal(cp, configuration, processes.getWindowsShell(), cwd); } - public runInTerminal(title: string, dir: string, args: string[], envVars: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise { - - const exec = settings.windowsExec || WindowsExternalTerminalService.getDefaultTerminalWindows(); - - return new Promise((resolve, reject) => { - - const title = `"${dir} - ${TERMINAL_TITLE}"`; - const command = `""${args.join('" "')}" & pause"`; // use '|' to only pause on non-zero exit code - - const cmdArgs = [ - '/c', 'start', title, '/wait', exec, '/c', command - ]; - - // merge environment variables into a copy of the process.env - const env = Object.assign({}, process.env, envVars); - - // delete environment variables that have a null value - Object.keys(env).filter(v => env[v] === null).forEach(key => delete env[key]); - - const options: any = { - cwd: dir, - env: env, - windowsVerbatimArguments: true - }; - - const cmd = cp.spawn(WindowsExternalTerminalService.CMD, cmdArgs, options); - cmd.on('error', err => { - reject(improveError(err)); - }); - - resolve(undefined); - }); - } - - private spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalConfiguration, command: string, cwd?: string): Promise { + public spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalConfiguration, command: string, cwd?: string): Promise { const terminalConfig = configuration.terminal.external; - const exec = terminalConfig.windowsExec || WindowsExternalTerminalService.getDefaultTerminalWindows(); + const exec = terminalConfig?.windowsExec || WindowsExternalTerminalService.getDefaultTerminalWindows(); // Make the drive letter uppercase on Windows (see #9448) if (cwd && cwd[1] === ':') { @@ -109,7 +78,38 @@ export class WindowsExternalTerminalService implements IExternalTerminalService }); } - private static _DEFAULT_TERMINAL_WINDOWS: string; + public runInTerminal(title: string, dir: string, args: string[], envVars: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise { + const exec = settings.windowsExec || WindowsExternalTerminalService.getDefaultTerminalWindows(); + + return new Promise((resolve, reject) => { + + const title = `"${dir} - ${TERMINAL_TITLE}"`; + const command = `""${args.join('" "')}" & pause"`; // use '|' to only pause on non-zero exit code + + const cmdArgs = [ + '/c', 'start', title, '/wait', exec, '/c', command + ]; + + // merge environment variables into a copy of the process.env + const env = Object.assign({}, process.env, envVars); + + // delete environment variables that have a null value + Object.keys(env).filter(v => env[v] === null).forEach(key => delete env[key]); + + const options: any = { + cwd: dir, + env: env, + windowsVerbatimArguments: true + }; + + const cmd = cp.spawn(WindowsExternalTerminalService.CMD, cmdArgs, options); + cmd.on('error', err => { + reject(improveError(err)); + }); + + resolve(undefined); + }); + } public static getDefaultTerminalWindows(): string { if (!WindowsExternalTerminalService._DEFAULT_TERMINAL_WINDOWS) { @@ -120,24 +120,18 @@ export class WindowsExternalTerminalService implements IExternalTerminalService } } -export class MacExternalTerminalService implements IExternalTerminalService { - public _serviceBrand: undefined; - +export class MacExternalTerminalService extends ExternalTerminalService implements IExternalTerminalMainService { private static readonly OSASCRIPT = '/usr/bin/osascript'; // osascript is the AppleScript interpreter on OS X - private readonly _configurationService?: IConfigurationService; - constructor( - @optional(IConfigurationService) configurationService: IConfigurationService + @optional(IConfigurationService) private readonly _configurationService: IConfigurationService ) { - this._configurationService = configurationService; + super(); } - public openTerminal(cwd?: string): void { - if (this._configurationService) { - const configuration = this._configurationService.getValue(); - this.spawnTerminal(cp, configuration, cwd); - } + public openTerminal(cwd?: string): Promise { + const configuration = this._configurationService.getValue(); + return this.spawnTerminal(cp, configuration, cwd); } public runInTerminal(title: string, dir: string, args: string[], envVars: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise { @@ -204,9 +198,9 @@ export class MacExternalTerminalService implements IExternalTerminalService { }); } - private spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalConfiguration, cwd?: string): Promise { + spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalConfiguration, cwd?: string): Promise { const terminalConfig = configuration.terminal.external; - const terminalApp = terminalConfig.osxExec || DEFAULT_TERMINAL_OSX; + const terminalApp = terminalConfig?.osxExec || DEFAULT_TERMINAL_OSX; return new Promise((c, e) => { const args = ['-a', terminalApp]; @@ -220,24 +214,19 @@ export class MacExternalTerminalService implements IExternalTerminalService { } } -export class LinuxExternalTerminalService implements IExternalTerminalService { - public _serviceBrand: undefined; +export class LinuxExternalTerminalService extends ExternalTerminalService implements IExternalTerminalMainService { private static readonly WAIT_MESSAGE = nls.localize('press.any.key', "Press any key to continue..."); - private readonly _configurationService?: IConfigurationService; - constructor( - @optional(IConfigurationService) configurationService: IConfigurationService + @optional(IConfigurationService) private readonly _configurationService: IConfigurationService ) { - this._configurationService = configurationService; + super(); } - public openTerminal(cwd?: string): void { - if (this._configurationService) { - const configuration = this._configurationService.getValue(); - this.spawnTerminal(cp, configuration, cwd); - } + public openTerminal(cwd?: string): Promise { + const configuration = this._configurationService.getValue(); + return this.spawnTerminal(cp, configuration, cwd); } public runInTerminal(title: string, dir: string, args: string[], envVars: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise { @@ -296,20 +285,6 @@ export class LinuxExternalTerminalService implements IExternalTerminalService { }); } - private spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalConfiguration, cwd?: string): Promise { - const terminalConfig = configuration.terminal.external; - const execPromise = terminalConfig.linuxExec ? Promise.resolve(terminalConfig.linuxExec) : LinuxExternalTerminalService.getDefaultTerminalLinuxReady(); - - return new Promise((c, e) => { - execPromise.then(exec => { - const env = cwd ? { cwd } : undefined; - const child = spawner.spawn(exec, [], env); - child.on('error', e); - child.on('exit', () => c()); - }); - }); - } - private static _DEFAULT_TERMINAL_LINUX_READY: Promise; public static async getDefaultTerminalLinuxReady(): Promise { @@ -337,6 +312,20 @@ export class LinuxExternalTerminalService implements IExternalTerminalService { } return LinuxExternalTerminalService._DEFAULT_TERMINAL_LINUX_READY; } + + spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalConfiguration, cwd?: string): Promise { + const terminalConfig = configuration.terminal.external; + const execPromise = terminalConfig?.linuxExec ? Promise.resolve(terminalConfig.linuxExec) : LinuxExternalTerminalService.getDefaultTerminalLinuxReady(); + + return new Promise((c, e) => { + execPromise.then(exec => { + const env = cwd ? { cwd } : undefined; + const child = spawner.spawn(exec, [], env); + child.on('error', e); + child.on('exit', () => c()); + }); + }); + } } /** diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 9ba96a894b5..12e894b4960 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -20,10 +20,10 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ExtHostDebugServiceBase, ExtHostDebugSession, ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService'; import { ISignService } from 'vs/platform/sign/common/sign'; import { SignService } from 'vs/platform/sign/node/signService'; -import { hasChildProcesses, prepareCommand, runInExternalTerminal } from 'vs/workbench/contrib/debug/node/terminals'; import { IDisposable } from 'vs/base/common/lifecycle'; import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver'; import { createCancelablePromise, firstParallel } from 'vs/base/common/async'; +import { hasChildProcesses, prepareCommand, runInExternalTerminal } from 'vs/workbench/contrib/debug/node/terminals'; export class ExtHostDebugService extends ExtHostDebugServiceBase { @@ -139,7 +139,6 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { return shellProcessId; } else if (args.kind === 'external') { - return runInExternalTerminal(args, await this._configurationService.getConfigProvider()); } return super.$runInTerminal(args, sessionId); diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 03c4fc8f6c5..7b3326f6e22 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -5,29 +5,12 @@ import * as cp from 'child_process'; import * as platform from 'vs/base/common/platform'; -import { WindowsExternalTerminalService, MacExternalTerminalService, LinuxExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/node/externalTerminalService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/common/externalTerminal'; -import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { getDriveLetter } from 'vs/base/common/extpath'; +import { LinuxExternalTerminalService, MacExternalTerminalService, WindowsExternalTerminalService } from 'vs/platform/externalTerminal/node/externalTerminalService'; +import { IExternalTerminalService } from 'vs/platform/externalTerminal/common/externalTerminal'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; -let externalTerminalService: IExternalTerminalService | undefined = undefined; - -export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, configProvider: ExtHostConfigProvider): Promise { - if (!externalTerminalService) { - if (platform.isWindows) { - externalTerminalService = new WindowsExternalTerminalService(undefined); - } else if (platform.isMacintosh) { - externalTerminalService = new MacExternalTerminalService(undefined); - } else if (platform.isLinux) { - externalTerminalService = new LinuxExternalTerminalService(undefined); - } else { - throw new Error('external terminals not supported on this platform'); - } - } - const config = configProvider.getConfiguration('terminal'); - return externalTerminalService.runInTerminal(args.title!, args.cwd, args.args, args.env || {}, config.external || {}); -} function spawnAsPromised(command: string, args: string[]): Promise { return new Promise((resolve, reject) => { @@ -47,6 +30,24 @@ function spawnAsPromised(command: string, args: string[]): Promise { }); } +let externalTerminalService: IExternalTerminalService | undefined = undefined; + +export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, configProvider: ExtHostConfigProvider): Promise { + if (!externalTerminalService) { + if (platform.isWindows) { + externalTerminalService = new WindowsExternalTerminalService(undefined); + } else if (platform.isMacintosh) { + externalTerminalService = new MacExternalTerminalService(undefined); + } else if (platform.isLinux) { + externalTerminalService = new LinuxExternalTerminalService(undefined); + } else { + throw new Error('external terminals not supported on this platform'); + } + } + const config = configProvider.getConfiguration('terminal'); + return externalTerminalService.runInTerminal(args.title!, args.cwd, args.args, args.env || {}, config.external || {}); +} + export function hasChildProcesses(processId: number | undefined): Promise { if (processId) { // if shell has at least one child process, assume that shell is busy diff --git a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts deleted file mode 100644 index 148401905f6..00000000000 --- a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts +++ /dev/null @@ -1,137 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { URI } from 'vs/base/common/uri'; -import { IExternalTerminalConfiguration, IExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/common/externalTerminal'; -import { MenuId, MenuRegistry, IMenuItem } from 'vs/platform/actions/common/actions'; -import { ITerminalService as IIntegratedTerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { ResourceContextKey } from 'vs/workbench/common/resources'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IListService } from 'vs/platform/list/browser/listService'; -import { getMultiSelectedResources, IExplorerService } from 'vs/workbench/contrib/files/browser/files'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { Schemas } from 'vs/base/common/network'; -import { distinct } from 'vs/base/common/arrays'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { optional } from 'vs/platform/instantiation/common/instantiation'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { isWeb, isWindows } from 'vs/base/common/platform'; -import { dirname, basename } from 'vs/base/common/path'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { Registry } from 'vs/platform/registry/common/platform'; - -const OPEN_IN_TERMINAL_COMMAND_ID = 'openInTerminal'; -CommandsRegistry.registerCommand({ - id: OPEN_IN_TERMINAL_COMMAND_ID, - handler: (accessor, resource: URI) => { - const configurationService = accessor.get(IConfigurationService); - const editorService = accessor.get(IEditorService); - const fileService = accessor.get(IFileService); - const terminalService: IExternalTerminalService | undefined = accessor.get(IExternalTerminalService, optional); - const integratedTerminalService = accessor.get(IIntegratedTerminalService); - const remoteAgentService = accessor.get(IRemoteAgentService); - - const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, accessor.get(IExplorerService)); - return fileService.resolveAll(resources.map(r => ({ resource: r }))).then(async stats => { - const targets = distinct(stats.filter(data => data.success)); - // Always use integrated terminal when using a remote - const useIntegratedTerminal = remoteAgentService.getConnection() || configurationService.getValue().terminal.explorerKind === 'integrated'; - if (useIntegratedTerminal) { - - - // TODO: Use uri for cwd in createterminal - - - const opened: { [path: string]: boolean } = {}; - targets.map(({ stat }) => { - const resource = stat!.resource; - if (stat!.isDirectory) { - return resource; - } - return URI.from({ - scheme: resource.scheme, - authority: resource.authority, - fragment: resource.fragment, - query: resource.query, - path: dirname(resource.path) - }); - }).forEach(cwd => { - if (opened[cwd.path]) { - return; - } - opened[cwd.path] = true; - const instance = integratedTerminalService.createTerminal({ cwd }); - if (instance && (resources.length === 1 || !resource || cwd.path === resource.path || cwd.path === dirname(resource.path))) { - integratedTerminalService.setActiveInstance(instance); - integratedTerminalService.showPanel(true); - } - }); - } else { - distinct(targets.map(({ stat }) => stat!.isDirectory ? stat!.resource.fsPath : dirname(stat!.resource.fsPath))).forEach(cwd => { - terminalService!.openTerminal(cwd); - }); - } - }); - } -}); - -export class ExternalTerminalContribution extends Disposable implements IWorkbenchContribution { - private _openInTerminalMenuItem: IMenuItem; - - constructor( - @IConfigurationService private readonly _configurationService: IConfigurationService - ) { - super(); - - this._openInTerminalMenuItem = { - group: 'navigation', - order: 30, - command: { - id: OPEN_IN_TERMINAL_COMMAND_ID, - title: nls.localize('scopedConsoleAction', "Open in Terminal") - }, - when: ContextKeyExpr.or(ResourceContextKey.Scheme.isEqualTo(Schemas.file), ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeRemote)) - }; - MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, this._openInTerminalMenuItem); - MenuRegistry.appendMenuItem(MenuId.ExplorerContext, this._openInTerminalMenuItem); - - this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('terminal.explorerKind') || e.affectsConfiguration('terminal.external')) { - this._refreshOpenInTerminalMenuItemTitle(); - } - }); - this._refreshOpenInTerminalMenuItemTitle(); - } - - private _refreshOpenInTerminalMenuItemTitle(): void { - if (isWeb) { - this._openInTerminalMenuItem.command.title = nls.localize('scopedConsoleAction.integrated', "Open in Integrated Terminal"); - return; - } - - const config = this._configurationService.getValue().terminal; - if (config.explorerKind === 'integrated') { - this._openInTerminalMenuItem.command.title = nls.localize('scopedConsoleAction.integrated', "Open in Integrated Terminal"); - return; - } - - if (isWindows && config.external.windowsExec) { - const file = basename(config.external.windowsExec); - if (file === 'wt' || file === 'wt.exe') { - this._openInTerminalMenuItem.command.title = nls.localize('scopedConsoleAction.wt', "Open in Windows Terminal"); - return; - } - } - - this._openInTerminalMenuItem.command.title = nls.localize('scopedConsoleAction.external', "Open in External Terminal"); - } -} - -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExternalTerminalContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/externalTerminal/node/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts similarity index 50% rename from src/vs/workbench/contrib/externalTerminal/node/externalTerminal.contribution.ts rename to src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts index 84e406323be..e63a96b7c60 100644 --- a/src/vs/workbench/contrib/externalTerminal/node/externalTerminal.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import * as paths from 'vs/base/common/path'; -import { IExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/common/externalTerminal'; +import { DEFAULT_TERMINAL_OSX, IExternalTerminalService } from 'vs/platform/externalTerminal/common/externalTerminal'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -13,12 +13,10 @@ import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Schemas } from 'vs/base/common/network'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; -import { WindowsExternalTerminalService, MacExternalTerminalService, LinuxExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/node/externalTerminalService'; import { IConfigurationRegistry, Extensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { isWindows, isMacintosh, isLinux } from 'vs/base/common/platform'; -import { DEFAULT_TERMINAL_OSX } from 'vs/workbench/contrib/externalTerminal/node/externalTerminal'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IExternalTerminalMainService } from 'vs/platform/externalTerminal/electron-sandbox/externalTerminalMainService'; const OPEN_NATIVE_CONSOLE_COMMAND_ID = 'workbench.action.terminal.openNativeConsole'; KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -54,53 +52,54 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { } }); -if (isWindows) { - registerSingleton(IExternalTerminalService, WindowsExternalTerminalService, true); -} else if (isMacintosh) { - registerSingleton(IExternalTerminalService, MacExternalTerminalService, true); -} else if (isLinux) { - registerSingleton(IExternalTerminalService, LinuxExternalTerminalService, true); -} +export class ExternalTerminalContribution implements IWorkbenchContribution { -LinuxExternalTerminalService.getDefaultTerminalLinuxReady().then(defaultTerminalLinux => { - let configurationRegistry = Registry.as(Extensions.Configuration); - configurationRegistry.registerConfiguration({ - id: 'externalTerminal', - order: 100, - title: nls.localize('terminalConfigurationTitle', "External Terminal"), - type: 'object', - properties: { - 'terminal.explorerKind': { - type: 'string', - enum: [ - 'integrated', - 'external' - ], - enumDescriptions: [ - nls.localize('terminal.explorerKind.integrated', "Use VS Code's integrated terminal."), - nls.localize('terminal.explorerKind.external', "Use the configured external terminal.") - ], - description: nls.localize('explorer.openInTerminalKind', "Customizes what kind of terminal to launch."), - default: 'integrated' - }, - 'terminal.external.windowsExec': { - type: 'string', - description: nls.localize('terminal.external.windowsExec', "Customizes which terminal to run on Windows."), - default: WindowsExternalTerminalService.getDefaultTerminalWindows(), - scope: ConfigurationScope.APPLICATION - }, - 'terminal.external.osxExec': { - type: 'string', - description: nls.localize('terminal.external.osxExec', "Customizes which terminal application to run on macOS."), - default: DEFAULT_TERMINAL_OSX, - scope: ConfigurationScope.APPLICATION - }, - 'terminal.external.linuxExec': { - type: 'string', - description: nls.localize('terminal.external.linuxExec', "Customizes which terminal to run on Linux."), - default: defaultTerminalLinux, - scope: ConfigurationScope.APPLICATION + public _serviceBrand: undefined; + constructor(@IExternalTerminalMainService private readonly _externalTerminalService: IExternalTerminalMainService) { + this._updateConfiguration(); + } + + private async _updateConfiguration(): Promise { + const terminals = await this._externalTerminalService.getDefaultTerminalForPlatforms(); + let configurationRegistry = Registry.as(Extensions.Configuration); + configurationRegistry.registerConfiguration({ + id: 'externalTerminal', + order: 100, + title: nls.localize('terminalConfigurationTitle', "External Terminal"), + type: 'object', + properties: { + 'terminal.explorerKind': { + type: 'string', + enum: [ + 'integrated', + 'external' + ], + enumDescriptions: [ + nls.localize('terminal.explorerKind.integrated', "Use VS Code's integrated terminal."), + nls.localize('terminal.explorerKind.external', "Use the configured external terminal.") + ], + description: nls.localize('explorer.openInTerminalKind', "Customizes what kind of terminal to launch."), + default: 'integrated' + }, + 'terminal.external.windowsExec': { + type: 'string', + description: nls.localize('terminal.external.windowsExec', "Customizes which terminal to run on Windows."), + default: terminals.windows, + scope: ConfigurationScope.APPLICATION + }, + 'terminal.external.osxExec': { + type: 'string', + description: nls.localize('terminal.external.osxExec', "Customizes which terminal application to run on macOS."), + default: DEFAULT_TERMINAL_OSX, + scope: ConfigurationScope.APPLICATION + }, + 'terminal.external.linuxExec': { + type: 'string', + description: nls.localize('terminal.external.linuxExec', "Customizes which terminal to run on Linux."), + default: terminals.linux, + scope: ConfigurationScope.APPLICATION + } } - } - }); -}); + }); + } +} diff --git a/src/vs/workbench/contrib/externalTerminal/node/externalTerminal.ts b/src/vs/workbench/contrib/externalTerminal/node/externalTerminal.ts deleted file mode 100644 index ff0fc24b7cf..00000000000 --- a/src/vs/workbench/contrib/externalTerminal/node/externalTerminal.ts +++ /dev/null @@ -1,6 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export const DEFAULT_TERMINAL_OSX = 'Terminal.app'; diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts index 03cd10397fb..6528bd19f1d 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts @@ -9,6 +9,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { ILocalTerminalService, TerminalIpcChannels } from 'vs/platform/terminal/common/terminal'; import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { ExternalTerminalContribution } from 'vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution'; import { LocalTerminalService } from 'vs/workbench/contrib/terminal/electron-sandbox/localTerminalService'; import { TerminalNativeContribution } from 'vs/workbench/contrib/terminal/electron-sandbox/terminalNativeContribution'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -19,3 +20,5 @@ registerSingleton(ILocalTerminalService, LocalTerminalService, true); const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(TerminalNativeContribution, LifecyclePhase.Ready); + +workbenchRegistry.registerWorkbenchContribution(ExternalTerminalContribution, LifecyclePhase.Ready); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 8aaf7217a64..626eb097b11 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -252,9 +252,6 @@ import 'vs/workbench/contrib/codeEditor/browser/codeEditor.contribution'; // Keybindings Contributions import 'vs/workbench/contrib/keybindings/browser/keybindings.contribution'; -// Execution -import 'vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution'; - // Snippets import 'vs/workbench/contrib/snippets/browser/snippets.contribution'; import 'vs/workbench/contrib/snippets/browser/snippetsService'; diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 7cf060c4da0..be79d8ca0d2 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -117,8 +117,6 @@ import 'vs/workbench/contrib/extensions/electron-browser/extensions.contribution // Terminal import 'vs/workbench/contrib/terminal/electron-browser/terminal.contribution'; -// External Terminal -import 'vs/workbench/contrib/externalTerminal/node/externalTerminal.contribution'; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts index 45db92aeb12..116eb3d988e 100644 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ b/src/vs/workbench/workbench.sandbox.main.ts @@ -135,4 +135,7 @@ import 'vs/workbench/contrib/performance/electron-sandbox/performance.contributi // Tasks import 'vs/workbench/contrib/tasks/electron-sandbox/taskService'; +// External terminal +import 'vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution'; + //#endregion