Не подтверждена Коммит aeccbc28 создал по автору Aiday Marlen Kyzy's avatar Aiday Marlen Kyzy Зафиксировано автором GitHub
Просмотр файлов

Changing the selection of the editor on selection change of the EditContext...

Changing the selection of the editor on selection change of the EditContext dom node (fixes NVDA cursor navigation) (#230204)

* add code

* adding work

* adding wip

* adding code

* adding code that fixes the nvda problem

* adding more code

* cleaning the code

* setting all of the endoflinepreferences to text defined

* adding the distinction between using line feed and not using it
владелец e64eeb4b
......@@ -5,7 +5,7 @@
import './nativeEditContext.css';
import { isFirefox } from '../../../../../base/browser/browser.js';
import { addDisposableListener } from '../../../../../base/browser/dom.js';
import { addDisposableListener, getActiveWindow } from '../../../../../base/browser/dom.js';
import { FastDomNode } from '../../../../../base/browser/fastDomNode.js';
import { StandardKeyboardEvent } from '../../../../../base/browser/keyboardEvent.js';
import { KeyCode } from '../../../../../base/common/keyCodes.js';
......@@ -26,6 +26,7 @@ import { Selection } from '../../../../common/core/selection.js';
import { Position } from '../../../../common/core/position.js';
import { IVisibleRangeProvider } from '../textArea/textAreaEditContext.js';
import { PositionOffsetTransformer } from '../../../../common/core/positionToOffset.js';
import { IDisposable, MutableDisposable } from '../../../../../base/common/lifecycle.js';
// Corresponds to classes in nativeEditContext.css
enum CompositionClassName {
......@@ -49,6 +50,8 @@ export class NativeEditContext extends AbstractEditContext {
private readonly _focusTracker: FocusTracker;
private readonly _selectionChangeListener: MutableDisposable<IDisposable>;
constructor(
context: ViewContext,
overflowGuardContainer: FastDomNode<HTMLElement>,
......@@ -66,7 +69,11 @@ export class NativeEditContext extends AbstractEditContext {
overflowGuardContainer.appendChild(this.domNode);
this._parent = overflowGuardContainer.domNode;
this._focusTracker = this._register(new FocusTracker(this.domNode.domNode, (newFocusValue: boolean) => this._context.viewModel.setHasFocus(newFocusValue)));
this._selectionChangeListener = this._register(new MutableDisposable());
this._focusTracker = this._register(new FocusTracker(this.domNode.domNode, (newFocusValue: boolean) => {
this._selectionChangeListener.value = newFocusValue ? this._setSelectionChangeListener(viewController) : undefined;
this._context.viewModel.setHasFocus(newFocusValue);
}));
this._editContext = new EditContext();
this.domNode.domNode.editContext = this._editContext;
......@@ -377,4 +384,38 @@ export class NativeEditContext extends AbstractEditContext {
);
clipboardService.writeText(dataToCopy.text);
}
private _setSelectionChangeListener(viewController: ViewController): IDisposable {
// See https://github.com/microsoft/vscode/issues/27216 and https://github.com/microsoft/vscode/issues/98256
// When using a Braille display or NVDA for example, it is possible for users to reposition the
// system caret. This is reflected in Chrome as a `selectionchange` event and needs to be reflected within the editor.
return addDisposableListener(this.domNode.domNode.ownerDocument, 'selectionchange', () => {
if (!this.isFocused()) {
return;
}
const activeDocument = getActiveWindow().document;
const activeDocumentSelection = activeDocument.getSelection();
if (!activeDocumentSelection) {
return;
}
const rangeCount = activeDocumentSelection.rangeCount;
if (rangeCount === 0) {
return;
}
const range = activeDocumentSelection.getRangeAt(0);
const startPositionOfScreenReaderContentWithinEditor = this._screenReaderSupport.startPositionOfScreenReaderContentWithinEditor();
if (!startPositionOfScreenReaderContentWithinEditor) {
return;
}
const model = this._context.viewModel.model;
const offsetOfStartOfScreenReaderContent = model.getOffsetAt(startPositionOfScreenReaderContentWithinEditor);
const offsetOfSelectionStart = range.startOffset + offsetOfStartOfScreenReaderContent;
const offsetOfSelectionEnd = range.endOffset + offsetOfStartOfScreenReaderContent;
const positionOfSelectionStart = model.getPositionAt(offsetOfSelectionStart);
const positionOfSelectionEnd = model.getPositionAt(offsetOfSelectionEnd);
const newSelection = Selection.fromPositions(positionOfSelectionStart, positionOfSelectionEnd);
viewController.setSelection(newSelection);
});
}
}
......@@ -121,6 +121,10 @@ export class ScreenReaderSupport {
this._setSelectionOfScreenReaderContent(this._screenReaderContentState.selectionStart, this._screenReaderContentState.selectionEnd);
}
public startPositionOfScreenReaderContentWithinEditor(): Position | undefined {
return this._screenReaderContentState?.startPositionWithinEditor;
}
private _getScreenReaderContentState(): ScreenReaderContentState | undefined {
if (this._accessibilitySupport === AccessibilitySupport.Disabled) {
return;
......@@ -142,7 +146,7 @@ export class ScreenReaderSupport {
return this._context.viewModel.modifyPosition(position, offset);
}
};
return PagedScreenReaderStrategy.fromEditorSelection(simpleModel, this._primarySelection, this._accessibilityPageSize, this._accessibilitySupport === AccessibilitySupport.Unknown);
return PagedScreenReaderStrategy.fromEditorSelection(simpleModel, this._primarySelection, this._accessibilityPageSize, this._accessibilitySupport === AccessibilitySupport.Unknown, false);
}
private _setSelectionOfScreenReaderContent(selectionOffsetStart: number, selectionOffsetEnd: number): void {
......
......@@ -31,6 +31,9 @@ export interface ScreenReaderContentState {
/** the editor range in the view coordinate system that matches the selection inside `value` */
selection: Range;
/** the position of the start of the `value` in the editor */
startPositionWithinEditor: Position;
/** the visible line count (wrapped, not necessarily matching \n characters) for the text in `value` before `selectionStart` */
newlineCountBeforeSelection: number;
}
......@@ -47,7 +50,7 @@ export class PagedScreenReaderStrategy {
return new Range(startLineNumber, 1, endLineNumber + 1, 1);
}
public static fromEditorSelection(model: ISimpleModel, selection: Range, linesPerPage: number, trimLongText: boolean): ScreenReaderContentState {
public static fromEditorSelection(model: ISimpleModel, selection: Range, linesPerPage: number, trimLongText: boolean, useLineFeed: boolean): ScreenReaderContentState {
// Chromium handles very poorly text even of a few thousand chars
// Cut text to avoid stalling the entire UI
const LIMIT_CHARS = 500;
......@@ -59,33 +62,34 @@ export class PagedScreenReaderStrategy {
const selectionEndPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionEndPage, linesPerPage);
let pretextRange = selectionStartPageRange.intersectRanges(new Range(1, 1, selection.startLineNumber, selection.startColumn))!;
if (trimLongText && model.getValueLengthInRange(pretextRange, EndOfLinePreference.LF) > LIMIT_CHARS) {
const endOfLinePreference = useLineFeed ? EndOfLinePreference.LF : EndOfLinePreference.TextDefined;
if (trimLongText && model.getValueLengthInRange(pretextRange, endOfLinePreference) > LIMIT_CHARS) {
const pretextStart = model.modifyPosition(pretextRange.getEndPosition(), -LIMIT_CHARS);
pretextRange = Range.fromPositions(pretextStart, pretextRange.getEndPosition());
}
const pretext = model.getValueInRange(pretextRange, EndOfLinePreference.LF);
const pretext = model.getValueInRange(pretextRange, endOfLinePreference);
const lastLine = model.getLineCount();
const lastLineMaxColumn = model.getLineMaxColumn(lastLine);
let posttextRange = selectionEndPageRange.intersectRanges(new Range(selection.endLineNumber, selection.endColumn, lastLine, lastLineMaxColumn))!;
if (trimLongText && model.getValueLengthInRange(posttextRange, EndOfLinePreference.LF) > LIMIT_CHARS) {
if (trimLongText && model.getValueLengthInRange(posttextRange, endOfLinePreference) > LIMIT_CHARS) {
const posttextEnd = model.modifyPosition(posttextRange.getStartPosition(), LIMIT_CHARS);
posttextRange = Range.fromPositions(posttextRange.getStartPosition(), posttextEnd);
}
const posttext = model.getValueInRange(posttextRange, EndOfLinePreference.LF);
const posttext = model.getValueInRange(posttextRange, endOfLinePreference);
let text: string;
if (selectionStartPage === selectionEndPage || selectionStartPage + 1 === selectionEndPage) {
// take full selection
text = model.getValueInRange(selection, EndOfLinePreference.LF);
text = model.getValueInRange(selection, endOfLinePreference);
} else {
const selectionRange1 = selectionStartPageRange.intersectRanges(selection)!;
const selectionRange2 = selectionEndPageRange.intersectRanges(selection)!;
text = (
model.getValueInRange(selectionRange1, EndOfLinePreference.LF)
model.getValueInRange(selectionRange1, endOfLinePreference)
+ String.fromCharCode(8230)
+ model.getValueInRange(selectionRange2, EndOfLinePreference.LF)
+ model.getValueInRange(selectionRange2, endOfLinePreference)
);
}
if (trimLongText && text.length > 2 * LIMIT_CHARS) {
......@@ -97,6 +101,7 @@ export class PagedScreenReaderStrategy {
selection: selection,
selectionStart: pretext.length,
selectionEnd: pretext.length + text.length,
startPositionWithinEditor: pretextRange.getStartPosition(),
newlineCountBeforeSelection: pretextRange.endLineNumber - pretextRange.startLineNumber,
};
}
......
......@@ -282,7 +282,7 @@ export class TextAreaEditContext extends AbstractEditContext {
return TextAreaState.EMPTY;
}
const screenReaderContentState = PagedScreenReaderStrategy.fromEditorSelection(simpleModel, this._selections[0], this._accessibilityPageSize, this._accessibilitySupport === AccessibilitySupport.Unknown);
const screenReaderContentState = PagedScreenReaderStrategy.fromEditorSelection(simpleModel, this._selections[0], this._accessibilityPageSize, this._accessibilitySupport === AccessibilitySupport.Unknown, true);
return TextAreaState.fromScreenReaderContentState(screenReaderContentState);
},
......
......@@ -118,7 +118,7 @@ function doCreateTest(description: string, inputStr: string, expectedStr: string
getScreenReaderContent: (): TextAreaState => {
const selection = new Range(1, 1 + cursorOffset, 1, 1 + cursorOffset + cursorLength);
const screenReaderContentState = PagedScreenReaderStrategy.fromEditorSelection(model, selection, 10, true);
const screenReaderContentState = PagedScreenReaderStrategy.fromEditorSelection(model, selection, 10, true, true);
return TextAreaState.fromScreenReaderContentState(screenReaderContentState);
},
deduceModelPosition: (viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position => {
......
......@@ -365,7 +365,7 @@ suite('TextAreaState', () => {
function testPagedScreenReaderStrategy(lines: string[], selection: Selection, expected: TextAreaState): void {
const model = createTextModel(lines.join('\n'));
const screenReaderContentState = PagedScreenReaderStrategy.fromEditorSelection(model, selection, 10, true);
const screenReaderContentState = PagedScreenReaderStrategy.fromEditorSelection(model, selection, 10, true, true);
const textAreaState = TextAreaState.fromScreenReaderContentState(screenReaderContentState);
assert.ok(equalsTextAreaState(textAreaState, expected));
model.dispose();
......
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать