diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 0ec2a192bc4..a624980adbf 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -74,6 +74,8 @@ export interface IAccessibleViewService { previous(): void; goToSymbol(): void; disableHint(): void; + getPosition(): Position | undefined; + setPosition(position: Position): void; /** * If the setting is enabled, provides the open accessible view hint as a localized string. * @param verbositySettingKey The setting key for the verbosity of the feature @@ -86,6 +88,11 @@ export const enum AccessibleViewType { View = 'view' } +export const enum NavigationType { + Previous = 'previous', + Next = 'next' +} + export interface IAccessibleViewOptions { readMoreUrl?: string; /** @@ -95,7 +102,7 @@ export interface IAccessibleViewOptions { type: AccessibleViewType; } -class AccessibleView extends Disposable { +export class AccessibleView extends Disposable { private _editorWidget: CodeEditorWidget; private _accessiblityHelpIsShown: IContextKey; @@ -572,7 +579,6 @@ export class AccessibleViewService extends Disposable implements IAccessibleView this._accessibleView = this._register(this._instantiationService.createInstance(AccessibleView)); } this._accessibleView.show(provider); - } next(): void { this._accessibleView?.next(); @@ -602,6 +608,12 @@ export class AccessibleViewService extends Disposable implements IAccessibleView showAccessibleViewHelp(): void { this._accessibleView?.showAccessibleViewHelp(); } + getPosition(): Position | undefined { + return this._accessibleView?.editorWidget.getPosition() ?? undefined; + } + setPosition(position: Position): void { + this._accessibleView?.editorWidget.setPosition(position); + } } diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 65539d8bed6..1073cf8767d 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -49,9 +49,6 @@ export namespace TerminalContextKeys { /** Whether any terminal is focused, including detached terminals used in other UI. */ export const focusInAny = new RawContextKey(TerminalContextKeyStrings.FocusInAny, false, localize('terminalFocusInAnyContextKey', "Whether any terminal is focused, including detached terminals used in other UI.")); - /** Whether the accessible buffer is focused. */ - export const accessibleBufferFocus = new RawContextKey(TerminalContextKeyStrings.AccessibleBufferFocus, false, localize('terminalAccessibleBufferFocusContextKey', "Whether the terminal accessible buffer is focused.")); - /** Whether a terminal in the editor area is focused. */ export const editorFocus = new RawContextKey(TerminalContextKeyStrings.EditorFocus, false, localize('terminalEditorFocusContextKey', "Whether a terminal in the editor area is focused.")); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index a9920c5d4a2..75c8bc9d66b 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -16,9 +16,9 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; -import { TerminalSettingId, terminalTabFocusModeContextKey } from 'vs/platform/terminal/common/terminal'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService, IAccessibleViewSymbol } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService, IAccessibleViewSymbol, NavigationType } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; @@ -30,11 +30,7 @@ import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/acces import { TerminalAccessibleContentProvider } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp'; import { TextAreaSyncAddon } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/textAreaSyncAddon'; import type { Terminal } from 'xterm'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { Registry } from 'vs/platform/registry/common/platform'; - -const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +import { Position } from 'vs/editor/common/core/position'; class TextAreaSyncContribution extends DisposableStore implements ITerminalContribution { static readonly ID = 'terminal.textAreaSync'; @@ -61,12 +57,10 @@ registerTerminalContribution(TextAreaSyncContribution.ID, TextAreaSyncContributi export class TerminalAccessibleBufferProvider extends DisposableStore implements IAccessibleContentProvider { options: IAccessibleViewOptions = { type: AccessibleViewType.View }; verbositySettingKey = AccessibilityVerbositySettingId.Terminal; - private _bufferTracker: BufferContentTracker; - + private _xterm: IXtermTerminal & { raw: Terminal } | undefined; constructor( private readonly _instance: Pick, - private readonly _xterm: Pick & { raw: Terminal }, - @IInstantiationService _instantiationService: IInstantiationService, + private _bufferTracker: BufferContentTracker, @IModelService _modelService: IModelService, @IConfigurationService _configurationService: IConfigurationService, @IContextKeyService _contextKeyService: IContextKeyService, @@ -75,7 +69,6 @@ export class TerminalAccessibleBufferProvider extends DisposableStore implements @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService ) { super(); - this._bufferTracker = _instantiationService.createInstance(BufferContentTracker, _xterm); this.add(_instance.onDidRunText(() => { const focusAfterRun = configurationService.getValue(TerminalSettingId.FocusAfterRun); if (focusAfterRun === 'terminal') { @@ -84,13 +77,18 @@ export class TerminalAccessibleBufferProvider extends DisposableStore implements _accessibleViewService.show(this); } })); + this.registerListeners(); } + onClose() { this._instance.focus(); } registerListeners(): void { + if (!this._xterm) { + return; + } this._xterm.raw.onWriteParsed(async () => { - if (this._xterm.raw.buffer.active.baseY === 0) { + if (this._xterm!.raw.buffer.active.baseY === 0) { this.provideContent(); this._accessibleViewService.show(this); } @@ -161,23 +159,101 @@ export class TerminalAccessibleBufferProvider extends DisposableStore implements } interface ICommandWithEditorLine { command: ITerminalCommand | ICurrentPartialCommand; lineNumber: number } -export class TerminalAccessibleViewContribution extends Disposable { - static ID: 'terminalAccessibleViewContribution'; - constructor() { + +export class TerminalAccessibleViewContribution extends Disposable implements ITerminalContribution { + static readonly ID = 'terminal.accessibleBufferProvider'; + static get(instance: ITerminalInstance): TerminalAccessibleViewContribution | null { + return instance.getContribution(TerminalAccessibleViewContribution.ID); + } + private _bufferTracker: BufferContentTracker | undefined; + private _xterm: Pick & { raw: Terminal } | undefined; + constructor( + private readonly _instance: ITerminalInstance, + processManager: ITerminalProcessManager, + widgetManager: TerminalWidgetManager, + @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService, + @IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); - this._register(AccessibleViewAction.addImplementation(90, 'terminal', async accessor => { - const accessibleViewService = accessor.get(IAccessibleViewService); - const instantiationService = accessor.get(IInstantiationService); - const terminalService = accessor.get(ITerminalService); - const terminal = await terminalService.getActiveOrCreateInstance(); - if (!terminal?.xterm) { - return; - } - accessibleViewService.show(instantiationService.createInstance(TerminalAccessibleBufferProvider, terminal, terminal.xterm)); + this._register(AccessibleViewAction.addImplementation(90, 'terminal', () => { + this.show(); + return true; }, TerminalContextKeys.focus)); } + xtermReady(xterm: IXtermTerminal & { raw: Terminal }): void { + const addon = this._instantiationService.createInstance(TextAreaSyncAddon, this._instance.capabilities); + xterm.raw.loadAddon(addon); + addon.activate(xterm.raw); + this._xterm = xterm; + } + show(): void { + if (!this._xterm) { + return; + } + if (!this._bufferTracker) { + this._bufferTracker = this._instantiationService.createInstance(BufferContentTracker, this._xterm); + } + this._accessibleViewService.show(this._instantiationService.createInstance(TerminalAccessibleBufferProvider, this._instance, this._bufferTracker)); + } + navigateToCommand(type: NavigationType): void { + const currentLine = this._accessibleViewService.getPosition()?.lineNumber; + const commands = this._getCommandsWithEditorLine(); + if (!commands?.length || !currentLine) { + return; + } + + const filteredCommands = type === NavigationType.Previous ? commands.filter(c => c.lineNumber < currentLine).sort((a, b) => b.lineNumber - a.lineNumber) : commands.filter(c => c.lineNumber > currentLine).sort((a, b) => a.lineNumber - b.lineNumber); + if (!filteredCommands.length) { + return; + } + this._accessibleViewService.setPosition(new Position(filteredCommands[0].lineNumber, 1)); + } + + private _getCommandsWithEditorLine(): ICommandWithEditorLine[] | undefined { + const capability = this._instance.capabilities.get(TerminalCapability.CommandDetection); + const commands = capability?.commands; + const currentCommand = capability?.currentCommand; + if (!commands?.length) { + return; + } + const result: ICommandWithEditorLine[] = []; + for (const command of commands) { + const lineNumber = this._getEditorLineForCommand(command); + if (!lineNumber) { + continue; + } + result.push({ command, lineNumber }); + } + if (currentCommand) { + const lineNumber = this._getEditorLineForCommand(currentCommand); + if (!!lineNumber) { + result.push({ command: currentCommand, lineNumber }); + } + } + return result; + } + + private _getEditorLineForCommand(command: ITerminalCommand | ICurrentPartialCommand): number | undefined { + if (!this._bufferTracker) { + return; + } + let line: number | undefined; + if ('marker' in command) { + line = command.marker?.line; + } else if ('commandStartMarker' in command) { + line = command.commandStartMarker?.line; + } + if (line === undefined || line < 0) { + return; + } + line = this._bufferTracker.bufferToEditorLineMapping.get(line); + if (line === undefined) { + return; + } + return line + 1; + } + } -workbenchRegistry.registerWorkbenchContribution(TerminalAccessibleViewContribution, LifecyclePhase.Eventually); +registerTerminalContribution(TerminalAccessibleViewContribution.ID, TerminalAccessibleViewContribution); export class TerminalAccessibilityHelpContribution extends Disposable { static ID: 'terminalAccessibilityHelpContribution'; @@ -195,7 +271,7 @@ export class TerminalAccessibilityHelpContribution extends Disposable { return; } accessibleViewService.show(instantiationService.createInstance(TerminalAccessibleContentProvider, instance, terminal)); - }, ContextKeyExpr.or(TerminalContextKeys.focus, TerminalContextKeys.accessibleBufferFocus))); + }, ContextKeyExpr.or(TerminalContextKeys.focus, ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))))); } } registerTerminalContribution(TerminalAccessibilityHelpContribution.ID, TerminalAccessibilityHelpContribution); @@ -216,20 +292,18 @@ class FocusAccessibleBufferAction extends Action2 { secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow] }, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, TerminalContextKeys.focus, ContextKeyExpr.or(terminalTabFocusModeContextKey, TerminalContextKeys.accessibleBufferFocus.negate())) + when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, TerminalContextKeys.focus) } ] }); } override async run(accessor: ServicesAccessor, ...args: any[]): Promise { - const instantiationService = accessor.get(IInstantiationService); - const accessibleViewService = accessor.get(IAccessibleViewService); const terminalService = accessor.get(ITerminalService); const terminal = await terminalService.getActiveOrCreateInstance(); if (!terminal?.xterm) { return; } - accessibleViewService.show(instantiationService.createInstance(TerminalAccessibleBufferProvider, terminal, terminal.xterm)); + TerminalAccessibleViewContribution.get(terminal)?.show(); } } registerAction2(FocusAccessibleBufferAction); @@ -237,16 +311,11 @@ registerAction2(FocusAccessibleBufferAction); registerTerminalAction({ id: TerminalCommandId.AccessibleBufferGoToNextCommand, title: { value: localize('workbench.action.terminal.accessibleBufferGoToNextCommand', 'Accessible Buffer Go to Next Command'), original: 'Accessible Buffer Go to Next Command' }, - precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated, TerminalContextKeys.accessibleBufferFocus), + precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated, ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), keybinding: [ - { - primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - when: ContextKeyExpr.and(TerminalContextKeys.accessibleBufferFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), - weight: KeybindingWeight.WorkbenchContrib + 2 - }, { primary: KeyMod.Alt | KeyCode.DownArrow, - when: ContextKeyExpr.and(TerminalContextKeys.accessibleBufferFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + when: ContextKeyExpr.and(ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), weight: KeybindingWeight.WorkbenchContrib + 2 } ], @@ -256,8 +325,7 @@ registerTerminalAction({ if (!instance) { return; } - //TODO - // await AccessibleBufferContribution.get(instance)?.navigateToCommand(NavigationType.Next); + await TerminalAccessibleViewContribution.get(instance)?.navigateToCommand(NavigationType.Next); } }); @@ -265,16 +333,11 @@ registerTerminalAction({ registerTerminalAction({ id: TerminalCommandId.AccessibleBufferGoToPreviousCommand, title: { value: localize('workbench.action.terminal.accessibleBufferGoToPreviousCommand', 'Accessible Buffer Go to Previous Command'), original: 'Accessible Buffer Go to Previous Command' }, - precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.accessibleBufferFocus), + precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), keybinding: [ - { - primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - when: ContextKeyExpr.and(TerminalContextKeys.accessibleBufferFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), - weight: KeybindingWeight.WorkbenchContrib + 2 - }, { primary: KeyMod.Alt | KeyCode.UpArrow, - when: ContextKeyExpr.and(TerminalContextKeys.accessibleBufferFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + when: ContextKeyExpr.and(ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), weight: KeybindingWeight.WorkbenchContrib + 2 } ], @@ -284,7 +347,6 @@ registerTerminalAction({ if (!instance) { return; } - //TODO - // await AccessibleBufferContribution.get(instance)?.navigateToCommand(NavigationType.Previous); + await TerminalAccessibleViewContribution.get(instance)?.navigateToCommand(NavigationType.Previous); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts index 340427fd528..5feda2c2854 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts @@ -8,15 +8,14 @@ import { format } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ShellIntegrationStatus, TerminalSettingId, WindowsShellType } from 'vs/platform/terminal/common/terminal'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { ITerminalInstance, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import type { Terminal } from 'xterm'; export const enum ClassName { @@ -29,7 +28,8 @@ export class TerminalAccessibleContentProvider extends Disposable implements IAc private readonly _hasShellIntegration: boolean = false; onClose() { - if (this._contextKeyService.getContextKeyValue(TerminalContextKeys.accessibleBufferFocus.key) === true) { + const expr = ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal)); + if (expr?.evaluate(this._contextKeyService.getContext(null))) { this._commandService.executeCommand(TerminalCommandId.FocusAccessibleBuffer); } else { this._instance.focus(); diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts index 5d144353e9f..b158eec3bee 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts @@ -10,6 +10,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveInstanceAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; @@ -116,7 +117,7 @@ registerActiveInstanceAction({ keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO, weight: KeybindingWeight.WorkbenchContrib + 1, - when: ContextKeyExpr.or(TerminalContextKeys.focus, TerminalContextKeys.accessibleBufferFocus) + when: ContextKeyExpr.or(TerminalContextKeys.focus, ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))) }, run: (activeInstance) => TerminalLinkContribution.get(activeInstance)?.showLinkQuickpick() });