diff --git a/lib/minimap-element.js b/lib/minimap-element.js index ee0ecbe8..4b66cce6 100644 --- a/lib/minimap-element.js +++ b/lib/minimap-element.js @@ -5,7 +5,6 @@ import { EventsDelegation, AncestorsMethods } from 'atom-utils-plus' import elementResizeDetectorImport from 'element-resize-detector' import * as Main from './main' -import DOMStylesReader from './dom-styles-reader' import CanvasDrawer from './mixins/canvas-drawer' import include from './decorators/include' import element from './decorators/element' @@ -83,10 +82,7 @@ class MinimapElement { * @access private */ this.minimap = undefined - /** - * @access private - */ - this.editorElement = undefined + /** * @access private */ @@ -181,11 +177,6 @@ class MinimapElement { */ this.quickSettingsElement = undefined - /** - * This MinimapElement's DOMStylesReader - */ - this.DOMStylesReader = new DOMStylesReader() - // States /** @@ -379,10 +370,10 @@ class MinimapElement { this.measureHeightAndWidth() this.updateMinimapFlexPosition() this.attached = true - this.attachedToTextEditor = this.queryParentSelector('atom-text-editor') === this.getTextEditorElement() + this.attachedToTextEditor = this.queryParentSelector('atom-text-editor') === this.minimap.getTextEditorElement() if (this.attachedToTextEditor) { - this.getTextEditorElement().setAttribute('with-minimap', '') + this.minimap.getTextEditorElement().setAttribute('with-minimap', '') } this.subscriptions.add( @@ -408,7 +399,7 @@ class MinimapElement { * @access private */ detachedCallback () { - this.getTextEditorElement().removeAttribute('with-minimap') + this.minimap.getTextEditorElement().removeAttribute('with-minimap') this.attached = false } @@ -442,7 +433,7 @@ class MinimapElement { attach (parent) { if (this.attached) { return } - const container = parent || this.getTextEditorElement() + const container = parent || this.minimap.getTextEditorElement() const minimaps = container.querySelectorAll('atom-text-editor-minimap') if (minimaps.length) { Array.prototype.forEach.call(minimaps, (el) => { el.destroy() }) @@ -666,25 +657,6 @@ class MinimapElement { delete this.openQuickSettings } - /** - * Returns the target `TextEditor` of the Minimap. - * - * @return {TextEditor} the minimap's text editor - */ - getTextEditor () { return this.minimap.getTextEditor() } - - /** - * Returns the `TextEditorElement` for the Minimap's `TextEditor`. - * - * @return {TextEditorElement} the minimap's text editor element - */ - getTextEditorElement () { - if (this.editorElement) { return this.editorElement } - - this.editorElement = atom.views.getView(this.getTextEditor()) - return this.editorElement - } - // ## ## ####### ######## ######## ## // ### ### ## ## ## ## ## ## // #### #### ## ## ## ## ## ## @@ -1088,7 +1060,7 @@ class MinimapElement { const row = Math.floor(deltaY / this.minimap.getLineHeight()) + this.minimap.getFirstVisibleScreenRow() const textEditor = this.minimap.getTextEditor() - const textEditorElement = this.getTextEditorElement() + const textEditorElement = this.minimap.getTextEditorElement() const scrollTop = row * textEditor.getLineHeightInPixels() - this.minimap.getTextEditorHeight() / 2 const textEditorScrollTop = textEditorElement.pixelPositionForScreenPosition([row, 0]).top - this.minimap.getTextEditorHeight() / 2 diff --git a/lib/minimap.js b/lib/minimap.js index f227265a..29c613b7 100644 --- a/lib/minimap.js +++ b/lib/minimap.js @@ -46,6 +46,13 @@ export default class Minimap { * @access private */ this.textEditor = options.textEditor + + /** + * The Minimap's text editor element. + * @access private + */ + this.editorElement = undefined + /** * The stand-alone state of the current Minimap. * @@ -525,6 +532,18 @@ export default class Minimap { */ getTextEditor () { return this.textEditor } + /** + * Returns the `TextEditorElement` for the Minimap's `TextEditor`. + * + * @return {TextEditorElement} the minimap's text editor element + */ + getTextEditorElement () { + if (this.editorElement) { return this.editorElement } + + this.editorElement = atom.views.getView(this.getTextEditor()) + return this.editorElement + } + /** * Returns the height of the `TextEditor` at the Minimap scale. * diff --git a/lib/mixins/canvas-drawer.js b/lib/mixins/canvas-drawer.js index 15218423..64ed37ff 100644 --- a/lib/mixins/canvas-drawer.js +++ b/lib/mixins/canvas-drawer.js @@ -5,6 +5,7 @@ import Mixin from 'mixto' import * as Main from '../main' import CanvasLayer from '../canvas-layer' +import DOMStylesReader from '../dom-styles-reader' /** * The `CanvasDrawer` mixin is responsible for the rendering of a `Minimap` @@ -63,6 +64,11 @@ export default class CanvasDrawer extends Mixin { // the maximum number of tokens to render in one line this.maxTokensInOneLine = atom.config.get('minimap.maxTokensInOneLine') + + /** + * This MinimapElement's DOMStylesReader + */ + this.DOMStylesReader = new DOMStylesReader() } /** @@ -191,7 +197,7 @@ export default class CanvasDrawer extends Mixin { * @return {string} a CSS color */ getDefaultColor () { - const color = this.DOMStylesReader.retrieveStyleFromDom(['.editor'], 'color', this.getTextEditorElement(), true) + const color = this.DOMStylesReader.retrieveStyleFromDom(['.editor'], 'color', this.minimap.getTextEditorElement(), true) return transparentize(color, this.getTextOpacity()) } @@ -206,7 +212,7 @@ export default class CanvasDrawer extends Mixin { */ getTokenColor (token) { const scopes = token.scopeDescriptor || token.scopes - const color = this.DOMStylesReader.retrieveStyleFromDom(scopes, 'color', this.getTextEditorElement(), true) + const color = this.DOMStylesReader.retrieveStyleFromDom(scopes, 'color', this.minimap.getTextEditorElement(), true) return transparentize(color, this.getTextOpacity()) } @@ -227,7 +233,7 @@ export default class CanvasDrawer extends Mixin { if (properties.scope) { const scopeString = properties.scope.split(/\s+/) - return this.DOMStylesReader.retrieveStyleFromDom(scopeString, 'background-color', this.getTextEditorElement(), true) + return this.DOMStylesReader.retrieveStyleFromDom(scopeString, 'background-color', this.minimap.getTextEditorElement(), true) } else { return this.getDefaultColor() } @@ -393,33 +399,6 @@ export default class CanvasDrawer extends Mixin { renderData.context.fill() } - /** - * Returns an array of tokens by line. - * - * @param {number} startRow The start row - * @param {number} endRow The end row - * @return {Array} An array of tokens by line - * @access private - */ - eachTokenForScreenRows (startRow, endRow, callback) { - const editor = this.getTextEditor() - const invisibleRegExp = this.getInvisibleRegExp() - endRow = Math.min(endRow, editor.getScreenLineCount()) - - for (let row = startRow; row < endRow; row++) { - const editorTokensForScreenRow = editor.tokensForScreenRow(row) - const numToken = editorTokensForScreenRow.length - const numTokenToRender = Math.min(numToken, this.maxTokensInOneLine) - for (let iToken = 0; iToken < numTokenToRender; iToken++) { - const token = editorTokensForScreenRow[iToken] - callback(row, { - text: token.text.replace(invisibleRegExp, ' '), - scopes: token.scopes - }) - } - } - } - /** * Draws lines on the corresponding layer. * @@ -445,7 +424,7 @@ export default class CanvasDrawer extends Mixin { let lastLine, x let y = (offsetRow * lineHeight) - lineHeight - this.eachTokenForScreenRows(firstRow, lastRow, (line, token) => { + eachTokenForScreenRows(firstRow, lastRow, this.minimap.getTextEditor(), this.maxTokensInOneLine, (line, token) => { if (lastLine !== line) { x = 0 y += lineHeight @@ -461,80 +440,14 @@ export default class CanvasDrawer extends Mixin { ? this.getTokenColor(token) : this.getDefaultColor() - x = this.drawToken( - context, token.text, color, x, y, charWidth, charHeight + x = drawToken( + context, token.text, color, x, y, charWidth, charHeight, this.ignoreWhitespacesInTokens ) } }) context.fill() } - /** - * Returns the regexp to replace invisibles substitution characters - * in editor lines. - * - * @return {RegExp} the regular expression to match invisible characters - * @access private - */ - getInvisibleRegExp () { - const invisibles = this.getTextEditor().getInvisibles() - const regexp = [] - if (invisibles.cr != null) { regexp.push(invisibles.cr) } - if (invisibles.eol != null) { regexp.push(invisibles.eol) } - if (invisibles.space != null) { regexp.push(invisibles.space) } - if (invisibles.tab != null) { regexp.push(invisibles.tab) } - - if (regexp.length !== 0) { - return RegExp(regexp.filter((s) => { - return typeof s === 'string' - }).map(escapeRegExp).join('|'), 'g') - } else { - return null - } - } - - /** - * Draws a single token on the given context. - * - * @param {CanvasRenderingContext2D} context the target canvas context - * @param {string} text the token's text content - * @param {string} color the token's CSS color - * @param {number} x the x position of the token in the line - * @param {number} y the y position of the line in the minimap - * @param {number} charWidth the width of a character in the minimap - * @param {number} charHeight the height of a character in the minimap - * @return {number} the x position at the end of the token - * @access private - */ - drawToken (context, text, color, x, y, charWidth, charHeight) { - context.fillStyle = color - - if (this.ignoreWhitespacesInTokens) { - const length = text.length * charWidth - context.fillRect(x, y, length, charHeight) - - return x + length - } else { - let chars = 0 - for (let j = 0, len = text.length; j < len; j++) { - const char = text[j] - if (/\s/.test(char)) { - if (chars > 0) { - context.fillRect(x - (chars * charWidth), y, chars * charWidth, charHeight) - } - chars = 0 - } else { - chars++ - } - x += charWidth - } - if (chars > 0) { - context.fillRect(x - (chars * charWidth), y, chars * charWidth, charHeight) - } - return x - } - } - /** * Draws the specified decorations for the current `screenRow`. * @@ -727,6 +640,109 @@ export default class CanvasDrawer extends Mixin { } } +// ######## ######## ### ## ## +// ## ## ## ## ## ## ## ## ## +// ## ## ## ## ## ## ## ## ## +// ## ## ######## ## ## ## ## ## +// ## ## ## ## ######### ## ## ## +// ## ## ## ## ## ## ## ## ## +// ######## ## ## ## ## ### ### + +/** + * Draws a single token on the given context. + * + * @param {CanvasRenderingContext2D} context the target canvas context + * @param {string} text the token's text content + * @param {string} color the token's CSS color + * @param {number} x the x position of the token in the line + * @param {number} y the y position of the line in the minimap + * @param {number} charWidth the width of a character in the minimap + * @param {number} charHeight the height of a character in the minimap + * @return {number} the x position at the end of the token + * @return {boolean} the x position at the end of the token + * @access private + */ +function drawToken (context, text, color, x, y, charWidth, charHeight, ignoreWhitespacesInTokens) { + context.fillStyle = color + + if (ignoreWhitespacesInTokens) { + const length = text.length * charWidth + context.fillRect(x, y, length, charHeight) + + return x + length + } else { + let chars = 0 + for (let j = 0, len = text.length; j < len; j++) { + const char = text[j] + if (/\s/.test(char)) { + if (chars > 0) { + context.fillRect(x - (chars * charWidth), y, chars * charWidth, charHeight) + } + chars = 0 + } else { + chars++ + } + x += charWidth + } + if (chars > 0) { + context.fillRect(x - (chars * charWidth), y, chars * charWidth, charHeight) + } + return x + } +} + +/** + * Returns an array of tokens by line. + * + * @param {number} startRow The start row + * @param {number} endRow The end row + * @param {TextEditor} editor + * @param {number} maxTokensInOneLine the maximum number of tokens to render in one line + * @return {Array} An array of tokens by line + * @access private + */ +function eachTokenForScreenRows (startRow, endRow, editor, maxTokensInOneLine, callback) { + const invisibleRegExp = getInvisibleRegExp(editor) + endRow = Math.min(endRow, editor.getScreenLineCount()) + + for (let row = startRow; row < endRow; row++) { + const editorTokensForScreenRow = editor.tokensForScreenRow(row) + const numToken = editorTokensForScreenRow.length + const numTokenToRender = Math.min(numToken, maxTokensInOneLine) + for (let iToken = 0; iToken < numTokenToRender; iToken++) { + const token = editorTokensForScreenRow[iToken] + callback(row, { + text: token.text.replace(invisibleRegExp, ' '), + scopes: token.scopes + }) + } + } +} + +/** + * Returns the regexp to replace invisibles substitution characters + * in editor lines. + * @param {TextEditor} editor + * @return {RegExp} the regular expression to match invisible characters + * @access private + */ +function getInvisibleRegExp (editor) { + const invisibles = editor.getInvisibles() + const regexp = [] + if (invisibles.cr != null) { regexp.push(invisibles.cr) } + if (invisibles.eol != null) { regexp.push(invisibles.eol) } + if (invisibles.space != null) { regexp.push(invisibles.space) } + if (invisibles.tab != null) { regexp.push(invisibles.tab) } + + if (regexp.length !== 0) { + return RegExp(regexp.filter((s) => { + return typeof s === 'string' + }).map(escapeRegExp).join('|'), 'g') + } else { + return null + } +} + // ###### ####### ## ####### ######## ###### // ## ## ## ## ## ## ## ## ## ## ## // ## ## ## ## ## ## ## ## ## diff --git a/spec/minimap-element-spec.js b/spec/minimap-element-spec.js index 299aef92..641b466f 100644 --- a/spec/minimap-element-spec.js +++ b/spec/minimap-element-spec.js @@ -177,7 +177,7 @@ describe('MinimapElement', () => { it('takes the height of the editor', () => { expect(minimapElement.offsetHeight).toEqual(editorElement.clientHeight) - expect(minimapElement.offsetWidth).toBeCloseTo(editorElement.clientWidth / 10, 0) + expect(minimapElement.offsetWidth).toBeNear(editorElement.clientWidth / 10, 1) }) it('knows when attached to a text editor', () => { @@ -232,7 +232,7 @@ describe('MinimapElement', () => { }) runs(() => { nextAnimationFrame() - expect(minimapElement.DOMStylesReader.retrieveStyleFromDom(['.editor'], 'color'), minimapElement.getTextEditorElement()).toEqual(`rgb(0, ${0x6d}, ${0x6d})`) + expect(minimapElement.DOMStylesReader.retrieveStyleFromDom(['.editor'], 'color'), minimapElement.minimap.getTextEditorElement()).toEqual(`rgb(0, ${0x6d}, ${0x6d})`) }) }) }) @@ -260,7 +260,7 @@ describe('MinimapElement', () => { }) runs(() => { nextAnimationFrame() - expect(minimapElement.DOMStylesReader.retrieveStyleFromDom(['.editor'], 'color'), minimapElement.getTextEditorElement()).toEqual(`rgba(0, ${0x6d}, ${0x6d}, 0)`) + expect(minimapElement.DOMStylesReader.retrieveStyleFromDom(['.editor'], 'color'), minimapElement.minimap.getTextEditorElement()).toEqual(`rgba(0, ${0x6d}, ${0x6d}, 0)`) }) }) })