From 4a8432f24f5216635489f99cc5ed6962ce2152f3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 28 Sep 2025 22:20:10 +0900 Subject: [PATCH] Pull editable logic into separate service --- .../terminal/browser/terminal.contribution.ts | 4 +- .../contrib/terminal/browser/terminal.ts | 49 ++++++++++++++++--- .../terminal/browser/terminalActions.ts | 12 +++-- .../browser/terminalEditingService.ts | 49 +++++++++++++++++++ .../terminal/browser/terminalService.ts | 34 +------------ .../terminal/browser/terminalTabsList.ts | 15 +++--- 6 files changed, 113 insertions(+), 50 deletions(-) create mode 100644 src/vs/workbench/contrib/terminal/browser/terminalEditingService.ts diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 08df4db79e3..40b580ef0f0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -21,6 +21,7 @@ import { WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/ import { EditorExtensions, IEditorFactoryRegistry } from '../../../common/editor.js'; import { IViewContainersRegistry, IViewsRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation } from '../../../common/views.js'; import { ITerminalProfileService, TERMINAL_VIEW_ID, TerminalCommandId } from '../common/terminal.js'; +import { TerminalEditingService } from './terminalEditingService.js'; import { registerColors } from '../common/terminalColorRegistry.js'; import { registerTerminalConfiguration } from '../common/terminalConfiguration.js'; import { terminalStrings } from '../common/terminalStrings.js'; @@ -29,7 +30,7 @@ import './media/terminalVoice.css'; import './media/widgets.css'; import './media/xterm.css'; import { RemoteTerminalBackendContribution } from './remoteTerminalBackend.js'; -import { ITerminalConfigurationService, ITerminalEditorService, ITerminalGroupService, ITerminalInstanceService, ITerminalService, TerminalDataTransfers, terminalEditorId } from './terminal.js'; +import { ITerminalConfigurationService, ITerminalEditingService, ITerminalEditorService, ITerminalGroupService, ITerminalInstanceService, ITerminalService, TerminalDataTransfers, terminalEditorId } from './terminal.js'; import { registerTerminalActions } from './terminalActions.js'; import { setupTerminalCommands } from './terminalCommands.js'; import { TerminalConfigurationService } from './terminalConfigurationService.js'; @@ -52,6 +53,7 @@ registerSingleton(ITerminalLogService, TerminalLogService, InstantiationType.Del registerSingleton(ITerminalConfigurationService, TerminalConfigurationService, InstantiationType.Delayed); registerSingleton(ITerminalService, TerminalService, InstantiationType.Delayed); registerSingleton(ITerminalEditorService, TerminalEditorService, InstantiationType.Delayed); +registerSingleton(ITerminalEditingService, TerminalEditingService, InstantiationType.Delayed); registerSingleton(ITerminalGroupService, TerminalGroupService, InstantiationType.Delayed); registerSingleton(ITerminalInstanceService, TerminalInstanceService, InstantiationType.Delayed); registerSingleton(ITerminalProfileService, TerminalProfileService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index d22e0083204..79c048232e6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -38,6 +38,7 @@ import type { TerminalEditorInput } from './terminalEditorInput.js'; export const ITerminalService = createDecorator('terminalService'); export const ITerminalConfigurationService = createDecorator('terminalConfigurationService'); export const ITerminalEditorService = createDecorator('terminalEditorService'); +export const ITerminalEditingService = createDecorator('terminalEditingService'); export const ITerminalGroupService = createDecorator('terminalGroupService'); export const ITerminalInstanceService = createDecorator('terminalInstanceService'); @@ -99,6 +100,48 @@ export interface ITerminalInstanceService { didRegisterBackend(backend: ITerminalBackend): void; } +/** + * A service responsible for managing terminal editing state and functionality. This includes + * tracking which terminal is currently being edited and managing editable data associated with + * terminal instances. + */ +export interface ITerminalEditingService { + readonly _serviceBrand: undefined; + + /** + * Get the editable data for a terminal instance. + * @param instance The terminal instance. + * @returns The editable data if the instance is editable, undefined otherwise. + */ + getEditableData(instance: ITerminalInstance): IEditableData | undefined; + + /** + * Set the editable data for a terminal instance. + * @param instance The terminal instance. + * @param data The editable data to set, or null to clear. + */ + setEditable(instance: ITerminalInstance, data: IEditableData | null): void; + + /** + * Check if a terminal instance is currently editable. + * @param instance The terminal instance to check. + * @returns True if the instance is editable, false otherwise. + */ + isEditable(instance: ITerminalInstance | undefined): boolean; + + /** + * Get the terminal instance that is currently being edited. + * @returns The terminal instance being edited, or undefined if none. + */ + getEditingTerminal(): ITerminalInstance | undefined; + + /** + * Set the terminal instance that is currently being edited. + * @param instance The terminal instance to set as editing, or undefined to clear. + */ + setEditingTerminal(instance: ITerminalInstance | undefined): void; +} + export const enum Direction { Left = 0, Right = 1, @@ -337,9 +380,6 @@ export interface ITerminalService extends ITerminalInstanceHost { requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): Promise; isAttachedToTerminal(remoteTerm: IRemoteTerminalAttachTarget): boolean; - getEditableData(instance: ITerminalInstance): IEditableData | undefined; - setEditable(instance: ITerminalInstance, data: IEditableData | null): void; - isEditable(instance: ITerminalInstance | undefined): boolean; safeDisposeTerminal(instance: ITerminalInstance): Promise; getDefaultInstanceHost(): ITerminalInstanceHost; @@ -348,9 +388,6 @@ export interface ITerminalService extends ITerminalInstanceHost { resolveLocation(location?: ITerminalLocationOptions): Promise; setNativeDelegate(nativeCalls: ITerminalServiceNativeDelegate): void; - getEditingTerminal(): ITerminalInstance | undefined; - setEditingTerminal(instance: ITerminalInstance | undefined): void; - /** * Creates an instance event listener that listens to all instances, dynamically adding new * instances and removing old instances as needed. diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 4a92db80ba3..f4a95af46bf 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -55,7 +55,7 @@ import { accessibleViewCurrentProviderId, accessibleViewIsShown, accessibleViewO import { IRemoteTerminalAttachTarget, ITerminalProfileResolverService, ITerminalProfileService, TERMINAL_VIEW_ID, TerminalCommandId } from '../common/terminal.js'; import { TerminalContextKeys } from '../common/terminalContextKey.js'; import { terminalStrings } from '../common/terminalStrings.js'; -import { Direction, ICreateTerminalOptions, IDetachedTerminalInstance, ITerminalConfigurationService, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService, IXtermTerminal } from './terminal.js'; +import { Direction, ICreateTerminalOptions, IDetachedTerminalInstance, ITerminalConfigurationService, ITerminalEditorService, ITerminalEditingService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService, IXtermTerminal } from './terminal.js'; import { isAuxiliaryWindow } from '../../../../base/browser/window.js'; import { InstanceContext } from './terminalContextMenu.js'; import { getColorClass, getIconId, getUriClasses } from './terminalIcon.js'; @@ -275,6 +275,7 @@ export interface ITerminalServicesCollection { groupService: ITerminalGroupService; instanceService: ITerminalInstanceService; editorService: ITerminalEditorService; + editingService: ITerminalEditingService; profileService: ITerminalProfileService; profileResolverService: ITerminalProfileResolverService; } @@ -286,6 +287,7 @@ function getTerminalServices(accessor: ServicesAccessor): ITerminalServicesColle groupService: accessor.get(ITerminalGroupService), instanceService: accessor.get(ITerminalInstanceService), editorService: accessor.get(ITerminalEditorService), + editingService: accessor.get(ITerminalEditingService), profileService: accessor.get(ITerminalProfileService), profileResolverService: accessor.get(ITerminalProfileResolverService) }; @@ -787,13 +789,13 @@ export function registerTerminalActions() { return renameWithQuickPick(c, accessor, firstInstance); } - c.service.setEditingTerminal(firstInstance); - c.service.setEditable(firstInstance, { + c.editingService.setEditingTerminal(firstInstance); + c.editingService.setEditable(firstInstance, { validationMessage: value => validateTerminalName(value), onFinish: async (value, success) => { // Cancel editing first as instance.rename will trigger a rerender automatically - c.service.setEditable(firstInstance, null); - c.service.setEditingTerminal(undefined); + c.editingService.setEditable(firstInstance, null); + c.editingService.setEditingTerminal(undefined); if (success) { const promises: Promise[] = []; for (const instance of instances) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditingService.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditingService.ts new file mode 100644 index 00000000000..50baa0c6cb5 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditingService.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IEditableData } from '../../../common/views.js'; +import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { ITerminalEditingService, ITerminalInstance } from './terminal.js'; +import { TERMINAL_VIEW_ID } from '../common/terminal.js'; +import { TerminalViewPane } from './terminalView.js'; + +export class TerminalEditingService implements ITerminalEditingService { + readonly _serviceBrand: undefined; + + private _editable: { instance: ITerminalInstance; data: IEditableData } | undefined; + private _editingTerminal: ITerminalInstance | undefined; + + constructor( + @IViewsService private readonly _viewsService: IViewsService + ) { + } + + getEditableData(instance: ITerminalInstance): IEditableData | undefined { + return this._editable && this._editable.instance === instance ? this._editable.data : undefined; + } + + setEditable(instance: ITerminalInstance, data: IEditableData | null): void { + if (!data) { + this._editable = undefined; + } else { + this._editable = { instance: instance, data }; + } + const pane = this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID); + const isEditing = this.isEditable(instance); + pane?.terminalTabbedView?.setEditable(isEditing); + } + + isEditable(instance: ITerminalInstance | undefined): boolean { + return !!this._editable && (this._editable.instance === instance || !instance); + } + + getEditingTerminal(): ITerminalInstance | undefined { + return this._editingTerminal; + } + + setEditingTerminal(instance: ITerminalInstance | undefined): void { + this._editingTerminal = instance; + } +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 0b6f74ab4be..2dc6b743173 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -29,16 +29,14 @@ import { IThemeService, Themable } from '../../../../platform/theme/common/theme import { ThemeIcon } from '../../../../base/common/themables.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; import { VirtualWorkspaceContext } from '../../../common/contextkeys.js'; -import { IEditableData } from '../../../common/views.js'; -import { IViewsService } from '../../../services/views/common/viewsService.js'; + import { ICreateTerminalOptions, IDetachedTerminalInstance, IDetachedXTermOptions, IRequestAddInstanceToGroupEvent, ITerminalConfigurationService, ITerminalEditorService, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceHost, ITerminalInstanceService, ITerminalLocationOptions, ITerminalService, ITerminalServiceNativeDelegate, TerminalConnectionState, TerminalEditorLocation } from './terminal.js'; import { getCwdForSplit } from './terminalActions.js'; import { TerminalEditorInput } from './terminalEditorInput.js'; import { getColorStyleContent, getUriClasses } from './terminalIcon.js'; import { TerminalProfileQuickpick } from './terminalProfileQuickpick.js'; import { getInstanceFromResource, getTerminalUri, parseTerminalUri } from './terminalUri.js'; -import { TerminalViewPane } from './terminalView.js'; -import { IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalProcessExtHostProxy, ITerminalProfileService, TERMINAL_VIEW_ID } from '../common/terminal.js'; +import { IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalProcessExtHostProxy, ITerminalProfileService } from '../common/terminal.js'; import { TerminalContextKeys } from '../common/terminalContextKey.js'; import { columnToEditorGroup } from '../../../services/editor/common/editorGroupColumn.js'; import { IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; @@ -80,8 +78,6 @@ export class TerminalService extends Disposable implements ITerminalService { private _nativeDelegate?: ITerminalServiceNativeDelegate; private _shutdownWindowCount?: number; - private _editable: { instance: ITerminalInstance; data: IEditableData } | undefined; - get isProcessSupportRegistered(): boolean { return !!this._processSupportContextKey.get(); } private _connectionState: TerminalConnectionState = TerminalConnectionState.Connecting; @@ -127,8 +123,6 @@ export class TerminalService extends Disposable implements ITerminalService { return this._activeInstance; } - private _editingTerminal: ITerminalInstance | undefined; - private readonly _onDidCreateInstance = this._register(new Emitter()); get onDidCreateInstance(): Event { return this._onDidCreateInstance.event; } private readonly _onDidChangeInstanceDimensions = this._register(new Emitter()); @@ -175,7 +169,6 @@ export class TerminalService extends Disposable implements ITerminalService { @IDialogService private _dialogService: IDialogService, @IInstantiationService private _instantiationService: IInstantiationService, @IRemoteAgentService private _remoteAgentService: IRemoteAgentService, - @IViewsService private _viewsService: IViewsService, @IConfigurationService private readonly _configurationService: IConfigurationService, @ITerminalConfigurationService private readonly _terminalConfigService: ITerminalConfigurationService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @@ -588,24 +581,7 @@ export class TerminalService extends Disposable implements ITerminalService { await this.revealTerminal(instance, preserveFocus); } - setEditable(instance: ITerminalInstance, data?: IEditableData | null): void { - if (!data) { - this._editable = undefined; - } else { - this._editable = { instance: instance, data }; - } - const pane = this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID); - const isEditing = this.isEditable(instance); - pane?.terminalTabbedView?.setEditable(isEditing); - } - isEditable(instance: ITerminalInstance | undefined): boolean { - return !!this._editable && (this._editable.instance === instance || !instance); - } - - getEditableData(instance: ITerminalInstance): IEditableData | undefined { - return this._editable && this._editable.instance === instance ? this._editable.data : undefined; - } requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): Promise { // The initial request came from the extension host, no need to wait for it @@ -1229,13 +1205,7 @@ export class TerminalService extends Disposable implements ITerminalService { this._terminalGroupService.setContainer(terminalContainer); } - getEditingTerminal(): ITerminalInstance | undefined { - return this._editingTerminal; - } - setEditingTerminal(instance: ITerminalInstance | undefined) { - this._editingTerminal = instance; - } createOnInstanceEvent(getEvent: (instance: ITerminalInstance) => Event): DynamicListEventMultiplexer { return new DynamicListEventMultiplexer(this.instances, this.onDidCreateInstance, this.onDidDisposeInstance, getEvent); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 71f21dfabfa..5318ed45e39 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -10,7 +10,7 @@ import { IContextKey, IContextKeyService } from '../../../../platform/contextkey import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; -import { ITerminalConfigurationService, ITerminalGroupService, ITerminalInstance, ITerminalService, TerminalDataTransfers } from './terminal.js'; +import { ITerminalConfigurationService, ITerminalGroupService, ITerminalInstance, ITerminalService, ITerminalEditingService, TerminalDataTransfers } from './terminal.js'; import { localize } from '../../../../nls.js'; import * as DOM from '../../../../base/browser/dom.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -81,6 +81,7 @@ export class TerminalTabList extends WorkbenchList { @IConfigurationService private readonly _configurationService: IConfigurationService, @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, + @ITerminalEditingService private readonly _terminalEditingService: ITerminalEditingService, @IInstantiationService instantiationService: IInstantiationService, @IDecorationsService decorationsService: IDecorationsService, @IThemeService private readonly _themeService: IThemeService, @@ -154,7 +155,7 @@ export class TerminalTabList extends WorkbenchList { await instance.focusWhenReady(); } - if (this._terminalService.getEditingTerminal()?.instanceId === e.element?.instanceId) { + if (this._terminalEditingService.getEditingTerminal()?.instanceId === e.element?.instanceId) { return; } @@ -166,7 +167,7 @@ export class TerminalTabList extends WorkbenchList { // on left click, if focus mode = single click, focus the element // unless multi-selection is in progress this.disposables.add(this.onMouseClick(async e => { - if (this._terminalService.getEditingTerminal()?.instanceId === e.element?.instanceId) { + if (this._terminalEditingService.getEditingTerminal()?.instanceId === e.element?.instanceId) { return; } @@ -220,7 +221,7 @@ export class TerminalTabList extends WorkbenchList { } refresh(cancelEditing: boolean = true): void { - if (cancelEditing && this._terminalService.isEditable(undefined)) { + if (cancelEditing && this._terminalEditingService.isEditable(undefined)) { this.domFocus(); } @@ -258,6 +259,7 @@ class TerminalTabsRenderer extends Disposable implements IListRenderer