Merge pull request #126407 from microsoft/merogge/terminal-editor
Terminal editors
This commit is contained in:
commit
d21b9b8e6d
18 changed files with 339 additions and 60 deletions
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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}"`);
|
||||
|
|
79
src/vs/workbench/contrib/terminal/browser/terminalEditor.ts
Normal file
79
src/vs/workbench/contrib/terminal/browser/terminalEditor.ts
Normal 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');
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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[] = [
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue