mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
[Gutter] Create Gutter and GutterContainer w/ API to hide/show
This commit is contained in:
49
spec/gutter-container-spec.coffee
Normal file
49
spec/gutter-container-spec.coffee
Normal file
@@ -0,0 +1,49 @@
|
||||
Gutter = require '../src/gutter'
|
||||
GutterContainer = require '../src/gutter-container'
|
||||
|
||||
describe 'GutterContainer', ->
|
||||
gutterContainer = null
|
||||
beforeEach ->
|
||||
gutterContainer = new GutterContainer
|
||||
|
||||
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
|
||||
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', ->
|
||||
otherGutterContainer = new GutterContainer
|
||||
gutter = new Gutter 'gutter-name', otherGutterContainer
|
||||
expect(gutterContainer.removeGutter.bind(null, gutter)).toThrow()
|
||||
64
spec/gutter-spec.coffee
Normal file
64
spec/gutter-spec.coffee
Normal file
@@ -0,0 +1,64 @@
|
||||
Gutter = require '../src/gutter'
|
||||
|
||||
describe 'Gutter', ->
|
||||
fakeGutterContainer = {}
|
||||
name = 'name'
|
||||
|
||||
describe '::hide', ->
|
||||
it 'hides the gutter if it is visible.', ->
|
||||
options =
|
||||
name: name
|
||||
visible: true
|
||||
gutter = new Gutter fakeGutterContainer, options
|
||||
events = []
|
||||
gutter.onDidChangeVisible (gutter) ->
|
||||
events.push gutter.isVisible()
|
||||
|
||||
expect(gutter.isVisible()).toBe true
|
||||
gutter.hide()
|
||||
expect(gutter.isVisible()).toBe false
|
||||
expect(events).toEqual [false]
|
||||
gutter.hide()
|
||||
expect(gutter.isVisible()).toBe false
|
||||
# An event should only be emitted when the visibility changes.
|
||||
expect(events.length).toBe 1
|
||||
|
||||
describe '::show', ->
|
||||
it 'shows the gutter if it is hidden.', ->
|
||||
options =
|
||||
name: name
|
||||
visible: false
|
||||
gutter = new Gutter fakeGutterContainer, options
|
||||
events = []
|
||||
gutter.onDidChangeVisible (gutter) ->
|
||||
events.push gutter.isVisible()
|
||||
|
||||
expect(gutter.isVisible()).toBe false
|
||||
gutter.show()
|
||||
expect(gutter.isVisible()).toBe true
|
||||
expect(events).toEqual [true]
|
||||
gutter.show()
|
||||
expect(gutter.isVisible()).toBe true
|
||||
# An event should only be emitted when the visibility changes.
|
||||
expect(events.length).toBe 1
|
||||
|
||||
describe '::destroy', ->
|
||||
[mockGutterContainer, mockGutterContainerRemovedGutters] = []
|
||||
|
||||
beforeEach ->
|
||||
mockGutterContainerRemovedGutters = [];
|
||||
mockGutterContainer = removeGutter: (destroyedGutter) ->
|
||||
mockGutterContainerRemovedGutters.push destroyedGutter
|
||||
|
||||
it 'removes the gutter from its container.', ->
|
||||
gutter = new Gutter mockGutterContainer, {name}
|
||||
gutter.destroy()
|
||||
expect(mockGutterContainerRemovedGutters).toEqual([gutter])
|
||||
|
||||
it 'calls all callbacks registered on ::onDidDestroy.', ->
|
||||
gutter = new Gutter mockGutterContainer, {name}
|
||||
didDestroy = false
|
||||
gutter.onDidDestroy ->
|
||||
didDestroy = true
|
||||
gutter.destroy()
|
||||
expect(didDestroy).toBe true
|
||||
78
src/gutter-container.coffee
Normal file
78
src/gutter-container.coffee
Normal file
@@ -0,0 +1,78 @@
|
||||
{Emitter} = require 'event-kit'
|
||||
{Subscriber} = require 'emissary'
|
||||
Gutter = require './gutter'
|
||||
|
||||
# This class encapsulates the logic for adding and modifying a set of gutters.
|
||||
|
||||
module.exports =
|
||||
class GutterContainer
|
||||
Subscriber.includeInto(this)
|
||||
constructor: ->
|
||||
@gutters = []
|
||||
@emitter = new Emitter
|
||||
|
||||
destroy: ->
|
||||
@gutters = null
|
||||
@emitter.dispose()
|
||||
@unsubscribe()
|
||||
|
||||
# Creates and returns a {Gutter}.
|
||||
# * `options` An {Object} with the following fields:
|
||||
# * `name` (required) A unique {String} to identify this gutter.
|
||||
# * `priority` (optional) A {Number} that determines stacking order between
|
||||
# gutters. Lower priority items are forced closer to the edges of the
|
||||
# window. (default: -100)
|
||||
# * `visible` (optional) {Boolean} specifying whether the gutter is visible
|
||||
# initially after being created. (default: true)
|
||||
addGutter: (options) ->
|
||||
options = options ? {}
|
||||
gutterName = options.name
|
||||
if gutterName == 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 !inserted
|
||||
@gutters.push newGutter
|
||||
return newGutter
|
||||
|
||||
getGutters: ->
|
||||
@gutters.slice()
|
||||
|
||||
gutterWithName: (name) ->
|
||||
for gutter in @gutters
|
||||
if gutter.name == name then return gutter
|
||||
null
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
###
|
||||
|
||||
# @param callback: function( nameOfRemovedGutter )
|
||||
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)
|
||||
@unsubscribe gutter
|
||||
@emitter.emit 'did-remove-gutter', gutter.name
|
||||
else
|
||||
throw new Error 'The given gutter cannot be removed because it is not ' +
|
||||
'within this GutterContainer.'
|
||||
58
src/gutter.coffee
Normal file
58
src/gutter.coffee
Normal file
@@ -0,0 +1,58 @@
|
||||
{Emitter} = require 'event-kit'
|
||||
|
||||
# Public: This class represents a gutter within a TextEditor.
|
||||
|
||||
DefaultPriority = -100
|
||||
|
||||
module.exports =
|
||||
class Gutter
|
||||
# * `gutterContainer` The {GutterContainer} object to which this gutter belongs.
|
||||
# * `options` An {Object} with the following fields:
|
||||
# * `name` (required) A unique {String} to identify this gutter.
|
||||
# * `priority` (optional) A {Number} that determines stacking order between
|
||||
# gutters. Lower priority items are forced closer to the edges of the
|
||||
# window. (default: -100)
|
||||
# * `visible` (optional) {Boolean} specifying whether the gutter is visible
|
||||
# initially after being created. (default: true)
|
||||
constructor: (gutterContainer, options) ->
|
||||
@gutterContainer = gutterContainer
|
||||
@name = options?.name
|
||||
@priority = options?.priority ? DefaultPriority
|
||||
@visible = options?.visible ? true
|
||||
|
||||
@emitter = new Emitter
|
||||
|
||||
destroy: ->
|
||||
@gutterContainer.removeGutter(this)
|
||||
@emitter.emit 'did-destroy'
|
||||
@emitter.dispose()
|
||||
|
||||
hide: ->
|
||||
if @visible
|
||||
@visible = false
|
||||
@emitter.emit 'did-change-visible', this
|
||||
|
||||
show: ->
|
||||
if not @visible
|
||||
@visible = true
|
||||
@emitter.emit 'did-change-visible', this
|
||||
|
||||
isVisible: ->
|
||||
@visible
|
||||
|
||||
# Calls your `callback` when the {Gutter}'s' visibility changes.
|
||||
#
|
||||
# * `callback` {Function}
|
||||
# * `gutter` The {Gutter} whose visibility changed.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidChangeVisible: (callback) ->
|
||||
@emitter.on 'did-change-visible', callback
|
||||
|
||||
# Calls your `callback` when the {Gutter} is destroyed
|
||||
#
|
||||
# * `callback` {Function}
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidDestroy: (callback) ->
|
||||
@emitter.on 'did-destroy', callback
|
||||
Reference in New Issue
Block a user