Merge pull request #126407 from microsoft/merogge/terminal-editor

Terminal editors
This commit is contained in:
Daniel Imms 2021-06-15 17:34:09 -07:00 committed by GitHub
commit d21b9b8e6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 339 additions and 60 deletions

View file

@ -478,7 +478,7 @@ export interface ITerminalChildProcess {
/**
* Detach the process from the UI and await reconnect.
*/
detach?(): void;
detach?(): Promise<void>;
/**
* Shutdown the terminal process.

View file

@ -151,7 +151,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
}
if (!terminal) {
terminal = this._terminalService.createTerminal(shellLaunchConfig);
terminal = this._terminalService.createTerminal({ config: shellLaunchConfig });
}
this._extHostTerminalIds.set(extHostTerminalId, terminal.instanceId);
}

View file

@ -64,7 +64,7 @@ CommandsRegistry.registerCommand({
return;
}
opened[cwd.path] = true;
const instance = integratedTerminalService.createTerminal({ cwd });
const instance = integratedTerminalService.createTerminal({ config: { cwd } });
if (instance && (resources.length === 1 || !resource || cwd.path === resource.path || cwd.path === dirname(resource.path))) {
integratedTerminalService.setActiveInstance(instance);
integratedTerminalService.showPanel(true);

View file

@ -1275,7 +1275,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
}
if (!result) {
// Either no group is used, no terminal with the group exists or splitting an existing terminal failed.
result = this.terminalService.createTerminal(launchConfigs);
result = this.terminalService.createTerminal({ config: launchConfigs });
}
const terminalKey = result.instanceId.toString();

View file

@ -17,40 +17,55 @@
.monaco-workbench .pane-body.integrated-terminal .terminal-outer-container,
.monaco-workbench .pane-body.integrated-terminal .terminal-groups-container,
.monaco-workbench .pane-body.integrated-terminal .terminal-group,
.monaco-workbench .pane-body.integrated-terminal .terminal-split-pane {
.monaco-workbench .pane-body.integrated-terminal .terminal-split-pane,
.monaco-workbench .editor-instance .terminal-split-pane,
.monaco-workbench .editor-instance .terminal-outer-container {
height: 100%;
}
.monaco-workbench .editor-instance .terminal-wrapper,
.monaco-workbench .pane-body.integrated-terminal .terminal-wrapper {
display: none;
margin: 0 10px;
bottom: 2px;
}
.monaco-workbench .editor-instance .terminal-wrapper.active,
.monaco-workbench .pane-body.integrated-terminal .terminal-wrapper.active {
display: block;
}
.monaco-workbench .pane-body.integrated-terminal .terminal-wrapper.active {
position: absolute;
top: 0;
}
.monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:first-child .terminal-wrapper,
.monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:first-child .terminal-wrapper {
margin-left: 20px;
}
.monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .terminal-wrapper,
.monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .terminal-wrapper {
margin-right: 20px;
}
.monaco-workbench .editor-instance .xterm a:not(.xterm-invalid-link),
.monaco-workbench .pane-body.integrated-terminal .xterm a:not(.xterm-invalid-link) {
/* To support message box sizing */
position: relative;
}
.monaco-workbench .editor-instance .terminal-wrapper > div,
.monaco-workbench .pane-body.integrated-terminal .terminal-wrapper > div {
height: 100%;
}
.monaco-workbench .editor-instance .xterm-viewport,
.monaco-workbench .pane-body.integrated-terminal .xterm-viewport {
box-sizing: border-box;
margin-right: -10px;
}
.monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm-viewport,
.monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm-viewport {
margin-right: -20px;
}

View file

