mirror of
https://github.com/Microsoft/vscode
synced 2024-10-04 02:14:06 +00:00
keep the accessible buffer visible until tab/escape
are used (#176054)
This commit is contained in:
parent
8ddd3bcf39
commit
ffe596935b
|
@ -87,13 +87,13 @@
|
|||
"vscode-proxy-agent": "^0.12.0",
|
||||
"vscode-regexpp": "^3.1.0",
|
||||
"vscode-textmate": "9.0.0",
|
||||
"xterm": "5.2.0-beta.29",
|
||||
"xterm": "5.2.0-beta.30",
|
||||
"xterm-addon-canvas": "0.4.0-beta.7",
|
||||
"xterm-addon-search": "0.11.0",
|
||||
"xterm-addon-serialize": "0.9.0",
|
||||
"xterm-addon-unicode11": "0.5.0",
|
||||
"xterm-addon-webgl": "0.15.0-beta.7",
|
||||
"xterm-headless": "5.2.0-beta.29",
|
||||
"xterm-headless": "5.2.0-beta.30",
|
||||
"yauzl": "^2.9.2",
|
||||
"yazl": "^2.4.3"
|
||||
},
|
||||
|
|
|
@ -24,13 +24,13 @@
|
|||
"vscode-proxy-agent": "^0.12.0",
|
||||
"vscode-regexpp": "^3.1.0",
|
||||
"vscode-textmate": "9.0.0",
|
||||
"xterm": "5.2.0-beta.29",
|
||||
"xterm": "5.2.0-beta.30",
|
||||
"xterm-addon-canvas": "0.4.0-beta.7",
|
||||
"xterm-addon-search": "0.11.0",
|
||||
"xterm-addon-serialize": "0.9.0",
|
||||
"xterm-addon-unicode11": "0.5.0",
|
||||
"xterm-addon-webgl": "0.15.0-beta.7",
|
||||
"xterm-headless": "5.2.0-beta.29",
|
||||
"xterm-headless": "5.2.0-beta.30",
|
||||
"yauzl": "^2.9.2",
|
||||
"yazl": "^2.4.3"
|
||||
},
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"tas-client-umd": "0.1.6",
|
||||
"vscode-oniguruma": "1.7.0",
|
||||
"vscode-textmate": "9.0.0",
|
||||
"xterm": "5.2.0-beta.29",
|
||||
"xterm": "5.2.0-beta.30",
|
||||
"xterm-addon-canvas": "0.4.0-beta.7",
|
||||
"xterm-addon-search": "0.11.0",
|
||||
"xterm-addon-unicode11": "0.5.0",
|
||||
|
|
|
@ -88,7 +88,7 @@ xterm-addon-webgl@0.15.0-beta.7:
|
|||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.15.0-beta.7.tgz#ab247b499f61e8eebff92e08ec5ca999d87e06af"
|
||||
integrity sha512-7WCI/D6uFNp3y9TeTsbSo1h7gCy4h/yP2lWn8ZEjCaiGvO11DbKMq17fbiwaR3YmGWXoRKkcLaNIiqxFnjKO4w==
|
||||
|
||||
xterm@5.2.0-beta.29:
|
||||
version "5.2.0-beta.29"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.29.tgz#99764aff5cd8cdb4335f5d59466b134cfcb45e3e"
|
||||
integrity sha512-zx5RKcQqo78bza4R/m3WtxAJCBAF4U61fy6cxqb1PkqXF9/qdYlySUCVOauMxv+6n6cAxt3EQWwLlgvbvQBbsw==
|
||||
xterm@5.2.0-beta.30:
|
||||
version "5.2.0-beta.30"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.30.tgz#6f50796d1652a61b30eeed7fa2bdd9c485a7d8ee"
|
||||
integrity sha512-l1YBwMnakKXd638oxbzEg9Y1sWqxcrm/q7i5gBuWaK8N7Tq1NvF51FCamxXtfdL4dostgw8WoM+/6KRlL53t6A==
|
||||
|
|
|
@ -866,15 +866,15 @@ xterm-addon-webgl@0.15.0-beta.7:
|
|||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.15.0-beta.7.tgz#ab247b499f61e8eebff92e08ec5ca999d87e06af"
|
||||
integrity sha512-7WCI/D6uFNp3y9TeTsbSo1h7gCy4h/yP2lWn8ZEjCaiGvO11DbKMq17fbiwaR3YmGWXoRKkcLaNIiqxFnjKO4w==
|
||||
|
||||
xterm-headless@5.2.0-beta.29:
|
||||
version "5.2.0-beta.29"
|
||||
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.2.0-beta.29.tgz#dd08312fdb4292c217e685d9e2e8b1957364e298"
|
||||
integrity sha512-1P4urIeDTkl2C+zGb4WUnKJMACZMPGYHwVXMjkB0WhMISbkt6M34MH9ljxHhnL99dHwlx2Lvi6wvhnpyZucWCg==
|
||||
xterm-headless@5.2.0-beta.30:
|
||||
version "5.2.0-beta.30"
|
||||
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.2.0-beta.30.tgz#f40b950f744111537a6403d33782669b1149fabb"
|
||||
integrity sha512-aW6yljrcuu74kxg3w1DG1CZJSz38nKY/HOX3YOOE7cqxlkVXM7lltXZFEiF0xXDR0GHcmnEwnFWqA2rDmdhoDA==
|
||||
|
||||
xterm@5.2.0-beta.29:
|
||||
version "5.2.0-beta.29"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.29.tgz#99764aff5cd8cdb4335f5d59466b134cfcb45e3e"
|
||||
integrity sha512-zx5RKcQqo78bza4R/m3WtxAJCBAF4U61fy6cxqb1PkqXF9/qdYlySUCVOauMxv+6n6cAxt3EQWwLlgvbvQBbsw==
|
||||
xterm@5.2.0-beta.30:
|
||||
version "5.2.0-beta.30"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.30.tgz#6f50796d1652a61b30eeed7fa2bdd9c485a7d8ee"
|
||||
integrity sha512-l1YBwMnakKXd638oxbzEg9Y1sWqxcrm/q7i5gBuWaK8N7Tq1NvF51FCamxXtfdL4dostgw8WoM+/6KRlL53t6A==
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
|
|
@ -587,18 +587,28 @@
|
|||
outline-color: var(--vscode-focusBorder);
|
||||
}
|
||||
|
||||
/* Overrides for the a11y buffer as we're hosting monaco within it */
|
||||
.xterm .xterm-accessible-buffer {
|
||||
padding: 0 !important;
|
||||
overflow: initial !important;
|
||||
overflow-x: initial !important;
|
||||
}
|
||||
.xterm .xterm-accessible-buffer:focus-within {
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
pointer-events: all !important;
|
||||
}
|
||||
.xterm > .xterm-accessible-buffer {
|
||||
.monaco-workbench .accessible-buffer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
/* Reset cursor style as monaco controls it here */
|
||||
cursor: default;
|
||||
padding: 0;
|
||||
overflow: initial;
|
||||
overflow-x: initial;
|
||||
}
|
||||
|
||||
.monaco-workbench .accessible-buffer div {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.monaco-workbench .accessible-buffer.focus-within,
|
||||
.monaco-workbench .accessible-buffer.active {
|
||||
pointer-events: all;
|
||||
opacity: 1;
|
||||
z-index: 33;
|
||||
background-color: var(--vscode-terminal-background, --vscode-panel-background);
|
||||
}
|
||||
|
|
|
@ -209,27 +209,6 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.xterm .xterm-accessible-buffer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: .5em;
|
||||
opacity: 0;
|
||||
overflow: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.xterm .xterm-accessible-buffer div {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.xterm .xterm-accessible-buffer:focus-within {
|
||||
opacity: 1;
|
||||
z-index: 33;
|
||||
top: -5px;
|
||||
left: 5px;
|
||||
pointer-events: none;
|
||||
background-color: var(--vscode-terminal-background, --vscode-panel-background);
|
||||
.xterm.terminal.hide {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import { IEditableData } from 'vs/workbench/common/views';
|
|||
import { ITerminalStatusList } from 'vs/workbench/contrib/terminal/browser/terminalStatusList';
|
||||
import { ScrollPosition } from 'vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon';
|
||||
import { ITerminalQuickFixAddon } from 'vs/workbench/contrib/terminal/browser/xterm/quickFixAddon';
|
||||
import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal';
|
||||
import { IRegisterContributedProfileArgs, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalBackend, ITerminalConfigHelper, ITerminalFont, ITerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
|
||||
import { ISimpleSelectedSuggestion } from 'vs/workbench/services/suggest/browser/simpleSuggestWidget';
|
||||
|
@ -587,7 +588,7 @@ export interface ITerminalInstance {
|
|||
/**
|
||||
* The xterm.js instance for this terminal.
|
||||
*/
|
||||
readonly xterm?: IXtermTerminal;
|
||||
readonly xterm?: XtermTerminal;
|
||||
|
||||
/**
|
||||
* Returns an array of data events that have fired within the first 10 seconds. If this is
|
||||
|
@ -1040,11 +1041,6 @@ export interface IXtermTerminal {
|
|||
*/
|
||||
getBufferReverseIterator(): IterableIterator<string>;
|
||||
|
||||
/**
|
||||
* Focuses the accessible buffer, updating its contents
|
||||
*/
|
||||
focusAccessibleBuffer(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Refreshes the terminal after it has been moved.
|
||||
*/
|
||||
|
|
|
@ -28,7 +28,7 @@ import { IListService } from 'vs/platform/list/browser/listService';
|
|||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IPickOptions, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ITerminalProfile, TerminalExitReason, TerminalLocation, TerminalSettingId, terminalTabFocusContextKey } from 'vs/platform/terminal/common/terminal';
|
||||
import { ITerminalProfile, TerminalExitReason, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
|
||||
import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
|
@ -60,7 +60,6 @@ import { FileKind } from 'vs/platform/files/common/files';
|
|||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
|
||||
import { killTerminalIcon, newTerminalIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons';
|
||||
import { editorTabFocusContextKey } from 'vs/workbench/browser/parts/editor/tabFocus';
|
||||
|
||||
export const switchTerminalActionViewItemSeparator = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500';
|
||||
export const switchTerminalShowTabsTitle = localize('showTerminalTabs', "Show Tabs");
|
||||
|
@ -380,27 +379,6 @@ export function registerTerminalActions() {
|
|||
}
|
||||
}
|
||||
});
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: TerminalCommandId.FocusAccessibleBuffer,
|
||||
title: { value: localize('workbench.action.terminal.focusAccessibleBuffer', 'Focus Accessible Buffer'), original: 'Focus Accessible Buffer' },
|
||||
f1: true,
|
||||
category,
|
||||
precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated),
|
||||
keybinding: [
|
||||
{
|
||||
primary: KeyMod.Shift | KeyCode.Tab,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.or(terminalTabFocusContextKey, ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, editorTabFocusContextKey))
|
||||
}
|
||||
],
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
await accessor.get(ITerminalService).activeInstance?.xterm?.focusAccessibleBuffer();
|
||||
}
|
||||
});
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
|
|
|
@ -116,7 +116,6 @@ function getXtermConstructor(): Promise<typeof XTermTerminal> {
|
|||
// Localize strings
|
||||
Terminal.strings.promptLabel = nls.localize('terminal.integrated.a11yPromptLabel', 'Terminal input');
|
||||
Terminal.strings.tooMuchOutput = nls.localize('terminal.integrated.a11yTooMuchOutput', 'Too much output to announce, navigate to rows manually to read');
|
||||
Terminal.strings.accessibleBuffer = nls.localize('terminal.integrated.accessibleBuffer', 'Terminal buffer');
|
||||
resolve(Terminal);
|
||||
});
|
||||
return xtermConstructor;
|
||||
|
|
|
@ -12,7 +12,7 @@ import type { SerializeAddon as SerializeAddonType } from 'xterm-addon-serialize
|
|||
import { IXtermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private';
|
||||
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
|
||||
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { IShellIntegration, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { ITerminalFont } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
|
@ -35,17 +35,6 @@ import { Emitter } from 'vs/base/common/event';
|
|||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { SuggestAddon } from 'vs/workbench/contrib/terminal/browser/xterm/suggestAddon';
|
||||
import { IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IModelService } from 'vs/editor/common/services/model';
|
||||
import { StringBuilder } from 'vs/editor/common/core/stringBuilder';
|
||||
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
|
||||
import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
|
||||
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
|
||||
import { LinkDetector } from 'vs/editor/contrib/links/browser/links';
|
||||
import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard';
|
||||
import { addDisposableListener } from 'vs/base/browser/dom';
|
||||
|
||||
const enum RenderConstants {
|
||||
/**
|
||||
|
@ -148,8 +137,6 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II
|
|||
private _webglAddon?: WebglAddonType;
|
||||
private _serializeAddon?: SerializeAddonType;
|
||||
|
||||
private _accessibleBuffer: AccessibleBuffer | undefined;
|
||||
|
||||
private _lastFindResult: { resultIndex: number; resultCount: number } | undefined;
|
||||
get findResult(): { resultIndex: number; resultCount: number } | undefined { return this._lastFindResult; }
|
||||
get isStdinDisabled(): boolean { return !!this.raw.options.disableStdin; }
|
||||
|
@ -273,10 +260,6 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II
|
|||
});
|
||||
}
|
||||
|
||||
async focusAccessibleBuffer(): Promise<void> {
|
||||
this._accessibleBuffer?.focus();
|
||||
}
|
||||
|
||||
async getSelectionAsHtml(command?: ITerminalCommand): Promise<string> {
|
||||
if (!this._serializeAddon) {
|
||||
const Addon = await this._getSerializeAddonConstructor();
|
||||
|
@ -304,7 +287,6 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II
|
|||
if (!this._container) {
|
||||
this.raw.open(container);
|
||||
}
|
||||
this._accessibleBuffer = this._instantiationService.createInstance(AccessibleBuffer, this, this._capabilities);
|
||||
// TODO: Move before open to the DOM renderer doesn't initialize
|
||||
if (this._shouldLoadWebgl()) {
|
||||
this._enableWebglRenderer();
|
||||
|
@ -754,152 +736,3 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II
|
|||
this.raw.write(data);
|
||||
}
|
||||
}
|
||||
|
||||
const enum AccessibleBufferConstants {
|
||||
Scheme = 'terminal-accessible-buffer'
|
||||
}
|
||||
|
||||
class AccessibleBuffer extends DisposableStore {
|
||||
private _accessibleBuffer: HTMLElement;
|
||||
private _bufferEditor: CodeEditorWidget;
|
||||
private _editorContainer: HTMLElement;
|
||||
private _commandFinishedDisposable: IDisposable | undefined;
|
||||
private _refreshSelection: boolean = true;
|
||||
private _registered: boolean = false;
|
||||
private _lastContentLength: number = 0;
|
||||
private _font: ITerminalFont;
|
||||
|
||||
constructor(
|
||||
private readonly _terminal: XtermTerminal,
|
||||
private readonly _capabilities: ITerminalCapabilityStore,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IModelService private readonly _modelService: IModelService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
const codeEditorWidgetOptions: ICodeEditorWidgetOptions = {
|
||||
isSimpleWidget: true,
|
||||
contributions: EditorExtensionsRegistry.getSomeEditorContributions([LinkDetector.ID, SelectionClipboardContributionID])
|
||||
};
|
||||
this._font = this._terminal.getFont();
|
||||
const editorOptions: IEditorConstructionOptions = {
|
||||
...getSimpleEditorOptions(),
|
||||
lineDecorationsWidth: 6,
|
||||
dragAndDrop: true,
|
||||
cursorWidth: 1,
|
||||
fontSize: this._font.fontSize,
|
||||
lineHeight: this._font.charHeight ? this._font.charHeight * this._font.lineHeight : 1,
|
||||
fontFamily: this._font.fontFamily,
|
||||
wrappingStrategy: 'advanced',
|
||||
wrappingIndent: 'none',
|
||||
padding: { top: 2, bottom: 2 },
|
||||
quickSuggestions: false,
|
||||
renderWhitespace: 'none',
|
||||
dropIntoEditor: { enabled: true },
|
||||
accessibilitySupport: configurationService.getValue<'auto' | 'off' | 'on'>('editor.accessibilitySupport'),
|
||||
cursorBlinking: configurationService.getValue('terminal.integrated.cursorBlinking'),
|
||||
readOnly: true
|
||||
};
|
||||
this._accessibleBuffer = this._terminal.raw.element!.querySelector('.xterm-accessible-buffer') as HTMLElement;
|
||||
// Prevent the accessible buffer letting mouse events to propogate to xterm.js while it's
|
||||
// visible.
|
||||
this.add(addDisposableListener(this._accessibleBuffer, 'mousedown', e => e.stopImmediatePropagation()));
|
||||
this._accessibleBuffer.tabIndex = -1;
|
||||
this._editorContainer = document.createElement('div');
|
||||
this._bufferEditor = this._instantiationService.createInstance(CodeEditorWidget, this._editorContainer, editorOptions, codeEditorWidgetOptions);
|
||||
this.add(configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectedKeys.has(TerminalSettingId.FontFamily)) {
|
||||
this._font = this._terminal.getFont();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async focus(): Promise<void> {
|
||||
await this._updateBufferEditor();
|
||||
}
|
||||
|
||||
private async _updateBufferEditor(): Promise<void> {
|
||||
const commandDetection = this._capabilities.get(TerminalCapability.CommandDetection);
|
||||
const fragment = !!commandDetection ? this._getShellIntegrationContent() : this._getAllContent();
|
||||
const model = await this._getTextModel(URI.from({ scheme: AccessibleBufferConstants.Scheme, fragment }));
|
||||
if (model) {
|
||||
this._bufferEditor.setModel(model);
|
||||
}
|
||||
|
||||
if (!this._registered) {
|
||||
this.add(this._terminal.raw.registerBufferElementProvider({ provideBufferElements: () => this._editorContainer }));
|
||||
// When this is created, the element isn't yet attached so the dimensions are tiny
|
||||
this._bufferEditor.layout({ width: this._accessibleBuffer.clientWidth, height: this._accessibleBuffer.clientHeight });
|
||||
this._registered = true;
|
||||
}
|
||||
|
||||
if (!this._commandFinishedDisposable && commandDetection) {
|
||||
this._commandFinishedDisposable = commandDetection.onCommandFinished(() => this._refreshSelection = true);
|
||||
this.add(this._commandFinishedDisposable);
|
||||
}
|
||||
|
||||
if (this._lastContentLength !== fragment.length || this._refreshSelection) {
|
||||
let lineNumber = 1;
|
||||
const lineCount = model?.getLineCount();
|
||||
if (lineCount && model) {
|
||||
lineNumber = commandDetection ? lineCount - 1 : lineCount > 2 ? lineCount - 2 : 1;
|
||||
}
|
||||
this._bufferEditor.setSelection({ startLineNumber: lineNumber, startColumn: 1, endLineNumber: lineNumber, endColumn: 1 });
|
||||
this._bufferEditor.setScrollTop(this._bufferEditor.getScrollHeight());
|
||||
this._refreshSelection = false;
|
||||
this._lastContentLength = fragment.length;
|
||||
}
|
||||
|
||||
// Updates xterm's accessibleBufferActive property
|
||||
// such that mouse events do not cause the terminal buffer
|
||||
// to steal the focus
|
||||
this._accessibleBuffer.focus();
|
||||
this._bufferEditor.focus();
|
||||
}
|
||||
|
||||
private async _getTextModel(resource: URI): Promise<ITextModel | null> {
|
||||
const existing = this._modelService.getModel(resource);
|
||||
if (existing && !existing.isDisposed()) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
return this._modelService.createModel(resource.fragment, null, resource, false);
|
||||
}
|
||||
|
||||
private _getShellIntegrationContent(): string {
|
||||
const commands = this._capabilities.get(TerminalCapability.CommandDetection)?.commands;
|
||||
const sb = new StringBuilder(10000);
|
||||
if (!commands?.length) {
|
||||
return this._getAllContent();
|
||||
}
|
||||
for (const command of commands) {
|
||||
sb.appendString(command.command.replace(new RegExp(' ', 'g'), '\xA0'));
|
||||
if (command.exitCode !== 0) {
|
||||
sb.appendString(` exited with code ${command.exitCode}`);
|
||||
}
|
||||
sb.appendString('\n');
|
||||
sb.appendString(command.getOutput()?.replace(new RegExp(' ', 'g'), '\xA0') || '');
|
||||
}
|
||||
return sb.build();
|
||||
}
|
||||
|
||||
private _getAllContent(): string {
|
||||
const lines: string[] = [];
|
||||
let currentLine: string = '';
|
||||
const buffer = this._terminal.raw.buffer.active;
|
||||
const end = buffer.length;
|
||||
for (let i = 0; i < end; i++) {
|
||||
const line = buffer.getLine(i);
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
const isWrapped = buffer.getLine(i + 1)?.isWrapped;
|
||||
currentLine += line.translateToString(!isWrapped);
|
||||
if (currentLine && !isWrapped || i === end - 1) {
|
||||
lines.push(currentLine.replace(new RegExp(' ', 'g'), '\xA0'));
|
||||
currentLine = '';
|
||||
}
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,19 +6,51 @@
|
|||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { TerminalLocation } from 'vs/platform/terminal/common/terminal';
|
||||
import { TerminalLocation, terminalTabFocusContextKey } from 'vs/platform/terminal/common/terminal';
|
||||
import { AccessibilityHelpWidget } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp';
|
||||
import { ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ITerminalContribution, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
|
||||
import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings';
|
||||
import { AccessibleBufferWidget } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer';
|
||||
import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Terminal } from 'xterm';
|
||||
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager';
|
||||
|
||||
const category = terminalStrings.actionCategory;
|
||||
|
||||
class AccessibleBufferContribution extends DisposableStore implements ITerminalContribution {
|
||||
static readonly ID: 'terminal.accessible-buffer';
|
||||
static get(instance: ITerminalInstance): AccessibleBufferContribution | null {
|
||||
return instance.getContribution<AccessibleBufferContribution>(AccessibleBufferContribution.ID);
|
||||
}
|
||||
private _accessibleBufferWidget: AccessibleBufferWidget | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly _instance: ITerminalInstance,
|
||||
processManager: ITerminalProcessManager,
|
||||
widgetManager: TerminalWidgetManager,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
xtermReady(xterm: IXtermTerminal & { raw: Terminal }): void {
|
||||
this._accessibleBufferWidget = this._instantiationService.createInstance(AccessibleBufferWidget, xterm, this._instance.capabilities);
|
||||
}
|
||||
show(): void {
|
||||
this._accessibleBufferWidget?.show();
|
||||
}
|
||||
}
|
||||
registerTerminalContribution(AccessibleBufferContribution.ID, AccessibleBufferContribution);
|
||||
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
|
@ -55,6 +87,37 @@ registerAction2(class extends Action2 {
|
|||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: TerminalCommandId.FocusAccessibleBuffer,
|
||||
title: { value: localize('workbench.action.terminal.focusAccessibleBuffer', 'Focus Accessible Buffer'), original: 'Focus Accessible Buffer' },
|
||||
f1: true,
|
||||
category,
|
||||
precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated),
|
||||
keybinding: [
|
||||
{
|
||||
primary: KeyMod.Shift | KeyCode.Tab,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, terminalTabFocusContextKey)
|
||||
}
|
||||
],
|
||||
});
|
||||
}
|
||||
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;
|
||||
}
|
||||
AccessibleBufferContribution.get(instance)?.show();
|
||||
}
|
||||
});
|
||||
|
||||
async function revealActiveTerminal(instance: ITerminalInstance, terminalEditorService: ITerminalEditorService, terminalGroupService: ITerminalGroupService): Promise<void> {
|
||||
if (instance.target === TerminalLocation.Editor) {
|
||||
await terminalEditorService.revealActiveEditor();
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
|
||||
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
|
||||
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { StringBuilder } from 'vs/editor/common/core/stringBuilder';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IModelService } from 'vs/editor/common/services/model';
|
||||
import { LinkDetector } from 'vs/editor/contrib/links/browser/links';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITerminalCapabilityStore, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
|
||||
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 type { Terminal } from 'xterm';
|
||||
|
||||
const enum AccessibleBufferConstants {
|
||||
Scheme = 'terminal-accessible-buffer'
|
||||
}
|
||||
|
||||
export class AccessibleBufferWidget extends DisposableStore {
|
||||
public static ID: string = AccessibleBufferConstants.Scheme;
|
||||
private _accessibleBuffer: HTMLElement;
|
||||
private _bufferEditor: CodeEditorWidget;
|
||||
private _editorContainer: HTMLElement;
|
||||
private _commandFinishedDisposable: IDisposable | undefined;
|
||||
private _refreshSelection: boolean = true;
|
||||
private _registered: boolean = false;
|
||||
private _lastContentLength: number = 0;
|
||||
private _font: ITerminalFont;
|
||||
private _xtermElement: HTMLElement;
|
||||
|
||||
constructor(
|
||||
private readonly _xterm: IXtermTerminal & { raw: Terminal },
|
||||
private readonly _capabilities: ITerminalCapabilityStore,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IModelService private readonly _modelService: IModelService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
const codeEditorWidgetOptions: ICodeEditorWidgetOptions = {
|
||||
isSimpleWidget: true,
|
||||
contributions: EditorExtensionsRegistry.getSomeEditorContributions([LinkDetector.ID, SelectionClipboardContributionID])
|
||||
};
|
||||
this._font = _xterm.getFont();
|
||||
this._xtermElement = _xterm.raw.element!;
|
||||
const editorOptions: IEditorConstructionOptions = {
|
||||
...getSimpleEditorOptions(),
|
||||
lineDecorationsWidth: 6,
|
||||
dragAndDrop: true,
|
||||
cursorWidth: 1,
|
||||
fontSize: this._font.fontSize,
|
||||
lineHeight: this._font.charHeight ? this._font.charHeight * this._font.lineHeight : 1,
|
||||
fontFamily: this._font.fontFamily,
|
||||
wrappingStrategy: 'advanced',
|
||||
wrappingIndent: 'none',
|
||||
padding: { top: 2, bottom: 2 },
|
||||
quickSuggestions: false,
|
||||
renderWhitespace: 'none',
|
||||
dropIntoEditor: { enabled: true },
|
||||
accessibilitySupport: this._configurationService.getValue<'auto' | 'off' | 'on'>('editor.accessibilitySupport'),
|
||||
cursorBlinking: this._configurationService.getValue('terminal.integrated.cursorBlinking'),
|
||||
readOnly: true
|
||||
};
|
||||
this._accessibleBuffer = document.createElement('div');
|
||||
this._accessibleBuffer.setAttribute('role', 'document');
|
||||
this._accessibleBuffer.ariaRoleDescription = localize('terminal.integrated.accessibleBuffer', 'Terminal buffer');
|
||||
this._accessibleBuffer.classList.add('accessible-buffer');
|
||||
const elt = _xterm.raw.element;
|
||||
if (elt) {
|
||||
elt.insertAdjacentElement('beforebegin', this._accessibleBuffer);
|
||||
}
|
||||
this._editorContainer = document.createElement('div');
|
||||
this._bufferEditor = this._instantiationService.createInstance(CodeEditorWidget, this._editorContainer, editorOptions, codeEditorWidgetOptions);
|
||||
this.add(this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectedKeys.has(TerminalSettingId.FontFamily)) {
|
||||
this._font = _xterm.getFont();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private _hide(): void {
|
||||
this._accessibleBuffer.classList.remove('active');
|
||||
this._xtermElement.classList.remove('hide');
|
||||
this._xterm.raw.focus();
|
||||
}
|
||||
|
||||
async show(): Promise<void> {
|
||||
const commandDetection = this._capabilities.get(TerminalCapability.CommandDetection);
|
||||
const fragment = !!commandDetection ? this._getShellIntegrationContent() : this._getAllContent();
|
||||
const model = await this._getTextModel(URI.from({ scheme: AccessibleBufferConstants.Scheme, fragment }));
|
||||
if (model) {
|
||||
this._bufferEditor.setModel(model);
|
||||
}
|
||||
if (!this._registered) {
|
||||
this._bufferEditor.layout({ width: this._xtermElement.clientWidth, height: this._xtermElement.clientHeight });
|
||||
this._bufferEditor.onKeyDown((e) => {
|
||||
if (e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Tab) {
|
||||
this._hide();
|
||||
}
|
||||
});
|
||||
if (commandDetection) {
|
||||
this._commandFinishedDisposable = commandDetection.onCommandFinished(() => this._refreshSelection = true);
|
||||
this.add(this._commandFinishedDisposable);
|
||||
}
|
||||
this._registered = true;
|
||||
}
|
||||
this._accessibleBuffer.tabIndex = -1;
|
||||
this._accessibleBuffer.classList.add('active');
|
||||
this._xtermElement.classList.add('hide');
|
||||
if (this._lastContentLength !== fragment.length || this._refreshSelection) {
|
||||
let lineNumber = 1;
|
||||
const lineCount = model?.getLineCount();
|
||||
if (lineCount && model) {
|
||||
lineNumber = commandDetection ? lineCount - 1 : lineCount > 2 ? lineCount - 2 : 1;
|
||||
}
|
||||
this._bufferEditor.setSelection({ startLineNumber: lineNumber, startColumn: 1, endLineNumber: lineNumber, endColumn: 1 });
|
||||
this._bufferEditor.setScrollTop(this._bufferEditor.getScrollHeight());
|
||||
this._refreshSelection = false;
|
||||
this._lastContentLength = fragment.length;
|
||||
}
|
||||
this._accessibleBuffer.replaceChildren(this._editorContainer);
|
||||
this._bufferEditor.focus();
|
||||
}
|
||||
|
||||
private async _getTextModel(resource: URI): Promise<ITextModel | null> {
|
||||
const existing = this._modelService.getModel(resource);
|
||||
if (existing && !existing.isDisposed()) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
return this._modelService.createModel(resource.fragment, null, resource, false);
|
||||
}
|
||||
|
||||
private _getShellIntegrationContent(): string {
|
||||
const commands = this._capabilities.get(TerminalCapability.CommandDetection)?.commands;
|
||||
const sb = new StringBuilder(10000);
|
||||
if (!commands?.length) {
|
||||
return this._getAllContent();
|
||||
}
|
||||
for (const command of commands) {
|
||||
sb.appendString(command.command.replace(new RegExp(' ', 'g'), '\xA0'));
|
||||
if (command.exitCode !== 0) {
|
||||
sb.appendString(` exited with code ${command.exitCode}`);
|
||||
}
|
||||
sb.appendString('\n');
|
||||
sb.appendString(command.getOutput()?.replace(new RegExp(' ', 'g'), '\xA0') || '');
|
||||
}
|
||||
return sb.build();
|
||||
}
|
||||
|
||||
private _getAllContent(): string {
|
||||
const lines: string[] = [];
|
||||
let currentLine: string = '';
|
||||
const buffer = this._xterm?.raw.buffer.active;
|
||||
if (!buffer) {
|
||||
return '';
|
||||
}
|
||||
const end = buffer.length;
|
||||
for (let i = 0; i < end; i++) {
|
||||
const line = buffer.getLine(i);
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
const isWrapped = buffer.getLine(i + 1)?.isWrapped;
|
||||
currentLine += line.translateToString(!isWrapped);
|
||||
if (currentLine && !isWrapped || i === end - 1) {
|
||||
lines.push(currentLine.replace(new RegExp(' ', 'g'), '\xA0'));
|
||||
currentLine = '';
|
||||
}
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
}
|
16
yarn.lock
16
yarn.lock
|
@ -11863,15 +11863,15 @@ xterm-addon-webgl@0.15.0-beta.7:
|
|||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.15.0-beta.7.tgz#ab247b499f61e8eebff92e08ec5ca999d87e06af"
|
||||
integrity sha512-7WCI/D6uFNp3y9TeTsbSo1h7gCy4h/yP2lWn8ZEjCaiGvO11DbKMq17fbiwaR3YmGWXoRKkcLaNIiqxFnjKO4w==
|
||||
|
||||
xterm-headless@5.2.0-beta.29:
|
||||
version "5.2.0-beta.29"
|
||||
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.2.0-beta.29.tgz#dd08312fdb4292c217e685d9e2e8b1957364e298"
|
||||
integrity sha512-1P4urIeDTkl2C+zGb4WUnKJMACZMPGYHwVXMjkB0WhMISbkt6M34MH9ljxHhnL99dHwlx2Lvi6wvhnpyZucWCg==
|
||||
xterm-headless@5.2.0-beta.30:
|
||||
version "5.2.0-beta.30"
|
||||
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.2.0-beta.30.tgz#f40b950f744111537a6403d33782669b1149fabb"
|
||||
integrity sha512-aW6yljrcuu74kxg3w1DG1CZJSz38nKY/HOX3YOOE7cqxlkVXM7lltXZFEiF0xXDR0GHcmnEwnFWqA2rDmdhoDA==
|
||||
|
||||
xterm@5.2.0-beta.29:
|
||||
version "5.2.0-beta.29"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.29.tgz#99764aff5cd8cdb4335f5d59466b134cfcb45e3e"
|
||||
integrity sha512-zx5RKcQqo78bza4R/m3WtxAJCBAF4U61fy6cxqb1PkqXF9/qdYlySUCVOauMxv+6n6cAxt3EQWwLlgvbvQBbsw==
|
||||
xterm@5.2.0-beta.30:
|
||||
version "5.2.0-beta.30"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.30.tgz#6f50796d1652a61b30eeed7fa2bdd9c485a7d8ee"
|
||||
integrity sha512-l1YBwMnakKXd638oxbzEg9Y1sWqxcrm/q7i5gBuWaK8N7Tq1NvF51FCamxXtfdL4dostgw8WoM+/6KRlL53t6A==
|
||||
|
||||
y18n@^3.2.1:
|
||||
version "3.2.2"
|
||||
|
|
Loading…
Reference in a new issue