From dbace171df4c31a931997ef75fa1f451ba86dcbc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 24 Jul 2018 09:36:56 -0400 Subject: [PATCH 01/19] Add a `kind` property to Gutters --- spec/text-editor-spec.js | 14 ++++++++++++++ src/gutter-container.js | 2 +- src/gutter.js | 2 ++ src/text-editor.js | 11 +++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index eba2c34f7..4a4723da1 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -6716,6 +6716,20 @@ describe('TextEditor', () => { const gutter = editor.addGutter(options) expect(editor.getGutters().length).toBe(2) expect(editor.getGutters()[1]).toBe(gutter) + expect(gutter.kind).toBe('decorated') + }) + + it('can add a custom line-number gutter', () => { + expect(editor.getGutters().length).toBe(1) + const options = { + name: 'another-gutter', + priority: 2, + kind: 'line-number' + } + const gutter = editor.addGutter(options) + expect(editor.getGutters().length).toBe(2) + expect(editor.getGutters()[1]).toBe(gutter) + expect(gutter.kind).toBe('line-number') }) it("does not allow a custom gutter with the 'line-number' name.", () => expect(editor.addGutter.bind(editor, {name: 'line-number'})).toThrow()) diff --git a/src/gutter-container.js b/src/gutter-container.js index 3faece073..220d9f017 100644 --- a/src/gutter-container.js +++ b/src/gutter-container.js @@ -97,7 +97,7 @@ module.exports = class GutterContainer { // The public interface is Gutter::decorateMarker or TextEditor::decorateMarker. addGutterDecoration (gutter, marker, options) { - if (gutter.name === 'line-number') { + if (gutter.kind === 'line-number') { options.type = 'line-number' } else { options.type = 'gutter' diff --git a/src/gutter.js b/src/gutter.js index 3bf7a72ea..6835e698a 100644 --- a/src/gutter.js +++ b/src/gutter.js @@ -11,6 +11,8 @@ module.exports = class Gutter { this.name = options && options.name this.priority = (options && options.priority != null) ? options.priority : DefaultPriority this.visible = (options && options.visible != null) ? options.visible : true + this.kind = (options && options.kind != null) ? options.kind : 'decorated' + this.labelFn = options && options.labelFn this.emitter = new Emitter() } diff --git a/src/text-editor.js b/src/text-editor.js index efa7353e0..bcf20651a 100644 --- a/src/text-editor.js +++ b/src/text-editor.js @@ -258,6 +258,7 @@ class TextEditor { this.gutterContainer = new GutterContainer(this) this.lineNumberGutter = this.gutterContainer.addGutter({ name: 'line-number', + kind: 'line-number', priority: 0, visible: params.lineNumberGutterVisible }) @@ -4211,6 +4212,16 @@ class TextEditor { // window. (default: -100) // * `visible` (optional) {Boolean} specifying whether the gutter is visible // initially after being created. (default: true) + // * `type` (optional) {String} specifying the type of gutter to create. `'decorated'` + // gutters are useful as a destination for decorations created with {Gutter::decorateMarker}. + // `'line-number'` gutters + // * `labelFn` (optional) {Function} called by a `'line-number'` gutter to generate the label for each line number + // element. Should return a {String} that will be used to label the corresponding line. + // * `lineData` an {Object} containing information about each line to label. + // * `bufferRow` {Number} indicating the zero-indexed buffer index of this line. + // * `screenRow` {Number} indicating the zero-indexed screen index. + // * `foldable` {Boolean} that is `true` if a fold may be created here. + // * `softWrapped` {Boolean} if this screen row is the soft-wrapped continuation of the same buffer row. // // Returns the newly-created {Gutter}. addGutter (options) { From e832c0b8dde803c4171a171a5f99ccf18dc47b01 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 24 Jul 2018 09:38:11 -0400 Subject: [PATCH 02/19] Support multiple kind: line-number gutters --- src/text-editor-component.js | 19 ++++++++++++------- src/text-editor.js | 4 ++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 9b30588e0..0af768c0f 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -886,7 +886,7 @@ class TextEditorComponent { queryLineNumbersToRender () { const {model} = this.props - if (!model.isLineNumberGutterVisible()) return + if (!model.anyLineNumberGutterVisible()) return if (this.showLineNumbers !== model.doesShowLineNumbers()) { this.remeasureGutterDimensions = true this.showLineNumbers = model.doesShowLineNumbers() @@ -942,7 +942,7 @@ class TextEditorComponent { queryMaxLineNumberDigits () { const {model} = this.props - if (model.isLineNumberGutterVisible()) { + if (model.anyLineNumberGutterVisible()) { const maxDigits = Math.max(2, model.getLineCount().toString().length) if (maxDigits !== this.lineNumbersToRender.maxDigits) { this.remeasureGutterDimensions = true @@ -3099,7 +3099,7 @@ class GutterContainerComponent { }, $.div({style: innerStyle}, guttersToRender.map((gutter) => { - if (gutter.name === 'line-number') { + if (gutter.kind === 'line-number') { return this.renderLineNumberGutter(gutter) } else { return $(CustomGutterComponent, { @@ -3118,18 +3118,23 @@ class GutterContainerComponent { renderLineNumberGutter (gutter) { const { - rootComponent, isLineNumberGutterVisible, showLineNumbers, hasInitialMeasurements, lineNumbersToRender, + rootComponent, showLineNumbers, hasInitialMeasurements, lineNumbersToRender, renderedStartRow, renderedEndRow, rowsPerTile, decorationsToRender, didMeasureVisibleBlockDecoration, scrollHeight, lineNumberGutterWidth, lineHeight } = this.props - if (!isLineNumberGutterVisible) return null + if (!gutter.isVisible()) { + return null; + } + + const ref = gutter.name === 'line-number' ? 'lineNumberGutter' : undefined; if (hasInitialMeasurements) { const {maxDigits, keys, bufferRows, screenRows, softWrappedFlags, foldableFlags} = lineNumbersToRender return $(LineNumberGutterComponent, { - ref: 'lineNumberGutter', + ref, element: gutter.getElement(), + labelFn: gutter.labelFn, rootComponent: rootComponent, startRow: renderedStartRow, endRow: renderedEndRow, @@ -3150,7 +3155,7 @@ class GutterContainerComponent { }) } else { return $(LineNumberGutterComponent, { - ref: 'lineNumberGutter', + ref, element: gutter.getElement(), maxDigits: lineNumbersToRender.maxDigits, showLineNumbers diff --git a/src/text-editor.js b/src/text-editor.js index bcf20651a..2c44b2ca7 100644 --- a/src/text-editor.js +++ b/src/text-editor.js @@ -1021,6 +1021,10 @@ class TextEditor { isLineNumberGutterVisible () { return this.lineNumberGutter.isVisible() } + anyLineNumberGutterVisible () { + return this.getGutters().some(gutter => gutter.kind === 'line-number' && gutter.visible) + } + onDidChangeLineNumberGutterVisible (callback) { return this.emitter.on('did-change-line-number-gutter-visible', callback) } From e61b1807770e3d8cbf87b04ee43226f336975d88 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 24 Jul 2018 09:38:57 -0400 Subject: [PATCH 03/19] Respect a custom label generation function --- src/text-editor-component.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 0af768c0f..93c71f253 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -3212,8 +3212,12 @@ class LineNumberGutterComponent { let number = null if (showLineNumbers) { - number = softWrapped ? '•' : bufferRow + 1 - number = NBSP_CHARACTER.repeat(maxDigits - number.length) + number + if (this.props.labelFn == null) { + number = softWrapped ? '•' : bufferRow + 1 + number = NBSP_CHARACTER.repeat(maxDigits - number.length) + number + } else { + number = this.props.labelFn({bufferRow, screenRow, foldable, softWrapped}) + } } // We need to adjust the line number position to account for block @@ -3283,6 +3287,7 @@ class LineNumberGutterComponent { if (oldProps.endRow !== newProps.endRow) return true if (oldProps.rowsPerTile !== newProps.rowsPerTile) return true if (oldProps.maxDigits !== newProps.maxDigits) return true + if (oldProps.labelFn !== newProps.labelFn) return true if (newProps.didMeasureVisibleBlockDecoration) return true if (!arraysEqual(oldProps.keys, newProps.keys)) return true if (!arraysEqual(oldProps.bufferRows, newProps.bufferRows)) return true From 795c3b27aad693189c269a60910e3b2490a4e08b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 24 Jul 2018 09:53:14 -0400 Subject: [PATCH 04/19] Use `type` instead of `kind` --- spec/text-editor-spec.js | 6 +++--- src/gutter-container.js | 2 +- src/gutter.js | 2 +- src/text-editor-component.js | 2 +- src/text-editor.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index 4a4723da1..c49e15a25 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -6716,7 +6716,7 @@ describe('TextEditor', () => { const gutter = editor.addGutter(options) expect(editor.getGutters().length).toBe(2) expect(editor.getGutters()[1]).toBe(gutter) - expect(gutter.kind).toBe('decorated') + expect(gutter.type).toBe('decorated') }) it('can add a custom line-number gutter', () => { @@ -6724,12 +6724,12 @@ describe('TextEditor', () => { const options = { name: 'another-gutter', priority: 2, - kind: 'line-number' + type: 'line-number' } const gutter = editor.addGutter(options) expect(editor.getGutters().length).toBe(2) expect(editor.getGutters()[1]).toBe(gutter) - expect(gutter.kind).toBe('line-number') + expect(gutter.type).toBe('line-number') }) it("does not allow a custom gutter with the 'line-number' name.", () => expect(editor.addGutter.bind(editor, {name: 'line-number'})).toThrow()) diff --git a/src/gutter-container.js b/src/gutter-container.js index 220d9f017..cd0c796b2 100644 --- a/src/gutter-container.js +++ b/src/gutter-container.js @@ -97,7 +97,7 @@ module.exports = class GutterContainer { // The public interface is Gutter::decorateMarker or TextEditor::decorateMarker. addGutterDecoration (gutter, marker, options) { - if (gutter.kind === 'line-number') { + if (gutter.type === 'line-number') { options.type = 'line-number' } else { options.type = 'gutter' diff --git a/src/gutter.js b/src/gutter.js index 6835e698a..e53d700b7 100644 --- a/src/gutter.js +++ b/src/gutter.js @@ -11,7 +11,7 @@ module.exports = class Gutter { this.name = options && options.name this.priority = (options && options.priority != null) ? options.priority : DefaultPriority this.visible = (options && options.visible != null) ? options.visible : true - this.kind = (options && options.kind != null) ? options.kind : 'decorated' + this.type = (options && options.type != null) ? options.type : 'decorated' this.labelFn = options && options.labelFn this.emitter = new Emitter() diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 93c71f253..0970eedae 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -3099,7 +3099,7 @@ class GutterContainerComponent { }, $.div({style: innerStyle}, guttersToRender.map((gutter) => { - if (gutter.kind === 'line-number') { + if (gutter.type === 'line-number') { return this.renderLineNumberGutter(gutter) } else { return $(CustomGutterComponent, { diff --git a/src/text-editor.js b/src/text-editor.js index 2c44b2ca7..6f77813aa 100644 --- a/src/text-editor.js +++ b/src/text-editor.js @@ -258,7 +258,7 @@ class TextEditor { this.gutterContainer = new GutterContainer(this) this.lineNumberGutter = this.gutterContainer.addGutter({ name: 'line-number', - kind: 'line-number', + type: 'line-number', priority: 0, visible: params.lineNumberGutterVisible }) From 6880ac9ff6f51f0b82e14a677b90a89d302c614d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 24 Jul 2018 10:03:03 -0400 Subject: [PATCH 05/19] Missed a "kind" --- src/text-editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor.js b/src/text-editor.js index 6f77813aa..9b0bcc0db 100644 --- a/src/text-editor.js +++ b/src/text-editor.js @@ -1022,7 +1022,7 @@ class TextEditor { isLineNumberGutterVisible () { return this.lineNumberGutter.isVisible() } anyLineNumberGutterVisible () { - return this.getGutters().some(gutter => gutter.kind === 'line-number' && gutter.visible) + return this.getGutters().some(gutter => gutter.type === 'line-number' && gutter.visible) } onDidChangeLineNumberGutterVisible (callback) { From 65dceec0455425f81f4b65d4ffbf234cdcc30f96 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 24 Jul 2018 10:26:31 -0400 Subject: [PATCH 06/19] Initialize lineNumbersToREnder.screenRows --- src/text-editor-component.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 0970eedae..c82120ea9 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -148,6 +148,7 @@ class TextEditorComponent { this.lineNumbersToRender = { maxDigits: 2, bufferRows: [], + screenRows: [], keys: [], softWrappedFlags: [], foldableFlags: [] From 10c2e2932c6d81dd1009f7b71c5d1fd0019f1f16 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 24 Jul 2018 10:26:47 -0400 Subject: [PATCH 07/19] Set gutter-name correctly on custom line number gutters --- src/text-editor-component.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index c82120ea9..721bb1893 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -3135,6 +3135,7 @@ class GutterContainerComponent { return $(LineNumberGutterComponent, { ref, element: gutter.getElement(), + name: gutter.name, labelFn: gutter.labelFn, rootComponent: rootComponent, startRow: renderedStartRow, @@ -3158,6 +3159,7 @@ class GutterContainerComponent { return $(LineNumberGutterComponent, { ref, element: gutter.getElement(), + name: gutter.name, maxDigits: lineNumbersToRender.maxDigits, showLineNumbers }) @@ -3263,7 +3265,7 @@ class LineNumberGutterComponent { return $.div( { className: 'gutter line-numbers', - attributes: {'gutter-name': 'line-number'}, + attributes: {'gutter-name': this.props.name}, style: {position: 'relative', height: ceilToPhysicalPixelBoundary(height) + 'px'}, on: { mousedown: this.didMouseDown From 931629769c932805bbdaa0705925186f7d31ecc7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 24 Jul 2018 10:31:17 -0400 Subject: [PATCH 08/19] :shirt: oh right standardjs --- src/text-editor-component.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 721bb1893..e3878268c 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -3125,10 +3125,10 @@ class GutterContainerComponent { } = this.props if (!gutter.isVisible()) { - return null; + return null } - const ref = gutter.name === 'line-number' ? 'lineNumberGutter' : undefined; + const ref = gutter.name === 'line-number' ? 'lineNumberGutter' : undefined if (hasInitialMeasurements) { const {maxDigits, keys, bufferRows, screenRows, softWrappedFlags, foldableFlags} = lineNumbersToRender From 4dc062958d1cb07bcbfba7ef372dd2e9f8f426f5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 24 Jul 2018 10:44:34 -0400 Subject: [PATCH 09/19] decorateMarker({type: 'line-number') only decorates the one true gutter --- src/text-editor-component.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index e3878268c..fe5fb7e8f 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -154,7 +154,7 @@ class TextEditorComponent { foldableFlags: [] } this.decorationsToRender = { - lineNumbers: null, + lineNumbers: new Map(), lines: null, highlights: [], cursors: [], @@ -978,7 +978,7 @@ class TextEditorComponent { } queryDecorationsToRender () { - this.decorationsToRender.lineNumbers = [] + this.decorationsToRender.lineNumbers.clear() this.decorationsToRender.lines = [] this.decorationsToRender.overlays.length = 0 this.decorationsToRender.customGutter.clear() @@ -1041,7 +1041,17 @@ class TextEditorComponent { } addLineDecorationToRender (type, decoration, screenRange, reversed) { - const decorationsToRender = (type === 'line') ? this.decorationsToRender.lines : this.decorationsToRender.lineNumbers + let decorationsToRender + if (type === 'line') { + decorationsToRender = this.decorationsToRender.lines + } else { + const gutterName = decoration.gutterName || 'line-number' + decorationsToRender = this.decorationsToRender.lineNumbers.get(gutterName) + if (!decorationsToRender) { + decorationsToRender = [] + this.decorationsToRender.lineNumbers.set(gutterName, decorationsToRender) + } + } let omitLastRow = false if (screenRange.isEmpty()) { @@ -3147,7 +3157,7 @@ class GutterContainerComponent { screenRows: screenRows, softWrappedFlags: softWrappedFlags, foldableFlags: foldableFlags, - decorations: decorationsToRender.lineNumbers, + decorations: decorationsToRender.lineNumbers.get(gutter.name) || [], blockDecorations: decorationsToRender.blocks, didMeasureVisibleBlockDecoration: didMeasureVisibleBlockDecoration, height: scrollHeight, From c064c87188c293d89292d1576109667ea756dd42 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 24 Jul 2018 10:52:50 -0400 Subject: [PATCH 10/19] Add a custom CSS class to the Gutter's root element --- src/gutter.js | 1 + src/text-editor-component.js | 19 ++++++++++++++++--- src/text-editor.js | 1 + 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/gutter.js b/src/gutter.js index e53d700b7..d4fefc61a 100644 --- a/src/gutter.js +++ b/src/gutter.js @@ -13,6 +13,7 @@ module.exports = class Gutter { this.visible = (options && options.visible != null) ? options.visible : true this.type = (options && options.type != null) ? options.type : 'decorated' this.labelFn = options && options.labelFn + this.className = options && options.class this.emitter = new Emitter() } diff --git a/src/text-editor-component.js b/src/text-editor-component.js index fe5fb7e8f..ea5ddf4c7 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -3146,6 +3146,7 @@ class GutterContainerComponent { ref, element: gutter.getElement(), name: gutter.name, + className: gutter.className, labelFn: gutter.labelFn, rootComponent: rootComponent, startRow: renderedStartRow, @@ -3170,6 +3171,7 @@ class GutterContainerComponent { ref, element: gutter.getElement(), name: gutter.name, + className: gutter.className, maxDigits: lineNumbersToRender.maxDigits, showLineNumbers }) @@ -3197,7 +3199,8 @@ class LineNumberGutterComponent { render () { const { rootComponent, showLineNumbers, height, width, startRow, endRow, rowsPerTile, - maxDigits, keys, bufferRows, screenRows, softWrappedFlags, foldableFlags, decorations + maxDigits, keys, bufferRows, screenRows, softWrappedFlags, foldableFlags, decorations, + className } = this.props let children = null @@ -3272,9 +3275,14 @@ class LineNumberGutterComponent { } } + let rootClassName = 'gutter line-numbers' + if (className) { + rootClassName += ' ' + className + } + return $.div( { - className: 'gutter line-numbers', + className: rootClassName, attributes: {'gutter-name': this.props.name}, style: {position: 'relative', height: ceilToPhysicalPixelBoundary(height) + 'px'}, on: { @@ -3301,6 +3309,7 @@ class LineNumberGutterComponent { if (oldProps.rowsPerTile !== newProps.rowsPerTile) return true if (oldProps.maxDigits !== newProps.maxDigits) return true if (oldProps.labelFn !== newProps.labelFn) return true + if (oldProps.className !== newProps.className) return true if (newProps.didMeasureVisibleBlockDecoration) return true if (!arraysEqual(oldProps.keys, newProps.keys)) return true if (!arraysEqual(oldProps.bufferRows, newProps.bufferRows)) return true @@ -3416,9 +3425,13 @@ class CustomGutterComponent { } render () { + let className = 'gutter' + if (this.props.className) { + className += ' ' + this.props.className + } return $.div( { - className: 'gutter', + className, attributes: {'gutter-name': this.props.name}, style: { display: this.props.visible ? '' : 'none' diff --git a/src/text-editor.js b/src/text-editor.js index 9b0bcc0db..164f5e8f0 100644 --- a/src/text-editor.js +++ b/src/text-editor.js @@ -4226,6 +4226,7 @@ class TextEditor { // * `screenRow` {Number} indicating the zero-indexed screen index. // * `foldable` {Boolean} that is `true` if a fold may be created here. // * `softWrapped` {Boolean} if this screen row is the soft-wrapped continuation of the same buffer row. + // * `class` (optional) {String} added to the CSS classnames of the gutter's root DOM element. // // Returns the newly-created {Gutter}. addGutter (options) { From fdf5d501965a1d1bc1f38532bce9c4fdbc3665ec Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 24 Jul 2018 12:20:30 -0400 Subject: [PATCH 11/19] Support custom onMouseDown and onMouseMove event handlers --- src/gutter.js | 3 +++ src/text-editor-component.js | 21 +++++++++++++++++++-- src/text-editor.js | 15 +++++++++++++-- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/gutter.js b/src/gutter.js index d4fefc61a..bd5955b78 100644 --- a/src/gutter.js +++ b/src/gutter.js @@ -15,6 +15,9 @@ module.exports = class Gutter { this.labelFn = options && options.labelFn this.className = options && options.class + this.onMouseDown = options && options.onMouseDown + this.onMouseMove = options && options.onMouseMove + this.emitter = new Emitter() } diff --git a/src/text-editor-component.js b/src/text-editor-component.js index ea5ddf4c7..4a3fc789c 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -3148,6 +3148,8 @@ class GutterContainerComponent { name: gutter.name, className: gutter.className, labelFn: gutter.labelFn, + onMouseDown: gutter.onMouseDown, + onMouseMove: gutter.onMouseMove, rootComponent: rootComponent, startRow: renderedStartRow, endRow: renderedEndRow, @@ -3172,6 +3174,8 @@ class GutterContainerComponent { element: gutter.getElement(), name: gutter.name, className: gutter.className, + onMouseDown: gutter.onMouseDown, + onMouseMove: gutter.onMouseMove, maxDigits: lineNumbersToRender.maxDigits, showLineNumbers }) @@ -3286,7 +3290,8 @@ class LineNumberGutterComponent { attributes: {'gutter-name': this.props.name}, style: {position: 'relative', height: ceilToPhysicalPixelBoundary(height) + 'px'}, on: { - mousedown: this.didMouseDown + mousedown: this.didMouseDown, + mousemove: this.didMouseMove } }, $.div({key: 'placeholder', className: 'line-number dummy', style: {visibility: 'hidden'}}, @@ -3356,7 +3361,19 @@ class LineNumberGutterComponent { } didMouseDown (event) { - this.props.rootComponent.didMouseDownOnLineNumberGutter(event) + if (this.props.onMouseDown == null) { + this.props.rootComponent.didMouseDownOnLineNumberGutter(event) + } else { + const {bufferRow, screenRow} = event.target.dataset + this.props.onMouseDown({bufferRow, screenRow}) + } + } + + didMouseMove (event) { + if (this.props.onMouseMove != null) { + const {bufferRow, screenRow} = event.target.dataset + this.props.onMouseMove({bufferRow, screenRow}) + } } } diff --git a/src/text-editor.js b/src/text-editor.js index 164f5e8f0..3cac563b5 100644 --- a/src/text-editor.js +++ b/src/text-editor.js @@ -4218,7 +4218,8 @@ class TextEditor { // initially after being created. (default: true) // * `type` (optional) {String} specifying the type of gutter to create. `'decorated'` // gutters are useful as a destination for decorations created with {Gutter::decorateMarker}. - // `'line-number'` gutters + // `'line-number'` gutters. + // * `class` (optional) {String} added to the CSS classnames of the gutter's root DOM element. // * `labelFn` (optional) {Function} called by a `'line-number'` gutter to generate the label for each line number // element. Should return a {String} that will be used to label the corresponding line. // * `lineData` an {Object} containing information about each line to label. @@ -4226,7 +4227,17 @@ class TextEditor { // * `screenRow` {Number} indicating the zero-indexed screen index. // * `foldable` {Boolean} that is `true` if a fold may be created here. // * `softWrapped` {Boolean} if this screen row is the soft-wrapped continuation of the same buffer row. - // * `class` (optional) {String} added to the CSS classnames of the gutter's root DOM element. + // * `onMouseDown` (optional) {Function} to be called when a mousedown event is received by a line-number + // element within this `type: 'line-number'` {Gutter}. If unspecified, the default behavior is to select the + // clicked buffer row. + // * `lineData` an {Object} containing information about the line that's being clicked. + // * `bufferRow` {Number} of the originating line element + // * `screenRow` {Number} + // * `onMouseMove` (optional) {Function} to be called when a mousemove event occurs on a line-number element within + // within this `type: 'line-number'` {Gutter}. + // * `lineData` an {Object} containing information about the line that's being clicked. + // * `bufferRow` {Number} of the originating line element + // * `screenRow` {Number} // // Returns the newly-created {Gutter}. addGutter (options) { From 291bf4d835ccc348530a488522d285541b897d69 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 24 Jul 2018 12:27:33 -0400 Subject: [PATCH 12/19] Pass maxDigits to labelFn --- src/text-editor-component.js | 2 +- src/text-editor.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 4a3fc789c..5f7cbdcbc 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -3236,7 +3236,7 @@ class LineNumberGutterComponent { number = softWrapped ? '•' : bufferRow + 1 number = NBSP_CHARACTER.repeat(maxDigits - number.length) + number } else { - number = this.props.labelFn({bufferRow, screenRow, foldable, softWrapped}) + number = this.props.labelFn({bufferRow, screenRow, foldable, softWrapped, maxDigits}) } } diff --git a/src/text-editor.js b/src/text-editor.js index 3cac563b5..ba063f7f0 100644 --- a/src/text-editor.js +++ b/src/text-editor.js @@ -4227,6 +4227,7 @@ class TextEditor { // * `screenRow` {Number} indicating the zero-indexed screen index. // * `foldable` {Boolean} that is `true` if a fold may be created here. // * `softWrapped` {Boolean} if this screen row is the soft-wrapped continuation of the same buffer row. + // * `maxDigits` {Number} the maximum number of digits necessary to represent any known screen row. // * `onMouseDown` (optional) {Function} to be called when a mousedown event is received by a line-number // element within this `type: 'line-number'` {Gutter}. If unspecified, the default behavior is to select the // clicked buffer row. From 2a1719f337616d192ffa1b2ad06119b0256e6bc4 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 25 Jul 2018 08:21:55 -0400 Subject: [PATCH 13/19] Pass bufferRow and screenRow as numbers --- src/text-editor-component.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 5f7cbdcbc..6dc968efc 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -3364,15 +3364,23 @@ class LineNumberGutterComponent { if (this.props.onMouseDown == null) { this.props.rootComponent.didMouseDownOnLineNumberGutter(event) } else { - const {bufferRow, screenRow} = event.target.dataset - this.props.onMouseDown({bufferRow, screenRow}) + const {bufferRowStr, screenRowStr} = event.target.dataset + this.props.onMouseDown({ + bufferRow: parseInt(bufferRowStr, 10), + screenRow: parseInt(screenRowStr, 10), + domEvent: event + }) } } didMouseMove (event) { if (this.props.onMouseMove != null) { - const {bufferRow, screenRow} = event.target.dataset - this.props.onMouseMove({bufferRow, screenRow}) + const {bufferRowStr, screenRowStr} = event.target.dataset + this.props.onMouseDown({ + bufferRow: parseInt(bufferRowStr, 10), + screenRow: parseInt(screenRowStr, 10), + domEvent: event + }) } } } From 192cf3b3fd0a5461348946b6b1aa29974dd6ce0a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 25 Jul 2018 08:26:21 -0400 Subject: [PATCH 14/19] mousedown !== mousemove --- src/text-editor-component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 6dc968efc..7c66f6f6a 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -3376,7 +3376,7 @@ class LineNumberGutterComponent { didMouseMove (event) { if (this.props.onMouseMove != null) { const {bufferRowStr, screenRowStr} = event.target.dataset - this.props.onMouseDown({ + this.props.onMouseMove({ bufferRow: parseInt(bufferRowStr, 10), screenRow: parseInt(screenRowStr, 10), domEvent: event From 3ec43b913f66cfbfde1c8df45c193cf15b11d479 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 25 Jul 2018 09:18:52 -0400 Subject: [PATCH 15/19] :eyes: --- src/text-editor-component.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 7c66f6f6a..8c4e7f17f 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -3364,10 +3364,10 @@ class LineNumberGutterComponent { if (this.props.onMouseDown == null) { this.props.rootComponent.didMouseDownOnLineNumberGutter(event) } else { - const {bufferRowStr, screenRowStr} = event.target.dataset + const {bufferRow, screenRow} = event.target.dataset this.props.onMouseDown({ - bufferRow: parseInt(bufferRowStr, 10), - screenRow: parseInt(screenRowStr, 10), + bufferRow: parseInt(bufferRow, 10), + screenRow: parseInt(screenRow, 10), domEvent: event }) } @@ -3375,10 +3375,10 @@ class LineNumberGutterComponent { didMouseMove (event) { if (this.props.onMouseMove != null) { - const {bufferRowStr, screenRowStr} = event.target.dataset + const {bufferRow, screenRow} = event.target.dataset this.props.onMouseMove({ - bufferRow: parseInt(bufferRowStr, 10), - screenRow: parseInt(screenRowStr, 10), + bufferRow: parseInt(bufferRow, 10), + screenRow: parseInt(screenRow, 10), domEvent: event }) } From 0f771ea6043f264be0632e2f25692690fd7dae86 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 25 Jul 2018 09:43:31 -0400 Subject: [PATCH 16/19] Only pass and respect `width` for the One True Gutter --- src/text-editor-component.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 8c4e7f17f..d9f784d70 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -3138,7 +3138,9 @@ class GutterContainerComponent { return null } - const ref = gutter.name === 'line-number' ? 'lineNumberGutter' : undefined + const oneTrueLineNumberGutter = gutter.name === 'line-number' + const ref = oneTrueLineNumberGutter ? 'lineNumberGutter' : undefined + const width = oneTrueLineNumberGutter ? lineNumberGutterWidth : undefined if (hasInitialMeasurements) { const {maxDigits, keys, bufferRows, screenRows, softWrappedFlags, foldableFlags} = lineNumbersToRender @@ -3164,7 +3166,7 @@ class GutterContainerComponent { blockDecorations: decorationsToRender.blocks, didMeasureVisibleBlockDecoration: didMeasureVisibleBlockDecoration, height: scrollHeight, - width: lineNumberGutterWidth, + width, lineHeight: lineHeight, showLineNumbers }) @@ -3264,6 +3266,7 @@ class LineNumberGutterComponent { const tileTop = rootComponent.pixelPositionBeforeBlocksForRow(tileStartRow) const tileBottom = rootComponent.pixelPositionBeforeBlocksForRow(tileEndRow) const tileHeight = tileBottom - tileTop + const tileWidth = width != null && width > 0 ? width + 'px' : '' children[i] = $.div({ key: rootComponent.idsByTileStartRow.get(tileStartRow), @@ -3272,7 +3275,7 @@ class LineNumberGutterComponent { position: 'absolute', top: 0, height: tileHeight + 'px', - width: width + 'px', + width: tileWidth, transform: `translateY(${tileTop}px)` } }, ...tileChildren) @@ -3389,7 +3392,8 @@ class LineNumberComponent { constructor (props) { const {className, width, marginTop, bufferRow, screenRow, number, nodePool} = props this.props = props - const style = {width: width + 'px'} + const style = {} + if (width != null && width > 0) style.width = width + 'px' if (marginTop != null && marginTop > 0) style.marginTop = marginTop + 'px' this.element = nodePool.getElement('DIV', className, style) this.element.dataset.bufferRow = bufferRow @@ -3409,9 +3413,15 @@ class LineNumberComponent { if (this.props.bufferRow !== bufferRow) this.element.dataset.bufferRow = bufferRow if (this.props.screenRow !== screenRow) this.element.dataset.screenRow = screenRow if (this.props.className !== className) this.element.className = className - if (this.props.width !== width) this.element.style.width = width + 'px' + if (this.props.width !== width) { + if (width != null && width > 0) { + this.element.style.width = width + 'px' + } else { + this.element.style.width = '' + } + } if (this.props.marginTop !== marginTop) { - if (marginTop != null) { + if (marginTop != null && marginTop > 0) { this.element.style.marginTop = marginTop + 'px' } else { this.element.style.marginTop = '' From 65bcd888f392aa099996d5b5dabf8ff2f9941e9e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 27 Jul 2018 10:18:42 -0400 Subject: [PATCH 17/19] Update LineNumberComponent correctly when "number" changes --- src/text-editor-component.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index d9f784d70..0d428089b 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -3427,14 +3427,17 @@ class LineNumberComponent { this.element.style.marginTop = '' } } + if (this.props.number !== number) { - if (number) { - this.element.insertBefore(nodePool.getTextNode(number), this.element.firstChild) - } else { + if (this.props.number != null) { const numberNode = this.element.firstChild numberNode.remove() nodePool.release(numberNode) } + + if (number != null) { + this.element.insertBefore(nodePool.getTextNode(number), this.element.firstChild); + } } this.props = props From 95658c3769442786184b0e42dde85ab10e600fa4 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 27 Jul 2018 11:18:35 -0400 Subject: [PATCH 18/19] :shirt: --- src/text-editor-component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 0d428089b..452a220ca 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -3436,7 +3436,7 @@ class LineNumberComponent { } if (number != null) { - this.element.insertBefore(nodePool.getTextNode(number), this.element.firstChild); + this.element.insertBefore(nodePool.getTextNode(number), this.element.firstChild) } } From e04da46e8aa62a2a610a74ab3ecde91772b2d719 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 21 Aug 2018 16:59:48 -0400 Subject: [PATCH 19/19] Quick spec for line number rendering --- spec/text-editor-component-spec.js | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 12c29e2a3..eaa27a5c4 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -2054,6 +2054,37 @@ describe('TextEditorComponent', () => { expect(decorationNode2.firstChild).toBeNull() expect(gutterB.getElement().firstChild.children.length).toBe(0) }) + + it('renders custom line number gutters', async () => { + const {component, editor} = buildComponent() + const gutterA = editor.addGutter({ + name: 'a', + priority: 1, + type: 'line-number', + class: 'a-number', + labelFn: ({bufferRow}) => `a - ${bufferRow}` + }) + const gutterB = editor.addGutter({ + name: 'b', + priority: 1, + type: 'line-number', + class: 'b-number', + labelFn: ({bufferRow}) => `b - ${bufferRow}` + }) + editor.setText('0000\n0001\n0002\n0003\n0004\n') + + await component.getNextUpdatePromise() + + const gutterAElement = gutterA.getElement() + const aNumbers = gutterAElement.querySelectorAll('div.line-number[data-buffer-row]') + const aLabels = Array.from(aNumbers, e => e.textContent) + expect(aLabels).toEqual(['a - 0', 'a - 1', 'a - 2', 'a - 3', 'a - 4', 'a - 5']) + + const gutterBElement = gutterB.getElement() + const bNumbers = gutterBElement.querySelectorAll('div.line-number[data-buffer-row]') + const bLabels = Array.from(bNumbers, e => e.textContent) + expect(bLabels).toEqual(['b - 0', 'b - 1', 'b - 2', 'b - 3', 'b - 4', 'b - 5']) + }) }) describe('block decorations', () => {