Merge pull request #179 from github/dev

Merge dev into master
This commit is contained in:
Kevin Sawicki
2013-02-05 13:26:40 -08:00
231 changed files with 5612 additions and 3341 deletions

7
.atom/config.cson Normal file
View File

@@ -0,0 +1,7 @@
'editor':
'fontSize': 16
'core':
'themes': [
'atom-dark-ui'
'atom-dark-syntax'
]

1
.atom/packages/README.md Normal file
View File

@@ -0,0 +1 @@
All packages in this directory will be automatically loaded

1
.atom/themes/README.md Normal file
View File

@@ -0,0 +1 @@
All themes in this directory will be automatically loaded

8
.atom/user.css Normal file
View File

@@ -0,0 +1,8 @@
/* User styles */
.tree-view {
}
.editor {
}

3
.gitmodules vendored
View File

@@ -43,3 +43,6 @@
[submodule "vendor/packages/property-list.tmbundle"]
path = vendor/packages/property-list.tmbundle
url = https://github.com/textmate/property-list.tmbundle.git
[submodule "vendor/packages/python.tmbundle"]
path = vendor/packages/python.tmbundle
url = https://github.com/textmate/python.tmbundle

1
.pairs
View File

@@ -5,6 +5,7 @@ pairs:
ks: Kevin Sawicki; kevin
jc: Jerry Cheung; jerry
bl: Brian Lopez; brian
jp: Justin Palmer; justin
email:
domain: github.com
#global: true

22
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,22 @@
# :rotating_light: Contributing to Atom :rotating_light:
## Issues
* Include screenshots and animated GIFs whenever possible, they are immensely
helpful
* Include the behavior you expected to happen and other places you've seen
that behavior such as Emacs, vi, Xcode, etc.
* Check the Console app for stack traces to include if reporting a crash
* Check the Dev tools (`alt-cmd-i`) for errors and stack traces to include
## Code
* Follow the [JavaScript](https://github.com/styleguide/javascript),
[CSS](https://github.com/styleguide/css),
and [Objective-C](https://github.com/github/objective-c-conventions)
styleguides
* Include thoughtfully worded [Jasmine](http://pivotal.github.com/jasmine/)
specs
* Style new elements in both the light and dark default themes when
appropriate
* New packages go in `src/packages/`
* Add 3rd-party packages by submoduling in `vendor/packages/`
* Commit messages are in the present tense

View File

@@ -21,7 +21,7 @@ Requirements
Atom is installed! Type `atom [path]` from the commmand line or find it in `/Applications/Atom.app`
## Your ~/.atom Directory
A basic ~/.atom directory is installed when you run `rake install`. Take a look at ~/.atom/atom.coffee for more information.
A basic ~/.atom directory is installed when you run `rake install`. Take a look at ~/.atom/user.coffee for more information.
## Basic Keyboard shortcuts
Atom doesn't have much in the way of menus yet. Use these keyboard shortcuts to
@@ -53,7 +53,7 @@ Most default OS X keybindings also work.
## Init Script
Atom will require `~/.atom/atom.coffee` whenever a window is opened or reloaded if it is present in your
Atom will require `~/.atom/user.coffee` whenever a window is opened or reloaded if it is present in your
home directory. This is a rudimentary jumping off point for your own customizations.
## Command Panel

View File

@@ -6,7 +6,7 @@ require 'erb'
desc "Build Atom via `xcodebuild`"
task :build => "create-project" do
command = "xcodebuild -target Atom configuration=Release SYMROOT=#{BUILD_DIR}"
command = "xcodebuild -target Atom -configuration Release SYMROOT=#{BUILD_DIR}"
output = `#{command}`
if $?.exitstatus != 0
$stderr.puts "Error #{$?.exitstatus}:\n#{output}"
@@ -76,14 +76,26 @@ task "create-dot-atom" do
dot_atom_template_path = ATOM_SRC_PATH + "/.atom"
replace_dot_atom = false
next if File.exists?(DOT_ATOM_PATH)
if File.exists?(DOT_ATOM_PATH)
user_config = "#{DOT_ATOM_PATH}/user.coffee"
old_user_config = "#{DOT_ATOM_PATH}/atom.coffee"
if File.exists?(old_user_config)
`mv #{old_user_config} #{user_config}`
puts "\033[32mRenamed #{old_user_config} to #{user_config}\033[0m"
end
next
end
`rm -rf "#{DOT_ATOM_PATH}"`
`mkdir "#{DOT_ATOM_PATH}"`
`cp "#{dot_atom_template_path}/atom.coffee" "#{DOT_ATOM_PATH}"`
`cp "#{dot_atom_template_path}/packages" "#{DOT_ATOM_PATH}"`
`cp "#{dot_atom_template_path}/user.coffee" "#{DOT_ATOM_PATH}"`
`cp "#{dot_atom_template_path}/user.css" "#{DOT_ATOM_PATH}"`
`cp -r "#{dot_atom_template_path}/packages" "#{DOT_ATOM_PATH}"`
`cp -r "#{ATOM_SRC_PATH}/themes" "#{DOT_ATOM_PATH}"`
`cp "#{ATOM_SRC_PATH}/vendor/themes/IR_Black.tmTheme" "#{DOT_ATOM_PATH}/themes"`
end
desc "Clone default bundles into vendor/bundles directory"
@@ -113,7 +125,7 @@ end
desc "Run the specs"
task :test => ["clean", "clone-default-bundles"] do
`pkill Atom`
Rake::Task["run"].invoke("--test")
Rake::Task["run"].invoke("--test --resource-path=#{ATOM_SRC_PATH}")
end
desc "Run the benchmarks"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -36,13 +36,13 @@
#define CEF_INCLUDE_CEF_VERSION_H_
#define CEF_VERSION_MAJOR 3
#define CEF_REVISION 1050
#define CEF_REVISION 1065
#define COPYRIGHT_YEAR 2013
#define CHROME_VERSION_MAJOR 25
#define CHROME_VERSION_MINOR 0
#define CHROME_VERSION_BUILD 1364
#define CHROME_VERSION_PATCH 29
#define CHROME_VERSION_PATCH 45
#define DO_MAKE_STRING(p) #p
#define MAKE_STRING(p) DO_MAKE_STRING(p)

View File

@@ -55,8 +55,8 @@ Or you can use `observeConfig` to track changes from a view object.
```coffeescript
class MyView extends View
initialize: ->
@observeConfig 'editor.lineHeight', (lineHeight) =>
@adjustLineHeight(lineHeight)
@observeConfig 'editor.fontSize', () =>
@adjustFontSize()
```
The `observeConfig` method will call the given callback immediately with the

View File

@@ -18,9 +18,9 @@ my-package/
index.coffee
```
**NOTE: NPM behavior is partially implemented until we get a working Node.js
**NOTE:** NPM behavior is partially implemented until we get a working Node.js
API built into Atom. The goal is to make Atom packages be a superset of NPM
packages**
packages
#### package.json

View File

@@ -9,8 +9,8 @@ gutter.
You can change the background color using the following CSS:
```css
.editor.focused .line.cursor-line,
.editor.focused .line-number.cursor-line {
.editor .line.cursor-line,
.editor .line-number.cursor-line {
background-color: green;
}
```
@@ -18,7 +18,7 @@ You can change the background color using the following CSS:
You can change the line number foreground color using the following CSS:
```css
.editor.focused .line-number.cursor-line {
.editor .line-number.cursor-line {
color: blue;
}
```

Binary file not shown.

View File

@@ -80,6 +80,9 @@ bool AtomCefClient::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
else if (name == "show") {
Show(browser);
}
else if (name == "toggleFullScreen") {
ToggleFullScreen(browser);
}
else {
return false;
}
@@ -141,6 +144,8 @@ bool AtomCefClient::OnKeyEvent(CefRefPtr<CefBrowser> browser,
ToggleDevTools(browser);
} else if (event.modifiers == EVENTFLAG_COMMAND_DOWN && event.unmodified_character == '`') {
FocusNextWindow();
} else if (event.modifiers == (EVENTFLAG_COMMAND_DOWN | EVENTFLAG_SHIFT_DOWN) && event.unmodified_character == '~') {
FocusPreviousWindow();
}
else {
return false;

View File

@@ -105,6 +105,7 @@ class AtomCefClient : public CefClient,
bool m_HandlePasteboardCommands = false;
void FocusNextWindow();
void FocusPreviousWindow();
void Open(std::string path);
void Open();
void OpenUnstable(std::string path);
@@ -123,6 +124,7 @@ class AtomCefClient : public CefClient,
void Exit(int status);
void Log(const char *message);
void Show(CefRefPtr<CefBrowser> browser);
void ToggleFullScreen(CefRefPtr<CefBrowser> browser);
IMPLEMENT_REFCOUNTING(AtomCefClient);
IMPLEMENT_LOCKING(AtomCefClient);

View File

@@ -23,6 +23,24 @@ void AtomCefClient::FocusNextWindow() {
}
}
void AtomCefClient::FocusPreviousWindow() {
NSArray *windows = [NSApp windows];
int count = [windows count];
int start = [windows indexOfObject:[NSApp keyWindow]];
int i = start;
while (true) {
i = i - 1;
if (i == 0) i = count -1;
if (i == start) break;
NSWindow *window = [windows objectAtIndex:i];
if ([window isVisible] && ![window isExcludedFromWindowsMenu]) {
[window makeKeyAndOrderFront:nil];
break;
}
}
}
void AtomCefClient::Open(std::string path) {
NSString *pathString = [NSString stringWithCString:path.c_str() encoding:NSUTF8StringEncoding];
[(AtomApplication *)[AtomApplication sharedApplication] open:pathString];
@@ -100,6 +118,10 @@ void AtomCefClient::Show(CefRefPtr<CefBrowser> browser) {
[windowController.webView setHidden:NO];
}
void AtomCefClient::ToggleFullScreen(CefRefPtr<CefBrowser> browser) {
[[browser->GetHost()->GetWindowHandle() window] toggleFullScreen:nil];
}
void AtomCefClient::ShowSaveDialog(int replyId, CefRefPtr<CefBrowser> browser) {
CefRefPtr<CefProcessMessage> replyMessage = CefProcessMessage::Create("reply");
CefRefPtr<CefListValue> replyArguments = replyMessage->GetArgumentList();

View File

@@ -16,10 +16,22 @@ class AtomCefRenderProcessHandler : public CefRenderProcessHandler {
virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
CefProcessId source_process,
CefRefPtr<CefProcessMessage> message) OVERRIDE;
virtual void OnWorkerContextCreated(int worker_id,
const CefString& url,
CefRefPtr<CefV8Context> context) OVERRIDE;
virtual void OnWorkerContextReleased(int worker_id,
const CefString& url,
CefRefPtr<CefV8Context> context) OVERRIDE;
virtual void OnWorkerUncaughtException(int worker_id,
const CefString& url,
CefRefPtr<CefV8Context> context,
CefRefPtr<CefV8Exception> exception,
CefRefPtr<CefV8StackTrace> stackTrace) OVERRIDE;
void Reload(CefRefPtr<CefBrowser> browser);
void Shutdown(CefRefPtr<CefBrowser> browser);
bool CallMessageReceivedHandler(CefRefPtr<CefV8Context> context, CefRefPtr<CefProcessMessage> message);
void InjectExtensionsIntoV8Context(CefRefPtr<CefV8Context> context);
IMPLEMENT_REFCOUNTING(AtomCefRenderProcessHandler);
};

View File

@@ -9,26 +9,44 @@
#import "path_watcher.h"
#include <iostream>
void AtomCefRenderProcessHandler::OnWebKitInitialized() {
new v8_extensions::Atom();
new v8_extensions::Native();
new v8_extensions::OnigRegExp();
new v8_extensions::OnigScanner();
new v8_extensions::Git();
new v8_extensions::Tags();
}
void AtomCefRenderProcessHandler::OnContextCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) {
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) {
InjectExtensionsIntoV8Context(context);
}
void AtomCefRenderProcessHandler::OnContextReleased(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) {
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) {
[PathWatcher removePathWatcherForContext:context];
}
void AtomCefRenderProcessHandler::OnWorkerContextCreated(int worker_id,
const CefString& url,
CefRefPtr<CefV8Context> context) {
InjectExtensionsIntoV8Context(context);
}
void AtomCefRenderProcessHandler::OnWorkerContextReleased(int worker_id,
const CefString& url,
CefRefPtr<CefV8Context> context) {
NSLog(@"Web worker context released");
}
void AtomCefRenderProcessHandler::OnWorkerUncaughtException(int worker_id,
const CefString& url,
CefRefPtr<CefV8Context> context,
CefRefPtr<CefV8Exception> exception,
CefRefPtr<CefV8StackTrace> stackTrace) {
std::string message = exception->GetMessage().ToString();
NSLog(@"Exception throw in worker thread %s", message.c_str());
}
bool AtomCefRenderProcessHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
CefProcessId source_process,
CefRefPtr<CefProcessMessage> message) {
@@ -100,3 +118,13 @@ bool AtomCefRenderProcessHandler::CallMessageReceivedHandler(CefRefPtr<CefV8Cont
return true;
}
}
void AtomCefRenderProcessHandler::InjectExtensionsIntoV8Context(CefRefPtr<CefV8Context> context) {
// these objects are deleted when the context removes all references to them
(new v8_extensions::Atom())->CreateContextBinding(context);
(new v8_extensions::Native())->CreateContextBinding(context);
(new v8_extensions::Git())->CreateContextBinding(context);
(new v8_extensions::OnigRegExp())->CreateContextBinding(context);
(new v8_extensions::OnigScanner())->CreateContextBinding(context);
(new v8_extensions::Tags())->CreateContextBinding(context);
}

View File

@@ -69,6 +69,7 @@
[self initWithBootstrapScript:@"window-bootstrap" background:YES alwaysUseBundleResourcePath:stable];
[self.window setFrame:NSMakeRect(0, 0, 0, 0) display:NO];
[self.window setExcludedFromWindowsMenu:YES];
[self.window setCollectionBehavior:NSWindowCollectionBehaviorStationary];
return self;
}

View File

@@ -5,7 +5,7 @@ namespace v8_extensions {
class Atom : public CefV8Handler {
public:
Atom();
void CreateContextBinding(CefRefPtr<CefV8Context> context);
virtual bool Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
@@ -14,5 +14,9 @@ namespace v8_extensions {
// Provide the reference counting implementation for this class.
IMPLEMENT_REFCOUNTING(Atom);
private:
Atom(Atom const&);
void operator=(Atom const&);
};
}

View File

@@ -1,9 +0,0 @@
(function () {
native function sendMessageToBrowserProcess(name, array);
this.atom = {
sendMessageToBrowserProcess: sendMessageToBrowserProcess
};
})();

View File

@@ -7,9 +7,14 @@
namespace v8_extensions {
Atom::Atom() : CefV8Handler() {
NSString *filePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"v8_extensions/atom.js"];
NSString *extensionCode = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
CefRegisterExtension("v8/atom", [extensionCode UTF8String], this);
}
void Atom::CreateContextBinding(CefRefPtr<CefV8Context> context) {
CefRefPtr<CefV8Value> function = CefV8Value::CreateFunction("sendMessageToBrowserProcess", this);
CefRefPtr<CefV8Value> atomObject = CefV8Value::CreateObject(NULL);
atomObject->SetValue("sendMessageToBrowserProcess", function, V8_PROPERTY_ATTRIBUTE_NONE);
CefRefPtr<CefV8Value> global = context->GetGlobal();
global->SetValue("atom", atomObject, V8_PROPERTY_ATTRIBUTE_NONE);
}
bool Atom::Execute(const CefString& name,

View File

@@ -2,19 +2,21 @@
#include "include/cef_v8.h"
namespace v8_extensions {
class Git : public CefV8Handler {
public:
Git();
void CreateContextBinding(CefRefPtr<CefV8Context> context);
virtual bool Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) OVERRIDE;
class Git : public CefV8Handler {
public:
Git();
virtual bool Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) OVERRIDE;
// Provide the reference counting implementation for this class.
IMPLEMENT_REFCOUNTING(Git);
};
// Provide the reference counting implementation for this class.
IMPLEMENT_REFCOUNTING(Git);
private:
Git(Git const&);
void operator=(Git const&);
};
}

View File

@@ -1,35 +0,0 @@
var $git = {};
(function() {
native function getRepository(pathInRepo);
native function getHead();
native function getPath();
native function getStatus(path);
native function isIgnored(path);
native function checkoutHead(path);
native function getDiffStats(path);
native function isSubmodule(path);
native function refreshIndex();
native function destroy();
function GitRepository(path) {
var repo = getRepository(path);
if (!repo)
throw new Error("No Git repository found searching path: " + path);
repo.constructor = GitRepository;
repo.__proto__ = GitRepository.prototype;
return repo;
}
GitRepository.prototype.getHead = getHead;
GitRepository.prototype.getPath = getPath;
GitRepository.prototype.getStatus = getStatus;
GitRepository.prototype.isIgnored = isIgnored;
GitRepository.prototype.checkoutHead = checkoutHead;
GitRepository.prototype.getDiffStats = getDiffStats;
GitRepository.prototype.isSubmodule = isSubmodule;
GitRepository.prototype.refreshIndex = refreshIndex;
GitRepository.prototype.destroy = destroy;
this.GitRepository = GitRepository;
})();

View File

@@ -4,264 +4,278 @@
namespace v8_extensions {
class GitRepository : public CefBase {
private:
git_repository *repo;
class GitRepository : public CefBase {
private:
git_repository *repo;
public:
GitRepository(const char *pathInRepo) {
if (git_repository_open_ext(&repo, pathInRepo, 0, NULL) != GIT_OK) {
repo = NULL;
public:
GitRepository(const char *pathInRepo) {
if (git_repository_open_ext(&repo, pathInRepo, 0, NULL) != GIT_OK) {
repo = NULL;
}
}
}
~GitRepository() {
Destroy();
}
void Destroy() {
if (Exists()) {
git_repository_free(repo);
repo = NULL;
~GitRepository() {
Destroy();
}
}
BOOL Exists() {
return repo != NULL;
}
void Destroy() {
if (Exists()) {
git_repository_free(repo);
repo = NULL;
}
}
CefRefPtr<CefV8Value> GetPath() {
return CefV8Value::CreateString(git_repository_path(repo));
}
BOOL Exists() {
return repo != NULL;
}
CefRefPtr<CefV8Value> GetHead() {
git_reference *head;
if (git_repository_head(&head, repo) == GIT_OK) {
if (git_repository_head_detached(repo) == 1) {
const git_oid* sha = git_reference_target(head);
if (sha) {
char oid[GIT_OID_HEXSZ + 1];
git_oid_tostr(oid, GIT_OID_HEXSZ + 1, sha);
git_reference_free(head);
return CefV8Value::CreateString(oid);
CefRefPtr<CefV8Value> GetPath() {
return CefV8Value::CreateString(git_repository_path(repo));
}
CefRefPtr<CefV8Value> GetHead() {
git_reference *head;
if (git_repository_head(&head, repo) == GIT_OK) {
if (git_repository_head_detached(repo) == 1) {
const git_oid* sha = git_reference_target(head);
if (sha) {
char oid[GIT_OID_HEXSZ + 1];
git_oid_tostr(oid, GIT_OID_HEXSZ + 1, sha);
git_reference_free(head);
return CefV8Value::CreateString(oid);
}
}
CefRefPtr<CefV8Value> result = CefV8Value::CreateString(git_reference_name(head));
git_reference_free(head);
return result;
}
CefRefPtr<CefV8Value> result = CefV8Value::CreateString(git_reference_name(head));
return CefV8Value::CreateNull();
}
CefRefPtr<CefV8Value> IsIgnored(const char *path) {
int ignored;
if (git_ignore_path_is_ignored(&ignored, repo, path) == GIT_OK) {
return CefV8Value::CreateBool(ignored == 1);
}
else {
return CefV8Value::CreateBool(false);
}
}
CefRefPtr<CefV8Value> GetStatus(const char *path) {
unsigned int status = 0;
if (git_status_file(&status, repo, path) == GIT_OK) {
return CefV8Value::CreateInt(status);
}
else {
return CefV8Value::CreateInt(0);
}
}
CefRefPtr<CefV8Value> CheckoutHead(const char *path) {
char *copiedPath = (char *)malloc(sizeof(char) * (strlen(path) + 1));
strcpy(copiedPath, path);
git_checkout_opts options = GIT_CHECKOUT_OPTS_INIT;
options.checkout_strategy = GIT_CHECKOUT_FORCE;
git_strarray paths;
paths.count = 1;
paths.strings = &copiedPath;
options.paths = paths;
int result = git_checkout_head(repo, &options);
free(copiedPath);
return CefV8Value::CreateBool(result == GIT_OK);
}
CefRefPtr<CefV8Value> GetDiffStats(const char *path) {
git_reference *head;
if (git_repository_head(&head, repo) != GIT_OK) {
return CefV8Value::CreateNull();
}
const git_oid* sha = git_reference_target(head);
git_commit *commit;
int commitStatus = git_commit_lookup(&commit, repo, sha);
git_reference_free(head);
return result;
}
if (commitStatus != GIT_OK) {
return CefV8Value::CreateNull();
}
return CefV8Value::CreateNull();
}
git_tree *tree;
int treeStatus = git_commit_tree(&tree, commit);
git_commit_free(commit);
if (treeStatus != GIT_OK) {
return CefV8Value::CreateNull();
}
CefRefPtr<CefV8Value> IsIgnored(const char *path) {
int ignored;
if (git_ignore_path_is_ignored(&ignored, repo, path) == GIT_OK) {
return CefV8Value::CreateBool(ignored == 1);
}
else {
return CefV8Value::CreateBool(false);
}
}
char *copiedPath = (char *)malloc(sizeof(char) * (strlen(path) + 1));
strcpy(copiedPath, path);
git_diff_options options = GIT_DIFF_OPTIONS_INIT;
git_strarray paths;
paths.count = 1;
paths.strings = &copiedPath;
options.pathspec = paths;
options.context_lines = 1;
options.flags = GIT_DIFF_DISABLE_PATHSPEC_MATCH;
CefRefPtr<CefV8Value> GetStatus(const char *path) {
unsigned int status = 0;
if (git_status_file(&status, repo, path) == GIT_OK) {
return CefV8Value::CreateInt(status);
}
else {
return CefV8Value::CreateInt(0);
}
}
git_diff_list *diffs;
int diffStatus = git_diff_tree_to_workdir(&diffs, repo, tree, &options);
free(copiedPath);
if (diffStatus != GIT_OK || git_diff_num_deltas(diffs) != 1) {
return CefV8Value::CreateNull();
}
CefRefPtr<CefV8Value> CheckoutHead(const char *path) {
char *copiedPath = (char *)malloc(sizeof(char) * (strlen(path) + 1));
strcpy(copiedPath, path);
git_checkout_opts options = GIT_CHECKOUT_OPTS_INIT;
options.checkout_strategy = GIT_CHECKOUT_FORCE;
git_strarray paths;
paths.count = 1;
paths.strings = &copiedPath;
options.paths = paths;
git_diff_patch *patch;
int patchStatus = git_diff_get_patch(&patch, NULL, diffs, 0);
git_diff_list_free(diffs);
if (patchStatus != GIT_OK) {
return CefV8Value::CreateNull();
}
int result = git_checkout_head(repo, &options);
free(copiedPath);
return CefV8Value::CreateBool(result == GIT_OK);
}
CefRefPtr<CefV8Value> GetDiffStats(const char *path) {
git_reference *head;
if (git_repository_head(&head, repo) != GIT_OK) {
return CefV8Value::CreateNull();
}
const git_oid* sha = git_reference_target(head);
git_commit *commit;
int commitStatus = git_commit_lookup(&commit, repo, sha);
git_reference_free(head);
if (commitStatus != GIT_OK) {
return CefV8Value::CreateNull();
}
git_tree *tree;
int treeStatus = git_commit_tree(&tree, commit);
git_commit_free(commit);
if (treeStatus != GIT_OK) {
return CefV8Value::CreateNull();
}
char *copiedPath = (char *)malloc(sizeof(char) * (strlen(path) + 1));
strcpy(copiedPath, path);
git_diff_options options = GIT_DIFF_OPTIONS_INIT;
git_strarray paths;
paths.count = 1;
paths.strings = &copiedPath;
options.pathspec = paths;
options.context_lines = 1;
options.flags = GIT_DIFF_DISABLE_PATHSPEC_MATCH;
git_diff_list *diffs;
int diffStatus = git_diff_tree_to_workdir(&diffs, repo, tree, &options);
free(copiedPath);
if (diffStatus != GIT_OK || git_diff_num_deltas(diffs) != 1) {
return CefV8Value::CreateNull();
}
git_diff_patch *patch;
int patchStatus = git_diff_get_patch(&patch, NULL, diffs, 0);
git_diff_list_free(diffs);
if (patchStatus != GIT_OK) {
return CefV8Value::CreateNull();
}
int added = 0;
int deleted = 0;
int hunks = git_diff_patch_num_hunks(patch);
for (int i = 0; i < hunks; i++) {
int lines = git_diff_patch_num_lines_in_hunk(patch, i);
for (int j = 0; j < lines; j++) {
char lineType;
if (git_diff_patch_get_line_in_hunk(&lineType, NULL, NULL, NULL, NULL, patch, i, j) == GIT_OK) {
switch (lineType) {
case GIT_DIFF_LINE_ADDITION:
added++;
break;
case GIT_DIFF_LINE_DELETION:
deleted++;
break;
int added = 0;
int deleted = 0;
int hunks = git_diff_patch_num_hunks(patch);
for (int i = 0; i < hunks; i++) {
int lines = git_diff_patch_num_lines_in_hunk(patch, i);
for (int j = 0; j < lines; j++) {
char lineType;
if (git_diff_patch_get_line_in_hunk(&lineType, NULL, NULL, NULL, NULL, patch, i, j) == GIT_OK) {
switch (lineType) {
case GIT_DIFF_LINE_ADDITION:
added++;
break;
case GIT_DIFF_LINE_DELETION:
deleted++;
break;
}
}
}
}
}
git_diff_patch_free(patch);
git_diff_patch_free(patch);
CefRefPtr<CefV8Value> result = CefV8Value::CreateObject(NULL);
result->SetValue("added", CefV8Value::CreateInt(added), V8_PROPERTY_ATTRIBUTE_NONE);
result->SetValue("deleted", CefV8Value::CreateInt(deleted), V8_PROPERTY_ATTRIBUTE_NONE);
return result;
CefRefPtr<CefV8Value> result = CefV8Value::CreateObject(NULL);
result->SetValue("added", CefV8Value::CreateInt(added), V8_PROPERTY_ATTRIBUTE_NONE);
result->SetValue("deleted", CefV8Value::CreateInt(deleted), V8_PROPERTY_ATTRIBUTE_NONE);
return result;
}
CefRefPtr<CefV8Value> IsSubmodule(const char *path) {
BOOL isSubmodule = false;
git_index* index;
if (git_repository_index(&index, repo) == GIT_OK) {
const git_index_entry *entry = git_index_get_bypath(index, path, 0);
isSubmodule = entry != NULL && (entry->mode & S_IFMT) == GIT_FILEMODE_COMMIT;
git_index_free(index);
}
return CefV8Value::CreateBool(isSubmodule);
}
void RefreshIndex() {
git_index* index;
if (git_repository_index(&index, repo) == GIT_OK) {
git_index_read(index);
git_index_free(index);
}
}
IMPLEMENT_REFCOUNTING(GitRepository);
};
Git::Git() : CefV8Handler() {
}
CefRefPtr<CefV8Value> IsSubmodule(const char *path) {
BOOL isSubmodule = false;
git_index* index;
if (git_repository_index(&index, repo) == GIT_OK) {
const git_index_entry *entry = git_index_get_bypath(index, path, 0);
isSubmodule = entry != NULL && (entry->mode & S_IFMT) == GIT_FILEMODE_COMMIT;
git_index_free(index);
void Git::CreateContextBinding(CefRefPtr<CefV8Context> context) {
const char* methodNames[] = {
"getRepository", "getHead", "getPath", "isIgnored", "getStatus", "checkoutHead",
"getDiffStats", "isSubmodule", "refreshIndex", "destroy"
};
CefRefPtr<CefV8Value> nativeObject = CefV8Value::CreateObject(NULL);
int arrayLength = sizeof(methodNames) / sizeof(const char *);
for (int i = 0; i < arrayLength; i++) {
const char *functionName = methodNames[i];
CefRefPtr<CefV8Value> function = CefV8Value::CreateFunction(functionName, this);
nativeObject->SetValue(functionName, function, V8_PROPERTY_ATTRIBUTE_NONE);
}
return CefV8Value::CreateBool(isSubmodule);
CefRefPtr<CefV8Value> global = context->GetGlobal();
global->SetValue("$git", nativeObject, V8_PROPERTY_ATTRIBUTE_NONE);
}
void RefreshIndex() {
git_index* index;
if (git_repository_index(&index, repo) == GIT_OK) {
git_index_read(index);
git_index_free(index);
bool Git::Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) {
if (name == "getRepository") {
GitRepository *repository = new GitRepository(arguments[0]->GetStringValue().ToString().c_str());
if (repository->Exists()) {
CefRefPtr<CefBase> userData = repository;
retval = CefV8Value::CreateObject(NULL);
retval->SetUserData(userData);
} else {
retval = CefV8Value::CreateNull();
}
return true;
}
if (name == "getHead") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
retval = userData->GetHead();
return true;
}
if (name == "getPath") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
retval = userData->GetPath();
return true;
}
if (name == "isIgnored") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
retval = userData->IsIgnored(arguments[0]->GetStringValue().ToString().c_str());
return true;
}
if (name == "getStatus") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
retval = userData->GetStatus(arguments[0]->GetStringValue().ToString().c_str());
return true;
}
if (name == "checkoutHead") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
retval = userData->CheckoutHead(arguments[0]->GetStringValue().ToString().c_str());
return true;
}
if (name == "getDiffStats") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
retval = userData->GetDiffStats(arguments[0]->GetStringValue().ToString().c_str());
return true;
}
if (name == "isSubmodule") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
retval = userData->IsSubmodule(arguments[0]->GetStringValue().ToString().c_str());
return true;
}
if (name == "refreshIndex") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
userData->RefreshIndex();
return true;
}
if (name == "destroy") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
userData->Destroy();
return true;
}
return false;
}
IMPLEMENT_REFCOUNTING(GitRepository);
};
Git::Git() : CefV8Handler() {
NSString *filePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"v8_extensions/git.js"];
NSString *extensionCode = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
CefRegisterExtension("v8/git", [extensionCode UTF8String], this);
}
bool Git::Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) {
if (name == "getRepository") {
GitRepository *repository = new GitRepository(arguments[0]->GetStringValue().ToString().c_str());
if (repository->Exists()) {
CefRefPtr<CefBase> userData = repository;
retval = CefV8Value::CreateObject(NULL);
retval->SetUserData(userData);
} else {
retval = CefV8Value::CreateNull();
}
return true;
}
if (name == "getHead") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
retval = userData->GetHead();
return true;
}
if (name == "getPath") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
retval = userData->GetPath();
return true;
}
if (name == "isIgnored") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
retval = userData->IsIgnored(arguments[0]->GetStringValue().ToString().c_str());
return true;
}
if (name == "getStatus") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
retval = userData->GetStatus(arguments[0]->GetStringValue().ToString().c_str());
return true;
}
if (name == "checkoutHead") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
retval = userData->CheckoutHead(arguments[0]->GetStringValue().ToString().c_str());
return true;
}
if (name == "getDiffStats") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
retval = userData->GetDiffStats(arguments[0]->GetStringValue().ToString().c_str());
return true;
}
if (name == "isSubmodule") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
retval = userData->IsSubmodule(arguments[0]->GetStringValue().ToString().c_str());
return true;
}
if (name == "refreshIndex") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
userData->RefreshIndex();
return true;
}
if (name == "destroy") {
GitRepository *userData = (GitRepository *)object->GetUserData().get();
userData->Destroy();
return true;
}
return false;
}
}

