From decc9834200bbd804063aa30938af2d557070825 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 Sep 2014 08:22:28 -0600 Subject: [PATCH 001/211] Start on CommandRegistry --- spec/command-registry-spec.coffee | 30 ++++++++++++++++++++++++++++++ src/command-registry.coffee | 21 +++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 spec/command-registry-spec.coffee create mode 100644 src/command-registry.coffee diff --git a/spec/command-registry-spec.coffee b/spec/command-registry-spec.coffee new file mode 100644 index 000000000..aec55e920 --- /dev/null +++ b/spec/command-registry-spec.coffee @@ -0,0 +1,30 @@ +CommandRegistry = require '../src/command-registry' + +describe "CommandRegistry", -> + [registry, parent, child, grandchild] = [] + + beforeEach -> + parent = document.createElement("div") + child = document.createElement("div") + grandchild = document.createElement("div") + parent.classList.add('parent') + child.classList.add('child') + grandchild.classList.add('grandchild') + child.appendChild(grandchild) + parent.appendChild(child) + document.querySelector('#jasmine-content').appendChild(parent) + + registry = new CommandRegistry(parent) + + it "invokes callbacks with selectors matching the target", -> + called = false + registry.add 'command', '.grandchild', (event) -> + expect(this).toBe grandchild + expect(event.type).toBe 'command' + expect(event.eventPhase).toBe Event.BUBBLING_PHASE + expect(event.target).toBe grandchild + expect(event.currentTarget).toBe grandchild + called = true + + grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) + expect(called).toBe true diff --git a/src/command-registry.coffee b/src/command-registry.coffee new file mode 100644 index 000000000..3c17f2ea8 --- /dev/null +++ b/src/command-registry.coffee @@ -0,0 +1,21 @@ +module.exports = +class CommandRegistry + constructor: (@rootNode) -> + @listenersByCommandName = {} + + add: (commandName, selector, callback) -> + unless @listenersByCommandName[commandName]? + @rootNode.addEventListener(commandName, @dispatchCommand, true) + @listenersByCommandName[commandName] = [] + + @listenersByCommandName[commandName].push({selector, callback}) + + dispatchCommand: (event) => + syntheticEvent = Object.create event, + eventPhase: value: Event.BUBBLING_PHASE + currentTarget: get: -> currentTarget + + currentTarget = event.target + for listener in @listenersByCommandName[event.type] + if event.target.webkitMatchesSelector(listener.selector) + listener.callback.call(currentTarget, syntheticEvent) From a075aa2b072b21c76c082389c47a704382ab9003 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 Sep 2014 08:39:14 -0600 Subject: [PATCH 002/211] Perform synthetic bubbling through event target ancestors --- spec/command-registry-spec.coffee | 17 +++++++++++++++++ src/command-registry.coffee | 10 +++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/spec/command-registry-spec.coffee b/spec/command-registry-spec.coffee index aec55e920..0addcde6a 100644 --- a/spec/command-registry-spec.coffee +++ b/spec/command-registry-spec.coffee @@ -28,3 +28,20 @@ describe "CommandRegistry", -> grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) expect(called).toBe true + + it "invokes callbacks with selectors matching ancestors of the target", -> + calls = [] + registry.add 'command', '.child', (event) -> + expect(this).toBe child + expect(event.target).toBe grandchild + expect(event.currentTarget).toBe child + calls.push('child') + + registry.add 'command', '.parent', (event) -> + expect(this).toBe parent + expect(event.target).toBe grandchild + expect(event.currentTarget).toBe parent + calls.push('parent') + + grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) + expect(calls).toEqual ['child', 'parent'] diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 3c17f2ea8..3042f7d8d 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -16,6 +16,10 @@ class CommandRegistry currentTarget: get: -> currentTarget currentTarget = event.target - for listener in @listenersByCommandName[event.type] - if event.target.webkitMatchesSelector(listener.selector) - listener.callback.call(currentTarget, syntheticEvent) + loop + for listener in @listenersByCommandName[event.type] + if currentTarget.webkitMatchesSelector(listener.selector) + listener.callback.call(currentTarget, syntheticEvent) + + break if currentTarget is @rootNode + currentTarget = currentTarget.parentNode From 5eb22520f1f03573a99ac13e53effd3cb48b47ac Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 Sep 2014 08:57:47 -0600 Subject: [PATCH 003/211] Order multiple matching listeners by selector specificity --- spec/command-registry-spec.coffee | 12 ++++++++++++ src/command-registry.coffee | 26 ++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/spec/command-registry-spec.coffee b/spec/command-registry-spec.coffee index 0addcde6a..28071932a 100644 --- a/spec/command-registry-spec.coffee +++ b/spec/command-registry-spec.coffee @@ -31,6 +31,7 @@ describe "CommandRegistry", -> it "invokes callbacks with selectors matching ancestors of the target", -> calls = [] + registry.add 'command', '.child', (event) -> expect(this).toBe child expect(event.target).toBe grandchild @@ -45,3 +46,14 @@ describe "CommandRegistry", -> grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) expect(calls).toEqual ['child', 'parent'] + + it "orders multiple matching listeners for an element by selector specificity", -> + child.classList.add('foo', 'bar') + calls = [] + + registry.add 'command', '.foo.bar', -> calls.push('.foo.bar') + registry.add 'command', '.foo', -> calls.push('.foo') + registry.add 'command', '.bar', -> calls.push('.bar') # specificity ties favor commands added later, like CSS + + grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) + expect(calls).toEqual ['.foo.bar', '.bar', '.foo'] diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 3042f7d8d..69776319d 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -1,3 +1,8 @@ +{specificity} = require 'clear-cut' + +SequenceCount = 0 +SpecificityCache = {} + module.exports = class CommandRegistry constructor: (@rootNode) -> @@ -8,7 +13,7 @@ class CommandRegistry @rootNode.addEventListener(commandName, @dispatchCommand, true) @listenersByCommandName[commandName] = [] - @listenersByCommandName[commandName].push({selector, callback}) + @listenersByCommandName[commandName].push(new CommandListener(selector, callback)) dispatchCommand: (event) => syntheticEvent = Object.create event, @@ -17,9 +22,22 @@ class CommandRegistry currentTarget = event.target loop - for listener in @listenersByCommandName[event.type] - if currentTarget.webkitMatchesSelector(listener.selector) - listener.callback.call(currentTarget, syntheticEvent) + matchingListeners = + @listenersByCommandName[event.type] + .filter (listener) -> currentTarget.webkitMatchesSelector(listener.selector) + .sort (a, b) -> a.compare(b) + + for listener in matchingListeners + listener.callback.call(currentTarget, syntheticEvent) break if currentTarget is @rootNode currentTarget = currentTarget.parentNode + +class CommandListener + constructor: (@selector, @callback) -> + @specificity = (SpecificityCache[@selector] ?= specificity(@selector)) + @sequenceNumber = SequenceCount++ + + compare: (other) -> + other.specificity - @specificity or + other.sequenceNumber - @sequenceNumber From fbaf956e1fda3ae610ac01c50ebb39df0a2b3676 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 Sep 2014 09:10:22 -0600 Subject: [PATCH 004/211] Handle .stopPropagation() being called on command events --- spec/command-registry-spec.coffee | 10 ++++++++++ src/command-registry.coffee | 6 +++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/spec/command-registry-spec.coffee b/spec/command-registry-spec.coffee index 28071932a..a76767523 100644 --- a/spec/command-registry-spec.coffee +++ b/spec/command-registry-spec.coffee @@ -57,3 +57,13 @@ describe "CommandRegistry", -> grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) expect(calls).toEqual ['.foo.bar', '.bar', '.foo'] + + it "stops bubbling through ancestors when .stopPropagation() is called on the event", -> + calls = [] + + registry.add 'command', '.parent', -> calls.push('parent') + registry.add 'command', '.child', -> calls.push('child-2') + registry.add 'command', '.child', (event) -> calls.push('child-1'); event.stopPropagation() + + grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) + expect(calls).toEqual ['child-1', 'child-2'] diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 69776319d..cb29c4a2f 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -16,11 +16,14 @@ class CommandRegistry @listenersByCommandName[commandName].push(new CommandListener(selector, callback)) dispatchCommand: (event) => + propagationStopped = false + currentTarget = event.target + syntheticEvent = Object.create event, eventPhase: value: Event.BUBBLING_PHASE currentTarget: get: -> currentTarget + stopPropagation: value: -> propagationStopped = true - currentTarget = event.target loop matchingListeners = @listenersByCommandName[event.type] @@ -30,6 +33,7 @@ class CommandRegistry for listener in matchingListeners listener.callback.call(currentTarget, syntheticEvent) + break if propagationStopped break if currentTarget is @rootNode currentTarget = currentTarget.parentNode From fe27ebec1bc206c776d2c5b77de1512e680f9fee Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 Sep 2014 09:12:42 -0600 Subject: [PATCH 005/211] Handle .stopImmediatePropagation() being called on command events --- spec/command-registry-spec.coffee | 10 ++++++++++ src/command-registry.coffee | 8 +++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/spec/command-registry-spec.coffee b/spec/command-registry-spec.coffee index a76767523..2b28c175a 100644 --- a/spec/command-registry-spec.coffee +++ b/spec/command-registry-spec.coffee @@ -67,3 +67,13 @@ describe "CommandRegistry", -> grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) expect(calls).toEqual ['child-1', 'child-2'] + + it "stops invoking callbacks when .stopImmediatePropagation() is called on the event", -> + calls = [] + + registry.add 'command', '.parent', -> calls.push('parent') + registry.add 'command', '.child', -> calls.push('child-2') + registry.add 'command', '.child', (event) -> calls.push('child-1'); event.stopImmediatePropagation() + + grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) + expect(calls).toEqual ['child-1'] diff --git a/src/command-registry.coffee b/src/command-registry.coffee index cb29c4a2f..650dd8450 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -17,12 +17,17 @@ class CommandRegistry dispatchCommand: (event) => propagationStopped = false + immediatePropagationStopped = false currentTarget = event.target syntheticEvent = Object.create event, eventPhase: value: Event.BUBBLING_PHASE currentTarget: get: -> currentTarget - stopPropagation: value: -> propagationStopped = true + stopPropagation: value: -> + propagationStopped = true + stopImmediatePropagation: value: -> + propagationStopped = true + immediatePropagationStopped = true loop matchingListeners = @@ -31,6 +36,7 @@ class CommandRegistry .sort (a, b) -> a.compare(b) for listener in matchingListeners + break if immediatePropagationStopped listener.callback.call(currentTarget, syntheticEvent) break if propagationStopped From 4de0865d289764c82e959b2378569dd313dcb39e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 Sep 2014 09:19:59 -0600 Subject: [PATCH 006/211] Allow listeners to be removed via a Disposable returned from ::add --- spec/command-registry-spec.coffee | 15 +++++++++++++++ src/command-registry.coffee | 11 ++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/spec/command-registry-spec.coffee b/spec/command-registry-spec.coffee index 2b28c175a..0a2321cdc 100644 --- a/spec/command-registry-spec.coffee +++ b/spec/command-registry-spec.coffee @@ -77,3 +77,18 @@ describe "CommandRegistry", -> grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) expect(calls).toEqual ['child-1'] + + it "allows listeners to be removed via a disposable returned by ::add", -> + calls = [] + + disposable1 = registry.add 'command', '.parent', -> calls.push('parent') + disposable2 = registry.add 'command', '.child', -> calls.push('child') + + disposable1.dispose() + grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) + expect(calls).toEqual ['child'] + + calls = [] + disposable2.dispose() + grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) + expect(calls).toEqual [] diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 650dd8450..3ceb5437a 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -1,3 +1,4 @@ +{Disposable} = require 'event-kit' {specificity} = require 'clear-cut' SequenceCount = 0 @@ -13,7 +14,15 @@ class CommandRegistry @rootNode.addEventListener(commandName, @dispatchCommand, true) @listenersByCommandName[commandName] = [] - @listenersByCommandName[commandName].push(new CommandListener(selector, callback)) + listener = new CommandListener(selector, callback) + listenersForCommand = @listenersByCommandName[commandName] + listenersForCommand.push(listener) + + new Disposable => + listenersForCommand.splice(listenersForCommand.indexOf(listener), 1) + if listenersForCommand.length is 0 + delete @listenersByCommandName[commandName] + @rootNode.removeEventListener(commandName, @dispatchCommand, true) dispatchCommand: (event) => propagationStopped = false From aee33fc126a3f4966eaa69d7451c304d45d95cda Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 Sep 2014 09:49:25 -0600 Subject: [PATCH 007/211] Pass selector first to CommandRegistry::add --- spec/command-registry-spec.coffee | 28 ++++++++++++++-------------- src/command-registry.coffee | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/spec/command-registry-spec.coffee b/spec/command-registry-spec.coffee index 0a2321cdc..705f89966 100644 --- a/spec/command-registry-spec.coffee +++ b/spec/command-registry-spec.coffee @@ -18,7 +18,7 @@ describe "CommandRegistry", -> it "invokes callbacks with selectors matching the target", -> called = false - registry.add 'command', '.grandchild', (event) -> + registry.add '.grandchild', 'command', (event) -> expect(this).toBe grandchild expect(event.type).toBe 'command' expect(event.eventPhase).toBe Event.BUBBLING_PHASE @@ -32,13 +32,13 @@ describe "CommandRegistry", -> it "invokes callbacks with selectors matching ancestors of the target", -> calls = [] - registry.add 'command', '.child', (event) -> + registry.add '.child', 'command', (event) -> expect(this).toBe child expect(event.target).toBe grandchild expect(event.currentTarget).toBe child calls.push('child') - registry.add 'command', '.parent', (event) -> + registry.add '.parent', 'command', (event) -> expect(this).toBe parent expect(event.target).toBe grandchild expect(event.currentTarget).toBe parent @@ -51,9 +51,9 @@ describe "CommandRegistry", -> child.classList.add('foo', 'bar') calls = [] - registry.add 'command', '.foo.bar', -> calls.push('.foo.bar') - registry.add 'command', '.foo', -> calls.push('.foo') - registry.add 'command', '.bar', -> calls.push('.bar') # specificity ties favor commands added later, like CSS + registry.add '.foo.bar', 'command', -> calls.push('.foo.bar') + registry.add '.foo', 'command', -> calls.push('.foo') + registry.add '.bar', 'command', -> calls.push('.bar') # specificity ties favor commands added later, like CSS grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) expect(calls).toEqual ['.foo.bar', '.bar', '.foo'] @@ -61,9 +61,9 @@ describe "CommandRegistry", -> it "stops bubbling through ancestors when .stopPropagation() is called on the event", -> calls = [] - registry.add 'command', '.parent', -> calls.push('parent') - registry.add 'command', '.child', -> calls.push('child-2') - registry.add 'command', '.child', (event) -> calls.push('child-1'); event.stopPropagation() + registry.add '.parent', 'command', -> calls.push('parent') + registry.add '.child', 'command', -> calls.push('child-2') + registry.add '.child', 'command', (event) -> calls.push('child-1'); event.stopPropagation() grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) expect(calls).toEqual ['child-1', 'child-2'] @@ -71,9 +71,9 @@ describe "CommandRegistry", -> it "stops invoking callbacks when .stopImmediatePropagation() is called on the event", -> calls = [] - registry.add 'command', '.parent', -> calls.push('parent') - registry.add 'command', '.child', -> calls.push('child-2') - registry.add 'command', '.child', (event) -> calls.push('child-1'); event.stopImmediatePropagation() + registry.add '.parent', 'command', -> calls.push('parent') + registry.add '.child', 'command', -> calls.push('child-2') + registry.add '.child', 'command', (event) -> calls.push('child-1'); event.stopImmediatePropagation() grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) expect(calls).toEqual ['child-1'] @@ -81,8 +81,8 @@ describe "CommandRegistry", -> it "allows listeners to be removed via a disposable returned by ::add", -> calls = [] - disposable1 = registry.add 'command', '.parent', -> calls.push('parent') - disposable2 = registry.add 'command', '.child', -> calls.push('child') + disposable1 = registry.add '.parent', 'command', -> calls.push('parent') + disposable2 = registry.add '.child', 'command', -> calls.push('child') disposable1.dispose() grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 3ceb5437a..621461429 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -9,7 +9,7 @@ class CommandRegistry constructor: (@rootNode) -> @listenersByCommandName = {} - add: (commandName, selector, callback) -> + add: (selector, commandName, callback) -> unless @listenersByCommandName[commandName]? @rootNode.addEventListener(commandName, @dispatchCommand, true) @listenersByCommandName[commandName] = [] From 43d3082d4e533b159559d0ad83cf78dec2967337 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 Sep 2014 09:54:02 -0600 Subject: [PATCH 008/211] Allow multiple commands to be registered by passing an object --- spec/command-registry-spec.coffee | 18 ++++++++++++++++++ src/command-registry.coffee | 9 ++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/spec/command-registry-spec.coffee b/spec/command-registry-spec.coffee index 705f89966..b39678c91 100644 --- a/spec/command-registry-spec.coffee +++ b/spec/command-registry-spec.coffee @@ -92,3 +92,21 @@ describe "CommandRegistry", -> disposable2.dispose() grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) expect(calls).toEqual [] + + it "allows multiple commands to be registered under one selector when called with an object", -> + calls = [] + + disposable = registry.add '.child', + 'command-1': -> calls.push('command-1') + 'command-2': -> calls.push('command-2') + + grandchild.dispatchEvent(new CustomEvent('command-1', bubbles: true)) + grandchild.dispatchEvent(new CustomEvent('command-2', bubbles: true)) + + expect(calls).toEqual ['command-1', 'command-2'] + + calls = [] + disposable.dispose() + grandchild.dispatchEvent(new CustomEvent('command-1', bubbles: true)) + grandchild.dispatchEvent(new CustomEvent('command-2', bubbles: true)) + expect(calls).toEqual [] diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 621461429..2a607fb52 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -1,4 +1,4 @@ -{Disposable} = require 'event-kit' +{Disposable, CompositeDisposable} = require 'event-kit' {specificity} = require 'clear-cut' SequenceCount = 0 @@ -10,6 +10,13 @@ class CommandRegistry @listenersByCommandName = {} add: (selector, commandName, callback) -> + if typeof commandName is 'object' + commands = commandName + disposable = new CompositeDisposable + for commandName, callback of commands + disposable.add @add(selector, commandName, callback) + return disposable + unless @listenersByCommandName[commandName]? @rootNode.addEventListener(commandName, @dispatchCommand, true) @listenersByCommandName[commandName] = [] From 7580d64d2e2fcc364e7748fb129da561ff4ef7bf Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 Sep 2014 15:48:10 -0600 Subject: [PATCH 009/211] Allow CommandRegistry::rootNode to be assigned after construction --- src/command-registry.coffee | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 2a607fb52..eb1a45ea2 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -9,6 +9,12 @@ class CommandRegistry constructor: (@rootNode) -> @listenersByCommandName = {} + setRootNode: (rootNode) -> + throw new Error("Already assigned root node.") if @rootNode? + @rootNode = rootNode + for commandName of @listenersByCommandName + @rootNode?.addEventListener(commandName, @dispatchCommand, true) + add: (selector, commandName, callback) -> if typeof commandName is 'object' commands = commandName @@ -18,7 +24,7 @@ class CommandRegistry return disposable unless @listenersByCommandName[commandName]? - @rootNode.addEventListener(commandName, @dispatchCommand, true) + @rootNode?.addEventListener(commandName, @dispatchCommand, true) @listenersByCommandName[commandName] = [] listener = new CommandListener(selector, callback) From dac127c30be992364060023f596d57084836ae57 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 Sep 2014 15:48:31 -0600 Subject: [PATCH 010/211] Assign atom.commands to a CommandRegistry instance --- src/atom.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/atom.coffee b/src/atom.coffee index dd87eb6a4..20c046396 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -158,6 +158,7 @@ class Atom extends Model Config = require './config' KeymapManager = require './keymap-extensions' + CommandRegistry = require './command-registry' PackageManager = require './package-manager' Clipboard = require './clipboard' Syntax = require './syntax' @@ -179,6 +180,7 @@ class Atom extends Model @config = new Config({configDirPath, resourcePath}) @keymaps = new KeymapManager({configDirPath, resourcePath}) @keymap = @keymaps # Deprecated + @commands = new CommandRegistry @packages = new PackageManager({devMode, configDirPath, resourcePath, safeMode}) @themes = new ThemeManager({packageManager: @packages, configDirPath, resourcePath, safeMode}) @contextMenu = new ContextMenuManager({resourcePath, devMode}) @@ -304,6 +306,7 @@ class Atom extends Model startTime = Date.now() @workspace = Workspace.deserialize(@state.workspace) ? new Workspace @workspaceView = new WorkspaceView(@workspace) + @commands.setRootNode(@workspaceView[0]) @deserializeTimings.workspace = Date.now() - startTime @keymaps.defaultTarget = @workspaceView[0] From a348ecf03499a7f58daf3848991c3fac5f2f5e7d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 Sep 2014 16:10:03 -0600 Subject: [PATCH 011/211] Allow CommandRegistry::rootNode to be reassigned --- src/command-registry.coffee | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/command-registry.coffee b/src/command-registry.coffee index eb1a45ea2..22f3fb392 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -9,11 +9,13 @@ class CommandRegistry constructor: (@rootNode) -> @listenersByCommandName = {} - setRootNode: (rootNode) -> - throw new Error("Already assigned root node.") if @rootNode? - @rootNode = rootNode + setRootNode: (newRootNode) -> + oldRootNode = @rootNode + @rootNode = newRootNode + for commandName of @listenersByCommandName - @rootNode?.addEventListener(commandName, @dispatchCommand, true) + oldRootNode?.removeEventListener(commandName, @dispatchCommand, true) + newRootNode?.addEventListener(commandName, @dispatchCommand, true) add: (selector, commandName, callback) -> if typeof commandName is 'object' From ab8ac369df07f0bba7c0cd9bf0001be4d5cdd19c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 Sep 2014 16:10:20 -0600 Subject: [PATCH 012/211] Move a few workspace commands to the command registry --- src/workspace-view.coffee | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 067600eb0..33482853c 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -15,6 +15,11 @@ PaneRowView = require './pane-row-view' PaneContainerView = require './pane-container-view' Editor = require './editor' +atom.commands.add '.workspace', + 'window:increase-font-size': -> @getModel().increaseFontSize() + 'window:decrease-font-size': -> @getModel().decreaseFontSize() + 'window:reset-font-size': -> @getModel().resetFontSize() + # Essential: The top-level view for the entire window. An instance of this class is # available via the `atom.workspaceView` global. # @@ -77,8 +82,9 @@ class WorkspaceView extends View @div class: 'vertical', outlet: 'vertical', => @div class: 'panes', outlet: 'panes' - initialize: (@model) -> - @model = atom.workspace ? new Workspace unless @model? + initialize: (model) -> + @model = model ? atom.workspace ? new Workspace unless @model? + @element.getModel = -> model panes = new PaneContainerView(@model.paneContainer) @panes.replaceWith(panes) @@ -139,9 +145,6 @@ class WorkspaceView extends View @command 'window:install-shell-commands', => @installShellCommands() @command 'window:run-package-specs', -> ipc.send('run-package-specs', path.join(atom.project.getPath(), 'spec')) - @command 'window:increase-font-size', => @increaseFontSize() - @command 'window:decrease-font-size', => @decreaseFontSize() - @command 'window:reset-font-size', => @model.resetFontSize() @command 'window:focus-next-pane', => @focusNextPaneView() @command 'window:focus-previous-pane', => @focusPreviousPaneView() From 155fb675ec1476c290fe43ca47c84ca276506107 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 Sep 2014 11:52:30 -0600 Subject: [PATCH 013/211] :lipstick: group dispatch specs --- spec/command-registry-spec.coffee | 143 +++++++++++++++--------------- 1 file changed, 72 insertions(+), 71 deletions(-) diff --git a/spec/command-registry-spec.coffee b/spec/command-registry-spec.coffee index b39678c91..35b8a02b6 100644 --- a/spec/command-registry-spec.coffee +++ b/spec/command-registry-spec.coffee @@ -16,97 +16,98 @@ describe "CommandRegistry", -> registry = new CommandRegistry(parent) - it "invokes callbacks with selectors matching the target", -> - called = false - registry.add '.grandchild', 'command', (event) -> - expect(this).toBe grandchild - expect(event.type).toBe 'command' - expect(event.eventPhase).toBe Event.BUBBLING_PHASE - expect(event.target).toBe grandchild - expect(event.currentTarget).toBe grandchild - called = true + describe "command dispatch", -> + it "invokes callbacks with selectors matching the target", -> + called = false + registry.add '.grandchild', 'command', (event) -> + expect(this).toBe grandchild + expect(event.type).toBe 'command' + expect(event.eventPhase).toBe Event.BUBBLING_PHASE + expect(event.target).toBe grandchild + expect(event.currentTarget).toBe grandchild + called = true - grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) - expect(called).toBe true + grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) + expect(called).toBe true - it "invokes callbacks with selectors matching ancestors of the target", -> - calls = [] + it "invokes callbacks with selectors matching ancestors of the target", -> + calls = [] - registry.add '.child', 'command', (event) -> - expect(this).toBe child - expect(event.target).toBe grandchild - expect(event.currentTarget).toBe child - calls.push('child') + registry.add '.child', 'command', (event) -> + expect(this).toBe child + expect(event.target).toBe grandchild + expect(event.currentTarget).toBe child + calls.push('child') - registry.add '.parent', 'command', (event) -> - expect(this).toBe parent - expect(event.target).toBe grandchild - expect(event.currentTarget).toBe parent - calls.push('parent') + registry.add '.parent', 'command', (event) -> + expect(this).toBe parent + expect(event.target).toBe grandchild + expect(event.currentTarget).toBe parent + calls.push('parent') - grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) - expect(calls).toEqual ['child', 'parent'] + grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) + expect(calls).toEqual ['child', 'parent'] - it "orders multiple matching listeners for an element by selector specificity", -> - child.classList.add('foo', 'bar') - calls = [] + it "orders multiple matching listeners for an element by selector specificity", -> + child.classList.add('foo', 'bar') + calls = [] - registry.add '.foo.bar', 'command', -> calls.push('.foo.bar') - registry.add '.foo', 'command', -> calls.push('.foo') - registry.add '.bar', 'command', -> calls.push('.bar') # specificity ties favor commands added later, like CSS + registry.add '.foo.bar', 'command', -> calls.push('.foo.bar') + registry.add '.foo', 'command', -> calls.push('.foo') + registry.add '.bar', 'command', -> calls.push('.bar') # specificity ties favor commands added later, like CSS - grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) - expect(calls).toEqual ['.foo.bar', '.bar', '.foo'] + grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) + expect(calls).toEqual ['.foo.bar', '.bar', '.foo'] - it "stops bubbling through ancestors when .stopPropagation() is called on the event", -> - calls = [] + it "stops bubbling through ancestors when .stopPropagation() is called on the event", -> + calls = [] - registry.add '.parent', 'command', -> calls.push('parent') - registry.add '.child', 'command', -> calls.push('child-2') - registry.add '.child', 'command', (event) -> calls.push('child-1'); event.stopPropagation() + registry.add '.parent', 'command', -> calls.push('parent') + registry.add '.child', 'command', -> calls.push('child-2') + registry.add '.child', 'command', (event) -> calls.push('child-1'); event.stopPropagation() - grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) - expect(calls).toEqual ['child-1', 'child-2'] + grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) + expect(calls).toEqual ['child-1', 'child-2'] - it "stops invoking callbacks when .stopImmediatePropagation() is called on the event", -> - calls = [] + it "stops invoking callbacks when .stopImmediatePropagation() is called on the event", -> + calls = [] - registry.add '.parent', 'command', -> calls.push('parent') - registry.add '.child', 'command', -> calls.push('child-2') - registry.add '.child', 'command', (event) -> calls.push('child-1'); event.stopImmediatePropagation() + registry.add '.parent', 'command', -> calls.push('parent') + registry.add '.child', 'command', -> calls.push('child-2') + registry.add '.child', 'command', (event) -> calls.push('child-1'); event.stopImmediatePropagation() - grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) - expect(calls).toEqual ['child-1'] + grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) + expect(calls).toEqual ['child-1'] - it "allows listeners to be removed via a disposable returned by ::add", -> - calls = [] + it "allows listeners to be removed via a disposable returned by ::add", -> + calls = [] - disposable1 = registry.add '.parent', 'command', -> calls.push('parent') - disposable2 = registry.add '.child', 'command', -> calls.push('child') + disposable1 = registry.add '.parent', 'command', -> calls.push('parent') + disposable2 = registry.add '.child', 'command', -> calls.push('child') - disposable1.dispose() - grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) - expect(calls).toEqual ['child'] + disposable1.dispose() + grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) + expect(calls).toEqual ['child'] - calls = [] - disposable2.dispose() - grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) - expect(calls).toEqual [] + calls = [] + disposable2.dispose() + grandchild.dispatchEvent(new CustomEvent('command', bubbles: true)) + expect(calls).toEqual [] - it "allows multiple commands to be registered under one selector when called with an object", -> - calls = [] + it "allows multiple commands to be registered under one selector when called with an object", -> + calls = [] - disposable = registry.add '.child', - 'command-1': -> calls.push('command-1') - 'command-2': -> calls.push('command-2') + disposable = registry.add '.child', + 'command-1': -> calls.push('command-1') + 'command-2': -> calls.push('command-2') - grandchild.dispatchEvent(new CustomEvent('command-1', bubbles: true)) - grandchild.dispatchEvent(new CustomEvent('command-2', bubbles: true)) + grandchild.dispatchEvent(new CustomEvent('command-1', bubbles: true)) + grandchild.dispatchEvent(new CustomEvent('command-2', bubbles: true)) - expect(calls).toEqual ['command-1', 'command-2'] + expect(calls).toEqual ['command-1', 'command-2'] - calls = [] - disposable.dispose() - grandchild.dispatchEvent(new CustomEvent('command-1', bubbles: true)) - grandchild.dispatchEvent(new CustomEvent('command-2', bubbles: true)) - expect(calls).toEqual [] + calls = [] + disposable.dispose() + grandchild.dispatchEvent(new CustomEvent('command-1', bubbles: true)) + grandchild.dispatchEvent(new CustomEvent('command-2', bubbles: true)) + expect(calls).toEqual [] From 3e0e19d51b45113734c06165125f047480b3cf5a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 Sep 2014 12:42:55 -0600 Subject: [PATCH 014/211] Add CommandRegistry::findCommands --- spec/command-registry-spec.coffee | 13 +++++++++++++ src/command-registry.coffee | 17 +++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/spec/command-registry-spec.coffee b/spec/command-registry-spec.coffee index 35b8a02b6..32f87a44c 100644 --- a/spec/command-registry-spec.coffee +++ b/spec/command-registry-spec.coffee @@ -111,3 +111,16 @@ describe "CommandRegistry", -> grandchild.dispatchEvent(new CustomEvent('command-1', bubbles: true)) grandchild.dispatchEvent(new CustomEvent('command-2', bubbles: true)) expect(calls).toEqual [] + + describe "::findCommands({target})", -> + it "returns commands that can be invoked on the target or its ancestors", -> + registry.add '.parent', 'namespace:command-1', -> + registry.add '.child', 'namespace:command-2', -> + registry.add '.grandchild', 'namespace:command-3', -> + registry.add '.grandchild.no-match', 'namespace:command-4', -> + + expect(registry.findCommands(target: grandchild)).toEqual [ + {name: 'namespace:command-3', displayName: 'Namespace: Command 3'} + {name: 'namespace:command-2', displayName: 'Namespace: Command 2'} + {name: 'namespace:command-1', displayName: 'Namespace: Command 1'} + ] diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 22f3fb392..4b31caff2 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -1,5 +1,6 @@ {Disposable, CompositeDisposable} = require 'event-kit' {specificity} = require 'clear-cut' +_ = require 'underscore-plus' SequenceCount = 0 SpecificityCache = {} @@ -67,6 +68,22 @@ class CommandRegistry break if currentTarget is @rootNode currentTarget = currentTarget.parentNode + findCommands: ({target}) -> + commands = [] + currentTarget = target + loop + for commandName, listeners of @listenersByCommandName + for listener in listeners + if currentTarget.webkitMatchesSelector(listener.selector) + commands.push + name: commandName + displayName: _.humanizeEventName(commandName) + + break if currentTarget is @rootNode + currentTarget = currentTarget.parentNode + + commands + class CommandListener constructor: (@selector, @callback) -> @specificity = (SpecificityCache[@selector] ?= specificity(@selector)) From 99cf8fabc03e6609bdf6692e5b7c27740c236aaf Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 Sep 2014 19:56:08 -0600 Subject: [PATCH 015/211] Clear atom.commands after each spec --- spec/spec-helper.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 4bad73ee7..d5107fc53 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -119,6 +119,8 @@ beforeEach -> addCustomMatchers(this) afterEach -> + atom.commands.clear() + atom.packages.deactivatePackages() atom.menu.template = [] From 8f9cf3c7905f88814db8e2bf213fe2537f02c6fa Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 11 Sep 2014 14:14:03 -0600 Subject: [PATCH 016/211] Set the atom.commands root node to document.body in beforeEach --- spec/spec-helper.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index d5107fc53..7f359536b 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -63,6 +63,7 @@ beforeEach -> atom.project = new Project(path: projectPath) atom.workspace = new Workspace() atom.keymaps.keyBindings = _.clone(keyBindingsToRestore) + atom.commands.setRootNode(document.body) window.resetTimeouts() atom.packages.packageStates = {} From e4264035d8ef65d984f117617e457c34f7fd1685 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 11 Sep 2014 14:15:35 -0600 Subject: [PATCH 017/211] fixup! Clear atom.commands after each spec --- src/command-registry.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 4b31caff2..00948d305 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -84,6 +84,9 @@ class CommandRegistry commands + clear: -> + @listenersByCommandName = {} + class CommandListener constructor: (@selector, @callback) -> @specificity = (SpecificityCache[@selector] ?= specificity(@selector)) From 8b7b94642971a76acfa6fe43be09b6c2f82e219e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 11 Sep 2014 14:16:12 -0600 Subject: [PATCH 018/211] =?UTF-8?q?Set=20root=20node=20to=20workspace=20vi?= =?UTF-8?q?ew=20when=20it=E2=80=99s=20constructed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Makes it easier to deal with specs --- src/atom.coffee | 1 - src/workspace-view.coffee | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atom.coffee b/src/atom.coffee index 20c046396..b3c1a4ad7 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -306,7 +306,6 @@ class Atom extends Model startTime = Date.now() @workspace = Workspace.deserialize(@state.workspace) ? new Workspace @workspaceView = new WorkspaceView(@workspace) - @commands.setRootNode(@workspaceView[0]) @deserializeTimings.workspace = Date.now() - startTime @keymaps.defaultTarget = @workspaceView[0] diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 33482853c..b97c178c6 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -85,6 +85,7 @@ class WorkspaceView extends View initialize: (model) -> @model = model ? atom.workspace ? new Workspace unless @model? @element.getModel = -> model + atom.commands.setRootNode(@[0]) panes = new PaneContainerView(@model.paneContainer) @panes.replaceWith(panes) From fb6a184b0e0df766aadf188a46d181f033099375 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 11 Sep 2014 14:16:34 -0600 Subject: [PATCH 019/211] Include commands registered via jQuery in CommandRegistry::findCommands --- src/command-registry.coffee | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 00948d305..4559c62e5 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -1,6 +1,7 @@ {Disposable, CompositeDisposable} = require 'event-kit' {specificity} = require 'clear-cut' _ = require 'underscore-plus' +{$} = require './space-pen-extensions' SequenceCount = 0 SpecificityCache = {} @@ -70,6 +71,7 @@ class CommandRegistry findCommands: ({target}) -> commands = [] + target = @rootNode unless @rootNode.contains(target) currentTarget = target loop for commandName, listeners of @listenersByCommandName @@ -82,6 +84,12 @@ class CommandRegistry break if currentTarget is @rootNode currentTarget = currentTarget.parentNode + for name, displayName of $(target).events() when displayName + commands.push({name, displayName, jQuery: true}) + + for name, displayName of $(window).events() when displayName + commands.push({name, displayName, jQuery: true}) + commands clear: -> From 98a51005c3f3db72658112d6789f8b851fb13b8c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 11 Sep 2014 14:25:53 -0600 Subject: [PATCH 020/211] Upgrade command-palette to use `atom.commands` global --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 93931377e..cd7656c85 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "background-tips": "0.16.0", "bookmarks": "0.28.0", "bracket-matcher": "0.55.0", - "command-palette": "0.24.0", + "command-palette": "0.25.0", "deprecation-cop": "0.10.0", "dev-live-reload": "0.34.0", "exception-reporting": "0.20.0", From a069f34ad680dcd6eef35b02eb7f0e269f95e1ef Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 16 Sep 2014 09:23:52 -0600 Subject: [PATCH 021/211] Fix CommandRegistry::findCommands spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don’t assert against commands registered on the window with jQuery --- spec/command-registry-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/command-registry-spec.coffee b/spec/command-registry-spec.coffee index 32f87a44c..8985e075b 100644 --- a/spec/command-registry-spec.coffee +++ b/spec/command-registry-spec.coffee @@ -119,7 +119,7 @@ describe "CommandRegistry", -> registry.add '.grandchild', 'namespace:command-3', -> registry.add '.grandchild.no-match', 'namespace:command-4', -> - expect(registry.findCommands(target: grandchild)).toEqual [ + expect(registry.findCommands(target: grandchild)[0..2]).toEqual [ {name: 'namespace:command-3', displayName: 'Namespace: Command 3'} {name: 'namespace:command-2', displayName: 'Namespace: Command 2'} {name: 'namespace:command-1', displayName: 'Namespace: Command 1'} From 1f95d8069a84e82a2f1c1464635395c890a48d9b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 16 Sep 2014 10:08:17 -0600 Subject: [PATCH 022/211] Document CommandRegistry --- src/atom.coffee | 3 ++ src/command-registry.coffee | 73 +++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/atom.coffee b/src/atom.coffee index b3c1a4ad7..0432024c9 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -113,6 +113,9 @@ class Atom extends Model # Public: A {KeymapManager} instance keymaps: null + # Public: A {CommandRegistry} instance + commands: null + # Public: A {MenuManager} instance menu: null diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 4559c62e5..2abb4d3e8 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -7,6 +7,41 @@ SequenceCount = 0 SpecificityCache = {} module.exports = + +# Experimental: Associates listener functions with commands in a +# context-sensitive way using CSS selectors. You can access a global instance of +# this class via `atom.commands`, and commands registered there will be +# presented in the command palette. +# +# The global command registry facilitates a style of event handling known as +# *event delegation* that was popularized by jQuery. Atom commands are expressed +# as custom DOM events that can be invoked on the currently focused element via +# a key binding or manually via the command palette. Rather than binding +# listeners for command events directly to DOM nodes, you instead register +# command event listeners globally on `atom.commands` and constrain them to +# specific kinds of elements with CSS selectors. +# +# As the event bubbles upward through the DOM, all registered event listeners +# with matching selectors are invoked in order of specificity. In the event of a +# specificity tie, the most recently registered listener is invoked first. This +# mirrors the "cascade" semantics of CSS. Event listeners are invoked in the +# context of the current DOM node, meaning `this` always points at +# `event.currentTarget`. As is normally the case with DOM events, +# `stopPropagation` and `stopImmediatePropagation` can be used to terminate the +# bubbling process and prevent invocation of additional listeners. +# +# ## Example +# +# Here is a command that inserts the current date in an editor: +# +# ```coffee +# atom.commands.add '.editor', +# 'user:insert-date': (event) -> +# editor = $(this).view().getModel() +# # soon the above above line will be: +# # editor = @getModel() +# editor.insertText(new Date().toLocaleString()) +# ``` class CommandRegistry constructor: (@rootNode) -> @listenersByCommandName = {} @@ -19,6 +54,31 @@ class CommandRegistry oldRootNode?.removeEventListener(commandName, @dispatchCommand, true) newRootNode?.addEventListener(commandName, @dispatchCommand, true) + # Public: Add one or more command listeners associated with a selector. + # + # ## Arguments: Registering One Command + # + # * `selector` A {String} containing a CSS selector matching elements on which + # you want to handle the commands. The `,` combinator is not currently + # supported. + # * `commandName` A {String} containing the name of a command you want to + # handle such as `user:insert-date`. + # * `callback` A {Function} to call when the given command is invoked on an + # element matching the selector. It will be called with `this` referencing + # the matching DOM node. + # * `event` A standard DOM event instance. Call `stopPropagation` or + # `stopImmediatePropagation` to terminate bubbling early. + # + # ## Arguments: Registering Multiple Commands + # + # * `selector` A {String} containing a CSS selector matching elements on which + # you want to handle the commands. The `,` combinator is not currently + # supported. + # * `commands` An {Object} mapping command names like `user:insert-date` to + # listener {Function}s. + # + # Returns a {Disposable} on which `.dispose()` can be called to remove the + # added command handler(s). add: (selector, commandName, callback) -> if typeof commandName is 'object' commands = commandName @@ -69,6 +129,17 @@ class CommandRegistry break if currentTarget is @rootNode currentTarget = currentTarget.parentNode + # Public: Find all registered commands matching a query. + # + # * `params` An {Object} containing one or more of the following keys: + # * `target` A DOM node that is the hypothetical target of a given command. + # + # Returns an {Array} of {Object}s containing the following keys: + # * `name` The name of the command. For example, `user:insert-date`. + # * `displayName` The display name of the command. For example, + # `User: Insert Date`. + # * `jQuery` Present if the command was registered with the legacy + # `$::command` method. findCommands: ({target}) -> commands = [] target = @rootNode unless @rootNode.contains(target) @@ -97,6 +168,8 @@ class CommandRegistry class CommandListener constructor: (@selector, @callback) -> + console.log "calc specificity", @selector + @specificity = (SpecificityCache[@selector] ?= specificity(@selector)) @sequenceNumber = SequenceCount++ From f12b70e3b70e7db578263d433367b5e572b89696 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 17:30:52 -0700 Subject: [PATCH 023/211] Editor::onDidChangeScreenLines -> onDidChange --- spec/editor-component-spec.coffee | 2 +- src/editor-component.coffee | 2 +- src/editor.coffee | 20 +++++++++++++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/spec/editor-component-spec.coffee b/spec/editor-component-spec.coffee index 86f70e9d1..282bc9854 100644 --- a/spec/editor-component-spec.coffee +++ b/spec/editor-component-spec.coffee @@ -2195,7 +2195,7 @@ describe "EditorComponent", -> editor.setSoftWrapped(true) callingOrder = [] - editor.onDidChangeScreenLines -> callingOrder.push 'screen-lines-changed' + editor.onDidChange -> callingOrder.push 'screen-lines-changed' wrapperView.on 'editor:display-updated', -> callingOrder.push 'editor:display-updated' editor.insertText("HELLO! HELLO!\n HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! ") nextAnimationFrame() diff --git a/src/editor-component.coffee b/src/editor-component.coffee index 646ff690c..dd4095d1d 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -350,7 +350,7 @@ EditorComponent = React.createClass observeEditor: -> {editor} = @props - @subscribe editor.onDidChangeScreenLines(@onScreenLinesChanged) + @subscribe editor.onDidChange(@onScreenLinesChanged) @subscribe editor.observeCursors(@onCursorAdded) @subscribe editor.observeSelections(@onSelectionAdded) @subscribe editor.observeDecorations(@onDecorationAdded) diff --git a/src/editor.coffee b/src/editor.coffee index e66110d8e..a89e21cf2 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -155,7 +155,7 @@ class Editor extends Model @subscribe @displayBuffer.onDidTokenize => @handleTokenization() @subscribe @displayBuffer.onDidChange (e) => @emit 'screen-lines-changed', e - @emitter.emit 'did-change-screen-lines', e + @emitter.emit 'did-change', e # TODO: remove these when we remove the deprecations. Though, no one is likely using them @subscribe @displayBuffer.onDidChangeSoftWrapped (softWrapped) => @emit 'soft-wrap-changed', softWrapped @@ -192,6 +192,19 @@ class Editor extends Model onDidChangePath: (callback) -> @emitter.on 'did-change-path', callback + # Essential: Invoke the given callback synchronously when the content of the + # buffer changes. + # + # Because observers are invoked synchronously, it's important not to perform + # any expensive operations via this method. Consider {::onDidStopChanging} to + # delay expensive operations until after changes stop occurring. + # + # * `callback` {Function} + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidChange: (callback) -> + @emitter.on 'did-change', callback + # Extended: Calls your `callback` when soft wrap was enabled or disabled. # # * `callback` {Function} @@ -380,9 +393,6 @@ class Editor extends Model onDidChangeCharacterWidths: (callback) -> @displayBuffer.onDidChangeCharacterWidths(callback) - onDidChangeScreenLines: (callback) -> - @emitter.on 'did-change-screen-lines', callback - onDidChangeScrollTop: (callback) -> @emitter.on 'did-change-scroll-top', callback @@ -437,7 +447,7 @@ class Editor extends Model deprecate("Use Marker::onDidChange instead. eg. `editor::decorateMarker(...).getMarker().onDidChange()`") when 'screen-lines-changed' - deprecate("Use Editor::onDidChangeScreenLines instead") + deprecate("Use Editor::onDidChange instead") when 'scroll-top-changed' deprecate("Use Editor::onDidChangeScrollTop instead") From 2491090c9117333326ecfa7c377d64568e218b7d Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 17:31:34 -0700 Subject: [PATCH 024/211] Move Essential methods above the extended versions --- src/editor.coffee | 68 +++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index a89e21cf2..5ef3adfda 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -205,6 +205,40 @@ class Editor extends Model onDidChange: (callback) -> @emitter.on 'did-change', callback + # Essential: Invoke `callback` when the buffer's contents change. It is + # emit asynchronously 300ms after the last buffer change. This is a good place + # to handle changes to the buffer without compromising typing performance. + # + # * `callback` {Function} + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidStopChanging: (callback) -> + @getBuffer().onDidStopChanging(callback) + + # Essential: Calls your `callback` when a {Cursor} is moved. If there are + # multiple cursors, your callback will be called for each cursor. + # + # * `callback` {Function} + # * `event` {Object} + # * `oldBufferPosition` {Point} + # * `oldScreenPosition` {Point} + # * `newBufferPosition` {Point} + # * `newScreenPosition` {Point} + # * `textChanged` {Boolean} + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidChangeCursorPosition: (callback) -> + @emitter.on 'did-change-cursor-position', callback + + # Essential: Calls your `callback` when a selection's screen range changes. + # + # * `callback` {Function} + # * `selection` {Selection} that moved + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidChangeSelectionRange: (callback) -> + @emitter.on 'did-change-selection-range', callback + # Extended: Calls your `callback` when soft wrap was enabled or disabled. # # * `callback` {Function} @@ -222,16 +256,6 @@ class Editor extends Model onDidChangeGrammar: (callback) -> @emitter.on 'did-change-grammar', callback - # Essential: Calls your `callback` when the buffer's contents change. It is - # emit asynchronously 300ms after the last buffer change. This is a good place - # to handle changes to the buffer without compromising typing performance. - # - # * `callback` {Function} - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidStopChanging: (callback) -> - @getBuffer().onDidStopChanging(callback) - # Extended: Calls your `callback` when the result of {::isModified} changes. # # * `callback` {Function} @@ -309,21 +333,6 @@ class Editor extends Model onDidRemoveCursor: (callback) -> @emitter.on 'did-remove-cursor', callback - # Essential: Calls your `callback` when a {Cursor} is moved. If there are - # multiple cursors, your callback will be called for each cursor. - # - # * `callback` {Function} - # * `event` {Object} - # * `oldBufferPosition` {Point} - # * `oldScreenPosition` {Point} - # * `newBufferPosition` {Point} - # * `newScreenPosition` {Point} - # * `textChanged` {Boolean} - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeCursorPosition: (callback) -> - @emitter.on 'did-change-cursor-position', callback - # Extended: Calls your `callback` when a {Selection} is added to the editor. # Immediately calls your callback for each existing selection. # @@ -353,15 +362,6 @@ class Editor extends Model onDidRemoveSelection: (callback) -> @emitter.on 'did-remove-selection', callback - # Essential: Calls your `callback` when a selection's screen range changes. - # - # * `callback` {Function} - # * `selection` {Selection} that moved - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeSelectionRange: (callback) -> - @emitter.on 'did-change-selection-range', callback - # Extended: Calls your `callback` with each {Decoration} added to the editor. # Calls your `callback` immediately for any existing decorations. # From 719ab078cc38cc5f73348619c05d8e0bd441b29b Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:10:28 -0700 Subject: [PATCH 025/211] Convert file details section to essential extended --- src/editor.coffee | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index 5ef3adfda..279cdd06d 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -492,7 +492,7 @@ class Editor extends Model Section: File Details ### - # Public: Get the title the editor's title for display in other parts of the + # Essential: Get the title the editor's title for display in other parts of the # UI such as the tabs. # # If the editor's buffer is saved, its title is the file name. If it is @@ -505,7 +505,7 @@ class Editor extends Model else 'untitled' - # Public: Get the editor's long title for display in other parts of the UI + # Essential: Get the editor's long title for display in other parts of the UI # such as the window title. # # If the editor's buffer is saved, its long title is formatted as @@ -521,28 +521,29 @@ class Editor extends Model else 'untitled' - # Public: Returns the {String} path of this editor's text buffer. + # Essential: Returns the {String} path of this editor's text buffer. getPath: -> @buffer.getPath() - # Public: Saves the editor's text buffer. + # Essential: Saves the editor's text buffer. # # See {TextBuffer::save} for more details. save: -> @buffer.save() - # Public: Saves the editor's text buffer as the given path. + # Essential: Saves the editor's text buffer as the given path. # # See {TextBuffer::saveAs} for more details. # # * `filePath` A {String} path. saveAs: (filePath) -> @buffer.saveAs(filePath) - # Public: Determine whether the user should be prompted to save before closing + # Extended: Determine whether the user should be prompted to save before closing # this editor. shouldPromptToSave: -> @isModified() and not @buffer.hasMultipleEditors() - # Public: Returns {Boolean} `true` if this editor has been modified. + # Essential: Returns {Boolean} `true` if this editor has been modified. isModified: -> @buffer.isModified() + # Essential: Returns {Boolean} `true` if this editor has no content. isEmpty: -> @buffer.isEmpty() # Copies the current file path to the native clipboard. From 783c1dd449cd5cacc859a2a94da2c56704d73517 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:16:00 -0700 Subject: [PATCH 026/211] Convert visibility in the mutating text section --- src/editor.coffee | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index 279cdd06d..299bdc844 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -555,10 +555,10 @@ class Editor extends Model Section: Reading Text ### - # Public: Returns a {String} representing the entire contents of the editor. + # Essential: Returns a {String} representing the entire contents of the editor. getText: -> @buffer.getText() - # Public: Get the text in the given {Range} in buffer coordinates. + # Essential: Get the text in the given {Range} in buffer coordinates. # # * `range` A {Range} or range-compatible {Array}. # @@ -566,20 +566,22 @@ class Editor extends Model getTextInBufferRange: (range) -> @buffer.getTextInRange(range) - # Public: Returns a {Number} representing the number of lines in the editor. + # Essential: Returns a {Number} representing the number of lines in the buffer. getLineCount: -> @buffer.getLineCount() - # {Delegates to: DisplayBuffer.getLineCount} + # Essential: Returns a {Number} representing the number of screen lines in the + # editor. This accounts for folds. getScreenLineCount: -> @displayBuffer.getLineCount() - # Public: Returns a {Number} representing the last zero-indexed buffer row + # Essential: Returns a {Number} representing the last zero-indexed buffer row # number of the editor. getLastBufferRow: -> @buffer.getLastRow() - # {Delegates to: DisplayBuffer.getLastRow} + # Essential: Returns a {Number} representing the last zero-indexed screen row + # number of the editor. getLastScreenRow: -> @displayBuffer.getLastRow() - # Public: Returns a {String} representing the contents of the line at the + # Essential: Returns a {String} representing the contents of the line at the # given buffer row. # # * `bufferRow` A {Number} representing a zero-indexed buffer row. @@ -588,7 +590,7 @@ class Editor extends Model deprecate 'Use Editor::lineTextForBufferRow(bufferRow) instead' @lineTextForBufferRow(bufferRow) - # Public: Returns a {String} representing the contents of the line at the + # Essential: Returns a {String} representing the contents of the line at the # given screen row. # # * `screenRow` A {Number} representing a zero-indexed screen row. From 6287f90a392d1c45ccdf77a12d07a68d46aaf475 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:16:35 -0700 Subject: [PATCH 027/211] Convert visibility in the Mutating text section --- src/editor.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index 299bdc844..8ce6a29de 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -662,10 +662,10 @@ class Editor extends Model Section: Mutating Text ### - # Public: Replaces the entire contents of the buffer with the given {String}. + # Essential: Replaces the entire contents of the buffer with the given {String}. setText: (text) -> @buffer.setText(text) - # Public: Set the text in the given {Range} in buffer coordinates. + # Essential: Set the text in the given {Range} in buffer coordinates. # # * `range` A {Range} or range-compatible {Array}. # * `text` A {String} @@ -673,7 +673,7 @@ class Editor extends Model # Returns the {Range} of the newly-inserted text. setTextInBufferRange: (range, text, normalizeLineEndings) -> @getBuffer().setTextInRange(range, text, normalizeLineEndings) - # Public: Mutate the text of all the selections in a single transaction. + # Extended: Mutate the text of all the selections in a single transaction. # # All the changes made inside the given {Function} can be reverted with a # single call to {::undo}. @@ -853,7 +853,7 @@ class Editor extends Model @addSelectionForBufferRange([[row, 0], [row, Infinity]]) @addSelectionForBufferRange([[end.row, 0], [end.row, end.column]]) unless end.column is 0 - # Public: For each selection, transpose the selected text. + # Extended: For each selection, transpose the selected text. # # If the selection is empty, the characters preceding and following the cursor # are swapped. Otherwise, the selected characters are reversed. @@ -868,14 +868,14 @@ class Editor extends Model else selection.insertText selection.getText().split('').reverse().join('') - # Public: Convert the selected text to upper case. + # Extended: Convert the selected text to upper case. # # For each selection, if the selection is empty, converts the containing word # to upper case. Otherwise convert the selected text to upper case. upperCase: -> @replaceSelectedText selectWordIfEmpty:true, (text) -> text.toUpperCase() - # Public: Convert the selected text to lower case. + # Extended: Convert the selected text to lower case. # # For each selection, if the selection is empty, converts the containing word # to upper case. Otherwise convert the selected text to upper case. From 6b71ea1875a4bba5681b0612b8fe381aeddbeb4e Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:18:23 -0700 Subject: [PATCH 028/211] Convert visibility in the Inserting Text section --- src/editor.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index 8ce6a29de..b9b3e1759 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -897,7 +897,7 @@ class Editor extends Model Section: Adding Text ### - # Public: For each selection, replace the selected text with the given text. + # Essential: For each selection, replace the selected text with the given text. # # * `text` A {String} representing the text to insert. # * `options` (optional) See {Selection::insertText}. @@ -923,17 +923,17 @@ class Editor extends Model else false - # Public: For each selection, replace the selected text with a newline. + # Essential: For each selection, replace the selected text with a newline. insertNewline: -> @insertText('\n') - # Public: For each cursor, insert a newline at beginning the following line. + # Extended: For each cursor, insert a newline at beginning the following line. insertNewlineBelow: -> @transact => @moveToEndOfLine() @insertNewline() - # Public: For each cursor, insert a newline at the end of the preceding line. + # Extended: For each cursor, insert a newline at the end of the preceding line. insertNewlineAbove: -> @transact => bufferRow = @getCursorBufferPosition().row From 49f5817b87c183b58bcf2ea672ca2ba46a66ee1a Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:22:16 -0700 Subject: [PATCH 029/211] Convert visibility of the removing text section --- src/editor.coffee | 72 +++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index b9b3e1759..b6f58f40b 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -894,7 +894,7 @@ class Editor extends Model @mutateSelectedText (selection) -> selection.joinLines() ### - Section: Adding Text + Section: Inserting Text ### # Essential: For each selection, replace the selected text with the given text. @@ -955,11 +955,45 @@ class Editor extends Model Section: Removing Text ### - # Public: For each selection, if the selection is empty, delete the character + # Essential: For each selection, if the selection is empty, delete the character + # preceding the cursor. Otherwise delete the selected text. + delete: -> + @mutateSelectedText (selection) -> selection.delete() + + # Essential: For each selection, if the selection is empty, delete the character # preceding the cursor. Otherwise delete the selected text. backspace: -> @mutateSelectedText (selection) -> selection.backspace() + # Extended: For each selection, if the selection is empty, delete all characters + # of the containing word that precede the cursor. Otherwise delete the + # selected text. + deleteToBeginningOfWord: -> + @mutateSelectedText (selection) -> selection.deleteToBeginningOfWord() + + # Extended: For each selection, if the selection is empty, delete all characters + # of the containing line that precede the cursor. Otherwise delete the + # selected text. + deleteToBeginningOfLine: -> + @mutateSelectedText (selection) -> selection.deleteToBeginningOfLine() + + # Extended: For each selection, if the selection is not empty, deletes the + # selection; otherwise, deletes all characters of the containing line + # following the cursor. If the cursor is already at the end of the line, + # deletes the following newline. + deleteToEndOfLine: -> + @mutateSelectedText (selection) -> selection.deleteToEndOfLine() + + # Extended: For each selection, if the selection is empty, delete all characters + # of the containing word following the cursor. Otherwise delete the selected + # text. + deleteToEndOfWord: -> + @mutateSelectedText (selection) -> selection.deleteToEndOfWord() + + # Extended: Delete all lines intersecting selections. + deleteLine: -> + @mutateSelectedText (selection) -> selection.deleteLine() + # Deprecated: Use {::deleteToBeginningOfWord} instead. backspaceToBeginningOfWord: -> deprecate("Use Editor::deleteToBeginningOfWord() instead") @@ -970,40 +1004,6 @@ class Editor extends Model deprecate("Use Editor::deleteToBeginningOfLine() instead") @deleteToBeginningOfLine() - # Public: For each selection, if the selection is empty, delete all characters - # of the containing word that precede the cursor. Otherwise delete the - # selected text. - deleteToBeginningOfWord: -> - @mutateSelectedText (selection) -> selection.deleteToBeginningOfWord() - - # Public: For each selection, if the selection is empty, delete all characters - # of the containing line that precede the cursor. Otherwise delete the - # selected text. - deleteToBeginningOfLine: -> - @mutateSelectedText (selection) -> selection.deleteToBeginningOfLine() - - # Public: For each selection, if the selection is empty, delete the character - # preceding the cursor. Otherwise delete the selected text. - delete: -> - @mutateSelectedText (selection) -> selection.delete() - - # Public: For each selection, if the selection is not empty, deletes the - # selection; otherwise, deletes all characters of the containing line - # following the cursor. If the cursor is already at the end of the line, - # deletes the following newline. - deleteToEndOfLine: -> - @mutateSelectedText (selection) -> selection.deleteToEndOfLine() - - # Public: For each selection, if the selection is empty, delete all characters - # of the containing word following the cursor. Otherwise delete the selected - # text. - deleteToEndOfWord: -> - @mutateSelectedText (selection) -> selection.deleteToEndOfWord() - - # Public: Delete all lines intersecting selections. - deleteLine: -> - @mutateSelectedText (selection) -> selection.deleteLine() - ### Section: Searching Text ### From 81165e0e41e896c40e5689587dc3a115824d6de9 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:29:17 -0700 Subject: [PATCH 030/211] Add docs for the Editor::scan methods --- src/editor.coffee | 48 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index b6f58f40b..916e0db18 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1008,15 +1008,49 @@ class Editor extends Model Section: Searching Text ### - # {Delegates to: TextBuffer.scan} - scan: (args...) -> @buffer.scan(args...) + # Essential: Scan regular expression matches in the entire buffer, calling the + # given iterator function on each match. + # + # If you're programmatically modifying the results, you may want to try + # {::backwardsScanInBufferRange} to avoid tripping over your own changes. + # + # * `regex` A {RegExp} to search for. + # * `iterator` A {Function} that's called on each match with an {Object} + # containing the following keys: + # * `match` The current regular expression match. + # * `matchText` A {String} with the text of the match. + # * `range` The {Range} of the match. + # * `stop` Call this {Function} to terminate the scan. + # * `replace` Call this {Function} with a {String} to replace the match. + scan: (regex, iterator) -> @buffer.scan(regex, iterator) - # {Delegates to: TextBuffer.scanInRange} - scanInBufferRange: (args...) -> @buffer.scanInRange(args...) - - # {Delegates to: TextBuffer.backwardsScanInRange} - backwardsScanInBufferRange: (args...) -> @buffer.backwardsScanInRange(args...) + # Essential: Scan regular expression matches in a given range, calling the given + # iterator function on each match. + # + # * `regex` A {RegExp} to search for. + # * `range` A {Range} in which to search. + # * `iterator` A {Function} that's called on each match with an {Object} + # containing the following keys: + # * `match` The current regular expression match. + # * `matchText` A {String} with the text of the match. + # * `range` The {Range} of the match. + # * `stop` Call this {Function} to terminate the scan. + # * `replace` Call this {Function} with a {String} to replace the match. + scanInBufferRange: (regex, range, iterator) -> @buffer.scanInRange(regex, range, iterator) + # Essential: Scan regular expression matches in a given range in reverse order, + # calling the given iterator function on each match. + # + # * `regex` A {RegExp} to search for. + # * `range` A {Range} in which to search. + # * `iterator` A {Function} that's called on each match with an {Object} + # containing the following keys: + # * `match` The current regular expression match. + # * `matchText` A {String} with the text of the match. + # * `range` The {Range} of the match. + # * `stop` Call this {Function} to terminate the scan. + # * `replace` Call this {Function} with a {String} to replace the match. + backwardsScanInBufferRange: (regex, range, iterator) -> @buffer.backwardsScanInRange(regex, range, iterator) ### Section: Tab Behavior From d17c6e409f0a4670fb62721c3b7bee37f008f0bf Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:32:17 -0700 Subject: [PATCH 031/211] Reorganize Tab Behavior section --- src/editor.coffee | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index 916e0db18..0b4d2781f 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1056,7 +1056,27 @@ class Editor extends Model Section: Tab Behavior ### - # Public: Determine if the buffer uses hard or soft tabs. + # Essential: Returns a {Boolean} indicating whether softTabs are enabled for this + # editor. + getSoftTabs: -> @softTabs + + # Essential: Enable or disable soft tabs for this editor. + # + # * `softTabs` A {Boolean} + setSoftTabs: (@softTabs) -> @softTabs + + # Essential: Toggle soft tabs for this editor + toggleSoftTabs: -> @setSoftTabs(not @getSoftTabs()) + + # Essential: Get the on-screen length of tab characters. + # + # Returns a {Number}. + getTabLength: -> @displayBuffer.getTabLength() + + # Essential: Set the on-screen length of tab characters. + setTabLength: (tabLength) -> @displayBuffer.setTabLength(tabLength) + + # Extended: Determine if the buffer uses hard or soft tabs. # # Returns `true` if the first non-comment line with leading whitespace starts # with a space character. Returns `false` if it starts with a hard tab (`\t`). @@ -1073,19 +1093,7 @@ class Editor extends Model undefined - # Public: Returns a {Boolean} indicating whether softTabs are enabled for this - # editor. - getSoftTabs: -> @softTabs - - # Public: Enable or disable soft tabs for this editor. - # - # * `softTabs` A {Boolean} - setSoftTabs: (@softTabs) -> @softTabs - - # Public: Toggle soft tabs for this editor - toggleSoftTabs: -> @setSoftTabs(not @getSoftTabs()) - - # Public: Get the text representing a single level of indent. + # Extended: Get the text representing a single level of indent. # # If soft tabs are enabled, the text is composed of N spaces, where N is the # tab length. Otherwise the text is a tab character (`\t`). @@ -1093,14 +1101,6 @@ class Editor extends Model # Returns a {String}. getTabText: -> @buildIndentString(1) - # Public: Get the on-screen length of tab characters. - # - # Returns a {Number}. - getTabLength: -> @displayBuffer.getTabLength() - - # Public: Set the on-screen length of tab characters. - setTabLength: (tabLength) -> @displayBuffer.setTabLength(tabLength) - # If soft tabs are enabled, convert all hard tabs to soft tabs in the given # {Range}. normalizeTabsInBufferRange: (bufferRange) -> From 30ced48d23d234aae59dc412f70cc056882fcfa4 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:34:05 -0700 Subject: [PATCH 032/211] Reorganize Soft Wrap Behavior section --- src/editor.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index 0b4d2781f..4f941afd2 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1111,10 +1111,7 @@ class Editor extends Model Section: Soft Wrap Behavior ### - # Public: Sets the column at which column will soft wrap - getSoftWrapColumn: -> @displayBuffer.getSoftWrapColumn() - - # Public: Determine whether lines in this editor are soft-wrapped. + # Essential: Determine whether lines in this editor are soft-wrapped. # # Returns a {Boolean}. isSoftWrapped: (softWrapped) -> @displayBuffer.isSoftWrapped() @@ -1122,7 +1119,7 @@ class Editor extends Model deprecate("Use Editor::isSoftWrapped instead") @displayBuffer.isSoftWrapped() - # Public: Enable or disable soft wrapping for this editor. + # Essential: Enable or disable soft wrapping for this editor. # # * `softWrapped` A {Boolean} # @@ -1132,7 +1129,7 @@ class Editor extends Model deprecate("Use Editor::setSoftWrapped instead") @setSoftWrapped(softWrapped) - # Public: Toggle soft wrapping for this editor + # Essential: Toggle soft wrapping for this editor # # Returns a {Boolean}. toggleSoftWrapped: -> @setSoftWrapped(not @isSoftWrapped()) @@ -1140,6 +1137,9 @@ class Editor extends Model deprecate("Use Editor::toggleSoftWrapped instead") @toggleSoftWrapped() + # Extended: Gets the column at which column will soft wrap + getSoftWrapColumn: -> @displayBuffer.getSoftWrapColumn() + ### Section: Indentation ### From 5f807df1b07d5f9fadc87f1b6b02e4389c7e8a1f Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:38:20 -0700 Subject: [PATCH 033/211] Reorganize the indentation section --- src/editor.coffee | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index 4f941afd2..76d5bb68c 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1144,7 +1144,7 @@ class Editor extends Model Section: Indentation ### - # Public: Get the indentation level of the given a buffer row. + # Essential: Get the indentation level of the given a buffer row. # # Returns how deeply the given row is indented based on the soft tabs and # tab length settings of this editor. Note that if soft tabs are enabled and @@ -1157,7 +1157,7 @@ class Editor extends Model indentationForBufferRow: (bufferRow) -> @indentLevelForLine(@lineTextForBufferRow(bufferRow)) - # Public: Set the indentation level for the given buffer row. + # Essential: Set the indentation level for the given buffer row. # # Inserts or removes hard tabs or spaces based on the soft tabs and tab length # settings of this editor in order to bring it to the given indentation level. @@ -1177,7 +1177,15 @@ class Editor extends Model newIndentString = @buildIndentString(newLevel) @buffer.setTextInRange([[bufferRow, 0], [bufferRow, endColumn]], newIndentString) - # Public: Get the indentation level of the given line of text. + # Extended: Indent rows intersecting selections by one level. + indentSelectedRows: -> + @mutateSelectedText (selection) -> selection.indentSelectedRows() + + # Extended: Outdent rows intersecting selections by one level. + outdentSelectedRows: -> + @mutateSelectedText (selection) -> selection.outdentSelectedRows() + + # Extended: Get the indentation level of the given line of text. # # Returns how deeply the given line is indented based on the soft tabs and # tab length settings of this editor. Note that if soft tabs are enabled and @@ -1190,25 +1198,17 @@ class Editor extends Model indentLevelForLine: (line) -> @displayBuffer.indentLevelForLine(line) + # Extended: Indent rows intersecting selections based on the grammar's suggested + # indent level. + autoIndentSelectedRows: -> + @mutateSelectedText (selection) -> selection.autoIndentSelectedRows() + # Indent all lines intersecting selections. See {Selection::indent} for more # information. indent: (options={}) -> options.autoIndent ?= @shouldAutoIndent() @mutateSelectedText (selection) -> selection.indent(options) - # Public: Indent rows intersecting selections by one level. - indentSelectedRows: -> - @mutateSelectedText (selection) -> selection.indentSelectedRows() - - # Public: Outdent rows intersecting selections by one level. - outdentSelectedRows: -> - @mutateSelectedText (selection) -> selection.outdentSelectedRows() - - # Public: Indent rows intersecting selections based on the grammar's suggested - # indent level. - autoIndentSelectedRows: -> - @mutateSelectedText (selection) -> selection.autoIndentSelectedRows() - # Constructs the string used for tabs. buildIndentString: (number, column=0) -> if @getSoftTabs() From 5790221c1598e3d726a7bb3a7dd42cadb366f05e Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:38:47 -0700 Subject: [PATCH 034/211] Reorg undo section --- src/editor.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index 76d5bb68c..4468416de 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1221,12 +1221,12 @@ class Editor extends Model Section: Undo Operations ### - # Public: Undo the last change. + # Essential: Undo the last change. undo: -> @getLastCursor().needsAutoscroll = true @buffer.undo(this) - # Public: Redo the last change. + # Essential: Redo the last change. redo: -> @getLastCursor().needsAutoscroll = true @buffer.redo(this) From 4238052dc317e15e738d8991e3b2c8eeee07b9d5 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:40:32 -0700 Subject: [PATCH 035/211] Move text mutation transactions into the mutating text section --- src/editor.coffee | 60 ++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index 4468416de..0d8332f34 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -893,6 +893,34 @@ class Editor extends Model joinLines: -> @mutateSelectedText (selection) -> selection.joinLines() + # Extended: Batch multiple operations as a single undo/redo step. + # + # Any group of operations that are logically grouped from the perspective of + # undoing and redoing should be performed in a transaction. If you want to + # abort the transaction, call {::abortTransaction} to terminate the function's + # execution and revert any changes performed up to the abortion. + # + # * `fn` A {Function} to call inside the transaction. + transact: (fn) -> @buffer.transact(fn) + + # Extended: Start an open-ended transaction. + # + # Call {::commitTransaction} or {::abortTransaction} to terminate the + # transaction. If you nest calls to transactions, only the outermost + # transaction is considered. You must match every begin with a matching + # commit, but a single call to abort will cancel all nested transactions. + beginTransaction: -> @buffer.beginTransaction() + + # Extended: Commit an open-ended transaction started with {::beginTransaction} + # and push it to the undo stack. + # + # If transactions are nested, only the outermost commit takes effect. + commitTransaction: -> @buffer.commitTransaction() + + # Extended: Abort an open transaction, undoing any operations performed so far + # within the transaction. + abortTransaction: -> @buffer.abortTransaction() + ### Section: Inserting Text ### @@ -1231,38 +1259,6 @@ class Editor extends Model @getLastCursor().needsAutoscroll = true @buffer.redo(this) - ### - Section: Text Mutation Transactions - ### - - # Public: Batch multiple operations as a single undo/redo step. - # - # Any group of operations that are logically grouped from the perspective of - # undoing and redoing should be performed in a transaction. If you want to - # abort the transaction, call {::abortTransaction} to terminate the function's - # execution and revert any changes performed up to the abortion. - # - # * `fn` A {Function} to call inside the transaction. - transact: (fn) -> @buffer.transact(fn) - - # Public: Start an open-ended transaction. - # - # Call {::commitTransaction} or {::abortTransaction} to terminate the - # transaction. If you nest calls to transactions, only the outermost - # transaction is considered. You must match every begin with a matching - # commit, but a single call to abort will cancel all nested transactions. - beginTransaction: -> @buffer.beginTransaction() - - # Public: Commit an open-ended transaction started with {::beginTransaction} - # and push it to the undo stack. - # - # If transactions are nested, only the outermost commit takes effect. - commitTransaction: -> @buffer.commitTransaction() - - # Public: Abort an open transaction, undoing any operations performed so far - # within the transaction. - abortTransaction: -> @buffer.abortTransaction() - ### Section: Editor Coordinates ### From d827d4ad340813c0adf9ec90c4ff0bc8cab52dc0 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:41:52 -0700 Subject: [PATCH 036/211] Change visibility on the Editor Coordinates section --- src/editor.coffee | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index 0d8332f34..095667198 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1263,7 +1263,7 @@ class Editor extends Model Section: Editor Coordinates ### - # Public: Convert a position in buffer-coordinates to screen-coordinates. + # Essential: Convert a position in buffer-coordinates to screen-coordinates. # # The position is clipped via {::clipBufferPosition} prior to the conversion. # The position is also clipped via {::clipScreenPosition} following the @@ -1275,7 +1275,7 @@ class Editor extends Model # Returns a {Point}. screenPositionForBufferPosition: (bufferPosition, options) -> @displayBuffer.screenPositionForBufferPosition(bufferPosition, options) - # Public: Convert a position in screen-coordinates to buffer-coordinates. + # Essential: Convert a position in screen-coordinates to buffer-coordinates. # # The position is clipped via {::clipScreenPosition} prior to the conversion. # @@ -1285,21 +1285,21 @@ class Editor extends Model # Returns a {Point}. bufferPositionForScreenPosition: (screenPosition, options) -> @displayBuffer.bufferPositionForScreenPosition(screenPosition, options) - # Public: Convert a range in buffer-coordinates to screen-coordinates. + # Essential: Convert a range in buffer-coordinates to screen-coordinates. # # * `bufferRange` {Range} in buffer coordinates to translate into screen coordinates. # # Returns a {Range}. screenRangeForBufferRange: (bufferRange) -> @displayBuffer.screenRangeForBufferRange(bufferRange) - # Public: Convert a range in screen-coordinates to buffer-coordinates. + # Essential: Convert a range in screen-coordinates to buffer-coordinates. # # * `screenRange` {Range} in screen coordinates to translate into buffer coordinates. # # Returns a {Range}. bufferRangeForScreenRange: (screenRange) -> @displayBuffer.bufferRangeForScreenRange(screenRange) - # Public: Clip the given {Point} to a valid position in the buffer. + # Extended: Clip the given {Point} to a valid position in the buffer. # # If the given {Point} describes a position that is actually reachable by the # cursor based on the current contents of the buffer, it is returned @@ -1320,7 +1320,7 @@ class Editor extends Model # Returns a {Point}. clipBufferPosition: (bufferPosition) -> @buffer.clipPosition(bufferPosition) - # Public: Clip the start and end of the given range to valid positions in the + # Essential: Clip the start and end of the given range to valid positions in the # buffer. See {::clipBufferPosition} for more information. # # * `range` The {Range} to clip. @@ -1328,7 +1328,7 @@ class Editor extends Model # Returns a {Range}. clipBufferRange: (range) -> @buffer.clipRange(range) - # Public: Clip the given {Point} to a valid position on screen. + # Essential: Clip the given {Point} to a valid position on screen. # # If the given {Point} describes a position that is actually reachable by the # cursor based on the current contents of the screen, it is returned @@ -1353,9 +1353,6 @@ class Editor extends Model # Returns a {Point}. clipScreenPosition: (screenPosition, options) -> @displayBuffer.clipScreenPosition(screenPosition, options) - - - ### Section: Grammars ### From af184fe2ff040e704ccbf9440753995861a83080 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:42:52 -0700 Subject: [PATCH 037/211] Convert visibility on the grammars section --- src/editor.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index 095667198..9eb09f323 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1357,11 +1357,11 @@ class Editor extends Model Section: Grammars ### - # Public: Get the current {Grammar} of this editor. + # Essential: Get the current {Grammar} of this editor. getGrammar: -> @displayBuffer.getGrammar() - # Public: Set the current {Grammar} of this editor. + # Essential: Set the current {Grammar} of this editor. # # Assigning a grammar will cause the editor to re-tokenize based on the new # grammar. From 9dacdaf2ef765da743e21595418c23912dc4a7da Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:48:21 -0700 Subject: [PATCH 038/211] Reorganize the Syntatic Queries section --- src/editor.coffee | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index 9eb09f323..a7eb1530a 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -882,6 +882,14 @@ class Editor extends Model lowerCase: -> @replaceSelectedText selectWordIfEmpty:true, (text) -> text.toLowerCase() + # Extended: Toggle line comments for rows intersecting selections. + # + # If the current grammar doesn't support comments, does nothing. + # + # Returns an {Array} of the commented {Range}s. + toggleLineCommentsInSelection: -> + @mutateSelectedText (selection) -> selection.toggleLineComments() + # Convert multiple lines to a single line. # # Operates on all selections. If the selection is empty, joins the current @@ -1376,7 +1384,7 @@ class Editor extends Model Section: Syntatic Queries ### - # Public: Get the syntactic scopes for the given position in buffer + # Essential: Get the syntactic scopes for the given position in buffer # coordinates. # # For example, if called with a position inside the parameter list of an @@ -1388,7 +1396,7 @@ class Editor extends Model # Returns an {Array} of {String}s. scopesForBufferPosition: (bufferPosition) -> @displayBuffer.scopesForBufferPosition(bufferPosition) - # Public: Get the range in buffer coordinates of all tokens surrounding the + # Essential: Get the range in buffer coordinates of all tokens surrounding the # cursor that match the given scope selector. # # For example, if you wanted to find the string surrounding the cursor, you @@ -1398,40 +1406,28 @@ class Editor extends Model bufferRangeForScopeAtCursor: (selector) -> @displayBuffer.bufferRangeForScopeAtPosition(selector, @getCursorBufferPosition()) - # {Delegates to: DisplayBuffer.tokenForBufferPosition} - tokenForBufferPosition: (bufferPosition) -> @displayBuffer.tokenForBufferPosition(bufferPosition) - # Public: Get the syntactic scopes for the most recently added cursor's # position. See {::scopesForBufferPosition} for more information. # # Returns an {Array} of {String}s. - getCursorScopes: -> @getLastCursor().getScopes() + scopesAtCursor: -> @getLastCursor().getScopes() + getCursorScopes: -> + deprecate 'Use Editor::scopesAtCursor() instead' + @scopesAtCursor() logCursorScope: -> console.log @getCursorScopes() + # {Delegates to: DisplayBuffer.tokenForBufferPosition} + tokenForBufferPosition: (bufferPosition) -> @displayBuffer.tokenForBufferPosition(bufferPosition) - # Public: Determine if the given row is entirely a comment + # Extended: Determine if the given row is entirely a comment isBufferRowCommented: (bufferRow) -> if match = @lineTextForBufferRow(bufferRow).match(/\S/) scopes = @tokenForBufferPosition([bufferRow, match.index]).scopes @commentScopeSelector ?= new TextMateScopeSelector('comment.*') @commentScopeSelector.matches(scopes) - # Public: Toggle line comments for rows intersecting selections. - # - # If the current grammar doesn't support comments, does nothing. - # - # Returns an {Array} of the commented {Range}s. - toggleLineCommentsInSelection: -> - @mutateSelectedText (selection) -> selection.toggleLineComments() - - - - - - - ### Section: Clipboard Operations ### From 48d02cf934a3afc7cfc291cfb972c888867faa72 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:54:55 -0700 Subject: [PATCH 039/211] Reorganize the Folds section --- src/editor.coffee | 137 +++++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 67 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index a7eb1530a..85368697a 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1432,14 +1432,21 @@ class Editor extends Model Section: Clipboard Operations ### - # Public: For each selection, copy the selected text. + # Essential: For each selection, copy the selected text. copySelectedText: -> maintainClipboard = false for selection in @getSelections() selection.copy(maintainClipboard) maintainClipboard = true - # Public: For each selection, replace the selected text with the contents of + # Essential: For each selection, cut the selected text. + cutSelectedText: -> + maintainClipboard = false + @mutateSelectedText (selection) -> + selection.cut(maintainClipboard) + maintainClipboard = true + + # Essential: For each selection, replace the selected text with the contents of # the clipboard. # # If the clipboard contains the same number of selections as the current @@ -1465,14 +1472,7 @@ class Editor extends Model @insertText(text, options) - # Public: For each selection, cut the selected text. - cutSelectedText: -> - maintainClipboard = false - @mutateSelectedText (selection) -> - selection.cut(maintainClipboard) - maintainClipboard = true - - # Public: For each selection, if the selection is empty, cut all characters + # Extended: For each selection, if the selection is empty, cut all characters # of the containing line following the cursor. Otherwise cut the selected # text. cutToEndOfLine: -> @@ -1486,7 +1486,7 @@ class Editor extends Model Section: Folds ### - # Public: Fold the most recent cursor's row based on its indentation level. + # Essential: Fold the most recent cursor's row based on its indentation level. # # The fold will extend from the nearest preceding line with a lower # indentation level up to the nearest following row with a lower indentation @@ -1495,30 +1495,12 @@ class Editor extends Model bufferRow = @bufferPositionForScreenPosition(@getCursorScreenPosition()).row @foldBufferRow(bufferRow) - # Public: Unfold the most recent cursor's row by one level. + # Essential: Unfold the most recent cursor's row by one level. unfoldCurrentRow: -> bufferRow = @bufferPositionForScreenPosition(@getCursorScreenPosition()).row @unfoldBufferRow(bufferRow) - # Public: For each selection, fold the rows it intersects. - foldSelectedLines: -> - selection.fold() for selection in @getSelections() - - # Public: Fold all foldable lines. - foldAll: -> - @languageMode.foldAll() - - # Public: Unfold all existing folds. - unfoldAll: -> - @languageMode.unfoldAll() - - # Public: Fold all foldable lines at the given indent level. - # - # * `level` A {Number}. - foldAllAtIndentLevel: (level) -> - @languageMode.foldAllAtIndentLevel(level) - - # Public: Fold the given row in buffer coordinates based on its indentation + # Essential: Fold the given row in buffer coordinates based on its indentation # level. # # If the given row is foldable, the fold will begin there. Otherwise, it will @@ -1528,13 +1510,31 @@ class Editor extends Model foldBufferRow: (bufferRow) -> @languageMode.foldBufferRow(bufferRow) - # Public: Unfold all folds containing the given row in buffer coordinates. + # Essential: Unfold all folds containing the given row in buffer coordinates. # # * `bufferRow` A {Number} unfoldBufferRow: (bufferRow) -> @displayBuffer.unfoldBufferRow(bufferRow) - # Public: Determine whether the given row in buffer coordinates is foldable. + # Extended: For each selection, fold the rows it intersects. + foldSelectedLines: -> + selection.fold() for selection in @getSelections() + + # Extended: Fold all foldable lines. + foldAll: -> + @languageMode.foldAll() + + # Extended: Unfold all existing folds. + unfoldAll: -> + @languageMode.unfoldAll() + + # Extended: Fold all foldable lines at the given indent level. + # + # * `level` A {Number}. + foldAllAtIndentLevel: (level) -> + @languageMode.foldAllAtIndentLevel(level) + + # Extended: Determine whether the given row in buffer coordinates is foldable. # # A *foldable* row is a row that *starts* a row range that can be folded. # @@ -1544,10 +1544,47 @@ class Editor extends Model isFoldableAtBufferRow: (bufferRow) -> @languageMode.isFoldableAtBufferRow(bufferRow) + # Extended: Determine whether the given row in screen coordinates is foldable. + # + # A *foldable* row is a row that *starts* a row range that can be folded. + # + # * `bufferRow` A {Number} + # + # Returns a {Boolean}. isFoldableAtScreenRow: (screenRow) -> bufferRow = @displayBuffer.bufferRowForScreenRow(screenRow) @isFoldableAtBufferRow(bufferRow) + # Extended: Fold the given buffer row if it isn't currently folded, and unfold + # it otherwise. + toggleFoldAtBufferRow: (bufferRow) -> + if @isFoldedAtBufferRow(bufferRow) + @unfoldBufferRow(bufferRow) + else + @foldBufferRow(bufferRow) + + # Extended: Determine whether the most recently added cursor's row is folded. + # + # Returns a {Boolean}. + isFoldedAtCursorRow: -> + @isFoldedAtScreenRow(@getCursorScreenPosition().row) + + # Extended: Determine whether the given row in buffer coordinates is folded. + # + # * `bufferRow` A {Number} + # + # Returns a {Boolean}. + isFoldedAtBufferRow: (bufferRow) -> + @displayBuffer.isFoldedAtBufferRow(bufferRow) + + # Extended: Determine whether the given row in screen coordinates is folded. + # + # * `screenRow` A {Number} + # + # Returns a {Boolean}. + isFoldedAtScreenRow: (screenRow) -> + @displayBuffer.isFoldedAtScreenRow(screenRow) + # TODO: Rename to foldRowRange? createFold: (startRow, endRow) -> @displayBuffer.createFold(startRow, endRow) @@ -1561,36 +1598,6 @@ class Editor extends Model for row in [bufferRange.start.row..bufferRange.end.row] @unfoldBufferRow(row) - # Public: Fold the given buffer row if it isn't currently folded, and unfold - # it otherwise. - toggleFoldAtBufferRow: (bufferRow) -> - if @isFoldedAtBufferRow(bufferRow) - @unfoldBufferRow(bufferRow) - else - @foldBufferRow(bufferRow) - - # Public: Determine whether the most recently added cursor's row is folded. - # - # Returns a {Boolean}. - isFoldedAtCursorRow: -> - @isFoldedAtScreenRow(@getCursorScreenPosition().row) - - # Public: Determine whether the given row in buffer coordinates is folded. - # - # * `bufferRow` A {Number} - # - # Returns a {Boolean}. - isFoldedAtBufferRow: (bufferRow) -> - @displayBuffer.isFoldedAtBufferRow(bufferRow) - - # Public: Determine whether the given row in screen coordinates is folded. - # - # * `screenRow` A {Number} - # - # Returns a {Boolean}. - isFoldedAtScreenRow: (screenRow) -> - @displayBuffer.isFoldedAtScreenRow(screenRow) - # {Delegates to: DisplayBuffer.largestFoldContainingBufferRow} largestFoldContainingBufferRow: (bufferRow) -> @displayBuffer.largestFoldContainingBufferRow(bufferRow) @@ -1603,10 +1610,6 @@ class Editor extends Model outermostFoldsInBufferRowRange: (startRow, endRow) -> @displayBuffer.outermostFoldsInBufferRowRange(startRow, endRow) - - - - ### Section: Decorations ### From 86bbf4276bd4175bb7cc50cf1589817074d5042b Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:56:07 -0700 Subject: [PATCH 040/211] Reorg the Decorations section --- src/editor.coffee | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index 85368697a..44c0cfe6d 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1481,7 +1481,6 @@ class Editor extends Model selection.cutToEndOfLine(maintainClipboard) maintainClipboard = true - ### Section: Folds ### @@ -1614,20 +1613,7 @@ class Editor extends Model Section: Decorations ### - # Public: Get all the decorations within a screen row range. - # - # * `startScreenRow` the {Number} beginning screen row - # * `endScreenRow` the {Number} end screen row (inclusive) - # - # Returns an {Object} of decorations in the form - # `{1: [{id: 10, type: 'gutter', class: 'someclass'}], 2: ...}` - # where the keys are {Marker} IDs, and the values are an array of decoration - # params objects attached to the marker. - # Returns an empty object when no decorations are found - decorationsForScreenRowRange: (startScreenRow, endScreenRow) -> - @displayBuffer.decorationsForScreenRowRange(startScreenRow, endScreenRow) - - # Public: Adds a decoration that tracks a {Marker}. When the marker moves, + # Essential: Adds a decoration that tracks a {Marker}. When the marker moves, # is invalidated, or is destroyed, the decoration will be updated to reflect # the marker's state. # @@ -1668,6 +1654,19 @@ class Editor extends Model decorateMarker: (marker, decorationParams) -> @displayBuffer.decorateMarker(marker, decorationParams) + # Extended: Get all the decorations within a screen row range. + # + # * `startScreenRow` the {Number} beginning screen row + # * `endScreenRow` the {Number} end screen row (inclusive) + # + # Returns an {Object} of decorations in the form + # `{1: [{id: 10, type: 'gutter', class: 'someclass'}], 2: ...}` + # where the keys are {Marker} IDs, and the values are an array of decoration + # params objects attached to the marker. + # Returns an empty object when no decorations are found + decorationsForScreenRowRange: (startScreenRow, endScreenRow) -> + @displayBuffer.decorationsForScreenRowRange(startScreenRow, endScreenRow) + decorationForId: (id) -> @displayBuffer.decorationForId(id) From 499888a3865f50dd3461c8ca3c140c410f45db60 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:58:11 -0700 Subject: [PATCH 041/211] Reorg marker section --- src/editor.coffee | 91 +++++++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 46 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index 44c0cfe6d..c46ac015d 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1674,15 +1674,43 @@ class Editor extends Model Section: Markers ### - # Public: Get the {DisplayBufferMarker} for the given marker id. - getMarker: (id) -> - @displayBuffer.getMarker(id) + # Essential: Mark the given range in buffer coordinates. + # + # * `range` A {Range} or range-compatible {Array}. + # * `options` (optional) See {TextBuffer::markRange}. + # + # Returns a {DisplayBufferMarker}. + markBufferRange: (args...) -> + @displayBuffer.markBufferRange(args...) - # Public: Get all {DisplayBufferMarker}s. - getMarkers: -> - @displayBuffer.getMarkers() + # Essential: Mark the given range in screen coordinates. + # + # * `range` A {Range} or range-compatible {Array}. + # * `options` (optional) See {TextBuffer::markRange}. + # + # Returns a {DisplayBufferMarker}. + markScreenRange: (args...) -> + @displayBuffer.markScreenRange(args...) - # Public: Find all {DisplayBufferMarker}s that match the given properties. + # Essential: Mark the given position in buffer coordinates. + # + # * `position` A {Point} or {Array} of `[row, column]`. + # * `options` (optional) See {TextBuffer::markRange}. + # + # Returns a {DisplayBufferMarker}. + markBufferPosition: (args...) -> + @displayBuffer.markBufferPosition(args...) + + # Essential: Mark the given position in screen coordinates. + # + # * `position` A {Point} or {Array} of `[row, column]`. + # * `options` (optional) See {TextBuffer::markRange}. + # + # Returns a {DisplayBufferMarker}. + markScreenPosition: (args...) -> + @displayBuffer.markScreenPosition(args...) + + # Essential: Find all {DisplayBufferMarker}s that match the given properties. # # This method finds markers based on the given properties. Markers can be # associated with custom properties that will be compared with basic equality. @@ -1704,52 +1732,23 @@ class Editor extends Model findMarkers: (properties) -> @displayBuffer.findMarkers(properties) - # Public: Mark the given range in screen coordinates. - # - # * `range` A {Range} or range-compatible {Array}. - # * `options` (optional) See {TextBuffer::markRange}. - # - # Returns a {DisplayBufferMarker}. - markScreenRange: (args...) -> - @displayBuffer.markScreenRange(args...) + # Extended: Get the {DisplayBufferMarker} for the given marker id. + getMarker: (id) -> + @displayBuffer.getMarker(id) - # Public: Mark the given range in buffer coordinates. - # - # * `range` A {Range} or range-compatible {Array}. - # * `options` (optional) See {TextBuffer::markRange}. - # - # Returns a {DisplayBufferMarker}. - markBufferRange: (args...) -> - @displayBuffer.markBufferRange(args...) + # Extended: Get all {DisplayBufferMarker}s. + getMarkers: -> + @displayBuffer.getMarkers() - # Public: Mark the given position in screen coordinates. - # - # * `position` A {Point} or {Array} of `[row, column]`. - # * `options` (optional) See {TextBuffer::markRange}. - # - # Returns a {DisplayBufferMarker}. - markScreenPosition: (args...) -> - @displayBuffer.markScreenPosition(args...) - - # Public: Mark the given position in buffer coordinates. - # - # * `position` A {Point} or {Array} of `[row, column]`. - # * `options` (optional) See {TextBuffer::markRange}. - # - # Returns a {DisplayBufferMarker}. - markBufferPosition: (args...) -> - @displayBuffer.markBufferPosition(args...) - - # {Delegates to: DisplayBuffer.destroyMarker} - destroyMarker: (args...) -> - @displayBuffer.destroyMarker(args...) - - # Public: Get the number of markers in this editor's buffer. + # Extended: Get the number of markers in this editor's buffer. # # Returns a {Number}. getMarkerCount: -> @buffer.getMarkerCount() + # {Delegates to: DisplayBuffer.destroyMarker} + destroyMarker: (args...) -> + @displayBuffer.destroyMarker(args...) ### Section: Cursors From b661cdd229bf782c866647987a47208333502489 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 18:59:32 -0700 Subject: [PATCH 042/211] :lipstick: --- src/editor.coffee | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index c46ac015d..b2773af9d 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -2576,7 +2576,6 @@ class Editor extends Model getRowsPerPage: -> Math.max(1, Math.ceil(@getHeight() / @getLineHeightInPixels())) - ### Section: Config ### @@ -2593,7 +2592,6 @@ class Editor extends Model else @displayBuffer.setInvisibles(null) - ### Section: Event Handlers ### From ff188723cd03827546e33a05f5cbab3fc611e78d Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 19:02:14 -0700 Subject: [PATCH 043/211] Create a saving section --- src/editor.coffee | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index b2773af9d..f449cfa86 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -524,22 +524,6 @@ class Editor extends Model # Essential: Returns the {String} path of this editor's text buffer. getPath: -> @buffer.getPath() - # Essential: Saves the editor's text buffer. - # - # See {TextBuffer::save} for more details. - save: -> @buffer.save() - - # Essential: Saves the editor's text buffer as the given path. - # - # See {TextBuffer::saveAs} for more details. - # - # * `filePath` A {String} path. - saveAs: (filePath) -> @buffer.saveAs(filePath) - - # Extended: Determine whether the user should be prompted to save before closing - # this editor. - shouldPromptToSave: -> @isModified() and not @buffer.hasMultipleEditors() - # Essential: Returns {Boolean} `true` if this editor has been modified. isModified: -> @buffer.isModified() @@ -551,6 +535,26 @@ class Editor extends Model if filePath = @getPath() atom.clipboard.write(filePath) + ### + Section: Saving + ### + + # Essential: Saves the editor's text buffer. + # + # See {TextBuffer::save} for more details. + save: -> @buffer.save() + + # Extended: Saves the editor's text buffer as the given path. + # + # See {TextBuffer::saveAs} for more details. + # + # * `filePath` A {String} path. + saveAs: (filePath) -> @buffer.saveAs(filePath) + + # Extended: Determine whether the user should be prompted to save before closing + # this editor. + shouldPromptToSave: -> @isModified() and not @buffer.hasMultipleEditors() + ### Section: Reading Text ### From 1ef2aa63d6b84a22315b24144fee0a52d5089dc3 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 19:11:26 -0700 Subject: [PATCH 044/211] Clean up visibilities where I made an error --- src/editor.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index f449cfa86..7a2792dbe 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -654,7 +654,7 @@ class Editor extends Model # {Delegates to: TextBuffer.getEndPosition} getEofBufferPosition: -> @buffer.getEndPosition() - # Public: Get the {Range} of the paragraph surrounding the most recently added + # Extended: Get the {Range} of the paragraph surrounding the most recently added # cursor. # # Returns a {Range}. @@ -1064,7 +1064,7 @@ class Editor extends Model # * `replace` Call this {Function} with a {String} to replace the match. scan: (regex, iterator) -> @buffer.scan(regex, iterator) - # Essential: Scan regular expression matches in a given range, calling the given + # Extended: Scan regular expression matches in a given range, calling the given # iterator function on each match. # # * `regex` A {RegExp} to search for. @@ -1078,7 +1078,7 @@ class Editor extends Model # * `replace` Call this {Function} with a {String} to replace the match. scanInBufferRange: (regex, range, iterator) -> @buffer.scanInRange(regex, range, iterator) - # Essential: Scan regular expression matches in a given range in reverse order, + # Extended: Scan regular expression matches in a given range in reverse order, # calling the given iterator function on each match. # # * `regex` A {RegExp} to search for. @@ -1332,7 +1332,7 @@ class Editor extends Model # Returns a {Point}. clipBufferPosition: (bufferPosition) -> @buffer.clipPosition(bufferPosition) - # Essential: Clip the start and end of the given range to valid positions in the + # Extended: Clip the start and end of the given range to valid positions in the # buffer. See {::clipBufferPosition} for more information. # # * `range` The {Range} to clip. @@ -1340,7 +1340,7 @@ class Editor extends Model # Returns a {Range}. clipBufferRange: (range) -> @buffer.clipRange(range) - # Essential: Clip the given {Point} to a valid position on screen. + # Extended: Clip the given {Point} to a valid position on screen. # # If the given {Point} describes a position that is actually reachable by the # cursor based on the current contents of the screen, it is returned From 6270f2ff558ca46f826ca3dd3a061ce000d8cc20 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 19:11:42 -0700 Subject: [PATCH 045/211] Add missing argument to docs --- src/editor.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/editor.coffee b/src/editor.coffee index 7a2792dbe..fdeeded2b 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1114,6 +1114,8 @@ class Editor extends Model getTabLength: -> @displayBuffer.getTabLength() # Essential: Set the on-screen length of tab characters. + # + # * `tabLength` {Number} length of a single tab setTabLength: (tabLength) -> @displayBuffer.setTabLength(tabLength) # Extended: Determine if the buffer uses hard or soft tabs. @@ -1377,6 +1379,8 @@ class Editor extends Model # # Assigning a grammar will cause the editor to re-tokenize based on the new # grammar. + # + # * `grammar` {Grammar} setGrammar: (grammar) -> @displayBuffer.setGrammar(grammar) From bbe69347eeae4e7107796adc85898db1354b2832 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 19:11:57 -0700 Subject: [PATCH 046/211] Clean up the syntax scopes section --- src/editor.coffee | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index fdeeded2b..125f6c336 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1389,9 +1389,18 @@ class Editor extends Model @displayBuffer.reloadGrammar() ### - Section: Syntatic Queries + Section: Managing Syntax Scopes ### + # Public: Get the syntactic scopes for the most recently added cursor's + # position. See {::scopesForBufferPosition} for more information. + # + # Returns an {Array} of {String}s. + scopesAtCursor: -> @getLastCursor().getScopes() + getCursorScopes: -> + deprecate 'Use Editor::scopesAtCursor() instead' + @scopesAtCursor() + # Essential: Get the syntactic scopes for the given position in buffer # coordinates. # @@ -1404,7 +1413,7 @@ class Editor extends Model # Returns an {Array} of {String}s. scopesForBufferPosition: (bufferPosition) -> @displayBuffer.scopesForBufferPosition(bufferPosition) - # Essential: Get the range in buffer coordinates of all tokens surrounding the + # Extended: Get the range in buffer coordinates of all tokens surrounding the # cursor that match the given scope selector. # # For example, if you wanted to find the string surrounding the cursor, you @@ -1414,15 +1423,6 @@ class Editor extends Model bufferRangeForScopeAtCursor: (selector) -> @displayBuffer.bufferRangeForScopeAtPosition(selector, @getCursorBufferPosition()) - # Public: Get the syntactic scopes for the most recently added cursor's - # position. See {::scopesForBufferPosition} for more information. - # - # Returns an {Array} of {String}s. - scopesAtCursor: -> @getLastCursor().getScopes() - getCursorScopes: -> - deprecate 'Use Editor::scopesAtCursor() instead' - @scopesAtCursor() - logCursorScope: -> console.log @getCursorScopes() From d349ec55f9e2611c36a7fd825fa5a7fef04f8fc4 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Mon, 15 Sep 2014 19:15:23 -0700 Subject: [PATCH 047/211] Use new methods --- src/editor.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor.coffee b/src/editor.coffee index 125f6c336..5dfab85d0 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1424,7 +1424,7 @@ class Editor extends Model @displayBuffer.bufferRangeForScopeAtPosition(selector, @getCursorBufferPosition()) logCursorScope: -> - console.log @getCursorScopes() + console.log @scopesAtCursor() # {Delegates to: DisplayBuffer.tokenForBufferPosition} tokenForBufferPosition: (bufferPosition) -> @displayBuffer.tokenForBufferPosition(bufferPosition) From e01d96862fd40004862e4766369a0dfa6dae0ee1 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 10:48:13 -0700 Subject: [PATCH 048/211] Update docs in decoration --- src/decoration.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/decoration.coffee b/src/decoration.coffee index 075641058..1728ce225 100644 --- a/src/decoration.coffee +++ b/src/decoration.coffee @@ -32,7 +32,7 @@ module.exports = class Decoration EmitterMixin.includeInto(this) - # Extended: Check if the `decorationProperties.type` matches `type` + # Private: Check if the `decorationProperties.type` matches `type` # # * `decorationProperties` {Object} eg. `{type: 'gutter', class: 'my-new-class'}` # * `type` {String} type like `'gutter'`, `'line'`, etc. `type` can also @@ -46,6 +46,10 @@ class Decoration else type is decorationProperties.type + ### + Section: Construction and Destruction + ### + constructor: (@marker, @displayBuffer, @properties) -> @emitter = new Emitter @id = nextId() From 44fd6cc3359c0dd77ee0b0fa85edf2342b6b9e9f Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 10:51:20 -0700 Subject: [PATCH 049/211] Workspace is Essential --- src/workspace.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workspace.coffee b/src/workspace.coffee index 64ea6404b..f8f5ba90b 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -10,7 +10,7 @@ Editor = require './editor' PaneContainer = require './pane-container' Pane = require './pane' -# Public: Represents the state of the user interface for the entire window. +# Essential: Represents the state of the user interface for the entire window. # An instance of this class is available via the `atom.workspace` global. # # Interact with this object to open files, be notified of current and future From 5b8e30580d1d9b44d04afeeed8a293123843af40 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 10:52:33 -0700 Subject: [PATCH 050/211] Reorg Event Subscription section --- src/workspace.coffee | 54 ++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/workspace.coffee b/src/workspace.coffee index f8f5ba90b..31b7cb261 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -91,6 +91,33 @@ class Workspace extends Model Section: Event Subscription ### + # Essential: Invoke the given callback with all current and future text + # editors in the workspace. + # + # * `callback` {Function} to be called with current and future text editors. + # * `editor` An {Editor} that is present in {::getTextEditors} at the time + # of subscription or that is added at some later time. + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + observeTextEditors: (callback) -> + callback(textEditor) for textEditor in @getTextEditors() + @onDidAddTextEditor ({textEditor}) -> callback(textEditor) + + # Essential: Invoke the given callback whenever an item is opened. Unlike + # ::onDidAddPaneItem, observers will be notified for items that are already + # present in the workspace when they are reopened. + # + # * `callback` {Function} to be called whenever an item is opened. + # * `event` {Object} with the following keys: + # * `uri` {String} representing the opened URI. Could be `undefined`. + # * `item` The opened item. + # * `pane` The pane in which the item was opened. + # * `index` The index of the opened item on its pane. + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidOpen: (callback) -> + @emitter.on 'did-open', callback + # Extended: Invoke the given callback when a pane is added to the workspace. # # * `callback` {Function} to be called panes are added. @@ -174,33 +201,6 @@ class Workspace extends Model @onDidAddPaneItem ({item, pane, index}) -> callback({textEditor: item, pane, index}) if item instanceof Editor - # Essential: Invoke the given callback with all current and future text - # editors in the workspace. - # - # * `callback` {Function} to be called with current and future text editors. - # * `editor` An {Editor} that is present in {::getTextEditors} at the time - # of subscription or that is added at some later time. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observeTextEditors: (callback) -> - callback(textEditor) for textEditor in @getTextEditors() - @onDidAddTextEditor ({textEditor}) -> callback(textEditor) - - # Essential: Invoke the given callback whenever an item is opened. Unlike - # ::onDidAddPaneItem, observers will be notified for items that are already - # present in the workspace when they are reopened. - # - # * `callback` {Function} to be called whenever an item is opened. - # * `event` {Object} with the following keys: - # * `uri` {String} representing the opened URI. Could be `undefined`. - # * `item` The opened item. - # * `pane` The pane in which the item was opened. - # * `index` The index of the opened item on its pane. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidOpen: (callback) -> - @emitter.on 'did-open', callback - eachEditor: (callback) -> deprecate("Use Workspace::observeTextEditors instead") From ac516102cabf97e59f19d43b8b12e5a24e6621dc Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 10:56:26 -0700 Subject: [PATCH 051/211] Atom is Essential --- src/atom.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atom.coffee b/src/atom.coffee index 9d0faab5d..81ef6f82f 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -14,7 +14,7 @@ fs = require 'fs-plus' {$} = require './space-pen-extensions' WindowEventHandler = require './window-event-handler' -# Public: Atom global for dealing with packages, themes, menus, and the window. +# Essential: Atom global for dealing with packages, themes, menus, and the window. # # An instance of this class is always available as the `atom` global. module.exports = From 9eb51dfd0a5dcade39b7fb60ff7e308b3bb40980 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 10:56:48 -0700 Subject: [PATCH 052/211] Make Atom.loadOrCreate Private --- src/atom.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atom.coffee b/src/atom.coffee index 81ef6f82f..1448979bf 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -21,7 +21,7 @@ module.exports = class Atom extends Model @version: 1 # Increment this when the serialization format changes - # Public: Load or create the Atom environment in the given mode. + # Load or create the Atom environment in the given mode. # # * `mode` A {String} mode that is either 'editor' or 'spec' depending on the # kind of environment you want to build. From f84666943c10bf04cbc1018b49c38f1cbc2e165f Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 10:58:08 -0700 Subject: [PATCH 053/211] Properties section --- src/atom.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/atom.coffee b/src/atom.coffee index 1448979bf..a813a66cb 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -98,6 +98,10 @@ class Atom extends Model workspaceViewParentSelector: 'body' lastUncaughtError: null + ### + Section: Properties + ### + # Public: A {Clipboard} instance clipboard: null From b3ec8ed03f8755a75447c63f5fffa2a69c982d4c Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 10:58:27 -0700 Subject: [PATCH 054/211] Make Atom::initialize private --- src/atom.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atom.coffee b/src/atom.coffee index a813a66cb..df8694a00 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -144,7 +144,7 @@ class Atom extends Model DeserializerManager = require './deserializer-manager' @deserializers = new DeserializerManager() - # Public: Sets up the basic services that should be available in all modes + # Sets up the basic services that should be available in all modes # (both spec and application). # # Call after this instance has been assigned to the `atom` global. From 779619a4f21edb35ce2238f7151ce892b211732e Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 10:58:36 -0700 Subject: [PATCH 055/211] Construction and destruction section --- src/atom.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/atom.coffee b/src/atom.coffee index df8694a00..52c6bcec9 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -138,6 +138,10 @@ class Atom extends Model # Public: A {WorkspaceView} instance workspaceView: null + ### + Section: Construction and Destruction + ### + # Call .loadOrCreate instead constructor: (@state) -> {@mode} = @state From 39d7e12ebb58d2d871f5a6fded3c7dc54f09f495 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 11:35:14 -0700 Subject: [PATCH 056/211] Fix deprecation method calls --- src/atom.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/atom.coffee b/src/atom.coffee index 52c6bcec9..299f55c31 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -7,7 +7,7 @@ screen = require 'screen' shell = require 'shell' _ = require 'underscore-plus' -{deprecated} = require 'grim' +{deprecate} = require 'grim' {Model} = require 'theorist' fs = require 'fs-plus' @@ -208,11 +208,11 @@ class Atom extends Model # Deprecated: Callers should be converted to use atom.deserializers registerRepresentationClass: -> - deprecated("Callers should be converted to use atom.deserializers") + deprecate("Callers should be converted to use atom.deserializers") # Deprecated: Callers should be converted to use atom.deserializers registerRepresentationClasses: -> - deprecated("Callers should be converted to use atom.deserializers") + deprecate("Callers should be converted to use atom.deserializers") setBodyPlatformClass: -> document.body.classList.add("platform-#{process.platform}") From c0dd53104a381dc06992ed6db5256589c36c1858 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 11:37:11 -0700 Subject: [PATCH 057/211] Make a managing the atom window section de-public the get/setWindowDimensions in favor of the (get|set)(Size|Position) --- spec/atom-spec.coffee | 17 ++++++ src/atom.coffee | 126 +++++++++++++++++++++++++----------------- 2 files changed, 92 insertions(+), 51 deletions(-) diff --git a/spec/atom-spec.coffee b/spec/atom-spec.coffee index 7b1ae1618..c71f89faa 100644 --- a/spec/atom-spec.coffee +++ b/spec/atom-spec.coffee @@ -8,6 +8,23 @@ describe "the `atom` global", -> beforeEach -> atom.workspaceView = new WorkspaceView + describe 'window sizing methods', -> + describe '::getPosition and ::setPosition', -> + it 'sets the position of the window, and can retrieve the position just set', -> + atom.setPosition(22, 45) + expect(atom.getPosition()).toEqual x: 22, y: 45 + + describe '::getSize and ::setSize', -> + originalSize = null + beforeEach -> + originalSize = atom.getSize() + afterEach -> + atom.setSize(originalSize.width, originalSize.height) + + it 'sets the size of the window, and can retrieve the size just set', -> + atom.setSize(100, 400) + expect(atom.getSize()).toEqual width: 100, height: 400 + describe "package lifecycle methods", -> describe ".loadPackage(name)", -> it "continues if the package has an invalid package.json", -> diff --git a/src/atom.coffee b/src/atom.coffee index 299f55c31..6c2c1fd53 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -217,11 +217,84 @@ class Atom extends Model setBodyPlatformClass: -> document.body.classList.add("platform-#{process.platform}") + + ### + Section: Managing The Atom Window + ### + # Public: Get the current window getCurrentWindow: -> @constructor.getCurrentWindow() - # Public: Get the dimensions of this window. + # Public: Get the size of current window. + # + # Returns an {Object} in the format `{width: 1000, height: 700}` + getSize: -> + [width, height] = @getCurrentWindow().getSize() + {width, height} + + # Public: Set the size of current window. + # + # * `width` The {Number} of pixels. + # * `height` The {Number} of pixels. + setSize: (width, height) -> + @getCurrentWindow().setSize(width, height) + + # Public: Get the position of current window. + # + # Returns an {Object} in the format `{x: 10, y: 20}` + getPosition: -> + [x, y] = @getCurrentWindow().getPosition() + {x, y} + + # Public: Set the position of current window. + # + # * `x` The {Number} of pixels. + # * `y` The {Number} of pixels. + setPosition: (x, y) -> + ipc.send('call-window-method', 'setPosition', x, y) + + # Public: Returns a {Boolean} true when the current window is maximized. + isMaximixed: -> + @getCurrentWindow().isMaximized() + + # Public: Move current window to the center of the screen. + center: -> + ipc.send('call-window-method', 'center') + + # Public: Focus the current window. + focus: -> + ipc.send('call-window-method', 'focus') + $(window).focus() + + # Public: Show the current window. + show: -> + ipc.send('call-window-method', 'show') + + # Public: Hide the current window. + hide: -> + ipc.send('call-window-method', 'hide') + + # Public: Close the current window. + close: -> + @getCurrentWindow().close() + + # Public: Reload the current window. + reload: -> + ipc.send('call-window-method', 'restart') + + # Schedule the window to be shown and focused on the next tick. + # + # This is done in a next tick to prevent a white flicker from occurring + # if called synchronously. + displayWindow: ({maximize}={}) -> + setImmediate => + @show() + @focus() + @setFullScreen(true) if @workspace.fullScreen + @maximize() if maximize + + # Get the dimensions of this window. # # Returns an {Object} with the following keys: # * `x` The window's x-position {Number}. @@ -235,7 +308,7 @@ class Atom extends Model maximized = browserWindow.isMaximized() {x, y, width, height, maximized} - # Public: Set the dimensions of the window. + # Set the dimensions of the window. # # The window will be centered if either the x or y coordinate is not set # in the dimensions parameter. If x or y are omitted the window will be @@ -472,56 +545,7 @@ class Atom extends Model executeJavaScriptInDevTools: (code) -> ipc.send('call-window-method', 'executeJavaScriptInDevTools', code) - # Public: Reload the current window. - reload: -> - ipc.send('call-window-method', 'restart') - # Public: Focus the current window. - focus: -> - ipc.send('call-window-method', 'focus') - $(window).focus() - - # Public: Show the current window. - show: -> - ipc.send('call-window-method', 'show') - - # Public: Hide the current window. - hide: -> - ipc.send('call-window-method', 'hide') - - # Public: Set the size of current window. - # - # * `width` The {Number} of pixels. - # * `height` The {Number} of pixels. - setSize: (width, height) -> - @getCurrentWindow().setSize(width, height) - - # Public: Set the position of current window. - # - # * `x` The {Number} of pixels. - # * `y` The {Number} of pixels. - setPosition: (x, y) -> - ipc.send('call-window-method', 'setPosition', x, y) - - # Public: Move current window to the center of the screen. - center: -> - ipc.send('call-window-method', 'center') - - - # Schedule the window to be shown and focused on the next tick. - # - # This is done in a next tick to prevent a white flicker from occurring - # if called synchronously. - displayWindow: ({maximize}={}) -> - setImmediate => - @show() - @focus() - @setFullScreen(true) if @workspace.fullScreen - @maximize() if maximize - - # Public: Close the current window. - close: -> - @getCurrentWindow().close() exit: (status) -> app = remote.require('app') From 53806d7d639ca7b466450e6de8f38d5e1d165e7f Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 11:37:21 -0700 Subject: [PATCH 058/211] Managing the dev tool section --- src/atom.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/atom.coffee b/src/atom.coffee index 6c2c1fd53..a20184e9d 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -533,6 +533,11 @@ class Atom extends Model dialog = remote.require('dialog') dialog.showSaveDialog currentWindow, {title: 'Save File', defaultPath} + + ### + Section: Managing the Dev Tools + ### + # Public: Open the dev tools for the current window. openDevTools: -> ipc.send('call-window-method', 'openDevTools') From 9e68e474326202d67e4808972f96dc15818cdba8 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 11:37:30 -0700 Subject: [PATCH 059/211] :lipstick: --- src/atom.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atom.coffee b/src/atom.coffee index a20184e9d..ba6272562 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -494,7 +494,7 @@ class Atom extends Model # detailedMessage: 'Be honest.' # buttons: # Good: -> window.alert('good to hear') - # Bad: -> window.alert('bummer') + # Bad: -> window.alert('bummer') # ``` # # * `options` An {Object} with the following keys: From c25a04fd530b9e526604a21b689c4a3958753664 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 11:42:07 -0700 Subject: [PATCH 060/211] Move fullscreen stuff into the managing window section --- src/atom.coffee | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/atom.coffee b/src/atom.coffee index ba6272562..3995d63bd 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -254,10 +254,6 @@ class Atom extends Model setPosition: (x, y) -> ipc.send('call-window-method', 'setPosition', x, y) - # Public: Returns a {Boolean} true when the current window is maximized. - isMaximixed: -> - @getCurrentWindow().isMaximized() - # Public: Move current window to the center of the screen. center: -> ipc.send('call-window-method', 'center') @@ -283,6 +279,26 @@ class Atom extends Model reload: -> ipc.send('call-window-method', 'restart') + # Public: Returns a {Boolean} true when the current window is maximized. + isMaximixed: -> + @getCurrentWindow().isMaximized() + + maximize: -> + ipc.send('call-window-method', 'maximize') + + # Public: Is the current window in full screen mode? + isFullScreen: -> + @getCurrentWindow().isFullScreen() + + # Public: Set the full screen state of the current window. + setFullScreen: (fullScreen=false) -> + ipc.send('call-window-method', 'setFullScreen', fullScreen) + if fullScreen then document.body.classList.add("fullscreen") else document.body.classList.remove("fullscreen") + + # Public: Toggle the full screen state of the current window. + toggleFullScreen: -> + @setFullScreen(!@isFullScreen()) + # Schedule the window to be shown and focused on the next tick. # # This is done in a next tick to prevent a white flicker from occurring @@ -571,22 +587,6 @@ class Atom extends Model inSpecMode: -> @getLoadSettings().isSpec - # Public: Toggle the full screen state of the current window. - toggleFullScreen: -> - @setFullScreen(!@isFullScreen()) - - # Public: Set the full screen state of the current window. - setFullScreen: (fullScreen=false) -> - ipc.send('call-window-method', 'setFullScreen', fullScreen) - if fullScreen then document.body.classList.add("fullscreen") else document.body.classList.remove("fullscreen") - - # Public: Is the current window in full screen mode? - isFullScreen: -> - @getCurrentWindow().isFullScreen() - - maximize: -> - ipc.send('call-window-method', 'maximize') - # Public: Get the version of the Atom application. # # Returns the version text {String}. From e84eba058ae2882b1df06bce7b36d9b93dc3ee8a Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 11:44:00 -0700 Subject: [PATCH 061/211] Move deprecated methods to the bottom of the file --- src/atom.coffee | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/atom.coffee b/src/atom.coffee index 3995d63bd..49933ebbb 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -206,18 +206,6 @@ class Atom extends Model @windowEventHandler = new WindowEventHandler - # Deprecated: Callers should be converted to use atom.deserializers - registerRepresentationClass: -> - deprecate("Callers should be converted to use atom.deserializers") - - # Deprecated: Callers should be converted to use atom.deserializers - registerRepresentationClasses: -> - deprecate("Callers should be converted to use atom.deserializers") - - setBodyPlatformClass: -> - document.body.classList.add("platform-#{process.platform}") - - ### Section: Managing The Atom Window ### @@ -381,6 +369,10 @@ class Atom extends Model dimensions = @getWindowDimensions() @state.windowDimensions = dimensions if @isValidDimensions(dimensions) + + + + # Public: Get the load settings for the current window. # # Returns an {Object} containing all the load setting key/value pairs. @@ -662,3 +654,14 @@ class Atom extends Model delete window[key] else window[key] = value + + # Deprecated: Callers should be converted to use atom.deserializers + registerRepresentationClass: -> + deprecate("Callers should be converted to use atom.deserializers") + + # Deprecated: Callers should be converted to use atom.deserializers + registerRepresentationClasses: -> + deprecate("Callers should be converted to use atom.deserializers") + + setBodyPlatformClass: -> + document.body.classList.add("platform-#{process.platform}") From a3e4ccbb833911f21cefd97c1fd69778f2c9ae8c Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 11:52:11 -0700 Subject: [PATCH 062/211] Create atom metadata section --- src/atom.coffee | 51 +++++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/atom.coffee b/src/atom.coffee index 49933ebbb..afa78c672 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -206,6 +206,34 @@ class Atom extends Model @windowEventHandler = new WindowEventHandler + ### + Section: Atom Metadata + ### + + # Public: Is the current window in development mode? + inDevMode: -> + @getLoadSettings().devMode + + # Public: Is the current window running specs? + inSpecMode: -> + @getLoadSettings().isSpec + + # Public: Get the version of the Atom application. + # + # Returns the version text {String}. + getVersion: -> + @appVersion ?= @getLoadSettings().appVersion + + # Public: Determine whether the current version is an official release. + isReleasedVersion: -> + not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix + + # Public: Get the directory path to Atom's configuration area. + # + # Returns the absolute path to `~/.atom`. + getConfigDirPath: -> + @constructor.getConfigDirPath() + ### Section: Managing The Atom Window ### @@ -571,29 +599,6 @@ class Atom extends Model setRepresentedFilename: (filename) -> ipc.send('call-window-method', 'setRepresentedFilename', filename) - # Public: Is the current window in development mode? - inDevMode: -> - @getLoadSettings().devMode - - # Public: Is the current window running specs? - inSpecMode: -> - @getLoadSettings().isSpec - - # Public: Get the version of the Atom application. - # - # Returns the version text {String}. - getVersion: -> - @appVersion ?= @getLoadSettings().appVersion - - # Public: Determine whether the current version is an official release. - isReleasedVersion: -> - not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix - - # Public: Get the directory path to Atom's configuration area. - # - # Returns the absolute path to `~/.atom`. - getConfigDirPath: -> - @constructor.getConfigDirPath() saveSync: -> stateString = JSON.stringify(@state) From e260064df2ca19ac85d6769e50b2e9b3d6b4e6fa Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 11:52:59 -0700 Subject: [PATCH 063/211] Add getLoadSettings to atom metadata section --- src/atom.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/atom.coffee b/src/atom.coffee index afa78c672..d5cab5dab 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -234,6 +234,12 @@ class Atom extends Model getConfigDirPath: -> @constructor.getConfigDirPath() + # Public: Get the load settings for the current window. + # + # Returns an {Object} containing all the load setting key/value pairs. + getLoadSettings: -> + @constructor.getLoadSettings() + ### Section: Managing The Atom Window ### @@ -401,12 +407,6 @@ class Atom extends Model - # Public: Get the load settings for the current window. - # - # Returns an {Object} containing all the load setting key/value pairs. - getLoadSettings: -> - @constructor.getLoadSettings() - deserializeProject: -> Project = require './project' From 816bb9b38d985de61a6aa1bec2fa536c8a4e8fb9 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 13:21:19 -0700 Subject: [PATCH 064/211] Deserializers are private --- src/atom.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atom.coffee b/src/atom.coffee index d5cab5dab..a74568474 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -111,7 +111,7 @@ class Atom extends Model # Public: A {ContextMenuManager} instance contextMenu: null - # Public: A {DeserializerManager} instance + # A {DeserializerManager} instance deserializers: null # Public: A {KeymapManager} instance From ebb6ebca2af50fde45d170761ca75d1cc42c0fd1 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 13:21:35 -0700 Subject: [PATCH 065/211] Move windowloadTime into metadata section --- src/atom.coffee | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/atom.coffee b/src/atom.coffee index a74568474..2d2db208e 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -240,6 +240,16 @@ class Atom extends Model getLoadSettings: -> @constructor.getLoadSettings() + # Public: Get the time taken to completely load the current window. + # + # This time include things like loading and activating packages, creating + # DOM elements for the editor, and reading the config. + # + # Returns the {Number} of milliseconds taken to load the window or null + # if the window hasn't finished loading yet. + getWindowLoadTime: -> + @loadTime + ### Section: Managing The Atom Window ### From 6a16a9b83f9c7624d525b3e9b6e07d0f212965d3 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 13:22:21 -0700 Subject: [PATCH 066/211] move Atom::open to the window management section --- src/atom.coffee | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/atom.coffee b/src/atom.coffee index 2d2db208e..79c3e7b3d 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -303,6 +303,23 @@ class Atom extends Model hide: -> ipc.send('call-window-method', 'hide') + # Public: Open a new Atom window using the given options. + # + # Calling this method without an options parameter will open a prompt to pick + # a file/folder to open in the new window. + # + # * `options` An {Object} with the following keys: + # * `pathsToOpen` An {Array} of {String} paths to open. + # * `newWindow` A {Boolean}, true to always open a new window instead of + # reusing existing windows depending on the paths to open. + # * `devMode` A {Boolean}, true to open the window in development mode. + # Development mode loads the Atom source from the locally cloned + # repository and also loads all the packages in ~/.atom/dev/packages + # * `safeMode` A {Boolean}, true to open the window in safe mode. Safe + # mode prevents all packages installed to ~/.atom/packages from loading. + open: (options) -> + ipc.send('open', options) + # Public: Close the current window. close: -> @getCurrentWindow().close() @@ -513,22 +530,6 @@ class Atom extends Model @subscribe @project, 'path-changed', onProjectPathChanged onProjectPathChanged() - # Public: Open a new Atom window using the given options. - # - # Calling this method without an options parameter will open a prompt to pick - # a file/folder to open in the new window. - # - # * `options` An {Object} with the following keys: - # * `pathsToOpen` An {Array} of {String} paths to open. - # * `newWindow` A {Boolean}, true to always open a new window instead of - # reusing existing windows depending on the paths to open. - # * `devMode` A {Boolean}, true to open the window in development mode. - # Development mode loads the Atom source from the locally cloned - # repository and also loads all the packages in ~/.atom/dev/packages - # * `safeMode` A {Boolean}, true to open the window in safe mode. Safe - # mode prevents all packages installed to ~/.atom/packages from loading. - open: (options) -> - ipc.send('open', options) # Public: Open a confirm dialog. # From cf927e640553fb8365c2fe0b44d086b48faf10ad Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 13:23:09 -0700 Subject: [PATCH 067/211] Create Messaging the user + Deserializing sections --- src/atom.coffee | 147 ++++++++++++++++++++++++------------------------ 1 file changed, 73 insertions(+), 74 deletions(-) diff --git a/src/atom.coffee b/src/atom.coffee index 79c3e7b3d..70e0f35e0 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -430,39 +430,6 @@ class Atom extends Model dimensions = @getWindowDimensions() @state.windowDimensions = dimensions if @isValidDimensions(dimensions) - - - - - deserializeProject: -> - Project = require './project' - - startTime = Date.now() - @project ?= @deserializers.deserialize(@state.project) ? new Project(path: @getLoadSettings().initialPath) - @deserializeTimings.project = Date.now() - startTime - - deserializeWorkspaceView: -> - Workspace = require './workspace' - WorkspaceView = require './workspace-view' - - startTime = Date.now() - @workspace = Workspace.deserialize(@state.workspace) ? new Workspace - @workspaceView = new WorkspaceView(@workspace) - @deserializeTimings.workspace = Date.now() - startTime - - @keymaps.defaultTarget = @workspaceView[0] - $(@workspaceViewParentSelector).append(@workspaceView) - - deserializePackageStates: -> - @packages.packageStates = @state.packageStates ? {} - delete @state.packageStates - - deserializeEditorWindow: -> - @deserializeTimings = {} - @deserializePackageStates() - @deserializeProject() - @deserializeWorkspaceView() - # Call this method when establishing a real application window. startEditorWindow: -> {resourcePath, safeMode} = @getLoadSettings() @@ -513,25 +480,16 @@ class Atom extends Model @windowEventHandler?.unsubscribe() - loadThemes: -> - @themes.load() + ### + Section: Messaging the User + ### - watchThemes: -> - @themes.onDidReloadAll => - # Only reload stylesheets from non-theme packages - for pack in @packages.getActivePackages() when pack.getType() isnt 'theme' - pack.reloadStylesheets?() - null + # Public: Visually and audibly trigger a beep. + beep: -> + shell.beep() if @config.get('core.audioBeep') + @workspaceView.trigger 'beep' - # Notify the browser project of the window's current project path - watchProjectPath: -> - onProjectPathChanged = => - ipc.send('window-command', 'project-path-changed', @project.getPath()) - @subscribe @project, 'path-changed', onProjectPathChanged - onProjectPathChanged() - - - # Public: Open a confirm dialog. + # Public: A flexible way to open a dialog akin to an alert dialog. # # ## Examples # @@ -546,8 +504,8 @@ class Atom extends Model # # * `options` An {Object} with the following keys: # * `message` The {String} message to display. - # * `detailedMessage` The {String} detailed message to display. - # * `buttons` Either an array of strings or an object where keys are + # * `detailedMessage` (optional) The {String} detailed message to display. + # * `buttons` (optional) Either an array of strings or an object where keys are # button names and the values are callbacks to invoke when clicked. # # Returns the chosen button index {Number} if the buttons option was an array. @@ -571,14 +529,58 @@ class Atom extends Model callback = buttons[buttonLabels[chosen]] callback?() - showSaveDialog: (callback) -> - callback(showSaveDialogSync()) + ### + Section: Deserialization + ### - showSaveDialogSync: (defaultPath) -> - defaultPath ?= @project?.getPath() - currentWindow = @getCurrentWindow() - dialog = remote.require('dialog') - dialog.showSaveDialog currentWindow, {title: 'Save File', defaultPath} + deserializeProject: -> + Project = require './project' + + startTime = Date.now() + @project ?= @deserializers.deserialize(@state.project) ? new Project(path: @getLoadSettings().initialPath) + @deserializeTimings.project = Date.now() - startTime + + deserializeWorkspaceView: -> + Workspace = require './workspace' + WorkspaceView = require './workspace-view' + + startTime = Date.now() + @workspace = Workspace.deserialize(@state.workspace) ? new Workspace + @workspaceView = new WorkspaceView(@workspace) + @deserializeTimings.workspace = Date.now() - startTime + + @keymaps.defaultTarget = @workspaceView[0] + $(@workspaceViewParentSelector).append(@workspaceView) + + deserializePackageStates: -> + @packages.packageStates = @state.packageStates ? {} + delete @state.packageStates + + deserializeEditorWindow: -> + @deserializeTimings = {} + @deserializePackageStates() + @deserializeProject() + @deserializeWorkspaceView() + + + + + loadThemes: -> + @themes.load() + + watchThemes: -> + @themes.onDidReloadAll => + # Only reload stylesheets from non-theme packages + for pack in @packages.getActivePackages() when pack.getType() isnt 'theme' + pack.reloadStylesheets?() + null + + # Notify the browser project of the window's current project path + watchProjectPath: -> + onProjectPathChanged = => + ipc.send('window-command', 'project-path-changed', @project.getPath()) + @subscribe @project, 'path-changed', onProjectPathChanged + onProjectPathChanged() ### @@ -599,6 +601,7 @@ class Atom extends Model + exit: (status) -> app = remote.require('app') app.emit('will-exit') @@ -611,6 +614,16 @@ class Atom extends Model ipc.send('call-window-method', 'setRepresentedFilename', filename) + + showSaveDialog: (callback) -> + callback(showSaveDialogSync()) + + showSaveDialogSync: (defaultPath) -> + defaultPath ?= @project?.getPath() + currentWindow = @getCurrentWindow() + dialog = remote.require('dialog') + dialog.showSaveDialog currentWindow, {title: 'Save File', defaultPath} + saveSync: -> stateString = JSON.stringify(@state) if statePath = @constructor.getStatePath(@mode) @@ -618,15 +631,6 @@ class Atom extends Model else @getCurrentWindow().loadSettings.windowState = stateString - # Public: Get the time taken to completely load the current window. - # - # This time include things like loading and activating packages, creating - # DOM elements for the editor, and reading the config. - # - # Returns the {Number} of milliseconds taken to load the window or null - # if the window hasn't finished loading yet. - getWindowLoadTime: -> - @loadTime crashMainProcess: -> remote.process.crash() @@ -634,11 +638,6 @@ class Atom extends Model crashRenderProcess: -> process.crash() - # Public: Visually and audibly trigger a beep. - beep: -> - shell.beep() if @config.get('core.audioBeep') - @workspaceView.trigger 'beep' - getUserInitScriptPath: -> initScriptPath = fs.resolve(@getConfigDirPath(), 'init', ['js', 'coffee']) initScriptPath ? path.join(@getConfigDirPath(), 'init.coffee') @@ -650,7 +649,7 @@ class Atom extends Model catch error console.error "Failed to load `#{userInitScriptPath}`", error.stack, error - # Public: Require the module with the given globals. + # Require the module with the given globals. # # The globals will be set on the `window` object and removed after the # require completes. From dbf8094fdbcf437c26ee5ad3b063009cf59d85b2 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 13:26:58 -0700 Subject: [PATCH 068/211] Reorder the sections so public methods are closer to the top --- src/atom.coffee | 44 +++++++++++++++++--------------------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/src/atom.coffee b/src/atom.coffee index 70e0f35e0..c24940a1b 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -530,7 +530,23 @@ class Atom extends Model callback?() ### - Section: Deserialization + Section: Managing the Dev Tools + ### + + # Public: Open the dev tools for the current window. + openDevTools: -> + ipc.send('call-window-method', 'openDevTools') + + # Public: Toggle the visibility of the dev tools for the current window. + toggleDevTools: -> + ipc.send('call-window-method', 'toggleDevTools') + + # Public: Execute code in dev tools. + executeJavaScriptInDevTools: (code) -> + ipc.send('call-window-method', 'executeJavaScriptInDevTools', code) + + ### + Section: Private ### deserializeProject: -> @@ -562,9 +578,6 @@ class Atom extends Model @deserializeProject() @deserializeWorkspaceView() - - - loadThemes: -> @themes.load() @@ -582,26 +595,6 @@ class Atom extends Model @subscribe @project, 'path-changed', onProjectPathChanged onProjectPathChanged() - - ### - Section: Managing the Dev Tools - ### - - # Public: Open the dev tools for the current window. - openDevTools: -> - ipc.send('call-window-method', 'openDevTools') - - # Public: Toggle the visibility of the dev tools for the current window. - toggleDevTools: -> - ipc.send('call-window-method', 'toggleDevTools') - - # Public: Execute code in dev tools. - executeJavaScriptInDevTools: (code) -> - ipc.send('call-window-method', 'executeJavaScriptInDevTools', code) - - - - exit: (status) -> app = remote.require('app') app.emit('will-exit') @@ -613,8 +606,6 @@ class Atom extends Model setRepresentedFilename: (filename) -> ipc.send('call-window-method', 'setRepresentedFilename', filename) - - showSaveDialog: (callback) -> callback(showSaveDialogSync()) @@ -631,7 +622,6 @@ class Atom extends Model else @getCurrentWindow().loadSettings.windowState = stateString - crashMainProcess: -> remote.process.crash() From b1df925d025c40a359fc4cdee698955599471414 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 13:30:47 -0700 Subject: [PATCH 069/211] Convert visibility in metadata section --- src/atom.coffee | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/atom.coffee b/src/atom.coffee index c24940a1b..b6c0ba86d 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -210,37 +210,37 @@ class Atom extends Model Section: Atom Metadata ### - # Public: Is the current window in development mode? + # Essential: Is the current window in development mode? inDevMode: -> @getLoadSettings().devMode - # Public: Is the current window running specs? + # Essential: Is the current window running specs? inSpecMode: -> @getLoadSettings().isSpec - # Public: Get the version of the Atom application. + # Essential: Get the version of the Atom application. # # Returns the version text {String}. getVersion: -> @appVersion ?= @getLoadSettings().appVersion - # Public: Determine whether the current version is an official release. + # Essential: Determine whether the current version is an official release. isReleasedVersion: -> not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix - # Public: Get the directory path to Atom's configuration area. + # Essential: Get the directory path to Atom's configuration area. # # Returns the absolute path to `~/.atom`. getConfigDirPath: -> @constructor.getConfigDirPath() - # Public: Get the load settings for the current window. + # Extended: Get the load settings for the current window. # # Returns an {Object} containing all the load setting key/value pairs. getLoadSettings: -> @constructor.getLoadSettings() - # Public: Get the time taken to completely load the current window. + # Extended: Get the time taken to completely load the current window. # # This time include things like loading and activating packages, creating # DOM elements for the editor, and reading the config. From 488b1819ae212da1d722476837ad8acf2c3336fb Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 13:31:18 -0700 Subject: [PATCH 070/211] Convert visibility in the managing the window section --- src/atom.coffee | 112 ++++++++++++++++++++++++------------------------ 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/src/atom.coffee b/src/atom.coffee index b6c0ba86d..4585c4dbe 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -254,56 +254,7 @@ class Atom extends Model Section: Managing The Atom Window ### - # Public: Get the current window - getCurrentWindow: -> - @constructor.getCurrentWindow() - - # Public: Get the size of current window. - # - # Returns an {Object} in the format `{width: 1000, height: 700}` - getSize: -> - [width, height] = @getCurrentWindow().getSize() - {width, height} - - # Public: Set the size of current window. - # - # * `width` The {Number} of pixels. - # * `height` The {Number} of pixels. - setSize: (width, height) -> - @getCurrentWindow().setSize(width, height) - - # Public: Get the position of current window. - # - # Returns an {Object} in the format `{x: 10, y: 20}` - getPosition: -> - [x, y] = @getCurrentWindow().getPosition() - {x, y} - - # Public: Set the position of current window. - # - # * `x` The {Number} of pixels. - # * `y` The {Number} of pixels. - setPosition: (x, y) -> - ipc.send('call-window-method', 'setPosition', x, y) - - # Public: Move current window to the center of the screen. - center: -> - ipc.send('call-window-method', 'center') - - # Public: Focus the current window. - focus: -> - ipc.send('call-window-method', 'focus') - $(window).focus() - - # Public: Show the current window. - show: -> - ipc.send('call-window-method', 'show') - - # Public: Hide the current window. - hide: -> - ipc.send('call-window-method', 'hide') - - # Public: Open a new Atom window using the given options. + # Essential: Open a new Atom window using the given options. # # Calling this method without an options parameter will open a prompt to pick # a file/folder to open in the new window. @@ -320,31 +271,80 @@ class Atom extends Model open: (options) -> ipc.send('open', options) - # Public: Close the current window. + # Essential: Close the current window. close: -> @getCurrentWindow().close() - # Public: Reload the current window. + # Essential: Get the size of current window. + # + # Returns an {Object} in the format `{width: 1000, height: 700}` + getSize: -> + [width, height] = @getCurrentWindow().getSize() + {width, height} + + # Essential: Set the size of current window. + # + # * `width` The {Number} of pixels. + # * `height` The {Number} of pixels. + setSize: (width, height) -> + @getCurrentWindow().setSize(width, height) + + # Essential: Get the position of current window. + # + # Returns an {Object} in the format `{x: 10, y: 20}` + getPosition: -> + [x, y] = @getCurrentWindow().getPosition() + {x, y} + + # Essential: Set the position of current window. + # + # * `x` The {Number} of pixels. + # * `y` The {Number} of pixels. + setPosition: (x, y) -> + ipc.send('call-window-method', 'setPosition', x, y) + + # Extended: Get the current window + getCurrentWindow: -> + @constructor.getCurrentWindow() + + # Extended: Move current window to the center of the screen. + center: -> + ipc.send('call-window-method', 'center') + + # Extended: Focus the current window. + focus: -> + ipc.send('call-window-method', 'focus') + $(window).focus() + + # Extended: Show the current window. + show: -> + ipc.send('call-window-method', 'show') + + # Extended: Hide the current window. + hide: -> + ipc.send('call-window-method', 'hide') + + # Extended: Reload the current window. reload: -> ipc.send('call-window-method', 'restart') - # Public: Returns a {Boolean} true when the current window is maximized. + # Extended: Returns a {Boolean} true when the current window is maximized. isMaximixed: -> @getCurrentWindow().isMaximized() maximize: -> ipc.send('call-window-method', 'maximize') - # Public: Is the current window in full screen mode? + # Extended: Is the current window in full screen mode? isFullScreen: -> @getCurrentWindow().isFullScreen() - # Public: Set the full screen state of the current window. + # Extended: Set the full screen state of the current window. setFullScreen: (fullScreen=false) -> ipc.send('call-window-method', 'setFullScreen', fullScreen) if fullScreen then document.body.classList.add("fullscreen") else document.body.classList.remove("fullscreen") - # Public: Toggle the full screen state of the current window. + # Extended: Toggle the full screen state of the current window. toggleFullScreen: -> @setFullScreen(!@isFullScreen()) From 88d0d291d41b0f8568e6667f6d41af8fec4f4098 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 13:31:53 -0700 Subject: [PATCH 071/211] Convert visibility in the messaging the user section --- src/atom.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/atom.coffee b/src/atom.coffee index 4585c4dbe..16718c85d 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -484,12 +484,12 @@ class Atom extends Model Section: Messaging the User ### - # Public: Visually and audibly trigger a beep. + # Essential: Visually and audibly trigger a beep. beep: -> shell.beep() if @config.get('core.audioBeep') @workspaceView.trigger 'beep' - # Public: A flexible way to open a dialog akin to an alert dialog. + # Essential: A flexible way to open a dialog akin to an alert dialog. # # ## Examples # From 6e7dae032de7e33923cea1e80269b1389fd0e11e Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 13:34:11 -0700 Subject: [PATCH 072/211] All dev tool methods are extended --- src/atom.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/atom.coffee b/src/atom.coffee index 16718c85d..372fbeea5 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -533,15 +533,15 @@ class Atom extends Model Section: Managing the Dev Tools ### - # Public: Open the dev tools for the current window. + # Extended: Open the dev tools for the current window. openDevTools: -> ipc.send('call-window-method', 'openDevTools') - # Public: Toggle the visibility of the dev tools for the current window. + # Extended: Toggle the visibility of the dev tools for the current window. toggleDevTools: -> ipc.send('call-window-method', 'toggleDevTools') - # Public: Execute code in dev tools. + # Extended: Execute code in dev tools. executeJavaScriptInDevTools: (code) -> ipc.send('call-window-method', 'executeJavaScriptInDevTools', code) From 19334889142221e66193290567471e369d6e212a Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 13:35:19 -0700 Subject: [PATCH 073/211] Buffered process classes are extended --- src/buffered-node-process.coffee | 2 +- src/buffered-process.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/buffered-node-process.coffee b/src/buffered-node-process.coffee index 808bc27a4..e9a3fbee4 100644 --- a/src/buffered-node-process.coffee +++ b/src/buffered-node-process.coffee @@ -1,7 +1,7 @@ BufferedProcess = require './buffered-process' path = require 'path' -# Public: Like {BufferedProcess}, but accepts a Node script as the command +# Extended: Like {BufferedProcess}, but accepts a Node script as the command # to run. # # This is necessary on Windows since it doesn't support shebang `#!` lines. diff --git a/src/buffered-process.coffee b/src/buffered-process.coffee index ed6474f3b..0a7751e57 100644 --- a/src/buffered-process.coffee +++ b/src/buffered-process.coffee @@ -1,7 +1,7 @@ _ = require 'underscore-plus' ChildProcess = require 'child_process' -# Public: A wrapper which provides standard error/output line buffering for +# Extended: A wrapper which provides standard error/output line buffering for # Node's ChildProcess. # # ## Examples From dbca4f1b8cd56127d44491d5e59ecd60cdecf4c8 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 13:36:48 -0700 Subject: [PATCH 074/211] Clipboard is extended --- src/clipboard.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clipboard.coffee b/src/clipboard.coffee index 48bfb8dd5..3fba6f4e8 100644 --- a/src/clipboard.coffee +++ b/src/clipboard.coffee @@ -1,7 +1,7 @@ clipboard = require 'clipboard' crypto = require 'crypto' -# Public: Represents the clipboard used for copying and pasting in Atom. +# Extended: Represents the clipboard used for copying and pasting in Atom. # # An instance of this class is always available as the `atom.clipboard` global. # From f3f4e8f7a3318454245244f900559d441da0cc2f Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 13:39:43 -0700 Subject: [PATCH 075/211] Config is Essential --- src/config.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.coffee b/src/config.coffee index f3606d7bb..92b11f891 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -6,7 +6,7 @@ path = require 'path' async = require 'async' pathWatcher = require 'pathwatcher' -# Public: Used to access all of Atom's configuration details. +# Essential: Used to access all of Atom's configuration details. # # An instance of this class is always available as the `atom.config` global. # From 6379f87b8afcabb2f1d95c90626a22d392f0b9d1 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 13:40:37 -0700 Subject: [PATCH 076/211] ContextMenuManager is Extended --- src/context-menu-manager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/context-menu-manager.coffee b/src/context-menu-manager.coffee index 5aa8392a6..a55daabf0 100644 --- a/src/context-menu-manager.coffee +++ b/src/context-menu-manager.coffee @@ -5,7 +5,7 @@ path = require 'path' CSON = require 'season' fs = require 'fs-plus' -# Public: Provides a registry for commands that you'd like to appear in the +# Extended: Provides a registry for commands that you'd like to appear in the # context menu. # # An instance of this class is always available as the `atom.contextMenu` From 69ba6e3e7e8014d9d1a26e0b918ceb26db89e073 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 13:42:26 -0700 Subject: [PATCH 077/211] DeserializerManager is extended --- src/deserializer-manager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deserializer-manager.coffee b/src/deserializer-manager.coffee index 7a4390f6a..9abefc4c6 100644 --- a/src/deserializer-manager.coffee +++ b/src/deserializer-manager.coffee @@ -1,4 +1,4 @@ -# Public: Manages the deserializers used for serialized state +# Extended: Manages the deserializers used for serialized state # # An instance of this class is always available as the `atom.deserializers` # global. From 050ec6ca643100ec2d458cf50d79ced18a3aac77 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 13:42:55 -0700 Subject: [PATCH 078/211] MenuManager is extended --- src/menu-manager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menu-manager.coffee b/src/menu-manager.coffee index da5ae6507..89462ec7a 100644 --- a/src/menu-manager.coffee +++ b/src/menu-manager.coffee @@ -5,7 +5,7 @@ ipc = require 'ipc' CSON = require 'season' fs = require 'fs-plus' -# Public: Provides a registry for menu items that you'd like to appear in the +# Extended: Provides a registry for menu items that you'd like to appear in the # application menu. # # An instance of this class is always available as the `atom.menu` global. From 1ee1eb3580f42b8e4314f96115cd81f3073b66a5 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 13:43:56 -0700 Subject: [PATCH 079/211] Syntax is extended --- src/syntax.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/syntax.coffee b/src/syntax.coffee index e4ab7de03..09af37c7c 100644 --- a/src/syntax.coffee +++ b/src/syntax.coffee @@ -9,7 +9,7 @@ PropertyAccessors = require 'property-accessors' {$, $$} = require './space-pen-extensions' Token = require './token' -# Public: Syntax class holding the grammars used for tokenizing. +# Extended: Syntax class holding the grammars used for tokenizing. # # An instance of this class is always available as the `atom.syntax` global. # From 679c52ffd1499ef0756c03f13a2bc12113ba32c0 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 14:03:17 -0700 Subject: [PATCH 080/211] Task is extended --- src/task.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/task.coffee b/src/task.coffee index 8d8d0316c..dea995979 100644 --- a/src/task.coffee +++ b/src/task.coffee @@ -2,7 +2,7 @@ _ = require 'underscore-plus' child_process = require 'child_process' {Emitter} = require 'emissary' -# Public: Run a node script in a separate process. +# Extended: Run a node script in a separate process. # # Used by the fuzzy-finder. # From f80334d61783d67a9022608556832526daa37d06 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 14:03:33 -0700 Subject: [PATCH 081/211] Task has better examples --- src/task.coffee | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/task.coffee b/src/task.coffee index dea995979..64ef7bb32 100644 --- a/src/task.coffee +++ b/src/task.coffee @@ -4,12 +4,36 @@ child_process = require 'child_process' # Extended: Run a node script in a separate process. # -# Used by the fuzzy-finder. +# Used by the fuzzy-finder and [find in project](https://github.com/atom/atom/blob/master/src/scan-handler.coffee). +# +# For a real-world example, see the [scan-handler](https://github.com/atom/atom/blob/master/src/scan-handler.coffee) +# and the [instantiation of the task](https://github.com/atom/atom/blob/4a20f13162f65afc816b512ad7201e528c3443d7/src/project.coffee#L245). # # ## Examples # +# In your package code: +# # ```coffee # {Task} = require 'atom' +# +# task = Task.once '/path/to/task-file.coffee', parameter1, parameter2, -> +# console.log 'task has finished' +# +# task.on 'some-event-from-the-task', (data) => +# console.log data.someString # prints 'yep this is it' +# ``` +# +# In `'/path/to/task-file.coffee'`: +# +# ```coffee +# module.exports = (parameter1, parameter2) -> +# # Indicates that this task will be async. +# # Call the `callback` to finish the task +# callback = @async() +# +# emit('some-event-from-the-task', {someString: 'yep this is it'}) +# +# callback() # ``` # # ## Events @@ -55,7 +79,7 @@ class Task # receives a completion callback, this is overridden. callback: null - # Public: Creates a task. + # Public: Creates a task. You should probably use {.once} # # * `taskPath` The {String} path to the CoffeeScript/JavaScript file that # exports a single {Function} to execute. From b9a63d50305b632a86327cd6811558cc74480e56 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 14:07:04 -0700 Subject: [PATCH 082/211] Add bit about editorView being sparse --- src/editor-view.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/editor-view.coffee b/src/editor-view.coffee index e91d69caa..fb8c458b4 100644 --- a/src/editor-view.coffee +++ b/src/editor-view.coffee @@ -9,6 +9,8 @@ EditorComponent = require './editor-component' # Public: Represents the entire visual pane in Atom. # # The EditorView manages the {Editor}, which manages the file buffers. +# `EditorView` is intentionally sparse. Most of the things you'll want +# to do are on {Editor}. # # ## Examples # From cc64a2c3b4a5d2ca8e03dc42e7eac8291221a8d9 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 14:10:49 -0700 Subject: [PATCH 083/211] Move things into the construction and destruction section --- src/git.coffee | 50 +++++++++++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/src/git.coffee b/src/git.coffee index 78916d343..d7a5e0942 100644 --- a/src/git.coffee +++ b/src/git.coffee @@ -47,8 +47,15 @@ class Git EmitterMixin.includeInto(this) Subscriber.includeInto(this) + @exists: (path) -> + if git = @open(path) + git.destroy() + true + else + false + ### - Section: Class Methods + Section: Construction and Destruction ### # Public: Creates a new Git instance. @@ -66,17 +73,6 @@ class Git catch null - @exists: (path) -> - if git = @open(path) - git.destroy() - true - else - false - - ### - Section: Construction - ### - constructor: (path, options={}) -> @emitter = new Emitter @repo = GitUtils.open(path) @@ -100,6 +96,21 @@ class Git if @project? @subscribe @project.eachBuffer (buffer) => @subscribeToBuffer(buffer) + # Public: Destroy this {Git} object. + # + # This destroys any tasks and subscriptions and releases the underlying + # libgit2 repository handle. + destroy: -> + if @statusTask? + @statusTask.terminate() + @statusTask = null + + if @repo? + @repo.release() + @repo = null + + @unsubscribe() + ### Section: Event Subscription ### @@ -175,21 +186,6 @@ class Git else checkoutHead() - # Public: Destroy this {Git} object. - # - # This destroys any tasks and subscriptions and releases the underlying - # libgit2 repository handle. - destroy: -> - if @statusTask? - @statusTask.terminate() - @statusTask = null - - if @repo? - @repo.release() - @repo = null - - @unsubscribe() - # Returns the corresponding {Repository} getRepo: (path) -> if @repo? From f1a5e8e1a8808d04bbfcea0b3d9f99043e34bd15 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 14:41:23 -0700 Subject: [PATCH 084/211] Reorganize Git into sections --- src/git.coffee | 472 +++++++++++++++++++++++++------------------------ 1 file changed, 244 insertions(+), 228 deletions(-) diff --git a/src/git.coffee b/src/git.coffee index d7a5e0942..dc40193ab 100644 --- a/src/git.coffee +++ b/src/git.coffee @@ -151,7 +151,250 @@ class Git EmitterMixin::on.apply(this, arguments) ### - Section: Instance Methods + Section: Repo Data + ### + + # Public: Returns the {String} path of the repository. + getPath: -> + @path ?= fs.absolute(@getRepo().getPath()) + + # Public: Returns the {String} working directory path of the repository. + getWorkingDirectory: -> @getRepo().getWorkingDirectory() + + # Public: Returns true if at the root, false if in a subfolder of the + # repository. + isProjectAtRoot: -> + @projectAtRoot ?= @project?.relativize(@getWorkingDirectory()) is '' + + # Public: Makes a path relative to the repository's working directory. + relativize: (path) -> @getRepo().relativize(path) + + # Public: Returns true if the given branch exists. + hasBranch: (branch) -> @getReferenceTarget("refs/heads/#{branch}")? + + # Public: Retrieves a shortened version of the HEAD reference value. + # + # This removes the leading segments of `refs/heads`, `refs/tags`, or + # `refs/remotes`. It also shortens the SHA-1 of a detached `HEAD` to 7 + # characters. + # + # * `path` An optional {String} path in the repository to get this information + # for, only needed if the repository contains submodules. + # + # Returns a {String}. + getShortHead: (path) -> @getRepo(path).getShortHead() + + # Public: Is the given path a submodule in the repository? + # + # * `path` The {String} path to check. + # + # Returns a {Boolean}. + isSubmodule: (path) -> + return false unless path + + repo = @getRepo(path) + if repo.isSubmodule(repo.relativize(path)) + true + else + # Check if the path is a working directory in a repo that isn't the root. + repo isnt @getRepo() and repo.relativize(join(path, 'dir')) is 'dir' + + # Public: Returns the number of commits behind the current branch is from the + # its upstream remote branch. + # + # * `reference` The {String} branch reference name. + # * `path` The {String} path in the repository to get this information for, + # only needed if the repository contains submodules. + getAheadBehindCount: (reference, path) -> + @getRepo(path).getAheadBehindCount(reference) + + # Public: Get the cached ahead/behind commit counts for the current branch's + # upstream branch. + # + # * `path` An optional {String} path in the repository to get this information + # for, only needed if the repository has submodules. + # + # Returns an {Object} with the following keys: + # * `ahead` The {Number} of commits ahead. + # * `behind` The {Number} of commits behind. + getCachedUpstreamAheadBehindCount: (path) -> + @getRepo(path).upstream ? @upstream + + # Public: Returns the git configuration value specified by the key. + # + # * `path` An optional {String} path in the repository to get this information + # for, only needed if the repository has submodules. + getConfigValue: (key, path) -> @getRepo(path).getConfigValue(key) + + # Public: Returns the origin url of the repository. + # + # * `path` (optional) {String} path in the repository to get this information + # for, only needed if the repository has submodules. + getOriginUrl: (path) -> @getConfigValue('remote.origin.url', path) + + # Public: Returns the upstream branch for the current HEAD, or null if there + # is no upstream branch for the current HEAD. + # + # * `path` An optional {String} path in the repo to get this information for, + # only needed if the repository contains submodules. + # + # Returns a {String} branch name such as `refs/remotes/origin/master`. + getUpstreamBranch: (path) -> @getRepo(path).getUpstreamBranch() + + # Public: Gets all the local and remote references. + # + # * `path` An optional {String} path in the repository to get this information + # for, only needed if the repository has submodules. + # + # Returns an {Object} with the following keys: + # * `heads` An {Array} of head reference names. + # * `remotes` An {Array} of remote reference names. + # * `tags` An {Array} of tag reference names. + getReferences: (path) -> @getRepo(path).getReferences() + + # Public: Returns the current {String} SHA for the given reference. + # + # * `reference` The {String} reference to get the target of. + # * `path` An optional {String} path in the repo to get the reference target + # for. Only needed if the repository contains submodules. + getReferenceTarget: (reference, path) -> + @getRepo(path).getReferenceTarget(reference) + + ### + Section: Reading Status + ### + + # Public: Returns true if the given path is modified. + isPathModified: (path) -> @isStatusModified(@getPathStatus(path)) + + # Public: Returns true if the given path is new. + isPathNew: (path) -> @isStatusNew(@getPathStatus(path)) + + # Public: Is the given path ignored? + # + # Returns a {Boolean}. + isPathIgnored: (path) -> @getRepo().isIgnored(@relativize(path)) + + # Extended: Get the status of a directory in the repository's working directory. + # + # * `path` The {String} path to check. + # + # Returns a {Number} representing the status. This value can be passed to + # {::isStatusModified} or {::isStatusNew} to get more information. + getDirectoryStatus: (directoryPath) -> + directoryPath = "#{@relativize(directoryPath)}/" + directoryStatus = 0 + for path, status of @statuses + directoryStatus |= status if path.indexOf(directoryPath) is 0 + directoryStatus + + # Extended: Get the status of a single path in the repository. + # + # `path` A {String} repository-relative path. + # + # Returns a {Number} representing the status. This value can be passed to + # {::isStatusModified} or {::isStatusNew} to get more information. + getPathStatus: (path) -> + repo = @getRepo(path) + relativePath = @relativize(path) + currentPathStatus = @statuses[relativePath] ? 0 + pathStatus = repo.getStatus(repo.relativize(path)) ? 0 + pathStatus = 0 if repo.isStatusIgnored(pathStatus) + if pathStatus > 0 + @statuses[relativePath] = pathStatus + else + delete @statuses[relativePath] + if currentPathStatus isnt pathStatus + @emit 'status-changed', path, pathStatus + @emitter.emit 'did-change-status', {path, pathStatus} + + pathStatus + + # Extended: Get the cached status for the given path. + # + # * `path` A {String} path in the repository, relative or absolute. + # + # Returns a status {Number} or null if the path is not in the cache. + getCachedPathStatus: (path) -> + @statuses[@relativize(path)] + + # Extended: Returns true if the given status indicates modification. + isStatusModified: (status) -> @getRepo().isStatusModified(status) + + # Extended: Returns true if the given status indicates a new path. + isStatusNew: (status) -> @getRepo().isStatusNew(status) + + ### + Section: Retrieving Diffs + ### + + # Public: Retrieves the number of lines added and removed to a path. + # + # This compares the working directory contents of the path to the `HEAD` + # version. + # + # * `path` The {String} path to check. + # + # Returns an {Object} with the following keys: + # * `added` The {Number} of added lines. + # * `deleted` The {Number} of deleted lines. + getDiffStats: (path) -> + repo = @getRepo(path) + repo.getDiffStats(repo.relativize(path)) + + # Public: Retrieves the line diffs comparing the `HEAD` version of the given + # path and the given text. + # + # * `path` The {String} path relative to the repository. + # * `text` The {String} to compare against the `HEAD` contents + # + # Returns an {Array} of hunk {Object}s with the following keys: + # * `oldStart` The line {Number} of the old hunk. + # * `newStart` The line {Number} of the new hunk. + # * `oldLines` The {Number} of lines in the old hunk. + # * `newLines` The {Number} of lines in the new hunk + getLineDiffs: (path, text) -> + # Ignore eol of line differences on windows so that files checked in as + # LF don't report every line modified when the text contains CRLF endings. + options = ignoreEolWhitespace: process.platform is 'win32' + repo = @getRepo(path) + repo.getLineDiffs(repo.relativize(path), text, options) + + ### + Section: Checking Out + ### + + # Public: Restore the contents of a path in the working directory and index + # to the version at `HEAD`. + # + # This is essentially the same as running: + # + # ```sh + # git reset HEAD -- + # git checkout HEAD -- + # ``` + # + # * `path` The {String} path to checkout. + # + # Returns a {Boolean} that's true if the method was successful. + checkoutHead: (path) -> + repo = @getRepo(path) + headCheckedOut = repo.checkoutHead(repo.relativize(path)) + @getPathStatus(path) if headCheckedOut + headCheckedOut + + # Public: Checks out a branch in your repository. + # + # * `reference` The {String} reference to checkout. + # * `create` A {Boolean} value which, if true creates the new reference if + # it doesn't exist. + # + # Returns a Boolean that's true if the method was successful. + checkoutReference: (reference, create) -> + @getRepo().checkoutReference(reference, create) + + ### + Section: Private ### # Subscribes to buffer events. @@ -197,233 +440,6 @@ class Git # last time the index was read. refreshIndex: -> @getRepo().refreshIndex() - # Public: Returns the {String} path of the repository. - getPath: -> - @path ?= fs.absolute(@getRepo().getPath()) - - # Public: Returns the {String} working directory path of the repository. - getWorkingDirectory: -> @getRepo().getWorkingDirectory() - - # Public: Get the status of a single path in the repository. - # - # `path` A {String} repository-relative path. - # - # Returns a {Number} representing the status. This value can be passed to - # {::isStatusModified} or {::isStatusNew} to get more information. - getPathStatus: (path) -> - repo = @getRepo(path) - relativePath = @relativize(path) - currentPathStatus = @statuses[relativePath] ? 0 - pathStatus = repo.getStatus(repo.relativize(path)) ? 0 - pathStatus = 0 if repo.isStatusIgnored(pathStatus) - if pathStatus > 0 - @statuses[relativePath] = pathStatus - else - delete @statuses[relativePath] - if currentPathStatus isnt pathStatus - @emit 'status-changed', path, pathStatus - @emitter.emit 'did-change-status', {path, pathStatus} - - pathStatus - - # Public: Is the given path ignored? - # - # Returns a {Boolean}. - isPathIgnored: (path) -> @getRepo().isIgnored(@relativize(path)) - - # Public: Returns true if the given status indicates modification. - isStatusModified: (status) -> @getRepo().isStatusModified(status) - - # Public: Returns true if the given path is modified. - isPathModified: (path) -> @isStatusModified(@getPathStatus(path)) - - # Public: Returns true if the given status indicates a new path. - isStatusNew: (status) -> @getRepo().isStatusNew(status) - - # Public: Returns true if the given path is new. - isPathNew: (path) -> @isStatusNew(@getPathStatus(path)) - - # Public: Returns true if at the root, false if in a subfolder of the - # repository. - isProjectAtRoot: -> - @projectAtRoot ?= @project?.relativize(@getWorkingDirectory()) is '' - - # Public: Makes a path relative to the repository's working directory. - relativize: (path) -> @getRepo().relativize(path) - - # Public: Retrieves a shortened version of the HEAD reference value. - # - # This removes the leading segments of `refs/heads`, `refs/tags`, or - # `refs/remotes`. It also shortens the SHA-1 of a detached `HEAD` to 7 - # characters. - # - # * `path` An optional {String} path in the repository to get this information - # for, only needed if the repository contains submodules. - # - # Returns a {String}. - getShortHead: (path) -> @getRepo(path).getShortHead() - - # Public: Restore the contents of a path in the working directory and index - # to the version at `HEAD`. - # - # This is essentially the same as running: - # - # ```sh - # git reset HEAD -- - # git checkout HEAD -- - # ``` - # - # * `path` The {String} path to checkout. - # - # Returns a {Boolean} that's true if the method was successful. - checkoutHead: (path) -> - repo = @getRepo(path) - headCheckedOut = repo.checkoutHead(repo.relativize(path)) - @getPathStatus(path) if headCheckedOut - headCheckedOut - - # Public: Checks out a branch in your repository. - # - # * `reference` The {String} reference to checkout. - # * `create` A {Boolean} value which, if true creates the new reference if - # it doesn't exist. - # - # Returns a Boolean that's true if the method was successful. - checkoutReference: (reference, create) -> - @getRepo().checkoutReference(reference, create) - - # Public: Retrieves the number of lines added and removed to a path. - # - # This compares the working directory contents of the path to the `HEAD` - # version. - # - # * `path` The {String} path to check. - # - # Returns an {Object} with the following keys: - # * `added` The {Number} of added lines. - # * `deleted` The {Number} of deleted lines. - getDiffStats: (path) -> - repo = @getRepo(path) - repo.getDiffStats(repo.relativize(path)) - - # Public: Is the given path a submodule in the repository? - # - # * `path` The {String} path to check. - # - # Returns a {Boolean}. - isSubmodule: (path) -> - return false unless path - - repo = @getRepo(path) - if repo.isSubmodule(repo.relativize(path)) - true - else - # Check if the path is a working directory in a repo that isn't the root. - repo isnt @getRepo() and repo.relativize(join(path, 'dir')) is 'dir' - - # Public: Get the status of a directory in the repository's working directory. - # - # * `path` The {String} path to check. - # - # Returns a {Number} representing the status. This value can be passed to - # {::isStatusModified} or {::isStatusNew} to get more information. - getDirectoryStatus: (directoryPath) -> - directoryPath = "#{@relativize(directoryPath)}/" - directoryStatus = 0 - for path, status of @statuses - directoryStatus |= status if path.indexOf(directoryPath) is 0 - directoryStatus - - # Public: Retrieves the line diffs comparing the `HEAD` version of the given - # path and the given text. - # - # * `path` The {String} path relative to the repository. - # * `text` The {String} to compare against the `HEAD` contents - # - # Returns an {Array} of hunk {Object}s with the following keys: - # * `oldStart` The line {Number} of the old hunk. - # * `newStart` The line {Number} of the new hunk. - # * `oldLines` The {Number} of lines in the old hunk. - # * `newLines` The {Number} of lines in the new hunk - getLineDiffs: (path, text) -> - # Ignore eol of line differences on windows so that files checked in as - # LF don't report every line modified when the text contains CRLF endings. - options = ignoreEolWhitespace: process.platform is 'win32' - repo = @getRepo(path) - repo.getLineDiffs(repo.relativize(path), text, options) - - # Public: Returns the git configuration value specified by the key. - # - # * `path` An optional {String} path in the repository to get this information - # for, only needed if the repository has submodules. - getConfigValue: (key, path) -> @getRepo(path).getConfigValue(key) - - # Public: Returns the origin url of the repository. - # - # * `path` An optional {String} path in the repository to get this information - # for, only needed if the repository has submodules. - getOriginUrl: (path) -> @getConfigValue('remote.origin.url', path) - - # Public: Returns the upstream branch for the current HEAD, or null if there - # is no upstream branch for the current HEAD. - # - # * `path` An optional {String} path in the repo to get this information for, - # only needed if the repository contains submodules. - # - # Returns a {String} branch name such as `refs/remotes/origin/master`. - getUpstreamBranch: (path) -> @getRepo(path).getUpstreamBranch() - - # Public: Returns the current {String} SHA for the given reference. - # - # * `reference` The {String} reference to get the target of. - # * `path` An optional {String} path in the repo to get the reference target - # for. Only needed if the repository contains submodules. - getReferenceTarget: (reference, path) -> - @getRepo(path).getReferenceTarget(reference) - - # Public: Gets all the local and remote references. - # - # * `path` An optional {String} path in the repository to get this information - # for, only needed if the repository has submodules. - # - # Returns an {Object} with the following keys: - # * `heads` An {Array} of head reference names. - # * `remotes` An {Array} of remote reference names. - # * `tags` An {Array} of tag reference names. - getReferences: (path) -> @getRepo(path).getReferences() - - # Public: Returns the number of commits behind the current branch is from the - # its upstream remote branch. - # - # * `reference` The {String} branch reference name. - # * `path` The {String} path in the repository to get this information for, - # only needed if the repository contains submodules. - getAheadBehindCount: (reference, path) -> - @getRepo(path).getAheadBehindCount(reference) - - # Public: Get the cached ahead/behind commit counts for the current branch's - # upstream branch. - # - # * `path` An optional {String} path in the repository to get this information - # for, only needed if the repository has submodules. - # - # Returns an {Object} with the following keys: - # * `ahead` The {Number} of commits ahead. - # * `behind` The {Number} of commits behind. - getCachedUpstreamAheadBehindCount: (path) -> - @getRepo(path).upstream ? @upstream - - # Public: Get the cached status for the given path. - # - # * `path` A {String} path in the repository, relative or absolute. - # - # Returns a status {Number} or null if the path is not in the cache. - getCachedPathStatus: (path) -> - @statuses[@relativize(path)] - - # Public: Returns true if the given branch exists. - hasBranch: (branch) -> @getReferenceTarget("refs/heads/#{branch}")? - # Refreshes the current git status in an outside process and asynchronously # updates the relevant properties. refreshStatus: -> From b444fbd22cd510da13603cffcc5a3bfc3c7ed338 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 14:43:57 -0700 Subject: [PATCH 085/211] Git module is extended --- src/git.coffee | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/git.coffee b/src/git.coffee index dc40193ab..4093f8890 100644 --- a/src/git.coffee +++ b/src/git.coffee @@ -10,7 +10,7 @@ GitUtils = require 'git-utils' Task = require './task' -# Public: Represents the underlying git operations performed by Atom. +# Extended: Represents the underlying git operations performed by Atom. # # This class shouldn't be instantiated directly but instead by accessing the # `atom.project` global and calling `getRepo()`. Note that this will only be @@ -115,7 +115,7 @@ class Git Section: Event Subscription ### - # Essential: Invoke the given callback when a specific file's status has + # Public: Invoke the given callback when a specific file's status has # changed. When a file is updated, reloaded, etc, and the status changes, this # will be fired. # @@ -129,7 +129,7 @@ class Git onDidChangeStatus: (callback) -> @emitter.on 'did-change-status', callback - # Essential: Invoke the given callback when a multiple files' statuses have + # Public: Invoke the given callback when a multiple files' statuses have # changed. For example, on window focus, the status of all the paths in the # repo is checked. If any of them have changed, this will be fired. Call # {::getPathStatus(path)} to get the status for your path of choice. @@ -151,7 +151,7 @@ class Git EmitterMixin::on.apply(this, arguments) ### - Section: Repo Data + Section: Repository Data ### # Public: Returns the {String} path of the repository. @@ -275,7 +275,7 @@ class Git # Returns a {Boolean}. isPathIgnored: (path) -> @getRepo().isIgnored(@relativize(path)) - # Extended: Get the status of a directory in the repository's working directory. + # Public: Get the status of a directory in the repository's working directory. # # * `path` The {String} path to check. # @@ -288,7 +288,7 @@ class Git directoryStatus |= status if path.indexOf(directoryPath) is 0 directoryStatus - # Extended: Get the status of a single path in the repository. + # Public: Get the status of a single path in the repository. # # `path` A {String} repository-relative path. # @@ -310,7 +310,7 @@ class Git pathStatus - # Extended: Get the cached status for the given path. + # Public: Get the cached status for the given path. # # * `path` A {String} path in the repository, relative or absolute. # @@ -318,10 +318,10 @@ class Git getCachedPathStatus: (path) -> @statuses[@relativize(path)] - # Extended: Returns true if the given status indicates modification. + # Public: Returns true if the given status indicates modification. isStatusModified: (status) -> @getRepo().isStatusModified(status) - # Extended: Returns true if the given status indicates a new path. + # Public: Returns true if the given status indicates a new path. isStatusNew: (status) -> @getRepo().isStatusNew(status) ### From 053d483b2b2356b9ca3a9e6941b578300d3af4e7 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 16:06:37 -0700 Subject: [PATCH 086/211] Rearrange SelectListView with sections --- src/select-list-view.coffee | 230 ++++++++++++++++++++---------------- 1 file changed, 127 insertions(+), 103 deletions(-) diff --git a/src/select-list-view.coffee b/src/select-list-view.coffee index ae5ffdc93..87d42ba8d 100644 --- a/src/select-list-view.coffee +++ b/src/select-list-view.coffee @@ -46,6 +46,10 @@ class SelectListView extends View inputThrottle: 50 cancelling: false + ### + Section: Construction + ### + # Public: Initialize the select list view. # # This method can be overridden by subclasses but `super` should always @@ -85,11 +89,37 @@ class SelectListView extends View @confirmSelection() if $(e.target).closest('li').hasClass('selected') e.preventDefault() - schedulePopulateList: -> - clearTimeout(@scheduleTimeout) - populateCallback = => - @populateList() if @isOnDom() - @scheduleTimeout = setTimeout(populateCallback, @inputThrottle) + ### + Section: Methods that must be overridden + ### + + # Public: Create a view for the given model item. + # + # This method must be overridden by subclasses. + # + # This is called when the item is about to appended to the list view. + # + # * `item` The model item being rendered. This will always be one of the items + # previously passed to {::setItems}. + # + # Returns a String of HTML, DOM element, jQuery object, or View. + viewForItem: (item) -> + throw new Error("Subclass must implement a viewForItem(item) method") + + # Public: Callback function for when an item is selected. + # + # This method must be overridden by subclasses. + # + # * `item` The selected model item. This will always be one of the items + # previously passed to {::setItems}. + # + # Returns a DOM element, jQuery object, or {View}. + confirmed: (item) -> + throw new Error("Subclass must implement a confirmed(item) method") + + ### + Section: Managing the list of items + ### # Public: Set the array of items to display in the list. # @@ -101,30 +131,26 @@ class SelectListView extends View @populateList() @setLoading() - # Public: Set the error message to display. + # Public: Get the model item that is currently selected in the list view. # - # * `message` The {String} error message (default: ''). - setError: (message='') -> - if message.length is 0 - @error.text('').hide() - else - @setLoading() - @error.text(message).show() + # Returns a model item. + getSelectedItem: -> + @getSelectedItemView().data('select-list-item') - # Public: Set the loading message to display. + # Extended: Get the property name to use when filtering items. # - # * `message` The {String} loading message (default: ''). - setLoading: (message='') -> - if message.length is 0 - @loading.text("") - @loadingBadge.text("") - @loadingArea.hide() - else - @setError() - @loading.text(message) - @loadingArea.show() + # This method may be overridden by classes to allow fuzzy filtering based + # on a specific property of the item objects. + # + # For example if the objects you pass to {::setItems} are of the type + # `{"id": 3, "name": "Atom"}` then you would return `"name"` from this method + # to fuzzy filter by that property when text is entered into this view's + # editor. + # + # Returns the property name to fuzzy filter by. + getFilterKey: -> - # Public: Get the filter query to use when fuzzy filtering the visible + # Extended: Get the filter query to use when fuzzy filtering the visible # elements. # # By default this method returns the text in the mini editor but it can be @@ -134,7 +160,12 @@ class SelectListView extends View getFilterQuery: -> @filterEditorView.getEditor().getText() - # Public: Populate the list view with the model items previously set by + # Extended: Set the maximum numbers of items to display in the list. + # + # * `maxItems` The maximum {Number} of items to display. + setMaxItems: (@maxItems) -> + + # Extended: Populate the list view with the model items previously set by # calling {::setItems}. # # Subclasses may override this method but should always call `super`. @@ -161,6 +192,33 @@ class SelectListView extends View else @setError(@getEmptyMessage(@items.length, filteredItems.length)) + ### + Section: Messages to the user + ### + + # Public: Set the error message to display. + # + # * `message` The {String} error message (default: ''). + setError: (message='') -> + if message.length is 0 + @error.text('').hide() + else + @setLoading() + @error.text(message).show() + + # Public: Set the loading message to display. + # + # * `message` The {String} loading message (default: ''). + setLoading: (message='') -> + if message.length is 0 + @loading.text("") + @loadingBadge.text("") + @loadingArea.hide() + else + @setError() + @loading.text(message) + @loadingArea.show() + # Public: Get the message to display when there are no items. # # Subclasses may override this method to customize the message. @@ -171,10 +229,36 @@ class SelectListView extends View # Returns a {String} message (default: 'No matches found'). getEmptyMessage: (itemCount, filteredItemCount) -> 'No matches found' - # Public: Set the maximum numbers of items to display in the list. + ### + Section: View Actions + ### + + # Public: Focus the fuzzy filter editor view. + focusFilterEditor: -> + @filterEditorView.focus() + + # Public: Cancel and close this select list view. # - # * `maxItems` The maximum {Number} of items to display. - setMaxItems: (@maxItems) -> + # This restores focus to the previously focused element if + # {::storeFocusedElement} was called prior to this view being attached. + cancel: -> + @list.empty() + @cancelling = true + filterEditorViewFocused = @filterEditorView.isFocused + @cancelled() + @detach() + @restoreFocus() if filterEditorViewFocused + @cancelling = false + clearTimeout(@scheduleTimeout) + + # Public: Store the currently focused element. This element will be given + # back focus when {::cancel} is called. + storeFocusedElement: -> + @previouslyFocusedElement = $(':focus') + + ### + Section: Private + ### selectPreviousItemView: -> view = @getSelectedItemView().prev() @@ -202,68 +286,6 @@ class SelectListView extends View else if desiredBottom > @list.scrollBottom() @list.scrollBottom(desiredBottom) - getSelectedItemView: -> - @list.find('li.selected') - - # Public: Get the model item that is currently selected in the list view. - # - # Returns a model item. - getSelectedItem: -> - @getSelectedItemView().data('select-list-item') - - confirmSelection: -> - item = @getSelectedItem() - if item? - @confirmed(item) - else - @cancel() - - # Public: Create a view for the given model item. - # - # This method must be overridden by subclasses. - # - # This is called when the item is about to appended to the list view. - # - # * `item` The model item being rendered. This will always be one of the items - # previously passed to {::setItems}. - # - # Returns a String of HTML, DOM element, jQuery object, or View. - viewForItem: (item) -> - throw new Error("Subclass must implement a viewForItem(item) method") - - # Public: Callback function for when an item is selected. - # - # This method must be overridden by subclasses. - # - # * `item` The selected model item. This will always be one of the items - # previously passed to {::setItems}. - # - # Returns a DOM element, jQuery object, or {View}. - confirmed: (item) -> - throw new Error("Subclass must implement a confirmed(item) method") - - # Public: Get the property name to use when filtering items. - # - # This method may be overridden by classes to allow fuzzy filtering based - # on a specific property of the item objects. - # - # For example if the objects you pass to {::setItems} are of the type - # `{"id": 3, "name": "Atom"}` then you would return `"name"` from this method - # to fuzzy filter by that property when text is entered into this view's - # editor. - # - # Returns the property name to fuzzy filter by. - getFilterKey: -> - - # Public: Focus the fuzzy filter editor view. - focusFilterEditor: -> - @filterEditorView.focus() - - # Public: Store the currently focused element. This element will be given - # back focus when {::cancel} is called. - storeFocusedElement: -> - @previouslyFocusedElement = $(':focus') - restoreFocus: -> if @previouslyFocusedElement?.isOnDom() @previouslyFocusedElement.focus() @@ -273,16 +295,18 @@ class SelectListView extends View cancelled: -> @filterEditorView.getEditor().setText('') - # Public: Cancel and close this select list view. - # - # This restores focus to the previously focused element if - # {::storeFocusedElement} was called prior to this view being attached. - cancel: -> - @list.empty() - @cancelling = true - filterEditorViewFocused = @filterEditorView.isFocused - @cancelled() - @detach() - @restoreFocus() if filterEditorViewFocused - @cancelling = false + getSelectedItemView: -> + @list.find('li.selected') + + confirmSelection: -> + item = @getSelectedItem() + if item? + @confirmed(item) + else + @cancel() + + schedulePopulateList: -> clearTimeout(@scheduleTimeout) + populateCallback = => + @populateList() if @isOnDom() + @scheduleTimeout = setTimeout(populateCallback, @inputThrottle) From 7ff59384540ec129aa932dec64d2d4f731566210 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 16:18:47 -0700 Subject: [PATCH 087/211] Convert visibility in SelectListView --- src/select-list-view.coffee | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/select-list-view.coffee b/src/select-list-view.coffee index 87d42ba8d..b3cc4a1bc 100644 --- a/src/select-list-view.coffee +++ b/src/select-list-view.coffee @@ -50,7 +50,7 @@ class SelectListView extends View Section: Construction ### - # Public: Initialize the select list view. + # Essential: Initialize the select list view. # # This method can be overridden by subclasses but `super` should always # be called. @@ -93,7 +93,7 @@ class SelectListView extends View Section: Methods that must be overridden ### - # Public: Create a view for the given model item. + # Essential: Create a view for the given model item. # # This method must be overridden by subclasses. # @@ -106,7 +106,7 @@ class SelectListView extends View viewForItem: (item) -> throw new Error("Subclass must implement a viewForItem(item) method") - # Public: Callback function for when an item is selected. + # Essential: Callback function for when an item is selected. # # This method must be overridden by subclasses. # @@ -121,7 +121,7 @@ class SelectListView extends View Section: Managing the list of items ### - # Public: Set the array of items to display in the list. + # Essential: Set the array of items to display in the list. # # This should be model items not actual views. {::viewForItem} will be # called to render the item when it is being appended to the list view. @@ -131,7 +131,7 @@ class SelectListView extends View @populateList() @setLoading() - # Public: Get the model item that is currently selected in the list view. + # Essential: Get the model item that is currently selected in the list view. # # Returns a model item. getSelectedItem: -> @@ -196,7 +196,7 @@ class SelectListView extends View Section: Messages to the user ### - # Public: Set the error message to display. + # Essential: Set the error message to display. # # * `message` The {String} error message (default: ''). setError: (message='') -> @@ -206,7 +206,7 @@ class SelectListView extends View @setLoading() @error.text(message).show() - # Public: Set the loading message to display. + # Essential: Set the loading message to display. # # * `message` The {String} loading message (default: ''). setLoading: (message='') -> @@ -219,7 +219,7 @@ class SelectListView extends View @loading.text(message) @loadingArea.show() - # Public: Get the message to display when there are no items. + # Extended: Get the message to display when there are no items. # # Subclasses may override this method to customize the message. # @@ -233,11 +233,7 @@ class SelectListView extends View Section: View Actions ### - # Public: Focus the fuzzy filter editor view. - focusFilterEditor: -> - @filterEditorView.focus() - - # Public: Cancel and close this select list view. + # Essential: Cancel and close this select list view. # # This restores focus to the previously focused element if # {::storeFocusedElement} was called prior to this view being attached. @@ -251,7 +247,11 @@ class SelectListView extends View @cancelling = false clearTimeout(@scheduleTimeout) - # Public: Store the currently focused element. This element will be given + # Extended: Focus the fuzzy filter editor view. + focusFilterEditor: -> + @filterEditorView.focus() + + # Extended: Store the currently focused element. This element will be given # back focus when {::cancel} is called. storeFocusedElement: -> @previouslyFocusedElement = $(':focus') From 51aaffb2e51bb0418f035f0e84c2c056ef98a8f5 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 16 Sep 2014 16:22:07 -0700 Subject: [PATCH 088/211] Upgrade to status-bar@0.45 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 135144923..c61d442d7 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "settings-view": "0.142.0", "snippets": "0.52.0", "spell-check": "0.42.0", - "status-bar": "0.44.0", + "status-bar": "0.45.0", "styleguide": "0.30.0", "symbols-view": "0.63.0", "tabs": "0.51.0", From fb1ac72b6ee793792c6bf327e35828611062d0a7 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 16:23:30 -0700 Subject: [PATCH 089/211] installShellCommands is not public --- src/workspace-view.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 458d6d311..57468b4df 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -167,7 +167,7 @@ class WorkspaceView extends View # Returns a {Workspace}. getModel: -> @model - # Public: Install the Atom shell commands on the user's system. + # Install the Atom shell commands on the user's system. installShellCommands: -> showErrorDialog = (error) -> installDirectory = CommandInstaller.getInstallDirectory() From 4c930453846653f8f14d1cf734f698dd9b3997db Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 16:30:13 -0700 Subject: [PATCH 090/211] Reorganize WorkspaceView into sections --- src/workspace-view.coffee | 267 +++++++++++++++++++++----------------- 1 file changed, 146 insertions(+), 121 deletions(-) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 57468b4df..98072639c 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -162,86 +162,74 @@ class WorkspaceView extends View @command 'core:save', => @saveActivePaneItem() @command 'core:save-as', => @saveActivePaneItemAs() + ### + Section: Accessing the Workspace Model + ### + # Public: Get the underlying model object. # # Returns a {Workspace}. getModel: -> @model - # Install the Atom shell commands on the user's system. - installShellCommands: -> - showErrorDialog = (error) -> - installDirectory = CommandInstaller.getInstallDirectory() - atom.confirm - message: "Failed to install shell commands" - detailedMessage: error.message + ### + Section: Accessing Views + ### - resourcePath = atom.getLoadSettings().resourcePath - CommandInstaller.installAtomCommand resourcePath, true, (error) -> - if error? - showErrorDialog(error) - else - CommandInstaller.installApmCommand resourcePath, true, (error) -> - if error? - showErrorDialog(error) - else - atom.confirm - message: "Commands installed." - detailedMessage: "The shell commands `atom` and `apm` are installed." - - handleFocus: -> - if @getActivePaneView() - @getActivePaneView().focus() - false - else - @updateTitle() - focusableChild = @find("[tabindex=-1]:visible:first") - if focusableChild.length - focusableChild.focus() - false - else - $(document.body).focus() - true - - afterAttach: (onDom) -> - @focus() if onDom - - # Prompts to save all unsaved items - confirmClose: -> - @panes.confirmClose() - - # Updates the application's title and proxy icon based on whichever file is - # open. - updateTitle: -> - if projectPath = atom.project.getPath() - if item = @getModel().getActivePaneItem() - title = "#{item.getTitle?() ? 'untitled'} - #{projectPath}" - @setTitle(title, item.getPath?()) - else - @setTitle(projectPath, projectPath) - else - @setTitle('untitled') - - # Sets the application's title (and the proxy icon on OS X) - setTitle: (title, proxyIconPath='') -> - document.title = title - atom.setRepresentedFilename(proxyIconPath) - - # On OS X, fades the application window's proxy icon when the current file - # has been modified. - updateDocumentEdited: -> - modified = @model.getActivePaneItem()?.isModified?() ? false - atom.setDocumentEdited(modified) - - # Get all editor views. + # Public: Register a function to be called for every current and future + # editor view in the workspace (only includes {EditorView}s that are pane + # items). # - # You should prefer {Workspace::getEditors} unless you absolutely need access - # to the view objects. Also consider using {::eachEditorView}, which will call - # a callback for all current and *future* editor views. + # * `callback` A {Function} with an {EditorView} as its only argument. + # * `editorView` {EditorView} # - # Returns an {Array} of {EditorView}s. - getEditorViews: -> - for editorElement in @panes.element.querySelectorAll('.pane > .item-views > .editor') - $(editorElement).view() + # Returns a subscription object with an `.off` method that you can call to + # unregister the callback. + eachEditorView: (callback) -> + callback(editorView) for editorView in @getEditorViews() + attachedCallback = (e, editorView) -> + callback(editorView) unless editorView.mini + @on('editor:attached', attachedCallback) + off: => @off('editor:attached', attachedCallback) + + # Public: Register a function to be called for every current and future + # pane view in the workspace. + # + # * `callback` A {Function} with a {PaneView} as its only argument. + # * `paneView` {PaneView} + # + # Returns a subscription object with an `.off` method that you can call to + # unregister the callback. + eachPaneView: (callback) -> + @panes.eachPaneView(callback) + + # Public: Get all existing pane views. + # + # Prefer {Workspace::getPanes} if you don't need access to the view objects. + # Also consider using {::eachPaneView} if you want to register a callback for + # all current and *future* pane views. + # + # Returns an Array of all open {PaneView}s. + getPaneViews: -> + @panes.getPaneViews() + + # Public: Get the active pane view. + # + # Prefer {Workspace::getActivePane} if you don't actually need access to the + # view. + # + # Returns a {PaneView}. + getActivePaneView: -> + @panes.getActivePaneView() + + # Public: Get the view associated with the active pane item. + # + # Returns a view. + getActiveView: -> + @panes.getActiveView() + + ### + Section: Adding elements to the workspace + ### # Public: Prepend an element or view to the panels at the top of the # workspace. @@ -298,20 +286,9 @@ class WorkspaceView extends View appendToRight: (element) -> @horizontal.append(element) - # Public: Get the active pane view. - # - # Prefer {Workspace::getActivePane} if you don't actually need access to the - # view. - # - # Returns a {PaneView}. - getActivePaneView: -> - @panes.getActivePaneView() - - # Public: Get the view associated with the active pane item. - # - # Returns a view. - getActiveView: -> - @panes.getActiveView() + ### + Section: Focusing pane views + ### # Focus the previous pane by id. focusPreviousPaneView: -> @model.activatePreviousPane() @@ -331,42 +308,12 @@ class WorkspaceView extends View # Public: Focus the pane directly to the right of the active pane. focusPaneViewOnRight: -> @panes.focusPaneViewOnRight() - # Public: Register a function to be called for every current and future - # pane view in the workspace. - # - # * `callback` A {Function} with a {PaneView} as its only argument. - # * `paneView` {PaneView} - # - # Returns a subscription object with an `.off` method that you can call to - # unregister the callback. - eachPaneView: (callback) -> - @panes.eachPaneView(callback) + ### + Section: Private + ### - # Public: Get all existing pane views. - # - # Prefer {Workspace::getPanes} if you don't need access to the view objects. - # Also consider using {::eachPaneView} if you want to register a callback for - # all current and *future* pane views. - # - # Returns an Array of all open {PaneView}s. - getPaneViews: -> - @panes.getPaneViews() - - # Public: Register a function to be called for every current and future - # editor view in the workspace (only includes {EditorView}s that are pane - # items). - # - # * `callback` A {Function} with an {EditorView} as its only argument. - # * `editorView` {EditorView} - # - # Returns a subscription object with an `.off` method that you can call to - # unregister the callback. - eachEditorView: (callback) -> - callback(editorView) for editorView in @getEditorViews() - attachedCallback = (e, editorView) -> - callback(editorView) unless editorView.mini - @on('editor:attached', attachedCallback) - off: => @off('editor:attached', attachedCallback) + afterAttach: (onDom) -> + @focus() if onDom # Called by SpacePen beforeRemove: -> @@ -381,6 +328,84 @@ class WorkspaceView extends View setEditorLineHeight: (lineHeight) -> atom.themes.updateGlobalEditorStyle('line-height', lineHeight) + # Install the Atom shell commands on the user's system. + installShellCommands: -> + showErrorDialog = (error) -> + installDirectory = CommandInstaller.getInstallDirectory() + atom.confirm + message: "Failed to install shell commands" + detailedMessage: error.message + + resourcePath = atom.getLoadSettings().resourcePath + CommandInstaller.installAtomCommand resourcePath, true, (error) -> + if error? + showErrorDialog(error) + else + CommandInstaller.installApmCommand resourcePath, true, (error) -> + if error? + showErrorDialog(error) + else + atom.confirm + message: "Commands installed." + detailedMessage: "The shell commands `atom` and `apm` are installed." + + handleFocus: -> + if @getActivePaneView() + @getActivePaneView().focus() + false + else + @updateTitle() + focusableChild = @find("[tabindex=-1]:visible:first") + if focusableChild.length + focusableChild.focus() + false + else + $(document.body).focus() + true + + # Prompts to save all unsaved items + confirmClose: -> + @panes.confirmClose() + + # Updates the application's title and proxy icon based on whichever file is + # open. + updateTitle: -> + if projectPath = atom.project.getPath() + if item = @getModel().getActivePaneItem() + title = "#{item.getTitle?() ? 'untitled'} - #{projectPath}" + @setTitle(title, item.getPath?()) + else + @setTitle(projectPath, projectPath) + else + @setTitle('untitled') + + # Sets the application's title (and the proxy icon on OS X) + setTitle: (title, proxyIconPath='') -> + document.title = title + atom.setRepresentedFilename(proxyIconPath) + + # On OS X, fades the application window's proxy icon when the current file + # has been modified. + updateDocumentEdited: -> + modified = @model.getActivePaneItem()?.isModified?() ? false + atom.setDocumentEdited(modified) + + # Get all editor views. + # + # You should prefer {Workspace::getEditors} unless you absolutely need access + # to the view objects. Also consider using {::eachEditorView}, which will call + # a callback for all current and *future* editor views. + # + # Returns an {Array} of {EditorView}s. + getEditorViews: -> + for editorElement in @panes.element.querySelectorAll('.pane > .item-views > .editor') + $(editorElement).view() + + + ### + Section: Deprecated + ### + # Deprecated eachPane: (callback) -> deprecate("Use WorkspaceView::eachPaneView instead") From 5252b5314bc11a3bd8536619cb1543fe9c32e071 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 16:30:46 -0700 Subject: [PATCH 091/211] WorkspaceView Public -> Essential --- src/workspace-view.coffee | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 98072639c..753f371bd 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -166,7 +166,7 @@ class WorkspaceView extends View Section: Accessing the Workspace Model ### - # Public: Get the underlying model object. + # Essential: Get the underlying model object. # # Returns a {Workspace}. getModel: -> @model @@ -175,7 +175,7 @@ class WorkspaceView extends View Section: Accessing Views ### - # Public: Register a function to be called for every current and future + # Essential: Register a function to be called for every current and future # editor view in the workspace (only includes {EditorView}s that are pane # items). # @@ -191,7 +191,7 @@ class WorkspaceView extends View @on('editor:attached', attachedCallback) off: => @off('editor:attached', attachedCallback) - # Public: Register a function to be called for every current and future + # Essential: Register a function to be called for every current and future # pane view in the workspace. # # * `callback` A {Function} with a {PaneView} as its only argument. @@ -202,7 +202,7 @@ class WorkspaceView extends View eachPaneView: (callback) -> @panes.eachPaneView(callback) - # Public: Get all existing pane views. + # Essential: Get all existing pane views. # # Prefer {Workspace::getPanes} if you don't need access to the view objects. # Also consider using {::eachPaneView} if you want to register a callback for @@ -212,7 +212,7 @@ class WorkspaceView extends View getPaneViews: -> @panes.getPaneViews() - # Public: Get the active pane view. + # Essential: Get the active pane view. # # Prefer {Workspace::getActivePane} if you don't actually need access to the # view. @@ -221,7 +221,7 @@ class WorkspaceView extends View getActivePaneView: -> @panes.getActivePaneView() - # Public: Get the view associated with the active pane item. + # Essential: Get the view associated with the active pane item. # # Returns a view. getActiveView: -> @@ -231,55 +231,55 @@ class WorkspaceView extends View Section: Adding elements to the workspace ### - # Public: Prepend an element or view to the panels at the top of the + # Essential: Prepend an element or view to the panels at the top of the # workspace. # # * `element` jQuery object or DOM element prependToTop: (element) -> @vertical.prepend(element) - # Public: Append an element or view to the panels at the top of the workspace. + # Essential: Append an element or view to the panels at the top of the workspace. # # * `element` jQuery object or DOM element appendToTop: (element) -> @panes.before(element) - # Public: Prepend an element or view to the panels at the bottom of the + # Essential: Prepend an element or view to the panels at the bottom of the # workspace. # # * `element` jQuery object or DOM element prependToBottom: (element) -> @panes.after(element) - # Public: Append an element or view to the panels at the bottom of the + # Essential: Append an element or view to the panels at the bottom of the # workspace. # # * `element` jQuery object or DOM element appendToBottom: (element) -> @vertical.append(element) - # Public: Prepend an element or view to the panels at the left of the + # Essential: Prepend an element or view to the panels at the left of the # workspace. # # * `element` jQuery object or DOM element prependToLeft: (element) -> @horizontal.prepend(element) - # Public: Append an element or view to the panels at the left of the + # Essential: Append an element or view to the panels at the left of the # workspace. # # * `element` jQuery object or DOM element appendToLeft: (element) -> @vertical.before(element) - # Public: Prepend an element or view to the panels at the right of the + # Essential: Prepend an element or view to the panels at the right of the # workspace. # # * `element` jQuery object or DOM element prependToRight: (element) -> @vertical.after(element) - # Public: Append an element or view to the panels at the right of the + # Essential: Append an element or view to the panels at the right of the # workspace. # # * `element` jQuery object or DOM element @@ -296,16 +296,16 @@ class WorkspaceView extends View # Focus the next pane by id. focusNextPaneView: -> @model.activateNextPane() - # Public: Focus the pane directly above the active pane. + # Essential: Focus the pane directly above the active pane. focusPaneViewAbove: -> @panes.focusPaneViewAbove() - # Public: Focus the pane directly below the active pane. + # Essential: Focus the pane directly below the active pane. focusPaneViewBelow: -> @panes.focusPaneViewBelow() - # Public: Focus the pane directly to the left of the active pane. + # Essential: Focus the pane directly to the left of the active pane. focusPaneViewOnLeft: -> @panes.focusPaneViewOnLeft() - # Public: Focus the pane directly to the right of the active pane. + # Essential: Focus the pane directly to the right of the active pane. focusPaneViewOnRight: -> @panes.focusPaneViewOnRight() ### From dd063c09d0aa817fb63b52658e80642d196cfe29 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 16 Sep 2014 16:41:37 -0700 Subject: [PATCH 092/211] Upgrade to pathwatcher@2.1.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c61d442d7..998a90eb7 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "nslog": "^1.0.1", "oniguruma": "^3.0.4", "optimist": "0.4.0", - "pathwatcher": "^2.1.2", + "pathwatcher": "^2.1.3", "property-accessors": "^1", "q": "^1.0.1", "random-words": "0.0.1", From ad288478d5c20a4b97cdcfe98488cc1231b5f465 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 16 Sep 2014 16:46:04 -0700 Subject: [PATCH 093/211] Upgrade to language-sql@0.11 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 998a90eb7..39a0d2fcd 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "language-sass": "0.21.0", "language-shellscript": "0.8.0", "language-source": "0.8.0", - "language-sql": "0.10.0", + "language-sql": "0.11.0", "language-text": "0.6.0", "language-todo": "0.11.0", "language-toml": "0.12.0", From 6467f3c42524a9cfbbcf178e3087d175759b01ad Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 16:50:17 -0700 Subject: [PATCH 094/211] Reorganize cursor into sections --- src/cursor.coffee | 312 +++++++++++++++++++++++++--------------------- 1 file changed, 170 insertions(+), 142 deletions(-) diff --git a/src/cursor.coffee b/src/cursor.coffee index b736dd424..d53318fc9 100644 --- a/src/cursor.coffee +++ b/src/cursor.coffee @@ -54,11 +54,14 @@ class Cursor extends Model @emitter.dispose() @needsAutoscroll = true + destroy: -> + @marker.destroy() + ### Section: Event Subscription ### - # Essential: Calls your `callback` when the cursor has been moved. + # Public: Calls your `callback` when the cursor has been moved. # # * `callback` {Function} # * `event` {Object} @@ -72,7 +75,7 @@ class Cursor extends Model onDidChangePosition: (callback) -> @emitter.on 'did-change-position', callback - # Extended: Calls your `callback` when the cursor is destroyed + # Public: Calls your `callback` when the cursor is destroyed # # * `callback` {Function} # @@ -80,7 +83,7 @@ class Cursor extends Model onDidDestroy: (callback) -> @emitter.on 'did-destroy', callback - # Extended: Calls your `callback` when the cursor's visibility has changed + # Public: Calls your `callback` when the cursor's visibility has changed # # * `callback` {Function} # * `visibility` {Boolean} @@ -102,23 +105,9 @@ class Cursor extends Model super ### - Section: Methods + Section: Managing Cursor Position ### - destroy: -> - @marker.destroy() - - changePosition: (options, fn) -> - @clearSelection() - @needsAutoscroll = options.autoscroll ? @isLastCursor() - fn() - if @needsAutoscroll - @emit 'autoscrolled' # Support legacy editor - @autoscroll() if @needsAutoscroll and @editor.manageScrollPosition # Support react editor view - - getPixelRect: -> - @editor.pixelRectForScreenRange(@getScreenRange()) - # Public: Moves a cursor to a given screen position. # # * `screenPosition` {Array} of two numbers: the screen row, and the screen column. @@ -133,10 +122,6 @@ class Cursor extends Model getScreenPosition: -> @marker.getHeadScreenPosition() - getScreenRange: -> - {row, column} = @getScreenPosition() - new Range(new Point(row, column), new Point(row, column + 1)) - # Public: Moves a cursor to a given buffer position. # # * `bufferPosition` {Array} of two numbers: the buffer row, and the buffer column. @@ -151,47 +136,38 @@ class Cursor extends Model getBufferPosition: -> @marker.getHeadBufferPosition() - autoscroll: (options) -> - @editor.scrollToScreenRange(@getScreenRange(), options) + # Public: Returns the cursor's current screen row. + getScreenRow: -> + @getScreenPosition().row - # Public: If the marker range is empty, the cursor is marked as being visible. - updateVisibility: -> - @setVisible(@marker.getBufferRange().isEmpty()) + # Public: Returns the cursor's current screen column. + getScreenColumn: -> + @getScreenPosition().column - # Public: Sets whether the cursor is visible. - setVisible: (visible) -> - if @visible != visible - @visible = visible - @needsAutoscroll ?= true if @visible and @isLastCursor() - @emit 'visibility-changed', @visible - @emitter.emit 'did-change-visibility', @visible + # Public: Retrieves the cursor's current buffer row. + getBufferRow: -> + @getBufferPosition().row - # Public: Returns the visibility of the cursor. - isVisible: -> @visible + # Public: Returns the cursor's current buffer column. + getBufferColumn: -> + @getBufferPosition().column - # Public: Get the RegExp used by the cursor to determine what a "word" is. - # - # * `options` (optional) {Object} with the following keys: - # * `includeNonWordCharacters` A {Boolean} indicating whether to include - # non-word characters in the regex. (default: true) - # - # Returns a {RegExp}. - wordRegExp: ({includeNonWordCharacters}={}) -> - includeNonWordCharacters ?= true - nonWordCharacters = atom.config.get('editor.nonWordCharacters') - segments = ["^[\t ]*$"] - segments.push("[^\\s#{_.escapeRegExp(nonWordCharacters)}]+") - if includeNonWordCharacters - segments.push("[#{_.escapeRegExp(nonWordCharacters)}]+") - new RegExp(segments.join("|"), "g") + # Public: Returns the cursor's current buffer row of text excluding its line + # ending. + getCurrentBufferLine: -> + @editor.lineTextForBufferRow(@getBufferRow()) - # Public: Identifies if this cursor is the last in the {Editor}. - # - # "Last" is defined as the most recently added cursor. - # - # Returns a {Boolean}. - isLastCursor: -> - this == @editor.getLastCursor() + # Public: Returns whether the cursor is at the start of a line. + isAtBeginningOfLine: -> + @getBufferPosition().column == 0 + + # Public: Returns whether the cursor is on the line return character. + isAtEndOfLine: -> + @getBufferPosition().isEqual(@getCurrentLineBufferRange().end) + + ### + Section: Position-dependent cursor properties + ### # Public: Identifies if the cursor is surrounded by whitespace. # @@ -229,34 +205,42 @@ class Cursor extends Model range = [[row, column], [row, Infinity]] @editor.getTextInBufferRange(range).search(@wordRegExp()) == 0 - # Public: Prevents this cursor from causing scrolling. - clearAutoscroll: -> - @needsAutoscroll = null + # Public: Returns the indentation level of the current line. + getIndentLevel: -> + if @editor.getSoftTabs() + @getBufferColumn() / @editor.getTabLength() + else + @getBufferColumn() - # Public: Deselects the current selection. - clearSelection: -> - @selection?.clear() + # Public: Retrieves the grammar's token scopes for the line. + # + # Returns an {Array} of {String}s. + getScopes: -> + @editor.scopesForBufferPosition(@getBufferPosition()) - # Public: Returns the cursor's current screen row. - getScreenRow: -> - @getScreenPosition().row + # Public: Returns true if this cursor has no non-whitespace characters before + # its current position. + hasPrecedingCharactersOnLine: -> + bufferPosition = @getBufferPosition() + line = @editor.lineTextForBufferRow(bufferPosition.row) + firstCharacterColumn = line.search(/\S/) - # Public: Returns the cursor's current screen column. - getScreenColumn: -> - @getScreenPosition().column + if firstCharacterColumn is -1 + false + else + bufferPosition.column > firstCharacterColumn - # Public: Retrieves the cursor's current buffer row. - getBufferRow: -> - @getBufferPosition().row + # Public: Identifies if this cursor is the last in the {Editor}. + # + # "Last" is defined as the most recently added cursor. + # + # Returns a {Boolean}. + isLastCursor: -> + this == @editor.getLastCursor() - # Public: Returns the cursor's current buffer column. - getBufferColumn: -> - @getBufferPosition().column - - # Public: Returns the cursor's current buffer row of text excluding its line - # ending. - getCurrentBufferLine: -> - @editor.lineTextForBufferRow(@getBufferRow()) + ### + Section: Moving the Cursor + ### # Public: Moves the cursor up one screen row. # @@ -417,6 +401,20 @@ class Cursor extends Model @setBufferPosition(endOfLeadingWhitespace) if endOfLeadingWhitespace.isGreaterThan(position) + # Public: Moves the cursor to the beginning of the next paragraph + moveToBeginningOfNextParagraph: -> + if position = @getBeginningOfNextParagraphBufferPosition() + @setBufferPosition(position) + + # Public: Moves the cursor to the beginning of the previous paragraph + moveToBeginningOfPreviousParagraph: -> + if position = @getBeginningOfPreviousParagraphBufferPosition() + @setBufferPosition(position) + + ### + Section: Retrieving Positions and Ranges of local boundaries + ### + # Public: Retrieves the buffer position of where the current word starts. # # * `options` (optional) An {Object} with the following keys: @@ -553,15 +551,98 @@ class Cursor extends Model getCurrentLineBufferRange: (options) -> @editor.bufferRangeForBufferRow(@getBufferRow(), options) - # Public: Moves the cursor to the beginning of the next paragraph - moveToBeginningOfNextParagraph: -> - if position = @getBeginningOfNextParagraphBufferPosition() - @setBufferPosition(position) + # Public: Retrieves the range for the current paragraph. + # + # A paragraph is defined as a block of text surrounded by empty lines. + # + # Returns a {Range}. + getCurrentParagraphBufferRange: -> + @editor.languageMode.rowRangeForParagraphAtBufferRow(@getBufferRow()) - # Public: Moves the cursor to the beginning of the previous paragraph - moveToBeginningOfPreviousParagraph: -> - if position = @getBeginningOfPreviousParagraphBufferPosition() - @setBufferPosition(position) + # Public: Returns the characters preceding the cursor in the current word. + getCurrentWordPrefix: -> + @editor.getTextInBufferRange([@getBeginningOfCurrentWordBufferPosition(), @getBufferPosition()]) + + ### + Section: Visibility + ### + + # Public: If the marker range is empty, the cursor is marked as being visible. + updateVisibility: -> + @setVisible(@marker.getBufferRange().isEmpty()) + + # Public: Sets whether the cursor is visible. + setVisible: (visible) -> + if @visible != visible + @visible = visible + @needsAutoscroll ?= true if @visible and @isLastCursor() + @emit 'visibility-changed', @visible + @emitter.emit 'did-change-visibility', @visible + + # Public: Returns the visibility of the cursor. + isVisible: -> @visible + + ### + Section: Comparing to another cursor + ### + + # Public: Compare this cursor's buffer position to another cursor's buffer position. + # + # See {Point::compare} for more details. + # + # * `otherCursor`{Cursor} to compare against + compare: (otherCursor) -> + @getBufferPosition().compare(otherCursor.getBufferPosition()) + + ### + Section: Utilities + ### + + # Public: Prevents this cursor from causing scrolling. + clearAutoscroll: -> + @needsAutoscroll = null + + # Public: Deselects the current selection. + clearSelection: -> + @selection?.clear() + + # Public: Get the RegExp used by the cursor to determine what a "word" is. + # + # * `options` (optional) {Object} with the following keys: + # * `includeNonWordCharacters` A {Boolean} indicating whether to include + # non-word characters in the regex. (default: true) + # + # Returns a {RegExp}. + wordRegExp: ({includeNonWordCharacters}={}) -> + includeNonWordCharacters ?= true + nonWordCharacters = atom.config.get('editor.nonWordCharacters') + segments = ["^[\t ]*$"] + segments.push("[^\\s#{_.escapeRegExp(nonWordCharacters)}]+") + if includeNonWordCharacters + segments.push("[#{_.escapeRegExp(nonWordCharacters)}]+") + new RegExp(segments.join("|"), "g") + + ### + Section: Private + ### + + changePosition: (options, fn) -> + @clearSelection() + @needsAutoscroll = options.autoscroll ? @isLastCursor() + fn() + if @needsAutoscroll + @emit 'autoscrolled' # Support legacy editor + @autoscroll() if @needsAutoscroll and @editor.manageScrollPosition # Support react editor view + + getPixelRect: -> + @editor.pixelRectForScreenRange(@getScreenRange()) + + getScreenRange: -> + {row, column} = @getScreenPosition() + new Range(new Point(row, column), new Point(row, column + 1)) + + autoscroll: (options) -> + @editor.scrollToScreenRange(@getScreenRange(), options) getBeginningOfNextParagraphBufferPosition: (editor) -> start = @getBufferPosition() @@ -589,56 +670,3 @@ class Cursor extends Model position = range.start stop() @editor.screenPositionForBufferPosition(position) - - # Public: Retrieves the range for the current paragraph. - # - # A paragraph is defined as a block of text surrounded by empty lines. - # - # Returns a {Range}. - getCurrentParagraphBufferRange: -> - @editor.languageMode.rowRangeForParagraphAtBufferRow(@getBufferRow()) - - # Public: Returns the characters preceding the cursor in the current word. - getCurrentWordPrefix: -> - @editor.getTextInBufferRange([@getBeginningOfCurrentWordBufferPosition(), @getBufferPosition()]) - - # Public: Returns whether the cursor is at the start of a line. - isAtBeginningOfLine: -> - @getBufferPosition().column == 0 - - # Public: Returns the indentation level of the current line. - getIndentLevel: -> - if @editor.getSoftTabs() - @getBufferColumn() / @editor.getTabLength() - else - @getBufferColumn() - - # Public: Returns whether the cursor is on the line return character. - isAtEndOfLine: -> - @getBufferPosition().isEqual(@getCurrentLineBufferRange().end) - - # Public: Retrieves the grammar's token scopes for the line. - # - # Returns an {Array} of {String}s. - getScopes: -> - @editor.scopesForBufferPosition(@getBufferPosition()) - - # Public: Returns true if this cursor has no non-whitespace characters before - # its current position. - hasPrecedingCharactersOnLine: -> - bufferPosition = @getBufferPosition() - line = @editor.lineTextForBufferRow(bufferPosition.row) - firstCharacterColumn = line.search(/\S/) - - if firstCharacterColumn is -1 - false - else - bufferPosition.column > firstCharacterColumn - - # Public: Compare this cursor's buffer position to another cursor's buffer position. - # - # See {Point::compare} for more details. - # - # * `otherCursor`{Cursor} to compare against - compare: (otherCursor) -> - @getBufferPosition().compare(otherCursor.getBufferPosition()) From 2dbaa524177c03e705e6af76b3baea924d62bb8d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 16 Sep 2014 16:53:25 -0700 Subject: [PATCH 095/211] Upgrade to grammar-selector@0.30 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 39a0d2fcd..28ec17127 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "fuzzy-finder": "0.58.0", "git-diff": "0.39.0", "go-to-line": "0.25.0", - "grammar-selector": "0.29.0", + "grammar-selector": "0.30.0", "image-view": "0.36.0", "incompatible-packages": "0.9.0", "keybinding-resolver": "0.20.0", From 3794cb606f4f8dc9913b70353f26fef246597ddb Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 16 Sep 2014 16:56:36 -0700 Subject: [PATCH 096/211] Upgrade to grammar-selector@0.31 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 28ec17127..9382936d7 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "fuzzy-finder": "0.58.0", "git-diff": "0.39.0", "go-to-line": "0.25.0", - "grammar-selector": "0.30.0", + "grammar-selector": "0.31.0", "image-view": "0.36.0", "incompatible-packages": "0.9.0", "keybinding-resolver": "0.20.0", From 13cc97e44f636e7cc85a56e482df776ad4d232c5 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Tue, 16 Sep 2014 17:05:23 -0700 Subject: [PATCH 097/211] Reorganize PackageManager into sections --- src/package-manager.coffee | 308 ++++++++++++++++++++----------------- 1 file changed, 166 insertions(+), 142 deletions(-) diff --git a/src/package-manager.coffee b/src/package-manager.coffee index f95073e10..838eda68e 100644 --- a/src/package-manager.coffee +++ b/src/package-manager.coffee @@ -48,7 +48,7 @@ class PackageManager Section: Event Subscription ### - # Essential: Invoke the given callback when all packages have been activated. + # Public: Invoke the given callback when all packages have been activated. # # * `callback` {Function} # @@ -56,7 +56,7 @@ class PackageManager onDidLoadAll: (callback) -> @emitter.on 'did-load-all', callback - # Essential: Invoke the given callback when all packages have been activated. + # Public: Invoke the given callback when all packages have been activated. # # * `callback` {Function} # @@ -75,10 +75,10 @@ class PackageManager EmitterMixin::on.apply(this, arguments) ### - Section: Instance Methods + Section: Package system data ### - # Extended: Get the path to the apm command. + # Public: Get the path to the apm command. # # Return a {String} file path to apm. getApmPath: -> @@ -86,19 +86,43 @@ class PackageManager commandName += '.cmd' if process.platform is 'win32' @apmPath ?= path.resolve(__dirname, '..', 'apm', 'node_modules', 'atom-package-manager', 'bin', commandName) - # Extended: Get the paths being used to look for packages. + # Public: Get the paths being used to look for packages. # # Returns an {Array} of {String} directory paths. getPackageDirPaths: -> _.clone(@packageDirPaths) - getPackageState: (name) -> - @packageStates[name] + ### + Section: General package data + ### - setPackageState: (name, state) -> - @packageStates[name] = state + # Public: Resolve the given package name to a path on disk. + # + # * `name` - The {String} package name. + # + # Return a {String} folder path or undefined if it could not be resolved. + resolvePackagePath: (name) -> + return name if fs.isDirectorySync(name) - # Extended: Enable the package with the given name. + packagePath = fs.resolve(@packageDirPaths..., name) + return packagePath if fs.isDirectorySync(packagePath) + + packagePath = path.join(@resourcePath, 'node_modules', name) + return packagePath if @hasAtomEngine(packagePath) + + # Public: Is the package with the given name bundled with Atom? + # + # * `name` - The {String} package name. + # + # Returns a {Boolean}. + isBundledPackage: (name) -> + @getPackageDependencies().hasOwnProperty(name) + + ### + Section: Enabling and disabling packages + ### + + # Public: Enable the package with the given name. # # Returns the {Package} that was enabled or null if it isn't loaded. enablePackage: (name) -> @@ -106,7 +130,7 @@ class PackageManager pack?.enable() pack - # Extended: Disable the package with the given name. + # Public: Disable the package with the given name. # # Returns the {Package} that was disabled or null if it isn't loaded. disablePackage: (name) -> @@ -114,51 +138,23 @@ class PackageManager pack?.disable() pack - # Activate all the packages that should be activated. - activate: -> - for [activator, types] in @packageActivators - packages = @getLoadedPackagesForTypes(types) - activator.activatePackages(packages) - @emit 'activated' - @emitter.emit 'did-activate-all' + # Public: Is the package with the given name disabled? + # + # * `name` - The {String} package name. + # + # Returns a {Boolean}. + isPackageDisabled: (name) -> + _.include(atom.config.get('core.disabledPackages') ? [], name) - # another type of package manager can handle other package types. - # See ThemeManager - registerPackageActivator: (activator, types) -> - @packageActivators.push([activator, types]) + ### + Section: Accessing active packages + ### - activatePackages: (packages) -> - @activatePackage(pack.name) for pack in packages - @observeDisabledPackages() - - # Activate a single package by name - activatePackage: (name) -> - if pack = @getActivePackage(name) - Q(pack) - else - pack = @loadPackage(name) - pack.activate().then => - @activePackages[pack.name] = pack - pack - - # Deactivate all packages - deactivatePackages: -> - @deactivatePackage(pack.name) for pack in @getLoadedPackages() - @unobserveDisabledPackages() - - # Deactivate the package with the given name - deactivatePackage: (name) -> - pack = @getLoadedPackage(name) - if @isPackageActive(name) - @setPackageState(pack.name, state) if state = pack.serialize?() - pack.deactivate() - delete @activePackages[pack.name] - - # Essential: Get an {Array} of all the active {Package}s. + # Public: Get an {Array} of all the active {Package}s. getActivePackages: -> _.values(@activePackages) - # Essential: Get the active {Package} with the given name. + # Public: Get the active {Package} with the given name. # # * `name` - The {String} package name. # @@ -174,6 +170,91 @@ class PackageManager isPackageActive: (name) -> @getActivePackage(name)? + ### + Section: Accessing loaded packages + ### + + # Public: Get an {Array} of all the loaded {Package}s + getLoadedPackages: -> + _.values(@loadedPackages) + + # Get packages for a certain package type + # + # * `types` an {Array} of {String}s like ['atom', 'textmate']. + getLoadedPackagesForTypes: (types) -> + pack for pack in @getLoadedPackages() when pack.getType() in types + + # Public: Get the loaded {Package} with the given name. + # + # * `name` - The {String} package name. + # + # Returns a {Package} or undefined. + getLoadedPackage: (name) -> + @loadedPackages[name] + + # Public: Is the package with the given name loaded? + # + # * `name` - The {String} package name. + # + # Returns a {Boolean}. + isPackageLoaded: (name) -> + @getLoadedPackage(name)? + + ### + Section: Accessing available packages + ### + + # Public: Get an {Array} of {String}s of all the available package paths. + getAvailablePackagePaths: -> + packagePaths = [] + + for packageDirPath in @packageDirPaths + for packagePath in fs.listSync(packageDirPath) + packagePaths.push(packagePath) if fs.isDirectorySync(packagePath) + + packagesPath = path.join(@resourcePath, 'node_modules') + for packageName, packageVersion of @getPackageDependencies() + packagePath = path.join(packagesPath, packageName) + packagePaths.push(packagePath) if fs.isDirectorySync(packagePath) + + _.uniq(packagePaths) + + # Public: Get an {Array} of {String}s of all the available package names. + getAvailablePackageNames: -> + _.uniq _.map @getAvailablePackagePaths(), (packagePath) -> path.basename(packagePath) + + # Public: Get an {Array} of {String}s of all the available package metadata. + getAvailablePackageMetadata: -> + packages = [] + for packagePath in @getAvailablePackagePaths() + name = path.basename(packagePath) + metadata = @getLoadedPackage(name)?.metadata ? Package.loadMetadata(packagePath, true) + packages.push(metadata) + packages + + ### + Section: Private + ### + + getPackageState: (name) -> + @packageStates[name] + + setPackageState: (name, state) -> + @packageStates[name] = state + + getPackageDependencies: -> + unless @packageDependencies? + try + metadataPath = path.join(@resourcePath, 'package.json') + {@packageDependencies} = JSON.parse(fs.readFileSync(metadataPath)) ? {} + @packageDependencies ?= {} + + @packageDependencies + + hasAtomEngine: (packagePath) -> + metadata = Package.loadMetadata(packagePath, true) + metadata?.engines?.atom? + unobserveDisabledPackages: -> @disabledPackagesSubscription?.off() @disabledPackagesSubscription = null @@ -234,99 +315,42 @@ class PackageManager else throw new Error("No loaded package for name '#{name}'") - # Essential: Get the loaded {Package} with the given name. - # - # * `name` - The {String} package name. - # - # Returns a {Package} or undefined. - getLoadedPackage: (name) -> - @loadedPackages[name] + # Activate all the packages that should be activated. + activate: -> + for [activator, types] in @packageActivators + packages = @getLoadedPackagesForTypes(types) + activator.activatePackages(packages) + @emit 'activated' + @emitter.emit 'did-activate-all' - # Essential: Is the package with the given name loaded? - # - # * `name` - The {String} package name. - # - # Returns a {Boolean}. - isPackageLoaded: (name) -> - @getLoadedPackage(name)? + # another type of package manager can handle other package types. + # See ThemeManager + registerPackageActivator: (activator, types) -> + @packageActivators.push([activator, types]) - # Essential: Get an {Array} of all the loaded {Package}s - getLoadedPackages: -> - _.values(@loadedPackages) + activatePackages: (packages) -> + @activatePackage(pack.name) for pack in packages + @observeDisabledPackages() - # Get packages for a certain package type - # - # * `types` an {Array} of {String}s like ['atom', 'textmate']. - getLoadedPackagesForTypes: (types) -> - pack for pack in @getLoadedPackages() when pack.getType() in types + # Activate a single package by name + activatePackage: (name) -> + if pack = @getActivePackage(name) + Q(pack) + else + pack = @loadPackage(name) + pack.activate().then => + @activePackages[pack.name] = pack + pack - # Extended: Resolve the given package name to a path on disk. - # - # * `name` - The {String} package name. - # - # Return a {String} folder path or undefined if it could not be resolved. - resolvePackagePath: (name) -> - return name if fs.isDirectorySync(name) + # Deactivate all packages + deactivatePackages: -> + @deactivatePackage(pack.name) for pack in @getLoadedPackages() + @unobserveDisabledPackages() - packagePath = fs.resolve(@packageDirPaths..., name) - return packagePath if fs.isDirectorySync(packagePath) - - packagePath = path.join(@resourcePath, 'node_modules', name) - return packagePath if @hasAtomEngine(packagePath) - - # Essential: Is the package with the given name disabled? - # - # * `name` - The {String} package name. - # - # Returns a {Boolean}. - isPackageDisabled: (name) -> - _.include(atom.config.get('core.disabledPackages') ? [], name) - - hasAtomEngine: (packagePath) -> - metadata = Package.loadMetadata(packagePath, true) - metadata?.engines?.atom? - - # Extended: Is the package with the given name bundled with Atom? - # - # * `name` - The {String} package name. - # - # Returns a {Boolean}. - isBundledPackage: (name) -> - @getPackageDependencies().hasOwnProperty(name) - - getPackageDependencies: -> - unless @packageDependencies? - try - metadataPath = path.join(@resourcePath, 'package.json') - {@packageDependencies} = JSON.parse(fs.readFileSync(metadataPath)) ? {} - @packageDependencies ?= {} - - @packageDependencies - - # Extended: Get an {Array} of {String}s of all the available package paths. - getAvailablePackagePaths: -> - packagePaths = [] - - for packageDirPath in @packageDirPaths - for packagePath in fs.listSync(packageDirPath) - packagePaths.push(packagePath) if fs.isDirectorySync(packagePath) - - packagesPath = path.join(@resourcePath, 'node_modules') - for packageName, packageVersion of @getPackageDependencies() - packagePath = path.join(packagesPath, packageName) - packagePaths.push(packagePath) if fs.isDirectorySync(packagePath) - - _.uniq(packagePaths) - - # Extended: Get an {Array} of {String}s of all the available package names. - getAvailablePackageNames: -> - _.uniq _.map @getAvailablePackagePaths(), (packagePath) -> path.basename(packagePath) - - # Extended: Get an {Array} of {String}s of all the available package metadata. - getAvailablePackageMetadata: -> - packages = [] - for packagePath in @getAvailablePackagePaths() - name = path.basename(packagePath) - metadata = @getLoadedPackage(name)?.metadata ? Package.loadMetadata(packagePath, true) - packages.push(metadata) - packages + # Deactivate the package with the given name + deactivatePackage: (name) -> + pack = @getLoadedPackage(name) + if @isPackageActive(name) + @setPackageState(pack.name, state) if state = pack.serialize?() + pack.deactivate() + delete @activePackages[pack.name] From b6b7ce31a8996bd93c7843f11536ddbb384eaa38 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 16 Sep 2014 18:10:32 -0700 Subject: [PATCH 098/211] Upgrade to language-javascript@0.40 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9382936d7..11ba595d1 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "language-html": "0.26.0", "language-hyperlink": "0.12.0", "language-java": "0.11.0", - "language-javascript": "0.39.0", + "language-javascript": "0.40.0", "language-json": "0.8.0", "language-less": "0.15.0", "language-make": "0.12.0", From 03fcda880753632cf1ff0a297339dc9fe7be3f0e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 16 Sep 2014 18:49:31 -0700 Subject: [PATCH 099/211] Upgrade to grammar-selector@0.32 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 11ba595d1..b48383697 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "fuzzy-finder": "0.58.0", "git-diff": "0.39.0", "go-to-line": "0.25.0", - "grammar-selector": "0.31.0", + "grammar-selector": "0.32.0", "image-view": "0.36.0", "incompatible-packages": "0.9.0", "keybinding-resolver": "0.20.0", From 72b92fc3e5debeb0b144fb05117e2cefc7d4e804 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 08:54:16 -0700 Subject: [PATCH 100/211] Only add command on platforms where it can be run Closes #3561 --- src/workspace-view.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 458d6d311..f4fc3b893 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -136,7 +136,8 @@ class WorkspaceView extends View @command 'application:open-your-stylesheet', -> ipc.send('command', 'application:open-your-stylesheet') @command 'application:open-license', => @model.openLicense() - @command 'window:install-shell-commands', => @installShellCommands() + if process.platform is 'darwin' + @command 'window:install-shell-commands', => @installShellCommands() @command 'window:run-package-specs', -> ipc.send('run-package-specs', path.join(atom.project.getPath(), 'spec')) @command 'window:increase-font-size', => @increaseFontSize() From b281737838e3b915a0269f5b178f5e7166cffe07 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 16 Sep 2014 17:42:34 -0700 Subject: [PATCH 101/211] Add leading/trailing classes to hard tab tokens Previously the leading-whitespace and trailing-whitespace classes were never added to tokens that were hard tabs. --- spec/editor-component-spec.coffee | 26 ++++++++++++++++++++++++++ src/token.coffee | 2 ++ 2 files changed, 28 insertions(+) diff --git a/spec/editor-component-spec.coffee b/spec/editor-component-spec.coffee index 86f70e9d1..0690528bb 100644 --- a/spec/editor-component-spec.coffee +++ b/spec/editor-component-spec.coffee @@ -197,6 +197,32 @@ describe "EditorComponent", -> nextAnimationFrame() expect(linesNode.style.backgroundColor).toBe 'rgb(255, 0, 0)' + it "applies .trailing-whitespace for lines with trailing spaces and/or tabs", -> + editor.setText('a ') + nextAnimationFrame() + + leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) + expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe true + + editor.setText('a\t') + nextAnimationFrame() + + leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) + expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe true + + it "applies .leading-whitespace for lines with trailing spaces and/or tabs", -> + editor.setText(' a') + nextAnimationFrame() + + leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) + expect(leafNodes[0].classList.contains('leading-whitespace')).toBe true + + editor.setText('\ta') + nextAnimationFrame() + + leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) + expect(leafNodes[0].classList.contains('leading-whitespace')).toBe true + describe "when showInvisibles is enabled", -> invisibles = null diff --git a/src/token.coffee b/src/token.coffee index fabf33de8..5366f33cc 100644 --- a/src/token.coffee +++ b/src/token.coffee @@ -153,6 +153,8 @@ class Token getValueAsHtml: ({hasIndentGuide}) -> if @isHardTab classes = 'hard-tab' + classes += ' leading-whitespace' if @hasLeadingWhitespace() + classes += ' trailing-whitespace' if @hasTrailingWhitespace() classes += ' indent-guide' if hasIndentGuide classes += ' invisible-character' if @hasInvisibleCharacters html = "#{@escapeString(@value)}" From 18f54e67803719b9847170330c4af6a7c7e4b427 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 16 Sep 2014 17:46:25 -0700 Subject: [PATCH 102/211] :lipstick: Put leading spec first --- spec/editor-component-spec.coffee | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/spec/editor-component-spec.coffee b/spec/editor-component-spec.coffee index 0690528bb..f52e4a0a5 100644 --- a/spec/editor-component-spec.coffee +++ b/spec/editor-component-spec.coffee @@ -197,6 +197,19 @@ describe "EditorComponent", -> nextAnimationFrame() expect(linesNode.style.backgroundColor).toBe 'rgb(255, 0, 0)' + it "applies .leading-whitespace for lines with leading spaces and/or tabs", -> + editor.setText(' a') + nextAnimationFrame() + + leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) + expect(leafNodes[0].classList.contains('leading-whitespace')).toBe true + + editor.setText('\ta') + nextAnimationFrame() + + leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) + expect(leafNodes[0].classList.contains('leading-whitespace')).toBe true + it "applies .trailing-whitespace for lines with trailing spaces and/or tabs", -> editor.setText('a ') nextAnimationFrame() @@ -210,19 +223,6 @@ describe "EditorComponent", -> leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe true - it "applies .leading-whitespace for lines with trailing spaces and/or tabs", -> - editor.setText(' a') - nextAnimationFrame() - - leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) - expect(leafNodes[0].classList.contains('leading-whitespace')).toBe true - - editor.setText('\ta') - nextAnimationFrame() - - leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) - expect(leafNodes[0].classList.contains('leading-whitespace')).toBe true - describe "when showInvisibles is enabled", -> invisibles = null From 348f865cab168995d4a83d0d0525e7beeaba3d07 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 09:47:40 -0700 Subject: [PATCH 103/211] Add spec for all whitespace lines --- spec/editor-component-spec.coffee | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spec/editor-component-spec.coffee b/spec/editor-component-spec.coffee index f52e4a0a5..3a9e2a904 100644 --- a/spec/editor-component-spec.coffee +++ b/spec/editor-component-spec.coffee @@ -211,6 +211,18 @@ describe "EditorComponent", -> expect(leafNodes[0].classList.contains('leading-whitespace')).toBe true it "applies .trailing-whitespace for lines with trailing spaces and/or tabs", -> + editor.setText(' ') + nextAnimationFrame() + + leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) + expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe true + + editor.setText('\t') + nextAnimationFrame() + + leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) + expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe true + editor.setText('a ') nextAnimationFrame() From 36f60c517efaebff390c9c10e9102d0aab381b1f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 09:50:51 -0700 Subject: [PATCH 104/211] Assert other whitespace class is not present --- spec/editor-component-spec.coffee | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/editor-component-spec.coffee b/spec/editor-component-spec.coffee index 3a9e2a904..f8bc55742 100644 --- a/spec/editor-component-spec.coffee +++ b/spec/editor-component-spec.coffee @@ -203,12 +203,14 @@ describe "EditorComponent", -> leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) expect(leafNodes[0].classList.contains('leading-whitespace')).toBe true + expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe false editor.setText('\ta') nextAnimationFrame() leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) expect(leafNodes[0].classList.contains('leading-whitespace')).toBe true + expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe false it "applies .trailing-whitespace for lines with trailing spaces and/or tabs", -> editor.setText(' ') @@ -216,24 +218,28 @@ describe "EditorComponent", -> leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe true + expect(leafNodes[0].classList.contains('leading-whitespace')).toBe false editor.setText('\t') nextAnimationFrame() leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe true + expect(leafNodes[0].classList.contains('leading-whitespace')).toBe false editor.setText('a ') nextAnimationFrame() leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe true + expect(leafNodes[0].classList.contains('leading-whitespace')).toBe false editor.setText('a\t') nextAnimationFrame() leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe true + expect(leafNodes[0].classList.contains('leading-whitespace')).toBe false describe "when showInvisibles is enabled", -> invisibles = null From 878da262d2b263222ce4cda628c58ddc974ec7cd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 09:26:52 -0700 Subject: [PATCH 105/211] Add support for Unicode variation sequences These are character pairs that should be treated as tokens with a buffer delta of 2 and a screen delta of 1. --- spec/text-utils-spec.coffee | 39 +++++++++++++++++------------- src/text-utils.coffee | 47 ++++++++++++++++++++++++++++--------- src/token.coffee | 28 +++++++++++----------- 3 files changed, 73 insertions(+), 41 deletions(-) diff --git a/spec/text-utils-spec.coffee b/spec/text-utils-spec.coffee index 36ac0b356..31f49cf72 100644 --- a/spec/text-utils-spec.coffee +++ b/spec/text-utils-spec.coffee @@ -6,25 +6,32 @@ describe 'text utilities', -> expect(textUtils.getCharacterCount('abc')).toBe 3 expect(textUtils.getCharacterCount('a\uD835\uDF97b\uD835\uDF97c')).toBe 5 expect(textUtils.getCharacterCount('\uD835\uDF97')).toBe 1 + expect(textUtils.getCharacterCount('\u2714\uFE0E')).toBe 1 expect(textUtils.getCharacterCount('\uD835')).toBe 1 expect(textUtils.getCharacterCount('\uDF97')).toBe 1 - describe '.hasSurrogatePair(string)', -> + describe '.hasPairedCharacter(string)', -> it 'returns true when the string contains a surrogate pair', -> - expect(textUtils.hasSurrogatePair('abc')).toBe false - expect(textUtils.hasSurrogatePair('a\uD835\uDF97b\uD835\uDF97c')).toBe true - expect(textUtils.hasSurrogatePair('\uD835\uDF97')).toBe true - expect(textUtils.hasSurrogatePair('\uD835')).toBe false - expect(textUtils.hasSurrogatePair('\uDF97')).toBe false + expect(textUtils.hasPairedCharacter('abc')).toBe false + expect(textUtils.hasPairedCharacter('a\uD835\uDF97b\uD835\uDF97c')).toBe true + expect(textUtils.hasPairedCharacter('\uD835\uDF97')).toBe true + expect(textUtils.hasPairedCharacter('\u2714\uFE0E')).toBe true + expect(textUtils.hasPairedCharacter('\uD835')).toBe false + expect(textUtils.hasPairedCharacter('\uDF97')).toBe false + expect(textUtils.hasPairedCharacter('\uFE0E')).toBe false - describe '.isSurrogatePair(string, index)', -> + describe '.isPairedCharacter(string, index)', -> it 'returns true when the index is the start of a high/low surrogate pair', -> - expect(textUtils.isSurrogatePair('a\uD835\uDF97b\uD835\uDF97c', 0)).toBe false - expect(textUtils.isSurrogatePair('a\uD835\uDF97b\uD835\uDF97c', 1)).toBe true - expect(textUtils.isSurrogatePair('a\uD835\uDF97b\uD835\uDF97c', 2)).toBe false - expect(textUtils.isSurrogatePair('a\uD835\uDF97b\uD835\uDF97c', 3)).toBe false - expect(textUtils.isSurrogatePair('a\uD835\uDF97b\uD835\uDF97c', 4)).toBe true - expect(textUtils.isSurrogatePair('a\uD835\uDF97b\uD835\uDF97c', 5)).toBe false - expect(textUtils.isSurrogatePair('a\uD835\uDF97b\uD835\uDF97c', 6)).toBe false - expect(textUtils.isSurrogatePair('\uD835')).toBe false - expect(textUtils.isSurrogatePair('\uDF97')).toBe false + expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 0)).toBe false + expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 1)).toBe true + expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 2)).toBe false + expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 3)).toBe false + expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 4)).toBe true + expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 5)).toBe false + expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 6)).toBe false + expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 0)).toBe false + expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 1)).toBe true + expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 2)).toBe false + expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 3)).toBe false + expect(textUtils.isPairedCharacter('\uD835')).toBe false + expect(textUtils.isPairedCharacter('\uDF97')).toBe false diff --git a/src/text-utils.coffee b/src/text-utils.coffee index a043d7c73..eff284d6d 100644 --- a/src/text-utils.coffee +++ b/src/text-utils.coffee @@ -4,34 +4,59 @@ isHighSurrogate = (string, index) -> isLowSurrogate = (string, index) -> 0xDC00 <= string.charCodeAt(index) <= 0xDFFF +isVariationSelector = (string, index) -> + 0xFE00 <= string.charCodeAt(index) <= 0xFE0F + # Is the character at the given index the start of a high/low surrogate pair? # -# string - The {String} to check for a surrogate pair. -# index - The {Number} index to look for a surrogate pair at. +# * `string` The {String} to check for a surrogate pair. +# * `index` The {Number} index to look for a surrogate pair at. # # Return a {Boolean}. isSurrogatePair = (string, index=0) -> isHighSurrogate(string, index) and isLowSurrogate(string, index + 1) -# Get the number of characters in the string accounting for surrogate pairs. +# Is the character at the given index the start of a variation sequence? # -# This method counts high/low surrogate pairs as a single character and will -# always returns a value less than or equal to `string.length`. +# * `string` The {String} to check for a surrogate pair. +# * `index` The {Number} index to look for a surrogate pair at. # -# string - The {String} to count the number of full characters in. +# Return a {Boolean}. +isVariationSequence = (string, index=0) -> + isVariationSelector(string, index + 1) + +# Is the character at the given index the start of high/low surrogate pair +# or a variation sequence? +# +# * `string` The {String} to check for a surrogate pair. +# * `index` The {Number} index to look for a surrogate pair at. +# +# Return a {Boolean}. + +isPairedCharacter = (string, index=0) -> + isSurrogatePair(string, index) or isVariationSequence(string, index) + +# Get the number of characters in the string accounting for surrogate pairs and +# variation sequences. +# +# This method counts high/low surrogate pairs and variation sequences as a +# single character and will always returns a value less than or equal to +# `string.length`. +# +# * `string` The {String} to count the number of full characters in. # # Returns a {Number}. getCharacterCount = (string) -> count = string.length - count-- for index in [0...string.length] when isSurrogatePair(string, index) + count-- for index in [0...string.length] when isPairedCharacter(string, index) count -# Does the given string contain at least one surrogate pair? +# Does the given string contain at least surrogate pair or variation sequence? # -# string - The {String} to check for the presence of surrogate pairs. +# * `string` The {String} to check for the presence of paired characters. # # Returns a {Boolean}. -hasSurrogatePair = (string) -> +hasPairedCharacter = (string) -> string.length isnt getCharacterCount(string) -module.exports = {getCharacterCount, isSurrogatePair, hasSurrogatePair} +module.exports = {getCharacterCount, isPairedCharacter, hasPairedCharacter} diff --git a/src/token.coffee b/src/token.coffee index 5366f33cc..a36117ea8 100644 --- a/src/token.coffee +++ b/src/token.coffee @@ -12,7 +12,7 @@ MaxTokenLength = 20000 module.exports = class Token value: null - hasSurrogatePair: false + hasPairedCharacter: false scopes: null isAtomic: null isHardTab: null @@ -23,7 +23,7 @@ class Token constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @isHardTab}) -> @screenDelta = @value.length @bufferDelta ?= @screenDelta - @hasSurrogatePair = textUtils.hasSurrogatePair(@value) + @hasPairedCharacter = textUtils.hasPairedCharacter(@value) isEqual: (other) -> @value == other.value and _.isEqual(@scopes, other.scopes) and !!@isAtomic == !!other.isAtomic @@ -57,11 +57,11 @@ class Token WhitespaceRegexesByTabLength[tabLength] ?= new RegExp("([ ]{#{tabLength}})|(\t)|([^\t]+)", "g") breakOutAtomicTokens: (tabLength, breakOutLeadingSoftTabs, startColumn) -> - if @hasSurrogatePair + if @hasPairedCharacter outputTokens = [] column = startColumn - for token in @breakOutSurrogatePairs() + for token in @breakOutPairedCharacters() if token.isAtomic outputTokens.push(token) else @@ -98,27 +98,27 @@ class Token outputTokens - breakOutSurrogatePairs: -> + breakOutPairedCharacters: -> outputTokens = [] index = 0 - nonSurrogatePairStart = 0 + nonPairStart = 0 while index < @value.length - if textUtils.isSurrogatePair(@value, index) - if nonSurrogatePairStart isnt index - outputTokens.push(new Token({value: @value[nonSurrogatePairStart...index], @scopes})) - outputTokens.push(@buildSurrogatePairToken(@value, index)) + if textUtils.isPairedCharacter(@value, index) + if nonPairStart isnt index + outputTokens.push(new Token({value: @value[nonPairStart...index], @scopes})) + outputTokens.push(@buildPairedCharacterToken(@value, index)) index += 2 - nonSurrogatePairStart = index + nonPairStart = index else index++ - if nonSurrogatePairStart isnt index - outputTokens.push(new Token({value: @value[nonSurrogatePairStart...index], @scopes})) + if nonPairStart isnt index + outputTokens.push(new Token({value: @value[nonPairStart...index], @scopes})) outputTokens - buildSurrogatePairToken: (value, index) -> + buildPairedCharacterToken: (value, index) -> new Token( value: value[index..index + 1] scopes: @scopes From fb7061f500acd21bd59879485787d75a1f4d9a0f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 09:28:24 -0700 Subject: [PATCH 106/211] :memo: Mention variation sequence in specs --- spec/text-utils-spec.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/text-utils-spec.coffee b/spec/text-utils-spec.coffee index 31f49cf72..83aa9a274 100644 --- a/spec/text-utils-spec.coffee +++ b/spec/text-utils-spec.coffee @@ -11,7 +11,7 @@ describe 'text utilities', -> expect(textUtils.getCharacterCount('\uDF97')).toBe 1 describe '.hasPairedCharacter(string)', -> - it 'returns true when the string contains a surrogate pair', -> + it 'returns true when the string contains a surrogate pair or variation sequence', -> expect(textUtils.hasPairedCharacter('abc')).toBe false expect(textUtils.hasPairedCharacter('a\uD835\uDF97b\uD835\uDF97c')).toBe true expect(textUtils.hasPairedCharacter('\uD835\uDF97')).toBe true @@ -21,7 +21,7 @@ describe 'text utilities', -> expect(textUtils.hasPairedCharacter('\uFE0E')).toBe false describe '.isPairedCharacter(string, index)', -> - it 'returns true when the index is the start of a high/low surrogate pair', -> + it 'returns true when the index is the start of a high/low surrogate pair or variation sequence', -> expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 0)).toBe false expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 1)).toBe true expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 2)).toBe false From df68ae26a2cf7e623730e5d0793e4758bf25bfef Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 09:30:26 -0700 Subject: [PATCH 107/211] Add specs for variation sequences --- spec/editor-spec.coffee | 43 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/spec/editor-spec.coffee b/spec/editor-spec.coffee index 6ff0aa304..c43b2bae8 100644 --- a/spec/editor-spec.coffee +++ b/spec/editor-spec.coffee @@ -3343,7 +3343,7 @@ describe "Editor", -> editor2.destroy() expect(editor.shouldPromptToSave()).toBeTruthy() - describe "when the edit session contains surrogate pair characters", -> + describe "when the editor text contains surrogate pair characters", -> it "correctly backspaces over them", -> editor.setText('\uD835\uDF97\uD835\uDF97\uD835\uDF97') editor.moveToBottom() @@ -3384,6 +3384,47 @@ describe "Editor", -> editor.moveLeft() expect(editor.getCursorBufferPosition()).toEqual [0, 0] + describe "when the editor contains variation sequence character pairs", -> + it "correctly backspaces over them", -> + editor.setText('\u2714\uFE0E\u2714\uFE0E\u2714\uFE0E') + editor.moveToBottom() + editor.backspace() + expect(editor.getText()).toBe '\u2714\uFE0E\u2714\uFE0E' + editor.backspace() + expect(editor.getText()).toBe '\u2714\uFE0E' + editor.backspace() + expect(editor.getText()).toBe '' + + it "correctly deletes over them", -> + editor.setText('\u2714\uFE0E\u2714\uFE0E\u2714\uFE0E') + editor.moveToTop() + editor.delete() + expect(editor.getText()).toBe '\u2714\uFE0E\u2714\uFE0E' + editor.delete() + expect(editor.getText()).toBe '\u2714\uFE0E' + editor.delete() + expect(editor.getText()).toBe '' + + it "correctly moves over them", -> + editor.setText('\u2714\uFE0E\u2714\uFE0E\u2714\uFE0E\n') + editor.moveToTop() + editor.moveRight() + expect(editor.getCursorBufferPosition()).toEqual [0, 2] + editor.moveRight() + expect(editor.getCursorBufferPosition()).toEqual [0, 4] + editor.moveRight() + expect(editor.getCursorBufferPosition()).toEqual [0, 6] + editor.moveRight() + expect(editor.getCursorBufferPosition()).toEqual [1, 0] + editor.moveLeft() + expect(editor.getCursorBufferPosition()).toEqual [0, 6] + editor.moveLeft() + expect(editor.getCursorBufferPosition()).toEqual [0, 4] + editor.moveLeft() + expect(editor.getCursorBufferPosition()).toEqual [0, 2] + editor.moveLeft() + expect(editor.getCursorBufferPosition()).toEqual [0, 0] + describe ".setIndentationForBufferRow", -> describe "when the editor uses soft tabs but the row has hard tabs", -> it "only replaces whitespace characters", -> From 3acddf3e711e67faa92481a628a63f9cb7de93d5 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 09:32:18 -0700 Subject: [PATCH 108/211] :memo: Drop text --- spec/editor-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/editor-spec.coffee b/spec/editor-spec.coffee index c43b2bae8..27a0dc099 100644 --- a/spec/editor-spec.coffee +++ b/spec/editor-spec.coffee @@ -3343,7 +3343,7 @@ describe "Editor", -> editor2.destroy() expect(editor.shouldPromptToSave()).toBeTruthy() - describe "when the editor text contains surrogate pair characters", -> + describe "when the editor contains surrogate pair characters", -> it "correctly backspaces over them", -> editor.setText('\uD835\uDF97\uD835\uDF97\uD835\uDF97') editor.moveToBottom() From c1aa5c9e48d255d237dcfbf352386cea978d0965 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 09:38:29 -0700 Subject: [PATCH 109/211] :memo: Mention variation sequence in comment --- src/text-utils.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/text-utils.coffee b/src/text-utils.coffee index eff284d6d..16a5427d8 100644 --- a/src/text-utils.coffee +++ b/src/text-utils.coffee @@ -18,8 +18,8 @@ isSurrogatePair = (string, index=0) -> # Is the character at the given index the start of a variation sequence? # -# * `string` The {String} to check for a surrogate pair. -# * `index` The {Number} index to look for a surrogate pair at. +# * `string` The {String} to check for a variation sequence. +# * `index` The {Number} index to look for a variation sequence at. # # Return a {Boolean}. isVariationSequence = (string, index=0) -> @@ -28,7 +28,7 @@ isVariationSequence = (string, index=0) -> # Is the character at the given index the start of high/low surrogate pair # or a variation sequence? # -# * `string` The {String} to check for a surrogate pair. +# * `string` The {String} to check for a surrogate pair or variation sequence. # * `index` The {Number} index to look for a surrogate pair at. # # Return a {Boolean}. From e343b0e0fca4c38b715a928dd2d17e8d10b52794 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 09:40:49 -0700 Subject: [PATCH 110/211] Don't treat consecutive variation selectors as a sequence --- spec/text-utils-spec.coffee | 6 ++++++ src/text-utils.coffee | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/text-utils-spec.coffee b/spec/text-utils-spec.coffee index 83aa9a274..9c20408e4 100644 --- a/spec/text-utils-spec.coffee +++ b/spec/text-utils-spec.coffee @@ -9,6 +9,8 @@ describe 'text utilities', -> expect(textUtils.getCharacterCount('\u2714\uFE0E')).toBe 1 expect(textUtils.getCharacterCount('\uD835')).toBe 1 expect(textUtils.getCharacterCount('\uDF97')).toBe 1 + expect(textUtils.getCharacterCount('\uFE0E')).toBe 1 + expect(textUtils.getCharacterCount('\uFE0E\uFE0E')).toBe 2 describe '.hasPairedCharacter(string)', -> it 'returns true when the string contains a surrogate pair or variation sequence', -> @@ -19,6 +21,7 @@ describe 'text utilities', -> expect(textUtils.hasPairedCharacter('\uD835')).toBe false expect(textUtils.hasPairedCharacter('\uDF97')).toBe false expect(textUtils.hasPairedCharacter('\uFE0E')).toBe false + expect(textUtils.hasPairedCharacter('\uFE0E\uFE0E')).toBe false describe '.isPairedCharacter(string, index)', -> it 'returns true when the index is the start of a high/low surrogate pair or variation sequence', -> @@ -35,3 +38,6 @@ describe 'text utilities', -> expect(textUtils.isPairedCharacter('a\u2714\uFE0E', 3)).toBe false expect(textUtils.isPairedCharacter('\uD835')).toBe false expect(textUtils.isPairedCharacter('\uDF97')).toBe false + expect(textUtils.isPairedCharacter('\uFE0E')).toBe false + expect(textUtils.isPairedCharacter('\uFE0E')).toBe false + expect(textUtils.isPairedCharacter('\uFE0E\uFE0E')).toBe false diff --git a/src/text-utils.coffee b/src/text-utils.coffee index 16a5427d8..fdf3f0128 100644 --- a/src/text-utils.coffee +++ b/src/text-utils.coffee @@ -23,7 +23,7 @@ isSurrogatePair = (string, index=0) -> # # Return a {Boolean}. isVariationSequence = (string, index=0) -> - isVariationSelector(string, index + 1) + not isVariationSelector(string, index) and isVariationSelector(string, index + 1) # Is the character at the given index the start of high/low surrogate pair # or a variation sequence? From f1fd13b0b2fea79ea4c7bdcec1009ef97aeeb6cd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 10:35:33 -0700 Subject: [PATCH 111/211] Return as soon as first paired character is found Previously the character count of the entire string was taken even though it was only checking for the presence of a paired character. Now hasPairedCharacter returns as early as possible and the now unused getCharacterCount has been removed. --- spec/text-utils-spec.coffee | 11 ----------- src/text-utils.coffee | 23 ++++++----------------- 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/spec/text-utils-spec.coffee b/spec/text-utils-spec.coffee index 9c20408e4..89cf34aca 100644 --- a/spec/text-utils-spec.coffee +++ b/spec/text-utils-spec.coffee @@ -1,17 +1,6 @@ textUtils = require '../src/text-utils' describe 'text utilities', -> - describe '.getCharacterCount(string)', -> - it 'returns the number of full characters in the string', -> - expect(textUtils.getCharacterCount('abc')).toBe 3 - expect(textUtils.getCharacterCount('a\uD835\uDF97b\uD835\uDF97c')).toBe 5 - expect(textUtils.getCharacterCount('\uD835\uDF97')).toBe 1 - expect(textUtils.getCharacterCount('\u2714\uFE0E')).toBe 1 - expect(textUtils.getCharacterCount('\uD835')).toBe 1 - expect(textUtils.getCharacterCount('\uDF97')).toBe 1 - expect(textUtils.getCharacterCount('\uFE0E')).toBe 1 - expect(textUtils.getCharacterCount('\uFE0E\uFE0E')).toBe 2 - describe '.hasPairedCharacter(string)', -> it 'returns true when the string contains a surrogate pair or variation sequence', -> expect(textUtils.hasPairedCharacter('abc')).toBe false diff --git a/src/text-utils.coffee b/src/text-utils.coffee index fdf3f0128..fc4870f5c 100644 --- a/src/text-utils.coffee +++ b/src/text-utils.coffee @@ -36,27 +36,16 @@ isVariationSequence = (string, index=0) -> isPairedCharacter = (string, index=0) -> isSurrogatePair(string, index) or isVariationSequence(string, index) -# Get the number of characters in the string accounting for surrogate pairs and -# variation sequences. -# -# This method counts high/low surrogate pairs and variation sequences as a -# single character and will always returns a value less than or equal to -# `string.length`. -# -# * `string` The {String} to count the number of full characters in. -# -# Returns a {Number}. -getCharacterCount = (string) -> - count = string.length - count-- for index in [0...string.length] when isPairedCharacter(string, index) - count - # Does the given string contain at least surrogate pair or variation sequence? # # * `string` The {String} to check for the presence of paired characters. # # Returns a {Boolean}. hasPairedCharacter = (string) -> - string.length isnt getCharacterCount(string) + index = 0 + while index < string.length + return true if isPairedCharacter(string, index) + index++ + false -module.exports = {getCharacterCount, isPairedCharacter, hasPairedCharacter} +module.exports = {isPairedCharacter, hasPairedCharacter} From 146e8c2a0bb967ce94a43a6462338cbd5b0f58cc Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 10:40:12 -0700 Subject: [PATCH 112/211] :lipstick: Remove extra newline --- src/text-utils.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/text-utils.coffee b/src/text-utils.coffee index fc4870f5c..90bf220ff 100644 --- a/src/text-utils.coffee +++ b/src/text-utils.coffee @@ -32,7 +32,6 @@ isVariationSequence = (string, index=0) -> # * `index` The {Number} index to look for a surrogate pair at. # # Return a {Boolean}. - isPairedCharacter = (string, index=0) -> isSurrogatePair(string, index) or isVariationSequence(string, index) From ad17b2d1c38a563082e6a5b3539b22a01284c571 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 11:29:24 -0700 Subject: [PATCH 113/211] Upgrade to apm 0.94 --- apm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm/package.json b/apm/package.json index 9a1db364f..4a04cd456 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "0.93.2" + "atom-package-manager": "0.94.0" } } From 8b34f85f348807732b965e20d34c8be80858babb Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 13:45:55 -0700 Subject: [PATCH 114/211] Upgrade to grammar-selector@0.33 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b48383697..a469d8983 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "fuzzy-finder": "0.58.0", "git-diff": "0.39.0", "go-to-line": "0.25.0", - "grammar-selector": "0.32.0", + "grammar-selector": "0.33.0", "image-view": "0.36.0", "incompatible-packages": "0.9.0", "keybinding-resolver": "0.20.0", From bfdb5bd15009dc6b5fd796a7f7450ae672ac6a95 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 13:52:21 -0700 Subject: [PATCH 115/211] Upgrade to tabs@0.52 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a469d8983..1855dfd36 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "status-bar": "0.45.0", "styleguide": "0.30.0", "symbols-view": "0.63.0", - "tabs": "0.51.0", + "tabs": "0.52.0", "timecop": "0.22.0", "tree-view": "0.125.0", "update-package-dependencies": "0.6.0", From aa8bfd8e5f81b25386646f09c8a98b809c147277 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 17 Sep 2014 15:03:04 -0600 Subject: [PATCH 116/211] Remove stray console.log --- src/command-registry.coffee | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 2abb4d3e8..23a54fea2 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -168,8 +168,6 @@ class CommandRegistry class CommandListener constructor: (@selector, @callback) -> - console.log "calc specificity", @selector - @specificity = (SpecificityCache[@selector] ?= specificity(@selector)) @sequenceNumber = SequenceCount++ From cfffae936c04d64951ee56ca57a15b89868ebc45 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 17 Sep 2014 14:23:46 -0700 Subject: [PATCH 117/211] Deprecate pathForRepositoryUrl for eventual removal --- src/project.coffee | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/project.coffee b/src/project.coffee index 371a7af3e..91e1d2959 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -23,10 +23,8 @@ class Project extends Model atom.deserializers.add(this) Serializable.includeInto(this) - # Public: Find the local path for the given repository URL. - # - # * `repoUrl` {String} url to a git repository @pathForRepositoryUrl: (repoUrl) -> + deprecate '::pathForRepositoryUrl will be removed. Please remove from your code.' [repoName] = url.parse(repoUrl).path.split('/')[-1..] repoName = repoName.replace(/\.git$/, '') path.join(atom.config.get('core.projectHome'), repoName) From a476bb220a962c74c7b293a29e9f040c50b619d6 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 14:26:12 -0700 Subject: [PATCH 118/211] Upgrade to language-coffee-script@0.31 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a1862f1cb..9599708ba 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "whitespace": "0.25.0", "wrap-guide": "0.22.0", "language-c": "0.28.0", - "language-coffee-script": "0.30.0", + "language-coffee-script": "0.31.0", "language-css": "0.17.0", "language-gfm": "0.50.0", "language-git": "0.9.0", From 0963077a327513897917cb59a5f1348bcc3a66bf Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 17 Sep 2014 14:26:15 -0700 Subject: [PATCH 119/211] Reorganize Project class into sections --- src/project.coffee | 210 +++++++++++++++++++++++++-------------------- 1 file changed, 117 insertions(+), 93 deletions(-) diff --git a/src/project.coffee b/src/project.coffee index 91e1d2959..361d9c950 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -29,6 +29,10 @@ class Project extends Model repoName = repoName.replace(/\.git$/, '') path.join(atom.config.get('core.projectHome'), repoName) + ### + Section: Construction and Destruction + ### + constructor: ({path, @buffers}={}) -> @buffers ?= [] @@ -38,14 +42,6 @@ class Project extends Model @setPath(path) - serializeParams: -> - path: @path - buffers: _.compact(@buffers.map (buffer) -> buffer.serialize() if buffer.isRetained()) - - deserializeParams: (params) -> - params.buffers = params.buffers.map (bufferState) -> atom.deserializers.deserialize(bufferState) - params - destroyed: -> buffer.destroy() for buffer in @getBuffers() @destroyRepo() @@ -58,9 +54,29 @@ class Project extends Model destroyUnretainedBuffers: -> buffer.destroy() for buffer in @getBuffers() when not buffer.isRetained() + ### + Section: Serialization + ### + + serializeParams: -> + path: @path + buffers: _.compact(@buffers.map (buffer) -> buffer.serialize() if buffer.isRetained()) + + deserializeParams: (params) -> + params.buffers = params.buffers.map (bufferState) -> atom.deserializers.deserialize(bufferState) + params + + ### + Section: Accessing the git repository + ### + # Public: Returns the {Git} repository if available. getRepo: -> @repo + ### + Section: Managing Paths + ### + # Public: Returns the project's {String} fullpath. getPath: -> @rootDirectory?.path @@ -122,6 +138,99 @@ class Project extends Model contains: (pathToCheck) -> @rootDirectory?.contains(pathToCheck) ? false + ### + Section: Searching and Replacing + ### + + # Public: Performs a search across all the files in the project. + # + # * `regex` {RegExp} to search with. + # * `options` (optional) {Object} (default: {}) + # * `paths` An {Array} of glob patterns to search within + # * `iterator` {Function} callback on each file found + scan: (regex, options={}, iterator) -> + if _.isFunction(options) + iterator = options + options = {} + + deferred = Q.defer() + + searchOptions = + ignoreCase: regex.ignoreCase + inclusions: options.paths + includeHidden: true + excludeVcsIgnores: atom.config.get('core.excludeVcsIgnoredPaths') + exclusions: atom.config.get('core.ignoredNames') + + task = Task.once require.resolve('./scan-handler'), @getPath(), regex.source, searchOptions, -> + deferred.resolve() + + task.on 'scan:result-found', (result) => + iterator(result) unless @isPathModified(result.filePath) + + task.on 'scan:file-error', (error) -> + iterator(null, error) + + if _.isFunction(options.onPathsSearched) + task.on 'scan:paths-searched', (numberOfPathsSearched) -> + options.onPathsSearched(numberOfPathsSearched) + + for buffer in @getBuffers() when buffer.isModified() + filePath = buffer.getPath() + continue unless @contains(filePath) + matches = [] + buffer.scan regex, (match) -> matches.push match + iterator {filePath, matches} if matches.length > 0 + + promise = deferred.promise + promise.cancel = -> + task.terminate() + deferred.resolve('cancelled') + promise + + # Public: Performs a replace across all the specified files in the project. + # + # * `regex` A {RegExp} to search with. + # * `replacementText` Text to replace all matches of regex with + # * `filePaths` List of file path strings to run the replace on. + # * `iterator` A {Function} callback on each file with replacements: + # * `options` {Object} with keys `filePath` and `replacements` + replace: (regex, replacementText, filePaths, iterator) -> + deferred = Q.defer() + + openPaths = (buffer.getPath() for buffer in @getBuffers()) + outOfProcessPaths = _.difference(filePaths, openPaths) + + inProcessFinished = !openPaths.length + outOfProcessFinished = !outOfProcessPaths.length + checkFinished = -> + deferred.resolve() if outOfProcessFinished and inProcessFinished + + unless outOfProcessFinished.length + flags = 'g' + flags += 'i' if regex.ignoreCase + + task = Task.once require.resolve('./replace-handler'), outOfProcessPaths, regex.source, flags, replacementText, -> + outOfProcessFinished = true + checkFinished() + + task.on 'replace:path-replaced', iterator + task.on 'replace:file-error', (error) -> iterator(null, error) + + for buffer in @getBuffers() + continue unless buffer.getPath() in filePaths + replacements = buffer.replace(regex, replacementText, iterator) + iterator({filePath: buffer.getPath(), replacements}) if replacements + + inProcessFinished = true + checkFinished() + + deferred.promise + + ### + Section: Private + ### + # Given a path to a file, this constructs and associates a new # {Editor}, showing the file. # @@ -220,91 +329,6 @@ class Project extends Model [buffer] = @buffers.splice(index, 1) buffer?.destroy() - # Public: Performs a search across all the files in the project. - # - # * `regex` {RegExp} to search with. - # * `options` (optional) {Object} (default: {}) - # * `paths` An {Array} of glob patterns to search within - # * `iterator` {Function} callback on each file found - scan: (regex, options={}, iterator) -> - if _.isFunction(options) - iterator = options - options = {} - - deferred = Q.defer() - - searchOptions = - ignoreCase: regex.ignoreCase - inclusions: options.paths - includeHidden: true - excludeVcsIgnores: atom.config.get('core.excludeVcsIgnoredPaths') - exclusions: atom.config.get('core.ignoredNames') - - task = Task.once require.resolve('./scan-handler'), @getPath(), regex.source, searchOptions, -> - deferred.resolve() - - task.on 'scan:result-found', (result) => - iterator(result) unless @isPathModified(result.filePath) - - task.on 'scan:file-error', (error) -> - iterator(null, error) - - if _.isFunction(options.onPathsSearched) - task.on 'scan:paths-searched', (numberOfPathsSearched) -> - options.onPathsSearched(numberOfPathsSearched) - - for buffer in @getBuffers() when buffer.isModified() - filePath = buffer.getPath() - continue unless @contains(filePath) - matches = [] - buffer.scan regex, (match) -> matches.push match - iterator {filePath, matches} if matches.length > 0 - - promise = deferred.promise - promise.cancel = -> - task.terminate() - deferred.resolve('cancelled') - promise - - # Public: Performs a replace across all the specified files in the project. - # - # * `regex` A {RegExp} to search with. - # * `replacementText` Text to replace all matches of regex with - # * `filePaths` List of file path strings to run the replace on. - # * `iterator` A {Function} callback on each file with replacements: - # * `options` {Object} with keys `filePath` and `replacements` - replace: (regex, replacementText, filePaths, iterator) -> - deferred = Q.defer() - - openPaths = (buffer.getPath() for buffer in @getBuffers()) - outOfProcessPaths = _.difference(filePaths, openPaths) - - inProcessFinished = !openPaths.length - outOfProcessFinished = !outOfProcessPaths.length - checkFinished = -> - deferred.resolve() if outOfProcessFinished and inProcessFinished - - unless outOfProcessFinished.length - flags = 'g' - flags += 'i' if regex.ignoreCase - - task = Task.once require.resolve('./replace-handler'), outOfProcessPaths, regex.source, flags, replacementText, -> - outOfProcessFinished = true - checkFinished() - - task.on 'replace:path-replaced', iterator - task.on 'replace:file-error', (error) -> iterator(null, error) - - for buffer in @getBuffers() - continue unless buffer.getPath() in filePaths - replacements = buffer.replace(regex, replacementText, iterator) - iterator({filePath: buffer.getPath(), replacements}) if replacements - - inProcessFinished = true - checkFinished() - - deferred.promise - buildEditorForBuffer: (buffer, editorOptions) -> editor = new Editor(_.extend({buffer, registerEditor: true}, editorOptions)) editor From 12181bcb020bdfbb093cf08fd5ebd7c8fce9f04a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 14:44:26 -0700 Subject: [PATCH 120/211] Upgrade to language-coffee-script@0.32 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9599708ba..d2891fcad 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "whitespace": "0.25.0", "wrap-guide": "0.22.0", "language-c": "0.28.0", - "language-coffee-script": "0.31.0", + "language-coffee-script": "0.32.0", "language-css": "0.17.0", "language-gfm": "0.50.0", "language-git": "0.9.0", From 27584cf069d372951ef55e954897cc6e75bd3303 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 15:20:31 -0700 Subject: [PATCH 121/211] Upgrade to grammar-selector@0.34 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d2891fcad..2ecbf0dcd 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "fuzzy-finder": "0.58.0", "git-diff": "0.39.0", "go-to-line": "0.25.0", - "grammar-selector": "0.33.0", + "grammar-selector": "0.34.0", "image-view": "0.36.0", "incompatible-packages": "0.9.0", "keybinding-resolver": "0.20.0", From 37ddf096a7da7efdc92a12c94c7e2517efbc8723 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 17 Sep 2014 15:39:16 -0700 Subject: [PATCH 122/211] Reorganize ThemeManager into sections --- src/theme-manager.coffee | 298 +++++++++++++++++++++------------------ 1 file changed, 159 insertions(+), 139 deletions(-) diff --git a/src/theme-manager.coffee b/src/theme-manager.coffee index 23c630043..1aebe757b 100644 --- a/src/theme-manager.coffee +++ b/src/theme-manager.coffee @@ -85,17 +85,29 @@ class ThemeManager EmitterMixin::on.apply(this, arguments) ### - Section: Instance Methods + Section: Accessing Available Themes ### getAvailableNames: -> # TODO: Maybe should change to list all the available themes out there? @getLoadedNames() + ### + Section: Accessing Loaded Themes + ### + # Public: Get an array of all the loaded theme names. getLoadedNames: -> theme.name for theme in @getLoadedThemes() + # Public: Get an array of all the loaded themes. + getLoadedThemes: -> + pack for pack in @packageManager.getLoadedPackages() when pack.isTheme() + + ### + Section: Accessing Active Themes + ### + # Public: Get an array of all the active theme names. getActiveNames: -> theme.name for theme in @getActiveThemes() @@ -104,13 +116,13 @@ class ThemeManager getActiveThemes: -> pack for pack in @packageManager.getActivePackages() when pack.isTheme() - # Public: Get an array of all the loaded themes. - getLoadedThemes: -> - pack for pack in @packageManager.getLoadedPackages() when pack.isTheme() - activatePackages: -> @activateThemes() - # Get the enabled theme names from the config. + ### + Section: Managing Enabled Themes + ### + + # Public: Get the enabled theme names from the config. # # Returns an array of theme names in the order that they should be activated. getEnabledThemeNames: -> @@ -145,6 +157,147 @@ class ThemeManager # the first/top theme to override later themes in the stack. themeNames.reverse() + # Public: Set the list of enabled themes. + # + # * `enabledThemeNames` An {Array} of {String} theme names. + setEnabledThemes: (enabledThemeNames) -> + atom.config.set('core.themes', enabledThemeNames) + + ### + Section: Managing Stylesheets + ### + + # Public: Returns the {String} path to the user's stylesheet under ~/.atom + getUserStylesheetPath: -> + stylesheetPath = fs.resolve(path.join(@configDirPath, 'styles'), ['css', 'less']) + if fs.isFileSync(stylesheetPath) + stylesheetPath + else + path.join(@configDirPath, 'styles.less') + + # Public: Resolve and apply the stylesheet specified by the path. + # + # This supports both CSS and Less stylsheets. + # + # * `stylesheetPath` A {String} path to the stylesheet that can be an absolute + # path or a relative path that will be resolved against the load path. + # + # Returns the absolute path to the required stylesheet. + requireStylesheet: (stylesheetPath, type='bundled') -> + if fullPath = @resolveStylesheet(stylesheetPath) + content = @loadStylesheet(fullPath) + @applyStylesheet(fullPath, content, type) + else + throw new Error("Could not find a file at path '#{stylesheetPath}'") + + fullPath + + unwatchUserStylesheet: -> + @userStylesheetFile?.off() + @userStylesheetFile = null + @removeStylesheet(@userStylesheetPath) if @userStylesheetPath? + + loadUserStylesheet: -> + @unwatchUserStylesheet() + userStylesheetPath = @getUserStylesheetPath() + return unless fs.isFileSync(userStylesheetPath) + + @userStylesheetPath = userStylesheetPath + @userStylesheetFile = new File(userStylesheetPath) + @userStylesheetFile.on 'contents-changed moved removed', => + @loadUserStylesheet() + userStylesheetContents = @loadStylesheet(userStylesheetPath, true) + @applyStylesheet(userStylesheetPath, userStylesheetContents, 'userTheme') + + loadBaseStylesheets: -> + @requireStylesheet('bootstrap/less/bootstrap') + @reloadBaseStylesheets() + + reloadBaseStylesheets: -> + @requireStylesheet('../static/atom') + if nativeStylesheetPath = fs.resolveOnLoadPath(process.platform, ['css', 'less']) + @requireStylesheet(nativeStylesheetPath) + + stylesheetElementForId: (id) -> + document.head.querySelector("""style[id="#{id}"]""") + + resolveStylesheet: (stylesheetPath) -> + if path.extname(stylesheetPath).length > 0 + fs.resolveOnLoadPath(stylesheetPath) + else + fs.resolveOnLoadPath(stylesheetPath, ['css', 'less']) + + loadStylesheet: (stylesheetPath, importFallbackVariables) -> + if path.extname(stylesheetPath) is '.less' + @loadLessStylesheet(stylesheetPath, importFallbackVariables) + else + fs.readFileSync(stylesheetPath, 'utf8') + + loadLessStylesheet: (lessStylesheetPath, importFallbackVariables=false) -> + unless @lessCache? + LessCompileCache = require './less-compile-cache' + @lessCache = new LessCompileCache({@resourcePath, importPaths: @getImportPaths()}) + + try + if importFallbackVariables + baseVarImports = """ + @import "variables/ui-variables"; + @import "variables/syntax-variables"; + """ + less = fs.readFileSync(lessStylesheetPath, 'utf8') + @lessCache.cssForFile(lessStylesheetPath, [baseVarImports, less].join('\n')) + else + @lessCache.read(lessStylesheetPath) + catch error + console.error """ + Error compiling Less stylesheet: #{lessStylesheetPath} + Line number: #{error.line} + #{error.message} + """ + + removeStylesheet: (stylesheetPath) -> + fullPath = @resolveStylesheet(stylesheetPath) ? stylesheetPath + element = @stylesheetElementForId(@stringToId(fullPath)) + if element? + {sheet} = element + element.remove() + @emit 'stylesheet-removed', sheet + @emitter.emit 'did-remove-stylesheet', sheet + @emit 'stylesheets-changed' + @emitter.emit 'did-change-stylesheets' + + applyStylesheet: (path, text, type='bundled') -> + styleId = @stringToId(path) + styleElement = @stylesheetElementForId(styleId) + + if styleElement? + @emit 'stylesheet-removed', styleElement.sheet + @emitter.emit 'did-remove-stylesheet', styleElement.sheet + styleElement.textContent = text + else + styleElement = document.createElement('style') + styleElement.setAttribute('class', type) + styleElement.setAttribute('id', styleId) + styleElement.textContent = text + + elementToInsertBefore = _.last(document.head.querySelectorAll("style.#{type}"))?.nextElementSibling + if elementToInsertBefore? + document.head.insertBefore(styleElement, elementToInsertBefore) + else + document.head.appendChild(styleElement) + + @emit 'stylesheet-added', styleElement.sheet + @emitter.emit 'did-add-stylesheet', styleElement.sheet + @emit 'stylesheets-changed' + @emitter.emit 'did-change-stylesheets' + + ### + Section: Private + ### + + stringToId: (string) -> + string.replace(/\\/g, '/') + activateThemes: -> deferred = Q.defer() @@ -194,12 +347,6 @@ class ThemeManager refreshLessCache: -> @lessCache?.setImportPaths(@getImportPaths()) - # Public: Set the list of enabled themes. - # - # * `enabledThemeNames` An {Array} of {String} theme names. - setEnabledThemes: (enabledThemeNames) -> - atom.config.set('core.themes', enabledThemeNames) - getImportPaths: -> activeThemes = @getActiveThemes() if activeThemes.length > 0 @@ -212,133 +359,6 @@ class ThemeManager themePaths.filter (themePath) -> fs.isDirectorySync(themePath) - # Public: Returns the {String} path to the user's stylesheet under ~/.atom - getUserStylesheetPath: -> - stylesheetPath = fs.resolve(path.join(@configDirPath, 'styles'), ['css', 'less']) - if fs.isFileSync(stylesheetPath) - stylesheetPath - else - path.join(@configDirPath, 'styles.less') - - unwatchUserStylesheet: -> - @userStylesheetFile?.off() - @userStylesheetFile = null - @removeStylesheet(@userStylesheetPath) if @userStylesheetPath? - - loadUserStylesheet: -> - @unwatchUserStylesheet() - userStylesheetPath = @getUserStylesheetPath() - return unless fs.isFileSync(userStylesheetPath) - - @userStylesheetPath = userStylesheetPath - @userStylesheetFile = new File(userStylesheetPath) - @userStylesheetFile.on 'contents-changed moved removed', => - @loadUserStylesheet() - userStylesheetContents = @loadStylesheet(userStylesheetPath, true) - @applyStylesheet(userStylesheetPath, userStylesheetContents, 'userTheme') - - loadBaseStylesheets: -> - @requireStylesheet('bootstrap/less/bootstrap') - @reloadBaseStylesheets() - - reloadBaseStylesheets: -> - @requireStylesheet('../static/atom') - if nativeStylesheetPath = fs.resolveOnLoadPath(process.platform, ['css', 'less']) - @requireStylesheet(nativeStylesheetPath) - - stylesheetElementForId: (id) -> - document.head.querySelector("""style[id="#{id}"]""") - - resolveStylesheet: (stylesheetPath) -> - if path.extname(stylesheetPath).length > 0 - fs.resolveOnLoadPath(stylesheetPath) - else - fs.resolveOnLoadPath(stylesheetPath, ['css', 'less']) - - # Public: Resolve and apply the stylesheet specified by the path. - # - # This supports both CSS and Less stylsheets. - # - # * `stylesheetPath` A {String} path to the stylesheet that can be an absolute - # path or a relative path that will be resolved against the load path. - # - # Returns the absolute path to the required stylesheet. - requireStylesheet: (stylesheetPath, type='bundled') -> - if fullPath = @resolveStylesheet(stylesheetPath) - content = @loadStylesheet(fullPath) - @applyStylesheet(fullPath, content, type) - else - throw new Error("Could not find a file at path '#{stylesheetPath}'") - - fullPath - - loadStylesheet: (stylesheetPath, importFallbackVariables) -> - if path.extname(stylesheetPath) is '.less' - @loadLessStylesheet(stylesheetPath, importFallbackVariables) - else - fs.readFileSync(stylesheetPath, 'utf8') - - loadLessStylesheet: (lessStylesheetPath, importFallbackVariables=false) -> - unless @lessCache? - LessCompileCache = require './less-compile-cache' - @lessCache = new LessCompileCache({@resourcePath, importPaths: @getImportPaths()}) - - try - if importFallbackVariables - baseVarImports = """ - @import "variables/ui-variables"; - @import "variables/syntax-variables"; - """ - less = fs.readFileSync(lessStylesheetPath, 'utf8') - @lessCache.cssForFile(lessStylesheetPath, [baseVarImports, less].join('\n')) - else - @lessCache.read(lessStylesheetPath) - catch error - console.error """ - Error compiling Less stylesheet: #{lessStylesheetPath} - Line number: #{error.line} - #{error.message} - """ - - stringToId: (string) -> - string.replace(/\\/g, '/') - - removeStylesheet: (stylesheetPath) -> - fullPath = @resolveStylesheet(stylesheetPath) ? stylesheetPath - element = @stylesheetElementForId(@stringToId(fullPath)) - if element? - {sheet} = element - element.remove() - @emit 'stylesheet-removed', sheet - @emitter.emit 'did-remove-stylesheet', sheet - @emit 'stylesheets-changed' - @emitter.emit 'did-change-stylesheets' - - applyStylesheet: (path, text, type='bundled') -> - styleId = @stringToId(path) - styleElement = @stylesheetElementForId(styleId) - - if styleElement? - @emit 'stylesheet-removed', styleElement.sheet - @emitter.emit 'did-remove-stylesheet', styleElement.sheet - styleElement.textContent = text - else - styleElement = document.createElement('style') - styleElement.setAttribute('class', type) - styleElement.setAttribute('id', styleId) - styleElement.textContent = text - - elementToInsertBefore = _.last(document.head.querySelectorAll("style.#{type}"))?.nextElementSibling - if elementToInsertBefore? - document.head.insertBefore(styleElement, elementToInsertBefore) - else - document.head.appendChild(styleElement) - - @emit 'stylesheet-added', styleElement.sheet - @emitter.emit 'did-add-stylesheet', styleElement.sheet - @emit 'stylesheets-changed' - @emitter.emit 'did-change-stylesheets' - updateGlobalEditorStyle: (property, value) -> unless styleNode = @stylesheetElementForId('global-editor-styles') @applyStylesheet('global-editor-styles', '.editor {}') From 6f2b1a4b2117f9ba4b01636150179fa6dba22454 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 17 Sep 2014 15:57:13 -0700 Subject: [PATCH 123/211] Reorganize Selection into sections --- src/selection.coffee | 411 +++++++++++++++++++++++-------------------- 1 file changed, 217 insertions(+), 194 deletions(-) diff --git a/src/selection.coffee b/src/selection.coffee index c381d4994..a220a49b6 100644 --- a/src/selection.coffee +++ b/src/selection.coffee @@ -30,6 +30,9 @@ class Selection extends Model @emitter.emit 'did-destroy' @emitter.dispose() + destroy: -> + @marker.destroy() + ### Section: Event Subscription ### @@ -60,37 +63,11 @@ class Selection extends Model super + ### - Section: Methods + Section: Managing the selection range ### - destroy: -> - @marker.destroy() - - finalize: -> - @initialScreenRange = null unless @initialScreenRange?.isEqual(@getScreenRange()) - if @isEmpty() - @wordwise = false - @linewise = false - - clearAutoscroll: -> - @needsAutoscroll = null - - # Public: Determines if the selection contains anything. - isEmpty: -> - @getBufferRange().isEmpty() - - # Public: Determines if the ending position of a marker is greater than the - # starting position. - # - # This can happen when, for example, you highlight text "up" in a {TextBuffer}. - isReversed: -> - @marker.isReversed() - - # Public: Returns whether the selection is a single line or not. - isSingleScreenLine: -> - @getScreenRange().isSingleLine() - # Public: Returns the screen {Range} for the selection. getScreenRange: -> @marker.getScreenRange() @@ -148,55 +125,61 @@ class Selection extends Model getHeadBufferPosition: -> @marker.getHeadBufferPosition() - autoscroll: -> - @editor.scrollToScreenRange(@getScreenRange()) + ### + Section: Info about the selection + ### + + # Public: Determines if the selection contains anything. + isEmpty: -> + @getBufferRange().isEmpty() + + # Public: Determines if the ending position of a marker is greater than the + # starting position. + # + # This can happen when, for example, you highlight text "up" in a {TextBuffer}. + isReversed: -> + @marker.isReversed() + + # Public: Returns whether the selection is a single line or not. + isSingleScreenLine: -> + @getScreenRange().isSingleLine() # Public: Returns the text in the selection. getText: -> @editor.buffer.getTextInRange(@getBufferRange()) + # Public: Identifies if a selection intersects with a given buffer range. + # + # * `bufferRange` A {Range} to check against. + # + # Returns a {Boolean} + intersectsBufferRange: (bufferRange) -> + @getBufferRange().intersectsWith(bufferRange) + + intersectsScreenRowRange: (startRow, endRow) -> + @getScreenRange().intersectsRowRange(startRow, endRow) + + intersectsScreenRow: (screenRow) -> + @getScreenRange().intersectsRow(screenRow) + + # Public: Identifies if a selection intersects with another selection. + # + # * `otherSelection` A {Selection} to check against. + # + # Returns a {Boolean} + intersectsWith: (otherSelection, exclusive) -> + @getBufferRange().intersectsWith(otherSelection.getBufferRange(), exclusive) + + ### + Section: Modifying the selected range + ### + # Public: Clears the selection, moving the marker to the head. clear: -> @marker.setAttributes(goalBufferRange: null) @marker.clearTail() unless @retainSelection @finalize() - # Public: Modifies the selection to encompass the current word. - # - # Returns a {Range}. - selectWord: -> - options = {} - options.wordRegex = /[\t ]*/ if @cursor.isSurroundedByWhitespace() - if @cursor.isBetweenWordAndNonWord() - options.includeNonWordCharacters = false - - @setBufferRange(@cursor.getCurrentWordBufferRange(options)) - @wordwise = true - @initialScreenRange = @getScreenRange() - - # Public: Expands the newest selection to include the entire word on which - # the cursors rests. - expandOverWord: -> - @setBufferRange(@getBufferRange().union(@cursor.getCurrentWordBufferRange())) - - # Public: Selects an entire line in the buffer. - # - # * `row` The line {Number} to select (default: the row of the cursor). - selectLine: (row=@cursor.getBufferPosition().row) -> - range = @editor.bufferRangeForBufferRow(row, includeNewline: true) - @setBufferRange(@getBufferRange().union(range)) - @linewise = true - @wordwise = false - @initialScreenRange = @getScreenRange() - - # Public: Expands the newest selection to include the entire line on which - # the cursor currently rests. - # - # It also includes the newline character. - expandOverLine: -> - range = @getBufferRange().union(@cursor.getCurrentLineBufferRange(includeNewline: true)) - @setBufferRange(range) - # Public: Selects the text from the current cursor position to a given screen # position. # @@ -311,46 +294,45 @@ class Selection extends Model selectToBeginningOfPreviousParagraph: -> @modifySelection => @cursor.moveToBeginningOfPreviousParagraph() - # Public: Moves the selection down one row. - addSelectionBelow: -> - range = (@getGoalBufferRange() ? @getBufferRange()).copy() - nextRow = range.end.row + 1 + # Public: Modifies the selection to encompass the current word. + # + # Returns a {Range}. + selectWord: -> + options = {} + options.wordRegex = /[\t ]*/ if @cursor.isSurroundedByWhitespace() + if @cursor.isBetweenWordAndNonWord() + options.includeNonWordCharacters = false - for row in [nextRow..@editor.getLastBufferRow()] - range.start.row = row - range.end.row = row - clippedRange = @editor.clipBufferRange(range) + @setBufferRange(@cursor.getCurrentWordBufferRange(options)) + @wordwise = true + @initialScreenRange = @getScreenRange() - if range.isEmpty() - continue if range.end.column > 0 and clippedRange.end.column is 0 - else - continue if clippedRange.isEmpty() + # Public: Expands the newest selection to include the entire word on which + # the cursors rests. + expandOverWord: -> + @setBufferRange(@getBufferRange().union(@cursor.getCurrentWordBufferRange())) - @editor.addSelectionForBufferRange(range, goalBufferRange: range) - break + # Public: Selects an entire line in the buffer. + # + # * `row` The line {Number} to select (default: the row of the cursor). + selectLine: (row=@cursor.getBufferPosition().row) -> + range = @editor.bufferRangeForBufferRow(row, includeNewline: true) + @setBufferRange(@getBufferRange().union(range)) + @linewise = true + @wordwise = false + @initialScreenRange = @getScreenRange() - # FIXME: I have no idea what this does. - getGoalBufferRange: -> - if goalBufferRange = @marker.getAttributes().goalBufferRange - Range.fromObject(goalBufferRange) + # Public: Expands the newest selection to include the entire line on which + # the cursor currently rests. + # + # It also includes the newline character. + expandOverLine: -> + range = @getBufferRange().union(@cursor.getCurrentLineBufferRange(includeNewline: true)) + @setBufferRange(range) - # Public: Moves the selection up one row. - addSelectionAbove: -> - range = (@getGoalBufferRange() ? @getBufferRange()).copy() - previousRow = range.end.row - 1 - - for row in [previousRow..0] - range.start.row = row - range.end.row = row - clippedRange = @editor.clipBufferRange(range) - - if range.isEmpty() - continue if range.end.column > 0 and clippedRange.end.column is 0 - else - continue if clippedRange.isEmpty() - - @editor.addSelectionForBufferRange(range, goalBufferRange: range) - break + ### + Section: Modifying the selected text + ### # Public: Replaces text at the current selection. # @@ -391,71 +373,6 @@ class Selection extends Model newBufferRange - # Public: Indents the given text to the suggested level based on the grammar. - # - # * `text` The {String} to indent within the selection. - # * `indentBasis` The beginning indent level. - normalizeIndents: (text, indentBasis) -> - textPrecedingCursor = @cursor.getCurrentBufferLine()[0...@cursor.getBufferColumn()] - isCursorInsideExistingLine = /\S/.test(textPrecedingCursor) - - lines = text.split('\n') - firstLineIndentLevel = @editor.indentLevelForLine(lines[0]) - if isCursorInsideExistingLine - minimumIndentLevel = @editor.indentationForBufferRow(@cursor.getBufferRow()) - else - minimumIndentLevel = @cursor.getIndentLevel() - - normalizedLines = [] - for line, i in lines - if i == 0 - indentLevel = 0 - else if line == '' # remove all indentation from empty lines - indentLevel = 0 - else - lineIndentLevel = @editor.indentLevelForLine(lines[i]) - indentLevel = minimumIndentLevel + (lineIndentLevel - indentBasis) - - normalizedLines.push(@setIndentationForLine(line, indentLevel)) - - normalizedLines.join('\n') - - # Indent the current line(s). - # - # If the selection is empty, indents the current line if the cursor precedes - # non-whitespace characters, and otherwise inserts a tab. If the selection is - # non empty, calls {::indentSelectedRows}. - # - # * `options` (optional) {Object} with the keys: - # * `autoIndent` If `true`, the line is indented to an automatically-inferred - # level. Otherwise, {Editor::getTabText} is inserted. - indent: ({ autoIndent }={}) -> - { row, column } = @cursor.getBufferPosition() - - if @isEmpty() - @cursor.skipLeadingWhitespace() - desiredIndent = @editor.suggestedIndentForBufferRow(row) - delta = desiredIndent - @cursor.getIndentLevel() - - if autoIndent and delta > 0 - @insertText(@editor.buildIndentString(delta)) - else - @insertText(@editor.buildIndentString(1, @cursor.getBufferColumn())) - else - @indentSelectedRows() - - # Public: If the selection spans multiple rows, indent all of them. - indentSelectedRows: -> - [start, end] = @getBufferRowRange() - for row in [start..end] - @editor.buffer.insert([row, 0], @editor.getTabText()) unless @editor.buffer.lineLengthForRow(row) == 0 - - # Public: ? - setIndentationForLine: (line, indentLevel) -> - desiredIndentLevel = Math.max(0, indentLevel) - desiredIndentString = @editor.buildIndentString(desiredIndentLevel) - line.replace(/^[\t ]*/, desiredIndentString) - # Public: Removes the first character before the selection if the selection # is empty otherwise it deletes the selection. backspace: -> @@ -632,41 +549,109 @@ class Selection extends Model @editor.createFold(range.start.row, range.end.row) @cursor.setBufferPosition([range.end.row + 1, 0]) - modifySelection: (fn) -> - @retainSelection = true - @plantTail() - fn() - @retainSelection = false + # Public: Indents the given text to the suggested level based on the grammar. + # + # * `text` The {String} to indent within the selection. + # * `indentBasis` The beginning indent level. + normalizeIndents: (text, indentBasis) -> + textPrecedingCursor = @cursor.getCurrentBufferLine()[0...@cursor.getBufferColumn()] + isCursorInsideExistingLine = /\S/.test(textPrecedingCursor) - # Sets the marker's tail to the same position as the marker's head. - # - # This only works if there isn't already a tail position. - # - # Returns a {Point} representing the new tail position. - plantTail: -> - @marker.plantTail() + lines = text.split('\n') + firstLineIndentLevel = @editor.indentLevelForLine(lines[0]) + if isCursorInsideExistingLine + minimumIndentLevel = @editor.indentationForBufferRow(@cursor.getBufferRow()) + else + minimumIndentLevel = @cursor.getIndentLevel() - # Public: Identifies if a selection intersects with a given buffer range. - # - # * `bufferRange` A {Range} to check against. - # - # Returns a {Boolean} - intersectsBufferRange: (bufferRange) -> - @getBufferRange().intersectsWith(bufferRange) + normalizedLines = [] + for line, i in lines + if i == 0 + indentLevel = 0 + else if line == '' # remove all indentation from empty lines + indentLevel = 0 + else + lineIndentLevel = @editor.indentLevelForLine(lines[i]) + indentLevel = minimumIndentLevel + (lineIndentLevel - indentBasis) - intersectsScreenRowRange: (startRow, endRow) -> - @getScreenRange().intersectsRowRange(startRow, endRow) + normalizedLines.push(@setIndentationForLine(line, indentLevel)) - intersectsScreenRow: (screenRow) -> - @getScreenRange().intersectsRow(screenRow) + normalizedLines.join('\n') - # Public: Identifies if a selection intersects with another selection. + # Indent the current line(s). # - # * `otherSelection` A {Selection} to check against. + # If the selection is empty, indents the current line if the cursor precedes + # non-whitespace characters, and otherwise inserts a tab. If the selection is + # non empty, calls {::indentSelectedRows}. # - # Returns a {Boolean} - intersectsWith: (otherSelection, exclusive) -> - @getBufferRange().intersectsWith(otherSelection.getBufferRange(), exclusive) + # * `options` (optional) {Object} with the keys: + # * `autoIndent` If `true`, the line is indented to an automatically-inferred + # level. Otherwise, {Editor::getTabText} is inserted. + indent: ({ autoIndent }={}) -> + { row, column } = @cursor.getBufferPosition() + + if @isEmpty() + @cursor.skipLeadingWhitespace() + desiredIndent = @editor.suggestedIndentForBufferRow(row) + delta = desiredIndent - @cursor.getIndentLevel() + + if autoIndent and delta > 0 + @insertText(@editor.buildIndentString(delta)) + else + @insertText(@editor.buildIndentString(1, @cursor.getBufferColumn())) + else + @indentSelectedRows() + + # Public: If the selection spans multiple rows, indent all of them. + indentSelectedRows: -> + [start, end] = @getBufferRowRange() + for row in [start..end] + @editor.buffer.insert([row, 0], @editor.getTabText()) unless @editor.buffer.lineLengthForRow(row) == 0 + + setIndentationForLine: (line, indentLevel) -> + desiredIndentLevel = Math.max(0, indentLevel) + desiredIndentString = @editor.buildIndentString(desiredIndentLevel) + line.replace(/^[\t ]*/, desiredIndentString) + + ### + Section: Managing multiple selections + ### + + # Public: Moves the selection down one row. + addSelectionBelow: -> + range = (@getGoalBufferRange() ? @getBufferRange()).copy() + nextRow = range.end.row + 1 + + for row in [nextRow..@editor.getLastBufferRow()] + range.start.row = row + range.end.row = row + clippedRange = @editor.clipBufferRange(range) + + if range.isEmpty() + continue if range.end.column > 0 and clippedRange.end.column is 0 + else + continue if clippedRange.isEmpty() + + @editor.addSelectionForBufferRange(range, goalBufferRange: range) + break + + # Public: Moves the selection up one row. + addSelectionAbove: -> + range = (@getGoalBufferRange() ? @getBufferRange()).copy() + previousRow = range.end.row - 1 + + for row in [previousRow..0] + range.start.row = row + range.end.row = row + clippedRange = @editor.clipBufferRange(range) + + if range.isEmpty() + continue if range.end.column > 0 and clippedRange.end.column is 0 + else + continue if clippedRange.isEmpty() + + @editor.addSelectionForBufferRange(range, goalBufferRange: range) + break # Public: Combines the given selection into this selection and then destroys # the given selection. @@ -683,6 +668,10 @@ class Selection extends Model @setBufferRange(@getBufferRange().union(otherSelection.getBufferRange()), options) otherSelection.destroy() + ### + Section: Comparing to other selections + ### + # Public: Compare this selection's buffer range to another selection's buffer # range. # @@ -692,7 +681,41 @@ class Selection extends Model compare: (otherSelection) -> @getBufferRange().compare(otherSelection.getBufferRange()) + ### + Section: Private Utilities + ### + screenRangeChanged: -> @emit 'screen-range-changed', @getScreenRange() @emitter.emit 'did-change-range' @editor.selectionRangeChanged(this) + + finalize: -> + @initialScreenRange = null unless @initialScreenRange?.isEqual(@getScreenRange()) + if @isEmpty() + @wordwise = false + @linewise = false + + autoscroll: -> + @editor.scrollToScreenRange(@getScreenRange()) + + clearAutoscroll: -> + @needsAutoscroll = null + + modifySelection: (fn) -> + @retainSelection = true + @plantTail() + fn() + @retainSelection = false + + # Sets the marker's tail to the same position as the marker's head. + # + # This only works if there isn't already a tail position. + # + # Returns a {Point} representing the new tail position. + plantTail: -> + @marker.plantTail() + + getGoalBufferRange: -> + if goalBufferRange = @marker.getAttributes().goalBufferRange + Range.fromObject(goalBufferRange) From fad83fff1c93c1b044ef7559450c628270a24e1a Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 17 Sep 2014 15:57:24 -0700 Subject: [PATCH 124/211] Change name of section in cursor --- src/cursor.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cursor.coffee b/src/cursor.coffee index d53318fc9..1848182db 100644 --- a/src/cursor.coffee +++ b/src/cursor.coffee @@ -166,7 +166,7 @@ class Cursor extends Model @getBufferPosition().isEqual(@getCurrentLineBufferRange().end) ### - Section: Position-dependent cursor properties + Section: Info about the cursor position ### # Public: Identifies if the cursor is surrounded by whitespace. From 736342b5276bf6e50c9f873ffd16c56e85e8a2c7 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 17 Sep 2014 15:59:22 -0700 Subject: [PATCH 125/211] Fix the title duplication --- src/editor.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor.coffee b/src/editor.coffee index 5dfab85d0..3e0b5bd37 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -492,7 +492,7 @@ class Editor extends Model Section: File Details ### - # Essential: Get the title the editor's title for display in other parts of the + # Essential: Get the editor's title for display in other parts of the # UI such as the tabs. # # If the editor's buffer is saved, its title is the file name. If it is From a85a5e84952e863b9580cb0eb427310021cbe308 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 17 Sep 2014 16:20:08 -0700 Subject: [PATCH 126/211] Add public back for the deserializers --- src/atom.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atom.coffee b/src/atom.coffee index 372fbeea5..e47c65f3a 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -111,7 +111,7 @@ class Atom extends Model # Public: A {ContextMenuManager} instance contextMenu: null - # A {DeserializerManager} instance + # Public: A {DeserializerManager} instance deserializers: null # Public: A {KeymapManager} instance From 7bf60a09dd8dfce2c3befd7eacd622fbc2ec2b6e Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 17 Sep 2014 17:02:41 -0700 Subject: [PATCH 127/211] Make ScrollView Extended --- src/scroll-view.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scroll-view.coffee b/src/scroll-view.coffee index 10e760e1b..c6d470579 100644 --- a/src/scroll-view.coffee +++ b/src/scroll-view.coffee @@ -1,6 +1,6 @@ {View} = require './space-pen-extensions' -# Public: Represents a view that scrolls. +# Extended: Represents a view that scrolls. # # Handles several core events to update scroll position: # From 14b3bd5b399a826cacaee74722b8eb180cc07393 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 17:00:55 -0700 Subject: [PATCH 128/211] Upgrade to language-coffee-script@0.33 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2ecbf0dcd..40cfc5de5 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "whitespace": "0.25.0", "wrap-guide": "0.22.0", "language-c": "0.28.0", - "language-coffee-script": "0.32.0", + "language-coffee-script": "0.33.0", "language-css": "0.17.0", "language-gfm": "0.50.0", "language-git": "0.9.0", From afd6f6144ca80efa7d64fd38723238e664e03c81 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 18:16:09 -0700 Subject: [PATCH 129/211] Upgrade to symbols-view@0.64 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 40cfc5de5..0ea092e42 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "spell-check": "0.42.0", "status-bar": "0.45.0", "styleguide": "0.30.0", - "symbols-view": "0.63.0", + "symbols-view": "0.64.0", "tabs": "0.52.0", "timecop": "0.22.0", "tree-view": "0.125.0", From 5d22f96f7b22fb2a9755f6b40df81d725408f9fd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 08:52:19 -0700 Subject: [PATCH 130/211] Upgrade to language-coffee-script@0.34 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0ea092e42..a06844d2c 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "whitespace": "0.25.0", "wrap-guide": "0.22.0", "language-c": "0.28.0", - "language-coffee-script": "0.33.0", + "language-coffee-script": "0.34.0", "language-css": "0.17.0", "language-gfm": "0.50.0", "language-git": "0.9.0", From ba49f5d0b8f9e0b4bafd19df9eddf8e005a427fd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 09:22:56 -0700 Subject: [PATCH 131/211] Upgrade to symbols-view@0.65 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a06844d2c..8bb9554b6 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "spell-check": "0.42.0", "status-bar": "0.45.0", "styleguide": "0.30.0", - "symbols-view": "0.64.0", + "symbols-view": "0.65.0", "tabs": "0.52.0", "timecop": "0.22.0", "tree-view": "0.125.0", From ca9d05f6fa89f55e3cfe272340676ed83e89e5c1 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 09:26:46 -0700 Subject: [PATCH 132/211] Remove notification stylesheet These were currently undocumented in the styleguide, had hard-coded colors, weren't being styled by the default light/dark UI themes, and were unused. They were also causing conflicts with the notification token scope that the Objective-C grammar uses. --- static/atom.less | 1 - static/notification.less | 56 ---------------------------------------- 2 files changed, 57 deletions(-) delete mode 100644 static/notification.less diff --git a/static/atom.less b/static/atom.less index d7567f34e..02302c2ab 100644 --- a/static/atom.less +++ b/static/atom.less @@ -20,7 +20,6 @@ @import "overlay"; @import "lists"; @import "popover-list"; -@import "notification"; @import "messages"; @import "markdown"; @import "editor"; diff --git a/static/notification.less b/static/notification.less deleted file mode 100644 index 1f676a982..000000000 --- a/static/notification.less +++ /dev/null @@ -1,56 +0,0 @@ -.notification { - position: absolute; - top: 40px; - left: 50%; - margin-left: -5%; - z-index: 9999; - border: 2px solid rgba(0, 0, 0, 0.2); - border-radius: 5px; - box-shadow: - inset 1px 1px 0 rgba(255, 255, 255, 0.05), - 0 0 5px rgba(0, 0, 0, 0.5); - background: -webkit-linear-gradient( - rgba(20, 20, 20, 0.5), - rgba(0, 0, 0, 0.5)); - color: #eee; - width: 300px; -} - -.notification .content { - padding: 10px; - margin-left: 42px; - box-sizing: border-box; -} - -.notification .content:after { - content: "."; - display: block; - clear: both; - visibility: hidden; -} - -.notification .title { - font-size: 12px; - font-weight: bold; - margin-bottom: 3px; -} - -.notification .message { - font-size: 12px; - color: #777; -} - -.notification .icon { - display: inline-block; - font-family: 'Octicons Regular'; - font-size: 32px; - width: 32px; - height: 32px; - margin-right: 5px; - -webkit-font-smoothing: antialiased; -} - -/* TODO: Full Octicon Support */ -.icon-gist { - content: "\f20e"; -} From e300677da00ca0b7c03071182247cd4d3157dad1 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 10:03:33 -0700 Subject: [PATCH 133/211] Upgrade to less-cache@0.15 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8bb9554b6..3b9ff4326 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "grim": "0.12.0", "guid": "0.0.10", "jasmine-tagged": "^1.1.2", - "less-cache": "0.14.0", + "less-cache": "0.15.0", "mixto": "^1", "mkdirp": "0.3.5", "nslog": "^1.0.1", From 616dae2f22ef477703639f7b5961b7d9f2a310aa Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 10:33:08 -0700 Subject: [PATCH 134/211] Upgrade to tree-view@0.126 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3b9ff4326..11c4d9505 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "symbols-view": "0.65.0", "tabs": "0.52.0", "timecop": "0.22.0", - "tree-view": "0.125.0", + "tree-view": "0.126.0", "update-package-dependencies": "0.6.0", "welcome": "0.18.0", "whitespace": "0.25.0", From 3fb22f123a9d64187105160664c6e4ea083eb040 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 11:03:11 -0700 Subject: [PATCH 135/211] Don't throw errors when folding comments Previously trying to fold single line comments at the start/end of files would throw errors since the +1 and -1 used in the index ranges would cause the loop to advance past the last row or before the first row. --- spec/language-mode-spec.coffee | 22 ++++++++++++++++++++++ src/language-mode.coffee | 21 +++++++++++++-------- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/spec/language-mode-spec.coffee b/spec/language-mode-spec.coffee index fd3a4a478..f6f0ebdd7 100644 --- a/spec/language-mode-spec.coffee +++ b/spec/language-mode-spec.coffee @@ -70,6 +70,28 @@ describe "LanguageMode", -> expect(languageMode.rowRangeForCodeFoldAtBufferRow(2)).toBeNull() expect(languageMode.rowRangeForCodeFoldAtBufferRow(4)).toEqual [4, 7] + describe ".rowRangeForCommentAtBufferRow(bufferRow)", -> + it "returns the start/end rows of the foldable comment starting at the given row", -> + buffer.setText("//this is a multi line comment\n//another line") + expect(languageMode.rowRangeForCommentAtBufferRow(0)).toEqual [0, 1] + expect(languageMode.rowRangeForCommentAtBufferRow(1)).toEqual [0, 1] + + buffer.setText("//this is a multi line comment\n//another line\n//and one more") + expect(languageMode.rowRangeForCommentAtBufferRow(0)).toEqual [0, 2] + expect(languageMode.rowRangeForCommentAtBufferRow(1)).toEqual [0, 2] + + buffer.setText("//this is a multi line comment\n\n//with an empty line") + expect(languageMode.rowRangeForCommentAtBufferRow(0)).toBeUndefined() + expect(languageMode.rowRangeForCommentAtBufferRow(1)).toBeUndefined() + expect(languageMode.rowRangeForCommentAtBufferRow(2)).toBeUndefined() + + buffer.setText("//this is a single line comment\n") + expect(languageMode.rowRangeForCommentAtBufferRow(0)).toBeUndefined() + expect(languageMode.rowRangeForCommentAtBufferRow(1)).toBeUndefined() + + buffer.setText("//this is a single line comment") + expect(languageMode.rowRangeForCommentAtBufferRow(0)).toBeUndefined() + describe "suggestedIndentForBufferRow", -> it "returns the suggested indentation based on auto-indent/outdent rules", -> expect(languageMode.suggestedIndentForBufferRow(0)).toBe 0 diff --git a/src/language-mode.coffee b/src/language-mode.coffee index de1a5b928..6ce4b7b3a 100644 --- a/src/language-mode.coffee +++ b/src/language-mode.coffee @@ -148,15 +148,20 @@ class LanguageMode return unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment() startRow = bufferRow - for currentRow in [bufferRow-1..0] - break if @buffer.isRowBlank(currentRow) - break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment() - startRow = currentRow endRow = bufferRow - for currentRow in [bufferRow+1..@buffer.getLastRow()] - break if @buffer.isRowBlank(currentRow) - break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment() - endRow = currentRow + + if bufferRow > 0 + for currentRow in [bufferRow-1..0] + break if @buffer.isRowBlank(currentRow) + break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment() + startRow = currentRow + + if bufferRow < @buffer.getLastRow() + for currentRow in [bufferRow+1..@buffer.getLastRow()] + break if @buffer.isRowBlank(currentRow) + break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment() + endRow = currentRow + return [startRow, endRow] if startRow isnt endRow rowRangeForCodeFoldAtBufferRow: (bufferRow) -> From c37e4649b55495bbbcc4e12cbc19f7cc557ad5b6 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 11:30:16 -0700 Subject: [PATCH 136/211] Only call replace on string arguments Prevents errors being thrown when trying to call replace on non-string arguments. --- spec/language-mode-spec.coffee | 4 +++- src/buffered-process.coffee | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/language-mode-spec.coffee b/spec/language-mode-spec.coffee index f6f0ebdd7..0c0fba9b0 100644 --- a/spec/language-mode-spec.coffee +++ b/spec/language-mode-spec.coffee @@ -71,7 +71,7 @@ describe "LanguageMode", -> expect(languageMode.rowRangeForCodeFoldAtBufferRow(4)).toEqual [4, 7] describe ".rowRangeForCommentAtBufferRow(bufferRow)", -> - it "returns the start/end rows of the foldable comment starting at the given row", -> + fit "returns the start/end rows of the foldable comment starting at the given row", -> buffer.setText("//this is a multi line comment\n//another line") expect(languageMode.rowRangeForCommentAtBufferRow(0)).toEqual [0, 1] expect(languageMode.rowRangeForCommentAtBufferRow(1)).toEqual [0, 1] @@ -86,10 +86,12 @@ describe "LanguageMode", -> expect(languageMode.rowRangeForCommentAtBufferRow(2)).toBeUndefined() buffer.setText("//this is a single line comment\n") + console.log buffer.getLastRow() expect(languageMode.rowRangeForCommentAtBufferRow(0)).toBeUndefined() expect(languageMode.rowRangeForCommentAtBufferRow(1)).toBeUndefined() buffer.setText("//this is a single line comment") + console.log languageMode.isLineCommentedAtBufferRow(0) expect(languageMode.rowRangeForCommentAtBufferRow(0)).toBeUndefined() describe "suggestedIndentForBufferRow", -> diff --git a/src/buffered-process.coffee b/src/buffered-process.coffee index 0a7751e57..9bb8cc4ae 100644 --- a/src/buffered-process.coffee +++ b/src/buffered-process.coffee @@ -49,8 +49,10 @@ class BufferedProcess # Don't wrap /root,C:\folder style arguments to explorer calls in # quotes since they will not be interpreted correctly if they are arg - else + else if typeof arg is 'string' "\"#{arg.replace(/"/g, '\\"')}\"" + else + arg else cmdArgs = [] if /\s/.test(command) From 44860ba5720498619c47de852a4a9ab619fd50f0 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 11:36:41 -0700 Subject: [PATCH 137/211] Filter null/undefined arguments on Windows These won't be able to be properly quoted --- src/buffered-process.coffee | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/buffered-process.coffee b/src/buffered-process.coffee index 9bb8cc4ae..d373a0395 100644 --- a/src/buffered-process.coffee +++ b/src/buffered-process.coffee @@ -44,15 +44,14 @@ class BufferedProcess if process.platform is "win32" # Quote all arguments and escapes inner quotes if args? + cmdArgs = args.filter (arg) -> arg? cmdArgs = args.map (arg) -> if command in ['explorer.exe', 'explorer'] and /^\/[a-zA-Z]+,.*$/.test(arg) # Don't wrap /root,C:\folder style arguments to explorer calls in # quotes since they will not be interpreted correctly if they are arg - else if typeof arg is 'string' - "\"#{arg.replace(/"/g, '\\"')}\"" else - arg + "\"#{arg.toString().replace(/"/g, '\\"')}\"" else cmdArgs = [] if /\s/.test(command) From 31dd9bed6ae39b2f711b52a3abe359450770253f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 11:37:37 -0700 Subject: [PATCH 138/211] Map over cmdArgs when quoting --- src/buffered-process.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buffered-process.coffee b/src/buffered-process.coffee index d373a0395..211efdaf7 100644 --- a/src/buffered-process.coffee +++ b/src/buffered-process.coffee @@ -45,7 +45,7 @@ class BufferedProcess # Quote all arguments and escapes inner quotes if args? cmdArgs = args.filter (arg) -> arg? - cmdArgs = args.map (arg) -> + cmdArgs = cmdArgs.map (arg) -> if command in ['explorer.exe', 'explorer'] and /^\/[a-zA-Z]+,.*$/.test(arg) # Don't wrap /root,C:\folder style arguments to explorer calls in # quotes since they will not be interpreted correctly if they are From 4b4dc7224eb101dd39cd09912789ed525b021155 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 15:54:28 -0700 Subject: [PATCH 139/211] Deprecate pane:removed event --- src/workspace-view.coffee | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index a46b46434..a4e5bc1b8 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -167,6 +167,17 @@ class WorkspaceView extends View @command 'core:save', => @saveActivePaneItem() @command 'core:save-as', => @saveActivePaneItemAs() + @setupViewEventDeprecations() + + setupViewEventDeprecations: -> + originalOn = @on + + @on = (eventName) => + switch eventName + when 'pane:removed' + deprecate("Use Pane::onDidDestroy instead") + originalOn.apply(this, arguments) + ### Section: Accessing the Workspace Model ### From c5f593cf083df9a4bdeaa599838795964b166355 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 15:55:41 -0700 Subject: [PATCH 140/211] deprecatedViewEvents -> setupViewEventDeprecations --- src/workspace-view.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index a4e5bc1b8..72fdfd9d9 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -167,9 +167,9 @@ class WorkspaceView extends View @command 'core:save', => @saveActivePaneItem() @command 'core:save-as', => @saveActivePaneItemAs() - @setupViewEventDeprecations() + @deprecatedViewEvents() - setupViewEventDeprecations: -> + deprecatedViewEvents: -> originalOn = @on @on = (eventName) => From ee701f3b8b0ae364164c8a59eb79da563d50ac66 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 15:57:16 -0700 Subject: [PATCH 141/211] Deprecate cursor:moved --- src/workspace-view.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 72fdfd9d9..d8ef8029c 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -175,7 +175,9 @@ class WorkspaceView extends View @on = (eventName) => switch eventName when 'pane:removed' - deprecate("Use Pane::onDidDestroy instead") + deprecate('Use Pane::onDidDestroy instead') + when 'cursor:moved' + deprecate('Use Editor::onDidChangeCursorPosition instead') originalOn.apply(this, arguments) ### From ce887fe877504273544ba77fbbfb09fcb37941b7 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 15:58:03 -0700 Subject: [PATCH 142/211] Deprecate selection:changed --- src/workspace-view.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index d8ef8029c..b37dd184f 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -178,6 +178,8 @@ class WorkspaceView extends View deprecate('Use Pane::onDidDestroy instead') when 'cursor:moved' deprecate('Use Editor::onDidChangeCursorPosition instead') + when 'selection:changed' + deprecate('Use Editor::onDidChangeSelectionRange instead') originalOn.apply(this, arguments) ### From 7344ba644e7ff0b758a4b4aedb38ec62facb8bbc Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 15:59:07 -0700 Subject: [PATCH 143/211] Deprecate pane-container:active-pane-item-changed --- src/workspace-view.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index b37dd184f..6484c0b05 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -176,6 +176,8 @@ class WorkspaceView extends View switch eventName when 'pane:removed' deprecate('Use Pane::onDidDestroy instead') + when 'pane-container:active-pane-item-changed' + deprecate('Use Workspace::onDidChangeActivePaneItem instead') when 'cursor:moved' deprecate('Use Editor::onDidChangeCursorPosition instead') when 'selection:changed' From 4521eeaeb1c7875734007b2ed899bdf7addc3ebf Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 16:01:17 -0700 Subject: [PATCH 144/211] Deprecate pane:active-item-changed --- src/workspace-view.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 6484c0b05..b226ada0e 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -174,6 +174,8 @@ class WorkspaceView extends View @on = (eventName) => switch eventName + when 'pane:active-item-changed' + deprecate('Use Pane::onDidChangeActiveItem instead') when 'pane:removed' deprecate('Use Pane::onDidDestroy instead') when 'pane-container:active-pane-item-changed' From fdddccf0944a75a5c57fed187a8d232d1401665c Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 16:01:41 -0700 Subject: [PATCH 145/211] :lipstick: --- src/workspace-view.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index b226ada0e..98e9051c7 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -174,14 +174,14 @@ class WorkspaceView extends View @on = (eventName) => switch eventName + when 'cursor:moved' + deprecate('Use Editor::onDidChangeCursorPosition instead') when 'pane:active-item-changed' deprecate('Use Pane::onDidChangeActiveItem instead') when 'pane:removed' deprecate('Use Pane::onDidDestroy instead') when 'pane-container:active-pane-item-changed' deprecate('Use Workspace::onDidChangeActivePaneItem instead') - when 'cursor:moved' - deprecate('Use Editor::onDidChangeCursorPosition instead') when 'selection:changed' deprecate('Use Editor::onDidChangeSelectionRange instead') originalOn.apply(this, arguments) From 5148deded15cb5f207ad42c58e12a0379a306db8 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 16:03:30 -0700 Subject: [PATCH 146/211] Deprecate pane:became-active --- src/workspace-view.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 98e9051c7..556d37fde 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -178,6 +178,8 @@ class WorkspaceView extends View deprecate('Use Editor::onDidChangeCursorPosition instead') when 'pane:active-item-changed' deprecate('Use Pane::onDidChangeActiveItem instead') + when 'pane:became-active' + deprecate('Use Pane::onDidActivate instead') when 'pane:removed' deprecate('Use Pane::onDidDestroy instead') when 'pane-container:active-pane-item-changed' From 33fdb0b518b646cbac4049593336c7c665986703 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 16:04:17 -0700 Subject: [PATCH 147/211] Deprecate pane:became-inactive --- src/workspace-view.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 556d37fde..979c79bff 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -180,6 +180,8 @@ class WorkspaceView extends View deprecate('Use Pane::onDidChangeActiveItem instead') when 'pane:became-active' deprecate('Use Pane::onDidActivate instead') + when 'pane:became-inactive' + depcreate('Use Pane::onDidChangeActive instead') when 'pane:removed' deprecate('Use Pane::onDidDestroy instead') when 'pane-container:active-pane-item-changed' From 099953c58b215578736a7340d78149fa034d8d90 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 16:05:49 -0700 Subject: [PATCH 148/211] Deprecate uri-opened --- src/workspace-view.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 979c79bff..574d21ea7 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -188,6 +188,8 @@ class WorkspaceView extends View deprecate('Use Workspace::onDidChangeActivePaneItem instead') when 'selection:changed' deprecate('Use Editor::onDidChangeSelectionRange instead') + when 'uri-opened' + deprecate('Use Workspace::onDidOpen instead') originalOn.apply(this, arguments) ### From c2c0962e3bd98bf49e89097027bb912e0937df14 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 16:07:44 -0700 Subject: [PATCH 149/211] Deprecate pane:item-added --- src/workspace-view.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 574d21ea7..04a426d1c 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -181,7 +181,9 @@ class WorkspaceView extends View when 'pane:became-active' deprecate('Use Pane::onDidActivate instead') when 'pane:became-inactive' - depcreate('Use Pane::onDidChangeActive instead') + deprecate('Use Pane::onDidChangeActive instead') + when 'pane:item-added' + deprecate('Use Pane::onDidAddItem instead') when 'pane:removed' deprecate('Use Pane::onDidDestroy instead') when 'pane-container:active-pane-item-changed' From 1850197f5509580c12854d88c6c9af9be5c97fac Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 16:20:31 -0700 Subject: [PATCH 150/211] Deprecate pane:item-removed --- src/workspace-view.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 04a426d1c..3a46e6410 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -184,6 +184,8 @@ class WorkspaceView extends View deprecate('Use Pane::onDidChangeActive instead') when 'pane:item-added' deprecate('Use Pane::onDidAddItem instead') + when 'pane:item-removed' + deprecate('Use Pane::onDidRemoveItem instead') when 'pane:removed' deprecate('Use Pane::onDidDestroy instead') when 'pane-container:active-pane-item-changed' From 291b989ff00c472b9e84b18c357aae5fe58435a6 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 16:21:19 -0700 Subject: [PATCH 151/211] Deprecate pane:item-moved --- src/workspace-view.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 3a46e6410..270deb17b 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -184,6 +184,8 @@ class WorkspaceView extends View deprecate('Use Pane::onDidChangeActive instead') when 'pane:item-added' deprecate('Use Pane::onDidAddItem instead') + when 'pane:item-moved' + deprecate('Use Pane::onDidMoveItem instead') when 'pane:item-removed' deprecate('Use Pane::onDidRemoveItem instead') when 'pane:removed' From 1b25ea8a8d1ca3e034ef77e0595d59b7990c7e8b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 16:33:05 -0700 Subject: [PATCH 152/211] Deprecate custom events on EditorView --- src/workspace-view.coffee | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 270deb17b..1f858d8a8 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -198,6 +198,16 @@ class WorkspaceView extends View deprecate('Use Workspace::onDidOpen instead') originalOn.apply(this, arguments) + EditorView = require './editor-view' + originalEditorOn = EditorView::on + EditorView::on = (eventName) -> + switch eventName + when 'cursor:moved' + deprecate('Use Editor::onDidChangeCursorPosition instead') + when 'selection:changed' + deprecate('Use Editor::onDidChangeSelectionRange instead') + originalEditorOn.apply(this, arguments) + ### Section: Accessing the Workspace Model ### From cf303a73b7c397472fdb1bf699248c1a12955fdf Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 16:34:38 -0700 Subject: [PATCH 153/211] Deprecate custom events on PaneView --- src/workspace-view.coffee | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 1f858d8a8..dfb435dc4 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -170,7 +170,7 @@ class WorkspaceView extends View @deprecatedViewEvents() deprecatedViewEvents: -> - originalOn = @on + originalWorkspaceViewOn = @on @on = (eventName) => switch eventName @@ -196,17 +196,40 @@ class WorkspaceView extends View deprecate('Use Editor::onDidChangeSelectionRange instead') when 'uri-opened' deprecate('Use Workspace::onDidOpen instead') - originalOn.apply(this, arguments) + originalWorkspaceViewOn.apply(this, arguments) EditorView = require './editor-view' - originalEditorOn = EditorView::on + originalEditorViewOn = EditorView::on EditorView::on = (eventName) -> switch eventName when 'cursor:moved' deprecate('Use Editor::onDidChangeCursorPosition instead') when 'selection:changed' deprecate('Use Editor::onDidChangeSelectionRange instead') - originalEditorOn.apply(this, arguments) + originalEditorViewOn.apply(this, arguments) + + originalPaneViewOn = PaneView::on + PaneView::on = (eventName) -> + switch eventName + when 'cursor:moved' + deprecate('Use Editor::onDidChangeCursorPosition instead') + when 'pane:active-item-changed' + deprecate('Use Pane::onDidChangeActiveItem instead') + when 'pane:became-active' + deprecate('Use Pane::onDidActivate instead') + when 'pane:became-inactive' + deprecate('Use Pane::onDidChangeActive instead') + when 'pane:item-added' + deprecate('Use Pane::onDidAddItem instead') + when 'pane:item-moved' + deprecate('Use Pane::onDidMoveItem instead') + when 'pane:item-removed' + deprecate('Use Pane::onDidRemoveItem instead') + when 'pane:removed' + deprecate('Use Pane::onDidDestroy instead') + when 'selection:changed' + deprecate('Use Editor::onDidChangeSelectionRange instead') + originalPaneViewOn.apply(this, arguments) ### Section: Accessing the Workspace Model From b1994b28b357b9155daf50a8048d00bce0a85932 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 17:09:53 -0700 Subject: [PATCH 154/211] Deprecate pane:active-item-title-changed --- src/workspace-view.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index dfb435dc4..2d5bf70fa 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -178,6 +178,8 @@ class WorkspaceView extends View deprecate('Use Editor::onDidChangeCursorPosition instead') when 'pane:active-item-changed' deprecate('Use Pane::onDidChangeActiveItem instead') + when 'pane:active-item-title-changed' + deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeTitle on the active item instead') when 'pane:became-active' deprecate('Use Pane::onDidActivate instead') when 'pane:became-inactive' From 088d4f439f4af781201b6b6f4e81ec83aefd2bc2 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 17:12:30 -0700 Subject: [PATCH 155/211] Deprecate pane:active-item-title-changed on PaneView --- src/workspace-view.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 2d5bf70fa..609795bee 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -217,6 +217,8 @@ class WorkspaceView extends View deprecate('Use Editor::onDidChangeCursorPosition instead') when 'pane:active-item-changed' deprecate('Use Pane::onDidChangeActiveItem instead') + when 'pane:active-item-title-changed' + deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeTitle on the active item instead') when 'pane:became-active' deprecate('Use Pane::onDidActivate instead') when 'pane:became-inactive' From ddc88ec3ed9103a64649844172eca6f86d270311 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 17:14:32 -0700 Subject: [PATCH 156/211] Deprecate when pane:active-item-modified-status-changed --- src/workspace-view.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 609795bee..5b79bfe64 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -178,6 +178,8 @@ class WorkspaceView extends View deprecate('Use Editor::onDidChangeCursorPosition instead') when 'pane:active-item-changed' deprecate('Use Pane::onDidChangeActiveItem instead') + when 'pane:active-item-modified-status-changed' + deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeModified on the active item instead') when 'pane:active-item-title-changed' deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeTitle on the active item instead') when 'pane:became-active' @@ -217,6 +219,8 @@ class WorkspaceView extends View deprecate('Use Editor::onDidChangeCursorPosition instead') when 'pane:active-item-changed' deprecate('Use Pane::onDidChangeActiveItem instead') + when 'pane:active-item-modified-status-changed' + deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeModified on the active item instead') when 'pane:active-item-title-changed' deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeTitle on the active item instead') when 'pane:became-active' From a5b7764b38a8e8abe467fbc144d5697c5c90e44b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 17:25:14 -0700 Subject: [PATCH 157/211] Deprecate pane:attached --- src/workspace-view.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 5b79bfe64..6dc129821 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -182,6 +182,8 @@ class WorkspaceView extends View deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeModified on the active item instead') when 'pane:active-item-title-changed' deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeTitle on the active item instead') + when 'pane:attached' + deprecate('Use Workspace::onDidAddPane instead') when 'pane:became-active' deprecate('Use Pane::onDidActivate instead') when 'pane:became-inactive' @@ -223,6 +225,8 @@ class WorkspaceView extends View deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeModified on the active item instead') when 'pane:active-item-title-changed' deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeTitle on the active item instead') + when 'pane:attached' + deprecate('Use Workspace::onDidAddPane instead') when 'pane:became-active' deprecate('Use Pane::onDidActivate instead') when 'pane:became-inactive' From 31dd10934360e5f0e6754a7b025358c0c6bd4db6 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 17:36:06 -0700 Subject: [PATCH 158/211] Add Editor::onDidDestroy --- spec/editor-spec.coffee | 7 +++++++ src/editor.coffee | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/spec/editor-spec.coffee b/spec/editor-spec.coffee index 27a0dc099..a2381b29d 100644 --- a/spec/editor-spec.coffee +++ b/spec/editor-spec.coffee @@ -3229,6 +3229,13 @@ describe "Editor", -> editor.destroy() expect(buffer.getMarkerCount()).toBe 0 + it "notifies ::onDidDestroy observers when the editor is destroyed", -> + destroyObserverCalled = false + editor.onDidDestroy -> destroyObserverCalled = true + + editor.destroy() + expect(destroyObserverCalled).toBe true + describe ".joinLines()", -> describe "when no text is selected", -> describe "when the line below isn't empty", -> diff --git a/src/editor.coffee b/src/editor.coffee index 3e0b5bd37..f63dfc0b5 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -171,6 +171,7 @@ class Editor extends Model @buffer.release() @displayBuffer.destroy() @languageMode.destroy() + @emitter.emit 'did-destroy' ### Section: Event Subscription @@ -304,6 +305,14 @@ class Editor extends Model onDidSave: (callback) -> @getBuffer().onDidSave(callback) + # Public: Invoke the given callback when the editor is destroyed. + # + # * `callback` {Function} to be called when the editor is destroyed. + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidDestroy: (callback) -> + @emitter.on 'did-destroy', callback + # Extended: Calls your `callback` when a {Cursor} is added to the editor. # Immediately calls your callback for each existing cursor. # From 6d2719c7830386fc062a50afeac05398d3985801 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 17:36:38 -0700 Subject: [PATCH 159/211] Deprecate editor:will-be-removed --- src/workspace-view.coffee | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 6dc129821..03f85b130 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -176,6 +176,8 @@ class WorkspaceView extends View switch eventName when 'cursor:moved' deprecate('Use Editor::onDidChangeCursorPosition instead') + when 'editor:will-be-removed' + deprecate('Use Editor::onDidDestroy instead') when 'pane:active-item-changed' deprecate('Use Pane::onDidChangeActiveItem instead') when 'pane:active-item-modified-status-changed' @@ -210,6 +212,8 @@ class WorkspaceView extends View switch eventName when 'cursor:moved' deprecate('Use Editor::onDidChangeCursorPosition instead') + when 'editor:will-be-removed' + deprecate('Use Editor::onDidDestroy instead') when 'selection:changed' deprecate('Use Editor::onDidChangeSelectionRange instead') originalEditorViewOn.apply(this, arguments) @@ -219,6 +223,8 @@ class WorkspaceView extends View switch eventName when 'cursor:moved' deprecate('Use Editor::onDidChangeCursorPosition instead') + when 'editor:will-be-removed' + deprecate('Use Editor::onDidDestroy instead') when 'pane:active-item-changed' deprecate('Use Pane::onDidChangeActiveItem instead') when 'pane:active-item-modified-status-changed' From 0b82e83806370a1460c956d4f1df7e30a7cbff9e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 17:42:27 -0700 Subject: [PATCH 160/211] Move deprecatedViewEvents to deprecated section --- src/workspace-view.coffee | 160 +++++++++++++++++++------------------- 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 03f85b130..9e94b9d8d 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -169,86 +169,6 @@ class WorkspaceView extends View @deprecatedViewEvents() - deprecatedViewEvents: -> - originalWorkspaceViewOn = @on - - @on = (eventName) => - switch eventName - when 'cursor:moved' - deprecate('Use Editor::onDidChangeCursorPosition instead') - when 'editor:will-be-removed' - deprecate('Use Editor::onDidDestroy instead') - when 'pane:active-item-changed' - deprecate('Use Pane::onDidChangeActiveItem instead') - when 'pane:active-item-modified-status-changed' - deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeModified on the active item instead') - when 'pane:active-item-title-changed' - deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeTitle on the active item instead') - when 'pane:attached' - deprecate('Use Workspace::onDidAddPane instead') - when 'pane:became-active' - deprecate('Use Pane::onDidActivate instead') - when 'pane:became-inactive' - deprecate('Use Pane::onDidChangeActive instead') - when 'pane:item-added' - deprecate('Use Pane::onDidAddItem instead') - when 'pane:item-moved' - deprecate('Use Pane::onDidMoveItem instead') - when 'pane:item-removed' - deprecate('Use Pane::onDidRemoveItem instead') - when 'pane:removed' - deprecate('Use Pane::onDidDestroy instead') - when 'pane-container:active-pane-item-changed' - deprecate('Use Workspace::onDidChangeActivePaneItem instead') - when 'selection:changed' - deprecate('Use Editor::onDidChangeSelectionRange instead') - when 'uri-opened' - deprecate('Use Workspace::onDidOpen instead') - originalWorkspaceViewOn.apply(this, arguments) - - EditorView = require './editor-view' - originalEditorViewOn = EditorView::on - EditorView::on = (eventName) -> - switch eventName - when 'cursor:moved' - deprecate('Use Editor::onDidChangeCursorPosition instead') - when 'editor:will-be-removed' - deprecate('Use Editor::onDidDestroy instead') - when 'selection:changed' - deprecate('Use Editor::onDidChangeSelectionRange instead') - originalEditorViewOn.apply(this, arguments) - - originalPaneViewOn = PaneView::on - PaneView::on = (eventName) -> - switch eventName - when 'cursor:moved' - deprecate('Use Editor::onDidChangeCursorPosition instead') - when 'editor:will-be-removed' - deprecate('Use Editor::onDidDestroy instead') - when 'pane:active-item-changed' - deprecate('Use Pane::onDidChangeActiveItem instead') - when 'pane:active-item-modified-status-changed' - deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeModified on the active item instead') - when 'pane:active-item-title-changed' - deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeTitle on the active item instead') - when 'pane:attached' - deprecate('Use Workspace::onDidAddPane instead') - when 'pane:became-active' - deprecate('Use Pane::onDidActivate instead') - when 'pane:became-inactive' - deprecate('Use Pane::onDidChangeActive instead') - when 'pane:item-added' - deprecate('Use Pane::onDidAddItem instead') - when 'pane:item-moved' - deprecate('Use Pane::onDidMoveItem instead') - when 'pane:item-removed' - deprecate('Use Pane::onDidRemoveItem instead') - when 'pane:removed' - deprecate('Use Pane::onDidDestroy instead') - when 'selection:changed' - deprecate('Use Editor::onDidChangeSelectionRange instead') - originalPaneViewOn.apply(this, arguments) - ### Section: Accessing the Workspace Model ### @@ -493,6 +413,86 @@ class WorkspaceView extends View Section: Deprecated ### + deprecatedViewEvents: -> + originalWorkspaceViewOn = @on + + @on = (eventName) => + switch eventName + when 'cursor:moved' + deprecate('Use Editor::onDidChangeCursorPosition instead') + when 'editor:will-be-removed' + deprecate('Use Editor::onDidDestroy instead') + when 'pane:active-item-changed' + deprecate('Use Pane::onDidChangeActiveItem instead') + when 'pane:active-item-modified-status-changed' + deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeModified on the active item instead') + when 'pane:active-item-title-changed' + deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeTitle on the active item instead') + when 'pane:attached' + deprecate('Use Workspace::onDidAddPane instead') + when 'pane:became-active' + deprecate('Use Pane::onDidActivate instead') + when 'pane:became-inactive' + deprecate('Use Pane::onDidChangeActive instead') + when 'pane:item-added' + deprecate('Use Pane::onDidAddItem instead') + when 'pane:item-moved' + deprecate('Use Pane::onDidMoveItem instead') + when 'pane:item-removed' + deprecate('Use Pane::onDidRemoveItem instead') + when 'pane:removed' + deprecate('Use Pane::onDidDestroy instead') + when 'pane-container:active-pane-item-changed' + deprecate('Use Workspace::onDidChangeActivePaneItem instead') + when 'selection:changed' + deprecate('Use Editor::onDidChangeSelectionRange instead') + when 'uri-opened' + deprecate('Use Workspace::onDidOpen instead') + originalWorkspaceViewOn.apply(this, arguments) + + EditorView = require './editor-view' + originalEditorViewOn = EditorView::on + EditorView::on = (eventName) -> + switch eventName + when 'cursor:moved' + deprecate('Use Editor::onDidChangeCursorPosition instead') + when 'editor:will-be-removed' + deprecate('Use Editor::onDidDestroy instead') + when 'selection:changed' + deprecate('Use Editor::onDidChangeSelectionRange instead') + originalEditorViewOn.apply(this, arguments) + + originalPaneViewOn = PaneView::on + PaneView::on = (eventName) -> + switch eventName + when 'cursor:moved' + deprecate('Use Editor::onDidChangeCursorPosition instead') + when 'editor:will-be-removed' + deprecate('Use Editor::onDidDestroy instead') + when 'pane:active-item-changed' + deprecate('Use Pane::onDidChangeActiveItem instead') + when 'pane:active-item-modified-status-changed' + deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeModified on the active item instead') + when 'pane:active-item-title-changed' + deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeTitle on the active item instead') + when 'pane:attached' + deprecate('Use Workspace::onDidAddPane instead') + when 'pane:became-active' + deprecate('Use Pane::onDidActivate instead') + when 'pane:became-inactive' + deprecate('Use Pane::onDidChangeActive instead') + when 'pane:item-added' + deprecate('Use Pane::onDidAddItem instead') + when 'pane:item-moved' + deprecate('Use Pane::onDidMoveItem instead') + when 'pane:item-removed' + deprecate('Use Pane::onDidRemoveItem instead') + when 'pane:removed' + deprecate('Use Pane::onDidDestroy instead') + when 'selection:changed' + deprecate('Use Editor::onDidChangeSelectionRange instead') + originalPaneViewOn.apply(this, arguments) + # Deprecated eachPane: (callback) -> deprecate("Use WorkspaceView::eachPaneView instead") From 280a3c30e60f800c4dec15d9f655254074f330eb Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 17:44:21 -0700 Subject: [PATCH 161/211] Deprecate editor:attached --- src/workspace-view.coffee | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 9e94b9d8d..6a61fe1b2 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -420,6 +420,8 @@ class WorkspaceView extends View switch eventName when 'cursor:moved' deprecate('Use Editor::onDidChangeCursorPosition instead') + when 'editor:attached' + deprecate('Use Editor::onDidAddTextEditor instead') when 'editor:will-be-removed' deprecate('Use Editor::onDidDestroy instead') when 'pane:active-item-changed' @@ -456,6 +458,8 @@ class WorkspaceView extends View switch eventName when 'cursor:moved' deprecate('Use Editor::onDidChangeCursorPosition instead') + when 'editor:attached' + deprecate('Use Editor::onDidAddTextEditor instead') when 'editor:will-be-removed' deprecate('Use Editor::onDidDestroy instead') when 'selection:changed' @@ -467,6 +471,8 @@ class WorkspaceView extends View switch eventName when 'cursor:moved' deprecate('Use Editor::onDidChangeCursorPosition instead') + when 'editor:attached' + deprecate('Use Editor::onDidAddTextEditor instead') when 'editor:will-be-removed' deprecate('Use Editor::onDidDestroy instead') when 'pane:active-item-changed' From fe30cf2135ec10265d628dca94dfddc8319a50d1 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 17 Sep 2014 17:47:08 -0700 Subject: [PATCH 162/211] Deprecate editor:detached --- src/workspace-view.coffee | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 6a61fe1b2..2496d47f0 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -422,6 +422,8 @@ class WorkspaceView extends View deprecate('Use Editor::onDidChangeCursorPosition instead') when 'editor:attached' deprecate('Use Editor::onDidAddTextEditor instead') + when 'editor:detached' + deprecate('Use Editor::onDidDestroy instead') when 'editor:will-be-removed' deprecate('Use Editor::onDidDestroy instead') when 'pane:active-item-changed' @@ -460,6 +462,8 @@ class WorkspaceView extends View deprecate('Use Editor::onDidChangeCursorPosition instead') when 'editor:attached' deprecate('Use Editor::onDidAddTextEditor instead') + when 'editor:detached' + deprecate('Use Editor::onDidDestroy instead') when 'editor:will-be-removed' deprecate('Use Editor::onDidDestroy instead') when 'selection:changed' @@ -473,6 +477,8 @@ class WorkspaceView extends View deprecate('Use Editor::onDidChangeCursorPosition instead') when 'editor:attached' deprecate('Use Editor::onDidAddTextEditor instead') + when 'editor:detached' + deprecate('Use Editor::onDidDestroy instead') when 'editor:will-be-removed' deprecate('Use Editor::onDidDestroy instead') when 'pane:active-item-changed' From 67610829f4405840e30864a1e587b38108ad64c7 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 09:42:08 -0700 Subject: [PATCH 163/211] Add Atom::onDidBeep --- src/atom.coffee | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/atom.coffee b/src/atom.coffee index 3a84070f9..04361bf36 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -8,6 +8,7 @@ shell = require 'shell' _ = require 'underscore-plus' {deprecate} = require 'grim' +{Emitter} = require 'event-kit' {Model} = require 'theorist' fs = require 'fs-plus' @@ -147,6 +148,7 @@ class Atom extends Model # Call .loadOrCreate instead constructor: (@state) -> + @emitter = new Emitter {@mode} = @state DeserializerManager = require './deserializer-manager' @deserializers = new DeserializerManager() @@ -211,6 +213,18 @@ class Atom extends Model @windowEventHandler = new WindowEventHandler + ### + Section: Event Subscription + ### + + # Extended: Invoke the given callback whenever {::beep} is called. + # + # * `callback` {Function} to be called whenever {::beep} is called. + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidBeep: (callback) -> + @emitter.on 'did-beep', callback + ### Section: Atom Metadata ### @@ -493,6 +507,7 @@ class Atom extends Model beep: -> shell.beep() if @config.get('core.audioBeep') @workspaceView.trigger 'beep' + @emitter.emit 'did-beep' # Essential: A flexible way to open a dialog akin to an alert dialog. # From 522a66c87693dc38bb3e18f047cc2221254bc1bc Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 09:42:47 -0700 Subject: [PATCH 164/211] :memo: Wrap ::onDidAddPaneItem in {} --- src/workspace.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workspace.coffee b/src/workspace.coffee index 31b7cb261..db97e6732 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -104,7 +104,7 @@ class Workspace extends Model @onDidAddTextEditor ({textEditor}) -> callback(textEditor) # Essential: Invoke the given callback whenever an item is opened. Unlike - # ::onDidAddPaneItem, observers will be notified for items that are already + # {::onDidAddPaneItem}, observers will be notified for items that are already # present in the workspace when they are reopened. # # * `callback` {Function} to be called whenever an item is opened. From 1a24c79c7f4825f46389feb77472d80094c9d1a6 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 09:43:32 -0700 Subject: [PATCH 165/211] Deprecate beep event --- src/workspace-view.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 2496d47f0..547d8daa9 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -418,6 +418,8 @@ class WorkspaceView extends View @on = (eventName) => switch eventName + when 'beep' + deprecate('Use Atom::onDidBeep instead') when 'cursor:moved' deprecate('Use Editor::onDidChangeCursorPosition instead') when 'editor:attached' From 95253758f32f9e9f07a12c46e11dc1c65cac4f57 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 10:09:28 -0700 Subject: [PATCH 166/211] Dispose emitter after emitting did-destroy --- src/editor.coffee | 1 + src/pane.coffee | 1 + 2 files changed, 2 insertions(+) diff --git a/src/editor.coffee b/src/editor.coffee index f63dfc0b5..c4b9716e4 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -172,6 +172,7 @@ class Editor extends Model @displayBuffer.destroy() @languageMode.destroy() @emitter.emit 'did-destroy' + @emitter.dispose() ### Section: Event Subscription diff --git a/src/pane.coffee b/src/pane.coffee index c6351f656..658cee6dd 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -518,6 +518,7 @@ class Pane extends Model destroyed: -> @container.activateNextPane() if @isActive() @emitter.emit 'did-destroy' + @emitter.dispose() item.destroy?() for item in @items.slice() ### From c291c705ec6acd2a2074f623c77627ce8c759344 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 10:20:56 -0700 Subject: [PATCH 167/211] Add missing support to deprecation message --- src/pane-view.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pane-view.coffee b/src/pane-view.coffee index 21f4c8cfa..ddeab69af 100644 --- a/src/pane-view.coffee +++ b/src/pane-view.coffee @@ -162,7 +162,7 @@ class PaneView extends View deprecate 'Please return a Disposable object from your ::onDidChangeTitle method!' unless disposable?.dispose? @activeItemDisposables.add(disposable) if disposable?.dispose? else if item.on? - deprecate '::on methods for items are no longer supported. If you would like your item to title change behavior, please implement a ::onDidChangeTitle() method.' + deprecate '::on methods for items are no longer supported. If you would like your item to support title change behavior, please implement a ::onDidChangeTitle() method.' disposable = item.on('title-changed', @activeItemTitleChanged) @activeItemDisposables.add(disposable) if disposable?.dispose? From 62a5c1c58d654fa989f2e4f272b0686bdaa92d4c Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 11:48:44 -0700 Subject: [PATCH 168/211] Don't dispose emitter, specs fail --- src/editor.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/editor.coffee b/src/editor.coffee index c4b9716e4..f63dfc0b5 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -172,7 +172,6 @@ class Editor extends Model @displayBuffer.destroy() @languageMode.destroy() @emitter.emit 'did-destroy' - @emitter.dispose() ### Section: Event Subscription From 0bf0829e77e80d0ceb3cadf369f625cd5db6b6ec Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 13:40:16 -0700 Subject: [PATCH 169/211] Upgrade to settings-view@0.143 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 11c4d9505..b6bc35a74 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "open-on-github": "0.30.0", "package-generator": "0.31.0", "release-notes": "0.36.0", - "settings-view": "0.142.0", + "settings-view": "0.143.0", "snippets": "0.52.0", "spell-check": "0.42.0", "status-bar": "0.45.0", From a88299284e97718596f403b44be02ab5d9723c22 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 13:51:34 -0700 Subject: [PATCH 170/211] Upgrade to language-ruby@0.38 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b6bc35a74..72163315e 100644 --- a/package.json +++ b/package.json @@ -129,7 +129,7 @@ "language-php": "0.16.0", "language-property-list": "0.7.0", "language-python": "0.19.0", - "language-ruby": "0.37.0", + "language-ruby": "0.38.0", "language-ruby-on-rails": "0.18.0", "language-sass": "0.21.0", "language-shellscript": "0.8.0", From e3ce6f8a20fdad578c769f3106e7c46eed917930 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 17 Sep 2014 17:42:04 -0700 Subject: [PATCH 171/211] Organize DisplayBufferMarker into sections. Add docs from Marker --- src/display-buffer-marker.coffee | 370 +++++++++++++++++++------------ 1 file changed, 232 insertions(+), 138 deletions(-) diff --git a/src/display-buffer-marker.coffee b/src/display-buffer-marker.coffee index 8300cac23..c8e4184b3 100644 --- a/src/display-buffer-marker.coffee +++ b/src/display-buffer-marker.coffee @@ -5,6 +5,26 @@ EmitterMixin = require('emissary').Emitter {Emitter} = require 'event-kit' Grim = require 'grim' +# Essential: Represents a buffer annotation that remains logically stationary +# even as the buffer changes. This is used to represent cursors, folds, snippet +# targets, misspelled words, and anything else that needs to track a logical +# location in the buffer over time. +# +# ### Head and Tail +# +# Markers always have a *head* and sometimes have a *tail*. If you think of a +# marker as an editor selection, the tail is the part that's stationary and the +# head is the part that moves when the mouse is moved. A marker without a tail +# always reports an empty range at the head position. A marker with a head position +# greater than the tail is in a "normal" orientation. If the head precedes the +# tail the marker is in a "reversed" orientation. +# +# ### Validity +# +# Markers are considered *valid* when they are first created. Depending on the +# invalidation strategy you choose, certain changes to the buffer can cause a +# marker to become invalid, for example if the text surrounding the marker is +# deleted. See {Editor::markBufferRange} for invalidation strategies. module.exports = class DisplayBufferMarker EmitterMixin.includeInto(this) @@ -18,6 +38,10 @@ class DisplayBufferMarker wasValid: true deferredChangeEvents: null + ### + Section: Construction and Destruction + ### + constructor: ({@bufferMarker, @displayBuffer}) -> @emitter = new Emitter @id = @bufferMarker.id @@ -30,9 +54,49 @@ class DisplayBufferMarker @subscribe @bufferMarker.onDidDestroy => @destroyed() @subscribe @bufferMarker.onDidChange (event) => @notifyObservers(event) + # Essential: Destroys the marker, causing it to emit the 'destroyed' event. Once + # destroyed, a marker cannot be restored by undo/redo operations. + destroy: -> + @bufferMarker.destroy() + @unsubscribe() + + # Essential: Creates and returns a new {Marker} with the same properties as this + # marker. + # + # * `properties` {Object} + copy: (properties) -> + @displayBuffer.getMarker(@bufferMarker.copy(properties).id) + + ### + Section: Event Subscription + ### + + # Essential: Invoke the given callback when the state of the marker changes. + # + # * `callback` {Function} to be called when the marker changes. + # * `event` {Object} with the following keys: + # * `oldHeadPosition` {Point} representing the former head position + # * `newHeadPosition` {Point} representing the new head position + # * `oldTailPosition` {Point} representing the former tail position + # * `newTailPosition` {Point} representing the new tail position + # * `wasValid` {Boolean} indicating whether the marker was valid before the change + # * `isValid` {Boolean} indicating whether the marker is now valid + # * `hadTail` {Boolean} indicating whether the marker had a tail before the change + # * `hasTail` {Boolean} indicating whether the marker now has a tail + # * `oldProperties` {Object} containing the marker's custom properties before the change. + # * `newProperties` {Object} containing the marker's custom properties after the change. + # * `textChanged` {Boolean} indicating whether this change was caused by a textual change + # to the buffer or whether the marker was manipulated directly via its public API. + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. onDidChange: (callback) -> @emitter.on 'did-change', callback + # Essential: Invoke the given callback when the marker is destroyed. + # + # * `callback` {Function} to be called when the marker is destroyed. + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. onDidDestroy: (callback) -> @emitter.on 'did-destroy', callback @@ -45,154 +109,35 @@ class DisplayBufferMarker EmitterMixin::on.apply(this, arguments) - copy: (attributes) -> - @displayBuffer.getMarker(@bufferMarker.copy(attributes).id) + ### + Section: Marker Metadata + ### - # Gets the screen range of the display marker. - # - # Returns a {Range}. - getScreenRange: -> - @displayBuffer.screenRangeForBufferRange(@getBufferRange(), wrapAtSoftNewlines: true) - - # Modifies the screen range of the display marker. - # - # screenRange - The new {Range} to use - # options - A hash of options matching those found in {Marker::setRange} - setScreenRange: (screenRange, options) -> - @setBufferRange(@displayBuffer.bufferRangeForScreenRange(screenRange), options) - - # Gets the buffer range of the display marker. - # - # Returns a {Range}. - getBufferRange: -> - @bufferMarker.getRange() - - # Modifies the buffer range of the display marker. - # - # screenRange - The new {Range} to use - # options - A hash of options matching those found in {Marker::setRange} - setBufferRange: (bufferRange, options) -> - @bufferMarker.setRange(bufferRange, options) - - getPixelRange: -> - @displayBuffer.pixelRangeForScreenRange(@getScreenRange(), false) - - # Retrieves the screen position of the marker's head. - # - # Returns a {Point}. - getHeadScreenPosition: -> - @displayBuffer.screenPositionForBufferPosition(@getHeadBufferPosition(), wrapAtSoftNewlines: true) - - # Sets the screen position of the marker's head. - # - # screenRange - The new {Point} to use - # options - A hash of options matching those found in {DisplayBuffer::bufferPositionForScreenPosition} - setHeadScreenPosition: (screenPosition, options) -> - screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options) - @setHeadBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options)) - - # Retrieves the buffer position of the marker's head. - # - # Returns a {Point}. - getHeadBufferPosition: -> - @bufferMarker.getHeadPosition() - - # Sets the buffer position of the marker's head. - # - # screenRange - The new {Point} to use - # options - A hash of options matching those found in {DisplayBuffer::bufferPositionForScreenPosition} - setHeadBufferPosition: (bufferPosition) -> - @bufferMarker.setHeadPosition(bufferPosition) - - # Retrieves the screen position of the marker's tail. - # - # Returns a {Point}. - getTailScreenPosition: -> - @displayBuffer.screenPositionForBufferPosition(@getTailBufferPosition(), wrapAtSoftNewlines: true) - - # Sets the screen position of the marker's tail. - # - # screenRange - The new {Point} to use - # options - A hash of options matching those found in {DisplayBuffer::bufferPositionForScreenPosition} - setTailScreenPosition: (screenPosition, options) -> - screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options) - @setTailBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options)) - - # Retrieves the buffer position of the marker's tail. - # - # Returns a {Point}. - getTailBufferPosition: -> - @bufferMarker.getTailPosition() - - # Sets the buffer position of the marker's tail. - # - # screenRange - The new {Point} to use - # options - A hash of options matching those found in {DisplayBuffer::bufferPositionForScreenPosition} - setTailBufferPosition: (bufferPosition) -> - @bufferMarker.setTailPosition(bufferPosition) - - # Retrieves the screen position of the marker's start. This will always be - # less than or equal to the result of {DisplayBufferMarker::getEndScreenPosition}. - # - # Returns a {Point}. - getStartScreenPosition: -> - @displayBuffer.screenPositionForBufferPosition(@getStartBufferPosition(), wrapAtSoftNewlines: true) - - # Retrieves the buffer position of the marker's start. This will always be - # less than or equal to the result of {DisplayBufferMarker::getEndBufferPosition}. - # - # Returns a {Point}. - getStartBufferPosition: -> - @bufferMarker.getStartPosition() - - # Retrieves the screen position of the marker's end. This will always be - # greater than or equal to the result of {DisplayBufferMarker::getStartScreenPosition}. - # - # Returns a {Point}. - getEndScreenPosition: -> - @displayBuffer.screenPositionForBufferPosition(@getEndBufferPosition(), wrapAtSoftNewlines: true) - - # Retrieves the buffer position of the marker's end. This will always be - # greater than or equal to the result of {DisplayBufferMarker::getStartBufferPosition}. - # - # Returns a {Point}. - getEndBufferPosition: -> - @bufferMarker.getEndPosition() - - # Sets the marker's tail to the same position as the marker's head. - # - # This only works if there isn't already a tail position. - # - # Returns a {Point} representing the new tail position. - plantTail: -> - @bufferMarker.plantTail() - - # Removes the tail from the marker. - clearTail: -> - @bufferMarker.clearTail() - - hasTail: -> - @bufferMarker.hasTail() - - # Returns whether the head precedes the tail in the buffer - isReversed: -> - @bufferMarker.isReversed() - - # Returns a {Boolean} indicating whether the marker is valid. Markers can be + # Essential: Returns a {Boolean} indicating whether the marker is valid. Markers can be # invalidated when a region surrounding them in the buffer is changed. isValid: -> @bufferMarker.isValid() - # Returns a {Boolean} indicating whether the marker has been destroyed. A marker + # Essential: Returns a {Boolean} indicating whether the marker has been destroyed. A marker # can be invalid without being destroyed, in which case undoing the invalidating # operation would restore the marker. Once a marker is destroyed by calling # {Marker::destroy}, no undo/redo operation can ever bring it back. isDestroyed: -> @bufferMarker.isDestroyed() + # Extended: Returns a {Boolean} indicating whether the head precedes the tail. + isReversed: -> + @bufferMarker.isReversed() + + # Extended: Returns an {Object} containing any custom properties associated with + # the marker. getAttributes: -> @bufferMarker.getProperties() + # Extended: Merges an {Object} containing new properties into the marker's + # existing properties. + # + # * `properties` {Object} setAttributes: (attributes) -> @bufferMarker.setProperties(attributes) @@ -200,18 +145,164 @@ class DisplayBufferMarker attributes = @displayBuffer.translateToBufferMarkerParams(attributes) @bufferMarker.matchesAttributes(attributes) - # Destroys the marker - destroy: -> - @bufferMarker.destroy() - @unsubscribe() + ### + Section: Comparing to other markers + ### + # Essential: Returns a {Boolean} indicating whether this marker is equivalent to + # another marker, meaning they have the same range and options. + # + # * `other` {Marker} other marker isEqual: (other) -> return false unless other instanceof @constructor @bufferMarker.isEqual(other.bufferMarker) + # Essential: Compares this marker to another based on their ranges. + # + # * `other` {Marker} + # + # Returns a {Number} compare: (other) -> @bufferMarker.compare(other.bufferMarker) + ### + Section: Managing the marker's range + ### + + # Essential: Gets the buffer range of the display marker. + # + # Returns a {Range}. + getBufferRange: -> + @bufferMarker.getRange() + + # Essential: Modifies the buffer range of the display marker. + # + # * `bufferRange` The new {Range} to use + # * `properties` (optional) {Object} properties to associate with the marker. + # * `reversed` {Boolean} If true, the marker will to be in a reversed orientation. + setBufferRange: (bufferRange, properties) -> + @bufferMarker.setRange(bufferRange, properties) + + # Essential: Gets the screen range of the display marker. + # + # Returns a {Range}. + getScreenRange: -> + @displayBuffer.screenRangeForBufferRange(@getBufferRange(), wrapAtSoftNewlines: true) + + # Essential: Modifies the screen range of the display marker. + # + # * `screenRange` The new {Range} to use + # * `properties` (optional) {Object} properties to associate with the marker. + # * `reversed` {Boolean} If true, the marker will to be in a reversed orientation. + setScreenRange: (screenRange, options) -> + @setBufferRange(@displayBuffer.bufferRangeForScreenRange(screenRange), options) + + # Essential: Retrieves the buffer position of the marker's start. This will always be + # less than or equal to the result of {DisplayBufferMarker::getEndBufferPosition}. + # + # Returns a {Point}. + getStartBufferPosition: -> + @bufferMarker.getStartPosition() + + # Essential: Retrieves the screen position of the marker's start. This will always be + # less than or equal to the result of {DisplayBufferMarker::getEndScreenPosition}. + # + # Returns a {Point}. + getStartScreenPosition: -> + @displayBuffer.screenPositionForBufferPosition(@getStartBufferPosition(), wrapAtSoftNewlines: true) + + # Essential: Retrieves the buffer position of the marker's end. This will always be + # greater than or equal to the result of {DisplayBufferMarker::getStartBufferPosition}. + # + # Returns a {Point}. + getEndBufferPosition: -> + @bufferMarker.getEndPosition() + + # Essential: Retrieves the screen position of the marker's end. This will always be + # greater than or equal to the result of {DisplayBufferMarker::getStartScreenPosition}. + # + # Returns a {Point}. + getEndScreenPosition: -> + @displayBuffer.screenPositionForBufferPosition(@getEndBufferPosition(), wrapAtSoftNewlines: true) + + # Extended: Retrieves the buffer position of the marker's head. + # + # Returns a {Point}. + getHeadBufferPosition: -> + @bufferMarker.getHeadPosition() + + # Extended: Sets the buffer position of the marker's head. + # + # * `screenRange` The new {Point} to use + # * `properties` (optional) {Object} properties to associate with the marker. + setHeadBufferPosition: (bufferPosition, properties) -> + @bufferMarker.setHeadPosition(bufferPosition, properties) + + # Extended: Retrieves the screen position of the marker's head. + # + # Returns a {Point}. + getHeadScreenPosition: -> + @displayBuffer.screenPositionForBufferPosition(@getHeadBufferPosition(), wrapAtSoftNewlines: true) + + # Extended: Sets the screen position of the marker's head. + # + # * `screenRange` The new {Point} to use + # * `properties` (optional) {Object} properties to associate with the marker. + setHeadScreenPosition: (screenPosition, properties) -> + screenPosition = @displayBuffer.clipScreenPosition(screenPosition, properties) + @setHeadBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, properties)) + + # Extended: Retrieves the buffer position of the marker's tail. + # + # Returns a {Point}. + getTailBufferPosition: -> + @bufferMarker.getTailPosition() + + # Extended: Sets the buffer position of the marker's tail. + # + # * `screenRange` The new {Point} to use + # * `properties` (optional) {Object} properties to associate with the marker. + setTailBufferPosition: (bufferPosition) -> + @bufferMarker.setTailPosition(bufferPosition) + + # Extended: Retrieves the screen position of the marker's tail. + # + # Returns a {Point}. + getTailScreenPosition: -> + @displayBuffer.screenPositionForBufferPosition(@getTailBufferPosition(), wrapAtSoftNewlines: true) + + # Extended: Sets the screen position of the marker's tail. + # + # * `screenRange` The new {Point} to use + # * `properties` (optional) {Object} properties to associate with the marker. + setTailScreenPosition: (screenPosition, options) -> + screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options) + @setTailBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options)) + + # Extended: Returns a {Boolean} indicating whether the marker has a tail. + hasTail: -> + @bufferMarker.hasTail() + + # Extended: Plants the marker's tail at the current head position. After calling + # the marker's tail position will be its head position at the time of the + # call, regardless of where the marker's head is moved. + # + # * `properties` (optional) {Object} properties to associate with the marker. + plantTail: -> + @bufferMarker.plantTail() + + # Extended: Removes the marker's tail. After calling the marker's head position + # will be reported as its current tail position until the tail is planted + # again. + # + # * `properties` (optional) {Object} properties to associate with the marker. + clearTail: (properties) -> + @bufferMarker.clearTail(properties) + + ### + Section: Private utility methods + ### + # Returns a {String} representation of the marker inspect: -> "DisplayBufferMarker(id: #{@id}, bufferRange: #{@getBufferRange()})" @@ -268,3 +359,6 @@ class DisplayBufferMarker for event in deferredChangeEvents @emit 'changed', event @emitter.emit 'did-change', event + + getPixelRange: -> + @displayBuffer.pixelRangeForScreenRange(@getScreenRange(), false) From bd19899dd83b94de29b2b2d215fb8d9883ba9aec Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 17 Sep 2014 17:43:39 -0700 Subject: [PATCH 172/211] Add DisplayBufferMarker::getInvalidationStrategy --- src/display-buffer-marker.coffee | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/display-buffer-marker.coffee b/src/display-buffer-marker.coffee index c8e4184b3..e7e03e407 100644 --- a/src/display-buffer-marker.coffee +++ b/src/display-buffer-marker.coffee @@ -129,6 +129,14 @@ class DisplayBufferMarker isReversed: -> @bufferMarker.isReversed() + # Extended: Get the invalidation strategy for this marker. + # + # Valid values include: `never`, `surround`, `overlap`, `inside`, and `touch`. + # + # Returns a {String}. + getInvalidationStrategy: -> + @bufferMarker.getInvalidationStrategy() + # Extended: Returns an {Object} containing any custom properties associated with # the marker. getAttributes: -> From cef8b95ef3cb53c96894eb2181f52744521e7bca Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 17 Sep 2014 17:57:37 -0700 Subject: [PATCH 173/211] Deprecate s|getAttributes for s|getProperties --- spec/display-buffer-spec.coffee | 4 ++-- src/display-buffer-marker.coffee | 12 +++++++++--- src/editor.coffee | 6 +++--- src/selection.coffee | 4 ++-- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 9f00eff3a..e3451e575 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -1020,8 +1020,8 @@ describe "DisplayBuffer", -> marker2 = marker1.copy(b: 3) expect(marker2.getBufferRange()).toEqual marker1.getBufferRange() expect(displayBuffer.getMarkerCount()).toBe initialMarkerCount + 2 - expect(marker1.getAttributes()).toEqual a: 1, b: 2 - expect(marker2.getAttributes()).toEqual a: 1, b: 3 + expect(marker1.getProperties()).toEqual a: 1, b: 2 + expect(marker2.getProperties()).toEqual a: 1, b: 3 describe "DisplayBufferMarker::getPixelRange()", -> it "returns the start and end positions of the marker based on the line height and character widths assigned to the DisplayBuffer", -> diff --git a/src/display-buffer-marker.coffee b/src/display-buffer-marker.coffee index e7e03e407..0e001dc65 100644 --- a/src/display-buffer-marker.coffee +++ b/src/display-buffer-marker.coffee @@ -139,15 +139,21 @@ class DisplayBufferMarker # Extended: Returns an {Object} containing any custom properties associated with # the marker. - getAttributes: -> + getProperties: -> @bufferMarker.getProperties() + getAttributes: -> + deprecate 'Use Marker::getProperties instead' + @getProperties() # Extended: Merges an {Object} containing new properties into the marker's # existing properties. # # * `properties` {Object} - setAttributes: (attributes) -> - @bufferMarker.setProperties(attributes) + setProperties: (properties) -> + @bufferMarker.setProperties(properties) + setAttributes: (properties) -> + deprecate 'Use Marker::getProperties instead' + @setProperties(properties) matchesAttributes: (attributes) -> attributes = @displayBuffer.translateToBufferMarkerParams(attributes) diff --git a/src/editor.coffee b/src/editor.coffee index f63dfc0b5..b7b918ae0 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -93,7 +93,7 @@ class Editor extends Model @softTabs = @usesSoftTabs() ? @softTabs ? atom.config.get('editor.softTabs') ? true for marker in @findMarkers(@getSelectionMarkerAttributes()) - marker.setAttributes(preserveFolds: true) + marker.setProperties(preserveFolds: true) @addSelection(marker) @subscribeToBuffer() @@ -2480,13 +2480,13 @@ class Editor extends Model # # Returns the new {Selection}. addSelection: (marker, options={}) -> - unless marker.getAttributes().preserveFolds + unless marker.getProperties().preserveFolds @destroyFoldsIntersectingBufferRange(marker.getBufferRange()) cursor = @addCursor(marker) selection = new Selection(_.extend({editor: this, marker, cursor}, options)) @selections.push(selection) selectionBufferRange = selection.getBufferRange() - @mergeIntersectingSelections(preserveFolds: marker.getAttributes().preserveFolds) + @mergeIntersectingSelections(preserveFolds: marker.getProperties().preserveFolds) if selection.destroyed for selection in @getSelections() if selection.intersectsBufferRange(selectionBufferRange) diff --git a/src/selection.coffee b/src/selection.coffee index a220a49b6..42fb54cae 100644 --- a/src/selection.coffee +++ b/src/selection.coffee @@ -176,7 +176,7 @@ class Selection extends Model # Public: Clears the selection, moving the marker to the head. clear: -> - @marker.setAttributes(goalBufferRange: null) + @marker.setProperties(goalBufferRange: null) @marker.clearTail() unless @retainSelection @finalize() @@ -717,5 +717,5 @@ class Selection extends Model @marker.plantTail() getGoalBufferRange: -> - if goalBufferRange = @marker.getAttributes().goalBufferRange + if goalBufferRange = @marker.getProperties().goalBufferRange Range.fromObject(goalBufferRange) From bf44cf89db1c190c25138414c5fc2df19bc3250c Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 17 Sep 2014 18:00:31 -0700 Subject: [PATCH 174/211] DisplayBufferMarker::matchesAttributes -> ::matchesProperties --- src/display-buffer-marker.coffee | 7 +++++-- src/display-buffer.coffee | 2 +- src/editor.coffee | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/display-buffer-marker.coffee b/src/display-buffer-marker.coffee index 0e001dc65..a34bc273a 100644 --- a/src/display-buffer-marker.coffee +++ b/src/display-buffer-marker.coffee @@ -155,9 +155,12 @@ class DisplayBufferMarker deprecate 'Use Marker::getProperties instead' @setProperties(properties) - matchesAttributes: (attributes) -> + matchesProperties: (attributes) -> attributes = @displayBuffer.translateToBufferMarkerParams(attributes) - @bufferMarker.matchesAttributes(attributes) + @bufferMarker.matchesParams(attributes) + matchesAttributes: (attributes) -> + deprecate 'Use Marker::matchesProperties instead' + @matchesProperties(attributes) ### Section: Comparing to other markers diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index c86324b35..d966c9d8c 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -1139,7 +1139,7 @@ class DisplayBuffer extends Model @emitDidChange(event, false) handleBufferMarkerCreated: (marker) => - @createFoldForMarker(marker) if marker.matchesAttributes(@getFoldMarkerAttributes()) + @createFoldForMarker(marker) if marker.matchesProperties(@getFoldMarkerAttributes()) if displayBufferMarker = @getMarker(marker.id) # The marker might have been removed in some other handler called before # this one. Only emit when the marker still exists. diff --git a/src/editor.coffee b/src/editor.coffee index b7b918ae0..6e10e266b 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -2622,7 +2622,7 @@ class Editor extends Model @emitter.emit 'did-change-grammar' handleMarkerCreated: (marker) => - if marker.matchesAttributes(@getSelectionMarkerAttributes()) + if marker.matchesProperties(@getSelectionMarkerAttributes()) @addSelection(marker) ### From fb7b9041ab43b3b25c2784edbb2817b9655d1a42 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 17 Sep 2014 18:10:19 -0700 Subject: [PATCH 175/211] Shore up the docs around marker creation and invalidation --- src/display-buffer-marker.coffee | 20 +++++++++++- src/editor.coffee | 56 +++++++++++++++++++++++++++----- 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/src/display-buffer-marker.coffee b/src/display-buffer-marker.coffee index a34bc273a..1611c1b52 100644 --- a/src/display-buffer-marker.coffee +++ b/src/display-buffer-marker.coffee @@ -10,6 +10,10 @@ Grim = require 'grim' # targets, misspelled words, and anything else that needs to track a logical # location in the buffer over time. # +# ### Marker Creation +# +# You will use {Editor::markBufferRange} rather than creating Markers directly. +# # ### Head and Tail # # Markers always have a *head* and sometimes have a *tail*. If you think of a @@ -24,7 +28,21 @@ Grim = require 'grim' # Markers are considered *valid* when they are first created. Depending on the # invalidation strategy you choose, certain changes to the buffer can cause a # marker to become invalid, for example if the text surrounding the marker is -# deleted. See {Editor::markBufferRange} for invalidation strategies. +# deleted. The strategies, in order of descending fragility: +# +# * __never__: The marker is never marked as invalid. This is a good choice for +# markers representing selections in an editor. +# * __surround__: The marker is invalidated by changes that completely surround it. +# * __overlap__: The marker is invalidated by changes that surround the +# start or end of the marker. This is the default. +# * __inside__: The marker is invalidated by changes that extend into the +# inside of the marker. Changes that end at the marker's start or +# start at the marker's end do not invalidate the marker. +# * __touch__: The marker is invalidated by a change that touches the marked +# region in any way, including changes that end at the marker's +# start or start at the marker's end. This is the most fragile strategy. +# +# See {Editor::markBufferRange} for usage. module.exports = class DisplayBufferMarker EmitterMixin.includeInto(this) diff --git a/src/editor.coffee b/src/editor.coffee index 6e10e266b..4a3ace087 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1691,21 +1691,61 @@ class Editor extends Model Section: Markers ### - # Essential: Mark the given range in buffer coordinates. + # Essential: Create a marker with the given range in buffer coordinates. This + # marker will maintain its logical location as the buffer is changed, so if + # you mark a particular word, the marker will remain over that word even if + # the word's location in the buffer changes. # - # * `range` A {Range} or range-compatible {Array}. - # * `options` (optional) See {TextBuffer::markRange}. + # * `range` A {Range} or range-compatible {Array} + # * `properties` A hash of key-value pairs to associate with the marker. There + # are also reserved property names that have marker-specific meaning. + # * `reversed` (optional) Creates the marker in a reversed orientation. (default: false) + # * `persistent` (optional) Whether to include this marker when serializing the buffer. (default: true) + # * `invalidate` (optional) Determines the rules by which changes to the + # buffer *invalidate* the marker. (default: 'overlap') It can be any of + # the following strategies, in order of fragility + # * __never__: The marker is never marked as invalid. This is a good choice for + # markers representing selections in an editor. + # * __surround__: The marker is invalidated by changes that completely surround it. + # * __overlap__: The marker is invalidated by changes that surround the + # start or end of the marker. This is the default. + # * __inside__: The marker is invalidated by changes that extend into the + # inside of the marker. Changes that end at the marker's start or + # start at the marker's end do not invalidate the marker. + # * __touch__: The marker is invalidated by a change that touches the marked + # region in any way, including changes that end at the marker's + # start or start at the marker's end. This is the most fragile strategy. # - # Returns a {DisplayBufferMarker}. + # Returns a {Marker}. markBufferRange: (args...) -> @displayBuffer.markBufferRange(args...) - # Essential: Mark the given range in screen coordinates. + # Essential: Create a marker with the given range in screen coordinates. This + # marker will maintain its logical location as the buffer is changed, so if + # you mark a particular word, the marker will remain over that word even if + # the word's location in the buffer changes. # - # * `range` A {Range} or range-compatible {Array}. - # * `options` (optional) See {TextBuffer::markRange}. + # * `range` A {Range} or range-compatible {Array} + # * `properties` A hash of key-value pairs to associate with the marker. There + # are also reserved property names that have marker-specific meaning. + # * `reversed` (optional) Creates the marker in a reversed orientation. (default: false) + # * `persistent` (optional) Whether to include this marker when serializing the buffer. (default: true) + # * `invalidate` (optional) Determines the rules by which changes to the + # buffer *invalidate* the marker. (default: 'overlap') It can be any of + # the following strategies, in order of fragility + # * __never__: The marker is never marked as invalid. This is a good choice for + # markers representing selections in an editor. + # * __surround__: The marker is invalidated by changes that completely surround it. + # * __overlap__: The marker is invalidated by changes that surround the + # start or end of the marker. This is the default. + # * __inside__: The marker is invalidated by changes that extend into the + # inside of the marker. Changes that end at the marker's start or + # start at the marker's end do not invalidate the marker. + # * __touch__: The marker is invalidated by a change that touches the marked + # region in any way, including changes that end at the marker's + # start or start at the marker's end. This is the most fragile strategy. # - # Returns a {DisplayBufferMarker}. + # Returns a {Marker}. markScreenRange: (args...) -> @displayBuffer.markScreenRange(args...) From 9af2325f17c50ec15db7cbac49c8ca3012c5caca Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 17 Sep 2014 18:14:26 -0700 Subject: [PATCH 176/211] Rename DisplayBufferMarker -> Marker Gnar!! :sunglasses: --- spec/display-buffer-spec.coffee | 4 ++-- src/display-buffer-marker.coffee | 16 ++++++++-------- src/display-buffer.coffee | 14 +++++++------- src/editor.coffee | 18 +++++++++--------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index e3451e575..9890504d3 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -1011,7 +1011,7 @@ describe "DisplayBuffer", -> buffer.getMarker(marker2.id).destroy() expect(destroyedHandler).toHaveBeenCalled() - describe "DisplayBufferMarker::copy(attributes)", -> + describe "Marker::copy(attributes)", -> it "creates a copy of the marker with the given attributes merged in", -> initialMarkerCount = displayBuffer.getMarkerCount() marker1 = displayBuffer.markScreenRange([[5, 4], [5, 10]], a: 1, b: 2) @@ -1023,7 +1023,7 @@ describe "DisplayBuffer", -> expect(marker1.getProperties()).toEqual a: 1, b: 2 expect(marker2.getProperties()).toEqual a: 1, b: 3 - describe "DisplayBufferMarker::getPixelRange()", -> + describe "Marker::getPixelRange()", -> it "returns the start and end positions of the marker based on the line height and character widths assigned to the DisplayBuffer", -> marker = displayBuffer.markScreenRange([[5, 10], [6, 4]]) diff --git a/src/display-buffer-marker.coffee b/src/display-buffer-marker.coffee index 1611c1b52..de28e3f10 100644 --- a/src/display-buffer-marker.coffee +++ b/src/display-buffer-marker.coffee @@ -44,7 +44,7 @@ Grim = require 'grim' # # See {Editor::markBufferRange} for usage. module.exports = -class DisplayBufferMarker +class Marker EmitterMixin.includeInto(this) Subscriber.includeInto(this) @@ -121,9 +121,9 @@ class DisplayBufferMarker on: (eventName) -> switch eventName when 'changed' - Grim.deprecate("Use DisplayBufferMarker::onDidChange instead") + Grim.deprecate("Use Marker::onDidChange instead") when 'destroyed' - Grim.deprecate("Use DisplayBufferMarker::onDidDestroy instead") + Grim.deprecate("Use Marker::onDidDestroy instead") EmitterMixin::on.apply(this, arguments) @@ -233,28 +233,28 @@ class DisplayBufferMarker @setBufferRange(@displayBuffer.bufferRangeForScreenRange(screenRange), options) # Essential: Retrieves the buffer position of the marker's start. This will always be - # less than or equal to the result of {DisplayBufferMarker::getEndBufferPosition}. + # less than or equal to the result of {Marker::getEndBufferPosition}. # # Returns a {Point}. getStartBufferPosition: -> @bufferMarker.getStartPosition() # Essential: Retrieves the screen position of the marker's start. This will always be - # less than or equal to the result of {DisplayBufferMarker::getEndScreenPosition}. + # less than or equal to the result of {Marker::getEndScreenPosition}. # # Returns a {Point}. getStartScreenPosition: -> @displayBuffer.screenPositionForBufferPosition(@getStartBufferPosition(), wrapAtSoftNewlines: true) # Essential: Retrieves the buffer position of the marker's end. This will always be - # greater than or equal to the result of {DisplayBufferMarker::getStartBufferPosition}. + # greater than or equal to the result of {Marker::getStartBufferPosition}. # # Returns a {Point}. getEndBufferPosition: -> @bufferMarker.getEndPosition() # Essential: Retrieves the screen position of the marker's end. This will always be - # greater than or equal to the result of {DisplayBufferMarker::getStartScreenPosition}. + # greater than or equal to the result of {Marker::getStartScreenPosition}. # # Returns a {Point}. getEndScreenPosition: -> @@ -340,7 +340,7 @@ class DisplayBufferMarker # Returns a {String} representation of the marker inspect: -> - "DisplayBufferMarker(id: #{@id}, bufferRange: #{@getBufferRange()})" + "Marker(id: #{@id}, bufferRange: #{@getBufferRange()})" destroyed: -> delete @displayBuffer.markers[@id] diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index d966c9d8c..bfb44fa57 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -10,7 +10,7 @@ RowMap = require './row-map' Fold = require './fold' Token = require './token' Decoration = require './decoration' -DisplayBufferMarker = require './display-buffer-marker' +Marker = require './display-buffer-marker' Grim = require 'grim' class BufferToScreenConversionError extends Error @@ -865,21 +865,21 @@ class DisplayBuffer extends Model @emitter.emit 'did-remove-decoration', decoration delete @decorationsByMarkerId[marker.id] if decorations.length is 0 - # Retrieves a {DisplayBufferMarker} based on its id. + # Retrieves a {Marker} based on its id. # # id - A {Number} representing a marker id # - # Returns the {DisplayBufferMarker} (if it exists). + # Returns the {Marker} (if it exists). getMarker: (id) -> unless marker = @markers[id] if bufferMarker = @buffer.getMarker(id) - marker = new DisplayBufferMarker({bufferMarker, displayBuffer: this}) + marker = new Marker({bufferMarker, displayBuffer: this}) @markers[id] = marker marker # Retrieves the active markers in the buffer. # - # Returns an {Array} of existing {DisplayBufferMarker}s. + # Returns an {Array} of existing {Marker}s. getMarkers: -> @buffer.getMarkers().map ({id}) => @getMarker(id) @@ -934,7 +934,7 @@ class DisplayBuffer extends Model # # Refer to {DisplayBuffer::findMarkers} for details. # - # Returns a {DisplayBufferMarker} or null + # Returns a {Marker} or null findMarker: (params) -> @findMarkers(params)[0] @@ -955,7 +955,7 @@ class DisplayBuffer extends Model # :containedInBufferRange - A {Range} or range-compatible {Array}. Only # returns markers contained within this range. # - # Returns an {Array} of {DisplayBufferMarker}s + # Returns an {Array} of {Marker}s findMarkers: (params) -> params = @translateToBufferMarkerParams(params) @buffer.findMarkers(params).map (stringMarker) => @getMarker(stringMarker.id) diff --git a/src/editor.coffee b/src/editor.coffee index 4a3ace087..fa38b5444 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1754,7 +1754,7 @@ class Editor extends Model # * `position` A {Point} or {Array} of `[row, column]`. # * `options` (optional) See {TextBuffer::markRange}. # - # Returns a {DisplayBufferMarker}. + # Returns a {Marker}. markBufferPosition: (args...) -> @displayBuffer.markBufferPosition(args...) @@ -1763,11 +1763,11 @@ class Editor extends Model # * `position` A {Point} or {Array} of `[row, column]`. # * `options` (optional) See {TextBuffer::markRange}. # - # Returns a {DisplayBufferMarker}. + # Returns a {Marker}. markScreenPosition: (args...) -> @displayBuffer.markScreenPosition(args...) - # Essential: Find all {DisplayBufferMarker}s that match the given properties. + # Essential: Find all {Marker}s that match the given properties. # # This method finds markers based on the given properties. Markers can be # associated with custom properties that will be compared with basic equality. @@ -1789,11 +1789,11 @@ class Editor extends Model findMarkers: (properties) -> @displayBuffer.findMarkers(properties) - # Extended: Get the {DisplayBufferMarker} for the given marker id. + # Extended: Get the {Marker} for the given marker id. getMarker: (id) -> @displayBuffer.getMarker(id) - # Extended: Get all {DisplayBufferMarker}s. + # Extended: Get all {Marker}s. getMarkers: -> @displayBuffer.getMarkers() @@ -2054,7 +2054,7 @@ class Editor extends Model getCursorsOrderedByBufferPosition: -> @getCursors().sort (a, b) -> a.compare(b) - # Add a cursor based on the given {DisplayBufferMarker}. + # Add a cursor based on the given {Marker}. addCursor: (marker) -> cursor = new Cursor(editor: this, marker: marker) @cursors.push(cursor) @@ -2397,7 +2397,7 @@ class Editor extends Model # Extended: Select the range of the given marker if it is valid. # - # * `marker` A {DisplayBufferMarker} + # * `marker` A {Marker} # # Returns the selected {Range} or `undefined` if the marker is invalid. selectMarker: (marker) -> @@ -2513,9 +2513,9 @@ class Editor extends Model _.reduce(@getSelections(), reducer, []) - # Add a {Selection} based on the given {DisplayBufferMarker}. + # Add a {Selection} based on the given {Marker}. # - # * `marker` The {DisplayBufferMarker} to highlight + # * `marker` The {Marker} to highlight # * `options` (optional) An {Object} that pertains to the {Selection} constructor. # # Returns the new {Selection}. From 65b41fa50279d590a52477ced4be51e5fe800295 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 17 Sep 2014 18:15:38 -0700 Subject: [PATCH 177/211] Move buffer-display-marker -> marker --- src/{display-buffer-marker.coffee => marker.coffee} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{display-buffer-marker.coffee => marker.coffee} (100%) diff --git a/src/display-buffer-marker.coffee b/src/marker.coffee similarity index 100% rename from src/display-buffer-marker.coffee rename to src/marker.coffee From 6121147fc128a26dbdbe5d95c3310f38641d9c1c Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Wed, 17 Sep 2014 18:18:14 -0700 Subject: [PATCH 178/211] Upgrade text-buffer to have a private marker --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 72163315e..782e98884 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "serializable": "^1", "space-pen": "3.4.7", "temp": "0.7.0", - "text-buffer": "^3.2.4", + "text-buffer": "^3.2.5", "theorist": "^1.0.2", "underscore-plus": "^1.5.1", "vm-compatibility-layer": "0.1.0" From 39343b0c529ba678f679ae352cca55fef4bc98b5 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 18 Sep 2014 09:42:36 -0700 Subject: [PATCH 179/211] Fix reference to display-buffer-marker --- src/display-buffer.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index bfb44fa57..3f5d60559 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -10,7 +10,7 @@ RowMap = require './row-map' Fold = require './fold' Token = require './token' Decoration = require './decoration' -Marker = require './display-buffer-marker' +Marker = require './marker' Grim = require 'grim' class BufferToScreenConversionError extends Error From 068c2c359f7284609ced27afd2cced93469a91a1 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 18 Sep 2014 09:44:17 -0700 Subject: [PATCH 180/211] displayBufferMarker -> marker --- src/display-buffer.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 3f5d60559..ffc08070c 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -1140,11 +1140,11 @@ class DisplayBuffer extends Model handleBufferMarkerCreated: (marker) => @createFoldForMarker(marker) if marker.matchesProperties(@getFoldMarkerAttributes()) - if displayBufferMarker = @getMarker(marker.id) + if marker = @getMarker(marker.id) # The marker might have been removed in some other handler called before # this one. Only emit when the marker still exists. - @emit 'marker-created', displayBufferMarker - @emitter.emit 'did-create-marker', displayBufferMarker + @emit 'marker-created', marker + @emitter.emit 'did-create-marker', marker createFoldForMarker: (marker) -> @decorateMarker(marker, type: 'gutter', class: 'folded') From 325cc95f4837f77a06bf9dcd6f6e5aedbc2af952 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 18 Sep 2014 13:19:26 -0700 Subject: [PATCH 181/211] Fix specs --- src/display-buffer.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index ffc08070c..4a686c88b 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -1138,9 +1138,9 @@ class DisplayBuffer extends Model @pendingChangeEvent = null @emitDidChange(event, false) - handleBufferMarkerCreated: (marker) => - @createFoldForMarker(marker) if marker.matchesProperties(@getFoldMarkerAttributes()) - if marker = @getMarker(marker.id) + handleBufferMarkerCreated: (textBufferMarker) => + @createFoldForMarker(textBufferMarker) if textBufferMarker.matchesParams(@getFoldMarkerAttributes()) + if marker = @getMarker(textBufferMarker.id) # The marker might have been removed in some other handler called before # this one. Only emit when the marker still exists. @emit 'marker-created', marker From 7f3279e7897db8bd7e530d8b46f85497755097ed Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 14:50:36 -0700 Subject: [PATCH 182/211] Unfocus spec --- spec/language-mode-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/language-mode-spec.coffee b/spec/language-mode-spec.coffee index 0c0fba9b0..ea92df2fe 100644 --- a/spec/language-mode-spec.coffee +++ b/spec/language-mode-spec.coffee @@ -71,7 +71,7 @@ describe "LanguageMode", -> expect(languageMode.rowRangeForCodeFoldAtBufferRow(4)).toEqual [4, 7] describe ".rowRangeForCommentAtBufferRow(bufferRow)", -> - fit "returns the start/end rows of the foldable comment starting at the given row", -> + it "returns the start/end rows of the foldable comment starting at the given row", -> buffer.setText("//this is a multi line comment\n//another line") expect(languageMode.rowRangeForCommentAtBufferRow(0)).toEqual [0, 1] expect(languageMode.rowRangeForCommentAtBufferRow(1)).toEqual [0, 1] From 597942c4ac876480738ffa498fe3c0fc6e4227e3 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 15:16:59 -0700 Subject: [PATCH 183/211] Fail focused specs on CI This ensures focused specs never end up as green builds --- spec/jasmine-helper.coffee | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/jasmine-helper.coffee b/spec/jasmine-helper.coffee index 3de6d065f..d0089316b 100644 --- a/spec/jasmine-helper.coffee +++ b/spec/jasmine-helper.coffee @@ -7,6 +7,8 @@ module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) -> {TerminalReporter} = require 'jasmine-tagged' + disableFocusMethods() if process.env.JANKY_SHA1 + TimeReporter = require './time-reporter' timeReporter = new TimeReporter() @@ -38,3 +40,10 @@ module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) -> $('body').append $$ -> @div id: 'jasmine-content' jasmineEnv.execute() + +disableFocusMethods = -> + ['fdescribe', 'ffdescribe', 'fffdescribe', 'fit', 'ffit', 'fffit'].forEach (methodName) -> + focusMethod = window[methodName] + window[methodName] = (description) -> + error = new Error('Focused spec is running on CI') + focusMethod description, -> throw error From 021278e902ce16124854631b4fc4b535051f6a1e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 15:36:31 -0700 Subject: [PATCH 184/211] Always auto indent at least one hard tab Previously when the delta between the suggested and current indent level was greater than zero but less than one, no text would be inserted since Editor::buildIndentString returns an empty string for levels less than one when using hard tabs. Closes #3575 --- spec/editor-spec.coffee | 10 ++++++++++ src/selection.coffee | 1 + 2 files changed, 11 insertions(+) diff --git a/spec/editor-spec.coffee b/spec/editor-spec.coffee index a2381b29d..f196bec78 100644 --- a/spec/editor-spec.coffee +++ b/spec/editor-spec.coffee @@ -2330,6 +2330,16 @@ describe "Editor", -> expect(buffer.lineForRow(5)).toMatch /^\t\t\t$/ expect(editor.getCursorBufferPosition()).toEqual [5, 3] + describe "when the difference between the suggested level of indentation and the current level of indentation is greater than 0 but less than 1", -> + it "inserts one tab", -> + editor.setSoftTabs(false) + buffer.setText(" \ntest") + editor.setCursorBufferPosition [1, 0] + + editor.indent(autoIndent: true) + expect(buffer.lineForRow(1)).toBe '\ttest' + expect(editor.getCursorBufferPosition()).toEqual [1, 1] + describe "when the line's indent level is greater than the suggested level of indentation", -> describe "when 'softTabs' is true (the default)", -> it "moves the cursor to the end of the leading whitespace and inserts 'tabLength' spaces into the buffer", -> diff --git a/src/selection.coffee b/src/selection.coffee index a220a49b6..71dc6834d 100644 --- a/src/selection.coffee +++ b/src/selection.coffee @@ -596,6 +596,7 @@ class Selection extends Model delta = desiredIndent - @cursor.getIndentLevel() if autoIndent and delta > 0 + delta = Math.max(delta, 1) unless @editor.getSoftTabs() @insertText(@editor.buildIndentString(delta)) else @insertText(@editor.buildIndentString(1, @cursor.getBufferColumn())) From c89bafb66e8c90b2baeb1b0a4d5210745fc989ac Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 16:24:58 -0700 Subject: [PATCH 185/211] Refresh horizontal scrollbar DOM node Previously the vertical scrollbar's DOM node was used as the horizontal node causing the horizontal scrollbar to not refresh properly Closes #3511 --- src/editor-component.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor-component.coffee b/src/editor-component.coffee index dd4095d1d..fb9d7216e 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -954,7 +954,7 @@ EditorComponent = React.createClass {verticalScrollbar, horizontalScrollbar, scrollbarCorner} = @refs verticalNode = verticalScrollbar.getDOMNode() - horizontalNode = verticalScrollbar.getDOMNode() + horizontalNode = horizontalScrollbar.getDOMNode() cornerNode = scrollbarCorner.getDOMNode() originalVerticalDisplayValue = verticalNode.style.display From 7c483f989fc95ccd2dff95dead4cd0b0673f4158 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 18 Sep 2014 16:43:00 -0700 Subject: [PATCH 186/211] Clean up marker --- src/marker.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/marker.coffee b/src/marker.coffee index de28e3f10..e54fc5248 100644 --- a/src/marker.coffee +++ b/src/marker.coffee @@ -12,7 +12,7 @@ Grim = require 'grim' # # ### Marker Creation # -# You will use {Editor::markBufferRange} rather than creating Markers directly. +# Use {Editor::markBufferRange} rather than creating Markers directly. # # ### Head and Tail # @@ -128,7 +128,7 @@ class Marker EmitterMixin::on.apply(this, arguments) ### - Section: Marker Metadata + Section: Marker Details ### # Essential: Returns a {Boolean} indicating whether the marker is valid. Markers can be @@ -143,11 +143,11 @@ class Marker isDestroyed: -> @bufferMarker.isDestroyed() - # Extended: Returns a {Boolean} indicating whether the head precedes the tail. + # Essential: Returns a {Boolean} indicating whether the head precedes the tail. isReversed: -> @bufferMarker.isReversed() - # Extended: Get the invalidation strategy for this marker. + # Essential: Get the invalidation strategy for this marker. # # Valid values include: `never`, `surround`, `overlap`, `inside`, and `touch`. # @@ -155,7 +155,7 @@ class Marker getInvalidationStrategy: -> @bufferMarker.getInvalidationStrategy() - # Extended: Returns an {Object} containing any custom properties associated with + # Essential: Returns an {Object} containing any custom properties associated with # the marker. getProperties: -> @bufferMarker.getProperties() @@ -163,7 +163,7 @@ class Marker deprecate 'Use Marker::getProperties instead' @getProperties() - # Extended: Merges an {Object} containing new properties into the marker's + # Essential: Merges an {Object} containing new properties into the marker's # existing properties. # # * `properties` {Object} From 69f54b90dc10b4b99a45aef275d476899087b340 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 18 Sep 2014 16:43:13 -0700 Subject: [PATCH 187/211] Clean up atom details section --- src/atom.coffee | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/atom.coffee b/src/atom.coffee index 04361bf36..40b52b9d6 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -226,40 +226,34 @@ class Atom extends Model @emitter.on 'did-beep', callback ### - Section: Atom Metadata + Section: Atom Details ### - # Essential: Is the current window in development mode? + # Public: Is the current window in development mode? inDevMode: -> @getLoadSettings().devMode - # Essential: Is the current window running specs? + # Public: Is the current window running specs? inSpecMode: -> @getLoadSettings().isSpec - # Essential: Get the version of the Atom application. + # Public: Get the version of the Atom application. # # Returns the version text {String}. getVersion: -> @appVersion ?= @getLoadSettings().appVersion - # Essential: Determine whether the current version is an official release. + # Public: Determine whether the current version is an official release. isReleasedVersion: -> not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix - # Essential: Get the directory path to Atom's configuration area. + # Public: Get the directory path to Atom's configuration area. # # Returns the absolute path to `~/.atom`. getConfigDirPath: -> @constructor.getConfigDirPath() - # Extended: Get the load settings for the current window. - # - # Returns an {Object} containing all the load setting key/value pairs. - getLoadSettings: -> - @constructor.getLoadSettings() - - # Extended: Get the time taken to completely load the current window. + # Public: Get the time taken to completely load the current window. # # This time include things like loading and activating packages, creating # DOM elements for the editor, and reading the config. @@ -269,6 +263,12 @@ class Atom extends Model getWindowLoadTime: -> @loadTime + # Public: Get the load settings for the current window. + # + # Returns an {Object} containing all the load setting key/value pairs. + getLoadSettings: -> + @constructor.getLoadSettings() + ### Section: Managing The Atom Window ### From 0b03c89010f9afe6e347dbfdc15446c8fe0a48ee Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 16:43:47 -0700 Subject: [PATCH 188/211] Upgrade to settings-view@0.144 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 782e98884..a42644093 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "open-on-github": "0.30.0", "package-generator": "0.31.0", "release-notes": "0.36.0", - "settings-view": "0.143.0", + "settings-view": "0.144.0", "snippets": "0.52.0", "spell-check": "0.42.0", "status-bar": "0.45.0", From e2d9e5bd743decb391c6b9ba6462f90f35393d27 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 18 Sep 2014 16:44:26 -0700 Subject: [PATCH 189/211] Make focusing other pane views from workspaceView private --- src/workspace-view.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 547d8daa9..ce4bca28f 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -303,16 +303,16 @@ class WorkspaceView extends View # Focus the next pane by id. focusNextPaneView: -> @model.activateNextPane() - # Essential: Focus the pane directly above the active pane. + # Focus the pane directly above the active pane. focusPaneViewAbove: -> @panes.focusPaneViewAbove() - # Essential: Focus the pane directly below the active pane. + # Focus the pane directly below the active pane. focusPaneViewBelow: -> @panes.focusPaneViewBelow() - # Essential: Focus the pane directly to the left of the active pane. + # Focus the pane directly to the left of the active pane. focusPaneViewOnLeft: -> @panes.focusPaneViewOnLeft() - # Essential: Focus the pane directly to the right of the active pane. + # Focus the pane directly to the right of the active pane. focusPaneViewOnRight: -> @panes.focusPaneViewOnRight() ### From d02c3e0d62960dbde74897a2848b0701bbdce9f8 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 18 Sep 2014 16:45:13 -0700 Subject: [PATCH 190/211] WorkspaceView is extended --- src/workspace-view.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index ce4bca28f..4c5c2fe3e 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -20,7 +20,7 @@ atom.commands.add '.workspace', 'window:decrease-font-size': -> @getModel().decreaseFontSize() 'window:reset-font-size': -> @getModel().resetFontSize() -# Essential: The top-level view for the entire window. An instance of this class is +# Extended: The top-level view for the entire window. An instance of this class is # available via the `atom.workspaceView` global. # # It is backed by a model object, an instance of {Workspace}, which is available From 97931ff259d538c00ef54841ad19e72704e23109 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 18 Sep 2014 16:51:30 -0700 Subject: [PATCH 191/211] Move observePaneItems and onDidChangePaneItem to essential --- src/workspace.coffee | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/workspace.coffee b/src/workspace.coffee index db97e6732..da2b7bf10 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -103,6 +103,25 @@ class Workspace extends Model callback(textEditor) for textEditor in @getTextEditors() @onDidAddTextEditor ({textEditor}) -> callback(textEditor) + # Essential: Invoke the given callback with all current and future panes items in + # the workspace. + # + # * `callback` {Function} to be called with current and future pane items. + # * `item` An item that is present in {::getPaneItems} at the time of + # subscription or that is added at some later time. + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + observePaneItems: (callback) -> @paneContainer.observePaneItems(callback) + + # Essential: Invoke the given callback when the active pane item changes. + # + # * `callback` {Function} to be called when the active pane item changes. + # * `event` {Object} with the following keys: + # * `activeItem` The active pane item. + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidChangeActivePaneItem: (callback) -> @paneContainer.onDidChangeActivePaneItem(callback) + # Essential: Invoke the given callback whenever an item is opened. Unlike # {::onDidAddPaneItem}, observers will be notified for items that are already # present in the workspace when they are reopened. @@ -167,25 +186,6 @@ class Workspace extends Model # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. onDidAddPaneItem: (callback) -> @paneContainer.onDidAddPaneItem(callback) - # Extended: Invoke the given callback when the active pane item changes. - # - # * `callback` {Function} to be called when the active pane item changes. - # * `event` {Object} with the following keys: - # * `activeItem` The active pane item. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeActivePaneItem: (callback) -> @paneContainer.onDidChangeActivePaneItem(callback) - - # Extended: Invoke the given callback with all current and future panes items in - # the workspace. - # - # * `callback` {Function} to be called with current and future pane items. - # * `item` An item that is present in {::getPaneItems} at the time of - # subscription or that is added at some later time. - # - # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - observePaneItems: (callback) -> @paneContainer.observePaneItems(callback) - # Extended: Invoke the given callback when a text editor is added to the # workspace. # From 2d3ea244ee36d22d170e8030b89c935a85826251 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 18 Sep 2014 16:51:59 -0700 Subject: [PATCH 192/211] Opener stuff is essential --- src/workspace.coffee | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/workspace.coffee b/src/workspace.coffee index da2b7bf10..11989e834 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -323,7 +323,7 @@ class Workspace extends Model .catch (error) -> console.error(error.stack ? error) - # Extended: Asynchronously reopens the last-closed item's URI if it hasn't already been + # Public: Asynchronously reopens the last-closed item's URI if it hasn't already been # reopened. # # Returns a promise that is resolved when the item is opened @@ -339,7 +339,9 @@ class Workspace extends Model if uri = @destroyedItemUris.pop() @openSync(uri) - # Extended: Register an opener for a uri. + # TODO: make ::registerOpener() return a disposable + + # Public: Register an opener for a uri. # # An {Editor} will be used if no openers return a value. # @@ -355,7 +357,7 @@ class Workspace extends Model registerOpener: (opener) -> @openers.push(opener) - # Extended: Unregister an opener registered with {::registerOpener}. + # Unregister an opener registered with {::registerOpener}. unregisterOpener: (opener) -> _.remove(@openers, opener) From f407ca3a0c15d63baa8089f63ec1d6002d37aba2 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 18 Sep 2014 16:52:11 -0700 Subject: [PATCH 193/211] saveAll is private --- src/workspace.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workspace.coffee b/src/workspace.coffee index 11989e834..76450181b 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -398,7 +398,7 @@ class Workspace extends Model getActiveEditor: -> @activePane?.getActiveEditor() - # Extended: Save all pane items. + # Save all pane items. saveAll: -> @paneContainer.saveAll() From 29f53d4432bf65da0a1a308fd672d38cf3b192b8 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 18 Sep 2014 16:52:56 -0700 Subject: [PATCH 194/211] PaneView is private --- src/pane-view.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pane-view.coffee b/src/pane-view.coffee index ddeab69af..0dca0bf6c 100644 --- a/src/pane-view.coffee +++ b/src/pane-view.coffee @@ -6,7 +6,7 @@ PropertyAccessors = require 'property-accessors' Pane = require './pane' -# Extended: A container which can contains multiple items to be switched between. +# A container which can contains multiple items to be switched between. # # Items can be almost anything however most commonly they're {EditorView}s. # From 33827d1dc890b1e079a2e5cc7ca68ba2726a804a Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 18 Sep 2014 16:59:06 -0700 Subject: [PATCH 195/211] Upgrade text-buffer for better docs --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 782e98884..093fb8e9f 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "serializable": "^1", "space-pen": "3.4.7", "temp": "0.7.0", - "text-buffer": "^3.2.5", + "text-buffer": "^3.2.6", "theorist": "^1.0.2", "underscore-plus": "^1.5.1", "vm-compatibility-layer": "0.1.0" From 9f1aabed0a3438331590365cadc7ae2120a32b22 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 18 Sep 2014 17:20:18 -0700 Subject: [PATCH 196/211] Reorg Editor a bit based on conversations Move heavier used sections near the top of the file. Make some of the single extended methods public. --- src/editor.coffee | 1091 ++++++++++++++++++++++----------------------- 1 file changed, 543 insertions(+), 548 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index fa38b5444..a4226afaa 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -545,7 +545,7 @@ class Editor extends Model atom.clipboard.write(filePath) ### - Section: Saving + Section: File Operations ### # Essential: Saves the editor's text buffer. @@ -553,14 +553,14 @@ class Editor extends Model # See {TextBuffer::save} for more details. save: -> @buffer.save() - # Extended: Saves the editor's text buffer as the given path. + # Public: Saves the editor's text buffer as the given path. # # See {TextBuffer::saveAs} for more details. # # * `filePath` A {String} path. saveAs: (filePath) -> @buffer.saveAs(filePath) - # Extended: Determine whether the user should be prompted to save before closing + # Determine whether the user should be prompted to save before closing # this editor. shouldPromptToSave: -> @isModified() and not @buffer.hasMultipleEditors() @@ -663,7 +663,7 @@ class Editor extends Model # {Delegates to: TextBuffer.getEndPosition} getEofBufferPosition: -> @buffer.getEndPosition() - # Extended: Get the {Range} of the paragraph surrounding the most recently added + # Public: Get the {Range} of the paragraph surrounding the most recently added # cursor. # # Returns a {Range}. @@ -686,6 +686,46 @@ class Editor extends Model # Returns the {Range} of the newly-inserted text. setTextInBufferRange: (range, text, normalizeLineEndings) -> @getBuffer().setTextInRange(range, text, normalizeLineEndings) + # Essential: For each selection, replace the selected text with the given text. + # + # * `text` A {String} representing the text to insert. + # * `options` (optional) See {Selection::insertText}. + # + # Returns a {Range} when the text has been inserted + # Returns a {Bool} false when the text has not been inserted + insertText: (text, options={}) -> + willInsert = true + cancel = -> willInsert = false + willInsertEvent = {cancel, text} + @emit('will-insert-text', willInsertEvent) + @emitter.emit 'will-insert-text', willInsertEvent + + if willInsert + options.autoIndentNewline ?= @shouldAutoIndent() + options.autoDecreaseIndent ?= @shouldAutoIndent() + @mutateSelectedText (selection) => + range = selection.insertText(text, options) + didInsertEvent = {text, range} + @emit('did-insert-text', didInsertEvent) + @emitter.emit 'did-insert-text', didInsertEvent + range + else + false + + # Essential: For each selection, replace the selected text with a newline. + insertNewline: -> + @insertText('\n') + + # Essential: For each selection, if the selection is empty, delete the character + # preceding the cursor. Otherwise delete the selected text. + delete: -> + @mutateSelectedText (selection) -> selection.delete() + + # Essential: For each selection, if the selection is empty, delete the character + # preceding the cursor. Otherwise delete the selected text. + backspace: -> + @mutateSelectedText (selection) -> selection.backspace() + # Extended: Mutate the text of all the selections in a single transaction. # # All the changes made inside the given {Function} can be reverted with a @@ -914,68 +954,6 @@ class Editor extends Model joinLines: -> @mutateSelectedText (selection) -> selection.joinLines() - # Extended: Batch multiple operations as a single undo/redo step. - # - # Any group of operations that are logically grouped from the perspective of - # undoing and redoing should be performed in a transaction. If you want to - # abort the transaction, call {::abortTransaction} to terminate the function's - # execution and revert any changes performed up to the abortion. - # - # * `fn` A {Function} to call inside the transaction. - transact: (fn) -> @buffer.transact(fn) - - # Extended: Start an open-ended transaction. - # - # Call {::commitTransaction} or {::abortTransaction} to terminate the - # transaction. If you nest calls to transactions, only the outermost - # transaction is considered. You must match every begin with a matching - # commit, but a single call to abort will cancel all nested transactions. - beginTransaction: -> @buffer.beginTransaction() - - # Extended: Commit an open-ended transaction started with {::beginTransaction} - # and push it to the undo stack. - # - # If transactions are nested, only the outermost commit takes effect. - commitTransaction: -> @buffer.commitTransaction() - - # Extended: Abort an open transaction, undoing any operations performed so far - # within the transaction. - abortTransaction: -> @buffer.abortTransaction() - - ### - Section: Inserting Text - ### - - # Essential: For each selection, replace the selected text with the given text. - # - # * `text` A {String} representing the text to insert. - # * `options` (optional) See {Selection::insertText}. - # - # Returns a {Range} when the text has been inserted - # Returns a {Bool} false when the text has not been inserted - insertText: (text, options={}) -> - willInsert = true - cancel = -> willInsert = false - willInsertEvent = {cancel, text} - @emit('will-insert-text', willInsertEvent) - @emitter.emit 'will-insert-text', willInsertEvent - - if willInsert - options.autoIndentNewline ?= @shouldAutoIndent() - options.autoDecreaseIndent ?= @shouldAutoIndent() - @mutateSelectedText (selection) => - range = selection.insertText(text, options) - didInsertEvent = {text, range} - @emit('did-insert-text', didInsertEvent) - @emitter.emit 'did-insert-text', didInsertEvent - range - else - false - - # Essential: For each selection, replace the selected text with a newline. - insertNewline: -> - @insertText('\n') - # Extended: For each cursor, insert a newline at beginning the following line. insertNewlineBelow: -> @transact => @@ -1000,20 +978,6 @@ class Editor extends Model @moveUp() @moveToEndOfLine() - ### - Section: Removing Text - ### - - # Essential: For each selection, if the selection is empty, delete the character - # preceding the cursor. Otherwise delete the selected text. - delete: -> - @mutateSelectedText (selection) -> selection.delete() - - # Essential: For each selection, if the selection is empty, delete the character - # preceding the cursor. Otherwise delete the selected text. - backspace: -> - @mutateSelectedText (selection) -> selection.backspace() - # Extended: For each selection, if the selection is empty, delete all characters # of the containing word that precede the cursor. Otherwise delete the # selected text. @@ -1054,222 +1018,7 @@ class Editor extends Model @deleteToBeginningOfLine() ### - Section: Searching Text - ### - - # Essential: Scan regular expression matches in the entire buffer, calling the - # given iterator function on each match. - # - # If you're programmatically modifying the results, you may want to try - # {::backwardsScanInBufferRange} to avoid tripping over your own changes. - # - # * `regex` A {RegExp} to search for. - # * `iterator` A {Function} that's called on each match with an {Object} - # containing the following keys: - # * `match` The current regular expression match. - # * `matchText` A {String} with the text of the match. - # * `range` The {Range} of the match. - # * `stop` Call this {Function} to terminate the scan. - # * `replace` Call this {Function} with a {String} to replace the match. - scan: (regex, iterator) -> @buffer.scan(regex, iterator) - - # Extended: Scan regular expression matches in a given range, calling the given - # iterator function on each match. - # - # * `regex` A {RegExp} to search for. - # * `range` A {Range} in which to search. - # * `iterator` A {Function} that's called on each match with an {Object} - # containing the following keys: - # * `match` The current regular expression match. - # * `matchText` A {String} with the text of the match. - # * `range` The {Range} of the match. - # * `stop` Call this {Function} to terminate the scan. - # * `replace` Call this {Function} with a {String} to replace the match. - scanInBufferRange: (regex, range, iterator) -> @buffer.scanInRange(regex, range, iterator) - - # Extended: Scan regular expression matches in a given range in reverse order, - # calling the given iterator function on each match. - # - # * `regex` A {RegExp} to search for. - # * `range` A {Range} in which to search. - # * `iterator` A {Function} that's called on each match with an {Object} - # containing the following keys: - # * `match` The current regular expression match. - # * `matchText` A {String} with the text of the match. - # * `range` The {Range} of the match. - # * `stop` Call this {Function} to terminate the scan. - # * `replace` Call this {Function} with a {String} to replace the match. - backwardsScanInBufferRange: (regex, range, iterator) -> @buffer.backwardsScanInRange(regex, range, iterator) - - ### - Section: Tab Behavior - ### - - # Essential: Returns a {Boolean} indicating whether softTabs are enabled for this - # editor. - getSoftTabs: -> @softTabs - - # Essential: Enable or disable soft tabs for this editor. - # - # * `softTabs` A {Boolean} - setSoftTabs: (@softTabs) -> @softTabs - - # Essential: Toggle soft tabs for this editor - toggleSoftTabs: -> @setSoftTabs(not @getSoftTabs()) - - # Essential: Get the on-screen length of tab characters. - # - # Returns a {Number}. - getTabLength: -> @displayBuffer.getTabLength() - - # Essential: Set the on-screen length of tab characters. - # - # * `tabLength` {Number} length of a single tab - setTabLength: (tabLength) -> @displayBuffer.setTabLength(tabLength) - - # Extended: Determine if the buffer uses hard or soft tabs. - # - # Returns `true` if the first non-comment line with leading whitespace starts - # with a space character. Returns `false` if it starts with a hard tab (`\t`). - # - # Returns a {Boolean} or undefined if no non-comment lines had leading - # whitespace. - usesSoftTabs: -> - for bufferRow in [0..@buffer.getLastRow()] - continue if @displayBuffer.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment() - - line = @buffer.lineForRow(bufferRow) - return true if line[0] is ' ' - return false if line[0] is '\t' - - undefined - - # Extended: Get the text representing a single level of indent. - # - # If soft tabs are enabled, the text is composed of N spaces, where N is the - # tab length. Otherwise the text is a tab character (`\t`). - # - # Returns a {String}. - getTabText: -> @buildIndentString(1) - - # If soft tabs are enabled, convert all hard tabs to soft tabs in the given - # {Range}. - normalizeTabsInBufferRange: (bufferRange) -> - return unless @getSoftTabs() - @scanInBufferRange /\t/g, bufferRange, ({replace}) => replace(@getTabText()) - - ### - Section: Soft Wrap Behavior - ### - - # Essential: Determine whether lines in this editor are soft-wrapped. - # - # Returns a {Boolean}. - isSoftWrapped: (softWrapped) -> @displayBuffer.isSoftWrapped() - getSoftWrapped: -> - deprecate("Use Editor::isSoftWrapped instead") - @displayBuffer.isSoftWrapped() - - # Essential: Enable or disable soft wrapping for this editor. - # - # * `softWrapped` A {Boolean} - # - # Returns a {Boolean}. - setSoftWrapped: (softWrapped) -> @displayBuffer.setSoftWrapped(softWrapped) - setSoftWrap: (softWrapped) -> - deprecate("Use Editor::setSoftWrapped instead") - @setSoftWrapped(softWrapped) - - # Essential: Toggle soft wrapping for this editor - # - # Returns a {Boolean}. - toggleSoftWrapped: -> @setSoftWrapped(not @isSoftWrapped()) - toggleSoftWrap: -> - deprecate("Use Editor::toggleSoftWrapped instead") - @toggleSoftWrapped() - - # Extended: Gets the column at which column will soft wrap - getSoftWrapColumn: -> @displayBuffer.getSoftWrapColumn() - - ### - Section: Indentation - ### - - # Essential: Get the indentation level of the given a buffer row. - # - # Returns how deeply the given row is indented based on the soft tabs and - # tab length settings of this editor. Note that if soft tabs are enabled and - # the tab length is 2, a row with 4 leading spaces would have an indentation - # level of 2. - # - # * `bufferRow` A {Number} indicating the buffer row. - # - # Returns a {Number}. - indentationForBufferRow: (bufferRow) -> - @indentLevelForLine(@lineTextForBufferRow(bufferRow)) - - # Essential: Set the indentation level for the given buffer row. - # - # Inserts or removes hard tabs or spaces based on the soft tabs and tab length - # settings of this editor in order to bring it to the given indentation level. - # Note that if soft tabs are enabled and the tab length is 2, a row with 4 - # leading spaces would have an indentation level of 2. - # - # * `bufferRow` A {Number} indicating the buffer row. - # * `newLevel` A {Number} indicating the new indentation level. - # * `options` (optional) An {Object} with the following keys: - # * `preserveLeadingWhitespace` `true` to preserve any whitespace already at - # the beginning of the line (default: false). - setIndentationForBufferRow: (bufferRow, newLevel, {preserveLeadingWhitespace}={}) -> - if preserveLeadingWhitespace - endColumn = 0 - else - endColumn = @lineTextForBufferRow(bufferRow).match(/^\s*/)[0].length - newIndentString = @buildIndentString(newLevel) - @buffer.setTextInRange([[bufferRow, 0], [bufferRow, endColumn]], newIndentString) - - # Extended: Indent rows intersecting selections by one level. - indentSelectedRows: -> - @mutateSelectedText (selection) -> selection.indentSelectedRows() - - # Extended: Outdent rows intersecting selections by one level. - outdentSelectedRows: -> - @mutateSelectedText (selection) -> selection.outdentSelectedRows() - - # Extended: Get the indentation level of the given line of text. - # - # Returns how deeply the given line is indented based on the soft tabs and - # tab length settings of this editor. Note that if soft tabs are enabled and - # the tab length is 2, a row with 4 leading spaces would have an indentation - # level of 2. - # - # * `line` A {String} representing a line of text. - # - # Returns a {Number}. - indentLevelForLine: (line) -> - @displayBuffer.indentLevelForLine(line) - - # Extended: Indent rows intersecting selections based on the grammar's suggested - # indent level. - autoIndentSelectedRows: -> - @mutateSelectedText (selection) -> selection.autoIndentSelectedRows() - - # Indent all lines intersecting selections. See {Selection::indent} for more - # information. - indent: (options={}) -> - options.autoIndent ?= @shouldAutoIndent() - @mutateSelectedText (selection) -> selection.indent(options) - - # Constructs the string used for tabs. - buildIndentString: (number, column=0) -> - if @getSoftTabs() - tabStopViolation = column % @getTabLength() - _.multiplyString(" ", Math.floor(number * @getTabLength()) - tabStopViolation) - else - _.multiplyString("\t", Math.floor(number)) - - ### - Section: Undo Operations + Section: History ### # Essential: Undo the last change. @@ -1282,6 +1031,34 @@ class Editor extends Model @getLastCursor().needsAutoscroll = true @buffer.redo(this) + # Extended: Batch multiple operations as a single undo/redo step. + # + # Any group of operations that are logically grouped from the perspective of + # undoing and redoing should be performed in a transaction. If you want to + # abort the transaction, call {::abortTransaction} to terminate the function's + # execution and revert any changes performed up to the abortion. + # + # * `fn` A {Function} to call inside the transaction. + transact: (fn) -> @buffer.transact(fn) + + # Extended: Start an open-ended transaction. + # + # Call {::commitTransaction} or {::abortTransaction} to terminate the + # transaction. If you nest calls to transactions, only the outermost + # transaction is considered. You must match every begin with a matching + # commit, but a single call to abort will cancel all nested transactions. + beginTransaction: -> @buffer.beginTransaction() + + # Extended: Commit an open-ended transaction started with {::beginTransaction} + # and push it to the undo stack. + # + # If transactions are nested, only the outermost commit takes effect. + commitTransaction: -> @buffer.commitTransaction() + + # Extended: Abort an open transaction, undoing any operations performed so far + # within the transaction. + abortTransaction: -> @buffer.abortTransaction() + ### Section: Editor Coordinates ### @@ -1376,256 +1153,6 @@ class Editor extends Model # Returns a {Point}. clipScreenPosition: (screenPosition, options) -> @displayBuffer.clipScreenPosition(screenPosition, options) - ### - Section: Grammars - ### - - # Essential: Get the current {Grammar} of this editor. - getGrammar: -> - @displayBuffer.getGrammar() - - # Essential: Set the current {Grammar} of this editor. - # - # Assigning a grammar will cause the editor to re-tokenize based on the new - # grammar. - # - # * `grammar` {Grammar} - setGrammar: (grammar) -> - @displayBuffer.setGrammar(grammar) - - # Reload the grammar based on the file name. - reloadGrammar: -> - @displayBuffer.reloadGrammar() - - ### - Section: Managing Syntax Scopes - ### - - # Public: Get the syntactic scopes for the most recently added cursor's - # position. See {::scopesForBufferPosition} for more information. - # - # Returns an {Array} of {String}s. - scopesAtCursor: -> @getLastCursor().getScopes() - getCursorScopes: -> - deprecate 'Use Editor::scopesAtCursor() instead' - @scopesAtCursor() - - # Essential: Get the syntactic scopes for the given position in buffer - # coordinates. - # - # For example, if called with a position inside the parameter list of an - # anonymous CoffeeScript function, the method returns the following array: - # `["source.coffee", "meta.inline.function.coffee", "variable.parameter.function.coffee"]` - # - # * `bufferPosition` A {Point} or {Array} of [row, column]. - # - # Returns an {Array} of {String}s. - scopesForBufferPosition: (bufferPosition) -> @displayBuffer.scopesForBufferPosition(bufferPosition) - - # Extended: Get the range in buffer coordinates of all tokens surrounding the - # cursor that match the given scope selector. - # - # For example, if you wanted to find the string surrounding the cursor, you - # could call `editor.bufferRangeForScopeAtCursor(".string.quoted")`. - # - # Returns a {Range}. - bufferRangeForScopeAtCursor: (selector) -> - @displayBuffer.bufferRangeForScopeAtPosition(selector, @getCursorBufferPosition()) - - logCursorScope: -> - console.log @scopesAtCursor() - - # {Delegates to: DisplayBuffer.tokenForBufferPosition} - tokenForBufferPosition: (bufferPosition) -> @displayBuffer.tokenForBufferPosition(bufferPosition) - - # Extended: Determine if the given row is entirely a comment - isBufferRowCommented: (bufferRow) -> - if match = @lineTextForBufferRow(bufferRow).match(/\S/) - scopes = @tokenForBufferPosition([bufferRow, match.index]).scopes - @commentScopeSelector ?= new TextMateScopeSelector('comment.*') - @commentScopeSelector.matches(scopes) - - ### - Section: Clipboard Operations - ### - - # Essential: For each selection, copy the selected text. - copySelectedText: -> - maintainClipboard = false - for selection in @getSelections() - selection.copy(maintainClipboard) - maintainClipboard = true - - # Essential: For each selection, cut the selected text. - cutSelectedText: -> - maintainClipboard = false - @mutateSelectedText (selection) -> - selection.cut(maintainClipboard) - maintainClipboard = true - - # Essential: For each selection, replace the selected text with the contents of - # the clipboard. - # - # If the clipboard contains the same number of selections as the current - # editor, each selection will be replaced with the content of the - # corresponding clipboard selection text. - # - # * `options` (optional) See {Selection::insertText}. - pasteText: (options={}) -> - {text, metadata} = atom.clipboard.readWithMetadata() - - containsNewlines = text.indexOf('\n') isnt -1 - - if metadata?.selections? and metadata.selections.length is @getSelections().length - @mutateSelectedText (selection, index) -> - text = metadata.selections[index] - selection.insertText(text, options) - - return - - else if atom.config.get("editor.normalizeIndentOnPaste") and metadata?.indentBasis? - if !@getLastCursor().hasPrecedingCharactersOnLine() or containsNewlines - options.indentBasis ?= metadata.indentBasis - - @insertText(text, options) - - # Extended: For each selection, if the selection is empty, cut all characters - # of the containing line following the cursor. Otherwise cut the selected - # text. - cutToEndOfLine: -> - maintainClipboard = false - @mutateSelectedText (selection) -> - selection.cutToEndOfLine(maintainClipboard) - maintainClipboard = true - - ### - Section: Folds - ### - - # Essential: Fold the most recent cursor's row based on its indentation level. - # - # The fold will extend from the nearest preceding line with a lower - # indentation level up to the nearest following row with a lower indentation - # level. - foldCurrentRow: -> - bufferRow = @bufferPositionForScreenPosition(@getCursorScreenPosition()).row - @foldBufferRow(bufferRow) - - # Essential: Unfold the most recent cursor's row by one level. - unfoldCurrentRow: -> - bufferRow = @bufferPositionForScreenPosition(@getCursorScreenPosition()).row - @unfoldBufferRow(bufferRow) - - # Essential: Fold the given row in buffer coordinates based on its indentation - # level. - # - # If the given row is foldable, the fold will begin there. Otherwise, it will - # begin at the first foldable row preceding the given row. - # - # * `bufferRow` A {Number}. - foldBufferRow: (bufferRow) -> - @languageMode.foldBufferRow(bufferRow) - - # Essential: Unfold all folds containing the given row in buffer coordinates. - # - # * `bufferRow` A {Number} - unfoldBufferRow: (bufferRow) -> - @displayBuffer.unfoldBufferRow(bufferRow) - - # Extended: For each selection, fold the rows it intersects. - foldSelectedLines: -> - selection.fold() for selection in @getSelections() - - # Extended: Fold all foldable lines. - foldAll: -> - @languageMode.foldAll() - - # Extended: Unfold all existing folds. - unfoldAll: -> - @languageMode.unfoldAll() - - # Extended: Fold all foldable lines at the given indent level. - # - # * `level` A {Number}. - foldAllAtIndentLevel: (level) -> - @languageMode.foldAllAtIndentLevel(level) - - # Extended: Determine whether the given row in buffer coordinates is foldable. - # - # A *foldable* row is a row that *starts* a row range that can be folded. - # - # * `bufferRow` A {Number} - # - # Returns a {Boolean}. - isFoldableAtBufferRow: (bufferRow) -> - @languageMode.isFoldableAtBufferRow(bufferRow) - - # Extended: Determine whether the given row in screen coordinates is foldable. - # - # A *foldable* row is a row that *starts* a row range that can be folded. - # - # * `bufferRow` A {Number} - # - # Returns a {Boolean}. - isFoldableAtScreenRow: (screenRow) -> - bufferRow = @displayBuffer.bufferRowForScreenRow(screenRow) - @isFoldableAtBufferRow(bufferRow) - - # Extended: Fold the given buffer row if it isn't currently folded, and unfold - # it otherwise. - toggleFoldAtBufferRow: (bufferRow) -> - if @isFoldedAtBufferRow(bufferRow) - @unfoldBufferRow(bufferRow) - else - @foldBufferRow(bufferRow) - - # Extended: Determine whether the most recently added cursor's row is folded. - # - # Returns a {Boolean}. - isFoldedAtCursorRow: -> - @isFoldedAtScreenRow(@getCursorScreenPosition().row) - - # Extended: Determine whether the given row in buffer coordinates is folded. - # - # * `bufferRow` A {Number} - # - # Returns a {Boolean}. - isFoldedAtBufferRow: (bufferRow) -> - @displayBuffer.isFoldedAtBufferRow(bufferRow) - - # Extended: Determine whether the given row in screen coordinates is folded. - # - # * `screenRow` A {Number} - # - # Returns a {Boolean}. - isFoldedAtScreenRow: (screenRow) -> - @displayBuffer.isFoldedAtScreenRow(screenRow) - - # TODO: Rename to foldRowRange? - createFold: (startRow, endRow) -> - @displayBuffer.createFold(startRow, endRow) - - # {Delegates to: DisplayBuffer.destroyFoldWithId} - destroyFoldWithId: (id) -> - @displayBuffer.destroyFoldWithId(id) - - # Remove any {Fold}s found that intersect the given buffer row. - destroyFoldsIntersectingBufferRange: (bufferRange) -> - for row in [bufferRange.start.row..bufferRange.end.row] - @unfoldBufferRow(row) - - # {Delegates to: DisplayBuffer.largestFoldContainingBufferRow} - largestFoldContainingBufferRow: (bufferRow) -> - @displayBuffer.largestFoldContainingBufferRow(bufferRow) - - # {Delegates to: DisplayBuffer.largestFoldStartingAtScreenRow} - largestFoldStartingAtScreenRow: (screenRow) -> - @displayBuffer.largestFoldStartingAtScreenRow(screenRow) - - # {Delegates to: DisplayBuffer.outermostFoldsForBufferRowRange} - outermostFoldsInBufferRowRange: (startRow, endRow) -> - @displayBuffer.outermostFoldsInBufferRowRange(startRow, endRow) - ### Section: Decorations ### @@ -1671,7 +1198,7 @@ class Editor extends Model decorateMarker: (marker, decorationParams) -> @displayBuffer.decorateMarker(marker, decorationParams) - # Extended: Get all the decorations within a screen row range. + # Public: Get all the decorations within a screen row range. # # * `startScreenRow` the {Number} beginning screen row # * `endScreenRow` the {Number} end screen row (inclusive) @@ -1790,10 +1317,12 @@ class Editor extends Model @displayBuffer.findMarkers(properties) # Extended: Get the {Marker} for the given marker id. + # + # * `id` {Number} id of the marker getMarker: (id) -> @displayBuffer.getMarker(id) - # Extended: Get all {Marker}s. + # Extended: Get all {Marker}s. Consider using {::findMarkers} getMarkers: -> @displayBuffer.getMarkers() @@ -2562,6 +2091,472 @@ class Editor extends Model @emit 'selection-screen-range-changed', selection @emitter.emit 'did-change-selection-range', selection + ### + Section: Searching and Replacing + ### + + # Essential: Scan regular expression matches in the entire buffer, calling the + # given iterator function on each match. + # + # `::scan` functions as the replace method as well via the `replace` + # + # If you're programmatically modifying the results, you may want to try + # {::backwardsScanInBufferRange} to avoid tripping over your own changes. + # + # * `regex` A {RegExp} to search for. + # * `iterator` A {Function} that's called on each match + # * `object` {Object} + # * `match` The current regular expression match. + # * `matchText` A {String} with the text of the match. + # * `range` The {Range} of the match. + # * `stop` Call this {Function} to terminate the scan. + # * `replace` Call this {Function} with a {String} to replace the match. + scan: (regex, iterator) -> @buffer.scan(regex, iterator) + + # Public: Scan regular expression matches in a given range, calling the given + # iterator function on each match. + # + # * `regex` A {RegExp} to search for. + # * `range` A {Range} in which to search. + # * `iterator` A {Function} that's called on each match with an {Object} + # containing the following keys: + # * `match` The current regular expression match. + # * `matchText` A {String} with the text of the match. + # * `range` The {Range} of the match. + # * `stop` Call this {Function} to terminate the scan. + # * `replace` Call this {Function} with a {String} to replace the match. + scanInBufferRange: (regex, range, iterator) -> @buffer.scanInRange(regex, range, iterator) + + # Public: Scan regular expression matches in a given range in reverse order, + # calling the given iterator function on each match. + # + # * `regex` A {RegExp} to search for. + # * `range` A {Range} in which to search. + # * `iterator` A {Function} that's called on each match with an {Object} + # containing the following keys: + # * `match` The current regular expression match. + # * `matchText` A {String} with the text of the match. + # * `range` The {Range} of the match. + # * `stop` Call this {Function} to terminate the scan. + # * `replace` Call this {Function} with a {String} to replace the match. + backwardsScanInBufferRange: (regex, range, iterator) -> @buffer.backwardsScanInRange(regex, range, iterator) + + ### + Section: Tab Behavior + ### + + # Essential: Returns a {Boolean} indicating whether softTabs are enabled for this + # editor. + getSoftTabs: -> @softTabs + + # Essential: Enable or disable soft tabs for this editor. + # + # * `softTabs` A {Boolean} + setSoftTabs: (@softTabs) -> @softTabs + + # Essential: Toggle soft tabs for this editor + toggleSoftTabs: -> @setSoftTabs(not @getSoftTabs()) + + # Essential: Get the on-screen length of tab characters. + # + # Returns a {Number}. + getTabLength: -> @displayBuffer.getTabLength() + + # Essential: Set the on-screen length of tab characters. + # + # * `tabLength` {Number} length of a single tab + setTabLength: (tabLength) -> @displayBuffer.setTabLength(tabLength) + + # Extended: Determine if the buffer uses hard or soft tabs. + # + # Returns `true` if the first non-comment line with leading whitespace starts + # with a space character. Returns `false` if it starts with a hard tab (`\t`). + # + # Returns a {Boolean} or undefined if no non-comment lines had leading + # whitespace. + usesSoftTabs: -> + for bufferRow in [0..@buffer.getLastRow()] + continue if @displayBuffer.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment() + + line = @buffer.lineForRow(bufferRow) + return true if line[0] is ' ' + return false if line[0] is '\t' + + undefined + + # Extended: Get the text representing a single level of indent. + # + # If soft tabs are enabled, the text is composed of N spaces, where N is the + # tab length. Otherwise the text is a tab character (`\t`). + # + # Returns a {String}. + getTabText: -> @buildIndentString(1) + + # If soft tabs are enabled, convert all hard tabs to soft tabs in the given + # {Range}. + normalizeTabsInBufferRange: (bufferRange) -> + return unless @getSoftTabs() + @scanInBufferRange /\t/g, bufferRange, ({replace}) => replace(@getTabText()) + + ### + Section: Soft Wrap Behavior + ### + + # Essential: Determine whether lines in this editor are soft-wrapped. + # + # Returns a {Boolean}. + isSoftWrapped: (softWrapped) -> @displayBuffer.isSoftWrapped() + getSoftWrapped: -> + deprecate("Use Editor::isSoftWrapped instead") + @displayBuffer.isSoftWrapped() + + # Essential: Enable or disable soft wrapping for this editor. + # + # * `softWrapped` A {Boolean} + # + # Returns a {Boolean}. + setSoftWrapped: (softWrapped) -> @displayBuffer.setSoftWrapped(softWrapped) + setSoftWrap: (softWrapped) -> + deprecate("Use Editor::setSoftWrapped instead") + @setSoftWrapped(softWrapped) + + # Essential: Toggle soft wrapping for this editor + # + # Returns a {Boolean}. + toggleSoftWrapped: -> @setSoftWrapped(not @isSoftWrapped()) + toggleSoftWrap: -> + deprecate("Use Editor::toggleSoftWrapped instead") + @toggleSoftWrapped() + + # Public: Gets the column at which column will soft wrap + getSoftWrapColumn: -> @displayBuffer.getSoftWrapColumn() + + ### + Section: Indentation + ### + + # Essential: Get the indentation level of the given a buffer row. + # + # Returns how deeply the given row is indented based on the soft tabs and + # tab length settings of this editor. Note that if soft tabs are enabled and + # the tab length is 2, a row with 4 leading spaces would have an indentation + # level of 2. + # + # * `bufferRow` A {Number} indicating the buffer row. + # + # Returns a {Number}. + indentationForBufferRow: (bufferRow) -> + @indentLevelForLine(@lineTextForBufferRow(bufferRow)) + + # Essential: Set the indentation level for the given buffer row. + # + # Inserts or removes hard tabs or spaces based on the soft tabs and tab length + # settings of this editor in order to bring it to the given indentation level. + # Note that if soft tabs are enabled and the tab length is 2, a row with 4 + # leading spaces would have an indentation level of 2. + # + # * `bufferRow` A {Number} indicating the buffer row. + # * `newLevel` A {Number} indicating the new indentation level. + # * `options` (optional) An {Object} with the following keys: + # * `preserveLeadingWhitespace` `true` to preserve any whitespace already at + # the beginning of the line (default: false). + setIndentationForBufferRow: (bufferRow, newLevel, {preserveLeadingWhitespace}={}) -> + if preserveLeadingWhitespace + endColumn = 0 + else + endColumn = @lineTextForBufferRow(bufferRow).match(/^\s*/)[0].length + newIndentString = @buildIndentString(newLevel) + @buffer.setTextInRange([[bufferRow, 0], [bufferRow, endColumn]], newIndentString) + + # Extended: Indent rows intersecting selections by one level. + indentSelectedRows: -> + @mutateSelectedText (selection) -> selection.indentSelectedRows() + + # Extended: Outdent rows intersecting selections by one level. + outdentSelectedRows: -> + @mutateSelectedText (selection) -> selection.outdentSelectedRows() + + # Extended: Get the indentation level of the given line of text. + # + # Returns how deeply the given line is indented based on the soft tabs and + # tab length settings of this editor. Note that if soft tabs are enabled and + # the tab length is 2, a row with 4 leading spaces would have an indentation + # level of 2. + # + # * `line` A {String} representing a line of text. + # + # Returns a {Number}. + indentLevelForLine: (line) -> + @displayBuffer.indentLevelForLine(line) + + # Extended: Indent rows intersecting selections based on the grammar's suggested + # indent level. + autoIndentSelectedRows: -> + @mutateSelectedText (selection) -> selection.autoIndentSelectedRows() + + # Indent all lines intersecting selections. See {Selection::indent} for more + # information. + indent: (options={}) -> + options.autoIndent ?= @shouldAutoIndent() + @mutateSelectedText (selection) -> selection.indent(options) + + # Constructs the string used for tabs. + buildIndentString: (number, column=0) -> + if @getSoftTabs() + tabStopViolation = column % @getTabLength() + _.multiplyString(" ", Math.floor(number * @getTabLength()) - tabStopViolation) + else + _.multiplyString("\t", Math.floor(number)) + + ### + Section: Grammars + ### + + # Essential: Get the current {Grammar} of this editor. + getGrammar: -> + @displayBuffer.getGrammar() + + # Essential: Set the current {Grammar} of this editor. + # + # Assigning a grammar will cause the editor to re-tokenize based on the new + # grammar. + # + # * `grammar` {Grammar} + setGrammar: (grammar) -> + @displayBuffer.setGrammar(grammar) + + # Reload the grammar based on the file name. + reloadGrammar: -> + @displayBuffer.reloadGrammar() + + ### + Section: Managing Syntax Scopes + ### + + # Public: Get the syntactic scopes for the most recently added cursor's + # position. See {::scopesForBufferPosition} for more information. + # + # Returns an {Array} of {String}s. + scopesAtCursor: -> @getLastCursor().getScopes() + getCursorScopes: -> + deprecate 'Use Editor::scopesAtCursor() instead' + @scopesAtCursor() + + # Essential: Get the syntactic scopes for the given position in buffer + # coordinates. + # + # For example, if called with a position inside the parameter list of an + # anonymous CoffeeScript function, the method returns the following array: + # `["source.coffee", "meta.inline.function.coffee", "variable.parameter.function.coffee"]` + # + # * `bufferPosition` A {Point} or {Array} of [row, column]. + # + # Returns an {Array} of {String}s. + scopesForBufferPosition: (bufferPosition) -> @displayBuffer.scopesForBufferPosition(bufferPosition) + + # Extended: Get the range in buffer coordinates of all tokens surrounding the + # cursor that match the given scope selector. + # + # For example, if you wanted to find the string surrounding the cursor, you + # could call `editor.bufferRangeForScopeAtCursor(".string.quoted")`. + # + # Returns a {Range}. + bufferRangeForScopeAtCursor: (selector) -> + @displayBuffer.bufferRangeForScopeAtPosition(selector, @getCursorBufferPosition()) + + logCursorScope: -> + console.log @scopesAtCursor() + + # {Delegates to: DisplayBuffer.tokenForBufferPosition} + tokenForBufferPosition: (bufferPosition) -> @displayBuffer.tokenForBufferPosition(bufferPosition) + + # Extended: Determine if the given row is entirely a comment + isBufferRowCommented: (bufferRow) -> + if match = @lineTextForBufferRow(bufferRow).match(/\S/) + scopes = @tokenForBufferPosition([bufferRow, match.index]).scopes + @commentScopeSelector ?= new TextMateScopeSelector('comment.*') + @commentScopeSelector.matches(scopes) + + ### + Section: Clipboard Operations + ### + + # Essential: For each selection, copy the selected text. + copySelectedText: -> + maintainClipboard = false + for selection in @getSelections() + selection.copy(maintainClipboard) + maintainClipboard = true + + # Essential: For each selection, cut the selected text. + cutSelectedText: -> + maintainClipboard = false + @mutateSelectedText (selection) -> + selection.cut(maintainClipboard) + maintainClipboard = true + + # Essential: For each selection, replace the selected text with the contents of + # the clipboard. + # + # If the clipboard contains the same number of selections as the current + # editor, each selection will be replaced with the content of the + # corresponding clipboard selection text. + # + # * `options` (optional) See {Selection::insertText}. + pasteText: (options={}) -> + {text, metadata} = atom.clipboard.readWithMetadata() + + containsNewlines = text.indexOf('\n') isnt -1 + + if metadata?.selections? and metadata.selections.length is @getSelections().length + @mutateSelectedText (selection, index) -> + text = metadata.selections[index] + selection.insertText(text, options) + + return + + else if atom.config.get("editor.normalizeIndentOnPaste") and metadata?.indentBasis? + if !@getLastCursor().hasPrecedingCharactersOnLine() or containsNewlines + options.indentBasis ?= metadata.indentBasis + + @insertText(text, options) + + # Public: For each selection, if the selection is empty, cut all characters + # of the containing line following the cursor. Otherwise cut the selected + # text. + cutToEndOfLine: -> + maintainClipboard = false + @mutateSelectedText (selection) -> + selection.cutToEndOfLine(maintainClipboard) + maintainClipboard = true + + ### + Section: Folds + ### + + # Essential: Fold the most recent cursor's row based on its indentation level. + # + # The fold will extend from the nearest preceding line with a lower + # indentation level up to the nearest following row with a lower indentation + # level. + foldCurrentRow: -> + bufferRow = @bufferPositionForScreenPosition(@getCursorScreenPosition()).row + @foldBufferRow(bufferRow) + + # Essential: Unfold the most recent cursor's row by one level. + unfoldCurrentRow: -> + bufferRow = @bufferPositionForScreenPosition(@getCursorScreenPosition()).row + @unfoldBufferRow(bufferRow) + + # Essential: Fold the given row in buffer coordinates based on its indentation + # level. + # + # If the given row is foldable, the fold will begin there. Otherwise, it will + # begin at the first foldable row preceding the given row. + # + # * `bufferRow` A {Number}. + foldBufferRow: (bufferRow) -> + @languageMode.foldBufferRow(bufferRow) + + # Essential: Unfold all folds containing the given row in buffer coordinates. + # + # * `bufferRow` A {Number} + unfoldBufferRow: (bufferRow) -> + @displayBuffer.unfoldBufferRow(bufferRow) + + # Extended: For each selection, fold the rows it intersects. + foldSelectedLines: -> + selection.fold() for selection in @getSelections() + + # Extended: Fold all foldable lines. + foldAll: -> + @languageMode.foldAll() + + # Extended: Unfold all existing folds. + unfoldAll: -> + @languageMode.unfoldAll() + + # Extended: Fold all foldable lines at the given indent level. + # + # * `level` A {Number}. + foldAllAtIndentLevel: (level) -> + @languageMode.foldAllAtIndentLevel(level) + + # Extended: Determine whether the given row in buffer coordinates is foldable. + # + # A *foldable* row is a row that *starts* a row range that can be folded. + # + # * `bufferRow` A {Number} + # + # Returns a {Boolean}. + isFoldableAtBufferRow: (bufferRow) -> + @languageMode.isFoldableAtBufferRow(bufferRow) + + # Extended: Determine whether the given row in screen coordinates is foldable. + # + # A *foldable* row is a row that *starts* a row range that can be folded. + # + # * `bufferRow` A {Number} + # + # Returns a {Boolean}. + isFoldableAtScreenRow: (screenRow) -> + bufferRow = @displayBuffer.bufferRowForScreenRow(screenRow) + @isFoldableAtBufferRow(bufferRow) + + # Extended: Fold the given buffer row if it isn't currently folded, and unfold + # it otherwise. + toggleFoldAtBufferRow: (bufferRow) -> + if @isFoldedAtBufferRow(bufferRow) + @unfoldBufferRow(bufferRow) + else + @foldBufferRow(bufferRow) + + # Extended: Determine whether the most recently added cursor's row is folded. + # + # Returns a {Boolean}. + isFoldedAtCursorRow: -> + @isFoldedAtScreenRow(@getCursorScreenPosition().row) + + # Extended: Determine whether the given row in buffer coordinates is folded. + # + # * `bufferRow` A {Number} + # + # Returns a {Boolean}. + isFoldedAtBufferRow: (bufferRow) -> + @displayBuffer.isFoldedAtBufferRow(bufferRow) + + # Extended: Determine whether the given row in screen coordinates is folded. + # + # * `screenRow` A {Number} + # + # Returns a {Boolean}. + isFoldedAtScreenRow: (screenRow) -> + @displayBuffer.isFoldedAtScreenRow(screenRow) + + # TODO: Rename to foldRowRange? + createFold: (startRow, endRow) -> + @displayBuffer.createFold(startRow, endRow) + + # {Delegates to: DisplayBuffer.destroyFoldWithId} + destroyFoldWithId: (id) -> + @displayBuffer.destroyFoldWithId(id) + + # Remove any {Fold}s found that intersect the given buffer row. + destroyFoldsIntersectingBufferRange: (bufferRange) -> + for row in [bufferRange.start.row..bufferRange.end.row] + @unfoldBufferRow(row) + + # {Delegates to: DisplayBuffer.largestFoldContainingBufferRow} + largestFoldContainingBufferRow: (bufferRow) -> + @displayBuffer.largestFoldContainingBufferRow(bufferRow) + + # {Delegates to: DisplayBuffer.largestFoldStartingAtScreenRow} + largestFoldStartingAtScreenRow: (screenRow) -> + @displayBuffer.largestFoldStartingAtScreenRow(screenRow) + + # {Delegates to: DisplayBuffer.outermostFoldsForBufferRowRange} + outermostFoldsInBufferRowRange: (startRow, endRow) -> + @displayBuffer.outermostFoldsInBufferRowRange(startRow, endRow) ### Section: Scrolling the Editor From 7632e5dd40361d05c48b5a507c9480115096fe06 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 18 Sep 2014 17:24:24 -0700 Subject: [PATCH 197/211] Reorg decoration --- src/decoration.coffee | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/decoration.coffee b/src/decoration.coffee index 1728ce225..45e265044 100644 --- a/src/decoration.coffee +++ b/src/decoration.coffee @@ -59,6 +59,19 @@ class Decoration @markerDestroyDisposable = @marker.onDidDestroy => @destroy() + # Essential: Destroy this marker. + # + # If you own the marker, you should use {Marker::destroy} which will destroy + # this decoration. + destroy: -> + return if @destroyed + @markerDestroyDisposable.dispose() + @markerDestroyDisposable = null + @destroyed = true + @emit 'destroyed' + @emitter.emit 'did-destroy' + @emitter.dispose() + ### Section: Event Subscription ### @@ -83,7 +96,7 @@ class Decoration @emitter.on 'did-destroy', callback ### - Section: Methods + Section: Decoration Details ### # Essential: An id unique across all {Decoration} objects @@ -102,6 +115,10 @@ class Decoration isType: (type) -> Decoration.isType(@properties, type) + ### + Section: Properties + ### + # Essential: Returns the {Decoration}'s properties. getProperties: -> @properties @@ -129,18 +146,9 @@ class Decoration Grim.deprecate 'Use Decoration::setProperties instead' @setProperties(newProperties) - # Essential: Destroy this marker. - # - # If you own the marker, you should use {Marker::destroy} which will destroy - # this decoration. - destroy: -> - return if @destroyed - @markerDestroyDisposable.dispose() - @markerDestroyDisposable = null - @destroyed = true - @emit 'destroyed' - @emitter.emit 'did-destroy' - @emitter.dispose() + ### + Section: Private methods + ### matchesPattern: (decorationPattern) -> return false unless decorationPattern? From b1f48338cdbe2ed1e1978d76f6c0279bd572c9fe Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 18 Sep 2014 17:26:46 -0700 Subject: [PATCH 198/211] Upgrade to apm 0.95 --- apm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm/package.json b/apm/package.json index 4a04cd456..5f8f18f6e 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "0.94.0" + "atom-package-manager": "0.95.0" } } From 2af699f35ead0737d6b6246f6526173c6562fc14 Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 18 Sep 2014 17:27:08 -0700 Subject: [PATCH 199/211] Rename cursor sections --- src/cursor.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cursor.coffee b/src/cursor.coffee index 1848182db..112065ff1 100644 --- a/src/cursor.coffee +++ b/src/cursor.coffee @@ -166,7 +166,7 @@ class Cursor extends Model @getBufferPosition().isEqual(@getCurrentLineBufferRange().end) ### - Section: Info about the cursor position + Section: Cursor Position Details ### # Public: Identifies if the cursor is surrounded by whitespace. @@ -412,7 +412,7 @@ class Cursor extends Model @setBufferPosition(position) ### - Section: Retrieving Positions and Ranges of local boundaries + Section: Local Positions and Ranges ### # Public: Retrieves the buffer position of where the current word starts. From e5096d8190aedc096a65547cef53369f096bfd3b Mon Sep 17 00:00:00 2001 From: Ben Ogle Date: Thu, 18 Sep 2014 18:40:46 -0700 Subject: [PATCH 200/211] Data -> Details --- src/git.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/git.coffee b/src/git.coffee index 4093f8890..339debf3b 100644 --- a/src/git.coffee +++ b/src/git.coffee @@ -151,7 +151,7 @@ class Git EmitterMixin::on.apply(this, arguments) ### - Section: Repository Data + Section: Repository Details ### # Public: Returns the {String} path of the repository. From 9e686c11e42eefe8c3720108d1c45f4f0c91200d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 19 Sep 2014 09:29:45 -0700 Subject: [PATCH 201/211] Upgrade to apm 0.96 --- apm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm/package.json b/apm/package.json index 5f8f18f6e..5c1b859aa 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "0.95.0" + "atom-package-manager": "0.96.0" } } From 17838f832d833440efe91c61140a26fab5e5b203 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 19 Sep 2014 09:36:05 -0700 Subject: [PATCH 202/211] :memo: Add installing on debian steps --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 74caf6502..25c61f643 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,17 @@ You can also download a `.zip` file from the [releases page](https://github.com/ The Windows version does not currently automatically update so you will need to manually upgrade to future releases by re-downloading the `.zip` file. +### Debian Linux (Ubuntu) + +Currently only a 64-bit version is available. + +1. Download `atom-amd64.deb` from the [Atom releases page](https://github.com/atom/atom/releases/latest). +2. Run `sudo dpkg --install atom-amd64.deb` on the downloaded package. +3. Launch Atom using the installed `atom` command. + +The Linux version does not currently automatically update so you will need to +repeat these steps to upgrade to future releases. + ## Building * [Linux](docs/build-instructions/linux.md) From 8b651328d2ea288276f99bfe1f7cceefbc292933 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 19 Sep 2014 09:45:25 -0700 Subject: [PATCH 203/211] Upgrade to settings-view@0.145 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 376536f33..953927690 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "open-on-github": "0.30.0", "package-generator": "0.31.0", "release-notes": "0.36.0", - "settings-view": "0.144.0", + "settings-view": "0.145.0", "snippets": "0.52.0", "spell-check": "0.42.0", "status-bar": "0.45.0", From d6842dc8a2989de2ba141e3076878ed61057f421 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 19 Sep 2014 09:56:11 -0700 Subject: [PATCH 204/211] Upgrade to language-xml@0.21 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 953927690..fa73a7a43 100644 --- a/package.json +++ b/package.json @@ -138,7 +138,7 @@ "language-text": "0.6.0", "language-todo": "0.11.0", "language-toml": "0.12.0", - "language-xml": "0.20.0", + "language-xml": "0.21.0", "language-yaml": "0.17.0" }, "private": true, From bac99222f312a672d854fdf4b3f5ddc0c7211048 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 19 Sep 2014 15:09:43 -0600 Subject: [PATCH 205/211] Force scrollbars to be GPU layers when acceleration is enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #3559 For some reason, Chromium 37 is not compositing scrollbars correctly on secondary monitors in OS X. They’re invisible when the lines layer extends beneath the scrollbars unless we apply this style. --- src/editor-component.coffee | 2 ++ src/scrollbar-component.coffee | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/editor-component.coffee b/src/editor-component.coffee index fb9d7216e..90de158d5 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -128,6 +128,7 @@ EditorComponent = React.createClass scrollableInOppositeDirection: verticallyScrollable verticalScrollbarWidth: verticalScrollbarWidth horizontalScrollbarHeight: horizontalScrollbarHeight + useHardwareAcceleration: @useHardwareAcceleration ScrollbarComponent ref: 'verticalScrollbar' @@ -140,6 +141,7 @@ EditorComponent = React.createClass scrollableInOppositeDirection: horizontallyScrollable verticalScrollbarWidth: verticalScrollbarWidth horizontalScrollbarHeight: horizontalScrollbarHeight + useHardwareAcceleration: @useHardwareAcceleration # Also used to measure the height/width of scrollbars after the initial render ScrollbarCornerComponent diff --git a/src/scrollbar-component.coffee b/src/scrollbar-component.coffee index 4a494da5e..1e23d686c 100644 --- a/src/scrollbar-component.coffee +++ b/src/scrollbar-component.coffee @@ -9,9 +9,11 @@ ScrollbarComponent = React.createClass render: -> {orientation, className, scrollHeight, scrollWidth, visible} = @props {scrollableInOppositeDirection, horizontalScrollbarHeight, verticalScrollbarWidth} = @props + {useHardwareAcceleration} = @props style = {} style.display = 'none' unless visible + style.transform = 'translateZ(0)' if useHardwareAcceleration # See atom/atom#3559 switch orientation when 'vertical' style.width = verticalScrollbarWidth From 481653ff60482a393434b3a222cfbd108da508ca Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 22 Sep 2014 10:26:24 -0700 Subject: [PATCH 206/211] :apple: Insert newline with alt-enter and shift-enter Closes #3588 --- keymaps/darwin.cson | 2 ++ 1 file changed, 2 insertions(+) diff --git a/keymaps/darwin.cson b/keymaps/darwin.cson index 34f977209..21bdeeb63 100644 --- a/keymaps/darwin.cson +++ b/keymaps/darwin.cson @@ -143,6 +143,8 @@ # Sublime Parity 'cmd-enter': 'editor:newline-below' 'cmd-shift-enter': 'editor:newline-above' + 'alt-enter': 'editor:newline' + 'shift-enter': 'editor:newline' 'cmd-]': 'editor:indent-selected-rows' 'cmd-[': 'editor:outdent-selected-rows' 'ctrl-cmd-up': 'editor:move-line-up' From c94b03f13de06738ab4488b7d6233a271d2daa8a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 22 Sep 2014 10:33:29 -0700 Subject: [PATCH 207/211] Run mkdeb task right before publishing --- build/Gruntfile.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index 7e70d9753..aedb8c179 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -229,8 +229,8 @@ module.exports = (grunt) -> ciTasks = ['output-disk-space', 'download-atom-shell', 'build'] ciTasks.push('dump-symbols') if process.platform isnt 'win32' - ciTasks.push('mkdeb') if process.platform is 'linux' ciTasks.push('set-version', 'check-licenses', 'lint') + ciTasks.push('mkdeb') if process.platform is 'linux' ciTasks.push('test') if process.platform isnt 'linux' ciTasks.push('codesign', 'publish-build') grunt.registerTask('ci', ciTasks) From 08871989e290cf7406ebab0e9182f2dd776895eb Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 22 Sep 2014 10:35:52 -0700 Subject: [PATCH 208/211] Correct process arch value --- build/tasks/publish-build-task.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/tasks/publish-build-task.coffee b/build/tasks/publish-build-task.coffee index b1d8a5802..50f7772d8 100644 --- a/build/tasks/publish-build-task.coffee +++ b/build/tasks/publish-build-task.coffee @@ -59,7 +59,7 @@ getAssets = -> when 'linux' buildDir = grunt.config.get('atom.buildDir') sourcePath = fs.listSync(buildDir, ['.deb'])[0] - if process.arch is 'ia32g' + if process.arch is 'ia32' arch = 'i386' else arch = 'amd64' From ec0bcd90a6d7521bc47ff083b6e32af4268b813d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 22 Sep 2014 10:38:56 -0700 Subject: [PATCH 209/211] Upload correct verison of .deb file --- build/tasks/publish-build-task.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/tasks/publish-build-task.coffee b/build/tasks/publish-build-task.coffee index 50f7772d8..82dedd095 100644 --- a/build/tasks/publish-build-task.coffee +++ b/build/tasks/publish-build-task.coffee @@ -58,11 +58,12 @@ getAssets = -> ] when 'linux' buildDir = grunt.config.get('atom.buildDir') - sourcePath = fs.listSync(buildDir, ['.deb'])[0] if process.arch is 'ia32' arch = 'i386' else arch = 'amd64' + {version} = grunt.file.readJSON('package.json') + sourcePath = "#{buildDir}/atom-#{version}-#{arch}.deb" assetName = "atom-#{arch}.deb" {cp} = require('./task-helpers')(grunt) From 9eed8a206ae1cb702592b519be4b67c586528146 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 22 Sep 2014 10:47:56 -0700 Subject: [PATCH 210/211] Upgrade to first-mate@2.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fa73a7a43..29a9e8677 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "delegato": "^1", "emissary": "^1.3.1", "event-kit": "0.7.2", - "first-mate": "^2.1.1", + "first-mate": "^2.1.2", "fs-plus": "^2.2.6", "fstream": "0.1.24", "fuzzaldrin": "^2.1", From 39d3724860160fcb6723ee709bf5664302c71bf1 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 22 Sep 2014 10:57:42 -0700 Subject: [PATCH 211/211] Handle error events in spawned processes Refs #3600 --- build/tasks/task-helpers.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/tasks/task-helpers.coffee b/build/tasks/task-helpers.coffee index a25f9ea31..a862f291f 100644 --- a/build/tasks/task-helpers.coffee +++ b/build/tasks/task-helpers.coffee @@ -53,8 +53,9 @@ module.exports = (grunt) -> proc = childProcess.spawn(options.cmd, options.args, options.opts) proc.stdout.on 'data', (data) -> stdout.push(data.toString()) proc.stderr.on 'data', (data) -> stderr.push(data.toString()) + proc.on 'error', (processError) -> error ?= processError proc.on 'close', (exitCode, signal) -> - error = new Error(signal) if exitCode != 0 + error ?= new Error(signal) if exitCode != 0 results = {stderr: stderr.join(''), stdout: stdout.join(''), code: exitCode} grunt.log.error results.stderr if exitCode != 0 callback(error, results, exitCode)