From d89a495bd26e6d503d0b86ddf29bdc90f3d8e745 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 3 Sep 2024 10:38:33 +0200 Subject: [PATCH] WIP suggested changes (needs fixing) --- .../editContext/native/nativeEditContext.css | 14 -- .../editContext/native/nativeEditContext.ts | 71 +++++---- .../native/nativeEditContextUtils.ts | 9 -- .../editContext/native/screenReaderSupport.ts | 146 ++++++++---------- .../editContext/screenReaderUtils.ts | 1 + src/vs/editor/browser/view.ts | 16 -- src/vs/editor/common/config/editorOptions.ts | 2 +- .../browser/view/renderers/webviewPreloads.ts | 1 - 8 files changed, 111 insertions(+), 149 deletions(-) diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.css b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.css index 5bde8d90605ac..e4305352ccc64 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.css +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.css @@ -12,17 +12,3 @@ white-space: pre-wrap; text-wrap: nowrap; } - -/* -.monaco-editor .native-edit-context { - margin: 0; - padding: 0; - position: absolute; - overflow: visible; - color: rgb(32, 32, 32); - background-color: white; - z-index: 100; - white-space: pre-wrap; - text-wrap: nowrap; -} -*/ diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts index 01b844be2cda8..7af679602c50e 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts @@ -28,12 +28,13 @@ import { Position } from 'vs/editor/common/core/position'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; import { Selection } from 'vs/editor/common/core/selection'; import { CursorState } from 'vs/editor/common/cursorCommon'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { Disposable } from 'vs/base/common/lifecycle'; // Boolean which controls whether we should show the control, selection and character bounds const showControlBounds = false; export class NativeEditContext extends AbstractEditContext { - public readonly domNode: FastDomNode; private readonly _editContext: EditContextWrapper; private readonly _screenReaderSupport: ScreenReaderSupport; @@ -45,11 +46,13 @@ export class NativeEditContext extends AbstractEditContext { private _renderingContext: RenderingContext | undefined; private _primarySelection: Selection = new Selection(1, 1, 1, 1); + private readonly _focusTracker: FocusTracker; + constructor( context: ViewContext, private readonly _viewController: ViewController, @IClipboardService private readonly _clipboardService: IClipboardService, - @IKeybindingService keybindingService: IKeybindingService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { super(context); @@ -57,15 +60,18 @@ export class NativeEditContext extends AbstractEditContext { this.domNode.setClassName(`native-edit-context ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`); this._updateDomAttributes(); + this._focusTracker = this._register(new FocusTracker(this.domNode.domNode, () => { + this._context.viewModel.setHasFocus(this._hasFocus); + })); + const editContext = showControlBounds ? new DebugEditContext() : new EditContext(); this.domNode.domNode.editContext = editContext; this._editContext = new EditContextWrapper(editContext); - this._screenReaderSupport = new ScreenReaderSupport(this.domNode, context, keybindingService); + this._screenReaderSupport = this._instantiationService.createInstance(ScreenReaderSupport, this.domNode, context); // Dom node events - this._register(dom.addDisposableListener(this.domNode.domNode, 'focus', () => this._setHasFocus(true))); - this._register(dom.addDisposableListener(this.domNode.domNode, 'blur', () => this._setHasFocus(false))); + this._register(dom.addDisposableListener(this.domNode.domNode, 'copy', async () => this._ensureClipboardGetsEditorSelection())); this._register(dom.addDisposableListener(this.domNode.domNode, 'keyup', (e) => this._viewController.emitKeyUp(new StandardKeyboardEvent(e)))); this._register(dom.addDisposableListener(this.domNode.domNode, 'keydown', async (e) => { @@ -188,19 +194,9 @@ export class NativeEditContext extends AbstractEditContext { this._screenReaderSupport.writeScreenReaderContent(); } - public isFocused(): boolean { - return this._hasFocus; - } + public isFocused(): boolean { return this._focusTracker.isFocused; } + public focus(): void { this._focusTracker.focus(); } - public focus(): void { - this._setHasFocus(true); - this.refreshFocusState(); - } - - public refreshFocusState(): void { - const hasFocus = dom.getActiveElement() === this.domNode.domNode; - this._setHasFocus(hasFocus); - } // --- Private methods --- @@ -286,19 +282,6 @@ export class NativeEditContext extends AbstractEditContext { this._context.viewModel.setCursorStates('editContext', CursorChangeReason.Explicit, newCursorStates); } - private _setHasFocus(newHasFocus: boolean): void { - if (this._hasFocus === newHasFocus) { - // no change - return; - } - this._hasFocus = newHasFocus; - if (this._hasFocus) { - this.domNode.domNode.focus(); - this._context.viewModel.setHasFocus(true); - } else { - this._context.viewModel.setHasFocus(false); - } - } private _getNewEditContextState(): { text: string; selectionStartOffset: number; selectionEndOffset: number; textStartPositionWithinEditor: Position } { const selectionStartOffset = this._primarySelection.startColumn - 1; @@ -436,3 +419,31 @@ export class NativeEditContext extends AbstractEditContext { } } +class FocusTracker extends Disposable { + private _isFocused: boolean = false; + + constructor( + private readonly _domNode: HTMLElement, + private readonly _onFocusChange: (newFocusValue: boolean) => void, + ) { + super(); + this._register(dom.addDisposableListener(this._domNode, 'focus', () => this._handleFocusedChanged(true))); + this._register(dom.addDisposableListener(this._domNode, 'blur', () => this._handleFocusedChanged(false))); + } + + private _handleFocusedChanged(focused: boolean): void { + if (this._isFocused === focused) { + return; + } + this._isFocused = focused; + this._onFocusChange(this._isFocused); + } + + public focus(): void { + this._domNode.focus(); + } + + get isFocused(): boolean { + return this._isFocused; + } +} diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts index 40b5b0ed48040..d16209a89382b 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts @@ -14,15 +14,6 @@ export class EditContextWrapper { constructor(private readonly _editContext: EditContext) { } - equals(other: EditContextWrapper): boolean { - return ( - this.text === other.text - && this.selectionStart === other.selectionStart - && this.selectionEnd === other.selectionEnd - && this.textStartPositionWithinEditor.equals(other.textStartPositionWithinEditor) - ); - } - onTextUpdate(listener: (this: GlobalEventHandlers, ev: TextUpdateEvent) => void) { return editContextAddDisposableListener(this._editContext, 'textupdate', listener); } diff --git a/src/vs/editor/browser/controller/editContext/native/screenReaderSupport.ts b/src/vs/editor/browser/controller/editContext/native/screenReaderSupport.ts index 07e44abfa0e03..7f22abdb8a543 100644 --- a/src/vs/editor/browser/controller/editContext/native/screenReaderSupport.ts +++ b/src/vs/editor/browser/controller/editContext/native/screenReaderSupport.ts @@ -18,16 +18,19 @@ import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibi import { EndOfLinePreference } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; +import { BugIndicatingError } from 'vs/base/common/errors'; +import { OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { IViewModel } from 'vs/editor/common/viewModel'; export class ScreenReaderSupport { // Configuration values - private _contentLeft!: number; - private _contentWidth!: number; - private _lineHeight!: number; - private _fontInfo!: FontInfo; - private _accessibilitySupport!: AccessibilitySupport; - private _accessibilityPageSize!: number; + private _contentLeft: number = -1; + private _contentWidth: number = -1; + private _lineHeight: number = -1; + private _fontInfo: FontInfo | null = null; + private _accessibilitySupport = AccessibilitySupport.Unknown; + private _accessibilityPageSize: number = -1; private _primarySelection: Selection = new Selection(1, 1, 1, 1); private _screenReaderContentState: ScreenReaderContentState | undefined; @@ -41,7 +44,36 @@ export class ScreenReaderSupport { this._updateDomAttributes(); } - // --- Public methods --- + public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): void { + this._updateConfigurationSettings(); + this._updateDomAttributes(); + if (e.hasChanged(EditorOption.accessibilitySupport)) { + this.writeScreenReaderContent(); + } + } + + private _updateConfigurationSettings(): void { + const options = this._context.configuration.options; + const layoutInfo = options.get(EditorOption.layoutInfo); + this._contentLeft = layoutInfo.contentLeft; + this._contentWidth = layoutInfo.contentWidth; + this._fontInfo = options.get(EditorOption.fontInfo); + this._lineHeight = options.get(EditorOption.lineHeight); + this._accessibilitySupport = options.get(EditorOption.accessibilitySupport); + this._accessibilityPageSize = options.get(EditorOption.accessibilityPageSize); + } + + private _updateDomAttributes(): void { + const options = this._context.configuration.options; + this._domNode.domNode.setAttribute('aria-label', ariaLabelForScreenReaderContent(options, this._keybindingService)); + const tabSize = this._context.viewModel.model.getOptions().tabSize; + const spaceWidth = options.get(EditorOption.fontInfo).spaceWidth; + this._domNode.domNode.style.tabSize = `${tabSize * spaceWidth}px`; + } + + public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): void { + this._primarySelection = e.selections[0] ?? new Selection(1, 1, 1, 1); + } public prepareRender(ctx: RenderingContext): void { this.writeScreenReaderContent(); @@ -52,7 +84,7 @@ export class ScreenReaderSupport { return; } // For correct alignment of the screen reader content, we need to apply the correct font - applyFontInfo(this._domNode, this._fontInfo); + applyFontInfo(this._domNode, this._fontInfo!); const verticalOffsetForPrimaryLineNumber = this._context.viewLayout.getVerticalOffsetForLineNumber(this._primarySelection.positionLineNumber); const editorScrollTop = this._context.viewLayout.getCurrentScrollTop(); @@ -71,17 +103,6 @@ export class ScreenReaderSupport { public setAriaOptions(): void { } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): void { - this._primarySelection = e.selections[0] ?? new Selection(1, 1, 1, 1); - } - - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): void { - this._updateConfigurationSettings(); - this._updateDomAttributes(); - if (e.hasChanged(EditorOption.accessibilitySupport)) { - this.writeScreenReaderContent(); - } - } public writeScreenReaderContent(): void { this._screenReaderContentState = this._getScreenReaderContentState(); @@ -94,71 +115,40 @@ export class ScreenReaderSupport { this._setSelectionOfScreenReaderContent(this._screenReaderContentState.rangeOffsetStart, this._screenReaderContentState.rangeOffsetEnd); } - /* Last rendered data needed for correct hit-testing and determining the mouse position. - * Without this, the selection will blink as incorrect mouse position is calculated */ - public getLastRenderData(): Position | null { - return this._primarySelection.getPosition(); - } - - // --- Private methods --- - - private _updateConfigurationSettings(): void { - const options = this._context.configuration.options; - const layoutInfo = options.get(EditorOption.layoutInfo); - this._contentLeft = layoutInfo.contentLeft; - this._contentWidth = layoutInfo.contentWidth; - this._fontInfo = options.get(EditorOption.fontInfo); - this._lineHeight = options.get(EditorOption.lineHeight); - this._accessibilitySupport = options.get(EditorOption.accessibilitySupport); - this._accessibilityPageSize = options.get(EditorOption.accessibilityPageSize); - } - - private _updateDomAttributes(): void { - const options = this._context.configuration.options; - this._domNode.domNode.setAttribute('aria-label', ariaLabelForScreenReaderContent(options, this._keybindingService)); - const tabSize = this._context.viewModel.model.getOptions().tabSize; - const spaceWidth = options.get(EditorOption.fontInfo).spaceWidth; - this._domNode.domNode.style.tabSize = `${tabSize * spaceWidth}px`; - } private _getScreenReaderContentState(): ScreenReaderContentState | undefined { if (this._accessibilitySupport === AccessibilitySupport.Disabled) { return; } - const simpleModel: ISimpleModel = { - getLineCount: (): number => { - return this._context.viewModel.getLineCount(); - }, - getLineMaxColumn: (lineNumber: number): number => { - return this._context.viewModel.getLineMaxColumn(lineNumber); - }, - getValueInRange: (range: Range, eol: EndOfLinePreference): string => { - return this._context.viewModel.getValueInRange(range, eol); - }, - getValueLengthInRange: (range: Range, eol: EndOfLinePreference): number => { - return this._context.viewModel.getValueLengthInRange(range, eol); - }, - modifyPosition: (position: Position, offset: number): Position => { - return this._context.viewModel.modifyPosition(position, offset); - } - }; - return PagedScreenReaderStrategy.fromEditorSelection(simpleModel, this._primarySelection, this._accessibilityPageSize, this._accessibilitySupport === AccessibilitySupport.Unknown); + return PagedScreenReaderStrategy.fromEditorSelection( + createSimpleModelFromViewModel(this._context.viewModel), + this._primarySelection, + this._accessibilityPageSize, + this._accessibilitySupport === AccessibilitySupport.Unknown + ); } +} - private _setSelectionOfScreenReaderContent(selectionOffsetStart: number, selectionOffsetEnd: number): void { - const activeDocument = dom.getActiveWindow().document; - const activeDocumentSelection = activeDocument.getSelection(); - if (!activeDocumentSelection) { - return; - } - const textContent = this._domNode.domNode.firstChild; - if (!textContent) { - return; - } - const range = new globalThis.Range(); - range.setStart(textContent, selectionOffsetStart); - range.setEnd(textContent, selectionOffsetEnd); - activeDocumentSelection.removeAllRanges(); - activeDocumentSelection.addRange(range); +function createSimpleModelFromViewModel(viewModel: IViewModel): ISimpleModel { + return { + getLineCount: (): number => viewModel.getLineCount(), + getLineMaxColumn: (lineNumber: number): number => viewModel.getLineMaxColumn(lineNumber), + getValueInRange: (range: Range, eol: EndOfLinePreference): string => viewModel.getValueInRange(range, eol), + getValueLengthInRange: (range: Range, eol: EndOfLinePreference): number => viewModel.getValueLengthInRange(range, eol), + modifyPosition: (position: Position, offset: number): Position => viewModel.modifyPosition(position, offset) + }; +} + +function setDomSelection(node: Node, selection: OffsetRange): void { + const activeDocument = dom.getActiveWindow().document; + const activeDocumentSelection = activeDocument.getSelection(); + if (!activeDocumentSelection) { + throw new BugIndicatingError(); } + activeDocumentSelection.removeAllRanges(); + + const range = new globalThis.Range(); + range.setStart(node, selection.start); + range.setEnd(node, selection.endExclusive); + activeDocumentSelection.addRange(range); } diff --git a/src/vs/editor/browser/controller/editContext/screenReaderUtils.ts b/src/vs/editor/browser/controller/editContext/screenReaderUtils.ts index 88b58d2d941b0..e7dae47d6cda7 100644 --- a/src/vs/editor/browser/controller/editContext/screenReaderUtils.ts +++ b/src/vs/editor/browser/controller/editContext/screenReaderUtils.ts @@ -10,6 +10,7 @@ import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibi import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Position } from 'vs/editor/common/core/position'; import * as nls from 'vs/nls'; +import { OffsetRange } from 'vs/editor/common/core/offsetRange'; export interface ISimpleModel { getLineCount(): number; diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index 622dda6e0cd6f..b085d752cee6c 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -365,22 +365,6 @@ export class View extends ViewEventHandler { return editContextHandler; } - private _updateEditContext(): void { - const editContextType = this._context.configuration.options.get(EditorOption.editContext).type; - if (this._editContextType === editContextType) { - return; - } - this._editContextType = editContextType; - this._editContext.dispose(); - this._editContext = this._instantiateEditContext(editContextType); - this._editContext.appendTo(this._overflowGuardContainer); - // Replace the view parts with the new edit context - const indexOfEditContextHandler = this._viewParts.indexOf(this._editContext); - if (indexOfEditContextHandler !== -1) { - this._viewParts.splice(indexOfEditContextHandler, 1, this._editContext); - } - } - // --- begin event handlers public override handleEvents(events: viewEvents.ViewEvent[]): void { super.handleEvents(events); diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 9656999e41696..2435d0e49dda7 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -5223,7 +5223,7 @@ class EditContextOption extends BaseEditorOption