Change:
a screen reader on, focus the terminal and type some characters left and right arrows to inspect the input something else and then refocus the terminal and try to do the same the textarea has been cleared so it announces blank
the text is in the textarea actually, but not visible and not detected by the screen reader after the blur !

Discussion:
*  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ <del> import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; </del> <ins> import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; </ins> import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ITerminalCapabilityStore, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { ITerminalLogService } from 'vs/platform/terminal/common/terminal'; import type { Terminal, ITerminalAddon } from 'xterm'; <ins> import { debounce } from 'vs/base/common/decorators'; import { addDisposableListener } from 'vs/base/browser/dom'; </ins> export interface ITextAreaData { content: string;

export class TextAreaSyncAddon extends Disposable implements ITerminalAddon { private _terminal: Terminal | undefined; <del> private _onCursorMoveListener = this._register(new MutableDisposable()); </del> <ins> private _listeners = this._register(new MutableDisposable<DisposableStore>()); private _currentCommand: string | undefined; private _cursorX: number | undefined; </ins> activate(terminal: Terminal): void { this._terminal = terminal; if (this._accessibilityService.isScreenReaderOptimized()) { <del> this._onCursorMoveListener.value = this._terminal.onCursorMove(() => this._refreshTextArea()); </del> <ins> this._registerSyncListeners(); </ins> } }

) { super(); this._register(this._accessibilityService.onDidChangeScreenReaderOptimized(() => { <del> if (this._accessibilityService.isScreenReaderOptimized() && this._terminal) { this._refreshTextArea(); this._onCursorMoveListener.value = this._terminal.onCursorMove(() => this._refreshTextArea()); </del> <ins> if (this._accessibilityService.isScreenReaderOptimized()) { this._syncTextArea(); this._registerSyncListeners(); </ins> } else { <del> this._onCursorMoveListener.clear(); </del> <ins> this._listeners.clear(); </ins> } })); } <del> private _refreshTextArea(focusChanged?: boolean): void { </del> <ins> 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 { </ins> if (!this._terminal) { return; } <del> this._logService.debug('TextAreaSyncAddon#refreshTextArea'); </del> const commandCapability = this._capabilities.get(TerminalCapability.CommandDetection); const currentCommand = commandCapability?.currentCommand; if (!currentCommand) { <del> this._logService.debug(`TextAreaSyncAddon#refreshTextArea: no currentCommand`); </del> <ins> this._logService.debug(`TextAreaSyncAddon#updateCommandAndCursor: no current command`); </ins> return; } const buffer = this._terminal.buffer.active; const line = buffer.getLine(buffer.cursorY)?.translateToString(true); <del> let commandStartX: number | undefined; </del> if (!line) { <del> this._logService.debug(`TextAreaSyncAddon#refreshTextArea: no line`); </del> <ins> this._logService.debug(`TextAreaSyncAddon#updateCommandAndCursor: no line`); </ins> return; } <del> let content: string | undefined; if (currentCommand.commandStartX) { </del> <ins> if (currentCommand.commandStartX !== undefined) { </ins> // Left prompt <del> content = line.substring(currentCommand.commandStartX); commandStartX = currentCommand.commandStartX; } else if (currentCommand.commandRightPromptStartX) { </del> <ins> this._currentCommand = line.substring(currentCommand.commandStartX); this._cursorX = buffer.cursorX - currentCommand.commandStartX; } else if (currentCommand.commandRightPromptStartX !== undefined) { </ins> // Right prompt <del> content = line.substring(0, currentCommand.commandRightPromptStartX); commandStartX = 0; } 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}`); </del> <ins> this._currentCommand = line.substring(0, currentCommand.commandRightPromptStartX); this._cursorX = buffer.cursorX; } else { this._currentCommand = undefined; this._cursorX = undefined; this._logService.debug(`TextAreaSyncAddon#updateCommandAndCursor: neither commandStartX nor commandRightPromptStartX`); </ins> } <del> // TODO: cursorY? </del> } }