Skip to content

Commit

Permalink
WIP suggested changes (needs fixing)
Browse files Browse the repository at this point in the history
  • Loading branch information
hediet committed Sep 3, 2024
1 parent e1a8704 commit d89a495
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
*/
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLDivElement>;
private readonly _editContext: EditContextWrapper;
private readonly _screenReaderSupport: ScreenReaderSupport;
Expand All @@ -45,27 +46,32 @@ 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);

this.domNode = new FastDomNode(document.createElement('div'));
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) => {
Expand Down Expand Up @@ -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 ---

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
16 changes: 0 additions & 16 deletions src/vs/editor/browser/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/vs/editor/common/config/editorOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5223,7 +5223,7 @@ class EditContextOption extends BaseEditorOption<EditorOption.editContext, IEdit
super(
EditorOption.editContext, 'editContext', defaults,
{
'editor.editContext.type': {
'editor.experimental.useEditContext': {
type: 'string',
markdownDescription: nls.localize('editContext.type', "Controls the type of the edit context that is used."),
enum: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3107,7 +3107,6 @@ export function preloadsScriptStr(styleValues: PreloadStyles, options: PreloadOp
)\n//# sourceURL=notebookWebviewPreloads.js\n`;
}

// Can not import directly from dom.ts file?
export function isEditableElement(element: Element): boolean {
return element.tagName.toLowerCase() === 'input' || element.tagName.toLowerCase() === 'textarea' || 'editContext' in element;
}

0 comments on commit d89a495

Please sign in to comment.