@ -23,7 +23,7 @@ import { registerColors } from 'vs/workbench/contrib/terminal/common/terminalCol
import { setupTerminalCommands } from 'vs/workbench/contrib/terminal/browser/terminalCommands';
import { TerminalService } from 'vs/workbench/contrib/terminal/browser/terminalService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IRemoteTerminalService, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IRemoteTerminalService, ITerminalEditorService, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess';
@ -37,9 +37,16 @@ import { isIOS, isWindows } from 'vs/base/common/platform';
import { setupTerminalMenus } from 'vs/workbench/contrib/terminal/browser/terminalMenus';
import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminalInstanceService';
import { registerTerminalPlatformConfiguration } from 'vs/platform/terminal/common/terminalPlatformConfiguration';
import { EditorExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor';
import { EditorDescriptor, IEditorRegistry } from 'vs/workbench/browser/editor';
import { TerminalInputSerializer, TerminalEditor } from 'vs/workbench/contrib/terminal/browser/terminalEditor';
import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput';
import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings';
import { TerminalEditorService } from 'vs/workbench/contrib/terminal/browser/terminalEditorService';
// Register services
registerSingleton(ITerminalService, TerminalService, true);
registerSingleton(ITerminalEditorService, TerminalEditorService, true);
registerSingleton(IRemoteTerminalService, RemoteTerminalService);
registerSingleton(ITerminalInstanceService, TerminalInstanceService, true);
@ -62,6 +69,18 @@ CommandsRegistry.registerCommand({ id: quickAccessNavigatePreviousInTerminalPick
registerTerminalPlatformConfiguration();
registerTerminalConfiguration();
Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(TerminalEditorInput.ID, TerminalInputSerializer);
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
EditorDescriptor.create(
TerminalEditor,
TerminalEditor.ID,
terminalStrings.terminal
),
[
new SyncDescriptor(TerminalEditorInput)
]
);
// Register views
const VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
id: TERMINAL_VIEW_ID,

View file

@ -20,6 +20,7 @@ import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { IEditableData } from 'vs/workbench/common/views';
export const ITerminalService = createDecorator<ITerminalService>('terminalService');
export const ITerminalEditorService = createDecorator<ITerminalEditorService>('terminalEditorService');
export const ITerminalInstanceService = createDecorator<ITerminalInstanceService>('terminalInstanceService');
export const IRemoteTerminalService = createDecorator<IRemoteTerminalService>('remoteTerminalService');
@ -90,6 +91,28 @@ export const enum TerminalConnectionState {
Connected
}
export const enum TerminalTarget {
TerminalView = 'view',
Editor = 'editor'
}
export interface ICreateTerminalOptions {
/**
* The shell launch config or profile to launch with, when not specified the default terminal
* profile will be used.
*/
config?: IShellLaunchConfig | ITerminalProfile;
/**
* The current working directory to start with, this will override IShellLaunchConfig.cwd if
* specified.
*/
cwd?: string | URI;
/**
* Where to create the terminal, when not specified the default target will be used.
*/
target?: TerminalTarget;
}
export interface ITerminalService {
readonly _serviceBrand: undefined;
@ -131,15 +154,10 @@ export interface ITerminalService {
/**
* Creates a terminal.
* @param shell The shell launch configuration to use.
* @param options The options to create the terminal with, when not specified the default
* profile will be used at the default target.
*/
createTerminal(shell?: IShellLaunchConfig, cwd?: string | URI): ITerminalInstance;
/**
* Creates a terminal.
* @param profile The profile to launch the terminal with.
*/
createTerminal(profile: ITerminalProfile): ITerminalInstance;
createTerminal(options?: ICreateTerminalOptions): ITerminalInstance;
createContributedTerminalProfile(extensionIdentifier: string, id: string, isSplitTerminal: boolean): Promise<void>;
@ -163,6 +181,7 @@ export interface ITerminalService {
*/
moveGroup(source: ITerminalInstance, target: ITerminalInstance): void;
moveInstance(source: ITerminalInstance, target: ITerminalInstance, side: 'before' | 'after'): void;
moveToEditor(source: ITerminalInstance): void;
/**
* Perform an action with the active terminal instance, if the terminal does
@ -217,6 +236,18 @@ export interface ITerminalService {
safeDisposeTerminal(instance: ITerminalInstance): Promise<void>;
}
/**
* This service is responsible for integrating with the editor service and managing terminal
* editors.
*/
export interface ITerminalEditorService {
readonly _serviceBrand: undefined;
readonly terminalEditorInstances: ITerminalInstance[];
createEditor(instance: ITerminalInstance): Promise<void>;
}
export interface IRemoteTerminalService extends IOffProcessTerminalService {
createProcess(shellLaunchConfig: IShellLaunchConfig, configuration: ICompleteTerminalConfiguration, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, shouldPersist: boolean, configHelper: ITerminalConfigHelper): Promise<ITerminalChildProcess>;
}
@ -295,6 +326,8 @@ export interface ITerminalInstance {
*/
processId: number | undefined;
target?: TerminalTarget;
/**
* The id of a persistent process. This is defined if this is a terminal created by a pty host
* that supports reconnection.
@ -564,6 +597,11 @@ export interface ITerminalInstance {
*/
attachToElement(container: HTMLElement): Promise<void> | void;
/**
* Detaches the terminal instance from the terminal editor DOM element.
*/
detachFromElement(): void;
/**
* Configure the dimensions of the terminal instance.
*

View file

@ -8,7 +8,7 @@ import { Action } from 'vs/base/common/actions';
import { Codicon } from 'vs/base/common/codicons';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Schemas } from 'vs/base/common/network';
import { isWindows, isLinux } from 'vs/base/common/platform';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { withNullAsUndefined } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
@ -30,9 +30,9 @@ import { ILocalTerminalService, ITerminalProfile, TerminalSettingId, TitleEventS
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
import { FindInFilesCommand, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActions';
import { Direction, IRemoteTerminalService, ITerminalInstance, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { Direction, IRemoteTerminalService, ITerminalInstance, ITerminalInstanceService, ITerminalService, TerminalTarget } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess';
import { IRemoteTerminalAttachTarget, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_TABS_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TABS_SINGULAR_SELECTION, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_ACTION_CATEGORY, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal';
import { IRemoteTerminalAttachTarget, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_TABS_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TABS_SINGULAR_SELECTION, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TerminalCommandId, TERMINAL_ACTION_CATEGORY } from 'vs/workbench/contrib/terminal/common/terminal';
import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
@ -114,7 +114,7 @@ export function registerTerminalActions() {
async run(accessor: ServicesAccessor) {
const terminalService = accessor.get(ITerminalService);
if (terminalService.isProcessSupportRegistered) {
const instance = terminalService.createTerminal(undefined);
const instance = terminalService.createTerminal();
if (!instance) {
return;
}
@ -178,7 +178,7 @@ export function registerTerminalActions() {
}
if (profile) {
instance = terminalService.createTerminal(profile, cwd);
instance = terminalService.createTerminal({ config: profile, cwd });
} else {
instance = await terminalService.showProfileQuickPick('createInstance', cwd);
}
@ -191,6 +191,39 @@ export function registerTerminalActions() {
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: TerminalCommandId.CreateTerminalEditor,
title: { value: localize('workbench.action.terminal.createTerminalEditor', "Create Terminal Editor"), original: 'Create Terminal Editor' },
f1: true,
category,
precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED
});
}
async run(accessor: ServicesAccessor) {
const terminalService = accessor.get(ITerminalService);
// TODO: Await openEditor
terminalService.createTerminal({
target: TerminalTarget.Editor
});
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: TerminalCommandId.MoveToEditor,
title: { value: localize('workbench.action.terminal.moveToEditor', "Move From Terminal View to Editor"), original: 'Move From Terminal View to Editor' },
f1: true,
category
});
}
async run(accessor: ServicesAccessor) {
const terminalService = accessor.get(ITerminalService);
terminalService.doWithActiveInstance(instance => terminalService.moveToEditor(instance));
}
});
registerAction2(class extends Action2 {
constructor() {
@ -898,7 +931,9 @@ export function registerTerminalActions() {
}
const selected = await quickInputService.pick<IRemoteTerminalPick>(items, { canPickMany: false });
if (selected) {
const instance = terminalService.createTerminal({ attachPersistentProcess: selected.term });
const instance = terminalService.createTerminal({
config: { attachPersistentProcess: selected.term }
});
terminalService.setActiveInstance(instance);
terminalService.showPanel(true);
}
@ -1531,7 +1566,7 @@ export function registerTerminalActions() {
if (folders.length <= 1) {
// Allow terminal service to handle the path when there is only a
// single root
instance = terminalService.createTerminal(undefined);
instance = terminalService.createTerminal();
} else {
const options: IPickOptions<IQuickPickItem> = {
placeHolder: localize('workbench.action.terminal.newWorkspacePlaceholder', "Select current working directory for new terminal")
@ -1779,7 +1814,7 @@ export function registerTerminalActions() {
if (quickSelectProfiles) {
const profile = quickSelectProfiles.find(profile => profile.profileName === profileSelection);
if (profile) {
const instance = terminalService.createTerminal(profile);
const instance = terminalService.createTerminal({ config: profile });
terminalService.setActiveInstance(instance);
} else {
console.warn(`No profile with name "${profileSelection}"`);

View file

@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Dimension } from 'vs/base/browser/dom';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { IEditorInputSerializer, IEditorOpenContext } from 'vs/workbench/common/editor';
import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
export class TerminalEditor extends EditorPane {
public static readonly ID = 'terminalEditor';
private _parentElement: HTMLElement | undefined;
private _editorInput?: TerminalEditorInput = undefined;
private _lastDimension?: Dimension;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService,
) {
super(TerminalEditor.ID, telemetryService, themeService, storageService);
}
override async setInput(newInput: TerminalEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken) {
this._editorInput?.terminalInstance?.detachFromElement();
this._editorInput = newInput;
await super.setInput(newInput, options, context, token);
this._editorInput.terminalInstance.attachToElement(this._parentElement!);
if (this._lastDimension) {
this.layout(this._lastDimension);
}
this._editorInput.terminalInstance.setVisible(true);
}
// eslint-disable-next-line @typescript-eslint/naming-convention
protected createEditor(parent: HTMLElement): void {
this._parentElement = parent;
}
layout(dimension: Dimension): void {
if (this._editorInput?.terminalInstance) {
this._editorInput.terminalInstance.layout(dimension);
}
this._lastDimension = dimension;
}
override setVisible(visible: boolean, group?: IEditorGroup): void {
super.setVisible(visible, group);
return this._editorInput?.terminalInstance?.setVisible(visible);
}
}
export class TerminalInputSerializer implements IEditorInputSerializer {
public canSerialize(editorInput: TerminalEditorInput): boolean {
return false;
}
public serialize(editorInput: TerminalEditorInput): string | undefined {
return undefined;
}
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): TerminalEditorInput {
// TODO: Attach to instanceId via pty service
throw new Error('NYI');
}
}

View file

@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { toDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
export class TerminalEditorInput extends EditorInput {
static readonly ID = 'workbench.editors.terminal';
override get typeId(): string {
return TerminalEditorInput.ID;
}
private readonly _terminalInstance: ITerminalInstance;
get terminalInstance(): ITerminalInstance {
return this._terminalInstance;
}
get resource(): URI {
return this.terminalInstance.resource;
}
constructor(
terminalInstance: ITerminalInstance
) {
super();
this._terminalInstance = terminalInstance;
this._terminalInstance.onTitleChanged(() => this._onDidChangeLabel.fire());
this._register(toDisposable(() => this.terminalInstance.dispose()));
}
override getName() {
return this.terminalInstance.title;
}
}

View file

@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITerminalEditorService, ITerminalInstance, TerminalTarget } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
export class TerminalEditorService implements ITerminalEditorService {
declare _serviceBrand: undefined;
terminalEditorInstances: ITerminalInstance[] = [];
private _editorInputs: Map</*instanceId*/number, TerminalEditorInput> = new Map();
constructor(
@IEditorService private readonly _editorService: IEditorService
) {
// TODO: Multiplex instance events
}
async createEditor(instance: ITerminalInstance): Promise<void> {
instance.target = TerminalTarget.Editor;
const input = new TerminalEditorInput(instance);
this._editorInputs.set(instance.instanceId, input);
await this._editorService.openEditor(input, {
pinned: true,
forceReload: true
});
this.terminalEditorInstances.push(instance);
}
}

View file

@ -629,14 +629,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
return xterm;
}
reattachToElement(container: HTMLElement): void {
if (!this._wrapperElement) {
throw new Error('The terminal instance has not been attached to a container yet');
}
this._wrapperElement.parentNode?.removeChild(this._wrapperElement);
this._container = container;
this._container.appendChild(this._wrapperElement);
detachFromElement(): void {
this._wrapperElement?.parentNode?.removeChild(this._wrapperElement);
this._wrapperElement = undefined;
this._container = undefined;
}
attachToElement(container: HTMLElement): Promise<void> | void {

View file

@ -19,7 +19,7 @@ import { IKeyMods, IPickOptions, IQuickInputButton, IQuickInputService, IQuickPi
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ILocalTerminalService, IOffProcessTerminalService, IShellLaunchConfig, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalSettingId, TerminalSettingPrefix } from 'vs/platform/terminal/common/terminal';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IRemoteTerminalService, ITerminalExternalLinkProvider, ITerminalInstance, ITerminalService, ITerminalGroup, TerminalConnectionState, ITerminalProfileProvider } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IRemoteTerminalService, ITerminalExternalLinkProvider, ITerminalInstance, ITerminalService, ITerminalGroup, TerminalConnectionState, ITerminalProfileProvider, ICreateTerminalOptions, TerminalTarget, ITerminalEditorService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IEditableData, IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views';
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance';
@ -147,6 +147,7 @@ export class TerminalService implements ITerminalService {
@IRemoteTerminalService private readonly _remoteTerminalService: IRemoteTerminalService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@ITerminalContributionService private readonly _terminalContributionService: ITerminalContributionService,
@ITerminalEditorService private readonly _terminalEditorService: ITerminalEditorService,
@IExtensionService private readonly _extensionService: IExtensionService,
@INotificationService private readonly _notificationService: INotificationService,
@optional(ILocalTerminalService) localTerminalService: ILocalTerminalService
@ -277,7 +278,10 @@ export class TerminalService implements ITerminalService {
terminalLayouts.forEach((terminalLayout) => {
if (!terminalInstance) {
// create group and terminal
terminalInstance = this.createTerminal({ attachPersistentProcess: terminalLayout.terminal! });
const config = { attachPersistentProcess: terminalLayout.terminal! } as IShellLaunchConfig;
terminalInstance = this.createTerminal(
config
);
group = this.getGroupForInstance(terminalInstance);
if (groupLayout.isActive) {
activeGroup = group;
@ -324,7 +328,7 @@ export class TerminalService implements ITerminalService {
getActiveOrCreateInstance(): ITerminalInstance {
const activeInstance = this.getActiveInstance();
return activeInstance ? activeInstance : this.createTerminal(undefined);
return activeInstance ? activeInstance : this.createTerminal();
}
async setEditable(instance: ITerminalInstance, data?: IEditableData | null): Promise<void> {
@ -759,6 +763,15 @@ export class TerminalService implements ITerminalService {
targetGroup.moveInstance(source, index);
}
moveToEditor(source: ITerminalInstance): void {
const sourceGroup = this.getGroupForInstance(source);
if (!sourceGroup) {
return;
}
sourceGroup.removeInstance(source);
this._terminalEditorService.createEditor(source);
}
protected _initInstanceListeners(instance: ITerminalInstance): void {
instance.addDisposable(instance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed));
instance.addDisposable(instance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged));
@ -897,8 +910,6 @@ export class TerminalService implements ITerminalService {
return !res.confirmed;
}
private async _getPlatformKey(): Promise<string> {
const env = await this._remoteAgentService.getEnvironment();
if (env) {
@ -988,7 +999,7 @@ export class TerminalService implements ITerminalService {
// create split, only valid if there's an active instance
instance = this.splitInstance(activeInstance, value.profile, cwd);
} else {
instance = this.createTerminal(value.profile, cwd);
instance = this.createTerminal({ config: value.profile, cwd });
}
}
@ -1100,13 +1111,11 @@ export class TerminalService implements ITerminalService {
return {};
}
createTerminal(shellLaunchConfig?: IShellLaunchConfig): ITerminalInstance;
createTerminal(profile: ITerminalProfile, cwd?: string | URI): ITerminalInstance;
createTerminal(shellLaunchConfigOrProfile: IShellLaunchConfig | ITerminalProfile, cwd?: string | URI): ITerminalInstance {
const shellLaunchConfig = this._convertProfileToShellLaunchConfig(shellLaunchConfigOrProfile);
createTerminal(options?: ICreateTerminalOptions): ITerminalInstance {
const shellLaunchConfig = this._convertProfileToShellLaunchConfig(options?.config);
if (cwd) {
shellLaunchConfig.cwd = cwd;
if (options?.cwd) {
shellLaunchConfig.cwd = options.cwd;
}
if (!shellLaunchConfig.customPtyImplementation && !this.isProcessSupportRegistered) {
@ -1131,17 +1140,26 @@ export class TerminalService implements ITerminalService {
}
}
const terminalGroup = this._instantiationService.createInstance(TerminalGroup, this._terminalContainer, shellLaunchConfig);
this._terminalGroups.push(terminalGroup);
terminalGroup.onPanelOrientationChanged((orientation) => this._onPanelOrientationChanged.fire(orientation));
let instance: ITerminalInstance;
if (options?.target === TerminalTarget.Editor) {
instance = this.createInstance(shellLaunchConfig);
this._terminalEditorService.createEditor(instance);
this._initInstanceListeners(instance);
this._onInstancesChanged.fire();
} else {
const terminalGroup = this._instantiationService.createInstance(TerminalGroup, this._terminalContainer, shellLaunchConfig);
this._terminalGroups.push(terminalGroup);
terminalGroup.onPanelOrientationChanged((orientation) => this._onPanelOrientationChanged.fire(orientation));
const instance = terminalGroup.terminalInstances[0];
instance = terminalGroup.terminalInstances[0];
terminalGroup.addDisposable(terminalGroup.onDisposed(this._onGroupDisposed.fire, this._onGroupDisposed));
terminalGroup.addDisposable(terminalGroup.onInstancesChanged(this._onInstancesChanged.fire, this._onInstancesChanged));
this._initInstanceListeners(instance);
this._onInstancesChanged.fire();
this._onGroupsChanged.fire();
}
terminalGroup.addDisposable(terminalGroup.onDisposed(this._onGroupDisposed.fire, this._onGroupDisposed));
terminalGroup.addDisposable(terminalGroup.onInstancesChanged(this._onInstancesChanged.fire, this._onInstancesChanged));
this._initInstanceListeners(instance);
this._onInstancesChanged.fire();
this._onGroupsChanged.fire();
if (this.terminalInstances.length === 1) {
// It's the first instance so it should be made active automatically, this must fire
// after onInstancesChanged so consumers can react to the instance being added first

View file

@ -9,7 +9,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ITerminalInstance, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalInstance, ITerminalInstanceService, ITerminalService, TerminalTarget } from 'vs/workbench/contrib/terminal/browser/terminal';
import { localize } from 'vs/nls';
import * as DOM from 'vs/base/browser/dom';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@ -107,7 +107,7 @@ export class TerminalTabList extends WorkbenchList<ITerminalInstance> {
this._terminalService.onDidChangeConnectionState(() => this.refresh());
this._themeService.onDidColorThemeChange(() => this.refresh());
this._terminalService.onActiveInstanceChanged(e => {
if (e) {
if (e && e.target !== TerminalTarget.Editor) {
const i = this._terminalService.terminalInstances.indexOf(e);
this.setSelection([i]);
this.reveal(i);

View file

@ -397,6 +397,7 @@ export const enum TerminalCommandId {
Relaunch = 'workbench.action.terminal.relaunch',
FocusPreviousPane = 'workbench.action.terminal.focusPreviousPane',
ShowTabs = 'workbench.action.terminal.showTabs',
CreateTerminalEditor = 'workbench.action.createTerminalEditor',
FocusTabs = 'workbench.action.terminal.focusTabs',
FocusNextPane = 'workbench.action.terminal.focusNextPane',
ResizePaneLeft = 'workbench.action.terminal.resizePaneLeft',
@ -448,7 +449,8 @@ export const enum TerminalCommandId {
ShowEnvironmentInformation = 'workbench.action.terminal.showEnvironmentInformation',
SearchWorkspace = 'workbench.action.terminal.searchWorkspace',
AttachToRemoteTerminal = 'workbench.action.terminal.attachToSession',
DetachProcess = 'workbench.action.terminal.detachProcess'
DetachProcess = 'workbench.action.terminal.detachProcess',
MoveToEditor = 'workbench.action.terminal.moveToEditor'
}
export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [

View file

@ -17,6 +17,7 @@ export function formatMessageForTerminal(message: string, excludeLeadingNewLine:
* An object holding strings shared by multiple parts of the terminal
*/
export const terminalStrings = {
terminal: localize('terminal', "Terminal"),
focus: {
value: localize('workbench.action.terminal.focus', "Focus Terminal"),
original: 'Focus Terminal'

View file

@ -44,8 +44,8 @@ export class LocalPty extends Disposable implements ITerminalChildProcess {
start(): Promise<ITerminalLaunchError | undefined> {
return this._localPtyService.start(this.id);
}
detach(): void {
this._localPtyService.detachFromProcess(this.id);
detach(): Promise<void> {
return this._localPtyService.detachFromProcess(this.id);
}
shutdown(immediate: boolean): void {
this._localPtyService.shutdown(this.id, immediate);

View file

@ -101,10 +101,12 @@ export class TestingOutputTerminalService implements ITestingOutputTerminalServi
const output = new TestOutputProcess();
this.showResultsInTerminal(this.terminalService.createTerminal({
isFeatureTerminal: true,
icon: testingViewIcon,
customPtyImplementation: () => output,
name: getTitle(result),
config: {
isFeatureTerminal: true,
icon: testingViewIcon,
customPtyImplementation: () => output,
name: getTitle(result),
}
}), output, result);
}