Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use monaco editor in terminal accessible buffer #174400

Merged
merged 15 commits into from
Feb 16, 2023
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@
"vscode-proxy-agent": "^0.12.0",
"vscode-regexpp": "^3.1.0",
"vscode-textmate": "8.0.0",
"xterm": "5.2.0-beta.28",
"xterm": "5.2.0-beta.29",
"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.28",
"xterm-headless": "5.2.0-beta.29",
"yauzl": "^2.9.2",
"yazl": "^2.4.3"
},
Expand Down
4 changes: 2 additions & 2 deletions remote/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@
"vscode-proxy-agent": "^0.12.0",
"vscode-regexpp": "^3.1.0",
"vscode-textmate": "8.0.0",
"xterm": "5.2.0-beta.28",
"xterm": "5.2.0-beta.29",
"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.28",
"xterm-headless": "5.2.0-beta.29",
"yauzl": "^2.9.2",
"yazl": "^2.4.3"
},
Expand Down
2 changes: 1 addition & 1 deletion remote/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"tas-client-umd": "0.1.6",
"vscode-oniguruma": "1.7.0",
"vscode-textmate": "8.0.0",
"xterm": "5.2.0-beta.28",
"xterm": "5.2.0-beta.29",
"xterm-addon-canvas": "0.4.0-beta.7",
"xterm-addon-search": "0.11.0",
"xterm-addon-unicode11": "0.5.0",
Expand Down
8 changes: 4 additions & 4 deletions remote/web/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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.28:
version "5.2.0-beta.28"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.28.tgz#852347e4eaf5aae7d82c90592a42adc9daab2a79"
integrity sha512-aLDxCuqjWHjvnhfWfkxy/y6coNrC+QIhbDe2sdfLPkrxhK6KnYE6qiZD5jXUIQXeq0KmSDcYi/esuKujvobC2A==
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==
18 changes: 9 additions & 9 deletions remote/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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.28:
version "5.2.0-beta.28"
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.2.0-beta.28.tgz#d2c149da51ef138f46268b755c4fdc4202eb771c"
integrity sha512-4XcjBhFwuyjpz2ubESwp75UceySOOKdJszKyyxOQ3/7L937uiVEBBLc8T231XU8lSwWUU7czyNjYyCfpszY4+Q==

xterm@5.2.0-beta.28:
version "5.2.0-beta.28"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.28.tgz#852347e4eaf5aae7d82c90592a42adc9daab2a79"
integrity sha512-aLDxCuqjWHjvnhfWfkxy/y6coNrC+QIhbDe2sdfLPkrxhK6KnYE6qiZD5jXUIQXeq0KmSDcYi/esuKujvobC2A==
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@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==

yallist@^4.0.0:
version "4.0.0"
Expand Down
3 changes: 1 addition & 2 deletions src/vs/platform/terminal/common/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@ export const enum TerminalSettingId {
ShellIntegrationDecorationsEnabled = 'terminal.integrated.shellIntegration.decorationsEnabled',
ShellIntegrationCommandHistory = 'terminal.integrated.shellIntegration.history',
ShellIntegrationSuggestEnabled = 'terminal.integrated.shellIntegration.suggestEnabled',
SmoothScrolling = 'terminal.integrated.smoothScrolling',
AccessibleBufferContentEditable = 'terminal.integrated.accessibleBufferContentEditable'
SmoothScrolling = 'terminal.integrated.smoothScrolling'
}

