Merge pull request #30354 from rebornix/OpenInIntegratedTerminal

Fix #8132. Open folder in integrated terminal.
This commit is contained in:
Daniel Imms 2017-07-12 11:50:23 -07:00 committed by GitHub
commit 3eefd7fa15
5 changed files with 212 additions and 149 deletions

View file

@ -4,10 +4,30 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import * as env from 'vs/base/common/platform';
import { WinTerminalService, MacTerminalService, LinuxTerminalService } from 'vs/workbench/parts/execution/electron-browser/terminalService';
import { TPromise } from 'vs/base/common/winjs.base';
import { Registry } from 'vs/platform/registry/common/platform';
import { IAction, Action } from 'vs/base/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
import paths = require('vs/base/common/paths');
import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions, ActionBarContributor } from 'vs/workbench/browser/actions';
import uri from 'vs/base/common/uri';
import { explorerItemToFileResource } from 'vs/workbench/parts/files/common/files';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ITerminalService } from 'vs/workbench/parts/execution/common/execution';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { toResource } from 'vs/workbench/common/editor';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { ITerminalService as IIntegratedTerminalService, KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED } from 'vs/workbench/parts/terminal/common/terminal';
import { DEFAULT_TERMINAL_WINDOWS, DEFAULT_TERMINAL_LINUX_READY, DEFAULT_TERMINAL_OSX, ITerminalConfiguration } from 'vs/workbench/parts/execution/electron-browser/terminal';
import { WinTerminalService, MacTerminalService, LinuxTerminalService } from 'vs/workbench/parts/execution/electron-browser/terminalService';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
if (env.isWindows) {
registerSingleton(ITerminalService, WinTerminalService);
@ -16,3 +36,192 @@ if (env.isWindows) {
} else if (env.isLinux) {
registerSingleton(ITerminalService, LinuxTerminalService);
}
DEFAULT_TERMINAL_LINUX_READY.then(defaultTerminalLinux => {
let configurationRegistry = <IConfigurationRegistry>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'
],
'description': nls.localize('explorer.openInTerminalKind', "Customizes what kind of terminal to launch."),
'default': 'integrated',
'isExecutable': false
},
'terminal.external.windowsExec': {
'type': 'string',
'description': nls.localize('terminal.external.windowsExec', "Customizes which terminal to run on Windows."),
'default': DEFAULT_TERMINAL_WINDOWS,
'isExecutable': true
},
'terminal.external.osxExec': {
'type': 'string',
'description': nls.localize('terminal.external.osxExec', "Customizes which terminal application to run on OS X."),
'default': DEFAULT_TERMINAL_OSX,
'isExecutable': true
},
'terminal.external.linuxExec': {
'type': 'string',
'description': nls.localize('terminal.external.linuxExec', "Customizes which terminal to run on Linux."),
'default': defaultTerminalLinux,
'isExecutable': true
}
}
});
});
export abstract class AbstractOpenInTerminalAction extends Action {
private resource: uri;
constructor(
id: string,
label: string,
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
@IHistoryService protected historyService: IHistoryService
) {
super(id, label);
this.order = 49; // Allow other actions to position before or after
}
public setResource(resource: uri): void {
this.resource = resource;
this.enabled = !paths.isUNC(this.resource.fsPath);
}
public getPathToOpen(): string {
let pathToOpen: string;
// Try workspace path first
const root = this.historyService.getLastActiveWorkspaceRoot();
pathToOpen = this.resource ? this.resource.fsPath : (root && root.fsPath);
// Otherwise check if we have an active file open
if (!pathToOpen) {
const file = toResource(this.editorService.getActiveEditorInput(), { supportSideBySide: true, filter: 'file' });
if (file) {
pathToOpen = paths.dirname(file.fsPath); // take parent folder of file
}
}
return pathToOpen;
}
}
export class OpenConsoleAction extends AbstractOpenInTerminalAction {
public static ID = 'workbench.action.terminal.openNativeConsole';
public static Label = env.isWindows ? nls.localize('globalConsoleActionWin', "Open New Command Prompt") :
nls.localize('globalConsoleActionMacLinux', "Open New Terminal");
public static ScopedLabel = env.isWindows ? nls.localize('scopedConsoleActionWin', "Open in Command Prompt") :
nls.localize('scopedConsoleActionMacLinux', "Open in Terminal");
constructor(
id: string,
label: string,
@ITerminalService private terminalService: ITerminalService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IHistoryService historyService: IHistoryService
) {
super(id, label, editorService, contextService, historyService);
}
public run(event?: any): TPromise<any> {
let pathToOpen = this.getPathToOpen();
this.terminalService.openTerminal(pathToOpen);
return TPromise.as(null);
}
}
export class OpenIntegratedTerminalAction extends AbstractOpenInTerminalAction {
public static ID = 'workbench.action.terminal.openFolderInIntegratedTerminal';
public static Label = nls.localize('openFolderInIntegratedTerminal', "Open in Terminal");
constructor(
id: string,
label: string,
@IIntegratedTerminalService private integratedTerminalService: IIntegratedTerminalService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IHistoryService historyService: IHistoryService
) {
super(id, label, editorService, contextService, historyService);
}
public run(event?: any): TPromise<any> {
let pathToOpen = this.getPathToOpen();
var instance = this.integratedTerminalService.createInstance({ cwd: pathToOpen }, true);
if (instance) {
this.integratedTerminalService.setActiveInstance(instance);
this.integratedTerminalService.showPanel(true);
}
return TPromise.as(null);
}
}
export class ExplorerViewerActionContributor extends ActionBarContributor {
constructor(
@IInstantiationService private instantiationService: IInstantiationService,
@IConfigurationService private configurationService: IConfigurationService
) {
super();
}
public hasSecondaryActions(context: any): boolean {
return !!explorerItemToFileResource(context.element);
}
public getSecondaryActions(context: any): IAction[] {
let fileResource = explorerItemToFileResource(context.element);
let resource = fileResource.resource;
// We want the parent unless this resource is a directory
if (!fileResource.isDirectory) {
resource = uri.file(paths.dirname(resource.fsPath));
}
const configuration = this.configurationService.getConfiguration<ITerminalConfiguration>();
const explorerKind = configuration.terminal.explorerKind;
if (explorerKind === 'integrated') {
let action = this.instantiationService.createInstance(OpenIntegratedTerminalAction, OpenIntegratedTerminalAction.ID, OpenIntegratedTerminalAction.Label);
action.setResource(resource);
return [action];
} else {
let action = this.instantiationService.createInstance(OpenConsoleAction, OpenConsoleAction.ID, OpenConsoleAction.ScopedLabel);
action.setResource(resource);
return [action];
}
}
}
const actionBarRegistry = Registry.as<IActionBarRegistry>(ActionBarExtensions.Actionbar);
actionBarRegistry.registerActionBarContributor(Scope.VIEWER, ExplorerViewerActionContributor);
// Register Global Action to Open Console
Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions).registerWorkbenchAction(
new SyncActionDescriptor(
OpenConsoleAction,
OpenConsoleAction.ID,
OpenConsoleAction.Label,
{ primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C },
KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED
),
env.isWindows ? 'Open New Command Prompt' : 'Open New Terminal'
);

