From 43e34d4f51a899071fb29af6cc14400113cef7ae Mon Sep 17 00:00:00 2001 From: Dave Rael Date: Mon, 4 Jan 2016 05:59:11 -0700 Subject: [PATCH 01/32] :checkered_flag: Use PowerShell to get User path in Windows when installing via Squirrel, replacing reg.exe that fails to execute if registry editing is disabled in the group policy. This was causing the user Path to get deleted if registry editing is disabled. The workaround that was in place kept the existing path, but failed to add Atom to it. --- src/browser/squirrel-update.coffee | 50 +++++++++++++----------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/src/browser/squirrel-update.coffee b/src/browser/squirrel-update.coffee index 0e4743a21..4efc089d7 100644 --- a/src/browser/squirrel-update.coffee +++ b/src/browser/squirrel-update.coffee @@ -11,9 +11,11 @@ exeName = path.basename(process.execPath) if process.env.SystemRoot system32Path = path.join(process.env.SystemRoot, 'System32') regPath = path.join(system32Path, 'reg.exe') + powershellPath = path.join(system32Path, 'WindowsPowerShell', 'v1.0', 'powershell.exe') setxPath = path.join(system32Path, 'setx.exe') else regPath = 'reg.exe' + powershellPath = 'powershell.exe' setxPath = 'setx.exe' # Registry keys used for context menu @@ -43,11 +45,23 @@ spawn = (command, args, callback) -> error?.code ?= code error?.stdout ?= stdout callback?(error, stdout) + # This is necessary if using Powershell 2 on Windows 7 to get the events to raise + # http://stackoverflow.com/questions/9155289/calling-powershell-from-nodejs + spawnedProcess.stdin.end() + # Spawn reg.exe and callback when it completes spawnReg = (args, callback) -> spawn(regPath, args, callback) +# Spawn powershell.exe and callback when it completes +spawnPowershell = (args, callback) -> + args.unshift('-command') + args.unshift('RemoteSigned') + args.unshift('-ExecutionPolicy') + args.unshift('-noprofile') + spawn(powershellPath, args, callback) + # Spawn setx.exe and callback when it completes spawnSetx = (args, callback) -> spawn(setxPath, args, callback) @@ -85,37 +99,17 @@ isAscii = (text) -> # Get the user's PATH environment variable registry value. getPath = (callback) -> - spawnReg ['query', environmentKeyPath, '/v', 'Path'], (error, stdout) -> + spawnPowershell ['[environment]::GetEnvironmentVariable(\'Path\',\'User\')'], (error, stdout) -> if error? - if error.code is 1 - # FIXME Don't overwrite path when reading value is disabled - # https://github.com/atom/atom/issues/5092 - if stdout.indexOf('ERROR: Registry editing has been disabled by your administrator.') isnt -1 - return callback(error) + return callback(error) - # The query failed so the Path does not exist yet in the registry - return callback(null, '') - else - return callback(error) - - # Registry query output is in the form: - # - # HKEY_CURRENT_USER\Environment - # Path REG_SZ C:\a\folder\on\the\path;C\another\folder - # - - lines = stdout.split(/[\r\n]+/).filter (line) -> line - segments = lines[lines.length - 1]?.split(' ') - if segments[1] is 'Path' and segments.length >= 3 - pathEnv = segments?[3..].join(' ') - if isAscii(pathEnv) - callback(null, pathEnv) - else - # FIXME Don't corrupt non-ASCII PATH values - # https://github.com/atom/atom/issues/5063 - callback(new Error('PATH contains non-ASCII values')) + pathOutput = stdout.replace(/^\s+|\s+$/g, '') + if isAscii(pathOutput) + callback(null, pathOutput) else - callback(new Error('Registry query for PATH failed')) + # FIXME Don't corrupt non-ASCII PATH values + # https://github.com/atom/atom/issues/5063 + callback(new Error('PATH contains non-ASCII values')) # Uninstall the Open with Atom explorer context menu items via the registry. uninstallContextMenu = (callback) -> From 91253910ef5b966e40f57b74366da5a0cec48144 Mon Sep 17 00:00:00 2001 From: Dave Rael Date: Wed, 6 Jan 2016 12:38:55 -0700 Subject: [PATCH 02/32] :checkered_flag: Fix corrupting Windows Paths containing non-Ascii characters Remove workaround that just didn't update the path at all - now including the Atom path update and preserving the non-Ascii characters --- src/browser/squirrel-update.coffee | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/browser/squirrel-update.coffee b/src/browser/squirrel-update.coffee index 4efc089d7..f9df3c0b5 100644 --- a/src/browser/squirrel-update.coffee +++ b/src/browser/squirrel-update.coffee @@ -56,6 +56,9 @@ spawnReg = (args, callback) -> # Spawn powershell.exe and callback when it completes spawnPowershell = (args, callback) -> + # set encoding and execute the command, capture the output, and return it via .NET's console in order to have consistent UTF-8 encoding + # http://stackoverflow.com/questions/22349139/utf-8-output-from-powershell + args[0] = "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8\r\n$output=#{args[0]}\r\n[Console]::WriteLine($output)" args.unshift('-command') args.unshift('RemoteSigned') args.unshift('-ExecutionPolicy') @@ -90,13 +93,6 @@ installContextMenu = (callback) -> installMenu directoryKeyPath, '%1', -> installMenu(backgroundKeyPath, '%V', callback) -isAscii = (text) -> - index = 0 - while index < text.length - return false if text.charCodeAt(index) > 127 - index++ - true - # Get the user's PATH environment variable registry value. getPath = (callback) -> spawnPowershell ['[environment]::GetEnvironmentVariable(\'Path\',\'User\')'], (error, stdout) -> @@ -104,12 +100,7 @@ getPath = (callback) -> return callback(error) pathOutput = stdout.replace(/^\s+|\s+$/g, '') - if isAscii(pathOutput) - callback(null, pathOutput) - else - # FIXME Don't corrupt non-ASCII PATH values - # https://github.com/atom/atom/issues/5063 - callback(new Error('PATH contains non-ASCII values')) + callback(null, pathOutput) # Uninstall the Open with Atom explorer context menu items via the registry. uninstallContextMenu = (callback) -> From a26ac5b363ad4c3c187dd337c556c07997bde489 Mon Sep 17 00:00:00 2001 From: Dave Rael Date: Mon, 11 Jan 2016 05:32:39 -0700 Subject: [PATCH 03/32] :art: Improve readability of multi-line command --- src/browser/squirrel-update.coffee | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/browser/squirrel-update.coffee b/src/browser/squirrel-update.coffee index f9df3c0b5..41e103363 100644 --- a/src/browser/squirrel-update.coffee +++ b/src/browser/squirrel-update.coffee @@ -58,7 +58,12 @@ spawnReg = (args, callback) -> spawnPowershell = (args, callback) -> # set encoding and execute the command, capture the output, and return it via .NET's console in order to have consistent UTF-8 encoding # http://stackoverflow.com/questions/22349139/utf-8-output-from-powershell - args[0] = "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8\r\n$output=#{args[0]}\r\n[Console]::WriteLine($output)" + # to address https://github.com/atom/atom/issues/5063 + args[0] = """ + [Console]::OutputEncoding=[System.Text.Encoding]::UTF8 + $output=#{args[0]} + [Console]::WriteLine($output) + """ args.unshift('-command') args.unshift('RemoteSigned') args.unshift('-ExecutionPolicy') From a487110521ef5f546f5d5b6c7be4ab3d6afe83ab Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Feb 2016 18:19:13 -0800 Subject: [PATCH 04/32] Refactor pending state to live in pane instead of items * New public API `workspace.setItemNotPending` that packages can use to set an item to set an item to not pending (e.g. when the user interacts with the item) * Pending state for newly opened items with `{pending: true}` is now tracked by `Pane` instead of the item, and packages like `tabs` that query this information now get it from the Pane. --- src/pane.coffee | 37 +++++++++++++++++++++++++++---------- src/text-editor.coffee | 21 ++++++--------------- src/workspace.coffee | 10 ++++++++-- 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/pane.coffee b/src/pane.coffee index 9dff81d5c..0dec5cbce 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -53,7 +53,7 @@ class Pane extends Model items: compact(@items.map((item) -> item.serialize?())) activeItemURI: activeItemURI focused: @focused - flexScale: @flexScale + flexScale: @flexScale # TODO: is it okay to not serialize pending state? does it need to be restored? getParent: -> @parent @@ -342,13 +342,15 @@ class Pane extends Model # Public: Make the given item *active*, causing it to be displayed by # the pane's view. - activateItem: (item) -> + # + # * `pending` TODO + activateItem: (item, pending=false) -> if item? - if @activeItem?.isPending?() + if @isItemPending(@activeItem) index = @getActiveItemIndex() else index = @getActiveItemIndex() + 1 - @addItem(item, index, false) + @addItem(item, index, false, pending) @setActiveItem(item) # Public: Add the given item to the pane. @@ -357,19 +359,18 @@ class Pane extends Model # view. # * `index` (optional) {Number} indicating the index at which to add the item. # If omitted, the item is added after the current active item. + # * `pending` TODO # # Returns the added item. - addItem: (item, index=@getActiveItemIndex() + 1, moved=false) -> + addItem: (item, index=@getActiveItemIndex() + 1, moved=false, pending=false) -> throw new Error("Pane items must be objects. Attempted to add item #{item}.") unless item? and typeof item is 'object' throw new Error("Adding a pane item with URI '#{item.getURI?()}' that has already been destroyed") if item.isDestroyed?() return if item in @items - if item.isPending?() - for existingItem, i in @items - if existingItem.isPending?() - @destroyItem(existingItem) - break + pendingItem = @getPendingItem() + @destroyItem(pendingItem) if pendingItem? + @setPendingItem(item) if pending if typeof item.onDidDestroy is 'function' @itemSubscriptions.set item, item.onDidDestroy => @removeItem(item, false) @@ -379,6 +380,20 @@ class Pane extends Model @setActiveItem(item) unless @getActiveItem()? item + setPendingItem: (item) => + @pendingItem = item + @emitter.emit 'did-terminate-pending-state' if not item + + getPendingItem: => + @pendingItem + + isItemPending: (item) => + @pendingItem is item + + onDidTerminatePendingState: (callback) => + @emitter.on 'did-terminate-pending-state', -> + callback() + # Public: Add the given items to the pane. # # * `items` An {Array} of items to add. Items can be views or models with @@ -397,6 +412,8 @@ class Pane extends Model index = @items.indexOf(item) return if index is -1 + @pendingItem = null if @isItemPending(item) + @emitter.emit 'will-remove-item', {item, index, destroyed: not moved, moved} @unsubscribeFromItem(item) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index c0a6f2057..e8207ca1e 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -92,7 +92,7 @@ class TextEditor extends Model softWrapped, @displayBuffer, @selectionsMarkerLayer, buffer, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode, @config, @notificationManager, @packageManager, @clipboard, @viewRegistry, @grammarRegistry, - @project, @assert, @applicationDelegate, @pending + @project, @assert, @applicationDelegate } = params throw new Error("Must pass a config parameter when constructing TextEditors") unless @config? @@ -111,6 +111,7 @@ class TextEditor extends Model @cursors = [] @cursorsByMarkerId = new Map @selections = [] + @bufferHasChanged = false buffer ?= new TextBuffer @displayBuffer ?= new DisplayBuffer({ @@ -151,7 +152,7 @@ class TextEditor extends Model firstVisibleScreenColumn: @getFirstVisibleScreenColumn() displayBuffer: @displayBuffer.serialize() selectionsMarkerLayerId: @selectionsMarkerLayer.id - pending: @isPending() + bufferHasChanged: @bufferHasChanged subscribeToBuffer: -> @buffer.retain() @@ -163,9 +164,9 @@ class TextEditor extends Model @disposables.add @buffer.onDidChangeEncoding => @emitter.emit 'did-change-encoding', @getEncoding() @disposables.add @buffer.onDidDestroy => @destroy() - if @pending - @disposables.add @buffer.onDidChangeModified => - @terminatePendingState() if @buffer.isModified() + @disposables.add @buffer.onDidChangeModified => + atom.workspace.setItemNotPending(this) if not @bufferHasChanged and @buffer.isModified() + @bufferHasChanged = true @preserveCursorPositionOnBufferReload() @@ -575,13 +576,6 @@ class TextEditor extends Model getEditorWidthInChars: -> @displayBuffer.getEditorWidthInChars() - onDidTerminatePendingState: (callback) -> - @emitter.on 'did-terminate-pending-state', callback - - terminatePendingState: -> - return if not @pending - @pending = false - @emitter.emit 'did-terminate-pending-state' ### Section: File Details @@ -666,9 +660,6 @@ class TextEditor extends Model # Essential: Returns {Boolean} `true` if this editor has no content. isEmpty: -> @buffer.isEmpty() - # Returns {Boolean} `true` if this editor is pending and `false` if it is permanent. - isPending: -> Boolean(@pending) - # Copies the current file path to the native clipboard. copyPathToClipboard: (relative = false) -> if filePath = @getPath() diff --git a/src/workspace.coffee b/src/workspace.coffee index 0bfff7e0f..1f6648271 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -477,7 +477,7 @@ class Workspace extends Model if uri? if item = pane.itemForURI(uri) - item.terminatePendingState?() if item.isPending?() and not options.pending + pane.setPendingItem(null) if not options.pending item ?= opener(uri, options) for opener in @getOpeners() when not item try @@ -500,7 +500,7 @@ class Workspace extends Model return item if pane.isDestroyed() @itemOpened(item) - pane.activateItem(item) if activateItem + pane.activateItem(item, options.pending) if activateItem pane.activate() if activatePane initialLine = initialColumn = 0 @@ -515,6 +515,12 @@ class Workspace extends Model @emitter.emit 'did-open', {uri, pane, item, index} item + setItemNotPending: (item) => + for pane in @getPanes() + if item is pane.getPendingItem() + pane.setPendingItem(null) + break + openTextFile: (uri, options) -> filePath = @project.resolvePath(uri) From d0ffbca845ca1f2f966b5f0b950c7b147c6e233a Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Thu, 25 Feb 2016 10:49:11 -0800 Subject: [PATCH 05/32] :lipstick: and :memo: for pending state --- src/pane.coffee | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/pane.coffee b/src/pane.coffee index 0dec5cbce..a01703ce5 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -8,6 +8,11 @@ TextEditor = require './text-editor' # Panes can contain multiple items, one of which is *active* at a given time. # The view corresponding to the active item is displayed in the interface. In # the default configuration, tabs are also displayed for each item. +# +# Each pane may also contain one *pending* item. When a pending item is added +# to a pane, it will replace the currently pending item, if any, instead of +# simply being added. In the default configuration, the text in the tab for +# pending items is shown in italics. module.exports = class Pane extends Model container: undefined @@ -380,19 +385,47 @@ class Pane extends Model @setActiveItem(item) unless @getActiveItem()? item + clearPendingItem: => + @setPendingItem(null) + setPendingItem: (item) => + # TODO: figure out events for changing/clearing pending item @pendingItem = item + @emitter.emit 'did-change-pending-item', @pendingItem @emitter.emit 'did-terminate-pending-state' if not item + # Public: Get the pending pane item in this pane, if any. + # + # Returns a pane item or `null`. getPendingItem: => - @pendingItem + @pendingItem or null isItemPending: (item) => @pendingItem is item + # Invoke the given callback when the value of {::getPendingItem} changes. + # + # * `callback` {Function} to be called with when the pending item changes. + # * `pendingItem` The current pending item, or `null`. + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidChangePendingItem: (callback) => + @emitter.on 'did-change-pending-item', callback + + # Public: Invoke the given callback with the current and future values of + # {::getPendingItem}. + # + # * `callback` {Function} to be called with the current and future pending + # items. + # * `pendingItem` The current pending item. + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + observePendingItem: (callback) -> + callback(@getPendingItem()) + @onDidChangePendingItem(callback) + onDidTerminatePendingState: (callback) => - @emitter.on 'did-terminate-pending-state', -> - callback() + @emitter.on 'did-terminate-pending-state', callback # Public: Add the given items to the pane. # From 1aa0cec4115d2d3ced6f7059f23676093ac1c7bb Mon Sep 17 00:00:00 2001 From: joshaber Date: Thu, 25 Feb 2016 15:24:22 -0500 Subject: [PATCH 06/32] :arrow_up: nodegit@0.11.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a9eab6b3b..c73fdec8a 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "less-cache": "0.23", "line-top-index": "0.2.0", "marked": "^0.3.4", - "nodegit": "0.9.0", + "nodegit": "0.11.5", "normalize-package-data": "^2.0.0", "nslog": "^3", "oniguruma": "^5", From b637366a58c97c0d3c98a55db8b91ed1c6482664 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 25 Feb 2016 16:09:40 -0800 Subject: [PATCH 07/32] Workspace#setItemNotPending :arrow_right: Item#onDidTerminatePendingState Signed-off-by: Michelle Tilley --- src/pane.coffee | 16 +++++++++++----- src/text-editor.coffee | 6 ++++-- src/workspace.coffee | 6 ------ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/pane.coffee b/src/pane.coffee index a01703ce5..16c2f79b2 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -1,5 +1,5 @@ {find, compact, extend, last} = require 'underscore-plus' -{Emitter} = require 'event-kit' +{CompositeDisposable, Emitter} = require 'event-kit' Model = require './model' PaneAxis = require './pane-axis' TextEditor = require './text-editor' @@ -42,7 +42,7 @@ class Pane extends Model } = params @emitter = new Emitter - @itemSubscriptions = new WeakMap + @subscriptionsPerItem = new WeakMap @items = [] @addItems(compact(params?.items ? [])) @@ -265,8 +265,8 @@ class Pane extends Model getPanes: -> [this] unsubscribeFromItem: (item) -> - @itemSubscriptions.get(item)?.dispose() - @itemSubscriptions.delete(item) + @subscriptionsPerItem.get(item)?.dispose() + @subscriptionsPerItem.delete(item) ### Section: Items @@ -378,7 +378,13 @@ class Pane extends Model @setPendingItem(item) if pending if typeof item.onDidDestroy is 'function' - @itemSubscriptions.set item, item.onDidDestroy => @removeItem(item, false) + itemSubscriptions = new CompositeDisposable + itemSubscriptions.add item.onDidDestroy => @removeItem(item, false) + if typeof item.onDidTerminatePendingState is "function" + itemSubscriptions.add item.onDidTerminatePendingState => + @clearPendingItem() if @getPendingItem() is item + itemSubscriptions.add item.onDidDestroy => @removeItem(item, false) + @subscriptionsPerItem.set item, itemSubscriptions @items.splice(index, 0, item) @emitter.emit 'did-add-item', {item, index, moved} diff --git a/src/text-editor.coffee b/src/text-editor.coffee index e8207ca1e..53889e4c9 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -165,11 +165,13 @@ class TextEditor extends Model @emitter.emit 'did-change-encoding', @getEncoding() @disposables.add @buffer.onDidDestroy => @destroy() @disposables.add @buffer.onDidChangeModified => - atom.workspace.setItemNotPending(this) if not @bufferHasChanged and @buffer.isModified() - @bufferHasChanged = true + @emitter.emit 'did-terminate-pending-state' @preserveCursorPositionOnBufferReload() + onDidTerminatePendingState: (callback) -> + @emitter.on 'did-terminate-pending-state', callback + subscribeToDisplayBuffer: -> @disposables.add @selectionsMarkerLayer.onDidCreateMarker @addSelection.bind(this) @disposables.add @displayBuffer.onDidChangeGrammar @handleGrammarChange.bind(this) diff --git a/src/workspace.coffee b/src/workspace.coffee index 1f6648271..39ec2c599 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -515,12 +515,6 @@ class Workspace extends Model @emitter.emit 'did-open', {uri, pane, item, index} item - setItemNotPending: (item) => - for pane in @getPanes() - if item is pane.getPendingItem() - pane.setPendingItem(null) - break - openTextFile: (uri, options) -> filePath = @project.resolvePath(uri) From 1c65d0e5e4acd3192e437012760145d00e650ee0 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 25 Feb 2016 16:48:16 -0800 Subject: [PATCH 08/32] Changed Pane and TextEditor specs to match new pending behavior --- spec/pane-spec.coffee | 81 ++++++++++++++++++++++++++++++------ spec/text-editor-spec.coffee | 60 -------------------------- src/text-editor.coffee | 9 ++-- 3 files changed, 75 insertions(+), 75 deletions(-) diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index 8c228e2a8..62971bfd1 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -18,8 +18,8 @@ describe "Pane", -> onDidDestroy: (fn) -> @emitter.on('did-destroy', fn) destroy: -> @destroyed = true; @emitter.emit('did-destroy') isDestroyed: -> @destroyed - isPending: -> @pending - pending: false + onDidTerminatePendingState: (callback) -> @emitter.on 'terminate-pending-state', callback + terminatePendingState: -> @emitter.emit 'terminate-pending-state' beforeEach -> confirm = spyOn(atom.applicationDelegate, 'confirm') @@ -136,10 +136,8 @@ describe "Pane", -> pane = new Pane(paneParams(items: [])) itemA = new Item("A") itemB = new Item("B") - itemA.pending = true - itemB.pending = true - pane.addItem(itemA) - pane.addItem(itemB) + pane.addItem(itemA, undefined, false, true) + pane.addItem(itemB, undefined, false, true) expect(itemA.isDestroyed()).toBe true describe "::activateItem(item)", -> @@ -172,19 +170,17 @@ describe "Pane", -> beforeEach -> itemC = new Item("C") itemD = new Item("D") - itemC.pending = true - itemD.pending = true it "replaces the active item if it is pending", -> - pane.activateItem(itemC) + pane.activateItem(itemC, true) expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'C', 'B'] - pane.activateItem(itemD) + pane.activateItem(itemD, true) expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'D', 'B'] it "adds the item after the active item if it is not pending", -> - pane.activateItem(itemC) + pane.activateItem(itemC, true) pane.activateItemAtIndex(2) - pane.activateItem(itemD) + pane.activateItem(itemD, true) expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'B', 'D'] describe "::activateNextItem() and ::activatePreviousItem()", -> @@ -806,6 +802,67 @@ describe "Pane", -> pane2.destroy() expect(container.root).toBe pane1 + describe "pending state", -> + editor1 = null + pane = null + eventCount = null + + beforeEach -> + waitsForPromise -> + atom.workspace.open('sample.txt', pending: true).then (o) -> + editor1 = o + pane = atom.workspace.getActivePane() + + runs -> + eventCount = 0 + editor1.onDidTerminatePendingState -> eventCount++ + + it "does not open file in pending state by default", -> + waitsForPromise -> + atom.workspace.open('sample.js').then (o) -> + editor1 = o + pane = atom.workspace.getActivePane() + + runs -> + expect(pane.getPendingItem()).toBeNull() + + it "opens file in pending state if 'pending' option is true", -> + expect(pane.getPendingItem()).toEqual editor1 + + it "terminates pending state if ::terminatePendingState is invoked", -> + editor1.terminatePendingState() + + expect(pane.getPendingItem()).toBeNull() + expect(eventCount).toBe 1 + + it "terminates pending state when buffer is changed", -> + editor1.insertText('I\'ll be back!') + advanceClock(editor1.getBuffer().stoppedChangingDelay) + + expect(pane.getPendingItem()).toBeNull() + expect(eventCount).toBe 1 + + it "only calls terminate handler once when text is modified twice", -> + editor1.insertText('Some text') + advanceClock(editor1.getBuffer().stoppedChangingDelay) + + editor1.save() + + editor1.insertText('More text') + advanceClock(editor1.getBuffer().stoppedChangingDelay) + + expect(pane.getPendingItem()).toBeNull() + expect(eventCount).toBe 1 + + it "only calls clearPendingItem if there is a pending item to clear", -> + spyOn(pane, "clearPendingItem").andCallThrough() + + editor1.terminatePendingState() + editor1.terminatePendingState() + + expect(pane.getPendingItem()).toBeNull() + expect(pane.clearPendingItem.callCount).toBe 1 + describe "serialization", -> pane = null diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 6959d4da5..6f16f0cf7 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -55,16 +55,6 @@ describe "TextEditor", -> expect(editor.tokenizedLineForScreenRow(0).invisibles.eol).toBe '?' - it "restores pending tabs in pending state", -> - expect(editor.isPending()).toBe false - editor2 = TextEditor.deserialize(editor.serialize(), atom) - expect(editor2.isPending()).toBe false - - pendingEditor = atom.workspace.buildTextEditor(pending: true) - expect(pendingEditor.isPending()).toBe true - editor3 = TextEditor.deserialize(pendingEditor.serialize(), atom) - expect(editor3.isPending()).toBe true - describe "when the editor is constructed with the largeFileMode option set to true", -> it "loads the editor but doesn't tokenize", -> editor = null @@ -5827,53 +5817,3 @@ describe "TextEditor", -> screenRange: marker1.getRange(), rangeIsReversed: false } - - describe "pending state", -> - editor1 = null - eventCount = null - - beforeEach -> - waitsForPromise -> - atom.workspace.open('sample.txt', pending: true).then (o) -> editor1 = o - - runs -> - eventCount = 0 - editor1.onDidTerminatePendingState -> eventCount++ - - it "does not open file in pending state by default", -> - expect(editor.isPending()).toBe false - - it "opens file in pending state if 'pending' option is true", -> - expect(editor1.isPending()).toBe true - - it "terminates pending state if ::terminatePendingState is invoked", -> - editor1.terminatePendingState() - - expect(editor1.isPending()).toBe false - expect(eventCount).toBe 1 - - it "terminates pending state when buffer is changed", -> - editor1.insertText('I\'ll be back!') - advanceClock(editor1.getBuffer().stoppedChangingDelay) - - expect(editor1.isPending()).toBe false - expect(eventCount).toBe 1 - - it "only calls terminate handler once when text is modified twice", -> - editor1.insertText('Some text') - advanceClock(editor1.getBuffer().stoppedChangingDelay) - - editor1.save() - - editor1.insertText('More text') - advanceClock(editor1.getBuffer().stoppedChangingDelay) - - expect(editor1.isPending()).toBe false - expect(eventCount).toBe 1 - - it "only calls terminate handler once when terminatePendingState is called twice", -> - editor1.terminatePendingState() - editor1.terminatePendingState() - - expect(editor1.isPending()).toBe false - expect(eventCount).toBe 1 diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 53889e4c9..c4d028856 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -111,7 +111,7 @@ class TextEditor extends Model @cursors = [] @cursorsByMarkerId = new Map @selections = [] - @bufferHasChanged = false + @hasTerminatedPendingState = false buffer ?= new TextBuffer @displayBuffer ?= new DisplayBuffer({ @@ -152,7 +152,6 @@ class TextEditor extends Model firstVisibleScreenColumn: @getFirstVisibleScreenColumn() displayBuffer: @displayBuffer.serialize() selectionsMarkerLayerId: @selectionsMarkerLayer.id - bufferHasChanged: @bufferHasChanged subscribeToBuffer: -> @buffer.retain() @@ -165,10 +164,14 @@ class TextEditor extends Model @emitter.emit 'did-change-encoding', @getEncoding() @disposables.add @buffer.onDidDestroy => @destroy() @disposables.add @buffer.onDidChangeModified => - @emitter.emit 'did-terminate-pending-state' + @terminatePendingState() if @buffer.isModified() @preserveCursorPositionOnBufferReload() + terminatePendingState: -> + @emitter.emit 'did-terminate-pending-state' if not @hasTerminatedPendingState + @hasTerminatedPendingState = true + onDidTerminatePendingState: (callback) -> @emitter.on 'did-terminate-pending-state', callback From 3848da4488c5a5a00eb679fb49a3faa5bdcefeea Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 25 Feb 2016 17:21:01 -0800 Subject: [PATCH 09/32] :lipstick: and :memo: for pending API --- src/pane.coffee | 45 ++++++++++---------------------------------- src/workspace.coffee | 5 ++++- 2 files changed, 14 insertions(+), 36 deletions(-) diff --git a/src/pane.coffee b/src/pane.coffee index 16c2f79b2..5952352d1 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -58,7 +58,7 @@ class Pane extends Model items: compact(@items.map((item) -> item.serialize?())) activeItemURI: activeItemURI focused: @focused - flexScale: @flexScale # TODO: is it okay to not serialize pending state? does it need to be restored? + flexScale: @flexScale getParent: -> @parent @@ -348,7 +348,9 @@ class Pane extends Model # Public: Make the given item *active*, causing it to be displayed by # the pane's view. # - # * `pending` TODO + # * `pending` (optional) {Boolean} indicating that the item should be added + # in a pending state if it does not yet exist in the pane. Existing pending + # items in a pane are replaced with new pending items when they are opened. activateItem: (item, pending=false) -> if item? if @isItemPending(@activeItem) @@ -364,7 +366,9 @@ class Pane extends Model # view. # * `index` (optional) {Number} indicating the index at which to add the item. # If omitted, the item is added after the current active item. - # * `pending` TODO + # * `pending` (optional) {Boolean} indicating that the item should be + # added in a pending state. Existing pending items in a pane are replaced with + # new pending items when they are opened. # # Returns the added item. addItem: (item, index=@getActiveItemIndex() + 1, moved=false, pending=false) -> @@ -391,44 +395,15 @@ class Pane extends Model @setActiveItem(item) unless @getActiveItem()? item - clearPendingItem: => - @setPendingItem(null) - setPendingItem: (item) => - # TODO: figure out events for changing/clearing pending item - @pendingItem = item - @emitter.emit 'did-change-pending-item', @pendingItem + @pendingItem = item if @pendingItem isnt item @emitter.emit 'did-terminate-pending-state' if not item - # Public: Get the pending pane item in this pane, if any. - # - # Returns a pane item or `null`. getPendingItem: => @pendingItem or null - isItemPending: (item) => - @pendingItem is item - - # Invoke the given callback when the value of {::getPendingItem} changes. - # - # * `callback` {Function} to be called with when the pending item changes. - # * `pendingItem` The current pending item, or `null`. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangePendingItem: (callback) => - @emitter.on 'did-change-pending-item', callback - - # Public: Invoke the given callback with the current and future values of - # {::getPendingItem}. - # - # * `callback` {Function} to be called with the current and future pending - # items. - # * `pendingItem` The current pending item. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observePendingItem: (callback) -> - callback(@getPendingItem()) - @onDidChangePendingItem(callback) + clearPendingItem: => + @setPendingItem(null) onDidTerminatePendingState: (callback) => @emitter.on 'did-terminate-pending-state', callback diff --git a/src/workspace.coffee b/src/workspace.coffee index 39ec2c599..636ebfd69 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -403,6 +403,9 @@ class Workspace extends Model # containing pane. Defaults to `true`. # * `activateItem` A {Boolean} indicating whether to call {Pane::activateItem} # on containing pane. Defaults to `true`. + # * `pending` A {Boolean} indicating whether or not the item should be opened + # in a pending state. Existing pending items in a pane are replaced with + # new pending items when they are opened. # * `searchAllPanes` A {Boolean}. If `true`, the workspace will attempt to # activate an existing item for the given URI on any pane. # If `false`, only the active pane will be searched for @@ -477,7 +480,7 @@ class Workspace extends Model if uri? if item = pane.itemForURI(uri) - pane.setPendingItem(null) if not options.pending + pane.clearPendingItem() if not options.pending and pane.getPendingItem() is item item ?= opener(uri, options) for opener in @getOpeners() when not item try From 6add9ce9e48a278911a10a52e69b1b367faf878f Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 25 Feb 2016 17:25:39 -0800 Subject: [PATCH 10/32] isItemPending(item) :arrow_right: getPendingItem() --- src/pane.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pane.coffee b/src/pane.coffee index 5952352d1..c37486d89 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -353,7 +353,7 @@ class Pane extends Model # items in a pane are replaced with new pending items when they are opened. activateItem: (item, pending=false) -> if item? - if @isItemPending(@activeItem) + if @getPendingItem() is @activeItem index = @getActiveItemIndex() else index = @getActiveItemIndex() + 1 From 8fff6b2dd0cc231c470f6dbf9349773298f2b546 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 25 Feb 2016 17:27:39 -0800 Subject: [PATCH 11/32] isItemPending(item) :arrow_right: getPendingItem() --- src/pane.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pane.coffee b/src/pane.coffee index c37486d89..cbcb801c8 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -426,7 +426,7 @@ class Pane extends Model index = @items.indexOf(item) return if index is -1 - @pendingItem = null if @isItemPending(item) + @pendingItem = null if @getPendingItem() is item @emitter.emit 'will-remove-item', {item, index, destroyed: not moved, moved} @unsubscribeFromItem(item) From 7643fa04ed16eb0d68f001d8049317dab06b9478 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 25 Feb 2016 17:32:17 -0800 Subject: [PATCH 12/32] Small :racehorse: when editing a `TextEditor` that is no longer pending --- src/text-editor.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index c4d028856..f5aa1f20b 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -164,7 +164,7 @@ class TextEditor extends Model @emitter.emit 'did-change-encoding', @getEncoding() @disposables.add @buffer.onDidDestroy => @destroy() @disposables.add @buffer.onDidChangeModified => - @terminatePendingState() if @buffer.isModified() + @terminatePendingState() if not @hasTerminatedPendingState and @buffer.isModified() @preserveCursorPositionOnBufferReload() From 84a2ef69af1f66ac9f05e718c56ddec7e2ea9db5 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Fri, 26 Feb 2016 07:21:18 -0800 Subject: [PATCH 13/32] Fix Workspace#open pending specs --- spec/workspace-spec.coffee | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/spec/workspace-spec.coffee b/spec/workspace-spec.coffee index ef89636a8..78bbf2fdb 100644 --- a/spec/workspace-spec.coffee +++ b/spec/workspace-spec.coffee @@ -588,19 +588,22 @@ describe "Workspace", -> describe "when the file is already open in pending state", -> it "should terminate the pending state", -> editor = null + pane = null waitsForPromise -> - atom.workspace.open('sample.js', pending: true).then (o) -> editor = o - + atom.workspace.open('sample.js', pending: true).then (o) -> + editor = o + pane = atom.workspace.getActivePane() + runs -> - expect(editor.isPending()).toBe true - + expect(pane.getPendingItem()).toEqual editor + waitsForPromise -> - atom.workspace.open('sample.js').then (o) -> editor = o - + atom.workspace.open('sample.js') + runs -> - expect(editor.isPending()).toBe false - + expect(pane.getPendingItem()).toBeNull() + describe "::reopenItem()", -> it "opens the uri associated with the last closed pane that isn't currently open", -> pane = workspace.getActivePane() @@ -1551,11 +1554,12 @@ describe "Workspace", -> describe "when the core.allowPendingPaneItems option is falsey", -> it "does not open item with `pending: true` option as pending", -> - editor = null + pane = null atom.config.set('core.allowPendingPaneItems', false) waitsForPromise -> - atom.workspace.open('sample.js', pending: true).then (o) -> editor = o + atom.workspace.open('sample.js', pending: true).then -> + pane = atom.workspace.getActivePane() runs -> - expect(editor.isPending()).toBeFalsy() + expect(pane.getPendingItem()).toBeFalsy() From ff0942dc1f7092dfded874527069fbf22debeb9e Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Fri, 26 Feb 2016 07:40:39 -0800 Subject: [PATCH 14/32] :arrow_up: bracket-matcher --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c73fdec8a..5dc94cda9 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "autosave": "0.23.1", "background-tips": "0.26.0", "bookmarks": "0.38.2", - "bracket-matcher": "0.80.0", + "bracket-matcher": "0.80.1", "command-palette": "0.38.0", "deprecation-cop": "0.54.1", "dev-live-reload": "0.47.0", From 715686fd4e5ff41ee37f2731b820d5dd009c0af9 Mon Sep 17 00:00:00 2001 From: Ian Olsen Date: Fri, 26 Feb 2016 14:11:45 -0800 Subject: [PATCH 15/32] build only my experimental branch on circle --- circle.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 circle.yml diff --git a/circle.yml b/circle.yml new file mode 100644 index 000000000..a55900cca --- /dev/null +++ b/circle.yml @@ -0,0 +1,4 @@ +general: + branches: + only: + - io-circle-ci From 7a6c8f53a444147a02acd143731f1e9c953aaf9c Mon Sep 17 00:00:00 2001 From: natalieogle Date: Mon, 25 Jan 2016 20:18:23 -0800 Subject: [PATCH 16/32] Add activateMostRecentlyUsedItem to pane model. --- keymaps/darwin.cson | 2 +- keymaps/linux.cson | 2 +- keymaps/win32.cson | 2 +- spec/pane-spec.coffee | 21 +++++++++++++++++++++ src/pane.coffee | 28 ++++++++++++++++++++++++++-- src/register-default-commands.coffee | 1 + 6 files changed, 51 insertions(+), 5 deletions(-) diff --git a/keymaps/darwin.cson b/keymaps/darwin.cson index 4859f9b67..3fc2aecf1 100644 --- a/keymaps/darwin.cson +++ b/keymaps/darwin.cson @@ -73,7 +73,7 @@ '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-tab': 'pane:show-most-recently-used-item' 'ctrl-shift-tab': 'pane:show-previous-item' 'cmd-=': 'window:increase-font-size' 'cmd-+': 'window:increase-font-size' diff --git a/keymaps/linux.cson b/keymaps/linux.cson index 9ddb760e2..25bfe3add 100644 --- a/keymaps/linux.cson +++ b/keymaps/linux.cson @@ -46,7 +46,7 @@ 'pagedown': 'core:page-down' 'backspace': 'core:backspace' 'shift-backspace': 'core:backspace' - 'ctrl-tab': 'pane:show-next-item' + 'ctrl-tab': 'pane:show-most-recently-used-item' 'ctrl-shift-tab': 'pane:show-previous-item' 'ctrl-pageup': 'pane:show-previous-item' 'ctrl-pagedown': 'pane:show-next-item' diff --git a/keymaps/win32.cson b/keymaps/win32.cson index e4703bac8..bde54c29f 100644 --- a/keymaps/win32.cson +++ b/keymaps/win32.cson @@ -52,7 +52,7 @@ 'pagedown': 'core:page-down' 'backspace': 'core:backspace' 'shift-backspace': 'core:backspace' - 'ctrl-tab': 'pane:show-next-item' + 'ctrl-tab': 'pane:show-most-recently-used-item' 'ctrl-shift-tab': 'pane:show-previous-item' 'ctrl-pageup': 'pane:show-previous-item' 'ctrl-pagedown': 'pane:show-next-item' diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index 62971bfd1..9bf2306a9 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -183,6 +183,27 @@ describe "Pane", -> pane.activateItem(itemD, true) expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'B', 'D'] + fdescribe "::activateMostRecentlyUsedItem()", -> + it "sets the active item to the most recently used item", -> + pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")])) + [item1, item2, item3] = pane.getItems() + pane.itemStack = [] + + pane.activateItem(item3) + expect(pane.getActiveItem()).toBe item3 + pane.activateItem(item1) + expect(pane.getActiveItem()).toBe item1 + pane.activateMostRecentlyUsedItem() + expect(pane.getActiveItem()).toBe item3 + pane.activateItem(item2) + expect(pane.getActiveItem()).toBe item2 + pane.activateMostRecentlyUsedItem() + expect(pane.getActiveItem()).toBe item3 + expect(pane.itemStack[0]).toBe item1 + pane.destroyItem(item3) + pane.activateMostRecentlyUsedItem() + expect(pane.getActiveItem()).toBe item1 + 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")])) diff --git a/src/pane.coffee b/src/pane.coffee index cbcb801c8..a6a47be54 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -44,6 +44,7 @@ class Pane extends Model @emitter = new Emitter @subscriptionsPerItem = new WeakMap @items = [] + @itemStack = [] @addItems(compact(params?.items ? [])) @setActiveItem(@items[0]) unless @getActiveItem()? @@ -285,10 +286,17 @@ class Pane extends Model setActiveItem: (activeItem) -> unless activeItem is @activeItem + @addItemToStack(activeItem) @activeItem = activeItem @emitter.emit 'did-change-active-item', @activeItem @activeItem + # 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 +309,15 @@ class Pane extends Model itemAtIndex: (index) -> @items[index] + # Makes the most recently used item active. + activateMostRecentlyUsedItem: -> + console.log(@items[0].serialize) + if @items.length > 1 + index = @itemStack.length - 2 + mostRecentlyUsedItem = @itemStack[index] + @itemStack.splice(index, 1) + @setActiveItem(mostRecentlyUsedItem) + # Public: Makes the next item active. activateNextItem: -> index = @getActiveItemIndex() @@ -425,9 +442,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 +459,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. diff --git a/src/register-default-commands.coffee b/src/register-default-commands.coffee index 1b1aad2cf..d65f8aced 100644 --- a/src/register-default-commands.coffee +++ b/src/register-default-commands.coffee @@ -2,6 +2,7 @@ module.exports = ({commandRegistry, commandInstaller, config}) -> commandRegistry.add 'atom-workspace', + 'pane:show-most-recently-used-item': -> @getModel().getActivePane().activateMostRecentlyUsedItem() 'pane:show-next-item': -> @getModel().getActivePane().activateNextItem() 'pane:show-previous-item': -> @getModel().getActivePane().activatePreviousItem() 'pane:show-item-1': -> @getModel().getActivePane().activateItemAtIndex(0) From 6466cb489e349422c0845dbac5b20edf8ef5b14c Mon Sep 17 00:00:00 2001 From: natalieogle Date: Wed, 3 Feb 2016 22:21:46 -0800 Subject: [PATCH 17/32] Add serialize and deserialize functionality to the itemStack. --- spec/pane-spec.coffee | 30 +++++++++++++++++++++++++++++- src/pane.coffee | 16 ++++++++++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index 9bf2306a9..b4057fb4c 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -183,7 +183,8 @@ describe "Pane", -> pane.activateItem(itemD, true) expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'B', 'D'] - fdescribe "::activateMostRecentlyUsedItem()", -> + + describe "::activateMostRecentlyUsedItem()", -> it "sets the active item to the most recently used item", -> pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")])) [item1, item2, item3] = pane.getItems() @@ -915,3 +916,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 items in the itemStack if they 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] diff --git a/src/pane.coffee b/src/pane.coffee index a6a47be54..0aa1ed8fd 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -20,7 +20,7 @@ class Pane extends Model focused: false @deserialize: (state, {deserializers, applicationDelegate, config, notifications}) -> - {items, activeItemURI, activeItemUri} = state + {items, itemStack, activeItemURI, activeItemUri} = state activeItemURI ?= activeItemUri state.items = compact(items.map (itemState) -> deserializers.deserialize(itemState)) state.activeItem = find state.items, (item) -> @@ -48,15 +48,20 @@ class Pane extends Model @addItems(compact(params?.items ? [])) @setActiveItem(@items[0]) unless @getActiveItem()? + @addItemsToStack(params?.itemStack ? []) @setFlexScale(params?.flexScale ? 1) serialize: -> if typeof @activeItem?.getURI is 'function' activeItemURI = @activeItem.getURI() + itemStack = [] + for item in @items + itemStack.push(@itemStack.indexOf(item)) if typeof item.serialize is 'function' deserializer: 'Pane' id: @id items: compact(@items.map((item) -> item.serialize?())) + itemStack: itemStack activeItemURI: activeItemURI focused: @focused flexScale: @flexScale @@ -291,6 +296,14 @@ class Pane extends Model @emitter.emit 'did-change-active-item', @activeItem @activeItem + # Build the itemStack after deserializing + addItemsToStack: (itemStack) -> + if itemStack.length is 0 or itemStack.length isnt @items.length or itemStack.indexOf(-1) >= 0 + itemStack = @items.map((item) => @items.indexOf(item)) + for item, i in itemStack + index = itemStack.indexOf(i) + @addItemToStack(@items[index]) unless index is -1 + # Add item (or move item) to the end of the itemStack addItemToStack: (newItem) -> index = @itemStack.indexOf(newItem) @@ -311,7 +324,6 @@ class Pane extends Model # Makes the most recently used item active. activateMostRecentlyUsedItem: -> - console.log(@items[0].serialize) if @items.length > 1 index = @itemStack.length - 2 mostRecentlyUsedItem = @itemStack[index] From fe52ce6011ae780ec362c296630fc3a31bb57ac4 Mon Sep 17 00:00:00 2001 From: natalieogle Date: Mon, 8 Feb 2016 18:58:05 -0800 Subject: [PATCH 18/32] Modify serialize functions and add function to move through the item stack in order of most recently used. --- spec/pane-spec.coffee | 26 +++++++++++++++++++++++ src/pane.coffee | 48 ++++++++++++++++++++++++++----------------- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index b4057fb4c..d3e8dc535 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -195,16 +195,42 @@ describe "Pane", -> pane.activateItem(item1) expect(pane.getActiveItem()).toBe item1 pane.activateMostRecentlyUsedItem() + pane.stopMovingThroughStackAndMoveItemToEndOfStack() expect(pane.getActiveItem()).toBe item3 pane.activateItem(item2) expect(pane.getActiveItem()).toBe item2 pane.activateMostRecentlyUsedItem() + pane.stopMovingThroughStackAndMoveItemToEndOfStack() expect(pane.getActiveItem()).toBe item3 expect(pane.itemStack[0]).toBe item1 pane.destroyItem(item3) pane.activateMostRecentlyUsedItem() + pane.stopMovingThroughStackAndMoveItemToEndOfStack() expect(pane.getActiveItem()).toBe item1 + describe "::activateNextRecentlyUsedItem()", -> + it "sets the active item to the next item in the itemStack", -> + 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.activateMostRecentlyUsedItem() + expect(pane.getActiveItem()).toBe item5 + pane.activateNextRecentlyUsedItem() + expect(pane.getActiveItem()).toBe item2 + pane.activateNextRecentlyUsedItem() + expect(pane.getActiveItem()).toBe item1 + pane.activateNextRecentlyUsedItem() + expect(pane.getActiveItem()).toBe item3 + pane.activateNextRecentlyUsedItem() + expect(pane.getActiveItem()).toBe item4 + pane.activateNextRecentlyUsedItem() + expect(pane.getActiveItem()).toBe item5 + pane.stopMovingThroughStackAndMoveItemToEndOfStack() + 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")])) diff --git a/src/pane.coffee b/src/pane.coffee index 0aa1ed8fd..93f399cd0 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -20,7 +20,7 @@ class Pane extends Model focused: false @deserialize: (state, {deserializers, applicationDelegate, config, notifications}) -> - {items, itemStack, 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) -> @@ -48,20 +48,21 @@ class Pane extends Model @addItems(compact(params?.items ? [])) @setActiveItem(@items[0]) unless @getActiveItem()? - @addItemsToStack(params?.itemStack ? []) + @addItemsToStack(params?.itemStackIndices ? []) @setFlexScale(params?.flexScale ? 1) serialize: -> if typeof @activeItem?.getURI is 'function' activeItemURI = @activeItem.getURI() - itemStack = [] - for item in @items - itemStack.push(@itemStack.indexOf(item)) if typeof item.serialize is 'function' + itemsToBeSerialized = compact(@items.map((item) -> item if typeof item.serialize is 'function')) + itemStackIndices = [] + for item in @itemStack + itemStackIndices.push(itemsToBeSerialized.indexOf(item)) if typeof item.serialize is 'function' deserializer: 'Pane' id: @id - items: compact(@items.map((item) -> item.serialize?())) - itemStack: itemStack + items: itemsToBeSerialized.map((item) -> item.serialize()) + itemStackIndices: itemStackIndices activeItemURI: activeItemURI focused: @focused flexScale: @flexScale @@ -289,20 +290,20 @@ 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) + @addItemToStack(activeItem) unless modifyStack is false @activeItem = activeItem @emitter.emit 'did-change-active-item', @activeItem @activeItem # Build the itemStack after deserializing - addItemsToStack: (itemStack) -> - if itemStack.length is 0 or itemStack.length isnt @items.length or itemStack.indexOf(-1) >= 0 - itemStack = @items.map((item) => @items.indexOf(item)) - for item, i in itemStack - index = itemStack.indexOf(i) - @addItemToStack(@items[index]) unless index is -1 + addItemsToStack: (itemStackIndices) -> + 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]) # Add item (or move item) to the end of the itemStack addItemToStack: (newItem) -> @@ -325,10 +326,19 @@ class Pane extends Model # Makes the most recently used item active. activateMostRecentlyUsedItem: -> if @items.length > 1 - index = @itemStack.length - 2 - mostRecentlyUsedItem = @itemStack[index] - @itemStack.splice(index, 1) - @setActiveItem(mostRecentlyUsedItem) + @itemStackIndex = @itemStack.length - 1 + @activateNextRecentlyUsedItem() + + # Makes the next item in the itemStack active. + activateNextRecentlyUsedItem: -> + @itemStackIndex = @itemStackIndex - 1 + nextRecentlyUsedItem = @itemStack[@itemStackIndex] + @setActiveItem(nextRecentlyUsedItem, modifyStack: false) + @itemStackIndex = @itemStack.length if @itemStackIndex is 0 + + # Moves the active item to the end of the itemStack once the ctrl key is lifted + stopMovingThroughStackAndMoveItemToEndOfStack: -> + @addItemToStack(@activeItem) # Public: Makes the next item active. activateNextItem: -> From 3641cc0296ab4720b57e3538ed6e2ccea702eeec Mon Sep 17 00:00:00 2001 From: natalieogle Date: Tue, 9 Feb 2016 20:43:02 -0800 Subject: [PATCH 19/32] Remove redundant MRU function. --- keymaps/darwin.cson | 2 +- keymaps/linux.cson | 2 +- keymaps/win32.cson | 2 +- spec/pane-spec.coffee | 41 ++++++++-------------------- src/pane.coffee | 17 +++++------- src/register-default-commands.coffee | 2 +- 6 files changed, 23 insertions(+), 43 deletions(-) diff --git a/keymaps/darwin.cson b/keymaps/darwin.cson index 3fc2aecf1..f854aaeaf 100644 --- a/keymaps/darwin.cson +++ b/keymaps/darwin.cson @@ -73,7 +73,7 @@ 'cmd-alt-right': 'pane:show-next-item' 'ctrl-pageup': 'pane:show-previous-item' 'ctrl-pagedown': 'pane:show-next-item' - 'ctrl-tab': 'pane:show-most-recently-used-item' + 'ctrl-tab': 'pane:show-next-recently-used-item' 'ctrl-shift-tab': 'pane:show-previous-item' 'cmd-=': 'window:increase-font-size' 'cmd-+': 'window:increase-font-size' diff --git a/keymaps/linux.cson b/keymaps/linux.cson index 25bfe3add..433975e89 100644 --- a/keymaps/linux.cson +++ b/keymaps/linux.cson @@ -46,7 +46,7 @@ 'pagedown': 'core:page-down' 'backspace': 'core:backspace' 'shift-backspace': 'core:backspace' - 'ctrl-tab': 'pane:show-most-recently-used-item' + 'ctrl-tab': 'pane:show-next-recently-used-item' 'ctrl-shift-tab': 'pane:show-previous-item' 'ctrl-pageup': 'pane:show-previous-item' 'ctrl-pagedown': 'pane:show-next-item' diff --git a/keymaps/win32.cson b/keymaps/win32.cson index bde54c29f..fb33ce09d 100644 --- a/keymaps/win32.cson +++ b/keymaps/win32.cson @@ -52,7 +52,7 @@ 'pagedown': 'core:page-down' 'backspace': 'core:backspace' 'shift-backspace': 'core:backspace' - 'ctrl-tab': 'pane:show-most-recently-used-item' + 'ctrl-tab': 'pane:show-next-recently-used-item' 'ctrl-shift-tab': 'pane:show-previous-item' 'ctrl-pageup': 'pane:show-previous-item' 'ctrl-pagedown': 'pane:show-next-item' diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index d3e8dc535..f17fee5b1 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -183,31 +183,6 @@ describe "Pane", -> pane.activateItem(itemD, true) expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'B', 'D'] - - describe "::activateMostRecentlyUsedItem()", -> - it "sets the active item to the most recently used item", -> - pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")])) - [item1, item2, item3] = pane.getItems() - pane.itemStack = [] - - pane.activateItem(item3) - expect(pane.getActiveItem()).toBe item3 - pane.activateItem(item1) - expect(pane.getActiveItem()).toBe item1 - pane.activateMostRecentlyUsedItem() - pane.stopMovingThroughStackAndMoveItemToEndOfStack() - expect(pane.getActiveItem()).toBe item3 - pane.activateItem(item2) - expect(pane.getActiveItem()).toBe item2 - pane.activateMostRecentlyUsedItem() - pane.stopMovingThroughStackAndMoveItemToEndOfStack() - expect(pane.getActiveItem()).toBe item3 - expect(pane.itemStack[0]).toBe item1 - pane.destroyItem(item3) - pane.activateMostRecentlyUsedItem() - pane.stopMovingThroughStackAndMoveItemToEndOfStack() - expect(pane.getActiveItem()).toBe item1 - describe "::activateNextRecentlyUsedItem()", -> it "sets the active item to the next item in the itemStack", -> pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C"), new Item("D"), new Item("E")])) @@ -216,20 +191,28 @@ describe "Pane", -> pane.activateItem(item4) expect(pane.getActiveItem()).toBe item4 - pane.activateMostRecentlyUsedItem() + pane.activateNextRecentlyUsedItem() expect(pane.getActiveItem()).toBe item5 pane.activateNextRecentlyUsedItem() expect(pane.getActiveItem()).toBe item2 pane.activateNextRecentlyUsedItem() + pane.stopMovingThroughStackAndMoveItemToEndOfStack() expect(pane.getActiveItem()).toBe item1 - pane.activateNextRecentlyUsedItem() - expect(pane.getActiveItem()).toBe item3 + expect(pane.itemStack[4]).toBe item1 pane.activateNextRecentlyUsedItem() expect(pane.getActiveItem()).toBe item4 pane.activateNextRecentlyUsedItem() expect(pane.getActiveItem()).toBe item5 + pane.activateNextRecentlyUsedItem() + expect(pane.getActiveItem()).toBe item2 + pane.activateNextRecentlyUsedItem() + expect(pane.getActiveItem()).toBe item3 + pane.activateNextRecentlyUsedItem() + expect(pane.getActiveItem()).toBe item1 + pane.activateNextRecentlyUsedItem() pane.stopMovingThroughStackAndMoveItemToEndOfStack() - expect(pane.itemStack[4]).toBe item5 + expect(pane.getActiveItem()).toBe item4 + expect(pane.itemStack[4]).toBe item4 describe "::activateNextItem() and ::activatePreviousItem()", -> it "sets the active item to the next/previous item, looping around at either end", -> diff --git a/src/pane.coffee b/src/pane.coffee index 93f399cd0..82b7ec4f6 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -323,21 +323,18 @@ class Pane extends Model itemAtIndex: (index) -> @items[index] - # Makes the most recently used item active. - activateMostRecentlyUsedItem: -> - if @items.length > 1 - @itemStackIndex = @itemStack.length - 1 - @activateNextRecentlyUsedItem() - # Makes the next item in the itemStack active. activateNextRecentlyUsedItem: -> - @itemStackIndex = @itemStackIndex - 1 - nextRecentlyUsedItem = @itemStack[@itemStackIndex] - @setActiveItem(nextRecentlyUsedItem, modifyStack: false) - @itemStackIndex = @itemStack.length if @itemStackIndex is 0 + if @items.length > 1 + @itemStackIndex = @itemStack.length - 1 unless @itemStackIndex? + @itemStackIndex = @itemStackIndex - 1 + nextRecentlyUsedItem = @itemStack[@itemStackIndex] + @setActiveItem(nextRecentlyUsedItem, modifyStack: false) + @itemStackIndex = @itemStack.length if @itemStackIndex is 0 # Moves the active item to the end of the itemStack once the ctrl key is lifted stopMovingThroughStackAndMoveItemToEndOfStack: -> + delete @itemStackIndex @addItemToStack(@activeItem) # Public: Makes the next item active. diff --git a/src/register-default-commands.coffee b/src/register-default-commands.coffee index d65f8aced..ae5cdc344 100644 --- a/src/register-default-commands.coffee +++ b/src/register-default-commands.coffee @@ -2,7 +2,7 @@ module.exports = ({commandRegistry, commandInstaller, config}) -> commandRegistry.add 'atom-workspace', - 'pane:show-most-recently-used-item': -> @getModel().getActivePane().activateMostRecentlyUsedItem() + 'pane:show-next-recently-used-item': -> @getModel().getActivePane().activateNextRecentlyUsedItem() 'pane:show-next-item': -> @getModel().getActivePane().activateNextItem() 'pane:show-previous-item': -> @getModel().getActivePane().activatePreviousItem() 'pane:show-item-1': -> @getModel().getActivePane().activateItemAtIndex(0) From 69a6b9e5c5c004f0df085ec8a161107b08577218 Mon Sep 17 00:00:00 2001 From: natalieogle Date: Tue, 16 Feb 2016 20:28:58 -0800 Subject: [PATCH 20/32] Add keymap for 'ctrl-tab ^ctrl' in order to move item to top of stack when lifting ctrl. --- keymaps/darwin.cson | 1 + keymaps/linux.cson | 1 + keymaps/win32.cson | 1 + src/pane.coffee | 2 +- src/register-default-commands.coffee | 1 + 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/keymaps/darwin.cson b/keymaps/darwin.cson index f854aaeaf..c39ee6d99 100644 --- a/keymaps/darwin.cson +++ b/keymaps/darwin.cson @@ -74,6 +74,7 @@ 'ctrl-pageup': 'pane:show-previous-item' 'ctrl-pagedown': 'pane:show-next-item' 'ctrl-tab': 'pane:show-next-recently-used-item' + 'ctrl-tab ^ctrl': 'pane:move-item-to-top-of-stack' 'ctrl-shift-tab': 'pane:show-previous-item' 'cmd-=': 'window:increase-font-size' 'cmd-+': 'window:increase-font-size' diff --git a/keymaps/linux.cson b/keymaps/linux.cson index 433975e89..1192fe052 100644 --- a/keymaps/linux.cson +++ b/keymaps/linux.cson @@ -47,6 +47,7 @@ 'backspace': 'core:backspace' 'shift-backspace': 'core:backspace' 'ctrl-tab': 'pane:show-next-recently-used-item' + 'ctrl-tab ^ctrl': 'pane:move-item-to-top-of-stack' 'ctrl-shift-tab': 'pane:show-previous-item' 'ctrl-pageup': 'pane:show-previous-item' 'ctrl-pagedown': 'pane:show-next-item' diff --git a/keymaps/win32.cson b/keymaps/win32.cson index fb33ce09d..c50a6e75a 100644 --- a/keymaps/win32.cson +++ b/keymaps/win32.cson @@ -53,6 +53,7 @@ 'backspace': 'core:backspace' 'shift-backspace': 'core:backspace' 'ctrl-tab': 'pane:show-next-recently-used-item' + 'ctrl-tab ^ctrl': 'pane:move-item-to-top-of-stack' 'ctrl-shift-tab': 'pane:show-previous-item' 'ctrl-pageup': 'pane:show-previous-item' 'ctrl-pagedown': 'pane:show-next-item' diff --git a/src/pane.coffee b/src/pane.coffee index 82b7ec4f6..277835d3c 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -333,7 +333,7 @@ class Pane extends Model @itemStackIndex = @itemStack.length if @itemStackIndex is 0 # Moves the active item to the end of the itemStack once the ctrl key is lifted - stopMovingThroughStackAndMoveItemToEndOfStack: -> + moveItemToTopOfStack: -> delete @itemStackIndex @addItemToStack(@activeItem) diff --git a/src/register-default-commands.coffee b/src/register-default-commands.coffee index ae5cdc344..09574cedb 100644 --- a/src/register-default-commands.coffee +++ b/src/register-default-commands.coffee @@ -3,6 +3,7 @@ module.exports = ({commandRegistry, commandInstaller, config}) -> commandRegistry.add 'atom-workspace', 'pane:show-next-recently-used-item': -> @getModel().getActivePane().activateNextRecentlyUsedItem() + 'pane:move-item-to-top-of-stack': -> @getModel().getActivePane().moveItemToTopOfStack() 'pane:show-next-item': -> @getModel().getActivePane().activateNextItem() 'pane:show-previous-item': -> @getModel().getActivePane().activatePreviousItem() 'pane:show-item-1': -> @getModel().getActivePane().activateItemAtIndex(0) From 463dc6955a638c943c5c67124bcdabef402df777 Mon Sep 17 00:00:00 2001 From: natalieogle Date: Tue, 16 Feb 2016 21:12:05 -0800 Subject: [PATCH 21/32] Update spec for MRU tab functionality with correct function name. --- spec/pane-spec.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index f17fee5b1..c9d467af3 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -196,7 +196,7 @@ describe "Pane", -> pane.activateNextRecentlyUsedItem() expect(pane.getActiveItem()).toBe item2 pane.activateNextRecentlyUsedItem() - pane.stopMovingThroughStackAndMoveItemToEndOfStack() + pane.moveItemToTopOfStack() expect(pane.getActiveItem()).toBe item1 expect(pane.itemStack[4]).toBe item1 pane.activateNextRecentlyUsedItem() @@ -210,7 +210,7 @@ describe "Pane", -> pane.activateNextRecentlyUsedItem() expect(pane.getActiveItem()).toBe item1 pane.activateNextRecentlyUsedItem() - pane.stopMovingThroughStackAndMoveItemToEndOfStack() + pane.moveItemToTopOfStack() expect(pane.getActiveItem()).toBe item4 expect(pane.itemStack[4]).toBe item4 From 96107038746354f77494bce1b3317ea8936eb436 Mon Sep 17 00:00:00 2001 From: natalieogle Date: Sun, 21 Feb 2016 18:50:54 -0800 Subject: [PATCH 22/32] Add check to only build itemStack if there are items. --- src/pane.coffee | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pane.coffee b/src/pane.coffee index 277835d3c..1504553af 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -300,10 +300,11 @@ class Pane extends Model # Build the itemStack after deserializing addItemsToStack: (itemStackIndices) -> - 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]) + 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]) unless @items.length is 0 + for itemIndex in itemStackIndices + @addItemToStack(@items[itemIndex]) # Add item (or move item) to the end of the itemStack addItemToStack: (newItem) -> From 48ef6725241ca69d40f0f439f3ed7d4b0e1230b5 Mon Sep 17 00:00:00 2001 From: natalieogle Date: Sun, 21 Feb 2016 19:11:32 -0800 Subject: [PATCH 23/32] Remove redundant items.length check. --- src/pane.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pane.coffee b/src/pane.coffee index 1504553af..7b3ec515b 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -302,7 +302,7 @@ class Pane extends Model 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]) unless @items.length is 0 + itemStackIndices = (i for i in [0..@items.length-1]) for itemIndex in itemStackIndices @addItemToStack(@items[itemIndex]) From 553b3f33009850b3e413f57aec26b4657406e61d Mon Sep 17 00:00:00 2001 From: natalieogle Date: Tue, 23 Feb 2016 19:02:45 -0800 Subject: [PATCH 24/32] Change name of function that moves the active item to the top of the item stack. --- keymaps/darwin.cson | 2 +- keymaps/linux.cson | 2 +- keymaps/win32.cson | 2 +- src/pane.coffee | 2 +- src/register-default-commands.coffee | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/keymaps/darwin.cson b/keymaps/darwin.cson index c39ee6d99..baa664da8 100644 --- a/keymaps/darwin.cson +++ b/keymaps/darwin.cson @@ -74,7 +74,7 @@ 'ctrl-pageup': 'pane:show-previous-item' 'ctrl-pagedown': 'pane:show-next-item' 'ctrl-tab': 'pane:show-next-recently-used-item' - 'ctrl-tab ^ctrl': 'pane:move-item-to-top-of-stack' + 'ctrl-tab ^ctrl': 'pane:move-active-item-to-top-of-stack' 'ctrl-shift-tab': 'pane:show-previous-item' 'cmd-=': 'window:increase-font-size' 'cmd-+': 'window:increase-font-size' diff --git a/keymaps/linux.cson b/keymaps/linux.cson index 1192fe052..d9758e5ea 100644 --- a/keymaps/linux.cson +++ b/keymaps/linux.cson @@ -47,7 +47,7 @@ 'backspace': 'core:backspace' 'shift-backspace': 'core:backspace' 'ctrl-tab': 'pane:show-next-recently-used-item' - 'ctrl-tab ^ctrl': 'pane:move-item-to-top-of-stack' + 'ctrl-tab ^ctrl': 'pane:move-active-item-to-top-of-stack' 'ctrl-shift-tab': 'pane:show-previous-item' 'ctrl-pageup': 'pane:show-previous-item' 'ctrl-pagedown': 'pane:show-next-item' diff --git a/keymaps/win32.cson b/keymaps/win32.cson index c50a6e75a..3befc6b0c 100644 --- a/keymaps/win32.cson +++ b/keymaps/win32.cson @@ -53,7 +53,7 @@ 'backspace': 'core:backspace' 'shift-backspace': 'core:backspace' 'ctrl-tab': 'pane:show-next-recently-used-item' - 'ctrl-tab ^ctrl': 'pane:move-item-to-top-of-stack' + 'ctrl-tab ^ctrl': 'pane:move-active-item-to-top-of-stack' 'ctrl-shift-tab': 'pane:show-previous-item' 'ctrl-pageup': 'pane:show-previous-item' 'ctrl-pagedown': 'pane:show-next-item' diff --git a/src/pane.coffee b/src/pane.coffee index 7b3ec515b..d405e77a7 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -334,7 +334,7 @@ class Pane extends Model @itemStackIndex = @itemStack.length if @itemStackIndex is 0 # Moves the active item to the end of the itemStack once the ctrl key is lifted - moveItemToTopOfStack: -> + moveActiveItemToTopOfStack: -> delete @itemStackIndex @addItemToStack(@activeItem) diff --git a/src/register-default-commands.coffee b/src/register-default-commands.coffee index 09574cedb..3d95f1cff 100644 --- a/src/register-default-commands.coffee +++ b/src/register-default-commands.coffee @@ -3,7 +3,7 @@ module.exports = ({commandRegistry, commandInstaller, config}) -> commandRegistry.add 'atom-workspace', 'pane:show-next-recently-used-item': -> @getModel().getActivePane().activateNextRecentlyUsedItem() - 'pane:move-item-to-top-of-stack': -> @getModel().getActivePane().moveItemToTopOfStack() + '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) From bc28a91e02f9248e530168fa39a14c5ab6b6ecfe Mon Sep 17 00:00:00 2001 From: natalieogle Date: Tue, 23 Feb 2016 19:20:18 -0800 Subject: [PATCH 25/32] :art: Change the structure of a few pieces relating to serialization. --- src/pane.coffee | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pane.coffee b/src/pane.coffee index d405e77a7..f2e92480b 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -55,9 +55,7 @@ class Pane extends Model if typeof @activeItem?.getURI is 'function' activeItemURI = @activeItem.getURI() itemsToBeSerialized = compact(@items.map((item) -> item if typeof item.serialize is 'function')) - itemStackIndices = [] - for item in @itemStack - itemStackIndices.push(itemsToBeSerialized.indexOf(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 @@ -305,6 +303,7 @@ class Pane extends Model 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) -> From f7de9052d6bef9fadb77c594f464db45c8ce9f7f Mon Sep 17 00:00:00 2001 From: natalieogle Date: Tue, 23 Feb 2016 20:04:16 -0800 Subject: [PATCH 26/32] Update specs for itemStack. --- spec/pane-spec.coffee | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index c9d467af3..d4e09e645 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -196,7 +196,7 @@ describe "Pane", -> pane.activateNextRecentlyUsedItem() expect(pane.getActiveItem()).toBe item2 pane.activateNextRecentlyUsedItem() - pane.moveItemToTopOfStack() + pane.moveActiveItemToTopOfStack() expect(pane.getActiveItem()).toBe item1 expect(pane.itemStack[4]).toBe item1 pane.activateNextRecentlyUsedItem() @@ -210,7 +210,7 @@ describe "Pane", -> pane.activateNextRecentlyUsedItem() expect(pane.getActiveItem()).toBe item1 pane.activateNextRecentlyUsedItem() - pane.moveItemToTopOfStack() + pane.moveActiveItemToTopOfStack() expect(pane.getActiveItem()).toBe item4 expect(pane.itemStack[4]).toBe item4 @@ -280,7 +280,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 @@ -291,6 +291,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) -> @@ -944,7 +957,7 @@ describe "Pane", -> newPane = Pane.deserialize(pane.serialize(), atom) expect(newPane.getItems()).toEqual newPane.itemStack - it "does not serialize items in the itemStack if they will not be serialized", -> + 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 = {} From 420a8d8692080a1d067caa936f6888ff6653a2f7 Mon Sep 17 00:00:00 2001 From: natalieogle Date: Sat, 27 Feb 2016 20:41:25 -0800 Subject: [PATCH 27/32] Add activatePreviousRecentlyUsedItem to pane model and add specs. --- keymaps/darwin.cson | 3 ++- keymaps/linux.cson | 3 ++- keymaps/win32.cson | 3 ++- spec/pane-spec.coffee | 26 ++++++++++++-------------- src/pane.coffee | 11 ++++++++++- src/register-default-commands.coffee | 1 + 6 files changed, 29 insertions(+), 18 deletions(-) diff --git a/keymaps/darwin.cson b/keymaps/darwin.cson index baa664da8..819e0079e 100644 --- a/keymaps/darwin.cson +++ b/keymaps/darwin.cson @@ -75,7 +75,8 @@ 'ctrl-pagedown': 'pane:show-next-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-item' + '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' diff --git a/keymaps/linux.cson b/keymaps/linux.cson index d9758e5ea..e676ed5ab 100644 --- a/keymaps/linux.cson +++ b/keymaps/linux.cson @@ -48,7 +48,8 @@ 'shift-backspace': 'core:backspace' '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-item' + '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' diff --git a/keymaps/win32.cson b/keymaps/win32.cson index 3befc6b0c..5869a3ed8 100644 --- a/keymaps/win32.cson +++ b/keymaps/win32.cson @@ -54,7 +54,8 @@ 'shift-backspace': 'core:backspace' '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-item' + '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' diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index d4e09e645..e5cbee1a6 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -183,8 +183,8 @@ describe "Pane", -> pane.activateItem(itemD, true) expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'B', 'D'] - describe "::activateNextRecentlyUsedItem()", -> - it "sets the active item to the next item in the itemStack", -> + 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] @@ -195,24 +195,22 @@ describe "Pane", -> expect(pane.getActiveItem()).toBe item5 pane.activateNextRecentlyUsedItem() expect(pane.getActiveItem()).toBe item2 - pane.activateNextRecentlyUsedItem() - pane.moveActiveItemToTopOfStack() - expect(pane.getActiveItem()).toBe item1 - expect(pane.itemStack[4]).toBe item1 - pane.activateNextRecentlyUsedItem() - expect(pane.getActiveItem()).toBe item4 - pane.activateNextRecentlyUsedItem() + pane.activatePreviousRecentlyUsedItem() expect(pane.getActiveItem()).toBe item5 - pane.activateNextRecentlyUsedItem() - expect(pane.getActiveItem()).toBe item2 + 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 item1 + expect(pane.getActiveItem()).toBe item4 pane.activateNextRecentlyUsedItem() pane.moveActiveItemToTopOfStack() - expect(pane.getActiveItem()).toBe item4 - expect(pane.itemStack[4]).toBe item4 + 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", -> diff --git a/src/pane.coffee b/src/pane.coffee index f2e92480b..12abc5448 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -327,10 +327,19 @@ class Pane extends Model 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) - @itemStackIndex = @itemStack.length if @itemStackIndex is 0 + + # 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: -> diff --git a/src/register-default-commands.coffee b/src/register-default-commands.coffee index 3d95f1cff..dacb1d228 100644 --- a/src/register-default-commands.coffee +++ b/src/register-default-commands.coffee @@ -3,6 +3,7 @@ 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() From 92bbbf064508d50f75b84e4e2195f194dfd9facc Mon Sep 17 00:00:00 2001 From: Damien Guard Date: Sun, 28 Feb 2016 15:05:07 -0800 Subject: [PATCH 28/32] Windows command line does not allow ELSE on separate line, fixes #10934 --- resources/win/atom.cmd | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/resources/win/atom.cmd b/resources/win/atom.cmd index c9bfdd5ba..8d5e6f97a 100644 --- a/resources/win/atom.cmd +++ b/resources/win/atom.cmd @@ -35,8 +35,7 @@ IF "%EXPECT_OUTPUT%"=="YES" ( "%~dp0\..\..\atom.exe" --pid=%PID% %* rem If the wait flag is set, don't exit this process until Atom tells it to. goto waitLoop - ) - ELSE ( + ) ELSE ( "%~dp0\..\..\atom.exe" %* ) ) ELSE ( From 2dad38a7826522455feb56bd33f276f6b8eeeb49 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Sun, 28 Feb 2016 17:57:59 -0800 Subject: [PATCH 29/32] onDidTerminatePendingState :arrow_right: onItemDidTerminatePendingState Signed-off-by: Katrina Uychaco --- src/pane.coffee | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pane.coffee b/src/pane.coffee index 12abc5448..9905680bd 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -441,8 +441,10 @@ class Pane extends Model item setPendingItem: (item) => - @pendingItem = item if @pendingItem isnt item - @emitter.emit 'did-terminate-pending-state' if not item + if @pendingItem isnt item + mostRecentPendingItem = @pendingItem + @pendingItem = item + @emitter.emit 'item-did-terminate-pending-state', mostRecentPendingItem getPendingItem: => @pendingItem or null @@ -450,8 +452,8 @@ class Pane extends Model clearPendingItem: => @setPendingItem(null) - onDidTerminatePendingState: (callback) => - @emitter.on 'did-terminate-pending-state', callback + onItemDidTerminatePendingState: (callback) => + @emitter.on 'item-did-terminate-pending-state', callback # Public: Add the given items to the pane. # From 92dea0379a041d0c43f37df8c8068485aef02eb6 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Sun, 28 Feb 2016 18:32:19 -0800 Subject: [PATCH 30/32] :arrow_up: tree-view Signed-off-by: Katrina Uychaco --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5dc94cda9..8830a3f98 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "symbols-view": "0.111.1", "tabs": "0.90.2", "timecop": "0.33.1", - "tree-view": "0.201.4", + "tree-view": "0.201.5", "update-package-dependencies": "0.10.0", "welcome": "0.34.0", "whitespace": "0.32.2", From d39418ad7c61626753ffc9ed4536341ef0fc0b12 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Sun, 28 Feb 2016 18:15:52 -0800 Subject: [PATCH 31/32] Add specs for Pane::setPendingItem and ::onItemDidTerminatePendingState Signed-off-by: Katrina Uychaco --- spec/pane-spec.coffee | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index e5cbee1a6..b4785e113 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -183,6 +183,41 @@ describe "Pane", -> pane.activateItem(itemD, true) expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'B', 'D'] + describe "::setPendingItem", -> + pane = null + + beforeEach -> + pane = atom.workspace.getActivePane() + + it "changes the pending item", -> + expect(pane.getPendingItem()).toBeNull() + pane.setPendingItem("fake item") + expect(pane.getPendingItem()).toEqual "fake item" + + describe "::onItemDidTerminatePendingState callback", -> + pane = null + callbackCalled = false + + beforeEach -> + pane = atom.workspace.getActivePane() + callbackCalled = false + + it "is called when the pending item changes", -> + pane.setPendingItem("fake item one") + pane.onItemDidTerminatePendingState (item) -> + callbackCalled = true + expect(item).toEqual "fake item one" + pane.setPendingItem("fake item two") + expect(callbackCalled).toBeTruthy() + + it "has access to the new pending item via ::getPendingItem", -> + pane.setPendingItem("fake item one") + pane.onItemDidTerminatePendingState (item) -> + callbackCalled = true + expect(pane.getPendingItem()).toEqual "fake item two" + pane.setPendingItem("fake item two") + expect(callbackCalled).toBeTruthy() + 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")])) From 920d348014ac2ddf42e54e0bed6bb2cd71465146 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 29 Feb 2016 11:32:28 -0800 Subject: [PATCH 32/32] Only move legit items to the top of the stack Fixes #11002 cc @natalieogle --- spec/pane-spec.coffee | 4 ++++ src/pane.coffee | 1 + 2 files changed, 5 insertions(+) diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index b4785e113..888ccf6e2 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -337,6 +337,10 @@ describe "Pane", -> expect(pane.itemStack).toEqual [item2] expect(pane.getActiveItem()).toBe item2 + pane.destroyItem(item2) + expect(pane.itemStack).toEqual [] + expect(pane.getActiveItem()).toBeUndefined() + it "invokes ::onWillDestroyItem() observers before destroying the item", -> events = [] pane.onWillDestroyItem (event) -> diff --git a/src/pane.coffee b/src/pane.coffee index 9905680bd..25f421934 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -307,6 +307,7 @@ class Pane extends Model # Add item (or move item) to the end of the itemStack addItemToStack: (newItem) -> + return unless newItem? index = @itemStack.indexOf(newItem) @itemStack.splice(index, 1) unless index is -1 @itemStack.push(newItem)