diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index ae12653ae6f9d..1f18dbafd69cb 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -17,44 +17,53 @@ .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 .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:first-child .terminal-wrapper { +.monaco-workbench .monaco-split-view2.horizontal .split-view-view:first-child .terminal-wrapper { margin-left: 20px; } -.monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .terminal-wrapper { +.monaco-workbench .monaco-split-view2.horizontal .split-view-view:last-child .terminal-wrapper { margin-right: 20px; } -.monaco-workbench .pane-body.integrated-terminal .xterm a:not(.xterm-invalid-link) { +.monaco-workbench .xterm a:not(.xterm-invalid-link) { /* To support message box sizing */ position: relative; } -.monaco-workbench .pane-body.integrated-terminal .terminal-wrapper > div { +.monaco-workbench .terminal-wrapper > div { height: 100%; } -.monaco-workbench .pane-body.integrated-terminal .xterm-viewport { +.monaco-workbench .xterm-viewport { box-sizing: border-box; margin-right: -10px; } -.monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm-viewport { +.monaco-workbench .monaco-split-view2.horizontal .split-view-view:last-child .xterm-viewport { margin-right: -20px; } +.monaco-workbench .editor-instance canvas, .monaco-workbench .pane-body.integrated-terminal canvas { /* Align the viewport and canvases to the bottom of the panel */ position: absolute; @@ -69,6 +78,7 @@ font-variant-ligatures: none; } +.monaco-workbench .editor-instance .split-view-view, .monaco-workbench .pane-body.integrated-terminal .split-view-view { box-sizing: border-box; overflow: hidden; diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index b1fe664336e3c..f17939e55f1d9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -37,6 +37,10 @@ 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 { TerminalEditor, TerminalInputSerializer } from 'vs/workbench/contrib/terminal/browser/terminalEditor'; +import { EditorExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; +import { EditorDescriptor, IEditorRegistry } from 'vs/workbench/browser/editor'; +import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput'; // Register services registerSingleton(ITerminalService, TerminalService, true); @@ -62,6 +66,18 @@ CommandsRegistry.registerCommand({ id: quickAccessNavigatePreviousInTerminalPick registerTerminalPlatformConfiguration(); registerTerminalConfiguration(); +Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(TerminalEditorInput.ID, TerminalInputSerializer); +Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create( + TerminalEditor, + TerminalEditor.ID, + nls.localize('terminal.editor.label', "Terminal") + ), + [ + new SyncDescriptor(TerminalEditorInput) + ] +); + // Register views const VIEW_CONTAINER = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: TERMINAL_VIEW_ID, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 8982c2ae0fbc1..679b49bc84339 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -19,7 +19,7 @@ import { Action2, ICommandActionTitle, ILocalizedString, registerAction2 } from import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ILabelService } from 'vs/platform/label/common/label'; import { IListService } from 'vs/platform/list/browser/listService'; @@ -31,10 +31,12 @@ import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspac 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 { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput'; 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 { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -191,6 +193,24 @@ 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 editorService = accessor.get(IEditorService); + const instantiationService = accessor.get(IInstantiationService); + const input: TerminalEditorInput = instantiationService.createInstance(TerminalEditorInput); + await editorService.openEditor(input, { pinned: false }); + return void (0); + } + }); registerAction2(class extends Action2 { constructor() { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts new file mode 100644 index 0000000000000..f7af0913b9d8a --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { 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 } from 'vs/workbench/common/editor'; +import { ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +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 _instance: ITerminalInstance | undefined; + private _parentElement: HTMLElement | undefined; + private _isAttached: boolean = false; + + // eslint-disable-next-line @typescript-eslint/naming-convention + protected createEditor(parent: HTMLElement): void { + this._parentElement = parent; + this._instance = this._terminalService.createInstance({}); + } + + layout(dimension: Dimension): void { + if (this._instance) { + this._instance.layout(dimension); + } + } + + override setVisible(visible: boolean, group?: IEditorGroup): void { + super.setVisible(visible, group); + + if (!this._instance) { + return; + } + + if (!this._isAttached) { + this._instance.attachToElement(this._parentElement!); + this._isAttached = true; + } + this._instance.setVisible(visible); + } + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, + @ITerminalService private readonly _terminalService: ITerminalService + ) { + + super(TerminalEditor.ID, telemetryService, themeService, storageService); + } +} + +export class TerminalInputSerializer implements IEditorInputSerializer { + public canSerialize(editorInput: TerminalEditorInput): boolean { + return true; + } + + public serialize(editorInput: TerminalEditorInput): string { + return ''; + } + + public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): TerminalEditorInput { + try { + return new TerminalEditorInput(); + } catch { } + return new TerminalEditorInput(); + } +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts new file mode 100644 index 0000000000000..41a707b6bd06b --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts @@ -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 { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; + +export const terminalInputId = 'workbench.editors.terminal'; +export class TerminalEditorInput extends EditorInput { + + static readonly ID = terminalInputId; + static readonly RESOURCE = URI.from({ scheme: Schemas.vscodeTerminal, authority: 'vscode_editor_terminal' }); + + override get typeId(): string { + return TerminalEditorInput.ID; + } + + get resource(): URI | undefined { + return TerminalEditorInput.RESOURCE; + } + + constructor( + ) { + super(); + } + + override getName() { + return localize('terminal.editor.input', "Terminal"); + } +} diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 0d6f364ce3b82..3d299107d9d59 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -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',