mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
Merge branch 'reboot'
This commit is contained in:
@@ -9,7 +9,4 @@
|
||||
- (AtomController *)createController:(NSString *)path;
|
||||
- (void)removeController:(AtomController *)controller;
|
||||
|
||||
- (id)storageGet:(NSString *)keyPath defaultValue:(id)defaultValue;
|
||||
- (id)storageSet:(NSString *)keyPath value:(id)value;
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,35 +6,20 @@
|
||||
|
||||
#define ATOM_USER_PATH ([[NSString stringWithString:@"~/.atomicity/"] stringByStandardizingPath])
|
||||
#define ATOM_STORAGE_PATH ([ATOM_USER_PATH stringByAppendingPathComponent:@".app-storage"])
|
||||
#define WEB_STORAGE_PATH ([ATOM_USER_PATH stringByAppendingPathComponent:@".web-storage"])
|
||||
|
||||
@implementation AtomApp
|
||||
|
||||
@synthesize controllers;
|
||||
|
||||
- (AtomController *)createController:(NSString *)path {
|
||||
if (path) {
|
||||
NSMutableArray *openedPaths = [self storageGet:@"app.openedPaths" defaultValue:[NSMutableArray array]];
|
||||
if (![openedPaths containsObject:path]) {
|
||||
[openedPaths addObject:path];
|
||||
[self storageSet:@"app.openedPaths" value:openedPaths];
|
||||
}
|
||||
}
|
||||
|
||||
AtomController *controller = [[AtomController alloc] initWithPath:path];
|
||||
AtomController *controller = [[AtomController alloc] initWithURL:path];
|
||||
[controllers addObject:controller];
|
||||
|
||||
// window.coffee will set the window size
|
||||
[[controller window] setFrame:NSMakeRect(0, 0, 0, 0) display:YES animate:NO];
|
||||
return controller;
|
||||
}
|
||||
|
||||
- (void)removeController:(AtomController *)controller {
|
||||
[controllers removeObject:controller];
|
||||
|
||||
NSMutableArray *openedPaths = [self storageGet:@"app.openedPaths" defaultValue:[NSMutableArray array]];
|
||||
[openedPaths removeObject:controller.path];
|
||||
[self storageSet:@"app.openedPaths" value:openedPaths];
|
||||
[controllers removeObject:controller];
|
||||
}
|
||||
|
||||
- (void)open:(NSString *)path {
|
||||
@@ -45,16 +30,8 @@
|
||||
|
||||
path = [[[panel URLs] lastObject] path];
|
||||
}
|
||||
|
||||
for (AtomController *controller in controllers) {
|
||||
JSValueRef value = [controller.jscocoa callJSFunctionNamed:@"canOpen" withArguments:path, nil];
|
||||
if ([controller.jscocoa toBool:value]) {
|
||||
[controller.jscocoa callJSFunctionNamed:@"open" withArguments:path, nil];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[self createController:path];
|
||||
|
||||
[self createController:path];
|
||||
}
|
||||
|
||||
// Events in the "app:*" namespace get sent to all controllers
|
||||
@@ -70,7 +47,7 @@
|
||||
BOOL handeled = NO;
|
||||
AtomController *controller = [[self keyWindow] windowController];
|
||||
|
||||
// The keyWindow could be a Cocoa Dialog or something, ignore that.
|
||||
// The keyWindow could be a Cocoa Dialog or something, ignore those.
|
||||
if ([controller isKindOfClass:[AtomController class]]) {
|
||||
JSValueRef value = [controller.jscocoa callJSFunctionNamed:@"handleKeyEvent" withArguments:event, nil];
|
||||
handeled = [controller.jscocoa toBool:value];
|
||||
@@ -95,74 +72,12 @@
|
||||
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification {
|
||||
self.controllers = [NSMutableArray array];
|
||||
|
||||
// Hack to make localStorage work
|
||||
WebPreferences* prefs = [WebPreferences standardPreferences];
|
||||
[prefs _setLocalStorageDatabasePath:WEB_STORAGE_PATH];
|
||||
[prefs setLocalStorageEnabled:YES];
|
||||
|
||||
NSDictionary *defaults = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool:YES], @"WebKitDeveloperExtras",
|
||||
nil];
|
||||
NSDictionary *defaults = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"WebKitDeveloperExtras", nil];
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
|
||||
}
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
NSError *error = nil;
|
||||
BOOL success = [[NSFileManager defaultManager] createDirectoryAtPath:ATOM_USER_PATH withIntermediateDirectories:YES attributes:nil error:&error];
|
||||
if (!success || error) {
|
||||
[NSException raise:@"Atom: Failed to open storage path at '%@'. %@" format:ATOM_USER_PATH, [error localizedDescription]];
|
||||
}
|
||||
|
||||
NSArray *openedPaths = [self storageGet:@"app.openedPaths" defaultValue:[NSMutableArray array]];
|
||||
if (openedPaths.count == 0) {
|
||||
[self createController:NULL];
|
||||
}
|
||||
else {
|
||||
for (NSString *path in openedPaths) {
|
||||
[self createController:path];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper Methods that should probably go elsewhere
|
||||
- (id)storage {
|
||||
id storage = [NSMutableDictionary dictionaryWithContentsOfFile:ATOM_STORAGE_PATH];
|
||||
if (!storage) storage = [NSMutableDictionary dictionary];
|
||||
|
||||
return storage;
|
||||
}
|
||||
|
||||
- (id)storageGet:(NSString *)keyPath defaultValue:(id)defaultValue {
|
||||
id storage = [NSMutableDictionary dictionaryWithContentsOfFile:ATOM_STORAGE_PATH];
|
||||
if (!storage) storage = [NSMutableDictionary dictionary];
|
||||
|
||||
id value = [storage valueForKeyPath:keyPath];
|
||||
if (!value) value = defaultValue;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (id)storageSet:(NSString *)keyPath value:(id)value {
|
||||
id storage = [NSMutableDictionary dictionaryWithContentsOfFile:ATOM_STORAGE_PATH];
|
||||
if (!storage) storage = [NSMutableDictionary dictionary];
|
||||
|
||||
NSArray *keys = [keyPath componentsSeparatedByString:@"."];
|
||||
id parent = storage;
|
||||
for (int i = 0; i < keys.count - 1; i++) {
|
||||
NSString *key = [keys objectAtIndex:i];
|
||||
id newParent = [parent valueForKey:key];
|
||||
if (!newParent) {
|
||||
newParent = [NSMutableDictionary dictionary];
|
||||
[parent setValue:newParent forKey:key];
|
||||
}
|
||||
parent = newParent;
|
||||
}
|
||||
|
||||
[storage setValue:value forKeyPath:keyPath];
|
||||
|
||||
[storage writeToFile:ATOM_STORAGE_PATH atomically:YES];
|
||||
|
||||
return value;
|
||||
[self createController:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
}
|
||||
|
||||
@property (retain) WebView *webView;
|
||||
@property (retain, readonly) NSString *path;
|
||||
@property (retain, readonly) NSString *url;
|
||||
@property (retain) JSCocoa *jscocoa;
|
||||
|
||||
- (AtomController *)initWithPath:(NSString *)aPath;
|
||||
- (AtomController *)initWithURL:(NSString *)url;
|
||||
|
||||
@end
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
#import "JSCocoa.h"
|
||||
|
||||
#import <WebKit/WebKit.h>
|
||||
#import <stdio.h>
|
||||
|
||||
@implementation AtomController
|
||||
|
||||
@synthesize webView, path, jscocoa;
|
||||
@synthesize webView, url, jscocoa;
|
||||
|
||||
- (void)dealloc {
|
||||
[jscocoa unlinkAllReferences];
|
||||
@@ -16,25 +15,23 @@
|
||||
[jscocoa release]; jscocoa = nil;
|
||||
|
||||
[webView release];
|
||||
[path release];
|
||||
[url release];
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (id)initWithPath:(NSString *)aPath {
|
||||
aPath = aPath ? aPath : @"/tmp";
|
||||
|
||||
- (id)initWithURL:(NSString *)_url {
|
||||
self = [super initWithWindowNibName:@"AtomWindow"];
|
||||
path = [[aPath stringByStandardizingPath] retain];
|
||||
url = [[_url stringByStandardizingPath] retain];
|
||||
|
||||
[self.window makeKeyWindow];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)windowDidLoad {
|
||||
[super windowDidLoad];
|
||||
|
||||
[[webView inspector] showConsole:self];
|
||||
|
||||
|
||||
[self.window setDelegate:self];
|
||||
[self.window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
|
||||
|
||||
@@ -49,18 +46,7 @@
|
||||
NSURL *resourceURL = [[NSBundle mainBundle] resourceURL];
|
||||
NSURL *indexURL = [resourceURL URLByAppendingPathComponent:@"index.html"];
|
||||
NSURLRequest *request = [NSURLRequest requestWithURL:indexURL];
|
||||
[[webView mainFrame] loadRequest:request];
|
||||
}
|
||||
|
||||
// Helper methods that should go elsewhere
|
||||
- (NSString *)tempfile {
|
||||
char *directory = "/tmp";
|
||||
char *prefix = "temp-file";
|
||||
char *tmpPath = tempnam(directory, prefix);
|
||||
NSString *tmpPathString = [NSString stringWithUTF8String:tmpPath];
|
||||
free(tmpPath);
|
||||
|
||||
return tmpPathString;
|
||||
[[webView mainFrame] loadRequest:request];
|
||||
}
|
||||
|
||||
// WebUIDelegate
|
||||
@@ -73,5 +59,5 @@
|
||||
[(AtomApp *)NSApp removeController:self];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -46,6 +46,11 @@ static JSClassRef hashObjectClass = NULL;
|
||||
static void throwException(JSContextRef ctx, JSValueRef* exception, NSString* reason);
|
||||
|
||||
|
||||
BOOL isUsingStret(id argumentEncodings);
|
||||
JSValueRef valueFromExternalContext(JSContextRef externalCtx, JSValueRef value, JSContextRef ctx);
|
||||
void* getObjCCallAddress(id argumentEncodings);
|
||||
JSValueRef boxedValueFromExternalContext(JSContextRef externalCtx, JSValueRef value, JSContextRef ctx);
|
||||
|
||||
// iPhone specifics
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
const JSClassDefinition kJSClassDefinitionEmpty = { 0, 0,
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#import "JSCocoaController.h"
|
||||
#include <sys/mman.h> // for mmap()
|
||||
|
||||
void closure_function(ffi_cif* cif, void* resp, void** args, void* userdata);
|
||||
|
||||
@implementation JSCocoaFFIClosure
|
||||
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ void UKFileSubscriptionProc(FNMessage message, OptionBits flags, void *refcon
|
||||
kNilOptions, &subscription );
|
||||
if( err != noErr )
|
||||
{
|
||||
NSLog( @"UKFNSubscribeFileWatcher addPath: %@ failed due to error ID=%ld.", path, err );
|
||||
NSLog( @"UKFNSubscribeFileWatcher addPath: %@ failed due to error ID=%d.", path, err );
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -269,7 +269,7 @@ static UKKQueue * gUKKQueueSharedQueueSingleton = nil;
|
||||
|
||||
-(void) removePathFromQueue: (NSString*)path
|
||||
{
|
||||
int index = 0;
|
||||
NSUInteger index = 0;
|
||||
int fd = -1;
|
||||
|
||||
AT_SYNCHRONIZED( self )
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||
# Atom — Futuristic Text Editing
|
||||
|
||||
## Informative Links
|
||||
|
||||
|
||||
8
docs/chris-wants.txt
Normal file
8
docs/chris-wants.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Want
|
||||
- open raw HTML in browser tab
|
||||
|
||||
- cmd-shift-p: preview markdown as HTML
|
||||
|
||||
- cmd-t file finder uses project.settings.extraURLs
|
||||
|
||||
- atom.process.run "shell-command", -> stdoutCallback()
|
||||
15
docs/corey-wants.txt
Normal file
15
docs/corey-wants.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
Intent
|
||||
- open a folder
|
||||
|
||||
- child urls of folder open in same window
|
||||
|
||||
- open/save/close on project
|
||||
|
||||
Want
|
||||
- cmd-shift-t: reopen last closed tab
|
||||
|
||||
- an extension that shows all available key commands
|
||||
|
||||
- stdlib should be indepentent of atom (no events in stdlib)
|
||||
|
||||
|
||||
30
docs/extensions.md
Normal file
30
docs/extensions.md
Normal file
@@ -0,0 +1,30 @@
|
||||
* Project
|
||||
find in project
|
||||
find file in project
|
||||
issues
|
||||
gem scraper
|
||||
npm scraper
|
||||
rake runner
|
||||
test runner
|
||||
rails extension
|
||||
ctags
|
||||
treeview
|
||||
tabs
|
||||
|
||||
* Resource
|
||||
gist resource
|
||||
|
||||
* Window
|
||||
gist creator/browser
|
||||
thunderhorse
|
||||
align
|
||||
git
|
||||
console
|
||||
|
||||
* Editor
|
||||
debugger
|
||||
markdown viewer
|
||||
(coffee -> java)script
|
||||
surround
|
||||
snippets
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# Keybinding ideas
|
||||
# ----------------
|
||||
# Are ctrl-v and ctrl-V different?
|
||||
# Nested commands? Use timeout?
|
||||
# Optional Regex, is that fucking crazy?
|
||||
# Command/Control/Option or cmd/ctrl/alt
|
||||
# How should we deal with scope?
|
||||
|
||||
keymap:
|
||||
# Take some method found in the keymap scope?
|
||||
'cmd-c': 'copyText'
|
||||
|
||||
# Take a block
|
||||
'cmd-v': -> paste.someText()
|
||||
|
||||
# Can take a regex
|
||||
# * how would this work with timeouts
|
||||
# * how can this not look so hackish
|
||||
# * do we even need this?
|
||||
'cmd-/(\d+)/': (number) ->
|
||||
window.switchTo(parseInt(number))
|
||||
|
||||
# Nested commands
|
||||
'cmd-ctrl-r':
|
||||
'r': -> run.something()
|
||||
't': -> test.something()
|
||||
|
||||
# Switch modes? I don't like this syntax
|
||||
'mode(normal):esc':
|
||||
'j': 'moveDown'
|
||||
'k': 'moveUp'
|
||||
'a': ->
|
||||
goto.endOfLine()
|
||||
keybindingMode('normal')
|
||||
'mode(insert):i':
|
||||
'delete': 'delete'
|
||||
166
extensions/docs/cdoc.coffee
Normal file
166
extensions/docs/cdoc.coffee
Normal file
@@ -0,0 +1,166 @@
|
||||
# cdoc is a bite sized library which scans CoffeeScript
|
||||
# source code and returns a js object containing class, module
|
||||
# and method names, along with the comments and line numbers
|
||||
# associated with them.
|
||||
#
|
||||
# It pairs finely with TomDoc, but really doesn't care which
|
||||
# documentation format you use. As long as your class, module, and
|
||||
# method definitions are preceded by a comment, cdoc will do its job.
|
||||
module.exports = cdoc =
|
||||
# Parses CoffeeScript source code into an object describing
|
||||
# the class names and modules defined therein. Method names,
|
||||
# comments, and line numbers are also included.
|
||||
#
|
||||
# text - The String CoffeeScript source code to parse into a js object.
|
||||
#
|
||||
# Returns an object like this:
|
||||
#
|
||||
# [
|
||||
# name: "App"
|
||||
# comment:"Singleton object representing the application.",
|
||||
# line: 5,
|
||||
# methods: [
|
||||
# name: "setTitle"
|
||||
# signature: "setTitle: (title) ->"
|
||||
# params: [
|
||||
# name: "title"
|
||||
# ]
|
||||
# comment: "Sets the title of the app."
|
||||
# line: 7
|
||||
# ,
|
||||
# name: "title"
|
||||
# signature: "title: ->"
|
||||
# params: []
|
||||
# comment: "Returns the String title of the app."
|
||||
# line: 11
|
||||
# ]
|
||||
# name: "class Robot"
|
||||
# comment: "A Robot that can walk and talk, just like a real boy."
|
||||
# line: 15
|
||||
# methods: [
|
||||
# name: "constructor"
|
||||
# signature: "constructor: (path) ->"
|
||||
# params: [
|
||||
# name: "path"
|
||||
# ]
|
||||
# comment: "Robots receive messages from a..."
|
||||
# line: 20
|
||||
# ]
|
||||
# ]
|
||||
#
|
||||
# In the above, App is an object while Robot is a class.
|
||||
parse: (text) ->
|
||||
scopes = []
|
||||
lastComment = ''
|
||||
methodIndent = null
|
||||
moduleIsFn = false
|
||||
|
||||
text.split("\n").forEach (line, lineno) =>
|
||||
return if moduleIsFn
|
||||
|
||||
lineno++
|
||||
|
||||
# Skip empty lines and lines containing only a comment symbol.
|
||||
return if not line.trim()
|
||||
|
||||
# If this line is a comment, record it and move on.
|
||||
if line.trim()[0] is '#'
|
||||
lastComment += line.trim().match(/^\s*\#\s?(.*)$/)[1] + "\n"
|
||||
return
|
||||
|
||||
# detect:
|
||||
# 1. module.exports = (params) ->
|
||||
# 2. module.exports = (params) =>
|
||||
if match = line.match /^\s*module.exports\s*=\s*((?:\((.+)\))?\s*(-|=)>)/
|
||||
moduleIsFn = true
|
||||
[ signature, params ] = match[1..2]
|
||||
params = for param in params?.split(',') or []
|
||||
{ name: param.trim() }
|
||||
scopes.push
|
||||
name: 'module'
|
||||
signature: signature
|
||||
params: params
|
||||
comment: lastComment.trim()
|
||||
line: lineno
|
||||
|
||||
# detect:
|
||||
# 1. module.exports =
|
||||
# 2. module.exports = MyModule =
|
||||
# 3. module.exports = class MyClass
|
||||
# 4. exports.MyClass = class MyClass
|
||||
else if match = line.match /^\s*(?:module.exports|exports\.[\w\.]+)\s*=\s*(?:(class [\w\.]+)(?:\s+extends [\w.]+)?|([\w\.]+)\s*=)\s*$/
|
||||
methodIndent = null
|
||||
scopes.push
|
||||
name: match[1] or match[2] or 'module'
|
||||
comment: lastComment.trim()
|
||||
line: lineno
|
||||
methods: []
|
||||
|
||||
# detect: class Window
|
||||
else if newScope = line.match(/^\s*class ([\w\.]+)/)?[0]
|
||||
methodIndent = null
|
||||
scopes.push
|
||||
name: newScope
|
||||
comment: lastComment.trim()
|
||||
line: lineno
|
||||
methods: []
|
||||
|
||||
# detect:
|
||||
# 1. hear: ->
|
||||
# 2. hear: (regex, args...) ->
|
||||
# 3. hear = ->
|
||||
# 4. hear = (regex, args...) ->
|
||||
# also detect `@hear` and `=>` forms of the above
|
||||
# as well as `exports.head = (regex, args...) ->` form
|
||||
else if match = line.match /^\s*(@|exports\.)?(([\w\.]+)\s*(:|=)\s*(?:\((.*?)\))?\s*(-|=)>)\s*/
|
||||
|
||||
# If the indentation of this method is deeper than the
|
||||
# previous method definition, and we're still in the same
|
||||
# scope, bail out. Probably an inner function.
|
||||
indent = line.match(/^(\s*)/)[0].length
|
||||
return if methodIndent? and methodIndent < indent
|
||||
methodIndent = indent
|
||||
|
||||
[ prefix, fn, name, type, params ] = match[1..-1]
|
||||
|
||||
# floating function
|
||||
if type is '=' and prefix isnt 'exports.'
|
||||
return
|
||||
|
||||
# Normalize the method signature.
|
||||
# 1. exports.method = (param) ->
|
||||
# exports.method: (param) ->
|
||||
# 2. method: () ->
|
||||
# method: ->
|
||||
fn = fn.trim()
|
||||
fn = fn.replace /\s+=\s+((\(|-|=))/, ': $1'
|
||||
fn = fn.replace /\s*\(\)/, ''
|
||||
|
||||
params = for param in params?.split(',') or []
|
||||
{ name: param.trim() }
|
||||
|
||||
if prefix is 'exports.'
|
||||
modules = scopes.filter (scope) -> scope?.name is 'module'
|
||||
scope = modules[0]
|
||||
|
||||
if not scope
|
||||
scopes.push scope =
|
||||
name: 'module'
|
||||
comment: ""
|
||||
line: 0
|
||||
methods: []
|
||||
else
|
||||
scope = scopes[scopes.length - 1]
|
||||
|
||||
scope.methods ?= []
|
||||
scope.methods.push
|
||||
name: name
|
||||
signature: fn
|
||||
params: params
|
||||
comment: lastComment.trim()
|
||||
line: lineno
|
||||
|
||||
# Clear the last comment, we're done with it.
|
||||
lastComment = ''
|
||||
|
||||
scopes
|
||||
23
extensions/docs/docs.coffee
Normal file
23
extensions/docs/docs.coffee
Normal file
@@ -0,0 +1,23 @@
|
||||
fs = require 'fs'
|
||||
|
||||
tdoc = require 'docs/tdoc'
|
||||
|
||||
Browser = require 'browser'
|
||||
|
||||
module.exports =
|
||||
class Docs extends Browser
|
||||
window.resourceTypes.push this
|
||||
|
||||
running: true
|
||||
|
||||
constructor: ->
|
||||
atom.keybinder.load require.resolve "docs/key-bindings.coffee"
|
||||
|
||||
open: (url) ->
|
||||
return false if not url
|
||||
|
||||
if match = url.match /^docs:\/\/(.+)/
|
||||
@url = url
|
||||
code = fs.read match[1]
|
||||
@show tdoc.html code
|
||||
true
|
||||
1494
extensions/docs/handlebars.js
Normal file
1494
extensions/docs/handlebars.js
Normal file
File diff suppressed because it is too large
Load Diff
20
extensions/docs/helpers.coffee
Normal file
20
extensions/docs/helpers.coffee
Normal file
@@ -0,0 +1,20 @@
|
||||
hbar = require './handlebars'
|
||||
{Showdown} = require './showdown'
|
||||
converter = new Showdown.converter
|
||||
|
||||
hbar.registerHelper 'format', (comment) ->
|
||||
comment = comment+''
|
||||
|
||||
# param - info => <b>param:</b> info
|
||||
comment = comment.replace /(?:^\s*(\S+?)\s+-\s+(.+)$)+/img,
|
||||
'<br>**$1:** $2'
|
||||
comment = comment.replace '<br>**', '<br><br>**'
|
||||
|
||||
# markdownize
|
||||
comment = converter.makeHtml comment
|
||||
|
||||
# <pre><code>code</code></pre> => <pre>code</pre>
|
||||
comment = comment.replace /<pre><code>((?:.|\n)+)<\/code><\/pre>/img,
|
||||
'<pre>$1</pre>'
|
||||
|
||||
new hbar.SafeString comment
|
||||
1
extensions/docs/index.coffee
Normal file
1
extensions/docs/index.coffee
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require 'docs/docs'
|
||||
2
extensions/docs/key-bindings.coffee
Normal file
2
extensions/docs/key-bindings.coffee
Normal file
@@ -0,0 +1,2 @@
|
||||
editor:
|
||||
'cmd-shift-d': (editor) -> window.open "docs://#{editor.url}"
|
||||
1302
extensions/docs/showdown.js
Normal file
1302
extensions/docs/showdown.js
Normal file
File diff suppressed because it is too large
Load Diff
82
extensions/docs/tdoc.coffee
Normal file
82
extensions/docs/tdoc.coffee
Normal file
@@ -0,0 +1,82 @@
|
||||
# tdoc is a bite sized library for generating HTML documentation
|
||||
# from CoffeeScript source code and Markdown files.
|
||||
#
|
||||
# It pairs finely with TomDoc, but really doesn't care which
|
||||
# documentation format you use. As long as your class, module, and
|
||||
# method definitions are preceded by a comment, tdoc will do its job.
|
||||
|
||||
File = require 'fs'
|
||||
cdoc = require './cdoc'
|
||||
hbar = require './handlebars'
|
||||
require './helpers'
|
||||
|
||||
# The tdoc module is our main interface. Using it we can turn CoffeeScript
|
||||
# or Markdown into HTML using a template.
|
||||
module.exports = tdoc =
|
||||
# Theme to use. Set using setTheme()
|
||||
theme: 'default'
|
||||
|
||||
# Turns code into HTML docs.
|
||||
#
|
||||
# code - String of source code.
|
||||
# options -
|
||||
# path: The path to the file this code is from.
|
||||
#
|
||||
# Returns a String of HTML.
|
||||
html: (code, options={}) ->
|
||||
options.path ?= ''
|
||||
options.paths = options.paths ? []
|
||||
options.sourceURL = "https://github.com/github/hubot/tree/master/"
|
||||
options.code = code
|
||||
|
||||
if /\.(md|markdown|mdown|txt)$/.test options.path
|
||||
@render @template('markdown'), options
|
||||
|
||||
else if options.path is '' or /\.coffee$/.test options.path
|
||||
context = cdoc.parse code
|
||||
context[key] = value for key, value of options
|
||||
@render @template(), context
|
||||
|
||||
else
|
||||
"Don't know how to parse #{options.path}."
|
||||
|
||||
# Sets the current theme.
|
||||
#
|
||||
# theme - String name of the theme you want to use.
|
||||
#
|
||||
# Returns nothing.
|
||||
setTheme: (name) ->
|
||||
@theme = name
|
||||
|
||||
# Finds a template.
|
||||
#
|
||||
# name - String name of the template you want.
|
||||
#
|
||||
# Returns a String template
|
||||
template: (name='module') ->
|
||||
# lame require.resolve hack :(
|
||||
layout = @read require.resolve "docs/themes/#{@theme}/layout.html"
|
||||
file = @read require.resolve "docs/themes/#{@theme}/#{name}.html"
|
||||
layout.replace /{{{\s*yield\s*}}}/, file
|
||||
|
||||
# Renders a template using Handlebars.js.
|
||||
#
|
||||
# template - String template to render.
|
||||
# context - Object to use as context of the template.
|
||||
#
|
||||
# Returns the fully rendered template.
|
||||
render: (template, context) ->
|
||||
compiled = hbar.compile template
|
||||
compiled context
|
||||
|
||||
# Reads a file synchronously using either CommonJS or node.
|
||||
#
|
||||
# path - String path to the file you want to read.
|
||||
# encoding - String encoding to use when reading the file.
|
||||
#
|
||||
# Returns a String.
|
||||
read: (path, encoding="utf8") ->
|
||||
if File.readFileSync?
|
||||
File.readFileSync path, encoding
|
||||
else if File.read?
|
||||
File.read path
|
||||
11
extensions/docs/themes/default/index.html
vendored
Normal file
11
extensions/docs/themes/default/index.html
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
<h1>Hubot</h1>
|
||||
|
||||
<h3>A simple helpful Robot for your Company</h3>
|
||||
|
||||
<p>
|
||||
<ul class="unstyled">
|
||||
<li><a href="#">Creator</a></li>
|
||||
<li><a href="#">Robot</a></li>
|
||||
<li><a href="#">Robot.Response</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
32
extensions/docs/themes/default/layout.html
vendored
Normal file
32
extensions/docs/themes/default/layout.html
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap-1.2.0.min.css">
|
||||
<style>
|
||||
p { color: #404040; margin-bottom: 20px; font-size: 14px; line-height: 20px; }
|
||||
ul { list-style-type: none; }
|
||||
li { color: #404040; }
|
||||
h5 { border-bottom: 1px dotted #404040; line-height: 20px; margin-bottom: 8px; }
|
||||
h5 a { color: inherit; }
|
||||
h5 a:hover { text-decoration: none; }
|
||||
pre { background-color: #FEE9CC; }
|
||||
#main { width: 610px; margin:0 auto; padding-top: 10px; }
|
||||
#sidebar { float: left; padding-top: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<div id="sidebar">
|
||||
<ul>
|
||||
{{# paths}}
|
||||
<li><a href="{{this}}.html">{{this}}</a></li>
|
||||
{{/ paths}}
|
||||
</ul>
|
||||
</div>
|
||||
<div id="main">
|
||||
{{{yield}}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
1
extensions/docs/themes/default/markdown.html
vendored
Normal file
1
extensions/docs/themes/default/markdown.html
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{{format code}}
|
||||
19
extensions/docs/themes/default/module.html
vendored
Normal file
19
extensions/docs/themes/default/module.html
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{{# this}}
|
||||
<h3>{{name}}</h3>
|
||||
|
||||
<ul>
|
||||
{{#if comment}}
|
||||
<li><p>{{comment}}</p></li>
|
||||
{{/if}}
|
||||
{{# methods}}
|
||||
<li>
|
||||
{{#if ../../sourceURL}}
|
||||
<h5><a href="{{../../../sourceURL}}{{../../../path}}#L{{line}}" target="_blank">{{signature}}</a></h5>
|
||||
{{else}}
|
||||
<h5>{{signature}}</h5>
|
||||
{{/if}}
|
||||
{{format comment}}
|
||||
</li>
|
||||
{{/ methods}}
|
||||
</ul>
|
||||
{{/ this}}
|
||||
@@ -1,99 +0,0 @@
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
|
||||
File = require 'fs'
|
||||
Pane = require 'pane'
|
||||
|
||||
jQuery = $
|
||||
Modal = require 'modal'
|
||||
|
||||
require 'filefinder/stringscore'
|
||||
|
||||
module.exports =
|
||||
class FilefinderPane extends Pane
|
||||
html: require "filefinder/filefinder.html"
|
||||
|
||||
constructor: (@filefinder) ->
|
||||
$('#filefinder input').live 'keydown', @onKeydown
|
||||
@modal = new Modal @html
|
||||
|
||||
onKeydown: (e) =>
|
||||
keys = up: 38, down: 40, enter: 13
|
||||
|
||||
if e.keyCode is keys.enter
|
||||
@openSelected()
|
||||
false
|
||||
else if e.keyCode is keys.up
|
||||
@moveUp()
|
||||
else if e.keyCode is keys.down
|
||||
@moveDown()
|
||||
else
|
||||
@filterFiles()
|
||||
|
||||
toggle: ->
|
||||
if @modal.showing
|
||||
@modal.hide()
|
||||
else
|
||||
@showFinder()
|
||||
|
||||
paths: ->
|
||||
_paths = []
|
||||
for dir in File.list window.path
|
||||
continue if /\.git|Cocoa/.test dir
|
||||
_paths.push File.listDirectoryTree dir
|
||||
_.reject _.flatten(_paths), (dir) -> File.isDirectory dir
|
||||
|
||||
showFinder: ->
|
||||
@modal.show()
|
||||
@files = []
|
||||
for file in @paths()
|
||||
@files.push file.replace "#{window.path}/", ''
|
||||
@filterFiles()
|
||||
|
||||
findMatchingFiles: (query) ->
|
||||
return [] if not query
|
||||
|
||||
results = []
|
||||
for file in @files
|
||||
score = file.score query
|
||||
if score > 0
|
||||
# Basename matches count for more.
|
||||
if not query.match '/'
|
||||
if name.match '/'
|
||||
score += name.replace(/^.*\//, '').score query
|
||||
else
|
||||
score *= 2
|
||||
results.push [score, file]
|
||||
|
||||
sorted = results.sort (a, b) -> b[0] - a[0]
|
||||
_.map sorted, (el) -> el[1]
|
||||
|
||||
filterFiles: ->
|
||||
if query = $('#filefinder input').val()
|
||||
files = @findMatchingFiles query
|
||||
else
|
||||
files = @files
|
||||
$('#filefinder ul').empty()
|
||||
for file in files[0..10]
|
||||
$('#filefinder ul').append "<li>#{file}</li>"
|
||||
$('#filefinder input').focus()
|
||||
$('#filefinder li:first').addClass 'selected'
|
||||
|
||||
openSelected: ->
|
||||
dir = window.path
|
||||
file = $('#filefinder .selected').text()
|
||||
window.open "#{dir}/#{file}"
|
||||
@toggle()
|
||||
|
||||
moveUp: ->
|
||||
selected = $('#filefinder .selected')
|
||||
if selected.prev().length
|
||||
selected.prev().addClass 'selected'
|
||||
selected.removeClass 'selected'
|
||||
|
||||
moveDown: ->
|
||||
selected = $('#filefinder .selected')
|
||||
if selected.next().length
|
||||
selected.next().addClass 'selected'
|
||||
selected.removeClass 'selected'
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
_ = require 'underscore'
|
||||
fs = require 'fs'
|
||||
|
||||
Extension = require 'extension'
|
||||
KeyBinder = require 'key-binder'
|
||||
Event = require 'event'
|
||||
FilefinderPane = require 'filefinder/filefinder-pane'
|
||||
ModalSelector = require 'modal-selector'
|
||||
|
||||
module.exports =
|
||||
class Filefinder extends Extension
|
||||
constructor: ->
|
||||
KeyBinder.register "filefinder", @
|
||||
KeyBinder.load require.resolve "filefinder/key-bindings.coffee"
|
||||
atom.keybinder.load require.resolve "filefinder/key-bindings.coffee"
|
||||
atom.on 'project:open', @startup
|
||||
|
||||
@pane = new FilefinderPane @
|
||||
startup: (@project) =>
|
||||
@pane = new ModalSelector => _.reject @project.allURLs(), ({url}) ->
|
||||
fs.isDirectory url
|
||||
|
||||
toggle: ->
|
||||
@pane.toggle()
|
||||
@pane?.toggle()
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
<style>
|
||||
#modal .content {
|
||||
background: #ededed;
|
||||
padding: 0;
|
||||
}
|
||||
#modal .close {
|
||||
display: none;
|
||||
}
|
||||
#filefinder .filelist {
|
||||
height: 100px;
|
||||
overflow: hidden;
|
||||
padding: 10px 0;
|
||||
}
|
||||
#filefinder input[type=search] {
|
||||
width: 95%;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#modal .content {
|
||||
min-width: 200px;
|
||||
height: 100%;
|
||||
background-color: #DDE3EA;
|
||||
color: #000;
|
||||
border-right: 1px solid #B4B4B4;
|
||||
cursor: default;
|
||||
-webkit-user-select: none;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#modal .content .cwd {
|
||||
padding-top: 5px;
|
||||
padding-left: 5px;
|
||||
font-weight: bold;
|
||||
color: #708193;
|
||||
text-transform: uppercase;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
#modal .content ul {
|
||||
margin: 0;
|
||||
padding-top: 2px;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#modal .content li {
|
||||
padding: 0;
|
||||
padding-left: 5px;
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#modal .content li.selected {
|
||||
background-image: -webkit-gradient(linear,0% 0,0% 100%,from(#BCCBEB),to(#8094BB));
|
||||
border-top: 1px solid #A0AFCD;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id='filefinder'>
|
||||
<input type='search'>
|
||||
<br>
|
||||
<ul class='filelist'>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -3,32 +3,39 @@ _ = require 'underscore'
|
||||
|
||||
fs = require 'fs'
|
||||
Extension = require 'extension'
|
||||
KeyBinder = require 'key-binder'
|
||||
Event = require 'event'
|
||||
Watcher = require 'watcher'
|
||||
ModalSelector = require 'modal-selector'
|
||||
|
||||
module.exports =
|
||||
class Gemfile extends Extension
|
||||
constructor: ->
|
||||
Event.on 'extensions:loaded', @addRubyGemsDir
|
||||
atom.keybinder.load require.resolve "gemfile/key-bindings.coffee"
|
||||
atom.on 'project:open', @startup
|
||||
|
||||
addRubyGemsDir: =>
|
||||
paths = window.extensions.Tree.paths
|
||||
gemfile = _.detect paths, ({path}) -> /Gemfile/i.test path
|
||||
startup: (@project) =>
|
||||
urls = @project.urls()
|
||||
gemfile = _.detect urls, ({url}) -> /Gemfile/i.test url
|
||||
{url} = gemfile if gemfile
|
||||
gems = @gems url if url
|
||||
|
||||
if gemfile
|
||||
paths.push
|
||||
label: "RubyGems"
|
||||
path: "http://rubygems.org/"
|
||||
paths: @gemsFromGemFile gemfile.path
|
||||
window.extensions.Tree.reload()
|
||||
if url and gems.length > 0
|
||||
@project.settings.extraURLs[@project.url] = [
|
||||
name: "RubyGems"
|
||||
url: "http://rubygems.org/"
|
||||
type: 'dir'
|
||||
]
|
||||
|
||||
@project.settings.extraURLs["http://rubygems.org/"] = gems
|
||||
@pane = new ModalSelector -> gems
|
||||
|
||||
gemsFromGemFile: (path) ->
|
||||
file = fs.read path
|
||||
toggle: ->
|
||||
@pane?.toggle()
|
||||
|
||||
gems: (url) ->
|
||||
file = fs.read url
|
||||
gems = []
|
||||
|
||||
for line in file.split "\n"
|
||||
if gem = line.match(/^\s*gem ['"](.+?)['"]/)?[1]
|
||||
gems.push label: gem, path: "https://rubygems.org/gems/#{gem}"
|
||||
gems.push type: 'file', name: gem, url: "https://rubygems.org/gems/#{gem}"
|
||||
|
||||
gems
|
||||
2
extensions/gemfile/key-bindings.coffee
Normal file
2
extensions/gemfile/key-bindings.coffee
Normal file
@@ -0,0 +1,2 @@
|
||||
gemfile:
|
||||
'cmd-ctrl-g': (gemfile) -> gemfile.toggle()
|
||||
144
extensions/gist/base64.js
Normal file
144
extensions/gist/base64.js
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
*
|
||||
* Base64 encode / decode
|
||||
* http://www.webtoolkit.info/
|
||||
*
|
||||
**/
|
||||
|
||||
var Base64 = {
|
||||
|
||||
// private property
|
||||
_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
|
||||
|
||||
// public method for encoding
|
||||
encode : function (input) {
|
||||
var output = "";
|
||||
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
|
||||
var i = 0;
|
||||
|
||||
input = Base64._utf8_encode(input);
|
||||
|
||||
while (i < input.length) {
|
||||
|
||||
chr1 = input.charCodeAt(i++);
|
||||
chr2 = input.charCodeAt(i++);
|
||||
chr3 = input.charCodeAt(i++);
|
||||
|
||||
enc1 = chr1 >> 2;
|
||||
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
||||
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
||||
enc4 = chr3 & 63;
|
||||
|
||||
if (isNaN(chr2)) {
|
||||
enc3 = enc4 = 64;
|
||||
} else if (isNaN(chr3)) {
|
||||
enc4 = 64;
|
||||
}
|
||||
|
||||
output = output +
|
||||
this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
|
||||
this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
|
||||
|
||||
}
|
||||
|
||||
return output;
|
||||
},
|
||||
|
||||
// public method for decoding
|
||||
decode : function (input) {
|
||||
var output = "";
|
||||
var chr1, chr2, chr3;
|
||||
var enc1, enc2, enc3, enc4;
|
||||
var i = 0;
|
||||
|
||||
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
|
||||
|
||||
while (i < input.length) {
|
||||
|
||||
enc1 = this._keyStr.indexOf(input.charAt(i++));
|
||||
enc2 = this._keyStr.indexOf(input.charAt(i++));
|
||||
enc3 = this._keyStr.indexOf(input.charAt(i++));
|
||||
enc4 = this._keyStr.indexOf(input.charAt(i++));
|
||||
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||
chr3 = ((enc3 & 3) << 6) | enc4;
|
||||
|
||||
output = output + String.fromCharCode(chr1);
|
||||
|
||||
if (enc3 != 64) {
|
||||
output = output + String.fromCharCode(chr2);
|
||||
}
|
||||
if (enc4 != 64) {
|
||||
output = output + String.fromCharCode(chr3);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
output = Base64._utf8_decode(output);
|
||||
|
||||
return output;
|
||||
|
||||
},
|
||||
|
||||
// private method for UTF-8 encoding
|
||||
_utf8_encode : function (string) {
|
||||
string = string.replace(/\r\n/g,"\n");
|
||||
var utftext = "";
|
||||
|
||||
for (var n = 0; n < string.length; n++) {
|
||||
|
||||
var c = string.charCodeAt(n);
|
||||
|
||||
if (c < 128) {
|
||||
utftext += String.fromCharCode(c);
|
||||
}
|
||||
else if((c > 127) && (c < 2048)) {
|
||||
utftext += String.fromCharCode((c >> 6) | 192);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
else {
|
||||
utftext += String.fromCharCode((c >> 12) | 224);
|
||||
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return utftext;
|
||||
},
|
||||
|
||||
// private method for UTF-8 decoding
|
||||
_utf8_decode : function (utftext) {
|
||||
var string = "";
|
||||
var i = 0;
|
||||
var c = c1 = c2 = 0;
|
||||
|
||||
while ( i < utftext.length ) {
|
||||
|
||||
c = utftext.charCodeAt(i);
|
||||
|
||||
if (c < 128) {
|
||||
string += String.fromCharCode(c);
|
||||
i++;
|
||||
}
|
||||
else if((c > 191) && (c < 224)) {
|
||||
c2 = utftext.charCodeAt(i+1);
|
||||
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
|
||||
i += 2;
|
||||
}
|
||||
else {
|
||||
c2 = utftext.charCodeAt(i+1);
|
||||
c3 = utftext.charCodeAt(i+2);
|
||||
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
|
||||
i += 3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Base64;
|
||||
64
extensions/gist/gist.coffee
Normal file
64
extensions/gist/gist.coffee
Normal file
@@ -0,0 +1,64 @@
|
||||
_ = require 'underscore'
|
||||
$ = require 'jquery'
|
||||
fs = require 'fs'
|
||||
|
||||
Extension = require 'resource'
|
||||
ModalSelector = require 'modal-selector'
|
||||
Editor = require 'editor'
|
||||
|
||||
Base64 = require 'gist/base64'
|
||||
|
||||
module.exports =
|
||||
class Gist extends Editor
|
||||
window.resourceTypes.push this
|
||||
|
||||
open: (url) ->
|
||||
return if not url
|
||||
if match = url.match /^https?:\/\/gist\.github\.com\/([^\/]+)\/?/
|
||||
super()
|
||||
@setCode "Loading Gist..."
|
||||
@url = url
|
||||
@id = match[1]
|
||||
$.ajax
|
||||
url: "https://api.github.com/gists/#{@id}"
|
||||
error: => @setCode "Loading Gist failed."
|
||||
success: (data) =>
|
||||
# only one file for now
|
||||
@filename = _.first _.keys data.files
|
||||
@metadata = _.first _.values data.files
|
||||
@setModeForURL @filename
|
||||
@setCode @metadata.content
|
||||
true
|
||||
|
||||
save: ->
|
||||
user = GitHub?.username
|
||||
pass = GitHub?.password
|
||||
|
||||
if not user or not pass
|
||||
console.error "Please set GitHub.username and GitHub.password to save."
|
||||
return
|
||||
|
||||
# Can't get this to work yet. 500ing
|
||||
if @id
|
||||
files = {}
|
||||
files[@filename] = content: @code()
|
||||
$.ajax
|
||||
# Needed for CORS, otherwise we send an unparseable Origin
|
||||
headers: { origin: null }
|
||||
url: "https://api.github.com/gists/#{@id}"
|
||||
type: 'patch'
|
||||
contentType: 'application/json'
|
||||
data: JSON.stringify { files }
|
||||
error: -> console.error "Saving Gist failed."
|
||||
success: (data) =>
|
||||
atom.native.writeToPasteboard @url
|
||||
console.log 'it worked'
|
||||
beforeSend: (req) =>
|
||||
req.setRequestHeader 'Authorization', @authorization user, pass
|
||||
true
|
||||
|
||||
# basic auth
|
||||
authorization: (user, pass) ->
|
||||
token = "#{user}:#{pass}"
|
||||
hash = Base64.encode token
|
||||
"Basic #{hash}"
|
||||
1
extensions/gist/index.coffee
Normal file
1
extensions/gist/index.coffee
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require 'gist/gist'
|
||||
1
extensions/markdownpreview/index.coffee
Normal file
1
extensions/markdownpreview/index.coffee
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require 'markdownpreview/markdownpreview'
|
||||
2
extensions/markdownpreview/key-bindings.coffee
Normal file
2
extensions/markdownpreview/key-bindings.coffee
Normal file
@@ -0,0 +1,2 @@
|
||||
editor:
|
||||
'cmd-ctrl-p': (editor) -> window.open "markdown:#{editor.url}"
|
||||
37
extensions/markdownpreview/markdownpreview.coffee
Normal file
37
extensions/markdownpreview/markdownpreview.coffee
Normal file
@@ -0,0 +1,37 @@
|
||||
fs = require 'fs'
|
||||
|
||||
tdoc = require 'docs/tdoc'
|
||||
|
||||
Browser = require 'browser'
|
||||
|
||||
{Showdown} = require './showdown'
|
||||
converter = new Showdown.converter
|
||||
|
||||
module.exports =
|
||||
class Markdownpreview extends Browser
|
||||
window.resourceTypes.push this
|
||||
|
||||
running: true
|
||||
|
||||
constructor: ->
|
||||
atom.keybinder.load require.resolve "markdownpreview/key-bindings.coffee"
|
||||
|
||||
open: (url) ->
|
||||
return false if not url
|
||||
|
||||
if match = url.match /^markdown:(.+)/
|
||||
@url = url
|
||||
|
||||
html = '''
|
||||
<link rel="stylesheet" href="http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css">
|
||||
<style>
|
||||
body { padding:10px; }
|
||||
code { line-height:16px; }
|
||||
</style>
|
||||
|
||||
'''
|
||||
html += converter.makeHtml fs.read match[1]
|
||||
|
||||
@show html
|
||||
|
||||
true
|
||||
1302
extensions/markdownpreview/showdown.js
Normal file
1302
extensions/markdownpreview/showdown.js
Normal file
File diff suppressed because it is too large
Load Diff
1
extensions/projectfinder/index.coffee
Normal file
1
extensions/projectfinder/index.coffee
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require 'projectfinder/projectfinder'
|
||||
2
extensions/projectfinder/key-bindings.coffee
Normal file
2
extensions/projectfinder/key-bindings.coffee
Normal file
@@ -0,0 +1,2 @@
|
||||
projectfinder:
|
||||
'cmd-shift-p': (projectfinder) => projectfinder.toggle()
|
||||
24
extensions/projectfinder/projectfinder.coffee
Normal file
24
extensions/projectfinder/projectfinder.coffee
Normal file
@@ -0,0 +1,24 @@
|
||||
_ = require 'underscore'
|
||||
fs = require 'fs'
|
||||
|
||||
Extension = require 'extension'
|
||||
ModalSelector = require 'modal-selector'
|
||||
|
||||
module.exports =
|
||||
class Projectfinder extends Extension
|
||||
settings:
|
||||
root: "~/Code"
|
||||
|
||||
constructor: ->
|
||||
atom.keybinder.load require.resolve "projectfinder/key-bindings.coffee"
|
||||
atom.on 'window:load', @startup
|
||||
|
||||
startup: (@project) =>
|
||||
@pane = new ModalSelector =>
|
||||
_.compact _.map (fs.list @settings.root), (url) =>
|
||||
return if fs.isFile url
|
||||
name = url.replace "#{fs.absolute @settings.root}/", ''
|
||||
{ name, url }
|
||||
|
||||
toggle: ->
|
||||
@pane?.toggle()
|
||||
1
extensions/showkeybindings/index.coffee
Normal file
1
extensions/showkeybindings/index.coffee
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require 'showkeybindings/showkeybindings'
|
||||
2
extensions/showkeybindings/key-bindings.coffee
Normal file
2
extensions/showkeybindings/key-bindings.coffee
Normal file
@@ -0,0 +1,2 @@
|
||||
showkeybindings:
|
||||
'cmd-shift-k': -> window.open 'atom://keybindings'
|
||||
41
extensions/showkeybindings/showkeybindings.coffee
Normal file
41
extensions/showkeybindings/showkeybindings.coffee
Normal file
@@ -0,0 +1,41 @@
|
||||
_ = require 'underscore'
|
||||
$ = require 'jquery'
|
||||
fs = require 'fs'
|
||||
|
||||
Browser = require 'browser'
|
||||
|
||||
module.exports =
|
||||
class Showkeybindings extends Browser
|
||||
window.resourceTypes.push this
|
||||
|
||||
constructor: ->
|
||||
atom.keybinder.load require.resolve "showkeybindings/key-bindings.coffee"
|
||||
@running = true
|
||||
|
||||
open: (url) ->
|
||||
return if not url
|
||||
|
||||
if url is 'atom://keybindings'
|
||||
@title = 'Keybindings'
|
||||
@url = url
|
||||
@show()
|
||||
true
|
||||
|
||||
innerHTML: ->
|
||||
html = '''
|
||||
<link rel="stylesheet" href="http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css">
|
||||
<style>body { padding:10px; }</style>
|
||||
'''
|
||||
html += '<h1>Keybindings</h1>'
|
||||
for name, bindings of atom.keybinder.keymaps
|
||||
html += "<h3>#{name}</h3>"
|
||||
html += "<ul>"
|
||||
for binding, method of bindings
|
||||
html += """
|
||||
<li>#{atom.keybinder.bindingFromAscii(binding)} - #{method}</li>
|
||||
"""
|
||||
html += "</ul>"
|
||||
html
|
||||
|
||||
show: ->
|
||||
super @innerHTML()
|
||||
@@ -2,7 +2,6 @@ $ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
|
||||
Pane = require 'pane'
|
||||
Browser = require 'browser'
|
||||
|
||||
module.exports =
|
||||
class TabsPane extends Pane
|
||||
@@ -42,12 +41,7 @@ class TabsPane extends Pane
|
||||
existing = $("#tabs [data-path='#{path}']")
|
||||
return @switchToTab existing if existing.length
|
||||
|
||||
name = if not path
|
||||
"untitled"
|
||||
else if Browser.isPathUrl path
|
||||
path.match(/(\w+:\/\/)([^\/]+)?/)[2]
|
||||
else
|
||||
_.last path.split '/'
|
||||
name = _.last (path or 'untitled').split '/'
|
||||
|
||||
$("#tabs ul .active").removeClass()
|
||||
$("#tabs ul").append """
|
||||
@@ -55,6 +49,20 @@ class TabsPane extends Pane
|
||||
"""
|
||||
$("#tabs ul li:last").addClass 'active'
|
||||
|
||||
closeActiveTab: ->
|
||||
tabsLength = $('#tabs ul li').length
|
||||
activePath = $('#tabs ul .active').data 'path'
|
||||
|
||||
if tabsLength is 1
|
||||
@removeTab activePath
|
||||
$('#main-container').children().css 'display', 'none !important'
|
||||
window.setTitle window.resource.title()
|
||||
else if tabsLength > 0
|
||||
@removeTab activePath
|
||||
@prevTab()
|
||||
else
|
||||
window.close()
|
||||
|
||||
removeTab: (path) ->
|
||||
tab = $("#tabs li[data-path='#{path}']")
|
||||
if tab.hasClass("active")
|
||||
|
||||
@@ -1,37 +1,26 @@
|
||||
$ = require 'jquery'
|
||||
|
||||
Extension = require 'extension'
|
||||
KeyBinder = require 'key-binder'
|
||||
Event = require 'event'
|
||||
TabsPane = require 'tabs/tabs-pane'
|
||||
|
||||
fs = require 'fs'
|
||||
Extension = require 'extension'
|
||||
TabsPane = require 'tabs/tabs-pane'
|
||||
|
||||
module.exports =
|
||||
class Tabs extends Extension
|
||||
constructor: () ->
|
||||
KeyBinder.register "tabs", @
|
||||
KeyBinder.load require.resolve "tabs/key-bindings.coffee"
|
||||
project: null
|
||||
|
||||
@pane = new TabsPane @
|
||||
constructor: ->
|
||||
atom.keybinder.load require.resolve "tabs/key-bindings.coffee"
|
||||
|
||||
Event.on 'editor:bufferAdd', (e) =>
|
||||
path = e.details
|
||||
@pane.addTab path
|
||||
atom.on 'project:open', @startup
|
||||
atom.on 'project:resource:active', @focus
|
||||
|
||||
Event.on 'editor:bufferFocus', (e) =>
|
||||
path = e.details
|
||||
@pane.addTab path
|
||||
|
||||
Event.on 'editor:bufferRemove', (e) =>
|
||||
path = e.details
|
||||
@pane.removeTab path
|
||||
|
||||
Event.on 'browser:focus', (e) =>
|
||||
path = e.details
|
||||
@pane.addTab path
|
||||
|
||||
startup: ->
|
||||
startup: (@project) =>
|
||||
@pane = new TabsPane this
|
||||
@pane.show()
|
||||
for path, buffer of window.editor.buffers
|
||||
@pane.addTab path
|
||||
super
|
||||
|
||||
shutdown: ->
|
||||
@pane.remove()
|
||||
super
|
||||
|
||||
focus: (project, resource) =>
|
||||
@pane?.addTab resource.url
|
||||
@@ -13,7 +13,7 @@ class TinyTest extends Extension
|
||||
'Command-Ctrl-T': 'runTests'
|
||||
|
||||
runTests: ->
|
||||
_.map File.list(@window.path + '/test'), @runTest
|
||||
_.map File.list(window.url + '/test'), @runTest
|
||||
|
||||
runTest: (path) ->
|
||||
# Even though we already have the path, run it
|
||||
@@ -14,53 +14,39 @@ class TreePane extends Pane
|
||||
html: $ require "tree/tree.html"
|
||||
|
||||
constructor: (@tree) ->
|
||||
@reload()
|
||||
@render()
|
||||
|
||||
$('#tree li').live 'click', (event) =>
|
||||
return true if event.__treeClicked__
|
||||
|
||||
$('#tree .active').removeClass 'active'
|
||||
|
||||
el = $(event.currentTarget)
|
||||
path = decodeURIComponent el.data 'path'
|
||||
url = decodeURIComponent el.data 'url'
|
||||
|
||||
if el.hasClass 'dir'
|
||||
if el.hasClass 'open'
|
||||
@tree.hideDir path
|
||||
el.removeClass 'open'
|
||||
el.children("ul").remove()
|
||||
el.find('ul').remove()
|
||||
else
|
||||
@tree.showDir path
|
||||
el.addClass 'open'
|
||||
list = @createList @tree.findPath(path).paths
|
||||
list = @createList @tree.urls url
|
||||
el.append list
|
||||
else
|
||||
el.addClass 'active'
|
||||
window.open path
|
||||
window.open url
|
||||
|
||||
# HACK I need the event to propogate beyond the tree pane,
|
||||
# but I need the tree pane to ignore it. Need somehting
|
||||
# cleaner here.
|
||||
event.__treeClicked__ = true
|
||||
false
|
||||
|
||||
true
|
||||
|
||||
reload: ->
|
||||
@html.children('#tree .cwd').text _.last window.path.split '/'
|
||||
fileList = @createList @tree.paths
|
||||
render: ->
|
||||
@html.children('#tree .cwd').text _.last window.url.split '/'
|
||||
fileList = @createList @tree.urls()
|
||||
fileList.addClass 'files'
|
||||
@html.children('#tree .files').replaceWith fileList
|
||||
|
||||
createList: (root) ->
|
||||
shownDirs = @tree.shownDirs()
|
||||
createList: (urls) ->
|
||||
list = $('<ul>')
|
||||
for {label, path, paths} in root
|
||||
type = if paths then 'dir' else 'file'
|
||||
encodedPath = encodeURIComponent path
|
||||
listItem = $("<li class='#{type}' data-path='#{encodedPath}'>#{label}</li>")
|
||||
if path in shownDirs and type is 'dir'
|
||||
listItem.append @createList paths
|
||||
listItem.addClass "open"
|
||||
for {name, url, type} in urls
|
||||
encodedURL = encodeURIComponent url
|
||||
listItem = $("<li class='#{type}' data-url='#{encodedURL}'>#{name}</li>")
|
||||
list.append listItem
|
||||
|
||||
list
|
||||
|
||||
@@ -1,91 +1,26 @@
|
||||
_ = require 'underscore'
|
||||
|
||||
Event = require 'event'
|
||||
Extension = require 'extension'
|
||||
KeyBinder = require 'key-binder'
|
||||
Storage = require 'storage'
|
||||
TreePane = require 'tree/tree-pane'
|
||||
Watcher = require 'watcher'
|
||||
|
||||
fs = require 'fs'
|
||||
|
||||
module.exports =
|
||||
class Tree extends Extension
|
||||
ignorePattern: /\.git|\.xcodeproj|\.DS_Store/
|
||||
|
||||
# a path is an object with three keys: label, path, and paths.
|
||||
# paths is an optional Array of other path objects.
|
||||
paths: []
|
||||
project: null
|
||||
|
||||
constructor: ->
|
||||
KeyBinder.register "tree", @
|
||||
KeyBinder.load require.resolve "tree/key-bindings.coffee"
|
||||
atom.keybinder.load require.resolve "tree/key-bindings.coffee"
|
||||
atom.on 'project:open', @startup
|
||||
|
||||
# watch the root dir
|
||||
Watcher.watch window.path, @watchDir
|
||||
|
||||
# Hide dirs that no longer exist, watch dirs that do.
|
||||
for dir in @shownDirs()
|
||||
if not fs.exists dir
|
||||
@hideDir dir
|
||||
else
|
||||
Watcher.watch dir, @watchDir
|
||||
|
||||
@paths = @findPaths window.path
|
||||
@pane = new TreePane @
|
||||
|
||||
startup: ->
|
||||
startup: (@project) =>
|
||||
@pane = new TreePane this
|
||||
@pane.show()
|
||||
super
|
||||
|
||||
shutdown: ->
|
||||
@unwatchDir window.path
|
||||
@unwatchDir dir for dir in @shownDirs()
|
||||
@pane.remove()
|
||||
super
|
||||
|
||||
reload: ->
|
||||
@pane.reload()
|
||||
|
||||
shownDirStorageKey: ->
|
||||
@.constructor.name + ":" + window.path + ":shownDirs"
|
||||
|
||||
watchDir: (changeType, dir) =>
|
||||
# Update the paths.
|
||||
@paths = @findPaths window.path
|
||||
@pane.reload()
|
||||
|
||||
unwatchDir: (dir) ->
|
||||
Watcher.unwatch dir, @watchDir
|
||||
|
||||
shownDirs: ->
|
||||
Storage.get @shownDirStorageKey(), []
|
||||
|
||||
showDir: (dir) ->
|
||||
dirs = @shownDirs().concat dir
|
||||
Storage.set @shownDirStorageKey(), dirs
|
||||
Watcher.watch dir, @watchDir
|
||||
|
||||
hideDir: (dir) ->
|
||||
dirs = _.without @shownDirs(), dir
|
||||
Storage.set @shownDirStorageKey(), dirs
|
||||
@unwatchDir dir, @watchDir
|
||||
|
||||
findPath: (searchPath, paths=@paths) ->
|
||||
found = null
|
||||
for obj in paths
|
||||
return found if found
|
||||
if obj.path is searchPath
|
||||
found = obj
|
||||
else if obj.paths
|
||||
found = @findPath searchPath, obj.paths
|
||||
found
|
||||
|
||||
findPaths: (root) ->
|
||||
paths = []
|
||||
|
||||
for path in fs.list root
|
||||
continue if @ignorePattern.test path
|
||||
paths.push
|
||||
label: _.last path.split '/'
|
||||
path: path
|
||||
paths: @findPaths path if fs.isDirectory path
|
||||
|
||||
paths
|
||||
urls: (root=@project.url) ->
|
||||
@project.urls root
|
||||
|
||||
62
index.html
62
index.html
@@ -1,72 +1,16 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Lucida Grande;
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app-horizontal {
|
||||
background-color: orange;
|
||||
display: -webkit-box;
|
||||
min-height: 100%;
|
||||
-webkit-box-orient: horizontal;
|
||||
}
|
||||
|
||||
#app-vertical {
|
||||
background-color: #AFEEEE;
|
||||
display: -webkit-box;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.main {
|
||||
-webkit-box-flex: 1;
|
||||
}
|
||||
|
||||
.pane {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.left {
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
.right {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.top {
|
||||
background-color: purple;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
background-color: blue;
|
||||
}
|
||||
|
||||
#ace-editor {
|
||||
position: relative;
|
||||
-webkit-box-flex: 1;
|
||||
font: 18px Inconsolata, Monaco, Courier;
|
||||
}
|
||||
</style>
|
||||
<link href="static/atom.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id='app-horizontal'>
|
||||
<div id='app-vertical'>
|
||||
<div id='main-container' class='main pane'></div>
|
||||
<div id='main'></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script src='src/require.js'></script>
|
||||
<script src='src/stdlib/require.js'></script>
|
||||
<script>
|
||||
window.onerror = function() {
|
||||
window.showConsole(true)
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
KeyBinder = require 'key-binder'
|
||||
fs = require 'fs'
|
||||
require 'window'
|
||||
|
||||
module.exports =
|
||||
class App
|
||||
@startup: ->
|
||||
KeyBinder.register "app", @
|
||||
window.startup()
|
||||
|
||||
@open: (path) ->
|
||||
OSX.NSApp.open path
|
||||
|
||||
@quit: ->
|
||||
OSX.NSApp.terminate null
|
||||
7
src/atom/app.coffee
Normal file
7
src/atom/app.coffee
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports =
|
||||
class App
|
||||
open: (url) ->
|
||||
OSX.NSApp.open url
|
||||
|
||||
quit: ->
|
||||
OSX.NSApp.terminate null
|
||||
36
src/atom/browser.coffee
Normal file
36
src/atom/browser.coffee
Normal file
@@ -0,0 +1,36 @@
|
||||
$ = require 'jquery'
|
||||
|
||||
Resource = require 'resource'
|
||||
|
||||
module.exports =
|
||||
class Browser extends Resource
|
||||
window.resourceTypes.push this
|
||||
|
||||
url: null
|
||||
|
||||
open: (url) ->
|
||||
return false if not /^https?:/.test url
|
||||
|
||||
@url = url
|
||||
@show()
|
||||
|
||||
true
|
||||
|
||||
# innerHTML - Optional String to set as iframe's content.
|
||||
show: (innerHTML=null) ->
|
||||
style = "width:100%;height:100%;background-color:#fff;border:none"
|
||||
@html = "<iframe src='#{@url}' style='#{style}'></iframe>"
|
||||
|
||||
super
|
||||
|
||||
iframe = @pane.find('iframe')[0]
|
||||
|
||||
if innerHTML
|
||||
iframe.contentWindow.document.body.innerHTML = innerHTML
|
||||
|
||||
if @title
|
||||
window.setTitle @title
|
||||
else
|
||||
window.setTitle iframe.contentWindow.document.title
|
||||
$(iframe).bind 'load', (e) =>
|
||||
window.setTitle e.target.contentWindow.document.title
|
||||
41
src/atom/editor-pane.coffee
Normal file
41
src/atom/editor-pane.coffee
Normal file
@@ -0,0 +1,41 @@
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
fs = require 'fs'
|
||||
Pane = require 'pane'
|
||||
|
||||
ace = require 'ace/ace'
|
||||
|
||||
module.exports =
|
||||
class EditorPane extends Pane
|
||||
id: null
|
||||
|
||||
html: null
|
||||
|
||||
position: 'main'
|
||||
|
||||
editor: null
|
||||
|
||||
constructor: ->
|
||||
@id = _.uniqueId 'editor-'
|
||||
@html = $ "<div id='#{@id}'></div>"
|
||||
@show()
|
||||
|
||||
@ace = ace.edit @id
|
||||
|
||||
# This stuff should all be grabbed from the .atomicity dir
|
||||
@ace.setTheme require "ace/theme/twilight"
|
||||
@ace.getSession().setUseSoftTabs true
|
||||
@ace.getSession().setTabSize 2
|
||||
@ace.setShowInvisibles true
|
||||
@ace.setPrintMarginColumn 78
|
||||
|
||||
# Resize editor when panes are added/removed
|
||||
el = document.body
|
||||
el.addEventListener 'DOMNodeInsertedIntoDocument', => @resize()
|
||||
el.addEventListener 'DOMNodeRemovedFromDocument', => @resize()
|
||||
|
||||
resize: (timeout=1) ->
|
||||
setTimeout =>
|
||||
@ace.focus()
|
||||
@ace.resize()
|
||||
, timeout
|
||||
165
src/atom/editor.coffee
Normal file
165
src/atom/editor.coffee
Normal file
@@ -0,0 +1,165 @@
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
fs = require 'fs'
|
||||
Resource = require 'resource'
|
||||
EditorPane = require 'editor-pane'
|
||||
|
||||
{EditSession} = require 'ace/edit_session'
|
||||
{UndoManager} = require 'ace/undomanager'
|
||||
|
||||
# Events:
|
||||
# editor:open (editor) -> Called when an editor is opened.
|
||||
module.exports =
|
||||
class Editor extends Resource
|
||||
window.resourceTypes.push this
|
||||
|
||||
dirty: false
|
||||
|
||||
url: null
|
||||
|
||||
pane: null
|
||||
|
||||
modeMap:
|
||||
js: 'javascript'
|
||||
c: 'c_cpp'
|
||||
cpp: 'c_cpp'
|
||||
h: 'c_cpp'
|
||||
m: 'c_cpp'
|
||||
md: 'markdown'
|
||||
cs: 'csharp'
|
||||
rb: 'ruby'
|
||||
ru: 'ruby'
|
||||
gemspec: 'ruby'
|
||||
|
||||
modeFileMap:
|
||||
Gemfile: 'ruby'
|
||||
Rakefile: 'ruby'
|
||||
|
||||
modeForURL: (url) ->
|
||||
return if not url
|
||||
|
||||
if not modeName = @modeFileMap[ _.last url.split '/' ]
|
||||
language = _.last url.split '.'
|
||||
language = language.toLowerCase()
|
||||
modeName = @modeMap[language] or language
|
||||
|
||||
try
|
||||
require("ace/mode/#{modeName}").Mode
|
||||
catch e
|
||||
console.error e
|
||||
require("ace/mode/text").Mode
|
||||
|
||||
setModeForURL: (url) ->
|
||||
@ace.session.setMode new (@modeForURL url)
|
||||
|
||||
title: ->
|
||||
if @url then _.last @url.split '/' else 'untitled'
|
||||
|
||||
show: ->
|
||||
@pane.show()
|
||||
@ace.resize()
|
||||
|
||||
open: (url) ->
|
||||
if url
|
||||
return false if not fs.isFile url
|
||||
return false if @url
|
||||
|
||||
window.setTitle @title()
|
||||
|
||||
@pane ?= new EditorPane
|
||||
@ace = @pane.ace
|
||||
@url = url
|
||||
|
||||
@session = new EditSession code = if @url then fs.read @url else ''
|
||||
@session.setValue code
|
||||
@session.setUseSoftTabs useSoftTabs = @usesSoftTabs code
|
||||
@session.setTabSize if useSoftTabs then @guessTabSize code else 8
|
||||
@session.setUndoManager new UndoManager
|
||||
@session.on 'change', => @dirty = true
|
||||
@ace.setSession @session
|
||||
|
||||
@show()
|
||||
@setModeForURL @url if @url
|
||||
|
||||
@dirty = false
|
||||
atom.trigger 'editor:open', this
|
||||
|
||||
true
|
||||
|
||||
close: ->
|
||||
if @dirty
|
||||
detailedMessage = if @url
|
||||
"#{@url} has changes."
|
||||
else
|
||||
"An untitled file has changes."
|
||||
|
||||
close = atom.native.alert "Do you want to save your changes?",
|
||||
detailedMessage,
|
||||
"Save": => @save()
|
||||
"Cancel": => false
|
||||
"Don't Save": => true
|
||||
|
||||
return if not close
|
||||
|
||||
super
|
||||
|
||||
save: ->
|
||||
return @saveAs() if not @url
|
||||
|
||||
@removeTrailingWhitespace()
|
||||
fs.write @url, @code()
|
||||
@dirty = false
|
||||
|
||||
@url
|
||||
|
||||
saveAs: ->
|
||||
if url = atom.native.savePanel()?.toString()
|
||||
@url = url
|
||||
@save url
|
||||
|
||||
code: ->
|
||||
@ace.getSession().getValue()
|
||||
|
||||
setCode: (code) ->
|
||||
@ace.getSession().setValue code
|
||||
|
||||
removeTrailingWhitespace: ->
|
||||
return
|
||||
@ace.replaceAll "",
|
||||
needle: "[ \t]+$"
|
||||
regExp: true
|
||||
wrap: true
|
||||
|
||||
usesSoftTabs: (code) ->
|
||||
not /^\t/m.test code or @code()
|
||||
|
||||
guessTabSize: (code) ->
|
||||
# * ignores indentation of css/js block comments
|
||||
match = /^( +)[^*]/im.exec code || @code()
|
||||
match?[1].length or 2
|
||||
|
||||
copy: ->
|
||||
text = @ace.getSession().doc.getTextRange @ace.getSelectionRange()
|
||||
atom.native.writeToPasteboard text
|
||||
|
||||
cut: ->
|
||||
text = @ace.getSession().doc.getTextRange @ace.getSelectionRange()
|
||||
atom.native.writeToPasteboard text
|
||||
@ace.session.remove @ace.getSelectionRange()
|
||||
|
||||
eval: -> eval @code()
|
||||
toggleComment: -> @ace.toggleCommentLines()
|
||||
outdent: -> @ace.blockOutdent()
|
||||
indent: -> @ace.indent()
|
||||
forwardWord: -> @ace.navigateWordRight()
|
||||
backWord: -> @ace.navigateWordLeft()
|
||||
deleteWord: -> @ace.removeWordRight()
|
||||
home: -> @ace.navigateFileStart()
|
||||
end: -> @ace.navigateFileEnd()
|
||||
|
||||
wordWrap: ->
|
||||
@ace.getSession().setUseWrapMode true
|
||||
|
||||
consolelog: ->
|
||||
@ace.insert 'console.log ""'
|
||||
@ace.navigateLeft()
|
||||
23
src/atom/extension-manager.coffee
Normal file
23
src/atom/extension-manager.coffee
Normal file
@@ -0,0 +1,23 @@
|
||||
fs = require 'fs'
|
||||
|
||||
module.exports =
|
||||
class ExtensionManager
|
||||
constructor: ->
|
||||
@loadExtensions()
|
||||
atom.on 'window:close', @unloadExtensions
|
||||
|
||||
loadExtensions: =>
|
||||
extension.shutdown() for name, extension of atom.extensions
|
||||
atom.extensions = {}
|
||||
|
||||
extensionPaths = fs.list require.resourcePath + "/extensions"
|
||||
for extensionPath in extensionPaths when fs.isDirectory extensionPath
|
||||
try
|
||||
extension = require extensionPath
|
||||
atom.extensions[extension.name] = new extension
|
||||
catch error
|
||||
console.warn "Loading Extension '#{fs.base extensionPath}' failed."
|
||||
console.warn error
|
||||
|
||||
unloadExtensions: =>
|
||||
extension.shutdown() for name, extension of atom.extensions
|
||||
15
src/atom/extension.coffee
Normal file
15
src/atom/extension.coffee
Normal file
@@ -0,0 +1,15 @@
|
||||
# Extension subclasses must call super in all overridden methods.
|
||||
module.exports =
|
||||
class Extension
|
||||
running: false
|
||||
|
||||
# `startup` should be called by you in Extension subclasses when they need
|
||||
# to appear on the screen, attach themselves to a Resource, or otherwise become active.
|
||||
startup: ->
|
||||
@running = true
|
||||
|
||||
# `shutdown` shuold be called by you in Extension subclasses when they need
|
||||
# to be remove from the screen, unattach themselves from a Resource, or otherwise become
|
||||
# inactive.
|
||||
shutdown: ->
|
||||
@running = false
|
||||
@@ -1,14 +1,15 @@
|
||||
app:
|
||||
'cmd-q': (app) -> app.quit()
|
||||
'cmd-o': (app) -> app.open()
|
||||
'cmd-n': (app) -> atom.native.newWindow()
|
||||
|
||||
window:
|
||||
'cmd-shift-i': (window) -> window.showConsole()
|
||||
'cmd-shift-I': (window) -> window.showConsole()
|
||||
'cmd-r': (window) -> window.reload()
|
||||
'cmd-o': (window) -> window.open()
|
||||
'cmd-s': (window) -> window.save()
|
||||
|
||||
editor:
|
||||
'cmd-w': 'removeBuffer'
|
||||
'cmd-s': 'save'
|
||||
'cmd-w': 'close'
|
||||
'cmd-shift-s': 'saveAs'
|
||||
'cmd-c': 'copy'
|
||||
'cmd-x': 'cut'
|
||||
167
src/atom/modal-selector.coffee
Normal file
167
src/atom/modal-selector.coffee
Normal file
@@ -0,0 +1,167 @@
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
|
||||
Modal = require 'modal'
|
||||
|
||||
jQuery = $
|
||||
require 'stringscore'
|
||||
|
||||
module.exports =
|
||||
class ModalSelector extends Modal
|
||||
selectorHTML: '''
|
||||
<div id="modal-selector">
|
||||
<input type="search">
|
||||
<br>
|
||||
<ul class="list">
|
||||
</ul>
|
||||
</div>
|
||||
'''
|
||||
|
||||
showing: false
|
||||
|
||||
# A callback which should return the items to filter.
|
||||
# Return should be an Array of {name, url} objects.
|
||||
filterCallback: -> []
|
||||
|
||||
constructor: (@filterCallback) ->
|
||||
super @selectorHTML
|
||||
|
||||
head = $('head')[0]
|
||||
style = document.createElement 'style'
|
||||
rules = document.createTextNode @selectorCSS
|
||||
style.type = 'text/css'
|
||||
style.appendChild rules
|
||||
head.appendChild style
|
||||
|
||||
onKeydown: (e) =>
|
||||
keys = up: 38, down: 40, enter: 13
|
||||
|
||||
if e.keyCode is keys.enter
|
||||
@openSelected()
|
||||
false
|
||||
else if e.keyCode is keys.up
|
||||
@moveUp()
|
||||
else if e.keyCode is keys.down
|
||||
@moveDown()
|
||||
else
|
||||
@filter()
|
||||
|
||||
show: ->
|
||||
super
|
||||
@list = @filterCallback()
|
||||
@filter()
|
||||
$('#modal-selector input').live 'keydown.modal-selector', @onKeydown
|
||||
|
||||
hide: ->
|
||||
super
|
||||
$(document).unbind '.modal-selector'
|
||||
|
||||
filter: ->
|
||||
if query = $('#modal-selector input').val()
|
||||
items = @findMatchingItems query
|
||||
else
|
||||
items = @list
|
||||
$('#modal-selector ul').empty()
|
||||
for {name} in items[0..10]
|
||||
$('#modal-selector ul').append "<li>#{name}</li>"
|
||||
$('#modal-selector input').focus()
|
||||
$('#modal-selector li:first').addClass 'selected'
|
||||
|
||||
findMatchingItems: (query) ->
|
||||
return [] if not query
|
||||
|
||||
results = []
|
||||
for item in @list
|
||||
{name, url} = item
|
||||
score = name.score query
|
||||
if score > 0
|
||||
# Basename matches count for more.
|
||||
if not query.match '/'
|
||||
if name.match '/'
|
||||
score += name.replace(/^.*\//, '').score query
|
||||
else
|
||||
score *= 2
|
||||
results.push [score, item]
|
||||
|
||||
sorted = results.sort (a, b) -> b[0] - a[0]
|
||||
_.map sorted, (el) -> el[1]
|
||||
|
||||
openSelected: ->
|
||||
text = $('#modal-selector .selected').text()
|
||||
{url} = _.find @list, (item) -> item.name is text
|
||||
if typeof url is 'function'
|
||||
url()
|
||||
else
|
||||
window.open url
|
||||
@toggle()
|
||||
|
||||
moveUp: ->
|
||||
selected = $('#modal-selector .selected')
|
||||
if selected.prev().length
|
||||
selected.prev().addClass 'selected'
|
||||
selected.removeClass 'selected'
|
||||
|
||||
moveDown: ->
|
||||
selected = $('#modal-selector .selected')
|
||||
if selected.next().length
|
||||
selected.next().addClass 'selected'
|
||||
selected.removeClass 'selected'
|
||||
|
||||
selectorCSS: '''
|
||||
#modal .content {
|
||||
background: #ededed;
|
||||
padding: 0;
|
||||
}
|
||||
#modal .close {
|
||||
display: none;
|
||||
}
|
||||
#modal-selector .list {
|
||||
height: 100px;
|
||||
overflow: hidden;
|
||||
padding: 10px 0;
|
||||
}
|
||||
#modal-selector input[type=search] {
|
||||
width: 95%;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#modal .content {
|
||||
min-width: 200px;
|
||||
height: 100%;
|
||||
background-color: #DDE3EA;
|
||||
color: #000;
|
||||
border-right: 1px solid #B4B4B4;
|
||||
cursor: default;
|
||||
-webkit-user-select: none;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#modal .content .cwd {
|
||||
padding-top: 5px;
|
||||
padding-left: 5px;
|
||||
font-weight: bold;
|
||||
color: #708193;
|
||||
text-transform: uppercase;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
#modal .content ul {
|
||||
margin: 0;
|
||||
padding-top: 2px;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#modal .content li {
|
||||
padding: 0;
|
||||
padding-left: 5px;
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#modal .content li.selected {
|
||||
background-image: -webkit-gradient(linear,0% 0,0% 100%,from(#BCCBEB),to(#8094BB));
|
||||
border-top: 1px solid #A0AFCD;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
'''
|
||||
@@ -1,10 +1,18 @@
|
||||
$ = require 'jquery'
|
||||
|
||||
Event = require 'event'
|
||||
|
||||
# Events:
|
||||
# modal:show (modal) -> Called when the modal is displayed on screen.
|
||||
# modal:hide (modal) -> Called when the modal hides.
|
||||
module.exports =
|
||||
class Modal
|
||||
template: '<div id="modal"><div class="popup"><div class="content"></div><a href="#" class="close inline"> </a></div></div>'
|
||||
template: '''
|
||||
<div id="modal">
|
||||
<div class="popup">
|
||||
<div class="content"></div>
|
||||
<a href="#" class="close inline"> </a>
|
||||
</div>
|
||||
</div>
|
||||
'''
|
||||
|
||||
showing: false
|
||||
|
||||
@@ -16,13 +24,13 @@ class Modal
|
||||
style.appendChild rules
|
||||
head.appendChild style
|
||||
|
||||
$(window).bind 'resize.modal', => @resize()
|
||||
|
||||
show: ->
|
||||
@showing = true
|
||||
|
||||
$('body').append('<div id="modal-overlay"></div>')
|
||||
|
||||
$(window).bind 'resize.modal', => @resize()
|
||||
|
||||
$(document).bind 'keydown.modal', (e) =>
|
||||
if e.keyCode is (esc = 27) then @hide(); false
|
||||
|
||||
@@ -37,7 +45,7 @@ class Modal
|
||||
$('#modal input').focus()
|
||||
@resize()
|
||||
|
||||
Event.trigger 'modal:show', @
|
||||
atom.trigger 'modal:show', this
|
||||
|
||||
resize: ->
|
||||
$('#modal').css
|
||||
@@ -50,10 +58,14 @@ class Modal
|
||||
$('#modal-overlay').fadeOut 200, -> $(this).remove()
|
||||
$(document).unbind '.modal'
|
||||
$(window).unbind '.modal'
|
||||
Event.trigger 'modal:hide', @
|
||||
|
||||
atom.trigger 'modal:hide', this
|
||||
|
||||
toggle: ->
|
||||
if @showing then @hide() else @show()
|
||||
if @showing
|
||||
@hide()
|
||||
else
|
||||
@show()
|
||||
|
||||
css: """
|
||||
#modal {
|
||||
@@ -16,10 +16,8 @@ class Pane
|
||||
|
||||
switch @position
|
||||
when 'main'
|
||||
# ICK: There can be multiple 'main' panes, but only one can be active
|
||||
# at at time.
|
||||
$('#main-container').children().css 'display', 'none !important'
|
||||
$('#main-container').append @pane
|
||||
$('#main > div').addClass 'hidden'
|
||||
$('#main').append @pane
|
||||
when 'top'
|
||||
verticalDiv.prepend @pane
|
||||
when 'left'
|
||||
@@ -29,19 +27,20 @@ class Pane
|
||||
when 'right'
|
||||
horizontalDiv.append @pane
|
||||
else
|
||||
throw "I DON'T KNOW HOW TO DEAL WITH #{@position}"
|
||||
throw "pane position of #{this} can't be `#{@position}`"
|
||||
|
||||
showing: ->
|
||||
@pane and not @pane.css('display').match /none/
|
||||
@pane and not @pane.hasClass 'hidden'
|
||||
|
||||
show: ->
|
||||
if @position == 'main'
|
||||
$('#main-container').children().css 'display', 'none !important'
|
||||
|
||||
if not @pane then @add() else @pane.css 'display', '-webkit-box !important'
|
||||
if not @pane
|
||||
@add()
|
||||
else
|
||||
$('#main > div').addClass 'hidden' if @position == 'main'
|
||||
@pane.removeClass 'hidden'
|
||||
|
||||
hide: ->
|
||||
@pane.css 'display', 'none !important'
|
||||
@pane.addClass 'hidden'
|
||||
|
||||
toggle: ->
|
||||
if @showing()
|
||||
97
src/atom/project.coffee
Normal file
97
src/atom/project.coffee
Normal file
@@ -0,0 +1,97 @@
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
fs = require 'fs'
|
||||
Resource = require 'resource'
|
||||
|
||||
# Events:
|
||||
# project:open (project) -> Called when a project is opened.
|
||||
# project:resource:open (project, resource) ->
|
||||
# Called when the project opens a resource.
|
||||
# project:resource:active (project, resource) ->
|
||||
# Called when a resource becomes active (i.e. the focal point)
|
||||
# in a project.
|
||||
module.exports =
|
||||
class Project extends Resource
|
||||
window.resourceTypes.push this
|
||||
|
||||
settings:
|
||||
# Regexp used to ignore paths.
|
||||
ignorePattern: /(\.git|\.xcodeproj|\.DS_Store|node_modules)/
|
||||
|
||||
# Arrays of { name, url, type } keyed by the root URL.
|
||||
# Used when looking up a directory's contents by url
|
||||
# to add metadata such as magic files or directories.
|
||||
extraURLs: {}
|
||||
|
||||
resources: {}
|
||||
|
||||
activeResource: null
|
||||
|
||||
responder: ->
|
||||
@activeResource or this
|
||||
|
||||
open: (url) ->
|
||||
if not @url
|
||||
# Can only open directories.
|
||||
return false if not fs.isDirectory url
|
||||
|
||||
@url = url
|
||||
window.setTitle @title()
|
||||
atom.trigger 'project:open', this
|
||||
|
||||
true
|
||||
else if @url
|
||||
# Can't open directories once we have a URL.
|
||||
if fs.isDirectory url
|
||||
return false
|
||||
|
||||
# Ignore non-children files
|
||||
if (fs.isFile url) and not @childURL url
|
||||
return false
|
||||
|
||||
# Is this resource already open?
|
||||
if @resources[url]
|
||||
@activeResource = @resources[url]
|
||||
atom.trigger 'project:resource:active', this, @activeResource
|
||||
@activeResource.show()
|
||||
true
|
||||
else
|
||||
# Try to open all others
|
||||
for resourceType in window.resourceTypes
|
||||
resource = new resourceType
|
||||
break if success = resource.open url
|
||||
|
||||
if success
|
||||
@resources[url] = @activeResource = resource
|
||||
atom.trigger 'project:resource:open', this, resource
|
||||
atom.trigger 'project:resource:active', this, resource
|
||||
true
|
||||
|
||||
save: ->
|
||||
@activeResource?.save()
|
||||
|
||||
title: ->
|
||||
_.last @url.split '/'
|
||||
|
||||
# Determines if a passed URL is a child of @url.
|
||||
# Returns a Boolean.
|
||||
childURL: (url) ->
|
||||
return false if not @url
|
||||
parent = @url.replace /([^\/])$/, "$1/"
|
||||
child = url.replace /([^\/])$/, "$1/"
|
||||
child.match "^" + parent
|
||||
|
||||
urls: (root=@url) ->
|
||||
_.compact _.map (fs.list root), (url) =>
|
||||
return if @settings.ignorePattern.test url
|
||||
type: if fs.isDirectory url then 'dir' else 'file'
|
||||
name: url.replace(root, "").substring 1
|
||||
url: url
|
||||
.concat @settings.extraURLs[root] or []
|
||||
|
||||
# WARNING THIS IS PROBABLY SLOW
|
||||
allURLs: ->
|
||||
_.compact _.map (fs.listDirectoryTree @url), (url) =>
|
||||
name = url.replace "#{window.url}/", ''
|
||||
return if @settings.ignorePattern.test name
|
||||
{ name, url }
|
||||
19
src/atom/resource.coffee
Normal file
19
src/atom/resource.coffee
Normal file
@@ -0,0 +1,19 @@
|
||||
_ = require 'underscore'
|
||||
Pane = require 'pane'
|
||||
|
||||
# When subclassing, call super() at the end of your
|
||||
# constructor.
|
||||
module.exports =
|
||||
class Resource extends Pane
|
||||
position: "main"
|
||||
url: null
|
||||
|
||||
constructor: ->
|
||||
|
||||
# Can be used to delegate key events to another object, such as a pane.
|
||||
responder: -> this
|
||||
|
||||
close: ->
|
||||
window.close()
|
||||
|
||||
save: ->
|
||||
55
src/atom/window.coffee
Normal file
55
src/atom/window.coffee
Normal file
@@ -0,0 +1,55 @@
|
||||
fs = require 'fs'
|
||||
_ = require 'underscore'
|
||||
|
||||
# This a weirdo file. We don't create a Window class, we just add stuff to
|
||||
# the DOM window.
|
||||
#
|
||||
# Events:
|
||||
# window:load - Same as window.onLoad. Final event of app startup.
|
||||
windowAdditions =
|
||||
resourceTypes: []
|
||||
|
||||
url: $atomController.url?.toString()
|
||||
|
||||
startup: ->
|
||||
atom.trigger 'window:load', this
|
||||
|
||||
success = false
|
||||
for resourceType in @resourceTypes.reverse()
|
||||
@resource = new resourceType
|
||||
break if success = @resource.open @url
|
||||
|
||||
throw "I DON'T KNOW ABOUT #{@url}" if not success
|
||||
|
||||
shutdown: ->
|
||||
|
||||
showConsole: ->
|
||||
$atomController.webView.inspector.showConsole true
|
||||
|
||||
setTitle: (title) ->
|
||||
$atomController.window.title = title
|
||||
|
||||
reload: ->
|
||||
@close()
|
||||
OSX.NSApp.createController @url
|
||||
|
||||
open: (url) ->
|
||||
url = atom.native.openPanel() unless url
|
||||
(@resource.open url) or atom.app.open url
|
||||
|
||||
save: ->
|
||||
@resource.save()
|
||||
|
||||
close: (url) ->
|
||||
@shutdown()
|
||||
$atomController.close
|
||||
|
||||
handleKeyEvent: ->
|
||||
atom.keybinder.handleEvent arguments...
|
||||
|
||||
triggerEvent: ->
|
||||
atom.trigger arguments...
|
||||
|
||||
for key, value of windowAdditions
|
||||
console.warn "DOMWindow already has a key named `#{key}`" if window[key]
|
||||
window[key] = value
|
||||
@@ -1,28 +0,0 @@
|
||||
$ = require 'jquery'
|
||||
|
||||
Event = require 'event'
|
||||
Pane = require 'pane'
|
||||
|
||||
module.exports =
|
||||
class Browser extends Pane
|
||||
buffers: {}
|
||||
|
||||
html: $ "<div id='browser'></div>"
|
||||
|
||||
position: 'main'
|
||||
|
||||
@isPathUrl: (path) ->
|
||||
/^https?:\/\//.test path
|
||||
|
||||
constructor: ->
|
||||
Event.on "window:open", (e) =>
|
||||
path = e.details
|
||||
return unless @constructor.isPathUrl path
|
||||
|
||||
@buffers[path] ?= $ "<iframe src='#{path}' style='width:100%;height:100%'></iframe>"
|
||||
|
||||
@html.html @buffers[path]
|
||||
|
||||
@show()
|
||||
|
||||
Event.trigger "browser:focus", path
|
||||
@@ -1,232 +0,0 @@
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
fs = require 'fs'
|
||||
ace = require 'ace/ace'
|
||||
|
||||
Event = require 'event'
|
||||
KeyBinder = require 'key-binder'
|
||||
Native = require 'native'
|
||||
Storage = require 'storage'
|
||||
Pane = require 'pane'
|
||||
|
||||
{EditSession} = require 'ace/edit_session'
|
||||
{UndoManager} = require 'ace/undomanager'
|
||||
|
||||
module.exports =
|
||||
class Editor extends Pane
|
||||
activePath: null
|
||||
|
||||
buffers: {}
|
||||
|
||||
openPathsKey: "editor.openPaths.#{window.path}"
|
||||
|
||||
focusedPathKey: "editor.focusedPath.#{window.path}"
|
||||
|
||||
html: $ "<div id='ace-editor'></div>"
|
||||
|
||||
position: "main"
|
||||
|
||||
constructor: ->
|
||||
KeyBinder.register "editor", @
|
||||
|
||||
@show()
|
||||
|
||||
@ace = ace.edit 'ace-editor'
|
||||
|
||||
# This stuff should all be grabbed from the .atomicity dir
|
||||
@ace.setTheme require "ace/theme/twilight"
|
||||
@ace.getSession().setUseSoftTabs true
|
||||
@ace.getSession().setTabSize 2
|
||||
@ace.setShowInvisibles(true)
|
||||
@ace.setPrintMarginColumn 78
|
||||
|
||||
Event.on 'window:open', (e) =>
|
||||
path = e.details
|
||||
@addBuffer e.details if fs.isFile path
|
||||
|
||||
Event.on 'window:close', (e) => @removeBuffer e.details
|
||||
Event.on 'editor:bufferFocus', (e) => @resize()
|
||||
|
||||
# Resize editor when panes are added/removed
|
||||
el = document.body
|
||||
el.addEventListener 'DOMNodeInsertedIntoDocument', => @resize()
|
||||
el.addEventListener 'DOMNodeRemovedFromDocument', => @resize()
|
||||
|
||||
modeMap:
|
||||
js: 'javascript'
|
||||
c: 'c_cpp'
|
||||
cpp: 'c_cpp'
|
||||
h: 'c_cpp'
|
||||
m: 'c_cpp'
|
||||
md: 'markdown'
|
||||
cs: 'csharp'
|
||||
rb: 'ruby'
|
||||
ru: 'ruby'
|
||||
gemspec: 'ruby'
|
||||
|
||||
modeFileMap:
|
||||
Gemfile: 'ruby'
|
||||
Rakefile: 'ruby'
|
||||
|
||||
modeForPath: (path) ->
|
||||
return null if not path
|
||||
|
||||
if not modeName = @modeFileMap[ _.last path.split '/' ]
|
||||
language = _.last path.split '.'
|
||||
language = language.toLowerCase()
|
||||
modeName = @modeMap[language] or language
|
||||
|
||||
try
|
||||
require("ace/mode/#{modeName}").Mode
|
||||
catch e
|
||||
null
|
||||
|
||||
restoreOpenBuffers: ->
|
||||
openPaths = Storage.get @openPathsKey, []
|
||||
focusedPath = Storage.get(@focusedPathKey)
|
||||
|
||||
@addBuffer path for path in openPaths
|
||||
@focusBuffer focusedPath if focusedPath
|
||||
|
||||
addBuffer: (path) ->
|
||||
throw "#{@constructor.name}: Cannot create buffer from a directory `#{path}`" if fs.isDirectory path
|
||||
|
||||
buffer = @buffers[path]
|
||||
if not buffer
|
||||
code = if path then fs.read path else ''
|
||||
buffer = new EditSession code
|
||||
buffer.setUndoManager new UndoManager
|
||||
buffer.setUseSoftTabs useSoftTabs = @usesSoftTabs code
|
||||
buffer.setTabSize if useSoftTabs then @guessTabSize code else 8
|
||||
|
||||
mode = @modeForPath path
|
||||
buffer.setMode new mode if mode
|
||||
|
||||
@buffers[path] = buffer
|
||||
|
||||
openPaths = Storage.get @openPathsKey, []
|
||||
unless path in openPaths
|
||||
openPaths.push path
|
||||
Storage.set @openPathsKey, openPaths
|
||||
|
||||
buffer.on 'change', -> buffer.$atom_dirty = true
|
||||
Event.trigger "editor:bufferAdd", path
|
||||
|
||||
@focusBuffer path
|
||||
|
||||
removeBuffer: (path) ->
|
||||
path ?= @activePath
|
||||
|
||||
return if not path
|
||||
|
||||
buffer = @buffers[path]
|
||||
return if not buffer
|
||||
|
||||
if buffer.$atom_dirty
|
||||
# This should be thrown into it's own method, but I can't think of a good
|
||||
# name.
|
||||
detailedMessage = if @activePath
|
||||
"#{@activePath} has changes."
|
||||
else
|
||||
"An untitled file has changes."
|
||||
|
||||
canceled = Native.alert "Do you want to save the changes you made?", detailedMessage,
|
||||
"Save": =>
|
||||
path = @save()
|
||||
not path # if save modal fails/cancels, consider it canceled
|
||||
"Cancel": => true
|
||||
"Don't Save": => false
|
||||
|
||||
return if canceled
|
||||
|
||||
delete @buffers[path]
|
||||
|
||||
openPaths = Storage.get @openPathsKey, []
|
||||
Storage.set @openPathsKey, _.without openPaths, path
|
||||
Event.trigger "editor:bufferRemove", path
|
||||
|
||||
if path is @activePath
|
||||
newActivePath = Object.keys(@buffers)[0]
|
||||
if newActivePath
|
||||
@focusBuffer newActivePath
|
||||
else
|
||||
@ace.setSession new EditSession ''
|
||||
|
||||
focusBuffer: (path) ->
|
||||
return if not path
|
||||
|
||||
@show()
|
||||
@activePath = path
|
||||
|
||||
buffer = @buffers[path] or @addBuffer path
|
||||
@ace.setSession buffer
|
||||
|
||||
Storage.set @focusedPathKey, path
|
||||
Event.trigger "editor:bufferFocus", path
|
||||
|
||||
save: (path) ->
|
||||
path ?= @activePath
|
||||
|
||||
return @saveAs() if not path
|
||||
|
||||
@removeTrailingWhitespace()
|
||||
fs.write path, @code()
|
||||
if @buffers[path]
|
||||
@buffers[path].$atom_dirty = false
|
||||
|
||||
path
|
||||
|
||||
saveAs: ->
|
||||
path = Native.savePanel()?.toString()
|
||||
if path
|
||||
@save path
|
||||
@addBuffer path
|
||||
|
||||
path
|
||||
|
||||
code: ->
|
||||
@ace.getSession().getValue()
|
||||
|
||||
removeTrailingWhitespace: ->
|
||||
return
|
||||
@ace.replaceAll "",
|
||||
needle: "[ \t]+$"
|
||||
regExp: true
|
||||
wrap: true
|
||||
|
||||
usesSoftTabs: (code) ->
|
||||
not /^\t/m.test code or @code()
|
||||
|
||||
guessTabSize: (code) ->
|
||||
# * ignores indentation of css/js block comments
|
||||
match = /^( +)[^*]/im.exec code || @code()
|
||||
match?[1].length or 2
|
||||
|
||||
resize: (timeout=1) ->
|
||||
setTimeout =>
|
||||
@ace.focus()
|
||||
@ace.resize()
|
||||
, timeout
|
||||
|
||||
copy: ->
|
||||
text = @ace.getSession().doc.getTextRange @ace.getSelectionRange()
|
||||
Native.writeToPasteboard text
|
||||
|
||||
cut: ->
|
||||
text = @ace.getSession().doc.getTextRange @ace.getSelectionRange()
|
||||
Native.writeToPasteboard text
|
||||
@ace.session.remove @ace.getSelectionRange()
|
||||
|
||||
eval: -> eval @code()
|
||||
toggleComment: -> @ace.toggleCommentLines()
|
||||
outdent: -> @ace.blockOutdent()
|
||||
indent: -> @ace.indent()
|
||||
forwardWord: -> @ace.navigateWordRight()
|
||||
backWord: -> @ace.navigateWordLeft()
|
||||
deleteWord: -> @ace.removeWordRight()
|
||||
home: -> @ace.navigateFileStart()
|
||||
end: -> @ace.navigateFileEnd()
|
||||
|
||||
consolelog: ->
|
||||
@ace.insert 'console.log ""'
|
||||
@ace.navigateLeft()
|
||||
@@ -1,28 +0,0 @@
|
||||
# Using the DOM event system, and copying the JQuery Event API
|
||||
# https://developer.mozilla.org/en/DOM/Creating_and_triggering_events
|
||||
|
||||
module.exports =
|
||||
class Event
|
||||
@events: {}
|
||||
|
||||
@on: (name, callback) ->
|
||||
window.document.addEventListener name, callback
|
||||
callback
|
||||
|
||||
@off: (name, callback) ->
|
||||
window.document.removeEventListener name, callback
|
||||
|
||||
@trigger: (name, data, bubbleToApp=true) ->
|
||||
if bubbleToApp and name.match /^app:/
|
||||
OSX.NSApp.triggerGlobalEvent_data name, data
|
||||
return
|
||||
|
||||
event = @events[name]
|
||||
if not event
|
||||
event = window.document.createEvent "CustomEvent"
|
||||
event.initCustomEvent name, true, true, null
|
||||
@events[name] = event
|
||||
|
||||
event.details = data
|
||||
window.document.dispatchEvent event
|
||||
null
|
||||
@@ -1,15 +0,0 @@
|
||||
KeyBinder = require 'key-binder'
|
||||
fs = require 'fs'
|
||||
|
||||
module.exports =
|
||||
class Extension
|
||||
pane: null
|
||||
|
||||
constructor: ->
|
||||
console.log "#{@constructor.name}: Loaded"
|
||||
|
||||
storageNamespace: -> @constructor.name
|
||||
|
||||
startup: ->
|
||||
|
||||
shutdown: ->
|
||||
@@ -1,147 +0,0 @@
|
||||
_ = require 'underscore'
|
||||
fs = require 'fs'
|
||||
Watcher = require 'watcher'
|
||||
{CoffeeScript} = require 'coffee-script'
|
||||
|
||||
module.exports =
|
||||
class KeyBinder
|
||||
@bindings: {}
|
||||
|
||||
@scopes: {}
|
||||
|
||||
@register: (name, scope) ->
|
||||
@scopes[name] = scope
|
||||
|
||||
@load: (path) ->
|
||||
try
|
||||
Watcher.watch path, =>
|
||||
# Should we keep track of which file bindings are associated with? That
|
||||
# way we could clear bindings when the file is changed or deleted. I
|
||||
# think the answer is yes, but I don't want to do this right now.
|
||||
console.log "#{@name}: Reloading #{path}"
|
||||
@load path
|
||||
|
||||
json = CoffeeScript.eval "return " + (fs.read path)
|
||||
for scopeName, bindings of json
|
||||
@create scopeName, binding, method for binding, method of bindings
|
||||
catch error
|
||||
console.error "#{@name}: Could not load key bindings at `#{path}`. #{error}"
|
||||
|
||||
@create: (scope, binding, method) ->
|
||||
if typeof scope is "string"
|
||||
throw "#{@name}: Unknown scope `#{scope}`" unless @scopes[scope]
|
||||
scope = @scopes[scope]
|
||||
|
||||
callback = if _.isFunction method
|
||||
-> method scope
|
||||
else if scope[method]
|
||||
-> scope[method]()
|
||||
else
|
||||
throw "#{@name}: '#{method}' not found found in scope #{scope}"
|
||||
|
||||
callbacks = @bindings[@bindingParser binding] ?= []
|
||||
|
||||
callbacks.push callback
|
||||
|
||||
@handleEvent: (event) ->
|
||||
keys = []
|
||||
keys.push @modifierKeys.command if event.modifierFlags & OSX.NSCommandKeyMask
|
||||
keys.push @modifierKeys.shift if event.modifierFlags & OSX.NSShiftKeyMask
|
||||
keys.push @modifierKeys.control if event.modifierFlags & OSX.NSControlKeyMask
|
||||
keys.push @modifierKeys.alt if event.modifierFlags & OSX.NSAlternateKeyMask
|
||||
keys.push event.charactersIgnoringModifiers.toLowerCase().charCodeAt 0
|
||||
|
||||
binding = keys.sort().join "-"
|
||||
|
||||
callbacks = @bindings[binding]
|
||||
return false if not callbacks
|
||||
|
||||
# Only use the most recently added binding
|
||||
try
|
||||
_.last(callbacks)()
|
||||
catch e
|
||||
console.warn "Failed to run binding #{@bindingFromAscii binding}. #{e}"
|
||||
|
||||
true
|
||||
|
||||
@bindingParser: (binding) ->
|
||||
keys = binding.trim().split '-'
|
||||
|
||||
modifiers = []
|
||||
key = null
|
||||
|
||||
for k in keys
|
||||
k = k.toLowerCase()
|
||||
if @modifierKeys[k]
|
||||
modifiers.push @modifierKeys[k]
|
||||
else if key
|
||||
throw "#{@name}: #{binding} specifies TWO keys, we don't handle that yet."
|
||||
else if @namedKeys[k]
|
||||
key = @namedKeys[k]
|
||||
else if k.length > 1
|
||||
throw "#{@name}: #{binding} uses an unknown key #{k}."
|
||||
else
|
||||
key = k.charCodeAt 0
|
||||
|
||||
modifiers.concat(key).sort().join "-"
|
||||
|
||||
@bindingFromAscii: (binding) ->
|
||||
inverseModifierKeys = {}
|
||||
inverseModifierKeys[number] = label for label, number of @modifierKeys
|
||||
|
||||
inverseNamedKeys = {}
|
||||
inverseNamedKeys[number] = label for label, number of @namedKeys
|
||||
|
||||
asciiKeys = binding.split '-'
|
||||
keys = []
|
||||
|
||||
for asciiKey in asciiKeys
|
||||
key = inverseModifierKeys[asciiKey]
|
||||
key ?= inverseNamedKeys[asciiKey]
|
||||
key ?= String.fromCharCode asciiKey
|
||||
keys.push key or "?"
|
||||
|
||||
keys.join '-'
|
||||
|
||||
@modifierKeys:
|
||||
'⇧': 16
|
||||
'⌘': 91
|
||||
'⌥': 18
|
||||
shift: 16
|
||||
alt: 18
|
||||
option: 18
|
||||
control: 17
|
||||
ctrl: 17
|
||||
command: 91
|
||||
cmd: 91
|
||||
|
||||
@namedKeys:
|
||||
backspace: 8
|
||||
tab: 9
|
||||
clear: 12
|
||||
enter: 13
|
||||
return: 13
|
||||
esc: 27
|
||||
escape: 27
|
||||
space: 32
|
||||
left: 37
|
||||
up: 38
|
||||
right: 39
|
||||
down: 40
|
||||
del: 46
|
||||
delete: 46
|
||||
home: 36
|
||||
end: 35
|
||||
pageup: 33
|
||||
pagedown: 34
|
||||
',': 188
|
||||
'.': 190
|
||||
'/': 191
|
||||
'`': 192
|
||||
'-': 189
|
||||
'=': 187
|
||||
';': 186
|
||||
'\'': 222
|
||||
'[': 219
|
||||
']': 221
|
||||
'\\': 220
|
||||
@@ -1,3 +1,33 @@
|
||||
# Like sands through the hourglass, so are the days of our lives.
|
||||
require 'window'
|
||||
|
||||
window.atom = {}
|
||||
|
||||
App = require 'app'
|
||||
App.startup()
|
||||
Browser = require 'browser'
|
||||
Editor = require 'editor'
|
||||
Event = require 'event'
|
||||
ExtensionManager = require 'extension-manager'
|
||||
KeyBinder = require 'key-binder'
|
||||
Native = require 'native'
|
||||
Project = require 'project'
|
||||
Resource = require 'resource'
|
||||
Settings = require 'settings'
|
||||
Storage = require 'storage'
|
||||
|
||||
atom.event = new Event
|
||||
# atom.on, atom.off, etc.
|
||||
for name, method of atom.event
|
||||
atom[name] = atom.event[name]
|
||||
|
||||
atom.native = new Native
|
||||
atom.storage = new Storage
|
||||
atom.keybinder = new KeyBinder
|
||||
atom.settings = new Settings
|
||||
|
||||
atom.extensions = {}
|
||||
atom.extensionManager = new ExtensionManager
|
||||
|
||||
atom.app = new App
|
||||
|
||||
window.startup()
|
||||
|
||||
20
src/stdlib/event.coffee
Normal file
20
src/stdlib/event.coffee
Normal file
@@ -0,0 +1,20 @@
|
||||
_ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
class Event
|
||||
events: {}
|
||||
|
||||
on: (name, callback) ->
|
||||
@events[name] ?= []
|
||||
@events[name].push callback
|
||||
|
||||
off: (name, callback) ->
|
||||
delete @events[name][_.indexOf callback] if @events[name]
|
||||
|
||||
trigger: (name, data...) ->
|
||||
if name.match /^app:/
|
||||
OSX.NSApp.triggerGlobalEvent_data name, data
|
||||
return
|
||||
|
||||
_.each @events[name], (callback) => callback data...
|
||||
null
|
||||
166
src/stdlib/key-binder.coffee
Normal file
166
src/stdlib/key-binder.coffee
Normal file
@@ -0,0 +1,166 @@
|
||||
_ = require 'underscore'
|
||||
fs = require 'fs'
|
||||
Watcher = require 'watcher'
|
||||
{CoffeeScript} = require 'coffee-script'
|
||||
|
||||
module.exports =
|
||||
class KeyBinder
|
||||
# keymaps are name => { binding: method } mappings
|
||||
keymaps: {}
|
||||
|
||||
constructor: ->
|
||||
@load require.resolve "key-bindings.coffee"
|
||||
if fs.isFile "~/.atomicity/key-bindings.coffee"
|
||||
@load "~/.atomicity/key-bindings.coffee"
|
||||
|
||||
register: (name, scope) ->
|
||||
|
||||
load: (path) ->
|
||||
try
|
||||
# Watcher.watch path, =>
|
||||
# @load path
|
||||
|
||||
json = CoffeeScript.eval "return " + (fs.read path)
|
||||
# Iterate in reverse order scopes are declared.
|
||||
# Scope at the top of the file is checked last.
|
||||
for name in _.keys(json).reverse()
|
||||
bindings = json[name]
|
||||
@keymaps[name] ?= {}
|
||||
for binding, method of bindings
|
||||
@keymaps[name][@bindingParser binding] = method
|
||||
catch error
|
||||
console.error "Can't load key bindings at `#{path}`."
|
||||
console.error error
|
||||
|
||||
handleEvent: (event) ->
|
||||
keys = []
|
||||
if event.modifierFlags & OSX.NSCommandKeyMask
|
||||
keys.push @modifierKeys.command
|
||||
if event.modifierFlags & OSX.NSControlKeyMask
|
||||
keys.push @modifierKeys.control
|
||||
if event.modifierFlags & OSX.NSAlternateKeyMask
|
||||
keys.push @modifierKeys.alt
|
||||
if event.modifierFlags & OSX.NSShiftKeyMask
|
||||
keys.push @modifierKeys.shift
|
||||
keys.push event.charactersIgnoringModifiers.toLowerCase().charCodeAt 0
|
||||
|
||||
binding = keys.sort().join "-"
|
||||
try
|
||||
@triggerBinding binding
|
||||
catch error
|
||||
console.error "Failed to run binding #{@bindingFromAscii binding}."
|
||||
console.error error
|
||||
|
||||
|
||||
# Given a keyboard combination, goes through the responder
|
||||
# chain and checks if any object (or any of that object's super
|
||||
# classes) respond to the binding.
|
||||
#
|
||||
# If so, it triggers the binding.
|
||||
#
|
||||
# binding - A String in the form of "#{charCode}-#{chardCode}"
|
||||
#
|
||||
# Returns true if we found and triggered the binding, false if not.
|
||||
triggerBinding: (binding) ->
|
||||
for responder in @responders()
|
||||
name = responder.constructor.name?.toLowerCase()
|
||||
name = 'window' if responder is window
|
||||
|
||||
if method = @keymaps[name]?[binding]
|
||||
if _.isFunction method
|
||||
method responder
|
||||
else
|
||||
responder[method]()
|
||||
return true
|
||||
|
||||
false
|
||||
|
||||
responders: ->
|
||||
extensions = _.select (_.values atom.extensions), (extension) ->
|
||||
extension.running?
|
||||
_.flatten [ extensions, window.resource.responder(), window, atom.app ]
|
||||
|
||||
bindingParser: (binding) ->
|
||||
keys = binding.trim().split '-'
|
||||
|
||||
modifiers = []
|
||||
key = null
|
||||
for k in keys
|
||||
if modifier = @modifierKeys[k.toLowerCase()]
|
||||
modifiers.push modifier
|
||||
else if key
|
||||
throw "#{@name}: #{binding} specifies TWO keys, we don't handle that yet."
|
||||
else if namedKey = @namedKeys[k.toLowerCase()]
|
||||
key = namedKey
|
||||
else if k.toLowerCase() isnt k
|
||||
if not _.include modifiers, @modifierKeys.shift
|
||||
modifiers.push @modifierKeys.shift
|
||||
key = k.toLowerCase().charCodeAt 0
|
||||
else if k.length > 1
|
||||
throw "#{@name}: #{binding} uses an unknown key #{k}."
|
||||
else
|
||||
charCode = k.charCodeAt 0
|
||||
key = k.charCodeAt 0
|
||||
|
||||
modifiers.concat(key).sort().join "-"
|
||||
|
||||
bindingFromAscii: (binding) ->
|
||||
inverseModifierKeys = {}
|
||||
inverseModifierKeys[number] = label for label, number of @modifierKeys
|
||||
|
||||
inverseNamedKeys = {}
|
||||
inverseNamedKeys[number] = label for label, number of @namedKeys
|
||||
|
||||
asciiKeys = binding.split '-'
|
||||
keys = []
|
||||
|
||||
for asciiKey in asciiKeys.reverse()
|
||||
key = inverseModifierKeys[asciiKey]
|
||||
key ?= inverseNamedKeys[asciiKey]
|
||||
key ?= String.fromCharCode asciiKey
|
||||
keys.push key or "?"
|
||||
|
||||
keys.join '-'
|
||||
|
||||
modifierKeys:
|
||||
'⇧': 16
|
||||
'⌘': 91
|
||||
'⌥': 18
|
||||
shift: 16
|
||||
alt: 18
|
||||
option: 18
|
||||
control: 17
|
||||
ctrl: 17
|
||||
command: 91
|
||||
cmd: 91
|
||||
|
||||
namedKeys:
|
||||
backspace: 8
|
||||
tab: 9
|
||||
clear: 12
|
||||
enter: 13
|
||||
return: 13
|
||||
esc: 27
|
||||
escape: 27
|
||||
space: 32
|
||||
left: OSX.NSLeftArrowFunctionKey
|
||||
up: OSX.NSUpArrowFunctionKey
|
||||
right: OSX.NSRightArrowFunctionKey
|
||||
down: OSX.NSDownArrowFunctionKey
|
||||
del: 46
|
||||
delete: 46
|
||||
home: 36
|
||||
end: 35
|
||||
pageup: 33
|
||||
pagedown: 34
|
||||
',': 188
|
||||
'.': 190
|
||||
'/': 191
|
||||
'`': 192
|
||||
'-': 189
|
||||
'=': 187
|
||||
';': 186
|
||||
'\'': 222
|
||||
'[': 219
|
||||
']': 221
|
||||
'\\': 220
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports =
|
||||
class Native
|
||||
@alert: (message, detailedMessage, buttons) ->
|
||||
alert: (message, detailedMessage, buttons) ->
|
||||
alert = OSX.NSAlert.alloc.init
|
||||
alert.setMessageText message
|
||||
alert.setInformativeText detailedMessage
|
||||
@@ -13,13 +13,13 @@ class Native
|
||||
return callbacks[buttonTag]()
|
||||
|
||||
# path - Optional. The String path to the file to base it on.
|
||||
@newWindow: (path) ->
|
||||
newWindow: (path) ->
|
||||
controller = OSX.NSApp.createController path
|
||||
controller.window
|
||||
controller.window.makeKeyAndOrderFront null
|
||||
|
||||
# Returns null or a file path.
|
||||
@openPanel: ->
|
||||
openPanel: ->
|
||||
panel = OSX.NSOpenPanel.openPanel
|
||||
panel.setCanChooseDirectories true
|
||||
if panel.runModal isnt OSX.NSFileHandlingPanelOKButton
|
||||
@@ -28,19 +28,18 @@ class Native
|
||||
localStorage.lastOpenedPath = filename
|
||||
filename.toString()
|
||||
|
||||
@openURL: (url) ->
|
||||
openURL: (url) ->
|
||||
window.location = url
|
||||
App = require 'app'
|
||||
App.activeWindow.setTitle _.last url.replace(/\/$/,'').split '/'
|
||||
atom.app.activeWindow.setTitle _.last url.replace(/\/$/,'').split '/'
|
||||
|
||||
# Returns null or a file path.
|
||||
@savePanel: ->
|
||||
savePanel: ->
|
||||
panel = OSX.NSSavePanel.savePanel
|
||||
if panel.runModal isnt OSX.NSFileHandlingPanelOKButton
|
||||
return null
|
||||
panel.filenames.lastObject
|
||||
|
||||
@writeToPasteboard: (text) ->
|
||||
writeToPasteboard: (text) ->
|
||||
pb = OSX.NSPasteboard.generalPasteboard
|
||||
pb.declareTypes_owner [OSX.NSStringPboardType], null
|
||||
pb.setString_forType text, OSX.NSStringPboardType
|
||||
15
src/stdlib/path.coffee
Normal file
15
src/stdlib/path.coffee
Normal file
@@ -0,0 +1,15 @@
|
||||
# node.js path module
|
||||
# http://nodejs.org/docs/v0.6.0/api/path.html
|
||||
|
||||
_ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
# Return the last portion of a path. Similar to the Unix basename command.
|
||||
basename: (filepath) ->
|
||||
_.last filepath.split '/'
|
||||
|
||||
# Return the extension of the path, from the last '.' to end of string in
|
||||
# the last portion of the path. If there is no '.' in the last portion of
|
||||
# the path or the first character of it is '.', then it returns an empty string.
|
||||
extname: (filepath) ->
|
||||
_.last filepath.split '.'
|
||||
@@ -7,6 +7,8 @@ else
|
||||
resourcePath = OSX.NSBundle.mainBundle.resourcePath
|
||||
|
||||
paths = [
|
||||
"#{resourcePath}/src/stdlib"
|
||||
"#{resourcePath}/src/atom"
|
||||
"#{resourcePath}/src"
|
||||
"#{resourcePath}/extensions"
|
||||
"#{resourcePath}/vendor"
|
||||
8
src/stdlib/settings.coffee
Normal file
8
src/stdlib/settings.coffee
Normal file
@@ -0,0 +1,8 @@
|
||||
fs = require 'fs'
|
||||
|
||||
module.exports =
|
||||
class Settings
|
||||
constructor: ->
|
||||
atom.on 'window:load', ->
|
||||
if fs.isFile "~/.atomicity/settings.coffee"
|
||||
require "~/.atomicity/settings.coffee"
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports =
|
||||
class Storage
|
||||
@get: (key, defaultValue) ->
|
||||
get: (key, defaultValue) ->
|
||||
try
|
||||
value = OSX.NSApp.storageGet_defaultValue(key, defaultValue)
|
||||
@toJS value
|
||||
@@ -8,10 +8,10 @@ class Storage
|
||||
error.message += "\nGetting #{key}"
|
||||
console.error(error)
|
||||
|
||||
@set: (key, value) ->
|
||||
set: (key, value) ->
|
||||
OSX.NSApp.storageSet_value key, value
|
||||
|
||||
@toJS: (value) ->
|
||||
toJS: (value) ->
|
||||
if not value
|
||||
value
|
||||
else if value.isKindOfClass OSX.NSDictionary.class
|
||||
@@ -1,126 +0,0 @@
|
||||
Browser = require 'browser'
|
||||
Editor = require 'editor'
|
||||
Extension = require 'extension'
|
||||
Event = require 'event'
|
||||
KeyBinder = require 'key-binder'
|
||||
Native = require 'native'
|
||||
Storage = require 'storage'
|
||||
|
||||
fs = require 'fs'
|
||||
_ = require 'underscore'
|
||||
|
||||
# This a weirdo file. We don't create a Window class, we just add stuff to
|
||||
# the DOM window.
|
||||
windowAdditions =
|
||||
editor: null
|
||||
|
||||
browser: null
|
||||
|
||||
extensions: {}
|
||||
|
||||
appRoot: OSX.NSBundle.mainBundle.resourcePath
|
||||
|
||||
path: null
|
||||
|
||||
startup: () ->
|
||||
KeyBinder.register "window", window
|
||||
|
||||
@path = $atomController.path
|
||||
@setTitle _.last @path.split '/'
|
||||
|
||||
# Remember sizing!
|
||||
defaultFrame = x: 0, y: 0, width: 600, height: 800
|
||||
frame = Storage.get "window.frame.#{@path}", defaultFrame
|
||||
rect = OSX.CGRectMake(frame.x, frame.y, frame.width, frame.height)
|
||||
$atomController.window.setFrame_display rect, true
|
||||
|
||||
@editor = new Editor
|
||||
@browser = new Browser
|
||||
|
||||
@loadExtensions()
|
||||
@loadKeyBindings()
|
||||
@loadSettings()
|
||||
|
||||
@editor.restoreOpenBuffers()
|
||||
|
||||
$atomController.window.makeKeyWindow
|
||||
|
||||
shutdown: ->
|
||||
extension.shutdown() for name, extension of @extensions
|
||||
|
||||
frame = $atomController.window.frame
|
||||
x = frame.origin.x
|
||||
y = frame.origin.y
|
||||
width = frame.size.width
|
||||
height = frame.size.height
|
||||
|
||||
Storage.set "window.frame.#{@path}", {x:x, y:y, width:width, height:height}
|
||||
|
||||
loadExtensions: ->
|
||||
extension.shutdown() for name, extension of @extensions
|
||||
@extensions = {}
|
||||
|
||||
extensionPaths = fs.list require.resourcePath + "/extensions"
|
||||
for extensionPath in extensionPaths when fs.isDirectory extensionPath
|
||||
try
|
||||
extension = require extensionPath
|
||||
@extensions[extension.name] = new extension
|
||||
catch error
|
||||
console.warn "window: Loading Extension '#{fs.base extensionPath}' failed."
|
||||
console.warn error
|
||||
|
||||
# After all the extensions are created, start them up.
|
||||
for name, extension of @extensions
|
||||
try
|
||||
extension.startup()
|
||||
catch error
|
||||
console.warn "window: Extension #{extension.constructor.name} failed to startup."
|
||||
console.warn error
|
||||
|
||||
Event.trigger 'extensions:loaded'
|
||||
|
||||
loadKeyBindings: ->
|
||||
KeyBinder.load "#{@appRoot}/static/key-bindings.coffee"
|
||||
if fs.isFile "~/.atomicity/key-bindings.coffee"
|
||||
KeyBinder.load "~/.atomicity/key-bindings.coffee"
|
||||
|
||||
loadSettings: ->
|
||||
if fs.isFile "~/.atomicity/settings.coffee"
|
||||
require "~/.atomicity/settings.coffee"
|
||||
|
||||
showConsole: ->
|
||||
$atomController.webView.inspector.showConsole true
|
||||
|
||||
setTitle: (title) ->
|
||||
$atomController.window.title = title
|
||||
|
||||
reload: ->
|
||||
@shutdown()
|
||||
$atomController.close
|
||||
OSX.NSApp.createController @path
|
||||
|
||||
open: (path) ->
|
||||
$atomController.window.makeKeyAndOrderFront $atomController
|
||||
Event.trigger 'window:open', path
|
||||
|
||||
close: (path) ->
|
||||
@shutdown()
|
||||
$atomController.close
|
||||
Event.trigger 'window:close', path
|
||||
|
||||
handleKeyEvent: ->
|
||||
KeyBinder.handleEvent.apply KeyBinder, arguments
|
||||
|
||||
triggerEvent: ->
|
||||
Event.trigger arguments...
|
||||
|
||||
canOpen: (path) ->
|
||||
parent = @path.replace(/([^\/])$/, "$1/")
|
||||
child = path.replace(/([^\/])$/, "$1/")
|
||||
|
||||
# If the child is contained by the parent, it can be opened by this window
|
||||
child.match "^" + parent
|
||||
|
||||
for key, value of windowAdditions
|
||||
console.warn "DOMWindow already has a key named `#{key}`" if window[key]
|
||||
window[key] = value
|
||||
64
static/atom.css
Normal file
64
static/atom.css
Normal file
@@ -0,0 +1,64 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Lucida Grande;
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app-horizontal {
|
||||
background-image: url(images/linen.png);
|
||||
display: -webkit-box;
|
||||
min-height: 100%;
|
||||
-webkit-box-orient: horizontal;
|
||||
}
|
||||
|
||||
#app-vertical {
|
||||
display: -webkit-box;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
#main {
|
||||
display: -webkit-box;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.main {
|
||||
-webkit-box-flex: 1;
|
||||
}
|
||||
|
||||
.pane.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pane {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.left {
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
.right {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.top {
|
||||
background-color: purple;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
background-color: blue;
|
||||
}
|
||||
|
||||
.ace_editor {
|
||||
position: relative !important;
|
||||
font: 18px Inconsolata, Monaco, Courier !important;
|
||||
-webkit-box-flex: 1;
|
||||
}
|
||||
BIN
static/images/linen.png
Normal file
BIN
static/images/linen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
2
vendor/ace/css/editor.css
vendored
2
vendor/ace/css/editor.css
vendored
@@ -1,4 +1,4 @@
|
||||
@import url(//fonts.googleapis.com/css?family=Droid+Sans+Mono);
|
||||
/*@import url(//fonts.googleapis.com/css?family=Droid+Sans+Mono);*/
|
||||
|
||||
|
||||
.ace_editor {
|
||||
|
||||
Reference in New Issue
Block a user