diff --git a/spec/gutter-container-spec.coffee b/spec/gutter-container-spec.coffee deleted file mode 100644 index dc4af0b8c..000000000 --- a/spec/gutter-container-spec.coffee +++ /dev/null @@ -1,64 +0,0 @@ -Gutter = require '../src/gutter' -GutterContainer = require '../src/gutter-container' - -describe 'GutterContainer', -> - gutterContainer = null - fakeTextEditor = { - scheduleComponentUpdate: -> - } - - beforeEach -> - gutterContainer = new GutterContainer fakeTextEditor - - describe 'when initialized', -> - it 'it has no gutters', -> - expect(gutterContainer.getGutters().length).toBe 0 - - describe '::addGutter', -> - it 'creates a new gutter', -> - newGutter = gutterContainer.addGutter {'test-gutter', priority: 1} - expect(gutterContainer.getGutters()).toEqual [newGutter] - expect(newGutter.priority).toBe 1 - - it 'throws an error if the provided gutter name is already in use', -> - name = 'test-gutter' - gutterContainer.addGutter {name} - expect(gutterContainer.addGutter.bind(null, {name})).toThrow() - - it 'keeps added gutters sorted by ascending priority', -> - gutter1 = gutterContainer.addGutter {name: 'first', priority: 1} - gutter3 = gutterContainer.addGutter {name: 'third', priority: 3} - gutter2 = gutterContainer.addGutter {name: 'second', priority: 2} - expect(gutterContainer.getGutters()).toEqual [gutter1, gutter2, gutter3] - - describe '::removeGutter', -> - removedGutters = null - - beforeEach -> - gutterContainer = new GutterContainer fakeTextEditor - removedGutters = [] - gutterContainer.onDidRemoveGutter (gutterName) -> - removedGutters.push gutterName - - it 'removes the gutter if it is contained by this GutterContainer', -> - gutter = gutterContainer.addGutter {'test-gutter'} - expect(gutterContainer.getGutters()).toEqual [gutter] - gutterContainer.removeGutter gutter - expect(gutterContainer.getGutters().length).toBe 0 - expect(removedGutters).toEqual [gutter.name] - - it 'throws an error if the gutter is not within this GutterContainer', -> - fakeOtherTextEditor = {} - otherGutterContainer = new GutterContainer fakeOtherTextEditor - gutter = new Gutter 'gutter-name', otherGutterContainer - expect(gutterContainer.removeGutter.bind(null, gutter)).toThrow() - - describe '::destroy', -> - it 'clears its array of gutters and destroys custom gutters', -> - newGutter = gutterContainer.addGutter {'test-gutter', priority: 1} - newGutterSpy = jasmine.createSpy() - newGutter.onDidDestroy(newGutterSpy) - - gutterContainer.destroy() - expect(newGutterSpy).toHaveBeenCalled() - expect(gutterContainer.getGutters()).toEqual [] diff --git a/spec/gutter-container-spec.js b/spec/gutter-container-spec.js new file mode 100644 index 000000000..f41f1d220 --- /dev/null +++ b/spec/gutter-container-spec.js @@ -0,0 +1,77 @@ +const Gutter = require('../src/gutter') +const GutterContainer = require('../src/gutter-container') + +describe('GutterContainer', () => { + let gutterContainer = null + const fakeTextEditor = { + scheduleComponentUpdate () {} + } + + beforeEach(() => { + gutterContainer = new GutterContainer(fakeTextEditor) + }) + + describe('when initialized', () => + it('it has no gutters', () => { + expect(gutterContainer.getGutters().length).toBe(0) + }) + ) + + describe('::addGutter', () => { + it('creates a new gutter', () => { + const newGutter = gutterContainer.addGutter({'test-gutter': 'test-gutter', priority: 1}) + expect(gutterContainer.getGutters()).toEqual([newGutter]) + expect(newGutter.priority).toBe(1) + }) + + it('throws an error if the provided gutter name is already in use', () => { + const name = 'test-gutter' + gutterContainer.addGutter({name}) + expect(gutterContainer.addGutter.bind(null, {name})).toThrow() + }) + + it('keeps added gutters sorted by ascending priority', () => { + const gutter1 = gutterContainer.addGutter({name: 'first', priority: 1}) + const gutter3 = gutterContainer.addGutter({name: 'third', priority: 3}) + const gutter2 = gutterContainer.addGutter({name: 'second', priority: 2}) + expect(gutterContainer.getGutters()).toEqual([gutter1, gutter2, gutter3]) + }) + }) + + describe('::removeGutter', () => { + let removedGutters + + beforeEach(function () { + gutterContainer = new GutterContainer(fakeTextEditor) + removedGutters = [] + gutterContainer.onDidRemoveGutter(gutterName => removedGutters.push(gutterName)) + }) + + it('removes the gutter if it is contained by this GutterContainer', () => { + const gutter = gutterContainer.addGutter({'test-gutter': 'test-gutter'}) + expect(gutterContainer.getGutters()).toEqual([gutter]) + gutterContainer.removeGutter(gutter) + expect(gutterContainer.getGutters().length).toBe(0) + expect(removedGutters).toEqual([gutter.name]) + }) + + it('throws an error if the gutter is not within this GutterContainer', () => { + const fakeOtherTextEditor = {} + const otherGutterContainer = new GutterContainer(fakeOtherTextEditor) + const gutter = new Gutter('gutter-name', otherGutterContainer) + expect(gutterContainer.removeGutter.bind(null, gutter)).toThrow() + }) + }) + + describe('::destroy', () => + it('clears its array of gutters and destroys custom gutters', () => { + const newGutter = gutterContainer.addGutter({'test-gutter': 'test-gutter', priority: 1}) + const newGutterSpy = jasmine.createSpy() + newGutter.onDidDestroy(newGutterSpy) + + gutterContainer.destroy() + expect(newGutterSpy).toHaveBeenCalled() + expect(gutterContainer.getGutters()).toEqual([]) + }) +) +}) diff --git a/src/gutter-container.coffee b/src/gutter-container.coffee deleted file mode 100644 index 677fa4521..000000000 --- a/src/gutter-container.coffee +++ /dev/null @@ -1,87 +0,0 @@ -{Emitter} = require 'event-kit' -Gutter = require './gutter' - -module.exports = -class GutterContainer - constructor: (textEditor) -> - @gutters = [] - @textEditor = textEditor - @emitter = new Emitter - - scheduleComponentUpdate: -> - @textEditor.scheduleComponentUpdate() - - destroy: -> - # Create a copy, because `Gutter::destroy` removes the gutter from - # GutterContainer's @gutters. - guttersToDestroy = @gutters.slice(0) - for gutter in guttersToDestroy - gutter.destroy() if gutter.name isnt 'line-number' - @gutters = [] - @emitter.dispose() - - addGutter: (options) -> - options = options ? {} - gutterName = options.name - if gutterName is null - throw new Error('A name is required to create a gutter.') - if @gutterWithName(gutterName) - throw new Error('Tried to create a gutter with a name that is already in use.') - newGutter = new Gutter(this, options) - - inserted = false - # Insert the gutter into the gutters array, sorted in ascending order by 'priority'. - # This could be optimized, but there are unlikely to be many gutters. - for i in [0...@gutters.length] - if @gutters[i].priority >= newGutter.priority - @gutters.splice(i, 0, newGutter) - inserted = true - break - if not inserted - @gutters.push newGutter - @scheduleComponentUpdate() - @emitter.emit 'did-add-gutter', newGutter - return newGutter - - getGutters: -> - @gutters.slice() - - gutterWithName: (name) -> - for gutter in @gutters - if gutter.name is name then return gutter - null - - observeGutters: (callback) -> - callback(gutter) for gutter in @getGutters() - @onDidAddGutter callback - - onDidAddGutter: (callback) -> - @emitter.on 'did-add-gutter', callback - - onDidRemoveGutter: (callback) -> - @emitter.on 'did-remove-gutter', callback - - ### - Section: Private Methods - ### - - # Processes the destruction of the gutter. Throws an error if this gutter is - # not within this gutterContainer. - removeGutter: (gutter) -> - index = @gutters.indexOf(gutter) - if index > -1 - @gutters.splice(index, 1) - @scheduleComponentUpdate() - @emitter.emit 'did-remove-gutter', gutter.name - else - throw new Error 'The given gutter cannot be removed because it is not ' + - 'within this GutterContainer.' - - # The public interface is Gutter::decorateMarker or TextEditor::decorateMarker. - addGutterDecoration: (gutter, marker, options) -> - if gutter.name is 'line-number' - options.type = 'line-number' - else - options.type = 'gutter' - options.gutterName = gutter.name - @textEditor.decorateMarker(marker, options) diff --git a/src/gutter-container.js b/src/gutter-container.js new file mode 100644 index 000000000..3faece073 --- /dev/null +++ b/src/gutter-container.js @@ -0,0 +1,108 @@ +const {Emitter} = require('event-kit') +const Gutter = require('./gutter') + +module.exports = class GutterContainer { + constructor (textEditor) { + this.gutters = [] + this.textEditor = textEditor + this.emitter = new Emitter() + } + + scheduleComponentUpdate () { + this.textEditor.scheduleComponentUpdate() + } + + destroy () { + // Create a copy, because `Gutter::destroy` removes the gutter from + // GutterContainer's @gutters. + const guttersToDestroy = this.gutters.slice(0) + for (let gutter of guttersToDestroy) { + if (gutter.name !== 'line-number') { gutter.destroy() } + } + this.gutters = [] + this.emitter.dispose() + } + + addGutter (options) { + options = options || {} + const gutterName = options.name + if (gutterName === null) { + throw new Error('A name is required to create a gutter.') + } + if (this.gutterWithName(gutterName)) { + throw new Error('Tried to create a gutter with a name that is already in use.') + } + const newGutter = new Gutter(this, options) + + let inserted = false + // Insert the gutter into the gutters array, sorted in ascending order by 'priority'. + // This could be optimized, but there are unlikely to be many gutters. + for (let i = 0; i < this.gutters.length; i++) { + if (this.gutters[i].priority >= newGutter.priority) { + this.gutters.splice(i, 0, newGutter) + inserted = true + break + } + } + if (!inserted) { + this.gutters.push(newGutter) + } + this.scheduleComponentUpdate() + this.emitter.emit('did-add-gutter', newGutter) + return newGutter + } + + getGutters () { + return this.gutters.slice() + } + + gutterWithName (name) { + for (let gutter of this.gutters) { + if (gutter.name === name) { return gutter } + } + return null + } + + observeGutters (callback) { + for (let gutter of this.getGutters()) { callback(gutter) } + return this.onDidAddGutter(callback) + } + + onDidAddGutter (callback) { + return this.emitter.on('did-add-gutter', callback) + } + + onDidRemoveGutter (callback) { + return this.emitter.on('did-remove-gutter', callback) + } + + /* + Section: Private Methods + */ + + // Processes the destruction of the gutter. Throws an error if this gutter is + // not within this gutterContainer. + removeGutter (gutter) { + const index = this.gutters.indexOf(gutter) + if (index > -1) { + this.gutters.splice(index, 1) + this.scheduleComponentUpdate() + this.emitter.emit('did-remove-gutter', gutter.name) + } else { + throw new Error('The given gutter cannot be removed because it is not ' + + 'within this GutterContainer.' + ) + } + } + + // The public interface is Gutter::decorateMarker or TextEditor::decorateMarker. + addGutterDecoration (gutter, marker, options) { + if (gutter.name === 'line-number') { + options.type = 'line-number' + } else { + options.type = 'gutter' + } + options.gutterName = gutter.name + return this.textEditor.decorateMarker(marker, options) + } +}