From e35a95352a16275e38de8d06d9f6751baaa6b74a Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 5 Jul 2024 14:38:38 +0200 Subject: [PATCH] wip edit context Signed-off-by: Henning Dieterichs --- .vscode/launch.json | 5 +- src/vs/base/common/hotReload.ts | 1 + .../controller/editContext/editContext.ts | 17 ++ .../browser/controller/editContext/impl.ts | 9 + .../editContext/native/debugEditContext.ts | 231 +++++++++++++++++ .../editContext/native/editContext.d.ts | 124 +++++++++ .../editContext/native/nativeEditContext.ts | 243 ++++++++++++++++++ .../textArea}/textAreaHandler.css | 0 .../textArea}/textAreaHandler.ts | 14 +- .../textArea}/textAreaInput.ts | 2 +- .../textArea}/textAreaState.ts | 0 .../browser/controller/pointerHandler.ts | 2 +- src/vs/editor/browser/view.ts | 17 +- .../browser/widget/codeEditor/editor.css | 30 +++ .../contrib/clipboard/browser/clipboard.ts | 2 +- .../browser/copyPasteController.ts | 2 +- .../browser/controller/imeRecordedTypes.ts | 2 +- .../test/browser/controller/imeRecorder.ts | 2 +- .../test/browser/controller/imeTester.ts | 4 +- .../browser/controller/textAreaInput.test.ts | 4 +- .../browser/controller/textAreaState.test.ts | 2 +- 21 files changed, 690 insertions(+), 23 deletions(-) create mode 100644 src/vs/editor/browser/controller/editContext/editContext.ts create mode 100644 src/vs/editor/browser/controller/editContext/impl.ts create mode 100644 src/vs/editor/browser/controller/editContext/native/debugEditContext.ts create mode 100644 src/vs/editor/browser/controller/editContext/native/editContext.d.ts create mode 100644 src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts rename src/vs/editor/browser/controller/{ => editContext/textArea}/textAreaHandler.css (100%) rename src/vs/editor/browser/controller/{ => editContext/textArea}/textAreaHandler.ts (98%) rename src/vs/editor/browser/controller/{ => editContext/textArea}/textAreaInput.ts (99%) rename src/vs/editor/browser/controller/{ => editContext/textArea}/textAreaState.ts (100%) diff --git a/.vscode/launch.json b/.vscode/launch.json index b2e25927a8a73..5ca40323bed45 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -352,7 +352,10 @@ "presentation": { "group": "0_vscode", "order": 3 - } + }, + "vscode-diagnostic-tools.debuggerScripts": [ + "${workspaceFolder}/scripts/hot-reload-injected-script.js" + ] }, { "type": "node", diff --git a/src/vs/base/common/hotReload.ts b/src/vs/base/common/hotReload.ts index 609fd9d8ef276..fecd92f6ea3e5 100644 --- a/src/vs/base/common/hotReload.ts +++ b/src/vs/base/common/hotReload.ts @@ -7,6 +7,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { env } from 'vs/base/common/process'; export function isHotReloadEnabled(): boolean { + return true; return env && !!env['VSCODE_DEV']; } export function registerHotReloadHandler(handler: HotReloadHandler): IDisposable { diff --git a/src/vs/editor/browser/controller/editContext/editContext.ts b/src/vs/editor/browser/controller/editContext/editContext.ts new file mode 100644 index 0000000000000..d630d35655e38 --- /dev/null +++ b/src/vs/editor/browser/controller/editContext/editContext.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { FastDomNode } from 'vs/base/browser/fastDomNode'; +import { IEditorAriaOptions } from 'vs/editor/browser/editorBrowser'; +import { ViewPart } from 'vs/editor/browser/view/viewPart'; + +export abstract class AbstractEditContext extends ViewPart { + abstract isFocused(): boolean; + abstract appendTo(overflowGuardContainer: FastDomNode): void; + abstract writeScreenReaderContent(reason: string): void; + abstract focusTextArea(): void; + abstract refreshFocusState(): void; + abstract setAriaOptions(options: IEditorAriaOptions): void; +} diff --git a/src/vs/editor/browser/controller/editContext/impl.ts b/src/vs/editor/browser/controller/editContext/impl.ts new file mode 100644 index 0000000000000..fe3f066f6b79f --- /dev/null +++ b/src/vs/editor/browser/controller/editContext/impl.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { NativeEditContext } from 'vs/editor/browser/controller/editContext/native/nativeEditContext'; +import { TextAreaHandler } from 'vs/editor/browser/controller/editContext/textArea/textAreaHandler'; + +export const EditContextImpl = false ? TextAreaHandler : NativeEditContext; diff --git a/src/vs/editor/browser/controller/editContext/native/debugEditContext.ts b/src/vs/editor/browser/controller/editContext/native/debugEditContext.ts new file mode 100644 index 0000000000000..9b123383d3b26 --- /dev/null +++ b/src/vs/editor/browser/controller/editContext/native/debugEditContext.ts @@ -0,0 +1,231 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export class DebugEditContext extends EditContext { + private _isDebugging = true; + private _controlBounds: DOMRect | null = null; + private _selectionBounds: DOMRect | null = null; + private _characterBounds: { rangeStart: number; characterBounds: DOMRect[] } | null = null; + + constructor(options?: EditContextInit | undefined) { + super(options); + } + + override updateText(rangeStart: number, rangeEnd: number, text: string): void { + super.updateText(rangeStart, rangeEnd, text); + this.renderDebug(); + } + override updateSelection(start: number, end: number): void { + super.updateSelection(start, end); + this.renderDebug(); + } + override updateControlBounds(controlBounds: DOMRect): void { + super.updateControlBounds(controlBounds); + this._controlBounds = controlBounds; + this.renderDebug(); + } + override updateSelectionBounds(selectionBounds: DOMRect): void { + super.updateSelectionBounds(selectionBounds); + this._selectionBounds = selectionBounds; + this.renderDebug(); + } + override updateCharacterBounds(rangeStart: number, characterBounds: DOMRect[]): void { + super.updateCharacterBounds(rangeStart, characterBounds); + this._characterBounds = { rangeStart, characterBounds }; + this.renderDebug(); + } + override attachedElements(): HTMLElement[] { + return super.attachedElements(); + } + + override characterBounds(): DOMRect[] { + return super.characterBounds(); + } + + private readonly _ontextupdateWrapper = new EventListenerWrapper('textupdate', this); + private readonly _ontextformatupdateWrapper = new EventListenerWrapper('textformatupdate', this); + private readonly _oncharacterboundsupdateWrapper = new EventListenerWrapper('characterboundsupdate', this); + private readonly _oncompositionstartWrapper = new EventListenerWrapper('compositionstart', this); + private readonly _oncompositionendWrapper = new EventListenerWrapper('compositionend', this); + + override get ontextupdate(): EventHandler | null { return this._ontextupdateWrapper.eventHandler; } + override set ontextupdate(value: EventHandler | null) { this._ontextupdateWrapper.eventHandler = value; } + override get ontextformatupdate(): EventHandler | null { return this._ontextformatupdateWrapper.eventHandler; } + override set ontextformatupdate(value: EventHandler | null) { this._ontextformatupdateWrapper.eventHandler = value; } + override get oncharacterboundsupdate(): EventHandler | null { return this._oncharacterboundsupdateWrapper.eventHandler; } + override set oncharacterboundsupdate(value: EventHandler | null) { this._oncharacterboundsupdateWrapper.eventHandler = value; } + override get oncompositionstart(): EventHandler | null { return this._oncompositionstartWrapper.eventHandler; } + override set oncompositionstart(value: EventHandler | null) { this._oncompositionstartWrapper.eventHandler = value; } + override get oncompositionend(): EventHandler | null { return this._oncompositionendWrapper.eventHandler; } + override set oncompositionend(value: EventHandler | null) { this._oncompositionendWrapper.eventHandler = value; } + + + private readonly _listenerMap = new Map(); + + override addEventListener(type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + override addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void { + if (!listener) { return; } + + const debugListener = (event: Event) => { + if (this._isDebugging) { + this.renderDebug(); + console.log(`DebugEditContex.on_${type}`, event); + } + + if (typeof listener === 'function') { + listener.call(this, event); + } else if (typeof listener === 'object' && 'handleEvent' in listener) { + listener.handleEvent(event); + } + }; + this._listenerMap.set(listener, debugListener); + super.addEventListener(type, debugListener, options); + + this.renderDebug(); + } + + override removeEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions | undefined): void { + if (!listener) { return; } + + const debugListener = this._listenerMap.get(listener); + if (debugListener) { + super.removeEventListener(type, debugListener, options); + this._listenerMap.delete(listener); + } + + this.renderDebug(); + } + + override dispatchEvent(event: Event): boolean { + return super.dispatchEvent(event); + } + + public startDebugging() { + this._isDebugging = true; + this.renderDebug(); + } + + public endDebugging() { + this._isDebugging = false; + this.renderDebug(); + } + + private _disposables: { dispose(): void }[] = []; + + public renderDebug() { + this._disposables.forEach(d => d.dispose()); + this._disposables = []; + + if (!this._isDebugging || this._listenerMap.size === 0) { + return; + } + + if (this._controlBounds) { + this._disposables.push(createRect(this._controlBounds)); + } + if (this._selectionBounds) { + this._disposables.push(createRect(this._selectionBounds)); + } + if (this._characterBounds) { + for (const rect of this._characterBounds.characterBounds) { + this._disposables.push(createRect(rect)); + } + } + + this._disposables.push(createDiv(this.text, this.selectionStart, this.selectionEnd)); + } +} + +function createDiv(text: string, selectionStart: number, selectionEnd: number) { + const ret = document.createElement('div'); + ret.style.position = 'absolute'; + ret.style.zIndex = '999999999'; + ret.style.bottom = '50px'; + ret.style.left = '60px'; + ret.style.backgroundColor = 'white'; + ret.style.border = '1px solid black'; + ret.style.padding = '5px'; + ret.style.whiteSpace = 'pre'; + ret.className = 'debug-rect-marker'; + ret.style.font = '12px monospace'; + ret.style.pointerEvents = 'none'; + + const before = text.substring(0, selectionStart); + const selected = text.substring(selectionStart, selectionEnd) || '|'; + const after = text.substring(selectionEnd) + ' '; + + const beforeNode = document.createTextNode(before); + ret.appendChild(beforeNode); + + const selectedNode = document.createElement('span'); + selectedNode.style.backgroundColor = 'yellow'; + selectedNode.appendChild(document.createTextNode(selected)); + + selectedNode.style.minWidth = '2px'; + selectedNode.style.minHeight = '16px'; + ret.appendChild(selectedNode); + + const afterNode = document.createTextNode(after); + ret.appendChild(afterNode); + + + // eslint-disable-next-line no-restricted-syntax + document.body.appendChild(ret); + + return { + dispose: () => { + ret.remove(); + } + }; +} + +function createRect(rect: DOMRect) { + const ret = document.createElement('div'); + ret.style.position = 'absolute'; + ret.style.zIndex = '999999999'; + ret.style.outline = '2px solid red'; + ret.className = 'debug-rect-marker'; + ret.style.pointerEvents = 'none'; + + ret.style.top = rect.top + 'px'; + ret.style.left = rect.left + 'px'; + ret.style.width = rect.width + 'px'; + ret.style.height = rect.height + 'px'; + + // eslint-disable-next-line no-restricted-syntax + document.body.appendChild(ret); + + return { + dispose: () => { + ret.remove(); + } + }; +} + +class EventListenerWrapper { + private _eventHandler: EventHandler | null = null; + + constructor( + private readonly _eventType: string, + private readonly _target: EventTarget, + ) { + } + + get eventHandler(): EventHandler | null { + return this._eventHandler; + } + + set eventHandler(value: EventHandler | null) { + if (this._eventHandler) { + this._target.removeEventListener(this._eventType, this._eventHandler); + } + this._eventHandler = value; + if (value) { + this._target.addEventListener(this._eventType, value); + } + } +} + + diff --git a/src/vs/editor/browser/controller/editContext/native/editContext.d.ts b/src/vs/editor/browser/controller/editContext/native/editContext.d.ts new file mode 100644 index 0000000000000..c2d5830a9b1c6 --- /dev/null +++ b/src/vs/editor/browser/controller/editContext/native/editContext.d.ts @@ -0,0 +1,124 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +type DOMString = string; + +declare class EditContext extends EventTarget { + constructor(options?: EditContextInit); + + updateText(rangeStart: number, rangeEnd: number, text: DOMString): void; + updateSelection(start: number, end: number): void; + updateControlBounds(controlBounds: DOMRect): void; + updateSelectionBounds(selectionBounds: DOMRect): void; + updateCharacterBounds(rangeStart: number, characterBounds: DOMRect[]): void; + + attachedElements(): HTMLElement[]; + + get text(): DOMString; + get selectionStart(): number; + get selectionEnd(): number; + get characterBoundsRangeStart(): number; + characterBounds(): DOMRect[]; + + get ontextupdate(): EventHandler | null; + set ontextupdate(value: EventHandler | null); + + get ontextformatupdate(): EventHandler | null; + set ontextformatupdate(value: EventHandler | null); + + get oncharacterboundsupdate(): EventHandler | null; + set oncharacterboundsupdate(value: EventHandler | null); + + get oncompositionstart(): EventHandler | null; + set oncompositionstart(value: EventHandler | null); + + get oncompositionend(): EventHandler | null; + set oncompositionend(value: EventHandler | null); + + addEventListener(type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; +} + +interface EditContextInit { + text: DOMString; + selectionStart: number; + selectionEnd: number; +} + +interface EditContextEventHandlersEventMap { + textupdate: TextUpdateEvent; + textformatupdate: TextFormatUpdateEvent; + characterboundsupdate: CharacterBoundsUpdateEvent; + oncompositionstart: Event; + oncompositionend: Event; +} + +type EventHandler = (event: TEvent) => void; + +declare class TextUpdateEvent extends Event { + new(type: DOMString, options?: TextUpdateEventInit): TextUpdateEvent; + + readonly updateRangeStart: number; + readonly updateRangeEnd: number; + readonly text: DOMString; + readonly selectionStart: number; + readonly selectionEnd: number; +} + +interface TextUpdateEventInit extends EventInit { + updateRangeStart: number; + updateRangeEnd: number; + text: DOMString; + selectionStart: number; + selectionEnd: number; + compositionStart: number; + compositionEnd: number; +} + +declare class TextFormat { + new(options?: TextFormatInit): TextFormat; + + readonly rangeStart: number; + readonly rangeEnd: number; + readonly underlineStyle: UnderlineStyle; + readonly underlineThickness: UnderlineThickness; +} + +interface TextFormatInit { + rangeStart: number; + rangeEnd: number; + underlineStyle: UnderlineStyle; + underlineThickness: UnderlineThickness; +} + +type UnderlineStyle = "none" | "solid" | "dotted" | "dashed" | "wavy"; +type UnderlineThickness = "none" | "thin" | "thick"; + +declare class TextFormatUpdateEvent extends Event { + new(type: DOMString, options?: TextFormatUpdateEventInit): TextFormatUpdateEvent; + getTextFormats(): TextFormat[]; +} + +interface TextFormatUpdateEventInit extends EventInit { + textFormats: TextFormat[]; +} + +declare class CharacterBoundsUpdateEvent extends Event { + new(type: DOMString, options?: CharacterBoundsUpdateEventInit): CharacterBoundsUpdateEvent; + + readonly rangeStart: number; + readonly rangeEnd: number; +} + +interface CharacterBoundsUpdateEventInit extends EventInit { + rangeStart: number; + rangeEnd: number; +} + +interface HTMLElement { + editContext?: EditContext; +} diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts new file mode 100644 index 0000000000000..94bca969719ba --- /dev/null +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts @@ -0,0 +1,243 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { FastDomNode } from 'vs/base/browser/fastDomNode'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { isDefined } from 'vs/base/common/types'; +import { AbstractEditContext } from 'vs/editor/browser/controller/editContext/editContext'; +import { DebugEditContext } from 'vs/editor/browser/controller/editContext/native/debugEditContext'; +import { IEditorAriaOptions } from 'vs/editor/browser/editorBrowser'; +import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext'; +import { ViewController } from 'vs/editor/browser/view/viewController'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { LineRange } from 'vs/editor/common/core/lineRange'; +import { OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { Position } from 'vs/editor/common/core/position'; +import { PositionOffsetTransformer } from 'vs/editor/common/core/positionToOffset'; +import { Range } from 'vs/editor/common/core/range'; +import { SingleTextEdit, TextEdit, LineBasedText } from 'vs/editor/common/core/textEdit'; +import { IModelDeltaDecoration } from 'vs/editor/common/model'; +import { ViewConfigurationChangedEvent, ViewCursorStateChangedEvent, ViewScrollChangedEvent } from 'vs/editor/common/viewEvents'; +import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; + +export class NativeEditContext extends AbstractEditContext { + private readonly _domElement = new FastDomNode(document.createElement('div')); + private readonly _ctx: EditContext = this._domElement.domNode.editContext = new DebugEditContext(); + + + private _parent!: HTMLElement; + private _scrollTop = 0; + private _contentLeft = 0; + + private _isFocused = false; + + private _editContextState: EditContextState | undefined = undefined; + + constructor( + context: ViewContext, + private readonly _viewController: ViewController, + ) { + super(context); + + this._domElement.domNode.tabIndex = 0; + this._domElement.domNode.style.width = '100px'; + this._domElement.domNode.style.height = '100px'; + + this._domElement.domNode.onfocus = () => { + this._isFocused = true; + this._context.viewModel.setHasFocus(true); + }; + + this._domElement.domNode.onblur = () => { + this._isFocused = false; + this._context.viewModel.setHasFocus(false); + }; + + this._domElement.domNode.onkeydown = e => { + const x = new StandardKeyboardEvent(e); + this._viewController.emitKeyDown(x); + }; + + this._domElement.domNode.onkeyup = e => { + const x = new StandardKeyboardEvent(e); + this._viewController.emitKeyUp(x); + }; + + const options = this._context.configuration.options; + const layoutInfo = options.get(EditorOption.layoutInfo); + this._contentLeft = layoutInfo.contentLeft; + + this._register(editContextAddDisposableListener(this._ctx, 'textupdate', e => this._handleTextUpdate(e))); + this._register(editContextAddDisposableListener(this._ctx, 'textformatupdate', e => this._handleTextFormatUpdate(e))); + } + + private _decorations: string[] = []; + + private _handleTextFormatUpdate(e: TextFormatUpdateEvent): void { + if (!this._editContextState) { + return; + } + const formats = e.getTextFormats(); + + const decorations: IModelDeltaDecoration[] = formats.map(f => { + const r = new OffsetRange(f.rangeStart, f.rangeEnd); + const range = this._editContextState!.textPositionTransformer.getRange(r); + const doc = new LineBasedText(lineNumber => this._context.viewModel.getLineContent(lineNumber), this._context.viewModel.getLineCount()); + const viewModelRange = this._editContextState!.viewModelToEditContextText.inverseMapRange(range, doc); + const modelRange = this._context.viewModel.coordinatesConverter.convertViewRangeToModelRange(viewModelRange); + + const classNames = [ + 'underline', + `style-${f.underlineStyle.toLowerCase()}`, + `thickness-${f.underlineThickness.toLowerCase()}`, + ]; + + return { + range: modelRange, + options: { + description: 'textFormatDecoration', + inlineClassName: classNames.join(' '), + } + }; + }); + console.log(decorations[0]?.options); + this._decorations = this._context.viewModel.model.deltaDecorations(this._decorations, decorations); + } + + private _handleTextUpdate(e: TextUpdateEvent): void { + if (!this._editContextState) { + return; + } + const updateRange = new OffsetRange(e.updateRangeStart, e.updateRangeEnd); + + if (!updateRange.equals(this._editContextState.selection)) { + const deleteBefore = this._editContextState.positionOffset - e.updateRangeStart; + const deleteAfter = e.updateRangeEnd - this._editContextState.positionOffset; + this._viewController.compositionType(e.text, deleteBefore, deleteAfter, 0); + } else { + this._viewController.type(e.text); + } + + this.updateText(); + } + + public override appendTo(overflowGuardContainer: FastDomNode): void { + overflowGuardContainer.appendChild(this._domElement); + this._parent = overflowGuardContainer.domNode; + } + + private updateText() { + const primaryViewState = this._context.viewModel.getCursorStates()[0].viewState; + + const doc = new LineBasedText(lineNumber => this._context.viewModel.getLineContent(lineNumber), this._context.viewModel.getLineCount()); + const docStart = new Position(1, 1); + const textStart = new Position(primaryViewState.selection.startLineNumber - 2, 1); + const textEnd = new Position(primaryViewState.selection.endLineNumber + 1, Number.MAX_SAFE_INTEGER); + const textEdit = new TextEdit([ + docStart.isBefore(textStart) ? new SingleTextEdit(Range.fromPositions(docStart, textStart), '...\n') : undefined, + (primaryViewState.selection.endLineNumber - primaryViewState.selection.startLineNumber > 6) ? + new SingleTextEdit(Range.fromPositions(new Position(primaryViewState.selection.startLineNumber + 2, 1), new Position(primaryViewState.selection.endLineNumber - 2, 1)), '...\n') : + undefined, + textEnd.isBefore(doc.endPositionExclusive) ? new SingleTextEdit(Range.fromPositions(textEnd, doc.endPositionExclusive), '\n...') : undefined + ].filter(isDefined)); + + const value = textEdit.apply(doc); + + const selectionStart = textEdit.mapPosition(primaryViewState.selection.getStartPosition()) as Position; + const selectionEnd = textEdit.mapPosition(primaryViewState.selection.getEndPosition()) as Position; + const position = textEdit.mapPosition(primaryViewState.selection.getPosition()) as Position; + + const t = new PositionOffsetTransformer(value); + const selection = new OffsetRange((t.getOffset(selectionStart)), (t.getOffset(selectionEnd))); + const positionOffset = t.getOffset(position); + + this._editContextState = new EditContextState(textEdit, t, positionOffset, selection); + + this._ctx.updateText(0, Number.MAX_SAFE_INTEGER, value); + this._ctx.updateSelection(selection.start, selection.endExclusive); + } + + public override prepareRender(ctx: RenderingContext): void { + const primaryViewState = this._context.viewModel.getCursorStates()[0].viewState; + + const linesVisibleRanges = ctx.linesVisibleRangesForRange(primaryViewState.selection, true) ?? []; + if (linesVisibleRanges.length === 0) { return; } + + const lineRange = new LineRange(linesVisibleRanges[0].lineNumber, linesVisibleRanges[linesVisibleRanges.length - 1].lineNumber + 1); + + const verticalOffsetStart = this._context.viewLayout.getVerticalOffsetForLineNumber(lineRange.startLineNumber); + const verticalOffsetEnd = this._context.viewLayout.getVerticalOffsetForLineNumber(lineRange.endLineNumberExclusive); + + const minLeft = Math.min(...linesVisibleRanges.map(r => Math.min(...r.ranges.map(r => r.left)))); + const maxLeft = Math.max(...linesVisibleRanges.map(r => Math.max(...r.ranges.map(r => r.left + r.width)))); + + const controlBounds = this._parent.getBoundingClientRect(); + const selectionBounds = new DOMRect( + controlBounds.left + minLeft + this._contentLeft, + controlBounds.top + verticalOffsetStart - this._scrollTop, + maxLeft - minLeft, + verticalOffsetEnd - verticalOffsetStart, + ); + + this._ctx.updateControlBounds(controlBounds); + this._ctx.updateSelectionBounds(selectionBounds); + + this.updateText(); + } + + public override render(ctx: RestrictedRenderingContext): void { + + } + + public override onConfigurationChanged(e: ViewConfigurationChangedEvent): boolean { + const options = this._context.configuration.options; + const layoutInfo = options.get(EditorOption.layoutInfo); + this._contentLeft = layoutInfo.contentLeft; + return true; + } + + override onCursorStateChanged(e: ViewCursorStateChangedEvent): boolean { + return true; + } + + override onScrollChanged(e: ViewScrollChangedEvent): boolean { + this._scrollTop = e.scrollTop; + return true; + } + + public override isFocused(): boolean { + return this._isFocused; + } + + public override writeScreenReaderContent(reason: string): void { + } + + public override focusTextArea(): void { + this._domElement.domNode.focus(); + } + + public override refreshFocusState(): void { } + + public override setAriaOptions(options: IEditorAriaOptions): void { } +} + +function editContextAddDisposableListener(target: EventTarget, type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => any, options?: boolean | AddEventListenerOptions): IDisposable { + target.addEventListener(type, listener as any, options); + return { + dispose() { + target.removeEventListener(type, listener as any); + } + }; +} + +class EditContextState { + constructor( + public readonly viewModelToEditContextText: TextEdit, + public readonly textPositionTransformer: PositionOffsetTransformer, + public readonly positionOffset: number, + public readonly selection: OffsetRange, + ) { } +} diff --git a/src/vs/editor/browser/controller/textAreaHandler.css b/src/vs/editor/browser/controller/editContext/textArea/textAreaHandler.css similarity index 100% rename from src/vs/editor/browser/controller/textAreaHandler.css rename to src/vs/editor/browser/controller/editContext/textArea/textAreaHandler.css diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/editContext/textArea/textAreaHandler.ts similarity index 98% rename from src/vs/editor/browser/controller/textAreaHandler.ts rename to src/vs/editor/browser/controller/editContext/textArea/textAreaHandler.ts index a5f824ca45065..b782d9c65909c 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/editContext/textArea/textAreaHandler.ts @@ -11,10 +11,10 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import * as platform from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; -import { CopyOptions, ICompositionData, IPasteData, ITextAreaInputHost, TextAreaInput, ClipboardDataToCopy, TextAreaWrapper } from 'vs/editor/browser/controller/textAreaInput'; -import { ISimpleModel, ITypeData, PagedScreenReaderStrategy, TextAreaState, _debugComposition } from 'vs/editor/browser/controller/textAreaState'; +import { CopyOptions, ICompositionData, IPasteData, ITextAreaInputHost, TextAreaInput, ClipboardDataToCopy, TextAreaWrapper } from 'vs/editor/browser/controller/editContext/textArea/textAreaInput'; +import { ISimpleModel, ITypeData, PagedScreenReaderStrategy, TextAreaState, _debugComposition } from 'vs/editor/browser/controller/editContext/textArea/textAreaState'; import { ViewController } from 'vs/editor/browser/view/viewController'; -import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart'; +import { PartFingerprint, PartFingerprints } from 'vs/editor/browser/view/viewPart'; import { LineNumbersOverlay } from 'vs/editor/browser/viewParts/lineNumbers/lineNumbers'; import { Margin } from 'vs/editor/browser/viewParts/margin/margin'; import { RenderLineNumbersType, EditorOption, IComputedEditorOptions, EditorOptions } from 'vs/editor/common/config/editorOptions'; @@ -37,6 +37,7 @@ import { Color } from 'vs/base/common/color'; import { IME } from 'vs/base/common/ime'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { AbstractEditContext } from 'vs/editor/browser/controller/editContext/editContext'; export interface IVisibleRangeProvider { visibleRangeForPosition(position: Position): HorizontalPosition | null; @@ -106,7 +107,7 @@ class VisibleTextAreaData { const canUseZeroSizeTextarea = (browser.isFirefox); -export class TextAreaHandler extends ViewPart { +export class TextAreaHandler extends AbstractEditContext { private readonly _viewController: ViewController; private readonly _visibleRangeProvider: IVisibleRangeProvider; @@ -479,6 +480,11 @@ export class TextAreaHandler extends ViewPart { })); } + appendTo(overflowGuardContainer: FastDomNode): void { + overflowGuardContainer.appendChild(this.textArea); + overflowGuardContainer.appendChild(this.textAreaCover); + } + public writeScreenReaderContent(reason: string): void { this._textAreaInput.writeNativeTextAreaContent(reason); } diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/editContext/textArea/textAreaInput.ts similarity index 99% rename from src/vs/editor/browser/controller/textAreaInput.ts rename to src/vs/editor/browser/controller/editContext/textArea/textAreaInput.ts index 88e783a1f7d0b..cabf977f946f7 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/editContext/textArea/textAreaInput.ts @@ -15,7 +15,7 @@ import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifec import { Mimes } from 'vs/base/common/mime'; import { OperatingSystem } from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; -import { ITextAreaWrapper, ITypeData, TextAreaState, _debugComposition } from 'vs/editor/browser/controller/textAreaState'; +import { ITextAreaWrapper, ITypeData, TextAreaState, _debugComposition } from 'vs/editor/browser/controller/editContext/textArea/textAreaState'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; diff --git a/src/vs/editor/browser/controller/textAreaState.ts b/src/vs/editor/browser/controller/editContext/textArea/textAreaState.ts similarity index 100% rename from src/vs/editor/browser/controller/textAreaState.ts rename to src/vs/editor/browser/controller/editContext/textArea/textAreaState.ts diff --git a/src/vs/editor/browser/controller/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts index 2019016ac4ce2..0e9cfee1ce1ef 100644 --- a/src/vs/editor/browser/controller/pointerHandler.ts +++ b/src/vs/editor/browser/controller/pointerHandler.ts @@ -10,7 +10,7 @@ import { mainWindow } from 'vs/base/browser/window'; import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { IPointerHandlerHelper, MouseHandler } from 'vs/editor/browser/controller/mouseHandler'; -import { TextAreaSyntethicEvents } from 'vs/editor/browser/controller/textAreaInput'; +import { TextAreaSyntethicEvents } from 'vs/editor/browser/controller/editContext/textArea/textAreaInput'; import { NavigationCommandRevealType } from 'vs/editor/browser/coreCommands'; import { IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { EditorMouseEvent, EditorPointerEventFactory } from 'vs/editor/browser/editorDom'; diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index 5558cf28cd4b6..c28c7b3d0ffb2 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -13,7 +13,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { IPointerHandlerHelper } from 'vs/editor/browser/controller/mouseHandler'; import { PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; import { PointerHandler } from 'vs/editor/browser/controller/pointerHandler'; -import { IVisibleRangeProvider, TextAreaHandler } from 'vs/editor/browser/controller/textAreaHandler'; +import { IVisibleRangeProvider } from 'vs/editor/browser/controller/editContext/textArea/textAreaHandler'; import { IContentWidget, IContentWidgetPosition, IEditorAriaOptions, IGlyphMarginWidget, IGlyphMarginWidgetPosition, IMouseTarget, IOverlayWidget, IOverlayWidgetPosition, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext'; import { ICommandDelegate, ViewController } from 'vs/editor/browser/view/viewController'; @@ -56,6 +56,8 @@ import { IViewModel } from 'vs/editor/common/viewModel'; import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IColorTheme, getThemeTypeSelector } from 'vs/platform/theme/common/themeService'; +import { AbstractEditContext } from 'vs/editor/browser/controller/editContext/editContext'; +import { EditContextImpl } from 'vs/editor/browser/controller/editContext/impl'; export interface IContentWidgetData { @@ -90,7 +92,7 @@ export class View extends ViewEventHandler { private readonly _viewCursors: ViewCursors; private readonly _viewParts: ViewPart[]; - private readonly _textAreaHandler: TextAreaHandler; + private readonly _textAreaHandler: AbstractEditContext; private readonly _pointerHandler: PointerHandler; // Dom nodes @@ -126,7 +128,7 @@ export class View extends ViewEventHandler { this._viewParts = []; // Keyboard handler - this._textAreaHandler = this._instantiationService.createInstance(TextAreaHandler, this._context, viewController, this._createTextAreaHandlerHelper()); + this._textAreaHandler = this._instantiationService.createInstance(EditContextImpl, this._context, viewController, this._createTextAreaHandlerHelper()); this._viewParts.push(this._textAreaHandler); // These two dom nodes must be constructed up front, since references are needed in the layout provider (scrolling & co.) @@ -222,8 +224,7 @@ export class View extends ViewEventHandler { this._overflowGuardContainer.appendChild(margin.getDomNode()); this._overflowGuardContainer.appendChild(this._scrollbar.getDomNode()); this._overflowGuardContainer.appendChild(scrollDecoration.getDomNode()); - this._overflowGuardContainer.appendChild(this._textAreaHandler.textArea); - this._overflowGuardContainer.appendChild(this._textAreaHandler.textAreaCover); + this._textAreaHandler.appendTo(this._overflowGuardContainer); this._overflowGuardContainer.appendChild(this._overlayWidgets.getDomNode()); this._overflowGuardContainer.appendChild(minimap.getDomNode()); this._overflowGuardContainer.appendChild(blockOutline.domNode); @@ -286,12 +287,14 @@ export class View extends ViewEventHandler { }, dispatchTextAreaEvent: (event: CustomEvent) => { - this._textAreaHandler.textArea.domNode.dispatchEvent(event); + // TODO + // this._textAreaHandler.textArea.domNode.dispatchEvent(event); }, getLastRenderData: (): PointerHandlerLastRenderData => { const lastViewCursorsRenderData = this._viewCursors.getLastRenderData() || []; - const lastTextareaPosition = this._textAreaHandler.getLastRenderData(); + // TODO + const lastTextareaPosition = null; // this._textAreaHandler.getLastRenderData(); return new PointerHandlerLastRenderData(lastViewCursorsRenderData, lastTextareaPosition); }, renderNow: (): void => { diff --git a/src/vs/editor/browser/widget/codeEditor/editor.css b/src/vs/editor/browser/widget/codeEditor/editor.css index 5ef7246d0fb57..2f22e39cdd486 100644 --- a/src/vs/editor/browser/widget/codeEditor/editor.css +++ b/src/vs/editor/browser/widget/codeEditor/editor.css @@ -108,3 +108,33 @@ text-decoration: line-through; text-decoration-color: var(--vscode-editor-foreground, inherit); } + + +.underline { + text-decoration: underline; + text-decoration-color: var(--vscode-editor-foreground, inherit); + + &.style-solid { + text-decoration-style: solid; + } + + &.style-dotted { + text-decoration-style: dotted; + } + + &.style-dashed { + text-decoration-style: dashed; + } + + &.style-wavy, &.style-squiggle { + text-decoration-style: wavy; + } + + &.thickness-thin { + text-decoration-thickness: thin; + } + + &.thickness-thick { + text-decoration-thickness: thick; + } +} diff --git a/src/vs/editor/contrib/clipboard/browser/clipboard.ts b/src/vs/editor/contrib/clipboard/browser/clipboard.ts index e10b43e1f3719..dde3c0e6e1517 100644 --- a/src/vs/editor/contrib/clipboard/browser/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/browser/clipboard.ts @@ -7,7 +7,7 @@ import * as browser from 'vs/base/browser/browser'; import { getActiveDocument } from 'vs/base/browser/dom'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as platform from 'vs/base/common/platform'; -import { CopyOptions, InMemoryClipboardMetadataManager } from 'vs/editor/browser/controller/textAreaInput'; +import { CopyOptions, InMemoryClipboardMetadataManager } from 'vs/editor/browser/controller/editContext/textArea/textAreaInput'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Command, EditorAction, MultiCommand, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts index ad79eb6a01f91..ebed61ba54967 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts @@ -13,7 +13,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import * as platform from 'vs/base/common/platform'; import { generateUuid } from 'vs/base/common/uuid'; -import { ClipboardEventUtils } from 'vs/editor/browser/controller/textAreaInput'; +import { ClipboardEventUtils } from 'vs/editor/browser/controller/editContext/textArea/textAreaInput'; import { toExternalVSDataTransfer, toVSDataTransfer } from 'vs/editor/browser/dnd'; import { ICodeEditor, PastePayload } from 'vs/editor/browser/editorBrowser'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; diff --git a/src/vs/editor/test/browser/controller/imeRecordedTypes.ts b/src/vs/editor/test/browser/controller/imeRecordedTypes.ts index 70499b308f164..052b5b92df2b2 100644 --- a/src/vs/editor/test/browser/controller/imeRecordedTypes.ts +++ b/src/vs/editor/test/browser/controller/imeRecordedTypes.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { OperatingSystem } from 'vs/base/common/platform'; -import { IBrowser } from 'vs/editor/browser/controller/textAreaInput'; +import { IBrowser } from 'vs/editor/browser/controller/editContext/textArea/textAreaInput'; export interface IRecordedTextareaState { selectionDirection: 'forward' | 'backward' | 'none'; diff --git a/src/vs/editor/test/browser/controller/imeRecorder.ts b/src/vs/editor/test/browser/controller/imeRecorder.ts index 36290eb2e98dd..fa9c1dbad7e8f 100644 --- a/src/vs/editor/test/browser/controller/imeRecorder.ts +++ b/src/vs/editor/test/browser/controller/imeRecorder.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { TextAreaWrapper } from 'vs/editor/browser/controller/textAreaInput'; +import { TextAreaWrapper } from 'vs/editor/browser/controller/editContext/textArea/textAreaInput'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { IRecorded, IRecordedCompositionEvent, IRecordedEvent, IRecordedInputEvent, IRecordedKeyboardEvent, IRecordedTextareaState } from 'vs/editor/test/browser/controller/imeRecordedTypes'; import * as browser from 'vs/base/browser/browser'; diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts index 3aeaed1d9d41c..6a21e04cea692 100644 --- a/src/vs/editor/test/browser/controller/imeTester.ts +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITextAreaInputHost, TextAreaInput, TextAreaWrapper } from 'vs/editor/browser/controller/textAreaInput'; -import { ISimpleModel, PagedScreenReaderStrategy, TextAreaState } from 'vs/editor/browser/controller/textAreaState'; +import { ITextAreaInputHost, TextAreaInput, TextAreaWrapper } from 'vs/editor/browser/controller/editContext/textArea/textAreaInput'; +import { ISimpleModel, PagedScreenReaderStrategy, TextAreaState } from 'vs/editor/browser/controller/editContext/textArea/textAreaState'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { EndOfLinePreference } from 'vs/editor/common/model'; diff --git a/src/vs/editor/test/browser/controller/textAreaInput.test.ts b/src/vs/editor/test/browser/controller/textAreaInput.test.ts index fd2e23e375155..27d1132a57f5e 100644 --- a/src/vs/editor/test/browser/controller/textAreaInput.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaInput.test.ts @@ -8,8 +8,8 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { OperatingSystem } from 'vs/base/common/platform'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { ClipboardDataToCopy, IBrowser, ICompleteTextAreaWrapper, ITextAreaInputHost, TextAreaInput } from 'vs/editor/browser/controller/textAreaInput'; -import { TextAreaState } from 'vs/editor/browser/controller/textAreaState'; +import { ClipboardDataToCopy, IBrowser, ICompleteTextAreaWrapper, ITextAreaInputHost, TextAreaInput } from 'vs/editor/browser/controller/editContext/textArea/textAreaInput'; +import { TextAreaState } from 'vs/editor/browser/controller/editContext/textArea/textAreaState'; import { Position } from 'vs/editor/common/core/position'; import { IRecorded, IRecordedEvent, IRecordedTextareaState } from 'vs/editor/test/browser/controller/imeRecordedTypes'; import { TestAccessibilityService } from 'vs/platform/accessibility/test/common/testAccessibilityService'; diff --git a/src/vs/editor/test/browser/controller/textAreaState.test.ts b/src/vs/editor/test/browser/controller/textAreaState.test.ts index 2baa9ddda39e3..bd39d557d5017 100644 --- a/src/vs/editor/test/browser/controller/textAreaState.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaState.test.ts @@ -6,7 +6,7 @@ import assert from 'assert'; import { Disposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { ITextAreaWrapper, PagedScreenReaderStrategy, TextAreaState } from 'vs/editor/browser/controller/textAreaState'; +import { ITextAreaWrapper, PagedScreenReaderStrategy, TextAreaState } from 'vs/editor/browser/controller/editContext/textArea/textAreaState'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { createTextModel } from 'vs/editor/test/common/testTextModel';