Merge pull request #10737 from natalieogle/mostRecentlyViewed

Add MRU tab switching functionality
This commit is contained in:
Ben Ogle
2016-02-28 12:32:52 -08:00
6 changed files with 142 additions and 12 deletions

View File

@@ -73,8 +73,10 @@
'cmd-alt-right': 'pane:show-next-item'
'ctrl-pageup': 'pane:show-previous-item'
'ctrl-pagedown': 'pane:show-next-item'
'ctrl-tab': 'pane:show-next-item'
'ctrl-shift-tab': 'pane:show-previous-item'
'ctrl-tab': 'pane:show-next-recently-used-item'
'ctrl-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
'ctrl-shift-tab': 'pane:show-previous-recently-used-item'
'ctrl-shift-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
'cmd-=': 'window:increase-font-size'
'cmd-+': 'window:increase-font-size'
'cmd--': 'window:decrease-font-size'

View File

@@ -46,8 +46,10 @@
'pagedown': 'core:page-down'
'backspace': 'core:backspace'
'shift-backspace': 'core:backspace'
'ctrl-tab': 'pane:show-next-item'
'ctrl-shift-tab': 'pane:show-previous-item'
'ctrl-tab': 'pane:show-next-recently-used-item'
'ctrl-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
'ctrl-shift-tab': 'pane:show-previous-recently-used-item'
'ctrl-shift-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
'ctrl-pageup': 'pane:show-previous-item'
'ctrl-pagedown': 'pane:show-next-item'
'ctrl-up': 'core:move-up'

View File

@@ -52,8 +52,10 @@
'pagedown': 'core:page-down'
'backspace': 'core:backspace'
'shift-backspace': 'core:backspace'
'ctrl-tab': 'pane:show-next-item'
'ctrl-shift-tab': 'pane:show-previous-item'
'ctrl-tab': 'pane:show-next-recently-used-item'
'ctrl-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
'ctrl-shift-tab': 'pane:show-previous-recently-used-item'
'ctrl-shift-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
'ctrl-pageup': 'pane:show-previous-item'
'ctrl-pagedown': 'pane:show-next-item'
'ctrl-shift-up': 'core:move-up'

View File