View file

@ -1,147 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import nls = require('vs/nls');
import { TPromise } from 'vs/base/common/winjs.base';
import { Registry } from 'vs/platform/registry/common/platform';
import baseplatform = require('vs/base/common/platform');
import { IAction, Action } from 'vs/base/common/actions';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
import paths = require('vs/base/common/paths');
import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions, ActionBarContributor } from 'vs/workbench/browser/actions';
import uri from 'vs/base/common/uri';
import { explorerItemToFileResource } from 'vs/workbench/parts/files/common/files';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ITerminalService } from 'vs/workbench/parts/execution/common/execution';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { toResource } from 'vs/workbench/common/editor';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED } from 'vs/workbench/parts/terminal/common/terminal';
import { DEFAULT_TERMINAL_WINDOWS, DEFAULT_TERMINAL_LINUX_READY, DEFAULT_TERMINAL_OSX } from 'vs/workbench/parts/execution/electron-browser/terminal';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
DEFAULT_TERMINAL_LINUX_READY.then(defaultTerminalLinux => {
let configurationRegistry = <IConfigurationRegistry>Registry.as(Extensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'externalTerminal',
'order': 100,
'title': nls.localize('terminalConfigurationTitle', "External Terminal"),
'type': 'object',
'properties': {
'terminal.external.windowsExec': {
'type': 'string',
'description': nls.localize('terminal.external.windowsExec', "Customizes which terminal to run on Windows."),
'default': DEFAULT_TERMINAL_WINDOWS,
'isExecutable': true
},
'terminal.external.osxExec': {
'type': 'string',
'description': nls.localize('terminal.external.osxExec', "Customizes which terminal application to run on OS X."),
'default': DEFAULT_TERMINAL_OSX,
'isExecutable': true
},
'terminal.external.linuxExec': {
'type': 'string',
'description': nls.localize('terminal.external.linuxExec', "Customizes which terminal to run on Linux."),
'default': defaultTerminalLinux,
'isExecutable': true
}
}
});
});
export class OpenConsoleAction extends Action {
public static ID = 'workbench.action.terminal.openNativeConsole';
public static Label = baseplatform.isWindows ? nls.localize('globalConsoleActionWin', "Open New Command Prompt") :
nls.localize('globalConsoleActionMacLinux', "Open New Terminal");
public static ScopedLabel = baseplatform.isWindows ? nls.localize('scopedConsoleActionWin', "Open in Command Prompt") :
nls.localize('scopedConsoleActionMacLinux', "Open in Terminal");
private resource: uri;
constructor(
id: string,
label: string,
@ITerminalService private terminalService: ITerminalService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IHistoryService private historyService: IHistoryService
) {
super(id, label);
this.order = 49; // Allow other actions to position before or after
}
public setResource(resource: uri): void {
this.resource = resource;
this.enabled = !paths.isUNC(this.resource.fsPath);
}
public run(event?: any): TPromise<any> {
let pathToOpen: string;
// Try workspace path first
const root = this.historyService.getLastActiveWorkspaceRoot();
pathToOpen = this.resource ? this.resource.fsPath : (root && root.fsPath);
// Otherwise check if we have an active file open
if (!pathToOpen) {
const file = toResource(this.editorService.getActiveEditorInput(), { supportSideBySide: true, filter: 'file' });
if (file) {
pathToOpen = paths.dirname(file.fsPath); // take parent folder of file
}
}
this.terminalService.openTerminal(pathToOpen);
return TPromise.as(null);
}
}
class ExplorerViewerActionContributor extends ActionBarContributor {
constructor( @IInstantiationService private instantiationService: IInstantiationService) {
super();
}
public hasSecondaryActions(context: any): boolean {
return !!explorerItemToFileResource(context.element);
}
public getSecondaryActions(context: any): IAction[] {
let fileResource = explorerItemToFileResource(context.element);
let resource = fileResource.resource;
// We want the parent unless this resource is a directory
if (!fileResource.isDirectory) {
resource = uri.file(paths.dirname(resource.fsPath));
}
let action = this.instantiationService.createInstance(OpenConsoleAction, OpenConsoleAction.ID, OpenConsoleAction.ScopedLabel);
action.setResource(resource);
return [action];
}
}
const actionBarRegistry = Registry.as<IActionBarRegistry>(ActionBarExtensions.Actionbar);
actionBarRegistry.registerActionBarContributor(Scope.VIEWER, ExplorerViewerActionContributor);
// Register Global Action to Open Console
Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions).registerWorkbenchAction(
new SyncActionDescriptor(
OpenConsoleAction,
OpenConsoleAction.ID,
OpenConsoleAction.Label,
{ primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C },
KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED
),
baseplatform.isWindows ? 'Open New Command Prompt' : 'Open New Terminal'
);

View file

@ -37,6 +37,7 @@ export const DEFAULT_TERMINAL_WINDOWS = `${process.env.windir}\\${process.env.ha
export interface ITerminalConfiguration {
terminal: {
explorerKind: 'integrated' | 'external',
external: {
linuxExec: string,
osxExec: string,

View file

@ -17,6 +17,7 @@ suite('Execution - TerminalService', () => {
setup(() => {
mockConfig = {
terminal: {
explorerKind: 'external',
external: {
windowsExec: 'testWindowsShell',
osxExec: 'testOSXShell',

View file

@ -86,7 +86,6 @@ import 'vs/workbench/parts/emmet/electron-browser/emmet.contribution';
import 'vs/workbench/parts/codeEditor/codeEditor.contribution';
import 'vs/workbench/parts/execution/electron-browser/execution.contribution';
import 'vs/workbench/parts/execution/electron-browser/terminal.contribution';
import 'vs/workbench/parts/snippets/electron-browser/snippets.contribution';