Merge pull request #31 from github/global-find

Search in project
This commit is contained in:
Nathan Sobo
2012-07-25 13:32:33 -07:00
62 changed files with 4399 additions and 479 deletions

View File

@@ -1142,6 +1142,7 @@
LIBRARY_SEARCH_PATHS = (
"$(SDKROOT)/System/Library/Frameworks",
"\"$(SRCROOT)/frameworks\"",
"\"$(SDKROOT)/usr/lib/system\"",
);
OTHER_CFLAGS = (
"-fno-strict-aliasing",
@@ -1188,6 +1189,7 @@
LIBRARY_SEARCH_PATHS = (
"$(SDKROOT)/System/Library/Frameworks",
"\"$(SRCROOT)/frameworks\"",
"\"$(SDKROOT)/usr/lib/system\"",
);
OTHER_CFLAGS = "-fno-strict-aliasing";
OTHER_LDFLAGS = (

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0487C91114FED5360045E5E3"
BuildableName = "Atom.app"
BlueprintName = "Atom"
ReferencedContainer = "container:Atom.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0487C91114FED5360045E5E3"
BuildableName = "Atom.app"
BlueprintName = "Atom"
ReferencedContainer = "container:Atom.xcodeproj">
</BuildableReference>
</MacroExpansion>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0487C91114FED5360045E5E3"
BuildableName = "Atom.app"
BlueprintName = "Atom"
ReferencedContainer = "container:Atom.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "--test"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0487C91114FED5360045E5E3"
BuildableName = "Atom.app"
BlueprintName = "Atom"
ReferencedContainer = "container:Atom.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -208,8 +208,8 @@ void AppGetBrowserSettings(CefBrowserSettings& settings) {
settings.databases_disabled = false;
settings.application_cache_disabled = false;
settings.webgl_disabled = false;
settings.accelerated_compositing_enabled = false;
settings.threaded_compositing_enabled = false;
settings.accelerated_compositing_enabled = true;
settings.threaded_compositing_enabled = true;
settings.accelerated_layers_disabled = false;
settings.accelerated_video_disabled = false;
settings.accelerated_2d_canvas_disabled = false;

View File

@@ -4,9 +4,12 @@
#import "AtomController.h"
#import "client_handler.h"
#import "PathWatcher.h"
#import <dispatch/dispatch.h>
#import <Cocoa/Cocoa.h>
#import <CommonCrypto/CommonDigest.h>
#import <iostream>
#define MY_EXCEPTION_TRY @try {
#define MY_EXCEPTION_HANDLE } @catch (NSException *localException) {}
@@ -15,10 +18,22 @@ NSString *stringFromCefV8Value(const CefRefPtr<CefV8Value>& value) {
return [NSString stringWithUTF8String:cc_value.c_str()];
}
void throwException(const CefRefPtr<CefV8Value>& global, CefRefPtr<CefV8Exception>& exception, NSString *message) {
CefV8ValueList arguments;
CefRefPtr<CefV8Value> retval;
CefRefPtr<CefV8Exception> e;
message = [message stringByAppendingFormat:@"\n%s", exception->GetMessage().ToString().c_str()];
arguments.push_back(CefV8Value::CreateString(std::string([message UTF8String], [message lengthOfBytesUsingEncoding:NSUTF8StringEncoding])));
CefRefPtr<CefV8Value> console = global->GetValue("console");
console->GetValue("error")->ExecuteFunction(console, arguments, retval, e, false);
}
NativeHandler::NativeHandler() : CefV8Handler() {
std::string extensionCode = "var $native = {}; (function() {";
const char *functionNames[] = {"exists", "alert", "read", "write", "absolute", "list", "isFile", "isDirectory", "remove", "asyncList", "open", "openDialog", "quit", "writeToPasteboard", "readFromPasteboard", "showDevTools", "toggleDevTools", "newWindow", "saveDialog", "exit", "watchPath", "unwatchPath", "makeDirectory", "move", "moveToTrash", "reload", "lastModified", "md5ForPath"};
const char *functionNames[] = {"exists", "alert", "read", "write", "absolute", "list", "isFile", "isDirectory", "remove", "asyncList", "open", "openDialog", "quit", "writeToPasteboard", "readFromPasteboard", "showDevTools", "toggleDevTools", "newWindow", "saveDialog", "exit", "watchPath", "unwatchPath", "makeDirectory", "move", "moveToTrash", "reload", "lastModified", "md5ForPath", "exec"};
NSUInteger arrayLength = sizeof(functionNames) / sizeof(const char *);
for (NSUInteger i = 0; i < arrayLength; i++) {
std::string functionName = std::string(functionNames[i]);
@@ -419,5 +434,96 @@ bool NativeHandler::Execute(const CefString& name,
retval = CefV8Value::CreateString([hash UTF8String]);
return true;
}
else if (name == "exec") {
NSString *command = stringFromCefV8Value(arguments[0]);
CefRefPtr<CefV8Value> options = arguments[1];
CefRefPtr<CefV8Value> callback = arguments[2];
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:@"/bin/sh"];
[task setStandardInput:[NSFileHandle fileHandleWithNullDevice]];
[task setArguments:[NSArray arrayWithObjects:@"-l", @"-c", command, nil]];
NSPipe *stdout = [NSPipe pipe];
NSPipe *stderr = [NSPipe pipe];
[task setStandardOutput:stdout];
[task setStandardError:stderr];
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
void (^outputHandle)(NSFileHandle *fileHandle, CefRefPtr<CefV8Value> function) = nil;
void (^taskTerminatedHandle)() = nil;
outputHandle = ^(NSFileHandle *fileHandle, CefRefPtr<CefV8Value> function) {
context->Enter();
NSData *data = [fileHandle availableData];
NSString *contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
CefV8ValueList args;
CefRefPtr<CefV8Value> retval = CefV8Value::CreateBool(YES);
CefRefPtr<CefV8Exception> e;
args.push_back(CefV8Value::CreateString(std::string([contents UTF8String], [contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding])));
function->ExecuteFunction(function, args, retval, e, false);
if (e.get()) {
throwException(context->GetGlobal(), e, @"Error thrown in OutputHandle");
}
[contents release];
context->Exit();
};
taskTerminatedHandle = ^() {
context->Enter();
NSString *output = [[NSString alloc] initWithData:[[stdout fileHandleForReading] readDataToEndOfFile] encoding:NSUTF8StringEncoding];
NSString *errorOutput = [[NSString alloc] initWithData:[[task.standardError fileHandleForReading] readDataToEndOfFile] encoding:NSUTF8StringEncoding];
CefV8ValueList args;
CefRefPtr<CefV8Value> retval;
CefRefPtr<CefV8Exception> e;
args.push_back(CefV8Value::CreateInt([task terminationStatus]));
args.push_back(CefV8Value::CreateString([output UTF8String]));
args.push_back(CefV8Value::CreateString([errorOutput UTF8String]));
callback->ExecuteFunction(callback, args, retval, e, false);
if (e.get()) {
throwException(context->GetGlobal(), e, @"Error thrown in TaskTerminatedHandle");
}
context->Exit();
stdout.fileHandleForReading.writeabilityHandler = nil;
stderr.fileHandleForReading.writeabilityHandler = nil;
};
task.terminationHandler = ^(NSTask *) {
dispatch_sync(dispatch_get_main_queue(), taskTerminatedHandle);
};
CefRefPtr<CefV8Value> stdoutFunction = options->GetValue("stdout");
if (stdoutFunction->IsFunction()) {
stdout.fileHandleForReading.writeabilityHandler = ^(NSFileHandle *fileHandle) {
dispatch_sync(dispatch_get_main_queue(), ^() {
outputHandle(fileHandle, stdoutFunction);
});
};
}
CefRefPtr<CefV8Value> stderrFunction = options->GetValue("stderr");
if (stderrFunction->IsFunction()) {
stderr.fileHandleForReading.writeabilityHandler = ^(NSFileHandle *fileHandle) {
dispatch_sync(dispatch_get_main_queue(), ^() {
outputHandle(fileHandle, stderrFunction);
});
};
}
[task launch];
return true;
}
return false;
};

View File

@@ -17,6 +17,10 @@ describe "editor.", ->
window.shutdown()
delete atom.rootViewStates[$windowNumber]
describe "opening-buffers.", ->
benchmark "300-line-file.", ->
buffer = rootView.project.bufferForPath('medium.coffee')
describe "empty-file.", ->
benchmark "insert-delete", ->
editor.insertText('x')
@@ -24,7 +28,7 @@ describe "editor.", ->
describe "300-line-file.", ->
beforeEach ->
editor.edit rootView.project.open('medium.coffee')
editor.edit rootView.project.buildEditSessionForPath('medium.coffee')
describe "at-begining.", ->
benchmark "insert-delete", ->
@@ -45,11 +49,11 @@ describe "editor.", ->
describe "9000-line-file.", ->
benchmark "opening.", 5, ->
editor.edit rootView.project.open('huge.js')
editor.edit rootView.project.buildEditSessionForPath('huge.js')
describe "after-opening.", ->
beforeEach ->
editor.edit rootView.project.open('huge.js')
editor.edit rootView.project.buildEditSessionForPath('huge.js')
benchmark "moving-to-eof.", 1, ->
editor.moveCursorToBottom()

View File

@@ -11,11 +11,11 @@ describe 'Buffer', ->
buffer = new Buffer(filePath)
afterEach ->
buffer.destroy()
buffer?.release()
describe 'constructor', ->
beforeEach ->
buffer.destroy()
buffer.release()
describe "when given a path", ->
describe "when a file exists for the path", ->
@@ -31,9 +31,9 @@ describe 'Buffer', ->
describe "when no file exists for the path", ->
it "throws an exception", ->
buffer = null
filePath = "does-not-exist.txt"
expect(fs.exists(filePath)).toBeFalsy()
expect(-> new Buffer(filePath)).toThrow()
describe "when no path is given", ->
@@ -82,8 +82,8 @@ describe 'Buffer', ->
beforeEach ->
path = "/tmp/tmp.txt"
fs.write(path, "first")
buffer.destroy()
buffer = new Buffer(path)
buffer.release()
buffer = new Buffer(path).retain()
afterEach ->
fs.remove(path)
@@ -146,7 +146,10 @@ describe 'Buffer', ->
describe ".isModified()", ->
beforeEach ->
buffer.destroy()
waitsFor "file to be removed", ->
not bufferToDelete.getPath()
describe ".isModified()", ->
it "returns true when user changes buffer", ->
expect(buffer.isModified()).toBeFalsy()
buffer.insert([0,0], "hi")
@@ -155,6 +158,7 @@ describe 'Buffer', ->
it "returns false after modified buffer is saved", ->
filePath = "/tmp/atom-tmp-file"
fs.write(filePath, '')
buffer.release()
buffer = new Buffer(filePath)
expect(buffer.isModified()).toBe false
@@ -298,7 +302,7 @@ describe 'Buffer', ->
describe ".save()", ->
beforeEach ->
buffer.destroy()
buffer.release()
describe "when the buffer has a path", ->
filePath = null
@@ -349,33 +353,34 @@ describe 'Buffer', ->
expect(buffer.getText()).toBe(fileContents)
describe ".saveAs(path)", ->
filePath = null
[filePath, saveAsBuffer] = []
beforeEach ->
buffer.destroy()
afterEach ->
saveAsBuffer.release()
it "saves the contents of the buffer to the path", ->
filePath = '/tmp/temp.txt'
fs.remove filePath if fs.exists(filePath)
buffer = new Buffer()
saveAsBuffer = new Buffer().retain()
eventHandler = jasmine.createSpy('eventHandler')
buffer.on 'path-change', eventHandler
saveAsBuffer.on 'path-change', eventHandler
buffer.setText 'Buffer contents!'
buffer.saveAs(filePath)
saveAsBuffer.setText 'Buffer contents!'
saveAsBuffer.saveAs(filePath)
expect(fs.read(filePath)).toEqual 'Buffer contents!'
expect(eventHandler).toHaveBeenCalledWith(buffer)
expect(eventHandler).toHaveBeenCalledWith(saveAsBuffer)
it "stops listening to events on previous path and begins listening to events on new path", ->
originalPath = "/tmp/original.txt"
newPath = "/tmp/new.txt"
fs.write(originalPath, "")
buffer = new Buffer(originalPath)
saveAsBuffer = new Buffer(originalPath).retain()
changeHandler = jasmine.createSpy('changeHandler')
buffer.on 'change', changeHandler
buffer.saveAs(newPath)
saveAsBuffer.on 'change', changeHandler
saveAsBuffer.saveAs(newPath)
expect(changeHandler).not.toHaveBeenCalled()
fs.write(originalPath, "should not trigger buffer event")
@@ -597,3 +602,23 @@ describe 'Buffer', ->
expect(buffer.positionForCharacterIndex(30)).toEqual [1, 0]
expect(buffer.positionForCharacterIndex(61)).toEqual [2, 0]
expect(buffer.positionForCharacterIndex(408)).toEqual [12, 2]
describe "anchors", ->
[anchor, destroyHandler] = []
beforeEach ->
destroyHandler = jasmine.createSpy("destroyHandler")
anchor = buffer.addAnchorAtPosition([4, 25])
anchor.on 'destroy', destroyHandler
describe "when a buffer change precedes an anchor", ->
it "moves the anchor in accordance with the change", ->
buffer.delete([[3, 0], [4, 10]])
expect(anchor.getBufferPosition()).toEqual [3, 15]
expect(destroyHandler).not.toHaveBeenCalled()
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

View File

@@ -7,7 +7,7 @@ describe "EditSession", ->
beforeEach ->
buffer = new Buffer()
editSession = fixturesProject.open('sample.js', autoIndent: false)
editSession = fixturesProject.buildEditSessionForPath('sample.js', autoIndent: false)
buffer = editSession.buffer
lineLengths = buffer.getLines().map (line) -> line.length
@@ -523,6 +523,47 @@ describe "EditSession", ->
editSession.selectWord()
expect(editSession.getSelectedText()).toBe ''
describe ".setSelectedBufferRanges(ranges)", ->
it "clears existing selections and creates selections for each of the given ranges", ->
editSession.setSelectedBufferRanges([[[2, 2], [3, 3]], [[4, 4], [5, 5]]])
expect(editSession.getSelectedBufferRanges()).toEqual [[[2, 2], [3, 3]], [[4, 4], [5, 5]]]
editSession.setSelectedBufferRanges([[[5, 5], [6, 6]]])
expect(editSession.getSelectedBufferRanges()).toEqual [[[5, 5], [6, 6]]]
it "merges intersecting selections", ->
editSession.setSelectedBufferRanges([[[2, 2], [3, 3]], [[3, 0], [5, 5]]])
expect(editSession.getSelectedBufferRanges()).toEqual [[[2, 2], [5, 5]]]
it "recyles existing selection instances", ->
selection = editSession.getSelection()
editSession.setSelectedBufferRanges([[[2, 2], [3, 3]], [[4, 4], [5, 5]]])
[selection1, selection2] = editSession.getSelections()
expect(selection1).toBe selection
expect(selection1.getBufferRange()).toEqual [[2, 2], [3, 3]]
describe "when the preserveFolds option is false (the default)", ->
it "removes folds that contain the selections", ->
editSession.setSelectedBufferRange([[0,0], [0,0]])
editSession.createFold(1, 4)
editSession.createFold(2, 3)
editSession.createFold(6, 8)
editSession.createFold(10, 11)
editSession.setSelectedBufferRanges([[[2, 2], [3, 3]], [[6, 6], [7, 7]]])
expect(editSession.lineForScreenRow(1).fold).toBeUndefined()
expect(editSession.lineForScreenRow(2).fold).toBeUndefined()
expect(editSession.lineForScreenRow(6).fold).toBeUndefined()
expect(editSession.lineForScreenRow(10).fold).toBeDefined()
describe "when the preserve folds option is true", ->
it "does not remove folds that contain the selections", ->
editSession.setSelectedBufferRange([[0,0], [0,0]])
editSession.createFold(1, 4)
editSession.setSelectedBufferRanges([[[2, 2], [3, 3]]], preserveFolds: true)
expect(editSession.lineForScreenRow(1).fold).toBeDefined()
describe "when the cursor is moved while there is a selection", ->
makeSelection = -> selection.setBufferRange [[1, 2], [1, 5]]
@@ -870,8 +911,8 @@ describe "EditSession", ->
describe "when the selection ends on a folded line", ->
it "destroys the fold", ->
editSession.toggleFoldAtBufferRow(4)
editSession.setSelectedBufferRange([[3,0], [4,0]])
editSession.toggleFoldAtBufferRow(4)
editSession.backspace()
expect(buffer.lineForRow(3)).toBe " return sort(left).concat(pivot).concat(sort(right));"
@@ -955,12 +996,12 @@ describe "EditSession", ->
describe "when the cursor is on a folded line", ->
it "removes the lines contained by the fold", ->
editSession.setSelectedBufferRange([[2, 0], [2, 0]])
editSession.createFold(2,4)
editSession.createFold(2,6)
oldLine7 = buffer.lineForRow(7)
oldLine8 = buffer.lineForRow(8)
editSession.setSelectedBufferRange([[2, 0], [2, 0]])
editSession.delete()
expect(editSession.lineForScreenRow(2).text).toBe oldLine7
expect(editSession.lineForScreenRow(3).text).toBe oldLine8
@@ -1265,23 +1306,20 @@ describe "EditSession", ->
selections = editSession.getSelections()
expect(buffer.lineForRow(1)).toBe ' var = function( {'
expect(selections[0].getBufferRange()).toEqual [[1, 6], [1, 6]]
expect(selections[1].getBufferRange()).toEqual [[1, 17], [1, 17]]
expect(editSession.getSelectedBufferRanges()).toEqual [[[1, 6], [1, 6]], [[1, 17], [1, 17]]]
editSession.undo()
expect(selections[0].getBufferRange()).toEqual [[1, 6], [1, 6]]
expect(selections[1].getBufferRange()).toEqual [[1, 18], [1, 18]]
expect(editSession.getSelectedBufferRanges()).toEqual [[[1, 6], [1, 6]], [[1, 18], [1, 18]]]
editSession.undo()
expect(selections[0].getBufferRange()).toEqual [[1, 6], [1, 10]]
expect(selections[1].getBufferRange()).toEqual [[1, 22], [1, 27]]
expect(editSession.getSelectedBufferRanges()).toEqual [[[1, 6], [1, 10]], [[1, 22], [1, 27]]]
editSession.redo()
expect(selections[0].getBufferRange()).toEqual [[1, 6], [1, 6]]
expect(selections[1].getBufferRange()).toEqual [[1, 18], [1, 18]]
expect(editSession.getSelectedBufferRanges()).toEqual [[[1, 6], [1, 6]], [[1, 18], [1, 18]]]
it "restores selected ranges even when the change occurred in another edit session", ->
otherEditSession = fixturesProject.open(editSession.getPath())
otherEditSession = fixturesProject.buildEditSessionForPath(editSession.getPath())
otherEditSession.setSelectedBufferRange([[2, 2], [3, 3]])
otherEditSession.delete()
@@ -1324,14 +1362,12 @@ describe "EditSession", ->
[cursor1, cursor2, cursor3] = editSession.getCursors()
expect(editSession.getCursors().length).toBe 3
editSession.backspace()
buffer.delete([[0, 0], [0, 1]])
expect(editSession.getCursors().length).toBe 2
expect(editSession.getCursors()).toEqual [cursor1, cursor3]
expect(cursor1.getBufferPosition()).toEqual [0,0]
expect(cursor3.getBufferPosition()).toEqual [1,0]
editSession.insertText "x"
expect(editSession.lineForBufferRow(0)).toBe "xar quicksort = function () {"
expect(editSession.lineForBufferRow(1)).toBe "x var sort = function(items) {"
expect(cursor3.getBufferPosition()).toEqual [1,1]
describe "folding", ->
describe "structural folding", ->

View File

@@ -14,7 +14,7 @@ describe "Editor", ->
getLineHeight = ->
return cachedLineHeight if cachedLineHeight?
editorForMeasurement = new Editor(editSession: rootView.project.open('sample.js'))
editorForMeasurement = new Editor(editSession: rootView.project.buildEditSessionForPath('sample.js'))
editorForMeasurement.attachToDom()
cachedLineHeight = editorForMeasurement.lineHeight
editorForMeasurement.remove()
@@ -49,7 +49,7 @@ describe "Editor", ->
rootView.height(8 * editor.lineHeight)
rootView.width(50 * editor.charWidth)
editor.edit(rootView.project.open('two-hundred.txt'))
editor.edit(rootView.project.buildEditSessionForPath('two-hundred.txt'))
editor.setCursorScreenPosition([5, 1])
editor.scrollTop(1.5 * editor.lineHeight)
editor.scrollView.scrollLeft(44)
@@ -113,7 +113,7 @@ describe "Editor", ->
describe ".remove()", ->
it "removes subscriptions from all edit session buffers", ->
previousEditSession = editor.activeEditSession
otherEditSession = rootView.project.open(rootView.project.resolve('sample.txt'))
otherEditSession = rootView.project.buildEditSessionForPath(rootView.project.resolve('sample.txt'))
expect(previousEditSession.buffer.subscriptionCount()).toBeGreaterThan 1
editor.edit(otherEditSession)
@@ -125,7 +125,7 @@ describe "Editor", ->
describe "when 'close' is triggered", ->
it "closes active edit session and loads next edit session", ->
editor.edit(rootView.project.open())
editor.edit(rootView.project.buildEditSessionForPath())
editSession = editor.activeEditSession
spyOn(editSession, 'destroy').andCallThrough()
spyOn(editor, "remove").andCallThrough()
@@ -161,7 +161,7 @@ describe "Editor", ->
otherEditSession = null
beforeEach ->
otherEditSession = rootView.project.open()
otherEditSession = rootView.project.buildEditSessionForPath()
describe "when the edit session wasn't previously assigned to this editor", ->
it "adds edit session to editor", ->
@@ -197,10 +197,10 @@ describe "Editor", ->
beforeEach ->
session0 = editor.activeEditSession
editor.edit(rootView.project.open('sample.txt'))
editor.edit(rootView.project.buildEditSessionForPath('sample.txt'))
session1 = editor.activeEditSession
editor.edit(rootView.project.open('two-hundred.txt'))
editor.edit(rootView.project.buildEditSessionForPath('two-hundred.txt'))
session2 = editor.activeEditSession
describe ".setActiveEditSessionIndex(index)", ->
@@ -277,7 +277,7 @@ describe "Editor", ->
describe "when the current buffer has no path", ->
selectedFilePath = null
beforeEach ->
editor.edit(rootView.project.open())
editor.edit(rootView.project.buildEditSessionForPath())
expect(editor.getPath()).toBeUndefined()
editor.getBuffer().setText 'Save me to a new path'
@@ -357,7 +357,7 @@ describe "Editor", ->
spyOn(editor, 'pane').andReturn(fakePane)
it "calls the corresponding split method on the containing pane with a new editor containing a copy of the active edit session", ->
editor.edit project.open("sample.txt")
editor.edit project.buildEditSessionForPath("sample.txt")
editor.splitUp()
expect(fakePane.splitUp).toHaveBeenCalled()
[newEditor] = fakePane.splitUp.argsForCall[0]
@@ -404,7 +404,7 @@ describe "Editor", ->
it "emits event when editor receives a new buffer", ->
eventHandler = jasmine.createSpy('eventHandler')
editor.on 'editor-path-change', eventHandler
editor.edit(rootView.project.open(path))
editor.edit(rootView.project.buildEditSessionForPath(path))
expect(eventHandler).toHaveBeenCalled()
it "stops listening to events on previously set buffers", ->
@@ -412,7 +412,7 @@ describe "Editor", ->
oldBuffer = editor.getBuffer()
editor.on 'editor-path-change', eventHandler
editor.edit(rootView.project.open(path))
editor.edit(rootView.project.buildEditSessionForPath(path))
expect(eventHandler).toHaveBeenCalled()
eventHandler.reset()
@@ -1010,7 +1010,7 @@ describe "Editor", ->
expect(editor.bufferPositionForScreenPosition(editor.getCursorScreenPosition())).toEqual [3, 60]
it "does not wrap the lines of any newly assigned buffers", ->
otherEditSession = rootView.project.open()
otherEditSession = rootView.project.buildEditSessionForPath()
otherEditSession.buffer.setText([1..100].join(''))
editor.edit(otherEditSession)
expect(editor.renderedLines.find('.line').length).toBe(1)
@@ -1046,7 +1046,7 @@ describe "Editor", ->
expect(editor.getCursorScreenPosition()).toEqual [11, 0]
it "calls .setSoftWrapColumn() when the editor is attached because now its dimensions are available to calculate it", ->
otherEditor = new Editor(editSession: rootView.project.open('sample.js'))
otherEditor = new Editor(editSession: rootView.project.buildEditSessionForPath('sample.js'))
spyOn(otherEditor, 'setSoftWrapColumn')
otherEditor.setSoftWrap(true)
@@ -1344,7 +1344,7 @@ describe "Editor", ->
describe "when autoscrolling at the end of the document", ->
it "renders lines properly", ->
editor.edit(rootView.project.open('two-hundred.txt'))
editor.edit(rootView.project.buildEditSessionForPath('two-hundred.txt'))
editor.attachToDom(heightInLines: 5.5)
expect(editor.renderedLines.find('.line').length).toBe 8
@@ -1510,7 +1510,7 @@ describe "Editor", ->
describe "folding", ->
beforeEach ->
editSession = rootView.project.open('two-hundred.txt')
editSession = rootView.project.buildEditSessionForPath('two-hundred.txt')
buffer = editSession.buffer
editor.edit(editSession)
editor.attachToDom()
@@ -1557,13 +1557,13 @@ describe "Editor", ->
it "adds/removes the 'selected' class to the fold's line element and hides the cursor if it is on the fold line", ->
editor.createFold(2, 4)
editor.setSelectedBufferRange([[1, 0], [2, 0]], reverse: true)
editor.setSelectedBufferRange([[1, 0], [2, 0]], preserveFolds: true, reverse: true)
expect(editor.lineElementForScreenRow(2)).toMatchSelector('.fold.selected')
editor.setSelectedBufferRange([[1, 0], [1, 1]])
editor.setSelectedBufferRange([[1, 0], [1, 1]], preserveFolds: true)
expect(editor.lineElementForScreenRow(2)).not.toMatchSelector('.fold.selected')
editor.setSelectedBufferRange([[1, 0], [5, 0]])
editor.setSelectedBufferRange([[1, 0], [5, 0]], preserveFolds: true)
expect(editor.lineElementForScreenRow(2)).toMatchSelector('.fold.selected')
editor.setCursorScreenPosition([3,0])
@@ -1582,7 +1582,7 @@ describe "Editor", ->
editor.renderLines() # re-render lines so certain lines are not rendered
editor.createFold(2, 4)
editor.setSelectedBufferRange([[1, 0], [5, 0]])
editor.setSelectedBufferRange([[1, 0], [5, 0]], preserveFolds: true)
expect(editor.renderedLines.find('.fold.selected')).toExist()
editor.scrollToBottom()
@@ -1593,8 +1593,8 @@ describe "Editor", ->
describe ".getOpenBufferPaths()", ->
it "returns the paths of all non-anonymous buffers with edit sessions on this editor", ->
editor.edit(project.open('sample.txt'))
editor.edit(project.open('two-hundred.txt'))
editor.edit(project.open())
editor.edit(project.buildEditSessionForPath('sample.txt'))
editor.edit(project.buildEditSessionForPath('two-hundred.txt'))
editor.edit(project.buildEditSessionForPath())
paths = editor.getOpenBufferPaths().map (path) -> project.relativize(path)
expect(paths).toEqual = ['sample.js', 'sample.txt', 'two-hundred.txt']

View File

@@ -11,23 +11,19 @@ describe "Project", ->
describe "when editSession is destroyed", ->
it "removes edit session and calls destroy on buffer (if buffer is not referenced by other edit sessions)", ->
editSession = project.open("a")
anotherEditSession = project.open("a")
buffer = editSession.buffer
spyOn(buffer, 'destroy').andCallThrough()
editSession = project.buildEditSessionForPath("a")
anotherEditSession = project.buildEditSessionForPath("a")
expect(project.editSessions.length).toBe 2
expect(editSession.buffer).toBe anotherEditSession.buffer
editSession.destroy()
expect(buffer.destroy).not.toHaveBeenCalled()
expect(project.editSessions.length).toBe 1
anotherEditSession.destroy()
expect(buffer.destroy).toHaveBeenCalled()
expect(project.editSessions.length).toBe 0
describe ".open(path)", ->
describe ".buildEditSessionForPath(path)", ->
[absolutePath, newBufferHandler, newEditSessionHandler] = []
beforeEach ->
absolutePath = require.resolve('fixtures/dir/a')
@@ -38,34 +34,48 @@ describe "Project", ->
describe "when given an absolute path that hasn't been opened previously", ->
it "returns a new edit session for the given path and emits 'new-buffer' and 'new-edit-session' events", ->
editSession = project.open(absolutePath)
editSession = project.buildEditSessionForPath(absolutePath)
expect(editSession.buffer.getPath()).toBe absolutePath
expect(newBufferHandler).toHaveBeenCalledWith editSession.buffer
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
describe "when given a relative path that hasn't been opened previously", ->
it "returns a new edit session for the given path (relative to the project root) and emits 'new-buffer' and 'new-edit-session' events", ->
editSession = project.open('a')
editSession = project.buildEditSessionForPath('a')
expect(editSession.buffer.getPath()).toBe absolutePath
expect(newBufferHandler).toHaveBeenCalledWith editSession.buffer
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
describe "when passed the path to a buffer that has already been opened", ->
it "returns a new edit session containing previously opened buffer and emits a 'new-edit-session' event", ->
editSession = project.open(absolutePath)
editSession = project.buildEditSessionForPath(absolutePath)
newBufferHandler.reset()
expect(project.open(absolutePath).buffer).toBe editSession.buffer
expect(project.open('a').buffer).toBe editSession.buffer
expect(project.buildEditSessionForPath(absolutePath).buffer).toBe editSession.buffer
expect(project.buildEditSessionForPath('a').buffer).toBe editSession.buffer
expect(newBufferHandler).not.toHaveBeenCalled()
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
describe "when not passed a path", ->
it "returns a new edit session and emits 'new-buffer' and 'new-edit-session' events", ->
editSession = project.open()
editSession = project.buildEditSessionForPath()
expect(editSession.buffer.getPath()).toBeUndefined()
expect(newBufferHandler).toHaveBeenCalledWith(editSession.buffer)
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
describe ".bufferForPath(path)", ->
describe "when opening a previously opened path", ->
it "does not create a new buffer", ->
buffer = project.bufferForPath("a").retain()
expect(project.bufferForPath("a")).toBe buffer
alternativeBuffer = project.bufferForPath("b").retain().release()
expect(alternativeBuffer).not.toBe buffer
buffer.release()
it "creates a new buffer if the previous buffer was destroyed", ->
buffer = project.bufferForPath("a").retain().release()
expect(project.bufferForPath("a").retain().release()).not.toBe buffer
describe ".resolve(path)", ->
it "returns an absolute path based on the project's root", ->
absolutePath = require.resolve('fixtures/dir/a')
@@ -106,3 +116,60 @@ describe "Project", ->
project.getFilePaths().done (paths) ->
expect(paths).not.toContain('a')
expect(paths).toContain('b')
describe ".scan(options, callback)", ->
describe "when called with a regex", ->
it "calls the callback with all regex matches in all files in the project", ->
matches = []
waitsForPromise ->
project.scan /(a)+/, ({path, match, range}) ->
matches.push({path, match, range})
runs ->
expect(matches[0]).toEqual
path: project.resolve('a')
match: 'aaa'
range: [[0, 0], [0, 3]]
expect(matches[1]).toEqual
path: project.resolve('a')
match: 'aa'
range: [[1, 3], [1, 5]]
it "works on evil filenames", ->
project.setPath(require.resolve('fixtures/evil-files'))
paths = []
matches = []
waitsForPromise ->
project.scan /evil/, ({path, match, range}) ->
paths.push(path)
matches.push(match)
runs ->
expect(paths.length).toBe 5
matches.forEach (match) -> expect(match).toEqual 'evil'
expect(paths[0]).toMatch /a_file_with_utf8.txt$/
expect(paths[1]).toMatch /file with spaces.txt$/
expect(paths[2]).toMatch /goddam\nnewlines$/m
expect(paths[3]).toMatch /quote".txt$/m
expect(fs.base(paths[4])).toBe "utfa\u0306.md"
it "handles breaks in the search subprocess's output following the filename", ->
spyOn $native, 'exec'
iterator = jasmine.createSpy('iterator')
project.scan /a+/, iterator
stdout = $native.exec.argsForCall[0][1].stdout
stdout ":#{require.resolve('fixtures/dir/a')}\n"
stdout "1;0 3:aaa bbb\n2;3 2:cc aa cc\n"
expect(iterator.argsForCall[0][0]).toEqual
path: project.resolve('a')
match: 'aaa'
range: [[0, 0], [0, 3]]
expect(iterator.argsForCall[1][0]).toEqual
path: project.resolve('a')
match: 'aa'
range: [[1, 3], [1, 5]]

View File

@@ -75,10 +75,10 @@ describe "RootView", ->
editor2 = editor1.splitRight()
editor3 = editor2.splitRight()
editor4 = editor2.splitDown()
editor2.edit(rootView.project.open('dir/b'))
editor3.edit(rootView.project.open('sample.js'))
editor2.edit(rootView.project.buildEditSessionForPath('dir/b'))
editor3.edit(rootView.project.buildEditSessionForPath('sample.js'))
editor3.setCursorScreenPosition([2, 3])
editor4.edit(rootView.project.open('sample.txt'))
editor4.edit(rootView.project.buildEditSessionForPath('sample.txt'))
editor4.setCursorScreenPosition([0, 2])
rootView.attachToDom()
editor2.focus()
@@ -455,7 +455,7 @@ describe "RootView", ->
editor2 = rootView.getActiveEditor().splitLeft()
path = rootView.project.resolve('b')
editor2.edit(rootView.project.open(path))
editor2.edit(rootView.project.buildEditSessionForPath(path))
expect(pathChangeHandler).toHaveBeenCalled()
expect(document.title).toBe rootView.project.resolve(path)
@@ -491,7 +491,7 @@ describe "RootView", ->
rootView.focus()
expect(pathChangeHandler).not.toHaveBeenCalled()
editor2.edit(editor1.activeEditSession)
editor2.edit(editor1.activeEditSession.copy())
editor2.focus()
expect(pathChangeHandler).not.toHaveBeenCalled()
@@ -526,16 +526,18 @@ describe "RootView", ->
expect(rootView.getActiveEditor()).toBeUndefined()
describe "when called with no path", ->
it "opens an empty buffer in a new editor", ->
rootView.open()
it "opens / returns an edit session for an empty buffer in a new editor", ->
editSession = rootView.open()
expect(rootView.getActiveEditor()).toBeDefined()
expect(rootView.getActiveEditor().getPath()).toBeUndefined()
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
describe "when called with a path", ->
it "opens a buffer with the given path in a new editor", ->
rootView.open('b')
editSession = rootView.open('b')
expect(rootView.getActiveEditor()).toBeDefined()
expect(rootView.getActiveEditor().getPath()).toBe require.resolve('fixtures/dir/b')
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
describe "when there is an active editor", ->
beforeEach ->
@@ -543,8 +545,9 @@ describe "RootView", ->
describe "when called with no path", ->
it "opens an empty buffer in the active editor", ->
rootView.open()
editSession = rootView.open()
expect(rootView.getActiveEditor().getPath()).toBeUndefined()
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
describe "when called with a path", ->
[editor1, editor2] = []
@@ -566,16 +569,19 @@ describe "RootView", ->
expect(activeEditor.getPath()).toBe require.resolve('fixtures/dir/a')
previousEditSession = activeEditor.activeEditSession
rootView.open('b')
editSession = rootView.open('b')
expect(activeEditor.activeEditSession).not.toBe previousEditSession
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
rootView.open('a')
editSession = rootView.open('a')
expect(activeEditor.activeEditSession).toBe previousEditSession
expect(editSession).toBe previousEditSession
describe "when the active editor does not have an edit session for the given path", ->
it "creates a new edit session for the given path in the active editor", ->
rootView.open('b')
editSession = rootView.open('b')
expect(activeEditor.editSessions.length).toBe 2
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
describe "when the 'allowActiveEditorChange' option is true", ->
describe "when the active editor has an edit session for the given path", ->
@@ -584,23 +590,27 @@ describe "RootView", ->
expect(activeEditor.getPath()).toBe require.resolve('fixtures/dir/a')
previousEditSession = activeEditor.activeEditSession
rootView.open('b')
editSession = rootView.open('b')
expect(activeEditor.activeEditSession).not.toBe previousEditSession
expect(editSession).toBe activeEditor.activeEditSession
rootView.open('a', allowActiveEditorChange: true)
editSession = rootView.open('a', allowActiveEditorChange: true)
expect(activeEditor.activeEditSession).toBe previousEditSession
expect(editSession).toBe activeEditor.activeEditSession
describe "when the active editor does *not* have an edit session for the given path", ->
describe "when another editor has an edit session for the path", ->
it "focuses the other editor and activates its edit session for the path", ->
expect(rootView.getActiveEditor()).toBe editor1
rootView.open('b', allowActiveEditorChange: true)
editSession = rootView.open('b', allowActiveEditorChange: true)
expect(rootView.getActiveEditor()).toBe editor2
expect(editor2.getPath()).toBe require.resolve('fixtures/dir/b')
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
describe "when no other editor has an edit session for the path either", ->
it "creates a new edit session for the path on the current active editor", ->
path = require.resolve('fixtures/sample.js')
rootView.open(path, allowActiveEditorChange: true)
editSession = rootView.open(path, allowActiveEditorChange: true)
expect(rootView.getActiveEditor()).toBe editor1
expect(editor1.getPath()).toBe path
expect(editSession).toBe rootView.getActiveEditor().activeEditSession

View File

@@ -66,3 +66,12 @@ describe "Selection", ->
buffer.insert([2, 5], 'abc')
expect(changeScreenRangeHandler).toHaveBeenCalled()
describe "when the selection is destroyed", ->
it "destroys its cursor and its anchor's cursor", ->
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

View File

@@ -10,12 +10,12 @@ describe "Autocomplete", ->
miniEditor = null
beforeEach ->
editor = new Editor(editSession: fixturesProject.open('sample.js'))
editor = new Editor(editSession: fixturesProject.buildEditSessionForPath('sample.js'))
autocomplete = new Autocomplete(editor)
miniEditor = autocomplete.miniEditor
afterEach ->
editor.remove()
editor?.remove()
describe "@activate(rootView)", ->
it "activates autocomplete on all existing and future editors (but not on autocomplete's own mini editor)", ->
@@ -38,8 +38,7 @@ describe "Autocomplete", ->
expect(Autocomplete.prototype.initialize).not.toHaveBeenCalled()
leftEditor.remove()
rightEditor.remove()
rootView.deactivate()
describe 'autocomplete:attach event', ->
it "shows autocomplete view and focuses its mini-editor", ->
@@ -357,7 +356,7 @@ describe "Autocomplete", ->
expect(wordList).toContain "quicksort"
expect(wordList).not.toContain "Some"
editor.edit(fixturesProject.open('sample.txt'))
editor.edit(fixturesProject.buildEditSessionForPath('sample.txt'))
wordList = autocomplete.wordList
expect(wordList).not.toContain "quicksort"
@@ -365,7 +364,7 @@ describe "Autocomplete", ->
it 'stops listening to previous buffers change events', ->
previousBuffer = editor.getBuffer()
editor.edit(fixturesProject.open('sample.txt'))
editor.edit(fixturesProject.buildEditSessionForPath('sample.txt'))
spyOn(autocomplete, "buildWordList")
previousBuffer.change([[0,0],[0,1]], "sauron")
@@ -382,6 +381,7 @@ describe "Autocomplete", ->
editor.remove()
editor.getBuffer().insert([0,0], "s")
expect(autocomplete.buildWordList).not.toHaveBeenCalled()
editor = null
describe ".attach()", ->
beforeEach ->

View File

@@ -1,258 +1,329 @@
CommandInterpreter = require 'command-panel/command-interpreter'
Project = require 'project'
Buffer = require 'buffer'
EditSession = require 'edit-session'
Editor = require 'editor'
describe "CommandInterpreter", ->
[interpreter, editor, buffer] = []
[project, interpreter, editSession, buffer, anchorCountBefore] = []
beforeEach ->
editSession = fixturesProject.open('sample.js')
project = new Project(fixturesProject.resolve('dir/'))
interpreter = new CommandInterpreter(fixturesProject)
editSession = fixturesProject.buildEditSessionForPath('sample.js')
buffer = editSession.buffer
editor = new Editor(editSession: editSession)
interpreter = new CommandInterpreter()
afterEach ->
editor.remove()
editSession?.destroy()
expect(buffer.getAnchors().length).toBe 0
describe "addresses", ->
beforeEach ->
editor.addSelectionForBufferRange([[7,0], [7,11]])
editor.addSelectionForBufferRange([[8,0], [8,11]])
editSession.addSelectionForBufferRange([[7,0], [7,11]])
editSession.addSelectionForBufferRange([[8,0], [8,11]])
describe "a line address", ->
it "selects the specified line", ->
interpreter.eval(editor, '4')
expect(editor.getSelections().length).toBe 1
expect(editor.getSelection().getBufferRange()).toEqual [[3, 0], [4, 0]]
waitsForPromise -> interpreter.eval('4', editSession)
runs ->
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[3, 0], [4, 0]]
describe "0", ->
it "selects the zero-length string at the start of the file", ->
interpreter.eval(editor, '0')
expect(editor.getSelections().length).toBe 1
expect(editor.getSelection().getBufferRange()).toEqual [[0,0], [0,0]]
waitsForPromise -> interpreter.eval('0', editSession)
runs ->
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[0,0], [0,0]]
interpreter.eval(editor, '0,1')
expect(editor.getSelections().length).toBe 1
expect(editor.getSelection().getBufferRange()).toEqual [[0,0], [1,0]]
interpreter.eval('0,1', editSession)
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[0,0], [1,0]]
describe "$", ->
it "selects EOF", ->
interpreter.eval(editor, '$')
expect(editor.getSelections().length).toBe 1
expect(editor.getSelection().getBufferRange()).toEqual [[12,2], [12,2]]
waitsForPromise -> interpreter.eval('$', editSession)
runs ->
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[12,2], [12,2]]
interpreter.eval(editor, '1,$')
expect(editor.getSelections().length).toBe 1
expect(editor.getSelection().getBufferRange()).toEqual [[0,0], [12,2]]
waitsForPromise -> interpreter.eval('1,$', editSession)
runs ->
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[0,0], [12,2]]
describe ".", ->
describe "when a single selection", ->
it 'maintains the current selection', ->
editor.clearSelections()
editor.setSelectedBufferRange([[1,1], [2,2]])
interpreter.eval(editor, '.')
expect(editor.getSelection().getBufferRange()).toEqual [[1,1], [2,2]]
editSession.clearSelections()
editor.setSelectedBufferRange([[1,1], [2,2]])
interpreter.eval(editor, '.,')
expect(editor.getSelection().getBufferRange()).toEqual [[1,1], [12,2]]
waitsForPromise ->
editSession.setSelectedBufferRange([[1,1], [2,2]])
interpreter.eval('.', editSession)
editor.setSelectedBufferRange([[1,1], [2,2]])
interpreter.eval(editor, ',.')
expect(editor.getSelection().getBufferRange()).toEqual [[0,0], [2,2]]
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[1,1], [2,2]]
waitsForPromise ->
editSession.setSelectedBufferRange([[1,1], [2,2]])
interpreter.eval('.,', editSession)
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[1,1], [12,2]]
waitsForPromise ->
editSession.setSelectedBufferRange([[1,1], [2,2]])
interpreter.eval(',.', editSession)
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[0,0], [2,2]]
describe "with multiple selections", ->
it "maintains the current selections", ->
preSelections = editor.getSelections()
preSelections = editSession.getSelections()
expect(preSelections.length).toBe 3
[preRange1, preRange2, preRange3] = preSelections.map (s) -> s.getScreenRange()
interpreter.eval(editor, '.')
waitsForPromise -> interpreter.eval('.', editSession)
selections = editor.getSelections()
expect(selections.length).toBe 3
[selection1, selection2, selection3] = selections
expect(selection1.getScreenRange()).toEqual preRange1
expect(selection2.getScreenRange()).toEqual preRange2
expect(selection3.getScreenRange()).toEqual preRange3
runs ->
selections = editSession.getSelections()
expect(selections.length).toBe 3
[selection1, selection2, selection3] = selections
expect(selection1.getScreenRange()).toEqual preRange1
expect(selection2.getScreenRange()).toEqual preRange2
expect(selection3.getScreenRange()).toEqual preRange3
describe "/regex/", ->
beforeEach ->
editor.clearSelections()
editSession.clearSelections()
it 'selects text matching regex after current selection', ->
editor.setSelectedBufferRange([[4,16], [4,20]])
interpreter.eval(editor, '/pivot/')
expect(editor.getSelection().getBufferRange()).toEqual [[6,16], [6,21]]
waitsForPromise ->
editSession.setSelectedBufferRange([[4,16], [4,20]])
interpreter.eval('/pivot/', editSession)
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[6,16], [6,21]]
it 'does not require the trailing slash', ->
editor.setSelectedBufferRange([[4,16], [4,20]])
interpreter.eval(editor, '/pivot')
expect(editor.getSelection().getBufferRange()).toEqual [[6,16], [6,21]]
waitsForPromise ->
editSession.setSelectedBufferRange([[4,16], [4,20]])
interpreter.eval('/pivot', editSession)
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[6,16], [6,21]]
it "searches from the end of each selection in the buffer", ->
editor.clearSelections()
editor.setSelectedBufferRange([[4,16], [4,20]])
editor.addSelectionForBufferRange([[1,16], [2,20]])
expect(editor.getSelections().length).toBe 2
interpreter.eval(editor, '/pivot')
selections = editor.getSelections()
expect(selections.length).toBe 2
expect(selections[0].getBufferRange()).toEqual [[3,8], [3,13]]
expect(selections[1].getBufferRange()).toEqual [[6,16], [6,21]]
waitsForPromise ->
editSession.clearSelections()
editSession.setSelectedBufferRange([[4,16], [4,20]])
editSession.addSelectionForBufferRange([[1,16], [2,20]])
expect(editSession.getSelections().length).toBe 2
interpreter.eval('/pivot', editSession)
runs ->
selections = editSession.getSelections()
expect(selections.length).toBe 2
expect(selections[0].getBufferRange()).toEqual [[3,8], [3,13]]
expect(selections[1].getBufferRange()).toEqual [[6,16], [6,21]]
it "wraps around to the beginning of the buffer, but doesn't infinitely loop if no matches are found", ->
editor.setSelectedBufferRange([[10, 0], [10,3]])
interpreter.eval(editor, '/pivot')
expect(editor.getSelection().getBufferRange()).toEqual [[3,8], [3,13]]
waitsForPromise ->
editSession.setSelectedBufferRange([[10, 0], [10,3]])
interpreter.eval('/pivot', editSession)
interpreter.eval(editor, '/mike tyson')
expect(editor.getSelection().getBufferRange()).toEqual [[3,8], [3,13]]
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[3,8], [3,13]]
waitsForPromise ->
interpreter.eval('/mike tyson', editSession)
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[3,8], [3,13]]
it "searches in reverse when prefixed with a -", ->
editor.setSelectedBufferRange([[6, 16], [6, 22]])
interpreter.eval(editor, '-/pivot')
expect(editor.getSelection().getBufferRange()).toEqual [[3,8], [3,13]]
waitsForPromise ->
editSession.setSelectedBufferRange([[6, 16], [6, 22]])
interpreter.eval('-/pivot', editSession)
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[3,8], [3,13]]
describe "address range", ->
describe "when two addresses are specified", ->
it "selects from the begining of the left address to the end of the right address", ->
interpreter.eval(editor, '4,7')
expect(editor.getSelections().length).toBe 1
expect(editor.getSelection().getBufferRange()).toEqual [[3, 0], [7, 0]]
waitsForPromise -> interpreter.eval('4,7', editSession)
runs ->
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[3, 0], [7, 0]]
describe "when the left address is unspecified", ->
it "selects from the begining of buffer to the end of the right address", ->
interpreter.eval(editor, ',7')
expect(editor.getSelections().length).toBe 1
expect(editor.getSelection().getBufferRange()).toEqual [[0, 0], [7, 0]]
waitsForPromise -> interpreter.eval(',7', editSession)
runs ->
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[0, 0], [7, 0]]
describe "when the right address is unspecified", ->
it "selects from the begining of left address to the end file", ->
interpreter.eval(editor, '4,')
expect(editor.getSelections().length).toBe 1
expect(editor.getSelection().getBufferRange()).toEqual [[3, 0], [12, 2]]
waitsForPromise -> interpreter.eval('4,', editSession)
runs ->
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[3, 0], [12, 2]]
describe "when the neither address is specified", ->
it "selects the entire file", ->
interpreter.eval(editor, ',')
expect(editor.getSelections().length).toBe 1
expect(editor.getSelection().getBufferRange()).toEqual [[0, 0], [12, 2]]
waitsForPromise -> interpreter.eval(',', editSession)
runs ->
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[0, 0], [12, 2]]
describe "x/regex/", ->
it "sets the current selection to every match of the regex in the current selection", ->
interpreter.eval(editor, '6,7 x/current/')
waitsForPromise -> interpreter.eval('6,7 x/current/', editSession)
selections = editor.getSelections()
expect(selections.length).toBe 4
runs ->
selections = editSession.getSelections()
expect(selections.length).toBe 4
expect(selections[0].getBufferRange()).toEqual [[5,6], [5,13]]
expect(selections[1].getBufferRange()).toEqual [[6,6], [6,13]]
expect(selections[2].getBufferRange()).toEqual [[6,34], [6,41]]
expect(selections[3].getBufferRange()).toEqual [[6,56], [6,63]]
expect(selections[0].getBufferRange()).toEqual [[5,6], [5,13]]
expect(selections[1].getBufferRange()).toEqual [[6,6], [6,13]]
expect(selections[2].getBufferRange()).toEqual [[6,34], [6,41]]
expect(selections[3].getBufferRange()).toEqual [[6,56], [6,63]]
describe "when matching /$/", ->
it "matches the end of each line in the selected region", ->
interpreter.eval(editor, '6,8 x/$/')
waitsForPromise -> interpreter.eval('6,8 x/$/', editSession)
cursors = editor.getCursors()
expect(cursors.length).toBe 3
runs ->
cursors = editSession.getCursors()
expect(cursors.length).toBe 3
expect(cursors[0].getBufferPosition()).toEqual [5, 30]
expect(cursors[1].getBufferPosition()).toEqual [6, 65]
expect(cursors[2].getBufferPosition()).toEqual [7, 5]
expect(cursors[0].getBufferPosition()).toEqual [5, 30]
expect(cursors[1].getBufferPosition()).toEqual [6, 65]
expect(cursors[2].getBufferPosition()).toEqual [7, 5]
it "loops through current selections and selects text matching the regex", ->
editor.setSelectedBufferRange [[3,0], [3,62]]
editor.addSelectionForBufferRange [[6,0], [6,65]]
describe "when text is initially selected", ->
it "loops through current selections and selects text matching the regex", ->
waitsForPromise ->
editSession.setSelectedBufferRange [[3,0], [3,62]]
editSession.addSelectionForBufferRange [[6,0], [6,65]]
interpreter.eval('x/current', editSession)
interpreter.eval(editor, 'x/current')
runs ->
selections = editSession.getSelections()
expect(selections.length).toBe 4
selections = editor.getSelections()
expect(selections.length).toBe 4
expect(selections[0].getBufferRange()).toEqual [[3,31], [3,38]]
expect(selections[1].getBufferRange()).toEqual [[6,6], [6,13]]
expect(selections[2].getBufferRange()).toEqual [[6,34], [6,41]]
expect(selections[3].getBufferRange()).toEqual [[6,56], [6,63]]
expect(selections[0].getBufferRange()).toEqual [[3,31], [3,38]]
expect(selections[1].getBufferRange()).toEqual [[6,6], [6,13]]
expect(selections[2].getBufferRange()).toEqual [[6,34], [6,41]]
expect(selections[3].getBufferRange()).toEqual [[6,56], [6,63]]
describe "substitution", ->
it "does nothing if there are no matches", ->
editor.setSelectedBufferRange([[6, 0], [6, 44]])
interpreter.eval(editor, 's/not-in-text/foo/')
expect(buffer.lineForRow(6)).toBe ' current < pivot ? left.push(current) : right.push(current);'
waitsForPromise ->
editSession.setSelectedBufferRange([[6, 0], [6, 44]])
interpreter.eval('s/not-in-text/foo/', editSession)
runs ->
expect(buffer.lineForRow(6)).toBe ' current < pivot ? left.push(current) : right.push(current);'
describe "when not global", ->
describe "when there is a single selection", ->
it "performs a single substitution within the current selection", ->
editor.setSelectedBufferRange([[6, 0], [6, 44]])
interpreter.eval(editor, 's/current/foo/')
expect(buffer.lineForRow(6)).toBe ' foo < pivot ? left.push(current) : right.push(current);'
waitsForPromise ->
editSession.setSelectedBufferRange([[6, 0], [6, 44]])
interpreter.eval('s/current/foo/', editSession)
runs ->
expect(buffer.lineForRow(6)).toBe ' foo < pivot ? left.push(current) : right.push(current);'
describe "when there are multiple selections", ->
it "performs a single substitutions within each of the selections", ->
editor.setSelectedBufferRange([[5, 0], [5, 20]])
editor.addSelectionForBufferRange([[6, 0], [6, 44]])
waitsForPromise ->
editSession.setSelectedBufferRange([[5, 0], [5, 20]])
editSession.addSelectionForBufferRange([[6, 0], [6, 44]])
interpreter.eval('s/current/foo/', editSession)
interpreter.eval(editor, 's/current/foo/')
expect(buffer.lineForRow(5)).toBe ' foo = items.shift();'
expect(buffer.lineForRow(6)).toBe ' foo < pivot ? left.push(current) : right.push(current);'
runs ->
expect(buffer.lineForRow(5)).toBe ' foo = items.shift();'
expect(buffer.lineForRow(6)).toBe ' foo < pivot ? left.push(current) : right.push(current);'
describe "when global", ->
it "performs a multiple substitutions within the current selection", ->
editor.setSelectedBufferRange([[6, 0], [6, 44]])
interpreter.eval(editor, 's/current/foo/g')
expect(buffer.lineForRow(6)).toBe ' foo < pivot ? left.push(foo) : right.push(current);'
waitsForPromise ->
editSession.setSelectedBufferRange([[6, 0], [6, 44]])
interpreter.eval('s/current/foo/g', editSession)
describe "when prefixed with an address", ->
it "only makes substitutions within given lines", ->
interpreter.eval(editor, '4,6s/ /!/g')
expect(buffer.lineForRow(2)).toBe ' if (items.length <= 1) return items;'
expect(buffer.lineForRow(3)).toBe '!!!!var!pivot!=!items.shift(),!current,!left!=![],!right!=![];'
expect(buffer.lineForRow(4)).toBe '!!!!while(items.length!>!0)!{'
expect(buffer.lineForRow(5)).toBe '!!!!!!current!=!items.shift();'
expect(buffer.lineForRow(6)).toBe ' current < pivot ? left.push(current) : right.push(current);'
describe "when matching $", ->
it "matches the end of each line and avoids infinitely looping on a zero-width match", ->
interpreter.eval(editor, ',s/$/!!!/g')
expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {!!!'
expect(buffer.lineForRow(2)).toBe ' if (items.length <= 1) return items;!!!'
expect(buffer.lineForRow(6)).toBe ' current < pivot ? left.push(current) : right.push(current);!!!'
expect(buffer.lineForRow(12)).toBe '};!!!'
describe "when matching ^", ->
it "matches the beginning of each line and avoids infinitely looping on a zero-width match", ->
interpreter.eval(editor, ',s/^/!!!/g')
expect(buffer.lineForRow(0)).toBe '!!!var quicksort = function () {'
expect(buffer.lineForRow(2)).toBe '!!! if (items.length <= 1) return items;'
expect(buffer.lineForRow(6)).toBe '!!! current < pivot ? left.push(current) : right.push(current);'
expect(buffer.lineForRow(12)).toBe '!!!};'
describe "when there are multiple selections", ->
it "performs a multiple substitutions within each of the selections", ->
editor.setSelectedBufferRange([[5, 0], [5, 20]])
editor.addSelectionForBufferRange([[6, 0], [6, 44]])
interpreter.eval(editor, 's/current/foo/g')
expect(buffer.lineForRow(5)).toBe ' foo = items.shift();'
runs ->
expect(buffer.lineForRow(6)).toBe ' foo < pivot ? left.push(foo) : right.push(current);'
describe "when prefixed with an address", ->
it "only makes substitutions within given lines", ->
waitsForPromise -> interpreter.eval('4,6s/ /!/g', editSession)
runs ->
expect(buffer.lineForRow(2)).toBe ' if (items.length <= 1) return items;'
expect(buffer.lineForRow(3)).toBe '!!!!var!pivot!=!items.shift(),!current,!left!=![],!right!=![];'
expect(buffer.lineForRow(4)).toBe '!!!!while(items.length!>!0)!{'
expect(buffer.lineForRow(5)).toBe '!!!!!!current!=!items.shift();'
expect(buffer.lineForRow(6)).toBe ' current < pivot ? left.push(current) : right.push(current);'
describe "when matching $", ->
it "matches the end of each line and avoids infinitely looping on a zero-width match", ->
waitsForPromise -> interpreter.eval(',s/$/!!!/g', editSession)
runs ->
expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {!!!'
expect(buffer.lineForRow(2)).toBe ' if (items.length <= 1) return items;!!!'
expect(buffer.lineForRow(6)).toBe ' current < pivot ? left.push(current) : right.push(current);!!!'
expect(buffer.lineForRow(12)).toBe '};!!!'
describe "when matching ^", ->
it "matches the beginning of each line and avoids infinitely looping on a zero-width match", ->
waitsForPromise -> interpreter.eval(',s/^/!!!/g', editSession)
runs ->
expect(buffer.lineForRow(0)).toBe '!!!var quicksort = function () {'
expect(buffer.lineForRow(2)).toBe '!!! if (items.length <= 1) return items;'
expect(buffer.lineForRow(6)).toBe '!!! current < pivot ? left.push(current) : right.push(current);'
expect(buffer.lineForRow(12)).toBe '!!!};'
describe "when there are multiple selections", ->
it "performs a multiple substitutions within each of the selections", ->
waitsForPromise ->
editSession.setSelectedBufferRange([[5, 0], [5, 20]])
editSession.addSelectionForBufferRange([[6, 0], [6, 44]])
interpreter.eval('s/current/foo/g', editSession)
runs ->
expect(buffer.lineForRow(5)).toBe ' foo = items.shift();'
expect(buffer.lineForRow(6)).toBe ' foo < pivot ? left.push(foo) : right.push(current);'
describe "when prefixed with an address", ->
it "restores the original selections upon completion if it is the last command", ->
editor.setSelectedBufferRanges([[[5, 0], [5, 20]], [[6, 0], [6, 44]]])
interpreter.eval(editor, ',s/current/foo/g')
expect(editor.getSelectedBufferRanges()).toEqual [[[5, 0], [5, 20]], [[6, 0], [6, 44]]]
waitsForPromise ->
editSession.setSelectedBufferRanges([[[5, 0], [5, 20]], [[6, 0], [6, 44]]])
interpreter.eval(',s/current/foo/g', editSession)
describe "when command selects folded text", ->
it "unfolds lines that command selects", ->
editor.createFold(1, 9)
editor.createFold(5, 8)
editor.setSelectedBufferRange([[0,0], [0,0]])
runs ->
expect(editSession.getSelectedBufferRanges()).toEqual [[[5, 0], [5, 16]], [[6, 0], [6, 36]]]
interpreter.eval(editor, '/push/')
expect(editor.getSelection().getBufferRange()).toEqual [[6,29], [6,33]]
expect(editor.lineForScreenRow(1).fold).toBeUndefined()
expect(editor.lineForScreenRow(5).fold).toBeUndefined()
expect(editor.lineForScreenRow(6).text).toBe buffer.lineForRow(6)
describe "X x/regex/", ->
it "returns selection operations for all regex matches in all the project's files", ->
editSession.destroy()
project = new Project(fixturesProject.resolve('dir/'))
interpreter = new CommandInterpreter(project)
operations = null
waitsForPromise ->
interpreter.eval("X x/a+/").done (ops) -> operations = ops
runs ->
expect(operations.length).toBeGreaterThan 3
for operation in operations
editSession = project.buildEditSessionForPath(operation.getPath())
operation.execute(editSession)
expect(editSession.getSelectedText()).toMatch /a+/
editSession.destroy()
operation.destroy()
editSession = null

View File

@@ -1,18 +1,21 @@
RootView = require 'root-view'
CommandPanel = require 'command-panel'
_ = require 'underscore'
describe "CommandPanel", ->
[rootView, editor, commandPanel] = []
[rootView, editor, buffer, commandPanel, project] = []
beforeEach ->
rootView = new RootView
rootView.open(require.resolve 'fixtures/sample.js')
rootView.enableKeymap()
project = rootView.project
editor = rootView.getActiveEditor()
buffer = editor.activeEditSession.buffer
commandPanel = requireExtension('command-panel')
afterEach ->
rootView.remove()
rootView.deactivate()
describe "serialization", ->
it "preserves the command panel's mini editor text and visibility across reloads", ->
@@ -26,6 +29,12 @@ describe "CommandPanel", ->
newRootView.remove()
describe "when command-panel:close is triggered on the command panel", ->
it "detaches the command panel", ->
commandPanel.attach()
commandPanel.trigger('command-panel:close')
expect(commandPanel.hasParent()).toBeFalsy()
describe "when command-panel:toggle is triggered on the root view", ->
beforeEach ->
rootView.attachToDom()
@@ -34,14 +43,14 @@ describe "CommandPanel", ->
beforeEach ->
commandPanel.attach()
describe "when the command panel is focused", ->
describe "when the mini editor is focused", ->
it "closes the command panel", ->
expect(commandPanel.miniEditor.hiddenInput).toMatchSelector ':focus'
rootView.trigger 'command-panel:toggle'
expect(commandPanel.hasParent()).toBeFalsy()
describe "when the command panel is not focused", ->
it "focuses the command panel", ->
describe "when the mini editor is not focused", ->
it "focuses the mini editor", ->
rootView.focus()
expect(commandPanel.miniEditor.hiddenInput).not.toMatchSelector ':focus'
rootView.trigger 'command-panel:toggle'
@@ -54,6 +63,82 @@ describe "CommandPanel", ->
rootView.trigger 'command-panel:toggle'
expect(commandPanel.hasParent()).toBeTruthy()
describe "when command-panel:toggle-preview is triggered on the root view", ->
beforeEach ->
rootView.attachToDom()
describe "when the preview list is/was previously visible", ->
beforeEach ->
rootView.trigger 'command-panel:toggle'
waitsForPromise -> commandPanel.execute('X x/a+/')
describe "when the command panel is visible", ->
beforeEach ->
expect(commandPanel.hasParent()).toBeTruthy()
describe "when the preview list is visible", ->
beforeEach ->
expect(commandPanel.previewList).toBeVisible()
describe "when the preview list is focused", ->
it "hides the command panel", ->
expect(commandPanel.previewList).toMatchSelector(':focus')
rootView.trigger 'command-panel:toggle-preview'
expect(commandPanel.hasParent()).toBeFalsy()
describe "when the preview list is not focused", ->
it "focuses the preview list", ->
commandPanel.miniEditor.focus()
rootView.trigger 'command-panel:toggle-preview'
expect(commandPanel.previewList).toMatchSelector(':focus')
describe "when the preview list is not visible", ->
beforeEach ->
commandPanel.miniEditor.focus()
rootView.trigger 'command-panel:toggle'
rootView.trigger 'command-panel:toggle'
expect(commandPanel.hasParent()).toBeTruthy()
expect(commandPanel.previewList).toBeHidden()
it "shows and focuses the preview list", ->
rootView.trigger 'command-panel:toggle-preview'
expect(commandPanel.previewList).toBeVisible()
expect(commandPanel.previewList).toMatchSelector(':focus')
describe "when the command panel is not visible", ->
it "shows the command panel and the preview list, and focuses the preview list", ->
commandPanel.miniEditor.focus()
rootView.trigger 'command-panel:toggle'
expect(commandPanel.hasParent()).toBeFalsy()
rootView.trigger 'command-panel:toggle-preview'
expect(commandPanel.hasParent()).toBeTruthy()
expect(commandPanel.previewList).toBeVisible()
expect(commandPanel.previewList).toMatchSelector(':focus')
describe "when the preview list has never been opened", ->
describe "when the command panel is visible", ->
beforeEach ->
rootView.trigger 'command-panel:toggle'
expect(commandPanel.hasParent()).toBeTruthy()
describe "when the mini editor is focused", ->
it "retains focus on the mini editor and does not show the preview list", ->
expect(commandPanel.miniEditor.isFocused).toBeTruthy()
rootView.trigger 'command-panel:toggle-preview'
expect(commandPanel.previewList).toBeHidden()
expect(commandPanel.miniEditor.isFocused).toBeTruthy()
describe "when the mini editor is not focused", ->
it "focuses the mini editor and does not show the preview list", ->
rootView.focus()
rootView.trigger 'command-panel:toggle-preview'
expect(commandPanel.previewList).toBeHidden()
expect(commandPanel.miniEditor.isFocused).toBeTruthy()
describe "when the command panel is not visible", ->
it "shows the command panel and focuses the mini editor, but does not show the preview list", ->
describe "when command-panel:unfocus is triggered on the command panel", ->
it "returns focus to the root view but does not hide the command panel", ->
rootView.attachToDom()
@@ -63,7 +148,6 @@ describe "CommandPanel", ->
expect(commandPanel.hasParent()).toBeTruthy()
expect(commandPanel.miniEditor.hiddenInput).not.toMatchSelector ':focus'
describe "when command-panel:repeat-relative-address is triggered on the root view", ->
it "repeats the last search command if there is one", ->
rootView.trigger 'command-panel:repeat-relative-address'
@@ -111,19 +195,61 @@ describe "CommandPanel", ->
expect(commandInterpreter.lastRelativeAddress.subcommands[0].regex.toString()).toEqual "/\\(items\\)/"
describe "when command-panel:find-in-file is triggered on an editor", ->
it "pre-populates command panel's editor with /", ->
it "pre-populates the command panel's editor with / and moves the cursor to the last column", ->
spyOn(commandPanel, 'attach').andCallThrough()
commandPanel.miniEditor.setText("foo")
commandPanel.miniEditor.setCursorBufferPosition([0, 0])
rootView.getActiveEditor().trigger "command-panel:find-in-file"
expect(commandPanel.attach).toHaveBeenCalled()
expect(commandPanel.parent).not.toBeEmpty()
expect(commandPanel.miniEditor.getText()).toBe "/"
expect(commandPanel.miniEditor.getCursorBufferPosition()).toEqual [0, 1]
describe "when command-panel:find-in-project is triggered on the root view", ->
it "pre-populates the command panel's editor with Xx/ and moves the cursor to the last column", ->
spyOn(commandPanel, 'attach').andCallThrough()
commandPanel.miniEditor.setText("foo")
commandPanel.miniEditor.setCursorBufferPosition([0, 0])
rootView.trigger "command-panel:find-in-project"
expect(commandPanel.attach).toHaveBeenCalled()
expect(commandPanel.parent).not.toBeEmpty()
expect(commandPanel.miniEditor.getText()).toBe "Xx/"
expect(commandPanel.miniEditor.getCursorBufferPosition()).toEqual [0, 3]
describe "when return is pressed on the panel's editor", ->
it "calls execute", ->
spyOn(commandPanel, 'execute')
rootView.trigger 'command-panel:toggle'
commandPanel.miniEditor.insertText 's/hate/love/g'
commandPanel.miniEditor.hiddenInput.trigger keydownEvent('enter')
describe "if the command has an immediate effect", ->
it "executes it immediately on the current buffer", ->
rootView.trigger 'command-panel:toggle'
commandPanel.miniEditor.insertText ',s/sort/torta/g'
commandPanel.miniEditor.hiddenInput.trigger keydownEvent('enter')
expect(commandPanel.execute).toHaveBeenCalled()
expect(buffer.lineForRow(0)).toMatch /quicktorta/
expect(buffer.lineForRow(1)).toMatch /var torta/
describe "when the command returns operations to be previewed", ->
beforeEach ->
rootView.attachToDom()
editor.remove()
rootView.trigger 'command-panel:toggle'
waitsForPromise -> commandPanel.execute('X x/a+/')
it "displays and focuses the operation preview list", ->
expect(commandPanel).toBeVisible()
expect(commandPanel.previewList).toBeVisible()
expect(commandPanel.previewList).toMatchSelector ':focus'
previewItem = commandPanel.previewList.find("li:contains(dir/a):first")
expect(previewItem.find('.path').text()).toBe "dir/a"
expect(previewItem.find('.preview').text()).toBe "aaa bbb"
expect(previewItem.find('.preview > .match').text()).toBe "aaa"
rootView.trigger 'command-panel:toggle-preview' # ensure we can close panel without problems
expect(commandPanel).toBeHidden()
it "destroys previously previewed operations if there are any", ->
waitsForPromise -> commandPanel.execute('X x/b+/')
# there shouldn't be any dangling operations after this
describe "if the command is malformed", ->
it "adds and removes an error class to the command panel and does not close it", ->
@@ -156,12 +282,74 @@ describe "CommandPanel", ->
commandPanel.miniEditor.trigger 'move-down'
expect(commandPanel.miniEditor.getText()).toBe ''
describe ".execute()", ->
it "executes the command and closes the command panel", ->
rootView.getActiveEditor().setText("i hate love")
rootView.getActiveEditor().getSelection().setBufferRange [[0,0], [0,Infinity]]
describe "when the preview list is focused with search operations", ->
previewList = null
beforeEach ->
previewList = commandPanel.previewList
rootView.trigger 'command-panel:toggle'
commandPanel.miniEditor.insertText 's/hate/love/'
commandPanel.execute()
expect(rootView.getActiveEditor().getText()).toBe "i love love"
expect(rootView.find('.command-panel')).not.toExist()
waitsForPromise -> commandPanel.execute('X x/a+/')
describe "when move-down and move-up are triggered on the preview list", ->
it "selects the next/previous operation (if there is one), and scrolls the list if needed", ->
rootView.attachToDom()
expect(previewList.find('li:eq(0)')).toHaveClass 'selected'
expect(previewList.getSelectedOperation()).toBe previewList.getOperations()[0]
previewList.trigger 'move-up'
expect(previewList.find('li:eq(0)')).toHaveClass 'selected'
expect(previewList.getSelectedOperation()).toBe previewList.getOperations()[0]
previewList.trigger 'move-down'
expect(previewList.find('li:eq(1)')).toHaveClass 'selected'
expect(previewList.getSelectedOperation()).toBe previewList.getOperations()[1]
previewList.trigger 'move-down'
expect(previewList.find('li:eq(2)')).toHaveClass 'selected'
expect(previewList.getSelectedOperation()).toBe previewList.getOperations()[2]
previewList.trigger 'move-up'
expect(previewList.find('li:eq(1)')).toHaveClass 'selected'
expect(previewList.getSelectedOperation()).toBe previewList.getOperations()[1]
_.times previewList.getOperations().length, -> previewList.trigger 'move-down'
expect(previewList.find('li:last')).toHaveClass 'selected'
expect(previewList.getSelectedOperation()).toBe _.last(previewList.getOperations())
expect(previewList.scrollBottom()).toBe previewList.prop('scrollHeight')
_.times previewList.getOperations().length, -> previewList.trigger 'move-up'
console.log previewList.find('li:first').position().top
describe "when command-panel:execute is triggered on the preview list", ->
it "opens the operation's buffer, selects the search result, and focuses the active editor", ->
spyOn(rootView, 'focus')
executeHandler = jasmine.createSpy('executeHandler')
commandPanel.on 'command-panel:execute', executeHandler
_.times 4, -> previewList.trigger 'move-down'
operation = previewList.getSelectedOperation()
previewList.trigger 'command-panel:execute'
editSession = rootView.getActiveEditSession()
expect(editSession.buffer.getPath()).toBe project.resolve(operation.getPath())
expect(editSession.getSelectedBufferRange()).toEqual operation.getBufferRange()
expect(rootView.focus).toHaveBeenCalled()
expect(executeHandler).not.toHaveBeenCalled()
describe "when an operation in the preview list is clicked", ->
it "opens the operation's buffer, selects the search result, and focuses the active editor", ->
spyOn(rootView, 'focus')
operation = previewList.getOperations()[4]
previewList.find('li:eq(4) span').mousedown()
expect(previewList.getSelectedOperation()).toBe operation
editSession = rootView.getActiveEditSession()
expect(editSession.buffer.getPath()).toBe project.resolve(operation.getPath())
expect(editSession.getSelectedBufferRange()).toEqual operation.getBufferRange()
expect(rootView.focus).toHaveBeenCalled()

View File

@@ -122,7 +122,7 @@ describe 'FuzzyFinder', ->
describe "when the active editor only contains edit sessions for anonymous buffers", ->
it "does not open", ->
editor = rootView.getActiveEditor()
editor.edit(rootView.project.open())
editor.edit(rootView.project.buildEditSessionForPath())
editor.loadPreviousEditSession()
editor.destroyActiveEditSession()
expect(editor.getOpenBufferPaths().length).toBe 0

View File

@@ -31,12 +31,12 @@ describe "TreeView", ->
expect(treeView.root.find('> .header .name')).toHaveText('fixtures/')
rootEntries = treeView.root.find('.entries')
subdir1 = rootEntries.find('> li:eq(0)')
expect(subdir1.find('.disclosure-arrow')).toHaveText('')
expect(subdir1.find('.name')).toHaveText('dir/')
expect(subdir1.find('.entries')).not.toExist()
subdir0 = rootEntries.find('> li:eq(0)')
expect(subdir0.find('.disclosure-arrow')).toHaveText('')
expect(subdir0.find('.name')).toHaveText('dir/')
expect(subdir0.find('.entries')).not.toExist()
subdir2 = rootEntries.find('> li:eq(1)')
subdir2 = rootEntries.find('> li:eq(2)')
expect(subdir2.find('.disclosure-arrow')).toHaveText('')
expect(subdir2.find('.name')).toHaveText('zed/')
expect(subdir2.find('.entries')).not.toExist()
@@ -610,8 +610,6 @@ describe "TreeView", ->
describe "when the directories along the new path don't exist", ->
it "creates the target directory before moving the file", ->
rootView.project.destroy() # Ensure there are no open buffers (renaming a file asynchronously updates the buffer's path, this causes the afterEach block to unwatch the previous path, which no longer exists.)
newPath = fs.join(rootDirPath, 'new/directory', 'renamed-test-file.txt')
moveDialog.miniEditor.setText(newPath)
@@ -620,6 +618,8 @@ describe "TreeView", ->
expect(fs.exists(newPath)).toBeTruthy()
expect(fs.exists(filePath)).toBeFalsy()
waits 50 # TODO: remove this workaround once we fix the race condition in fs events
describe "when a file or directory already exists at the target path", ->
it "shows an error message and does not close the dialog", ->
runs ->

2
spec/fixtures/dir/a vendored
View File

@@ -0,0 +1,2 @@
aaa bbb
cc aa cc

View File

@@ -0,0 +1 @@
bbb aaaa

1
spec/fixtures/dir/b vendored
View File

@@ -0,0 +1 @@
aaa ccc

View File

@@ -0,0 +1 @@
I am evil because there's a UTF-8 character right here: ă

View File

@@ -0,0 +1 @@
I am evil because there are spaces in my name

View File

@@ -0,0 +1 @@
I am evil because there's a newline in my name

1
spec/fixtures/evil-files/quote".txt vendored Normal file
View File

@@ -0,0 +1 @@
I am evil because there's a " in my filename. Why you do that!?

1
spec/fixtures/evil-files/utfă.md vendored Normal file
View File

@@ -0,0 +1 @@
I am evil because there's a UTF-8 character in my name

View File

@@ -26,6 +26,7 @@ afterEach ->
$('#jasmine-content').empty()
document.title = defaultTitle
ensureNoPathSubscriptions()
window.fixturesProject.destroy()
window.keymap.bindKeys '*', 'meta-w': 'close'
$(document).on 'close', -> window.close()
@@ -88,15 +89,32 @@ window.mousedownEvent = (properties={}) ->
window.mousemoveEvent = (properties={}) ->
window.mouseEvent('mousemove', properties)
window.waitsForPromise = (fn) ->
window.waitsForPromise = (args...) ->
if args.length > 1
{ shouldReject } = args[0]
else
shouldReject = false
fn = _.last(args)
window.waitsFor (moveOn) ->
fn().done(moveOn)
promise = fn()
if shouldReject
promise.fail(moveOn)
promise.done ->
jasmine.getEnv().currentSpec.fail("Expected promise to be rejected, but it was resolved")
moveOn()
else
promise.done(moveOn)
promise.fail (error) ->
jasmine.getEnv().currentSpec.fail("Expected promise to be resolved, but it was rejected with #{jasmine.pp(error)}")
moveOn()
window.resetTimeouts = ->
window.now = 0
window.timeoutCount = 0
window.timeouts = []
window.originalSetTimeout = window.setTimeout
window.setTimeout = (callback, ms) ->
id = ++window.timeoutCount
window.timeouts.push([id, window.now + ms, callback])

View File

@@ -0,0 +1,94 @@
ChildProcess = require 'child-process'
describe 'Child Processes', ->
describe ".exec(command, options)", ->
[stderrHandler, stdoutHandler] = []
beforeEach ->
stderrHandler = jasmine.createSpy "stderrHandler"
stdoutHandler = jasmine.createSpy "stdoutHandler"
it "returns a promise that resolves to stdout and stderr", ->
waitsForPromise ->
cmd = "echo 'good' && echo 'bad' >&2"
ChildProcess.exec(cmd).done (stdout, stderr) ->
expect(stdout).toBe 'good\n'
expect(stderr).toBe 'bad\n'
describe "when options are given", ->
it "calls the options.stdout callback when new data is received on stdout", ->
cmd = "echo 'first' && sleep .1 && echo 'second' && sleep .1 && echo 'third'"
ChildProcess.exec(cmd, stdout: stdoutHandler)
waitsFor ->
stdoutHandler.callCount > 2
runs ->
expect(stdoutHandler.argsForCall[0][0]).toBe "first\n"
expect(stdoutHandler.argsForCall[1][0]).toBe "second\n"
expect(stdoutHandler.argsForCall[2][0]).toBe "third\n"
it "calls the options.stderr callback when new data is received on stderr", ->
cmd = "echo '1111' >&2 && sleep .1 && echo '2222' >&2"
ChildProcess.exec(cmd, stderr: stderrHandler)
waitsFor ->
stderrHandler.callCount > 1
runs ->
expect(stderrHandler.argsForCall[0][0]).toBe "1111\n"
expect(stderrHandler.argsForCall[1][0]).toBe "2222\n"
describe "when the `bufferLines` option is true ", ->
[simulateStdout, simulateStderr] = []
beforeEach ->
spyOn($native, 'exec')
ChildProcess.exec("print_the_things", bufferLines: true, stdout: stdoutHandler, stderr: stderrHandler)
{ stdout, stderr } = $native.exec.argsForCall[0][1]
simulateStdout = stdout
simulateStderr = stderr
it "only triggers stdout callbacks with complete lines", ->
simulateStdout """
I am a full line
I am part of """
expect(stdoutHandler).toHaveBeenCalledWith("I am a full line\n")
stdoutHandler.reset()
simulateStdout """
a line
I am another full line\n
"""
expect(stdoutHandler).toHaveBeenCalledWith """
I am part of a line
I am another full line\n
"""
it "only triggers stderr callbacks with complete lines", ->
simulateStderr """
I am a full line
I am part of """
expect(stderrHandler).toHaveBeenCalledWith("I am a full line\n")
stdoutHandler.reset()
simulateStderr """
a line
I am another full line\n
"""
expect(stderrHandler).toHaveBeenCalledWith """
I am part of a line
I am another full line\n
"""
describe "when the command fails", ->
it "executes the callback with error set to the exit status", ->
waitsForPromise shouldReject: true, ->
cmd = "echo 'bad' >&2 && exit 2"
ChildProcess.exec(cmd).fail (error) ->
expect(error.exitStatus).toBe 2
expect(error.stderr).toBe "bad\n"

View File

@@ -4,11 +4,13 @@ module.exports =
class AnchorRange
start: null
end: null
buffer: null
editSession: null # optional
constructor: (@editSession, bufferRange) ->
constructor: (bufferRange, @buffer, @editSession) ->
bufferRange = Range.fromObject(bufferRange)
@startAnchor = @editSession.addAnchorAtBufferPosition(bufferRange.start, ignoreEqual: true)
@endAnchor = @editSession.addAnchorAtBufferPosition(bufferRange.end)
@startAnchor = @buffer.addAnchorAtPosition(bufferRange.start, ignoreEqual: true)
@endAnchor = @buffer.addAnchorAtPosition(bufferRange.end)
getBufferRange: ->
new Range(@startAnchor.getBufferPosition(), @endAnchor.getBufferPosition())
@@ -22,3 +24,5 @@ class AnchorRange
destroy: ->
@startAnchor.destroy()
@endAnchor.destroy()
@buffer.removeAnchorRange(this)
@editSession?.removeAnchorRange(this)

View File

@@ -4,12 +4,15 @@ _ = require 'underscore'
module.exports =
class Anchor
editor: null
buffer: null
editSession: null # optional
bufferPosition: null
screenPosition: null
ignoreEqual: false
strong: false
constructor: (@editSession, options = {}) ->
{ @ignoreEqual, @strong } = options
constructor: (@buffer, options = {}) ->
{ @editSession, @ignoreEqual, @strong } = options
handleBufferChange: (e) ->
{ oldRange, newRange } = e
@@ -43,7 +46,7 @@ class Anchor
setBufferPosition: (position, options={}) ->
@bufferPosition = Point.fromObject(position)
clip = options.clip ? true
@bufferPosition = @editSession.clipBufferPosition(@bufferPosition) if clip
@bufferPosition = @buffer.clipPosition(@bufferPosition) if clip
@refreshScreenPosition(options)
getScreenPosition: ->
@@ -65,11 +68,13 @@ class Anchor
@trigger 'change-screen-position', @screenPosition, bufferChange: options.bufferChange
refreshScreenPosition: (options={}) ->
return unless @editSession
screenPosition = @editSession.screenPositionForBufferPosition(@bufferPosition, options)
@setScreenPosition(screenPosition, bufferChange: options.bufferChange, clip: false, assignBufferPosition: false)
destroy: ->
@editSession.removeAnchor(this)
@buffer.removeAnchor(this)
@editSession?.removeAnchor(this)
@trigger 'destroy'
@off()

View File

@@ -38,7 +38,11 @@ class BufferChangeOperation
newTextLines[lastLineIndex] += suffix
@buffer.replaceLines(oldRange.start.row, oldRange.end.row, newTextLines)
@buffer.trigger 'change', { oldRange, newRange, oldText, newText }
event = { oldRange, newRange, oldText, newText }
@buffer.trigger 'change', event
anchor.handleBufferChange(event) for anchor in @buffer.getAnchors()
@buffer.trigger 'update-anchors-after-change'
newRange
calculateNewRange: (oldRange, newText) ->

View File

@@ -6,6 +6,8 @@ Range = require 'range'
EventEmitter = require 'event-emitter'
UndoManager = require 'undo-manager'
BufferChangeOperation = require 'buffer-change-operation'
Anchor = require 'anchor'
AnchorRange = require 'anchor-range'
module.exports =
class Buffer
@@ -15,9 +17,14 @@ class Buffer
modifiedOnDisk: null
lines: null
file: null
anchors: null
anchorRanges: null
refcount: 0
constructor: (path) ->
constructor: (path, @project) ->
@id = @constructor.idCounter++
@anchors = []
@anchorRanges = []
@lines = ['']
if path
@@ -31,22 +38,21 @@ class Buffer
@modified = false
destroy: ->
throw new Error("Destroying buffer twice with path '#{@getPath()}'") if @destroyed
@file?.off()
@destroyed = true
@project?.removeBuffer(this)
reload: ->
@setText(fs.read(@file.getPath()))
@modified = false
@modifiedOnDisk = false
retain: ->
@refcount++
this
getPath: ->
@file?.getPath()
setPath: (path) ->
return if path == @getPath()
@file?.off()
@file = new File(path)
release: ->
@refcount--
@destroy() if @refcount <= 0
this
subscribeToFile: ->
@file.on "contents-change", =>
if @isModified()
@modifiedOnDisk = true
@@ -61,6 +67,20 @@ class Buffer
@file.on "move", =>
@trigger "path-change", this
reload: ->
@setText(fs.read(@file.getPath()))
@modified = false
@modifiedOnDisk = false
getPath: ->
@file?.getPath()
setPath: (path) ->
return if path == @getPath()
@file?.off()
@file = new File(path)
@subscribeToFile()
@trigger "path-change", this
getExtension: ->
@@ -151,6 +171,15 @@ class Buffer
operation = new BufferChangeOperation({buffer: this, oldRange, newText})
@pushOperation(operation)
clipPosition: (position) ->
{ row, column } = Point.fromObject(position)
row = 0 if row < 0
column = 0 if column < 0
row = Math.min(@getLastRow(), row)
column = Math.min(@lineLengthForRow(row), column)
new Point(row, column)
prefixAndSuffixForRange: (range) ->
prefix: @lines[range.start.row][0...range.start.column]
suffix: @lines[range.end.row][range.end.column..]
@@ -194,6 +223,29 @@ class Buffer
isModified: ->
@modified
getAnchors: -> new Array(@anchors...)
addAnchor: (options) ->
anchor = new Anchor(this, options)
@anchors.push(anchor)
anchor
addAnchorAtPosition: (position, options) ->
anchor = @addAnchor(options)
anchor.setBufferPosition(position)
anchor
addAnchorRange: (range, editSession) ->
anchorRange = new AnchorRange(range, this, editSession)
@anchorRanges.push(anchorRange)
anchorRange
removeAnchor: (anchor) ->
_.remove(@anchors, anchor)
removeAnchorRange: (anchorRange) ->
_.remove(@anchorRanges, anchorRange)
matchesInCharacterRange: (regex, startIndex, endIndex) ->
text = @getText()
matches = []

View File

@@ -18,7 +18,7 @@ class Cursor
@setBufferPosition(bufferPosition) if bufferPosition
destroy: ->
@editSession.removeAnchor(@anchor)
@anchor.destroy()
@editSession.removeCursor(this)
@trigger 'destroy'

View File

@@ -5,6 +5,7 @@ DisplayBuffer = require 'display-buffer'
Cursor = require 'cursor'
Selection = require 'selection'
EventEmitter = require 'event-emitter'
Range = require 'range'
AnchorRange = require 'anchor-range'
_ = require 'underscore'
@@ -13,7 +14,7 @@ class EditSession
@idCounter: 1
@deserialize: (state, project) ->
session = project.open(state.buffer)
session = project.buildEditSessionForPath(state.buffer)
session.setScrollTop(state.scrollTop)
session.setScrollLeft(state.scrollLeft)
session.setCursorScreenPosition(state.cursorScreenPosition)
@@ -41,11 +42,11 @@ class EditSession
@selections = []
@addCursorAtScreenPosition([0, 0])
@buffer.retain()
@buffer.on "path-change.edit-session-#{@id}", =>
@trigger 'buffer-path-change'
@buffer.on "change.edit-session-#{@id}", (e) =>
anchor.handleBufferChange(e) for anchor in @getAnchors()
@buffer.on "update-anchors-after-change.edit-session-#{@id}", =>
@mergeCursors()
@displayBuffer.on "change.edit-session-#{@id}", (e) =>
@@ -54,10 +55,16 @@ class EditSession
anchor.refreshScreenPosition() for anchor in @getAnchors()
destroy: ->
throw new Error("Edit session already destroyed") if @destroyed
@destroyed = true
@buffer.off ".edit-session-#{@id}"
@buffer.release()
@displayBuffer.off ".edit-session-#{@id}"
@displayBuffer.destroy()
@project.removeEditSession(this)
anchor.destroy() for anchor in @getAnchors()
anchorRange.destroy() for anchorRange in @getAnchorRanges()
serialize: ->
buffer: @buffer.getPath()
@@ -88,14 +95,8 @@ class EditSession
getSoftWrap: -> @softWrap
setSoftWrap: (@softWrap) ->
clipBufferPosition: (bufferPosition, options) ->
{ row, column } = Point.fromObject(bufferPosition)
row = 0 if row < 0
column = 0 if column < 0
row = Math.min(@buffer.getLastRow(), row)
column = Math.min(@buffer.lineLengthForRow(row), column)
new Point(row, column)
clipBufferPosition: (bufferPosition) ->
@buffer.clipPosition(bufferPosition)
getFileExtension: -> @buffer.getExtension()
getPath: -> @buffer.getPath()
@@ -268,8 +269,11 @@ class EditSession
getAnchors: ->
new Array(@anchors...)
addAnchor: (options) ->
anchor = new Anchor(this, options)
getAnchorRanges: ->
new Array(@anchorRanges...)
addAnchor: (options={}) ->
anchor = @buffer.addAnchor(_.extend({editSession: this}, options))
@anchors.push(anchor)
anchor
@@ -279,13 +283,16 @@ class EditSession
anchor
addAnchorRange: (range) ->
anchorRange = new AnchorRange(this, range)
anchorRange = @buffer.addAnchorRange(range, this)
@anchorRanges.push(anchorRange)
anchorRange
removeAnchor: (anchor) ->
_.remove(@anchors, anchor)
removeAnchorRange: (anchorRange) ->
_.remove(@anchorRanges, anchorRange)
getCursors: -> new Array(@cursors...)
getCursor: (index=0) ->
@@ -317,19 +324,27 @@ class EditSession
addSelectionForBufferRange: (bufferRange, options) ->
@addCursor().selection.setBufferRange(bufferRange, options)
@mergeIntersectingSelections()
setSelectedBufferRange: (bufferRange, options) ->
@clearSelections()
@getLastSelection().setBufferRange(bufferRange, options)
@setSelectedBufferRanges([bufferRange], options)
setSelectedBufferRanges: (bufferRanges, options={}) ->
throw new Error("Passed an empty array to setSelectedBufferRanges") unless bufferRanges.length
setSelectedBufferRanges: (bufferRanges, options) ->
selections = @getSelections()
selection.destroy() for selection in selections[bufferRanges.length...]
for bufferRange, i in bufferRanges
bufferRange = Range.fromObject(bufferRange)
unless options.preserveFolds
for row in [bufferRange.start.row..bufferRange.end.row]
@destroyFoldsContainingBufferRow(row)
if selections[i]
selections[i].setBufferRange(bufferRange, options)
else
@addSelectionForBufferRange(bufferRange, options)
@mergeIntersectingSelections()
@mergeIntersectingSelections(options)
removeSelection: (selection) ->
_.remove(@selections, selection)
@@ -340,6 +355,9 @@ class EditSession
selection.destroy()
lastSelection.clear()
clearAllSelections: ->
selection.destroy() for selection in @getSelections()
getSelections: -> new Array(@selections...)
getSelection: (index) ->

View File

@@ -337,7 +337,8 @@ class Editor extends View
@calculateDimensions()
@hiddenInput.width(@charWidth)
@setSoftWrapColumn() if @activeEditSession.getSoftWrap()
$(window).on "resize.editor#{@id}", => @updateRenderedLines()
$(window).on "resize.editor#{@id}", =>
@updateRenderedLines()
@focus() if @isFocused
@renderWhenAttached()
@@ -393,7 +394,7 @@ class Editor extends View
for editSession, index in @editSessions
if editSession.buffer.getPath() == path
@setActiveEditSessionIndex(index)
return true
return @activeEditSession
false
getOpenBufferPaths: ->
@@ -569,8 +570,11 @@ class Editor extends View
if @pane() then @pane().remove() else super
rootView?.focus()
getEditSessions: ->
new Array(@editSessions...)
destroyEditSessions: ->
for session in @editSessions
for session in @getEditSessions()
session.destroy()
renderWhenAttached: ->

View File

@@ -9,6 +9,7 @@ class File
md5: null
constructor: (@path) ->
throw "Creating file with path that is not a file: #{@path}" unless fs.isFile(@path)
@updateMd5()
setPath: (@path) ->

View File

@@ -1,11 +1,12 @@
fs = require 'fs'
_ = require 'underscore'
$ = require 'jquery'
Range = require 'range'
Buffer = require 'buffer'
EditSession = require 'edit-session'
EventEmitter = require 'event-emitter'
Directory = require 'directory'
ChildProcess = require 'child-process'
module.exports =
class Project
@@ -15,13 +16,22 @@ class Project
autoIndent: null
softTabs: null
softWrap: null
ignoredPathRegexes: null
constructor: (path) ->
@setPath(path)
@editSessions = []
@buffers = []
@setTabText(' ')
@setAutoIndent(true)
@setSoftTabs(true)
@ignoredPathRegexes = [
/\.DS_Store$/
/(^|\/)\.git(\/|$)/
]
destroy: ->
editSession.destroy() for editSession in @getEditSessions()
getPath: ->
@rootDirectory?.path
@@ -54,7 +64,10 @@ class Project
deferred
ignorePath: (path) ->
fs.base(path).match(/\.DS_Store/) or path.match(/(^|\/)\.git(\/|$)/)
_.find @ignoredPathRegexes, (regex) -> path.match(regex)
ignorePathRegex: ->
@ignoredPathRegexes.map((regex) -> "(#{regex.source})").join("|")
resolve: (filePath) ->
filePath = fs.join(@getPath(), filePath) unless filePath[0] == '/'
@@ -75,14 +88,8 @@ class Project
getSoftWrap: -> @softWrap
setSoftWrap: (@softWrap) ->
open: (filePath, editSessionOptions={}) ->
if filePath?
filePath = @resolve(filePath)
buffer = @bufferWithPath(filePath) ? @buildBuffer(filePath)
else
buffer = @buildBuffer()
@buildEditSession(buffer, editSessionOptions)
buildEditSessionForPath: (filePath, editSessionOptions={}) ->
@buildEditSession(@bufferForPath(filePath), editSessionOptions)
buildEditSession: (buffer, editSessionOptions) ->
options = _.extend(@defaultEditSessionOptions(), editSessionOptions)
@@ -99,22 +106,11 @@ class Project
softTabs: @getSoftTabs()
softWrap: @getSoftWrap()
destroy: ->
for editSession in _.clone(@editSessions)
@removeEditSession(editSession)
getEditSessions: ->
new Array(@editSessions...)
removeEditSession: (editSession) ->
_.remove(@editSessions, editSession)
@destroyBufferIfOrphaned(editSession.buffer)
destroyBufferIfOrphaned: (buffer) ->
unless _.find(@editSessions, (editSession) -> editSession.buffer == buffer)
buffer.destroy()
buildBuffer: (filePath) ->
buffer = new Buffer(filePath)
@trigger 'new-buffer', buffer
buffer
getBuffers: ->
buffers = []
@@ -123,7 +119,64 @@ class Project
buffers
bufferWithPath: (path) ->
return editSession.buffer for editSession in @editSessions when editSession.buffer.getPath() == path
bufferForPath: (filePath) ->
if filePath?
filePath = @resolve(filePath)
buffer = _.find @buffers, (buffer) -> buffer.getPath() == filePath
buffer or @buildBuffer(filePath)
else
@buildBuffer()
buildBuffer: (filePath) ->
buffer = new Buffer(filePath, this)
@buffers.push buffer
@trigger 'new-buffer', buffer
buffer
removeBuffer: (buffer) ->
_.remove(@buffers, buffer)
scan: (regex, iterator) ->
regex = new RegExp(regex.source, 'g')
command = "#{require.resolve('ag')} --ackmate \"#{regex.source}\" \"#{@getPath()}\""
bufferedData = ""
state = 'readingPath'
path = null
readPath = (line) ->
if /^[0-9,; ]+:/.test(line)
state = 'readingLines'
else if /^:/.test line
path = line.substr(1)
else
path += ('\n' + line)
readLine = (line) ->
if line.length == 0
state = 'readingPath'
path = null
else
colonIndex = line.indexOf(':')
matchInfo = line.substring(0, colonIndex)
lineText = line.substring(colonIndex + 1)
readMatches(matchInfo, lineText)
readMatches = (matchInfo, lineText) ->
[lineNumber, matchPositionsText] = matchInfo.match(/(\d+);(.+)/)[1..]
row = parseInt(lineNumber) - 1
matchPositions = matchPositionsText.split(',').map (positionText) -> positionText.split(' ').map (pos) -> parseInt(pos)
for [column, length] in matchPositions
range = new Range([row, column], [row, column + length])
match = lineText.substr(column, length)
iterator({path, range, match})
ChildProcess.exec command , bufferLines: true, stdout: (data) ->
lines = data.split('\n')
lines.pop() # the last segment is a spurios '' because data always ends in \n due to bufferLines: true
for line in lines
readPath(line) if state is 'readingPath'
readLine(line) if state is 'readingLines'
_.extend Project.prototype, EventEmitter

View File

@@ -96,8 +96,9 @@ class RootView extends View
changeFocus = options.changeFocus ? true
allowActiveEditorChange = options.allowActiveEditorChange ? false
unless @openInExistingEditor(path, allowActiveEditorChange)
editor = new Editor(editSession: @project.open(path))
unless editSession = @openInExistingEditor(path, allowActiveEditorChange)
editSession = @project.buildEditSessionForPath(path)
editor = new Editor({editSession})
pane = new Pane(editor)
@panes.append(pane)
if changeFocus
@@ -105,23 +106,24 @@ class RootView extends View
else
@makeEditorActive(editor)
editSession
openInExistingEditor: (path, allowActiveEditorChange) ->
if activeEditor = @getActiveEditor()
path = @project.resolve(path) if path
if activeEditor.activateEditSessionForPath(path)
return true
if editSession = activeEditor.activateEditSessionForPath(path)
return editSession
if allowActiveEditorChange
for editor in @getEditors()
if editor.activateEditSessionForPath(path)
if editSession = editor.activateEditSessionForPath(path)
editor.focus()
return true
return editSession
activeEditor.edit(@project.open(path))
true
else
false
editSession = @project.buildEditSessionForPath(path)
activeEditor.edit(editSession)
editSession
editorFocused: (editor) ->
@makeEditorActive(editor) if @panes.containsElement(editor)
@@ -144,7 +146,7 @@ class RootView extends View
document.title = title
getEditors: ->
@panes.find('.editor').map(-> $(this).view()).toArray()
@panes.find('.pane > .editor').map(-> $(this).view()).toArray()
getModifiedBuffers: ->
modifiedBuffers = []
@@ -163,6 +165,9 @@ class RootView extends View
else
@panes.find('.editor:first').view()
getActiveEditSession: ->
@getActiveEditor()?.activeEditSession
focusNextPane: ->
panes = @panes.find('.pane')
currentIndex = panes.toArray().indexOf(@getFocusedPane()[0])

View File

@@ -21,6 +21,7 @@ class Selection
if @cursor
@cursor.off('.selection')
@cursor.destroy()
@anchor?.destroy()
@editSession.removeSelection(this)
@trigger 'destroy'

View File

@@ -83,6 +83,12 @@ windowAdditions =
onerror: ->
$native.showDevTools()
measure: (description, fn) ->
start = new Date().getTime()
fn()
result = new Date().getTime() - start
console.log description, result
window[key] = value for key, value of windowAdditions
window.setUpKeymap()

View File

@@ -27,7 +27,7 @@ class Autocomplete extends View
@activate: (rootView) ->
new Autocomplete(editor) for editor in rootView.getEditors()
rootView.on 'editor-open', (e, editor) ->
new Autocomplete(editor) unless editor.is('.autocomplete .mini')
editor.autoComplete = new Autocomplete(editor) unless editor.is('.autocomplete .mini')
initialize: (@editor) ->
requireStylesheet 'autocomplete.css'

View File

@@ -3,16 +3,16 @@ PEG = require 'pegjs'
module.exports =
class CommandInterpreter
constructor: ->
constructor: (@project) ->
@parser = PEG.buildParser(fs.read(require.resolve 'command-panel/commands.pegjs'))
eval: (editor, string) ->
eval: (string, activeEditSession) ->
compositeCommand = @parser.parse(string)
@lastRelativeAddress = compositeCommand if compositeCommand.isRelativeAddress()
compositeCommand.execute(editor)
compositeCommand.execute(@project, activeEditSession)
repeatRelativeAddress: (editor) ->
@lastRelativeAddress?.execute(editor)
repeatRelativeAddress: (activeEditSession) ->
@lastRelativeAddress?.execute(@project, activeEditSession)
repeatRelativeAddressInReverse: (editor) ->
@lastRelativeAddress?.reverse().execute(editor)
repeatRelativeAddressInReverse: (activeEditSession) ->
@lastRelativeAddress?.reverse().execute(@project, activeEditSession)

View File

@@ -1,7 +1,8 @@
{View} = require 'space-pen'
{View, $$$} = require 'space-pen'
CommandInterpreter = require 'command-panel/command-interpreter'
RegexAddress = require 'command-panel/commands/regex-address'
CompositeCommand = require 'command-panel/commands/composite-command'
PreviewList = require 'command-panel/preview-list'
Editor = require 'editor'
{SyntaxError} = require('pegjs').parser
@@ -16,6 +17,9 @@ class CommandPanel extends View
else
@instance = new CommandPanel(rootView)
@deactivate: ->
@instance.destroy()
@serialize: ->
text: @instance.miniEditor.getText()
visible: @instance.hasParent()
@@ -25,23 +29,29 @@ class CommandPanel extends View
commandPanel.attach(state.text) if state.visible
commandPanel
@content: ->
@content: (rootView) ->
@div class: 'command-panel', =>
@div ':', class: 'prompt', outlet: 'prompt'
@subview 'miniEditor', new Editor(mini: true)
@subview 'previewList', new PreviewList(rootView)
@div class: 'prompt-and-editor', =>
@div ':', class: 'prompt', outlet: 'prompt'
@subview 'miniEditor', new Editor(mini: true)
commandInterpreter: null
history: null
historyIndex: 0
initialize: (@rootView)->
@commandInterpreter = new CommandInterpreter()
@commandInterpreter = new CommandInterpreter(@rootView.project)
@history = []
@on 'command-panel:unfocus', => @rootView.focus()
@on 'command-panel:close', => @detach()
@rootView.on 'command-panel:toggle', => @toggle()
@rootView.on 'command-panel:toggle-preview', => @togglePreview()
@rootView.on 'command-panel:execute', => @execute()
@rootView.on 'command-panel:find-in-file', => @attach("/")
@rootView.on 'command-panel:find-in-project', => @attach("Xx/")
@rootView.on 'command-panel:repeat-relative-address', => @repeatRelativeAddress()
@rootView.on 'command-panel:repeat-relative-address-in-reverse', => @repeatRelativeAddressInReverse()
@rootView.on 'command-panel:set-selection-as-regex-address', => @setSelectionAsLastRelativeAddress()
@@ -50,6 +60,11 @@ class CommandPanel extends View
@miniEditor.on 'move-up', => @navigateBackwardInHistory()
@miniEditor.on 'move-down', => @navigateForwardInHistory()
@previewList.hide()
destroy: ->
@previewList.destroy()
toggle: ->
if @miniEditor.isFocused
@detach()
@@ -58,30 +73,46 @@ class CommandPanel extends View
@attach() unless @hasParent()
@miniEditor.focus()
togglePreview: ->
if @previewList.is(':focus')
@previewList.hide()
@detach()
@rootView.focus()
else
@attach() unless @hasParent()
if @previewList.hasOperations()
@previewList.show().focus()
else
@miniEditor.focus()
attach: (text='') ->
@rootView.vertical.append(this)
@miniEditor.focus()
@miniEditor.setText(text)
@prompt.css 'font', @miniEditor.css('font')
@miniEditor.setCursorBufferPosition([0, Infinity])
detach: ->
@rootView.focus()
@previewList.hide()
super
execute: (command = @miniEditor.getText()) ->
try
@commandInterpreter.eval(@rootView.getActiveEditor(), command)
@commandInterpreter.eval(command, @rootView.getActiveEditSession()).done (operationsToPreview) =>
@history.push(command)
@historyIndex = @history.length
if operationsToPreview?.length
@previewList.populate(operationsToPreview)
@previewList.focus()
else
@detach()
catch error
if error instanceof SyntaxError
if error.name is "SyntaxError"
@flashError()
return
else
throw error
@history.push(command)
@historyIndex = @history.length
@detach()
navigateBackwardInHistory: ->
return if @historyIndex == 0
@historyIndex--
@@ -93,10 +124,10 @@ class CommandPanel extends View
@miniEditor.setText(@history[@historyIndex] or '')
repeatRelativeAddress: ->
@commandInterpreter.repeatRelativeAddress(@rootView.getActiveEditor())
@commandInterpreter.repeatRelativeAddress(@rootView.getActiveEditSession())
repeatRelativeAddressInReverse: ->
@commandInterpreter.repeatRelativeAddressInReverse(@rootView.getActiveEditor())
@commandInterpreter.repeatRelativeAddressInReverse(@rootView.getActiveEditSession())
setSelectionAsLastRelativeAddress: ->
selection = @rootView.getActiveEditor().getSelectedText()

View File

@@ -1,12 +1,14 @@
{
var CompositeCommand = require('command-panel/commands/composite-command')
var Substitution = require('command-panel/commands/substitution');
var ZeroAddress = require('command-panel/commands/zero-address');
var EofAddress = require('command-panel/commands/eof-address');
var LineAddress = require('command-panel/commands/line-address');
var AddressRange = require('command-panel/commands/address-range');
var EofAddress = require('command-panel/commands/eof-address');
var CurrentSelectionAddress = require('command-panel/commands/current-selection-address')
var RegexAddress = require('command-panel/commands/regex-address')
var SelectAllMatches = require('command-panel/commands/select-all-matches')
var SelectAllMatchesInProject = require('command-panel/commands/select-all-matches-in-project')
}
start = expressions:(expression+) {
@@ -19,21 +21,22 @@ address = addressRange / primitiveAddress
addressRange
= start:primitiveAddress? _ ',' _ end:address? {
if (!start) start = new LineAddress(0)
if (!start) start = new ZeroAddress()
if (!end) end = new EofAddress()
return new AddressRange(start, end)
}
primitiveAddress
= lineNumber:integer { return new LineAddress(lineNumber) }
= '0' { return new ZeroAddress() }
/ '$' { return new EofAddress() }
/ '.' { return new CurrentSelectionAddress() }
/ lineNumber:integer { return new LineAddress(lineNumber) }
/ regexAddress
regexAddress
= reverse:'-'? '/' pattern:pattern '/'? { return new RegexAddress(pattern, reverse.length > 0)}
command = substitution / selectAllMatches
command = substitution / selectAllMatches / selectAllMatchesInProject
substitution
= "s" _ "/" find:pattern "/" replace:pattern "/" _ options:[g]* {
@@ -43,6 +46,9 @@ substitution
selectAllMatches
= 'x' _ '/' pattern:pattern '/'? { return new SelectAllMatches(pattern) }
selectAllMatchesInProject
= 'X' _ 'x' _ '/' pattern:pattern '/'? { return new SelectAllMatchesInProject(pattern) }
pattern
= pattern:[^/]* { return pattern.join('') }

View File

@@ -5,8 +5,8 @@ module.exports =
class AddressRange extends Address
constructor: (@startAddress, @endAddress) ->
getRange: (editor, currentRange) ->
new Range(@startAddress.getRange(editor, currentRange).start, @endAddress.getRange(editor, currentRange).end)
getRange: (buffer, range) ->
new Range(@startAddress.getRange(buffer, range).start, @endAddress.getRange(buffer, range).end)
isRelative: ->
@startAddress.isRelative() and @endAddress.isRelative()

View File

@@ -1,8 +1,17 @@
Command = require 'command-panel/commands/command'
Operation = require 'command-panel/operation'
$ = require 'jquery'
module.exports =
class Address extends Command
execute: (editor, currentRange) ->
[@getRange(editor, currentRange)]
compile: (project, buffer, ranges) ->
deferred = $.Deferred()
deferred.resolve ranges.map (range) =>
new Operation
project: project
buffer: buffer
bufferRange: @getRange(buffer, range)
deferred.promise()
isAddress: -> true

View File

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

View File

@@ -1,23 +1,36 @@
_ = require 'underscore'
$ = require 'jquery'
module.exports =
class CompositeCommand
constructor: (@subcommands) ->
execute: (editor) ->
initialRanges = editor.getSelectedBufferRanges()
for command in @subcommands
newRanges = []
currentRanges = editor.getSelectedBufferRanges()
for currentRange in currentRanges
newRanges.push(command.execute(editor, currentRange)...)
execute: (project, editSession) ->
currentRanges = editSession?.getSelectedBufferRanges()
@executeCommands(@subcommands, project, editSession, currentRanges)
for range in newRanges
for row in [range.start.row..range.end.row]
editor.destroyFoldsContainingBufferRow(row)
executeCommands: (commands, project, editSession, ranges) ->
deferred = $.Deferred()
[currentCommand, remainingCommands...] = commands
editor.setSelectedBufferRanges(newRanges)
editor.setSelectedBufferRanges(initialRanges) if command.restoreSelections
currentCommand.compile(project, editSession?.buffer, ranges).done (operations) =>
if remainingCommands.length
nextRanges = operations.map (operation) ->
operation.destroy()
operation.getBufferRange()
@executeCommands(remainingCommands, project, editSession, nextRanges).done ->
deferred.resolve()
else
if currentCommand.previewOperations
deferred.resolve(operations)
else
editSession?.clearAllSelections() unless currentCommand.preserveSelections
for operation in operations
operation.execute(editSession)
operation.destroy()
deferred.resolve()
deferred.promise()
reverse: ->
new CompositeCommand(@subcommands.map (command) -> command.reverse())

View File

@@ -3,7 +3,7 @@ Range = require 'range'
module.exports =
class CurrentSelectionAddress extends Address
getRange: (editor, currentRange) ->
currentRange
getRange: (buffer, range) ->
range
isRelative: -> true

View File

@@ -3,9 +3,8 @@ Range = require 'range'
module.exports =
class EofAddress extends Address
getRange: (editor) ->
lastRow = editor.getLastBufferRow()
column = editor.lineLengthForBufferRow(lastRow)
new Range([lastRow, column], [lastRow, column])
getRange: (buffer, range) ->
eof = buffer.getEofPosition()
new Range(eof, eof)
isRelative: -> false

View File

@@ -10,25 +10,25 @@ class RegexAddress extends Address
@isReversed = isReversed
@regex = new RegExp(pattern)
getRange: (editor, currentRange) ->
rangeBefore = new Range([0, 0], currentRange.start)
rangeAfter = new Range(currentRange.end, editor.getEofPosition())
getRange: (buffer, range) ->
rangeBefore = new Range([0, 0], range.start)
rangeAfter = new Range(range.end, buffer.getEofPosition())
rangeToSearch = if @isReversed then rangeBefore else rangeAfter
rangeToReturn = null
scanMethodName = if @isReversed then "backwardsScanInRange" else "scanInRange"
editor[scanMethodName] @regex, rangeToSearch, (match, range) ->
buffer[scanMethodName] @regex, rangeToSearch, (match, range) ->
rangeToReturn = range
if rangeToReturn
rangeToReturn
else
rangeToSearch = if @isReversed then rangeAfter else rangeBefore
editor[scanMethodName] @regex, rangeToSearch, (match, range) ->
buffer[scanMethodName] @regex, rangeToSearch, (match, range) ->
rangeToReturn = range
rangeToReturn or currentRange
rangeToReturn or range
isRelative: -> true

View File

@@ -0,0 +1,24 @@
Command = require 'command-panel/commands/command'
Operation = require 'command-panel/operation'
$ = require 'jquery'
module.exports =
class SelectAllMatchesInProject extends Command
regex: null
previewOperations: true
constructor: (pattern) ->
@regex = new RegExp(pattern, 'g')
compile: (project, buffer, range) ->
deferred = $.Deferred()
operations = []
promise = project.scan @regex, ({path, range}) ->
operations.push(new Operation(
project: project
buffer: project.bufferForPath(path)
bufferRange: range
))
promise.done -> deferred.resolve(operations)
deferred.promise()

View File

@@ -1,5 +1,6 @@
Command = require 'command-panel/commands/command'
Range = require 'range'
Operation = require 'command-panel/operation'
$ = require 'jquery'
module.exports =
class SelectAllMatches extends Command
@@ -8,8 +9,15 @@ class SelectAllMatches extends Command
constructor: (pattern) ->
@regex = new RegExp(pattern, 'g')
execute: (editor, currentRange) ->
rangesToSelect = []
editor.scanInRange @regex, currentRange, (match, range) ->
rangesToSelect.push(range)
rangesToSelect
compile: (project, buffer, ranges) ->
deferred = $.Deferred()
operations = []
for range in ranges
buffer.scanInRange @regex, range, (match, matchRange) ->
operations.push(new Operation(
project: project
buffer: buffer
bufferRange: matchRange
))
deferred.resolve(operations)
deferred.promise()

View File

@@ -1,16 +1,28 @@
Command = require 'command-panel/commands/command'
Operation = require 'command-panel/operation'
$ = require 'jquery'
module.exports =
class Substitution extends Command
regex: null
replacementText: null
restoreSelections: true
preserveSelections: true
constructor: (pattern, replacementText, options) ->
@replacementText = replacementText
@regex = new RegExp(pattern, options.join(''))
execute: (editor, currentRange) ->
editor.scanInRange @regex, currentRange, (match, matchRange, { replace }) =>
replace(@replacementText)
[currentRange]
compile: (project, buffer, ranges) ->
deferred = $.Deferred()
operations = []
for range in ranges
buffer.scanInRange @regex, range, (match, matchRange, { replace }) =>
operations.push(new Operation(
project: project
buffer: buffer
bufferRange: matchRange
newText: @replacementText
preserveSelection: true
))
deferred.resolve(operations)
deferred.promise()

View File

@@ -0,0 +1,9 @@
Address = require 'command-panel/commands/address'
Range = require 'range'
module.exports =
class ZeroAddress extends Address
getRange: ->
new Range([0, 0], [0, 0])
isRelative: -> false

View File

@@ -1,11 +1,11 @@
window.keymap.bindKeys '*'
'ctrl-0': 'command-panel:toggle'
'ctrl-meta-0': 'command-panel:toggle-preview'
'ctrl-2': 'command-panel:toggle-preview'
'meta-:': 'command-panel:toggle'
'meta-F': 'command-panel:find-in-project'
window.keymap.bindKeys '.command-panel .editor input',
'meta-w': 'command-panel:toggle'
window.keymap.bindKeys '.command-panel .preview-list, .command-panel .editor input',
'meta-w': 'command-panel:close'
escape: 'command-panel:unfocus'
enter: 'command-panel:execute'
@@ -14,3 +14,4 @@ window.keymap.bindKeys '.editor',
'meta-G': 'command-panel:repeat-relative-address-in-reverse'
'meta-e': 'command-panel:set-selection-as-regex-address'
'meta-f': 'command-panel:find-in-file'
'meta-F': 'command-panel:find-in-project'

View File

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

View File

@@ -0,0 +1,82 @@
$ = require 'jquery'
{$$$, View} = require 'space-pen'
module.exports =
class PreviewList extends View
@content: ->
@ol class: 'preview-list', tabindex: -1, ->
selectedOperationIndex: 0
operations: null
initialize: (@rootView) ->
@on 'move-down', => @selectNextOperation()
@on 'move-up', => @selectPreviousOperation()
@on 'command-panel:execute', => @executeSelectedOperation()
@on 'mousedown', 'li', (e) =>
@setSelectedOperationIndex(parseInt($(e.target).closest('li').data('index')))
@executeSelectedOperation()
destroy: ->
@destroyOperations() if @operations
hasOperations: -> @operations?
populate: (operations) ->
@destroyOperations() if @operations
@operations = operations
@empty()
@html $$$ ->
for operation, index in operations
{prefix, suffix, match} = operation.preview()
@li 'data-index': index, =>
@span operation.getPath(), outlet: "path", class: "path"
@span outlet: "preview", class: "preview", =>
@span prefix
@span match, class: 'match'
@span suffix
@setSelectedOperationIndex(0)
@show()
selectNextOperation: ->
@setSelectedOperationIndex(@selectedOperationIndex + 1)
selectPreviousOperation: ->
@setSelectedOperationIndex(@selectedOperationIndex - 1)
setSelectedOperationIndex: (index) ->
index = Math.max(0, index)
index = Math.min(@operations.length - 1, index)
@children(".selected").removeClass('selected')
element = @children("li:eq(#{index})")
element.addClass('selected')
@scrollToElement(element)
@selectedOperationIndex = index
executeSelectedOperation: ->
operation = @getSelectedOperation()
editSession = @rootView.open(operation.getPath())
operation.execute(editSession)
@rootView.focus()
false
getOperations: ->
new Array(@operations...)
destroyOperations: ->
operation.destroy() for operation in @getOperations()
@operations = null
getSelectedOperation: ->
@operations[@selectedOperationIndex]
scrollToElement: (element) ->
top = @scrollTop() + element.position().top
bottom = top + element.outerHeight()
if bottom > @scrollBottom()
@scrollBottom(bottom)
if top < @scrollTop()
@scrollTop(top)

View File

@@ -27,7 +27,7 @@ module.exports =
editor.on 'snippets:expand', (e) =>
editSession = editor.activeEditSession
prefix = editSession.getLastCursor().getCurrentWordPrefix()
if snippet = @snippetsByExtension[editSession.getFileExtension()][prefix]
if snippet = @snippetsByExtension[editSession.getFileExtension()]?[prefix]
editSession.transact ->
snippetExpansion = new SnippetExpansion(snippet, editSession)
editSession.snippetExpansion = snippetExpansion

View File

@@ -1,50 +1,35 @@
# node.js child-process
# http://nodejs.org/docs/v0.6.3/api/child_processes.html
$ = require 'jquery'
_ = require 'underscore'
module.exports =
exec: (command, options, callback) ->
callback = options if _.isFunction options
class ChildProccess
@exec: (command, options={}) ->
deferred = $.Deferred()
# make a task
task = OSX.NSTask.alloc.init
if options.bufferLines
options.stdout = @bufferLines(options.stdout) if options.stdout
options.stderr = @bufferLines(options.stderr) if options.stderr
# try to use their login shell
task.setLaunchPath "/bin/bash"
$native.exec command, options, (exitStatus, stdout, stderr) ->
try
if exitStatus != 0
deferred.reject({command, exitStatus, stderr})
else
deferred.resolve(stdout, stderr)
catch e
console.error "In ChildProccess termination callback: ", e.message
console.error e.stack
# set stdin to /dev/null
task.setStandardInput OSX.NSFileHandle.fileHandleWithNullDevice
deferred
# -l = login shell, -c = command
args = ["-l", "-c", command]
task.setArguments args
# setup stdout and stderr
task.setStandardOutput stdout = OSX.NSPipe.pipe
task.setStandardError stderr = OSX.NSPipe.pipe
stdoutHandle = stdout.fileHandleForReading
stderrHandle = stderr.fileHandleForReading
# begin
task.launch
# read pipes
err = @readHandle stderrHandle
out = @readHandle stdoutHandle
# check for a dirty exit
if not task.isRunning
code = task.terminationStatus
if code > 0
error = new Error
error.code = code
# call callback
callback error, out, err
readHandle: (handle) ->
OSX.NSString.
alloc.
initWithData_encoding(handle.readDataToEndOfFile, OSX.NSUTF8StringEncoding).
toString()
@bufferLines: (callback) ->
buffered = ""
(data) ->
buffered += data
lastNewlineIndex = buffered.lastIndexOf('\n')
if lastNewlineIndex >= 0
callback(buffered.substring(0, lastNewlineIndex + 1))
buffered = buffered.substring(lastNewlineIndex + 1)

View File

@@ -92,6 +92,7 @@ resolve = (file) ->
return file
__expand = (path) ->
return path if __isFile path
for ext, handler of exts
if __exists "#{path}.#{ext}"
return "#{path}.#{ext}"
@@ -104,6 +105,9 @@ __expand = (path) ->
__exists = (path) ->
$native.exists path
__isFile = (path) ->
$native.isFile path
__coffeeCache = (filePath) ->
{CoffeeScript} = require 'coffee-script'
tmpPath = "/tmp/atom-compiled-scripts"

View File

@@ -2,6 +2,44 @@
width: 100%;
background: #515151;
padding: 3px;
}
.command-panel .preview-list {
max-height: 300px;
overflow: auto;
margin-bottom: 3px;
position: relative;
background: #161616;
}
.command-panel .preview-list {
cursor: default;
}
.command-panel .preview-list li.selected {
background: #444;
}
.command-panel .preview-list:focus li.selected {
background: #223555;
}
.command-panel .preview-list .path {
padding-left: 3px;
color: #f9ee98;
margin-right: 1ex;
}
.command-panel .preview-list .preview {
color: #f6f3e8;
}
.command-panel .preview-list .preview .match {
background-color: rgba(255,255,255,.25);
padding: 1px;
}
.command-panel .prompt-and-editor {
display: -webkit-box;
}

2784
vendor/ack vendored Executable file

File diff suppressed because it is too large Load Diff

BIN
vendor/ag vendored Executable file

Binary file not shown.