diff --git a/lib/adapters/legacy-adapter.js b/lib/adapters/legacy-adapter.ts similarity index 99% rename from lib/adapters/legacy-adapter.js rename to lib/adapters/legacy-adapter.ts index 24a6d2f2..20f02b17 100644 --- a/lib/adapters/legacy-adapter.js +++ b/lib/adapters/legacy-adapter.ts @@ -1,8 +1,8 @@ "use strict" - /** * @access private */ + export default class LegacyAdapter { constructor(textEditor) { this.textEditor = textEditor @@ -33,8 +33,10 @@ export default class LegacyAdapter { if (!this.heightCache) { this.heightCache = this.textEditor.getHeight() } + return this.heightCache } + return this.textEditor.getHeight() } @@ -43,8 +45,10 @@ export default class LegacyAdapter { if (!this.scrollTopCache) { this.scrollTopCache = this.textEditor.getScrollTop() } + return this.scrollTopCache } + return this.textEditor.getScrollTop() } @@ -57,6 +61,7 @@ export default class LegacyAdapter { if (!this.scrollLeftCache) { this.scrollLeftCache = this.textEditor.getScrollLeft() } + return this.scrollLeftCache } @@ -67,19 +72,22 @@ export default class LegacyAdapter { if (this.maxScrollTopCache != null && this.useCache) { return this.maxScrollTopCache } + let maxScrollTop = this.textEditor.displayBuffer.getMaxScrollTop() const lineHeight = this.textEditor.getLineHeightInPixels() if (this.scrollPastEnd) { maxScrollTop -= this.getHeight() - 3 * lineHeight } + if (this.useCache) { this.maxScrollTopCache = maxScrollTop } + return maxScrollTop } editorDestroyed() { return !this.textEditor || this.textEditor.isDestroyed() } -} +} \ No newline at end of file diff --git a/lib/adapters/stable-adapter.js b/lib/adapters/stable-adapter.ts similarity index 99% rename from lib/adapters/stable-adapter.js rename to lib/adapters/stable-adapter.ts index ea87bb10..d214dd8c 100644 --- a/lib/adapters/stable-adapter.js +++ b/lib/adapters/stable-adapter.ts @@ -1,8 +1,8 @@ "use strict" - /** * @access private */ + export default class StableAdapter { constructor(textEditor) { this.textEditor = textEditor @@ -38,8 +38,10 @@ export default class StableAdapter { if (!this.heightCache) { this.heightCache = this.textEditorElement.getHeight() } + return this.heightCache } + return this.textEditorElement.getHeight() } @@ -52,8 +54,10 @@ export default class StableAdapter { if (!this.scrollTopCache) { this.scrollTopCache = this.computeScrollTop() } + return this.scrollTopCache } + return this.computeScrollTop() } @@ -100,8 +104,10 @@ export default class StableAdapter { if (!this.scrollLeftCache) { this.scrollLeftCache = this.textEditorElement.getScrollLeft() } + return this.scrollLeftCache } + return this.textEditorElement.getScrollLeft() } @@ -115,6 +121,7 @@ export default class StableAdapter { } let maxScrollTop + if (this.textEditorElement.getMaxScrollTop) { maxScrollTop = this.textEditorElement.getMaxScrollTop() @@ -149,4 +156,4 @@ export default class StableAdapter { !this.textEditorElement.parentNode ) } -} +} \ No newline at end of file diff --git a/lib/canvas-layer.js b/lib/canvas-layer.ts similarity index 95% rename from lib/canvas-layer.js rename to lib/canvas-layer.ts index c2ca3de5..4a026abf 100644 --- a/lib/canvas-layer.js +++ b/lib/canvas-layer.ts @@ -1,9 +1,9 @@ /** @babel */ "use strict" - /** * @access private */ + export default class CanvasLayer { constructor() { /** @@ -11,14 +11,15 @@ export default class CanvasLayer { * @type {HTMLCanvasElement} */ this.canvas = document.createElement("canvas") - const desynchronized = false // TODO Electron 9 has color issues #786 /** * The onscreen canvas context. * @type {CanvasRenderingContext2D} */ - this.context = this.canvas.getContext("2d", { desynchronized }) + this.context = this.canvas.getContext("2d", { + desynchronized, + }) this.canvas.webkitImageSmoothingEnabled = false this.context.imageSmoothingEnabled = false @@ -28,12 +29,15 @@ export default class CanvasLayer { * @access private */ this.offscreenCanvas = document.createElement("canvas") + /** * The offscreen canvas context. * @type {CanvasRenderingContext2D} * @access private */ - this.offscreenContext = this.offscreenCanvas.getContext("2d", { desynchronized }) + this.offscreenContext = this.offscreenCanvas.getContext("2d", { + desynchronized, + }) this.offscreenCanvas.webkitImageSmoothingEnabled = false this.offscreenContext.imageSmoothingEnabled = false } @@ -97,4 +101,4 @@ export default class CanvasLayer { clearCanvas() { this.context.clearRect(0, 0, this.canvas.width, this.canvas.height) } -} +} \ No newline at end of file diff --git a/lib/decoration-management.js b/lib/decoration-management.ts similarity index 97% rename from lib/decoration-management.js rename to lib/decoration-management.ts index ec71cf93..1007de26 100644 --- a/lib/decoration-management.js +++ b/lib/decoration-management.ts @@ -4,7 +4,6 @@ import { Emitter } from "atom" import { escapeRegExp } from "./deps/underscore-plus" import path from "path" import Decoration from "./decoration" - /** * The mixin that provides the decorations API to the minimap editor * view. @@ -12,6 +11,7 @@ import Decoration from "./decoration" * This mixin is injected into the `Minimap` prototype, so every methods defined * in this file will be available on any `Minimap` instance. */ + export default class DecorationManagement { /** * Initializes the decorations related properties. @@ -36,12 +36,14 @@ export default class DecorationManagement { * @access private */ this.decorationsById = new Map() + /** * The decorations stored in an array indexed with their marker id. * @type {Object} * @access private */ this.decorationsByMarkerId = new Map() + /** * The subscriptions to the markers `did-change` event indexed using the * marker id. @@ -49,6 +51,7 @@ export default class DecorationManagement { * @access private */ this.decorationMarkerChangedSubscriptions = new Map() + /** * The subscriptions to the markers `did-destroy` event indexed using the * marker id. @@ -56,6 +59,7 @@ export default class DecorationManagement { * @access private */ this.decorationMarkerDestroyedSubscriptions = new Map() + /** * The subscriptions to the decorations `did-change-properties` event * indexed using the decoration id. @@ -63,6 +67,7 @@ export default class DecorationManagement { * @access private */ this.decorationUpdatedSubscriptions = new Map() + /** * The subscriptions to the decorations `did-destroy` event indexed using * the decoration id. @@ -70,7 +75,6 @@ export default class DecorationManagement { * @access private */ this.decorationDestroyedSubscriptions = new Map() - // is set to true when a minimapElement is destroyed this.destroyed = false } @@ -96,7 +100,7 @@ export default class DecorationManagement { * - decoration: the decoration object that was created * @return {Disposable} a disposable to stop listening to the event */ - onDidAddDecoration(callback: (event: Object) => void): Disposable { + onDidAddDecoration(callback: (event: Record) => void): Disposable { return this.emitter.on("did-add-decoration", callback) } @@ -112,7 +116,7 @@ export default class DecorationManagement { * - decoration: the decoration object that was created * @return {Disposable} a disposable to stop listening to the event */ - onDidRemoveDecoration(callback: (event: Object) => void): Disposable { + onDidRemoveDecoration(callback: (event: Record) => void): Disposable { return this.emitter.on("did-remove-decoration", callback) } @@ -131,7 +135,7 @@ export default class DecorationManagement { * - decoration: the decoration object that was created * @return {Disposable} a disposable to stop listening to the event */ - onDidChangeDecoration(callback: (event: Object) => void): Disposable { + onDidChangeDecoration(callback: (event: Record) => void): Disposable { return this.emitter.on("did-change-decoration", callback) } @@ -150,7 +154,7 @@ export default class DecorationManagement { * - decoration: the decoration object that was created * @return {Disposable} a disposable to stop listening to the event */ - onDidChangeDecorationRange(callback: (event: Object) => void): Disposable { + onDidChangeDecorationRange(callback: (event: Record) => void): Disposable { return this.emitter.on("did-change-decoration-range", callback) } @@ -237,8 +241,8 @@ export default class DecorationManagement { } const cache = {} - const decorations = this.decorationsById.values() + for (const decoration of decorations) { const range = decoration.marker.getScreenRange() const type = decoration.getProperties().type @@ -330,7 +334,14 @@ export default class DecorationManagement { */ decorateMarker( marker: Marker, - decorationParams: { class: string, color: string, plugin: string, render: Function, scope: string, type: string } + decorationParams: { + class: string + color: string + plugin: string + render: (...args: Array) => any + scope: string + type: string + } ): Decoration { if (this.destroyed || this.minimap.destroyed || marker == null) { return @@ -368,7 +379,6 @@ export default class DecorationManagement { marker.onDidChange((event) => { const decorations = this.decorationsByMarkerId.get(id) const screenRange = marker.getScreenRange() - this.invalidateDecorationForScreenRowsCache() if (decorations !== undefined) { @@ -380,10 +390,10 @@ export default class DecorationManagement { event, }) this.emitDecorationChanges(decoration.type, decoration) - decoration.screenRange = screenRange } } + let oldStart = event.oldTailScreenPosition let oldEnd = event.oldHeadScreenPosition let newStart = event.newTailScreenPosition @@ -392,6 +402,7 @@ export default class DecorationManagement { if (oldStart.row > oldEnd.row) { ;[oldStart, oldEnd] = [oldEnd, oldStart] } + if (newStart.row > newEnd.row) { ;[newStart, newEnd] = [newEnd, newStart] } @@ -437,13 +448,11 @@ export default class DecorationManagement { this.removeDecoration(decoration) }) ) - this.emitDecorationChanges(type, decoration) this.emitter.emit("did-add-decoration", { marker, decoration, }) - return decoration } @@ -461,8 +470,8 @@ export default class DecorationManagement { } this.invalidateDecorationForScreenRowsCache() - const range = decoration.screenRange + if (!range.start || !range.end) { return } @@ -495,7 +504,6 @@ export default class DecorationManagement { screenDelta, type, } - this.emitter.emit("did-change-decoration-range", changeEvent) } @@ -513,33 +521,32 @@ export default class DecorationManagement { const marker = decoration.marker let subscription - this.decorationsById.delete(decoration.id) - subscription = this.decorationUpdatedSubscriptions.get(decoration.id) + if (subscription !== undefined) { subscription.dispose() } subscription = this.decorationDestroyedSubscriptions.get(decoration.id) + if (subscription !== undefined) { subscription.dispose() } this.decorationUpdatedSubscriptions.delete(decoration.id) this.decorationDestroyedSubscriptions.delete(decoration.id) - const decorations = this.decorationsByMarkerId.get(marker.id) + if (decorations === undefined) { return } this.emitDecorationChanges(decoration.getProperties().type, decoration) - const index = decorations.indexOf(decoration) + if (index > -1) { decorations.splice(index, 1) - this.emitter.emit("did-remove-decoration", { marker, decoration, @@ -564,6 +571,7 @@ export default class DecorationManagement { } const decorations = this.decorationsByMarkerId.get(marker.id) + if (decorations === undefined) { return } @@ -574,6 +582,7 @@ export default class DecorationManagement { if (!this.destroyed && !this.minimap.editorDestroyed()) { this.emitDecorationChanges(decoration.getProperties().type, decoration) } + this.emitter.emit("did-remove-decoration", { marker, decoration, @@ -596,7 +605,6 @@ export default class DecorationManagement { this.decorationMarkerChangedSubscriptions.get(marker.id).dispose() this.decorationMarkerDestroyedSubscriptions.get(marker.id).dispose() - this.decorationsByMarkerId.delete(marker.id) this.decorationMarkerChangedSubscriptions.delete(marker.id) this.decorationMarkerDestroyedSubscriptions.delete(marker.id) @@ -607,26 +615,31 @@ export default class DecorationManagement { */ removeAllDecorations() { const decorationMarkerChangedSubscriptionsValues = this.decorationMarkerChangedSubscriptions.values() + for (const decoration of decorationMarkerChangedSubscriptionsValues) { decoration.dispose() } const decorationMarkerDestroyedSubscriptionsValues = this.decorationMarkerDestroyedSubscriptions.values() + for (const decoration of decorationMarkerDestroyedSubscriptionsValues) { decoration.dispose() } const decorationUpdatedSubscriptionsValues = this.decorationUpdatedSubscriptions.values() + for (const decoration of decorationUpdatedSubscriptionsValues) { decoration.dispose() } const decorationDestroyedSubscriptionsValues = this.decorationDestroyedSubscriptions.values() + for (const decoration of decorationDestroyedSubscriptionsValues) { decoration.dispose() } const decorationsByIdValues = this.decorationsById.values() + for (const decoration of decorationsByIdValues) { decoration.destroy() } @@ -685,4 +698,4 @@ function computeRangesDiffs(oldStart: number, oldEnd: number, newStart: number, } return diffs -} +} \ No newline at end of file diff --git a/lib/decoration.js b/lib/decoration.ts similarity index 97% rename from lib/decoration.js rename to lib/decoration.ts index d59772d4..f74cb267 100644 --- a/lib/decoration.js +++ b/lib/decoration.ts @@ -1,17 +1,17 @@ "use strict" import { Emitter } from "atom" - let idCounter = 0 + const nextId = function () { return idCounter++ } - /** * The `Decoration` class represents a decoration in the Minimap. * * It has the same API than the `Decoration` class of a text editor. */ + export default class Decoration { /** * Returns `true` if the passed-in decoration properties matches the @@ -26,6 +26,7 @@ export default class Decoration { if (decorationProperties.type.indexOf(type) >= 0) { return true } + return false } else { return type === decorationProperties.type @@ -45,35 +46,40 @@ export default class Decoration { * @access private */ this.marker = marker + /** * @access private */ this.minimap = minimap + /** * @access private */ this.emitter = new Emitter() + /** * @access private */ this.id = nextId() + /** * @access private */ this.properties = null this.setProperties(properties) this.properties.id = this.id + /** * @access private */ this.destroyed = false + /** * @access private */ this.markerDestroyDisposable = this.marker.onDidDestroy(() => { this.destroy() }) - this.screenRange = marker.getScreenRange() } @@ -182,7 +188,9 @@ export default class Decoration { const oldProperties = this.properties this.properties = newProperties this.properties.id = this.id - - this.emitter.emit("did-change-properties", { oldProperties, newProperties }) + this.emitter.emit("did-change-properties", { + oldProperties, + newProperties, + }) } -} +} \ No newline at end of file diff --git a/lib/decorators/element.js b/lib/decorators/element.ts similarity index 94% rename from lib/decorators/element.js rename to lib/decorators/element.ts index 9e0aed44..e224c238 100644 --- a/lib/decorators/element.js +++ b/lib/decorators/element.ts @@ -1,7 +1,6 @@ "use strict" import { registerOrUpdateElement } from "atom-utils-plus" - /** * Generates a decorator function to convert a class into a custom element * through the `registerOrUpdateElement` method from `atom-utils-plus`. @@ -34,6 +33,9 @@ import { registerOrUpdateElement } from "atom-utils-plus" * // ... * } */ + export default function element(cls, elementName) { - return registerOrUpdateElement(elementName, { class: cls }) -} + return registerOrUpdateElement(elementName, { + class: cls, + }) +} \ No newline at end of file diff --git a/lib/decorators/include.js b/lib/decorators/include.ts similarity index 99% rename from lib/decorators/include.js rename to lib/decorators/include.ts index 8195bdc3..a9eff56d 100644 --- a/lib/decorators/include.js +++ b/lib/decorators/include.ts @@ -1,5 +1,4 @@ "use strict" - /** * Generates a decorator function to includes many `mixto` mixins into a class. * @@ -12,6 +11,7 @@ * // ... * } */ + export default function include(cls, ...mixins) { mixins.forEach((mixin) => { includeMixin(cls, mixin) @@ -27,7 +27,6 @@ function includeMixin(target, source) { const descriptor = Object.getOwnPropertyDescriptor(source, k) Object.defineProperty(target, k, descriptor) }) - Object.getOwnPropertyNames(source.prototype).forEach((k) => { if (k === "constructor") { return @@ -36,4 +35,4 @@ function includeMixin(target, source) { const descriptor = Object.getOwnPropertyDescriptor(source.prototype, k) Object.defineProperty(target.prototype, k, descriptor) }) -} +} \ No newline at end of file diff --git a/lib/deps/underscore-plus.js b/lib/deps/underscore-plus.ts similarity index 99% rename from lib/deps/underscore-plus.js rename to lib/deps/underscore-plus.ts index 7d7f37b7..cc508fec 100644 --- a/lib/deps/underscore-plus.js +++ b/lib/deps/underscore-plus.ts @@ -6,7 +6,6 @@ export function escapeRegExp(string) { return "" } } - const regexDaherize = /([A-Z])|(_)/g export function dasherize(string) { if (!string) { @@ -22,7 +21,6 @@ export function dasherize(string) { } }) } - export function debounce(callback, wait) { let timeoutId = null return (...args) => { @@ -31,4 +29,4 @@ export function debounce(callback, wait) { callback.apply(null, args) }, wait) } -} +} \ No newline at end of file diff --git a/lib/dom-styles-reader.js b/lib/dom-styles-reader.ts similarity index 98% rename from lib/dom-styles-reader.js rename to lib/dom-styles-reader.ts index 383e93f0..e48c4269 100644 --- a/lib/dom-styles-reader.js +++ b/lib/dom-styles-reader.ts @@ -1,5 +1,4 @@ "use strict" - /** * This class is used by the `CanvasDrawer` in `MinimapElement` to * read the styles informations (color and background-color) from the DOM to use when rendering @@ -8,6 +7,7 @@ * It attaches a dummyNode to the targetNode, renders them, and finds the computed style back. * TODO: find a better way to get the token colors */ + export default class DOMStylesReader { constructor() { /** @@ -20,10 +20,8 @@ export default class DOMStylesReader { * @access private */ this.dummyNode = undefined - // used to check if the dummyNode is on the current targetNode this.targetNode = undefined - /** * Set to true once tokenized * @access private @@ -50,7 +48,9 @@ export default class DOMStylesReader { retrieveStyleFromDom(scopes: Array, property: string, targetNode: Node, getFromCache: boolean): string { if (!scopes.length) { return "" - } // no scopes + } + + // no scopes const key = scopes.join(" ") let cachedData = this.domStylesCache.get(key) @@ -58,6 +58,7 @@ export default class DOMStylesReader { if (getFromCache) { // if should get the value from the cache const value = cachedData[property] + if (value !== undefined) { // value exists return value @@ -69,21 +70,22 @@ export default class DOMStylesReader { } this.ensureDummyNodeExistence(targetNode) - let parent = this.dummyNode + for (let i = 0, len = scopes.length; i < len; i++) { const scope = scopes[i] const node = document.createElement("span") node.className = scope.replace(dotRegexp, " ") // TODO why replace is needed? + parent.appendChild(node) parent = node } const style = window.getComputedStyle(parent) let value = style.getPropertyValue(property) - // rotate hue if webkit-filter available const filter = style.getPropertyValue("-webkit-filter") + if (filter.indexOf("hue-rotate") > -1) { value = rotateHue(value, filter) } @@ -108,7 +110,6 @@ export default class DOMStylesReader { if (this.targetNode !== targetNode || this.dummyNode === undefined) { this.dummyNode = document.createElement("span") this.dummyNode.style.visibility = "hidden" - // attach to the target node targetNode.appendChild(this.dummyNode) this.targetNode = targetNode @@ -122,13 +123,13 @@ export default class DOMStylesReader { invalidateDOMStylesCache() { this.domStylesCache.clear() } - /** * Invalidates the cache only for the first tokenization event. * * @access private * unused */ + /* invalidateIfFirstTokenization () { if (this.hasTokenizedOnce) { return } @@ -136,9 +137,7 @@ export default class DOMStylesReader { this.hasTokenizedOnce = true } */ -} - -// ## ## ######## ## ######## ######## ######## ###### +} // ## ## ######## ## ######## ######## ######## ###### // ## ## ## ## ## ## ## ## ## ## ## // ## ## ## ## ## ## ## ## ## ## // ######### ###### ## ######## ###### ######## ###### @@ -162,9 +161,7 @@ const hueRegexp = /hue-rotate\((\d+)deg\)/ function rotateHue(value: string, filter: string): string { const match = value.match(rgbExtractRegexp) let [, , r, g, b, , a] = match - let [, hue] = filter.match(hueRegexp) - ;[r, g, b, a, hue] = [r, g, b, a, hue].map(Number) ;[r, g, b] = rotate(r, g, b, hue) @@ -196,7 +193,6 @@ function rotate(r: number, g: number, b: number, angle: number): Array { const hueRotateB = 0.283 const cos = Math.cos((angle * Math.PI) / 180) const sin = Math.sin((angle * Math.PI) / 180) - matrix[0] = lumR + (1 - lumR) * cos - lumR * sin matrix[1] = lumG - lumG * cos - lumG * sin matrix[2] = lumB - lumB * cos + (1 - lumB) * sin @@ -206,7 +202,6 @@ function rotate(r: number, g: number, b: number, angle: number): Array { matrix[6] = lumR - lumR * cos - (1 - lumR) * sin matrix[7] = lumG - lumG * cos + lumG * sin matrix[8] = lumB + (1 - lumB) * cos + lumB * sin - return [ clamp(matrix[0] * r + matrix[1] * g + matrix[2] * b), clamp(matrix[3] * r + matrix[4] * g + matrix[5] * b), @@ -216,4 +211,4 @@ function rotate(r: number, g: number, b: number, angle: number): Array { function clamp(num) { return Math.ceil(Math.max(0, Math.min(255, num))) } -} +} \ No newline at end of file diff --git a/lib/main.js b/lib/main.ts similarity index 99% rename from lib/main.js rename to lib/main.ts index 90339b6c..ff6b5f2b 100644 --- a/lib/main.js +++ b/lib/main.ts @@ -8,7 +8,6 @@ import * as PluginManagement from "./plugin-management" import { treeSitterWarning } from "./performance-monitor" import DOMStylesReader from "./dom-styles-reader" import { debounce } from "./deps/underscore-plus" - export { default as config } from "./config.json" export * from "./plugin-management" export { default as Minimap } from "./minimap" @@ -28,6 +27,7 @@ export { default as MinimapElement } from "./minimap-element" * @access private */ let active: boolean = false + /** * The toggle state of the package. * @@ -35,6 +35,7 @@ let active: boolean = false * @access private */ let toggled: boolean = false + /** * The `Map` where Minimap instances are stored with the text editor they * target as key. @@ -43,6 +44,7 @@ let toggled: boolean = false * @access private */ export let editorsMinimaps = null + /** * The composite disposable that stores the package's subscriptions. * @@ -50,6 +52,7 @@ export let editorsMinimaps = null * @access private */ let subscriptions: CompositeDisposable = null + /** * The disposable that stores the package's commands subscription. * @@ -93,10 +96,8 @@ export function activate() { await generatePlugin("babel") }, }) - editorsMinimaps = new Map() domStylesReader = new DOMStylesReader() - subscriptions = new CompositeDisposable() active = true @@ -114,10 +115,12 @@ export function activate() { export function minimapViewProvider(model) { if (model instanceof Minimap) { let element = model.getMinimapElement() + if (!element) { element = new MinimapElement() element.setModel(model) } + return element } } @@ -145,7 +148,6 @@ export function deactivate() { toggled = false active = false } - export function getConfigSchema() { return config || atom.packages.getLoadedPackage("minimap").metadata.configSchema } @@ -167,12 +169,13 @@ export function toggle() { }) editorsMinimaps.clear() } - subscriptions.dispose() + subscriptions.dispose() // HACK: this hack forces rerendering editor size which moves the scrollbar to the right once minimap is removed const wasMaximized = atom.isMaximized() const { width, height } = atom.getSize() atom.setSize(width, height) + if (wasMaximized) { atom.maximize() } @@ -180,6 +183,7 @@ export function toggle() { toggled = true initSubscriptions() } + domStylesReader.invalidateDOMStylesCache() } @@ -302,6 +306,7 @@ export function minimapForEditorElement(editorElement) { if (!editorElement) { return } + return minimapForEditor(editorElement.getModel()) } @@ -316,6 +321,7 @@ export function minimapForEditor(textEditor) { if (!textEditor) { return } + if (!editorsMinimaps) { return } @@ -323,17 +329,20 @@ export function minimapForEditor(textEditor) { let minimap = editorsMinimaps.get(textEditor) if (minimap === undefined || minimap.destroyed) { - minimap = new Minimap({ textEditor }) + minimap = new Minimap({ + textEditor, + }) editorsMinimaps.set(textEditor, minimap) - const editorSubscription = textEditor.onDidDestroy(() => { if (editorsMinimaps) { editorsMinimaps.delete(textEditor) } + if (minimap) { // just in case minimap.destroy() } + editorSubscription.dispose() }) // dispose the editorSubscription if minimap is deactivated before destroying the editor @@ -388,6 +397,7 @@ export function observeMinimaps(iterator) { iterator(minimap) }) } + return onDidCreateMinimap((minimap) => { iterator(minimap) }) @@ -404,11 +414,9 @@ function initSubscriptions() { atom.workspace.observeTextEditors((textEditor) => { const minimap = minimapForEditor(textEditor) const minimapElement = minimapViewProvider(minimap) - emitter.emit("did-create-minimap", minimap) minimapElement.attach(textEditor.getElement()) - }), - // empty color cache if the theme changes + }), // empty color cache if the theme changes atom.themes.onDidChangeActiveThemes(debounceUpdateStyles), atom.styles.onDidUpdateStyleElement(debounceUpdateStyles), atom.styles.onDidAddStyleElement(debounceUpdateStyles), @@ -461,4 +469,4 @@ const MinimapServiceV1 = { */ export function provideMinimapServiceV1() { return MinimapServiceV1 -} +} \ No newline at end of file diff --git a/lib/minimap-element.js b/lib/minimap-element.ts similarity index 95% rename from lib/minimap-element.js rename to lib/minimap-element.ts index 5c2f9de4..88efdb7e 100644 --- a/lib/minimap-element.js +++ b/lib/minimap-element.ts @@ -4,17 +4,15 @@ import { CompositeDisposable, Disposable } from "atom" import { EventsDelegation, AncestorsMethods } from "atom-utils-plus" import elementResizeDetectorImport from "element-resize-detector" import DecorationManagement from "./decoration-management" - import * as Main from "./main" import CanvasDrawer from "./mixins/canvas-drawer" import include from "./decorators/include" import element from "./decorators/element" - import MinimapQuickSettingsElement from "./minimap-quick-settings-element" -const elementResizeDetector = elementResizeDetectorImport({ strategy: "scroll" }) - +const elementResizeDetector = elementResizeDetectorImport({ + strategy: "scroll", +}) const SPEC_MODE = atom.inSpecMode() - /** * Public: The MinimapElement is the view meant to render a {@link Minimap} * instance in the DOM. @@ -29,6 +27,7 @@ const SPEC_MODE = atom.inSpecMode() * @example * let minimapElement = atom.views.getView(minimap) */ + class MinimapElement { static initClass() { include(this, CanvasDrawer, EventsDelegation, AncestorsMethods) @@ -60,142 +59,154 @@ class MinimapElement { * @access private */ this.width = undefined + /** * @access private */ this.height = undefined - // Subscriptions /** * @access private */ this.subscriptions = new CompositeDisposable() + /** * @access private */ this.visibleAreaSubscription = undefined + /** * @access private */ this.quickSettingsSubscription = undefined + /** * @access private */ this.dragSubscription = undefined + /** * @access private */ this.openQuickSettingSubscription = undefined - // Configs /** * @access private */ this.minimapScrollIndicator = undefined + /** * @access private */ this.displayMinimapOnLeft = undefined + /** * @access private */ this.displayPluginsControls = undefined + /** * @access private */ this.textOpacity = undefined + /** * @access private */ this.displayCodeHighlights = undefined + /** * @access private */ this.adjustToSoftWrap = undefined + /** * @access private */ this.useHardwareAcceleration = undefined + /** * @access private */ this.absoluteMode = undefined - // Elements /** * @access private */ this.visibleArea = undefined + /** * @access private */ this.controls = undefined + /** * @access private */ this.scrollIndicator = undefined + /** * @access private */ this.openQuickSettings = undefined + /** * @access private */ this.quickSettingsElement = undefined - this.DecorationManagement = new DecorationManagement() - // States /** * @access private */ this.attached = undefined + /** * @access private */ this.attachedToTextEditor = undefined + /** * @access private */ this.standAlone = undefined + /** * @access private */ this.wasVisible = undefined - // Other /** * @access private */ this.offscreenFirstRow = undefined + /** * @access private */ this.offscreenLastRow = undefined + /** * @access private */ this.frameRequested = undefined + /** * @access private */ this.flexBasis = undefined - this.initializeContent() - this.subscriptions.add( atom.config.observe("minimap.displayMinimapOnLeft", (displayMinimapOnLeft) => { this.displayMinimapOnLeft = displayMinimapOnLeft - this.updateMinimapFlexPosition() this.measureHeightAndWidth(true, true) }), - atom.config.observe("minimap.minimapScrollIndicator", (minimapScrollIndicator) => { this.minimapScrollIndicator = minimapScrollIndicator @@ -209,7 +220,6 @@ class MinimapElement { this.requestUpdate() } }), - atom.config.observe("minimap.displayPluginsControls", (displayPluginsControls) => { this.displayPluginsControls = displayPluginsControls @@ -219,7 +229,6 @@ class MinimapElement { this.disposeOpenQuickSettings() } }), - atom.config.observe("minimap.textOpacity", (textOpacity) => { this.textOpacity = textOpacity @@ -227,7 +236,6 @@ class MinimapElement { this.requestForcedUpdate() } }), - atom.config.observe("minimap.displayCodeHighlights", (displayCodeHighlights) => { this.displayCodeHighlights = displayCodeHighlights @@ -235,7 +243,6 @@ class MinimapElement { this.requestForcedUpdate() } }), - atom.config.observe("minimap.smoothScrolling", (smoothScrolling) => { this.smoothScrolling = smoothScrolling @@ -249,7 +256,6 @@ class MinimapElement { } } }), - atom.config.observe("minimap.adjustMinimapWidthToSoftWrap", (adjustToSoftWrap) => { this.adjustToSoftWrap = adjustToSoftWrap @@ -257,7 +263,6 @@ class MinimapElement { this.measureHeightAndWidth() } }), - atom.config.observe("minimap.adjustMinimapWidthOnlyIfSmaller", (adjustOnlyIfSmaller) => { this.adjustOnlyIfSmaller = adjustOnlyIfSmaller @@ -265,7 +270,6 @@ class MinimapElement { this.measureHeightAndWidth() } }), - atom.config.observe("minimap.useHardwareAcceleration", (useHardwareAcceleration) => { this.useHardwareAcceleration = useHardwareAcceleration @@ -273,23 +277,18 @@ class MinimapElement { this.requestUpdate() } }), - atom.config.observe("minimap.absoluteMode", (absoluteMode) => { this.absoluteMode = absoluteMode - this.classList.toggle("absolute", this.absoluteMode) }), - atom.config.observe("minimap.adjustAbsoluteModeHeight", (adjustAbsoluteModeHeight) => { this.adjustAbsoluteModeHeight = adjustAbsoluteModeHeight - this.classList.toggle("adjust-absolute-height", this.adjustAbsoluteModeHeight) if (this.attached) { this.measureHeightAndWidth() } }), - atom.config.observe("minimap.ignoreWhitespacesInTokens", (ignoreWhitespacesInTokens) => { this.ignoreWhitespacesInTokens = ignoreWhitespacesInTokens @@ -297,31 +296,26 @@ class MinimapElement { this.requestForcedUpdate() } }), - atom.config.observe("editor.preferredLineLength", () => { if (this.attached) { this.measureHeightAndWidth() } }), - atom.config.observe("editor.softWrap", () => { if (this.attached) { this.requestUpdate() } }), - atom.config.observe("editor.showInvisibles", () => { if (this.attached) { this.requestUpdate() } }), - atom.config.observe("editor.invisibles", () => { if (this.attached) { this.requestUpdate() } }), - atom.config.observe("editor.softWrapAtPreferredLineLength", () => { if (this.attached) { this.requestUpdate() @@ -345,12 +339,13 @@ class MinimapElement { } else { this.intersectionObserver = new IntersectionObserver((entries) => { const { intersectionRect } = entries[entries.length - 1] + if (intersectionRect.width > 0 || intersectionRect.height > 0) { this.measureHeightAndWidth(true, true) } }) - this.intersectionObserver.observe(this) + if (this.isVisible()) { this.measureHeightAndWidth(true, true) } @@ -358,9 +353,11 @@ class MinimapElement { const measureDimensions = () => { this.measureHeightAndWidth(false, false) } - elementResizeDetector.listenTo(this, measureDimensions) - window.addEventListener("resize", measureDimensions, { passive: true }) + elementResizeDetector.listenTo(this, measureDimensions) + window.addEventListener("resize", measureDimensions, { + passive: true, + }) this.subscriptions.add( new Disposable(() => { elementResizeDetector.removeListener(this, measureDimensions) @@ -429,9 +426,11 @@ class MinimapElement { 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() + try { container.removeChild(el) } catch (e) { @@ -440,6 +439,7 @@ class MinimapElement { } }) } + container.appendChild(this) } @@ -450,6 +450,7 @@ class MinimapElement { if (!this.attached || this.parentNode == null) { return } + this.parentNode.removeChild(this) } @@ -461,6 +462,7 @@ class MinimapElement { */ updateMinimapFlexPosition() { this.classList.toggle("left", this.displayMinimapOnLeft) + if (this.attachedToTextEditor) { this.minimap.getTextEditorElement().setAttribute("with-minimap", this.displayMinimapOnLeft ? "left" : "right") } @@ -471,9 +473,11 @@ class MinimapElement { */ destroy() { this.DecorationManagement.destroy() + if (this.quickSettingsElement) { this.quickSettingsElement.destroy() } + this.subscriptions.dispose() this.detach() } @@ -494,12 +498,9 @@ class MinimapElement { */ initializeContent() { this.initializeCanvas() - this.attachCanvases(this) - this.createVisibleArea() this.createControls() - this.subscriptions.add( this.subscribeTo( this, @@ -510,9 +511,10 @@ class MinimapElement { } }, }, - { passive: true } + { + passive: true, + } ), - this.subscribeTo( this.getFrontCanvas(), { @@ -523,7 +525,9 @@ class MinimapElement { this.canvasPressed(extractTouchEventData(e)) }, }, - { passive: true } + { + passive: true, + } ) ) } @@ -551,9 +555,10 @@ class MinimapElement { this.startDrag(extractTouchEventData(e)) }, }, - { passive: true } + { + passive: true, + } ) - this.subscriptions.add(this.visibleAreaSubscription) } @@ -647,7 +652,6 @@ class MinimapElement { this.openQuickSettings = document.createElement("div") this.openQuickSettings.classList.add("open-minimap-quick-settings") this.controls.appendChild(this.openQuickSettings) - this.openQuickSettingSubscription = this.subscribeTo(this.openQuickSettings, { mousedown: (e) => { e.preventDefault() @@ -662,7 +666,6 @@ class MinimapElement { this.quickSettingsSubscription = this.quickSettingsElement.onDidDestroy(() => { this.quickSettingsElement = null }) - const { top, left, right } = this.getFrontCanvas().getBoundingClientRect() this.quickSettingsElement.style.top = `${top}px` this.quickSettingsElement.attach() @@ -726,56 +729,47 @@ class MinimapElement { */ setModel(minimap: Minimap): Minimap { this.minimap = minimap - // set minimapElement for Minimap this.minimap.minimapElement = this - this.DecorationManagement.initializeDecorations(this.minimap) - this.subscriptions.add( this.minimap.onDidChangeScrollTop(() => { this.requestUpdate() }), - this.minimap.onDidChangeScrollLeft(() => { this.requestUpdate() }), - this.minimap.onDidDestroy(() => { this.destroy() }), - this.minimap.onDidChangeConfig(() => { if (this.attached) { return this.requestForcedUpdate() } }), - this.minimap.onDidChangeStandAlone(() => { this.setStandAlone(this.minimap.isStandAlone()) this.requestUpdate() }), - this.minimap.onDidChange((change) => { this.pendingChanges.push(change) this.requestUpdate() }), - this.DecorationManagement.onDidChangeDecorationRange((change) => { const { type } = change + if (type === "line" || type === "highlight-under" || type === "background-custom") { this.pendingBackDecorationChanges.push(change) } else { this.pendingFrontDecorationChanges.push(change) } + this.requestUpdate() }), - Main.onDidChangePluginOrder(() => { this.requestForcedUpdate() }) ) - this.setStandAlone(this.minimap.isStandAlone()) if (this.width != null && this.height != null) { @@ -803,9 +797,11 @@ class MinimapElement { this.removeAttribute("stand-alone") this.createVisibleArea() this.createControls() + if (this.minimapScrollIndicator) { this.initializeScrollIndicator() } + if (this.displayPluginsControls) { this.initializeOpenQuickSettings() } @@ -854,10 +850,10 @@ class MinimapElement { if (!(this.attached && this.isVisible() && this.minimap)) { return } + const minimap = this.minimap minimap.enableCache() const canvas = this.getFrontCanvas() - const devicePixelRatio = this.minimap.getDevicePixelRatio() const visibleAreaLeft = minimap.getTextEditorScaledScrollLeft() const visibleAreaTop = minimap.getTextEditorScaledScrollTop() - minimap.getScrollTop() @@ -888,31 +884,52 @@ class MinimapElement { }) } - applyStyles(this.controls, { width: `${Math.round(width)}px` }) - + applyStyles(this.controls, { + width: `${Math.round(width)}px`, + }) const canvasTop = minimap.getFirstVisibleScreenRow() * minimap.getLineHeight() - minimap.getScrollTop() if (this.smoothScrolling) { if (SPEC_MODE) { - applyStyles(this.backLayer.canvas, { top: `${canvasTop}px` }) - applyStyles(this.tokensLayer.canvas, { top: `${canvasTop}px` }) - applyStyles(this.frontLayer.canvas, { top: `${canvasTop}px` }) + applyStyles(this.backLayer.canvas, { + top: `${canvasTop}px`, + }) + applyStyles(this.tokensLayer.canvas, { + top: `${canvasTop}px`, + }) + applyStyles(this.frontLayer.canvas, { + top: `${canvasTop}px`, + }) } else { let canvasTransform = makeTranslate(0, canvasTop, this.useHardwareAcceleration) + if (devicePixelRatio !== 1) { const scale = 1 / devicePixelRatio canvasTransform += ` ${makeScale(scale, scale, this.useHardwareAcceleration)}` } - applyStyles(this.backLayer.canvas, { transform: canvasTransform }) - applyStyles(this.tokensLayer.canvas, { transform: canvasTransform }) - applyStyles(this.frontLayer.canvas, { transform: canvasTransform }) + + applyStyles(this.backLayer.canvas, { + transform: canvasTransform, + }) + applyStyles(this.tokensLayer.canvas, { + transform: canvasTransform, + }) + applyStyles(this.frontLayer.canvas, { + transform: canvasTransform, + }) } } else { const scale = 1 / devicePixelRatio const canvasTransform = makeScale(scale, scale, this.useHardwareAcceleration) - applyStyles(this.backLayer.canvas, { transform: canvasTransform }) - applyStyles(this.tokensLayer.canvas, { transform: canvasTransform }) - applyStyles(this.frontLayer.canvas, { transform: canvasTransform }) + applyStyles(this.backLayer.canvas, { + transform: canvasTransform, + }) + applyStyles(this.tokensLayer.canvas, { + transform: canvasTransform, + }) + applyStyles(this.frontLayer.canvas, { + transform: canvasTransform, + }) } if (this.minimapScrollIndicator && !this.scrollIndicator && minimap.canScroll()) { @@ -957,6 +974,7 @@ class MinimapElement { */ setDisplayCodeHighlights(displayCodeHighlights: Boolean) { this.displayCodeHighlights = displayCodeHighlights + if (this.attached) { this.requestForcedUpdate() } @@ -969,6 +987,7 @@ class MinimapElement { */ pollDOM() { const visibilityChanged = this.checkForVisibilityChange() + if (this.isVisible()) { if (!this.wasVisible) { this.requestForcedUpdate() @@ -1022,9 +1041,7 @@ class MinimapElement { const safeFlexBasis = this.style.flexBasis this.style.flexBasis = "" - const wasResized = this.width !== this.clientWidth || this.height !== this.clientHeight - this.height = this.clientHeight this.width = this.clientWidth let canvasWidth = this.width @@ -1084,6 +1101,7 @@ class MinimapElement { if (canvasWidth !== canvas.width || newHeight !== canvas.height) { this.setCanvasesSize(canvasWidth * devicePixelRatio, newHeight * devicePixelRatio) + if (this.absoluteMode && this.adjustAbsoluteModeHeight) { this.offscreenFirstRow = null this.offscreenLastRow = null @@ -1111,12 +1129,17 @@ class MinimapElement { if (this.minimap.isStandAlone()) { return } + if (isLeftMouse) { this.canvasLeftMousePressed(y) } else if (isMiddleMouse) { this.canvasMiddleMousePressed(y) const { top, height } = this.visibleArea.getBoundingClientRect() - this.startDrag({ y: top + height / 2, isLeftMouse: false, isMiddleMouse: true }) + this.startDrag({ + y: top + height / 2, + isLeftMouse: false, + isMiddleMouse: true, + }) } } @@ -1132,10 +1155,8 @@ class MinimapElement { canvasLeftMousePressed(y) { const deltaY = y - this.getBoundingClientRect().top const row = Math.floor(deltaY / this.minimap.getLineHeight()) + this.minimap.getFirstVisibleScreenRow() - const textEditor = this.minimap.getTextEditor() 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 @@ -1147,7 +1168,6 @@ class MinimapElement { if (atom.config.get("minimap.scrollAnimation")) { const duration = atom.config.get("minimap.scrollAnimationDuration") const independentScroll = this.minimap.scrollIndependentlyOnMouseWheel() - const from = this.minimap.getTextEditorScrollTop() const to = textEditorScrollTop let step @@ -1159,16 +1179,30 @@ class MinimapElement { step = (now, t) => { if (this.minimap === null) return // TODO why this happens in the tests? + this.minimap.setTextEditorScrollTop(now, true) this.minimap.setScrollTop(minimapFrom + (minimapTo - minimapFrom) * t) } - animate({ from, to, duration, step }) + + animate({ + from, + to, + duration, + step, + }) } else { step = (now) => { if (this.minimap === null) return // TODO why this happens in the tests? + this.minimap.setTextEditorScrollTop(now) } - animate({ from, to, duration, step }) + + animate({ + from, + to, + duration, + step, + }) } } else { this.minimap.setTextEditorScrollTop(textEditorScrollTop) @@ -1186,9 +1220,7 @@ class MinimapElement { canvasMiddleMousePressed(y) { const { top: offsetTop } = this.getBoundingClientRect() const deltaY = y - offsetTop - this.minimap.getTextEditorScaledHeight() / 2 - const ratio = deltaY / (this.minimap.getVisibleHeight() - this.minimap.getTextEditorScaledHeight()) - this.minimap.setTextEditorScrollTop(ratio * this.minimap.getTextEditorMaxScrollTop()) } @@ -1201,11 +1233,12 @@ class MinimapElement { */ subscribeToMediaQuery(): Disposable { const mediaQuery = window.matchMedia("screen and (-webkit-min-device-pixel-ratio: 1.5)") + const mediaListener = () => { this.requestForcedUpdate() } - mediaQuery.addEventListener("change", mediaListener) + mediaQuery.addEventListener("change", mediaListener) return new Disposable(() => { mediaQuery.removeEventListener("change", mediaListener) }) @@ -1232,6 +1265,7 @@ class MinimapElement { if (!this.minimap) { return } + if (!isLeftMouse && !isMiddleMouse) { return } @@ -1242,25 +1276,34 @@ class MinimapElement { } // TODO can we avoid adding and removing the listeners every time? - const mousemoveHandler = (e) => this.drag(extractMouseEventData(e), initial) + const dragendHandler = () => this.endDrag() const touchmoveHandler = (e) => this.drag(extractTouchEventData(e), initial) - document.body.addEventListener("mousemove", mousemoveHandler, { passive: true }) - document.body.addEventListener("mouseup", dragendHandler, { passive: true }) - document.body.addEventListener("mouseleave", dragendHandler, { passive: true }) - - document.body.addEventListener("touchmove", touchmoveHandler, { passive: true }) - document.body.addEventListener("touchend", dragendHandler, { passive: true }) - document.body.addEventListener("touchcancel", dragendHandler, { passive: true }) - + document.body.addEventListener("mousemove", mousemoveHandler, { + passive: true, + }) + document.body.addEventListener("mouseup", dragendHandler, { + passive: true, + }) + document.body.addEventListener("mouseleave", dragendHandler, { + passive: true, + }) + document.body.addEventListener("touchmove", touchmoveHandler, { + passive: true, + }) + document.body.addEventListener("touchend", dragendHandler, { + passive: true, + }) + document.body.addEventListener("touchcancel", dragendHandler, { + passive: true, + }) this.dragSubscription = new Disposable(function () { document.body.removeEventListener("mousemove", mousemoveHandler) document.body.removeEventListener("mouseup", dragendHandler) document.body.removeEventListener("mouseleave", dragendHandler) - document.body.removeEventListener("touchmove", touchmoveHandler) document.body.removeEventListener("touchend", dragendHandler) document.body.removeEventListener("touchcancel", dragendHandler) @@ -1283,13 +1326,13 @@ class MinimapElement { if (!this.minimap) { return } + if (!isLeftMouse && !isMiddleMouse) { return } - const deltaY = y - initial.offsetTop - initial.dragOffset + const deltaY = y - initial.offsetTop - initial.dragOffset const ratio = deltaY / (this.minimap.getVisibleHeight() - this.minimap.getTextEditorScaledHeight()) - this.minimap.setTextEditorScrollTop(ratio * this.minimap.getTextEditorMaxScrollTop()) } @@ -1302,14 +1345,13 @@ class MinimapElement { if (!this.minimap) { return } + this.dragSubscription.dispose() } } const minimapElement = MinimapElement.initClass() -export default minimapElement - -// ######## ## ## ######## ## ## ######## ###### +export default minimapElement // ######## ## ## ######## ## ## ######## ###### // ## ## ## ## ### ## ## ## ## // ## ## ## ## #### ## ## ## // ###### ## ## ###### ## ## ## ## ###### @@ -1327,6 +1369,7 @@ export default minimapElement * @param {MouseEvent} mouseEvent the mouse event object * @access private */ + function extractMouseEventData(mouseEvent: MouseEvent) { return { x: mouseEvent.pageX, @@ -1350,11 +1393,11 @@ function extractTouchEventData(touchEvent: TouchEvent) { // Use the first touch on the target area. Other touches will be ignored in // case of multi-touch. const touch = touchEvent.changedTouches[0] - return { x: touch.pageX, y: touch.pageY, - isLeftMouse: true, // Touch is treated like a left mouse button click + isLeftMouse: true, + // Touch is treated like a left mouse button click isMiddleMouse: false, } } @@ -1380,6 +1423,7 @@ function applyStyles(element: HTMLElement, styles: {}) { } let cssText = "" + for (const property in styles) { cssText += `${property}: ${styles[property]}; ` } @@ -1432,20 +1476,23 @@ function makeScale(x: number = 0, y: number = x, useHardwareAcceleration: boolea * @param {[type]} param.step the easing function for the animation * @access private */ -function animate({ from, to, duration, step }: { duration: [type], from: [type], step: [type], to: [type] }) { +function animate({ from, to, duration, step }: { duration: [type]; from: [type]; step: [type]; to: [type] }) { const start = getTime() let progress const update = () => { const passed = getTime() - start + if (duration === 0) { progress = 1 } else { progress = passed / duration } + if (progress > 1) { progress = 1 } + const delta = swing(progress) const value = from + (to - from) * delta step(value, delta) @@ -1472,4 +1519,4 @@ function swing(progress) { */ function getTime(): Date { return new Date() -} +} \ No newline at end of file diff --git a/lib/minimap-plugin-generator-element.js b/lib/minimap-plugin-generator-element.ts similarity index 92% rename from lib/minimap-plugin-generator-element.js rename to lib/minimap-plugin-generator-element.ts index e447fccf..e4985715 100644 --- a/lib/minimap-plugin-generator-element.js +++ b/lib/minimap-plugin-generator-element.ts @@ -5,10 +5,10 @@ import { getHomeDirectory, existsSync } from "fs-plus" import path from "path" import { BufferedProcess } from "atom" import element from "./decorators/element" - /** * @access private */ + class MinimapPluginGeneratorElement { static initClass() { this.registerCommands() @@ -20,6 +20,7 @@ class MinimapPluginGeneratorElement { "core:confirm"() { this.confirm() }, + "core:cancel"() { this.detach() }, @@ -29,27 +30,22 @@ class MinimapPluginGeneratorElement { createdCallback() { this.previouslyFocusedElement = null this.mode = null - this.modal = document.createElement("atom-panel") - this.modal.classList.add("minimap-plugin-generator") this.modal.classList.add("modal") this.modal.classList.add("overlay") this.modal.classList.add("from-top") - - this.editor = atom.workspace.buildTextEditor({ mini: true }) + this.editor = atom.workspace.buildTextEditor({ + mini: true, + }) this.editorElement = atom.views.getView(this.editor) - this.error = document.createElement("div") this.error.classList.add("error") - this.message = document.createElement("div") this.message.classList.add("message") - this.modal.appendChild(this.editorElement) this.modal.appendChild(this.error) this.modal.appendChild(this.message) - this.appendChild(this.modal) } @@ -70,12 +66,9 @@ class MinimapPluginGeneratorElement { } const packagesDirectory = getPackagesDirectory() - this.editor.setText(path.join(packagesDirectory, placeholderName)) - const pathLength = this.editor.getText().length const endOfDirectoryIndex = pathLength - placeholderName.length - this.editor.setSelectedBufferRange([ [0, endOfDirectoryIndex + rangeToSelect[0]], [0, endOfDirectoryIndex + rangeToSelect[1]], @@ -101,13 +94,13 @@ class MinimapPluginGeneratorElement { Generate plugin at ${this.getPackagePath()} ` - this.createPackageFiles(() => { const packagePath = this.getPackagePath() - atom.open({ pathsToOpen: [packagePath], devMode: atom.config.get("minimap.createPluginInDevMode") }) - + atom.open({ + pathsToOpen: [packagePath], + devMode: atom.config.get("minimap.createPluginInDevMode"), + }) this.message.innerHTML = 'Plugin successfully generated, opening it now...' - setTimeout(() => { this.detach() }, 2000) @@ -118,7 +111,6 @@ class MinimapPluginGeneratorElement { getPackagePath() { const packagePath = this.editor.getText() const packageName = dasherize(path.basename(packagePath)) - return path.join(path.dirname(packagePath), packageName) } @@ -159,18 +151,20 @@ export default minimapPluginGeneratorElement function linkPackage(packagePath, callback) { const args = ["link"] + if (atom.config.get("minimap.createPluginInDevMode")) { args.push("--dev") } - args.push(packagePath.toString()) + args.push(packagePath.toString()) runCommand(atom.packages.getApmPath(), args, callback) } function installPackage(packagePath, callback) { const args = ["install"] - - runCommand(atom.packages.getApmPath(), args, callback, { cwd: packagePath }) + runCommand(atom.packages.getApmPath(), args, callback, { + cwd: packagePath, + }) } function getPackagesDirectory() { @@ -179,15 +173,20 @@ function getPackagesDirectory() { function isStoredInDotAtom(packagePath) { const packagesPath = path.join(atom.getConfigDirPath(), "packages", path.sep) + if (packagePath.indexOf(packagesPath) === 0) { return true } const devPackagesPath = path.join(atom.getConfigDirPath(), "dev", "packages", path.sep) - return packagePath.indexOf(devPackagesPath) === 0 } function runCommand(command, args, exit, options = {}) { - return new BufferedProcess({ command, args, exit, options }) -} + return new BufferedProcess({ + command, + args, + exit, + options, + }) +} \ No newline at end of file diff --git a/lib/minimap-quick-settings-element.js b/lib/minimap-quick-settings-element.ts similarity index 80% rename from lib/minimap-quick-settings-element.js rename to lib/minimap-quick-settings-element.ts index bca721d6..6ed61253 100644 --- a/lib/minimap-quick-settings-element.js +++ b/lib/minimap-quick-settings-element.ts @@ -2,14 +2,13 @@ import { CompositeDisposable, Emitter } from "atom" import { EventsDelegation, SpacePenDSL } from "atom-utils-plus" - import * as Main from "./main" import element from "./decorators/element" import include from "./decorators/include" - /** * @access private */ + class MinimapQuickSettingsElement { static initClass() { include(this, EventsDelegation, SpacePenDSL.Babel) @@ -17,22 +16,72 @@ class MinimapQuickSettingsElement { } static content() { - this.div({ class: "select-list popover-list minimap-quick-settings" }, () => { - this.input({ type: "text", class: "hidden-input", outlet: "hiddenInput" }) - this.ol({ class: "list-group mark-active", outlet: "list" }, () => { - this.li({ class: "separator", outlet: "separator" }) - this.li({ class: "code-highlights", outlet: "codeHighlights" }, "code-highlights") - this.li({ class: "absolute-mode", outlet: "absoluteMode" }, "absolute-mode") - this.li( - { class: "adjust-absolute-mode-height", outlet: "adjustAbsoluteModeHeight" }, - "adjust-absolute-mode-height" + this.div( + { + class: "select-list popover-list minimap-quick-settings", + }, + () => { + this.input({ + type: "text", + class: "hidden-input", + outlet: "hiddenInput", + }) + this.ol( + { + class: "list-group mark-active", + outlet: "list", + }, + () => { + this.li({ + class: "separator", + outlet: "separator", + }) + this.li( + { + class: "code-highlights", + outlet: "codeHighlights", + }, + "code-highlights" + ) + this.li( + { + class: "absolute-mode", + outlet: "absoluteMode", + }, + "absolute-mode" + ) + this.li( + { + class: "adjust-absolute-mode-height", + outlet: "adjustAbsoluteModeHeight", + }, + "adjust-absolute-mode-height" + ) + } ) - }) - this.div({ class: "btn-group" }, () => { - this.button({ class: "btn btn-default", outlet: "onLeftButton" }, "On Left") - this.button({ class: "btn btn-default", outlet: "onRightButton" }, "On Right") - }) - }) + this.div( + { + class: "btn-group", + }, + () => { + this.button( + { + class: "btn btn-default", + outlet: "onLeftButton", + }, + "On Left" + ) + this.button( + { + class: "btn btn-default", + outlet: "onRightButton", + }, + "On Right" + ) + } + ) + } + ) } createdCallback() { @@ -46,21 +95,16 @@ class MinimapQuickSettingsElement { this.subscriptions = new CompositeDisposable() this.plugins = {} this.itemsActions = new WeakMap() - this.codeHighlights.classList.toggle("active", this.minimap.displayCodeHighlights) - this.itemsActions.set(this.codeHighlights, () => { atom.config.set("minimap.displayCodeHighlights", !this.minimap.displayCodeHighlights) }) - this.itemsActions.set(this.absoluteMode, () => { atom.config.set("minimap.absoluteMode", !atom.config.get("minimap.absoluteMode")) }) - this.itemsActions.set(this.adjustAbsoluteModeHeight, () => { atom.config.set("minimap.adjustAbsoluteModeHeight", !atom.config.get("minimap.adjustAbsoluteModeHeight")) }) - this.subscriptions.add( Main.onDidAddPlugin(({ name, plugin }) => { return this.addItemFor(name, plugin) @@ -74,7 +118,6 @@ class MinimapQuickSettingsElement { Main.onDidDeactivatePlugin(({ name, plugin }) => { return this.deactivateItem(name, plugin) }), - atom.commands.add("minimap-quick-settings", { "core:move-up": () => { this.selectPreviousItem() @@ -95,28 +138,24 @@ class MinimapQuickSettingsElement { this.toggleSelectedItem() }, }), - this.subscribeTo(this.codeHighlights, { mousedown: (e) => { e.preventDefault() atom.config.set("minimap.displayCodeHighlights", !this.minimap.displayCodeHighlights) }, }), - this.subscribeTo(this.absoluteMode, { mousedown: (e) => { e.preventDefault() atom.config.set("minimap.absoluteMode", !atom.config.get("minimap.absoluteMode")) }, }), - this.subscribeTo(this.adjustAbsoluteModeHeight, { mousedown: (e) => { e.preventDefault() atom.config.set("minimap.adjustAbsoluteModeHeight", !atom.config.get("minimap.adjustAbsoluteModeHeight")) }, }), - this.subscribeTo( this.hiddenInput, { @@ -124,41 +163,36 @@ class MinimapQuickSettingsElement { this.destroy() }, }, - { passive: true } + { + passive: true, + } ), - this.subscribeTo(this.onLeftButton, { mousedown: (e) => { e.preventDefault() atom.config.set("minimap.displayMinimapOnLeft", true) }, }), - this.subscribeTo(this.onRightButton, { mousedown: (e) => { e.preventDefault() atom.config.set("minimap.displayMinimapOnLeft", false) }, }), - atom.config.observe("minimap.displayCodeHighlights", (bool) => { this.codeHighlights.classList.toggle("active", bool) }), - atom.config.observe("minimap.absoluteMode", (bool) => { this.absoluteMode.classList.toggle("active", bool) }), - atom.config.observe("minimap.adjustAbsoluteModeHeight", (bool) => { this.adjustAbsoluteModeHeight.classList.toggle("active", bool) }), - atom.config.observe("minimap.displayMinimapOnLeft", (bool) => { this.onLeftButton.classList.toggle("selected", bool) this.onRightButton.classList.toggle("selected", !bool) }) ) - this.initList() } @@ -180,6 +214,7 @@ class MinimapQuickSettingsElement { initList() { this.itemsDisposables = new WeakMap() + for (const name in Main.plugins) { this.addItemFor(name, Main.plugins[name]) } @@ -187,6 +222,7 @@ class MinimapQuickSettingsElement { toggleSelectedItem() { const fn = this.itemsActions.get(this.selectedItem) + if (typeof fn === "function") { fn() } @@ -194,32 +230,39 @@ class MinimapQuickSettingsElement { selectNextItem() { this.selectedItem.classList.remove("selected") + if (this.selectedItem.nextSibling != null) { this.selectedItem = this.selectedItem.nextSibling + if (this.selectedItem.matches(".separator")) { this.selectedItem = this.selectedItem.nextSibling } } else { this.selectedItem = this.list.firstChild } + this.selectedItem.classList.add("selected") } selectPreviousItem() { this.selectedItem.classList.remove("selected") + if (this.selectedItem.previousSibling != null) { this.selectedItem = this.selectedItem.previousSibling + if (this.selectedItem.matches(".separator")) { this.selectedItem = this.selectedItem.previousSibling } } else { this.selectedItem = this.list.lastChild } + this.selectedItem.classList.add("selected") } addItemFor(name, plugin) { const item = document.createElement("li") + const action = () => { Main.togglePluginActivation(name) } @@ -229,7 +272,6 @@ class MinimapQuickSettingsElement { } item.textContent = name - this.itemsActions.set(item, action) this.itemsDisposables.set( item, @@ -238,7 +280,6 @@ class MinimapQuickSettingsElement { action() }) ) - this.plugins[name] = item this.list.insertBefore(item, this.separator) @@ -266,4 +307,4 @@ class MinimapQuickSettingsElement { } const minimapQuickSettingsElement = MinimapQuickSettingsElement.initClass() -export default minimapQuickSettingsElement +export default minimapQuickSettingsElement \ No newline at end of file diff --git a/lib/minimap.js b/lib/minimap.ts similarity index 97% rename from lib/minimap.js rename to lib/minimap.ts index bf7795e2..1a7e7a39 100644 --- a/lib/minimap.js +++ b/lib/minimap.ts @@ -3,9 +3,7 @@ import { Emitter, CompositeDisposable } from "atom" import StableAdapter from "./adapters/stable-adapter" import { editorsMinimaps } from "./main" - let nextModelId = 1 - /** * The Minimap class is the underlying model of a . * Most manipulations of the minimap is done through the model. @@ -14,6 +12,7 @@ let nextModelId = 1 * Their lifecycle follow the one of their target `TextEditor`, so they are * destroyed whenever their `TextEditor` is destroyed. */ + export default class Minimap { /** * Creates a new Minimap instance for the given `TextEditor`. @@ -27,7 +26,14 @@ export default class Minimap { * @param {number} [options.height] the minimap height in pixels * @throws {Error} Cannot create a minimap without an editor */ - constructor(options: { height: number, standAlone: boolean, textEditor: TextEditor, width: number } = {}) { + constructor( + options: { + height: number + standAlone: boolean + textEditor: TextEditor + width: number + } = {} + ) { if (!options.textEditor) { throw new Error("Cannot create a minimap without an editor") } @@ -39,7 +45,6 @@ export default class Minimap { * @access private */ this.minimapElement = undefined - // local cache of this.minimapElement.DecorationManagement this.DecorationManagement = undefined @@ -64,6 +69,7 @@ export default class Minimap { * @access private */ this.standAlone = options.standAlone + /** * The width of the current Minimap. * @@ -71,6 +77,7 @@ export default class Minimap { * @access private */ this.width = options.width + /** * The height of the current Minimap. * @@ -78,6 +85,7 @@ export default class Minimap { * @access private */ this.height = options.height + /** * The id of the current Minimap. * @@ -85,6 +93,7 @@ export default class Minimap { * @access private */ this.id = nextModelId++ + /** * The events emitter of the current Minimap. * @@ -92,6 +101,7 @@ export default class Minimap { * @access private */ this.emitter = new Emitter() + /** * The Minimap's subscriptions. * @@ -99,6 +109,7 @@ export default class Minimap { * @access private */ this.subscriptions = new CompositeDisposable() + /** * The adapter object leverage the access to several properties from * the `TextEditor`/`TextEditorElement` to support the different APIs @@ -108,6 +119,7 @@ export default class Minimap { * @access private */ this.adapter = null + /** * The char height of the current Minimap, will be `undefined` unless * `setCharWidth` is called. @@ -116,6 +128,7 @@ export default class Minimap { * @access private */ this.charHeight = null + /** * The char height from the package's configuration. Will be overriden * by the instance value. @@ -124,6 +137,7 @@ export default class Minimap { * @access private */ this.configCharHeight = null + /** * The char width of the current Minimap, will be `undefined` unless * `setCharWidth` is called. @@ -132,6 +146,7 @@ export default class Minimap { * @access private */ this.charWidth = null + /** * The char width from the package's configuration. Will be overriden * by the instance value. @@ -140,6 +155,7 @@ export default class Minimap { * @access private */ this.configCharWidth = null + /** * The interline of the current Minimap, will be `undefined` unless * `setCharWidth` is called. @@ -148,6 +164,7 @@ export default class Minimap { * @access private */ this.interline = null + /** * The interline from the package's configuration. Will be overriden * by the instance value. @@ -156,6 +173,7 @@ export default class Minimap { * @access private */ this.configInterline = null + /** * The devicePixelRatioRounding of the current Minimap, will be * `undefined` unless `setDevicePixelRatioRounding` is called. @@ -164,6 +182,7 @@ export default class Minimap { * @access private */ this.devicePixelRatioRounding = null + /** * The devicePixelRatioRounding from the package's configuration. * Will be overriden by the instance value. @@ -172,6 +191,7 @@ export default class Minimap { * @access private */ this.configDevicePixelRatioRounding = null + /** * A number of milliseconds which determines how often the minimap should redraw itself after * detecting changes in the text editor. A value of 0 will cause the minimap to redraw @@ -181,6 +201,7 @@ export default class Minimap { * @access private */ this.redrawDelay = 0 + /** * A boolean value to store whether this Minimap have been destroyed or not. * @@ -188,6 +209,7 @@ export default class Minimap { * @access private */ this.destroyed = false + /** * A boolean value to store whether the `scrollPastEnd` setting is enabled * or not. @@ -221,7 +243,9 @@ export default class Minimap { atom.notifications.addWarning( "LegacyAdapter of Minimap is deprecated and will be removed in the next major version. Please upgrade Atom to the latest version." ) + const LegacyAdapter = require("./adapters/legacy-adapter") + this.adapter = new LegacyAdapter(this.textEditor) } @@ -233,20 +257,15 @@ export default class Minimap { * @access private */ this.scrollTop = 0 - let configSubscription = this.subscribeToConfig() - this.subscriptions.add( configSubscription, - this.textEditor.onDidChangeGrammar(() => { this.subscriptions.remove(configSubscription) configSubscription.dispose() - configSubscription = this.subscribeToConfig() this.subscriptions.add(configSubscription) }), - this.adapter.onDidChangeScrollTop(() => { if (!this.standAlone && !this.ignoreTextEditorScroll && !this.inChangeScrollTop) { this.inChangeScrollTop = true @@ -259,31 +278,28 @@ export default class Minimap { this.ignoreTextEditorScroll = false } }), - this.adapter.onDidChangeScrollLeft(() => { if (!this.standAlone) { this.emitter.emit("did-change-scroll-left", this) } }), - this.textEditor.onDidChange((changes) => { this.scheduleChanges(changes) }), - this.textEditor.onDidDestroy(() => { if (editorsMinimaps) { editorsMinimaps.delete(this.textEditor) } + this.destroy() }), - /* - FIXME Some changes occuring during the tokenization produces - ranges that deceive the canvas rendering by making some - lines at the end of the buffer intact while they are in fact not, - resulting in extra lines appearing at the end of the minimap. - Forcing a whole repaint to fix that bug is suboptimal but works. - */ + FIXME Some changes occuring during the tokenization produces + ranges that deceive the canvas rendering by making some + lines at the end of the buffer intact while they are in fact not, + resulting in extra lines appearing at the end of the minimap. + Forcing a whole repaint to fix that bug is suboptimal but works. + */ this.textEditor.onDidTokenize(() => { this.emitter.emit("did-change-config") }) @@ -362,6 +378,7 @@ export default class Minimap { if (!this.requestedFlushChanges) { this.requestedFlushChanges = requestAnimationFrame(() => { this.flushChanges() + if (this.requestedFlushChanges) { cancelAnimationFrame(this.requestedFlushChanges) this.requestedFlushChanges = null @@ -384,7 +401,7 @@ export default class Minimap { * after the change * @return {Disposable} a disposable to stop listening to the event */ - onDidChange(callback: (event: Object) => void): Disposable { + onDidChange(callback: (event: Record) => void): Disposable { return this.emitter.on("did-change", callback) } @@ -471,54 +488,46 @@ export default class Minimap { */ subscribeToConfig(): Disposable { const subs = new CompositeDisposable() - const opts = { scope: this.textEditor.getRootScopeDescriptor() } - + const opts = { + scope: this.textEditor.getRootScopeDescriptor(), + } subs.add( atom.config.observe("editor.scrollPastEnd", opts, (scrollPastEnd) => { this.scrollPastEnd = scrollPastEnd this.adapter.scrollPastEnd = this.scrollPastEnd this.emitter.emit("did-change-config") }), - atom.config.observe("minimap.charHeight", opts, (configCharHeight) => { this.configCharHeight = configCharHeight this.updateScrollTop() this.emitter.emit("did-change-config") }), - atom.config.observe("minimap.charWidth", opts, (configCharWidth) => { this.configCharWidth = configCharWidth this.updateScrollTop() this.emitter.emit("did-change-config") }), - atom.config.observe("minimap.interline", opts, (configInterline) => { this.configInterline = configInterline this.updateScrollTop() this.emitter.emit("did-change-config") }), - atom.config.observe("minimap.independentMinimapScroll", opts, (independentMinimapScroll) => { this.independentMinimapScroll = independentMinimapScroll this.updateScrollTop() }), - atom.config.observe("minimap.scrollSensitivity", opts, (scrollSensitivity) => { this.scrollSensitivity = scrollSensitivity }), - atom.config.observe("minimap.redrawDelay", opts, (redrawDelay) => { this.redrawDelay = redrawDelay - }), - // cdprr is shorthand for configDevicePixelRatioRounding - + }), // cdprr is shorthand for configDevicePixelRatioRounding atom.config.observe("minimap.devicePixelRatioRounding", opts, (cdprr) => { this.configDevicePixelRatioRounding = cdprr this.updateScrollTop() this.emitter.emit("did-change-config") }) ) - return subs } @@ -988,6 +997,7 @@ export default class Minimap { try { this.setScrollTop(this.getScrollTopFromEditor()) } catch (err) {} + this.emitter.emit("did-change-scroll-top", this) } } @@ -1037,7 +1047,6 @@ export default class Minimap { const { wheelDeltaY } = event const previousScrollTop = this.getScrollTop() const updatedScrollTop = previousScrollTop - Math.round(wheelDeltaY * this.scrollSensitivity) - event.preventDefault() this.setScrollTop(updatedScrollTop) } @@ -1125,6 +1134,7 @@ export default class Minimap { `) } } + return this.DecorationManagement } @@ -1132,37 +1142,48 @@ export default class Minimap { getDecorations() { return this.getDecorationManagement().getDecorations() } + onDidAddDecoration(...args) { return this.getDecorationManagement().onDidAddDecoration(...args) } + onDidRemoveDecoration(...args) { return this.getDecorationManagement().onDidRemoveDecoration(...args) } + onDidChangeDecorationRange(...args) { return this.getDecorationManagement().onDidChangeDecorationRange(...args) } + onDidUpdateDecoration(...args) { return this.getDecorationManagement().onDidUpdateDecoration(...args) } + decorationForId(...args) { return this.getDecorationManagement().decorationForId(...args) } + decorationsForScreenRowRange(...args) { return this.getDecorationManagement().decorationsForScreenRowRange(...args) } + decorationsByTypeThenRows() { return this.getDecorationManagement().decorationsByTypeThenRows() } + decorateMarker(...args) { return this.getDecorationManagement().decorateMarker(...args) } + removeDecoration(...args) { return this.getDecorationManagement().removeDecoration(...args) } + removeAllDecorationsForMarker(...args) { return this.getDecorationManagement().removeAllDecorationsForMarker(...args) } + removeAllDecorations() { return this.getDecorationManagement().removeAllDecorations() } -} +} \ No newline at end of file diff --git a/lib/mixins/canvas-drawer.js b/lib/mixins/canvas-drawer.ts similarity index 98% rename from lib/mixins/canvas-drawer.js rename to lib/mixins/canvas-drawer.ts index 4664fcb2..07c98fdf 100644 --- a/lib/mixins/canvas-drawer.js +++ b/lib/mixins/canvas-drawer.ts @@ -2,15 +2,12 @@ import { escapeRegExp } from "../deps/underscore-plus" import Mixin from "mixto" - import * as Main from "../main" import { domStylesReader } from "../main" import CanvasLayer from "../canvas-layer" - const SPEC_MODE = atom.inSpecMode() // an instance of MinimapElement used for testing and calling spies let thisSpec - /** * The `CanvasDrawer` mixin is responsible for the rendering of a `Minimap` * in a `canvas` element. @@ -18,6 +15,7 @@ let thisSpec * This mixin is injected in the `MinimapElement` prototype, so all these * methods are available on any `MinimapElement` instance. */ + export default class CanvasDrawer extends Mixin { /** * Initializes the canvas elements needed to perform the `Minimap` rendering. @@ -26,8 +24,12 @@ export default class CanvasDrawer extends Mixin { if (SPEC_MODE) { // class methods only used for spying the calls this.drawLines = (firstLine, lastLine) => { - console.log({ firstLine, lastLine }) + console.log({ + firstLine, + lastLine, + }) } + this.drawLineDecoration = drawLineDecoration this.drawGutterDecoration = drawGutterDecoration this.drawHighlightDecoration = drawHighlightDecoration @@ -42,11 +44,13 @@ export default class CanvasDrawer extends Mixin { * @type {CanvasLayer} */ this.tokensLayer = new CanvasLayer() + /** * The canvas layer for decorations below the text. * @type {CanvasLayer} */ this.backLayer = new CanvasLayer() + /** * The canvas layer for decorations above the text. * @type {CanvasLayer} @@ -125,7 +129,6 @@ export default class CanvasDrawer extends Mixin { updateCanvas() { const firstRow = this.minimap.getFirstVisibleScreenRow() const lastRow = this.minimap.getLastVisibleScreenRow() - const devicePixelRatio = this.minimap.getDevicePixelRatio() const lineHeight = this.minimap.getLineHeight() * devicePixelRatio const charHeight = this.minimap.getCharHeight() * devicePixelRatio @@ -133,12 +136,10 @@ export default class CanvasDrawer extends Mixin { const { width: canvasWidth, height: canvasHeight } = this.tokensLayer.getSize() const editor = this.minimap.getTextEditor() const editorElement = this.minimap.getTextEditorElement() - // TODO avoid closure: https://stackoverflow.com/a/46256398/7910299 const getTokenColorClosure = this.displayCodeHighlights ? (scopes) => getTokenColor(scopes, editorElement, this.textOpacity) : () => getDefaultColor(editorElement, this.textOpacity) - updateTokensLayer( this.tokensLayer, firstRow, @@ -164,7 +165,6 @@ export default class CanvasDrawer extends Mixin { } const decorations = this.DecorationManagement.decorationsByTypeThenRows(firstRow, lastRow) - const renderData = { context: this.backLayer.context, canvasWidth, @@ -177,9 +177,9 @@ export default class CanvasDrawer extends Mixin { const drawCustomDecorationLambda = (decoration, data, decorationColor) => drawCustomDecoration(decoration, data, decorationColor, editorElement) + backgroundDecorationDispatcher["background-custom"] = drawCustomDecorationLambda frontDecorationDispatcher["foreground-custom"] = drawCustomDecorationLambda - updateBackDecorationsLayer( this.backLayer, firstRow, @@ -192,9 +192,7 @@ export default class CanvasDrawer extends Mixin { editorElement, decorations ) - renderData.context = this.frontLayer.context - updateFrontDecorationsLayer( this.frontLayer, firstRow, @@ -207,7 +205,6 @@ export default class CanvasDrawer extends Mixin { editorElement, decorations ) - this.pendingChanges = [] this.pendingBackDecorationChanges = [] this.pendingFrontDecorationChanges = [] @@ -218,15 +215,14 @@ export default class CanvasDrawer extends Mixin { * @access private */ this.offscreenFirstRow = firstRow + /** * The last row in the last render of the offscreen canvas. * @type {number} * @access private */ this.offscreenLastRow = lastRow - } - - // ######## ######## ### ## ## + } // ######## ######## ### ## ## // ## ## ## ## ## ## ## ## ## // ## ## ## ## ## ## ## ## ## // ## ## ######## ## ## ## ## ## @@ -294,9 +290,7 @@ export default class CanvasDrawer extends Mixin { // method.call(this, currentRow, lastRow, currentRow - firstRow) // } // } -} - -// ######## ######## ### ## ## +} // ######## ######## ### ## ## // ## ## ## ## ## ## ## ## ## // ## ## ## ## ## ## ## ## ## // ## ## ######## ## ## ## ## ## @@ -323,6 +317,7 @@ export default class CanvasDrawer extends Mixin { * @param {number} maxTokensInOneLine this.maxTokensInOneLine * @access private */ + function updateTokensLayer( tokensLayer: CanvasLayer, firstRow: number, @@ -342,12 +337,9 @@ function updateTokensLayer( maxTokensInOneLine: number ) { // NOTE: this method is the hot function of Minimap. Do not refactor. The code is inlined delibarately. - const intactRanges = computeIntactRanges(firstRow, lastRow, pendingChanges, offscreenFirstRow, offscreenLastRow) - // redrawRangesOnLayer const context = tokensLayer.context - tokensLayer.clearCanvas() if (intactRanges.length === 0) { @@ -370,18 +362,18 @@ function updateTokensLayer( } else { for (let j = 0, len = intactRanges.length; j < len; j++) { const intact = intactRanges[j] - tokensLayer.copyPartFromOffscreen( intact.offscreenRow * lineHeight, (intact.start - firstRow) * lineHeight, (intact.end - intact.start) * lineHeight ) } + // drawLinesForRanges let currentRow = firstRow + for (let i = 0, len = intactRanges.length; i < len; i++) { const range = intactRanges[i] - drawLines( currentRow, range.start, @@ -398,9 +390,9 @@ function updateTokensLayer( ignoreWhitespacesInTokens, maxTokensInOneLine ) - currentRow = range.end } + if (currentRow <= lastRow) { drawLines( currentRow, @@ -448,7 +440,7 @@ function updateBackDecorationsLayer( offscreenFirstRow: number, offscreenLastRow: number, pendingBackDecorationChanges: Array<>, - renderData: Object, + renderData: Record, lineHeight: number, editorElement: TextEditorElement, decorations: Array @@ -460,11 +452,8 @@ function updateBackDecorationsLayer( offscreenFirstRow, offscreenLastRow ) - // NOTE: this method is the hot function of Minimap. Do not refactor. The code is inlined delibarately. - // redrawRangesOnLayer - backLayer.clearCanvas() if (intactRanges.length === 0) { @@ -472,18 +461,18 @@ function updateBackDecorationsLayer( } else { for (let j = 0, len = intactRanges.length; j < len; j++) { const intact = intactRanges[j] - backLayer.copyPartFromOffscreen( intact.offscreenRow * lineHeight, (intact.start - firstRow) * lineHeight, (intact.end - intact.start) * lineHeight ) } + // drawLinesForRanges let currentRow = firstRow + for (let i = 0, len = intactRanges.length; i < len; i++) { const range = intactRanges[i] - drawBackDecorationsForLines( currentRow, range.start, @@ -493,9 +482,9 @@ function updateBackDecorationsLayer( editorElement, decorations ) - currentRow = range.end } + if (currentRow <= lastRow) { drawBackDecorationsForLines( currentRow, @@ -537,7 +526,7 @@ function updateFrontDecorationsLayer( offscreenFirstRow: number, offscreenLastRow: number, pendingFrontDecorationChanges: Array<>, - renderData: Object, + renderData: Record, lineHeight: number, editorElement: TextEditorElement, decorations: Array @@ -549,11 +538,8 @@ function updateFrontDecorationsLayer( offscreenFirstRow, offscreenLastRow ) - // NOTE: this method is the hot function of Minimap. Do not refactor. The code is inlined delibarately. - // redrawRangesOnLayer - frontLayer.clearCanvas() if (intactRanges.length === 0) { @@ -561,18 +547,18 @@ function updateFrontDecorationsLayer( } else { for (let j = 0, len = intactRanges.length; j < len; j++) { const intact = intactRanges[j] - frontLayer.copyPartFromOffscreen( intact.offscreenRow * lineHeight, (intact.start - firstRow) * lineHeight, (intact.end - intact.start) * lineHeight ) } + // drawLinesForRanges let currentRow = firstRow + for (let i = 0, len = intactRanges.length; i < len; i++) { const range = intactRanges[i] - drawFrontDecorationsForLines( currentRow, range.start, @@ -582,9 +568,9 @@ function updateFrontDecorationsLayer( editorElement, decorations ) - currentRow = range.end } + if (currentRow <= lastRow) { drawFrontDecorationsForLines( currentRow, @@ -633,25 +619,30 @@ function drawToken( 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 (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 } } @@ -696,14 +687,12 @@ function drawLines( maxTokensInOneLine: number ) { // NOTE: this method is the hot function of Minimap. Do not refactor. The code is inlined delibarately. - if (firstRow > lastRow) { return } let lastLine, x let y = offsetRow * lineHeight - lineHeight - // eachTokenForScreenRows lastRow = Math.min(lastRow, editorScreenLineCount) @@ -711,6 +700,7 @@ function drawLines( const editorTokensForScreenRow = editor.tokensForScreenRow(line) const numToken = editorTokensForScreenRow.length const numTokenToRender = Math.min(numToken, maxTokensInOneLine) + for (let iToken = 0; iToken < numTokenToRender; iToken++) { const token = editorTokensForScreenRow[iToken] const tokenText = token.text.replace(invisibleRegExp, " ") @@ -722,6 +712,7 @@ function drawLines( lastLine = line context.clearRect(x, y, canvasWidth, lineHeight) } + if (x > canvasWidth) { continue } @@ -756,15 +747,19 @@ function drawLines( function getInvisibleRegExp(editor: TextEditor): RegExp { 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) } @@ -815,7 +810,6 @@ const frontDecorationDispatcher = { */ function drawLineDecoration(decoration: Decoration, data: {}, decorationColor: string) { const { context, lineHeight, canvasWidth, yRow } = data - context.fillStyle = decorationColor context.fillRect(0, yRow, canvasWidth, lineHeight) } @@ -830,7 +824,6 @@ function drawLineDecoration(decoration: Decoration, data: {}, decorationColor: s */ function drawGutterDecoration(decoration: Decoration, data: {}, decorationColor: string) { const { context, lineHeight, yRow } = data - context.fillStyle = decorationColor context.fillRect(0, yRow, 1, lineHeight) } @@ -848,10 +841,8 @@ function drawGutterDecoration(decoration: Decoration, data: {}, decorationColor: */ function drawHighlightDecoration(decoration: Decoration, data: {}, decorationColor: string) { const { context, lineHeight, charWidth, canvasWidth, screenRow, yRow } = data - const range = decoration.getMarker().getScreenRange() const rowSpan = range.end.row - range.start.row - context.fillStyle = decorationColor if (rowSpan === 0) { @@ -880,13 +871,11 @@ function drawHighlightDecoration(decoration: Decoration, data: {}, decorationCol */ function drawHighlightOutlineDecoration(decoration: Decoration, data: {}, decorationColor: string) { const { context, lineHeight, charWidth, canvasWidth, screenRow, yRow } = data - let bottomWidth, colSpan, width, xBottomStart, xEnd, xStart const range = decoration.getMarker().getScreenRange() const rowSpan = range.end.row - range.start.row const yStart = yRow const yEnd = yStart + lineHeight - context.fillStyle = decorationColor if (rowSpan === 0) { @@ -894,7 +883,6 @@ function drawHighlightOutlineDecoration(decoration: Decoration, data: {}, decora width = colSpan * charWidth xStart = range.start.column * charWidth xEnd = xStart + width - context.fillRect(xStart, yStart, width, 1) context.fillRect(xStart, yEnd - 1, width, 1) context.fillRect(xStart, yStart, 1, lineHeight) @@ -907,7 +895,6 @@ function drawHighlightOutlineDecoration(decoration: Decoration, data: {}, decora width = canvasWidth - xStart xBottomStart = Math.max(xStart, xEnd) bottomWidth = canvasWidth - xBottomStart - context.fillRect(xStart, yStart, width, 1) context.fillRect(xBottomStart, yEnd - 1, bottomWidth, 1) context.fillRect(xStart, yStart, 1, lineHeight) @@ -915,7 +902,6 @@ function drawHighlightOutlineDecoration(decoration: Decoration, data: {}, decora } else { width = canvasWidth - xStart bottomWidth = canvasWidth - xEnd - context.fillRect(0, yStart, xStart, 1) context.fillRect(0, yEnd - 1, xEnd, 1) context.fillRect(0, yStart, 1, lineHeight) @@ -924,24 +910,25 @@ function drawHighlightOutlineDecoration(decoration: Decoration, data: {}, decora } else { xStart = range.start.column * charWidth xEnd = range.end.column * charWidth + if (screenRow === range.start.row) { width = canvasWidth - xStart - context.fillRect(xStart, yStart, width, 1) context.fillRect(xStart, yStart, 1, lineHeight) context.fillRect(canvasWidth - 1, yStart, 1, lineHeight) } else if (screenRow === range.end.row) { width = canvasWidth - xStart - context.fillRect(0, yEnd - 1, xEnd, 1) context.fillRect(0, yStart, 1, lineHeight) context.fillRect(xEnd, yStart, 1, lineHeight) } else { context.fillRect(0, yStart, 1, lineHeight) context.fillRect(canvasWidth - 1, yStart, 1, lineHeight) + if (screenRow === range.start.row + 1) { context.fillRect(0, yStart, xStart, 1) } + if (screenRow === range.end.row - 1) { context.fillRect(xEnd, yEnd - 1, canvasWidth - xEnd, 1) } @@ -998,7 +985,6 @@ function drawDecorations( editorElement: TextEditorElement ) { let decorationsToRender = [] - renderData.context.clearRect(0, renderData.yRow, renderData.canvasWidth, renderData.lineHeight) for (const i in types) { @@ -1013,8 +999,14 @@ function drawDecorations( for (let i = 0, len = decorationsToRender.length; i < len; i++) { const decoration = decorationsToRender[i] const decorationDrawer = types[decoration.properties.type] + if (!SPEC_MODE) { - decorationDrawer(decoration, renderData, /* decorationColor */ getDecorationColor(decoration, editorElement)) + decorationDrawer( + decoration, + renderData, + /* decorationColor */ + getDecorationColor(decoration, editorElement) + ) } else { // get the real function name from the mangeld Parcel names const functionName = decorationDrawer.name.split("$").pop().replace("Lambda", "") @@ -1022,7 +1014,8 @@ function drawDecorations( thisSpec[functionName]( decoration, renderData, - /* decorationColor */ getDecorationColor(decoration, editorElement) + /* decorationColor */ + getDecorationColor(decoration, editorElement) ) } } @@ -1063,7 +1056,6 @@ function drawFrontDecorationsForLines( renderData.row = offsetRow + (screenRow - firstRow) renderData.yRow = renderData.row * lineHeight renderData.screenRow = screenRow - drawDecorations(screenRow, decorations, renderData, frontDecorationDispatcher, editorElement) } @@ -1104,7 +1096,6 @@ function drawBackDecorationsForLines( renderData.row = offsetRow + (screenRow - firstRow) renderData.yRow = renderData.row * lineHeight renderData.screenRow = screenRow - drawDecorations(screenRow, decorations, renderData, backgroundDecorationDispatcher, editorElement) } @@ -1153,7 +1144,6 @@ function getDefaultColor(editorElement: TextEditorElement, textOpacity: number): */ function getTokenColor(scopes: Array, editorElement: TextEditorElement, textOpacity: number): string { const color = domStylesReader.retrieveStyleFromDom(scopes, "color", editorElement, true) - return transparentize(color, textOpacity) } @@ -1184,6 +1174,7 @@ function transparentize(color: string, opacity: number): string { */ function getDecorationColor(decoration: Decoration, editorElement: TextEditorElement): string { const properties = decoration.getProperties() + if (properties.color) { return properties.color } @@ -1264,6 +1255,7 @@ function computeIntactRanges( offscreenRow: range.offscreenRow, }) } + if (change.end < range.end) { // The change ends within the range if (change.bufferDelta !== 0) { @@ -1295,6 +1287,7 @@ function computeIntactRanges( } } } + intactRanges = newIntactRanges } @@ -1313,6 +1306,7 @@ function computeIntactRanges( */ function truncateIntactRanges(intactRanges: Array<{}>, firstRow: number, lastRow: number): Array<{}> { let i = 0 + while (i < intactRanges.length) { const range = intactRanges[i] @@ -1335,4 +1329,4 @@ function truncateIntactRanges(intactRanges: Array<{}>, firstRow: number, lastRow return intactRanges.sort((a, b) => { return a.offscreenRow - b.offscreenRow }) -} +} \ No newline at end of file diff --git a/lib/performance-monitor.js b/lib/performance-monitor.ts similarity index 99% rename from lib/performance-monitor.js rename to lib/performance-monitor.ts index 22adf35b..3320db5b 100644 --- a/lib/performance-monitor.js +++ b/lib/performance-monitor.ts @@ -1,5 +1,4 @@ // Functions used to recommend the configurations required for the best performance of Minimap - export function treeSitterWarning() { return observeAndWarn( "core.useTreeSitterParsers", @@ -18,19 +17,21 @@ function observeAndWarn(configName, recommendedValue, warningTitle, warningDescr const today = new Date() const previousWarning = window.localStorage.getItem(storageName) let previousWarningDay = null + if (previousWarning) { previousWarningDay = new Date(Date.parse(previousWarning)).getDay() } + // throw the warning once a day if (!previousWarningDay || (typeof previousWarningDay === "number" && previousWarningDay - today.getDay() >= 1)) { window.localStorage.setItem(storageName, today) - const notification = atom.notifications.addWarning(warningTitle, { description: warningDescription, dismissable: true, buttons: [ { text: `Set to ${recommendedValue} and restart Atom`, + onDidClick() { atom.config.set(configName, true) notification.dismiss() @@ -45,4 +46,4 @@ function observeAndWarn(configName, recommendedValue, warningTitle, warningDescr } } }) -} +} \ No newline at end of file diff --git a/lib/plugin-management.js b/lib/plugin-management.ts similarity index 96% rename from lib/plugin-management.js rename to lib/plugin-management.ts index 76f99d8a..7e311389 100644 --- a/lib/plugin-management.js +++ b/lib/plugin-management.ts @@ -18,7 +18,6 @@ import { emitter, getConfigSchema } from "./main" * * @access public */ - // Initialize the properties for plugin management. /** @@ -28,6 +27,7 @@ import { emitter, getConfigSchema } from "./main" * @access private */ export const plugins = {} + /** * The plugins' subscriptions stored using the plugin names as keys. * @@ -58,8 +58,10 @@ const pluginsOrderMap: {} = {} export function registerPlugin(name, plugin) { plugins[name] = plugin pluginsSubscriptions[name] = new CompositeDisposable() - - const event = { name, plugin } + const event = { + name, + plugin, + } emitter.emit("did-add-plugin", event) if (atom.config.get("minimap.displayPluginsControls")) { @@ -84,8 +86,10 @@ export function unregisterPlugin(name) { } delete plugins[name] - - const event = { name, plugin } + const event = { + name, + plugin, + } emitter.emit("did-remove-plugin", event) } @@ -120,7 +124,10 @@ export function togglePluginActivation(name, boolean) { export function deactivateAllPlugins() { for (const [name, plugin] of eachPlugin()) { plugin.deactivatePlugin() - emitter.emit("did-deactivate-plugin", { name, plugin }) + emitter.emit("did-deactivate-plugin", { + name, + plugin, + }) } } @@ -165,15 +172,18 @@ function updatesPluginActivationState(name: string) { } export function activatePlugin(name, plugin) { - const event = { name, plugin } - + const event = { + name, + plugin, + } plugin.activatePlugin() emitter.emit("did-activate-plugin", event) } - export function deactivatePlugin(name, plugin) { - const event = { name, plugin } - + const event = { + name, + plugin, + } plugin.deactivatePlugin() emitter.emit("did-deactivate-plugin", event) } @@ -195,16 +205,13 @@ export function deactivatePlugin(name, plugin) { function registerPluginControls(name: string, plugin: MinimapPlugin) { const settingsKey = `minimap.plugins.${name}` const orderSettingsKey = `minimap.plugins.${name}DecorationsZIndex` - const config = getConfigSchema() - config.plugins.properties[name] = { type: "boolean", title: name, description: `Whether the ${name} plugin is activated and displayed in the Minimap.`, default: true, } - config.plugins.properties[`${name}DecorationsZIndex`] = { type: "integer", title: `${name} decorations order`, @@ -225,15 +232,17 @@ function registerPluginControls(name: string, plugin: MinimapPlugin) { updatesPluginActivationState(name) }) ) - pluginsSubscriptions[name].add( atom.config.observe(orderSettingsKey, (order) => { updatePluginsOrderMap(name) - const event = { name, plugin, order } + const event = { + name, + plugin, + order, + } emitter.emit("did-change-plugin-order", event) }) ) - pluginsSubscriptions[name].add( atom.commands.add("atom-workspace", { [`minimap:toggle-${name}`]: () => { @@ -241,7 +250,6 @@ function registerPluginControls(name: string, plugin: MinimapPlugin) { }, }) ) - updatePluginsOrderMap(name) } @@ -253,7 +261,6 @@ function registerPluginControls(name: string, plugin: MinimapPlugin) { */ function updatePluginsOrderMap(name: string) { const orderSettingsKey = `minimap.plugins.${name}DecorationsZIndex` - pluginsOrderMap[name] = atom.config.get(orderSettingsKey) } @@ -278,4 +285,4 @@ function unregisterPluginControls(name: string) { pluginsSubscriptions[name].dispose() delete pluginsSubscriptions[name] delete getConfigSchema().plugins.properties[name] -} +} \ No newline at end of file