export const enum TerminalLogConstants {
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/terminal/browser/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1053,7 +1053,7 @@ export interface IXtermTerminal {
/**
* Focuses the accessible buffer, updating its contents
*/
focusAccessibleBuffer(): void;
focusAccessibleBuffer(): Promise<void>;
}

export interface IInternalXtermTerminal {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ export function registerTerminalActions() {
});
}
async run(accessor: ServicesAccessor): Promise<void> {
accessor.get(ITerminalService).activeInstance?.xterm?.focusAccessibleBuffer();
await accessor.get(ITerminalService).activeInstance?.xterm?.focusAccessibleBuffer();
}
});
registerAction2(class extends Action2 {
Expand Down
174 changes: 116 additions & 58 deletions src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,16 @@ 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 { isLinux } from 'vs/base/common/platform';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
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';

const enum RenderConstants {
/**
Expand Down Expand Up @@ -281,7 +289,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II
});
}

focusAccessibleBuffer(): void {
async focusAccessibleBuffer(): Promise<void> {
this._accessibileBuffer?.focus();
}
meganrogge marked this conversation as resolved.
Show resolved Hide resolved

Expand Down Expand Up @@ -312,7 +320,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II
if (!this._container) {
this.raw.open(container);
}
this._accessibileBuffer = this._instantiationService.createInstance(AccessibleBuffer, this.raw, this.getFont(), this._capabilities);
this._accessibileBuffer = this._instantiationService.createInstance(AccessibleBuffer, this, this._capabilities);
// TODO: Move before open to the DOM renderer doesn't initialize
if (this._shouldLoadWebgl()) {
this._enableWebglRenderer();
Expand Down Expand Up @@ -763,76 +771,126 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II
this.raw.write(data);
}
}

const enum ACCESSIBLE_BUFFER { Scheme = 'terminal-accessible-buffer' }
class AccessibleBuffer extends DisposableStore {

private _accessibleBuffer: HTMLElement | undefined;
private _bufferElementFragment: DocumentFragment | undefined;
private _accessibleBuffer: HTMLElement;
private _bufferEditor: CodeEditorWidget;
private _editorContainer: HTMLElement;
private _registered: boolean = false;
private _font: ITerminalFont;

constructor(
private readonly _terminal: RawXtermTerminal,
private readonly _font: ITerminalFont,
private readonly _terminal: XtermTerminal,
private readonly _capabilities: ITerminalCapabilityStore,
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
@IConfigurationService private readonly _configurationService: IConfigurationService
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IModelService private readonly _modelService: IModelService,
@IConfigurationService configurationService: IConfigurationService
) {
super();
this.add(this._terminal.registerBufferElementProvider({ provideBufferElements: () => this.focus() }));
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,
scrollbar: { alwaysConsumeMouseWheel: 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;
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();
// 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();
}

focus(): DocumentFragment {
if (!this._bufferElementFragment) {
this._bufferElementFragment = document.createDocumentFragment();
private async _updateBufferEditor(): Promise<void> {
if (!this._registered) {
// Registration is delayed until focus so the capability has time to have been added
this.add(this._terminal.raw.registerBufferElementProvider({ provideBufferElements: () => this._editorContainer }));
this._registered = true;
}
this._accessibleBuffer = this._terminal.element?.querySelector('.xterm-accessible-buffer') as HTMLElement || undefined;
if (!this._accessibleBuffer) {
return this._bufferElementFragment;
// 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 });
const commandDetection = this._capabilities.has(TerminalCapability.CommandDetection);
const fragment = commandDetection ? this._getShellIntegrationContent() : this._getAllContent();
const model = await this._getTextModel(URI.from({ scheme: ACCESSIBLE_BUFFER.Scheme, fragment }));
if (model) {
this._bufferEditor.setModel(model);
}
// see https://github.com/microsoft/vscode/issues/173532
const accessibleBufferContentEditable = isLinux ? 'on' : this._configurationService.getValue(TerminalSettingId.AccessibleBufferContentEditable);
this._accessibleBuffer.contentEditable = accessibleBufferContentEditable === 'on' || (accessibleBufferContentEditable === 'auto' && !this._accessibilityService.isScreenReaderOptimized()) ? 'true' : 'false';
// The viewport is undefined when this is focused, so we cannot get the cell height from that. Instead, estimate using the font.
const lineHeight = this._font?.charHeight ? this._font.charHeight * this._font.lineHeight + 'px' : '';
this._accessibleBuffer.style.lineHeight = lineHeight;
}

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);
let content = localize('terminal.integrated.noContent', "No terminal content available for this session. Run some commands to create content.");
if (!commands?.length) {
const noContent = document.createElement('div');
const noContentLabel = localize('terminal.integrated.noContent', "No terminal content available for this session.");
noContent.textContent = noContentLabel;
this._bufferElementFragment.replaceChildren(noContent);
this._accessibleBuffer.focus();
return this._bufferElementFragment;
}
let header;
let replaceChildren = true;
return content;
}
for (const command of commands) {
header = document.createElement('h2');
// without this, the text area gets focused when keyboard shortcuts are used
header.tabIndex = -1;
header.textContent = command.command.replace(new RegExp(' ', 'g'), '\xA0');
sb.appendString(command.command.replace(new RegExp(' ', 'g'), '\xA0'));
if (command.exitCode !== 0) {
header.textContent += ` exited with code ${command.exitCode}`;
sb.appendString(` exited with code ${command.exitCode}`);
}
const output = document.createElement('div');
// without this, the text area gets focused when keyboard shortcuts are used
output.tabIndex = -1;
output.textContent = command.getOutput()?.replace(new RegExp(' ', 'g'), '\xA0') || '';
if (replaceChildren) {
this._bufferElementFragment.replaceChildren(header, output);
replaceChildren = false;
} else {
this._bufferElementFragment.appendChild(header);
this._bufferElementFragment.appendChild(output);
sb.appendString('\n');
sb.appendString(command.getOutput()?.replace(new RegExp(' ', 'g'), '\xA0') || '');
}
content = sb.build();
return content;
}

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 (!isWrapped || i === end - 1) {
lines.push(currentLine.replace(new RegExp(' ', 'g'), '\xA0'));
currentLine = '';
}
}
this._accessibleBuffer.focus();
if (this._accessibleBuffer.contentEditable === 'true') {
document.execCommand('selectAll', false, undefined);
document.getSelection()?.collapseToEnd();
} else if (header) {
// focus the cursor line's header
header.tabIndex = 0;
}
return this._bufferElementFragment;
return lines.join('\n');
}
}
1 change: 0 additions & 1 deletion src/vs/workbench/contrib/terminal/common/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,6 @@ export interface ITerminalConfiguration {
};
useWslProfiles: boolean;
altClickMovesCursor: boolean;
accessibleBufferContentEditable: 'auto' | 'on' | 'off';
macOptionIsMeta: boolean;
macOptionClickForcesSelection: boolean;
gpuAcceleration: 'auto' | 'on' | 'canvas' | 'off';
Expand Down
11 changes: 0 additions & 11 deletions src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -579,17 +579,6 @@ const terminalConfiguration: IConfigurationNode = {
markdownDescription: localize('terminal.integrated.smoothScrolling', "Controls whether the terminal will scroll using an animation."),
type: 'boolean',
default: false
},
[TerminalSettingId.AccessibleBufferContentEditable]: {
markdownDescription: localize('terminal.integrated.accessibleBufferContentEditable', "Controls whether the accessible buffer is marks as a `contenteditable` element. This adds a text cursor to the buffer, allowing selection with the keyboard without a screen reader. Screen reader users will typically want to leave this as `auto` or `off` which will treat the buffer similar to a document. By default, on Linux, this will be set to `on` so that it works when using Orca."),
type: 'string',
enum: ['auto', 'on', 'off'],
enumDescriptions: [
localize('accessibleBufferContentEditable.auto', "Automatically enable when a screen reader is not detected."),
localize('accessibleBufferContentEditable.on', "Always on."),
localize('accessibleBufferContentEditable.off', "Always off.")
],
default: 'auto'
}
}
};
Expand Down
18 changes: 9 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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.28:
version "5.2.0-beta.28"
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.2.0-beta.28.tgz#d2c149da51ef138f46268b755c4fdc4202eb771c"
integrity sha512-4XcjBhFwuyjpz2ubESwp75UceySOOKdJszKyyxOQ3/7L937uiVEBBLc8T231XU8lSwWUU7czyNjYyCfpszY4+Q==

xterm@5.2.0-beta.28:
version "5.2.0-beta.28"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.28.tgz#852347e4eaf5aae7d82c90592a42adc9daab2a79"
integrity sha512-aLDxCuqjWHjvnhfWfkxy/y6coNrC+QIhbDe2sdfLPkrxhK6KnYE6qiZD5jXUIQXeq0KmSDcYi/esuKujvobC2A==
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@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==

y18n@^3.2.1:
version "3.2.2"
Expand Down