Merge pull request #239 from github/dev

Merge dev into master
This commit is contained in:
Kevin Sawicki
2013-02-11 17:47:36 -08:00
198 changed files with 3191 additions and 2680 deletions

View File

@@ -20,3 +20,4 @@
* New packages go in `src/packages/`
* Add 3rd-party packages by submoduling in `vendor/packages/`
* Commit messages are in the present tense
* Files end with a newline

View File

@@ -27,30 +27,39 @@ A basic ~/.atom directory is installed when you run `rake install`. Take a look
Atom doesn't have much in the way of menus yet. Use these keyboard shortcuts to
explore features.
`cmd-o` : open file/directory
`meta-o` : open file/directory
`cmd-n` : new window
`meta-n` : new window
`cmd-t` : open fuzzy file finder
`meta-t` : open fuzzy file finder
`cmd-:` : open command prompt
`meta-:` : open command prompt
`cmd-f` : open command prompt with /
`meta-f` : open command prompt with /
`cmd-g` : repeat the last search
`meta-g` : repeat the last search
`cmd-r` : reload the current window
`meta-r` : reload the current window
`cmd-alt-ctrl-s` : run specs
`meta-alt-ctrl-s` : run specs
`cmd-alt-arrows` : split screen in direction of arrow
`meta-alt-arrows` : split screen in direction of arrow
`cmd-alt-w` : toggle word wrap
`meta-alt-w` : toggle word wrap
`cmd-alt-f` : fold selected lines
`meta-alt-f` : fold selected lines
`meta-l` : go to line
Most default OS X keybindings also work.
## TreeView Keyboard shortcuts
With the treeview focused:
`a` : Add a new file or directory. Directories end with '/'.
`m` : Rename a file or directory
## Init Script
Atom will require `~/.atom/user.coffee` whenever a window is opened or reloaded if it is present in your

View File

@@ -25,7 +25,7 @@ task "bootstrap" do
end
desc "Creates symlink from `application_path() to /Applications/Atom and creates `atom` cli app"
task :install => :build do
task :install => [:clean, :build] do
path = application_path()
exit 1 if not path
@@ -109,6 +109,7 @@ task :clean do
output = `xcodebuild clean`
`rm -rf #{application_path()}`
`rm -rf #{BUILD_DIR}`
`rm -rf /tmp/atom-compiled-scripts`
end
desc "Run Atom"

View File

@@ -10,7 +10,7 @@ require 'window'
requireStylesheet "jasmine.css"
# Load TextMate bundles, which specs rely on (but not other packages)
atom.loadPackages(atom.getAvailableTextMateBundles())
atom.loadTextMatePackages()
beforeEach ->
# reset config after each benchmark; don't load or save from/to `config.json`

View File

@@ -17,9 +17,15 @@ describe "editor.", ->
editor = rootView.getActiveEditor()
afterEach ->
$(window).off 'beforeunload'
window.shutdown()
atom.setRootViewStateForPath(rootView.project.getPath(), null)
if editor.pendingDisplayUpdate
waitsFor "editor to finish rendering", (done) ->
editor.on 'editor:display-updated', done
runs ->
projectPath = rootView.project.getPath()
$(window).off 'beforeunload'
window.shutdown()
atom.setRootViewStateForPath(projectPath, null)
describe "keymap.", ->
event = null

View File

@@ -17,8 +17,9 @@
AtomCefClient::AtomCefClient(){
}
AtomCefClient::AtomCefClient(bool handlePasteboardCommands) {
AtomCefClient::AtomCefClient(bool handlePasteboardCommands, bool ignoreTitleChanges) {
m_HandlePasteboardCommands = handlePasteboardCommands;
m_IgnoreTitleChanges = ignoreTitleChanges;
}
AtomCefClient::~AtomCefClient() {

View File

@@ -16,7 +16,7 @@ class AtomCefClient : public CefClient,
public CefRequestHandler {
public:
AtomCefClient();
AtomCefClient(bool handlePasteboardCommands);
AtomCefClient(bool handlePasteboardCommands, bool ignoreTitleChanges);
virtual ~AtomCefClient();
CefRefPtr<CefBrowser> GetBrowser() { return m_Browser; }
@@ -103,6 +103,7 @@ class AtomCefClient : public CefClient,
protected:
CefRefPtr<CefBrowser> m_Browser;
bool m_HandlePasteboardCommands = false;
bool m_IgnoreTitleChanges = false;
void FocusNextWindow();
void FocusPreviousWindow();

View File

@@ -99,6 +99,8 @@ void AtomCefClient::Confirm(int replyId,
void AtomCefClient::OnTitleChange(CefRefPtr<CefBrowser> browser, const CefString& title) {
if (m_IgnoreTitleChanges) return;
NSWindow *window = [browser->GetHost()->GetWindowHandle() window];
[window setTitle:[NSString stringWithUTF8String:title.ToString().c_str()]];
}

View File

@@ -146,7 +146,7 @@
[_splitView addSubview:_devToolsView];
[_splitView adjustSubviews];
_cefDevToolsClient = new AtomCefClient(true);
_cefDevToolsClient = new AtomCefClient(true, true);
std::string devtools_url = _cefClient->GetBrowser()->GetHost()->GetDevToolsURL(true);
[self addBrowserToView:_devToolsView url:devtools_url.c_str() cefHandler:_cefDevToolsClient];
}

Binary file not shown.

View File

@@ -485,6 +485,11 @@ namespace v8_extensions {
};
}
CefRefPtr<CefV8Value> currentWorkingDirectory = options->GetValue("cwd");
if (!currentWorkingDirectory->IsUndefined() && !currentWorkingDirectory->IsNull()) {
[task setCurrentDirectoryPath:stringFromCefV8Value(currentWorkingDirectory)];
}
[task launch];
return true;

View File

@@ -0,0 +1,67 @@
RootView = require 'root-view'
AtomPackage = require 'atom-package'
fs = require 'fs'
describe "AtomPackage", ->
describe ".load()", ->
afterEach ->
rootView.deactivate()
describe "when the package metadata includes activation events", ->
[packageMainModule, pack] = []
beforeEach ->
new RootView(fixturesProject.getPath())
pack = new AtomPackage(fs.resolve(config.packageDirPaths..., 'package-with-activation-events'))
packageMainModule = require 'fixtures/packages/package-with-activation-events/main'
spyOn(packageMainModule, 'activate').andCallThrough()
pack.load()
it "defers activating the package until an activation event bubbles to the root view", ->
expect(packageMainModule.activate).not.toHaveBeenCalled()
rootView.trigger 'activation-event'
expect(packageMainModule.activate).toHaveBeenCalled()
it "triggers the activation event on all handlers registered during activation", ->
rootView.open('sample.js')
editor = rootView.getActiveEditor()
eventHandler = jasmine.createSpy("activation-event")
editor.command 'activation-event', eventHandler
editor.trigger 'activation-event'
expect(packageMainModule.activate.callCount).toBe 1
expect(packageMainModule.activationEventCallCount).toBe 1
expect(eventHandler.callCount).toBe 1
editor.trigger 'activation-event'
expect(packageMainModule.activationEventCallCount).toBe 2
expect(eventHandler.callCount).toBe 2
expect(packageMainModule.activate.callCount).toBe 1
describe "when the package does not specify a main module", ->
describe "when the package has an index.coffee", ->
it "uses index.coffee as the main module", ->
new RootView(fixturesProject.getPath())
pack = new AtomPackage(fs.resolve(config.packageDirPaths..., 'package-with-module'))
packageMainModule = require 'fixtures/packages/package-with-module'
spyOn(packageMainModule, 'activate').andCallThrough()
expect(packageMainModule.activate).not.toHaveBeenCalled()
pack.load()
expect(packageMainModule.activate).toHaveBeenCalled()
describe "when the package doesn't have an index.coffee", ->
it "does not throw an exception or log an error", ->
spyOn(console, "error")
spyOn(console, "warn")
new RootView(fixturesProject.getPath())
pack = new AtomPackage(fs.resolve(config.packageDirPaths..., 'package-with-keymaps-manifest'))
expect(-> pack.load()).not.toThrow()
expect(console.error).not.toHaveBeenCalled()
expect(console.warn).not.toHaveBeenCalled()
describe "when a package is activated", ->
it "loads config defaults based on the `configDefaults` key", ->
expect(config.get('package-with-module.numbers.one')).toBeUndefined()
atom.loadPackage("package-with-module")
expect(config.get('package-with-module.numbers.one')).toBe 1
expect(config.get('package-with-module.numbers.two')).toBe 2

View File

@@ -2,11 +2,16 @@ RootView = require 'root-view'
{$$} = require 'space-pen'
describe "the `atom` global", ->
beforeEach ->
new RootView
afterEach ->
rootView.deactivate()
describe ".loadPackage(name)", ->
[extension, stylesheetPath] = []
beforeEach ->
rootView = new RootView
extension = require "package-with-module"
stylesheetPath = require.resolve("fixtures/packages/package-with-module/stylesheets/styles.css")
@@ -14,9 +19,9 @@ describe "the `atom` global", ->
removeStylesheet(stylesheetPath)
it "requires and activates the package's main module if it exists", ->
spyOn(rootView, 'activatePackage').andCallThrough()
spyOn(atom, 'activateAtomPackage').andCallThrough()
atom.loadPackage("package-with-module")
expect(rootView.activatePackage).toHaveBeenCalled()
expect(atom.activateAtomPackage).toHaveBeenCalled()
it "logs warning instead of throwing an exception if a package fails to load", ->
config.set("core.disabledPackages", [])
@@ -70,6 +75,7 @@ describe "the `atom` global", ->
syntax.on 'grammars-loaded', eventHandler
disabledPackages = config.get("core.disabledPackages")
disabledPackages.push('textmate-package.tmbundle')
disabledPackages.push('package-with-snippets')
config.set "core.disabledPackages", disabledPackages
atom.loadPackages()
@@ -78,3 +84,61 @@ describe "the `atom` global", ->
runs ->
expect(Worker.prototype.terminate).toHaveBeenCalled()
expect(Worker.prototype.terminate.calls.length).toBe 1
describe "package lifecycle", ->
[pack, packageModule] = []
beforeEach ->
pack =
name: "package"
packageMain:
activate: jasmine.createSpy("activate")
deactivate: ->
serialize: -> "it worked"
packageModule = pack.packageMain
describe ".activateAtomPackage(package)", ->
it "calls activate on the package", ->
atom.activateAtomPackage(pack)
expect(packageModule.activate).toHaveBeenCalledWith(undefined)
it "calls activate on the package module with its previous state", ->
atom.activateAtomPackage(pack)
packageModule.activate.reset()
serializedState = rootView.serialize()
rootView.deactivate()
RootView.deserialize(serializedState)
atom.activateAtomPackage(pack)
expect(packageModule.activate).toHaveBeenCalledWith("it worked")
describe ".deactivateAtomPackages()", ->
it "deactivates and removes the package module from the package module map", ->
atom.activateAtomPackage(pack)
spyOn(packageModule, "deactivate").andCallThrough()
atom.deactivateAtomPackages()
expect(packageModule.deactivate).toHaveBeenCalled()
expect(rootView.packages.length).toBe 0
describe ".serializeAtomPackages()", ->
it "absorbs exceptions that are thrown by the package module's serialize methods", ->
spyOn(console, 'error')
atom.activateAtomPackage
name: "bad-egg"
packageMain:
activate: ->
serialize: -> throw new Error("I'm broken")
atom.activateAtomPackage
name: "good-egg"
packageMain:
activate: ->
serialize: -> "I still get called"
packageStates = atom.serializeAtomPackages()
expect(packageStates['good-egg']).toBe "I still get called"
expect(packageStates['bad-egg']).toBeUndefined()
expect(console.error).toHaveBeenCalled()

View File

@@ -666,95 +666,283 @@ describe 'Buffer', ->
expect(buffer.positionForCharacterIndex(61)).toEqual [2, 0]
expect(buffer.positionForCharacterIndex(408)).toEqual [12, 2]
describe "anchors", ->
[anchor, destroyHandler] = []
describe "markers", ->
describe "marker creation", ->
it "allows markers to be created with ranges and positions", ->
marker1 = buffer.markRange([[4, 20], [4, 23]])
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
expect(buffer.getMarkerPosition(marker1)).toEqual [4, 23]
expect(buffer.getMarkerTailPosition(marker1)).toEqual [4, 20]
beforeEach ->
destroyHandler = jasmine.createSpy("destroyHandler")
anchor = buffer.addAnchorAtPosition([4, 25])
anchor.on 'destroyed', destroyHandler
marker2 = buffer.markPosition([4, 20])
expect(buffer.getMarkerRange(marker2)).toEqual [[4, 20], [4, 20]]
expect(buffer.getMarkerPosition(marker2)).toEqual [4, 20]
expect(buffer.getMarkerTailPosition(marker2)).toEqual [4, 20]
describe "when anchor.ignoreChangesStartingOnAnchor is true", ->
it "allows markers to be created in a reversed orientation", ->
marker = buffer.markRange([[4, 20], [4, 23]], reverse: true)
expect(buffer.isMarkerReversed(marker)).toBeTruthy()
expect(buffer.getMarkerRange(marker)).toEqual [[4, 20], [4, 23]]
expect(buffer.getMarkerHeadPosition(marker)).toEqual [4, 20]
expect(buffer.getMarkerTailPosition(marker)).toEqual [4, 23]
describe "marker manipulation", ->
marker = null
beforeEach ->
anchor.ignoreChangesStartingOnAnchor = true
marker = buffer.markRange([[4, 20], [4, 23]])
describe "when the change ends before the anchor position", ->
it "moves the anchor", ->
buffer.change([[4, 23], [4, 24]], "...")
expect(anchor.getBufferPosition()).toEqual [4, 27]
expect(destroyHandler).not.toHaveBeenCalled()
it "allows a marker's head and tail positions to be changed", ->
buffer.setMarkerHeadPosition(marker, [5, 3])
expect(buffer.getMarkerRange(marker)).toEqual [[4, 20], [5, 3]]
describe "when the change ends on the anchor position", ->
it "moves the anchor", ->
buffer.change([[4, 24], [4, 25]], "...")
expect(anchor.getBufferPosition()).toEqual [4, 27]
expect(destroyHandler).not.toHaveBeenCalled()
buffer.setMarkerTailPosition(marker, [6, 3])
expect(buffer.getMarkerRange(marker)).toEqual [[5, 3], [6, 3]]
expect(buffer.isMarkerReversed(marker)).toBeTruthy()
describe "when the change begins on the anchor position", ->
it "doesn't move the anchor", ->
buffer.change([[4, 25], [4, 26]], ".....")
expect(anchor.getBufferPosition()).toEqual [4, 25]
expect(destroyHandler).not.toHaveBeenCalled()
it "clips head and tail positions to ensure they are in bounds", ->
buffer.setMarkerHeadPosition(marker, [-100, -5])
expect(buffer.getMarkerRange(marker)).toEqual([[0, 0], [4, 20]])
buffer.setMarkerTailPosition(marker, [Infinity, Infinity])
expect(buffer.getMarkerRange(marker)).toEqual([[0, 0], [12, 2]])
describe "when the change begins after the anchor position", ->
it "doesn't move the anchor", ->
buffer.change([[4, 26], [4, 27]], ".....")
expect(anchor.getBufferPosition()).toEqual [4, 25]
expect(destroyHandler).not.toHaveBeenCalled()
it "allows a marker's tail to be placed and cleared", ->
buffer.clearMarkerTail(marker)
expect(buffer.getMarkerRange(marker)).toEqual [[4, 23], [4, 23]]
buffer.placeMarkerTail(marker)
buffer.setMarkerHeadPosition(marker, [2, 0])
expect(buffer.getMarkerRange(marker)).toEqual [[2, 0], [4, 23]]
expect(buffer.isMarkerReversed(marker)).toBeTruthy()
describe "when the buffer changes and the oldRange is equalTo than the newRange (text is replaced)", ->
describe "when the anchor is contained by the oldRange", ->
it "destroys the anchor", ->
buffer.change([[4, 20], [4, 26]], ".......")
expect(destroyHandler).toHaveBeenCalled()
it "returns whether the position changed", ->
expect(buffer.setMarkerHeadPosition(marker, [5, 3])).toBeTruthy()
expect(buffer.setMarkerHeadPosition(marker, [5, 3])).toBeFalsy()
describe "when the anchor is not contained by the oldRange", ->
it "does not move the anchor", ->
buffer.change([[4, 20], [4, 21]], ".")
expect(anchor.getBufferPosition()).toEqual [4, 25]
expect(destroyHandler).not.toHaveBeenCalled()
expect(buffer.setMarkerTailPosition(marker, [6, 3])).toBeTruthy()
expect(buffer.setMarkerTailPosition(marker, [6, 3])).toBeFalsy()
describe "when the buffer changes and the oldRange is smaller than the newRange (text is inserted)", ->
describe "when the buffer changes and the oldRange starts and ends before the anchor ", ->
it "updates the anchor position", ->
buffer.change([[4, 24], [4, 24]], "..")
expect(anchor.getBufferPosition()).toEqual [4, 27]
expect(destroyHandler).not.toHaveBeenCalled()
describe ".observeMarker(marker, callback)", ->
[observeHandler, marker, subscription] = []
describe "when the buffer changes and the oldRange contains before the anchor ", ->
it "destroys the anchor", ->
buffer.change([[4, 24], [4, 26]], ".....")
expect(destroyHandler).toHaveBeenCalled()
beforeEach ->
observeHandler = jasmine.createSpy("observeHandler")
marker = buffer.markRange([[4, 20], [4, 23]])
subscription = buffer.observeMarker(marker, observeHandler)
describe "when the buffer changes and the oldRange stars after the anchor", ->
it "does not move the anchor", ->
buffer.change([[4, 26], [4, 26]], "....")
expect(anchor.getBufferPosition()).toEqual [4, 25]
expect(destroyHandler).not.toHaveBeenCalled()
it "calls the callback when the marker's head position changes", ->
buffer.setMarkerHeadPosition(marker, [6, 2])
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual {
oldHeadPosition: [4, 23]
newHeadPosition: [6, 2]
oldTailPosition: [4, 20]
newTailPosition: [4, 20]
bufferChanged: false
}
observeHandler.reset()
describe "when the buffer changes and the oldRange is larger than the newRange (text is deleted)", ->
describe "when the buffer changes and the oldRange starts and ends before the anchor ", ->
it "updates the anchor position", ->
buffer.change([[4, 20], [4, 21]], "")
expect(anchor.getBufferPosition()).toEqual [4, 24]
expect(destroyHandler).not.toHaveBeenCalled()
buffer.insert([6, 0], '...')
expect(observeHandler.argsForCall[0][0]).toEqual {
oldTailPosition: [4, 20]
newTailPosition: [4, 20]
oldHeadPosition: [6, 2]
newHeadPosition: [6, 5]
bufferChanged: true
}
describe "when the buffer changes and the oldRange contains before the anchor ", ->
it "destroys the anchor", ->
buffer.change([[4, 24], [4, 26]], ".")
expect(destroyHandler).toHaveBeenCalled()
it "calls the given callback when the marker's tail position changes", ->
buffer.setMarkerTailPosition(marker, [6, 2])
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual {
oldHeadPosition: [4, 23]
newHeadPosition: [4, 23]
oldTailPosition: [4, 20]
newTailPosition: [6, 2]
bufferChanged: false
}
observeHandler.reset()
describe "when the oldRange stars after the anchor", ->
it "does not move the anchor", ->
buffer.change([[4, 26], [4, 27]], "")
expect(anchor.getBufferPosition()).toEqual [4, 25]
expect(destroyHandler).not.toHaveBeenCalled()
buffer.insert([6, 0], '...')
describe "when a buffer change surrounds an anchor", ->
it "destroys the anchor", ->
buffer.delete([[3, 0], [5, 0]])
expect(destroyHandler).toHaveBeenCalled()
expect(buffer.getAnchors().indexOf(anchor)).toBe -1
expect(observeHandler.argsForCall[0][0]).toEqual {
oldHeadPosition: [4, 23]
newHeadPosition: [4, 23]
oldTailPosition: [6, 2]
newTailPosition: [6, 5]
bufferChanged: true
}
it "calls the callback when the selection's tail is cleared", ->
buffer.clearMarkerTail(marker)
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual {
oldHeadPosition: [4, 23]
newHeadPosition: [4, 23]
oldTailPosition: [4, 20]
newTailPosition: [4, 23]
bufferChanged: false
}
it "only calls the callback once when both the marker's head and tail positions change due to the same operation", ->
buffer.insert([4, 0], '...')
expect(observeHandler.callCount).toBe 1
expect(observeHandler.argsForCall[0][0]).toEqual {
oldTailPosition: [4, 20]
newTailPosition: [4, 23]
oldHeadPosition: [4, 23]
newHeadPosition: [4, 26]
bufferChanged: true
}
observeHandler.reset()
buffer.setMarkerRange(marker, [[0, 0], [1, 1]])
expect(observeHandler.callCount).toBe 1
expect(observeHandler.argsForCall[0][0]).toEqual {
oldTailPosition: [4, 23]
newTailPosition: [0, 0]
oldHeadPosition: [4, 26]
newHeadPosition: [1, 1]
bufferChanged: false
}
it "allows the observation subscription to be cancelled", ->
subscription.cancel()
buffer.setMarkerHeadPosition(marker, [6, 2])
expect(observeHandler).not.toHaveBeenCalled()
describe "marker destruction", ->
marker = null
beforeEach ->
marker = buffer.markRange([[4, 20], [4, 23]])
it "allows a marker to be destroyed", ->
buffer.destroyMarker(marker)
expect(buffer.getMarkerRange(marker)).toBeUndefined()
it "does not restore invalidated markers that have been destroyed", ->
buffer.delete([[4, 15], [4, 25]])
expect(buffer.getMarkerRange(marker)).toBeUndefined()
buffer.destroyMarker(marker)
buffer.undo()
expect(buffer.getMarkerRange(marker)).toBeUndefined()
# even "stayValid" markers get destroyed properly
marker2 = buffer.markRange([[4, 20], [4, 23]], stayValid: true)
buffer.delete([[4, 15], [4, 25]])
buffer.destroyMarker(marker2)
buffer.undo()
expect(buffer.getMarkerRange(marker2)).toBeUndefined()
describe "marker updates due to buffer changes", ->
[marker1, marker2] = []
beforeEach ->
marker1 = buffer.markRange([[4, 20], [4, 23]])
marker2 = buffer.markRange([[4, 20], [4, 23]], stayValid: true)
describe "when the buffer changes due to a new operation", ->
describe "when the change precedes the marker range", ->
it "moves the marker", ->
buffer.insert([4, 5], '...')
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 23], [4, 26]]
buffer.delete([[4, 5], [4, 8]])
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
buffer.insert([0, 0], '\nhi\n')
expect(buffer.getMarkerRange(marker1)).toEqual [[6, 20], [6, 23]]
# undo works
buffer.undo()
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
buffer.undo()
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 23], [4, 26]]
describe "when the change follows the marker range", ->
it "does not move the marker", ->
buffer.insert([6, 5], '...')
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
buffer.delete([[6, 5], [6, 8]])
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
buffer.insert([10, 0], '\nhi\n')
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
describe "when the change is an insertion at the start of the marker range", ->
it "does not move the start point, but does move the end point", ->
buffer.insert([4, 20], '...')
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 26]]
describe "when the change is an insertion at the end of the marker range", ->
it "moves the end point", ->
buffer.insert([4, 23], '...')
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 26]]
describe "when the change surrounds the marker range", ->
describe "when the marker was created with stayValid: false (the default)", ->
it "invalidates the marker", ->
buffer.delete([[4, 15], [4, 25]])
expect(buffer.getMarkerRange(marker1)).toBeUndefined()
buffer.undo()
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
describe "when the marker was created with stayValid: true", ->
it "does not invalidate the marker, but sets it to an empty range at the end of the change", ->
buffer.change([[4, 15], [4, 25]], "...")
expect(buffer.getMarkerRange(marker2)).toEqual [[4, 18], [4, 18]]
buffer.undo()
expect(buffer.getMarkerRange(marker2)).toEqual [[4, 20], [4, 23]]
describe "when the change straddles the start of the marker range", ->
describe "when the marker was created with stayValid: false (the default)", ->
it "invalidates the marker", ->
buffer.delete([[4, 15], [4, 22]])
expect(buffer.getMarkerRange(marker1)).toBeUndefined()
buffer.undo()
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
describe "when the marker was created with stayValid: true", ->
it "moves the start of the marker range to the end of the change", ->
buffer.delete([[4, 15], [4, 22]])
expect(buffer.getMarkerRange(marker2)).toEqual [[4, 15], [4, 16]]
buffer.undo()
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
describe "when the change straddles the end of the marker range", ->
describe "when the marker was created with stayValid: false (the default)", ->
it "invalidates the marker", ->
buffer.delete([[4, 22], [4, 25]])
expect(buffer.getMarkerRange(marker1)).toBeUndefined()
buffer.undo()
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
describe "when the marker was created with stayValid: true", ->
it "moves the end of the marker range to the start of the change", ->
buffer.delete([[4, 22], [4, 25]])
expect(buffer.getMarkerRange(marker2)).toEqual [[4, 20], [4, 22]]
buffer.undo()
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
describe "when the buffer changes due to the undo or redo of a previous operation", ->
it "restores invalidated markers when undoing/redoing in the other direction", ->
buffer.change([[4, 21], [4, 24]], "foo")
expect(buffer.getMarkerRange(marker1)).toBeUndefined()
marker3 = buffer.markRange([[4, 20], [4, 23]])
buffer.undo()
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
expect(buffer.getMarkerRange(marker3)).toBeUndefined()
marker4 = buffer.markRange([[4, 20], [4, 23]])
buffer.redo()
expect(buffer.getMarkerRange(marker3)).toEqual [[4, 20], [4, 23]]
expect(buffer.getMarkerRange(marker4)).toBeUndefined()
buffer.undo()
expect(buffer.getMarkerRange(marker4)).toEqual [[4, 20], [4, 23]]
describe ".markersForPosition(position)", ->
it "returns all markers that intersect the given position", ->
m1 = buffer.markRange([[3, 4], [3, 10]])
m2 = buffer.markRange([[3, 4], [3, 5]])
m3 = buffer.markPosition([3, 5])
expect(_.difference(buffer.markersForPosition([3, 5]), [m1, m2, m3]).length).toBe 0
expect(_.difference(buffer.markersForPosition([3, 4]), [m1, m2]).length).toBe 0
expect(_.difference(buffer.markersForPosition([3, 10]), [m1]).length).toBe 0
describe ".usesSoftTabs()", ->
it "returns true if the first indented line begins with tabs", ->
@@ -800,7 +988,6 @@ describe 'Buffer', ->
expect(contentsModifiedHandler).toHaveBeenCalledWith(differsFromDisk:true)
bufferToDelete.destroy()
describe "when the buffer text has been changed", ->
it "triggers the contents-modified event 'stoppedChangingDelay' ms after the last buffer change", ->
delay = buffer.stoppedChangingDelay