View File

@@ -1,24 +1,23 @@
#include "include/cef_base.h"
#include "include/cef_v8.h"
namespace v8_extensions {
class Native : public CefV8Handler {
public:
Native();
class Native : public CefV8Handler {
public:
Native();
void CreateContextBinding(CefRefPtr<CefV8Context> context);
virtual bool Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) OVERRIDE;
virtual bool Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) OVERRIDE;
// Provide the reference counting implementation for this class.
IMPLEMENT_REFCOUNTING(Native);
private:
std::string windowState;
};
// Provide the reference counting implementation for this class.
IMPLEMENT_REFCOUNTING(Native);
private:
Native(Native const&);
void operator=(Native const&);
};
}

View File

@@ -1,85 +0,0 @@
var $native = {};
(function() {
native function exists(path);
$native.exists = exists;
native function read(path);
$native.read = read;
native function write(path, content);
$native.write = write;
native function absolute(path);
$native.absolute = absolute;
native function traverseTree(path, onFile, onDirectory);
$native.traverseTree = traverseTree;
native function getAllFilePathsAsync(path, callback);
$native.getAllFilePathsAsync = getAllFilePathsAsync;
native function isFile(path);
$native.isFile = isFile;
native function isDirectory(path);
$native.isDirectory = isDirectory;
native function remove(path);
$native.remove = remove;
native function open(path);
$native.open = open;
native function quit();
$native.quit = quit;
native function writeToPasteboard(text);
$native.writeToPasteboard = writeToPasteboard;
native function readFromPasteboard();
$native.readFromPasteboard = readFromPasteboard;
native function watchPath(path);
$native.watchPath = watchPath;
native function unwatchPath(path, callbackId);
$native.unwatchPath = unwatchPath;
native function getWatchedPaths();
$native.getWatchedPaths = getWatchedPaths;
native function unwatchAllPaths();
$native.unwatchAllPaths = unwatchAllPaths;
native function makeDirectory(path);
$native.makeDirectory = makeDirectory;
native function move(sourcePath, targetPath);
$native.move = move;
native function moveToTrash(path);
$native.moveToTrash = moveToTrash;
native function reload();
$native.reload = reload;
native function lastModified(path);
$native.lastModified = lastModified;
native function md5ForPath(path);
$native.md5ForPath = md5ForPath;
native function exec(command, options, callback);
$native.exec = exec;
native function getPlatform();
$native.getPlatform = getPlatform;
native function setWindowState(state);
$native.setWindowState = setWindowState;
native function getWindowState();
$native.getWindowState = getWindowState;
})();

View File

@@ -8,496 +8,522 @@
#import "path_watcher.h"
#import <iostream>
#include <fts.h>
static std::string windowState = "{}";
static NSLock *windowStateLock = [[NSLock alloc] init];
namespace v8_extensions {
using namespace std;
NSString *stringFromCefV8Value(const CefRefPtr<CefV8Value>& value) {
std::string cc_value = value->GetStringValue().ToString();
return [NSString stringWithUTF8String:cc_value.c_str()];
}
NSString *stringFromCefV8Value(const CefRefPtr<CefV8Value>& value);
void throwException(const CefRefPtr<CefV8Value>& global, CefRefPtr<CefV8Exception> exception, NSString *message);
void throwException(const CefRefPtr<CefV8Value>& global, CefRefPtr<CefV8Exception> exception, NSString *message) {
CefV8ValueList arguments;
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);
}
Native::Native() : CefV8Handler() {
NSString *filePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"v8_extensions/native.js"];
NSString *extensionCode = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
windowState = "{}";
CefRegisterExtension("v8/native", [extensionCode UTF8String], this);
}
bool Native::Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) {
if (name == "exists") {
std::string cc_value = arguments[0]->GetStringValue().ToString();
const char *path = cc_value.c_str();
retval = CefV8Value::CreateBool(access(path, F_OK) == 0);
return true;
Native::Native() : CefV8Handler() {
}
else if (name == "read") {
NSString *path = stringFromCefV8Value(arguments[0]);
NSError *error = nil;
NSStringEncoding *encoding = nil;
NSString *contents = [NSString stringWithContentsOfFile:path usedEncoding:encoding error:&error];
void Native::CreateContextBinding(CefRefPtr<CefV8Context> context) {
const char* methodNames[] = {
"exists", "read", "write", "absolute", "getAllFilePathsAsync", "traverseTree", "isDirectory",
"isFile", "remove", "writeToPasteboard", "readFromPasteboard", "quit", "watchPath", "unwatchPath",
"getWatchedPaths", "unwatchAllPaths", "makeDirectory", "move", "moveToTrash", "reload", "lastModified",
"md5ForPath", "exec", "getPlatform", "setWindowState", "getWindowState"
};
NSError *binaryFileError = nil;
if (error) {
contents = [NSString stringWithContentsOfFile:path encoding:NSASCIIStringEncoding error:&binaryFileError];
CefRefPtr<CefV8Value> nativeObject = CefV8Value::CreateObject(NULL);
int arrayLength = sizeof(methodNames) / sizeof(const char *);
for (int i = 0; i < arrayLength; i++) {
const char *functionName = methodNames[i];
CefRefPtr<CefV8Value> function = CefV8Value::CreateFunction(functionName, this);
nativeObject->SetValue(functionName, function, V8_PROPERTY_ATTRIBUTE_NONE);
}
if (binaryFileError) {
exception = [[binaryFileError localizedDescription] UTF8String];
}
else {
retval = CefV8Value::CreateString([contents UTF8String]);
}
return true;
CefRefPtr<CefV8Value> global = context->GetGlobal();
global->SetValue("$native", nativeObject, V8_PROPERTY_ATTRIBUTE_NONE);
}
else if (name == "write") {
NSString *path = stringFromCefV8Value(arguments[0]);
NSString *content = stringFromCefV8Value(arguments[1]);
NSFileManager *fm = [NSFileManager defaultManager];
bool Native::Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) {
if (name == "exists") {
std::string cc_value = arguments[0]->GetStringValue().ToString();
const char *path = cc_value.c_str();
retval = CefV8Value::CreateBool(access(path, F_OK) == 0);
// Create parent directories if they don't exist
BOOL exists = [fm fileExistsAtPath:[path stringByDeletingLastPathComponent] isDirectory:nil];
if (!exists) {
[fm createDirectoryAtPath:[path stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
return true;
}
else if (name == "read") {
NSString *path = stringFromCefV8Value(arguments[0]);
NSError *error = nil;
BOOL success = [content writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error];
NSError *error = nil;
NSStringEncoding *encoding = nil;
NSString *contents = [NSString stringWithContentsOfFile:path usedEncoding:encoding error:&error];
if (error) {
exception = [[error localizedDescription] UTF8String];
NSError *binaryFileError = nil;
if (error) {
contents = [NSString stringWithContentsOfFile:path encoding:NSASCIIStringEncoding error:&binaryFileError];
}
if (binaryFileError) {
exception = [[binaryFileError localizedDescription] UTF8String];
}
else {
retval = CefV8Value::CreateString([contents UTF8String]);
}
return true;
}
else if (!success) {
std::string exception = "Cannot write to '";
exception += [path UTF8String];
exception += "'";
else if (name == "write") {
NSString *path = stringFromCefV8Value(arguments[0]);
NSString *content = stringFromCefV8Value(arguments[1]);
NSFileManager *fm = [NSFileManager defaultManager];
// Create parent directories if they don't exist
BOOL exists = [fm fileExistsAtPath:[path stringByDeletingLastPathComponent] isDirectory:nil];
if (!exists) {
[fm createDirectoryAtPath:[path stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
}
NSError *error = nil;
BOOL success = [content writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (error) {
exception = [[error localizedDescription] UTF8String];
}
else if (!success) {
std::string exception = "Cannot write to '";
exception += [path UTF8String];
exception += "'";
}
return true;
}
else if (name == "absolute") {
NSString *path = stringFromCefV8Value(arguments[0]);
return true;
}
else if (name == "absolute") {
NSString *path = stringFromCefV8Value(arguments[0]);
path = [path stringByStandardizingPath];
if ([path characterAtIndex:0] == '/') {
retval = CefV8Value::CreateString([path UTF8String]);
}
path = [path stringByStandardizingPath];
if ([path characterAtIndex:0] == '/') {
retval = CefV8Value::CreateString([path UTF8String]);
return true;
}
else if (name == "getAllFilePathsAsync") {
std::string argument = arguments[0]->GetStringValue().ToString();
CefRefPtr<CefV8Value> callback = arguments[1];
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
return true;
}
else if (name == "getAllFilePathsAsync") {
std::string argument = arguments[0]->GetStringValue().ToString();
CefRefPtr<CefV8Value> callback = arguments[1];
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
int rootPathLength = argument.size() + 1;
char rootPath[rootPathLength];
strcpy(rootPath, argument.c_str());
char * const treePaths[] = {rootPath, NULL};
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
FTS *tree = fts_open(treePaths, FTS_COMFOLLOW | FTS_PHYSICAL| FTS_NOCHDIR | FTS_NOSTAT, NULL);
std::vector<std::string> paths;
if (tree != NULL) {
FTSENT *entry;
int arrayIndex = 0;
while ((entry = fts_read(tree)) != NULL) {
if (entry->fts_level == 0) {
continue;
}
bool isFile = entry->fts_info == FTS_NSOK;
if (!isFile) {
continue;
}
int pathLength = entry->fts_pathlen - rootPathLength;
char relative[pathLength + 1];
relative[pathLength] = '\0';
strncpy(relative, entry->fts_path + rootPathLength, pathLength);
paths.push_back(relative);
}
}
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
context->Enter();
CefRefPtr<CefV8Value> v8Paths = CefV8Value::CreateArray(paths.size());
for (int i = 0; i < paths.size(); i++) {
v8Paths->SetValue(i, CefV8Value::CreateString(paths[i]));
}
CefV8ValueList callbackArgs;
callbackArgs.push_back(v8Paths);
callback->ExecuteFunction(callback, callbackArgs);
context->Exit();
});
});
return true;
}
else if (name == "traverseTree") {
std::string argument = arguments[0]->GetStringValue().ToString();
int rootPathLength = argument.size() + 1;
char rootPath[rootPathLength];
strcpy(rootPath, argument.c_str());
char * const treePaths[] = {rootPath, NULL};
char * const paths[] = {rootPath, NULL};
FTS *tree = fts_open(treePaths, FTS_COMFOLLOW | FTS_PHYSICAL| FTS_NOCHDIR | FTS_NOSTAT, NULL);
std::vector<std::string> paths;
FTS *tree = fts_open(paths, FTS_COMFOLLOW | FTS_PHYSICAL| FTS_NOCHDIR | FTS_NOSTAT, NULL);
if (tree == NULL) {
return true;
}
if (tree != NULL) {
FTSENT *entry;
int arrayIndex = 0;
while ((entry = fts_read(tree)) != NULL) {
if (entry->fts_level == 0) {
continue;
CefRefPtr<CefV8Value> onFile = arguments[1];
CefRefPtr<CefV8Value> onDir = arguments[2];
CefV8ValueList args;
FTSENT *entry;
while ((entry = fts_read(tree)) != NULL) {
if (entry->fts_level == 0) {
continue;
}
bool isFile = entry->fts_info == FTS_NSOK;
bool isDir = entry->fts_info == FTS_D;
if (!isFile && !isDir) {
continue;
}
int pathLength = entry->fts_pathlen - rootPathLength;
char relative[pathLength + 1];
relative[pathLength] = '\0';
strncpy(relative, entry->fts_path + rootPathLength, pathLength);
args.clear();
args.push_back(CefV8Value::CreateString(relative));
if (isFile) {
onFile->ExecuteFunction(onFile, args);
}
else {
CefRefPtr<CefV8Value> enterDir = onDir->ExecuteFunction(onDir, args);
if(enterDir != NULL && !enterDir->GetBoolValue()) {
fts_set(tree, entry, FTS_SKIP);
}
bool isFile = entry->fts_info == FTS_NSOK;
if (!isFile) {
continue;
}
int pathLength = entry->fts_pathlen - rootPathLength;
char relative[pathLength + 1];
relative[pathLength] = '\0';
strncpy(relative, entry->fts_path + rootPathLength, pathLength);
paths.push_back(relative);
}
}
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
context->Enter();
CefRefPtr<CefV8Value> v8Paths = CefV8Value::CreateArray(paths.size());
for (int i = 0; i < paths.size(); i++) {
v8Paths->SetValue(i, CefV8Value::CreateString(paths[i]));
}
CefV8ValueList callbackArgs;
callbackArgs.push_back(v8Paths);
callback->ExecuteFunction(callback, callbackArgs);
context->Exit();
});
});
return true;
}
else if (name == "traverseTree") {
std::string argument = arguments[0]->GetStringValue().ToString();
int rootPathLength = argument.size() + 1;
char rootPath[rootPathLength];
strcpy(rootPath, argument.c_str());
char * const paths[] = {rootPath, NULL};
return true;
}
else if (name == "isDirectory") {
NSString *path = stringFromCefV8Value(arguments[0]);
FTS *tree = fts_open(paths, FTS_COMFOLLOW | FTS_PHYSICAL| FTS_NOCHDIR | FTS_NOSTAT, NULL);
if (tree == NULL) {
BOOL isDir = false;
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir];
retval = CefV8Value::CreateBool(exists && isDir);
return true;
}
else if (name == "isFile") {
NSString *path = stringFromCefV8Value(arguments[0]);
BOOL isDir = false;
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir];
retval = CefV8Value::CreateBool(exists && !isDir);
return true;
}
else if (name == "remove") {
NSString *path = stringFromCefV8Value(arguments[0]);
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:path error:&error];
if (error) {
exception = [[error localizedDescription] UTF8String];
}
return true;
}
else if (name == "writeToPasteboard") {
NSString *text = stringFromCefV8Value(arguments[0]);
NSPasteboard *pb = [NSPasteboard generalPasteboard];
[pb declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] owner:nil];
[pb setString:text forType:NSStringPboardType];
return true;
}
else if (name == "readFromPasteboard") {
NSPasteboard *pb = [NSPasteboard generalPasteboard];
NSArray *results = [pb readObjectsForClasses:[NSArray arrayWithObjects:[NSString class], nil] options:nil];
if (results) {
retval = CefV8Value::CreateString([[results objectAtIndex:0] UTF8String]);
}
return true;
}
else if (name == "quit") {
[NSApp terminate:nil];
return true;
}
else if (name == "watchPath") {
NSString *path = stringFromCefV8Value(arguments[0]);
CefRefPtr<CefV8Value> function = arguments[1];
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
WatchCallback callback = ^(NSString *eventType, NSString *path) {
context->Enter();
CefV8ValueList args;
args.push_back(CefV8Value::CreateString(std::string([eventType UTF8String], [eventType lengthOfBytesUsingEncoding:NSUTF8StringEncoding])));
args.push_back(CefV8Value::CreateString(std::string([path UTF8String], [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding])));
function->ExecuteFunction(function, args);
context->Exit();
};
PathWatcher *pathWatcher = [PathWatcher pathWatcherForContext:CefV8Context::GetCurrentContext()];
NSString *watchId = [pathWatcher watchPath:path callback:[[callback copy] autorelease]];
if (watchId) {
retval = CefV8Value::CreateString([watchId UTF8String]);
}
else {
exception = std::string("Failed to watch path '") + std::string([path UTF8String]) + std::string("' (it may not exist)");
}
return true;
}
else if (name == "unwatchPath") {
NSString *path = stringFromCefV8Value(arguments[0]);
NSString *callbackId = stringFromCefV8Value(arguments[1]);
NSError *error = nil;
PathWatcher *pathWatcher = [PathWatcher pathWatcherForContext:CefV8Context::GetCurrentContext()];
[pathWatcher unwatchPath:path callbackId:callbackId error:&error];
if (error) {
exception = [[error localizedDescription] UTF8String];
}
return true;
}
else if (name == "getWatchedPaths") {
PathWatcher *pathWatcher = [PathWatcher pathWatcherForContext:CefV8Context::GetCurrentContext()];
NSArray *paths = [pathWatcher watchedPaths];
CefRefPtr<CefV8Value> pathsArray = CefV8Value::CreateArray([paths count]);
for (int i = 0; i < [paths count]; i++) {
CefRefPtr<CefV8Value> path = CefV8Value::CreateString([[paths objectAtIndex:i] UTF8String]);
pathsArray->SetValue(i, path);
}
retval = pathsArray;
return true;
}
else if (name == "unwatchAllPaths") {
PathWatcher *pathWatcher = [PathWatcher pathWatcherForContext:CefV8Context::GetCurrentContext()];
[pathWatcher unwatchAllPaths];
return true;
}
else if (name == "makeDirectory") {
NSString *path = stringFromCefV8Value(arguments[0]);
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error = nil;
[fm createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:&error];
if (error) {
exception = [[error localizedDescription] UTF8String];
}
return true;
}
else if (name == "move") {
NSString *sourcePath = stringFromCefV8Value(arguments[0]);
NSString *targetPath = stringFromCefV8Value(arguments[1]);
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error = nil;
[fm moveItemAtPath:sourcePath toPath:targetPath error:&error];
if (error) {
exception = [[error localizedDescription] UTF8String];
}
return true;
}
else if (name == "moveToTrash") {
NSString *sourcePath = stringFromCefV8Value(arguments[0]);
bool success = [[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation
source:[sourcePath stringByDeletingLastPathComponent]
destination:@""
files:[NSArray arrayWithObject:[sourcePath lastPathComponent]]
tag:nil];
if (!success) {
std::string exception = "Can not move ";
exception += [sourcePath UTF8String];
exception += " to trash.";
}
return true;
}
else if (name == "reload") {
CefV8Context::GetCurrentContext()->GetBrowser()->ReloadIgnoreCache();
}
else if (name == "lastModified") {
NSString *path = stringFromCefV8Value(arguments[0]);
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error = nil;
NSDictionary *attributes = [fm attributesOfItemAtPath:path error:&error];
if (error) {
exception = [[error localizedDescription] UTF8String];
}
NSDate *lastModified = [attributes objectForKey:NSFileModificationDate];
retval = CefV8Value::CreateDate(CefTime([lastModified timeIntervalSince1970]));
return true;
}
else if (name == "md5ForPath") {
NSString *path = stringFromCefV8Value(arguments[0]);
unsigned char outputData[CC_MD5_DIGEST_LENGTH];
NSData *inputData = [[NSData alloc] initWithContentsOfFile:path];
CC_MD5([inputData bytes], [inputData length], outputData);
[inputData release];
NSMutableString *hash = [[NSMutableString alloc] init];
for (NSUInteger i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
[hash appendFormat:@"%02x", outputData[i]];
}
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)(NSString *contents, CefRefPtr<CefV8Value> function) = nil;
void (^taskTerminatedHandle)(NSString *output, NSString *errorOutput) = nil;
outputHandle = ^(NSString *contents, CefRefPtr<CefV8Value> function) {
context->Enter();
CefV8ValueList args;
args.push_back(CefV8Value::CreateString(std::string([contents UTF8String], [contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding])));
CefRefPtr<CefV8Value> retval = function->ExecuteFunction(function, args);
if (function->HasException()) {
throwException(context->GetGlobal(), function->GetException(), @"Error thrown in OutputHandle");
}
context->Exit();
};
taskTerminatedHandle = ^(NSString *output, NSString *errorOutput) {
context->Enter();
CefV8ValueList args;
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);
if (callback->HasException()) {
throwException(context->GetGlobal(), callback->GetException(), @"Error thrown in TaskTerminatedHandle");
}
context->Exit();
stdout.fileHandleForReading.writeabilityHandler = nil;
stderr.fileHandleForReading.writeabilityHandler = nil;
};
task.terminationHandler = ^(NSTask *) {
NSString *output = [[NSString alloc] initWithData:[[stdout fileHandleForReading] readDataToEndOfFile] encoding:NSUTF8StringEncoding];
NSString *errorOutput = [[NSString alloc] initWithData:[[stderr fileHandleForReading] readDataToEndOfFile] encoding:NSUTF8StringEncoding];
dispatch_sync(dispatch_get_main_queue(), ^() {
taskTerminatedHandle(output, errorOutput);
});
[output release];
[errorOutput release];
};
CefRefPtr<CefV8Value> stdoutFunction = options->GetValue("stdout");
if (stdoutFunction->IsFunction()) {
stdout.fileHandleForReading.writeabilityHandler = ^(NSFileHandle *fileHandle) {
NSData *data = [fileHandle availableData];
NSString *contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
dispatch_sync(dispatch_get_main_queue(), ^() {
outputHandle(contents, stdoutFunction);
});
[contents release];
};
}
CefRefPtr<CefV8Value> stderrFunction = options->GetValue("stderr");
if (stderrFunction->IsFunction()) {
stderr.fileHandleForReading.writeabilityHandler = ^(NSFileHandle *fileHandle) {
NSData *data = [fileHandle availableData];
NSString *contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
dispatch_sync(dispatch_get_main_queue(), ^() {
outputHandle(contents, stderrFunction);
});
[contents release];
};
}
[task launch];
return true;
}
else if (name == "getPlatform") {
retval = CefV8Value::CreateString("mac");
return true;
}
CefRefPtr<CefV8Value> onFile = arguments[1];
CefRefPtr<CefV8Value> onDir = arguments[2];
CefV8ValueList args;
FTSENT *entry;
while ((entry = fts_read(tree)) != NULL) {
if (entry->fts_level == 0) {
continue;
}
bool isFile = entry->fts_info == FTS_NSOK;
bool isDir = entry->fts_info == FTS_D;
if (!isFile && !isDir) {
continue;
}
int pathLength = entry->fts_pathlen - rootPathLength;
char relative[pathLength + 1];
relative[pathLength] = '\0';
strncpy(relative, entry->fts_path + rootPathLength, pathLength);
args.clear();
args.push_back(CefV8Value::CreateString(relative));
if (isFile) {
onFile->ExecuteFunction(onFile, args);
}
else {
CefRefPtr<CefV8Value> enterDir = onDir->ExecuteFunction(onDir, args);
if(enterDir != NULL && !enterDir->GetBoolValue()) {
fts_set(tree, entry, FTS_SKIP);
}
}
else if (name == "setWindowState") {
[windowStateLock lock];
windowState = arguments[0]->GetStringValue().ToString();
[windowStateLock unlock];
return true;
}
return true;
}
else if (name == "isDirectory") {
NSString *path = stringFromCefV8Value(arguments[0]);
BOOL isDir = false;
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir];
retval = CefV8Value::CreateBool(exists && isDir);
return true;
}
else if (name == "isFile") {
NSString *path = stringFromCefV8Value(arguments[0]);
BOOL isDir = false;
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir];
retval = CefV8Value::CreateBool(exists && !isDir);
return true;
}
else if (name == "remove") {
NSString *path = stringFromCefV8Value(arguments[0]);
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:path error:&error];
if (error) {
exception = [[error localizedDescription] UTF8String];
else if (name == "getWindowState") {
[windowStateLock lock];
retval = CefV8Value::CreateString(windowState);
[windowStateLock unlock];
return true;
}
return true;
}
else if (name == "writeToPasteboard") {
NSString *text = stringFromCefV8Value(arguments[0]);
return false;
};
NSPasteboard *pb = [NSPasteboard generalPasteboard];
[pb declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] owner:nil];
[pb setString:text forType:NSStringPboardType];
return true;
}
else if (name == "readFromPasteboard") {
NSPasteboard *pb = [NSPasteboard generalPasteboard];
NSArray *results = [pb readObjectsForClasses:[NSArray arrayWithObjects:[NSString class], nil] options:nil];
if (results) {
retval = CefV8Value::CreateString([[results objectAtIndex:0] UTF8String]);
}
return true;
}
else if (name == "quit") {
[NSApp terminate:nil];
return true;
}
else if (name == "watchPath") {
NSString *path = stringFromCefV8Value(arguments[0]);
CefRefPtr<CefV8Value> function = arguments[1];
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
WatchCallback callback = ^(NSString *eventType, NSString *path) {
context->Enter();
CefV8ValueList args;
args.push_back(CefV8Value::CreateString(std::string([eventType UTF8String], [eventType lengthOfBytesUsingEncoding:NSUTF8StringEncoding])));
args.push_back(CefV8Value::CreateString(std::string([path UTF8String], [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding])));
function->ExecuteFunction(function, args);
context->Exit();
};
PathWatcher *pathWatcher = [PathWatcher pathWatcherForContext:CefV8Context::GetCurrentContext()];
NSString *watchId = [pathWatcher watchPath:path callback:[[callback copy] autorelease]];
if (watchId) {
retval = CefV8Value::CreateString([watchId UTF8String]);
}
else {
exception = std::string("Failed to watch path '") + std::string([path UTF8String]) + std::string("' (it may not exist)");
}
return true;
}
else if (name == "unwatchPath") {
NSString *path = stringFromCefV8Value(arguments[0]);
NSString *callbackId = stringFromCefV8Value(arguments[1]);
NSError *error = nil;
PathWatcher *pathWatcher = [PathWatcher pathWatcherForContext:CefV8Context::GetCurrentContext()];
[pathWatcher unwatchPath:path callbackId:callbackId error:&error];
if (error) {
exception = [[error localizedDescription] UTF8String];
}
return true;
}
else if (name == "getWatchedPaths") {
PathWatcher *pathWatcher = [PathWatcher pathWatcherForContext:CefV8Context::GetCurrentContext()];
NSArray *paths = [pathWatcher watchedPaths];
CefRefPtr<CefV8Value> pathsArray = CefV8Value::CreateArray([paths count]);
for (int i = 0; i < [paths count]; i++) {
CefRefPtr<CefV8Value> path = CefV8Value::CreateString([[paths objectAtIndex:i] UTF8String]);
pathsArray->SetValue(i, path);
}
retval = pathsArray;
return true;
}
else if (name == "unwatchAllPaths") {
PathWatcher *pathWatcher = [PathWatcher pathWatcherForContext:CefV8Context::GetCurrentContext()];
[pathWatcher unwatchAllPaths];
return true;
}
else if (name == "makeDirectory") {
NSString *path = stringFromCefV8Value(arguments[0]);
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error = nil;
[fm createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:&error];
if (error) {
exception = [[error localizedDescription] UTF8String];
}
return true;
}
else if (name == "move") {
NSString *sourcePath = stringFromCefV8Value(arguments[0]);
NSString *targetPath = stringFromCefV8Value(arguments[1]);
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error = nil;
[fm moveItemAtPath:sourcePath toPath:targetPath error:&error];
if (error) {
exception = [[error localizedDescription] UTF8String];
}
return true;
}
else if (name == "moveToTrash") {
NSString *sourcePath = stringFromCefV8Value(arguments[0]);
bool success = [[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation
source:[sourcePath stringByDeletingLastPathComponent]
destination:@""
files:[NSArray arrayWithObject:[sourcePath lastPathComponent]]
tag:nil];
if (!success) {
std::string exception = "Can not move ";
exception += [sourcePath UTF8String];
exception += " to trash.";
}
return true;
}
else if (name == "reload") {
CefV8Context::GetCurrentContext()->GetBrowser()->ReloadIgnoreCache();
}
else if (name == "lastModified") {
NSString *path = stringFromCefV8Value(arguments[0]);
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error = nil;
NSDictionary *attributes = [fm attributesOfItemAtPath:path error:&error];
if (error) {
exception = [[error localizedDescription] UTF8String];
}
NSDate *lastModified = [attributes objectForKey:NSFileModificationDate];
retval = CefV8Value::CreateDate(CefTime([lastModified timeIntervalSince1970]));
return true;
}
else if (name == "md5ForPath") {
NSString *path = stringFromCefV8Value(arguments[0]);
unsigned char outputData[CC_MD5_DIGEST_LENGTH];
NSData *inputData = [[NSData alloc] initWithContentsOfFile:path];
CC_MD5([inputData bytes], [inputData length], outputData);
[inputData release];
NSMutableString *hash = [[NSMutableString alloc] init];
for (NSUInteger i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
[hash appendFormat:@"%02x", outputData[i]];
}
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)(NSString *contents, CefRefPtr<CefV8Value> function) = nil;
void (^taskTerminatedHandle)(NSString *output, NSString *errorOutput) = nil;
outputHandle = ^(NSString *contents, CefRefPtr<CefV8Value> function) {
context->Enter();
CefV8ValueList args;
args.push_back(CefV8Value::CreateString(std::string([contents UTF8String], [contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding])));
CefRefPtr<CefV8Value> retval = function->ExecuteFunction(function, args);
if (function->HasException()) {
throwException(context->GetGlobal(), function->GetException(), @"Error thrown in OutputHandle");
}
context->Exit();
};
taskTerminatedHandle = ^(NSString *output, NSString *errorOutput) {
context->Enter();
CefV8ValueList args;
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);
if (callback->HasException()) {
throwException(context->GetGlobal(), callback->GetException(), @"Error thrown in TaskTerminatedHandle");
}
context->Exit();
stdout.fileHandleForReading.writeabilityHandler = nil;
stderr.fileHandleForReading.writeabilityHandler = nil;
};
task.terminationHandler = ^(NSTask *) {
NSString *output = [[NSString alloc] initWithData:[[stdout fileHandleForReading] readDataToEndOfFile] encoding:NSUTF8StringEncoding];
NSString *errorOutput = [[NSString alloc] initWithData:[[stderr fileHandleForReading] readDataToEndOfFile] encoding:NSUTF8StringEncoding];
dispatch_sync(dispatch_get_main_queue(), ^() {
taskTerminatedHandle(output, errorOutput);
});
[output release];
[errorOutput release];
};
CefRefPtr<CefV8Value> stdoutFunction = options->GetValue("stdout");
if (stdoutFunction->IsFunction()) {
stdout.fileHandleForReading.writeabilityHandler = ^(NSFileHandle *fileHandle) {
NSData *data = [fileHandle availableData];
NSString *contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
dispatch_sync(dispatch_get_main_queue(), ^() {
outputHandle(contents, stdoutFunction);
});
[contents release];
};
}
CefRefPtr<CefV8Value> stderrFunction = options->GetValue("stderr");
if (stderrFunction->IsFunction()) {
stderr.fileHandleForReading.writeabilityHandler = ^(NSFileHandle *fileHandle) {
NSData *data = [fileHandle availableData];
NSString *contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
dispatch_sync(dispatch_get_main_queue(), ^() {
outputHandle(contents, stderrFunction);
});
[contents release];
};
}
[task launch];
return true;
}
else if (name == "getPlatform") {
retval = CefV8Value::CreateString("mac");
return true;
NSString *stringFromCefV8Value(const CefRefPtr<CefV8Value>& value) {
std::string cc_value = value->GetStringValue().ToString();
return [NSString stringWithUTF8String:cc_value.c_str()];
}
else if (name == "setWindowState") {
windowState = arguments[0]->GetStringValue().ToString();
return true;
}
void throwException(const CefRefPtr<CefV8Value>& global, CefRefPtr<CefV8Exception> exception, NSString *message) {
CefV8ValueList arguments;
else if (name == "getWindowState") {
retval = CefV8Value::CreateString(windowState);
return true;
}
message = [message stringByAppendingFormat:@"\n%s", exception->GetMessage().ToString().c_str()];
arguments.push_back(CefV8Value::CreateString(std::string([message UTF8String], [message lengthOfBytesUsingEncoding:NSUTF8StringEncoding])));
return false;
};
CefRefPtr<CefV8Value> console = global->GetValue("console");
console->GetValue("error")->ExecuteFunction(console, arguments);
}
} // namespace v8_extensions

View File

@@ -3,18 +3,22 @@
namespace v8_extensions {
class OnigRegExp : public CefV8Handler {
public:
OnigRegExp();
virtual bool Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) OVERRIDE;
// Provide the reference counting implementation for this class.
IMPLEMENT_REFCOUNTING(OnigRegExp);
};
class OnigRegExp : public CefV8Handler {
public:
OnigRegExp();
void CreateContextBinding(CefRefPtr<CefV8Context> context);
virtual bool Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) OVERRIDE;
// Provide the reference counting implementation for this class.
IMPLEMENT_REFCOUNTING(OnigRegExp);
private:
OnigRegExp(OnigRegExp const&);
void operator=(OnigRegExp const&);
};
}

View File

@@ -1,19 +0,0 @@
(function() {
native function buildOnigRegExp(source);
native function search(string, index);
native function test(string);
function OnigRegExp(source) {
var regexp = buildOnigRegExp(source);
regexp.constructor = OnigRegExp;
regexp.__proto__ = OnigRegExp.prototype;
regexp.source = source;
return regexp;
}
OnigRegExp.prototype.search = search;
OnigRegExp.prototype.test = test;
this.OnigRegExp = OnigRegExp;
})();

View File

@@ -38,21 +38,33 @@ public:
return resultArray;
}
CefRefPtr<CefV8Value> Test(CefRefPtr<CefV8Value> string, CefRefPtr<CefV8Value> index) {
OnigResult *result = [m_regex search:stringFromCefV8Value(string) start:index->GetIntValue()];
return CefV8Value::CreateBool(result);
}
OnigRegexp *m_regex;
IMPLEMENT_REFCOUNTING(OnigRegexpUserData);
};
OnigRegExp::OnigRegExp() : CefV8Handler() {
NSString *filePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"v8_extensions/onig_reg_exp.js"];
NSString *extensionCode = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
CefRegisterExtension("v8/onig-reg-exp", [extensionCode UTF8String], this);
}
void OnigRegExp::CreateContextBinding(CefRefPtr<CefV8Context> context) {
const char* methodNames[] = { "search", "test", "buildOnigRegExp" };
CefRefPtr<CefV8Value> nativeObject = CefV8Value::CreateObject(NULL);
int arrayLength = sizeof(methodNames) / sizeof(const char *);
for (int i = 0; i < arrayLength; i++) {
const char *functionName = methodNames[i];
CefRefPtr<CefV8Value> function = CefV8Value::CreateFunction(functionName, this);
nativeObject->SetValue(functionName, function, V8_PROPERTY_ATTRIBUTE_NONE);
}
CefRefPtr<CefV8Value> global = context->GetGlobal();
global->SetValue("$onigRegExp", nativeObject, V8_PROPERTY_ATTRIBUTE_NONE);
}
bool OnigRegExp::Execute(const CefString& name,
@@ -73,7 +85,7 @@ bool OnigRegExp::Execute(const CefString& name,
CefRefPtr<CefV8Value> index = arguments.size() > 1 ? arguments[1] : CefV8Value::CreateInt(0);
OnigRegExpUserData *userData = (OnigRegExpUserData *)object->GetUserData().get();
retval = userData->Test(string, index);
return true;
return true;
}
else if (name == "buildOnigRegExp") {
CefRefPtr<CefV8Value> pattern = arguments[0];

View File

@@ -2,19 +2,23 @@
#include "include/cef_v8.h"
namespace v8_extensions {
class OnigScanner : public CefV8Handler {
public:
OnigScanner();
void CreateContextBinding(CefRefPtr<CefV8Context> context);
virtual bool Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) OVERRIDE;
// Provide the reference counting implementation for this class.
IMPLEMENT_REFCOUNTING(OnigRegExp);
private:
OnigScanner(OnigScanner const&);
void operator=(OnigScanner const&);
};
}

View File

@@ -1,17 +0,0 @@
(function() {
native function buildScanner(sources);
native function findNextMatch(string, startPosition);
function OnigScanner(sources) {
var scanner = buildScanner(sources);
scanner.constructor = OnigScanner;
scanner.__proto__ = OnigScanner.prototype;
scanner.sources = sources;
return scanner;
}
OnigScanner.prototype.buildScanner = buildScanner;
OnigScanner.prototype.findNextMatch = findNextMatch;
this.OnigScanner = OnigScanner;
})();

View File

@@ -130,11 +130,22 @@ class OnigScannerUserData : public CefBase {
};
OnigScanner::OnigScanner() : CefV8Handler() {
NSString *filePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"v8_extensions/onig_scanner.js"];
NSString *extensionCode = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
CefRegisterExtension("v8/onig-scanner", [extensionCode UTF8String], this);
}
void OnigScanner::CreateContextBinding(CefRefPtr<CefV8Context> context) {
const char* methodNames[] = { "findNextMatch", "buildScanner" };
CefRefPtr<CefV8Value> nativeObject = CefV8Value::CreateObject(NULL);
int arrayLength = sizeof(methodNames) / sizeof(const char *);
for (int i = 0; i < arrayLength; i++) {
const char *functionName = methodNames[i];
CefRefPtr<CefV8Value> function = CefV8Value::CreateFunction(functionName, this);
nativeObject->SetValue(functionName, function, V8_PROPERTY_ATTRIBUTE_NONE);
}
CefRefPtr<CefV8Value> global = context->GetGlobal();
global->SetValue("$onigScanner", nativeObject, V8_PROPERTY_ATTRIBUTE_NONE);
}
bool OnigScanner::Execute(const CefString& name,
CefRefPtr<CefV8Value> object,

View File

@@ -4,21 +4,23 @@
namespace v8_extensions {
class Tags : public CefV8Handler {
public:
Tags();
class Tags : public CefV8Handler {
public:
Tags();
void CreateContextBinding(CefRefPtr<CefV8Context> context);
virtual bool Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) OVERRIDE;
virtual bool Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) OVERRIDE;
// Provide the reference counting implementation for this class.
IMPLEMENT_REFCOUNTING(Tags);
// Provide the reference counting implementation for this class.
IMPLEMENT_REFCOUNTING(Tags);
private:
CefRefPtr<CefV8Value> ParseEntry(tagEntry entry);
};
private:
Tags(Tags const&);
void operator=(Tags const&);
CefRefPtr<CefV8Value> ParseEntry(tagEntry entry);
};
}

View File

@@ -1,10 +0,0 @@
var $tags = {};
(function() {
native function find(path, tag);
$tags.find = find;
native function getAllTagsAsync(path, callback);
$tags.getAllTagsAsync = getAllTagsAsync;
})();

View File

@@ -3,100 +3,112 @@
namespace v8_extensions {
Tags::Tags() : CefV8Handler() {
NSString *filePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"v8_extensions/tags.js"];
NSString *extensionCode = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
CefRegisterExtension("v8/tags", [extensionCode UTF8String], this);
}
CefRefPtr<CefV8Value> Tags::ParseEntry(tagEntry entry) {
CefRefPtr<CefV8Value> tagEntry = CefV8Value::CreateObject(NULL);
tagEntry->SetValue("name", CefV8Value::CreateString(entry.name), V8_PROPERTY_ATTRIBUTE_NONE);
tagEntry->SetValue("file", CefV8Value::CreateString(entry.file), V8_PROPERTY_ATTRIBUTE_NONE);
if (entry.address.pattern) {
tagEntry->SetValue("pattern", CefV8Value::CreateString(entry.address.pattern), V8_PROPERTY_ATTRIBUTE_NONE);
Tags::Tags() : CefV8Handler() {
}
return tagEntry;
}
bool Tags::Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) {
void Tags::CreateContextBinding(CefRefPtr<CefV8Context> context) {
const char* methodNames[] = { "find", "getAllTagsAsync" };
if (name == "find") {
std::string path = arguments[0]->GetStringValue().ToString();
std::string tag = arguments[1]->GetStringValue().ToString();
tagFileInfo info;
tagFile* tagFile;
tagFile = tagsOpen(path.c_str(), &info);
if (info.status.opened) {
tagEntry entry;
std::vector<CefRefPtr<CefV8Value>> entries;
if (tagsFind(tagFile, &entry, tag.c_str(), TAG_FULLMATCH | TAG_OBSERVECASE) == TagSuccess) {
entries.push_back(ParseEntry(entry));
while (tagsFindNext(tagFile, &entry) == TagSuccess) {
entries.push_back(ParseEntry(entry));
}
}
retval = CefV8Value::CreateArray(entries.size());
for (int i = 0; i < entries.size(); i++) {
retval->SetValue(i, entries[i]);
}
tagsClose(tagFile);
CefRefPtr<CefV8Value> nativeObject = CefV8Value::CreateObject(NULL);
int arrayLength = sizeof(methodNames) / sizeof(const char *);
for (int i = 0; i < arrayLength; i++) {
const char *functionName = methodNames[i];
CefRefPtr<CefV8Value> function = CefV8Value::CreateFunction(functionName, this);
nativeObject->SetValue(functionName, function, V8_PROPERTY_ATTRIBUTE_NONE);
}
return true;
CefRefPtr<CefV8Value> global = context->GetGlobal();
global->SetValue("$tags", nativeObject, V8_PROPERTY_ATTRIBUTE_NONE);
}
if (name == "getAllTagsAsync") {
std::string path = arguments[0]->GetStringValue().ToString();
CefRefPtr<CefV8Value> callback = arguments[1];
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
CefRefPtr<CefV8Value> Tags::ParseEntry(tagEntry entry) {
CefRefPtr<CefV8Value> tagEntry = CefV8Value::CreateObject(NULL);
tagEntry->SetValue("name", CefV8Value::CreateString(entry.name), V8_PROPERTY_ATTRIBUTE_NONE);
tagEntry->SetValue("file", CefV8Value::CreateString(entry.file), V8_PROPERTY_ATTRIBUTE_NONE);
if (entry.address.pattern) {
tagEntry->SetValue("pattern", CefV8Value::CreateString(entry.address.pattern), V8_PROPERTY_ATTRIBUTE_NONE);
}
return tagEntry;
}
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
bool Tags::Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) {
if (name == "find") {
std::string path = arguments[0]->GetStringValue().ToString();
std::string tag = arguments[1]->GetStringValue().ToString();
tagFileInfo info;
tagFile* tagFile;
tagFile = tagsOpen(path.c_str(), &info);
std::vector<tagEntry> entries;
if (info.status.opened) {
tagEntry entry;
while (tagsNext(tagFile, &entry) == TagSuccess) {
entry.name = strdup(entry.name);
entry.file = strdup(entry.file);
if (entry.address.pattern) {
entry.address.pattern = strdup(entry.address.pattern);
std::vector<CefRefPtr<CefV8Value>> entries;
if (tagsFind(tagFile, &entry, tag.c_str(), TAG_FULLMATCH | TAG_OBSERVECASE) == TagSuccess) {
entries.push_back(ParseEntry(entry));
while (tagsFindNext(tagFile, &entry) == TagSuccess) {
entries.push_back(ParseEntry(entry));
}
entries.push_back(entry);
}
retval = CefV8Value::CreateArray(entries.size());
for (int i = 0; i < entries.size(); i++) {
retval->SetValue(i, entries[i]);
}
tagsClose(tagFile);
}
return true;
}
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
context->Enter();
CefRefPtr<CefV8Value> v8Tags = CefV8Value::CreateArray(entries.size());
for (int i = 0; i < entries.size(); i++) {
v8Tags->SetValue(i, ParseEntry(entries[i]));
free((void*)entries[i].name);
free((void*)entries[i].file);
if (entries[i].address.pattern) {
free((void*)entries[i].address.pattern);
if (name == "getAllTagsAsync") {
std::string path = arguments[0]->GetStringValue().ToString();
CefRefPtr<CefV8Value> callback = arguments[1];
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
tagFileInfo info;
tagFile* tagFile;
tagFile = tagsOpen(path.c_str(), &info);
std::vector<tagEntry> entries;
if (info.status.opened) {
tagEntry entry;
while (tagsNext(tagFile, &entry) == TagSuccess) {
entry.name = strdup(entry.name);
entry.file = strdup(entry.file);
if (entry.address.pattern) {
entry.address.pattern = strdup(entry.address.pattern);
}
entries.push_back(entry);
}
tagsClose(tagFile);
}
CefV8ValueList callbackArgs;
callbackArgs.push_back(v8Tags);
callback->ExecuteFunction(callback, callbackArgs);
context->Exit();
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
context->Enter();
CefRefPtr<CefV8Value> v8Tags = CefV8Value::CreateArray(entries.size());
for (int i = 0; i < entries.size(); i++) {
v8Tags->SetValue(i, ParseEntry(entries[i]));
free((void*)entries[i].name);
free((void*)entries[i].file);
if (entries[i].address.pattern) {
free((void*)entries[i].address.pattern);
}
}
CefV8ValueList callbackArgs;
callbackArgs.push_back(v8Tags);
callback->ExecuteFunction(callback, callbackArgs);
context->Exit();
});
});
});
return true;
return true;
}
return false;
}
return false;
}
}

17
script/fix-author Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/sh
usage() {
echo "usage: $0 sha name email"
exit 1
}
if [ ! $3 ]; then
usage
fi
git filter-branch -f --env-filter "
export GIT_AUTHOR_NAME='$2'
export GIT_AUTHOR_EMAIL='$3'
export GIT_COMMITTER_NAME='$2'
export GIT_COMMITTER_EMAIL='$3'
" -- $1..HEAD

View File

@@ -1,7 +1,7 @@
#!/bin/sh
usage() {
echo 'usage: `basename $0` 'chromium-dir' [cef-release-branch] 1>&2'
echo 'usage: update-cef chromium-dir [cef-release-branch] 1>&2'
echo
echo 'chromium-dir is the root of the chromium directory (i.e. ~/code/chromium)'
echo 'CEF release branches can be found at http://code.google.com/p/chromiumembedded/wiki/BranchesAndBuilding'
@@ -14,9 +14,13 @@ fi
ATOM_ROOT=$(cd $(dirname $0); pwd -P)/..
CHROMIUM_DIR=$1
CEF_DIR=$CHROMIUM_DIR/src/cef/
CEF_DIR=$CHROMIUM_DIR/src/cef
CEF_RELEASE_BRANCH=${2:-1271}
CEF_BINARY_PATH=$(echo "$CEF_DIR"/binary_distrib/cef_binary_*_macosx/) # Expand the path
CEF_REVISION=$(svnversion $CEF_DIR)
CEF_BINARY_PATH=$(echo ${CEF_DIR}/binary_distrib/cef_binary_3.${CEF_RELEASE_BRANCH}.${CEF_REVISION}_macosx/) # Expand the path
echo "CEF_RELEASE_BRANCH=$CEF_RELEASE_BRANCH"
echo "CEF_BINARY_PATH=$CEF_BINARY_PATH"
# Update and compile CEF
CEF_AUTOMATE_SCRIPT_PATH=/tmp/cef-update

View File

@@ -1,4 +1,18 @@
#!/bin/sh
# From root of libgit2 repo:
# mkdir build
# cd build
# cmake .. -DCMAKE_INSTALL_PREFIX=~/repos/atom/git2 -DCMAKE_OSX_ARCHITECTURES="i386;x86_64" -DCMAKE_BUILD_TYPE=Release
# cmake --build . --target install
#
# From root of atom repo:
# mv git2/lib/libgit2.0.17.0.dylib git2/frameworks/libgit2.0.17.0.dylib
# rm -fr git2/lib
# script/update-libgit2
# update the id of the dylib
install_name_tool -id @executable_path/libgit2.0.17.0.dylib git2/frameworks/libgit2.0.17.0.dylib
# Verify @executable_path is in the output of:
otool -L git2/frameworks/libgit2.0.17.0.dylib

View File

@@ -19,23 +19,27 @@ describe "the `atom` global", ->
expect(rootView.activatePackage).toHaveBeenCalled()
it "logs warning instead of throwing an exception if a package fails to load", ->
config.set("core.disabledPackages", [])
spyOn(console, "warn")
expect(-> atom.loadPackage("package-that-throws-an-exception")).not.toThrow()
expect(console.warn).toHaveBeenCalled()
describe "keymap loading", ->
describe "when package.json does not contain a 'keymaps' manifest", ->
it "loads all keymaps in the directory", ->
it "loads all the .cson/.json files in the keymaps directory", ->
element1 = $$ -> @div class: 'test-1'
element2 = $$ -> @div class: 'test-2'
element3 = $$ -> @div class: 'test-3'
expect(keymap.bindingsForElement(element1)['ctrl-z']).toBeUndefined()
expect(keymap.bindingsForElement(element2)['ctrl-z']).toBeUndefined()
expect(keymap.bindingsForElement(element3)['ctrl-z']).toBeUndefined()
atom.loadPackage("package-with-module")
expect(keymap.bindingsForElement(element1)['ctrl-z']).toBe "test-1"
expect(keymap.bindingsForElement(element2)['ctrl-z']).toBe "test-2"
expect(keymap.bindingsForElement(element3)['ctrl-z']).toBeUndefined()
describe "when package.json contains a 'keymaps' manifest", ->
it "loads only the keymaps specified by the manifest, in the specified order", ->
@@ -55,3 +59,22 @@ describe "the `atom` global", ->
expect(stylesheetElementForId(stylesheetPath).length).toBe 0
atom.loadPackage("package-with-module")
expect(stylesheetElementForId(stylesheetPath).length).toBe 1
describe ".loadPackages()", ->
beforeEach ->
spyOn(syntax, 'addGrammar')
it "terminates the worker when all packages have been loaded", ->
spyOn(Worker.prototype, 'terminate').andCallThrough()
eventHandler = jasmine.createSpy('eventHandler')
syntax.on 'grammars-loaded', eventHandler
disabledPackages = config.get("core.disabledPackages")
disabledPackages.push('textmate-package.tmbundle')
config.set "core.disabledPackages", disabledPackages
atom.loadPackages()
waitsFor "all packages to load", 5000, -> eventHandler.callCount > 0
runs ->
expect(Worker.prototype.terminate).toHaveBeenCalled()
expect(Worker.prototype.terminate.calls.length).toBe 1

View File

@@ -465,6 +465,14 @@ describe 'Buffer', ->
range = [[2,10], [4,10]]
expect(buffer.getTextInRange(range)).toBe "ems.length <= 1) return items;\n var pivot = items.shift(), current, left = [], right = [];\n while("
describe "when the range starts before the start of the buffer", ->
it "clips the range to the start of the buffer", ->
expect(buffer.getTextInRange([[-Infinity, -Infinity], [0, Infinity]])).toBe buffer.lineForRow(0)
describe "when the range ends after the end of the buffer", ->
it "clips the range to the end of the buffer", ->
expect(buffer.getTextInRange([[12], [13, Infinity]])).toBe buffer.lineForRow(12)
describe ".scanInRange(range, regex, fn)", ->
describe "when given a regex with a ignore case flag", ->
it "does a case-insensitive search", ->
@@ -833,3 +841,65 @@ describe 'Buffer', ->
expect(buffer.getText()).toBe "a"
buffer.append("b\nc");
expect(buffer.getText()).toBe "ab\nc"
describe "line ending support", ->
describe ".lineEndingForRow(line)", ->
it "return the line ending for each buffer line", ->
buffer.setText("a\r\nb\nc")
expect(buffer.lineEndingForRow(0)).toBe '\r\n'
expect(buffer.lineEndingForRow(1)).toBe '\n'
expect(buffer.lineEndingForRow(2)).toBeUndefined()
describe ".lineForRow(line)", ->
it "returns the line text without the line ending for both lf and crlf lines", ->
buffer.setText("a\r\nb\nc")
expect(buffer.lineForRow(0)).toBe 'a'
expect(buffer.lineForRow(1)).toBe 'b'
expect(buffer.lineForRow(2)).toBe 'c'
describe ".getText()", ->
it "returns the text with the corrent line endings for each row", ->
buffer.setText("a\r\nb\nc")
expect(buffer.getText()).toBe "a\r\nb\nc"
buffer.setText("a\r\nb\nc\n")
expect(buffer.getText()).toBe "a\r\nb\nc\n"
describe "when editing a line", ->
it "preserves the existing line ending", ->
buffer.setText("a\r\nb\nc")
buffer.insert([0, 1], "1")
expect(buffer.getText()).toBe "a1\r\nb\nc"
describe "when inserting text with multiple lines", ->
describe "when the current line has a line ending", ->
it "uses the same line ending as the line where the text is inserted", ->
buffer.setText("a\r\n")
buffer.insert([0,1], "hello\n1\n\n2")
expect(buffer.getText()).toBe "ahello\r\n1\r\n\r\n2\r\n"
describe "when the current line has no line ending (because it's the last line of the buffer)", ->
describe "when the buffer contains only a single line", ->
it "honors the line endings in the inserted text", ->
buffer.setText("initialtext")
buffer.append("hello\n1\r\n2\n")
expect(buffer.getText()).toBe "initialtexthello\n1\r\n2\n"
describe "when the buffer contains a preceding line", ->
it "uses the line ending of the preceding line", ->
buffer.setText("\ninitialtext")
buffer.append("hello\n1\r\n2\n")
expect(buffer.getText()).toBe "\ninitialtexthello\n1\n2\n"
describe ".clipPosition(position)", ->
describe "when the position is before the start of the buffer", ->
it "returns the first position in the buffer", ->
expect(buffer.clipPosition([-1,0])).toEqual [0,0]
expect(buffer.clipPosition([0,-1])).toEqual [0,0]
expect(buffer.clipPosition([-1,-1])).toEqual [0,0]
describe "when the position is after the end of the buffer", ->
it "returns the last position in the buffer", ->
buffer.setText('some text')
expect(buffer.clipPosition([1, 0])).toEqual [0,9]
expect(buffer.clipPosition([0,10])).toEqual [0,9]
expect(buffer.clipPosition([10,Infinity])).toEqual [0,9]

View File

@@ -22,17 +22,36 @@ describe "Config", ->
spyOn(fs, 'write')
jasmine.unspy config, 'save'
it "writes any non-default properties to the config.json in the user's .atom directory", ->
config.set("a.b.c", 1)
config.set("a.b.d", 2)
config.set("x.y.z", 3)
config.setDefaults("a.b", e: 4, f: 5)
describe "when ~/.atom/config.json exists", ->
it "writes any non-default properties to ~/.atom/config.json", ->
config.configFilePath = fs.join(config.configDirPath, "config.json")
config.set("a.b.c", 1)
config.set("a.b.d", 2)
config.set("x.y.z", 3)
config.setDefaults("a.b", e: 4, f: 5)
fs.write.reset()
config.save()
fs.write.reset()
config.save()
writtenConfig = JSON.parse(fs.write.argsForCall[0][1])
expect(writtenConfig).toEqual config.settings
expect(fs.write.argsForCall[0][0]).toBe(fs.join(config.configDirPath, "config.json"))
writtenConfig = JSON.parse(fs.write.argsForCall[0][1])
expect(writtenConfig).toEqual config.settings
describe "when ~/.atom/config.json doesn't exist", ->
it "writes any non-default properties to ~/.atom/config.cson", ->
config.configFilePath = fs.join(config.configDirPath, "config.cson")
config.set("a.b.c", 1)
config.set("a.b.d", 2)
config.set("x.y.z", 3)
config.setDefaults("a.b", e: 4, f: 5)
fs.write.reset()
config.save()
expect(fs.write.argsForCall[0][0]).toBe(fs.join(config.configDirPath, "config.cson"))
{CoffeeScript} = require 'coffee-script'
writtenConfig = CoffeeScript.eval(fs.write.argsForCall[0][1], bare: true)
expect(writtenConfig).toEqual config.settings
describe ".setDefaults(keyPath, defaults)", ->
it "assigns any previously-unassigned keys to the object at the key path", ->

View File

@@ -495,6 +495,20 @@ describe "DisplayBuffer", ->
expect(displayBuffer.lineForRow(8).text).toMatch /^9-+/
expect(displayBuffer.lineForRow(10).fold).toBeDefined()
describe "when the line being deleted preceeds a fold", ->
describe "when the command is undone", ->
it "restores the line and preserves the fold", ->
editSession.setCursorBufferPosition([4])
editSession.foldCurrentRow()
expect(editSession.isFoldedAtScreenRow(4)).toBeTruthy()
editSession.setCursorBufferPosition([3])
editSession.deleteLine()
expect(editSession.isFoldedAtScreenRow(3)).toBeTruthy()
expect(buffer.lineForRow(3)).toBe ' while(items.length > 0) {'
editSession.undo()
expect(editSession.isFoldedAtScreenRow(4)).toBeTruthy()
expect(buffer.lineForRow(3)).toBe ' var pivot = items.shift(), current, left = [], right = [];'
describe ".clipScreenPosition(screenPosition, wrapBeyondNewlines: false, wrapAtSoftNewlines: false, skipAtomicTokens: false)", ->
beforeEach ->
displayBuffer.setSoftWrapColumn(50)

View File

@@ -276,17 +276,22 @@ describe "EditSession", ->
editSession.moveCursorToBeginningOfWord()
expect(cursor1.getBufferPosition()).toEqual [0, 4]
expect(cursor2.getBufferPosition()).toEqual [1, 10]
expect(cursor2.getBufferPosition()).toEqual [1, 11]
expect(cursor3.getBufferPosition()).toEqual [2, 39]
it "does not fail at position [0, 0]", ->
editSession.setCursorBufferPosition([0, 0])
editSession.moveCursorToBeginningOfWord()
it "works when the preceding line is blank", ->
it "treats lines with only whitespace as a word", ->
editSession.setCursorBufferPosition([11, 0])
editSession.moveCursorToBeginningOfWord()
expect(editSession.getCursorBufferPosition()).toEqual [10, 0]
it "works when the current line is blank", ->
editSession.setCursorBufferPosition([10, 0])
editSession.moveCursorToBeginningOfWord()
expect(editSession.getCursorBufferPosition()).toEqual [9, 0]
expect(editSession.getCursorBufferPosition()).toEqual [9, 2]
describe ".moveCursorToEndOfWord()", ->
it "moves the cursor to the end of the word", ->
@@ -298,8 +303,8 @@ describe "EditSession", ->
editSession.moveCursorToEndOfWord()
expect(cursor1.getBufferPosition()).toEqual [0, 13]
expect(cursor2.getBufferPosition()).toEqual [1, 13]
expect(cursor3.getBufferPosition()).toEqual [3, 4]
expect(cursor2.getBufferPosition()).toEqual [1, 12]
expect(cursor3.getBufferPosition()).toEqual [3, 7]
it "does not blow up when there is no next word", ->
editSession.setCursorBufferPosition [Infinity, Infinity]
@@ -307,6 +312,17 @@ describe "EditSession", ->
editSession.moveCursorToEndOfWord()
expect(editSession.getCursorBufferPosition()).toEqual endPosition
it "treats lines with only whitespace as a word", ->
editSession.setCursorBufferPosition([9, 4])
editSession.moveCursorToEndOfWord()
expect(editSession.getCursorBufferPosition()).toEqual [10, 0]
it "works when the current line is blank", ->
editSession.setCursorBufferPosition([10, 0])
editSession.moveCursorToEndOfWord()
expect(editSession.getCursorBufferPosition()).toEqual [11, 8]
describe ".getCurrentParagraphBufferRange()", ->
it "returns the buffer range of the current paragraph, delimited by blank lines or the beginning / end of the file", ->
buffer.setText """
@@ -518,13 +534,13 @@ describe "EditSession", ->
expect(editSession.getCursors().length).toBe 2
[cursor1, cursor2] = editSession.getCursors()
expect(cursor1.getBufferPosition()).toEqual [0,4]
expect(cursor2.getBufferPosition()).toEqual [3,44]
expect(cursor2.getBufferPosition()).toEqual [3,47]
expect(editSession.getSelections().length).toBe 2
[selection1, selection2] = editSession.getSelections()
expect(selection1.getBufferRange()).toEqual [[0,4], [0,13]]
expect(selection1.isReversed()).toBeTruthy()
expect(selection2.getBufferRange()).toEqual [[3,44], [3,49]]
expect(selection2.getBufferRange()).toEqual [[3,47], [3,49]]
expect(selection2.isReversed()).toBeTruthy()
describe ".selectToEndOfWord()", ->
@@ -537,40 +553,49 @@ describe "EditSession", ->
expect(editSession.getCursors().length).toBe 2
[cursor1, cursor2] = editSession.getCursors()
expect(cursor1.getBufferPosition()).toEqual [0,13]
expect(cursor2.getBufferPosition()).toEqual [3,51]
expect(cursor2.getBufferPosition()).toEqual [3,50]
expect(editSession.getSelections().length).toBe 2
[selection1, selection2] = editSession.getSelections()
expect(selection1.getBufferRange()).toEqual [[0,4], [0,13]]
expect(selection1.isReversed()).toBeFalsy()
expect(selection2.getBufferRange()).toEqual [[3,48], [3,51]]
expect(selection2.getBufferRange()).toEqual [[3,48], [3,50]]
expect(selection2.isReversed()).toBeFalsy()
describe ".selectWord()", ->
describe "when the cursor is inside a word", ->
it "selects the entire word", ->
editSession.setCursorScreenPosition([0, 8])
editSession.selectWord()
expect(editSession.getSelectedText()).toBe 'quicksort'
describe "when the cursor is inside a word", ->
it "selects the entire word", ->
editSession.setCursorScreenPosition([0, 8])
editSession.selectWord()
expect(editSession.getSelectedText()).toBe 'quicksort'
describe "when the cursor is between two words", ->
it "selects both words", ->
editSession.setCursorScreenPosition([0, 4])
editSession.selectWord()
expect(editSession.getSelectedText()).toBe ' quicksort'
describe "when the cursor is between two words", ->
it "selects the word the cursor is on", ->
editSession.setCursorScreenPosition([0, 4])
editSession.selectWord()
expect(editSession.getSelectedText()).toBe 'quicksort'
describe "when the cursor is inside a region of whitespace", ->
it "selects the whitespace region", ->
editSession.setCursorScreenPosition([5, 2])
editSession.selectWord()
expect(editSession.getSelectedBufferRange()).toEqual [[5, 0], [5, 6]]
editSession.setCursorScreenPosition([0, 3])
editSession.selectWord()
expect(editSession.getSelectedText()).toBe 'var'
describe "when the cursor is at the end of the text", ->
it "select the previous word", ->
editSession.buffer.append 'word'
editSession.moveCursorToBottom()
editSession.selectWord()
expect(editSession.getSelectedBufferRange()).toEqual [[12, 2], [12, 6]]
describe "when the cursor is inside a region of whitespace", ->
it "selects the whitespace region", ->
editSession.setCursorScreenPosition([5, 2])
editSession.selectWord()
expect(editSession.getSelectedBufferRange()).toEqual [[5, 0], [5, 6]]
editSession.setCursorScreenPosition([5, 0])
editSession.selectWord()
expect(editSession.getSelectedBufferRange()).toEqual [[5, 0], [5, 6]]
describe "when the cursor is at the end of the text", ->
it "select the previous word", ->
editSession.buffer.append 'word'
editSession.moveCursorToBottom()
editSession.selectWord()
expect(editSession.getSelectedBufferRange()).toEqual [[12, 2], [12, 6]]
describe ".setSelectedBufferRanges(ranges)", ->
it "clears existing selections and creates selections for each of the given ranges", ->
@@ -1110,25 +1135,26 @@ describe "EditSession", ->
describe "when no text is selected", ->
it "deletes all text between the cursor and the beginning of the word", ->
editSession.setCursorBufferPosition([1, 24])
editSession.addCursorAtBufferPosition([2, 5])
editSession.addCursorAtBufferPosition([3, 5])
[cursor1, cursor2] = editSession.getCursors()
editSession.backspaceToBeginningOfWord()
expect(buffer.lineForRow(1)).toBe ' var sort = function(ems) {'
expect(buffer.lineForRow(2)).toBe ' f (items.length <= 1) return items;'
expect(buffer.lineForRow(3)).toBe ' ar pivot = items.shift(), current, left = [], right = [];'
expect(cursor1.getBufferPosition()).toEqual [1, 22]
expect(cursor2.getBufferPosition()).toEqual [2, 4]
expect(cursor2.getBufferPosition()).toEqual [3, 4]
editSession.backspaceToBeginningOfWord()
expect(buffer.lineForRow(1)).toBe ' var sort = functionems) {'
expect(buffer.lineForRow(2)).toBe 'f (items.length <= 1) return items;'
expect(buffer.lineForRow(2)).toBe ' if (items.length <= 1) return itemsar pivot = items.shift(), current, left = [], right = [];'
expect(cursor1.getBufferPosition()).toEqual [1, 21]
expect(cursor2.getBufferPosition()).toEqual [2, 0]
expect(cursor2.getBufferPosition()).toEqual [2, 39]
editSession.backspaceToBeginningOfWord()
expect(buffer.lineForRow(1)).toBe ' var sort = emsf (items.length <= 1) return items;'
expect(buffer.lineForRow(1)).toBe ' var sort = ems) {'
expect(buffer.lineForRow(2)).toBe ' if (items.length <= 1) return ar pivot = items.shift(), current, left = [], right = [];'
expect(cursor1.getBufferPosition()).toEqual [1, 13]
expect(cursor2.getBufferPosition()).toEqual [1, 16]
expect(cursor2.getBufferPosition()).toEqual [2, 34]
describe "when text is selected", ->
it "deletes only selected text", ->
@@ -1296,7 +1322,7 @@ describe "EditSession", ->
expect(cursor2.getBufferPosition()).toEqual [2, 5]
editSession.deleteToEndOfWord()
expect(buffer.lineForRow(1)).toBe ' var sort = function(it'
expect(buffer.lineForRow(1)).toBe ' var sort = function(it {'
expect(buffer.lineForRow(2)).toBe ' iitems.length <= 1) return items;'
expect(cursor1.getBufferPosition()).toEqual [1, 24]
expect(cursor2.getBufferPosition()).toEqual [2, 5]

View File

@@ -111,11 +111,9 @@ describe "Editor", ->
editor.isFocused = false
editor.hiddenInput.focus()
expect(editor.isFocused).toBeTruthy()
expect(editor).toHaveClass('focused')
editor.hiddenInput.focusout()
expect(editor.isFocused).toBeFalsy()
expect(editor).not.toHaveClass('focused')
describe "when the activeEditSession's file is modified on disk", ->
it "triggers an alert", ->
@@ -516,14 +514,61 @@ describe "Editor", ->
editor.getBuffer().saveAs(path)
expect(editor.getGrammar().name).toBe 'Plain Text'
describe "font size", ->
it "sets the initial font size based on the value from config", ->
config.set("editor.fontSize", 20)
newEditor = editor.splitRight()
expect(editor.css('font-size')).toBe '20px'
expect(newEditor.css('font-size')).toBe '20px'
describe "font family", ->
beforeEach ->
expect(editor.css('font-family')).not.toBe 'Courier'
it "when there is no config in fontFamily don't set it", ->
expect($("head style.font-family")).not.toExist()
describe "when the font family changes", ->
it "updates the font family on new and existing editors", ->
rootView.attachToDom()
rootView.height(200)
rootView.width(200)
config.set("editor.fontFamily", "Courier")
newEditor = editor.splitRight()
expect($("head style.font-family").text()).toMatch "{font-family: Courier}"
expect(editor.css('font-family')).toBe 'Courier'
expect(newEditor.css('font-family')).toBe 'Courier'
it "updates the font family of editors and recalculates dimensions critical to cursor positioning", ->
rootView.attachToDom()
rootView.height(200)
rootView.width(200)
lineHeightBefore = editor.lineHeight
charWidthBefore = editor.charWidth
config.set("editor.fontFamily", "Inconsolata")
editor.setCursorScreenPosition [5, 6]
expect(editor.charWidth).not.toBe charWidthBefore
expect(editor.getCursorView().position()).toEqual { top: 5 * editor.lineHeight, left: 6 * editor.charWidth }
expect(editor.verticalScrollbarContent.height()).toBe buffer.getLineCount() * editor.lineHeight
describe "font size", ->
beforeEach ->
expect(editor.css('font-size')).not.toBe "20px"
expect(editor.css('font-size')).not.toBe "10px"
it "sets the initial font size based on the value from config", ->
expect($("head style.font-size")).toExist()
expect($("head style.font-size").text()).toMatch "{font-size: #{config.get('editor.fontSize')}px}"
describe "when the font size changes", ->
it "updates the font family on new and existing editors", ->
rootView.attachToDom()
rootView.height(200)
rootView.width(200)
config.set("editor.fontSize", 20)
newEditor = editor.splitRight()
expect($("head style.font-size").text()).toMatch "{font-size: 20px}"
expect(editor.css('font-size')).toBe '20px'
expect(newEditor.css('font-size')).toBe '20px'
describe "when the font size changes on the view", ->
it "updates the font sizes of editors and recalculates dimensions critical to cursor positioning", ->
rootView.attachToDom()
rootView.height(200)
@@ -558,7 +603,7 @@ describe "Editor", ->
rootView.attachToDom()
config.set("editor.fontSize", 16 * 4)
expect(editor.gutter.css('font-size')).toBe "#{16 * 4}px"
expect(editor.gutter.width()).toBe(64)
expect(editor.gutter.width()).toBe(64 + editor.gutter.calculateLineNumberPadding())
it "updates lines if there are unrendered lines", ->
editor.attachToDom(heightInLines: 5)
@@ -568,6 +613,23 @@ describe "Editor", ->
config.set("editor.fontSize", 10)
expect(editor.renderedLines.find(".line").length).toBeGreaterThan originalLineCount
describe "when the editor is detached", ->
it "updates the font-size correctly and recalculates the dimensions by placing the rendered lines on the DOM", ->
rootView.attachToDom()
rootView.height(200)
rootView.width(200)
newEditor = editor.splitRight()
newEditorParent = newEditor.parent()
newEditor.detach()
config.set("editor.fontSize", 10)
newEditorParent.append(newEditor)
expect(newEditor.lineHeight).toBe editor.lineHeight
expect(newEditor.charWidth).toBe editor.charWidth
expect(newEditor.getCursorView().position()).toEqual editor.getCursorView().position()
expect(newEditor.verticalScrollbarContent.height()).toBe editor.verticalScrollbarContent.height()
describe "mouse events", ->
beforeEach ->
editor.attachToDom()
@@ -1604,6 +1666,49 @@ describe "Editor", ->
expect(rightEditor.find(".line:first").text()).toBe "_tab _;"
expect(leftEditor.find(".line:first").text()).toBe "_tab _;"
it "displays trailing carriage return using a visible non-empty value", ->
editor.setText "a line that ends with a carriage return\r\n"
editor.attachToDom()
expect(config.get("editor.showInvisibles")).toBeFalsy()
expect(editor.renderedLines.find('.line:first').text()).toBe "a line that ends with a carriage return"
config.set("editor.showInvisibles", true)
cr = editor.invisibles?.cr
expect(cr).toBeTruthy()
eol = editor.invisibles?.eol
expect(eol).toBeTruthy()
expect(editor.renderedLines.find('.line:first').text()).toBe "a line that ends with a carriage return#{cr}#{eol}"
describe "when wrapping is on", ->
it "doesn't show the end of line invisible at the end of lines broken due to wrapping", ->
editor.setSoftWrapColumn(6)
editor.setText "a line that wraps"
editor.attachToDom()
config.set "editor.showInvisibles", true
space = editor.invisibles?.space
expect(space).toBeTruthy()
eol = editor.invisibles?.eol
expect(eol).toBeTruthy()
expect(editor.renderedLines.find('.line:first').text()).toBe "a line#{space}"
expect(editor.renderedLines.find('.line:last').text()).toBe "wraps#{eol}"
it "displays trailing carriage return using a visible non-empty value", ->
editor.setSoftWrapColumn(6)
editor.setText "a line that\r\n"
editor.attachToDom()
config.set "editor.showInvisibles", true
space = editor.invisibles?.space
expect(space).toBeTruthy()
cr = editor.invisibles?.cr
expect(cr).toBeTruthy()
eol = editor.invisibles?.eol
expect(eol).toBeTruthy()
expect(editor.renderedLines.find('.line:first').text()).toBe "a line#{space}"
expect(editor.renderedLines.find('.line:eq(1)').text()).toBe "that#{cr}#{eol}"
expect(editor.renderedLines.find('.line:last').text()).toBe "#{eol}"
describe "gutter rendering", ->
beforeEach ->
editor.attachToDom(heightInLines: 5.5)
@@ -1687,6 +1792,11 @@ describe "Editor", ->
fold.destroy()
expect(editor.gutter.find('.line-number').length).toBe 13
it "styles folded line numbers", ->
editor.createFold(3, 5)
expect(editor.gutter.find('.line-number.fold').length).toBe 1
expect(editor.gutter.find('.line-number.fold:eq(0)').text()).toBe '4'
describe "when the scrollView is scrolled to the right", ->
it "adds a drop shadow to the gutter", ->
editor.attachToDom()
@@ -1855,16 +1965,18 @@ describe "Editor", ->
editor.attachToDom()
describe "when a fold-selection event is triggered", ->
it "folds the lines covered by the selection into a single line with a fold class", ->
it "folds the lines covered by the selection into a single line with a fold class and marker", ->
editor.getSelection().setBufferRange(new Range([4, 29], [7, 4]))
editor.trigger 'editor:fold-selection'
expect(editor.renderedLines.find('.line:eq(4)')).toHaveClass('fold')
expect(editor.renderedLines.find('.line:eq(4) > .fold-marker')).toExist()
expect(editor.renderedLines.find('.line:eq(5)').text()).toBe '8'
expect(editor.getSelection().isEmpty()).toBeTruthy()
expect(editor.getCursorScreenPosition()).toEqual [5, 0]
describe "when a fold placeholder line is clicked", ->
it "removes the associated fold and places the cursor at its beginning", ->
editor.setCursorBufferPosition([3,0])
@@ -1873,6 +1985,7 @@ describe "Editor", ->
editor.find('.fold.line').mousedown()
expect(editor.find('.fold')).not.toExist()
expect(editor.find('.fold-marker')).not.toExist()
expect(editor.renderedLines.find('.line:eq(4)').text()).toMatch /4-+/
expect(editor.renderedLines.find('.line:eq(5)').text()).toMatch /5/
@@ -2212,3 +2325,244 @@ describe "Editor", ->
edited = editor.replaceSelectedText(replacer)
expect(replaced).toBe true
expect(edited).toBe false
describe "when editor:copy-path is triggered", ->
it "copies the absolute path to the editor's file to the pasteboard", ->
editor.trigger 'editor:copy-path'
expect(pasteboard.read()[0]).toBe editor.getPath()
describe "when editor:move-line-up is triggered", ->
describe "when there is no selection", ->
it "moves the line where the cursor is up", ->
editor.setCursorBufferPosition([1,0])
editor.trigger 'editor:move-line-up'
expect(buffer.lineForRow(0)).toBe ' var sort = function(items) {'
expect(buffer.lineForRow(1)).toBe 'var quicksort = function () {'
it "moves the cursor to the new row and the same column", ->
editor.setCursorBufferPosition([1,2])
editor.trigger 'editor:move-line-up'
expect(editor.getCursorBufferPosition()).toEqual [0,2]
describe "where there is a selection", ->
describe "when the selection falls inside the line", ->
it "maintains the selection", ->
editor.setSelectedBufferRange([[1, 2], [1, 5]])
expect(editor.getSelectedText()).toBe 'var'
editor.trigger 'editor:move-line-up'
expect(editor.getSelectedBufferRange()).toEqual [[0, 2], [0, 5]]
expect(editor.getSelectedText()).toBe 'var'
describe "where there are multiple lines selected", ->
it "moves the selected lines up", ->
editor.setSelectedBufferRange([[2, 0], [3, Infinity]])
editor.trigger 'editor:move-line-up'
expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {'
expect(buffer.lineForRow(1)).toBe ' if (items.length <= 1) return items;'
expect(buffer.lineForRow(2)).toBe ' var pivot = items.shift(), current, left = [], right = [];'
expect(buffer.lineForRow(3)).toBe ' var sort = function(items) {'
it "maintains the selection", ->
editor.setSelectedBufferRange([[2, 0], [3, 62]])
editor.trigger 'editor:move-line-up'
expect(editor.getSelectedBufferRange()).toEqual [[1, 0], [2, 62]]
describe "when the last line is selected", ->
it "moves the selected line up", ->
editor.setSelectedBufferRange([[12, 0], [12, Infinity]])
editor.trigger 'editor:move-line-up'
expect(buffer.lineForRow(11)).toBe '};'
expect(buffer.lineForRow(12)).toBe ' return sort(Array.apply(this, arguments));'
describe "when the last two lines are selected", ->
it "moves the selected lines up", ->
editor.setSelectedBufferRange([[11, 0], [12, Infinity]])
editor.trigger 'editor:move-line-up'
expect(buffer.lineForRow(10)).toBe ' return sort(Array.apply(this, arguments));'
expect(buffer.lineForRow(11)).toBe '};'
expect(buffer.lineForRow(12)).toBe ''
describe "when the cursor is on the first line", ->
it "does not move the line", ->
editor.setCursorBufferPosition([0,0])
originalText = editor.getText()
editor.trigger 'editor:move-line-up'
expect(editor.getText()).toBe originalText
describe "when the cursor is on the trailing newline", ->
it "does not move the line", ->
editor.moveCursorToBottom()
editor.insertNewline()
editor.moveCursorToBottom()
originalText = editor.getText()
editor.trigger 'editor:move-line-up'
expect(editor.getText()).toBe originalText
describe "when the cursor is on a folded line", ->
it "moves all lines in the fold up and preserves the fold", ->
editor.setCursorBufferPosition([4, 0])
editor.foldCurrentRow()
editor.trigger 'editor:move-line-up'
expect(buffer.lineForRow(3)).toBe ' while(items.length > 0) {'
expect(buffer.lineForRow(7)).toBe ' var pivot = items.shift(), current, left = [], right = [];'
expect(editor.getSelectedBufferRange()).toEqual [[3, 0], [3, 0]]
expect(editor.isFoldedAtScreenRow(3)).toBeTruthy()
describe "when the selection contains a folded and unfolded line", ->
it "moves the selected lines up and preserves the fold", ->
editor.setCursorBufferPosition([4, 0])
editor.foldCurrentRow()
editor.setCursorBufferPosition([3, 4])
editor.selectDown()
expect(editor.isFoldedAtScreenRow(4)).toBeTruthy()
editor.trigger 'editor:move-line-up'
expect(buffer.lineForRow(2)).toBe ' var pivot = items.shift(), current, left = [], right = [];'
expect(buffer.lineForRow(3)).toBe ' while(items.length > 0) {'
expect(editor.getSelectedBufferRange()).toEqual [[2, 4], [3, 0]]
expect(editor.isFoldedAtScreenRow(3)).toBeTruthy()
describe "when an entire line is selected including the newline", ->
it "moves the selected line up", ->
editor.setCursorBufferPosition([1])
editor.selectToEndOfLine()
editor.selectRight()
editor.trigger 'editor:move-line-up'
expect(buffer.lineForRow(0)).toBe ' var sort = function(items) {'
expect(buffer.lineForRow(1)).toBe 'var quicksort = function () {'
describe "when editor:move-line-down is triggered", ->
describe "when there is no selection", ->
it "moves the line where the cursor is down", ->
editor.setCursorBufferPosition([0, 0])
editor.trigger 'editor:move-line-down'
expect(buffer.lineForRow(0)).toBe ' var sort = function(items) {'
expect(buffer.lineForRow(1)).toBe 'var quicksort = function () {'
it "moves the cursor to the new row and the same column", ->
editor.setCursorBufferPosition([0, 2])
editor.trigger 'editor:move-line-down'
expect(editor.getCursorBufferPosition()).toEqual [1, 2]
describe "when the cursor is on the last line", ->
it "does not move the line", ->
editor.moveCursorToBottom()
editor.trigger 'editor:move-line-down'
expect(buffer.lineForRow(12)).toBe '};'
expect(editor.getSelectedBufferRange()).toEqual [[12, 2], [12, 2]]
describe "when the cursor is on the second to last line", ->
it "moves the line down", ->
editor.setCursorBufferPosition([11, 0])
editor.trigger 'editor:move-line-down'
expect(buffer.lineForRow(11)).toBe '};'
expect(buffer.lineForRow(12)).toBe ' return sort(Array.apply(this, arguments));'
expect(buffer.lineForRow(13)).toBeUndefined()
describe "when the cursor is on the second to last line and the last line is empty", ->
it "does not move the line", ->
editor.moveCursorToBottom()
editor.insertNewline()
editor.setCursorBufferPosition([12, 2])
editor.trigger 'editor:move-line-down'
expect(buffer.lineForRow(12)).toBe '};'
expect(buffer.lineForRow(13)).toBe ''
expect(editor.getSelectedBufferRange()).toEqual [[12, 2], [12, 2]]
describe "where there is a selection", ->
describe "when the selection falls inside the line", ->
it "maintains the selection", ->
editor.setSelectedBufferRange([[1, 2], [1, 5]])
expect(editor.getSelectedText()).toBe 'var'
editor.trigger 'editor:move-line-down'
expect(editor.getSelectedBufferRange()).toEqual [[2, 2], [2, 5]]
expect(editor.getSelectedText()).toBe 'var'
describe "where there are multiple lines selected", ->
it "moves the selected lines down", ->
editor.setSelectedBufferRange([[2, 0], [3, Infinity]])
editor.trigger 'editor:move-line-down'
expect(buffer.lineForRow(2)).toBe ' while(items.length > 0) {'
expect(buffer.lineForRow(3)).toBe ' if (items.length <= 1) return items;'
expect(buffer.lineForRow(4)).toBe ' var pivot = items.shift(), current, left = [], right = [];'
expect(buffer.lineForRow(5)).toBe ' current = items.shift();'
it "maintains the selection", ->
editor.setSelectedBufferRange([[2, 0], [3, 62]])
editor.trigger 'editor:move-line-down'
expect(editor.getSelectedBufferRange()).toEqual [[3, 0], [4, 62]]
describe "when the cursor is on a folded line", ->
it "moves all lines in the fold down and preserves the fold", ->
editor.setCursorBufferPosition([4, 0])
editor.foldCurrentRow()
editor.trigger 'editor:move-line-down'
expect(buffer.lineForRow(4)).toBe ' return sort(left).concat(pivot).concat(sort(right));'
expect(buffer.lineForRow(5)).toBe ' while(items.length > 0) {'
expect(editor.getSelectedBufferRange()).toEqual [[5, 0], [5, 0]]
expect(editor.isFoldedAtScreenRow(5)).toBeTruthy()
describe "when the selection contains a folded and unfolded line", ->
it "moves the selected lines down and preserves the fold", ->
editor.setCursorBufferPosition([4, 0])
editor.foldCurrentRow()
editor.setCursorBufferPosition([3, 4])
editor.selectDown()
expect(editor.isFoldedAtScreenRow(4)).toBeTruthy()
editor.trigger 'editor:move-line-down'
expect(buffer.lineForRow(3)).toBe ' return sort(left).concat(pivot).concat(sort(right));'
expect(buffer.lineForRow(4)).toBe ' var pivot = items.shift(), current, left = [], right = [];'
expect(buffer.lineForRow(5)).toBe ' while(items.length > 0) {'
expect(editor.getSelectedBufferRange()).toEqual [[4, 4], [5, 0]]
expect(editor.isFoldedAtScreenRow(5)).toBeTruthy()
describe "when an entire line is selected including the newline", ->
it "moves the selected line down", ->
editor.setCursorBufferPosition([1])
editor.selectToEndOfLine()
editor.selectRight()
editor.trigger 'editor:move-line-down'
expect(buffer.lineForRow(1)).toBe ' if (items.length <= 1) return items;'
expect(buffer.lineForRow(2)).toBe ' var sort = function(items) {'
describe "when editor:duplicate-line is triggered", ->
describe "where there is no selection", ->
describe "when the cursor isn't on a folded line", ->
it "duplicates the current line below and moves the cursor down one row", ->
editor.setCursorBufferPosition([0, 5])
editor.trigger 'editor:duplicate-line'
expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {'
expect(buffer.lineForRow(1)).toBe 'var quicksort = function () {'
expect(editor.getCursorBufferPosition()).toEqual [1, 5]
describe "when the cursor is on a folded line", ->
it "duplicates the entire fold before and moves the cursor to the new fold", ->
editor.setCursorBufferPosition([4])
editor.foldCurrentRow()
editor.trigger 'editor:duplicate-line'
expect(editor.getCursorScreenPosition()).toEqual [5]
expect(editor.isFoldedAtScreenRow(4)).toBeTruthy()
expect(editor.isFoldedAtScreenRow(5)).toBeTruthy()
expect(buffer.lineForRow(8)).toBe ' while(items.length > 0) {'
expect(buffer.lineForRow(9)).toBe ' current = items.shift();'
expect(buffer.lineForRow(10)).toBe ' current < pivot ? left.push(current) : right.push(current);'
expect(buffer.lineForRow(11)).toBe ' }'
describe "when the cursor is on the last line and it doesn't have a trailing newline", ->
it "inserts a newline and the duplicated line", ->
editor.moveCursorToBottom()
editor.trigger 'editor:duplicate-line'
expect(buffer.lineForRow(12)).toBe '};'
expect(buffer.lineForRow(13)).toBe '};'
expect(buffer.lineForRow(14)).toBeUndefined()
expect(editor.getCursorBufferPosition()).toEqual [13, 2]
describe "when the cursor in on the last line and it is only a newline", ->
it "duplicates the current line below and moves the cursor down one row", ->
editor.moveCursorToBottom()
editor.insertNewline()
editor.moveCursorToBottom()
editor.trigger 'editor:duplicate-line'
expect(buffer.lineForRow(13)).toBe ''
expect(buffer.lineForRow(14)).toBe ''
expect(buffer.lineForRow(15)).toBeUndefined()
expect(editor.getCursorBufferPosition()).toEqual [14, 0]

View File

@@ -308,16 +308,16 @@ describe "LanguageMode", ->
expect(buffer.lineForRow(3)).toBe " font-weight: bold !important;"
it "uncomments lines with leading whitespace", ->
buffer.replaceLines(2, 2, " /*width: 110%;*/")
buffer.change([[2, 0], [2, Infinity]], " /*width: 110%;*/")
languageMode.toggleLineCommentsForBufferRows(2, 2)
expect(buffer.lineForRow(2)).toBe " width: 110%;"
it "uncomments lines with trailing whitespace", ->
buffer.replaceLines(2, 2, "/*width: 110%;*/ ")
buffer.change([[2, 0], [2, Infinity]], "/*width: 110%;*/ ")
languageMode.toggleLineCommentsForBufferRows(2, 2)
expect(buffer.lineForRow(2)).toBe "width: 110%; "
it "uncomments lines with leading and trailing whitespace", ->
buffer.replaceLines(2, 2, " /*width: 110%;*/ ")
buffer.change([[2, 0], [2, Infinity]], " /*width: 110%;*/ ")
languageMode.toggleLineCommentsForBufferRows(2, 2)
expect(buffer.lineForRow(2)).toBe " width: 110%; "

View File

@@ -25,3 +25,9 @@ describe "Point", ->
expect(new Point(5, 0).compare(new Point(6, 1))).toBe -1
expect(new Point(5, 5).compare(new Point(4, 1))).toBe 1
expect(new Point(5, 5).compare(new Point(5, 3))).toBe 1
describe ".translate(other)", ->
it "returns a translated point", ->
expect(new Point(1,2).translate([2,4])).toEqual [3,6]
expect(new Point(1,2).translate([-1])).toEqual [0,2]
expect(new Point(1,2).translate([0,-2])).toEqual [1,0]

View File

@@ -32,3 +32,8 @@ describe "Range", ->
expect(new Range([2, 1], [3, 10]).union(new Range([1, 1], [2, 10]))).toEqual [[1, 1], [3, 10]]
expect(new Range([2, 1], [3, 10]).union(new Range([2, 5], [3, 1]))).toEqual [[2, 1], [3, 10]]
expect(new Range([2, 5], [3, 1]).union(new Range([2, 1], [3, 10]))).toEqual [[2, 1], [3, 10]]
describe ".translate(startPoint, endPoint)", ->
it "returns a range translates by the specified start and end points", ->
expect(new Range([1, 1], [2, 10]).translate([1])).toEqual [[2, 1], [3, 10]]
expect(new Range([1, 1], [2, 10]).translate([1,2], [3,4])).toEqual [[2, 3], [5, 14]]

View File

@@ -567,9 +567,9 @@ describe "RootView", ->
editor = null
beforeEach ->
editor = rootView.getActiveEditor()
editor.attachToDom()
it "increases/decreases font size when increase/decrease-font-size events are triggered", ->
editor = rootView.getActiveEditor()
fontSizeBefore = editor.getFontSize()
rootView.trigger 'window:increase-font-size'
expect(editor.getFontSize()).toBe fontSizeBefore + 1

View File

@@ -164,3 +164,16 @@ describe "SelectList", ->
miniEditor.trigger 'focusout'
expect(selectList.cancelled).toHaveBeenCalled()
expect(selectList.detach).toHaveBeenCalled()
describe "the core:move-to-top event", ->
it "scrolls to the top and selects the first element", ->
selectList.trigger 'core:move-down'
expect(list.find('li:eq(1)')).toHaveClass 'selected'
selectList.trigger 'core:move-to-top'
expect(list.find('li:first')).toHaveClass 'selected'
describe "the core:move-to-bottom event", ->
it "scrolls to the bottom and selects the last element", ->
expect(list.find('li:first')).toHaveClass 'selected'
selectList.trigger 'core:move-to-bottom'
expect(list.find('li:last')).toHaveClass 'selected'

View File

@@ -23,8 +23,7 @@ describe "the `syntax` global", ->
expect(syntax.grammarForFilePath("/tmp/.git/config").name).toBe "Git Config"
it "uses plain text if no grammar can be found", ->
filePath = require.resolve("this-is-not-a-real-file")
expect(syntax.grammarForFilePath(filePath).name).toBe "Plain Text"
expect(syntax.grammarForFilePath("this-is-not-a-real-file").name).toBe "Plain Text"
describe ".getProperty(scopeDescriptor)", ->
it "returns the property with the most specific scope selector", ->

View File

@@ -27,12 +27,12 @@ describe "TextMateTheme", ->
'color': '#F8F8F8'
expect(rulesets[1]).toEqual
selector: '.editor.focused .cursor'
selector: '.editor.is-focused .cursor'
properties:
'border-color': '#A7A7A7'
expect(rulesets[2]).toEqual
selector: '.editor.focused .selection .region'
selector: '.editor.is-focused .selection .region'
properties:
'background-color': "rgba(221, 240, 255, 0.2)"
@@ -42,7 +42,7 @@ describe "TextMateTheme", ->
selector: ".invalid.deprecated"
properties:
'color': "#D2A8A1"
# 'font-style': 'italic'
'font-style': 'italic'
'text-decoration': 'underline'
expect(rulesets[13]).toEqual

View File

@@ -5,6 +5,7 @@ describe "Window", ->
[rootView] = []
beforeEach ->
window.setUpEventHandlers()
window.attachRootView(require.resolve('fixtures'))
rootView = window.rootView
@@ -13,6 +14,25 @@ describe "Window", ->
atom.setRootViewStateForPath(rootView.project.getPath(), null)
$(window).off 'beforeunload'
describe "when the window is loaded", ->
it "doesn't have .is-blurred on the body tag", ->
expect($("body")).not.toHaveClass("is-blurred")
describe "when the window is blurred", ->
beforeEach ->
$(window).trigger 'blur'
afterEach ->
$('body').removeClass('is-blurred')
it "adds the .is-blurred class on the body", ->
expect($("body")).toHaveClass("is-blurred")
describe "when the window is focused again", ->
it "removes the .is-blurred class from the body", ->
$(window).trigger 'focus'
expect($("body")).not.toHaveClass("is-blurred")
describe ".close()", ->
it "is triggered by the 'core:close' event", ->
spyOn window, 'close'

View File

@@ -0,0 +1,4 @@
module.exports =
load: ->
$ = require 'jquery'
callTaskMethod('loaded', $?)

View File

@@ -0,0 +1,2 @@
".test-3":
"ctrl-z": "test-3"

View File

@@ -0,0 +1 @@
I am hidden so I shouldn't be loaded

View File

@@ -0,0 +1 @@
I am not a valid plist but that shouldn't cause a crisis

View File

@@ -35,6 +35,7 @@ beforeEach ->
spyOn(config, 'save')
config.set "editor.fontSize", 16
config.set "editor.autoIndent", false
config.set "core.disabledPackages", ["package-that-throws-an-exception"]
# make editor display updates synchronous
spyOn(Editor.prototype, 'requestDisplayUpdate').andCallFake -> @updateDisplay()
@@ -84,7 +85,7 @@ jasmine.unspy = (object, methodName) ->
throw new Error("Not a spy") unless object[methodName].originalValue?
object[methodName] = object[methodName].originalValue
jasmine.getEnv().defaultTimeoutInterval = 500
jasmine.getEnv().defaultTimeoutInterval = 1000
window.keyIdentifierForKey = (key) ->
if key.length > 1 # named key

View File

@@ -0,0 +1,86 @@
CSON = require 'cson'
describe "CSON", ->
describe "@stringify(object)", ->
describe "when the object is undefined", ->
it "throws an exception", ->
expect(-> CSON.stringify()).toThrow()
describe "when the object is a function", ->
it "throws an exception", ->
expect(-> CSON.stringify(-> 'function')).toThrow()
describe "when the object contains a function", ->
it "throws an exception", ->
expect(-> CSON.stringify(a: -> 'function')).toThrow()
describe "when formatting an undefined key", ->
it "does not include the key in the formatted CSON", ->
expect(CSON.stringify(b: 1, c: undefined)).toBe "'b': 1"
describe "when formatting a string", ->
it "returns formatted CSON", ->
expect(CSON.stringify(a: 'b')).toBe "'a': 'b'"
it "escapes single quotes", ->
expect(CSON.stringify(a: "'b'")).toBe "'a': '\\\'b\\\''"
it "doesn't escape double quotes", ->
expect(CSON.stringify(a: '"b"')).toBe "'a': '\"b\"'"
it "escapes newlines", ->
expect(CSON.stringify("a\nb")).toBe "'a\\nb'"
describe "when formatting a boolean", ->
it "returns formatted CSON", ->
expect(CSON.stringify(true)).toBe 'true'
expect(CSON.stringify(false)).toBe 'false'
expect(CSON.stringify(a: true)).toBe "'a': true"
expect(CSON.stringify(a: false)).toBe "'a': false"
describe "when formatting a number", ->
it "returns formatted CSON", ->
expect(CSON.stringify(54321.012345)).toBe '54321.012345'
expect(CSON.stringify(a: 14)).toBe "'a': 14"
expect(CSON.stringify(a: 1.23)).toBe "'a': 1.23"
describe "when formatting null", ->
it "returns formatted CSON", ->
expect(CSON.stringify(null)).toBe 'null'
expect(CSON.stringify(a: null)).toBe "'a': null"
describe "when formatting an array", ->
describe "when the array is empty", ->
it "puts the array on a single line", ->
expect(CSON.stringify([])).toBe "[]"
it "returns formatted CSON", ->
expect(CSON.stringify(a: ['b'])).toBe "'a': [\n 'b'\n]"
expect(CSON.stringify(a: ['b', 4])).toBe "'a': [\n 'b'\n 4\n]"
describe "when the array has an undefined value", ->
it "formats the undefined value as null", ->
expect(CSON.stringify(['a', undefined, 'b'])).toBe "[\n 'a'\n null\n 'b'\n]"
describe "when formatting an object", ->
describe "when the object is empty", ->
it "returns the empty string", ->
expect(CSON.stringify({})).toBe ""
it "returns formatted CSON", ->
expect(CSON.stringify(a: {b: 'c'})).toBe "'a':\n 'b': 'c'"
describe "when converting back to an object", ->
it "produces the original object", ->
object =
showInvisibles: true
fontSize: 20
core:
themes: ['a', 'b']
stripTrailingWhitespace:
singleTrailingNewline: true
cson = CSON.stringify(object)
{CoffeeScript} = require 'coffee-script'
evaledObject = CoffeeScript.eval(cson, bare: true)
expect(evaledObject).toEqual object

View File

@@ -128,3 +128,8 @@ describe "fs", ->
describe ".md5ForPath(path)", ->
it "returns the MD5 hash of the file at the given path", ->
expect(fs.md5ForPath(require.resolve('fixtures/sample.js'))).toBe 'dd38087d0d7e3e4802a6d3f9b9745f2b'
describe ".list(path, extensions)", ->
it "returns the paths with the specified extensions", ->
path = require.resolve('fixtures/css.css')
expect(fs.list(require.resolve('fixtures'), ['.css'])).toEqual [path]

View File

@@ -1,3 +1,5 @@
OnigRegExp = require 'onig-reg-exp'
describe "OnigRegExp", ->
describe ".search(string, index)", ->
it "returns an array of the match and all capture groups", ->

View File

@@ -0,0 +1,23 @@
Task = require 'task'
describe "Task shell", ->
describe "populating the window with fake properties", ->
describe "when jQuery is loaded in a web worker", ->
it "doesn't log to the console", ->
spyOn(console, 'log')
spyOn(console, 'error')
spyOn(console, 'warn')
class JQueryTask extends Task
constructor: -> super('fixtures/jquery-task-handler.coffee')
started: -> @callWorkerMethod('load')
loaded: (@jqueryLoaded) ->
task = new JQueryTask()
task.start()
waitsFor "web worker to start and jquery to be required", 5000, ->
task.jqueryLoaded
runs ->
expect(task.jqueryLoaded).toBeTruthy()
expect(console.log).not.toHaveBeenCalled()
expect(console.error).not.toHaveBeenCalled()
expect(console.warn).not.toHaveBeenCalled()

View File

@@ -16,7 +16,7 @@ class AtomPackage extends Package
@loadMetadata()
@loadKeymaps()
@loadStylesheets() if @autoloadStylesheets
rootView.activatePackage(@name, this) unless @isDirectory
rootView?.activatePackage(@name, this) unless @isDirectory
catch e
console.warn "Failed to load package named '#{@name}'", e.stack
this
@@ -26,15 +26,12 @@ class AtomPackage extends Package
@metadata = fs.readObject(metadataPath)
loadKeymaps: ->
for keymapPath in @getKeymapPaths()
keymap.load(keymapPath)
getKeymapPaths: ->
if keymaps = @metadata?.keymaps
keymaps.map (relativePath) =>
keymaps = keymaps.map (relativePath) =>
fs.resolve(@keymapsDirPath, relativePath, ['cson', 'json', ''])
keymap.load(keymapPath) for keymapPath in keymaps
else
fs.list(@keymapsDirPath)
keymap.loadDirectory(@keymapsDirPath)
loadStylesheets: ->
for stylesheetPath in @getStylesheetPaths()

View File

@@ -3,9 +3,16 @@ Theme = require 'theme'
module.exports =
class AtomTheme extends Theme
loadStylesheet: (stylesheetPath)->
@stylesheets[stylesheetPath] = fs.read(stylesheetPath)
load: ->
json = fs.read(fs.join(@path, "package.json"))
for stylesheetName in JSON.parse(json).stylesheets
stylesheetPath = fs.join(@path, stylesheetName)
@stylesheets[stylesheetPath] = fs.read(stylesheetPath)
if /\.css$/.test(@path)
@loadStylesheet @path
else
json = fs.read(fs.join(@path, "package.json"))
for stylesheetName in JSON.parse(json).stylesheets
stylesheetPath = fs.join(@path, stylesheetName)
@loadStylesheet stylesheetPath
super

View File

@@ -3,6 +3,7 @@ _ = require 'underscore'
Package = require 'package'
TextMatePackage = require 'text-mate-package'
Theme = require 'theme'
LoadTextMatePackagesTask = require 'load-text-mate-packages-task'
messageIdCounter = 1
originalSendMessageToBrowserProcess = atom.sendMessageToBrowserProcess
@@ -13,10 +14,20 @@ _.extend atom,
pendingBrowserProcessCallbacks: {}
loadPackages: ->
pack.load() for pack in @getPackages()
{packages, asyncTextMatePackages} = _.groupBy @getPackages(), (pack) ->
if pack instanceof TextMatePackage and pack.name isnt 'text.tmbundle'
'asyncTextMatePackages'
else
'packages'
pack.load() for pack in packages
if asyncTextMatePackages.length
new LoadTextMatePackagesTask(asyncTextMatePackages).start()
getPackages: ->
@getPackageNames().map((name) -> Package.build(name)).filter (pack) -> pack?
@packages ?= @getPackageNames().map((name) -> Package.build(name))
.filter((pack) -> pack?)
new Array(@packages...)
loadTextMatePackages: ->
pack.load() for pack in @getTextMatePackages()
@@ -39,15 +50,21 @@ _.extend atom,
.filter (name) -> not _.contains(disabledPackages, name)
loadThemes: ->
themeNames = config.get("core.themes") ? ['Atom - Dark', 'IR_Black']
themeNames = config.get("core.themes") ? ['atom-dark-ui', 'atom-dark-syntax']
themeNames = [themeNames] unless _.isArray(themeNames)
@loadTheme(themeName) for themeName in themeNames
@loadUserStylesheet()
loadTheme: (name) ->
@loadedThemes.push Theme.load(name)
loadUserStylesheet: ->
userStylesheetPath = fs.join(config.configDirPath, 'user.css')
if fs.isFile(userStylesheetPath)
applyStylesheet(userStylesheetPath, fs.read(userStylesheetPath), 'userTheme')
getAtomThemeStylesheets: ->
themeNames = config.get("core.themes") ? ['Atom - Dark', 'IR_Black']
themeNames = config.get("core.themes") ? ['atom-dark-ui', 'atom-dark-syntax']
themeNames = [themeNames] unless _.isArray(themeNames)
open: (args...) ->
@@ -94,6 +111,9 @@ _.extend atom,
endTracing: ->
@sendMessageToBrowserProcess('endTracing')
toggleFullScreen: ->
@sendMessageToBrowserProcess('toggleFullScreen')
getRootViewStateForPath: (path) ->
if json = localStorage[path]
JSON.parse(json)

View File

@@ -1,4 +1,5 @@
Range = require 'range'
_ = require 'underscore'
module.exports =
class BufferChangeOperation
@@ -8,7 +9,8 @@ class BufferChangeOperation
newRange: null
newText: null
constructor: ({@buffer, @oldRange, @newText}) ->
constructor: ({@buffer, @oldRange, @newText, @options}) ->
@options ?= {}
do: ->
@oldText = @buffer.getTextInRange(@oldRange)
@@ -26,18 +28,39 @@ class BufferChangeOperation
oldText: @newText
newText: @oldText
splitLines: (text) ->
lines = text.split('\n')
lineEndings = []
for line, index in lines
if _.endsWith(line, '\r')
lines[index] = line[...-1]
lineEndings[index] = '\r\n'
else
lineEndings[index] = '\n'
{lines, lineEndings}
changeBuffer: ({ oldRange, newRange, newText, oldText }) ->
{ prefix, suffix } = @buffer.prefixAndSuffixForRange(oldRange)
newTextLines = newText.split('\n')
if newTextLines.length == 1
newTextLines = [prefix + newText + suffix]
else
lastLineIndex = newTextLines.length - 1
newTextLines[0] = prefix + newTextLines[0]
newTextLines[lastLineIndex] += suffix
{lines, lineEndings} = @splitLines(newText)
lastLineIndex = lines.length - 1
@buffer.replaceLines(oldRange.start.row, oldRange.end.row, newTextLines)
if lines.length == 1
lines = [prefix + newText + suffix]
else
lines[0] = prefix + lines[0]
lines[lastLineIndex] += suffix
startRow = oldRange.start.row
endRow = oldRange.end.row
normalizeLineEndings = @options.normalizeLineEndings ? true
if normalizeLineEndings and suggestedLineEnding = @buffer.suggestedLineEndingForRow(startRow)
lineEndings[index] = suggestedLineEnding for index in [0..lastLineIndex]
@buffer.lines[startRow..endRow] = lines
@buffer.lineEndings[startRow..endRow] = lineEndings
@buffer.cachedMemoryContents = null
@buffer.conflict = false if @buffer.conflict and !@buffer.isModified()
event = { oldRange, newRange, oldText, newText }
@buffer.trigger 'changed', event
@@ -47,11 +70,11 @@ class BufferChangeOperation
calculateNewRange: (oldRange, newText) ->
newRange = new Range(oldRange.start.copy(), oldRange.start.copy())
newTextLines = newText.split('\n')
if newTextLines.length == 1
{lines} = @splitLines(newText)
if lines.length == 1
newRange.end.column += newText.length
else
lastLineIndex = newTextLines.length - 1
lastLineIndex = lines.length - 1
newRange.end.row += lastLineIndex
newRange.end.column = newTextLines[lastLineIndex].length
newRange.end.column = lines[lastLineIndex].length
newRange

View File

@@ -19,6 +19,7 @@ class Buffer
cachedMemoryContents: null
conflict: false
lines: null
lineEndings: null
file: null
anchors: null
anchorRanges: null
@@ -29,6 +30,7 @@ class Buffer
@anchors = []
@anchorRanges = []
@lines = ['']
@lineEndings = []
if path
throw "Path '#{path}' does not exist" unless fs.exists(path)
@@ -104,26 +106,28 @@ class Buffer
null
getText: ->
@cachedMemoryContents ?= @lines.join('\n')
@cachedMemoryContents ?= @getTextInRange(@getRange())
setText: (text) ->
@change(@getRange(), text)
@change(@getRange(), text, normalizeLineEndings: false)
getRange: ->
new Range([0, 0], [@getLastRow(), @getLastLine().length])
getTextInRange: (range) ->
range = Range.fromObject(range)
range = @clipRange(range)
if range.start.row == range.end.row
return @lines[range.start.row][range.start.column...range.end.column]
return @lineForRow(range.start.row)[range.start.column...range.end.column]
multipleLines = []
multipleLines.push @lines[range.start.row][range.start.column..] # first line
multipleLines.push @lineForRow(range.start.row)[range.start.column..] # first line
multipleLines.push @lineEndingForRow(range.start.row)
for row in [range.start.row + 1...range.end.row]
multipleLines.push @lines[row] # middle lines
multipleLines.push @lines[range.end.row][0...range.end.column] # last line
multipleLines.push @lineForRow(row) # middle lines
multipleLines.push @lineEndingForRow(row)
multipleLines.push @lineForRow(range.end.row)[0...range.end.column] # last line
return multipleLines.join '\n'
return multipleLines.join ''
getLines: ->
@lines
@@ -131,6 +135,12 @@ class Buffer
lineForRow: (row) ->
@lines[row]
lineEndingForRow: (row) ->
@lineEndings[row] unless row is @getLastRow()
suggestedLineEndingForRow: (row) ->
@lineEndingForRow(row) ? @lineEndingForRow(row - 1)
lineLengthForRow: (row) ->
@lines[row].length
@@ -184,7 +194,7 @@ class Buffer
startPoint = [start, 0]
endPoint = [end + 1, 0]
@change(new Range(startPoint, endPoint), '')
@delete(new Range(startPoint, endPoint))
append: (text) ->
@insert(@getEofPosition(), text)
@@ -195,30 +205,31 @@ class Buffer
delete: (range) ->
@change(range, '')
change: (oldRange, newText) ->
change: (oldRange, newText, options) ->
oldRange = Range.fromObject(oldRange)
operation = new BufferChangeOperation({buffer: this, oldRange, newText})
operation = new BufferChangeOperation({buffer: this, oldRange, newText, options})
range = @pushOperation(operation)
range
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)
position = Point.fromObject(position)
eofPosition = @getEofPosition()
if position.isGreaterThan(eofPosition)
eofPosition
else
row = Math.max(position.row, 0)
column = Math.max(position.column, 0)
column = Math.min(@lineLengthForRow(row), column)
new Point(row, column)
new Point(row, column)
clipRange: (range) ->
range = Range.fromObject(range)
new Range(@clipPosition(range.start), @clipPosition(range.end))
prefixAndSuffixForRange: (range) ->
prefix: @lines[range.start.row][0...range.start.column]
suffix: @lines[range.end.row][range.end.column..]
replaceLines: (startRow, endRow, newLines) ->
@lines[startRow..endRow] = newLines
@cachedMemoryContents = null
@conflict = false if @conflict and !@isModified()
pushOperation: (operation, editSession) ->
if @undoManager
@undoManager.pushOperation(operation, editSession)

View File

@@ -1,14 +1,9 @@
fs = require 'fs'
_ = require 'underscore'
EventEmitter = require 'event-emitter'
{$$} = require 'space-pen'
jQuery = require 'jquery'
Specificity = require 'specificity'
Theme = require 'theme'
configDirPath = fs.absolute("~/.atom")
configJsonPath = fs.join(configDirPath, "config.json")
userInitScriptPath = fs.join(configDirPath, "atom.coffee")
userInitScriptPath = fs.join(configDirPath, "user.coffee")
bundledPackagesDirPath = fs.join(resourcePath, "src/packages")
bundledThemesDirPath = fs.join(resourcePath, "themes")
vendoredPackagesDirPath = fs.join(resourcePath, "vendor/packages")
@@ -31,6 +26,8 @@ class Config
core: _.clone(require('root-view').configDefaults)
editor: _.clone(require('editor').configDefaults)
@settings = {}
@configFilePath = fs.resolve(configDirPath, 'config', ['json', 'cson'])
@configFilePath ?= fs.join(configDirPath, 'config.cson')
load: ->
@loadUserConfig()
@@ -39,8 +36,8 @@ class Config
atom.loadPackages()
loadUserConfig: ->
if fs.exists(configJsonPath)
userConfig = JSON.parse(fs.read(configJsonPath))
if fs.exists(@configFilePath)
userConfig = fs.readObject(@configFilePath)
_.extend(@settings, userConfig)
get: (keyPath) ->
@@ -81,7 +78,7 @@ class Config
@trigger 'updated'
save: ->
fs.write(configJsonPath, JSON.stringify(@settings, undefined, 2) + "\n")
fs.writeObject(@configFilePath, @settings)
requireUserInitScript: ->
try

View File

@@ -9,7 +9,6 @@ class Cursor
screenPosition: null
bufferPosition: null
goalColumn: null
wordRegex: /(\w+)|([^\w\n]+)/g
visible: true
needsAutoscroll: false
@@ -56,9 +55,18 @@ class Cursor
isVisible: -> @visible
wordRegExp: ->
nonWordCharacters = config.get("editor.nonWordCharacters")
new RegExp("^[\t ]*$|[^\\s#{_.escapeRegExp(nonWordCharacters)}]+|[#{_.escapeRegExp(nonWordCharacters)}]+", "g")
isLastCursor: ->
this == @editSession.getCursor()
isSurroundedByWhitespace: ->
{row, column} = @getBufferPosition()
range = [[row, Math.min(0, column - 1)], [row, Math.max(0, column + 1)]]
/^\s+$/.test @editSession.getTextInBufferRange(range)
autoscrolled: ->
@needsAutoscroll = false
@@ -147,14 +155,16 @@ class Cursor
allowPrevious = options.allowPrevious ? true
currentBufferPosition = @getBufferPosition()
previousNonBlankRow = @editSession.buffer.previousNonBlankRow(currentBufferPosition.row)
previousLinesRange = [[previousNonBlankRow, 0], currentBufferPosition]
range = [[previousNonBlankRow, 0], currentBufferPosition]
beginningOfWordPosition = currentBufferPosition
@editSession.backwardsScanInRange (options.wordRegex || @wordRegex), previousLinesRange, (match, matchRange, { stop }) =>
beginningOfWordPosition = null
@editSession.backwardsScanInRange (options.wordRegex ? @wordRegExp()), range, (match, matchRange, { stop }) =>
if matchRange.end.isGreaterThanOrEqual(currentBufferPosition) or allowPrevious
beginningOfWordPosition = matchRange.start
stop()
beginningOfWordPosition
if not beginningOfWordPosition?.isEqual(currentBufferPosition)
stop()
beginningOfWordPosition or currentBufferPosition
getEndOfCurrentWordBufferPosition: (options = {}) ->
allowNext = options.allowNext ? true
@@ -162,11 +172,12 @@ class Cursor
range = [currentBufferPosition, @editSession.getEofBufferPosition()]
endOfWordPosition = null
@editSession.scanInRange (options.wordRegex || @wordRegex), range, (match, matchRange, { stop }) =>
endOfWordPosition = matchRange.end
if not allowNext and matchRange.start.isGreaterThan(currentBufferPosition)
endOfWordPosition = currentBufferPosition
stop()
@editSession.scanInRange (options.wordRegex ? @wordRegExp()), range, (match, matchRange, { stop }) =>
if matchRange.start.isLessThanOrEqual(currentBufferPosition) or allowNext
endOfWordPosition = matchRange.end
if not endOfWordPosition?.isEqual(currentBufferPosition)
stop()
endOfWordPosition or currentBufferPosition
getCurrentWordBufferRange: (options={}) ->

View File

@@ -18,7 +18,7 @@ class EditSession
if fs.exists(state.buffer)
session = project.buildEditSessionForPath(state.buffer)
else
console.warn "Could not build edit session for path '#{state.buffer}' because that file no longer exists"
console.warn "Could not build edit session for path '#{state.buffer}' because that file no longer exists" if state.buffer
session = project.buildEditSessionForPath(null)
session.setScrollTop(state.scrollTop)
session.setScrollLeft(state.scrollLeft)
@@ -211,6 +211,10 @@ class EditSession
autoIndentSelectedRows: ->
@mutateSelectedText (selection) -> selection.autoIndentSelectedRows()
normalizeTabsInBufferRange: (bufferRange) ->
return unless @softTabs
@scanInRange /\t/, bufferRange, (match, range, {replace}) => replace(@getTabText())
cutToEndOfLine: ->
maintainPasteboard = false
@mutateSelectedText (selection) ->
@@ -251,8 +255,9 @@ class EditSession
undo: (editSession) ->
editSession?.setSelectedBufferRanges(oldSelectedRanges)
if fn
fn()
result = fn()
@commit() if isNewTransaction
result
commit: ->
newSelectedRanges = @getSelectedBufferRanges()
@@ -302,6 +307,13 @@ class EditSession
fold.destroy()
@setCursorBufferPosition([fold.startRow, 0])
isFoldedAtCursorRow: ->
@isFoldedAtScreenRow(@getCursorScreenRow())
isFoldedAtBufferRow: (bufferRow) ->
screenRow = @screenPositionForBufferPosition([bufferRow]).row
@isFoldedAtScreenRow(screenRow)
isFoldedAtScreenRow: (screenRow) ->
@lineForScreenRow(screenRow)?.fold?
@@ -329,6 +341,99 @@ class EditSession
toggleLineCommentsForBufferRows: (start, end) ->
@languageMode.toggleLineCommentsForBufferRows(start, end)
moveLineUp: ->
selection = @getSelectedBufferRange()
return if selection.start.row is 0
lastRow = @buffer.getLastRow()
return if selection.isEmpty() and selection.start.row is lastRow and @buffer.getLastLine() is ''
@transact =>
foldedRows = []
rows = [selection.start.row..selection.end.row]
if selection.start.row isnt selection.end.row and selection.end.column is 0
rows.pop() unless @isFoldedAtBufferRow(selection.end.row)
for row in rows
screenRow = @screenPositionForBufferPosition([row]).row
if @isFoldedAtScreenRow(screenRow)
bufferRange = @bufferRangeForScreenRange([[screenRow], [screenRow + 1]])
startRow = bufferRange.start.row
endRow = bufferRange.end.row - 1
foldedRows.push(endRow - 1)
else
startRow = row
endRow = row
endPosition = Point.min([endRow + 1], @buffer.getEofPosition())
lines = @buffer.getTextInRange([[startRow], endPosition])
if endPosition.row is lastRow and endPosition.column > 0 and not @buffer.lineEndingForRow(endPosition.row)
lines = "#{lines}\n"
@buffer.deleteRows(startRow, endRow)
@buffer.insert([startRow - 1], lines)
@foldBufferRow(foldedRow) for foldedRow in foldedRows
@setSelectedBufferRange(selection.translate([-1]), preserveFolds: true)
moveLineDown: ->
selection = @getSelectedBufferRange()
lastRow = @buffer.getLastRow()
return if selection.end.row is lastRow
return if selection.end.row is lastRow - 1 and @buffer.getLastLine() is ''
@transact =>
foldedRows = []
rows = [selection.end.row..selection.start.row]
if selection.start.row isnt selection.end.row and selection.end.column is 0
rows.shift() unless @isFoldedAtBufferRow(selection.end.row)
for row in rows
screenRow = @screenPositionForBufferPosition([row]).row
if @isFoldedAtScreenRow(screenRow)
bufferRange = @bufferRangeForScreenRange([[screenRow], [screenRow + 1]])
startRow = bufferRange.start.row
endRow = bufferRange.end.row - 1
foldedRows.push(endRow + 1)
else
startRow = row
endRow = row
if endRow + 1 is lastRow
endPosition = [endRow, @buffer.lineLengthForRow(endRow)]
else
endPosition = [endRow + 1]
lines = @buffer.getTextInRange([[startRow], endPosition])
@buffer.deleteRows(startRow, endRow)
insertPosition = Point.min([startRow + 1], @buffer.getEofPosition())
if insertPosition.row is @buffer.getLastRow() and insertPosition.column > 0
lines = "\n#{lines}"
@buffer.insert(insertPosition, lines)
@foldBufferRow(foldedRow) for foldedRow in foldedRows
@setSelectedBufferRange(selection.translate([1]), preserveFolds: true)
duplicateLine: ->
return unless @getSelection().isEmpty()
@transact =>
cursorPosition = @getCursorBufferPosition()
cursorRowFolded = @isFoldedAtCursorRow()
if cursorRowFolded
screenRow = @screenPositionForBufferPosition(cursorPosition).row
bufferRange = @bufferRangeForScreenRange([[screenRow], [screenRow + 1]])
else
bufferRange = new Range([cursorPosition.row], [cursorPosition.row + 1])
insertPosition = new Point(bufferRange.end.row)
if insertPosition.row >= @buffer.getLastRow()
@unfoldCurrentRow() if cursorRowFolded
@buffer.append("\n#{@getTextInBufferRange(bufferRange)}")
@foldCurrentRow() if cursorRowFolded
else
@buffer.insert(insertPosition, @getTextInBufferRange(bufferRange))
@setCursorScreenPosition(@getCursorScreenPosition().translate([1]))
@foldCurrentRow() if cursorRowFolded
mutateSelectedText: (fn) ->
@transact => fn(selection) for selection in @getSelections()

View File

@@ -19,6 +19,7 @@ class Editor extends View
autosave: false
autoIndent: true
autoIndentOnPaste: false
nonWordCharacters: "./\\()\"'-_:,.;<>~!@#$%^&*|+=[]{}`~?"
@content: (params) ->
@div class: @classes(params), tabindex: -1, =>
@@ -182,6 +183,10 @@ class Editor extends View
'editor:close-other-edit-sessions': @destroyInactiveEditSessions
'editor:close-all-edit-sessions': @destroyAllEditSessions
'editor:select-grammar': @selectGrammar
'editor:copy-path': @copyPathToPasteboard
'editor:move-line-up': @moveLineUp
'editor:move-line-down': @moveLineDown
'editor:duplicate-line': @duplicateLine
documentation = {}
for name, method of editorBindings
@@ -203,6 +208,9 @@ class Editor extends View
moveCursorToBeginningOfLine: -> @activeEditSession.moveCursorToBeginningOfLine()
moveCursorToFirstCharacterOfLine: -> @activeEditSession.moveCursorToFirstCharacterOfLine()
moveCursorToEndOfLine: -> @activeEditSession.moveCursorToEndOfLine()
moveLineUp: -> @activeEditSession.moveLineUp()
moveLineDown: -> @activeEditSession.moveLineDown()
duplicateLine: -> @activeEditSession.duplicateLine()
setCursorScreenPosition: (position) -> @activeEditSession.setCursorScreenPosition(position)
getCursorScreenPosition: -> @activeEditSession.getCursorScreenPosition()
getCursorScreenRow: -> @activeEditSession.getCursorScreenRow()
@@ -270,6 +278,8 @@ class Editor extends View
destroyFold: (foldId) -> @activeEditSession.destroyFold(foldId)
destroyFoldsContainingBufferRow: (bufferRow) -> @activeEditSession.destroyFoldsContainingBufferRow(bufferRow)
isFoldedAtScreenRow: (screenRow) -> @activeEditSession.isFoldedAtScreenRow(screenRow)
isFoldedAtBufferRow: (bufferRow) -> @activeEditSession.isFoldedAtBufferRow(bufferRow)
isFoldedAtCursorRow: -> @activeEditSession.isFoldedAtCursorRow()
lineForScreenRow: (screenRow) -> @activeEditSession.lineForScreenRow(screenRow)
linesForScreenRows: (start, end) -> @activeEditSession.linesForScreenRows(start, end)
@@ -310,9 +320,10 @@ class Editor extends View
setInvisibles: (@invisibles={}) ->
_.defaults @invisibles,
eol: '\u00ac',
space: '\u2022',
eol: '\u00ac'
space: '\u00b7'
tab: '\u00bb'
cr: '\u00a4'
@resetDisplay()
checkoutHead: -> @getBuffer().checkoutHead()
@@ -333,6 +344,7 @@ class Editor extends View
@observeConfig 'editor.showInvisibles', (showInvisibles) => @setShowInvisibles(showInvisibles)
@observeConfig 'editor.invisibles', (invisibles) => @setInvisibles(invisibles)
@observeConfig 'editor.fontSize', (fontSize) => @setFontSize(fontSize)
@observeConfig 'editor.fontFamily', (fontFamily) => @setFontFamily(fontFamily)
handleEvents: ->
@on 'focus', =>
@@ -342,12 +354,12 @@ class Editor extends View
@hiddenInput.on 'focus', =>
@rootView()?.editorFocused(this)
@isFocused = true
@addClass 'focused'
@addClass 'is-focused'
@hiddenInput.on 'focusout', =>
@isFocused = false
@removeClass 'focused'
@autosave() if config.get "editor.autosave"
@removeClass 'is-focused'
@underlayer.on 'click', (e) =>
return unless e.target is @underlayer[0]
@@ -402,6 +414,11 @@ class Editor extends View
@gutter.widthChanged = (newWidth) =>
@scrollView.css('left', newWidth + 'px')
syntax.on 'grammars-loaded', =>
@reloadGrammar()
for session in @editSessions
session.reloadGrammar() unless session is @activeEditSession
@scrollView.on 'scroll', =>
if @scrollView.scrollLeft() == 0
@gutter.removeClass('drop-shadow')
@@ -668,16 +685,38 @@ class Editor extends View
autosave: ->
@save() if @getPath()?
setFontSize: (@fontSize) ->
if fontSize?
@css('font-size', fontSize + 'px')
return unless @attached
@calculateDimensions()
@updatePaddingOfRenderedLines()
@updateLayerDimensions()
@requestDisplayUpdate()
setFontSize: (fontSize) ->
headTag = $("head")
styleTag = headTag.find("style.font-size")
if styleTag.length == 0
styleTag = $$ -> @style class: 'font-size'
headTag.append styleTag
getFontSize: -> @fontSize
styleTag.text(".editor {font-size: #{fontSize}px}")
@redraw()
getFontSize: ->
parseInt(@css("font-size"))
setFontFamily: (fontFamily) ->
return if fontFamily == undefined
headTag = $("head")
styleTag = headTag.find("style.font-family")
if styleTag.length == 0
styleTag = $$ -> @style class: 'font-family'
headTag.append styleTag
styleTag.text(".editor {font-family: #{fontFamily}}")
@redraw()
getFontFamily: -> @css("font-family")
redraw: ->
return unless @attached
@calculateDimensions()
@updatePaddingOfRenderedLines()
@updateLayerDimensions()
@requestDisplayUpdate()
newSplitEditor: (editSession) ->
new Editor { editSession: editSession ? @activeEditSession.copy() }
@@ -768,6 +807,10 @@ class Editor extends View
@overlayer.append(view)
calculateDimensions: ->
if not @isOnDom()
detachedEditorParent = _.last(@parents()) ? this
$(document.body).append(detachedEditorParent)
fragment = $('<pre class="line" style="position: absolute; visibility: hidden;"><span>x</span></div>')
@renderedLines.append(fragment)
@@ -779,6 +822,8 @@ class Editor extends View
@height(@lineHeight) if @mini
fragment.remove()
$(detachedEditorParent).detach()
updateLayerDimensions: ->
@gutter.calculateWidth()
@@ -1046,8 +1091,6 @@ class Editor extends View
if fold = screenLine.fold
lineAttributes = { class: 'fold line', 'fold-id': fold.id }
if @activeEditSession.selectionIntersectsBufferRange(fold.getBufferRange())
lineAttributes.class += ' selected'
else
lineAttributes = { class: 'line' }
@@ -1074,8 +1117,13 @@ class Editor extends View
position += token.value.length
popScope() while scopeStack.length > 0
if not @mini and invisibles?.eol
line.push("<span class='invisible'>#{invisibles.eol}</span>")
if invisibles and not @mini and not screenLine.isSoftWrapped()
if invisibles.cr and screenLine.lineEnding is '\r\n'
line.push("<span class='invisible'>#{invisibles.cr}</span>")
if invisibles.eol
line.push("<span class='invisible'>#{invisibles.eol}</span>")
line.push("<span class='fold-marker'/>") if fold
line.push('</pre>')
line.join('')
@@ -1154,3 +1202,7 @@ class Editor extends View
@insertText(text, select: true)
true
copyPathToPasteboard: ->
path = @getPath()
pasteboard.write(path) if path?

View File

@@ -36,8 +36,8 @@ class Fold
@displayBuffer.unregisterFold(@startRow, this)
return
@updateStartRow(event)
@updateEndRow(event)
@startRow += @getRowDelta(event, @startRow)
@endRow += @getRowDelta(event, @endRow)
if @startRow != oldStartRow
@displayBuffer.unregisterFold(oldStartRow, this)
@@ -49,26 +49,12 @@ class Fold
isContainedByFold: (fold) ->
@isContainedByRange(fold.getBufferRange())
updateStartRow: (event) ->
getRowDelta: (event, row) ->
{ newRange, oldRange } = event
if oldRange.end.row < @startRow
delta = newRange.end.row - oldRange.end.row
else if newRange.end.row < @startRow
delta = newRange.end.row - @startRow
if oldRange.end.row <= row
newRange.end.row - oldRange.end.row
else if newRange.end.row < row
newRange.end.row - row
else
delta = 0
@startRow += delta
updateEndRow: (event) ->
{ newRange, oldRange } = event
if oldRange.end.row <= @endRow
delta = newRange.end.row - oldRange.end.row
else if newRange.end.row <= @endRow
delta = newRange.end.row - @endRow
else
delta = 0
@endRow += delta
0

View File

@@ -1,13 +1,13 @@
$ = require 'jquery'
_ = require 'underscore'
Subscriber = require 'subscriber'
GitRepository = require 'git-repository'
module.exports =
class Git
@open: (path) ->
@open: (path, options) ->
try
new Git(path)
new Git(path, options)
catch e
null
@@ -23,9 +23,12 @@ class Git
working_dir_typechange: 1 << 10
ignore: 1 << 14
constructor: (path) ->
constructor: (path, options={}) ->
@repo = new GitRepository(path)
@subscribe $(window), 'focus', => @refreshIndex()
refreshIndexOnFocus = options.refreshIndexOnFocus ? true
if refreshIndexOnFocus
$ = require 'jquery'
@subscribe $(window), 'focus', => @refreshIndex()
getRepo: ->
unless @repo?

View File

@@ -4,7 +4,7 @@ SelectList = require 'select-list'
module.exports =
class GrammarView extends SelectList
@viewClass: -> "#{super} grammar-view"
@viewClass: -> "#{super} grammar-view from-top overlay"
filterKey: 'name'
@@ -12,7 +12,6 @@ class GrammarView extends SelectList
@currentGrammar = @editor.getGrammar()
@path = @editor.getPath()
@autoDetect = name: 'Auto Detect'
requireStylesheet 'grammar-view.css'
@command 'editor:select-grammar', =>
@cancel()
false

View File

@@ -58,16 +58,19 @@ class Gutter extends View
@renderLineNumbers(renderFrom, renderTo) if performUpdate
renderLineNumbers: (startScreenRow, endScreenRow) ->
rows = @editor().bufferRowsForScreenRows(startScreenRow, endScreenRow)
editor = @editor()
rows = editor.bufferRowsForScreenRows(startScreenRow, endScreenRow)
cursorScreenRow = @editor().getCursorScreenPosition().row
cursorScreenRow = editor.getCursorScreenPosition().row
@lineNumbers[0].innerHTML = $$$ ->
for row in rows
if row == lastScreenRow
rowValue = ''
else
rowValue = row + 1
@div {class: 'line-number'}, rowValue
classes = ['line-number']
classes.push('fold') if editor.isFoldedAtBufferRow(row)
@div rowValue, class: classes.join(' ')
lastScreenRow = row
@calculateWidth()

View File

@@ -35,7 +35,7 @@ class Keymap
@loadDirectory(fs.join(config.configDirPath, 'keymaps'))
loadDirectory: (directoryPath) ->
@load(filePath) for filePath in fs.list(directoryPath)
@load(filePath) for filePath in fs.list(directoryPath, ['.cson', '.json'])
load: (path) ->
@add(fs.readObject(path))

View File

@@ -27,6 +27,8 @@
'meta-+': 'window:increase-font-size'
'meta--': 'window:decrease-font-size'
'ctrl-w w': 'window:focus-next-pane'
'ctrl-tab': 'window:focus-next-pane'
'ctrl-meta-f': 'window:toggle-full-screen'
'alt-meta-i': 'toggle-dev-tools'

View File

@@ -37,3 +37,7 @@
'alt-meta-w': 'editor:close-other-edit-sessions'
'meta-P': 'editor:close-all-edit-sessions'
'meta-L': 'editor:select-grammar'
'ctrl-C': 'editor:copy-path'
'ctrl-meta-up': 'editor:move-line-up'
'ctrl-meta-down': 'editor:move-line-down'
'meta-D': 'editor:duplicate-line'

View File

@@ -1,6 +1,7 @@
Range = require 'range'
_ = require 'underscore'
require 'underscore-extensions'
OnigRegExp = require 'onig-reg-exp'
module.exports =
class LanguageMode

View File

@@ -133,6 +133,7 @@ class LineMap
new Range(start, end)
bufferRangeForScreenRange: (screenRange) ->
screenRange = Range.fromObject(screenRange)
start = @bufferPositionForScreenPosition(screenRange.start)
end = @bufferPositionForScreenPosition(screenRange.end)
new Range(start, end)
@@ -141,4 +142,3 @@ class LineMap
for row in [start..end]
line = @lineForScreenRow(row).text
console.log row, line, line.length

View File

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

View File

@@ -0,0 +1,26 @@
Task = require 'task'
module.exports =
class LoadTextMatePackagesTask extends Task
constructor: (@packages) ->
super('load-text-mate-packages-handler')
started: ->
@loadNextPackage()
loadNextPackage: ->
unless @packages.length
@terminate()
syntax.trigger 'grammars-loaded'
return
@package = @packages.shift()
@loadPackage(@package.name)
loadPackage: (name) ->
@callWorkerMethod('loadPackage', name)
packageLoaded: (grammars) ->
@package.loadGrammars(grammars)
@loadNextPackage()

View File

@@ -11,6 +11,14 @@ class Point
new Point(row, column)
@min: (point1, point2) ->
point1 = @fromObject(point1)
point2 = @fromObject(point2)
if point1.isLessThanOrEqual(point2)
point1
else
point2
constructor: (@row=0, @column=0) ->
copy: ->
@@ -26,6 +34,10 @@ class Point
new Point(row, column)
translate: (other) ->
other = Point.fromObject(other)
new Point(@row + other.row, @column + other.column)
splitAt: (column) ->
if @row == 0
rightColumn = @column - column

View File

@@ -48,6 +48,9 @@ class Range
add: (point) ->
new Range(@start.add(point), @end.add(point))
translate: (startPoint, endPoint=startPoint) ->
new Range(@start.translate(startPoint), @end.translate(endPoint))
intersectsWith: (otherRange) ->
if @start.isLessThanOrEqual(otherRange.start)
@end.isGreaterThanOrEqual(otherRange.start)

View File

@@ -18,7 +18,7 @@ class RootView extends View
disabledPackages: []
@content: ->
@div id: 'root-view', tabindex: -1, =>
@div id: 'root-view', tabindex: 0, =>
@div id: 'horizontal', outlet: 'horizontal', =>
@div id: 'vertical', outlet: 'vertical', =>
@div id: 'panes', outlet: 'panes'

View File

@@ -2,7 +2,7 @@ _ = require 'underscore'
module.exports =
class ScreenLine
constructor: ({tokens, @ruleStack, @bufferRows, @startBufferColumn, @fold, tabLength}) ->
constructor: ({tokens, @lineEnding, @ruleStack, @bufferRows, @startBufferColumn, @fold, tabLength}) ->
@tokens = @breakOutAtomicTokens(tokens, tabLength)
@bufferRows ?= 1
@startBufferColumn ?= 0
@@ -74,11 +74,13 @@ class ScreenLine
bufferRows: 0
startBufferColumn: @startBufferColumn
ruleStack: @ruleStack
lineEnding: @lineEnding
)
rightFragment = new ScreenLine(
tokens: rightTokens
startBufferColumn: @startBufferColumn + column
ruleStack: @ruleStack
lineEnding: @lineEnding
)
[leftFragment, rightFragment]

View File

@@ -26,6 +26,12 @@ class SelectList extends View
@miniEditor.on 'focusout', => @cancel() unless @cancelling
@on 'core:move-up', => @selectPreviousItem()
@on 'core:move-down', => @selectNextItem()
@on 'core:move-to-top', =>
@selectItem(@list.find('li:first'))
@list.scrollToTop()
@on 'core:move-to-bottom', =>
@selectItem(@list.find('li:last'))
@list.scrollToBottom()
@on 'core:confirm', => @confirmSelection()
@on 'core:cancel', => @cancel()
@@ -103,7 +109,7 @@ class SelectList extends View
scrollToItem: (item) ->
scrollTop = @list.scrollTop()
desiredTop = item.position().top + scrollTop
desiredBottom = desiredTop + item.height()
desiredBottom = desiredTop + item.outerHeight()
if desiredTop < scrollTop
@list.scrollTop(desiredTop)

View File

@@ -96,7 +96,10 @@ class Selection
@screenRangeChanged()
selectWord: ->
@setBufferRange(@cursor.getCurrentWordBufferRange())
options = {}
options.wordRegex = /[\t ]*/ if @cursor.isSurroundedByWhitespace()
@setBufferRange(@cursor.getCurrentWordBufferRange(options))
@wordwise = true
@initialScreenRange = @getScreenRange()
@@ -185,6 +188,8 @@ class Selection
else
@editSession.autoDecreaseIndentForRow(newBufferRange.start.row)
newBufferRange
indent: ({ autoIndent }={})->
{ row, column } = @cursor.getBufferPosition()

View File

@@ -3,6 +3,7 @@ jQuery = require 'jquery'
Specificity = require 'specificity'
{$$} = require 'space-pen'
fs = require 'fs'
EventEmitter = require 'event-emitter'
module.exports =
class Syntax
@@ -104,3 +105,5 @@ class Syntax
deepestChild[0]
else
element[0]
_.extend(Syntax.prototype, EventEmitter)

View File

@@ -2,16 +2,18 @@ _ = require 'underscore'
fs = require 'fs'
plist = require 'plist'
Token = require 'token'
OnigRegExp = require 'onig-reg-exp'
OnigScanner = require 'onig-scanner'
module.exports =
class TextMateGrammar
@loadFromPath: (path) ->
grammar = null
@readFromPath: (path) ->
grammarContent = null
plist.parseString fs.read(path), (e, data) ->
throw new Error(e) if e
grammar = new TextMateGrammar(data[0])
throw new Error("Failed to load grammar at path `#{path}`") unless grammar
grammar
grammarContent = data[0]
throw new Error("Failed to load grammar at path `#{path}`") unless grammarContent
grammarContent
name: null
fileTypes: null
@@ -24,6 +26,7 @@ class TextMateGrammar
@initialRule = new Rule(this, {@scopeName, patterns})
@repository = {}
@firstLineRegex = new OnigRegExp(firstLineMatch) if firstLineMatch
@fileTypes ?= []
for name, data of repository
data = {patterns: [data], tempName: name} if data.begin? or data.match?

View File

@@ -22,28 +22,41 @@ class TextMatePackage extends Package
super
@preferencesPath = fs.join(@path, "Preferences")
@syntaxesPath = fs.join(@path, "Syntaxes")
@grammars = []
load: ->
try
for grammar in @getGrammars()
syntax.addGrammar(grammar)
for { selector, properties } in @getScopedProperties()
syntax.addProperties(selector, properties)
@loadGrammars()
catch e
console.warn "Failed to load package named '#{@name}'", e.stack
this
getGrammars: ->
return @grammars if @grammars
getGrammars: -> @grammars
readGrammars: ->
grammars = []
for grammarPath in fs.list(@syntaxesPath)
try
grammars.push(TextMateGrammar.readFromPath(grammarPath))
catch e
console.warn "Failed to load grammar at path '#{grammarPath}'", e.stack
grammars
addGrammar: (rawGrammar) ->
grammar = new TextMateGrammar(rawGrammar)
@grammars.push(grammar)
syntax.addGrammar(grammar)
loadGrammars: (rawGrammars) ->
rawGrammars = @readGrammars() unless rawGrammars?
@grammars = []
if fs.exists(@syntaxesPath)
for grammarPath in fs.list(@syntaxesPath)
try
@grammars.push TextMateGrammar.loadFromPath(grammarPath)
catch e
console.warn "Failed to load grammar at path '#{grammarPath}'", e.stack
@grammars
@addGrammar(rawGrammar) for rawGrammar in rawGrammars
@loadScopedProperties()
loadScopedProperties: ->
for { selector, properties } in @getScopedProperties()
syntax.addProperties(selector, properties)
getScopedProperties: ->
scopedProperties = []

View File

@@ -44,17 +44,17 @@ class TextMateTheme extends Theme
'color': @translateColor(foreground)
@rulesets.push
selector: '.editor.focused .cursor'
selector: '.editor.is-focused .cursor'
properties:
'border-color': @translateColor(caret)
@rulesets.push
selector: '.editor.focused .selection .region'
selector: '.editor.is-focused .selection .region'
properties:
'background-color': @translateColor(selection)
@rulesets.push
selector: '.editor.focused .line-number.cursor-line-no-selection, .editor.focused .line.cursor-line'
selector: '.editor.is-focused .line-number.cursor-line-no-selection, .editor.is-focused .line.cursor-line'
properties:
'background-color': @translateColor(lineHighlight)
@@ -75,8 +75,8 @@ class TextMateTheme extends Theme
if fontStyle
fontStyles = fontStyle.split(/\s+/)
# properties['font-weight'] = 'bold' if _.contains(fontStyles, 'bold')
# properties['font-style'] = 'italic' if _.contains(fontStyles, 'italic')
properties['font-weight'] = 'bold' if _.contains(fontStyles, 'bold')
properties['font-style'] = 'italic' if _.contains(fontStyles, 'italic')
properties['text-decoration'] = 'underline' if _.contains(fontStyles, 'underline')
properties['color'] = @translateColor(foreground) if foreground

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