mirror of
https://github.com/atom/atom.git
synced 2026-02-03 19:25:06 -05:00
7
.atom/config.cson
Normal file
7
.atom/config.cson
Normal file
@@ -0,0 +1,7 @@
|
||||
'editor':
|
||||
'fontSize': 16
|
||||
'core':
|
||||
'themes': [
|
||||
'atom-dark-ui'
|
||||
'atom-dark-syntax'
|
||||
]
|
||||
1
.atom/packages/README.md
Normal file
1
.atom/packages/README.md
Normal file
@@ -0,0 +1 @@
|
||||
All packages in this directory will be automatically loaded
|
||||
1
.atom/themes/README.md
Normal file
1
.atom/themes/README.md
Normal file
@@ -0,0 +1 @@
|
||||
All themes in this directory will be automatically loaded
|
||||
8
.atom/user.css
Normal file
8
.atom/user.css
Normal file
@@ -0,0 +1,8 @@
|
||||
/* User styles */
|
||||
.tree-view {
|
||||
|
||||
}
|
||||
|
||||
.editor {
|
||||
|
||||
}
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -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
1
.pairs
@@ -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
22
CONTRIBUTING.md
Normal 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
|
||||
@@ -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
|
||||
|
||||
24
Rakefile
24
Rakefile
@@ -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.
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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&);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
(function () {
|
||||
|
||||
native function sendMessageToBrowserProcess(name, array);
|
||||
|
||||
this.atom = {
|
||||
sendMessageToBrowserProcess: sendMessageToBrowserProcess
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -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,
|
||||
|
||||
@@ -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&);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
})();
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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&);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
})();
|
||||
@@ -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
|
||||
|
||||
@@ -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&);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
})();
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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&);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
})();
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
var $tags = {};
|
||||
(function() {
|
||||
|
||||
native function find(path, tag);
|
||||
$tags.find = find;
|
||||
|
||||
native function getAllTagsAsync(path, callback);
|
||||
$tags.getAllTagsAsync = getAllTagsAsync;
|
||||
|
||||
})();
|
||||
@@ -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
17
script/fix-author
Executable 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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", ->
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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%; "
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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", ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
4
spec/fixtures/jquery-task-handler.coffee
vendored
Normal file
4
spec/fixtures/jquery-task-handler.coffee
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports =
|
||||
load: ->
|
||||
$ = require 'jquery'
|
||||
callTaskMethod('loaded', $?)
|
||||
2
spec/fixtures/packages/package-with-module/keymaps/keymap-3.cjson
vendored
Normal file
2
spec/fixtures/packages/package-with-module/keymaps/keymap-3.cjson
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
".test-3":
|
||||
"ctrl-z": "test-3"
|
||||
1
spec/fixtures/packages/textmate-package.tmbundle/Snippets/.hidden-file
vendored
Normal file
1
spec/fixtures/packages/textmate-package.tmbundle/Snippets/.hidden-file
vendored
Normal file
@@ -0,0 +1 @@
|
||||
I am hidden so I shouldn't be loaded
|
||||
1
spec/fixtures/packages/textmate-package.tmbundle/Snippets/invalid.plist
vendored
Normal file
1
spec/fixtures/packages/textmate-package.tmbundle/Snippets/invalid.plist
vendored
Normal file
@@ -0,0 +1 @@
|
||||
I am not a valid plist but that shouldn't cause a crisis
|
||||
@@ -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
|
||||
|
||||
86
spec/stdlib/cson-spec.coffee
Normal file
86
spec/stdlib/cson-spec.coffee
Normal 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
|
||||
@@ -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]
|
||||
|
||||
@@ -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", ->
|
||||
|
||||
23
spec/stdlib/task-shell-spec.coffee
Normal file
23
spec/stdlib/task-shell-spec.coffee
Normal 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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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={}) ->
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
Range = require 'range'
|
||||
_ = require 'underscore'
|
||||
require 'underscore-extensions'
|
||||
OnigRegExp = require 'onig-reg-exp'
|
||||
|
||||
module.exports =
|
||||
class LanguageMode
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
5
src/app/load-text-mate-packages-handler.coffee
Normal file
5
src/app/load-text-mate-packages-handler.coffee
Normal file
@@ -0,0 +1,5 @@
|
||||
TextMatePackage = require 'text-mate-package'
|
||||
|
||||
module.exports =
|
||||
loadPackage: (name) ->
|
||||
callTaskMethod('packageLoaded', new TextMatePackage(name).readGrammars())
|
||||
26
src/app/load-text-mate-packages-task.coffee
Normal file
26
src/app/load-text-mate-packages-task.coffee
Normal 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()
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user