@@ -183,6 +183,35 @@ describe "Pane", ->
pane.activateItem(itemD, true)
expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'B', 'D']
describe "::activateNextRecentlyUsedItem() and ::activatePreviousRecentlyUsedItem()", ->
it "sets the active item to the next/previous item in the itemStack, looping around at either end", ->
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C"), new Item("D"), new Item("E")]))
[item1, item2, item3, item4, item5] = pane.getItems()
pane.itemStack = [item3, item1, item2, item5, item4]
pane.activateItem(item4)
expect(pane.getActiveItem()).toBe item4
pane.activateNextRecentlyUsedItem()
expect(pane.getActiveItem()).toBe item5
pane.activateNextRecentlyUsedItem()
expect(pane.getActiveItem()).toBe item2
pane.activatePreviousRecentlyUsedItem()
expect(pane.getActiveItem()).toBe item5
pane.activatePreviousRecentlyUsedItem()
expect(pane.getActiveItem()).toBe item4
pane.activatePreviousRecentlyUsedItem()
expect(pane.getActiveItem()).toBe item3
pane.activatePreviousRecentlyUsedItem()
expect(pane.getActiveItem()).toBe item1
pane.activateNextRecentlyUsedItem()
expect(pane.getActiveItem()).toBe item3
pane.activateNextRecentlyUsedItem()
expect(pane.getActiveItem()).toBe item4
pane.activateNextRecentlyUsedItem()
pane.moveActiveItemToTopOfStack()
expect(pane.getActiveItem()).toBe item5
expect(pane.itemStack[4]).toBe item5
describe "::activateNextItem() and ::activatePreviousItem()", ->
it "sets the active item to the next/previous item, looping around at either end", ->
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
@@ -249,7 +278,7 @@ describe "Pane", ->
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
[item1, item2, item3] = pane.getItems()
it "removes the item from the items list and destroyes it", ->
it "removes the item from the items list and destroys it", ->
expect(pane.getActiveItem()).toBe item1
pane.destroyItem(item2)
expect(item2 in pane.getItems()).toBe false
@@ -260,6 +289,19 @@ describe "Pane", ->
expect(item1 in pane.getItems()).toBe false
expect(item1.isDestroyed()).toBe true
it "removes the item from the itemStack", ->
pane.itemStack = [item2, item3, item1]
pane.activateItem(item1)
expect(pane.getActiveItem()).toBe item1
pane.destroyItem(item3)
expect(pane.itemStack).toEqual [item2, item1]
expect(pane.getActiveItem()).toBe item1
pane.destroyItem(item1)
expect(pane.itemStack).toEqual [item2]
expect(pane.getActiveItem()).toBe item2
it "invokes ::onWillDestroyItem() observers before destroying the item", ->
events = []
pane.onWillDestroyItem (event) ->
@@ -894,3 +936,30 @@ describe "Pane", ->
pane.focus()
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.focused).toBe true
it "can serialize and deserialize the order of the items in the itemStack", ->
[item1, item2, item3] = pane.getItems()
pane.itemStack = [item3, item1, item2]
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.itemStack).toEqual pane.itemStack
expect(newPane.itemStack[2]).toEqual item2
it "builds the itemStack if the itemStack is not serialized", ->
[item1, item2, item3] = pane.getItems()
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.getItems()).toEqual newPane.itemStack
it "rebuilds the itemStack if items.length does not match itemStack.length", ->
[item1, item2, item3] = pane.getItems()
pane.itemStack = [item2, item3]
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.getItems()).toEqual newPane.itemStack
it "does not serialize the reference to the items in the itemStack for pane items that will not be serialized", ->
[item1, item2, item3] = pane.getItems()
pane.itemStack = [item2, item1, item3]
unserializable = {}
pane.activateItem(unserializable)
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.itemStack).toEqual [item2, item1, item3]

View File