View File

@@ -118,6 +118,12 @@ describe "DisplayBuffer", ->
expect(displayBuffer.screenPositionForBufferPosition([4, 5])).toEqual([5, 5])
expect(displayBuffer.bufferPositionForScreenPosition([5, 5])).toEqual([4, 5])
# clip screen position inputs before translating
expect(displayBuffer.bufferPositionForScreenPosition([-5, -5])).toEqual([0, 0])
expect(displayBuffer.bufferPositionForScreenPosition([Infinity, Infinity])).toEqual([12, 2])
expect(displayBuffer.bufferPositionForScreenPosition([3, -5])).toEqual([3, 0])
expect(displayBuffer.bufferPositionForScreenPosition([3, Infinity])).toEqual([3, 50])
describe ".setSoftWrapColumn(length)", ->
it "changes the length at which lines are wrapped and emits a change event for all screen lines", ->
displayBuffer.setSoftWrapColumn(40)
@@ -477,6 +483,10 @@ describe "DisplayBuffer", ->
expect(displayBuffer.bufferPositionForScreenPosition([5, 0])).toEqual [8, 0]
expect(displayBuffer.bufferPositionForScreenPosition([9, 2])).toEqual [12, 2]
# clip screen positions before translating
expect(displayBuffer.bufferPositionForScreenPosition([-5, -5])).toEqual([0, 0])
expect(displayBuffer.bufferPositionForScreenPosition([Infinity, Infinity])).toEqual([200, 0])
describe ".destroyFoldsContainingBufferRow(row)", ->
it "destroys all folds containing the given row", ->
displayBuffer.createFold(2, 4)
@@ -579,3 +589,154 @@ describe "DisplayBuffer", ->
describe ".maxLineLength()", ->
it "returns the length of the longest screen line", ->
expect(displayBuffer.maxLineLength()).toBe 65
describe "markers", ->
beforeEach ->
displayBuffer.foldBufferRow(4)
describe "marker creation and manipulation", ->
it "allows markers to be created in terms of both screen and buffer coordinates", ->
marker1 = displayBuffer.markScreenRange([[5, 4], [5, 10]])
marker2 = displayBuffer.markBufferRange([[8, 4], [8, 10]])
expect(displayBuffer.getMarkerBufferRange(marker1)).toEqual [[8, 4], [8, 10]]
expect(displayBuffer.getMarkerScreenRange(marker2)).toEqual [[5, 4], [5, 10]]
it "allows marker head and tail positions to be manipulated in both screen and buffer coordinates", ->
marker = displayBuffer.markScreenRange([[5, 4], [5, 10]])
displayBuffer.setMarkerHeadScreenPosition(marker, [5, 4])
displayBuffer.setMarkerTailBufferPosition(marker, [5, 4])
expect(displayBuffer.isMarkerReversed(marker)).toBeFalsy()
expect(displayBuffer.getMarkerBufferRange(marker)).toEqual [[5, 4], [8, 4]]
displayBuffer.setMarkerHeadBufferPosition(marker, [5, 4])
displayBuffer.setMarkerTailScreenPosition(marker, [5, 4])
expect(displayBuffer.isMarkerReversed(marker)).toBeTruthy()
expect(displayBuffer.getMarkerBufferRange(marker)).toEqual [[5, 4], [8, 4]]
it "returns whether a position changed when it is assigned", ->
marker = displayBuffer.markScreenRange([[0, 0], [0, 0]])
expect(displayBuffer.setMarkerHeadScreenPosition(marker, [5, 4])).toBeTruthy()
expect(displayBuffer.setMarkerHeadScreenPosition(marker, [5, 4])).toBeFalsy()
expect(displayBuffer.setMarkerHeadBufferPosition(marker, [1, 0])).toBeTruthy()
expect(displayBuffer.setMarkerHeadBufferPosition(marker, [1, 0])).toBeFalsy()
expect(displayBuffer.setMarkerTailScreenPosition(marker, [5, 4])).toBeTruthy()
expect(displayBuffer.setMarkerTailScreenPosition(marker, [5, 4])).toBeFalsy()
expect(displayBuffer.setMarkerTailBufferPosition(marker, [1, 0])).toBeTruthy()
expect(displayBuffer.setMarkerTailBufferPosition(marker, [1, 0])).toBeFalsy()
describe ".observeMarker(marker, callback)", ->
[observeHandler, marker, subscription] = []
beforeEach ->
observeHandler = jasmine.createSpy("observeHandler")
marker = displayBuffer.markScreenRange([[5, 4], [5, 10]])
subscription = displayBuffer.observeMarker(marker, observeHandler)
it "calls the callback whenever the markers head's screen position changes in the buffer or on screen", ->
displayBuffer.setMarkerHeadScreenPosition(marker, [8, 20])
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual {
oldHeadScreenPosition: [5, 10]
oldHeadBufferPosition: [8, 10]
newHeadScreenPosition: [8, 20]
newHeadBufferPosition: [11, 20]
oldTailScreenPosition: [5, 4]
oldTailBufferPosition: [8, 4]
newTailScreenPosition: [5, 4]
newTailBufferPosition: [8, 4]
bufferChanged: false
}
observeHandler.reset()
buffer.insert([11, 0], '...')
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual {
oldHeadScreenPosition: [8, 20]
oldHeadBufferPosition: [11, 20]
newHeadScreenPosition: [8, 23]
newHeadBufferPosition: [11, 23]
oldTailScreenPosition: [5, 4]
oldTailBufferPosition: [8, 4]
newTailScreenPosition: [5, 4]
newTailBufferPosition: [8, 4]
bufferChanged: true
}
observeHandler.reset()
displayBuffer.unfoldBufferRow(4)
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual {
oldHeadScreenPosition: [8, 23]
oldHeadBufferPosition: [11, 23]
newHeadScreenPosition: [11, 23]
newHeadBufferPosition: [11, 23]
oldTailScreenPosition: [5, 4]
oldTailBufferPosition: [8, 4]
newTailScreenPosition: [8, 4]
newTailBufferPosition: [8, 4]
bufferChanged: false
}
observeHandler.reset()
displayBuffer.foldBufferRow(4)
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual {
oldHeadScreenPosition: [11, 23]
oldHeadBufferPosition: [11, 23]
newHeadScreenPosition: [8, 23]
newHeadBufferPosition: [11, 23]
oldTailScreenPosition: [8, 4]
oldTailBufferPosition: [8, 4]
newTailScreenPosition: [5, 4]
newTailBufferPosition: [8, 4]
bufferChanged: false
}
it "calls the callback whenever the marker tail's position changes in the buffer or on screen", ->
displayBuffer.setMarkerTailScreenPosition(marker, [8, 20])
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual {
oldHeadScreenPosition: [5, 10]
oldHeadBufferPosition: [8, 10]
newHeadScreenPosition: [5, 10]
newHeadBufferPosition: [8, 10]
oldTailScreenPosition: [5, 4]
oldTailBufferPosition: [8, 4]
newTailScreenPosition: [8, 20]
newTailBufferPosition: [11, 20]
bufferChanged: false
}
observeHandler.reset()
buffer.insert([11, 0], '...')
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual {
oldHeadScreenPosition: [5, 10]
oldHeadBufferPosition: [8, 10]
newHeadScreenPosition: [5, 10]
newHeadBufferPosition: [8, 10]
oldTailScreenPosition: [8, 20]
oldTailBufferPosition: [11, 20]
newTailScreenPosition: [8, 23]
newTailBufferPosition: [11, 23]
bufferChanged: true
}
it "does not call the callback for screen changes that don't change the position of the marker", ->
displayBuffer.createFold(10, 11)
expect(observeHandler).not.toHaveBeenCalled()
it "allows observation subscriptions to be cancelled", ->
subscription.cancel()
displayBuffer.setMarkerHeadScreenPosition(marker, [8, 20])
displayBuffer.unfoldBufferRow(4)
expect(observeHandler).not.toHaveBeenCalled()
describe "marker destruction", ->
it "allows markers to be destroyed", ->
marker = displayBuffer.markScreenRange([[5, 4], [5, 10]])
displayBuffer.destroyMarker(marker)
expect(displayBuffer.getMarkerBufferRange(marker)).toBeUndefined()

View File

