diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index c532f559d..7f240f958 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -974,6 +974,23 @@ describe('TextEditorComponent', () => { }) }) + describe('custom gutter decorations', () => { + it('arranges custom gutters based on their priority', async () => { + const {component, element, editor} = buildComponent() + editor.addGutter({name: 'e', priority: 2}) + editor.addGutter({name: 'a', priority: -2}) + editor.addGutter({name: 'd', priority: 1}) + editor.addGutter({name: 'b', priority: -1}) + editor.addGutter({name: 'c', priority: 0}) + + await component.getNextUpdatePromise() + const gutters = component.refs.gutterContainer.querySelectorAll('.gutter') + expect(Array.from(gutters).map((g) => g.getAttribute('gutter-name'))).toEqual([ + 'a', 'b', 'c', 'line-number', 'd', 'e' + ]) + }) + }) + describe('mouse input', () => { describe('on the lines', () => { it('positions the cursor on single-click', async () => { diff --git a/src/text-editor-component.js b/src/text-editor-component.js index f72d90175..760587929 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -74,12 +74,14 @@ class TextEditorComponent { this.lastKeydown = null this.lastKeydownBeforeKeypress = null this.accentedCharacterMenuIsOpen = false + this.guttersToRender = [] this.decorationsToRender = { lineNumbers: new Map(), lines: new Map(), highlights: new Map(), cursors: [], - overlays: [] + overlays: [], + customGutter: new Map() } this.decorationsToMeasure = { highlights: new Map(), @@ -134,6 +136,7 @@ class TextEditorComponent { if (this.pendingAutoscroll) this.autoscrollVertically() this.populateVisibleRowRange() this.queryScreenLinesToRender() + this.queryGuttersToRender() this.queryDecorationsToRender() this.shouldRenderDummyScrollbars = !this.refreshedScrollbarStyle etch.updateSync(this) @@ -228,10 +231,22 @@ class TextEditorComponent { const innerStyle = { willChange: 'transform', - backgroundColor: 'inherit' + backgroundColor: 'inherit', + display: 'flex' } + + let gutterNodes if (this.measurements) { innerStyle.transform = `translateY(${-this.getScrollTop()}px)` + gutterNodes = this.guttersToRender.map((gutter) => { + if (gutter.name === 'line-number') { + return this.renderLineNumberGutter() + } else { + return this.renderCustomGutter(gutter.name) + } + }) + } else { + gutterNodes = this.renderLineNumberGutter() } return $.div( @@ -244,9 +259,7 @@ class TextEditorComponent { backgroundColor: 'inherit' } }, - $.div({style: innerStyle}, - this.renderLineNumberGutter() - ) + $.div({style: innerStyle}, gutterNodes) ) } @@ -300,7 +313,7 @@ class TextEditorComponent { { ref: 'lineNumberGutter', className: 'gutter line-numbers', - 'gutter-name': 'line-number' + attributes: {'gutter-name': 'line-number'} }, $.div({className: 'line-number'}, '0'.repeat(maxLineNumberDigits), @@ -310,6 +323,19 @@ class TextEditorComponent { } } + renderCustomGutter (gutterName) { + return $.div( + { + className: 'gutter', + attributes: {'gutter-name': gutterName} + }, + $.div({ + className: 'custom-decorations', + style: {height: this.getScrollHeight() + 'px'} + }) + ) + } + renderScrollContainer () { const style = { position: 'absolute', @@ -614,13 +640,19 @@ class TextEditorComponent { return this.renderedScreenLines[row - this.getRenderedStartRow()] } + queryGuttersToRender () { + this.guttersToRender = this.props.model.getGutters() + } + queryDecorationsToRender () { this.decorationsToRender.lineNumbers.clear() this.decorationsToRender.lines.clear() this.decorationsToRender.overlays.length = 0 + this.decorationsToRender.customGutter.clear() this.decorationsToMeasure.highlights.clear() this.decorationsToMeasure.cursors.length = 0 + const decorationsByMarker = this.props.model.decorationManager.decorationPropertiesByMarkerForScreenRowRange( this.getRenderedStartRow(), @@ -1589,6 +1621,8 @@ class TextEditorComponent { this.disposables.add(model.selectionsMarkerLayer.onDidUpdate(scheduleUpdate)) this.disposables.add(model.displayLayer.onDidChangeSync(scheduleUpdate)) this.disposables.add(model.onDidUpdateDecorations(scheduleUpdate)) + this.disposables.add(model.onDidAddGutter(scheduleUpdate)) + this.disposables.add(model.onDidRemoveGutter(scheduleUpdate)) this.disposables.add(model.onDidRequestAutoscroll(this.didRequestAutoscroll.bind(this))) } @@ -2038,7 +2072,7 @@ class LineNumberGutterComponent { return $.div( { className: 'gutter line-numbers', - 'gutter-name': 'line-number', + attributes: {'gutter-name': 'line-number'}, style: { contain: 'strict', overflow: 'hidden',