@@ -20,7 +20,7 @@ class Pane extends Model
focused: false
@deserialize: (state, {deserializers, applicationDelegate, config, notifications}) ->
{items, activeItemURI, activeItemUri} = state
{items, itemStackIndices, activeItemURI, activeItemUri} = state
activeItemURI ?= activeItemUri
state.items = compact(items.map (itemState) -> deserializers.deserialize(itemState))
state.activeItem = find state.items, (item) ->
@@ -44,18 +44,23 @@ class Pane extends Model
@emitter = new Emitter
@subscriptionsPerItem = new WeakMap
@items = []
@itemStack = []
@addItems(compact(params?.items ? []))
@setActiveItem(@items[0]) unless @getActiveItem()?
@addItemsToStack(params?.itemStackIndices ? [])
@setFlexScale(params?.flexScale ? 1)
serialize: ->
if typeof @activeItem?.getURI is 'function'
activeItemURI = @activeItem.getURI()
itemsToBeSerialized = compact(@items.map((item) -> item if typeof item.serialize is 'function'))
itemStackIndices = (itemsToBeSerialized.indexOf(item) for item in @itemStack when typeof item.serialize is 'function')
deserializer: 'Pane'
id: @id
items: compact(@items.map((item) -> item.serialize?()))
items: itemsToBeSerialized.map((item) -> item.serialize())
itemStackIndices: itemStackIndices
activeItemURI: activeItemURI
focused: @focused
flexScale: @flexScale
@@ -283,12 +288,29 @@ class Pane extends Model
# Returns a pane item.
getActiveItem: -> @activeItem
setActiveItem: (activeItem) ->
setActiveItem: (activeItem, options) ->
{modifyStack} = options if options?
unless activeItem is @activeItem
@addItemToStack(activeItem) unless modifyStack is false
@activeItem = activeItem
@emitter.emit 'did-change-active-item', @activeItem
@activeItem
# Build the itemStack after deserializing
addItemsToStack: (itemStackIndices) ->
if @items.length > 0
if itemStackIndices.length is 0 or itemStackIndices.length isnt @items.length or itemStackIndices.indexOf(-1) >= 0
itemStackIndices = (i for i in [0..@items.length-1])
for itemIndex in itemStackIndices
@addItemToStack(@items[itemIndex])
return
# Add item (or move item) to the end of the itemStack
addItemToStack: (newItem) ->
index = @itemStack.indexOf(newItem)
@itemStack.splice(index, 1) unless index is -1
@itemStack.push(newItem)
# Return an {TextEditor} if the pane item is an {TextEditor}, or null otherwise.
getActiveEditor: ->
@activeItem if @activeItem instanceof TextEditor
@@ -301,6 +323,29 @@ class Pane extends Model
itemAtIndex: (index) ->
@items[index]
# Makes the next item in the itemStack active.
activateNextRecentlyUsedItem: ->
if @items.length > 1
@itemStackIndex = @itemStack.length - 1 unless @itemStackIndex?
@itemStackIndex = @itemStack.length if @itemStackIndex is 0
@itemStackIndex = @itemStackIndex - 1
nextRecentlyUsedItem = @itemStack[@itemStackIndex]
@setActiveItem(nextRecentlyUsedItem, modifyStack: false)
# Makes the previous item in the itemStack active.
activatePreviousRecentlyUsedItem: ->
if @items.length > 1
if @itemStackIndex + 1 is @itemStack.length or not @itemStackIndex?
@itemStackIndex = -1
@itemStackIndex = @itemStackIndex + 1
previousRecentlyUsedItem = @itemStack[@itemStackIndex]
@setActiveItem(previousRecentlyUsedItem, modifyStack: false)
# Moves the active item to the end of the itemStack once the ctrl key is lifted
moveActiveItemToTopOfStack: ->
delete @itemStackIndex
@addItemToStack(@activeItem)
# Public: Makes the next item active.
activateNextItem: ->
index = @getActiveItemIndex()
@@ -425,9 +470,8 @@ class Pane extends Model
removeItem: (item, moved) ->
index = @items.indexOf(item)
return if index is -1
@pendingItem = null if @getPendingItem() is item
@removeItemFromStack(item)
@emitter.emit 'will-remove-item', {item, index, destroyed: not moved, moved}
@unsubscribeFromItem(item)
@@ -443,6 +487,14 @@ class Pane extends Model
@container?.didDestroyPaneItem({item, index, pane: this}) unless moved
@destroy() if @items.length is 0 and @config.get('core.destroyEmptyPanes')
# Remove the given item from the itemStack.
#
# * `item` The item to remove.
# * `index` {Number} indicating the index to which to remove the item from the itemStack.
removeItemFromStack: (item) ->
index = @itemStack.indexOf(item)
@itemStack.splice(index, 1) unless index is -1
# Public: Move the given item to the given index.
#
# * `item` The item to move.

View File

@@ -2,6 +2,9 @@
module.exports = ({commandRegistry, commandInstaller, config}) ->
commandRegistry.add 'atom-workspace',
'pane:show-next-recently-used-item': -> @getModel().getActivePane().activateNextRecentlyUsedItem()
'pane:show-previous-recently-used-item': -> @getModel().getActivePane().activatePreviousRecentlyUsedItem()
'pane:move-active-item-to-top-of-stack': -> @getModel().getActivePane().moveActiveItemToTopOfStack()
'pane:show-next-item': -> @getModel().getActivePane().activateNextItem()
'pane:show-previous-item': -> @getModel().getActivePane().activatePreviousItem()
'pane:show-item-1': -> @getModel().getActivePane().activateItemAtIndex(0)