@@ -76,12 +76,12 @@ describe "EditSession", ->
describe "when the cursor is on the first line", ->
it "moves the cursor to the beginning of the line, but retains the goal column", ->
editSession.setCursorScreenPosition(row: 0, column: 4)
editSession.setCursorScreenPosition([0, 4])
editSession.moveCursorUp()
expect(editSession.getCursorScreenPosition()).toEqual(row: 0, column: 0)
expect(editSession.getCursorScreenPosition()).toEqual([0, 0])
editSession.moveCursorDown()
expect(editSession.getCursorScreenPosition()).toEqual(row: 1, column: 4)
expect(editSession.getCursorScreenPosition()).toEqual([1, 4])
it "merges cursors when they overlap", ->
editSession.addCursorAtScreenPosition([1, 0])
@@ -185,9 +185,9 @@ describe "EditSession", ->
describe "when the cursor is on the last column of a line", ->
describe "when there is a subsequent line", ->
it "wraps to the beginning of the next line", ->
editSession.setCursorScreenPosition(row: 0, column: buffer.lineForRow(0).length)
editSession.setCursorScreenPosition([0, buffer.lineForRow(0).length])
editSession.moveCursorRight()
expect(editSession.getCursorScreenPosition()).toEqual(row: 1, column: 0)
expect(editSession.getCursorScreenPosition()).toEqual [1, 0]
describe "when the cursor is on the last line", ->
it "remains in the same position", ->
@@ -322,7 +322,6 @@ describe "EditSession", ->
editSession.moveCursorToEndOfWord()
expect(editSession.getCursorBufferPosition()).toEqual [11, 8]
describe ".getCurrentParagraphBufferRange()", ->
it "returns the buffer range of the current paragraph, delimited by blank lines or the beginning / end of the file", ->
buffer.setText """
@@ -353,6 +352,31 @@ describe "EditSession", ->
editSession.setCursorBufferPosition([3, 1])
expect(editSession.getCurrentParagraphBufferRange()).toBeUndefined()
describe "cursor-moved events", ->
cursorMovedHandler = null
beforeEach ->
editSession.foldBufferRow(4)
editSession.setSelectedBufferRange([[8, 1], [9, 0]])
cursorMovedHandler = jasmine.createSpy("cursorMovedHandler")
editSession.on 'cursor-moved', cursorMovedHandler
describe "when the position of the cursor changes", ->
it "emits a cursor-moved event", ->
buffer.insert([9, 0], '...')
expect(cursorMovedHandler).toHaveBeenCalledWith(
oldBufferPosition: [9, 0]
oldScreenPosition: [6, 0]
newBufferPosition: [9, 3]
newScreenPosition: [6, 3]
bufferChanged: true
)
describe "when the position of the associated selection's tail changes, but not the cursor's position", ->
it "does not emit a cursor-moved event", ->
buffer.insert([8, 0], '...')
expect(cursorMovedHandler).not.toHaveBeenCalled()
describe "selection", ->
selection = null
@@ -638,6 +662,19 @@ describe "EditSession", ->
editSession.setSelectedBufferRanges([[[2, 2], [3, 3]]], preserveFolds: true)
expect(editSession.lineForScreenRow(1).fold).toBeDefined()
describe ".selectMarker(marker)", ->
describe "when the marker exists", ->
it "selects the marker's range and returns true", ->
marker = editSession.markBufferRange([[0, 1], [3, 3]])
expect(editSession.selectMarker(marker)).toBeTruthy()
expect(editSession.getSelectedBufferRange()).toEqual [[0, 1], [3, 3]]
describe "when the marker does not exist", ->
it "does not select the marker's range and returns false", ->
rangeBefore = editSession.getSelectedBufferRange()
expect(editSession.selectMarker('bogus')).toBeFalsy()
expect(editSession.getSelectedBufferRange()).toEqual rangeBefore
describe "when the cursor is moved while there is a selection", ->
makeSelection = -> selection.setBufferRange [[1, 2], [1, 5]]
@@ -1005,6 +1042,7 @@ describe "EditSession", ->
expect(line).toBe " var ort = function(items) {"
expect(editSession.getCursorScreenPosition()).toEqual {row: 1, column: 6}
expect(changeScreenRangeHandler).toHaveBeenCalled()
expect(editSession.getCursor().isVisible()).toBeTruthy()
describe "when the cursor is at the beginning of a line", ->
it "joins it with the line above", ->
@@ -1635,7 +1673,7 @@ describe "EditSession", ->
expect(buffer.lineForRow(7)).toBe " }"
it "preserves selection emptiness", ->
editSession.setSelectedBufferRange([[4, 0], [4, 0]])
editSession.setCursorBufferPosition([4, 0])
editSession.toggleLineCommentsInSelection()
expect(editSession.getSelection().isEmpty()).toBeTruthy()
@@ -1647,7 +1685,7 @@ describe "EditSession", ->
expect(buffer.lineForRow(4)).toBe " while(items.length > 0) {"
it "uncomments when the line lacks the trailing whitespace in the comment regex", ->
editSession.setSelectedBufferRange([[10, 0], [10, 0]])
editSession.setCursorBufferPosition([10, 0])
editSession.toggleLineCommentsInSelection()
expect(buffer.lineForRow(10)).toBe "// "
@@ -1660,7 +1698,7 @@ describe "EditSession", ->
expect(editSession.getSelectedBufferRange()).toEqual [[10, 0], [10, 0]]
it "uncomments when the line has leading whitespace", ->
editSession.setSelectedBufferRange([[10, 0], [10, 0]])
editSession.setCursorBufferPosition([10, 0])
editSession.toggleLineCommentsInSelection()
expect(buffer.lineForRow(10)).toBe "// "
@@ -1763,18 +1801,18 @@ describe "EditSession", ->
expect(cursor2.getScreenPosition()).toEqual [0, 8]
expect(cursor3.getScreenPosition()).toEqual [1, 0]
it "does not destroy cursor or selection anchors when a change encompasses them", ->
it "does not destroy cursors or selections when a change encompasses them", ->
cursor = editSession.getCursor()
cursor.setBufferPosition [3, 3]
editSession.buffer.delete([[3, 1], [3, 5]])
expect(cursor.getBufferPosition()).toEqual [3, 1]
expect(editSession.getAnchors().indexOf(cursor.anchor)).not.toBe -1
expect(editSession.getCursors().indexOf(cursor)).not.toBe -1
selection = editSession.getLastSelection()
selection.setBufferRange [[3, 5], [3, 10]]
editSession.buffer.delete [[3, 3], [3, 8]]
expect(selection.getBufferRange()).toEqual [[3, 3], [3, 5]]
expect(editSession.getAnchors().indexOf(selection.anchor)).not.toBe -1
expect(editSession.getSelections().indexOf(selection)).not.toBe -1
it "merges cursors when the change causes them to overlap", ->
editSession.setCursorScreenPosition([0, 0])
@@ -1798,34 +1836,6 @@ describe "EditSession", ->
editSession.foldAll()
expect(editSession.getCursorBufferPosition()).toEqual([5,5])
describe "anchors", ->
[anchor, destroyHandler] = []
beforeEach ->
destroyHandler = jasmine.createSpy("destroyHandler")
anchor = editSession.addAnchorAtBufferPosition([4, 25])
anchor.on 'destroyed', destroyHandler
describe "when a buffer change precedes an anchor", ->
it "moves the anchor in accordance with the change", ->
editSession.setSelectedBufferRange([[3, 0], [4, 10]])
editSession.delete()
expect(anchor.getBufferPosition()).toEqual [3, 15]
expect(destroyHandler).not.toHaveBeenCalled()
describe "when a buffer change surrounds an anchor", ->
it "destroys the anchor", ->
editSession.setSelectedBufferRange([[3, 0], [5, 0]])
editSession.delete()
expect(destroyHandler).toHaveBeenCalled()
expect(editSession.getAnchors().indexOf(anchor)).toBe -1
describe ".clipBufferPosition(bufferPosition)", ->
it "clips the given position to a valid position", ->
expect(editSession.clipBufferPosition([-1, -1])).toEqual [0,0]
expect(editSession.clipBufferPosition([Infinity, Infinity])).toEqual [12,2]
expect(editSession.clipBufferPosition([8, 57])).toEqual [8, 56]
describe ".deleteLine()", ->
it "deletes the first line when the cursor is there", ->
editSession.getCursor().moveToTop()
@@ -1891,7 +1901,7 @@ describe "EditSession", ->
expect(buffer.getLineCount()).toBe(1)
expect(buffer.getText()).toBe('')
describe ".tranpose()", ->
describe ".transpose()", ->
it "swaps two characters", ->
editSession.buffer.setText("abc")
editSession.setCursorScreenPosition([0, 1])
@@ -2046,3 +2056,9 @@ describe "EditSession", ->
editSession.insertText("var i;\n}")
editSession.autoDecreaseIndentForRow(1)
expect(editSession.lineForBufferRow(1)).toBe "}"
describe ".destroy()", ->
it "destroys all markers associated with the edit session", ->
expect(buffer.getMarkerCount()).toBeGreaterThan 0
editSession.destroy()
expect(buffer.getMarkerCount()).toBe 0

View File

@@ -39,8 +39,9 @@ describe "Editor", ->
rootView.remove()
describe "construction", ->
it "throws an error if no editor session is given", ->
it "throws an error if no editor session is given unless deserializing", ->
expect(-> new Editor).toThrow()
expect(-> new Editor(deserializing: true)).not.toThrow()
describe ".copy()", ->
it "builds a new editor with the same edit sessions, cursor position, and scroll position as the receiver", ->
@@ -69,7 +70,7 @@ describe "Editor", ->
newEditor.height(editor.height())
newEditor.width(editor.width())
rootView.remove()
newEditor.attachToDom()
expect(newEditor.scrollTop()).toBe editor.scrollTop()
expect(newEditor.scrollView.scrollLeft()).toBe 44
@@ -1005,41 +1006,52 @@ describe "Editor", ->
expect(editor.scrollTop()).toBe 0
expect(editor.scrollView.scrollTop()).toBe 0
# does auto-scroll when the selection is cleared
# does autoscroll when the selection is cleared
editor.moveCursorDown()
expect(editor.scrollTop()).toBeGreaterThan(0)
describe "selection autoscrolling and highlighting when setting selected buffer range", ->
it "only if autoscroll is true, centers the viewport on the selection if its vertical center is currently offscreen", ->
beforeEach ->
setEditorHeightInLines(editor, 4)
editor.setSelectedBufferRange([[2, 0], [4, 0]], autoscroll: true)
expect(editor.scrollTop()).toBe 0
describe "if autoscroll is true", ->
it "centers the viewport on the selection if its vertical center is currently offscreen", ->
editor.setSelectedBufferRange([[2, 0], [4, 0]], autoscroll: true)
expect(editor.scrollTop()).toBe 0
editor.setSelectedBufferRange([[6, 0], [8, 0]], autoscroll: true)
expect(editor.scrollTop()).toBe 5 * editor.lineHeight
editor.setSelectedBufferRange([[6, 0], [8, 0]], autoscroll: true)
expect(editor.scrollTop()).toBe 5 * editor.lineHeight
editor.setSelectedBufferRange([[0, 0], [1, 0]]) # autoscroll is false, the default
expect(editor.scrollTop()).toBe 5 * editor.lineHeight
it "highlights the selection if autoscroll is true", ->
editor.setSelectedBufferRange([[2, 0], [4, 0]], autoscroll: true)
expect(editor.getSelectionView()).toHaveClass 'highlighted'
advanceClock(1000)
expect(editor.getSelectionView()).not.toHaveClass 'highlighted'
it "highlights the selection if autoscroll is true", ->
editor.setSelectedBufferRange([[2, 0], [4, 0]], autoscroll: true)
expect(editor.getSelectionView()).toHaveClass 'highlighted'
advanceClock(1000)
expect(editor.getSelectionView()).not.toHaveClass 'highlighted'
editor.setSelectedBufferRange([[3, 0], [5, 0]], autoscroll: true)
expect(editor.getSelectionView()).toHaveClass 'highlighted'
editor.setSelectedBufferRange([[3, 0], [5, 0]], autoscroll: true)
expect(editor.getSelectionView()).toHaveClass 'highlighted'
advanceClock(500)
spyOn(editor.getSelectionView(), 'removeClass').andCallThrough()
editor.setSelectedBufferRange([[2, 0], [4, 0]], autoscroll: true)
expect(editor.getSelectionView().removeClass).toHaveBeenCalledWith('highlighted')
expect(editor.getSelectionView()).toHaveClass 'highlighted'
advanceClock(500)
spyOn(editor.getSelectionView(), 'removeClass').andCallThrough()
editor.setSelectedBufferRange([[2, 0], [4, 0]], autoscroll: true)
expect(editor.getSelectionView().removeClass).toHaveBeenCalledWith('highlighted')
expect(editor.getSelectionView()).toHaveClass 'highlighted'
advanceClock(500)
expect(editor.getSelectionView()).toHaveClass 'highlighted'
describe "if autoscroll is false", ->
it "does not scroll to the selection or the cursor", ->
editor.scrollToBottom()
scrollTopBefore = editor.scrollTop()
editor.setSelectedBufferRange([[0, 0], [1, 0]], autoscroll: false)
expect(editor.scrollTop()).toBe scrollTopBefore
advanceClock(500)
expect(editor.getSelectionView()).toHaveClass 'highlighted'
describe "if autoscroll is not specified", ->
it "autoscrolls to the cursor as normal", ->
editor.scrollToBottom()
editor.setSelectedBufferRange([[0, 0], [1, 0]])
expect(editor.scrollTop()).toBe 0
describe "cursor rendering", ->
describe "when the cursor moves", ->
@@ -1068,8 +1080,8 @@ describe "Editor", ->
expect(editor.getSelection().isEmpty()).toBeTruthy()
expect(cursorView).toBeVisible()
describe "auto-scrolling", ->
it "only auto-scrolls when the last cursor is moved", ->
describe "autoscrolling", ->
it "only autoscrolls when the last cursor is moved", ->
editor.setCursorBufferPosition([11,0])
editor.addCursorAtBufferPosition([6,50])
[cursor1, cursor2] = editor.getCursors()
@@ -1081,6 +1093,22 @@ describe "Editor", ->
cursor2.setScreenPosition([11, 11])
expect(editor.scrollToPixelPosition).toHaveBeenCalled()
it "does not autoscroll if the 'autoscroll' option is false", ->
editor.setCursorBufferPosition([11,0])
spyOn(editor, 'scrollToPixelPosition')
editor.setCursorScreenPosition([10, 10], autoscroll: false)
expect(editor.scrollToPixelPosition).not.toHaveBeenCalled()
it "autoscrolls to cursor if autoscroll is true, even if the position does not change", ->
spyOn(editor, 'scrollToPixelPosition')
editor.setCursorScreenPosition([4, 10], autoscroll: false)
editor.setCursorScreenPosition([4, 10])
expect(editor.scrollToPixelPosition).toHaveBeenCalled()
editor.scrollToPixelPosition.reset()
editor.setCursorBufferPosition([4, 10])
expect(editor.scrollToPixelPosition).toHaveBeenCalled()
describe "when the last cursor exceeds the upper or lower scroll margins", ->
describe "when the editor is taller than twice the vertical scroll margin", ->
it "sets the scrollTop so the cursor remains within the scroll margin", ->
@@ -1756,12 +1784,6 @@ describe "Editor", ->
expect(editor.gutter.find('.line-number:first').text()).toBe '2'
expect(editor.gutter.find('.line-number:last').text()).toBe '11'
describe "when the insertion of lines causes the editor to scroll", ->
it "renders line numbers correctly", ->
oneHundredLines = [0..100].join("\n")
editor.insertText(oneHundredLines)
expect(editor.gutter.lineNumbers.find('.line-number').length).toBe 6 + editor.lineOverdraw * 2
describe "when wrapping is on", ->
it "renders a • instead of line number for wrapped portions of lines", ->
editor.setSoftWrapColumn(50)

View File

@@ -19,192 +19,6 @@ describe "LanguageMode", ->
expect(jsEditSession.languageMode.grammar.name).toBe "JavaScript"
jsEditSession.destroy()
describe "bracket insertion", ->
beforeEach ->
editSession.buffer.setText("")
describe "when more than one character is inserted", ->
it "does not insert a matching bracket", ->
editSession.insertText("woah(")
expect(editSession.buffer.getText()).toBe "woah("
describe "when there is a word character after the cursor", ->
it "does not insert a matching bracket", ->
editSession.buffer.setText("ab")
editSession.setCursorBufferPosition([0, 1])
editSession.insertText("(")
expect(editSession.buffer.getText()).toBe "a(b"
describe "when there are multiple cursors", ->
it "inserts ) at each cursor", ->
editSession.buffer.setText("()\nab\n[]\n12")
editSession.setCursorBufferPosition([3, 1])
editSession.addCursorAtBufferPosition([2, 1])
editSession.addCursorAtBufferPosition([1, 1])
editSession.addCursorAtBufferPosition([0, 1])
editSession.insertText ')'
expect(editSession.buffer.getText()).toBe "())\na)b\n[)]\n1)2"
describe "when there is a non-word character after the cursor", ->
it "inserts a closing bracket after an opening bracket is inserted", ->
editSession.buffer.setText("}")
editSession.setCursorBufferPosition([0, 0])
editSession.insertText '{'
expect(buffer.lineForRow(0)).toBe "{}}"
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
describe "when the cursor is at the end of the line", ->
it "inserts a closing bracket after an opening bracket is inserted", ->
editSession.buffer.setText("")
editSession.insertText '{'
expect(buffer.lineForRow(0)).toBe "{}"
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
editSession.buffer.setText("")
editSession.insertText '('
expect(buffer.lineForRow(0)).toBe "()"
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
editSession.buffer.setText("")
editSession.insertText '['
expect(buffer.lineForRow(0)).toBe "[]"
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
editSession.buffer.setText("")
editSession.insertText '"'
expect(buffer.lineForRow(0)).toBe '""'
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
editSession.buffer.setText("")
editSession.insertText "'"
expect(buffer.lineForRow(0)).toBe "''"
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
describe "when the cursor is on a closing bracket and a closing bracket is inserted", ->
describe "when the closing bracket was there previously", ->
it "inserts a closing bracket", ->
editSession.insertText '()x'
editSession.setCursorBufferPosition([0, 1])
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "())x"
expect(editSession.getCursorBufferPosition().column).toBe 2
describe "when the closing bracket was automatically inserted from inserting an opening bracket", ->
it "only moves cursor over the closing bracket one time", ->
editSession.insertText '('
expect(buffer.lineForRow(0)).toBe "()"
editSession.setCursorBufferPosition([0, 1])
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "()"
expect(editSession.getCursorBufferPosition()).toEqual [0, 2]
editSession.setCursorBufferPosition([0, 1])
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "())"
expect(editSession.getCursorBufferPosition()).toEqual [0, 2]
it "moves cursor over the closing bracket after other text is inserted", ->
editSession.insertText '('
editSession.insertText 'ok cool'
expect(buffer.lineForRow(0)).toBe "(ok cool)"
editSession.setCursorBufferPosition([0, 8])
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "(ok cool)"
expect(editSession.getCursorBufferPosition()).toEqual [0, 9]
it "works with nested brackets", ->
editSession.insertText '('
editSession.insertText '1'
editSession.insertText '('
editSession.insertText '2'
expect(buffer.lineForRow(0)).toBe "(1(2))"
editSession.setCursorBufferPosition([0, 4])
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "(1(2))"
expect(editSession.getCursorBufferPosition()).toEqual [0, 5]
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "(1(2))"
expect(editSession.getCursorBufferPosition()).toEqual [0, 6]
it "works with mixed brackets", ->
editSession.insertText '('
editSession.insertText '}'
expect(buffer.lineForRow(0)).toBe "(})"
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "(})"
expect(editSession.getCursorBufferPosition()).toEqual [0, 3]
it "closes brackets with the same begin/end character correctly", ->
editSession.insertText '"'
editSession.insertText 'ok'
expect(buffer.lineForRow(0)).toBe '"ok"'
expect(editSession.getCursorBufferPosition()).toEqual [0, 3]
editSession.insertText '"'
expect(buffer.lineForRow(0)).toBe '"ok"'
expect(editSession.getCursorBufferPosition()).toEqual [0, 4]
describe "when there is text selected on a single line", ->
it "wraps the selection with brackets", ->
editSession.insertText 'text'
editSession.moveCursorToBottom()
editSession.selectToTop()
editSession.selectAll()
editSession.insertText '('
expect('(text)').toBe buffer.getText()
expect(editSession.getSelectedBufferRange()).toEqual [[0, 1], [0, 5]]
expect(editSession.getSelection().isReversed()).toBeTruthy()
describe "when there is text selected on multiple lines", ->
it "wraps the selection with brackets", ->
editSession.insertText 'text\nabcd'
editSession.moveCursorToBottom()
editSession.selectToTop()
editSession.selectAll()
editSession.insertText '('
expect('(text\nabcd)').toBe buffer.getText()
expect(editSession.getSelectedBufferRange()).toEqual [[0, 1], [1, 4]]
expect(editSession.getSelection().isReversed()).toBeTruthy()
describe "when inserting a quote", ->
describe "when a word character is before the cursor", ->
it "does not automatically insert closing quote", ->
editSession.buffer.setText("abc")
editSession.setCursorBufferPosition([0, 3])
editSession.insertText '"'
expect(buffer.lineForRow(0)).toBe "abc\""
editSession.buffer.setText("abc")
editSession.setCursorBufferPosition([0, 3])
editSession.insertText '\''
expect(buffer.lineForRow(0)).toBe "abc\'"
describe "when a non word character is before the cursor", ->
it "automatically insert closing quote", ->
editSession.buffer.setText("ab@")
editSession.setCursorBufferPosition([0, 3])
editSession.insertText '"'
expect(buffer.lineForRow(0)).toBe "ab@\"\""
expect(editSession.getCursorBufferPosition()).toEqual [0, 4]
describe "when the cursor is on an empty line", ->
it "automatically insert closing quote", ->
editSession.buffer.setText("")
editSession.setCursorBufferPosition([0, 0])
editSession.insertText '"'
expect(buffer.lineForRow(0)).toBe "\"\""
expect(editSession.getCursorBufferPosition()).toEqual [0, 1]
describe "bracket deletion", ->
it "deletes the end bracket when it directly proceeds a begin bracket that is being backspaced", ->
buffer.setText("")
editSession.setCursorBufferPosition([0, 0])
editSession.insertText '{'
expect(buffer.lineForRow(0)).toBe "{}"
editSession.backspace()
expect(buffer.lineForRow(0)).toBe ""
describe "javascript", ->
beforeEach ->
editSession = fixturesProject.buildEditSessionForPath('sample.js', autoIndent: false)

View File

@@ -50,6 +50,14 @@ describe "RootView", ->
expect(rootView.getEditors()[0].getText()).toEqual ""
expect(rootView.getTitle()).toBe 'untitled'
describe ".deactivate()", ->
it "deactivates all packages", ->
pack = atom.loadPackage("package-with-module")
atom.activateAtomPackage(pack)
spyOn(pack.packageMain, "deactivate").andCallThrough()
rootView.deactivate()
expect(pack.packageMain.deactivate).toHaveBeenCalled()
describe "@deserialize()", ->
viewState = null
@@ -151,25 +159,6 @@ describe "RootView", ->
expect(rootView.find('.pane').length).toBe 1
expect(rootView.find('.pane').children().length).toBe 0
describe ".serialize()", ->
it "absorbs exceptions that are thrown by the package module's serialize methods", ->
spyOn(console, 'error')
rootView.activatePackage("bad-egg",
activate: ->
serialize: -> throw new Error("I'm broken")
)
rootView.activatePackage("good-egg"
activate: ->
serialize: -> "I still get called"
)
data = rootView.serialize()
expect(data.packageStates['good-egg']).toBe "I still get called"
expect(data.packageStates['bad-egg']).toBeUndefined()
expect(console.error).toHaveBeenCalled()
describe "focus", ->
describe "when there is an active editor", ->
it "hands off focus to the active editor", ->
@@ -418,54 +407,6 @@ describe "RootView", ->
rootView.focusNextPane()
expect(view1.focus).toHaveBeenCalled()
describe "packages", ->
packageModule = null
beforeEach ->
packageModule =
configDefaults: foo: { bar: 2, baz: 3 }
activate: jasmine.createSpy("activate")
deactivate: ->
serialize: -> "it worked"
describe ".activatePackage(name, packageModule)", ->
it "calls activate on the package module", ->
rootView.activatePackage('package', packageModule)
expect(packageModule.activate).toHaveBeenCalledWith(rootView, undefined)
it "calls activate on the package module with its previous state", ->
rootView.activatePackage('package', packageModule)
packageModule.activate.reset()
newRootView = RootView.deserialize(rootView.serialize())
newRootView.activatePackage('package', packageModule)
expect(packageModule.activate).toHaveBeenCalledWith(newRootView, "it worked")
newRootView.remove()
it "loads config defaults based on the `configDefaults` key", ->
expect(config.get('foo.bar')).toBeUndefined()
rootView.activatePackage('package', packageModule)
config.set("package.foo.bar", 1)
expect(config.get('package.foo.bar')).toBe 1
expect(config.get('package.foo.baz')).toBe 3
describe ".deactivatePackage(packageName)", ->
it "deactivates and removes the package module from the package module map", ->
rootView.activatePackage('package', packageModule)
expect(rootView.packageModules['package']).toBeTruthy()
spyOn(packageModule, "deactivate").andCallThrough()
rootView.deactivatePackage('package')
expect(packageModule.deactivate).toHaveBeenCalled()
expect(rootView.packageModules['package']).toBeFalsy()
it "is called when the rootView is deactivated to deactivate all packages", ->
rootView.activatePackage('package', packageModule)
spyOn(rootView, "deactivatePackage").andCallThrough()
spyOn(packageModule, "deactivate").andCallThrough()
rootView.deactivate()
expect(rootView.deactivatePackage).toHaveBeenCalled()
expect(packageModule.deactivate).toHaveBeenCalled()
describe "keymap wiring", ->
commandHandler = null
beforeEach ->

View File

@@ -40,7 +40,7 @@ describe "Selection", ->
expect(buffer.lineForRow(0)).toBe "v;"
expect(selection.isEmpty()).toBeTruthy()
describe "when the cursor precedes the anchor", ->
describe "when the cursor precedes the tail", ->
it "deletes selected text and clears the selection", ->
selection.cursor.setScreenPosition [0,13]
selection.selectToScreenPosition [0,4]
@@ -50,7 +50,7 @@ describe "Selection", ->
expect(selection.isEmpty()).toBeTruthy()
describe ".isReversed()", ->
it "returns true if the cursor precedes the anchor", ->
it "returns true if the cursor precedes the tail", ->
selection.cursor.setScreenPosition([0, 20])
selection.selectToScreenPosition([0, 10])
expect(selection.isReversed()).toBeTruthy()
@@ -58,7 +58,7 @@ describe "Selection", ->
selection.selectToScreenPosition([0, 25])
expect(selection.isReversed()).toBeFalsy()
describe "when only the selection's anchor is moved (regression)", ->
describe "when only the selection's tail is moved (regression)", ->
it "emits the 'screen-range-changed' event", ->
selection.setBufferRange([[2, 0], [2, 10]], reverse: true)
changeScreenRangeHandler = jasmine.createSpy('changeScreenRangeHandler')
@@ -68,10 +68,7 @@ describe "Selection", ->
expect(changeScreenRangeHandler).toHaveBeenCalled()
describe "when the selection is destroyed", ->
it "destroys its cursor and its anchor's cursor", ->
it "destroys its marker", ->
selection.setBufferRange([[2, 0], [2, 10]])
selection.destroy()
expect(editSession.getAnchors().indexOf(selection.anchor)).toBe -1
expect(editSession.getAnchors().indexOf(selection.cursor.anchor)).toBe -1
expect(editSession.getMarkerBufferRange(selection.marker)).toBeUndefined()

View File

@@ -1,4 +1,5 @@
TextMateGrammar = require 'text-mate-grammar'
TextMatePackage = require 'text-mate-package'
plist = require 'plist'
fs = require 'fs'
_ = require 'underscore'
@@ -257,3 +258,14 @@ describe "TextMateGrammar", ->
{tokens, ruleStack} = grammar.tokenizeLine("if(1){if(1){m()}}")
expect(tokens[5]).toEqual value: "if", scopes: ["source.c", "meta.block.c", "keyword.control.c"]
expect(tokens[10]).toEqual value: "m", scopes: ["source.c", "meta.block.c", "meta.block.c", "meta.function-call.c", "support.function.any-method.c"]
describe "when the grammar is CSON", ->
it "loads the grammar and correctly parses a keyword", ->
spyOn(syntax, 'addGrammar')
pack = new TextMatePackage(fixturesProject.resolve("packages/package-with-a-cson-grammar.tmbundle"))
pack.load()
grammar = pack.grammars[0]
expect(grammar).toBeTruthy()
expect(grammar.scopeName).toBe "source.alot"
{tokens} = grammar.tokenizeLine("this is alot of code")
expect(tokens[1]).toEqual value: "alot", scopes: ["source.alot", "keyword.alot"]

View File

@@ -63,161 +63,145 @@ describe "UndoManager", ->
undoManager.redo()
expect(buffer.getText()).toContain 'qsport'
describe "transact([fn])", ->
describe "when called with a function", ->
it "causes changes performed within the function's dynamic extent to be undone simultaneously", ->
buffer.insert([0, 0], "foo")
describe "transaction methods", ->
describe "transact([fn])", ->
describe "when called with a function", ->
it "causes changes performed within the function's dynamic extent to be undone simultaneously", ->
buffer.insert([0, 0], "foo")
undoManager.transact ->
undoManager.transact ->
buffer.insert([1, 2], "111")
buffer.insert([1, 9], "222")
undoManager.transact ->
buffer.insert([1, 2], "111")
buffer.insert([1, 9], "222")
expect(buffer.lineForRow(1)).toBe ' 111var 222sort = function(items) {'
expect(buffer.lineForRow(1)).toBe ' 111var 222sort = function(items) {'
undoManager.undo()
expect(buffer.lineForRow(1)).toBe ' var sort = function(items) {'
expect(buffer.lineForRow(0)).toContain 'foo'
undoManager.undo()
expect(buffer.lineForRow(1)).toBe ' var sort = function(items) {'
expect(buffer.lineForRow(0)).toContain 'foo'
undoManager.undo()
undoManager.undo()
expect(buffer.lineForRow(0)).not.toContain 'foo'
expect(buffer.lineForRow(0)).not.toContain 'foo'
undoManager.redo()
expect(buffer.lineForRow(0)).toContain 'foo'
undoManager.redo()
expect(buffer.lineForRow(0)).toContain 'foo'
undoManager.redo()
expect(buffer.lineForRow(1)).toBe ' 111var 222sort = function(items) {'
undoManager.redo()
expect(buffer.lineForRow(1)).toBe ' 111var 222sort = function(items) {'
undoManager.undo()
expect(buffer.lineForRow(1)).toBe ' var sort = function(items) {'
undoManager.undo()
expect(buffer.lineForRow(1)).toBe ' var sort = function(items) {'
it "does not record empty transactions", ->
buffer.insert([0,0], "foo")
undoManager.transact ->
undoManager.undo()
expect(buffer.lineForRow(0)).not.toContain("foo")
it "undoes operations that occured prior to an exception when the transaction is undone", ->
buffer.setText("jumpstreet")
expect(->
it "does not record empty transactions", ->
buffer.insert([0,0], "foo")
undoManager.transact ->
buffer.insert([0,0], "3")
buffer.insert([0,0], "2")
throw new Error("problem")
buffer.insert([0,0], "2")
).toThrow('problem')
expect(buffer.lineForRow(0)).toBe "23jumpstreet"
undoManager.undo()
expect(buffer.lineForRow(0)).toBe "jumpstreet"
undoManager.undo()
expect(buffer.lineForRow(0)).not.toContain("foo")
describe "when called without a function", ->
beforeEach ->
it "undoes operations that occured prior to an exception when the transaction is undone", ->
buffer.setText("jumpstreet")
expect(->
undoManager.transact ->
buffer.insert([0,0], "3")
buffer.insert([0,0], "2")
throw new Error("problem")
buffer.insert([0,0], "2")
).toThrow('problem')
expect(buffer.lineForRow(0)).toBe "23jumpstreet"
undoManager.undo()
expect(buffer.lineForRow(0)).toBe "jumpstreet"
describe "when called without a function", ->
beforeEach ->
buffer.setText('')
it "returns a transaction object that can be committed later", ->
buffer.append('1')
undoManager.transact()
buffer.append('2')
buffer.append('3')
undoManager.commit()
buffer.append('4')
expect(buffer.getText()).toBe '1234'
undoManager.undo()
expect(buffer.getText()).toBe '123'
undoManager.undo()
expect(buffer.getText()).toBe '1'
undoManager.redo()
expect(buffer.getText()).toBe '123'
it "returns a transaction object that can be aborted later", ->
buffer.append('1')
buffer.append('2')
undoManager.transact()
buffer.append('3')
buffer.append('4')
expect(buffer.getText()).toBe '1234'
undoManager.abort()
expect(buffer.getText()).toBe '12'
undoManager.undo()
expect(buffer.getText()).toBe '1'
undoManager.redo()
expect(buffer.getText()).toBe '12'
undoManager.redo()
expect(buffer.getText()).toBe '12'
describe "commit", ->
it "throws an exception if there is no current transaction", ->
expect(-> buffer.commit()).toThrow()
describe "abort", ->
it "does not affect the undo stack when the current transaction is empty", ->
buffer.setText('')
it "returns a transaction object that can be committed later", ->
buffer.append('1')
undoManager.transact()
buffer.append('2')
buffer.append('3')
undoManager.commit()
buffer.append('4')
expect(buffer.getText()).toBe '1234'
undoManager.undo()
expect(buffer.getText()).toBe '123'
undoManager.undo()
expect(buffer.getText()).toBe '1'
undoManager.redo()
expect(buffer.getText()).toBe '123'
it "returns a transaction object that can be aborted later", ->
buffer.append('1')
buffer.append('2')
undoManager.transact()
buffer.append('3')
buffer.append('4')
expect(buffer.getText()).toBe '1234'
undoManager.abort()
expect(buffer.getText()).toBe '12'
undoManager.undo()
expect(buffer.getText()).toBe '1'
undoManager.redo()
expect(buffer.getText()).toBe '12'
undoManager.redo()
expect(buffer.getText()).toBe '12'
describe "commit", ->
it "throws an exception if there is no current transaction", ->
expect(->
buffer.commit()
).toThrow()
describe "abort", ->
it "does not affect the undo stack when the current transaction is empty", ->
buffer.setText('')
buffer.append('1')
buffer.transact()
buffer.abort()
expect(buffer.getText()).toBe '1'
buffer.undo()
expect(buffer.getText()).toBe ''
it "throws an exception if there is no current transaction", ->
expect(->
buffer.transact()
buffer.abort()
).toThrow()
expect(buffer.getText()).toBe '1'
buffer.undo()
expect(buffer.getText()).toBe ''
describe "when a `do` operation throws an exception", ->
it "clears the stack", ->
spyOn(console, 'error')
buffer.setText("word")
class FailingOperation
do: -> throw new Error("I'm a bad do operation")
it "throws an exception if there is no current transaction", ->
expect(-> buffer.abort()).toThrow()
buffer.insert([0,0], "1")
describe "exception handling", ->
describe "when a `do` operation throws an exception", ->
it "clears the stack", ->
spyOn(console, 'error')
buffer.setText("word")
buffer.insert([0,0], "1")
expect(->
undoManager.pushOperation(do: -> throw new Error("I'm a bad do operation"))
).toThrow("I'm a bad do operation")
expect(->
undoManager.pushOperation(new FailingOperation())
).toThrow("I'm a bad do operation")
undoManager.undo()
expect(buffer.lineForRow(0)).toBe "1word"
describe "when an `undo` operation throws an exception", ->
it "clears the stack", ->
spyOn(console, 'error')
buffer.setText("word")
class FailingOperation
undo: -> throw new Error("I'm a bad undo operation")
buffer.insert([0,0], "1")
undoManager.pushOperation(new FailingOperation())
expect(->
undoManager.undo()
).toThrow("I'm a bad undo operation")
expect(buffer.lineForRow(0)).toBe "1word"
expect(buffer.lineForRow(0)).toBe "1word"
describe "when an `redo` operation throws an exception", ->
it "clears the stack", ->
spyOn(console, 'error')
buffer.setText("word")
class FailingOperation
redo: -> throw new Error("I'm a bad redo operation")
describe "when an `undo` operation throws an exception", ->
it "clears the stack", ->
spyOn(console, 'error')
buffer.setText("word")
buffer.insert([0,0], "1")
undoManager.pushOperation(undo: -> throw new Error("I'm a bad undo operation"))
expect(-> undoManager.undo()).toThrow("I'm a bad undo operation")
expect(buffer.lineForRow(0)).toBe "1word"
buffer.insert([0,0], "1")
undoManager.pushOperation(new FailingOperation())
undoManager.undo()
expect(->
undoManager.redo()
).toThrow("I'm a bad redo operation")
expect(buffer.lineForRow(0)).toBe "1word"
describe "when an `redo` operation throws an exception", ->
it "clears the stack", ->
spyOn(console, 'error')
buffer.setText("word")
buffer.insert([0,0], "1")
undoManager.pushOperation(redo: -> throw new Error("I'm a bad redo operation"))
undoManager.undo()
expect(-> undoManager.redo()).toThrow("I'm a bad redo operation")
expect(buffer.lineForRow(0)).toBe "1word"

View File

@@ -0,0 +1,4 @@
'name': 'Really'
'scope': 'source.alot'
'tabTrigger': 'really'
'content': 'I really like $1 alot$0'

View File

@@ -0,0 +1,11 @@
'fileTypes': ['alot']
'name': 'Alot'
'scopeName': 'source.alot'
'patterns': [
{
'captures':
'0':
'name': 'keyword.alot'
'match': 'alot'
}
]

View File

@@ -0,0 +1,6 @@
module.exports =
activationEventCallCount: 0
activate: ->
rootView.getActiveEditor()?.command 'activation-event', =>
@activationEventCallCount++

View File

@@ -0,0 +1,2 @@
'activationEvents': ['activation-event']
'main': 'main'

View File

@@ -1,6 +1,8 @@
AtomPackage = require 'atom-package'
module.exports =
class MyPackage extends AtomPackage
configDefaults:
numbers: { one: 1, two: 2 }
activate: ->
@activateCalled = true
deactivate: ->

View File

@@ -17,12 +17,20 @@ fixturePackagesPath = require.resolve('fixtures/packages')
require.paths.unshift(fixturePackagesPath)
[bindingSetsToRestore, bindingSetsByFirstKeystrokeToRestore] = []
# Load TextMate bundles, which specs rely on (but not other packages)
atom.loadTextMatePackages()
# Specs rely on TextMate bundles (but not atom packages)
window.loadTextMatePackages = ->
TextMatePackage = require 'text-mate-package'
config.packageDirPaths.unshift(fixturePackagesPath)
window.textMatePackages = []
for path in atom.getPackagePaths() when TextMatePackage.testName(path)
window.textMatePackages.push atom.loadPackage(fs.base(path))
window.loadTextMatePackages()
beforeEach ->
window.fixturesProject = new Project(require.resolve('fixtures'))
window.resetTimeouts()
atom.atomPackageStates = {}
# used to reset keymap after each spec
bindingSetsToRestore = _.clone(keymap.bindingSets)
@@ -30,7 +38,6 @@ beforeEach ->
# reset config before each spec; don't load or save from/to `config.json`
window.config = new Config()
config.packageDirPaths.unshift(fixturePackagesPath)
spyOn(config, 'load')
spyOn(config, 'save')
config.set "editor.fontSize", 16
@@ -63,17 +70,19 @@ afterEach ->
window.keymap.bindKeys '*', 'meta-w': 'close'
$(document).on 'close', -> window.close()
$(document).on 'toggle-dev-tools', (e) ->
atom.toggleDevTools() if $('#root-view').length is 0
$('html,body').css('overflow', 'auto')
jasmine.getEnv().addEqualityTester(_.isEqual) # Use underscore's definition of equality for toEqual assertions
jasmine.getEnv().defaultTimeoutInterval = 1000
ensureNoPathSubscriptions = ->
watchedPaths = $native.getWatchedPaths()
$native.unwatchAllPaths()
if watchedPaths.length > 0
throw new Error("Leaking subscriptions for paths: " + watchedPaths.join(", "))
# Use underscore's definition of equality for toEqual assertions
jasmine.Env.prototype.equals_ = _.isEqual
emitObject = jasmine.StringPrettyPrinter.prototype.emitObject
jasmine.StringPrettyPrinter.prototype.emitObject = (obj) ->
if obj.inspect
@@ -85,8 +94,6 @@ jasmine.unspy = (object, methodName) ->
throw new Error("Not a spy") unless object[methodName].originalValue?
object[methodName] = object[methodName].originalValue
jasmine.getEnv().defaultTimeoutInterval = 1000
window.keyIdentifierForKey = (key) ->
if key.length > 1 # named key
key

View File

@@ -127,3 +127,18 @@ describe 'Child Processes', ->
runs ->
expect(output.length).toBeGreaterThan 1
describe "when the cwd option is set", ->
it "runs the task from the specified current working directory", ->
output = []
waitsForPromise ->
options =
cwd: fixturesProject.getPath()
stdout: (data) -> output.push(data)
stderr: (data) ->
ChildProcess.exec("pwd", options)
runs ->
expect(output.join('')).toBe "#{fixturesProject.getPath()}\n"

View File

@@ -1,41 +0,0 @@
Range = require 'range'
EventEmitter = require 'event-emitter'
Subscriber = require 'subscriber'
_ = require 'underscore'
module.exports =
class AnchorRange
start: null
end: null
buffer: null
editSession: null # optional
destroyed: false
constructor: (bufferRange, @buffer, @editSession) ->
bufferRange = Range.fromObject(bufferRange)
@startAnchor = @buffer.addAnchorAtPosition(bufferRange.start, ignoreChangesStartingOnAnchor: true)
@endAnchor = @buffer.addAnchorAtPosition(bufferRange.end)
@subscribe @startAnchor, 'destroyed', => @destroy()
@subscribe @endAnchor, 'destroyed', => @destroy()
getBufferRange: ->
new Range(@startAnchor.getBufferPosition(), @endAnchor.getBufferPosition())
getScreenRange: ->
new Range(@startAnchor.getScreenPosition(), @endAnchor.getScreenPosition())
containsBufferPosition: (bufferPosition) ->
@getBufferRange().containsPoint(bufferPosition)
destroy: ->
return if @destroyed
@unsubscribe()
@startAnchor.destroy()
@endAnchor.destroy()
@buffer.removeAnchorRange(this)
@editSession?.removeAnchorRange(this)
@destroyed = true
@trigger 'destroyed'
_.extend(AnchorRange.prototype, EventEmitter)
_.extend(AnchorRange.prototype, Subscriber)

View File

@@ -1,91 +0,0 @@
Point = require 'point'
EventEmitter = require 'event-emitter'
_ = require 'underscore'
module.exports =
class Anchor
buffer: null
editSession: null # optional
bufferPosition: null
screenPosition: null
ignoreChangesStartingOnAnchor: false
strong: false
destroyed: false
constructor: (@buffer, options = {}) ->
{ @editSession, @ignoreChangesStartingOnAnchor, @strong } = options
handleBufferChange: (e) ->
{ oldRange, newRange } = e
position = @getBufferPosition()
if oldRange.containsPoint(position, exclusive: true)
if @strong
@setBufferPosition(oldRange.start)
else
@destroy()
return
return if @ignoreChangesStartingOnAnchor and position.isEqual(oldRange.start)
return if position.isLessThan(oldRange.end)
newRow = newRange.end.row
newColumn = newRange.end.column
if position.row == oldRange.end.row
newColumn += position.column - oldRange.end.column
else
newColumn = position.column
newRow += position.row - oldRange.end.row
@setBufferPosition([newRow, newColumn], bufferChange: true)
getBufferPosition: ->
@bufferPosition
setBufferPosition: (position, options={}) ->
@bufferPosition = Point.fromObject(position)
clip = options.clip ? true
@bufferPosition = @buffer.clipPosition(@bufferPosition) if clip
@refreshScreenPosition(options)
getScreenPosition: ->
@screenPosition
getScreenRow: ->
@screenPosition.row
setScreenPosition: (position, options={}) ->
oldScreenPosition = @screenPosition
oldBufferPosition = @bufferPosition
@screenPosition = Point.fromObject(position)
clip = options.clip ? true
assignBufferPosition = options.assignBufferPosition ? true
@screenPosition = @editSession.clipScreenPosition(@screenPosition, options) if clip
@bufferPosition = @editSession.bufferPositionForScreenPosition(@screenPosition, options) if assignBufferPosition
Object.freeze @screenPosition
Object.freeze @bufferPosition
unless @screenPosition.isEqual(oldScreenPosition)
@trigger 'moved',
oldScreenPosition: oldScreenPosition
newScreenPosition: @screenPosition
oldBufferPosition: oldBufferPosition
newBufferPosition: @bufferPosition
bufferChange: options.bufferChange
autoscroll: options.autoscroll
refreshScreenPosition: (options={}) ->
return unless @editSession
screenPosition = @editSession.screenPositionForBufferPosition(@bufferPosition, options)
@setScreenPosition(screenPosition, bufferChange: options.bufferChange, clip: false, assignBufferPosition: false, autoscroll: options.autoscroll)
destroy: ->
return if @destroyed
@buffer.removeAnchor(this)
@editSession?.removeAnchor(this)
@destroyed = true
@trigger 'destroyed'
_.extend(Anchor.prototype, EventEmitter)

View File

@@ -1,42 +1,88 @@
Package = require 'package'
fs = require 'fs'
_ = require 'underscore'
$ = require 'jquery'
module.exports =
class AtomPackage extends Package
metadata: null
keymapsDirPath: null
autoloadStylesheets: true
packageMain: null
constructor: (@name) ->
super
@keymapsDirPath = fs.join(@path, 'keymaps')
load: ->
load: ({activateImmediately}={}) ->
try
@loadMetadata()
@loadKeymaps()
@loadStylesheets() if @autoloadStylesheets
rootView?.activatePackage(@name, this) unless @isDirectory
@loadStylesheets()
if @metadata.activationEvents and not activateImmediately
@subscribeToActivationEvents()
else
@activatePackageMain()
catch e
console.warn "Failed to load package named '#{@name}'", e.stack
this
disableEventHandlersOnBubblePath: (event) ->
bubblePathEventHandlers = []
disabledHandler = ->
element = $(event.target)
while element.length
if eventHandlers = element.data('events')?[event.type]
for eventHandler in eventHandlers
eventHandler.disabledHandler = eventHandler.handler
eventHandler.handler = disabledHandler
bubblePathEventHandlers.push(eventHandler)
element = element.parent()
bubblePathEventHandlers
restoreEventHandlersOnBubblePath: (eventHandlers) ->
for eventHandler in eventHandlers
eventHandler.handler = eventHandler.disabledHandler
delete eventHandler.disabledHandler
unsubscribeFromActivationEvents: (activateHandler) ->
if _.isArray(@metadata.activationEvents)
rootView.off(event, activateHandler) for event in @metadata.activationEvents
else
rootView.off(event, selector, activateHandler) for event, selector of @metadata.activationEvents
subscribeToActivationEvents: () ->
activateHandler = (event) =>
bubblePathEventHandlers = @disableEventHandlersOnBubblePath(event)
@activatePackageMain()
$(event.target).trigger(event)
@restoreEventHandlersOnBubblePath(bubblePathEventHandlers)
@unsubscribeFromActivationEvents(activateHandler)
if _.isArray(@metadata.activationEvents)
rootView.command(event, activateHandler) for event in @metadata.activationEvents
else
rootView.command(event, selector, activateHandler) for event, selector of @metadata.activationEvents
activatePackageMain: ->
mainPath = @path
mainPath = fs.join(mainPath, @metadata.main) if @metadata.main
mainPath = require.resolve(mainPath)
if fs.isFile(mainPath)
@packageMain = require(mainPath)
config.setDefaults(@name, @packageMain.configDefaults)
atom.activateAtomPackage(this)
loadMetadata: ->
if metadataPath = fs.resolveExtension(fs.join(@path, "package"), ['cson', 'json'])
if metadataPath = fs.resolveExtension(fs.join(@path, 'package'), ['cson', 'json'])
@metadata = fs.readObject(metadataPath)
@metadata ?= {}
loadKeymaps: ->
if keymaps = @metadata?.keymaps
keymaps = keymaps.map (relativePath) =>
fs.resolve(@keymapsDirPath, relativePath, ['cson', 'json', ''])
keymap.load(keymapPath) for keymapPath in keymaps
keymapsDirPath = fs.join(@path, 'keymaps')
if @metadata.keymaps
for path in @metadata.keymaps
keymapPath = fs.resolve(keymapsDirPath, path, ['cson', 'json', ''])
keymap.load(keymapPath)
else
keymap.loadDirectory(@keymapsDirPath)
keymap.loadDirectory(keymapsDirPath)
loadStylesheets: ->
for stylesheetPath in @getStylesheetPaths()
requireStylesheet(stylesheetPath)
getStylesheetPaths: ->
stylesheetDirPath = fs.join(@path, 'stylesheets')
fs.list(stylesheetDirPath)
for stylesheetPath in fs.list(stylesheetDirPath)
requireStylesheet(stylesheetPath)

View File

@@ -8,11 +8,10 @@ class AtomTheme extends Theme
@stylesheets[stylesheetPath] = fs.read(stylesheetPath)
load: ->
if /\.css$/.test(@path)
@loadStylesheet @path
if fs.extension(@path) is '.css'
@loadStylesheet(@path)
else
json = fs.read(fs.join(@path, "package.json"))
for stylesheetName in JSON.parse(json).stylesheets
stylesheetPath = fs.join(@path, stylesheetName)
@loadStylesheet stylesheetPath
metadataPath = fs.resolveExtension(fs.join(@path, 'package'), ['cson', 'json'])
stylesheetNames = fs.readObject(metadataPath).stylesheets
@loadStylesheet(fs.join(@path, name)) for name in stylesheetNames
super

View File

@@ -12,42 +12,58 @@ _.extend atom,
exitWhenDone: window.location.params.exitWhenDone
loadedThemes: []
pendingBrowserProcessCallbacks: {}
loadedPackages: []
activatedAtomPackages: []
atomPackageStates: {}
activateAtomPackage: (pack) ->
@activatedAtomPackages.push(pack)
pack.packageMain.activate(@atomPackageStates[pack.name])
deactivateAtomPackages: ->
pack.packageMain.deactivate?() for pack in @activatedAtomPackages
@activatedAtomPackages = []
serializeAtomPackages: ->
packageStates = {}
for pack in @activatedAtomPackages
try
packageStates[pack.name] = pack.packageMain.serialize?()
catch e
console?.error("Exception serializing '#{pack.name}' package's module\n", e.stack)
packageStates
loadPackage: (name, options) ->
packagePath = _.find @getPackagePaths(), (packagePath) -> fs.base(packagePath) == name
pack = Package.build(packagePath)
pack?.load(options)
loadPackages: ->
{packages, asyncTextMatePackages} = _.groupBy @getPackages(), (pack) ->
if pack instanceof TextMatePackage and pack.name isnt 'text.tmbundle'
'asyncTextMatePackages'
textMatePackages = []
for path in @getPackagePaths()
pack = Package.build(path)
@loadedPackages.push(pack)
if pack instanceof TextMatePackage and fs.base(pack.path) isnt 'text.tmbundle'
textMatePackages.push(pack)
else
'packages'
pack.load()
pack.load() for pack in packages
if asyncTextMatePackages.length
new LoadTextMatePackagesTask(asyncTextMatePackages).start()
new LoadTextMatePackagesTask(textMatePackages).start() if textMatePackages.length > 0
getPackages: ->
@packages ?= @getPackageNames().map((name) -> Package.build(name))
.filter((pack) -> pack?)
new Array(@packages...)
getLoadedPackages: ->
_.clone(@loadedPackages)
loadTextMatePackages: ->
pack.load() for pack in @getTextMatePackages()
getTextMatePackages: ->
@getPackages().filter (pack) -> pack instanceof TextMatePackage
loadPackage: (name) ->
Package.build(name)?.load()
getPackageNames: ->
getPackagePaths: ->
disabledPackages = config.get("core.disabledPackages") ? []
allPackageNames = []
packagePaths = []
for packageDirPath in config.packageDirPaths
packageNames = fs.list(packageDirPath)
.filter((packagePath) -> fs.isDirectory(packagePath))
.map((packagePath) -> fs.base(packagePath))
allPackageNames.push(packageNames...)
_.unique(allPackageNames)
.filter (name) -> not _.contains(disabledPackages, name)
for packagePath in fs.list(packageDirPath)
continue if not fs.isDirectory(packagePath)
continue if fs.base(packagePath) in disabledPackages
continue if packagePath in packagePaths
packagePaths.push(packagePath)
packagePaths
loadThemes: ->
themeNames = config.get("core.themes") ? ['atom-dark-ui', 'atom-dark-syntax']

View File

@@ -8,6 +8,8 @@ class BufferChangeOperation
oldText: null
newRange: null
newText: null
markersToRestoreOnUndo: null
markersToRestoreOnRedo: null
constructor: ({@buffer, @oldRange, @newText, @options}) ->
@options ?= {}
@@ -15,18 +17,24 @@ class BufferChangeOperation
do: ->
@oldText = @buffer.getTextInRange(@oldRange)
@newRange = @calculateNewRange(@oldRange, @newText)
@markersToRestoreOnUndo = @invalidateMarkers(@oldRange)
@changeBuffer
oldRange: @oldRange
newRange: @newRange
oldText: @oldText
newText: @newText
redo: ->
@restoreMarkers(@markersToRestoreOnRedo)
undo: ->
@markersToRestoreOnRedo = @invalidateMarkers(@newRange)
@changeBuffer
oldRange: @newRange
newRange: @oldRange
oldText: @newText
newText: @oldText
@restoreMarkers(@markersToRestoreOnUndo)
splitLines: (text) ->
lines = text.split('\n')
@@ -41,7 +49,6 @@ class BufferChangeOperation
changeBuffer: ({ oldRange, newRange, newText, oldText }) ->
{ prefix, suffix } = @buffer.prefixAndSuffixForRange(oldRange)
{lines, lineEndings} = @splitLines(newText)
lastLineIndex = lines.length - 1
@@ -65,7 +72,7 @@ class BufferChangeOperation
event = { oldRange, newRange, oldText, newText }
@buffer.trigger 'changed', event
@buffer.scheduleStoppedChangingEvent()
@buffer.updateAnchors(event)
@updateMarkers(event)
newRange
calculateNewRange: (oldRange, newText) ->
@@ -78,3 +85,18 @@ class BufferChangeOperation
newRange.end.row += lastLineIndex
newRange.end.column = lines[lastLineIndex].length
newRange
invalidateMarkers: (oldRange) ->
_.compact(@buffer.getMarkers().map (marker) -> marker.tryToInvalidate(oldRange))
updateMarkers: (bufferChange) ->
marker.handleBufferChange(bufferChange) for marker in @buffer.getMarkers()
@buffer.trigger 'markers-updated'
restoreMarkers: (markersToRestore) ->
for [id, previousRange] in markersToRestore
if validMarker = @buffer.validMarkers[id]
validMarker.setRange(previousRange)
else if invalidMarker = @buffer.invalidMarkers[id]
@buffer.validMarkers[id] = invalidMarker

View File

@@ -0,0 +1,152 @@
_ = require 'underscore'
Point = require 'point'
Range = require 'range'
module.exports =
class BufferMarker
headPosition: null
tailPosition: null
observers: null
suppressObserverNotification: false
stayValid: false
constructor: ({@id, @buffer, range, @stayValid, noTail, reverse}) ->
@headPositionObservers = []
@observers = []
@setRange(range, {noTail, reverse})
setRange: (range, options={}) ->
@consolidateObserverNotifications false, =>
range = Range.fromObject(range)
if options.reverse
@setTailPosition(range.end) unless options.noTail
@setHeadPosition(range.start)
else
@setTailPosition(range.start) unless options.noTail
@setHeadPosition(range.end)
isReversed: ->
@tailPosition? and @headPosition.isLessThan(@tailPosition)
getRange: ->
if @tailPosition
new Range(@tailPosition, @headPosition)
else
new Range(@headPosition, @headPosition)
getHeadPosition: -> @headPosition
getTailPosition: -> @tailPosition ? @getHeadPosition()
setHeadPosition: (newHeadPosition, options={}) ->
oldHeadPosition = @getHeadPosition()
newHeadPosition = Point.fromObject(newHeadPosition)
newHeadPosition = @buffer.clipPosition(newHeadPosition) if options.clip ? true
return if newHeadPosition.isEqual(@headPosition)
@headPosition = newHeadPosition
bufferChanged = !!options.bufferChanged
@notifyObservers({oldHeadPosition, newHeadPosition, bufferChanged})
@headPosition
setTailPosition: (newTailPosition, options={}) ->
oldTailPosition = @getTailPosition()
newTailPosition = Point.fromObject(newTailPosition)
newTailPosition = @buffer.clipPosition(newTailPosition) if options.clip ? true
return if newTailPosition.isEqual(@tailPosition)
@tailPosition = newTailPosition
bufferChanged = !!options.bufferChanged
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged})
@tailPosition
getStartPosition: ->
@getRange().start
getEndPosition: ->
@getRange().end
placeTail: ->
@setTailPosition(@getHeadPosition()) unless @tailPosition
clearTail: ->
oldTailPosition = @getTailPosition()
@tailPosition = null
newTailPosition = @getTailPosition()
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged: false})
tryToInvalidate: (oldRange) ->
containsStart = oldRange.containsPoint(@getStartPosition(), exclusive: true)
containsEnd = oldRange.containsPoint(@getEndPosition(), exclusive: true)
return unless containsEnd or containsStart
if @stayValid
previousRange = @getRange()
if containsStart and containsEnd
@setRange([oldRange.end, oldRange.end])
else if containsStart
@setRange([oldRange.end, @getEndPosition()])
else
@setRange([@getStartPosition(), oldRange.start])
[@id, previousRange]
else
@invalidate()
[@id]
handleBufferChange: (bufferChange) ->
@consolidateObserverNotifications true, =>
@setHeadPosition(@updatePosition(@headPosition, bufferChange, false), clip: false, bufferChanged: true)
@setTailPosition(@updatePosition(@tailPosition, bufferChange, true), clip: false, bufferChanged: true) if @tailPosition
updatePosition: (position, bufferChange, isFirstPoint) ->
{ oldRange, newRange } = bufferChange
return position if oldRange.containsPoint(position, exclusive: true)
return position if isFirstPoint and oldRange.start.isEqual(position)
return position if position.isLessThan(oldRange.end)
newRow = newRange.end.row
newColumn = newRange.end.column
if position.row == oldRange.end.row
newColumn += position.column - oldRange.end.column
else
newColumn = position.column
newRow += position.row - oldRange.end.row
[newRow, newColumn]
observe: (callback) ->
@observers.push(callback)
cancel: => @unobserve(callback)
unobserve: (callback) ->
_.remove(@observers, callback)
containsPoint: (point) ->
@getRange().containsPoint(point)
notifyObservers: ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged}) ->
return if @suppressObserverNotification
return if _.isEqual(newHeadPosition, oldHeadPosition) and _.isEqual(newTailPosition, oldTailPosition)
oldHeadPosition ?= @getHeadPosition()
newHeadPosition ?= @getHeadPosition()
oldTailPosition ?= @getTailPosition()
newTailPosition ?= @getTailPosition()
for observer in @getObservers()
observer({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged})
getObservers: ->
new Array(@observers...)
consolidateObserverNotifications: (bufferChanged, fn) ->
@suppressObserverNotification = true
oldHeadPosition = @getHeadPosition()
oldTailPosition = @getTailPosition()
fn()
newHeadPosition = @getHeadPosition()
newTailPosition = @getTailPosition()
@suppressObserverNotification = false
@notifyObservers({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged})
invalidate: (preserve) ->
delete @buffer.validMarkers[@id]
@buffer.invalidMarkers[@id] = this

View File

@@ -6,8 +6,7 @@ Range = require 'range'
EventEmitter = require 'event-emitter'
UndoManager = require 'undo-manager'
BufferChangeOperation = require 'buffer-change-operation'
Anchor = require 'anchor'
AnchorRange = require 'anchor-range'
BufferMarker = require 'buffer-marker'
module.exports =
class Buffer
@@ -21,14 +20,15 @@ class Buffer
lines: null
lineEndings: null
file: null
anchors: null
anchorRanges: null
validMarkers: null
invalidMarkers: null
refcount: 0
constructor: (path, @project) ->
@id = @constructor.idCounter++
@anchors = []
@anchorRanges = []
@nextMarkerId = 1
@validMarkers = {}
@invalidMarkers = {}
@lines = ['']
@lineEndings = []
@@ -264,38 +264,70 @@ class Buffer
isEmpty: -> @lines.length is 1 and @lines[0].length is 0
getAnchors: -> new Array(@anchors...)
getMarkers: ->
_.values(@validMarkers)
addAnchor: (options) ->
anchor = new Anchor(this, options)
@anchors.push(anchor)
anchor
getMarkerCount: ->
_.size(@validMarkers)
addAnchorAtPosition: (position, options) ->
anchor = @addAnchor(options)
anchor.setBufferPosition(position)
anchor
markRange: (range, options={}) ->
marker = new BufferMarker(_.defaults({
id: (@nextMarkerId++).toString()
buffer: this
range
}, options))
@validMarkers[marker.id] = marker
marker.id
addAnchorRange: (range, editSession) ->
anchorRange = new AnchorRange(range, this, editSession)
@anchorRanges.push(anchorRange)
anchorRange
markPosition: (position, options) ->
@markRange([position, position], _.defaults({noTail: true}, options))
removeAnchor: (anchor) ->
_.remove(@anchors, anchor)
destroyMarker: (id) ->
delete @validMarkers[id]
delete @invalidMarkers[id]
removeAnchorRange: (anchorRange) ->
_.remove(@anchorRanges, anchorRange)
getMarkerPosition: (args...) ->
@getMarkerHeadPosition(args...)
anchorRangesForPosition: (position) ->
_.filter @anchorRanges, (anchorRange) -> anchorRange.containsBufferPosition(position)
setMarkerPosition: (args...) ->
@setMarkerHeadPosition(args...)
updateAnchors: (change) ->
anchors = @getAnchors()
anchor.pauseEvents() for anchor in anchors
anchor.handleBufferChange(change) for anchor in anchors
anchor.resumeEvents() for anchor in anchors
@trigger 'anchors-updated'
getMarkerHeadPosition: (id) ->
@validMarkers[id]?.getHeadPosition()
setMarkerHeadPosition: (id, position, options) ->
@validMarkers[id]?.setHeadPosition(position)
getMarkerTailPosition: (id) ->
@validMarkers[id]?.getTailPosition()
setMarkerTailPosition: (id, position, options) ->
@validMarkers[id]?.setTailPosition(position)
getMarkerRange: (id) ->
@validMarkers[id]?.getRange()
setMarkerRange: (id, range, options) ->
@validMarkers[id]?.setRange(range, options)
placeMarkerTail: (id) ->
@validMarkers[id]?.placeTail()
clearMarkerTail: (id) ->
@validMarkers[id]?.clearTail()
isMarkerReversed: (id) ->
@validMarkers[id]?.isReversed()
observeMarker: (id, callback) ->
@validMarkers[id]?.observe(callback)
markersForPosition: (bufferPosition) ->
bufferPosition = Point.fromObject(bufferPosition)
ids = []
for id, marker of @validMarkers
ids.push(id) if marker.containsPoint(bufferPosition)
ids
matchesInCharacterRange: (regex, startIndex, endIndex) ->
text = @getText()

View File

@@ -1,5 +1,4 @@
{View} = require 'space-pen'
Anchor = require 'anchor'
Point = require 'point'
Range = require 'range'
_ = require 'underscore'
@@ -18,18 +17,18 @@ class CursorView extends View
shouldPauseBlinking: false
initialize: (@cursor, @editor) ->
@cursor.on 'moved.cursor-view', ({ autoscroll }) =>
@cursor.on 'moved.cursor-view', =>
@needsUpdate = true
@shouldPauseBlinking = true
@editor.requestDisplayUpdate()
@cursor.on 'visibility-changed.cursor-view', (visible) =>
@needsUpdate = true
@cursor.on 'autoscrolled.cursor-view', =>
@editor.requestDisplayUpdate()
@cursor.on 'destroyed.cursor-view', =>
@needsRemoval = true
@editor.requestDisplayUpdate()
remove: ->
@editor.removeCursorView(this)
@@ -55,8 +54,8 @@ class CursorView extends View
needsAutoscroll: ->
@cursor.needsAutoscroll
autoscrolled: ->
@cursor.autoscrolled()
clearAutoscroll: ->
@cursor.clearAutoscroll()
getPixelPosition: ->
@editor.pixelPositionForScreenPosition(@getScreenPosition())

View File

@@ -1,6 +1,5 @@
Point = require 'point'
Range = require 'range'
Anchor = require 'anchor'
EventEmitter = require 'event-emitter'
_ = require 'underscore'
@@ -10,47 +9,60 @@ class Cursor
bufferPosition: null
goalColumn: null
visible: true
needsAutoscroll: false
needsAutoscroll: null
constructor: ({@editSession, screenPosition, bufferPosition}) ->
@anchor = @editSession.addAnchor(strong: true)
@anchor.on 'moved', (e) =>
@needsAutoscroll = (e.autoscroll ? true) and @isLastCursor()
@trigger 'moved', e
@editSession.trigger 'cursor-moved', e
constructor: ({@editSession, @marker}) ->
@editSession.observeMarker @marker, (e) =>
@setVisible(@selection.isEmpty())
@setScreenPosition(screenPosition) if screenPosition
@setBufferPosition(bufferPosition) if bufferPosition
{oldHeadScreenPosition, newHeadScreenPosition} = e
{oldHeadBufferPosition, newHeadBufferPosition} = e
{bufferChanged} = e
return if oldHeadScreenPosition.isEqual(newHeadScreenPosition)
@needsAutoscroll ?= @isLastCursor() and !bufferChanged
movedEvent =
oldBufferPosition: oldHeadBufferPosition
oldScreenPosition: oldHeadScreenPosition
newBufferPosition: newHeadBufferPosition
newScreenPosition: newHeadScreenPosition
bufferChanged: bufferChanged
@trigger 'moved', movedEvent
@editSession.trigger 'cursor-moved', movedEvent
@needsAutoscroll = true
destroy: ->
@anchor.destroy()
@editSession.destroyMarker(@marker)
@editSession.removeCursor(this)
@trigger 'destroyed'
setScreenPosition: (screenPosition, options) ->
@goalColumn = null
@clearSelection()
@anchor.setScreenPosition(screenPosition, options)
setScreenPosition: (screenPosition, options={}) ->
@changePosition options, =>
@editSession.setMarkerHeadScreenPosition(@marker, screenPosition, options)
getScreenPosition: ->
@anchor.getScreenPosition()
@editSession.getMarkerHeadScreenPosition(@marker)
getScreenRow: ->
@anchor.getScreenRow()
setBufferPosition: (bufferPosition, options) ->
@goalColumn = null
@clearSelection()
@anchor.setBufferPosition(bufferPosition, options)
setBufferPosition: (bufferPosition, options={}) ->
@changePosition options, =>
@editSession.setMarkerHeadBufferPosition(@marker, bufferPosition, options)
getBufferPosition: ->
@anchor.getBufferPosition()
@editSession.getMarkerHeadBufferPosition(@marker)
changePosition: (options, fn) ->
@goalColumn = null
@clearSelection()
@needsAutoscroll = options.autoscroll ? @isLastCursor()
unless fn()
@trigger 'autoscrolled' if @needsAutoscroll
setVisible: (visible) ->
if @visible != visible
@visible = visible
@needsAutoscroll = @visible and @isLastCursor()
@needsAutoscroll ?= true if @visible and @isLastCursor()
@trigger 'visibility-changed', @visible
isVisible: -> @visible
@@ -67,8 +79,8 @@ class Cursor
range = [[row, Math.min(0, column - 1)], [row, Math.max(0, column + 1)]]
/^\s+$/.test @editSession.getTextInBufferRange(range)
autoscrolled: ->
@needsAutoscroll = false
clearAutoscroll: ->
@needsAutoscroll = null
clearSelection: ->
if @selection
@@ -89,9 +101,6 @@ class Cursor
getCurrentBufferLine: ->
@editSession.lineForBufferRow(@getBufferRow())
refreshScreenPosition: ->
@anchor.refreshScreenPosition()
moveUp: (rowCount = 1) ->
{ row, column } = @getScreenPosition()
column = @goalColumn if @goalColumn?

View File

@@ -1,36 +0,0 @@
AtomPackage = require 'atom-package'
_ = require 'underscore'
module.exports =
class DeferredAtomPackage extends AtomPackage
constructor: ->
super
@autoloadStylesheets = false
activate: (@rootView, @state) ->
@instance = null
onLoadEvent = (e) => @onLoadEvent(e, @getInstance())
if _.isArray(@loadEvents)
for event in @loadEvents
@rootView.command(event, onLoadEvent)
else
for event, selector of @loadEvents
@rootView.command(event, selector, onLoadEvent)
this
deactivate: -> @instance?.deactivate?()
serialize: ->
if @instance
@instance.serialize?()
else
@state
getInstance: ->
unless @instance
@loadStylesheets()
InstanceClass = require @instanceClass
@instance = InstanceClass.activate(@rootView, @state)
@instance

View File

@@ -0,0 +1,114 @@
Range = require 'range'
_ = require 'underscore'
module.exports =
class DisplayBufferMarker
observers: null
bufferMarkerSubscription: null
headScreenPosition: null
tailScreenPosition: null
constructor: ({@id, @displayBuffer}) ->
@buffer = @displayBuffer.buffer
getScreenRange: ->
@displayBuffer.screenRangeForBufferRange(@getBufferRange(), wrapAtSoftNewlines: true)
setScreenRange: (screenRange, options) ->
@setBufferRange(@displayBuffer.bufferRangeForScreenRange(screenRange, options), options)
getBufferRange: ->
@buffer.getMarkerRange(@id)
setBufferRange: (bufferRange, options) ->
@buffer.setMarkerRange(@id, bufferRange, options)
getHeadScreenPosition: ->
@headScreenPosition ?= @displayBuffer.screenPositionForBufferPosition(@getHeadBufferPosition(), wrapAtSoftNewlines: true)
setHeadScreenPosition: (screenPosition, options) ->
screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options)
@setHeadBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options))
getHeadBufferPosition: ->
@buffer.getMarkerHeadPosition(@id)
setHeadBufferPosition: (bufferPosition) ->
@buffer.setMarkerHeadPosition(@id, bufferPosition)
getTailScreenPosition: ->
@tailScreenPosition ?= @displayBuffer.screenPositionForBufferPosition(@getTailBufferPosition(), wrapAtSoftNewlines: true)
setTailScreenPosition: (screenPosition, options) ->
screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options)
@setTailBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options))
getTailBufferPosition: ->
@buffer.getMarkerTailPosition(@id)
setTailBufferPosition: (bufferPosition) ->
@buffer.setMarkerTailPosition(@id, bufferPosition)
placeTail: ->
@buffer.placeMarkerTail(@id)
clearTail: ->
@buffer.clearMarkerTail(@id)
observe: (callback) ->
@observeBufferMarkerIfNeeded()
@observers.push(callback)
cancel: => @unobserve(callback)
unobserve: (callback) ->
_.remove(@observers, callback)
@unobserveBufferMarkerIfNeeded()
observeBufferMarkerIfNeeded: ->
return if @observers
@observers = []
@getHeadScreenPosition() # memoize current value
@getTailScreenPosition() # memoize current value
@bufferMarkerSubscription =
@buffer.observeMarker @id, ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged}) =>
@notifyObservers
oldHeadBufferPosition: oldHeadPosition
newHeadBufferPosition: newHeadPosition
oldTailBufferPosition: oldTailPosition
newTailBufferPosition: newTailPosition
bufferChanged: bufferChanged
@displayBuffer.markers[@id] = this
unobserveBufferMarkerIfNeeded: ->
return if @observers.length
@observers = null
@bufferMarkerSubscription.cancel()
delete @displayBuffer.markers[@id]
notifyObservers: ({oldHeadBufferPosition, oldTailBufferPosition, bufferChanged}) ->
oldHeadScreenPosition = @getHeadScreenPosition()
@headScreenPosition = null
newHeadScreenPosition = @getHeadScreenPosition()
oldTailScreenPosition = @getTailScreenPosition()
@tailScreenPosition = null
newTailScreenPosition = @getTailScreenPosition()
return if _.isEqual(newHeadScreenPosition, oldHeadScreenPosition) and _.isEqual(newTailScreenPosition, oldTailScreenPosition)
oldHeadBufferPosition ?= @getHeadBufferPosition()
newHeadBufferPosition = @getHeadBufferPosition()
oldTailBufferPosition ?= @getTailBufferPosition()
newTailBufferPosition = @getTailBufferPosition()
for observer in @getObservers()
observer({
oldHeadScreenPosition, newHeadScreenPosition,
oldTailScreenPosition, newTailScreenPosition,
oldHeadBufferPosition, newHeadBufferPosition,
oldTailBufferPosition, newTailBufferPosition,
bufferChanged
})
getObservers: ->
new Array(@observers...)

View File

@@ -7,6 +7,7 @@ Range = require 'range'
Fold = require 'fold'
ScreenLine = require 'screen-line'
Token = require 'token'
DisplayBufferMarker = require 'display-buffer-marker'
module.exports =
class DisplayBuffer
@@ -16,6 +17,7 @@ class DisplayBuffer
tokenizedBuffer: null
activeFolds: null
foldsById: null
markers: null
constructor: (@buffer, options={}) ->
@id = @constructor.idCounter++
@@ -24,6 +26,7 @@ class DisplayBuffer
@softWrapColumn = options.softWrapColumn ? Infinity
@activeFolds = {}
@foldsById = {}
@markers = {}
@buildLineMap()
@tokenizedBuffer.on 'changed', (e) => @handleTokenizedBufferChange(e)
@@ -33,13 +36,17 @@ class DisplayBuffer
@lineMap = new LineMap
@lineMap.insertAtScreenRow 0, @buildLinesForBufferRows(0, @buffer.getLastRow())
triggerChanged: (eventProperties, refreshMarkers=true) ->
@refreshMarkerScreenPositions() if refreshMarkers
@trigger 'changed', eventProperties
setSoftWrapColumn: (@softWrapColumn) ->
start = 0
end = @getLastRow()
@buildLineMap()
screenDelta = @getLastRow() - end
bufferDelta = 0
@trigger 'changed', { start, end, screenDelta, bufferDelta }
@triggerChanged({ start, end, screenDelta, bufferDelta })
lineForRow: (row) ->
@lineMap.lineForScreenRow(row)
@@ -95,7 +102,7 @@ class DisplayBuffer
end = oldScreenRange.end.row
screenDelta = newScreenRange.end.row - oldScreenRange.end.row
bufferDelta = 0
@trigger 'changed', { start, end, screenDelta, bufferDelta }
@triggerChanged({ start, end, screenDelta, bufferDelta })
fold
@@ -124,7 +131,8 @@ class DisplayBuffer
screenDelta = newScreenRange.end.row - oldScreenRange.end.row
bufferDelta = 0
@trigger 'changed', { start, end, screenDelta, bufferDelta }
@refreshMarkerScreenPositions()
@triggerChanged({ start, end, screenDelta, bufferDelta })
destroyFoldsContainingBufferRow: (bufferRow) ->
for row, folds of @activeFolds
@@ -213,19 +221,17 @@ class DisplayBuffer
@handleBufferChange(bufferChange)
bufferDelta = bufferChange.newRange.end.row - bufferChange.oldRange.end.row
tokenizedBufferStart = @bufferRowForScreenRow(@screenRowForBufferRow(tokenizedBufferChange.start))
tokenizedBufferEnd = tokenizedBufferChange.end
tokenizedBufferDelta = tokenizedBufferChange.delta
start = @screenRowForBufferRow(tokenizedBufferStart)
end = @lastScreenRowForBufferRow(tokenizedBufferEnd)
newScreenLines = @buildLinesForBufferRows(tokenizedBufferStart, tokenizedBufferEnd + tokenizedBufferDelta)
@lineMap.replaceScreenRows(start, end, newScreenLines)
screenDelta = @lastScreenRowForBufferRow(tokenizedBufferEnd + tokenizedBufferDelta) - end
@trigger 'changed', { start, end, screenDelta, bufferDelta }
@triggerChanged({ start, end, screenDelta, bufferDelta }, false)
buildLineForBufferRow: (bufferRow) ->
@buildLinesForBufferRows(bufferRow, bufferRow)
@@ -290,6 +296,86 @@ class DisplayBuffer
rangeForAllLines: ->
new Range([0, 0], @clipScreenPosition([Infinity, Infinity]))
getMarker: (id) ->
@markers[id] ? new DisplayBufferMarker({id, displayBuffer: this})
getMarkers: ->
_.values(@markers)
markScreenRange: (screenRange) ->
@markBufferRange(@bufferRangeForScreenRange(screenRange))
markBufferRange: (args...) ->
@buffer.markRange(args...)
markScreenPosition: (screenPosition, options) ->
@markBufferPosition(@bufferPositionForScreenPosition(screenPosition), options)
markBufferPosition: (bufferPosition, options) ->
@buffer.markPosition(bufferPosition, options)
destroyMarker: (id) ->
@buffer.destroyMarker(id)
delete @markers[id]
getMarkerScreenRange: (id) ->
@getMarker(id).getScreenRange()
setMarkerScreenRange: (id, screenRange, options) ->
@getMarker(id).setScreenRange(screenRange, options)
getMarkerBufferRange: (id) ->
@getMarker(id).getBufferRange()
setMarkerBufferRange: (id, bufferRange, options) ->
@getMarker(id).setBufferRange(bufferRange, options)
getMarkerScreenPosition: (id) ->
@getMarkerHeadScreenPosition(id)
getMarkerBufferPosition: (id) ->
@getMarkerHeadBufferPosition(id)
getMarkerHeadScreenPosition: (id) ->
@getMarker(id).getHeadScreenPosition()
setMarkerHeadScreenPosition: (id, screenPosition, options) ->
@getMarker(id).setHeadScreenPosition(screenPosition, options)
getMarkerHeadBufferPosition: (id) ->
@getMarker(id).getHeadBufferPosition()
setMarkerHeadBufferPosition: (id, bufferPosition) ->
@getMarker(id).setHeadBufferPosition(bufferPosition)
getMarkerTailScreenPosition: (id) ->
@getMarker(id).getTailScreenPosition()
setMarkerTailScreenPosition: (id, screenPosition, options) ->
@getMarker(id).setTailScreenPosition(screenPosition, options)
getMarkerTailBufferPosition: (id) ->
@getMarker(id).getTailBufferPosition()
setMarkerTailBufferPosition: (id, bufferPosition) ->
@getMarker(id).setTailBufferPosition(bufferPosition)
placeMarkerTail: (id) ->
@getMarker(id).placeTail()
clearMarkerTail: (id) ->
@getMarker(id).clearTail()
isMarkerReversed: (id) ->
@buffer.isMarkerReversed(id)
observeMarker: (id, callback) ->
@getMarker(id).observe(callback)
refreshMarkerScreenPositions: ->
for marker in @getMarkers()
marker.notifyObservers(bufferChanged: false)
destroy: ->
@tokenizedBuffer.destroy()

View File

@@ -1,6 +1,5 @@
Point = require 'point'
Buffer = require 'buffer'
Anchor = require 'anchor'
LanguageMode = require 'language-mode'
DisplayBuffer = require 'display-buffer'
Cursor = require 'cursor'
@@ -8,7 +7,6 @@ Selection = require 'selection'
EventEmitter = require 'event-emitter'
Subscriber = require 'subscriber'
Range = require 'range'
AnchorRange = require 'anchor-range'
_ = require 'underscore'
fs = require 'fs'
@@ -29,8 +27,6 @@ class EditSession
scrollLeft: 0
languageMode: null
displayBuffer: null
anchors: null
anchorRanges: null
cursors: null
selections: null
softTabs: true
@@ -40,8 +36,6 @@ class EditSession
@softTabs = @buffer.usesSoftTabs() ? softTabs ? true
@languageMode = new LanguageMode(this, @buffer.getExtension())
@displayBuffer = new DisplayBuffer(@buffer, { @languageMode, tabLength })
@anchors = []
@anchorRanges = []
@cursors = []
@selections = []
@addCursorAtScreenPosition([0, 0])
@@ -49,12 +43,11 @@ class EditSession
@buffer.retain()
@subscribe @buffer, "path-changed", => @trigger "path-changed"
@subscribe @buffer, "contents-conflicted", => @trigger "contents-conflicted"
@subscribe @buffer, "anchors-updated", => @mergeCursors()
@subscribe @buffer, "markers-updated", => @mergeCursors()
@preserveCursorPositionOnBufferReload()
@subscribe @displayBuffer, "changed", (e) =>
@refreshAnchorScreenPositions() unless e.bufferDelta
@trigger 'screen-lines-changed', e
destroy: ->
@@ -62,10 +55,11 @@ class EditSession
@destroyed = true
@unsubscribe()
@buffer.release()
selection.destroy() for selection in @getSelections()
@displayBuffer.destroy()
@project.removeEditSession(this)
anchor.destroy() for anchor in @getAnchors()
anchorRange.destroy() for anchorRange in @getAnchorRanges()
@project?.removeEditSession(this)
@trigger 'destroyed'
@off()
serialize: ->
buffer: @buffer.getPath()
@@ -451,38 +445,77 @@ class EditSession
pushOperation: (operation) ->
@buffer.pushOperation(operation, this)
getAnchors: ->
new Array(@anchors...)
markScreenRange: (args...) ->
@displayBuffer.markScreenRange(args...)
getAnchorRanges: ->
new Array(@anchorRanges...)
markBufferRange: (args...) ->
@displayBuffer.markBufferRange(args...)
addAnchor: (options={}) ->
anchor = @buffer.addAnchor(_.extend({editSession: this}, options))
@anchors.push(anchor)
anchor
markScreenPosition: (args...) ->
@displayBuffer.markScreenPosition(args...)
addAnchorAtBufferPosition: (bufferPosition, options) ->
anchor = @addAnchor(options)
anchor.setBufferPosition(bufferPosition)
anchor
markBufferPosition: (args...) ->
@displayBuffer.markBufferPosition(args...)
addAnchorRange: (range) ->
anchorRange = @buffer.addAnchorRange(range, this)
@anchorRanges.push(anchorRange)
anchorRange
destroyMarker: (args...) ->
@displayBuffer.destroyMarker(args...)
removeAnchor: (anchor) ->
_.remove(@anchors, anchor)
getMarkerCount: ->
@buffer.getMarkerCount()
refreshAnchorScreenPositions: ->
anchor.refreshScreenPosition() for anchor in @getAnchors()
getMarkerScreenRange: (args...) ->
@displayBuffer.getMarkerScreenRange(args...)
removeAnchorRange: (anchorRange) ->
_.remove(@anchorRanges, anchorRange)
setMarkerScreenRange: (args...) ->
@displayBuffer.setMarkerScreenRange(args...)
anchorRangesForBufferPosition: (bufferPosition) ->
_.intersect(@anchorRanges, @buffer.anchorRangesForPosition(bufferPosition))
getMarkerBufferRange: (args...) ->
@displayBuffer.getMarkerBufferRange(args...)
setMarkerBufferRange: (args...) ->
@displayBuffer.setMarkerBufferRange(args...)
getMarkerScreenPosition: (args...) ->
@displayBuffer.getMarkerScreenPosition(args...)
getMarkerBufferPosition: (args...) ->
@displayBuffer.getMarkerBufferPosition(args...)
getMarkerHeadScreenPosition: (args...) ->
@displayBuffer.getMarkerHeadScreenPosition(args...)
setMarkerHeadScreenPosition: (args...) ->
@displayBuffer.setMarkerHeadScreenPosition(args...)
getMarkerHeadBufferPosition: (args...) ->
@displayBuffer.getMarkerHeadBufferPosition(args...)
setMarkerHeadBufferPosition: (args...) ->
@displayBuffer.setMarkerHeadBufferPosition(args...)
getMarkerTailScreenPosition: (args...) ->
@displayBuffer.getMarkerTailScreenPosition(args...)
setMarkerTailScreenPosition: (args...) ->
@displayBuffer.setMarkerTailScreenPosition(args...)
getMarkerTailBufferPosition: (args...) ->
@displayBuffer.getMarkerTailBufferPosition(args...)
setMarkerTailBufferPosition: (args...) ->
@displayBuffer.setMarkerTailBufferPosition(args...)
observeMarker: (args...) ->
@displayBuffer.observeMarker(args...)
placeMarkerTail: (args...) ->
@displayBuffer.placeMarkerTail(args...)
clearMarkerTail: (args...) ->
@displayBuffer.clearMarkerTail(args...)
isMarkerReversed: (args...) ->
@displayBuffer.isMarkerReversed(args...)
hasMultipleCursors: ->
@getCursors().length > 1
@@ -493,31 +526,36 @@ class EditSession
_.last(@cursors)
addCursorAtScreenPosition: (screenPosition) ->
@addCursor(new Cursor(editSession: this, screenPosition: screenPosition))
marker = @markScreenPosition(screenPosition, stayValid: true)
@addSelection(marker).cursor
addCursorAtBufferPosition: (bufferPosition) ->
@addCursor(new Cursor(editSession: this, bufferPosition: bufferPosition))
marker = @markBufferPosition(bufferPosition, stayValid: true)
@addSelection(marker).cursor
addCursor: (cursor=new Cursor(editSession: this, screenPosition: [0,0])) ->
addCursor: (marker) ->
cursor = new Cursor(editSession: this, marker: marker)
@cursors.push(cursor)
@trigger 'cursor-added', cursor
@addSelectionForCursor(cursor)
cursor
removeCursor: (cursor) ->
_.remove(@cursors, cursor)
addSelectionForCursor: (cursor) ->
selection = new Selection(editSession: this, cursor: cursor)
addSelection: (marker, options={}) ->
unless options.preserveFolds
@destroyFoldsIntersectingBufferRange(@getMarkerBufferRange(marker))
cursor = @addCursor(marker)
selection = new Selection({editSession: this, marker, cursor})
@selections.push(selection)
@mergeIntersectingSelections()
@trigger 'selection-added', selection
selection
addSelectionForBufferRange: (bufferRange, options={}) ->
bufferRange = Range.fromObject(bufferRange)
@destroyFoldsIntersectingBufferRange(bufferRange) unless options.preserveFolds
@addCursor().selection.setBufferRange(bufferRange, options)
@mergeIntersectingSelections()
options = _.defaults({stayValid: true}, options)
marker = @markBufferRange(bufferRange, options)
@addSelection(marker)
setSelectedBufferRange: (bufferRange, options) ->
@setSelectedBufferRanges([bufferRange], options)
@@ -570,8 +608,8 @@ class EditSession
_.any @getSelections(), (selection) ->
selection.intersectsBufferRange(bufferRange)
setCursorScreenPosition: (position) ->
@moveCursors (cursor) -> cursor.setScreenPosition(position)
setCursorScreenPosition: (position, options) ->
@moveCursors (cursor) -> cursor.setScreenPosition(position, options)
getCursorScreenPosition: ->
@getCursor().getScreenPosition()
@@ -710,9 +748,19 @@ class EditSession
expandLastSelectionOverWord: ->
@getLastSelection().expandOverWord()
selectMarker: (id) ->
if bufferRange = @getMarkerBufferRange(id)
@setSelectedBufferRange(bufferRange)
true
else
false
markersForBufferPosition: (bufferPosition) ->
@buffer.markersForPosition(bufferPosition)
mergeCursors: ->
positions = []
for cursor in new Array(@getCursors()...)
for cursor in @getCursors()
position = cursor.getBufferPosition().toString()
if position in positions
cursor.destroy()

View File

@@ -6,7 +6,6 @@ Range = require 'range'
EditSession = require 'edit-session'
CursorView = require 'cursor-view'
SelectionView = require 'selection-view'
Native = require 'native'
fs = require 'fs'
$ = require 'jquery'
_ = require 'underscore'
@@ -21,6 +20,8 @@ class Editor extends View
autoIndentOnPaste: false
nonWordCharacters: "./\\()\"'-_:,.;<>~!@#$%^&*|+=[]{}`~?"
@nextEditorId: 1
@content: (params) ->
@div class: @classes(params), tabindex: -1, =>
@subview 'gutter', new Gutter
@@ -55,15 +56,17 @@ class Editor extends View
newSelections: null
@deserialize: (state, rootView) ->
editor = new Editor(mini: state.mini, deserializing: true)
editSessions = state.editSessions.map (state) -> EditSession.deserialize(state, rootView.project)
editor = new Editor(editSession: editSessions[state.activeEditSessionIndex], mini: state.mini)
editor.editSessions = editSessions
editor.pushEditSession(editSession) for editSession in editSessions
editor.setActiveEditSessionIndex(state.activeEditSessionIndex)
editor.isFocused = state.isFocused
editor
initialize: ({editSession, @mini} = {}) ->
initialize: ({editSession, @mini, deserializing} = {}) ->
requireStylesheet 'editor.css'
@id = Editor.nextEditorId++
@lineCache = []
@configure()
@bindKeys()
@@ -76,19 +79,16 @@ class Editor extends View
@newSelections = []
if editSession?
@editSessions.push editSession
@setActiveEditSessionIndex(0)
@edit(editSession)
else if @mini
editSession = new EditSession
@edit(new EditSession
buffer: new Buffer()
softWrap: false
tabLength: 2
softTabs: true
@editSessions.push editSession
@setActiveEditSessionIndex(0)
else
throw new Error("Editor initialization requires an editSession")
)
else if not deserializing
throw new Error("Editor must be constructed with an 'editSession' or 'mini: true' param")
serialize: ->
@saveScrollPositionForActiveEditSession()
@@ -210,8 +210,8 @@ class Editor extends View
moveCursorToEndOfLine: -> @activeEditSession.moveCursorToEndOfLine()
moveLineUp: -> @activeEditSession.moveLineUp()
moveLineDown: -> @activeEditSession.moveLineDown()
setCursorScreenPosition: (position, options) -> @activeEditSession.setCursorScreenPosition(position, options)
duplicateLine: -> @activeEditSession.duplicateLine()
setCursorScreenPosition: (position) -> @activeEditSession.setCursorScreenPosition(position)
getCursorScreenPosition: -> @activeEditSession.getCursorScreenPosition()
getCursorScreenRow: -> @activeEditSession.getCursorScreenRow()
setCursorBufferPosition: (position, options) -> @activeEditSession.setCursorBufferPosition(position, options)
@@ -432,10 +432,10 @@ class Editor extends View
@selectToScreenPosition(@screenPositionFromMouseEvent(event))
lastMoveEvent = event
$(document).on 'mousemove', moveHandler
$(document).on "mousemove.editor-#{@id}", moveHandler
interval = setInterval(moveHandler, 20)
$(document).one 'mouseup', =>
$(document).one "mouseup.editor-#{@id}", =>
clearInterval(interval)
$(document).off 'mousemove', moveHandler
reverse = @activeEditSession.getLastSelection().isReversed()
@@ -449,7 +449,7 @@ class Editor extends View
@calculateDimensions()
@hiddenInput.width(@charWidth)
@setSoftWrapColumn() if @activeEditSession.getSoftWrap()
@subscribe $(window), "resize", => @requestDisplayUpdate()
@subscribe $(window), "resize.editor-#{@id}", => @requestDisplayUpdate()
@focus() if @isFocused
@resetDisplay()
@@ -458,14 +458,16 @@ class Editor extends View
edit: (editSession) ->
index = @editSessions.indexOf(editSession)
if index == -1
index = @editSessions.length
@editSessions.push(editSession)
@trigger 'editor:edit-session-added', [editSession, index]
index = @pushEditSession(editSession) if index == -1
@setActiveEditSessionIndex(index)
pushEditSession: (editSession) ->
index = @editSessions.length
@editSessions.push(editSession)
editSession.on 'destroyed', => @editSessionDestroyed(editSession)
@trigger 'editor:edit-session-added', [editSession, index]
index
getBuffer: -> @activeEditSession.buffer
destroyActiveEditSession: ->
@@ -475,23 +477,18 @@ class Editor extends View
return if @mini
editSession = @editSessions[index]
destroySession = =>
if index is @getActiveEditSessionIndex() and @editSessions.length > 1
@loadPreviousEditSession()
_.remove(@editSessions, editSession)
destroySession = ->
editSession.destroy()
@trigger 'editor:edit-session-removed', [editSession, index]
@remove() if @editSessions.length is 0
callback(index) if callback
callback?(index)
if editSession.isModified() and not editSession.hasEditors()
@promptToSaveDirtySession(editSession, destroySession)
else
destroySession(editSession)
destroySession()
destroyInactiveEditSessions: ->
destroyIndex = (index) =>
index++ if @activeEditSession is @editSessions[index]
index++ if index is @getActiveEditSessionIndex()
@destroyEditSessionIndex(index, destroyIndex) if @editSessions[index]
destroyIndex(0)
@@ -500,6 +497,13 @@ class Editor extends View
@destroyEditSessionIndex(index, destroyIndex) if @editSessions[index]
destroyIndex(0)
editSessionDestroyed: (editSession) ->
index = @editSessions.indexOf(editSession)
@loadPreviousEditSession() if index is @getActiveEditSessionIndex() and @editSessions.length > 1
_.remove(@editSessions, editSession)
@trigger 'editor:edit-session-removed', [editSession, index]
@remove() if @editSessions.length is 0
loadNextEditSession: ->
nextIndex = (@getActiveEditSessionIndex() + 1) % @editSessions.length
@setActiveEditSessionIndex(nextIndex)
@@ -664,7 +668,7 @@ class Editor extends View
if @activeEditSession.getSoftWrap()
@addClass 'soft-wrap'
@_setSoftWrapColumn = => @setSoftWrapColumn()
$(window).on "resize", @_setSoftWrapColumn
$(window).on "resize.editor-#{@id}", @_setSoftWrapColumn
else
@removeClass 'soft-wrap'
$(window).off 'resize', @_setSoftWrapColumn
@@ -751,15 +755,17 @@ class Editor extends View
)
remove: (selector, keepData) ->
return super if keepData
return super if keepData or @removed
@trigger 'editor:will-be-removed'
@destroyEditSessions()
if @pane() then @pane().remove() else super
rootView?.focus()
afterRemove: ->
@removed = true
@destroyEditSessions()
$(window).off(".editor-#{@id}")
$(document).off(".editor-#{@id}")
getEditSessions: ->
new Array(@editSessions...)
@@ -883,6 +889,7 @@ class Editor extends View
@updateCursorViews()
@updateSelectionViews()
@autoscroll(options)
@trigger 'editor:display-updated'
updateCursorViews: ->
if @newCursors.length > 0
@@ -912,13 +919,16 @@ class Editor extends View
do (cursorView) -> cursorView.resetBlinking()
autoscroll: (options={}) ->
for cursorView in @getCursorViews() when cursorView.needsAutoscroll()
@scrollToPixelPosition(cursorView.getPixelPosition()) unless options.suppressAutoScroll
cursorView.autoscrolled()
for cursorView in @getCursorViews()
if !options.suppressAutoScroll and cursorView.needsAutoscroll()
@scrollToPixelPosition(cursorView.getPixelPosition())
cursorView.clearAutoscroll()
for selectionView in @getSelectionViews() when selectionView.needsAutoscroll()
@scrollToPixelPosition(selectionView.getCenterPixelPosition(), center: true)
selectionView.autoscrolled()
for selectionView in @getSelectionViews()
if !options.suppressAutoScroll and selectionView.needsAutoscroll()
@scrollToPixelPosition(selectionView.getCenterPixelPosition(), center: true)
selectionView.highlight()
selectionView.clearAutoscroll()
updateRenderedLines: ->
firstVisibleScreenRow = @getFirstVisibleScreenRow()

View File

@@ -4,7 +4,7 @@ SelectList = require 'select-list'
module.exports =
class GrammarView extends SelectList
@viewClass: -> "#{super} grammar-view from-top overlay"
@viewClass: -> "#{super} grammar-view from-top overlay mini"
filterKey: 'name'

View File

@@ -8,78 +8,10 @@ class LanguageMode
buffer = null
grammar = null
editSession = null
pairedCharacters:
'(': ')'
'[': ']'
'{': '}'
'"': '"'
"'": "'"
constructor: (@editSession) ->
@buffer = @editSession.buffer
@reloadGrammar()
@bracketAnchorRanges = []
_.adviseBefore @editSession, 'insertText', (text) =>
return true if @editSession.hasMultipleCursors()
cursorBufferPosition = @editSession.getCursorBufferPosition()
previousCharacter = @editSession.getTextInBufferRange([cursorBufferPosition.add([0, -1]), cursorBufferPosition])
nextCharacter = @editSession.getTextInBufferRange([cursorBufferPosition, cursorBufferPosition.add([0,1])])
if @isOpeningBracket(text) and not @editSession.getSelection().isEmpty()
@wrapSelectionInBrackets(text)
return false
hasWordAfterCursor = /\w/.test(nextCharacter)
hasWordBeforeCursor = /\w/.test(previousCharacter)
autoCompleteOpeningBracket = @isOpeningBracket(text) and not hasWordAfterCursor and not (@isQuote(text) and hasWordBeforeCursor)
skipOverExistingClosingBracket = false
if @isClosingBracket(text) and nextCharacter == text
if bracketAnchorRange = @bracketAnchorRanges.filter((anchorRange) -> anchorRange.getBufferRange().end.isEqual(cursorBufferPosition))[0]
skipOverExistingClosingBracket = true
if skipOverExistingClosingBracket
bracketAnchorRange.destroy()
_.remove(@bracketAnchorRanges, bracketAnchorRange)
@editSession.moveCursorRight()
false
else if autoCompleteOpeningBracket
@editSession.insertText(text + @pairedCharacters[text])
@editSession.moveCursorLeft()
range = [cursorBufferPosition, cursorBufferPosition.add([0, text.length])]
@bracketAnchorRanges.push @editSession.addAnchorRange(range)
false
_.adviseBefore @editSession, 'backspace', =>
return if @editSession.hasMultipleCursors()
return unless @editSession.getSelection().isEmpty()
cursorBufferPosition = @editSession.getCursorBufferPosition()
previousCharacter = @editSession.getTextInBufferRange([cursorBufferPosition.add([0, -1]), cursorBufferPosition])
nextCharacter = @editSession.getTextInBufferRange([cursorBufferPosition, cursorBufferPosition.add([0,1])])
if @pairedCharacters[previousCharacter] is nextCharacter
@editSession.transact =>
@editSession.moveCursorLeft()
@editSession.delete()
@editSession.delete()
false
wrapSelectionInBrackets: (bracket) ->
pair = @pairedCharacters[bracket]
@editSession.mutateSelectedText (selection) =>
return if selection.isEmpty()
range = selection.getBufferRange()
options = reverse: selection.isReversed()
selection.insertText("#{bracket}#{selection.getText()}#{pair}")
selectionStart = range.start.add([0, 1])
if range.start.row is range.end.row
selectionEnd = range.end.add([0, 1])
else
selectionEnd = range.end
selection.setBufferRange([selectionStart, selectionEnd], options)
reloadGrammar: ->
path = @buffer.getPath()
@@ -92,23 +24,6 @@ class LanguageMode
throw new Error("No grammar found for path: #{path}") unless @grammar
previousGrammar isnt @grammar
isQuote: (string) ->
/'|"/.test(string)
isOpeningBracket: (string) ->
@pairedCharacters[string]?
isClosingBracket: (string) ->
@getInvertedPairedCharacters()[string]?
getInvertedPairedCharacters: ->
return @invertedPairedCharacters if @invertedPairedCharacters
@invertedPairedCharacters = {}
for open, close of @pairedCharacters
@invertedPairedCharacters[close] = open
@invertedPairedCharacters
toggleLineCommentsForBufferRows: (start, end) ->
scopes = @editSession.scopesForBufferPosition([start, 0])
return unless commentStartString = syntax.getProperty(scopes, "editor.commentStart")

View File

@@ -111,7 +111,7 @@ class LineMap
[screenRow, screenLines]
bufferPositionForScreenPosition: (screenPosition, options) ->
{ row, column } = Point.fromObject(screenPosition)
{ row, column } = @clipScreenPosition(Point.fromObject(screenPosition))
[bufferRow, screenLine] = @bufferRowAndScreenLineForScreenRow(row)
bufferColumn = screenLine.bufferColumnForScreenColumn(column)
new Point(bufferRow, bufferColumn)

View File

@@ -1,5 +1,5 @@
TextMatePackage = require 'text-mate-package'
module.exports =
loadPackage: (name) ->
callTaskMethod('packageLoaded', new TextMatePackage(name).readGrammars())
loadPackage: (path) ->
callTaskMethod('packageLoaded', new TextMatePackage(path).readGrammars())

View File

@@ -16,10 +16,10 @@ class LoadTextMatePackagesTask extends Task
return
@package = @packages.shift()
@loadPackage(@package.name)
@loadPackage(@package.path)
loadPackage: (name) ->
@callWorkerMethod('loadPackage', name)
loadPackage: (path) ->
@callWorkerMethod('loadPackage', path)
packageLoaded: (grammars) ->
@package.loadGrammars(grammars)

View File

@@ -2,34 +2,17 @@ fs = require 'fs'
module.exports =
class Package
@resolve: (name) ->
path = require.resolve(name, verifyExistence: false)
return path if path
throw new Error("No package found named '#{name}'")
@build: (name) ->
@build: (path) ->
TextMatePackage = require 'text-mate-package'
AtomPackage = require 'atom-package'
if TextMatePackage.testName(name)
new TextMatePackage(name)
if TextMatePackage.testName(path)
new TextMatePackage(path)
else
if fs.isDirectory(@resolve(name))
new AtomPackage(name)
else
try
PackageClass = require name
new PackageClass(name) if typeof PackageClass is 'function'
catch e
console.warn "Failed to load package named '#{name}'", e.stack
new AtomPackage(path)
name: null
path: null
isDirectory: false
module: null
constructor: (@name) ->
@path = Package.resolve(@name)
@isDirectory = fs.isDirectory(@path)
@path = fs.directory(@path) unless @isDirectory
activate: (rootView) ->
constructor: (@path) ->
@name = fs.base(@path)

View File

@@ -118,6 +118,10 @@ class Project
getEditSessions: ->
new Array(@editSessions...)
eachEditSession: (callback) ->
callback(editSession) for editSession in @getEditSessions()
@on 'edit-session-created', (editSession) -> callback(editSession)
removeEditSession: (editSession) ->
_.remove(@editSessions, editSession)
@@ -125,9 +129,12 @@ class Project
buffers = []
for editSession in @editSessions when not _.include(buffers, editSession.buffer)
buffers.push editSession.buffer
buffers
eachBuffer: (callback) ->
callback(buffer) for buffer in @getBuffers()
@on 'buffer-created', (buffer) -> callback(buffer)
bufferForPath: (filePath) ->
if filePath?
filePath = @resolve(filePath)

View File

@@ -29,19 +29,19 @@ class RootView extends View
else
projectOrPathToOpen = projectPath # This will migrate people over to the new project serialization scheme. It should be removed eventually.
rootView = new RootView(projectOrPathToOpen , packageStates: packageStates, suppressOpen: true)
atom.atomPackageStates = packageStates ? {}
rootView = new RootView(projectOrPathToOpen , suppressOpen: true)
rootView.setRootPane(rootView.deserializeView(panesViewState)) if panesViewState
rootView
packageModules: null
packageStates: null
packages: null
title: null
pathToOpenIsFile: false
initialize: (projectOrPathToOpen, { @packageStates, suppressOpen } = {}) ->
initialize: (projectOrPathToOpen, { suppressOpen } = {}) ->
window.rootView = this
@packageStates ?= {}
@packageModules = {}
@packages = []
@viewClasses = {
"Pane": Pane,
"PaneRow": PaneRow,
@@ -68,7 +68,7 @@ class RootView extends View
serialize: ->
projectState: @project?.serialize()
panesViewState: @panes.children().view()?.serialize()
packageStates: @serializePackages()
packageStates: atom.serializeAtomPackages()
handleFocus: (e) ->
if @getActiveEditor()
@@ -118,33 +118,15 @@ class RootView extends View
afterAttach: (onDom) ->
@focus() if onDom
serializePackages: ->
packageStates = {}
for name, packageModule of @packageModules
try
packageStates[name] = packageModule.serialize?()
catch e
console?.error("Exception serializing '#{name}' package's module\n", e.stack)
packageStates
registerViewClass: (viewClass) ->
@viewClasses[viewClass.name] = viewClass
deserializeView: (viewState) ->
@viewClasses[viewState.viewClass]?.deserialize(viewState, this)
activatePackage: (name, packageModule) ->
config.setDefaults(name, packageModule.configDefaults) if packageModule.configDefaults?
@packageModules[name] = packageModule
packageModule.activate(this, @packageStates[name])
deactivatePackage: (name) ->
@packageModules[name].deactivate?()
delete @packageModules[name]
deactivate: ->
atom.setRootViewStateForPath(@project.getPath(), @serialize())
@deactivatePackage(name) for name of @packageModules
atom.deactivateAtomPackages()
@remove()
open: (path, options = {}) ->
@@ -274,6 +256,8 @@ class RootView extends View
callback(editor) for editor in @getEditors()
@on 'editor:attached', (e, editor) -> callback(editor)
eachEditSession: (callback) ->
@project.eachEditSession(callback)
eachBuffer: (callback) ->
callback(buffer) for buffer in @project.getBuffers()
@project.on 'buffer-created', (buffer) -> callback(buffer)
@project.eachBuffer(callback)

View File

@@ -1,4 +1,3 @@
Anchor = require 'anchor'
Point = require 'point'
Range = require 'range'
{View, $$} = require 'space-pen'
@@ -69,9 +68,8 @@ class SelectionView extends View
needsAutoscroll: ->
@selection.needsAutoscroll
autoscrolled: ->
@selection.autoscrolled()
@highlight()
clearAutoscroll: ->
@selection.clearAutoscroll()
highlight: ->
@unhighlight()

View File

@@ -1,31 +1,26 @@
Range = require 'range'
Anchor = require 'anchor'
EventEmitter = require 'event-emitter'
_ = require 'underscore'
module.exports =
class Selection
anchor: null
wordwise: false
initialScreenRange: null
needsAutoscroll: null
constructor: ({@cursor, @editSession}) ->
constructor: ({@cursor, @marker, @editSession}) ->
@cursor.selection = this
@cursor.on 'moved.selection', ({bufferChange}) =>
@screenRangeChanged() unless bufferChange
@editSession.observeMarker @marker, => @screenRangeChanged()
@cursor.on 'destroyed.selection', =>
@cursor = null
@destroy()
destroy: ->
if @cursor
@cursor.off('.selection')
@cursor.destroy()
@anchor?.destroy()
return if @destroyed
@destroyed = true
@editSession.removeSelection(this)
@trigger 'destroyed'
@trigger 'destroyed' unless @editSession.destroyed
@cursor?.destroy()
finalize: ->
@initialScreenRange = null unless @initialScreenRange?.isEqual(@getScreenRange())
@@ -37,42 +32,31 @@ class Selection
@getBufferRange().isEmpty()
isReversed: ->
not @isEmpty() and @cursor.getBufferPosition().isLessThan(@anchor.getBufferPosition())
@editSession.isMarkerReversed(@marker)
isSingleScreenLine: ->
@getScreenRange().isSingleLine()
autoscrolled: ->
@needsAutoscroll = false
clearAutoscroll: ->
@needsAutoscroll = null
getScreenRange: ->
if @anchor
new Range(@anchor.getScreenPosition(), @cursor.getScreenPosition())
else
new Range(@cursor.getScreenPosition(), @cursor.getScreenPosition())
@editSession.getMarkerScreenRange(@marker)
setScreenRange: (screenRange, options) ->
bufferRange = editSession.bufferRangeForScreenRange(screenRange)
@setBufferRange(bufferRange, options)
@setBufferRange(@editSession.bufferRangeForScreenRange(screenRange), options)
getBufferRange: ->
if @anchor
new Range(@anchor.getBufferPosition(), @cursor.getBufferPosition())
else
new Range(@cursor.getBufferPosition(), @cursor.getBufferPosition())
@editSession.getMarkerBufferRange(@marker)
setBufferRange: (bufferRange, options={}) ->
bufferRange = Range.fromObject(bufferRange)
{ start, end } = bufferRange
[start, end] = [end, start] if options.reverse ? @isReversed()
@needsAutoscroll = options.autoscroll
options.reverse ?= @isReversed()
@editSession.destroyFoldsIntersectingBufferRange(bufferRange) unless options.preserveFolds
@placeAnchor() unless @anchor
@modifySelection =>
@anchor.setBufferPosition(start, autoscroll: false)
@cursor.setBufferPosition(end, autoscroll: false)
@cursor.needsAutoscroll = false if options.autoscroll?
@editSession.setMarkerBufferRange(@marker, bufferRange, options)
getBufferRowRange: ->
range = @getBufferRange()
@@ -84,16 +68,12 @@ class Selection
screenRangeChanged: ->
screenRange = @getScreenRange()
@trigger 'screen-range-changed', screenRange
@cursor?.setVisible(screenRange.isEmpty())
getText: ->
@editSession.buffer.getTextInRange(@getBufferRange())
clear: ->
return unless @anchor
@anchor.destroy()
@anchor = null
@screenRangeChanged()
@editSession.clearMarkerTail(@marker)
selectWord: ->
options = {}
@@ -121,11 +101,9 @@ class Selection
@modifySelection =>
if @initialScreenRange
if position.isLessThan(@initialScreenRange.start)
@anchor.setScreenPosition(@initialScreenRange.end)
@cursor.setScreenPosition(position)
@editSession.setMarkerScreenRange(@marker, [position, @initialScreenRange.end], reverse: true)
else
@anchor.setScreenPosition(@initialScreenRange.start)
@cursor.setScreenPosition(position)
@editSession.setMarkerScreenRange(@marker, [@initialScreenRange.start, position])
else
@cursor.setScreenPosition(position)
@@ -317,8 +295,7 @@ class Selection
@editSession.autoIndentBufferRows(start, end)
toggleLineComments: ->
@modifySelection =>
@editSession.toggleLineCommentsForBufferRows(@getBufferRowRange()...)
@editSession.toggleLineCommentsForBufferRows(@getBufferRowRange()...)
cutToEndOfLine: (maintainPasteboard) ->
@selectToEndOfLine() if @isEmpty()
@@ -352,18 +329,12 @@ class Selection
modifySelection: (fn) ->
@retainSelection = true
@placeAnchor() unless @anchor
@anchor.pauseEvents()
@cursor.pauseEvents()
@placeTail()
fn()
@anchor.resumeEvents()
@cursor.resumeEvents()
@retainSelection = false
placeAnchor: ->
@anchor = @editSession.addAnchor(strong: true)
@anchor.setScreenPosition(@cursor.getScreenPosition())
@anchor.on 'moved.selection', => @screenRangeChanged()
placeTail: ->
@editSession.placeMarkerTail(@marker)
intersectsBufferRange: (bufferRange) ->
@getBufferRange().intersectsWith(bufferRange)

View File

@@ -9,9 +9,12 @@ module.exports =
class TextMateGrammar
@readFromPath: (path) ->
grammarContent = null
plist.parseString fs.read(path), (e, data) ->
throw new Error(e) if e
grammarContent = data[0]
if fs.extension(path) is '.cson'
grammarContent = fs.readObject(path)
else
plist.parseString fs.read(path), (e, data) ->
throw new Error(e) if e
grammarContent = data[0]
throw new Error("Failed to load grammar at path `#{path}`") unless grammarContent
grammarContent

View File

@@ -28,7 +28,7 @@ class TextMatePackage extends Package
try
@loadGrammars()
catch e
console.warn "Failed to load package named '#{@name}'", e.stack
console.warn "Failed to load package at '#{@path}'", e.stack
this
getGrammars: -> @grammars

View File

@@ -1,7 +1,6 @@
# This a weirdo file. We don't create a Window class, we just add stuff to
# the DOM window.
Native = require 'native'
fs = require 'fs'
$ = require 'jquery'
Config = require 'config'
@@ -38,8 +37,8 @@ windowAdditions =
# Note: RootView assigns itself on window on initialization so that
# window.rootView is available when loading user configuration
attachRootView: (pathToOpen) ->
if rootViewState = atom.getRootViewStateForPath(pathToOpen)
RootView.deserialize(rootViewState)
if pathState = atom.getRootViewStateForPath(pathToOpen)
RootView.deserialize(pathState)
else
new RootView(pathToOpen)
@@ -96,11 +95,11 @@ windowAdditions =
atom.confirm(
"There are unsaved buffers, reload anyway?",
"You will lose all unsaved changes if you reload",
"Reload", (-> Native.reload()),
"Reload", (-> $native.reload()),
"Cancel"
)
else
Native.reload()
$native.reload()
onerror: ->
atom.showDevTools()

View File

@@ -1,6 +0,0 @@
AtomPackage = require 'atom-package'
AutocompleteView = require './src/autocomplete-view'
module.exports =
class Autocomplete extends AtomPackage
activate: (rootView) -> AutocompleteView.activate(rootView)

View File

@@ -4,10 +4,6 @@ SelectList = require 'select-list'
module.exports =
class AutocompleteView extends SelectList
@activate: (rootView) ->
rootView.eachEditor (editor) ->
new AutocompleteView(editor) if editor.attached and not editor.mini
@viewClass: -> "autocomplete #{super} popover-list"
editor: null
@@ -21,7 +17,6 @@ class AutocompleteView extends SelectList
initialize: (@editor) ->
super
@handleEvents()
@setCurrentBuffer(@editor.getBuffer())

View File

@@ -0,0 +1,10 @@
AutocompleteView = require './autocomplete-view'
module.exports =
autoCompleteViews: []
activate: ->
rootView.eachEditor (editor) =>
if editor.attached and not editor.mini
@autoCompleteViews.push new AutocompleteView(editor)

View File

@@ -0,0 +1,3 @@
'main': 'lib/autocomplete'
'activationEvents':
'autocomplete:attach': '.editor'

View File

@@ -1,37 +1,33 @@
$ = require 'jquery'
Autocomplete = require 'autocomplete/src/autocomplete-view'
AutocompleteView = require 'autocomplete/lib/autocomplete-view'
Autocomplete = require 'autocomplete/lib/autocomplete'
Buffer = require 'buffer'
Editor = require 'editor'
RootView = require 'root-view'
describe "Autocomplete", ->
autocomplete = null
editor = null
miniEditor = null
beforeEach ->
editor = new Editor(editSession: fixturesProject.buildEditSessionForPath('sample.js'))
atom.loadPackage('autocomplete')
autocomplete = new Autocomplete(editor)
miniEditor = autocomplete.miniEditor
rootView = new RootView(require.resolve('fixtures/sample.js'))
rootView.simulateDomAttachment()
afterEach ->
editor?.remove()
rootView.deactivate()
describe "@activate(rootView)", ->
describe "@activate()", ->
it "activates autocomplete on all existing and future editors (but not on autocomplete's own mini editor)", ->
rootView = new RootView(require.resolve('fixtures/sample.js'))
rootView.simulateDomAttachment()
Autocomplete.activate(rootView)
spyOn(AutocompleteView.prototype, 'initialize').andCallThrough()
autocompletePackage = atom.loadPackage("autocomplete")
expect(AutocompleteView.prototype.initialize).not.toHaveBeenCalled()
leftEditor = rootView.getActiveEditor()
rightEditor = rootView.getActiveEditor().splitRight()
spyOn(Autocomplete.prototype, 'initialize')
leftEditor.trigger 'autocomplete:attach'
expect(leftEditor.find('.autocomplete')).toExist()
expect(rightEditor.find('.autocomplete')).not.toExist()
expect(AutocompleteView.prototype.initialize).toHaveBeenCalled()
autoCompleteView = leftEditor.find('.autocomplete').view()
autoCompleteView.trigger 'core:cancel'
expect(leftEditor.find('.autocomplete')).not.toExist()
@@ -39,9 +35,20 @@ describe "Autocomplete", ->
rightEditor.trigger 'autocomplete:attach'
expect(rightEditor.find('.autocomplete')).toExist()
expect(Autocomplete.prototype.initialize).not.toHaveBeenCalled()
describe "AutocompleteView", ->
autocomplete = null
editor = null
miniEditor = null
rootView.deactivate()
beforeEach ->
new RootView
editor = new Editor(editSession: fixturesProject.buildEditSessionForPath('sample.js'))
atom.loadPackage('autocomplete')
autocomplete = new AutocompleteView(editor)
miniEditor = autocomplete.miniEditor
afterEach ->
editor?.remove()
describe 'autocomplete:attach event', ->
it "shows autocomplete view and focuses its mini-editor", ->

View File

@@ -1,8 +1,5 @@
AtomPackage = require 'atom-package'
module.exports =
class Autoflow extends AtomPackage
activate: (rootView) ->
activate: ->
rootView.command 'autoflow:reflow-paragraph', '.editor', (e) =>
@reflowParagraph(e.currentTargetView())

View File

@@ -0,0 +1 @@
'main': 'autoflow'

View File

@@ -1,10 +1,15 @@
AtomPackage = require 'atom-package'
_ = require 'underscore'
{$$} = require 'space-pen'
Range = require 'range'
module.exports =
class BracketMatcher extends AtomPackage
pairedCharacters:
'(': ')'
'[': ']'
'{': '}'
'"': '"'
"'": "'"
startPairMatches:
'(': ')'
'[': ']'
@@ -17,8 +22,9 @@ class BracketMatcher extends AtomPackage
pairHighlighted: false
activate: (rootView) ->
activate: ->
rootView.eachEditor (editor) => @subscribeToEditor(editor) if editor.attached
rootView.eachEditSession (editSession) => @subscribeToEditSession(editSession)
subscribeToEditor: (editor) ->
editor.on 'cursor:moved.bracket-matcher', => @updateMatch(editor)
@@ -49,7 +55,7 @@ class BracketMatcher extends AtomPackage
view = $$ -> @div class: 'bracket-matcher'
view.data('bufferPosition', bufferPosition)
view.css('top', pixelPosition.top).css('left', pixelPosition.left)
view.width(editor.charWidth).height(editor.charHeight)
view.width(editor.charWidth).height(editor.lineHeight)
findCurrentPair: (editor, buffer, matches) ->
position = editor.getCursorBufferPosition()
@@ -118,3 +124,84 @@ class BracketMatcher extends AtomPackage
underlayer.append(@createView(editor, matchPosition))
underlayer.append(@createView(editor, position))
@pairHighlighted = true
subscribeToEditSession: (editSession) ->
@bracketMarkers = []
_.adviseBefore editSession, 'insertText', (text) =>
return true if editSession.hasMultipleCursors()
cursorBufferPosition = editSession.getCursorBufferPosition()
previousCharacter = editSession.getTextInBufferRange([cursorBufferPosition.add([0, -1]), cursorBufferPosition])
nextCharacter = editSession.getTextInBufferRange([cursorBufferPosition, cursorBufferPosition.add([0,1])])
if @isOpeningBracket(text) and not editSession.getSelection().isEmpty()
@wrapSelectionInBrackets(editSession, text)
return false
hasWordAfterCursor = /\w/.test(nextCharacter)
hasWordBeforeCursor = /\w/.test(previousCharacter)
autoCompleteOpeningBracket = @isOpeningBracket(text) and not hasWordAfterCursor and not (@isQuote(text) and hasWordBeforeCursor)
skipOverExistingClosingBracket = false
if @isClosingBracket(text) and nextCharacter == text
if bracketMarker = _.find(@bracketMarkers, (marker) => editSession.getMarkerBufferRange(marker)?.end.isEqual(cursorBufferPosition))
skipOverExistingClosingBracket = true
if skipOverExistingClosingBracket
editSession.destroyMarker(bracketMarker)
_.remove(@bracketMarkers, bracketMarker)
editSession.moveCursorRight()
false
else if autoCompleteOpeningBracket
editSession.insertText(text + @pairedCharacters[text])
editSession.moveCursorLeft()
range = [cursorBufferPosition, cursorBufferPosition.add([0, text.length])]
@bracketMarkers.push editSession.markBufferRange(range)
false
_.adviseBefore editSession, 'backspace', =>
return if editSession.hasMultipleCursors()
return unless editSession.getSelection().isEmpty()
cursorBufferPosition = editSession.getCursorBufferPosition()
previousCharacter = editSession.getTextInBufferRange([cursorBufferPosition.add([0, -1]), cursorBufferPosition])
nextCharacter = editSession.getTextInBufferRange([cursorBufferPosition, cursorBufferPosition.add([0,1])])
if @pairedCharacters[previousCharacter] is nextCharacter
editSession.transact =>
editSession.moveCursorLeft()
editSession.delete()
editSession.delete()
false
wrapSelectionInBrackets: (editSession, bracket) ->
pair = @pairedCharacters[bracket]
editSession.mutateSelectedText (selection) =>
return if selection.isEmpty()
range = selection.getBufferRange()
options = reverse: selection.isReversed()
selection.insertText("#{bracket}#{selection.getText()}#{pair}")
selectionStart = range.start.add([0, 1])
if range.start.row is range.end.row
selectionEnd = range.end.add([0, 1])
else
selectionEnd = range.end
selection.setBufferRange([selectionStart, selectionEnd], options)
isQuote: (string) ->
/'|"/.test(string)
getInvertedPairedCharacters: ->
return @invertedPairedCharacters if @invertedPairedCharacters
@invertedPairedCharacters = {}
for open, close of @pairedCharacters
@invertedPairedCharacters[close] = open
@invertedPairedCharacters
isOpeningBracket: (string) ->
@pairedCharacters[string]?
isClosingBracket: (string) ->
@getInvertedPairedCharacters()[string]?

View File

@@ -0,0 +1 @@
'main': 'lib/bracket-matcher'

View File

@@ -1,63 +1,66 @@
RootView = require 'root-view'
describe "bracket matching", ->
[rootView, editor] = []
[editor, editSession, buffer] = []
beforeEach ->
rootView = new RootView(require.resolve('fixtures/sample.js'))
atom.loadPackage('bracket-matcher')
rootView.attachToDom()
editor = rootView.getActiveEditor()
editSession = editor.activeEditSession
buffer = editSession.buffer
afterEach ->
rootView.deactivate()
describe "when the cursor is before a starting pair", ->
it "highlights the starting pair and ending pair", ->
editor.moveCursorToEndOfLine()
editor.moveCursorLeft()
expect(editor.underlayer.find('.bracket-matcher').length).toBe 2
expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([0,28])
expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([12,0])
describe "when the cursor is after a starting pair", ->
it "highlights the starting pair and ending pair", ->
editor.moveCursorToEndOfLine()
expect(editor.underlayer.find('.bracket-matcher').length).toBe 2
expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([0,28])
expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([12,0])
describe "when the cursor is before an ending pair", ->
it "highlights the starting pair and ending pair", ->
editor.moveCursorToBottom()
editor.moveCursorLeft()
editor.moveCursorLeft()
expect(editor.underlayer.find('.bracket-matcher').length).toBe 2
expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([12,0])
expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([0,28])
describe "when the cursor is after an ending pair", ->
it "highlights the starting pair and ending pair", ->
editor.moveCursorToBottom()
editor.moveCursorLeft()
expect(editor.underlayer.find('.bracket-matcher').length).toBe 2
expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([12,0])
expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([0,28])
describe "when the cursor is moved off a pair", ->
it "removes the starting pair and ending pair highlights", ->
editor.moveCursorToEndOfLine()
expect(editor.underlayer.find('.bracket-matcher').length).toBe 2
editor.moveCursorToBeginningOfLine()
expect(editor.underlayer.find('.bracket-matcher').length).toBe 0
describe "pair balancing", ->
describe "when a second starting pair preceeds the first ending pair", ->
it "advances to the second ending pair", ->
editor.setCursorBufferPosition([8,42])
describe "matching bracket highlighting", ->
describe "when the cursor is before a starting pair", ->
it "highlights the starting pair and ending pair", ->
editor.moveCursorToEndOfLine()
editor.moveCursorLeft()
expect(editor.underlayer.find('.bracket-matcher').length).toBe 2
expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([8,42])
expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([8,54])
expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([0,28])
expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([12,0])
describe "when the cursor is after a starting pair", ->
it "highlights the starting pair and ending pair", ->
editor.moveCursorToEndOfLine()
expect(editor.underlayer.find('.bracket-matcher').length).toBe 2
expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([0,28])
expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([12,0])
describe "when the cursor is before an ending pair", ->
it "highlights the starting pair and ending pair", ->
editor.moveCursorToBottom()
editor.moveCursorLeft()
editor.moveCursorLeft()
expect(editor.underlayer.find('.bracket-matcher').length).toBe 2
expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([12,0])
expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([0,28])
describe "when the cursor is after an ending pair", ->
it "highlights the starting pair and ending pair", ->
editor.moveCursorToBottom()
editor.moveCursorLeft()
expect(editor.underlayer.find('.bracket-matcher').length).toBe 2
expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([12,0])
expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([0,28])
describe "when the cursor is moved off a pair", ->
it "removes the starting pair and ending pair highlights", ->
editor.moveCursorToEndOfLine()
expect(editor.underlayer.find('.bracket-matcher').length).toBe 2
editor.moveCursorToBeginningOfLine()
expect(editor.underlayer.find('.bracket-matcher').length).toBe 0
describe "pair balancing", ->
describe "when a second starting pair preceeds the first ending pair", ->
it "advances to the second ending pair", ->
editor.setCursorBufferPosition([8,42])
expect(editor.underlayer.find('.bracket-matcher').length).toBe 2
expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([8,42])
expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([8,54])
describe "when editor:go-to-matching-bracket is triggered", ->
describe "when the cursor is before the starting pair", ->
@@ -84,3 +87,189 @@ describe "bracket matching", ->
editor.setCursorBufferPosition([12, 1])
editor.trigger "editor:go-to-matching-bracket"
expect(editor.getCursorBufferPosition()).toEqual [0, 28]
describe "matching bracket insertion", ->
beforeEach ->
editSession.buffer.setText("")
describe "when more than one character is inserted", ->
it "does not insert a matching bracket", ->
editSession.insertText("woah(")
expect(editSession.buffer.getText()).toBe "woah("
describe "when there is a word character after the cursor", ->
it "does not insert a matching bracket", ->
editSession.buffer.setText("ab")
editSession.setCursorBufferPosition([0, 1])
editSession.insertText("(")
expect(editSession.buffer.getText()).toBe "a(b"
describe "when there are multiple cursors", ->
it "inserts ) at each cursor", ->
editSession.buffer.setText("()\nab\n[]\n12")
editSession.setCursorBufferPosition([3, 1])
editSession.addCursorAtBufferPosition([2, 1])
editSession.addCursorAtBufferPosition([1, 1])
editSession.addCursorAtBufferPosition([0, 1])
editSession.insertText ')'
expect(editSession.buffer.getText()).toBe "())\na)b\n[)]\n1)2"
describe "when there is a non-word character after the cursor", ->
it "inserts a closing bracket after an opening bracket is inserted", ->
editSession.buffer.setText("}")
editSession.setCursorBufferPosition([0, 0])
editSession.insertText '{'
expect(buffer.lineForRow(0)).toBe "{}}"
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
describe "when the cursor is at the end of the line", ->
it "inserts a closing bracket after an opening bracket is inserted", ->
editSession.buffer.setText("")
editSession.insertText '{'
expect(buffer.lineForRow(0)).toBe "{}"
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
editSession.buffer.setText("")
editSession.insertText '('
expect(buffer.lineForRow(0)).toBe "()"
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
editSession.buffer.setText("")
editSession.insertText '['
expect(buffer.lineForRow(0)).toBe "[]"
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
editSession.buffer.setText("")
editSession.insertText '"'
expect(buffer.lineForRow(0)).toBe '""'
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
editSession.buffer.setText("")
editSession.insertText "'"
expect(buffer.lineForRow(0)).toBe "''"
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
describe "when the cursor is on a closing bracket and a closing bracket is inserted", ->
describe "when the closing bracket was there previously", ->
it "inserts a closing bracket", ->
editSession.insertText '()x'
editSession.setCursorBufferPosition([0, 1])
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "())x"
expect(editSession.getCursorBufferPosition().column).toBe 2
describe "when the closing bracket was automatically inserted from inserting an opening bracket", ->
it "only moves cursor over the closing bracket one time", ->
editSession.insertText '('
expect(buffer.lineForRow(0)).toBe "()"
editSession.setCursorBufferPosition([0, 1])
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "()"
expect(editSession.getCursorBufferPosition()).toEqual [0, 2]
editSession.setCursorBufferPosition([0, 1])
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "())"
expect(editSession.getCursorBufferPosition()).toEqual [0, 2]
it "moves cursor over the closing bracket after other text is inserted", ->
editSession.insertText '('
editSession.insertText 'ok cool'
expect(buffer.lineForRow(0)).toBe "(ok cool)"
editSession.setCursorBufferPosition([0, 8])
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "(ok cool)"
expect(editSession.getCursorBufferPosition()).toEqual [0, 9]
it "works with nested brackets", ->
editSession.insertText '('
editSession.insertText '1'
editSession.insertText '('
editSession.insertText '2'
expect(buffer.lineForRow(0)).toBe "(1(2))"
editSession.setCursorBufferPosition([0, 4])
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "(1(2))"
expect(editSession.getCursorBufferPosition()).toEqual [0, 5]
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "(1(2))"
expect(editSession.getCursorBufferPosition()).toEqual [0, 6]
it "works with mixed brackets", ->
editSession.insertText '('
editSession.insertText '}'
expect(buffer.lineForRow(0)).toBe "(})"
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "(})"
expect(editSession.getCursorBufferPosition()).toEqual [0, 3]
it "closes brackets with the same begin/end character correctly", ->
editSession.insertText '"'
editSession.insertText 'ok'
expect(buffer.lineForRow(0)).toBe '"ok"'
expect(editSession.getCursorBufferPosition()).toEqual [0, 3]
editSession.insertText '"'
expect(buffer.lineForRow(0)).toBe '"ok"'
expect(editSession.getCursorBufferPosition()).toEqual [0, 4]
describe "when there is text selected on a single line", ->
it "wraps the selection with brackets", ->
editSession.insertText 'text'
editSession.moveCursorToBottom()
editSession.selectToTop()
editSession.selectAll()
editSession.insertText '('
expect('(text)').toBe buffer.getText()
expect(editSession.getSelectedBufferRange()).toEqual [[0, 1], [0, 5]]
expect(editSession.getSelection().isReversed()).toBeTruthy()
describe "when there is text selected on multiple lines", ->
it "wraps the selection with brackets", ->
editSession.insertText 'text\nabcd'
editSession.moveCursorToBottom()
editSession.selectToTop()
editSession.selectAll()
editSession.insertText '('
expect('(text\nabcd)').toBe buffer.getText()
expect(editSession.getSelectedBufferRange()).toEqual [[0, 1], [1, 4]]
expect(editSession.getSelection().isReversed()).toBeTruthy()
describe "when inserting a quote", ->
describe "when a word character is before the cursor", ->
it "does not automatically insert closing quote", ->
editSession.buffer.setText("abc")
editSession.setCursorBufferPosition([0, 3])
editSession.insertText '"'
expect(buffer.lineForRow(0)).toBe "abc\""
editSession.buffer.setText("abc")
editSession.setCursorBufferPosition([0, 3])
editSession.insertText '\''
expect(buffer.lineForRow(0)).toBe "abc\'"
describe "when a non word character is before the cursor", ->
it "automatically insert closing quote", ->
editSession.buffer.setText("ab@")
editSession.setCursorBufferPosition([0, 3])
editSession.insertText '"'
expect(buffer.lineForRow(0)).toBe "ab@\"\""
expect(editSession.getCursorBufferPosition()).toEqual [0, 4]
describe "when the cursor is on an empty line", ->
it "automatically insert closing quote", ->
editSession.buffer.setText("")
editSession.setCursorBufferPosition([0, 0])
editSession.insertText '"'
expect(buffer.lineForRow(0)).toBe "\"\""
expect(editSession.getCursorBufferPosition()).toEqual [0, 1]
describe "matching bracket deletion", ->
it "deletes the end bracket when it directly proceeds a begin bracket that is being backspaced", ->
buffer.setText("")
editSession.setCursorBufferPosition([0, 0])
editSession.insertText '{'
expect(buffer.lineForRow(0)).toBe "{}"
editSession.backspace()
expect(buffer.lineForRow(0)).toBe ""

View File

@@ -1,3 +1,4 @@
.bracket-matcher {
border-bottom: 1px dotted lime;
position: absolute;
}

View File

@@ -4,18 +4,12 @@ _ = require 'underscore'
module.exports =
class CommandLoggerView extends ScrollView
@activate: (rootView, state) ->
@instance = new CommandLoggerView(rootView)
@content: (rootView) ->
@div class: 'command-logger', tabindex: -1, =>
@h1 class: 'category-header', outlet: 'categoryHeader'
@h1 class: 'category-summary', outlet: 'categorySummary'
@div class: 'tree-map', outlet: 'treeMap'
@serialize: ->
@instance.serialize()
eventLog: null
ignoredEvents: [
'core:backspace'
@@ -30,7 +24,7 @@ class CommandLoggerView extends ScrollView
'tree-view:directory-modified'
]
initialize: (@rootView) ->
initialize: ->
super
@command 'core:cancel', => @detach()
@@ -176,7 +170,7 @@ class CommandLoggerView extends ScrollView
d3.select('.command-logger').on('click', -> zoom(root))
attach: ->
@rootView.append(this)
rootView.append(this)
@addTreeMap()
@focus()
@@ -184,8 +178,5 @@ class CommandLoggerView extends ScrollView
return if @detaching
@detaching = true
super
@rootView.focus()
rootView.focus()
@detaching = false
serialize: ->
eventLog: @eventLog

View File

@@ -1,18 +1,14 @@
DeferredAtomPackage = require 'deferred-atom-package'
$ = require 'jquery'
module.exports =
class CommandLogger extends DeferredAtomPackage
loadEvents: ['command-logger:toggle']
instanceClass: 'command-logger/src/command-logger-view'
activate: (rootView, state={})->
super
eventLog: {}
commandLoggerView: null
originalTrigger: null
activate: (state={})->
@eventLog = state.eventLog ? {}
rootView.command 'command-logger:clear-data', => @eventLog = {}
rootView.command 'command-logger:toggle', => @createView().toggle(@eventLog)
registerTriggeredEvent = (eventName) =>
eventNameLog = @eventLog[eventName]
@@ -23,10 +19,22 @@ class CommandLogger extends DeferredAtomPackage
@eventLog[eventName] = eventNameLog
eventNameLog.count++
eventNameLog.lastRun = new Date().getTime()
originalTrigger = $.fn.trigger
trigger = $.fn.trigger
@originalTrigger = trigger
$.fn.trigger = (eventName) ->
eventName = eventName.type if eventName.type
registerTriggeredEvent(eventName) if $(this).events()[eventName]
originalTrigger.apply(this, arguments)
trigger.apply(this, arguments)
onLoadEvent: (event, instance) -> instance.toggle(@eventLog)
deactivate: ->
$.fn.trigger = @originalTrigger if @originalTrigger?
@commandLoggerView = null
@eventLog = {}
serialize: ->
{@eventLog}
createView: ->
unless @commandLoggerView
CommandLoggerView = require 'command-logger/lib/command-logger-view'
@commandLoggerView = new CommandLoggerView

View File

@@ -0,0 +1 @@
'main': 'lib/command-logger'

View File

@@ -1,12 +1,13 @@
RootView = require 'root-view'
CommandLogger = require 'command-logger/src/command-logger-view'
CommandLogger = require 'command-logger/lib/command-logger-view'
describe "CommandLogger", ->
[rootView, commandLogger, editor] = []
[commandLogger, editor] = []
beforeEach ->
rootView = new RootView(require.resolve('fixtures/sample.js'))
commandLogger = atom.loadPackage('command-logger')
new RootView(require.resolve('fixtures/sample.js'))
commandLogger = atom.loadPackage('command-logger').packageMain
commandLogger.eventLog = {}
editor = rootView.getActiveEditor()
afterEach ->
@@ -43,7 +44,7 @@ describe "CommandLogger", ->
describe "when an event is ignored", ->
it "does not create a node for that event", ->
commandLoggerView = commandLogger.getInstance()
commandLoggerView = commandLogger.createView()
commandLoggerView.ignoredEvents.push 'editor:delete-line'
editor.trigger 'editor:delete-line'
commandLoggerView.eventLog = commandLogger.eventLog

View File

@@ -1,10 +0,0 @@
DeferredAtomPackage = require 'deferred-atom-package'
module.exports =
class CommandPalette extends DeferredAtomPackage
loadEvents: ['command-palette:toggle']
instanceClass: 'command-palette/src/command-palette-view'
onLoadEvent: (event, instance) -> instance.attach()

View File

@@ -5,8 +5,8 @@ _ = require 'underscore'
module.exports =
class CommandPaletteView extends SelectList
@activate: (rootView) ->
@instance = new CommandPaletteView(rootView)
@activate: ->
new CommandPaletteView
@viewClass: ->
"#{super} command-palette overlay from-top"
@@ -16,12 +16,17 @@ class CommandPaletteView extends SelectList
previouslyFocusedElement: null
keyBindings: null
initialize: (@rootView) ->
@command 'command-palette:toggle', =>
@cancel()
false
initialize: ->
super
rootView.command 'command-palette:toggle', => @toggle()
toggle: ->
if @hasParent()
@cancel()
else
@attach()
attach: ->
super
@@ -34,7 +39,7 @@ class CommandPaletteView extends SelectList
events = _.sortBy events, (e) -> e.eventDescription
@setArray(events)
@appendTo(@rootView)
@appendTo(rootView)
@miniEditor.focus()
itemForElement: ({eventName, eventDescription}) ->

View File

@@ -0,0 +1,2 @@
'main': 'lib/command-palette-view'
'activationEvents': ['command-palette:toggle']

View File

@@ -1,17 +1,17 @@
RootView = require 'root-view'
CommandPalette = require 'command-palette/src/command-palette-view'
CommandPalette = require 'command-palette/lib/command-palette-view'
$ = require 'jquery'
_ = require 'underscore'
describe "CommandPalette", ->
[rootView, palette] = []
[palette] = []
beforeEach ->
rootView = new RootView(require.resolve('fixtures/sample.js'))
atom.loadPackage("command-palette").getInstance()
palette = CommandPalette.instance
atom.loadPackage("command-palette")
rootView.attachToDom().focus()
rootView.trigger 'command-palette:toggle'
palette = rootView.find('.command-palette').view()
afterEach ->
rootView.remove()

View File

@@ -1,33 +0,0 @@
DeferredAtomPackage = require 'deferred-atom-package'
module.exports =
class CommandPanel extends DeferredAtomPackage
loadEvents: [
'command-panel:toggle'
'command-panel:toggle-preview'
'command-panel:find-in-file'
'command-panel:find-in-project'
'command-panel:repeat-relative-address'
'command-panel:repeat-relative-address-in-reverse'
'command-panel:set-selection-as-regex-address'
]
instanceClass: 'command-panel/src/command-panel-view'
onLoadEvent: (event, instance) ->
switch event.type
when 'command-panel:toggle'
instance.toggle()
when 'command-panel:toggle-preview'
instance.togglePreview()
when 'command-panel:find-in-file'
instance.attach("/")
when 'command-panel:find-in-project'
instance.attach("Xx/")
when 'command-panel:repeat-relative-address'
instance.repeatRelativeAddress()
when 'command-panel:repeat-relative-address-in-reverse'
instance.repeatRelativeAddressInReverse()
when 'command-panel:set-selection-as-regex-address'
instance.setSelectionAsLastRelativeAddress()

View File

@@ -6,7 +6,7 @@ class CommandInterpreter
constructor: (@project) ->
eval: (string, activeEditSession) ->
@parser ?= PEG.buildParser(fs.read(require.resolve 'command-panel/commands.pegjs'))
@parser ?= PEG.buildParser(fs.read(require.resolve 'command-panel/lib/commands.pegjs'))
compositeCommand = @parser.parse(string)
@lastRelativeAddress = compositeCommand if compositeCommand.isRelativeAddress()
compositeCommand.execute(@project, activeEditSession)

View File

@@ -1,28 +1,15 @@
{View, $$, $$$} = require 'space-pen'
CommandInterpreter = require 'command-panel/src/command-interpreter'
RegexAddress = require 'command-panel/src/commands/regex-address'
CompositeCommand = require 'command-panel/src/commands/composite-command'
PreviewList = require 'command-panel/src/preview-list'
CommandInterpreter = require './command-interpreter'
RegexAddress = require './commands/regex-address'
CompositeCommand = require './commands/composite-command'
PreviewList = require './preview-list'
Editor = require 'editor'
{SyntaxError} = require('pegjs').parser
_ = require 'underscore'
module.exports =
class CommandPanelView extends View
@activate: (rootView, state) ->
if state?
@instance = @deserialize(state, rootView)
else
@instance = new CommandPanelView(rootView)
@deserialize: (state, rootView) ->
commandPanel = new CommandPanelView(rootView, state.history)
commandPanel.attach(state.text, focus: false) if state.visible
commandPanel.miniEditor.focus() if state.miniEditorFocused
commandPanel
@content: (rootView) ->
@content: ->
@div class: 'command-panel tool-panel', =>
@div outlet: 'previewCount', class: 'preview-count'
@subview 'previewList', new PreviewList(rootView)
@@ -36,38 +23,42 @@ class CommandPanelView extends View
historyIndex: 0
maxSerializedHistorySize: 100
initialize: (@rootView, @history) ->
@commandInterpreter = new CommandInterpreter(@rootView.project)
initialize: (state={}) ->
@commandInterpreter = new CommandInterpreter(rootView.project)
@history ?= []
@historyIndex = @history.length
@command 'tool-panel:unfocus', => @rootView.focus()
@command 'tool-panel:unfocus', => rootView.focus()
@command 'core:close', => @detach(); false
@command 'core:confirm', => @execute()
@command 'core:move-up', => @navigateBackwardInHistory()
@command 'core:move-down', => @navigateForwardInHistory()
rootView.command 'command-panel:toggle', => @toggle()
rootView.command 'command-panel:toggle-preview', => @togglePreview()
rootView.command 'command-panel:find-in-file', => @attach('/')
rootView.command 'command-panel:find-in-project', => @attach('Xx/')
rootView.command 'command-panel:repeat-relative-address', => @repeatRelativeAddress()
rootView.command 'command-panel:repeat-relative-address-in-reverse', => @repeatRelativeAddressInReverse()
rootView.command 'command-panel:set-selection-as-regex-address', => @setSelectionAsLastRelativeAddress()
@previewList.hide()
@previewCount.hide()
@errorMessages.hide()
@prompt.iconSize(@miniEditor.getFontSize())
@history = state.history ? []
@historyIndex = @history.length
serialize: ->
text: @miniEditor.getText()
visible: @hasParent()
miniEditorFocused: @miniEditor.isFocused
history: @history[-@maxSerializedHistorySize..]
deactivate: -> @destroy()
destroy: ->
@previewList.destroy()
toggle: ->
if @miniEditor.isFocused
@detach()
@rootView.focus()
rootView.focus()
else
@attach() unless @hasParent()
@miniEditor.focus()
@@ -77,7 +68,7 @@ class CommandPanelView extends View
@previewList.hide()
@previewCount.hide()
@detach()
@rootView.focus()
rootView.focus()
else
@attach() unless @hasParent()
if @previewList.hasOperations()
@@ -90,13 +81,13 @@ class CommandPanelView extends View
@errorMessages.hide()
focus = options.focus ? true
@rootView.vertical.append(this)
rootView.vertical.append(this)
@miniEditor.focus() if focus
@miniEditor.setText(text)
@miniEditor.setCursorBufferPosition([0, Infinity])
detach: ->
@rootView.focus()
rootView.focus()
@previewList.hide()
@previewCount.hide()
super
@@ -108,7 +99,7 @@ class CommandPanelView extends View
@errorMessages.empty()
try
@commandInterpreter.eval(command, @rootView.getActiveEditSession()).done ({operationsToPreview, errorMessages}) =>
@commandInterpreter.eval(command, rootView.getActiveEditSession()).done ({operationsToPreview, errorMessages}) =>
@history.push(command)
@historyIndex = @history.length
@@ -141,12 +132,12 @@ class CommandPanelView extends View
@miniEditor.setText(@history[@historyIndex] or '')
repeatRelativeAddress: ->
@commandInterpreter.repeatRelativeAddress(@rootView.getActiveEditSession())
@commandInterpreter.repeatRelativeAddress(rootView.getActiveEditSession())
repeatRelativeAddressInReverse: ->
@commandInterpreter.repeatRelativeAddressInReverse(@rootView.getActiveEditSession())
@commandInterpreter.repeatRelativeAddressInReverse(rootView.getActiveEditSession())
setSelectionAsLastRelativeAddress: ->
selection = @rootView.getActiveEditor().getSelectedText()
selection = rootView.getActiveEditor().getSelectedText()
regex = _.escapeRegExp(selection)
@commandInterpreter.lastRelativeAddress = new CompositeCommand([new RegexAddress(regex)])

View File

@@ -0,0 +1,17 @@
CommandPanelView = require './command-panel-view'
module.exports =
commandPanelView: null
activate: (@state) ->
@commandPanelView = new CommandPanelView(@state)
deactivate: ->
@commandPanelView?.destroy()
@commandPanelView = null
serialize: ->
if @commandPanelView?
@commandPanelView.serialize()
else
@state

View File

@@ -1,15 +1,15 @@
{
var CompositeCommand = require('command-panel/src/commands/composite-command')
var Substitution = require('command-panel/src/commands/substitution');
var ZeroAddress = require('command-panel/src/commands/zero-address');
var EofAddress = require('command-panel/src/commands/eof-address');
var LineAddress = require('command-panel/src/commands/line-address');
var AddressRange = require('command-panel/src/commands/address-range');
var DefaultAddressRange = require('command-panel/src/commands/default-address-range');
var CurrentSelectionAddress = require('command-panel/src/commands/current-selection-address')
var RegexAddress = require('command-panel/src/commands/regex-address')
var SelectAllMatches = require('command-panel/src/commands/select-all-matches')
var SelectAllMatchesInProject = require('command-panel/src/commands/select-all-matches-in-project')
var CompositeCommand = require('command-panel/lib/commands/composite-command')
var Substitution = require('command-panel/lib/commands/substitution');
var ZeroAddress = require('command-panel/lib/commands/zero-address');
var EofAddress = require('command-panel/lib/commands/eof-address');
var LineAddress = require('command-panel/lib/commands/line-address');
var AddressRange = require('command-panel/lib/commands/address-range');
var DefaultAddressRange = require('command-panel/lib/commands/default-address-range');
var CurrentSelectionAddress = require('command-panel/lib/commands/current-selection-address')
var RegexAddress = require('command-panel/lib/commands/regex-address')
var SelectAllMatches = require('command-panel/lib/commands/select-all-matches')
var SelectAllMatchesInProject = require('command-panel/lib/commands/select-all-matches-in-project')
}
start = _ commands:( selectAllMatchesInProject / textCommand ) {

View File

@@ -1,4 +1,4 @@
Address = require 'command-panel/src/commands/address'
Address = require 'command-panel/lib/commands/address'
Range = require 'range'
module.exports =

View File

@@ -1,5 +1,5 @@
Command = require 'command-panel/src/commands/command'
Operation = require 'command-panel/src/operation'
Command = require './command'
Operation = require 'command-panel/lib/operation'
$ = require 'jquery'
module.exports =

View File

@@ -1,5 +1,3 @@
_ = require 'underscore'
module.exports =
class Command
isAddress: -> false

View File

@@ -1,5 +1,4 @@
Address = require 'command-panel/src/commands/address'
Range = require 'range'
Address = require './address'
module.exports =
class CurrentSelectionAddress extends Address

View File

@@ -1,5 +1,4 @@
Address = require 'command-panel/src/commands/address'
Range = require 'range'
Address = require './address'
module.exports =
class DefaultAddressRange extends Address

View File

@@ -1,4 +1,4 @@
Address = require 'command-panel/src/commands/address'
Address = require './address'
Range = require 'range'
module.exports =

View File

@@ -1,4 +1,4 @@
Address = require 'command-panel/src/commands/address'
Address = require './address'
Range = require 'range'
module.exports =

View File

@@ -1,4 +1,4 @@
Address = require 'command-panel/src/commands/address'
Address = require './address'
Range = require 'range'
module.exports =

View File

@@ -1,5 +1,5 @@
Command = require 'command-panel/src/commands/command'
Operation = require 'command-panel/src/operation'
Command = require './command'
Operation = require 'command-panel/lib/operation'
$ = require 'jquery'
module.exports =

View File

@@ -1,5 +1,5 @@
Command = require 'command-panel/src/commands/command'
Operation = require 'command-panel/src/operation'
Command = require './command'
Operation = require 'command-panel/lib/operation'
$ = require 'jquery'
module.exports =

View File

@@ -1,5 +1,5 @@
Command = require 'command-panel/src/commands/command'
Operation = require 'command-panel/src/operation'
Command = require './command'
Operation = require 'command-panel/lib/operation'
$ = require 'jquery'
module.exports =

View File

@@ -1,4 +1,4 @@
Address = require 'command-panel/src/commands/address'
Address = require './address'
Range = require 'range'
module.exports =

View File

@@ -1,23 +1,21 @@
{$$$} = require 'space-pen'
module.exports =
class Operation
constructor: ({@project, @buffer, bufferRange, @newText, @preserveSelection, @errorMessage}) ->
@buffer.retain()
@anchorRange = @buffer.addAnchorRange(bufferRange)
@marker = @buffer.markRange(bufferRange)
getPath: ->
@project.relativize(@buffer.getPath())
getBufferRange: ->
@anchorRange.getBufferRange()
@buffer.getMarkerRange(@marker)
execute: (editSession) ->
@buffer.change(@getBufferRange(), @newText) if @newText
@getBufferRange() unless @preserveSelection
preview: ->
range = @anchorRange.getBufferRange()
range = @buffer.getMarkerRange(@marker)
line = @buffer.lineForRow(range.start.row)
prefix = line[0...range.start.column]
match = line[range.start.column...range.end.column]
@@ -26,5 +24,5 @@ class Operation
{prefix, suffix, match, range}
destroy: ->
@buffer.destroyMarker(@marker)
@buffer.release()
@anchorRange.destroy()

View File

@@ -0,0 +1,10 @@
'main': 'lib/command-panel'
'activationEvents': [
'command-panel:toggle'
'command-panel:toggle-preview'
'command-panel:find-in-file'
'command-panel:find-in-project'
'command-panel:repeat-relative-address'
'command-panel:repeat-relative-address-in-reverse'
'command-panel:set-selection-as-regex-address'
]

View File

@@ -1,10 +1,12 @@
CommandInterpreter = require 'command-panel/src/command-interpreter'
CommandInterpreter = require 'command-panel/lib/command-interpreter'
Project = require 'project'
Buffer = require 'buffer'
EditSession = require 'edit-session'
_ = require 'underscore'
describe "CommandInterpreter", ->
[project, interpreter, editSession, buffer, anchorCountBefore] = []
[project, interpreter, editSession, buffer] = []
beforeEach ->
project = new Project(fixturesProject.resolve('dir/'))
@@ -14,7 +16,7 @@ describe "CommandInterpreter", ->
afterEach ->
editSession?.destroy()
expect(buffer.getAnchors().length).toBe 0
expect(buffer.getMarkerCount()).toBe 0
describe "addresses", ->
beforeEach ->

View File

@@ -1,19 +1,19 @@
RootView = require 'root-view'
CommandPanelView = require 'command-panel/src/command-panel-view'
CommandPanelView = require 'command-panel/lib/command-panel-view'
_ = require 'underscore'
describe "CommandPanel", ->
[rootView, editor, buffer, commandPanel, project, CommandPanel] = []
[editor, buffer, commandPanel, project, CommandPanel] = []
beforeEach ->
rootView = new RootView
new RootView
rootView.open(require.resolve 'fixtures/sample.js')
rootView.enableKeymap()
project = rootView.project
editor = rootView.getActiveEditor()
buffer = editor.activeEditSession.buffer
CommandPanel = atom.loadPackage('command-panel')
commandPanel = CommandPanel.getInstance()
commandPanelMain = atom.loadPackage('command-panel', activateImmediately: true).packageMain
commandPanel = commandPanelMain.commandPanelView
commandPanel.history = []
commandPanel.historyIndex = 0
@@ -21,7 +21,7 @@ describe "CommandPanel", ->
rootView.deactivate()
describe "serialization", ->
it "preserves the command panel's mini-editor text, visibility, focus, and history across reloads", ->
it "preserves the command panel's history across reloads", ->
rootView.attachToDom()
rootView.trigger 'command-panel:toggle'
expect(commandPanel.miniEditor.isFocused).toBeTruthy()
@@ -31,29 +31,20 @@ describe "CommandPanel", ->
expect(commandPanel.historyIndex).toBe(1)
rootView.trigger 'command-panel:toggle'
expect(commandPanel.miniEditor.isFocused).toBeTruthy()
commandPanel.miniEditor.insertText 'abc'
rootView2 = RootView.deserialize(rootView.serialize())
rootView.deactivate()
rootView2.attachToDom()
commandPanel = rootView2.activatePackage('command-panel', CommandPanel).getInstance()
expect(rootView2.find('.command-panel')).toExist()
expect(commandPanel.miniEditor.getText()).toBe 'abc'
expect(commandPanel.miniEditor.isFocused).toBeTruthy()
rootViewState = rootView.serialize()
rootView.deactivate()
RootView.deserialize(rootViewState).attachToDom()
atom.loadPackage('command-panel')
expect(rootView.find('.command-panel')).not.toExist()
rootView.trigger 'command-panel:toggle'
expect(rootView.find('.command-panel')).toExist()
commandPanel = rootView.find('.command-panel').view()
expect(commandPanel.history.length).toBe(1)
expect(commandPanel.history[0]).toBe('/.')
expect(commandPanel.historyIndex).toBe(1)
rootView2.focus()
expect(commandPanel.miniEditor.isFocused).toBeFalsy()
rootView3 = RootView.deserialize(rootView2.serialize())
rootView2.deactivate()
rootView3.attachToDom()
commandPanel = rootView3.activatePackage('command-panel', CommandPanel).getInstance()
expect(commandPanel.miniEditor.isFocused).toBeFalsy()
rootView3.deactivate()
it "only retains the configured max serialized history size", ->
rootView.attachToDom()
@@ -67,18 +58,18 @@ describe "CommandPanel", ->
expect(commandPanel.history[2]).toBe('/test3')
expect(commandPanel.historyIndex).toBe(3)
rootView2 = RootView.deserialize(rootView.serialize())
rootViewState = rootView.serialize()
rootView.deactivate()
rootView2.attachToDom()
RootView.deserialize(rootViewState).attachToDom()
atom.loadPackage('command-panel')
rootView.trigger 'command-panel:toggle'
commandPanel = rootView2.activatePackage('command-panel', CommandPanel).getInstance()
commandPanel = rootView.find('.command-panel').view()
expect(commandPanel.history.length).toBe(2)
expect(commandPanel.history[0]).toBe('/test2')
expect(commandPanel.history[1]).toBe('/test3')
expect(commandPanel.historyIndex).toBe(2)
rootView2.deactivate()
describe "when core:close is triggered on the command panel", ->
it "detaches the command panel, focuses the RootView and does not bubble the core:close event", ->
commandPanel.attach()

View File

@@ -1,16 +0,0 @@
DeferredAtomPackage = require 'deferred-atom-package'
Stats = require './src/stats'
module.exports =
class EditorStats extends DeferredAtomPackage
loadEvents: ['editor-stats:toggle']
instanceClass: 'editor-stats/src/editor-stats-view'
stats: new Stats
activate: (rootView) ->
super
rootView.on 'keydown', => @stats.track()
rootView.on 'mouseup', => @stats.track()
onLoadEvent: (event, instance) -> instance.toggle(@stats)

Some files were not shown because too many files have changed in this diff Show More