mirror of
https://github.com/Microsoft/vscode
synced 2024-10-12 14:30:13 +00:00
Merge pull request #191010 from microsoft/merogge/text-area
synchronize xterm textarea on focus & up arrow
This commit is contained in:
commit
691b5edcf6
|
@ -3,11 +3,13 @@
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||||
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
||||||
import { ITerminalCapabilityStore, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
|
import { ITerminalCapabilityStore, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
|
||||||
import { ITerminalLogService } from 'vs/platform/terminal/common/terminal';
|
import { ITerminalLogService } from 'vs/platform/terminal/common/terminal';
|
||||||
import type { Terminal, ITerminalAddon } from 'xterm';
|
import type { Terminal, ITerminalAddon } from 'xterm';
|
||||||
|
import { debounce } from 'vs/base/common/decorators';
|
||||||
|
import { addDisposableListener } from 'vs/base/browser/dom';
|
||||||
|
|
||||||
export interface ITextAreaData {
|
export interface ITextAreaData {
|
||||||
content: string;
|
content: string;
|
||||||
|
@ -16,11 +18,14 @@ export interface ITextAreaData {
|
||||||
|
|
||||||
export class TextAreaSyncAddon extends Disposable implements ITerminalAddon {
|
export class TextAreaSyncAddon extends Disposable implements ITerminalAddon {
|
||||||
private _terminal: Terminal | undefined;
|
private _terminal: Terminal | undefined;
|
||||||
private _onCursorMoveListener = this._register(new MutableDisposable());
|
private _listeners = this._register(new MutableDisposable<DisposableStore>());
|
||||||
|
private _currentCommand: string | undefined;
|
||||||
|
private _cursorX: number | undefined;
|
||||||
|
|
||||||
activate(terminal: Terminal): void {
|
activate(terminal: Terminal): void {
|
||||||
this._terminal = terminal;
|
this._terminal = terminal;
|
||||||
if (this._accessibilityService.isScreenReaderOptimized()) {
|
if (this._accessibilityService.isScreenReaderOptimized()) {
|
||||||
this._onCursorMoveListener.value = this._terminal.onCursorMove(() => this._refreshTextArea());
|
this._registerSyncListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,75 +36,78 @@ export class TextAreaSyncAddon extends Disposable implements ITerminalAddon {
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this._register(this._accessibilityService.onDidChangeScreenReaderOptimized(() => {
|
this._register(this._accessibilityService.onDidChangeScreenReaderOptimized(() => {
|
||||||
if (this._accessibilityService.isScreenReaderOptimized() && this._terminal) {
|
if (this._accessibilityService.isScreenReaderOptimized()) {
|
||||||
this._refreshTextArea();
|
this._syncTextArea();
|
||||||
this._onCursorMoveListener.value = this._terminal.onCursorMove(() => this._refreshTextArea());
|
this._registerSyncListeners();
|
||||||
} else {
|
} else {
|
||||||
this._onCursorMoveListener.clear();
|
this._listeners.clear();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _refreshTextArea(focusChanged?: boolean): void {
|
private _registerSyncListeners(): void {
|
||||||
|
if (this._accessibilityService.isScreenReaderOptimized() && this._terminal?.textarea) {
|
||||||
|
this._listeners.value = new DisposableStore();
|
||||||
|
this._listeners.value.add(this._terminal.onCursorMove(() => this._syncTextArea()));
|
||||||
|
this._listeners.value.add(this._terminal.onData(() => this._syncTextArea()));
|
||||||
|
this._listeners.value.add(addDisposableListener(this._terminal.textarea, 'focus', () => this._syncTextArea()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@debounce(50)
|
||||||
|
private _syncTextArea(): void {
|
||||||
|
this._logService.debug('TextAreaSyncAddon#syncTextArea');
|
||||||
|
const textArea = this._terminal?.textarea;
|
||||||
|
if (!textArea) {
|
||||||
|
this._logService.debug(`TextAreaSyncAddon#syncTextArea: no textarea`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateCommandAndCursor();
|
||||||
|
|
||||||
|
if (this._currentCommand !== textArea.value) {
|
||||||
|
textArea.value = this._currentCommand || '';
|
||||||
|
this._logService.debug(`TextAreaSyncAddon#syncTextArea: text changed to "${this._currentCommand}"`);
|
||||||
|
} else if (!this._currentCommand) {
|
||||||
|
textArea.value = '';
|
||||||
|
this._logService.debug(`TextAreaSyncAddon#syncTextArea: text cleared`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._cursorX !== textArea.selectionStart) {
|
||||||
|
textArea.selectionStart = this._cursorX ?? 0;
|
||||||
|
textArea.selectionEnd = this._cursorX ?? 0;
|
||||||
|
this._logService.debug(`TextAreaSyncAddon#syncTextArea: selection start/end changed to ${this._cursorX}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateCommandAndCursor(): void {
|
||||||
if (!this._terminal) {
|
if (!this._terminal) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._logService.debug('TextAreaSyncAddon#refreshTextArea');
|
|
||||||
const commandCapability = this._capabilities.get(TerminalCapability.CommandDetection);
|
const commandCapability = this._capabilities.get(TerminalCapability.CommandDetection);
|
||||||
const currentCommand = commandCapability?.currentCommand;
|
const currentCommand = commandCapability?.currentCommand;
|
||||||
if (!currentCommand) {
|
if (!currentCommand) {
|
||||||
this._logService.debug(`TextAreaSyncAddon#refreshTextArea: no currentCommand`);
|
this._logService.debug(`TextAreaSyncAddon#updateCommandAndCursor: no current command`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const buffer = this._terminal.buffer.active;
|
const buffer = this._terminal.buffer.active;
|
||||||
const line = buffer.getLine(buffer.cursorY)?.translateToString(true);
|
const line = buffer.getLine(buffer.cursorY)?.translateToString(true);
|
||||||
let commandStartX: number | undefined;
|
|
||||||
if (!line) {
|
if (!line) {
|
||||||
this._logService.debug(`TextAreaSyncAddon#refreshTextArea: no line`);
|
this._logService.debug(`TextAreaSyncAddon#updateCommandAndCursor: no line`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let content: string | undefined;
|
if (currentCommand.commandStartX !== undefined) {
|
||||||
if (currentCommand.commandStartX) {
|
|
||||||
// Left prompt
|
// Left prompt
|
||||||
content = line.substring(currentCommand.commandStartX);
|
this._currentCommand = line.substring(currentCommand.commandStartX);
|
||||||
commandStartX = currentCommand.commandStartX;
|
this._cursorX = buffer.cursorX - currentCommand.commandStartX;
|
||||||
} else if (currentCommand.commandRightPromptStartX) {
|
} else if (currentCommand.commandRightPromptStartX !== undefined) {
|
||||||
// Right prompt
|
// Right prompt
|
||||||
content = line.substring(0, currentCommand.commandRightPromptStartX);
|
this._currentCommand = line.substring(0, currentCommand.commandRightPromptStartX);
|
||||||
commandStartX = 0;
|
this._cursorX = buffer.cursorX;
|
||||||
|
} else {
|
||||||
|
this._currentCommand = undefined;
|
||||||
|
this._cursorX = undefined;
|
||||||
|
this._logService.debug(`TextAreaSyncAddon#updateCommandAndCursor: neither commandStartX nor commandRightPromptStartX`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!content) {
|
|
||||||
this._logService.debug(`TextAreaSyncAddon#refreshTextArea: no content`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (commandStartX === undefined) {
|
|
||||||
this._logService.debug(`TextAreaSyncAddon#refreshTextArea: no commandStartX`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const textArea = this._terminal.textarea;
|
|
||||||
if (!textArea) {
|
|
||||||
this._logService.debug(`TextAreaSyncAddon#refreshTextArea: no textarea`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._logService.debug(`TextAreaSyncAddon#refreshTextArea: content is "${content}"`);
|
|
||||||
this._logService.debug(`TextAreaSyncAddon#refreshTextArea: textContent is "${textArea.textContent}"`);
|
|
||||||
if (focusChanged || content !== textArea.textContent) {
|
|
||||||
textArea.textContent = content;
|
|
||||||
this._logService.debug(`TextAreaSyncAddon#refreshTextArea: textContent changed to "${content}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const cursorX = buffer.cursorX - commandStartX;
|
|
||||||
this._logService.debug(`TextAreaSyncAddon#refreshTextArea: cursorX is ${cursorX}`);
|
|
||||||
this._logService.debug(`TextAreaSyncAddon#refreshTextArea: selectionStart is ${textArea.selectionStart}`);
|
|
||||||
if (focusChanged || cursorX !== textArea.selectionStart) {
|
|
||||||
textArea.selectionStart = cursorX;
|
|
||||||
textArea.selectionEnd = cursorX;
|
|
||||||
this._logService.debug(`TextAreaSyncAddon#refreshTextArea: selectionStart changed to ${cursorX}`);
|
|
||||||
}
|
|
||||||
// TODO: cursorY?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue