mirror of
https://github.com/atom/atom.git
synced 2026-01-24 14:28:14 -05:00
@@ -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 = (
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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", ->
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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]]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
2
spec/fixtures/dir/a
vendored
@@ -0,0 +1,2 @@
|
||||
aaa bbb
|
||||
cc aa cc
|
||||
|
||||
1
spec/fixtures/dir/a-dir/oh-git
vendored
1
spec/fixtures/dir/a-dir/oh-git
vendored
@@ -0,0 +1 @@
|
||||
bbb aaaa
|
||||
1
spec/fixtures/dir/b
vendored
1
spec/fixtures/dir/b
vendored
@@ -0,0 +1 @@
|
||||
aaa ccc
|
||||
|
||||
1
spec/fixtures/evil-files/a_file_with_utf8.txt
vendored
Normal file
1
spec/fixtures/evil-files/a_file_with_utf8.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
I am evil because there's a UTF-8 character right here: ă
|
||||
1
spec/fixtures/evil-files/file with spaces.txt
vendored
Normal file
1
spec/fixtures/evil-files/file with spaces.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
I am evil because there are spaces in my name
|
||||
1
spec/fixtures/evil-files/goddam
newlines
vendored
Normal file
1
spec/fixtures/evil-files/goddam
newlines
vendored
Normal 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
1
spec/fixtures/evil-files/quote".txt
vendored
Normal 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
1
spec/fixtures/evil-files/utfă.md
vendored
Normal file
@@ -0,0 +1 @@
|
||||
I am evil because there's a UTF-8 character in my name
|
||||
@@ -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])
|
||||
|
||||
94
spec/stdlib/child-process-spec.coffee
Normal file
94
spec/stdlib/child-process-spec.coffee
Normal 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"
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -18,7 +18,7 @@ class Cursor
|
||||
@setBufferPosition(bufferPosition) if bufferPosition
|
||||
|
||||
destroy: ->
|
||||
@editSession.removeAnchor(@anchor)
|
||||
@anchor.destroy()
|
||||
@editSession.removeCursor(this)
|
||||
@trigger 'destroy'
|
||||
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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: ->
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -21,6 +21,7 @@ class Selection
|
||||
if @cursor
|
||||
@cursor.off('.selection')
|
||||
@cursor.destroy()
|
||||
@anchor?.destroy()
|
||||
@editSession.removeSelection(this)
|
||||
@trigger 'destroy'
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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('') }
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,4 +3,5 @@ _ = require 'underscore'
|
||||
module.exports =
|
||||
class Command
|
||||
isAddress: -> false
|
||||
restoreSelections: false
|
||||
preserveSelections: false
|
||||
previewOperations: false
|
||||
@@ -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())
|
||||
|
||||
@@ -3,7 +3,7 @@ Range = require 'range'
|
||||
|
||||
module.exports =
|
||||
class CurrentSelectionAddress extends Address
|
||||
getRange: (editor, currentRange) ->
|
||||
currentRange
|
||||
getRange: (buffer, range) ->
|
||||
range
|
||||
|
||||
isRelative: -> true
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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'
|
||||
|
||||
30
src/extensions/command-panel/operation.coffee
Normal file
30
src/extensions/command-panel/operation.coffee
Normal 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()
|
||||
82
src/extensions/command-panel/preview-list.coffee
Normal file
82
src/extensions/command-panel/preview-list.coffee
Normal 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
2784
vendor/ack
vendored
Executable file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user