diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ab19877b4..2afb8ac19 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,7 @@ in the proper package's repository. * Follow the [CoffeeScript](#coffeescript-styleguide), [JavaScript](https://github.com/styleguide/javascript), and [CSS](https://github.com/styleguide/css) styleguides - * Include thoughtfully worded [Jasmine](http://pivotal.github.com/jasmine/) + * Include thoughtfully worded [Jasmine](http://pivotal.github.com/jasmine) specs * Avoid placing files in `vendor`. 3rd-party packages should be added as a `package.json` dependency. @@ -61,3 +61,32 @@ in the proper package's repository. * Set parameter defaults without spaces around the equal sign * `clear = (count=1) ->` instead of `clear = (count = 1) ->` + +## Documentation Styleguide + +* Use [TomDoc](http://tomdoc.org). +* Use [Markdown](https://daringfireball.net/projects/markdown). +* Reference classes with `{ClassName}` style notation. +* Reference methods with `{ClassName.methodName}` style notation. +* Delegate to comments elsewhere with `{Delegates to: ClassName.methodName}` + style notation. + +### Example + +```coffee +# Public: Disable the package with the given name. +# +# This method emits multiple events: +# +# * `package-will-be-disabled` - before the package is disabled. +# * `package-disabled` - after the package is disabled. +# +# name - The {String} name of the package to disable. +# options - The {Object} with disable options (default: {}): +# :trackTime - `true` to track the amount of time disabling took. +# :ignoreErrors - `true` to catch and ignore errors thrown. +# callback - The {Function} to call after the package has been disabled. +# +# Returns `undefined`. +disablePackage: (name, options, callback) -> +``` diff --git a/build/package.json b/build/package.json index 89b1ab675..c3a6e3811 100644 --- a/build/package.json +++ b/build/package.json @@ -7,7 +7,7 @@ }, "dependencies": { "async": "~0.2.9", - "biscotto": "git://github.com/atom/biscotto.git#12188bfbe5f7303fa9f1aa3c4f8662d40ce3c3be", + "biscotto": "0.6.0", "first-mate": "1.x", "formidable": "~1.0.14", "fs-plus": "1.x", @@ -32,7 +32,6 @@ "runas": "~0.3.0", "underscore-plus": "1.x", "unzip": "~0.1.9", - "vm-compatibility-layer": "~0.1.0", - "walkdir": "0.0.7" + "vm-compatibility-layer": "~0.1.0" } } diff --git a/build/tasks/build-task.coffee b/build/tasks/build-task.coffee index 0e1d47e61..bcbcd47aa 100644 --- a/build/tasks/build-task.coffee +++ b/build/tasks/build-task.coffee @@ -21,7 +21,6 @@ module.exports = (grunt) -> cp 'atom.sh', path.join(appDir, 'atom.sh') cp 'package.json', path.join(appDir, 'package.json') - cp 'apm', path.join(appDir, 'apm') packageDirectories = [] nonPackageDirectories = [ @@ -47,8 +46,12 @@ module.exports = (grunt) -> path.join('less', 'dist') path.join('less', 'test') path.join('bootstrap', 'docs') + path.join('bootstrap', 'examples') path.join('spellchecker', 'vendor') path.join('xmldom', 'test') + path.join('jasmine-reporters', 'ext') + path.join('build', 'Release', 'obj.target') + path.join('build', 'Release', '.deps') path.join('vendor', 'apm') path.join('resources', 'mac') path.join('resources', 'win') @@ -64,6 +67,7 @@ module.exports = (grunt) -> cp 'spec', path.join(appDir, 'spec') cp 'src', path.join(appDir, 'src'), filter: /.+\.(cson|coffee)$/ cp 'static', path.join(appDir, 'static') + cp 'apm', path.join(appDir, 'apm'), filter: nodeModulesFilter if process.platform is 'darwin' grunt.file.recurse path.join('resources', 'mac'), (sourcePath, rootDirectory, subDirectory='', filename) -> diff --git a/build/tasks/docs-task.coffee b/build/tasks/docs-task.coffee index 6f9b3c8b4..53dab4ca7 100644 --- a/build/tasks/docs-task.coffee +++ b/build/tasks/docs-task.coffee @@ -15,24 +15,7 @@ module.exports = (grunt) -> grunt.registerTask 'build-docs', 'Builds the API docs in src', -> done = @async() - downloadFileFromRepo = ({repo, file}, callback) -> - uri = "https://raw2.github.com/atom/#{repo}/master/#{file}" - request uri, (error, response, contents) -> - return callback(error) if error? - downloadPath = path.join('docs', 'includes', repo, file) - fs.writeFile downloadPath, contents, (error) -> - callback(error, downloadPath) - - includes = [ - {repo: 'first-mate', file: 'src/grammar-registry.coffee'} - {repo: 'space-pen', file: 'src/space-pen.coffee'} - {repo: 'text-buffer', file: 'src/marker.coffee'} - {repo: 'text-buffer', file: 'src/point.coffee'} - {repo: 'text-buffer', file: 'src/range.coffee'} - {repo: 'theorist', file: 'src/model.coffee'} - ] - - async.map includes, downloadFileFromRepo, (error, includePaths) -> + downloadIncludes (error, includePaths) -> if error? done(error) else @@ -49,13 +32,32 @@ module.exports = (grunt) -> grunt.registerTask 'lint-docs', 'Generate stats about the doc coverage', -> done = @async() - args = [commonArgs..., '--noOutput', 'src/'] - grunt.util.spawn({cmd, args, opts}, done) + downloadIncludes (error, includePaths) -> + if error? + done(error) + else + args = [ + commonArgs... + '--noOutput' + 'src/' + includePaths... + ] + grunt.util.spawn({cmd, args, opts}, done) grunt.registerTask 'missing-docs', 'Generate stats about the doc coverage', -> done = @async() - args = [commonArgs..., '--noOutput', '--missing', 'src/'] - grunt.util.spawn({cmd, args, opts}, done) + downloadIncludes (error, includePaths) -> + if error? + done(error) + else + args = [ + commonArgs... + '--noOutput' + '--missing' + 'src/' + includePaths... + ] + grunt.util.spawn({cmd, args, opts}, done) grunt.registerTask 'copy-docs', 'Copies over latest API docs to atom-docs', -> done = @async() @@ -129,3 +131,24 @@ module.exports = (grunt) -> grunt.util.spawn({cmd, args, opts}, callback) grunt.util.async.waterfall [fetchTag, stageDocs, fetchSha, commitChanges, pushOrigin, pushHeroku], done + +downloadFileFromRepo = ({repo, file}, callback) -> + uri = "https://raw.github.com/atom/#{repo}/master/#{file}" + request uri, (error, response, contents) -> + return callback(error) if error? + downloadPath = path.join('docs', 'includes', repo, file) + fs.writeFile downloadPath, contents, (error) -> + callback(error, downloadPath) + +downloadIncludes = (callback) -> + includes = [ + {repo: 'first-mate', file: 'src/grammar.coffee'} + {repo: 'first-mate', file: 'src/grammar-registry.coffee'} + {repo: 'space-pen', file: 'src/space-pen.coffee'} + {repo: 'text-buffer', file: 'src/marker.coffee'} + {repo: 'text-buffer', file: 'src/point.coffee'} + {repo: 'text-buffer', file: 'src/range.coffee'} + {repo: 'theorist', file: 'src/model.coffee'} + ] + + async.map(includes, downloadFileFromRepo, callback) diff --git a/build/tasks/task-helpers.coffee b/build/tasks/task-helpers.coffee index f83430884..4b388ce63 100644 --- a/build/tasks/task-helpers.coffee +++ b/build/tasks/task-helpers.coffee @@ -1,27 +1,41 @@ -fs = require 'fs' +fs = require 'fs-plus' path = require 'path' -walkdir = require 'walkdir' module.exports = (grunt) -> cp: (source, destination, {filter}={}) -> unless grunt.file.exists(source) grunt.fatal("Cannot copy non-existent #{source.cyan} to #{destination.cyan}") - try - walkdir.sync source, (sourcePath, stats) -> - return if filter?.test(sourcePath) + copyFile = (sourcePath, destinationPath) -> + return if filter?.test(sourcePath) - destinationPath = path.join(destination, path.relative(source, sourcePath)) - if stats.isSymbolicLink() - grunt.file.mkdir(path.dirname(destinationPath)) - fs.symlinkSync(fs.readlinkSync(sourcePath), destinationPath) - else if stats.isFile() - grunt.file.copy(sourcePath, destinationPath) + stats = fs.lstatSync(sourcePath) + if stats.isSymbolicLink() + grunt.file.mkdir(path.dirname(destinationPath)) + fs.symlinkSync(fs.readlinkSync(sourcePath), destinationPath) + else if stats.isFile() + grunt.file.copy(sourcePath, destinationPath) - if grunt.file.exists(destinationPath) - fs.chmodSync(destinationPath, fs.statSync(sourcePath).mode) - catch error - grunt.fatal(error) + if grunt.file.exists(destinationPath) + fs.chmodSync(destinationPath, fs.statSync(sourcePath).mode) + + if grunt.file.isFile(source) + copyFile(source, destination) + else + try + onFile = (sourcePath) -> + destinationPath = path.join(destination, path.relative(source, sourcePath)) + copyFile(sourcePath, destinationPath) + onDirectory = (sourcePath) -> + if fs.isSymbolicLinkSync(sourcePath) + destinationPath = path.join(destination, path.relative(source, sourcePath)) + copyFile(sourcePath, destinationPath) + false + else + true + fs.traverseTreeSync source, onFile, onDirectory + catch error + grunt.fatal(error) grunt.verbose.writeln("Copied #{source.cyan} to #{destination.cyan}.") diff --git a/docs/converting-a-text-mate-theme.md b/docs/converting-a-text-mate-theme.md new file mode 100644 index 000000000..f28798dd3 --- /dev/null +++ b/docs/converting-a-text-mate-theme.md @@ -0,0 +1,65 @@ +## Converting a TextMate Theme + +This guide will show you how to convert a [TextMate][TextMate] theme to an Atom +theme. + +### Differences + +TextMate themes use [plist][plist] files while Atom themes use [CSS][CSS] or +[LESS][LESS] to style the UI and syntax in the editor. + +The utility that converts the theme first parses the theme's plist file and +then creates comparable CSS rules and properties that will style Atom similarly. + +### Install apm + +The `apm` command line utility that ships with Atom supports converting +a TextMate theme to an Atom theme. + +Check that you have `apm` installed by running the following command in your +terminal: + +```sh +apm help init +``` + +You should see a message print out with details about the `apm init` command. + +If you do not, launch Atom and run the _Atom > Install Shell Commmands_ menu +to install the `apm` and `atom` commands. + +You can now run `apm help init` to see all the options for initializing new +packages and themes. + +### Convert the Theme + +Download the theme you wish to convert, you can browse existing TextMate themes +[here][TextMateThemes]. + +Now, let's say you've downloaded the theme to `~/Downloads/MyTheme.tmTheme`, +you can convert the theme with the following command: + +```sh +apm init --theme ~/.atom/packages/my-theme --convert ~/Downloads/MyTheme.tmTheme +``` + +You can browse to `~/.atom/packages/my-theme` to see the converted theme. + +### Activate the Theme + +Now that your theme is installed to `~/.atom/packages` you can enable it +by launching Atom and selecting the _Atom > Preferences..._ menu. + +Select the _Themes_ link on the left side and choose _My Theme_ from the +__Syntax Theme__ dropdown menu to enable your new theme. + +:tada: Your theme is now enabled, open an editor to see it in action! + +:bulb: Consider using `apm publish` to publish this theme to [atom.io][atomio]. + +[atomio]: https://atom.io +[CSS]: http://en.wikipedia.org/wiki/Cascading_Style_Sheets +[LESS]: http://lesscss.org +[plist]: http://en.wikipedia.org/wiki/Property_list +[TextMate]: http://macromates.com +[TextMateThemes]: http://wiki.macromates.com/Themes/UserSubmittedThemes diff --git a/docs/converting-a-textmate-bundle.md b/docs/converting-a-textmate-bundle.md new file mode 100644 index 000000000..ae7183e2b --- /dev/null +++ b/docs/converting-a-textmate-bundle.md @@ -0,0 +1,50 @@ +## Converting a TextMate Bundle + +This guide will show you how to convert a [TextMate][TextMate] bundle to an +Atom package. + +Converting a TextMate bundle will allow you to use its editor preferences, +snippets, and colorization inside Atom. + +### Install apm + +The `apm` command line utility that ships with Atom supports converting +a TextMate bundle to an Atom package. + +Check that you have `apm` installed by running the following command in your +terminal: + +```sh +apm help init +``` + +You should see a message print out with details about the `apm init` command. + +If you do not, launch Atom and run the _Atom > Install Shell Commmands_ menu +to install the `apm` and `atom` commands. + +### Convert the Package + +Let's convert the TextMate bundle for the [R][R] programming language. You can find other existing TextMate bundles [here][TextMateOrg]. + +You can convert the R bundle with the following command: + +```sh +apm init --package ~/.atom/packages/language-r --convert https://github.com/textmate/r.tmbundle +``` + +You can now browse to `~/.atom/packages/language-r` to see the converted bundle. + +:tada: Your new package is now ready to use, launch Atom and open a `.r` file in +the editor to see it in action! + +:bulb: Consider using `apm publish` to publish this package to +[atom.io][atomio]. + +[atomio]: https://atom.io +[CSS]: http://en.wikipedia.org/wiki/Cascading_Style_Sheets +[LESS]: http://lesscss.org +[plist]: http://en.wikipedia.org/wiki/Property_list +[R]: http://en.wikipedia.org/wiki/R_(programming_language) +[TextMate]: http://macromates.com +[TextMateOrg]: https://github.com/textmate/r.tmbundle diff --git a/docs/customizing-atom.md b/docs/customizing-atom.md index e6ad89305..49d196e50 100644 --- a/docs/customizing-atom.md +++ b/docs/customizing-atom.md @@ -112,14 +112,16 @@ namespaces: `core` and `editor`. ### Quick Personal Hacks -### user.coffee +### init.coffee -When Atom finishes loading, it will evaluate _user.coffee_ in your _~/.atom_ +When Atom finishes loading, it will evaluate _init.coffee_ in your _~/.atom_ directory, giving you a chance to run arbitrary personal CoffeeScript code to make customizations. You have full access to Atom's API from code in this file. If customizations become extensive, consider [creating a package][create-a-package]. +This file can also be named _init.js_ and contain JavaScript code. + ### styles.css If you want to apply quick-and-dirty personal styling changes without creating diff --git a/docs/proposals/atom-docs.md b/docs/proposals/atom-docs.md deleted file mode 100644 index f3b6a0ec9..000000000 --- a/docs/proposals/atom-docs.md +++ /dev/null @@ -1,63 +0,0 @@ -## Atom Documentation Format - -This document describes our documentation format, which is markdown with -a few rules. - -### Philosophy - -1. Method and argument names **should** clearly communicate its use. -1. Use documentation to enhance and not correct method/argument names. - -#### Basic - -In some cases all that's required is a single line. **Do not** feel -obligated to write more because we have a format. - -```markdown -# Private: Returns the number of pixels from the top of the screen. -``` - -* **Each method should declare whether it's public or private by using `Public:` -or `Private:`** prefix. -* Following the colon, there should be a short description (that isn't redundant with the -method name). -* Documentation should be hard wrapped to 80 columns. - -### Public vs Private - -If a method is public it can be used by other classes (and possibly by -the public API). The appropriate steps should be taken to minimize the impact -when changing public methods. In some cases that might mean adding an -appropriate release note. In other cases it might mean doing the legwork to -ensure all affected packages are updated. - -#### Complex - -For complex methods it's necessary to explain exactly what arguments -are required and how different inputs effect the operation of the -function. - -The idea is to communicate things that the API user might not know about, -so repeating information that can be gleaned from the method or argument names -is not useful. - -```markdown -# Private: Determine the accelerator for a given command. -# -# * command: -# The name of the command. -# * keystrokesByCommand: -# An {Object} whose keys are commands and the values are Arrays containing -# the keystrokes. -# * options: -# + accelerators: -# Boolean to determine whether accelerators should be shown. -# -# Returns a String containing the keystroke in a format that can be interpreted -# by atom shell to provide nice icons where available. -# -# Raises an Exception if no window is available. -``` - -* Use curly brackets `{}` to provide links to other classes. -* Use `+` for the options list. diff --git a/docs/your-first-package.md b/docs/your-first-package.md index c1e0b991d..43222c4cc 100644 --- a/docs/your-first-package.md +++ b/docs/your-first-package.md @@ -5,6 +5,7 @@ selected text with [ascii art](http://en.wikipedia.org/wiki/ASCII_art). When you run our new command with the word "cool" selected, it will be replaced with: ``` + ___ /\_ \ ___ ___ ___\//\ \ /'___\ / __`\ / __`\\ \ \ @@ -25,13 +26,13 @@ Atom will open a new window with the contents of our new _ascii-art_ package displayed in the Tree View. Because this window is opened **after** the package is created, the ASCII Art package will be loaded and available in our new window. To verify this, toggle the Command Palette (`cmd-shift-P`) and type -"ASCII Art" you'll see a new `ASCII Art: Toggle` command. When triggered, this +"ASCII Art". You'll see a new `ASCII Art: Toggle` command. When triggered, this command displays a default message. -Now let's edit the package files to make our ascii art package do something +Now let's edit the package files to make our ASCII Art package do something interesting. Since this package doesn't need any UI, we can remove all view-related code. Start by opening up _lib/ascii-art.coffee_. Remove all view -code, so the file looks like this: +code, so the `module.exports` section looks like this: ```coffeescript module.exports = @@ -69,23 +70,24 @@ command palette or by pressing `ctrl-alt-cmd-l`. ## Trigger the Command Now open the command panel and search for the `ascii-art:convert` command. But -its not there! To fix this open _package.json_ and find the property called -`activationEvents`. Activation Events speed up load time by allowing an Atom to -delay a package's activation until it's needed. So add the `ascii-art:convert` -to the activationEvents array: +it's not there! To fix this, open _package.json_ and find the property called +`activationEvents`. Activation Events speed up load time by allowing Atom to +delay a package's activation until it's needed. So remove the existing command +and add `ascii-art:convert` to the `activationEvents` array: ```json "activationEvents": ["ascii-art:convert"], ``` -First, run reload the window by running the command `window:reload`. Now when -you run the `ascii-art:convert` command it will output 'Hello, World!' +First, reload the window by running the command `window:reload`. Now when you +run the `ascii-art:convert` command it will output 'Hello, World!' -## Add A Key Binding +## Add a Key Binding Now let's add a key binding to trigger the `ascii-art:convert` command. Open _keymaps/ascii-art.cson_ and add a key binding linking `ctrl-alt-a` to the -`ascii-art:convert` command. When finished, the file will look like this: +`ascii-art:convert` command. You can delete the pre-existing key binding since +you don't need it anymore. When finished, the file will look like this: ```coffeescript '.editor': @@ -105,7 +107,7 @@ that it **doesn't** work when the Tree View is focused. ## Add the ASCII Art -Now we need to convert the selected text to ascii art. To do this we will use +Now we need to convert the selected text to ASCII art. To do this we will use the [figlet](https://npmjs.org/package/figlet) [node](http://nodejs.org/) module from [npm](https://npmjs.org/). Open _package.json_ and add the latest version of figlet to the dependencies: @@ -116,14 +118,14 @@ figlet to the dependencies: } ``` -After saving the file run the command 'update-package-dependencies:update' from -the Command Palette. This will install the packages node module dependencies, +After saving the file, run the command 'update-package-dependencies:update' from +the Command Palette. This will install the package's node module dependencies, only figlet in this case. You will need to run 'update-package-dependencies:update' whenever you update the dependencies field in your _package.json_ file. Now require the figlet node module in _lib/ascii-art.coffee_ and instead of -inserting 'Hello, World!' convert the selected text to ascii art! +inserting 'Hello, World!' convert the selected text to ASCII art. ```coffeescript convert: -> @@ -139,6 +141,9 @@ convert: -> selection.insertText("\n#{asciiArt}\n") ``` +Select some text in an editor window and hit `cmd-alt-a`. :tada: You're now an +ASCII art professional! + ## Further reading For more information on the mechanics of packages, check out [Creating a diff --git a/dot-atom/init.coffee b/dot-atom/init.coffee new file mode 100644 index 000000000..4d10e775b --- /dev/null +++ b/dot-atom/init.coffee @@ -0,0 +1,14 @@ +# Your init script +# +# Atom will evaluate this file each time a new window is opened. It is run +# after packages are loaded/activated and after the previous editor state +# has been restored. +# +# An example hack to make opened Markdown files always be soft wrapped: +# +# path = require 'path' +# +# atom.workspaceView.eachEditorView (editorView) -> +# editor = editorView.getEditor() +# if path.extname(editor.getPath()) is '.md' +# editor.setSoftWrap(true) diff --git a/dot-atom/keymap.cson b/dot-atom/keymap.cson index 872395168..bce80b757 100644 --- a/dot-atom/keymap.cson +++ b/dot-atom/keymap.cson @@ -1,9 +1,13 @@ -# User keymap +# Your keymap # # Atom keymaps work similarly to stylesheets. Just as stylesheets use selectors # to apply styles to elements, Atom keymaps use selectors to associate -# keystrokes with events in specific contexts. Here's a small example, excerpted -# from Atom's built-in keymaps: +# keystrokes with events in specific contexts. +# +# You can create a new keybinding in this file by typing "key" and then hitting +# tab. +# +# Here's an example taken from Atom's built-in keymap: # # '.editor': # 'enter': 'editor:newline' @@ -11,3 +15,4 @@ # 'body': # 'ctrl-P': 'core:move-up' # 'ctrl-p': 'core:move-down' +# diff --git a/dot-atom/snippets.cson b/dot-atom/snippets.cson index e9d644de1..957899361 100644 --- a/dot-atom/snippets.cson +++ b/dot-atom/snippets.cson @@ -3,7 +3,7 @@ # Atom snippets allow you to enter a simple prefix in the editor and hit tab to # expand the prefix into a larger code block with templated values. # -# You can create a new snippet in this file by typing `snip` and then hitting +# You can create a new snippet in this file by typing "snip" and then hitting # tab. # # An example CoffeeScript snippet to expand log to console.log: diff --git a/dot-atom/user.coffee b/dot-atom/user.coffee deleted file mode 100644 index 60880641d..000000000 --- a/dot-atom/user.coffee +++ /dev/null @@ -1 +0,0 @@ -# For more on how to configure atom open `~/github/atom/docs/configuring-and-extending.md` diff --git a/exports/atom.coffee b/exports/atom.coffee index f4fd1053a..ca48423f8 100644 --- a/exports/atom.coffee +++ b/exports/atom.coffee @@ -4,7 +4,6 @@ module.exports = _: require 'underscore-plus' BufferedNodeProcess: require '../src/buffered-node-process' BufferedProcess: require '../src/buffered-process' - ConfigObserver: require '../src/config-observer' Directory: require '../src/directory' File: require '../src/file' fs: require 'fs-plus' diff --git a/keymaps/darwin.cson b/keymaps/darwin.cson index a112bfbf7..c93079efa 100644 --- a/keymaps/darwin.cson +++ b/keymaps/darwin.cson @@ -75,8 +75,12 @@ 'cmd-k right': 'pane:split-right' # Atom Specific 'cmd-k cmd-w': 'pane:close' # Atom Specific 'cmd-k alt-cmd-w': 'pane:close-other-items' # Atom Specific - 'cmd-k cmd-left': 'window:focus-previous-pane' - 'cmd-k cmd-right': 'window:focus-next-pane' + 'cmd-k cmd-p': 'window:focus-previous-pane' + 'cmd-k cmd-n': 'window:focus-next-pane' + 'cmd-k cmd-up': 'window:focus-pane-above' + 'cmd-k cmd-down': 'window:focus-pane-below' + 'cmd-k cmd-left': 'window:focus-pane-on-left' + 'cmd-k cmd-right': 'window:focus-pane-on-right' 'cmd-1': 'pane:show-item-1' 'cmd-2': 'pane:show-item-2' 'cmd-3': 'pane:show-item-3' diff --git a/keymaps/win32.cson b/keymaps/win32.cson index 3b6a230a5..9c78e3622 100644 --- a/keymaps/win32.cson +++ b/keymaps/win32.cson @@ -47,8 +47,12 @@ 'ctrl-k right': 'pane:split-right' # Atom Specific 'ctrl-k ctrl-w': 'pane:close' # Atom Specific 'ctrl-k alt-ctrl-w': 'pane:close-other-items' # Atom Specific - 'ctrl-k ctrl-left': 'window:focus-previous-pane' - 'ctrl-k ctrl-right': 'window:focus-next-pane' + 'ctrl-k ctrl-p': 'window:focus-previous-pane' + 'ctrl-k ctrl-n': 'window:focus-next-pane' + 'ctrl-k ctrl-up': 'window:focus-pane-above' + 'ctrl-k ctrl-down': 'window:focus-pane-below' + 'ctrl-k ctrl-left': 'window:focus-pane-on-left' + 'ctrl-k ctrl-right': 'window:focus-pane-on-right' '.workspace .editor': # Windows specific diff --git a/menus/darwin.cson b/menus/darwin.cson index 44e88dead..4ac239e8a 100644 --- a/menus/darwin.cson +++ b/menus/darwin.cson @@ -4,10 +4,12 @@ submenu: [ { label: 'About Atom', command: 'application:about' } { label: "VERSION", enabled: false } - { label: "Install update", command: 'application:install-update', visible: false } + { label: "Restart and Install Update", command: 'application:install-update', visible: false} + { label: "Check for Update", command: 'application:check-for-update'} { type: 'separator' } { label: 'Preferences...', command: 'application:show-settings' } { label: 'Open Your Config', command: 'application:open-your-config' } + { label: 'Open Your Init Script', command: 'application:open-your-init-script' } { label: 'Open Your Keymap', command: 'application:open-your-keymap' } { label: 'Open Your Snippets', command: 'application:open-your-snippets' } { label: 'Open Your Stylesheet', command: 'application:open-your-stylesheet' } diff --git a/package.json b/package.json index b11f3f1ed..4e17fa78d 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "url": "http://github.com/atom/atom/raw/master/LICENSE.md" } ], - "atomShellVersion": "0.8.7", + "atomShellVersion": "0.9.1", "dependencies": { "async": "0.2.6", "bootstrap": "git://github.com/atom/bootstrap.git#6af81906189f1747fd6c93479e3d998ebe041372", @@ -25,11 +25,11 @@ "coffeestack": "0.7.0", "delegato": "1.x", "emissary": "1.x", - "first-mate": ">=1.1 <2.0", + "first-mate": ">=1.1.4 <2.0", "fs-plus": "1.x", "fstream": "0.1.24", "fuzzaldrin": "1.x", - "git-utils": "0.34.0", + "git-utils": "1.x", "guid": "0.0.10", "jasmine-tagged": "1.x", "mkdirp": "0.3.5", @@ -42,14 +42,14 @@ "pathwatcher": "0.14.2", "pegjs": "0.8.0", "property-accessors": "1.x", - "q": "0.9.7", - "scandal": "0.13.0", + "q": "1.0.x", + "scandal": "0.14.0", "season": "1.x", "semver": "1.1.4", "serializable": "1.x", "space-pen": "3.1.1", "temp": "0.5.0", - "text-buffer": "0.16.0", + "text-buffer": "1.x", "theorist": "1.x", "underscore-plus": "1.x", "vm-compatibility-layer": "0.1.0" @@ -63,81 +63,81 @@ "solarized-dark-syntax": "0.9.0", "solarized-light-syntax": "0.5.0", "archive-view": "0.21.0", - "autocomplete": "0.21.0", - "autoflow": "0.12.0", + "autocomplete": "0.22.0", + "autoflow": "0.13.0", "autosave": "0.10.0", "background-tips": "0.5.0", "bookmarks": "0.18.0", - "bracket-matcher": "0.19.0", - "command-logger": "0.10.0", - "command-palette": "0.15.0", - "dev-live-reload": "0.23.0", - "editor-stats": "0.12.0", + "bracket-matcher": "0.20.0", + "command-logger": "0.11.0", + "command-palette": "0.16.0", + "dev-live-reload": "0.24.0", + "editor-stats": "0.13.0", "exception-reporting": "0.13.0", - "feedback": "0.22.0", + "feedback": "0.23.0", "find-and-replace": "0.81.0", - "fuzzy-finder": "0.32.0", - "gists": "0.16.0", - "git-diff": "0.23.0", + "fuzzy-finder": "0.34.0", + "gists": "0.17.0", + "git-diff": "0.24.0", "github-sign-in": "0.18.0", "go-to-line": "0.16.0", - "grammar-selector": "0.18.0", - "image-view": "0.18.0", + "grammar-selector": "0.19.0", + "image-view": "0.19.0", "keybinding-resolver": "0.9.0", - "link": "0.15.0", + "link": "0.16.0", "markdown-preview": "0.25.1", - "metrics": "0.24.0", - "package-generator": "0.25.0", - "release-notes": "0.17.0", - "settings-view": "0.65.0", - "snippets": "0.24.0", - "spell-check": "0.21.0", + "metrics": "0.25.0", + "package-generator": "0.26.0", + "release-notes": "0.18.0", + "settings-view": "0.71.0", + "snippets": "0.27.0", + "spell-check": "0.23.0", "status-bar": "0.32.0", - "styleguide": "0.22.0", - "symbols-view": "0.30.0", - "tabs": "0.18.0", + "styleguide": "0.23.0", + "symbols-view": "0.33.0", + "tabs": "0.19.0", "terminal": "0.27.0", "timecop": "0.13.0", "to-the-hubs": "0.19.0", - "tree-view": "0.67.0", + "tree-view": "0.69.0", "update-package-dependencies": "0.2.0", "visual-bell": "0.6.0", "welcome": "0.4.0", - "whitespace": "0.10.0", - "wrap-guide": "0.12.0", - "language-c": "0.2.0", + "whitespace": "0.11.0", + "wrap-guide": "0.14.0", + "language-c": "0.4.0", "language-clojure": "0.1.0", - "language-coffee-script": "0.6.0", - "language-css": "0.2.0", - "language-gfm": "0.12.0", - "language-git": "0.3.0", - "language-go": "0.2.0", - "language-html": "0.2.0", + "language-coffee-script": "0.7.0", + "language-css": "0.3.0", + "language-gfm": "0.13.0", + "language-git": "0.4.0", + "language-go": "0.3.0", + "language-html": "0.3.0", "language-hyperlink": "0.3.0", - "language-java": "0.2.0", - "language-javascript": "0.5.0", - "language-json": "0.2.0", - "language-less": "0.1.0", - "language-make": "0.1.0", - "language-mustache": "0.1.0", - "language-objective-c": "0.2.0", - "language-pegjs": "0.1.0", - "language-perl": "0.2.0", - "language-php": "0.3.0", - "language-property-list": "0.2.0", - "language-puppet": "0.2.0", - "language-python": "0.2.0", - "language-ruby": "0.8.0", - "language-ruby-on-rails": "0.4.0", - "language-sass": "0.3.0", - "language-shellscript": "0.2.0", - "language-source": "0.2.0", - "language-sql": "0.2.0", - "language-text": "0.2.0", - "language-todo": "0.2.0", + "language-java": "0.3.0", + "language-javascript": "0.6.0", + "language-json": "0.3.0", + "language-less": "0.2.0", + "language-make": "0.2.0", + "language-mustache": "0.2.0", + "language-objective-c": "0.3.0", + "language-pegjs": "0.2.0", + "language-perl": "0.3.0", + "language-php": "0.4.0", + "language-property-list": "0.3.0", + "language-puppet": "0.3.0", + "language-python": "0.3.0", + "language-ruby": "0.9.0", + "language-ruby-on-rails": "0.5.0", + "language-sass": "0.4.0", + "language-shellscript": "0.3.0", + "language-source": "0.3.0", + "language-sql": "0.3.0", + "language-text": "0.3.0", + "language-todo": "0.3.0", "language-toml": "0.7.0", - "language-xml": "0.2.0", - "language-yaml": "0.1.0" + "language-xml": "0.3.0", + "language-yaml": "0.2.0" }, "private": true, "scripts": { diff --git a/spec/atom-reporter.coffee b/spec/atom-reporter.coffee index c9b5e1bd2..6133ef327 100644 --- a/spec/atom-reporter.coffee +++ b/spec/atom-reporter.coffee @@ -6,8 +6,7 @@ sourceMaps = {} formatStackTrace = (stackTrace) -> return stackTrace unless stackTrace - jasminePath = require.resolve('../vendor/jasmine') - jasminePattern = new RegExp("\\(#{_.escapeRegExp(jasminePath)}:\\d+:\\d+\\)\\s*$") + jasminePattern = /^\s*at\s+.*\(?.*\/jasmine(-[^\/]*)?\.js:\d+:\d+\)?\s*$/ convertedLines = [] for line in stackTrace.split('\n') convertedLines.push(line) unless jasminePattern.test(line) diff --git a/spec/atom-spec.coffee b/spec/atom-spec.coffee index 6a3becd99..eacd3ff60 100644 --- a/spec/atom-spec.coffee +++ b/spec/atom-spec.coffee @@ -1,6 +1,7 @@ {$, $$, fs, WorkspaceView} = require 'atom' Exec = require('child_process').exec path = require 'path' +AtomPackage = require '../src/atom-package' ThemeManager = require '../src/theme-manager' describe "the `atom` global", -> @@ -21,12 +22,16 @@ describe "the `atom` global", -> describe ".unloadPackage(name)", -> describe "when the package is active", -> it "throws an error", -> - pack = atom.packages.activatePackage('package-with-main') - expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy() - expect(atom.packages.isPackageActive(pack.name)).toBeTruthy() - expect( -> atom.packages.unloadPackage(pack.name)).toThrow() - expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy() - expect(atom.packages.isPackageActive(pack.name)).toBeTruthy() + pack = null + waitsForPromise -> + atom.packages.activatePackage('package-with-main').then (p) -> pack = p + + runs -> + expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy() + expect(atom.packages.isPackageActive(pack.name)).toBeTruthy() + expect( -> atom.packages.unloadPackage(pack.name)).toThrow() + expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy() + expect(atom.packages.isPackageActive(pack.name)).toBeTruthy() describe "when the package is not loaded", -> it "throws an error", -> @@ -43,22 +48,42 @@ describe "the `atom` global", -> describe ".activatePackage(id)", -> describe "atom packages", -> + describe "when called multiple times", -> + it "it only calls activate on the package once", -> + spyOn(AtomPackage.prototype, 'activateNow').andCallThrough() + atom.packages.activatePackage('package-with-index') + atom.packages.activatePackage('package-with-index') + + waitsForPromise -> + atom.packages.activatePackage('package-with-index') + + runs -> + expect(AtomPackage.prototype.activateNow.callCount).toBe 1 + describe "when the package has a main module", -> describe "when the metadata specifies a main module path˜", -> it "requires the module at the specified path", -> mainModule = require('./fixtures/packages/package-with-main/main-module') spyOn(mainModule, 'activate') - pack = atom.packages.activatePackage('package-with-main') - expect(mainModule.activate).toHaveBeenCalled() - expect(pack.mainModule).toBe mainModule + pack = null + waitsForPromise -> + atom.packages.activatePackage('package-with-main').then (p) -> pack = p + + runs -> + expect(mainModule.activate).toHaveBeenCalled() + expect(pack.mainModule).toBe mainModule describe "when the metadata does not specify a main module", -> it "requires index.coffee", -> indexModule = require('./fixtures/packages/package-with-index/index') spyOn(indexModule, 'activate') - pack = atom.packages.activatePackage('package-with-index') - expect(indexModule.activate).toHaveBeenCalled() - expect(pack.mainModule).toBe indexModule + pack = null + waitsForPromise -> + atom.packages.activatePackage('package-with-index').then (p) -> pack = p + + runs -> + expect(indexModule.activate).toHaveBeenCalled() + expect(pack.mainModule).toBe indexModule it "assigns config defaults from the module", -> expect(atom.config.get('package-with-config-defaults.numbers.one')).toBeUndefined() @@ -67,20 +92,22 @@ describe "the `atom` global", -> expect(atom.config.get('package-with-config-defaults.numbers.two')).toBe 2 describe "when the package metadata includes activation events", -> - [mainModule, pack] = [] + [mainModule, promise] = [] beforeEach -> mainModule = require './fixtures/packages/package-with-activation-events/index' spyOn(mainModule, 'activate').andCallThrough() AtomPackage = require '../src/atom-package' spyOn(AtomPackage.prototype, 'requireMainModule').andCallThrough() - pack = atom.packages.activatePackage('package-with-activation-events') + + promise = atom.packages.activatePackage('package-with-activation-events') it "defers requiring/activating the main module until an activation event bubbles to the root view", -> - expect(pack.requireMainModule).not.toHaveBeenCalled() - expect(mainModule.activate).not.toHaveBeenCalled() + expect(promise.isFulfilled()).not.toBeTruthy() atom.workspaceView.trigger 'activation-event' - expect(mainModule.activate).toHaveBeenCalled() + + waitsForPromise -> + promise it "triggers the activation event on all handlers registered during activation", -> atom.workspaceView.openSync() @@ -105,13 +132,17 @@ describe "the `atom` global", -> expect(console.warn).not.toHaveBeenCalled() it "passes the activate method the package's previously serialized state if it exists", -> - pack = atom.packages.activatePackage("package-with-serialization") - expect(pack.mainModule.someNumber).not.toBe 77 - pack.mainModule.someNumber = 77 - atom.packages.deactivatePackage("package-with-serialization") - spyOn(pack.mainModule, 'activate').andCallThrough() - atom.packages.activatePackage("package-with-serialization") - expect(pack.mainModule.activate).toHaveBeenCalledWith({someNumber: 77}) + pack = null + waitsForPromise -> + atom.packages.activatePackage("package-with-serialization").then (p) -> pack = p + + runs -> + expect(pack.mainModule.someNumber).not.toBe 77 + pack.mainModule.someNumber = 77 + atom.packages.deactivatePackage("package-with-serialization") + spyOn(pack.mainModule, 'activate').andCallThrough() + atom.packages.activatePackage("package-with-serialization") + expect(pack.mainModule.activate).toHaveBeenCalledWith({someNumber: 77}) it "logs warning instead of throwing an exception if the package fails to load", -> atom.config.set("core.disabledPackages", []) @@ -234,29 +265,38 @@ describe "the `atom` global", -> describe "scoped-property loading", -> it "loads the scoped properties", -> - atom.packages.activatePackage("package-with-scoped-properties") - expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a' + waitsForPromise -> + atom.packages.activatePackage("package-with-scoped-properties") + + runs -> + expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a' describe "textmate packages", -> it "loads the package's grammars", -> expect(atom.syntax.selectGrammar("file.rb").name).toBe "Null Grammar" - atom.packages.activatePackage('language-ruby', sync: true) - expect(atom.syntax.selectGrammar("file.rb").name).toBe "Ruby" + + waitsForPromise -> + atom.packages.activatePackage('language-ruby') + + runs -> + expect(atom.syntax.selectGrammar("file.rb").name).toBe "Ruby" it "translates the package's scoped properties to Atom terms", -> expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBeUndefined() - atom.packages.activatePackage('language-ruby', sync: true) - expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBe '# ' + + waitsForPromise -> + atom.packages.activatePackage('language-ruby') + + runs -> + expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBe '# ' describe "when the package has no grammars but does have preferences", -> it "loads the package's preferences as scoped properties", -> jasmine.unspy(window, 'setTimeout') spyOn(atom.syntax, 'addProperties').andCallThrough() - atom.packages.activatePackage('package-with-preferences-tmbundle') - - waitsFor -> - atom.syntax.addProperties.callCount > 0 + waitsForPromise -> + atom.packages.activatePackage('package-with-preferences-tmbundle') runs -> expect(atom.syntax.getProperty(['.source.pref'], 'editor.increaseIndentPattern')).toBe '^abc$' @@ -264,30 +304,43 @@ describe "the `atom` global", -> describe ".deactivatePackage(id)", -> describe "atom packages", -> it "calls `deactivate` on the package's main module if activate was successful", -> - pack = atom.packages.activatePackage("package-with-deactivate") - expect(atom.packages.isPackageActive("package-with-deactivate")).toBeTruthy() - spyOn(pack.mainModule, 'deactivate').andCallThrough() + pack = null + waitsForPromise -> + atom.packages.activatePackage("package-with-deactivate").then (p) -> pack = p - atom.packages.deactivatePackage("package-with-deactivate") - expect(pack.mainModule.deactivate).toHaveBeenCalled() - expect(atom.packages.isPackageActive("package-with-module")).toBeFalsy() + runs -> + expect(atom.packages.isPackageActive("package-with-deactivate")).toBeTruthy() + spyOn(pack.mainModule, 'deactivate').andCallThrough() - spyOn(console, 'warn') - badPack = atom.packages.activatePackage("package-that-throws-on-activate") - expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeTruthy() - spyOn(badPack.mainModule, 'deactivate').andCallThrough() + atom.packages.deactivatePackage("package-with-deactivate") + expect(pack.mainModule.deactivate).toHaveBeenCalled() + expect(atom.packages.isPackageActive("package-with-module")).toBeFalsy() - atom.packages.deactivatePackage("package-that-throws-on-activate") - expect(badPack.mainModule.deactivate).not.toHaveBeenCalled() - expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeFalsy() + spyOn(console, 'warn') + + badPack = null + waitsForPromise -> + atom.packages.activatePackage("package-that-throws-on-activate").then (p) -> badPack = p + + runs -> + expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeTruthy() + spyOn(badPack.mainModule, 'deactivate').andCallThrough() + + atom.packages.deactivatePackage("package-that-throws-on-activate") + expect(badPack.mainModule.deactivate).not.toHaveBeenCalled() + expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeFalsy() it "does not serialize packages that have not been activated called on their main module", -> spyOn(console, 'warn') - badPack = atom.packages.activatePackage("package-that-throws-on-activate") - spyOn(badPack.mainModule, 'serialize').andCallThrough() + badPack = null + waitsForPromise -> + atom.packages.activatePackage("package-that-throws-on-activate").then (p) -> badPack = p - atom.packages.deactivatePackage("package-that-throws-on-activate") - expect(badPack.mainModule.serialize).not.toHaveBeenCalled() + runs -> + spyOn(badPack.mainModule, 'serialize').andCallThrough() + + atom.packages.deactivatePackage("package-that-throws-on-activate") + expect(badPack.mainModule.serialize).not.toHaveBeenCalled() it "absorbs exceptions that are thrown by the package module's serialize methods", -> spyOn(console, 'error') @@ -299,32 +352,44 @@ describe "the `atom` global", -> expect(console.error).toHaveBeenCalled() it "removes the package's grammars", -> - atom.packages.activatePackage('package-with-grammars') - atom.packages.deactivatePackage('package-with-grammars') - expect(atom.syntax.selectGrammar('a.alot').name).toBe 'Null Grammar' - expect(atom.syntax.selectGrammar('a.alittle').name).toBe 'Null Grammar' + waitsForPromise -> + atom.packages.activatePackage('package-with-grammars') + + runs -> + atom.packages.deactivatePackage('package-with-grammars') + expect(atom.syntax.selectGrammar('a.alot').name).toBe 'Null Grammar' + expect(atom.syntax.selectGrammar('a.alittle').name).toBe 'Null Grammar' it "removes the package's keymaps", -> - atom.packages.activatePackage('package-with-keymaps') - atom.packages.deactivatePackage('package-with-keymaps') - expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-1')).toHaveLength 0 - expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-2')).toHaveLength 0 + waitsForPromise -> + atom.packages.activatePackage('package-with-keymaps') + + runs -> + atom.packages.deactivatePackage('package-with-keymaps') + expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-1')).toHaveLength 0 + expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-2')).toHaveLength 0 it "removes the package's stylesheets", -> - atom.packages.activatePackage('package-with-stylesheets') - atom.packages.deactivatePackage('package-with-stylesheets') - one = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/1.css") - two = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/2.less") - three = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/3.css") - expect(atom.themes.stylesheetElementForId(one)).not.toExist() - expect(atom.themes.stylesheetElementForId(two)).not.toExist() - expect(atom.themes.stylesheetElementForId(three)).not.toExist() + waitsForPromise -> + atom.packages.activatePackage('package-with-stylesheets') + + runs -> + atom.packages.deactivatePackage('package-with-stylesheets') + one = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/1.css") + two = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/2.less") + three = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/3.css") + expect(atom.themes.stylesheetElementForId(one)).not.toExist() + expect(atom.themes.stylesheetElementForId(two)).not.toExist() + expect(atom.themes.stylesheetElementForId(three)).not.toExist() it "removes the package's scoped-properties", -> - atom.packages.activatePackage("package-with-scoped-properties") - expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a' - atom.packages.deactivatePackage("package-with-scoped-properties") - expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBeUndefined() + waitsForPromise -> + atom.packages.activatePackage("package-with-scoped-properties") + + runs -> + expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBe '^a' + atom.packages.deactivatePackage("package-with-scoped-properties") + expect(atom.syntax.getProperty ['.source.omg'], 'editor.increaseIndentPattern').toBeUndefined() describe "textmate packages", -> it "removes the package's grammars", -> @@ -371,7 +436,7 @@ describe "the `atom` global", -> themes = themeActivator.mostRecentCall.args[0] expect(['theme']).toContain(theme.getType()) for theme in themes - describe ".en/disablePackage()", -> + describe ".enablePackage() and disablePackage()", -> describe "with packages", -> it ".enablePackage() enables a disabled package", -> packageName = 'package-with-main' @@ -380,28 +445,36 @@ describe "the `atom` global", -> expect(atom.config.get('core.disabledPackages')).toContain packageName pack = atom.packages.enablePackage(packageName) - loadedPackages = atom.packages.getLoadedPackages() - activatedPackages = atom.packages.getActivePackages() - expect(loadedPackages).toContain(pack) - expect(activatedPackages).toContain(pack) - expect(atom.config.get('core.disabledPackages')).not.toContain packageName + activatedPackages = null + waitsFor -> + activatedPackages = atom.packages.getActivePackages() + activatedPackages.length > 0 + + runs -> + expect(loadedPackages).toContain(pack) + expect(activatedPackages).toContain(pack) + expect(atom.config.get('core.disabledPackages')).not.toContain packageName it ".disablePackage() disables an enabled package", -> packageName = 'package-with-main' - atom.packages.activatePackage(packageName) - atom.packages.observeDisabledPackages() - expect(atom.config.get('core.disabledPackages')).not.toContain packageName + waitsForPromise -> + atom.packages.activatePackage(packageName) - pack = atom.packages.disablePackage(packageName) + runs -> + atom.packages.observeDisabledPackages() + expect(atom.config.get('core.disabledPackages')).not.toContain packageName - activatedPackages = atom.packages.getActivePackages() - expect(activatedPackages).not.toContain(pack) - expect(atom.config.get('core.disabledPackages')).toContain packageName + pack = atom.packages.disablePackage(packageName) + + activatedPackages = atom.packages.getActivePackages() + expect(activatedPackages).not.toContain(pack) + expect(atom.config.get('core.disabledPackages')).toContain packageName describe "with themes", -> beforeEach -> - atom.themes.activateThemes() + waitsForPromise -> + atom.themes.activateThemes() afterEach -> atom.themes.deactivateThemes() @@ -415,18 +488,24 @@ describe "the `atom` global", -> # enabling of theme pack = atom.packages.enablePackage(packageName) - activatedPackages = atom.packages.getActivePackages() - expect(activatedPackages).toContain(pack) - expect(atom.config.get('core.themes')).toContain packageName - expect(atom.config.get('core.disabledPackages')).not.toContain packageName - # disabling of theme - pack = atom.packages.disablePackage(packageName) - activatedPackages = atom.packages.getActivePackages() - expect(activatedPackages).not.toContain(pack) - expect(atom.config.get('core.themes')).not.toContain packageName - expect(atom.config.get('core.themes')).not.toContain packageName - expect(atom.config.get('core.disabledPackages')).not.toContain packageName + activatedPackages = null + waitsFor -> + activatedPackages = atom.packages.getActivePackages() + activatedPackages.length > 0 + + runs -> + expect(activatedPackages).toContain(pack) + expect(atom.config.get('core.themes')).toContain packageName + expect(atom.config.get('core.disabledPackages')).not.toContain packageName + + # disabling of theme + pack = atom.packages.disablePackage(packageName) + activatedPackages = atom.packages.getActivePackages() + expect(activatedPackages).not.toContain(pack) + expect(atom.config.get('core.themes')).not.toContain packageName + expect(atom.config.get('core.themes')).not.toContain packageName + expect(atom.config.get('core.disabledPackages')).not.toContain packageName describe ".isReleasedVersion()", -> it "returns false if the version is a SHA and true otherwise", -> diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index 77455c5c2..9f8afa408 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -220,6 +220,8 @@ describe "Config", -> expect(fs.existsSync(path.join(atom.config.configDirPath, 'packages'))).toBeTruthy() expect(fs.isFileSync(path.join(atom.config.configDirPath, 'snippets.cson'))).toBeTruthy() expect(fs.isFileSync(path.join(atom.config.configDirPath, 'config.cson'))).toBeTruthy() + expect(fs.isFileSync(path.join(atom.config.configDirPath, 'init.coffee'))).toBeTruthy() + expect(fs.isFileSync(path.join(atom.config.configDirPath, 'styles.css'))).toBeTruthy() describe ".loadUserConfig()", -> beforeEach -> diff --git a/spec/jasmine-helper.coffee b/spec/jasmine-helper.coffee index 5ff4ee868..87765634c 100644 --- a/spec/jasmine-helper.coffee +++ b/spec/jasmine-helper.coffee @@ -21,10 +21,6 @@ module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) -> print: (str) -> log(str) onComplete: (runner) -> - log('\n') - timeReporter.logLongestSuites 10, (line) -> log("#{line}\n") - log('\n') - timeReporter.logLongestSpecs 10, (line) -> log("#{line}\n") fs.closeSync(logStream) if logStream? atom.exit(runner.results().failedCount > 0 ? 1 : 0) else diff --git a/spec/pane-container-view-spec.coffee b/spec/pane-container-view-spec.coffee index 667d3bd6a..28fbd56a6 100644 --- a/spec/pane-container-view-spec.coffee +++ b/spec/pane-container-view-spec.coffee @@ -27,32 +27,6 @@ describe "PaneContainerView", -> afterEach -> atom.deserializers.remove(TestView) - describe ".focusNextPane()", -> - it "focuses the pane following the focused pane or the first pane if no pane has focus", -> - container.attachToDom() - container.focusNextPane() - expect(pane1.activeItem).toMatchSelector ':focus' - container.focusNextPane() - expect(pane2.activeItem).toMatchSelector ':focus' - container.focusNextPane() - expect(pane3.activeItem).toMatchSelector ':focus' - container.focusNextPane() - expect(pane1.activeItem).toMatchSelector ':focus' - - describe ".focusPreviousPane()", -> - it "focuses the pane preceding the focused pane or the last pane if no pane has focus", -> - container.attachToDom() - container.getPanes()[0].focus() # activate first pane - - container.focusPreviousPane() - expect(pane3.activeItem).toMatchSelector ':focus' - container.focusPreviousPane() - expect(pane2.activeItem).toMatchSelector ':focus' - container.focusPreviousPane() - expect(pane1.activeItem).toMatchSelector ':focus' - container.focusPreviousPane() - expect(pane3.activeItem).toMatchSelector ':focus' - describe ".getActivePane()", -> it "returns the most-recently focused pane", -> focusStealer = $$ -> @div tabindex: -1, "focus stealer" @@ -265,3 +239,115 @@ describe "PaneContainerView", -> pane1.remove() pane2.remove() expect(activeItemChangedHandler).not.toHaveBeenCalled() + + describe ".focusNextPane()", -> + it "focuses the pane following the focused pane or the first pane if no pane has focus", -> + container.attachToDom() + container.focusNextPane() + expect(pane1.activeItem).toMatchSelector ':focus' + container.focusNextPane() + expect(pane2.activeItem).toMatchSelector ':focus' + container.focusNextPane() + expect(pane3.activeItem).toMatchSelector ':focus' + container.focusNextPane() + expect(pane1.activeItem).toMatchSelector ':focus' + + describe ".focusPreviousPane()", -> + it "focuses the pane preceding the focused pane or the last pane if no pane has focus", -> + container.attachToDom() + container.getPanes()[0].focus() # activate first pane + + container.focusPreviousPane() + expect(pane3.activeItem).toMatchSelector ':focus' + container.focusPreviousPane() + expect(pane2.activeItem).toMatchSelector ':focus' + container.focusPreviousPane() + expect(pane1.activeItem).toMatchSelector ':focus' + container.focusPreviousPane() + expect(pane3.activeItem).toMatchSelector ':focus' + + describe "changing focus directionally between panes", -> + [pane1, pane2, pane3, pane4, pane5, pane6, pane7, pane8, pane9] = [] + + beforeEach -> + # Set up a grid of 9 panes, in the following arrangement, where the + # numbers correspond to the variable names below. + # + # ------- + # |1|2|3| + # ------- + # |4|5|6| + # ------- + # |7|8|9| + # ------- + + container = new PaneContainerView + pane1 = container.getRoot() + pane1.activateItem(new TestView('1')) + pane4 = pane1.splitDown(new TestView('4')) + pane7 = pane4.splitDown(new TestView('7')) + + pane2 = pane1.splitRight(new TestView('2')) + pane3 = pane2.splitRight(new TestView('3')) + + pane5 = pane4.splitRight(new TestView('5')) + pane6 = pane5.splitRight(new TestView('6')) + + pane8 = pane7.splitRight(new TestView('8')) + pane9 = pane8.splitRight(new TestView('9')) + + container.height(400) + container.width(400) + container.attachToDom() + + describe ".focusPaneAbove()", -> + describe "when there are multiple rows above the focused pane", -> + it "focuses up to the adjacent row", -> + pane8.focus() + container.focusPaneAbove() + expect(pane5.activeItem).toMatchSelector ':focus' + + describe "when there are no rows above the focused pane", -> + it "keeps the current pane focused", -> + pane2.focus() + container.focusPaneAbove() + expect(pane2.activeItem).toMatchSelector ':focus' + + describe ".focusPaneBelow()", -> + describe "when there are multiple rows below the focused pane", -> + it "focuses down to the adjacent row", -> + pane2.focus() + container.focusPaneBelow() + expect(pane5.activeItem).toMatchSelector ':focus' + + describe "when there are no rows below the focused pane", -> + it "keeps the current pane focused", -> + pane8.focus() + container.focusPaneBelow() + expect(pane8.activeItem).toMatchSelector ':focus' + + describe ".focusPaneOnLeft()", -> + describe "when there are multiple columns to the left of the focused pane", -> + it "focuses left to the adjacent column", -> + pane6.focus() + container.focusPaneOnLeft() + expect(pane5.activeItem).toMatchSelector ':focus' + + describe "when there are no columns to the left of the focused pane", -> + it "keeps the current pane focused", -> + pane4.focus() + container.focusPaneOnLeft() + expect(pane4.activeItem).toMatchSelector ':focus' + + describe ".focusPaneOnRight()", -> + describe "when there are multiple columns to the right of the focused pane", -> + it "focuses right to the adjacent column", -> + pane4.focus() + container.focusPaneOnRight() + expect(pane5.activeItem).toMatchSelector ':focus' + + describe "when there are no columns to the right of the focused pane", -> + it "keeps the current pane focused", -> + pane6.focus() + container.focusPaneOnRight() + expect(pane6.activeItem).toMatchSelector ':focus' diff --git a/spec/space-pen-extensions-spec.coffee b/spec/space-pen-extensions-spec.coffee index 28849c72c..92b95c600 100644 --- a/spec/space-pen-extensions-spec.coffee +++ b/spec/space-pen-extensions-spec.coffee @@ -11,35 +11,6 @@ describe "SpacePen extensions", -> parent = $$ -> @div() parent.append(view) - describe "View.observeConfig(keyPath, callback)", -> - observeHandler = null - - beforeEach -> - observeHandler = jasmine.createSpy("observeHandler") - view.observeConfig "foo.bar", observeHandler - expect(view.hasParent()).toBeTruthy() - - it "observes the keyPath and cancels the subscription when `.unobserveConfig()` is called", -> - expect(observeHandler).toHaveBeenCalledWith(undefined) - observeHandler.reset() - - atom.config.set("foo.bar", "hello") - - expect(observeHandler).toHaveBeenCalledWith("hello", previous: undefined) - observeHandler.reset() - - view.unobserveConfig() - - atom.config.set("foo.bar", "goodbye") - - expect(observeHandler).not.toHaveBeenCalled() - - it "unobserves when the view is removed", -> - observeHandler.reset() - parent.remove() - atom.config.set("foo.bar", "hello") - expect(observeHandler).not.toHaveBeenCalled() - describe "View.subscribe(eventEmitter, eventName, callback)", -> [emitter, eventHandler] = [] diff --git a/spec/theme-manager-spec.coffee b/spec/theme-manager-spec.coffee index c8d257b54..384844e93 100644 --- a/spec/theme-manager-spec.coffee +++ b/spec/theme-manager-spec.coffee @@ -26,11 +26,14 @@ describe "ThemeManager", -> expect(themes.length).toBeGreaterThan(2) it 'getActiveThemes get all the active themes', -> - themeManager.activateThemes() - names = atom.config.get('core.themes') - expect(names.length).toBeGreaterThan(0) - themes = themeManager.getActiveThemes() - expect(themes).toHaveLength(names.length) + waitsForPromise -> + themeManager.activateThemes() + + runs -> + names = atom.config.get('core.themes') + expect(names.length).toBeGreaterThan(0) + themes = themeManager.getActiveThemes() + expect(themes).toHaveLength(names.length) describe "getImportPaths()", -> it "returns the theme directories before the themes are loaded", -> @@ -51,29 +54,58 @@ describe "ThemeManager", -> it "add/removes stylesheets to reflect the new config value", -> themeManager.on 'reloaded', reloadHandler = jasmine.createSpy() spyOn(themeManager, 'getUserStylesheetPath').andCallFake -> null - themeManager.activateThemes() - atom.config.set('core.themes', []) - expect($('style.theme').length).toBe 0 - expect(reloadHandler).toHaveBeenCalled() + waitsForPromise -> + themeManager.activateThemes() - atom.config.set('core.themes', ['atom-dark-syntax']) - expect($('style.theme').length).toBe 1 - expect($('style.theme:eq(0)').attr('id')).toMatch /atom-dark-syntax/ + runs -> + reloadHandler.reset() + atom.config.set('core.themes', []) - atom.config.set('core.themes', ['atom-light-syntax', 'atom-dark-syntax']) - expect($('style.theme').length).toBe 2 - expect($('style.theme:eq(0)').attr('id')).toMatch /atom-dark-syntax/ - expect($('style.theme:eq(1)').attr('id')).toMatch /atom-light-syntax/ + waitsFor -> + reloadHandler.callCount == 1 - atom.config.set('core.themes', []) - expect($('style.theme').length).toBe 0 + runs -> + reloadHandler.reset() + expect($('style.theme')).toHaveLength 0 + atom.config.set('core.themes', ['atom-dark-syntax']) - # atom-dark-ui has an directory path, the syntax one doesn't - atom.config.set('core.themes', ['theme-with-index-less', 'atom-dark-ui']) - importPaths = themeManager.getImportPaths() - expect(importPaths.length).toBe 1 - expect(importPaths[0]).toContain 'atom-dark-ui' + waitsFor -> + reloadHandler.callCount == 1 + + runs -> + reloadHandler.reset() + expect($('style.theme')).toHaveLength 1 + expect($('style.theme:eq(0)').attr('id')).toMatch /atom-dark-syntax/ + atom.config.set('core.themes', ['atom-light-syntax', 'atom-dark-syntax']) + + waitsFor -> + reloadHandler.callCount == 1 + + runs -> + reloadHandler.reset() + expect($('style.theme')).toHaveLength 2 + expect($('style.theme:eq(0)').attr('id')).toMatch /atom-dark-syntax/ + expect($('style.theme:eq(1)').attr('id')).toMatch /atom-light-syntax/ + atom.config.set('core.themes', []) + + waitsFor -> + reloadHandler.callCount == 1 + + runs -> + reloadHandler.reset() + expect($('style.theme')).toHaveLength 0 + # atom-dark-ui has an directory path, the syntax one doesn't + atom.config.set('core.themes', ['theme-with-index-less', 'atom-dark-ui']) + + waitsFor -> + reloadHandler.callCount == 1 + + runs -> + expect($('style.theme')).toHaveLength 2 + importPaths = themeManager.getImportPaths() + expect(importPaths.length).toBe 1 + expect(importPaths[0]).toContain 'atom-dark-ui' describe "when a theme fails to load", -> it "logs a warning", -> @@ -145,18 +177,25 @@ describe "ThemeManager", -> atom.workspaceView = new WorkspaceView atom.workspaceView.append $$ -> @div class: 'editor' atom.workspaceView.attachToDom() - themeManager.activateThemes() + + waitsForPromise -> + themeManager.activateThemes() it "loads the correct values from the theme's ui-variables file", -> + themeManager.on 'reloaded', reloadHandler = jasmine.createSpy() atom.config.set('core.themes', ['theme-with-ui-variables']) - # an override loaded in the base css - expect(atom.workspaceView.css("background-color")).toBe "rgb(0, 0, 255)" + waitsFor -> + reloadHandler.callCount > 0 - # from within the theme itself - expect($(".editor").css("padding-top")).toBe "150px" - expect($(".editor").css("padding-right")).toBe "150px" - expect($(".editor").css("padding-bottom")).toBe "150px" + runs -> + # an override loaded in the base css + expect(atom.workspaceView.css("background-color")).toBe "rgb(0, 0, 255)" + + # from within the theme itself + expect($(".editor").css("padding-top")).toBe "150px" + expect($(".editor").css("padding-right")).toBe "150px" + expect($(".editor").css("padding-bottom")).toBe "150px" describe "when the user stylesheet changes", -> it "reloads it", -> @@ -164,12 +203,14 @@ describe "ThemeManager", -> fs.writeFileSync(userStylesheetPath, 'body {border-style: dotted !important;}') spyOn(themeManager, 'getUserStylesheetPath').andReturn userStylesheetPath - themeManager.activateThemes() - expect($(document.body).css('border-style')).toBe 'dotted' - spyOn(themeManager, 'loadUserStylesheet').andCallThrough() + waitsForPromise -> + themeManager.activateThemes() - fs.writeFileSync(userStylesheetPath, 'body {border-style: dashed}') + runs -> + expect($(document.body).css('border-style')).toBe 'dotted' + spyOn(themeManager, 'loadUserStylesheet').andCallThrough() + fs.writeFileSync(userStylesheetPath, 'body {border-style: dashed}') waitsFor -> themeManager.loadUserStylesheet.callCount is 1 diff --git a/src/atom-package.coffee b/src/atom-package.coffee index 955274d05..049322a72 100644 --- a/src/atom-package.coffee +++ b/src/atom-package.coffee @@ -2,12 +2,13 @@ Package = require './package' fs = require 'fs-plus' path = require 'path' _ = require 'underscore-plus' +Q = require 'q' {$} = require './space-pen-extensions' CSON = require 'season' {Emitter} = require 'emissary' -### Internal: Loads and resolves packages. ### - +# Loads and activates a package's main module and resources such as +# stylesheets, keymaps, grammar, editor properties, and menus. module.exports = class AtomPackage extends Package Emitter.includeInto(this) @@ -55,14 +56,34 @@ class AtomPackage extends Package @grammars = [] @scopedProperties = [] - activate: ({immediate}={}) -> + activate: -> + return @activationDeferred.promise if @activationDeferred? + + @activationDeferred = Q.defer() @measure 'activateTime', => @activateResources() - if @metadata.activationEvents? and not immediate + if @metadata.activationEvents? @subscribeToActivationEvents() else @activateNow() + @activationDeferred.promise + + # Deprecated + activateSync: ({immediate}={}) -> + @activateResources() + if @metadata.activationEvents? and not immediate + @subscribeToActivationEvents() + else + try + @activateConfig() + @activateStylesheets() + if @requireMainModule() + @mainModule.activate(atom.packages.getPackageState(@name) ? {}) + @mainActivated = true + catch e + console.warn "Failed to activate package named '#{@name}'", e.stack + activateNow: -> try @activateConfig() @@ -73,6 +94,8 @@ class AtomPackage extends Package catch e console.warn "Failed to activate package named '#{@name}'", e.stack + @activationDeferred.resolve() + activateConfig: -> return if @configActivated @@ -158,6 +181,8 @@ class AtomPackage extends Package console.error "Error serializing package '#{@name}'", e.stack deactivate: -> + @activationDeferred?.reject() + @activationDeferred = null @unsubscribeFromActivationEvents() @deactivateResources() @deactivateConfig() @@ -216,6 +241,8 @@ class AtomPackage extends Package @unsubscribeFromActivationEvents() unsubscribeFromActivationEvents: -> + return unless atom.workspaceView? + if _.isArray(@metadata.activationEvents) atom.workspaceView.off(event, @handleActivationEvent) for event in @metadata.activationEvents else if _.isString(@metadata.activationEvents) diff --git a/src/atom.coffee b/src/atom.coffee index 606ba6b89..9cfffb74a 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -20,16 +20,18 @@ WindowEventHandler = require './window-event-handler' # # ## Useful properties available: # -# * `atom.config` - A {Config} instance -# * `atom.contextMenu` - A {ContextMenuManager} instance -# * `atom.keymap` - A {Keymap} instance -# * `atom.menu` - A {MenuManager} instance -# * `atom.workspaceView` - A {WorkspaceView} instance -# * `atom.packages` - A {PackageManager} instance -# * `atom.clipboard` - A {Clipboard} instance -# * `atom.project` - A {Project} instance -# * `atom.syntax` - A {Syntax} instance -# * `atom.themes` - A {ThemeManager} instance +# * `atom.clipboard` - A {Clipboard} instance +# * `atom.config` - A {Config} instance +# * `atom.contextMenu` - A {ContextMenuManager} instance +# * `atom.deserializers` - A {DeserializerManager} instance +# * `atom.keymap` - A {Keymap} instance +# * `atom.menu` - A {MenuManager} instance +# * `atom.packages` - A {PackageManager} instance +# * `atom.project` - A {Project} instance +# * `atom.syntax` - A {Syntax} instance +# * `atom.themes` - A {ThemeManager} instance +# * `atom.workspace` - A {Workspace} instance +# * `atom.workspaceView` - A {WorkspaceView} instance module.exports = class Atom extends Model # Public: Load or create the Atom environment in the given mode. @@ -41,11 +43,11 @@ class Atom extends Model @loadOrCreate: (mode) -> @deserialize(@loadState(mode)) ? new this({mode, version: @getVersion()}) - # Private: Deserializes the Atom environment from a state object + # Deserializes the Atom environment from a state object @deserialize: (state) -> new this(state) if state?.version is @getVersion() - # Private: Loads and returns the serialized state corresponding to this window + # Loads and returns the serialized state corresponding to this window # if it exists; otherwise returns undefined. @loadState: (mode) -> statePath = @getStatePath(mode) @@ -63,7 +65,7 @@ class Atom extends Model catch error console.warn "Error parsing window state: #{statePath} #{error.stack}", error - # Private: Returns the path where the state for the current window will be + # Returns the path where the state for the current window will be # located if it exists. @getStatePath: (mode) -> switch mode @@ -80,19 +82,19 @@ class Atom extends Model else null - # Private: Get the directory path to Atom's configuration area. + # Get the directory path to Atom's configuration area. # # Returns the absolute path to ~/.atom @getConfigDirPath: -> @configDirPath ?= fs.absolute('~/.atom') - # Private: Get the path to Atom's storage directory. + # Get the path to Atom's storage directory. # # Returns the absolute path to ~/.atom/storage @getStorageDirPath: -> @storageDirPath ?= path.join(@getConfigDirPath(), 'storage') - # Private: Returns the load settings hash associated with the current window. + # Returns the load settings hash associated with the current window. @getLoadSettings: -> @loadSettings ?= JSON.parse(decodeURIComponent(location.search.substr(14))) cloned = _.deepClone(@loadSettings) @@ -103,25 +105,24 @@ class Atom extends Model @getCurrentWindow().loadSettings.windowState = value cloned - # Private: @getCurrentWindow: -> remote.getCurrentWindow() - # Private: Get the version of the Atom application. + # Get the version of the Atom application. @getVersion: -> @version ?= @getLoadSettings().appVersion - # Private: Determine whether the current version is an official release. + # Determine whether the current version is an official release. @isReleasedVersion: -> not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix workspaceViewParentSelector: 'body' - # Private: Call .loadOrCreate instead + # Call .loadOrCreate instead constructor: (@state) -> {@mode} = @state DeserializerManager = require './deserializer-manager' - @deserializers = new DeserializerManager(this) + @deserializers = new DeserializerManager() # Public: Sets up the basic services that should be available in all modes # (both spec and application). Call after this instance has been assigned to @@ -173,7 +174,6 @@ class Atom extends Model # Deprecated: Callers should be converted to use atom.deserializers registerRepresentationClasses: -> - # Private: setBodyPlatformClass: -> document.body.classList.add("platform-#{process.platform}") @@ -209,7 +209,6 @@ class Atom extends Model else @center() - # Private: restoreWindowDimensions: -> workAreaSize = screen.getPrimaryDisplay().workAreaSize windowDimensions = @state.windowDimensions ? {} @@ -218,7 +217,6 @@ class Atom extends Model windowDimensions.width ?= initialSize?.width ? Math.min(workAreaSize.width, 1024) @setWindowDimensions(windowDimensions) - # Private: storeWindowDimensions: -> @state.windowDimensions = @getWindowDimensions() @@ -228,12 +226,10 @@ class Atom extends Model getLoadSettings: -> @constructor.getLoadSettings() - # Private: deserializeProject: -> Project = require './project' @project ?= @deserializers.deserialize(@project) ? new Project(path: @getLoadSettings().initialPath) - # Private: deserializeWorkspaceView: -> Workspace = require './workspace' WorkspaceView = require './workspace-view' @@ -241,18 +237,16 @@ class Atom extends Model @workspaceView = new WorkspaceView(@workspace) $(@workspaceViewParentSelector).append(@workspaceView) - # Private: deserializePackageStates: -> @packages.packageStates = @state.packageStates ? {} delete @state.packageStates - # Private: deserializeEditorWindow: -> @deserializePackageStates() @deserializeProject() @deserializeWorkspaceView() - # Private: Call this method when establishing a real application window. + # Call this method when establishing a real application window. startEditorWindow: -> CommandInstaller = require './command-installer' resourcePath = atom.getLoadSettings().resourcePath @@ -281,7 +275,6 @@ class Atom extends Model @displayWindow() - # Private: unloadEditorWindow: -> return if not @project and not @workspaceView @@ -297,11 +290,9 @@ class Atom extends Model @keymap.destroy() @windowState = null - # Private: loadThemes: -> @themes.load() - # Private: watchThemes: -> @themes.on 'reloaded', => # Only reload stylesheets from non-theme packages @@ -314,31 +305,32 @@ class Atom extends Model # Calling this method without an options parameter will open a prompt to pick # a file/folder to open in the new window. # - # * options - # * pathsToOpen: A string array of paths to open + # options - An {Object} with the following keys: + # :pathsToOpen - An {Array} of {String} paths to open. open: (options) -> ipc.sendChannel('open', options) # Public: Open a confirm dialog. # - # ## Example: - # ```coffeescript + # ## Example + # + # ```coffee # atom.confirm - # message: 'How you feeling?' - # detailedMessage: 'Be honest.' - # buttons: - # Good: -> window.alert('good to hear') - # Bad: -> window.alert('bummer') + # message: 'How you feeling?' + # detailedMessage: 'Be honest.' + # buttons: + # Good: -> window.alert('good to hear') + # Bad: -> window.alert('bummer') # ``` # - # * options: - # + message: The string message to display. - # + detailedMessage: The string detailed message to display. - # + buttons: Either an array of strings or an object where the values - # are callbacks to invoke when clicked. + # options - An {Object} with the following keys: + # :message - The {String} message to display. + # :detailedMessage - The {String} detailed message to display. + # :buttons - Either an array of strings or an object where keys are + # button names and the values are callbacks to invoke when + # clicked. # - # Returns the chosen index if buttons was an array or the return of the - # callback if buttons was an object. + # Returns the chosen button index {Number} if the buttons option was an array. confirm: ({message, detailedMessage, buttons}={}) -> buttons ?= {} if _.isArray(buttons) @@ -359,11 +351,9 @@ class Atom extends Model callback = buttons[buttonLabels[chosen]] callback?() - # Private: showSaveDialog: (callback) -> callback(showSaveDialogSync()) - # Private: showSaveDialogSync: (defaultPath) -> defaultPath ?= @project?.getPath() currentWindow = @getCurrentWindow() @@ -396,10 +386,16 @@ class Atom extends Model ipc.sendChannel('call-window-method', 'hide') # Public: Set the size of current window. + # + # width - The {Number} of pixels. + # height - The {Number} of pixels. setSize: (width, height) -> ipc.sendChannel('call-window-method', 'setSize', width, height) # Public: Set the position of current window. + # + # x - The {Number} of pixels. + # y - The {Number} of pixels. setPosition: (x, y) -> ipc.sendChannel('call-window-method', 'setPosition', x, y) @@ -407,7 +403,7 @@ class Atom extends Model center: -> ipc.sendChannel('call-window-method', 'center') - # Private: Schedule the window to be shown and focused on the next tick. + # Schedule the window to be shown and focused on the next tick. # # This is done in a next tick to prevent a white flicker from occurring # if called synchronously. @@ -421,7 +417,6 @@ class Atom extends Model close: -> @getCurrentWindow().close() - # Private: exit: (status) -> app = remote.require('app') app.exit(status) @@ -471,7 +466,6 @@ class Atom extends Model getConfigDirPath: -> @constructor.getConfigDirPath() - # Private: saveSync: -> stateString = JSON.stringify(@state) if statePath = @constructor.getStatePath(@mode) @@ -489,11 +483,9 @@ class Atom extends Model getWindowLoadTime: -> @loadTime - # Private: crashMainProcess: -> remote.process.crash() - # Private: crashRenderProcess: -> process.crash() @@ -502,9 +494,12 @@ class Atom extends Model shell.beep() if @config.get('core.audioBeep') @workspaceView.trigger 'beep' - # Private: + getUserInitScriptPath: -> + initScriptPath = fs.resolve(@getConfigDirPath(), 'init', ['js', 'coffee']) + initScriptPath ? path.join(@getConfigDirPath(), 'init.coffee') + requireUserInitScript: -> - if userInitScriptPath = fs.resolve(@getConfigDirPath(), 'user', ['js', 'coffee']) + if userInitScriptPath = @getUserInitScriptPath() try require userInitScriptPath catch error @@ -514,6 +509,9 @@ class Atom extends Model # # The globals will be set on the `window` object and removed after the # require completes. + # + # id - The {String} module name or path. + # globals - An {Object} to set as globals during require (default: {}) requireWithGlobals: (id, globals={}) -> existingGlobals = {} for key, value of globals diff --git a/src/browser/application-menu.coffee b/src/browser/application-menu.coffee index 7de362838..905655ac8 100644 --- a/src/browser/application-menu.coffee +++ b/src/browser/application-menu.coffee @@ -3,7 +3,7 @@ ipc = require 'ipc' Menu = require 'menu' _ = require 'underscore-plus' -# Private: Used to manage the global application menu. +# Used to manage the global application menu. # # It's created by {AtomApplication} upon instantiation and used to add, remove # and maintain the state of all menu items. @@ -29,7 +29,7 @@ class ApplicationMenu @menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(@menu) - # Private: Flattens the given menu and submenu items into an single Array. + # Flattens the given menu and submenu items into an single Array. # # * menu: # A complete menu configuration object for atom-shell's menu API. @@ -42,7 +42,7 @@ class ApplicationMenu items = items.concat(@flattenMenuItems(item.submenu)) if item.submenu items - # Private: Flattens the given menu template into an single Array. + # Flattens the given menu template into an single Array. # # * template: # An object describing the menu item. @@ -64,26 +64,22 @@ class ApplicationMenu for item in @flattenMenuItems(@menu) item.enabled = enable if item.metadata?['windowSpecific'] - # Private: Replaces VERSION with the current version. + # Replaces VERSION with the current version. substituteVersion: (template) -> if (item = _.find(@flattenMenuTemplate(template), (i) -> i.label == 'VERSION')) item.label = "Version #{@version}" - # Public: Makes the download menu item visible if available. - # - # Note: The update menu item's must match 'Install update' exactly otherwise - # this function will fail to work. - # - # * newVersion: - # FIXME: Unused. - # * quitAndUpdateCallback: - # Function to call when the install menu item has been clicked. - showDownloadUpdateItem: (newVersion, quitAndUpdateCallback) -> - if (item = _.find(@flattenMenuItems(@menu), (i) -> i.label == 'Install update')) - item.visible = true - item.click = quitAndUpdateCallback + # Toggles Install Update Item + showInstallUpdateItem: (visible=true) -> + if (item = _.find(@flattenMenuItems(@menu), (i) -> i.label == 'Restart and Install Update')) + item.visible = visible - # Private: Default list of menu items. + # Toggles Check For Update Item + showCheckForUpdateItem: (visible=true) -> + if (item = _.find(@flattenMenuItems(@menu), (i) -> i.label == 'Check for Update')) + item.visible = visible + + # Default list of menu items. # # Returns an Array of menu item Objects. getDefaultTemplate: -> @@ -97,7 +93,7 @@ class ApplicationMenu ] ] - # Private: Combines a menu template with the appropriate keystroke. + # Combines a menu template with the appropriate keystroke. # # * template: # An Object conforming to atom-shell's menu api but lacking accelerator and @@ -117,7 +113,7 @@ class ApplicationMenu @translateTemplate(item.submenu, keystrokesByCommand) if item.submenu template - # Private: Determine the accelerator for a given command. + # Determine the accelerator for a given command. # # * command: # The name of the command. diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index 881036b69..149c6f61c 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -22,7 +22,7 @@ socketPath = else path.join(os.tmpdir(), 'atom.sock') -# Private: The application's singleton class. +# The application's singleton class. # # It's the entry point into the Atom application and maintains the global state # of the application. @@ -73,11 +73,11 @@ class AtomApplication @listenForArgumentsFromNewProcess() @setupJavaScriptArguments() @handleEvents() - @checkForUpdates() + @setupAutoUpdater() @openWithOptions(options) - # Private: Opens a new window based on the options provided. + # Opens a new window based on the options provided. openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, newWindow, specDirectory, logFile}) -> if test @runSpecs({exitWhenDone: true, @resourcePath, specDirectory, logFile}) @@ -98,7 +98,7 @@ class AtomApplication @windows.push window @applicationMenu?.enableWindowSpecificItems(true) - # Private: Creates server to listen for additional atom application launches. + # Creates server to listen for additional atom application launches. # # You can run the atom command multiple times, but after the first launch # the other launches will just pass their information to this server and then @@ -113,23 +113,59 @@ class AtomApplication server.listen socketPath server.on 'error', (error) -> console.error 'Application server failed', error - # Private: Configures required javascript environment flags. + # Configures required javascript environment flags. setupJavaScriptArguments: -> app.commandLine.appendSwitch 'js-flags', '--harmony_collections --harmony-proxies' - # Private: Enable updates unless running from a local build of Atom. - checkForUpdates: -> - versionIsSha = /\w{7}/.test @version + # Enable updates unless running from a local build of Atom. + setupAutoUpdater: -> + autoUpdater.setFeedUrl "https://atom.io/api/updates?version=#{@version}" - if versionIsSha - autoUpdater.setAutomaticallyDownloadsUpdates false - autoUpdater.setAutomaticallyChecksForUpdates false - else - autoUpdater.setAutomaticallyDownloadsUpdates true - autoUpdater.setAutomaticallyChecksForUpdates true - autoUpdater.checkForUpdatesInBackground() + autoUpdater.on 'checking-for-update', => + @applicationMenu.showInstallUpdateItem(false) + @applicationMenu.showCheckForUpdateItem(false) - # Private: Registers basic application commands, non-idempotent. + autoUpdater.on 'update-not-available', => + @applicationMenu.showInstallUpdateItem(false) + @applicationMenu.showCheckForUpdateItem(true) + + autoUpdater.on 'update-downloaded', (event, releaseNotes, releaseName, releaseDate, releaseURL) => + atomWindow.sendCommand('window:update-available', releaseName) for atomWindow in @windows + @applicationMenu.showInstallUpdateItem(true) + @applicationMenu.showCheckForUpdateItem(false) + @updateVersion = releaseName + + autoUpdater.on 'error', (event, message) => + @applicationMenu.showInstallUpdateItem(false) + @applicationMenu.showCheckForUpdateItem(true) + + autoUpdater.checkForUpdates() + + checkForUpdate: -> + autoUpdater.once 'update-available', -> + dialog.showMessageBox + type: 'info' + buttons: ['OK'] + message: 'Update available.' + detail: 'A new update is being downloading.' + + autoUpdater.once 'update-not-available', => + dialog.showMessageBox + type: 'info' + buttons: ['OK'] + message: 'No update available.' + detail: "Version #{@version} is the latest version." + + autoUpdater.once 'error', (event, message)-> + dialog.showMessageBox + type: 'warning' + buttons: ['OK'] + message: 'There was an error checking for updates.' + detail: message + + autoUpdater.checkForUpdates() + + # Registers basic application commands, non-idempotent. handleEvents: -> @on 'application:about', -> Menu.sendActionToFirstResponder('orderFrontStandardAboutPanel:') @on 'application:run-all-specs', -> @runSpecs(exitWhenDone: false, resourcePath: global.devResourcePath) @@ -148,9 +184,12 @@ class AtomApplication @on 'application:inspect', ({x,y}) -> @focusedWindow().browserWindow.inspectElement(x, y) @on 'application:open-documentation', -> shell.openExternal('https://www.atom.io/docs/latest/?app') @on 'application:report-issue', -> shell.openExternal('https://github.com/atom/atom/issues/new') + @on 'application:install-update', -> autoUpdater.quitAndInstall() + @on 'application:check-for-update', => @checkForUpdate() @openPathOnEvent('application:show-settings', 'atom://config') @openPathOnEvent('application:open-your-config', 'atom://.atom/config') + @openPathOnEvent('application:open-your-init-script', 'atom://.atom/init-script') @openPathOnEvent('application:open-your-keymap', 'atom://.atom/keymap') @openPathOnEvent('application:open-your-snippets', 'atom://.atom/snippets') @openPathOnEvent('application:open-your-stylesheet', 'atom://.atom/stylesheet') @@ -171,12 +210,6 @@ class AtomApplication event.preventDefault() @openUrl({urlToOpen, @devMode}) - autoUpdater.on 'ready-for-update-on-quit', (event, version, quitAndUpdateCallback) => - event.preventDefault() - @updateVersion = version - @applicationMenu.showDownloadUpdateItem(version, quitAndUpdateCallback) - atomWindow.sendCommand('window:update-available', version) for atomWindow in @windows - # A request from the associated render process to open a new render process. ipc.on 'open', (processId, routingId, options) => if options? @@ -230,7 +263,7 @@ class AtomApplication else @openPath({pathToOpen}) - # Private: Returns the {AtomWindow} for the given path. + # Returns the {AtomWindow} for the given path. windowForPath: (pathToOpen) -> for atomWindow in @windows return atomWindow if atomWindow.containsPath(pathToOpen) @@ -318,7 +351,7 @@ class AtomApplication console.log("Killing process #{pid} failed: #{error.code}") delete @pidsToOpenWindows[pid] - # Private: Open an atom:// url. + # Open an atom:// url. # # The host of the URL being opened is assumed to be the package name # responsible for opening the URL. A new window will be created with @@ -350,7 +383,7 @@ class AtomApplication else console.log "Opening unknown url: #{urlToOpen}" - # Private: Opens up a new {AtomWindow} to run specs within. + # Opens up a new {AtomWindow} to run specs within. # # * options # + exitWhenDone: @@ -381,7 +414,7 @@ class AtomApplication isSpec = true new AtomWindow({bootstrapScript, @resourcePath, isSpec}) - # Private: Opens a native dialog to prompt the user for a path. + # Opens a native dialog to prompt the user for a path. # # Once paths are selected, they're opened in a new or existing {AtomWindow}s. # diff --git a/src/browser/atom-protocol-handler.coffee b/src/browser/atom-protocol-handler.coffee index 247a67dcc..786d50243 100644 --- a/src/browser/atom-protocol-handler.coffee +++ b/src/browser/atom-protocol-handler.coffee @@ -3,7 +3,7 @@ fs = require 'fs-plus' path = require 'path' protocol = require 'protocol' -# Private: Handles requests with 'atom' protocol. +# Handles requests with 'atom' protocol. # # It's created by {AtomApplication} upon instantiation, and is used to create a # custom resource loader by adding the 'atom' custom protocol. @@ -18,7 +18,7 @@ class AtomProtocolHandler @registerAtomProtocol() - # Private: Creates the 'atom' custom protocol handler. + # Creates the 'atom' custom protocol handler. registerAtomProtocol: -> protocol.registerProtocol 'atom', (request) => relativePath = path.normalize(request.url.substr(7)) diff --git a/src/browser/atom-window.coffee b/src/browser/atom-window.coffee index 534154ddf..156db8f6e 100644 --- a/src/browser/atom-window.coffee +++ b/src/browser/atom-window.coffee @@ -9,7 +9,6 @@ fs = require 'fs' url = require 'url' _ = require 'underscore-plus' -# Private: module.exports = class AtomWindow @iconPath: path.resolve(__dirname, '..', '..', 'resources', 'atom.png') diff --git a/src/browser/context-menu.coffee b/src/browser/context-menu.coffee index 85d3b0426..e3044b30d 100644 --- a/src/browser/context-menu.coffee +++ b/src/browser/context-menu.coffee @@ -1,6 +1,5 @@ Menu = require 'menu' -# Private: module.exports = class ContextMenu constructor: (template, browserWindow) -> @@ -8,7 +7,7 @@ class ContextMenu menu = Menu.buildFromTemplate(template) menu.popup(browserWindow) - # Private: It's necessary to build the event handlers in this process, otherwise + # It's necessary to build the event handlers in this process, otherwise # closures are drug across processes and failed to be garbage collected # appropriately. createClickHandlers: (template) -> diff --git a/src/browser/main.coffee b/src/browser/main.coffee index 5d4a66224..c72081cd7 100644 --- a/src/browser/main.coffee +++ b/src/browser/main.coffee @@ -1,6 +1,5 @@ global.shellStartTime = Date.now() -autoUpdater = require 'auto-updater' crashReporter = require 'crash-reporter' app = require 'app' fs = require 'fs' @@ -42,7 +41,6 @@ start = -> app.on 'will-finish-launching', -> setupCrashReporter() - setupAutoUpdater() app.on 'finish-launching', -> app.removeListener 'open-file', addPathToOpen @@ -66,9 +64,6 @@ global.devResourcePath = path.join(app.getHomeDir(), 'github', 'atom') setupCrashReporter = -> crashReporter.start(productName: 'Atom', companyName: 'GitHub') -setupAutoUpdater = -> - autoUpdater.setFeedUrl 'https://speakeasy.githubapp.com/apps/27/appcast.xml' - parseCommandLine = -> version = app.getVersion() options = optimist(process.argv[1..]) diff --git a/src/buffered-process.coffee b/src/buffered-process.coffee index b27c315be..ed0907f3f 100644 --- a/src/buffered-process.coffee +++ b/src/buffered-process.coffee @@ -12,30 +12,27 @@ class BufferedProcess process: null killed: false - # Executes the given executable. + # Public: Executes the given executable. # - # * options - # + command: - # The path to the executable to execute. - # + args: - # The array of arguments to pass to the script (optional). - # + options: - # The options Object to pass to Node's `ChildProcess.spawn` (optional). - # + stdout: - # The callback that receives a single argument which contains the - # standard output of the script. The callback is called as data is - # received but it's buffered to ensure only complete lines are passed - # until the source stream closes. After the source stream has closed - # all remaining data is sent in a final call (optional). - # + stderr: - # The callback that receives a single argument which contains the - # standard error of the script. The callback is called as data is - # received but it's buffered to ensure only complete lines are passed - # until the source stream closes. After the source stream has closed - # all remaining data is sent in a final call (optional). - # + exit: - # The callback which receives a single argument containing the exit - # status (optional). + # options - An {Object} with the following keys: + # :command - The {String} command to execute. + # :args - The {String}} of arguments to pass to the script (optional). + # :options - The options {Object} to pass to Node's `ChildProcess.spawn` + # (optional). + # :stdout - The callback that receives a single argument which contains the + # standard output of the script. The callback is called as data is + # received but it's buffered to ensure only complete lines are + # passed until the source stream closes. After the source stream + # has closed all remaining data is sent in a final call + # (optional). + # :stderr - The callback that receives a single argument which contains the + # standard error of the script. The callback is called as data is + # received but it's buffered to ensure only complete lines are + # passed until the source stream closes. After the source stream + # has closed all remaining data is sent in a final call + # (optional). + # :exit - The callback which receives a single argument containing the exit + # status (optional). constructor: ({command, args, options, stdout, stderr, exit}={}) -> options ?= {} @process = ChildProcess.spawn(command, args, options) @@ -68,7 +65,7 @@ class BufferedProcess processExited = true triggerExitCallback() - # Private: Helper method to pass data line by line. + # Helper method to pass data line by line. # # * stream: # The Stream to read from. @@ -93,7 +90,7 @@ class BufferedProcess onLines(buffered) if buffered.length > 0 onDone() - # Public: Terminates the process. + # Public: Terminate the process. kill: -> @killed = true @process.kill() diff --git a/src/clipboard.coffee b/src/clipboard.coffee index 00725b5e7..82d75d2d4 100644 --- a/src/clipboard.coffee +++ b/src/clipboard.coffee @@ -3,7 +3,7 @@ crypto = require 'crypto' # Public: Represents the clipboard used for copying and pasting in Atom. # -# A clipboard instance is always available under the `atom.clipboard` global. +# An instance of this class is always available as the `atom.clipboard` global. module.exports = class Clipboard metadata: null @@ -11,7 +11,7 @@ class Clipboard # Creates an `md5` hash of some text. # - # * text: A {String} to hash. + # text - A {String} to hash. # # Returns a hashed {String}. md5: (text) -> @@ -22,8 +22,8 @@ class Clipboard # The metadata associated with the text is available by calling # {.readWithMetadata}. # - # * text: A {String} to store. - # * metadata: An {Object} of additional info to associate with the text. + # text - The {String} to store. + # metadata - The additional info to associate with the text. write: (text, metadata) -> @signatureForMetadata = @md5(text) @metadata = metadata @@ -38,8 +38,9 @@ class Clipboard # Public: Read the text from the clipboard and return both the text and the # associated metadata. # - # Returns an {Object} with a `text` key and a `metadata` key if it has - # associated metadata. + # Returns an {Object} with the following keys: + # :text - The {String} clipboard text. + # :metadata - The metadata stored by an earlier call to {.write}. readWithMetadata: -> text = @read() if @signatureForMetadata is @md5(text) diff --git a/src/config-observer.coffee b/src/config-observer.coffee deleted file mode 100644 index 46d3c44dd..000000000 --- a/src/config-observer.coffee +++ /dev/null @@ -1,12 +0,0 @@ -Mixin = require 'mixto' - -module.exports = -class ConfigObserver extends Mixin - observeConfig: (keyPath, args...) -> - @configSubscriptions ?= {} - @configSubscriptions[keyPath] = atom.config.observe(keyPath, args...) - - unobserveConfig: -> - if @configSubscriptions? - subscription.off() for keyPath, subscription of @configSubscriptions - @configSubscriptions = null diff --git a/src/config.coffee b/src/config.coffee index 68a7b464f..7896fa795 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -8,15 +8,14 @@ pathWatcher = require 'pathwatcher' # Public: Used to access all of Atom's configuration details. # -# A global instance of this class is available to all plugins which can be -# referenced using `atom.config` +# An instance of this class is always available as the `atom.config` global. # -# ### Best practices +# ## Best practices # # * Create your own root keypath using your package's name. # * Don't depend on (or write to) configuration keys outside of your keypath. # -# ### Example +# ## Example # # ```coffeescript # atom.config.set('myplugin.key', 'value') @@ -27,18 +26,14 @@ module.exports = class Config Emitter.includeInto(this) - defaultSettings: null - settings: null - configFileHasErrors: null - - # Private: Created during initialization, available as `global.config` + # Created during initialization, available as `atom.config` constructor: ({@configDirPath, @resourcePath}={}) -> @defaultSettings = {} @settings = {} + @configFileHasErrors = false @configFilePath = fs.resolve(@configDirPath, 'config', ['json', 'cson']) @configFilePath ?= path.join(@configDirPath, 'config.cson') - # Private: initializeConfigDirectory: (done) -> return if fs.existsSync(@configDirPath) @@ -55,13 +50,11 @@ class Config queue.push({sourcePath, destinationPath}) fs.traverseTree(templateConfigDirPath, onConfigDirFile, (path) -> true) - # Private: load: -> @initializeConfigDirectory() @loadUserConfig() @observeUserConfig() - # Private: loadUserConfig: -> unless fs.existsSync(@configFilePath) fs.makeTreeSync(path.dirname(@configFilePath)) @@ -77,17 +70,14 @@ class Config console.error "Failed to load user config '#{@configFilePath}'", e.message console.error e.stack - # Private: observeUserConfig: -> @watchSubscription ?= pathWatcher.watch @configFilePath, (eventType) => @loadUserConfig() if eventType is 'change' and @watchSubscription? - # Private: unobserveUserConfig: -> @watchSubscription?.close() @watchSubscription = null - # Private: setDefaults: (keyPath, defaults) -> keys = keyPath.split('.') hash = @defaultSettings @@ -205,6 +195,9 @@ class Config # options - An optional {Object} containing the `callNow` key. # callback - The {Function} that fires when the. It is given a single argument, `value`, # which is the new value of `keyPath`. + # + # Returns an {Object} with the following keys: + # :off - A {Function} that unobserves the `keyPath` with called. observe: (keyPath, options={}, callback) -> if _.isFunction(options) callback = options @@ -230,12 +223,10 @@ class Config unobserve: (keyPath) -> @off("updated.#{keyPath.replace(/\./, '-')}") - # Private: update: -> return if @configFileHasErrors @save() @emit 'updated' - # Private: save: -> CSON.writeFileSync(@configFilePath, @settings) diff --git a/src/context-menu-manager.coffee b/src/context-menu-manager.coffee index 2cbfe0364..40228156b 100644 --- a/src/context-menu-manager.coffee +++ b/src/context-menu-manager.coffee @@ -5,10 +5,10 @@ remote = require 'remote' # Public: Provides a registry for commands that you'd like to appear in the # context menu. # -# Should be accessed via `atom.contextMenu`. +# An instance of this class is always available as the `atom.contextMenu` +# global. module.exports = class ContextMenuManager - # Private: constructor: (@devMode=false) -> @definitions = {} @devModeDefinitions = {} @@ -24,11 +24,11 @@ class ContextMenuManager # Public: Creates menu definitions from the object specified by the menu # cson API. # - # * name: The path of the file that contains the menu definitions. - # * object: The 'context-menu' object specified in the menu cson API. - # * options: - # + devMode - Determines whether the entries should only be shown when - # the window is in dev mode. + # name - The path of the file that contains the menu definitions. + # object - The 'context-menu' object specified in the menu cson API. + # options - An {Object} with the following keys: + # :devMode - Determines whether the entries should only be shown when + # the window is in dev mode. # # Returns nothing. add: (name, object, {devMode}={}) -> @@ -36,20 +36,20 @@ class ContextMenuManager for label, command of items @addBySelector(selector, {label, command}, {devMode}) - # Private: Registers a command to be displayed when the relevant item is right + # Registers a command to be displayed when the relevant item is right # clicked. # - # * selector: The css selector for the active element which should include - # the given command in its context menu. - # * definition: The object containing keys which match the menu template API. - # * options: - # + devMode: Indicates whether this command should only appear while the - # editor is in dev mode. + # selector - The css selector for the active element which should include + # the given command in its context menu. + # definition - The object containing keys which match the menu template API. + # options - An {Object} with the following keys: + # :devMode - Indicates whether this command should only appear while the + # editor is in dev mode. addBySelector: (selector, definition, {devMode}={}) -> definitions = if devMode then @devModeDefinitions else @definitions (definitions[selector] ?= []).push(definition) - # Private: Returns definitions which match the element and devMode. + # Returns definitions which match the element and devMode. definitionsForElement: (element, {devMode}={}) -> definitions = if devMode then @devModeDefinitions else @definitions matchedDefinitions = [] @@ -58,14 +58,14 @@ class ContextMenuManager matchedDefinitions - # Private: Used to generate the context menu for a specific element and it's + # Used to generate the context menu for a specific element and it's # parents. # # The menu items are sorted such that menu items that match closest to the # active element are listed first. The further down the list you go, the higher # up the ancestor hierarchy they match. # - # * element: The DOM element to generate the menu template for. + # element - The DOM element to generate the menu template for. menuTemplateForMostSpecificElement: (element, {devMode}={}) -> menuTemplate = @definitionsForElement(element, {devMode}) if element.parentElement @@ -73,7 +73,7 @@ class ContextMenuManager else menuTemplate - # Private: Returns a menu template for both normal entries as well as + # Returns a menu template for both normal entries as well as # development mode entries. combinedMenuTemplateForElement: (element) -> normalItems = @menuTemplateForMostSpecificElement(element) @@ -83,7 +83,7 @@ class ContextMenuManager menuTemplate.push({ type: 'separator' }) if normalItems.length > 0 and devItems.length > 0 menuTemplate.concat(devItems) - # Private: Executes `executeAtBuild` if defined for each menu item with + # Executes `executeAtBuild` if defined for each menu item with # the provided event and then removes the `executeAtBuild` property from # the menu item. # diff --git a/src/cursor-view.coffee b/src/cursor-view.coffee index f76de9436..a9048bd73 100644 --- a/src/cursor-view.coffee +++ b/src/cursor-view.coffee @@ -2,7 +2,6 @@ {Point, Range} = require 'text-buffer' _ = require 'underscore-plus' -### Internal ### module.exports = class CursorView extends View @content: -> diff --git a/src/cursor.coffee b/src/cursor.coffee index daf91e4dc..6b3747479 100644 --- a/src/cursor.coffee +++ b/src/cursor.coffee @@ -17,7 +17,7 @@ class Cursor visible: true needsAutoscroll: null - # Private: Instantiated by an {Editor} + # Instantiated by an {Editor} constructor: ({@editor, @marker}) -> @updateVisibility() @marker.on 'changed', (e) => @@ -45,11 +45,9 @@ class Cursor @emit 'destroyed' @needsAutoscroll = true - # Private: destroy: -> @marker.destroy() - # Private: changePosition: (options, fn) -> @clearSelection() @needsAutoscroll = options.autoscroll ? @isLastCursor() @@ -58,12 +56,11 @@ class Cursor # Public: Moves a cursor to a given screen position. # - # * screenPosition: - # An {Array} of two numbers: the screen row, and the screen column. - # * options: - # + autoscroll: - # A Boolean which, if `true`, scrolls the {Editor} to wherever the - # cursor moves to. + # screenPosition - An {Array} of two numbers: the screen row, and the screen + # column. + # options - An {Object} with the following keys: + # :autoscroll - A Boolean which, if `true`, scrolls the {Editor} to wherever + # the cursor moves to. setScreenPosition: (screenPosition, options={}) -> @changePosition options, => @marker.setHeadScreenPosition(screenPosition, options) @@ -74,12 +71,11 @@ class Cursor # Public: Moves a cursor to a given buffer position. # - # * bufferPosition: - # An {Array} of two numbers: the buffer row, and the buffer column. - # * options: - # + autoscroll: - # A Boolean which, if `true`, scrolls the {Editor} to wherever the - # cursor moves to. + # bufferPosition - An {Array} of two numbers: the buffer row, and the buffer + # column. + # options - An {Object} with the following keys: + # :autoscroll - A Boolean which, if `true`, scrolls the {Editor} to wherever + # the cursor moves to. setBufferPosition: (bufferPosition, options={}) -> @changePosition options, => @marker.setHeadBufferPosition(bufferPosition, options) @@ -104,11 +100,11 @@ class Cursor # Public: Get the RegExp used by the cursor to determine what a "word" is. # - # * options: - # + includeNonWordCharacters: - # A Boolean indicating whether to include non-word characters in the regex. + # options: An {Object} with the following keys: + # :includeNonWordCharacters - A {Boolean} indicating whether to include + # non-word characters in the regex. # - # Returns a RegExp. + # Returns a {RegExp}. wordRegExp: ({includeNonWordCharacters}={})-> includeNonWordCharacters ?= true nonWordCharacters = atom.config.get('editor.nonWordCharacters') @@ -122,7 +118,7 @@ class Cursor # # "Last" is defined as the most recently added cursor. # - # Returns a Boolean. + # Returns a {Boolean}. isLastCursor: -> this == @editor.getCursor() @@ -131,7 +127,7 @@ class Cursor # "Surrounded" here means that all characters before and after the cursor is # whitespace. # - # Returns a Boolean. + # Returns a {Boolean}. isSurroundedByWhitespace: -> {row, column} = @getBufferPosition() range = [[row, Math.min(0, column - 1)], [row, Math.max(0, column + 1)]] @@ -217,9 +213,9 @@ class Cursor # Public: Moves the cursor left one screen column. # - # * options: - # + moveToEndOfSelection: - # if true, move to the left of the selection if a selection exists. + # options - An {Object} with the following keys: + # :moveToEndOfSelection - if true, move to the left of the selection if a + # selection exists. moveLeft: ({moveToEndOfSelection}={}) -> range = @marker.getScreenRange() if moveToEndOfSelection and not range.isEmpty() @@ -231,9 +227,9 @@ class Cursor # Public: Moves the cursor right one screen column. # - # * options: - # + moveToEndOfSelection: - # if true, move to the right of the selection if a selection exists. + # options - An {Object} with the following keys: + # :moveToEndOfSelection - if true, move to the right of the selection if a + # selection exists. moveRight: ({moveToEndOfSelection}={}) -> range = @marker.getScreenRange() if moveToEndOfSelection and not range.isEmpty() @@ -313,12 +309,12 @@ class Cursor # Public: Retrieves the buffer position of where the current word starts. # - # * options: - # + wordRegex: - # A RegExp indicating what constitutes a "word" (default: {.wordRegExp}) - # + includeNonWordCharacters: - # A Boolean indicating whether to include non-word characters in the - # default word regex. Has no effect if wordRegex is set. + # options - An {Object} with the following keys: + # :wordRegex - A {RegExp} indicating what constitutes a "word" + # (default: {.wordRegExp}). + # :includeNonWordCharacters - A {Boolean} indicating whether to include + # non-word characters in the default word regex. + # Has no effect if wordRegex is set. # # Returns a {Range}. getBeginningOfCurrentWordBufferPosition: (options = {}) -> @@ -381,12 +377,12 @@ class Cursor # Public: Retrieves the buffer position of where the current word ends. # - # * options: - # + wordRegex: - # A RegExp indicating what constitutes a "word" (default: {.wordRegExp}) - # + includeNonWordCharacters: - # A Boolean indicating whether to include non-word characters in the - # default word regex. Has no effect if wordRegex is set. + # options - An {Object} with the following keys: + # :wordRegex - A {RegExp} indicating what constitutes a "word" + # (default: {.wordRegExp}) + # :includeNonWordCharacters - A Boolean indicating whether to include + # non-word characters in the default word regex. + # Has no effect if wordRegex is set. # # Returns a {Range}. getEndOfCurrentWordBufferPosition: (options = {}) -> @@ -405,9 +401,9 @@ class Cursor # Public: Retrieves the buffer position of where the next word starts. # - # * options: - # + wordRegex: - # A RegExp indicating what constitutes a "word" (default: {.wordRegExp}) + # options - + # :wordRegex - A {RegExp} indicating what constitutes a "word" + # (default: {.wordRegExp}). # # Returns a {Range}. getBeginningOfNextWordBufferPosition: (options = {}) -> @@ -424,9 +420,9 @@ class Cursor # Public: Returns the buffer Range occupied by the word located under the cursor. # - # * options: - # + wordRegex: - # A RegExp indicating what constitutes a "word" (default: {.wordRegExp}) + # options - + # :wordRegex - A {RegExp} indicating what constitutes a "word" + # (default: {.wordRegExp}). getCurrentWordBufferRange: (options={}) -> startOptions = _.extend(_.clone(options), allowPrevious: false) endOptions = _.extend(_.clone(options), allowNext: false) @@ -434,9 +430,9 @@ class Cursor # Public: Returns the buffer Range for the current line. # - # * options: - # + includeNewline: - # A boolean which controls whether the Range should include the newline. + # options - + # :includeNewline: - A {Boolean} which controls whether the Range should + # include the newline. getCurrentLineBufferRange: (options) -> @editor.bufferRangeForBufferRow(@getBufferRow(), options) diff --git a/src/deserializer-manager.coffee b/src/deserializer-manager.coffee index 2266845d6..3c7cc2f5e 100644 --- a/src/deserializer-manager.coffee +++ b/src/deserializer-manager.coffee @@ -1,20 +1,39 @@ # Public: Manages the deserializers used for serialized state # -# Should be accessed via `atom.deserializers` +# An instance of this class is always available as the `atom.deserializers` +# global. +# +# ### Registering a deserializer +# +# ```coffee +# class MyPackageView extends View +# atom.deserializers.add(this) +# +# @deserialize: (state) -> +# new MyPackageView(state) +# ``` module.exports = class DeserializerManager - constructor: (@environment) -> + constructor: -> @deserializers = {} # Public: Register the given class(es) as deserializers. - add: (klasses...) -> - @deserializers[klass.name] = klass for klass in klasses + # + # classes - One or more classes to register. + add: (classes...) -> + @deserializers[klass.name] = klass for klass in classes # Public: Remove the given class(es) as deserializers. - remove: (klasses...) -> - delete @deserializers[klass.name] for klass in klasses + # + # classes - One or more classes to remove. + remove: (classes...) -> + delete @deserializers[name] for {name} in classes # Public: Deserialize the state and params. + # + # state - The state {Object} to deserialize. + # params - The params {Object} to pass as the second arguments to the + # deserialize method of the deserializer. deserialize: (state, params) -> return unless state? @@ -25,7 +44,9 @@ class DeserializerManager else console.warn "No deserializer found for", state - # Private: Get the deserializer for the state. + # Get the deserializer for the state. + # + # state - The state {Object} being deserialized. get: (state) -> return unless state? diff --git a/src/directory.coffee b/src/directory.coffee index 6404dc5ba..790de62f6 100644 --- a/src/directory.coffee +++ b/src/directory.coffee @@ -7,7 +7,7 @@ pathWatcher = require 'pathwatcher' File = require './file' -# Public: Represents a directory using {File}s. +# Public: Represents a directory on disk. # # ## Requiring in packages # @@ -18,15 +18,12 @@ module.exports = class Directory Emitter.includeInto(this) - path: null realPath: null # Public: Configures a new Directory instance, no files are accessed. # - # * path: - # A String containing the absolute path to the directory. - # + symlink: - # A Boolean indicating if the path is a symlink (defaults to false). + # path - A {String} containing the absolute path to the directory. + # symlink - A {Boolean} indicating if the path is a symlink (default: false). constructor: (@path, @symlink=false) -> @on 'first-contents-changed-subscription-will-be-added', => # Triggered by emissary, when a new contents-changed listener attaches @@ -36,7 +33,7 @@ class Directory # Triggered by emissary, when the last contents-changed listener detaches @unsubscribeFromNativeChangeEvents() - # Public: Returns the basename of the directory. + # Public: Returns the {String} basename of the directory. getBaseName: -> path.basename(@path) @@ -108,8 +105,8 @@ class Directory # Public: Reads file entries in this directory from disk asynchronously. # - # * callback: A function to call with an Error as the first argument and - # an {Array} of {File} and {Directory} objects as the second argument. + # callback - A {Function} to call with an {Error} as the 1st argument and + # an {Array} of {File} and {Directory} objects as the 2nd argument. getEntries: (callback) -> fs.list @path, (error, entries) -> return callback(error) if error? @@ -134,18 +131,16 @@ class Directory async.eachLimit entries, 1, statEntry, -> callback(null, directories.concat(files)) - # Private: subscribeToNativeChangeEvents: -> unless @watchSubscription? @watchSubscription = pathWatcher.watch @path, (eventType) => @emit "contents-changed" if eventType is "change" - # Private: unsubscribeFromNativeChangeEvents: -> if @watchSubscription? @watchSubscription.close() @watchSubscription = null - # Private: Does given full path start with the given prefix? + # Does given full path start with the given prefix? isPathPrefixOf: (prefix, fullPath) -> fullPath.indexOf(prefix) is 0 and fullPath[prefix.length] is path.sep diff --git a/src/display-buffer-marker.coffee b/src/display-buffer-marker.coffee index 8c08b3c9e..184162622 100644 --- a/src/display-buffer-marker.coffee +++ b/src/display-buffer-marker.coffee @@ -2,7 +2,6 @@ _ = require 'underscore-plus' {Emitter, Subscriber} = require 'emissary' -# Private: module.exports = class DisplayBufferMarker Emitter.includeInto(this) @@ -15,8 +14,6 @@ class DisplayBufferMarker oldTailScreenPosition: null wasValid: true - ### Internal ### - constructor: ({@bufferMarker, @displayBuffer}) -> @id = @bufferMarker.id @oldHeadBufferPosition = @getHeadBufferPosition() @@ -28,8 +25,6 @@ class DisplayBufferMarker @subscribe @bufferMarker, 'destroyed', => @destroyed() @subscribe @bufferMarker, 'changed', (event) => @notifyObservers(event) - ### Public ### - copy: (attributes) -> @displayBuffer.getMarker(@bufferMarker.copy(attributes).id) @@ -170,8 +165,6 @@ class DisplayBufferMarker inspect: -> "DisplayBufferMarker(id: #{@id}, bufferRange: #{@getBufferRange()})" - ### Internal ### - destroyed: -> delete @displayBuffer.markers[@id] @emit 'destroyed' diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 3c835863b..06e2d397a 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -1,5 +1,5 @@ _ = require 'underscore-plus' -{Emitter, Subscriber} = require 'emissary' +{Emitter} = require 'emissary' guid = require 'guid' Serializable = require 'serializable' {Model} = require 'theorist' @@ -9,13 +9,10 @@ RowMap = require './row-map' Fold = require './fold' Token = require './token' DisplayBufferMarker = require './display-buffer-marker' -ConfigObserver = require './config-observer' -# Private: module.exports = class DisplayBuffer extends Model Serializable.includeInto(this) - ConfigObserver.includeInto(this) @properties softWrap: null @@ -39,10 +36,10 @@ class DisplayBuffer extends Model @emit 'soft-wrap-changed', softWrap @updateWrappedScreenLines() - @observeConfig 'editor.preferredLineLength', callNow: false, => + @subscribe atom.config.observe 'editor.preferredLineLength', callNow: false, => @updateWrappedScreenLines() if @softWrap and atom.config.get('editor.softWrapAtPreferredLineLength') - @observeConfig 'editor.softWrapAtPreferredLineLength', callNow: false, => + @subscribe atom.config.observe 'editor.softWrapAtPreferredLineLength', callNow: false, => @updateWrappedScreenLines() if @softWrap serializeParams: -> @@ -82,8 +79,6 @@ class DisplayBuffer extends Model bufferDelta = 0 @emitChanged({ start, end, screenDelta, bufferDelta }) - ### Public ### - # Sets the visibility of the tokenized buffer. # # visible - A {Boolean} indicating of the tokenized buffer is shown @@ -419,8 +414,6 @@ class DisplayBuffer extends Model column = screenLine.clipScreenColumn(column, options) new Point(row, column) - ### Public ### - # Given a line, finds the point where it would wrap. # # line - The {String} to check @@ -470,7 +463,7 @@ class DisplayBuffer extends Model getMarkerCount: -> @buffer.getMarkerCount() - # Constructs a new marker at the given screen range. + # Public: Constructs a new marker at the given screen range. # # range - The marker {Range} (representing the distance between the head and tail) # options - Options to pass to the {Marker} constructor @@ -480,7 +473,7 @@ class DisplayBuffer extends Model bufferRange = @bufferRangeForScreenRange(args.shift()) @markBufferRange(bufferRange, args...) - # Constructs a new marker at the given buffer range. + # Public: Constructs a new marker at the given buffer range. # # range - The marker {Range} (representing the distance between the head and tail) # options - Options to pass to the {Marker} constructor @@ -489,7 +482,7 @@ class DisplayBuffer extends Model markBufferRange: (args...) -> @getMarker(@buffer.markRange(args...).id) - # Constructs a new marker at the given screen position. + # Public: Constructs a new marker at the given screen position. # # range - The marker {Range} (representing the distance between the head and tail) # options - Options to pass to the {Marker} constructor @@ -498,7 +491,7 @@ class DisplayBuffer extends Model markScreenPosition: (screenPosition, options) -> @markBufferPosition(@bufferPositionForScreenPosition(screenPosition), options) - # Constructs a new marker at the given buffer position. + # Public: Constructs a new marker at the given buffer position. # # range - The marker {Range} (representing the distance between the head and tail) # options - Options to pass to the {Marker} constructor @@ -507,7 +500,7 @@ class DisplayBuffer extends Model markBufferPosition: (bufferPosition, options) -> @getMarker(@buffer.markPosition(bufferPosition, options).id) - # Removes the marker with the given id. + # Public: Removes the marker with the given id. # # id - The {Number} of the ID to remove destroyMarker: (id) -> @@ -573,15 +566,12 @@ class DisplayBuffer extends Model marker.unsubscribe() for marker in @getMarkers() @tokenizedBuffer.destroy() @unsubscribe() - @unobserveConfig() logLines: (start=0, end=@getLastRow())-> for row in [start..end] line = @lineForRow(row).text console.log row, line, line.length - ### Internal ### - handleTokenizedBufferChange: (tokenizedBufferChange) => {start, end, delta, bufferChange} = tokenizedBufferChange @updateScreenLines(start, end + 1, delta, delayChangeEvent: bufferChange?) diff --git a/src/editor-view.coffee b/src/editor-view.coffee index e29a2cc13..19636802a 100644 --- a/src/editor-view.coffee +++ b/src/editor-view.coffee @@ -41,8 +41,6 @@ class EditorView extends View @nextEditorId: 1 - ### Internal ### - @content: (params) -> attributes = { class: @classes(params), tabindex: -1 } _.extend(attributes, params.attributes) if params.attributes @@ -79,14 +77,12 @@ class EditorView extends View redrawOnReattach: false bottomPaddingInLines: 10 - ### Public ### - # The constructor for setting up an `EditorView` instance. # # editorOrOptions - Either an {Editor}, or an object with one property, `mini`. - # If `mini` is `true`, a "miniature" `Editor` is constructed. - # Typically, this is ideal for scenarios where you need an Atom editor, - # but without all the chrome, like scrollbars, gutter, _e.t.c._. + # If `mini` is `true`, a "miniature" `Editor` is constructed. + # Typically, this is ideal for scenarios where you need an Atom editor, + # but without all the chrome, like scrollbars, gutter, _e.t.c._. # initialize: (editorOrOptions) -> if editorOrOptions instanceof Editor @@ -120,7 +116,7 @@ class EditorView extends View else throw new Error("Must supply an Editor or mini: true") - # Internal: Sets up the core Atom commands. + # Sets up the core Atom commands. # # Some commands are excluded from mini-editors. bindKeys: -> @@ -223,6 +219,9 @@ class EditorView extends View do (name, method) => @command name, (e) -> method(e); false + # Public: Get the underlying editor model for this view. + # + # Returns an {Editor}. getEditor: -> @editor @@ -238,7 +237,6 @@ class EditorView extends View insertText: (text, options) -> @editor.insertText(text, options) - # Private: setHeightInLines: (heightInLines)-> heightInLines ?= @calculateHeightInLines() @heightInLines = heightInLines if heightInLines @@ -248,39 +246,41 @@ class EditorView extends View widthInChars ?= @calculateWidthInChars() @editor.setEditorWidthInChars(widthInChars) if widthInChars - # Public: Emulates the "page down" key, where the last row of a buffer scrolls to become the first. + # Public: Emulates the "page down" key, where the last row of a buffer scrolls + # to become the first. pageDown: -> newScrollTop = @scrollTop() + @scrollView[0].clientHeight @editor.moveCursorDown(@getPageRows()) @scrollTop(newScrollTop, adjustVerticalScrollbar: true) - # Public: Emulates the "page up" key, where the frst row of a buffer scrolls to become the last. + # Public: Emulates the "page up" key, where the frst row of a buffer scrolls + # to become the last. pageUp: -> newScrollTop = @scrollTop() - @scrollView[0].clientHeight @editor.moveCursorUp(@getPageRows()) @scrollTop(newScrollTop, adjustVerticalScrollbar: true) - # Gets the number of actual page rows existing in an editor. + # Public: Gets the number of actual page rows existing in an editor. # # Returns a {Number}. getPageRows: -> Math.max(1, Math.ceil(@scrollView[0].clientHeight / @lineHeight)) - # Set whether invisible characters are shown. + # Public: Set whether invisible characters are shown. # - # showInvisibles - A {Boolean} which, if `true`, show invisible characters + # showInvisibles - A {Boolean} which, if `true`, show invisible characters. setShowInvisibles: (showInvisibles) -> return if showInvisibles == @showInvisibles @showInvisibles = showInvisibles @resetDisplay() - # Defines which characters are invisible. + # Public: Defines which characters are invisible. # - # invisibles - A hash defining the invisible characters: The defaults are: - # eol: `\u00ac` - # space: `\u00b7` - # tab: `\u00bb` - # cr: `\u00a4` + # invisibles - An {Object} defining the invisible characters: + # :eol - The end of line invisible {String} (default: `\u00ac`). + # :space - The space invisible {String} (default: `\u00b7`). + # :tab - The tab invisible {String} (default: `\u00bb`). + # :cr - The carriage return invisible {String} (default: `\u00a4`). setInvisibles: (@invisibles={}) -> _.defaults @invisibles, eol: '\u00ac' @@ -289,14 +289,20 @@ class EditorView extends View cr: '\u00a4' @resetDisplay() - # Sets whether you want to show the indentation guides. + # Public: Sets whether you want to show the indentation guides. # - # showIndentGuide - A {Boolean} you can set to `true` if you want to see the indentation guides. + # showIndentGuide - A {Boolean} you can set to `true` if you want to see the + # indentation guides. setShowIndentGuide: (showIndentGuide) -> return if showIndentGuide == @showIndentGuide @showIndentGuide = showIndentGuide @resetDisplay() + # Public: Set the text to appear in the editor when it is empty. + # + # This only affects mini editors. + # + # placeholderText - A {String} of text to display when empty. setPlaceholderText: (placeholderText) -> return unless @mini @placeholderText = placeholderText @@ -310,15 +316,13 @@ class EditorView extends View if path = @editor.getPath() atom.project.getRepo()?.checkoutHead(path) - ### Internal ### - configure: -> - @observeConfig 'editor.showLineNumbers', (showLineNumbers) => @gutter.setShowLineNumbers(showLineNumbers) - @observeConfig 'editor.showInvisibles', (showInvisibles) => @setShowInvisibles(showInvisibles) - @observeConfig 'editor.showIndentGuide', (showIndentGuide) => @setShowIndentGuide(showIndentGuide) - @observeConfig 'editor.invisibles', (invisibles) => @setInvisibles(invisibles) - @observeConfig 'editor.fontSize', (fontSize) => @setFontSize(fontSize) - @observeConfig 'editor.fontFamily', (fontFamily) => @setFontFamily(fontFamily) + @subscribe atom.config.observe 'editor.showLineNumbers', (showLineNumbers) => @gutter.setShowLineNumbers(showLineNumbers) + @subscribe atom.config.observe 'editor.showInvisibles', (showInvisibles) => @setShowInvisibles(showInvisibles) + @subscribe atom.config.observe 'editor.showIndentGuide', (showIndentGuide) => @setShowIndentGuide(showIndentGuide) + @subscribe atom.config.observe 'editor.invisibles', (invisibles) => @setInvisibles(invisibles) + @subscribe atom.config.observe 'editor.fontSize', (fontSize) => @setFontSize(fontSize) + @subscribe atom.config.observe 'editor.fontFamily', (fontFamily) => @setFontFamily(fontFamily) handleEvents: -> @on 'focus', => @@ -488,12 +492,11 @@ class EditorView extends View @trigger 'editor:attached', [this] - # TODO: This should be private and only called from the constructor edit: (editor) -> return if editor is @editor if @editor - @saveScrollPositionForeditor() + @saveScrollPositionForEditor() @editor.off(".editor") @editor = editor @@ -590,19 +593,18 @@ class EditorView extends View else @scrollView.scrollRight() - ### Public ### - - # Scrolls the editor to the bottom. + # Public: Scrolls the editor to the bottom. scrollToBottom: -> @scrollBottom(@editor.getScreenLineCount() * @lineHeight) - # Scrolls the editor to the position of the most recently added cursor. + # Public: Scrolls the editor to the position of the most recently added + # cursor. # # The editor is also centered. scrollToCursorPosition: -> @scrollToBufferPosition(@editor.getCursorBufferPosition(), center: true) - # Scrolls the editor to the given buffer position. + # Public: Scrolls the editor to the given buffer position. # # bufferPosition - An object that represents a buffer position. It can be either # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} @@ -610,7 +612,7 @@ class EditorView extends View scrollToBufferPosition: (bufferPosition, options) -> @scrollToPixelPosition(@pixelPositionForBufferPosition(bufferPosition), options) - # Scrolls the editor to the given screen position. + # Public: Scrolls the editor to the given screen position. # # screenPosition - An object that represents a buffer position. It can be either # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} @@ -618,18 +620,20 @@ class EditorView extends View scrollToScreenPosition: (screenPosition, options) -> @scrollToPixelPosition(@pixelPositionForScreenPosition(screenPosition), options) - # Scrolls the editor to the given pixel position. + # Public: Scrolls the editor to the given pixel position. # # pixelPosition - An object that represents a pixel position. It can be either - # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} + # an {Object} (`{row, column}`), {Array} (`[row, column]`), or + # {Point}. # options - A hash with the following keys: - # center: if `true`, the position is scrolled such that it's in the center of the editor + # :center - if `true`, the position is scrolled such that it's in + # the center of the editor scrollToPixelPosition: (pixelPosition, options) -> return unless @attached @scrollVertically(pixelPosition, options) @scrollHorizontally(pixelPosition) - # Highlight all the folds within the given buffer range. + # Public: Highlight all the folds within the given buffer range. # # "Highlighting" essentially just adds the `fold-selected` class to the line's # DOM element. @@ -647,29 +651,27 @@ class EditorView extends View else element.removeClass('fold-selected') - saveScrollPositionForeditor: -> + saveScrollPositionForEditor: -> if @attached @editor.setScrollTop(@scrollTop()) @editor.setScrollLeft(@scrollLeft()) - # Toggle soft tabs on the edit session. + # Public: Toggle soft tabs on the edit session. toggleSoftTabs: -> @editor.setSoftTabs(not @editor.getSoftTabs()) - # Toggle soft wrap on the edit session. + # Public: Toggle soft wrap on the edit session. toggleSoftWrap: -> @setWidthInChars() @editor.setSoftWrap(not @editor.getSoftWrap()) - # Private: calculateWidthInChars: -> Math.floor(@scrollView.width() / @charWidth) - # Private: calculateHeightInLines: -> Math.ceil($(window).height() / @lineHeight) - # Enables/disables soft wrap on the editor. + # Public: Enables/disables soft wrap on the editor. # # softWrap - A {Boolean} which, if `true`, enables soft wrap setSoftWrap: (softWrap) -> @@ -679,7 +681,7 @@ class EditorView extends View else @removeClass 'soft-wrap' - # Sets the font size for the editor. + # Public: Sets the font size for the editor. # # fontSize - A {Number} indicating the font size in pixels. setFontSize: (fontSize) -> @@ -692,13 +694,13 @@ class EditorView extends View else @redrawOnReattach = @attached - # Retrieves the font size for the editor. + # Public: Retrieves the font size for the editor. # # Returns a {Number} indicating the font size in pixels. getFontSize: -> parseInt(@css("font-size")) - # Sets the font family for the editor. + # Public: Sets the font family for the editor. # # fontFamily - A {String} identifying the CSS `font-family`, setFontFamily: (fontFamily='') -> @@ -708,12 +710,12 @@ class EditorView extends View @redraw() - # Gets the font family for the editor. + # Public: Gets the font family for the editor. # # Returns a {String} identifying the CSS `font-family`, getFontFamily: -> @css("font-family") - # Redraw the editor + # Public: Redraw the editor redraw: -> return unless @hasParent() return unless @attached @@ -723,23 +725,27 @@ class EditorView extends View @updateLayerDimensions() @requestDisplayUpdate() + # Public: Split the editor view left. splitLeft: -> pane = @getPane() pane?.splitLeft(pane?.copyActiveItem()).activeView + # Public: Split the editor view right. splitRight: -> pane = @getPane() pane?.splitRight(pane?.copyActiveItem()).activeView + # Public: Split the editor view up. splitUp: -> pane = @getPane() pane?.splitUp(pane?.copyActiveItem()).activeView + # Public: Split the editor view down. splitDown: -> pane = @getPane() pane?.splitDown(pane?.copyActiveItem()).activeView - # Retrieve's the `EditorView`'s pane. + # Public: Get this view's pane. # # Returns a {Pane}. getPane: -> @@ -750,7 +756,6 @@ class EditorView extends View super atom.workspaceView?.focus() - # Private: beforeRemove: -> @trigger 'editor:will-be-removed' @removed = true @@ -797,8 +802,6 @@ class EditorView extends View appendToLinesView: (view) -> @overlayer.append(view) - ### Internal ### - # Scrolls the editor vertically to a given position. scrollVertically: (pixelPosition, {center}={}) -> scrollViewHeight = @scrollView.height() @@ -835,7 +838,7 @@ class EditorView extends View @scrollRight(desiredRight) else if desiredLeft < @scrollLeft() @scrollLeft(desiredLeft) - @saveScrollPositionForeditor() + @saveScrollPositionForEditor() calculateDimensions: -> fragment = $('
') @@ -1135,9 +1138,8 @@ class EditorView extends View @renderedLines.css('padding-bottom', paddingBottom) @gutter.lineNumbers.css('padding-bottom', paddingBottom) - ### Public ### - - # Retrieves the number of the row that is visible and currently at the top of the editor. + # Public: Retrieves the number of the row that is visible and currently at the + # top of the editor. # # Returns a {Number}. getFirstVisibleScreenRow: -> @@ -1145,7 +1147,8 @@ class EditorView extends View screenRow = 0 if isNaN(screenRow) screenRow - # Retrieves the number of the row that is visible and currently at the bottom of the editor. + # Public: Retrieves the number of the row that is visible and currently at the + # bottom of the editor. # # Returns a {Number}. getLastVisibleScreenRow: -> @@ -1154,7 +1157,7 @@ class EditorView extends View screenRow = 0 if isNaN(screenRow) screenRow - # Given a row number, identifies if it is currently visible. + # Public: Given a row number, identifies if it is currently visible. # # row - A row {Number} to check # @@ -1162,8 +1165,6 @@ class EditorView extends View isScreenRowVisible: (row) -> @getFirstVisibleScreenRow() <= row <= @getLastVisibleScreenRow() - ### Internal ### - handleScreenLinesChange: (change) -> @pendingChanges.push(change) @requestDisplayUpdate() @@ -1246,21 +1247,19 @@ class EditorView extends View toggleLineCommentsInSelection: -> @editor.toggleLineCommentsInSelection() - ### Public ### - - # Converts a buffer position to a pixel position. + # Public: Converts a buffer position to a pixel position. # # position - An object that represents a buffer position. It can be either - # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} + # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} # # Returns an object with two values: `top` and `left`, representing the pixel positions. pixelPositionForBufferPosition: (position) -> @pixelPositionForScreenPosition(@editor.screenPositionForBufferPosition(position)) - # Converts a screen position to a pixel position. + # Public: Converts a screen position to a pixel position. # # position - An object that represents a screen position. It can be either - # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} + # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} # # Returns an object with two values: `top` and `left`, representing the pixel positions. pixelPositionForScreenPosition: (position) -> @@ -1297,7 +1296,6 @@ class EditorView extends View index++ left - # Private: measureToColumn: (lineElement, tokenizedLine, screenColumn) -> left = oldLeft = index = 0 iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, TextNodeFilter) @@ -1343,7 +1341,6 @@ class EditorView extends View returnLeft ? left - # Private: getCharacterWidthCache: (scopes, char) -> scopes ?= NoScope obj = @constructor.characterWidthCache @@ -1352,7 +1349,6 @@ class EditorView extends View return null unless obj? obj[char] - # Private: setCharacterWidthCache: (scopes, char, val) -> scopes ?= NoScope obj = @constructor.characterWidthCache @@ -1361,7 +1357,6 @@ class EditorView extends View obj = obj[scope] obj[char] = val - # Private: clearCharacterWidthCache: -> @constructor.characterWidthCache = {} @@ -1415,8 +1410,6 @@ class EditorView extends View path = @editor.getPath() atom.clipboard.write(path) if path? - ### Internal ### - @buildLineHtml: ({tokens, text, lineEnding, fold, isSoftWrapped, invisibles, eolInvisibles, htmlEolInvisibles, attributes, showIndentGuide, indentation, editor, mini}) -> scopeStack = [] line = [] diff --git a/src/editor.coffee b/src/editor.coffee index 3e0ea7023..3befee1f8 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -97,7 +97,6 @@ class Editor extends Model params.registerEditor = true params - # Private: subscribeToBuffer: -> @buffer.retain() @subscribe @buffer, "path-changed", => @@ -111,7 +110,6 @@ class Editor extends Model @subscribe @buffer, "destroyed", => @destroy() @preserveCursorPositionOnBufferReload() - # Private: subscribeToDisplayBuffer: -> @subscribe @displayBuffer, 'marker-created', @handleMarkerCreated @subscribe @displayBuffer, "changed", (e) => @emit 'screen-lines-changed', e @@ -119,11 +117,9 @@ class Editor extends Model @subscribe @displayBuffer, 'grammar-changed', => @handleGrammarChange() @subscribe @displayBuffer, 'soft-wrap-changed', (args...) => @emit 'soft-wrap-changed', args... - # Private: getViewClass: -> require './editor-view' - # Private: destroyed: -> @unsubscribe() selection.destroy() for selection in @getSelections() @@ -132,7 +128,7 @@ class Editor extends Model @languageMode.destroy() atom.project?.removeEditor(this) - # Private: Creates an {Editor} with the same initial state + # Creates an {Editor} with the same initial state copy: -> tabLength = @getTabLength() displayBuffer = @displayBuffer.copy() @@ -242,8 +238,7 @@ class Editor extends Model # or if its column goes beyond a line's length, this "sanitizes" the value # to a real position. # - # * bufferPosition: - # The {Point} to clip + # bufferPosition - The {Point} to clip. # # Returns the new, clipped {Point}. Note that this could be the same as # `bufferPosition` if no clipping was performed. @@ -255,8 +250,7 @@ class Editor extends Model # or if its column goes beyond a line's length, this "sanitizes" the value # to a real range. # - # * range: - # The {Range} to clip + # range - The {Range} to clip. # # Returns the new, clipped {Range}. Note that this could be the same as # `range` if no clipping was performed. @@ -264,17 +258,14 @@ class Editor extends Model # Public: Returns the indentation level of the given a buffer row # - # * bufferRow: - # A Number indicating the buffer row. + # bufferRow - A {Number} indicating the buffer row. indentationForBufferRow: (bufferRow) -> @indentLevelForLine(@lineForBufferRow(bufferRow)) # Public: Sets the indentation level for the given buffer row. # - # * bufferRow: - # A {Number} indicating the buffer row. - # * newLevel: - # A {Number} indicating the new indentation level. + # bufferRow - A {Number} indicating the buffer row. + # newLevel - A {Number} indicating the new indentation level. setIndentationForBufferRow: (bufferRow, newLevel) -> currentIndentLength = @lineForBufferRow(bufferRow).match(/^\s*/)[0].length newIndentString = @buildIndentString(newLevel) @@ -282,8 +273,7 @@ class Editor extends Model # Public: Returns the indentation level of the given line of text. # - # * line: - # A {String} in the current buffer. + # line - A {String} in the current buffer. # # Returns a {Number} or 0 if the text isn't found within the buffer. indentLevelForLine: (line) -> @@ -295,7 +285,7 @@ class Editor extends Model else 0 - # Private: Constructs the string used for tabs. + # Constructs the string used for tabs. buildIndentString: (number) -> if @getSoftTabs() _.multiplyString(" ", number * @getTabLength()) @@ -326,7 +316,7 @@ class Editor extends Model # Public: Returns a {Number} representing the number of lines in the editor. getLineCount: -> @buffer.getLineCount() - # Private: Retrieves the current {TextBuffer}. + # Retrieves the current {TextBuffer}. getBuffer: -> @buffer # Public: Retrieves the current buffer's URI. @@ -353,8 +343,8 @@ class Editor extends Model # Public: Returns the range for the given buffer row. # - # * row: A row {Number}. - # * options: An options hash with an `includeNewline` key. + # row - A row {Number}. + # options - An options hash with an `includeNewline` key. # # Returns a {Range}. bufferRangeForBufferRow: (row, options) -> @buffer.rangeForRow(row, options) @@ -362,7 +352,7 @@ class Editor extends Model # Public: Returns a {String} representing the contents of the line at the # given buffer row. # - # * row - A {Number} representing a zero-indexed buffer row. + # row - A {Number} representing a zero-indexed buffer row. lineForBufferRow: (row) -> @buffer.lineForRow(row) # Public: Returns a {Number} representing the line length for the given @@ -437,10 +427,8 @@ class Editor extends Model # Public: Inserts text at the current cursor positions # - # * text: - # A String representing the text to insert. - # * options: - # + A set of options equivalent to {Selection.insertText} + # text - A {String} representing the text to insert. + # options - A set of options equivalent to {Selection.insertText}. insertText: (text, options={}) -> options.autoIndentNewline ?= @shouldAutoIndent() options.autoDecreaseIndent ?= @shouldAutoIndent() @@ -467,8 +455,7 @@ class Editor extends Model # Public: Indents the current line. # - # * options - # + A set of options equivalent to {Selection.indent}. + # options - A set of options equivalent to {Selection.indent}. indent: (options={})-> options.autoIndent ?= @shouldAutoIndent() @mutateSelectedText (selection) -> selection.indent(options) @@ -519,7 +506,7 @@ class Editor extends Model # # If the language doesn't have comments, nothing happens. # - # Returns an {Array} of the commented {Ranges}. + # Returns an {Array} of the commented {Range}s. toggleLineCommentsInSelection: -> @mutateSelectedText (selection) -> selection.toggleLineComments() @@ -556,8 +543,7 @@ class Editor extends Model # Public: Pastes the text in the clipboard. # - # * options: - # + A set of options equivalent to {Selection.insertText}. + # options - A set of options equivalent to {Selection.insertText}. pasteText: (options={}) -> {text, metadata} = atom.clipboard.readWithMetadata() @@ -737,11 +723,9 @@ class Editor extends Model @setCursorScreenPosition(@getCursorScreenPosition().translate([1])) @foldCurrentRow() if cursorRowFolded - # Private: mutateSelectedText: (fn) -> @transact => fn(selection) for selection in @getSelections() - # Private: replaceSelectedText: (options={}, fn) -> {selectWordIfEmpty} = options @mutateSelectedText (selection) -> @@ -785,7 +769,9 @@ class Editor extends Model destroyMarker: (args...) -> @displayBuffer.destroyMarker(args...) - # Public: {Delegates to: DisplayBuffer.getMarkerCount} + # Public: Get the number of markers in this editor's buffer. + # + # Returns a {Number}. getMarkerCount: -> @buffer.getMarkerCount() @@ -824,10 +810,8 @@ class Editor extends Model # Public: Creates a new selection at the given marker. # - # * marker: - # The {DisplayBufferMarker} to highlight - # * options: - # + A hash of options that pertain to the {Selection} constructor. + # marker - The {DisplayBufferMarker} to highlight + # options - An {Object} that pertains to the {Selection} constructor. # # Returns the new {Selection}. addSelection: (marker, options={}) -> @@ -848,10 +832,8 @@ class Editor extends Model # Public: Given a buffer range, this adds a new selection for it. # - # * bufferRange: - # A {Range} in the buffer - # * options: - # + A hash of options for {.markBufferRange} + # bufferRange - A {Range} in the buffer. + # options - An options {Object} for {.markBufferRange}. # # Returns the new {Selection}. addSelectionForBufferRange: (bufferRange, options={}) -> @@ -861,20 +843,16 @@ class Editor extends Model # Public: Given a buffer range, this removes all previous selections and # creates a new selection for it. # - # * bufferRange: - # A {Range} in the buffer - # * options: - # + A hash of options for {.setSelectedBufferRanges} + # bufferRange - A {Range} in the buffer. + # options - An options {Object} for {.setSelectedBufferRanges}. setSelectedBufferRange: (bufferRange, options) -> @setSelectedBufferRanges([bufferRange], options) # Public: Given an array of buffer ranges, this removes all previous # selections and creates new selections for them. # - # * bufferRange: - # A {Range} in the buffer - # * options: - # + A hash of options for {.setSelectedBufferRanges} + # bufferRange - A {Range} in the buffer. + # options - An options {Object} for {.setSelectedBufferRanges}. setSelectedBufferRanges: (bufferRanges, options={}) -> throw new Error("Passed an empty array to setSelectedBufferRanges") unless bufferRanges.length @@ -891,7 +869,7 @@ class Editor extends Model # Public: Unselects a given selection. # - # * selection - The {Selection} to remove. + # selection - The {Selection} to remove. removeSelection: (selection) -> _.remove(@selections, selection) @@ -902,9 +880,7 @@ class Editor extends Model @consolidateSelections() @getSelection().clear() - # Public: - # - # Removes all but one cursor (if there are multiple cursors) + # Removes all but one cursor (if there are multiple cursors). consolidateSelections: -> selections = @getSelections() if selections.length > 1 @@ -941,8 +917,7 @@ class Editor extends Model # Public: Determines if a given buffer range is included in a {Selection}. # - # * bufferRange: - # The {Range} you're checking against + # bufferRange - The {Range} you're checking against. # # Returns a {Boolean}. selectionIntersectsBufferRange: (bufferRange) -> @@ -951,10 +926,8 @@ class Editor extends Model # Public: Moves every local cursor to a given screen position. # - # * position: - # An {Array} of two numbers: the screen row, and the screen column. - # * options: - # An object with properties based on {Cursor.setScreenPosition} + # position - An {Array} of two numbers: the screen row, and the screen column. + # options - An {Object} with properties based on {Cursor.setScreenPosition}. setCursorScreenPosition: (position, options) -> @moveCursors (cursor) -> cursor.setScreenPosition(position, options) @@ -973,10 +946,8 @@ class Editor extends Model # Public: Moves every cursor to a given buffer position. # - # * position: - # An {Array} of two numbers: the buffer row, and the buffer column. - # * options: - # + An object with properties based on {Cursor.setBufferPosition} + # position - An {Array} of two numbers: the buffer row, and the buffer column. + # options - An object with properties based on {Cursor.setBufferPosition}. setCursorBufferPosition: (position, options) -> @moveCursors (cursor) -> cursor.setBufferPosition(position, options) @@ -1019,9 +990,8 @@ class Editor extends Model # Public: Returns the word under the most recently added local {Cursor}. # - # * options: - # + An object with properties based on - # {Cursor.getBeginningOfCurrentWordBufferPosition}. + # options - An object with properties based on + # {Cursor.getBeginningOfCurrentWordBufferPosition}. getWordUnderCursor: (options) -> @getTextInBufferRange(@getCursor().getCurrentWordBufferRange(options)) @@ -1089,7 +1059,6 @@ class Editor extends Model moveCursorToNextWordBoundary: -> @moveCursors (cursor) -> cursor.moveToNextWordBoundary() - # Internal: Executes given function on all local cursors. moveCursors: (fn) -> fn(cursor) for cursor in @getCursors() @mergeCursors() @@ -1097,8 +1066,7 @@ class Editor extends Model # Public: Selects the text from the current cursor position to a given screen # position. # - # * position: - # An instance of {Point}, with a given `row` and `column`. + # position - An instance of {Point}, with a given `row` and `column`. selectToScreenPosition: (position) -> lastSelection = @getLastSelection() lastSelection.selectToScreenPosition(position) @@ -1257,8 +1225,6 @@ class Editor extends Model @setSelectedBufferRange(range) range - # Public: - # # FIXME: Not sure how to describe what this does. mergeCursors: -> positions = [] @@ -1269,27 +1235,21 @@ class Editor extends Model else positions.push(position) - # Public: - # # FIXME: Not sure how to describe what this does. expandSelectionsForward: (fn) -> @mergeIntersectingSelections => fn(selection) for selection in @getSelections() - # Public: - # # FIXME: Not sure how to describe what this does. expandSelectionsBackward: (fn) -> @mergeIntersectingSelections isReversed: true, => fn(selection) for selection in @getSelections() - # Public: - # # FIXME: No idea what this does. finalizeSelections: -> selection.finalize() for selection in @getSelections() - # Private: Merges intersecting selections. If passed a function, it executes + # Merges intersecting selections. If passed a function, it executes # the function with merging suppressed, then merges intersecting selections # afterward. mergeIntersectingSelections: (args...) -> @@ -1313,7 +1273,6 @@ class Editor extends Model _.reduce(@getSelections(), reducer, []) - # Private: preserveCursorPositionOnBufferReload: -> cursorPosition = null @subscribe @buffer, "will-reload", => @@ -1334,7 +1293,6 @@ class Editor extends Model reloadGrammar: -> @displayBuffer.reloadGrammar() - # Private: shouldAutoIndent: -> atom.config.get("editor.autoIndent") @@ -1345,32 +1303,24 @@ class Editor extends Model # undo stack remains relevant. transact: (fn) -> @buffer.transact(fn) - # Private: beginTransaction: -> @buffer.beginTransaction() - # Private: commitTransaction: -> @buffer.commitTransaction() - # Private: abortTransaction: -> @buffer.abortTransaction() - # Private: inspect: -> "