Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP suggested changes (needs fixing) #227428

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}