Skip to content
Merged
1 change: 1 addition & 0 deletions src/vs/workbench/contrib/terminal/common/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ export const enum TerminalCommandId {
OpenWebLink = 'workbench.action.terminal.openUrlLink',
RunRecentCommand = 'workbench.action.terminal.runRecentCommand',
FocusAccessibleBuffer = 'workbench.action.terminal.focusAccessibleBuffer',
NavigateAccessibleBuffer = 'workbench.action.terminal.navigateAccessibleBuffer',
CopyLastCommandOutput = 'workbench.action.terminal.copyLastCommandOutput',
GoToRecentDirectory = 'workbench.action.terminal.goToRecentDirectory',
CopyAndClearSelection = 'workbench.action.terminal.copyAndClearSelection',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const enum TerminalContextKeyStrings {
HasFixedWidth = 'terminalHasFixedWidth',
ProcessSupported = 'terminalProcessSupported',
Focus = 'terminalFocus',
AccessibleBufferFocus = 'terminalAccessibleBufferFocus',
EditorFocus = 'terminalEditorFocus',
TabsFocus = 'terminalTabsFocus',
WebExtensionContributedProfile = 'terminalWebExtensionContributedProfile',
Expand Down Expand Up @@ -42,6 +43,9 @@ export namespace TerminalContextKeys {
/** Whether the terminal is focused. */
export const focus = new RawContextKey<boolean>(TerminalContextKeyStrings.Focus, false, localize('terminalFocusContextKey', "Whether the terminal is focused."));

/** Whether the accessible buffer is focused. */
export const accessibleBufferFocus = new RawContextKey<boolean>(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<boolean>(TerminalContextKeyStrings.EditorFocus, false, localize('terminalEditorFocusContextKey', "Whether a terminal in the editor area is focused."));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/brow
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Terminal } from 'xterm';
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager';
import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';

const category = terminalStrings.actionCategory;

Expand All @@ -42,11 +43,15 @@ class AccessibleBufferContribution extends DisposableStore implements ITerminalC
}
layout(xterm: IXtermTerminal & { raw: Terminal }): void {
if (!this._accessibleBufferWidget) {
this._accessibleBufferWidget = this._instantiationService.createInstance(AccessibleBufferWidget, this._instance.instanceId, xterm);
this._accessibleBufferWidget = this._instantiationService.createInstance(AccessibleBufferWidget, this._instance, xterm);
}
}
show(): void {
this._accessibleBufferWidget?.show();
async show(): Promise<void> {
await this._accessibleBufferWidget?.show();
}

async createCommandQuickPick(): Promise<IQuickPick<IQuickPickItem> | undefined> {
return this._accessibleBufferWidget?.createQuickPick();
}
}
registerTerminalContribution(AccessibleBufferContribution.ID, AccessibleBufferContribution);
Expand Down Expand Up @@ -115,7 +120,39 @@ registerAction2(class extends Action2 {
if (!instance) {
return;
}
AccessibleBufferContribution.get(instance)?.show();
await AccessibleBufferContribution.get(instance)?.show();
}
});

registerAction2(class extends Action2 {
constructor() {
super({
id: TerminalCommandId.NavigateAccessibleBuffer,
title: { value: localize('workbench.action.terminal.navigateAccessibleBuffer', 'Navigate Accessible Buffer'), original: 'Navigate Accessible Buffer' },
f1: true,
category,
precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated),
keybinding: [
{
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO,
weight: KeybindingWeight.WorkbenchContrib + 2,
when: ContextKeyExpr.or(TerminalContextKeys.accessibleBufferFocus, ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, TerminalContextKeys.focus))
}
],
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const terminalService = accessor.get(ITerminalService);
const terminalGroupService = accessor.get(ITerminalGroupService);
const terminalEditorService = accessor.get(ITerminalEditorService);

const instance = await terminalService.getActiveOrCreateInstance();
await revealActiveTerminal(instance, terminalEditorService, terminalGroupService);
if (!instance) {
return;
}
const quickPick = await AccessibleBufferContribution.get(instance)?.createCommandQuickPick();
quickPick?.show();
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { KeyCode } from 'vs/base/common/keyCodes';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import * as dom from 'vs/base/browser/dom';
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
Expand All @@ -19,8 +20,13 @@ import { TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard';
import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
import { ITerminalFont } from 'vs/workbench/contrib/terminal/common/terminal';
import { IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalInstance, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal';
import type { Terminal } from 'xterm';
import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';

const enum Constants {
Scheme = 'terminal-accessible-buffer',
Expand All @@ -35,15 +41,22 @@ export class AccessibleBufferWidget extends DisposableStore {
private _editorContainer: HTMLElement;
private _font: ITerminalFont;
private _xtermElement: HTMLElement;
private readonly _focusedContextKey: IContextKey<boolean>;
private readonly _focusTracker: dom.IFocusTracker;

constructor(
private readonly _instanceId: number,
private readonly _instance: ITerminalInstance,
private readonly _xterm: IXtermTerminal & { raw: Terminal },
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IModelService private readonly _modelService: IModelService,
@IConfigurationService private readonly _configurationService: IConfigurationService
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IQuickInputService private readonly _quickInputService: IQuickInputService,
@IAudioCueService private readonly _audioCueService: IAudioCueService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,

) {
super();
this._focusedContextKey = TerminalContextKeys.accessibleBufferFocus.bindTo(this._contextKeyService);
const codeEditorWidgetOptions: ICodeEditorWidgetOptions = {
isSimpleWidget: true,
contributions: EditorExtensionsRegistry.getSomeEditorContributions([LinkDetector.ID, SelectionClipboardContributionID])
Expand Down Expand Up @@ -76,6 +89,9 @@ export class AccessibleBufferWidget extends DisposableStore {
this._editorContainer = document.createElement('div');
this._accessibleBuffer.tabIndex = -1;
this._bufferEditor = this._instantiationService.createInstance(CodeEditorWidget, this._editorContainer, editorOptions, codeEditorWidgetOptions);
this._focusTracker = this.add(dom.trackFocus(this._editorContainer));
this.add(this._focusTracker.onDidFocus(() => this._focusedContextKey.set(true)));
this.add(this._focusTracker.onDidBlur(() => this._focusedContextKey.reset()));
this._accessibleBuffer.replaceChildren(this._editorContainer);
this._xtermElement.insertAdjacentElement('beforebegin', this._accessibleBuffer);
this._bufferEditor.layout({ width: this._xtermElement.clientWidth, height: this._xtermElement.clientHeight });
Expand All @@ -95,18 +111,18 @@ export class AccessibleBufferWidget extends DisposableStore {
}
}));
this.add(this._xterm.raw.onWriteParsed(async () => {
if (this._accessibleBuffer.classList.contains(Constants.Active)) {
if (this._focusedContextKey.get()) {
await this._updateEditor(true);
}
}));
this._updateEditor();
this.add(this._bufferEditor.onDidFocusEditorText(async () => {
// if the editor is focused via tab, we need to update the model
// and show it
await this._updateEditor();
this._accessibleBuffer.classList.add(Constants.Active);
this._xtermElement.classList.add(Constants.Hide);
}));
this._updateEditor();
}

private _hide(): void {
Expand All @@ -122,7 +138,7 @@ export class AccessibleBufferWidget extends DisposableStore {
const lineNumber = lineCount + 1;
model.pushEditOperations(null, [{ range: { startLineNumber: lineNumber, endLineNumber: lineNumber, startColumn: 1, endColumn: 1 }, text: await this._getContent(lineNumber - 1) }], () => []);
} else {
model = await this._getTextModel(URI.from({ scheme: `${Constants.Scheme}-${this._instanceId}`, fragment: await this._getContent() }));
model = await this._getTextModel(URI.from({ scheme: `${Constants.Scheme}-${this._instance.instanceId}`, fragment: await this._getContent() }));
}
if (!model) {
throw new Error('Could not create accessible buffer editor model');
Expand All @@ -145,6 +161,49 @@ export class AccessibleBufferWidget extends DisposableStore {
this._bufferEditor.setScrollTop(this._bufferEditor.getScrollHeight());
}

async createQuickPick(): Promise<IQuickPick<IQuickPickItem> | undefined> {
if (!this._focusedContextKey.get()) {
await this.show();
}
const commands = this._instance.capabilities.get(TerminalCapability.CommandDetection)?.commands;
if (!commands?.length) {
return;
}
const quickPickItems: IQuickPickItem[] = [];
for (const command of commands) {
const line = command.marker?.line;
if (!line) {
continue;
}
quickPickItems.push(
{
label: localize('terminal.integrated.symbolQuickPick.labelNoExitCode', '{0}', command.command),
meta: JSON.stringify({ line: line + 1, exitCode: command.exitCode })
});
}
const quickPick = this._quickInputService.createQuickPick<IQuickPickItem>();
quickPick.onDidAccept(() => {
const item = quickPick.activeItems[0];
const model = this._bufferEditor.getModel();
if (!model || !item.meta) {
return;
}
quickPick.hide();
const data: { line: number; exitCode: number } = JSON.parse(item.meta);
this._bufferEditor.setSelection({ startLineNumber: data.line, startColumn: 1, endLineNumber: data.line, endColumn: 1 });
this._bufferEditor.revealLine(data.line);
return;
});
quickPick.onDidChangeActive(() => {
const data = quickPick.activeItems?.[0]?.meta;
if (data && JSON.parse(data).exitCode) {
this._audioCueService.playAudioCue(AudioCue.error, true);
}
});
quickPick.items = quickPickItems.reverse();
return quickPick;
}

async show(): Promise<void> {
await this._updateEditor();
this._accessibleBuffer.tabIndex = -1;
Expand Down