mirror of
https://github.com/atom/atom.git
synced 2026-02-04 03:35:20 -05:00
@@ -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
|
||||
|
||||
31
README.md
31
README.md
@@ -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
|
||||
|
||||
3
Rakefile
3
Rakefile
@@ -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"
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
AtomCefClient::AtomCefClient(){
|
||||
}
|
||||
|
||||
AtomCefClient::AtomCefClient(bool handlePasteboardCommands) {
|
||||
AtomCefClient::AtomCefClient(bool handlePasteboardCommands, bool ignoreTitleChanges) {
|
||||
m_HandlePasteboardCommands = handlePasteboardCommands;
|
||||
m_IgnoreTitleChanges = ignoreTitleChanges;
|
||||
}
|
||||
|
||||
AtomCefClient::~AtomCefClient() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()]];
|
||||
}
|
||||
|
||||
@@ -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.
@@ -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;
|
||||
|
||||
67
spec/app/atom-package-spec.coffee
Normal file
67
spec/app/atom-package-spec.coffee
Normal 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
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"
|
||||
|
||||
4
spec/fixtures/packages/package-with-a-cson-grammar.tmbundle/Snippets/test.cson
vendored
Normal file
4
spec/fixtures/packages/package-with-a-cson-grammar.tmbundle/Snippets/test.cson
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
'name': 'Really'
|
||||
'scope': 'source.alot'
|
||||
'tabTrigger': 'really'
|
||||
'content': 'I really like $1 alot$0'
|
||||
11
spec/fixtures/packages/package-with-a-cson-grammar.tmbundle/Syntaxes/alot.cson
vendored
Normal file
11
spec/fixtures/packages/package-with-a-cson-grammar.tmbundle/Syntaxes/alot.cson
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
'fileTypes': ['alot']
|
||||
'name': 'Alot'
|
||||
'scopeName': 'source.alot'
|
||||
'patterns': [
|
||||
{
|
||||
'captures':
|
||||
'0':
|
||||
'name': 'keyword.alot'
|
||||
'match': 'alot'
|
||||
}
|
||||
]
|
||||
6
spec/fixtures/packages/package-with-activation-events/main.coffee
vendored
Normal file
6
spec/fixtures/packages/package-with-activation-events/main.coffee
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports =
|
||||
activationEventCallCount: 0
|
||||
|
||||
activate: ->
|
||||
rootView.getActiveEditor()?.command 'activation-event', =>
|
||||
@activationEventCallCount++
|
||||
2
spec/fixtures/packages/package-with-activation-events/package.cson
vendored
Normal file
2
spec/fixtures/packages/package-with-activation-events/package.cson
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
'activationEvents': ['activation-event']
|
||||
'main': 'main'
|
||||
@@ -1,6 +1,8 @@
|
||||
AtomPackage = require 'atom-package'
|
||||
|
||||
module.exports =
|
||||
class MyPackage extends AtomPackage
|
||||
configDefaults:
|
||||
numbers: { one: 1, two: 2 }
|
||||
|
||||
activate: ->
|
||||
@activateCalled = true
|
||||
|
||||
deactivate: ->
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
152
src/app/buffer-marker.coffee
Normal file
152
src/app/buffer-marker.coffee
Normal 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
|
||||
@@ -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()
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
114
src/app/display-buffer-marker.coffee
Normal file
114
src/app/display-buffer-marker.coffee
Normal 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...)
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
AtomPackage = require 'atom-package'
|
||||
AutocompleteView = require './src/autocomplete-view'
|
||||
|
||||
module.exports =
|
||||
class Autocomplete extends AtomPackage
|
||||
activate: (rootView) -> AutocompleteView.activate(rootView)
|
||||
@@ -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())
|
||||
|
||||
10
src/packages/autocomplete/lib/autocomplete.coffee
Normal file
10
src/packages/autocomplete/lib/autocomplete.coffee
Normal 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)
|
||||
|
||||
3
src/packages/autocomplete/package.cson
Normal file
3
src/packages/autocomplete/package.cson
Normal file
@@ -0,0 +1,3 @@
|
||||
'main': 'lib/autocomplete'
|
||||
'activationEvents':
|
||||
'autocomplete:attach': '.editor'
|
||||
@@ -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", ->
|
||||
|
||||
@@ -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())
|
||||
|
||||
1
src/packages/autoflow/package.cson
Normal file
1
src/packages/autoflow/package.cson
Normal file
@@ -0,0 +1 @@
|
||||
'main': 'autoflow'
|
||||
@@ -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]?
|
||||
1
src/packages/bracket-matcher/package.cson
Normal file
1
src/packages/bracket-matcher/package.cson
Normal file
@@ -0,0 +1 @@
|
||||
'main': 'lib/bracket-matcher'
|
||||
@@ -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 ""
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
.bracket-matcher {
|
||||
border-bottom: 1px dotted lime;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
1
src/packages/command-logger/package.cson
Normal file
1
src/packages/command-logger/package.cson
Normal file
@@ -0,0 +1 @@
|
||||
'main': 'lib/command-logger'
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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}) ->
|
||||
2
src/packages/command-palette/package.cson
Normal file
2
src/packages/command-palette/package.cson
Normal file
@@ -0,0 +1,2 @@
|
||||
'main': 'lib/command-palette-view'
|
||||
'activationEvents': ['command-palette:toggle']
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)])
|
||||
17
src/packages/command-panel/lib/command-panel.coffee
Normal file
17
src/packages/command-panel/lib/command-panel.coffee
Normal 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
|
||||
@@ -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 ) {
|
||||
@@ -1,4 +1,4 @@
|
||||
Address = require 'command-panel/src/commands/address'
|
||||
Address = require 'command-panel/lib/commands/address'
|
||||
Range = require 'range'
|
||||
|
||||
module.exports =
|
||||
@@ -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 =
|
||||
@@ -1,5 +1,3 @@
|
||||
_ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
class Command
|
||||
isAddress: -> false
|
||||
@@ -1,5 +1,4 @@
|
||||
Address = require 'command-panel/src/commands/address'
|
||||
Range = require 'range'
|
||||
Address = require './address'
|
||||
|
||||
module.exports =
|
||||
class CurrentSelectionAddress extends Address
|
||||
@@ -1,5 +1,4 @@
|
||||
Address = require 'command-panel/src/commands/address'
|
||||
Range = require 'range'
|
||||
Address = require './address'
|
||||
|
||||
module.exports =
|
||||
class DefaultAddressRange extends Address
|
||||
@@ -1,4 +1,4 @@
|
||||
Address = require 'command-panel/src/commands/address'
|
||||
Address = require './address'
|
||||
Range = require 'range'
|
||||
|
||||
module.exports =
|
||||
@@ -1,4 +1,4 @@
|
||||
Address = require 'command-panel/src/commands/address'
|
||||
Address = require './address'
|
||||
Range = require 'range'
|
||||
|
||||
module.exports =
|
||||
@@ -1,4 +1,4 @@
|
||||
Address = require 'command-panel/src/commands/address'
|
||||
Address = require './address'
|
||||
Range = require 'range'
|
||||
|
||||
module.exports =
|
||||
@@ -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 =
|
||||
@@ -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 =
|
||||
@@ -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 =
|
||||
@@ -1,4 +1,4 @@
|
||||
Address = require 'command-panel/src/commands/address'
|
||||
Address = require './address'
|
||||
Range = require 'range'
|
||||
|
||||
module.exports =
|
||||
@@ -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()
|
||||
10
src/packages/command-panel/package.cson
Normal file
10
src/packages/command-panel/package.cson
Normal 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'
|
||||
]
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user