Path: blob/main/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as nls from '../../../../nls.js';6import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';7import { URI } from '../../../../base/common/uri.js';8import { MenuId, MenuRegistry, IMenuItem } from '../../../../platform/actions/common/actions.js';9import { ITerminalGroupService, ITerminalService as IIntegratedTerminalService } from '../../terminal/browser/terminal.js';10import { ResourceContextKey } from '../../../common/contextkeys.js';11import { IFileService } from '../../../../platform/files/common/files.js';12import { getMultiSelectedResources, IExplorerService } from '../../files/browser/files.js';13import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';14import { Schemas } from '../../../../base/common/network.js';15import { distinct } from '../../../../base/common/arrays.js';16import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js';17import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';18import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../common/contributions.js';19import { Disposable } from '../../../../base/common/lifecycle.js';20import { isWindows } from '../../../../base/common/platform.js';21import { dirname, basename } from '../../../../base/common/path.js';22import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js';23import { Registry } from '../../../../platform/registry/common/platform.js';24import { IExternalTerminalConfiguration, IExternalTerminalService } from '../../../../platform/externalTerminal/common/externalTerminal.js';25import { TerminalLocation } from '../../../../platform/terminal/common/terminal.js';26import { IListService } from '../../../../platform/list/browser/listService.js';27import { IEditorService } from '../../../services/editor/common/editorService.js';28import { IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js';2930const OPEN_IN_TERMINAL_COMMAND_ID = 'openInTerminal';31const OPEN_IN_INTEGRATED_TERMINAL_COMMAND_ID = 'openInIntegratedTerminal';3233function registerOpenTerminalCommand(id: string, explorerKind: 'integrated' | 'external') {34CommandsRegistry.registerCommand({35id: id,36handler: async (accessor, resource: URI) => {3738const configurationService = accessor.get(IConfigurationService);39const fileService = accessor.get(IFileService);40const integratedTerminalService = accessor.get(IIntegratedTerminalService);41const remoteAgentService = accessor.get(IRemoteAgentService);42const terminalGroupService = accessor.get(ITerminalGroupService);43let externalTerminalService: IExternalTerminalService | undefined = undefined;44try {45externalTerminalService = accessor.get(IExternalTerminalService);46} catch { }4748const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IExplorerService));49return fileService.resolveAll(resources.map(r => ({ resource: r }))).then(async stats => {50// Always use integrated terminal when using a remote51const config = configurationService.getValue<IExternalTerminalConfiguration>();5253const useIntegratedTerminal = remoteAgentService.getConnection() || explorerKind === 'integrated';54const targets = distinct(stats.filter(data => data.success));55if (useIntegratedTerminal) {56// TODO: Use uri for cwd in createterminal57const opened: { [path: string]: boolean } = {};58const cwds = targets.map(({ stat }) => {59const resource = stat!.resource;60if (stat!.isDirectory) {61return resource;62}63return URI.from({64scheme: resource.scheme,65authority: resource.authority,66fragment: resource.fragment,67query: resource.query,68path: dirname(resource.path)69});70});71for (const cwd of cwds) {72if (opened[cwd.path]) {73return;74}75opened[cwd.path] = true;76const instance = await integratedTerminalService.createTerminal({ config: { cwd } });77if (instance && instance.target !== TerminalLocation.Editor && (resources.length === 1 || !resource || cwd.path === resource.path || cwd.path === dirname(resource.path))) {78integratedTerminalService.setActiveInstance(instance);79terminalGroupService.showPanel(true);80}81}82} else if (externalTerminalService) {83distinct(targets.map(({ stat }) => stat!.isDirectory ? stat!.resource.fsPath : dirname(stat!.resource.fsPath))).forEach(cwd => {84externalTerminalService.openTerminal(config.terminal.external, cwd);85});86}87});88}89});90}9192registerOpenTerminalCommand(OPEN_IN_TERMINAL_COMMAND_ID, 'external');93registerOpenTerminalCommand(OPEN_IN_INTEGRATED_TERMINAL_COMMAND_ID, 'integrated');9495export class ExternalTerminalContribution extends Disposable implements IWorkbenchContribution {96private _openInIntegratedTerminalMenuItem: IMenuItem;97private _openInTerminalMenuItem: IMenuItem;9899constructor(100@IConfigurationService private readonly _configurationService: IConfigurationService101) {102super();103104const shouldShowIntegratedOnLocal = ContextKeyExpr.and(105ResourceContextKey.Scheme.isEqualTo(Schemas.file),106ContextKeyExpr.or(ContextKeyExpr.equals('config.terminal.explorerKind', 'integrated'), ContextKeyExpr.equals('config.terminal.explorerKind', 'both')));107108109const shouldShowExternalKindOnLocal = ContextKeyExpr.and(110ResourceContextKey.Scheme.isEqualTo(Schemas.file),111ContextKeyExpr.or(ContextKeyExpr.equals('config.terminal.explorerKind', 'external'), ContextKeyExpr.equals('config.terminal.explorerKind', 'both')));112113this._openInIntegratedTerminalMenuItem = {114group: 'navigation',115order: 30,116command: {117id: OPEN_IN_INTEGRATED_TERMINAL_COMMAND_ID,118title: nls.localize('scopedConsoleAction.Integrated', "Open in Integrated Terminal")119},120when: ContextKeyExpr.or(shouldShowIntegratedOnLocal, ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeRemote))121};122123124this._openInTerminalMenuItem = {125group: 'navigation',126order: 31,127command: {128id: OPEN_IN_TERMINAL_COMMAND_ID,129title: nls.localize('scopedConsoleAction.external', "Open in External Terminal")130},131when: shouldShowExternalKindOnLocal132};133134135MenuRegistry.appendMenuItem(MenuId.ExplorerContext, this._openInTerminalMenuItem);136MenuRegistry.appendMenuItem(MenuId.ExplorerContext, this._openInIntegratedTerminalMenuItem);137138this._register(this._configurationService.onDidChangeConfiguration(e => {139if (e.affectsConfiguration('terminal.explorerKind') || e.affectsConfiguration('terminal.external')) {140this._refreshOpenInTerminalMenuItemTitle();141}142}));143144this._refreshOpenInTerminalMenuItemTitle();145}146147private isWindows(): boolean {148const config = this._configurationService.getValue<IExternalTerminalConfiguration>().terminal;149if (isWindows && config.external?.windowsExec) {150const file = basename(config.external.windowsExec);151if (file === 'wt' || file === 'wt.exe') {152return true;153}154}155return false;156}157158private _refreshOpenInTerminalMenuItemTitle(): void {159if (this.isWindows()) {160this._openInTerminalMenuItem.command.title = nls.localize('scopedConsoleAction.wt', "Open in Windows Terminal");161}162}163}164165Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExternalTerminalContribution, LifecyclePhase.Restored);166167168