From eacf0d8f64d04445d6ad8215840f94b4aaca54c9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 6 Mar 2017 20:46:42 -0700 Subject: [PATCH] Decorate cursors via private 'cursor' decoration type This eliminates the need to query the selections marker layer more than once per frame, since it is already queried for highlights and line decorations associated with the selections. --- src/text-editor-component.js | 120 +++++++++++++++-------------------- src/text-editor.coffee | 8 ++- 2 files changed, 57 insertions(+), 71 deletions(-) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 8494450fd..ce923bbdb 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -37,14 +37,15 @@ class TextEditorComponent { this.lastKeydown = null this.lastKeydownBeforeKeypress = null this.openedAccentedCharacterMenu = false - this.cursorsToRender = [] this.decorationsToRender = { lineNumbers: new Map(), lines: new Map(), - highlights: new Map() + highlights: new Map(), + cursors: [] } this.decorationsToMeasure = { - highlights: new Map() + highlights: new Map(), + cursors: [] } if (this.props.model) this.observeModel() @@ -83,15 +84,13 @@ class TextEditorComponent { this.populateVisibleRowRange() const longestLineToMeasure = this.checkForNewLongestLine() this.queryDecorationsToRender() - this.queryCursorsToRender() etch.updateSync(this) this.measureHorizontalPositions() if (longestLineToMeasure) this.measureLongestLineWidth(longestLineToMeasure) if (this.pendingAutoscroll) this.finalizeAutoscroll() - this.positionCursorsToRender() - this.updateHighlightsToRender() + this.updateAbsolutePositionedDecorations() etch.updateSync(this) @@ -324,8 +323,8 @@ class TextEditorComponent { const children = [this.renderHiddenInput()] - for (let i = 0; i < this.cursorsToRender.length; i++) { - const {pixelLeft, pixelTop, pixelWidth} = this.cursorsToRender[i] + for (let i = 0; i < this.decorationsToRender.cursors.length; i++) { + const {pixelLeft, pixelTop, pixelWidth} = this.decorationsToRender.cursors[i] children.push($.div({ className: 'cursor', style: { @@ -349,10 +348,9 @@ class TextEditorComponent { renderHiddenInput () { let top, left - const hiddenInputState = this.getHiddenInputState() - if (hiddenInputState) { - top = hiddenInputState.pixelTop - left = hiddenInputState.pixelLeft + if (this.hiddenInputPosition) { + top = this.hiddenInputPosition.pixelTop + left = this.hiddenInputPosition.pixelLeft } else { top = 0 left = 0 @@ -384,45 +382,11 @@ class TextEditorComponent { }) } - queryCursorsToRender () { - const model = this.getModel() - const cursorMarkers = model.selectionsMarkerLayer.findMarkers({ - intersectsScreenRowRange: [ - this.getRenderedStartRow(), - this.getRenderedEndRow() - 1, - ] - }) - const lastCursorMarker = model.getLastCursor().getMarker() - - this.cursorsToRender.length = 0 - this.lastCursorIndex = -1 - for (let i = 0; i < cursorMarkers.length; i++) { - const cursorMarker = cursorMarkers[i] - if (cursorMarker === lastCursorMarker) this.lastCursorIndex = i - const screenPosition = cursorMarker.getHeadScreenPosition() - const {row, column} = screenPosition - - if (row < this.getRenderedStartRow() || row >= this.getRenderedEndRow()) { - continue - } - - this.requestHorizontalMeasurement(row, column) - let columnWidth = 0 - if (model.lineLengthForScreenRow(row) > column) { - columnWidth = 1 - this.requestHorizontalMeasurement(row, column + 1) - } - this.cursorsToRender.push({ - screenPosition, columnWidth, - pixelTop: 0, pixelLeft: 0, pixelWidth: 0 - }) - } - } - queryDecorationsToRender () { this.decorationsToRender.lineNumbers.clear() this.decorationsToRender.lines.clear() this.decorationsToMeasure.highlights.clear() + this.decorationsToMeasure.cursors.length = 0 const decorationsByMarker = this.getModel().decorationManager.decorationPropertiesByMarkerForScreenRowRange( @@ -435,15 +399,15 @@ class TextEditorComponent { const reversed = marker.isReversed() for (let i = 0, length = decorations.length; i < decorations.length; i++) { const decoration = decorations[i] - this.addDecorationToRender(decoration.type, decoration, screenRange, reversed) + this.addDecorationToRender(decoration.type, decoration, marker, screenRange, reversed) } }) } - addDecorationToRender (type, decoration, screenRange, reversed) { + addDecorationToRender (type, decoration, marker, screenRange, reversed) { if (Array.isArray(type)) { for (let i = 0, length = type.length; i < length; i++) { - this.addDecorationToRender(type[i], decoration, screenRange, reversed) + this.addDecorationToRender(type[i], decoration, marker, screenRange, reversed) } } else { switch (type) { @@ -454,6 +418,9 @@ class TextEditorComponent { case 'highlight': this.addHighlightDecorationToMeasure(decoration, screenRange) break + case 'cursor': + this.addCursorDecorationToMeasure(marker, screenRange) + break } } } @@ -514,6 +481,28 @@ class TextEditorComponent { } } + addCursorDecorationToMeasure (marker, screenRange, reversed) { + const model = this.getModel() + const isLastCursor = model.getLastCursor().getMarker() === marker + const screenPosition = reversed ? screenRange.start : screenRange.end + const {row, column} = screenPosition + + if (row < this.getRenderedStartRow() || row >= this.getRenderedEndRow()) return + + this.requestHorizontalMeasurement(row, column) + let columnWidth = 0 + if (model.lineLengthForScreenRow(row) > column) { + columnWidth = 1 + this.requestHorizontalMeasurement(row, column + 1) + } + this.decorationsToMeasure.cursors.push({screenPosition, columnWidth, isLastCursor}) + } + + updateAbsolutePositionedDecorations () { + this.updateHighlightsToRender() + this.updateCursorsToRender() + } + updateHighlightsToRender () { this.decorationsToRender.highlights.clear() this.decorationsToMeasure.highlights.forEach((highlights, tileRow) => { @@ -529,28 +518,24 @@ class TextEditorComponent { }) } - positionCursorsToRender () { + updateCursorsToRender () { + this.decorationsToRender.cursors.length = 0 + const height = this.measurements.lineHeight + 'px' - for (let i = 0; i < this.cursorsToRender.length; i++) { - const cursorToRender = this.cursorsToRender[i] - const {row, column} = cursorToRender.screenPosition + for (let i = 0; i < this.decorationsToMeasure.cursors.length; i++) { + const cursor = this.decorationsToMeasure.cursors[i] + const {row, column} = cursor.screenPosition const pixelTop = this.pixelTopForScreenRow(row) const pixelLeft = this.pixelLeftForScreenRowAndColumn(row, column) - const pixelRight = (cursorToRender.columnWidth === 0) + const pixelRight = (cursor.columnWidth === 0) ? pixelLeft : this.pixelLeftForScreenRowAndColumn(row, column + 1) const pixelWidth = pixelRight - pixelLeft - cursorToRender.pixelTop = pixelTop - cursorToRender.pixelLeft = pixelLeft - cursorToRender.pixelWidth = pixelWidth - } - } - - getHiddenInputState () { - if (this.lastCursorIndex >= 0) { - return this.cursorsToRender[this.lastCursorIndex] + const cursorPosition = {pixelTop, pixelLeft, pixelWidth} + this.decorationsToRender.cursors[i] = cursorPosition + if (cursor.isLastCursor) this.hiddenInputPosition = cursorPosition } } @@ -608,10 +593,9 @@ class TextEditorComponent { // Restore the previous position of the field now that it is already focused // and won't cause unwanted scrolling. - const currentHiddenInputState = this.getHiddenInputState() - if (currentHiddenInputState) { - hiddenInput.style.top = currentHiddenInputState.pixelTop + 'px' - hiddenInput.style.left = currentHiddenInputState.pixelLeft + 'px' + if (this.hiddenInputPosition) { + hiddenInput.style.top = this.hiddenInputPosition.pixelTop + 'px' + hiddenInput.style.left = this.hiddenInputPosition.pixelLeft + 'px' } else { hiddenInput.style.top = 0 hiddenInput.style.left = 0 diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 540e1d2fd..b1d281abb 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -199,6 +199,11 @@ class TextEditor extends Model @selectionsMarkerLayer.trackDestructionInOnDidCreateMarkerCallbacks = true @decorationManager = new DecorationManager(@displayLayer) + @decorateMarkerLayer(@selectionsMarkerLayer, type: 'cursor') + @decorateMarkerLayer(@selectionsMarkerLayer, type: 'line-number', class: 'cursor-line') + @decorateMarkerLayer(@selectionsMarkerLayer, type: 'line-number', class: 'cursor-line-no-selection', onlyHead: true, onlyEmpty: true) + @decorateMarkerLayer(@selectionsMarkerLayer, type: 'line', class: 'cursor-line', onlyEmpty: true) + @decorateMarkerLayer(@displayLayer.foldsMarkerLayer, {type: 'line-number', class: 'folded'}) for marker in @selectionsMarkerLayer.getMarkers() @@ -2282,9 +2287,6 @@ class TextEditor extends Model cursor = new Cursor(editor: this, marker: marker, showCursorOnSelection: @showCursorOnSelection) @cursors.push(cursor) @cursorsByMarkerId.set(marker.id, cursor) - @decorateMarker(marker, type: 'line-number', class: 'cursor-line') - @decorateMarker(marker, type: 'line-number', class: 'cursor-line-no-selection', onlyHead: true, onlyEmpty: true) - @decorateMarker(marker, type: 'line', class: 'cursor-line', onlyEmpty: true) cursor moveCursors: (fn) ->