diff --git a/src/vs/editor/browser/controller/keyboardHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts similarity index 71% rename from src/vs/editor/browser/controller/keyboardHandler.ts rename to src/vs/editor/browser/controller/textAreaHandler.ts index f442e4a5b16297818a9659e04de0ff8cb6269bcf..90c12809f3efde73b83508ad8226f2cbf9adc256 100644 --- a/src/vs/editor/browser/controller/keyboardHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -14,15 +14,17 @@ import { Configuration } from 'vs/editor/browser/config/configuration'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import { HorizontalRange } from 'vs/editor/common/view/renderingContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; -import { FastDomNode } from 'vs/base/browser/fastDomNode'; +import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { VerticalRevealType } from 'vs/editor/common/controller/cursorEvents'; import { ViewController } from 'vs/editor/browser/view/viewController'; import { EndOfLinePreference } from "vs/editor/common/editorCommon"; import { IKeyboardEvent } from "vs/base/browser/keyboardEvent"; +import { PartFingerprints, PartFingerprint } from "vs/editor/browser/view/viewPart"; +import { Margin } from "vs/editor/browser/viewParts/margin/margin"; +import { LineNumbersOverlay } from "vs/editor/browser/viewParts/lineNumbers/lineNumbers"; -export interface IKeyboardHandlerHelper { +export interface ITextAreaHandlerHelper { viewDomNode: FastDomNode; - textArea: FastDomNode; visibleRangeForPositionRelativeToEditor(lineNumber: number, column: number): HorizontalRange; getVerticalOffsetForLineNumber(lineNumber: number): number; } @@ -44,12 +46,11 @@ export const enum TextAreaStrategy { NVDA } -export class KeyboardHandler extends ViewEventHandler { +export class TextAreaHandler extends ViewEventHandler { private readonly _context: ViewContext; private readonly _viewController: ViewController; - private readonly _textArea: FastDomNode; - private readonly _viewHelper: IKeyboardHandlerHelper; + private readonly _viewHelper: ITextAreaHandlerHelper; private _contentLeft: number; private _contentWidth: number; @@ -61,15 +62,15 @@ export class KeyboardHandler extends ViewEventHandler { private _lastCopiedValue: string; private _lastCopiedValueIsFromEmptySelection: boolean; + public readonly textArea: FastDomNode; + public readonly textAreaCover: FastDomNode; private readonly _textAreaInput: TextAreaInput; - constructor(context: ViewContext, viewController: ViewController, viewHelper: IKeyboardHandlerHelper) { + constructor(context: ViewContext, viewController: ViewController, viewHelper: ITextAreaHandlerHelper) { super(); this._context = context; this._viewController = viewController; - this._textArea = viewHelper.textArea; - Configuration.applyFontInfo(this._textArea, this._context.configuration.editor.fontInfo); this._viewHelper = viewHelper; this._contentLeft = this._context.configuration.editor.layoutInfo.contentLeft; @@ -82,6 +83,43 @@ export class KeyboardHandler extends ViewEventHandler { this._lastCopiedValue = null; this._lastCopiedValueIsFromEmptySelection = false; + // Text Area (The focus will always be in the textarea when the cursor is blinking) + this.textArea = createFastDomNode(document.createElement('textarea')); + PartFingerprints.write(this.textArea, PartFingerprint.TextArea); + this.textArea.setClassName('inputarea'); + this.textArea.setAttribute('wrap', 'off'); + this.textArea.setAttribute('autocorrect', 'off'); + this.textArea.setAttribute('autocapitalize', 'off'); + this.textArea.setAttribute('spellcheck', 'false'); + this.textArea.setAttribute('aria-label', this._context.configuration.editor.viewInfo.ariaLabel); + this.textArea.setAttribute('role', 'textbox'); + this.textArea.setAttribute('aria-multiline', 'true'); + this.textArea.setAttribute('aria-haspopup', 'false'); + this.textArea.setAttribute('aria-autocomplete', 'both'); + + this.textArea.setTop(0); + this.textArea.setLeft(0); + Configuration.applyFontInfo(this.textArea, this._context.configuration.editor.fontInfo); + + // On top of the text area, we position a dom node to cover it up + // (there have been reports of tiny blinking cursors) + // (in WebKit the textarea is 1px by 1px because it cannot handle input to a 0x0 textarea) + this.textAreaCover = createFastDomNode(document.createElement('div')); + if (this._context.configuration.editor.viewInfo.glyphMargin) { + this.textAreaCover.setClassName('monaco-editor-background ' + Margin.CLASS_NAME + ' ' + 'textAreaCover'); + } else { + if (this._context.configuration.editor.viewInfo.renderLineNumbers) { + this.textAreaCover.setClassName('monaco-editor-background ' + LineNumbersOverlay.CLASS_NAME + ' ' + 'textAreaCover'); + } else { + this.textAreaCover.setClassName('monaco-editor-background ' + 'textAreaCover'); + } + } + this.textAreaCover.setPosition('absolute'); + this.textAreaCover.setWidth(1); + this.textAreaCover.setHeight(1); + this.textAreaCover.setTop(0); + this.textAreaCover.setLeft(0); + const simpleModel: ISimpleModel = { getLineCount: (): number => { return this._context.model.getLineCount(); @@ -136,7 +174,7 @@ export class KeyboardHandler extends ViewEventHandler { } }; - this._textAreaInput = this._register(new TextAreaInput(textAreaInputHost, this._textArea)); + this._textAreaInput = this._register(new TextAreaInput(textAreaInputHost, this.textArea)); this._register(this._textAreaInput.onKeyDown((e: IKeyboardEvent) => { this._viewController.emitKeyDown(e); @@ -184,12 +222,12 @@ export class KeyboardHandler extends ViewEventHandler { this._viewHelper.getVerticalOffsetForLineNumber(lineNumber), visibleRange.left ); - this._textArea.setTop(this._visiblePosition.top - this._scrollTop); - this._textArea.setLeft(this._contentLeft + this._visiblePosition.left - this._scrollLeft); + this.textArea.setTop(this._visiblePosition.top - this._scrollTop); + this.textArea.setLeft(this._contentLeft + this._visiblePosition.left - this._scrollLeft); } // Show the textarea - this._textArea.setHeight(this._context.configuration.editor.lineHeight); + this.textArea.setHeight(this._context.configuration.editor.lineHeight); this._viewHelper.viewDomNode.addClassName('ime-input'); this._viewController.compositionStart('keyboard'); @@ -199,30 +237,30 @@ export class KeyboardHandler extends ViewEventHandler { if (browser.isEdgeOrIE) { // Due to isEdgeOrIE (where the textarea was not cleared initially) // we cannot assume the text consists only of the composited text - this._textArea.setWidth(0); + this.textArea.setWidth(0); } else { // adjust width by its size let canvasElem = document.createElement('canvas'); let context = canvasElem.getContext('2d'); - let cs = dom.getComputedStyle(this._textArea.domNode); + let cs = dom.getComputedStyle(this.textArea.domNode); if (browser.isFirefox) { // computedStyle.font is empty in Firefox... context.font = `${cs.fontStyle} ${cs.fontVariant} ${cs.fontWeight} ${cs.fontStretch} ${cs.fontSize} / ${cs.lineHeight} ${cs.fontFamily}`; let metrics = context.measureText(e.data); - this._textArea.setWidth(metrics.width + 2); // +2 for Japanese... + this.textArea.setWidth(metrics.width + 2); // +2 for Japanese... } else { context.font = cs.font; let metrics = context.measureText(e.data); - this._textArea.setWidth(metrics.width); + this.textArea.setWidth(metrics.width); } } })); this._register(this._textAreaInput.onCompositionEnd(() => { - this._textArea.unsetHeight(); - this._textArea.unsetWidth(); - this._textArea.setLeft(0); - this._textArea.setTop(0); + this.textArea.unsetHeight(); + this.textArea.unsetWidth(); + this.textArea.setLeft(0); + this.textArea.setTop(0); this._viewHelper.viewDomNode.removeClassName('ime-input'); this._visiblePosition = null; @@ -266,7 +304,7 @@ export class KeyboardHandler extends ViewEventHandler { public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { // Give textarea same font size & line height as editor, for the IME case (when the textarea is visible) if (e.fontInfo) { - Configuration.applyFontInfo(this._textArea, this._context.configuration.editor.fontInfo); + Configuration.applyFontInfo(this.textArea, this._context.configuration.editor.fontInfo); } if (e.viewInfo.experimentalScreenReader) { this._textAreaInput.writeScreenReaderContent('strategy changed'); @@ -276,7 +314,7 @@ export class KeyboardHandler extends ViewEventHandler { this._contentWidth = this._context.configuration.editor.layoutInfo.contentWidth; } if (e.viewInfo.ariaLabel) { - this._textArea.setAttribute('aria-label', this._context.configuration.editor.viewInfo.ariaLabel); + this.textArea.setAttribute('aria-label', this._context.configuration.editor.viewInfo.ariaLabel); } return false; } @@ -290,8 +328,8 @@ export class KeyboardHandler extends ViewEventHandler { this._scrollLeft = e.scrollLeft; this._scrollTop = e.scrollTop; if (this._visiblePosition) { - this._textArea.setTop(this._visiblePosition.top - this._scrollTop); - this._textArea.setLeft(this._contentLeft + this._visiblePosition.left - this._scrollLeft); + this.textArea.setTop(this._visiblePosition.top - this._scrollTop); + this.textArea.setLeft(this._contentLeft + this._visiblePosition.left - this._scrollLeft); } return false; } @@ -306,15 +344,15 @@ export class KeyboardHandler extends ViewEventHandler { public setAriaActiveDescendant(id: string): void { if (id) { - this._textArea.setAttribute('role', 'combobox'); - if (this._textArea.getAttribute('aria-activedescendant') !== id) { - this._textArea.setAttribute('aria-haspopup', 'true'); - this._textArea.setAttribute('aria-activedescendant', id); + this.textArea.setAttribute('role', 'combobox'); + if (this.textArea.getAttribute('aria-activedescendant') !== id) { + this.textArea.setAttribute('aria-haspopup', 'true'); + this.textArea.setAttribute('aria-activedescendant', id); } } else { - this._textArea.setAttribute('role', 'textbox'); - this._textArea.removeAttribute('aria-activedescendant'); - this._textArea.removeAttribute('aria-haspopup'); + this.textArea.setAttribute('role', 'textbox'); + this.textArea.removeAttribute('aria-activedescendant'); + this.textArea.removeAttribute('aria-haspopup'); } } diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index b3301542736981b7cd137e49552d2da1146ce48d..c2dbbd4c6ce49bacaee535f14bcee65d2e6ffeca 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -14,7 +14,7 @@ import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; import { Configuration } from 'vs/editor/browser/config/configuration'; -import { KeyboardHandler, IKeyboardHandlerHelper } from 'vs/editor/browser/controller/keyboardHandler'; +import { TextAreaHandler, ITextAreaHandlerHelper } from 'vs/editor/browser/controller/textAreaHandler'; import { PointerHandler } from 'vs/editor/browser/controller/pointerHandler'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { ViewController, ExecCoreEditorCommandFunc } from 'vs/editor/browser/view/viewController'; @@ -80,16 +80,14 @@ export class View extends ViewEventHandler { private viewCursors: ViewCursors; private viewParts: ViewPart[]; - private keyboardHandler: KeyboardHandler; - private pointerHandler: PointerHandler; + private readonly _textAreaHandler: TextAreaHandler; + private readonly pointerHandler: PointerHandler; private outgoingEvents: ViewOutgoingEvents; // Dom nodes private linesContent: FastDomNode; public domNode: FastDomNode; - private textArea: FastDomNode; - private textAreaCover: FastDomNode; private overflowGuardContainer: FastDomNode; // Actual mutable state @@ -125,13 +123,12 @@ export class View extends ViewEventHandler { // The view context is passed on to most classes (basically to reduce param. counts in ctors) this._context = new ViewContext(configuration, model, this.eventDispatcher); - this.createTextArea(); + // Keyboard handler + this._textAreaHandler = new TextAreaHandler(this._context, viewController, this.createTextAreaHandlerHelper()); + this.createViewParts(); this._setLayout(); - // Keyboard handler - this.keyboardHandler = new KeyboardHandler(this._context, viewController, this.createKeyboardHandlerHelper()); - // Pointer handler this.pointerHandler = new PointerHandler(this._context, viewController, this.createPointerHandlerHelper()); @@ -140,44 +137,6 @@ export class View extends ViewEventHandler { })); } - private createTextArea(): void { - // Text Area (The focus will always be in the textarea when the cursor is blinking) - this.textArea = createFastDomNode(document.createElement('textarea')); - PartFingerprints.write(this.textArea, PartFingerprint.TextArea); - this.textArea.setClassName('inputarea'); - this.textArea.setAttribute('wrap', 'off'); - this.textArea.setAttribute('autocorrect', 'off'); - this.textArea.setAttribute('autocapitalize', 'off'); - this.textArea.setAttribute('spellcheck', 'false'); - this.textArea.setAttribute('aria-label', this._context.configuration.editor.viewInfo.ariaLabel); - this.textArea.setAttribute('role', 'textbox'); - this.textArea.setAttribute('aria-multiline', 'true'); - this.textArea.setAttribute('aria-haspopup', 'false'); - this.textArea.setAttribute('aria-autocomplete', 'both'); - - this.textArea.setTop(0); - this.textArea.setLeft(0); - - // On top of the text area, we position a dom node to cover it up - // (there have been reports of tiny blinking cursors) - // (in WebKit the textarea is 1px by 1px because it cannot handle input to a 0x0 textarea) - this.textAreaCover = createFastDomNode(document.createElement('div')); - if (this._context.configuration.editor.viewInfo.glyphMargin) { - this.textAreaCover.setClassName('monaco-editor-background ' + Margin.CLASS_NAME + ' ' + 'textAreaCover'); - } else { - if (this._context.configuration.editor.viewInfo.renderLineNumbers) { - this.textAreaCover.setClassName('monaco-editor-background ' + LineNumbersOverlay.CLASS_NAME + ' ' + 'textAreaCover'); - } else { - this.textAreaCover.setClassName('monaco-editor-background ' + 'textAreaCover'); - } - } - this.textAreaCover.setPosition('absolute'); - this.textAreaCover.setWidth(1); - this.textAreaCover.setHeight(1); - this.textAreaCover.setTop(0); - this.textAreaCover.setLeft(0); - } - private createViewParts(): void { // These two dom nodes must be constructed up front, since references are needed in the layout provider (scrolling & co.) this.linesContent = createFastDomNode(document.createElement('div')); @@ -268,8 +227,8 @@ export class View extends ViewEventHandler { this.overflowGuardContainer.appendChild(this._scrollbar.getDomNode()); this.overflowGuardContainer.appendChild(scrollDecoration.getDomNode()); this.overflowGuardContainer.appendChild(this.overlayWidgets.getDomNode()); - this.overflowGuardContainer.appendChild(this.textArea); - this.overflowGuardContainer.appendChild(this.textAreaCover); + this.overflowGuardContainer.appendChild(this._textAreaHandler.textArea); + this.overflowGuardContainer.appendChild(this._textAreaHandler.textAreaCover); this.overflowGuardContainer.appendChild(minimap.getDomNode()); this.domNode.appendChild(this.overflowGuardContainer); this.domNode.appendChild(this.contentWidgets.overflowingContentWidgetsDomNode); @@ -341,10 +300,9 @@ export class View extends ViewEventHandler { }; } - private createKeyboardHandlerHelper(): IKeyboardHandlerHelper { + private createTextAreaHandlerHelper(): ITextAreaHandlerHelper { return { viewDomNode: this.domNode, - textArea: this.textArea, visibleRangeForPositionRelativeToEditor: (lineNumber: number, column: number) => { this._flushAccumulatedAndRenderNow(); let visibleRanges = this.viewLines.visibleRangesForRange2(new Range(lineNumber, column, lineNumber, column)); @@ -435,7 +393,7 @@ export class View extends ViewEventHandler { this.eventDispatcher.removeEventHandler(this); this.outgoingEvents.dispose(); - this.keyboardHandler.dispose(); + this._textAreaHandler.dispose(); this.pointerHandler.dispose(); this.viewLines.dispose(); @@ -492,7 +450,7 @@ export class View extends ViewEventHandler { if (!this.viewLines.shouldRender() && viewPartsToRender.length === 0) { // Nothing to render - this.keyboardHandler.writeToTextArea(); + this._textAreaHandler.writeToTextArea(); return; } @@ -503,14 +461,14 @@ export class View extends ViewEventHandler { if (this.viewLines.shouldRender()) { this.viewLines.renderText(viewportData, () => { - this.keyboardHandler.writeToTextArea(); + this._textAreaHandler.writeToTextArea(); }); this.viewLines.onDidRender(); // Rendering of viewLines might cause scroll events to occur, so collect view parts to render again viewPartsToRender = this._getViewPartsToRender(); } else { - this.keyboardHandler.writeToTextArea(); + this._textAreaHandler.writeToTextArea(); } let renderingContext = new RenderingContext(this.layoutProvider, viewportData, this.viewLines); @@ -667,7 +625,7 @@ export class View extends ViewEventHandler { } public setAriaActiveDescendant(id: string): void { - this.keyboardHandler.setAriaActiveDescendant(id); + this._textAreaHandler.setAriaActiveDescendant(id); } public saveState(): editorCommon.IViewState { @@ -679,11 +637,11 @@ export class View extends ViewEventHandler { } public focus(): void { - this.keyboardHandler.focusTextArea(); + this._textAreaHandler.focusTextArea(); } public isFocused(): boolean { - return this.keyboardHandler.isFocused(); + return this._textAreaHandler.isFocused(); } public addContentWidget(widgetData: IContentWidgetData): void { diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts index 828c278edb4c9cd5cfe1375d2071841423f85f11..63999ce1c14debd980dfaecc25d30292b269a071 100644 --- a/src/vs/editor/test/browser/controller/imeTester.ts +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -10,7 +10,7 @@ import { Range, IRange } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { createFastDomNode } from 'vs/base/browser/fastDomNode'; import * as browser from 'vs/base/browser/browser'; -import { TextAreaStrategy } from "vs/editor/browser/controller/keyboardHandler"; +import { TextAreaStrategy } from "vs/editor/browser/controller/textAreaHandler"; // To run this test, open imeTester.html