Merge branch 'master' into ns-fix-softwrap

Conflicts:
	src/row-map.coffee
This commit is contained in:
Nathan Sobo
2014-02-14 08:36:58 -07:00
117 changed files with 2967 additions and 2470 deletions

View File

@@ -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) ->
```

View File

@@ -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",
@@ -25,15 +25,13 @@
"grunt-peg": "~1.1.0",
"grunt-shell": "~0.3.1",
"harmony-collections": "~0.3.8",
"js-yaml": "~2.1.0",
"json-front-matter": "~0.1.3",
"rcedit": "~0.1.2",
"request": "~2.27.0",
"rimraf": "~2.2.2",
"runas": "~0.3.0",
"runas": "0.5.x",
"underscore-plus": "1.x",
"unzip": "~0.1.9",
"vm-compatibility-layer": "~0.1.0",
"walkdir": "0.0.7"
"vm-compatibility-layer": "~0.1.0"
}
}

View File

@@ -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) ->

View File

@@ -1,102 +0,0 @@
path = require 'path'
_ = require 'underscore-plus'
fs = require 'fs-plus'
{ScopeSelector} = require 'first-mate'
module.exports = (grunt) ->
grunt.registerTask 'convert-theme', 'Convert a TextMate theme to an Atom theme', ->
if textMateThemePath = grunt.option('path')
textMateThemePath = path.resolve(textMateThemePath)
if grunt.file.isFile(textMateThemePath)
textMateTheme = new TextMateTheme(textMateThemePath)
themeName = path.basename(textMateThemePath, path.extname(textMateThemePath))
atomThemePath = path.join(path.dirname(textMateThemePath), "#{themeName.toLowerCase()}-syntax.css")
grunt.file.write(atomThemePath, textMateTheme.getStylesheet())
grunt.log.ok("Atom theme written to: #{atomThemePath}")
else
grunt.log.error("No theme file found at: #{textMateThemePath}")
false
else
grunt.log.error('Must specify --path=<path to TextMate theme>')
false
class TextMateTheme
constructor: (@path) ->
@rulesets = []
@buildRulesets()
buildRulesets: ->
{settings} = fs.readPlistSync(@path)
@buildGlobalSettingsRulesets(settings[0])
@buildScopeSelectorRulesets(settings[1..])
getStylesheet: ->
lines = []
for {selector, properties} in @getRulesets()
lines.push("#{selector} {")
lines.push " #{name}: #{value};" for name, value of properties
lines.push("}\n")
lines.join('\n')
getRulesets: -> @rulesets
buildGlobalSettingsRulesets: ({settings}) ->
{ background, foreground, caret, selection, lineHighlight } = settings
@rulesets.push
selector: '.editor, .editor .gutter'
properties:
'background-color': @translateColor(background)
'color': @translateColor(foreground)
@rulesets.push
selector: '.editor.is-focused .cursor'
properties:
'border-color': @translateColor(caret)
@rulesets.push
selector: '.editor.is-focused .selection .region'
properties:
'background-color': @translateColor(selection)
@rulesets.push
selector: '.editor.is-focused .line-number.cursor-line-no-selection, .editor.is-focused .line.cursor-line'
properties:
'background-color': @translateColor(lineHighlight)
buildScopeSelectorRulesets: (scopeSelectorSettings) ->
for { name, scope, settings } in scopeSelectorSettings
continue unless scope
@rulesets.push
comment: name
selector: @translateScopeSelector(scope)
properties: @translateScopeSelectorSettings(settings)
translateScopeSelector: (textmateScopeSelector) ->
new ScopeSelector(textmateScopeSelector).toCssSelector()
translateScopeSelectorSettings: ({ foreground, background, fontStyle }) ->
properties = {}
if fontStyle
fontStyles = fontStyle.split(/\s+/)
properties['font-weight'] = 'bold' if _.contains(fontStyles, 'bold')
properties['font-style'] = 'italic' if _.contains(fontStyles, 'italic')
properties['text-decoration'] = 'underline' if _.contains(fontStyles, 'underline')
properties['color'] = @translateColor(foreground) if foreground
properties['background-color'] = @translateColor(background) if background
properties
translateColor: (textmateColor) ->
if textmateColor.length <= 7
textmateColor
else
r = parseInt(textmateColor[1..2], 16)
g = parseInt(textmateColor[3..4], 16)
b = parseInt(textmateColor[5..6], 16)
a = parseInt(textmateColor[7..8], 16)
a = Math.round((a / 255.0) * 100) / 100
"rgba(#{r}, #{g}, #{b}, #{a})"

View File

@@ -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)

View File

@@ -1,24 +1,20 @@
path = require 'path'
runas = null
module.exports = (grunt) ->
{cp, mkdir, rm, spawn} = require('./task-helpers')(grunt)
{cp, mkdir, rm} = require('./task-helpers')(grunt)
grunt.registerTask 'install', 'Install the built application', ->
installDir = grunt.config.get('atom.installDir')
shellAppDir = grunt.config.get('atom.shellAppDir')
if process.platform is 'win32'
done = @async()
runas = require 'runas'
runas ?= require 'runas'
copyFolder = path.resolve 'script', 'copy-folder.cmd'
# cmd /c ""script" "source" "destination""
arg = "/c \"\"#{copyFolder}\" \"#{shellAppDir}\" \"#{installDir}\"\""
if runas('cmd', [arg], hide: true) isnt 0
done("Failed to copy #{shellAppDir} to #{installDir}")
if runas('cmd', ['/c', copyFolder, shellAppDir, installDir], admin: true) isnt 0
grunt.log.error("Failed to copy #{shellAppDir} to #{installDir}")
createShortcut = path.resolve 'script', 'create-shortcut.cmd'
args = ['/c', createShortcut, path.join(installDir, 'atom.exe'), 'Atom']
spawn {cmd: 'cmd', args}, done
runas('cmd', ['/c', createShortcut, path.join(installDir, 'atom.exe'), 'Atom'])
else
rm installDir
mkdir path.dirname(installDir)

View File

@@ -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}.")

View File

@@ -0,0 +1,22 @@
path = require 'path'
module.exports = (grunt) ->
grunt.registerTask 'update-octicons', 'Update octicon font and LESS variables', ->
pathToOcticons = path.resolve('..', 'octicons')
if grunt.file.isDir(pathToOcticons)
# Copy font-file
fontSrc = path.join(pathToOcticons, 'octicons', 'octicons.woff')
fontDest = path.resolve('static', 'octicons.woff')
grunt.file.copy(fontSrc, fontDest)
# Update Octicon UTF codes
glyphsSrc = path.join(pathToOcticons, 'data', 'glyphs.yml')
output = []
for {css, code} in grunt.file.readYAML(glyphsSrc)
output.push "@#{css}: \"\\#{code}\";"
octiconUtfDest = path.resolve('static', 'variables', 'octicon-utf-codes.less')
grunt.file.write(octiconUtfDest, "#{output.join('\n')}\n")
else
grunt.log.error("octicons repo must be cloned to #{pathToOcticons}")
false

View File

@@ -71,27 +71,3 @@ will only attempt to call deserialize if the two versions match, and otherwise
return undefined. We plan on implementing a migration system in the future, but
this at least protects you from improperly deserializing old state. If you find
yourself in dire need of the migration system, let us know.
### Deferred Package Deserializers
If your package defers loading on startup with an `activationEvents` property in
its `package.cson`, your deserializers won't be loaded until your package is
activated. If you want to deserialize an object from your package on startup,
this could be a problem.
The solution is to also supply a `deferredDeserializers` array in your
`package.cson` with the names of all your deserializers. When Atom attempts to
deserialize some state whose `deserializer` matches one of these names, it will
load your package first so it can register any necessary deserializers before
proceeding.
For example, the markdown preview package doesn't fully load until a preview is
triggered. But if you refresh a window with a preview pane, it loads the
markdown package early so Atom can deserialize the view correctly.
```coffee-script
# markdown-preview/package.cson
'activationEvents': 'markdown-preview:toggle': '.editor'
'deferredDeserializers': ['MarkdownPreviewView']
...
```

View File

@@ -0,0 +1,52 @@
## 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!
### Further Reading
* Check out [Publishing a Package](publish-a-package.html) for more information
on publishing the package you just created 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

View File

@@ -0,0 +1,68 @@
## 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!
### Further Reading
* Check out [Publishing a Package](publish-a-package.html) for more information
on publishing the theme you just created 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

View File

@@ -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

View File

@@ -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.

View File

@@ -0,0 +1,97 @@
## Publishing a Package
This guide will show you how to publish a package or theme to the
[atom.io][atomio] package registry.
Publishing a package allows other people to install it and use it in Atom. It
is a great way to share what you've made and get feedback and contributions from
others.
This guide assumes your package's name is `my-package` and but you should pick a
better name.
### Install apm
The `apm` command line utility that ships with Atom supports publishing packages
to the atom.io registry.
Check that you have `apm` installed by running the following command in your
terminal:
```sh
apm help publish
```
You should see a message print out with details about the `apm publish` command.
If you do not, launch Atom and run the _Atom > Install Shell Commmands_ menu
to install the `apm` and `atom` commands.
### Prepare Your Package
If you've followed the steps in the [your first package][your-first-package]
doc then you should be ready to publish and you can skip to the next step.
If not, there are a few things you should check before publishing:
* Your *package.json* file has `name`, `description`, and `repository` fields.
* Your *package.json* file has a `version` field with a value of `"0.0.0"`.
* Your *package.json* file has an `engines` field that contains an entry
for Atom such as: `"engines": {"atom": ">=0.50.0"}`.
* Your package has a `README.md` file at the root.
* Your package is in a Git repository that has been pushed to
[GitHub][github]. Follow [this guide][repo-guide] if your package isn't
already on GitHub.
### Publish Your Package
Before you publish a package it is a good idea to check ahead of time if
a package with the same name has already been published to atom.io. You can do
that by visiting `http://atom.io/packages/my-package` to see if the package
already exists. If it does, update your package's name to something that is
available before proceeding.
Now let's review what the `apm publish` command does:
1. Registers the package name on atom.io if it is being published for the
first time.
2. Updates the `version` field in the *package.json* file and commits it.
3. Creates a new [Git tag][git-tag] for the version being published.
4. Pushes the tag and current branch up to GitHub.
5. Updates atom.io with the new version being published.
Now run the following commands to publish your package:
```sh
cd ~/github/my-package
apm publish minor
```
If this is the first package you are publishing, the `apm publish` command may
prompt you for your GitHub username and password. This is required to publish
and you only need to enter this information the first time you publish. The
credentials are stored securely in your [keychain][keychain] once you login.
:tada: Your package is now published and available on atom.io. Head on over to
`http://atom.io/packages/my-package` to see your package's page.
The `minor` option to the publish command tells apm to increment the second
digit of the version before publishing so the published version will be `0.1.0`
and the Git tag created will be `v0.1.0`.
In the future you can run `apm publish major` to publish the `1.0.0` version but
since this was the first version being published it is a good idead to start
with a minor release.
### Further Reading
* Check out [semantic versioning][semver] to learn more about versioning your
package releases.
[atomio]: https://atom.io
[github]: https://github.com
[git-tag]: http://git-scm.com/book/en/Git-Basics-Tagging
[keychain]: http://en.wikipedia.org/wiki/Keychain_(Apple)
[repo-guide]: http://guides.github.com/overviews/desktop
[semver]: http://semver.org
[your-first-package]: your-first-package.html

View File

@@ -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,17 +26,17 @@ 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 =
activate: ->
module.exports =
activate: ->
```
## Create a Command
@@ -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,25 +107,25 @@ 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:
```json
"dependencies": {
"figlet": "1.0.8"
}
"dependencies": {
"figlet": "1.0.8"
}
```
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,7 +141,15 @@ 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
Package](creating-a-package.html)
* [Getting your project on GitHub guide](http://guides.github.com/overviews/desktop)
* [Creating a package guide](creating-a-package.html) for more information
on the mechanics of packages
* [Publishing a package guide](publish-a-package.html) for more information
on publishing your package to [atom.io](https://atom.io)

14
dot-atom/init.coffee Normal file
View File

@@ -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)

View File

@@ -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'
#

View File

@@ -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:

View File

@@ -1 +0,0 @@
# For more on how to configure atom open `~/github/atom/docs/configuring-and-extending.md`

View File

@@ -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'

View File

@@ -68,6 +68,8 @@
'cmd-=': 'window:increase-font-size'
'cmd-+': 'window:increase-font-size'
'cmd--': 'window:decrease-font-size'
'cmd-_': 'window:decrease-font-size'
'cmd-0': 'window:reset-font-size'
'cmd-k up': 'pane:split-up' # Atom Specific
'cmd-k down': 'pane:split-down' # Atom Specific
@@ -75,8 +77,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'
@@ -118,7 +124,6 @@
'alt-cmd-z': 'editor:checkout-head-revision'
'cmd-<': 'editor:scroll-to-cursor'
'alt-cmd-ctrl-f': 'editor:fold-selection'
'cmd-=': 'editor:auto-indent'
# Sublime Parity
'cmd-enter': 'editor:newline-below'

View File

@@ -40,6 +40,8 @@
'ctrl-=': 'window:increase-font-size'
'ctrl-+': 'window:increase-font-size'
'ctrl--': 'window:decrease-font-size'
'ctrl-_': 'window:decrease-font-size'
'ctrl-0': 'window:reset-font-size'
'ctrl-k up': 'pane:split-up' # Atom Specific
'ctrl-k down': 'pane:split-down' # Atom Specific
@@ -47,8 +49,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
@@ -65,7 +71,6 @@
'alt-ctrl-z': 'editor:checkout-head-revision'
'ctrl-<': 'editor:scroll-to-cursor'
'alt-ctrl-f': 'editor:fold-selection'
'ctrl-=': 'editor:auto-indent'
# Sublime Parity
'ctrl-enter': 'editor:newline-below'

View File

@@ -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', visible: false}
{ 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' }

View File

@@ -1,7 +1,7 @@
{
"name": "atom",
"productName": "Atom",
"version": "0.49.0",
"version": "0.51.0",
"main": "./src/browser/main.js",
"repository": {
"type": "git",
@@ -16,7 +16,7 @@
"url": "http://github.com/atom/atom/raw/master/LICENSE.md"
}
],
"atomShellVersion": "0.8.7",
"atomShellVersion": "0.9.2",
"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,102 +42,103 @@
"pathwatcher": "0.14.2",
"pegjs": "0.8.0",
"property-accessors": "1.x",
"q": "0.9.7",
"scandal": "0.13.0",
"q": "1.0.x",
"runas": "0.5.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"
},
"packageDependencies": {
"atom-dark-syntax": "0.12.0",
"atom-dark-ui": "0.21.0",
"atom-light-syntax": "0.12.0",
"atom-light-ui": "0.20.0",
"base16-tomorrow-dark-theme": "0.10.0",
"solarized-dark-syntax": "0.8.0",
"solarized-light-syntax": "0.4.0",
"archive-view": "0.21.0",
"autocomplete": "0.21.0",
"autoflow": "0.12.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",
"atom-dark-syntax": "0.13.0",
"atom-dark-ui": "0.22.0",
"atom-light-syntax": "0.13.0",
"atom-light-ui": "0.21.0",
"base16-tomorrow-dark-theme": "0.11.0",
"solarized-dark-syntax": "0.9.0",
"solarized-light-syntax": "0.5.0",
"archive-view": "0.22.0",
"autocomplete": "0.22.0",
"autoflow": "0.14.0",
"autosave": "0.11.0",
"background-tips": "0.7.0",
"bookmarks": "0.19.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",
"find-and-replace": "0.81.0",
"fuzzy-finder": "0.32.0",
"gists": "0.15.0",
"git-diff": "0.23.0",
"github-sign-in": "0.18.0",
"feedback": "0.23.0",
"find-and-replace": "0.83.0",
"fuzzy-finder": "0.34.0",
"gists": "0.17.0",
"git-diff": "0.24.0",
"github-sign-in": "0.19.0",
"go-to-line": "0.16.0",
"grammar-selector": "0.18.0",
"image-view": "0.17.0",
"keybinding-resolver": "0.9.0",
"link": "0.15.0",
"markdown-preview": "0.25.1",
"metrics": "0.24.0",
"package-generator": "0.25.0",
"release-notes": "0.17.0",
"settings-view": "0.63.0",
"snippets": "0.24.0",
"spell-check": "0.21.0",
"grammar-selector": "0.19.0",
"image-view": "0.23.0",
"keybinding-resolver": "0.10.0",
"link": "0.17.0",
"markdown-preview": "0.29.0",
"metrics": "0.26.0",
"package-generator": "0.26.0",
"release-notes": "0.20.0",
"settings-view": "0.72.0",
"snippets": "0.27.0",
"spell-check": "0.24.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.18.0",
"tree-view": "0.65.0",
"update-package-dependencies": "0.2.0",
"visual-bell": "0.6.0",
"to-the-hubs": "0.19.0",
"tree-view": "0.69.0",
"update-package-dependencies": "0.3.0",
"visual-bell": "0.7.0",
"welcome": "0.4.0",
"whitespace": "0.10.0",
"wrap-guide": "0.12.0",
"language-c": "0.2.0",
"whitespace": "0.12.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.16.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-toml": "0.7.0",
"language-xml": "0.2.0",
"language-yaml": "0.1.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.8.0",
"language-xml": "0.3.0",
"language-yaml": "0.2.0"
},
"private": true,
"scripts": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 KiB

After

Width:  |  Height:  |  Size: 361 KiB

View File

@@ -1,32 +0,0 @@
#!/usr/bin/env coffee
usage = """
Usage:
update-octicons PATH-TO-OCTICONS
"""
path = require 'path'
fs = require 'fs'
YAML = require 'js-yaml'
scriptPath = process.argv[1]
pathToOcticons = process.argv[2] ? path.join(process.env.HOME, 'github', 'octicons')
atomDir = path.resolve(scriptPath, "../../..")
unless fs.existsSync(pathToOcticons)
console.error(usage)
process.exit(1)
# Copy font-file
fontSrc = path.join(pathToOcticons, 'octicons', 'octicons.woff')
fontDest = path.join(atomDir, 'static', 'octicons.woff')
fs.createReadStream(fontSrc).pipe(fs.createWriteStream(fontDest))
# Update Octicon UTF codes
glyphsSrc = path.join(pathToOcticons, 'data', 'glyphs.yml')
octiconUtfDest = path.join atomDir, 'static', 'variables', 'octicon-utf-codes.less'
output = []
for {css, code} in YAML.load(fs.readFileSync(glyphsSrc).toString())
output.push "@#{css}: \"\\#{code}\";"
fs.writeFileSync octiconUtfDest, "#{output.join('\n')}\n"

View File

@@ -1,35 +1,49 @@
{View, $, $$} = require '../src/space-pen-extensions'
path = require 'path'
_ = require 'underscore-plus'
{convertStackTrace} = require 'coffeestack'
{View, $, $$} = require '../src/space-pen-extensions'
sourceMaps = {}
formatStackTrace = (stackTrace) ->
formatStackTrace = (message='', 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*$/
firstJasmineLinePattern = /^\s*at \/.*\/jasmine(-[^\/]*)?\.js:\d+:\d+\)?\s*$/
convertedLines = []
for line in stackTrace.split('\n')
convertedLines.push(line) unless jasminePattern.test(line)
break if firstJasmineLinePattern.test(line)
convertStackTrace(convertedLines.join('\n'), sourceMaps)
stackTrace = convertStackTrace(convertedLines.join('\n'), sourceMaps)
lines = stackTrace.split('\n')
# Remove first line of stack when it is the same as the error message
errorMatch = lines[0]?.match(/^Error: (.*)/)
lines.shift() if message.trim() is errorMatch?[1]?.trim()
# Remove prefix of lines matching: at [object Object].<anonymous> (path:1:2)
for line, index in lines
prefixMatch = line.match(/at \[object Object\]\.<anonymous> \(([^\)]+)\)/)
lines[index] = "at #{prefixMatch[1]}" if prefixMatch
lines = lines.map (line) -> line.trim()
lines.join('\n')
module.exports =
class AtomReporter extends View
@content: ->
@div id: 'HTMLReporter', class: 'jasmine_reporter', =>
@div outlet: 'specPopup', class: "spec-popup"
@div class: 'spec-reporter', =>
@div outlet: "suites"
@div outlet: 'coreArea', =>
@div outlet: 'coreHeader', class: 'symbolHeader'
@ul outlet: 'coreSummary', class: 'symbolSummary list-unstyled'
@div outlet: 'bundledArea', =>
@div outlet: 'bundledHeader', class: 'symbolHeader'
@ul outlet: 'bundledSummary', class: 'symbolSummary list-unstyled'
@div outlet: 'userArea', =>
@div outlet: 'userHeader', class: 'symbolHeader'
@ul outlet: 'userSummary', class: 'symbolSummary list-unstyled'
@div outlet: "status", class: 'status', =>
@div outlet: 'coreArea', class: 'symbol-area', =>
@div outlet: 'coreHeader', class: 'symbol-header'
@ul outlet: 'coreSummary', class: 'symbol-summary list-unstyled'
@div outlet: 'bundledArea', class: 'symbol-area', =>
@div outlet: 'bundledHeader', class: 'symbol-header'
@ul outlet: 'bundledSummary', class: 'symbol-summary list-unstyled'
@div outlet: 'userArea', class: 'symbol-area', =>
@div outlet: 'userHeader', class: 'symbol-header'
@ul outlet: 'userSummary', class: 'symbol-summary list-unstyled'
@div outlet: "status", class: 'status alert alert-info', =>
@div outlet: "time", class: 'time'
@div outlet: "specCount", class: 'spec-count'
@div outlet: "message", class: 'message'
@@ -46,7 +60,7 @@ class AtomReporter extends View
reportRunnerStarting: (runner) ->
@handleEvents()
@startedAt = new Date()
@startedAt = Date.now()
specs = runner.specs()
@totalSpecCount = specs.length
@addSpecs(specs)
@@ -54,57 +68,29 @@ class AtomReporter extends View
reportRunnerResults: (runner) ->
@updateSpecCounts()
if @failedCount == 0
@message.text "Success!"
@status.addClass('alert-success').removeClass('alert-info') if @failedCount is 0
if @failedCount is 1
@message.text "#{@failedCount} failure"
else
@message.text "Game Over"
@message.text "#{@failedCount} failures"
reportSuiteResults: (suite) ->
reportSpecResults: (spec) ->
@completeSpecCount++
spec.endedAt = new Date().getTime()
spec.endedAt = Date.now()
@specComplete(spec)
@updateStatusView(spec)
reportSpecStarting: (spec) ->
@specStarted(spec)
specFilter: (spec) ->
globalFocusPriority = jasmine.getEnv().focusPriority
parent = spec.parentSuite ? spec.suite
if !globalFocusPriority
true
else if spec.focusPriority >= globalFocusPriority
true
else if not parent
false
else
@specFilter(parent)
handleEvents: ->
$(document).on "mouseover", ".spec-summary", ({currentTarget}) =>
element = $(currentTarget)
description = element.data("description")
return unless description
clearTimeout @timeoutId if @timeoutId?
@specPopup.show()
spec = _.find(window.timedSpecs, ({fullName}) -> description is fullName)
description = "#{description} #{spec.time}ms" if spec
@specPopup.text description
{left, top} = element.offset()
left += 20
top += 20
@specPopup.offset({left, top})
@timeoutId = setTimeout((=> @specPopup.hide()), 3000)
$(document).on "click", ".spec-toggle", ({currentTarget}) =>
element = $(currentTarget)
specFailures = element.parent().find('.spec-failures')
specFailures.toggle()
if specFailures.is(":visible") then element.text "\uf03d" else element.html "\uf03f"
element.toggleClass('folded')
false
updateSpecCounts: ->
@@ -116,7 +102,7 @@ class AtomReporter extends View
updateStatusView: (spec) ->
if @failedCount > 0
@status.addClass('failed') unless @status.hasClass('failed')
@status.addClass('alert-danger').removeClass('alert-info')
@updateSpecCounts()
@@ -124,7 +110,7 @@ class AtomReporter extends View
rootSuite = rootSuite.parentSuite while rootSuite.parentSuite
@message.text rootSuite.description
time = "#{Math.round((spec.endedAt - @startedAt.getTime()) / 10)}"
time = "#{Math.round((spec.endedAt - @startedAt) / 10)}"
time = "0#{time}" if time.length < 3
@time[0].textContent = "#{time[0...-2]}.#{time[-2..]}s"
@@ -146,15 +132,22 @@ class AtomReporter extends View
@userSummary.append symbol
if coreSpecs > 0
@coreHeader.text("Core Specs (#{coreSpecs}):")
@coreHeader.text("Core Specs (#{coreSpecs})")
else
@coreArea.hide()
if bundledPackageSpecs > 0
@bundledHeader.text("Bundled Package Specs (#{bundledPackageSpecs}):")
@bundledHeader.text("Bundled Package Specs (#{bundledPackageSpecs})")
else
@bundledArea.hide()
if userPackageSpecs > 0
@userHeader.text("User Package Specs (#{userPackageSpecs}):")
if coreSpecs is 0 and bundledPackageSpecs is 0
# Package specs being run, show a more descriptive label
{specDirectory} = specs[0]
packageFolderName = path.basename(path.dirname(specDirectory))
packageName = _.undasherize(_.uncamelcase(packageFolderName))
@userHeader.text("#{packageName} Specs")
else
@userHeader.text("User Package Specs (#{userPackageSpecs})")
else
@userArea.hide()
@@ -164,7 +157,7 @@ class AtomReporter extends View
specComplete: (spec) ->
specSummaryElement = $("#spec-summary-#{spec.id}")
specSummaryElement.removeClass('pending')
specSummaryElement.data("description", spec.getFullName())
specSummaryElement.setTooltip(title: spec.getFullName(), container: '.spec-reporter')
results = spec.results()
if results.skipped
@@ -185,11 +178,9 @@ class SuiteResultView extends View
@div class: 'suite', =>
@div outlet: 'description', class: 'description'
suite: null
initialize: (@suite) ->
@attr('id', "suite-view-#{@suite.id}")
@description.html @suite.description
@description.text(@suite.description)
attach: ->
(@parentSuiteView() or $('.results')).append this
@@ -206,20 +197,22 @@ class SuiteResultView extends View
class SpecResultView extends View
@content: ->
@div class: 'spec', =>
@div "\uf03d", class: 'spec-toggle'
@div class: 'spec-toggle'
@div outlet: 'description', class: 'description'
@div outlet: 'specFailures', class: 'spec-failures'
spec: null
initialize: (@spec) ->
@addClass("spec-view-#{@spec.id}")
@description.html @spec.description
description = @spec.description
description = "it #{description}" if description.indexOf('it ') isnt 0
@description.text(description)
for result in @spec.results().getItems() when not result.passed()
stackTrace = formatStackTrace(result.trace.stack)
stackTrace = formatStackTrace(result.message, result.trace.stack)
@specFailures.append $$ ->
@div result.message, class: 'resultMessage fail'
@div stackTrace, class: 'stackTrace' if stackTrace
@div result.message, class: 'result-message fail'
@pre stackTrace, class: 'stack-trace padded' if stackTrace
attach: ->
@parentSuiteView().append this

View File

@@ -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", ->
@@ -9,35 +10,28 @@ describe "the `atom` global", ->
describe "package lifecycle methods", ->
describe ".loadPackage(name)", ->
describe "when the package has deferred deserializers", ->
it "requires the package's main module if one of its deferred deserializers is referenced", ->
pack = atom.packages.loadPackage('package-with-activation-events')
spyOn(pack, 'activateStylesheets').andCallThrough()
expect(pack.mainModule).toBeNull()
object = atom.deserializers.deserialize({deserializer: 'Foo', data: 5})
expect(pack.mainModule).toBeDefined()
expect(object.constructor.name).toBe 'Foo'
expect(object.data).toBe 5
expect(pack.activateStylesheets).toHaveBeenCalled()
it "continues if the package has an invalid package.json", ->
spyOn(console, 'warn')
atom.config.set("core.disabledPackages", [])
expect(-> atom.packages.loadPackage("package-with-broken-package-json")).not.toThrow()
it "continues if the package has an invalid package.json", ->
spyOn(console, 'warn')
atom.config.set("core.disabledPackages", [])
expect(-> atom.packages.loadPackage("package-with-broken-package-json")).not.toThrow()
it "continues if the package has an invalid keymap", ->
atom.config.set("core.disabledPackages", [])
expect(-> atom.packages.loadPackage("package-with-broken-keymap")).not.toThrow()
it "continues if the package has an invalid keymap", ->
atom.config.set("core.disabledPackages", [])
expect(-> atom.packages.loadPackage("package-with-broken-keymap")).not.toThrow()
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", ->
@@ -54,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()
@@ -78,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()
@@ -116,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", [])
@@ -245,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$'
@@ -275,80 +304,118 @@ 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')
atom.packages.activatePackage('package-with-serialize-error', immediate: true)
atom.packages.activatePackage('package-with-serialization', immediate: true)
atom.packages.deactivatePackages()
expect(atom.packages.packageStates['package-with-serialize-error']).toBeUndefined()
expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1
expect(console.error).toHaveBeenCalled()
waitsForPromise ->
atom.packages.activatePackage('package-with-serialize-error')
waitsForPromise ->
atom.packages.activatePackage('package-with-serialization')
runs ->
atom.packages.deactivatePackages()
expect(atom.packages.packageStates['package-with-serialize-error']).toBeUndefined()
expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1
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", ->
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"
atom.packages.deactivatePackage('language-ruby')
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Null Grammar"
waitsForPromise ->
atom.packages.activatePackage('language-ruby')
runs ->
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Ruby"
atom.packages.deactivatePackage('language-ruby')
expect(atom.syntax.selectGrammar("file.rb").name).toBe "Null Grammar"
it "removes the package's scoped properties", ->
atom.packages.activatePackage('language-ruby', sync: true)
atom.packages.deactivatePackage('language-ruby')
expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBeUndefined()
waitsForPromise ->
atom.packages.activatePackage('language-ruby')
runs ->
atom.packages.deactivatePackage('language-ruby')
expect(atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')).toBeUndefined()
describe ".activate()", ->
packageActivator = null
@@ -382,7 +449,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'
@@ -391,28 +458,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()
@@ -426,18 +501,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", ->

View File

@@ -0,0 +1,12 @@
describe "Clipboard", ->
describe "write(text, metadata) and read()", ->
it "writes and reads text to/from the native clipboard", ->
expect(atom.clipboard.read()).toBe 'initial clipboard content'
atom.clipboard.write('next')
expect(atom.clipboard.read()).toBe 'next'
it "returns metadata if the item on the native clipboard matches the last written item", ->
atom.clipboard.write('next', {meta: 'data'})
expect(atom.clipboard.read()).toBe 'next'
expect(atom.clipboard.readWithMetadata().text).toBe 'next'
expect(atom.clipboard.readWithMetadata().metadata).toEqual {meta: 'data'}

View File

@@ -20,7 +20,7 @@ describe "install(commandPath, callback)", ->
installDone = false
installError = null
installer.install commandFilePath, (error) ->
installer.install commandFilePath, false, (error) ->
installDone = true
installError = error

View File

@@ -63,6 +63,19 @@ describe "Config", ->
atom.config.toggle('foo.a')
expect(atom.config.get('foo.a')).toBe false
describe ".restoreDefault(keyPath)", ->
it "sets the value of the key path to its default", ->
atom.config.setDefaults('a', b: 3)
atom.config.set('a.b', 4)
expect(atom.config.get('a.b')).toBe 4
atom.config.restoreDefault('a.b')
expect(atom.config.get('a.b')).toBe 3
atom.config.set('a.c', 5)
expect(atom.config.get('a.c')).toBe 5
atom.config.restoreDefault('a.c')
expect(atom.config.get('a.c')).toBeUndefined()
describe ".pushAtKeyPath(keyPath, value)", ->
it "pushes the given value to the array at the key path and updates observers", ->
atom.config.set("foo.bar.baz", ["a"])
@@ -220,6 +233,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 ->

View File

@@ -5,12 +5,15 @@ describe "DisplayBuffer", ->
[displayBuffer, buffer, changeHandler, tabLength] = []
beforeEach ->
tabLength = 2
atom.packages.activatePackage('language-javascript', sync: true)
buffer = atom.project.bufferForPathSync('sample.js')
displayBuffer = new DisplayBuffer({buffer, tabLength})
changeHandler = jasmine.createSpy 'changeHandler'
displayBuffer.on 'changed', changeHandler
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
afterEach ->
displayBuffer.destroy()
buffer.release()

View File

@@ -16,11 +16,13 @@ describe "Editor", ->
describe "with default options", ->
beforeEach ->
atom.packages.activatePackage('language-javascript', sync: true)
editor = atom.project.openSync('sample.js', autoIndent: false)
buffer = editor.buffer
lineLengths = buffer.getLines().map (line) -> line.length
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
describe "when the editor is deserialized", ->
it "restores selections and folds based on markers in the buffer", ->
editor.setSelectedBufferRange([[1, 2], [3, 4]])
@@ -1856,12 +1858,12 @@ describe "Editor", ->
expect(editor.getCursorBufferPosition()).toEqual [0, 2]
expect(editor.getCursorScreenPosition()).toEqual [0, editor.getTabLength() * 2]
describe "pasteboard operations", ->
describe "clipboard operations", ->
beforeEach ->
editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]])
describe ".cutSelectedText()", ->
it "removes the selected text from the buffer and places it on the pasteboard", ->
it "removes the selected text from the buffer and places it on the clipboard", ->
editor.cutSelectedText()
expect(buffer.lineForRow(0)).toBe "var = function () {"
expect(buffer.lineForRow(1)).toBe " var = function(items) {"
@@ -1885,7 +1887,7 @@ describe "Editor", ->
editor.cutToEndOfLine()
expect(buffer.lineForRow(2)).toBe ' if (items.length'
expect(buffer.lineForRow(3)).toBe ' var pivot = item'
expect(atom.pasteboard.read()[0]).toBe ' <= 1) return items;\ns.shift(), current, left = [], right = [];'
expect(atom.clipboard.read()).toBe ' <= 1) return items;\ns.shift(), current, left = [], right = [];'
describe "when text is selected", ->
it "only cuts the selected text, not to the end of the line", ->
@@ -1895,7 +1897,7 @@ describe "Editor", ->
expect(buffer.lineForRow(2)).toBe ' if (items.lengthurn items;'
expect(buffer.lineForRow(3)).toBe ' var pivot = item'
expect(atom.pasteboard.read()[0]).toBe ' <= 1) ret\ns.shift(), current, left = [], right = [];'
expect(atom.clipboard.read()).toBe ' <= 1) ret\ns.shift(), current, left = [], right = [];'
describe ".copySelectedText()", ->
it "copies selected text onto the clipboard", ->
@@ -1906,7 +1908,7 @@ describe "Editor", ->
describe ".pasteText()", ->
it "pastes text into the buffer", ->
atom.pasteboard.write('first')
atom.clipboard.write('first')
editor.pasteText()
expect(editor.buffer.lineForRow(0)).toBe "var first = function () {"
expect(buffer.lineForRow(1)).toBe " var first = function(items) {"
@@ -2733,19 +2735,26 @@ describe "Editor", ->
describe "when the editor's grammar has an injection selector", ->
beforeEach ->
atom.packages.activatePackage('language-text', sync: true)
atom.packages.activatePackage('language-javascript', sync: true)
waitsForPromise ->
atom.packages.activatePackage('language-text')
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
it "includes the grammar's patterns when the selector matches the current scope in other grammars", ->
atom.packages.activatePackage('language-hyperlink', sync: true)
grammar = atom.syntax.selectGrammar("text.js")
{tokens} = grammar.tokenizeLine("var i; // http://github.com")
waitsForPromise ->
atom.packages.activatePackage('language-hyperlink')
expect(tokens[0].value).toBe "var"
expect(tokens[0].scopes).toEqual ["source.js", "storage.modifier.js"]
runs ->
grammar = atom.syntax.selectGrammar("text.js")
{tokens} = grammar.tokenizeLine("var i; // http://github.com")
expect(tokens[6].value).toBe "http://github.com"
expect(tokens[6].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
expect(tokens[0].value).toBe "var"
expect(tokens[0].scopes).toEqual ["source.js", "storage.modifier.js"]
expect(tokens[6].value).toBe "http://github.com"
expect(tokens[6].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
describe "when the grammar is added", ->
it "retokenizes existing buffers that contain tokens that match the injection selector", ->
@@ -2756,11 +2765,13 @@ describe "Editor", ->
expect(tokens[1].value).toBe " http://github.com"
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
atom.packages.activatePackage('language-hyperlink', sync: true)
waitsForPromise ->
atom.packages.activatePackage('language-hyperlink')
{tokens} = editor.lineForScreenRow(0)
expect(tokens[2].value).toBe "http://github.com"
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
runs ->
{tokens} = editor.lineForScreenRow(0)
expect(tokens[2].value).toBe "http://github.com"
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
describe "when the grammar is updated", ->
it "retokenizes existing buffers that contain tokens that match the injection selector", ->
@@ -2771,14 +2782,17 @@ describe "Editor", ->
expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS"
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
atom.packages.activatePackage('package-with-injection-selector', sync: true)
atom.packages.activatePackage('package-with-injection-selector')
{tokens} = editor.lineForScreenRow(0)
expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS"
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
atom.packages.activatePackage('language-sql', sync: true)
waitsForPromise ->
atom.packages.activatePackage('language-sql')
{tokens} = editor.lineForScreenRow(0)
expect(tokens[2].value).toBe "SELECT"
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "keyword.other.DML.sql"]
runs ->
{tokens} = editor.lineForScreenRow(0)
expect(tokens[2].value).toBe "SELECT"
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "keyword.other.DML.sql"]

View File

@@ -10,8 +10,6 @@ describe "EditorView", ->
[buffer, editorView, editor, cachedLineHeight, cachedCharWidth] = []
beforeEach ->
atom.packages.activatePackage('language-text', sync: true)
atom.packages.activatePackage('language-javascript', sync: true)
editor = atom.project.openSync('sample.js')
buffer = editor.buffer
editorView = new EditorView(editor)
@@ -26,6 +24,12 @@ describe "EditorView", ->
@width(getCharWidth() * widthInChars) if widthInChars
$('#jasmine-content').append(this)
waitsForPromise ->
atom.packages.activatePackage('language-text', sync: true)
waitsForPromise ->
atom.packages.activatePackage('language-javascript', sync: true)
getLineHeight = ->
return cachedLineHeight if cachedLineHeight?
calcDimensions()
@@ -2515,9 +2519,9 @@ describe "EditorView", ->
expect(edited).toBe false
describe "when editor:copy-path is triggered", ->
it "copies the absolute path to the editor view's file to the pasteboard", ->
it "copies the absolute path to the editor view's file to the clipboard", ->
editorView.trigger 'editor:copy-path'
expect(atom.pasteboard.read()[0]).toBe editor.getPath()
expect(atom.clipboard.read()).toBe editor.getPath()
describe "when editor:move-line-up is triggered", ->
describe "when there is no selection", ->
@@ -2532,6 +2536,52 @@ describe "EditorView", ->
editorView.trigger 'editor:move-line-up'
expect(editor.getCursorBufferPosition()).toEqual [0,2]
describe "when the line above is folded", ->
it "moves the line around the fold", ->
editor.foldBufferRow(1)
editor.setCursorBufferPosition([10, 0])
editorView.trigger 'editor:move-line-up'
expect(editor.getCursorBufferPosition()).toEqual [1, 0]
expect(buffer.lineForRow(1)).toBe ''
expect(buffer.lineForRow(2)).toBe ' var sort = function(items) {'
expect(editor.isFoldedAtBufferRow(1)).toBe false
expect(editor.isFoldedAtBufferRow(2)).toBe true
describe "when the line being moved is folded", ->
it "moves the fold around the fold above it", ->
editor.setCursorBufferPosition([0, 0])
editor.insertText """
var a = function() {
b = 3;
};
"""
editor.foldBufferRow(0)
editor.foldBufferRow(3)
editor.setCursorBufferPosition([3, 0])
editorView.trigger 'editor:move-line-up'
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {'
expect(buffer.lineForRow(13)).toBe 'var a = function() {'
editor.logScreenLines()
expect(editor.isFoldedAtBufferRow(0)).toBe true
expect(editor.isFoldedAtBufferRow(13)).toBe true
describe "when the line above is empty and the line above that is folded", ->
it "moves the line to the empty line", ->
editor.foldBufferRow(2)
editor.setCursorBufferPosition([11, 0])
editorView.trigger 'editor:move-line-up'
expect(editor.getCursorBufferPosition()).toEqual [10, 0]
expect(buffer.lineForRow(9)).toBe ' };'
expect(buffer.lineForRow(10)).toBe ' return sort(Array.apply(this, arguments));'
expect(buffer.lineForRow(11)).toBe ''
expect(editor.isFoldedAtBufferRow(2)).toBe true
expect(editor.isFoldedAtBufferRow(10)).toBe false
describe "where there is a selection", ->
describe "when the selection falls inside the line", ->
it "maintains the selection", ->
@@ -2631,6 +2681,54 @@ describe "EditorView", ->
editorView.trigger 'editor:move-line-down'
expect(editor.getCursorBufferPosition()).toEqual [1, 2]
describe "when the line below is folded", ->
it "moves the line around the fold", ->
editor.setCursorBufferPosition([0, 0])
editor.foldBufferRow(1)
editorView.trigger 'editor:move-line-down'
expect(editor.getCursorBufferPosition()).toEqual [9, 0]
expect(buffer.lineForRow(0)).toBe ' var sort = function(items) {'
expect(buffer.lineForRow(9)).toBe 'var quicksort = function () {'
expect(editor.isFoldedAtBufferRow(0)).toBe true
expect(editor.isFoldedAtBufferRow(9)).toBe false
describe "when the line being moved is folded", ->
it "moves the fold around the fold below it", ->
editor.setCursorBufferPosition([0, 0])
editor.insertText """
var a = function() {
b = 3;
};
"""
editor.foldBufferRow(0)
editor.foldBufferRow(3)
editor.setCursorBufferPosition([0, 0])
editorView.trigger 'editor:move-line-down'
expect(editor.getCursorBufferPosition()).toEqual [13, 0]
expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {'
expect(buffer.lineForRow(13)).toBe 'var a = function() {'
expect(editor.isFoldedAtBufferRow(0)).toBe true
expect(editor.isFoldedAtBufferRow(13)).toBe true
describe "when the line below is empty and the line below that is folded", ->
it "moves the line to the empty line", ->
editor.setCursorBufferPosition([0, Infinity])
editor.insertText('\n')
editor.setCursorBufferPosition([0, 0])
editor.foldBufferRow(2)
editorView.trigger 'editor:move-line-down'
expect(editor.getCursorBufferPosition()).toEqual [1, 0]
expect(buffer.lineForRow(0)).toBe ''
expect(buffer.lineForRow(1)).toBe 'var quicksort = function () {'
expect(buffer.lineForRow(2)).toBe ' var sort = function(items) {'
expect(editor.isFoldedAtBufferRow(0)).toBe false
expect(editor.isFoldedAtBufferRow(1)).toBe false
expect(editor.isFoldedAtBufferRow(2)).toBe true
describe "when the cursor is on the last line", ->
it "does not move the line", ->
editor.moveCursorToBottom()

View File

@@ -1,2 +1 @@
'activationEvents': ['activation-event']
'deferredDeserializers': ['Foo']

View File

@@ -1,5 +1 @@
# This package loads async, otherwise it would log errors when it
# is automatically serialized when workspaceView is deactivatated
'main': 'index.coffee'
'activationEvents': ['activation-event']

View File

@@ -1,3 +1,3 @@
{
"theme": true
"theme": "ui"
}

View File

@@ -1,3 +1,3 @@
{
"theme": true
"theme": "ui"
}

View File

@@ -1,3 +1,3 @@
{
"theme": true
"theme": "ui"
}

View File

@@ -1,4 +1,4 @@
{
"theme": true,
"theme": "ui",
"stylesheets": ["first.css", "second.less", "last.css"]
}

View File

@@ -1,4 +1,4 @@
{
"theme": true,
"theme": "ui",
"stylesheets": ["editor.less"]
}

View File

@@ -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

View File

@@ -6,10 +6,12 @@ describe "LanguageMode", ->
describe "javascript", ->
beforeEach ->
atom.packages.activatePackage('language-javascript', sync: true)
editor = atom.project.openSync('sample.js', autoIndent: false)
{buffer, languageMode} = editor
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
describe ".minIndentLevelForRowRange(startRow, endRow)", ->
it "returns the minimum indent level for the given row range", ->
expect(languageMode.minIndentLevelForRowRange(4, 7)).toBe 2
@@ -100,10 +102,12 @@ describe "LanguageMode", ->
describe "coffeescript", ->
beforeEach ->
atom.packages.activatePackage('language-coffee-script', sync: true)
editor = atom.project.openSync('coffee.coffee', autoIndent: false)
{buffer, languageMode} = editor
waitsForPromise ->
atom.packages.activatePackage('language-coffee-script')
describe ".toggleLineCommentsForBufferRows(start, end)", ->
it "comments/uncomments lines in the given range", ->
languageMode.toggleLineCommentsForBufferRows(4, 6)
@@ -147,10 +151,12 @@ describe "LanguageMode", ->
describe "css", ->
beforeEach ->
atom.packages.activatePackage('language-css', sync: true)
editor = atom.project.openSync('css.css', autoIndent: false)
{buffer, languageMode} = editor
waitsForPromise ->
atom.packages.activatePackage('language-css')
describe ".toggleLineCommentsForBufferRows(start, end)", ->
it "comments/uncomments lines in the given range", ->
languageMode.toggleLineCommentsForBufferRows(0, 1)
@@ -188,11 +194,15 @@ describe "LanguageMode", ->
describe "less", ->
beforeEach ->
atom.packages.activatePackage('language-less', sync: true)
atom.packages.activatePackage('language-css', sync: true)
editor = atom.project.openSync('sample.less', autoIndent: false)
{buffer, languageMode} = editor
waitsForPromise ->
atom.packages.activatePackage('language-less')
waitsForPromise ->
atom.packages.activatePackage('language-css')
describe "when commenting lines", ->
it "only uses the `commentEnd` pattern if it comes from the same grammar as the `commentStart`", ->
languageMode.toggleLineCommentsForBufferRows(0, 0)
@@ -200,10 +210,12 @@ describe "LanguageMode", ->
describe "folding", ->
beforeEach ->
atom.packages.activatePackage('language-javascript', sync: true)
editor = atom.project.openSync('sample.js', autoIndent: false)
{buffer, languageMode} = editor
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
it "maintains cursor buffer position when a folding/unfolding", ->
editor.setCursorBufferPosition([5,5])
languageMode.foldAll()
@@ -298,10 +310,12 @@ describe "LanguageMode", ->
describe "folding with comments", ->
beforeEach ->
atom.packages.activatePackage('language-javascript', sync: true)
editor = atom.project.openSync('sample-with-comments.js', autoIndent: false)
{buffer, languageMode} = editor
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
describe ".unfoldAll()", ->
it "unfolds every folded line", ->
initialScreenLineCount = editor.getScreenLineCount()
@@ -362,10 +376,12 @@ describe "LanguageMode", ->
describe "css", ->
beforeEach ->
atom.packages.activatePackage('language-source', sync: true)
atom.packages.activatePackage('language-css', sync: true)
editor = atom.project.openSync('css.css', autoIndent: true)
waitsForPromise ->
atom.packages.activatePackage('language-source')
atom.packages.activatePackage('language-css')
describe "suggestedIndentForBufferRow", ->
it "does not return negative values (regression)", ->
editor.setText('.test {\npadding: 0;\n}')

View File

@@ -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'

View File

@@ -1,10 +0,0 @@
describe "Pasteboard", ->
describe "write(text, metadata) and read()", ->
it "writes and reads text to/from the native pasteboard", ->
expect(atom.pasteboard.read()).toEqual ['initial pasteboard content']
atom.pasteboard.write('next')
expect(atom.pasteboard.read()[0]).toBe 'next'
it "returns metadata if the item on the native pasteboard matches the last written item", ->
atom.pasteboard.write('next', {meta: 'data'})
expect(atom.pasteboard.read()).toEqual ['next', {meta: 'data'}]

View File

@@ -72,7 +72,7 @@ describe "Project", ->
expect(atom.project.getEditors()[1]).toBe editor2
describe ".openSync(path)", ->
[fooOpener, barOpener, absolutePath, newBufferHandler, newEditorHandler] = []
[absolutePath, newBufferHandler, newEditorHandler] = []
beforeEach ->
absolutePath = require.resolve('./fixtures/dir/a')
newBufferHandler = jasmine.createSpy('newBufferHandler')
@@ -80,129 +80,92 @@ describe "Project", ->
newEditorHandler = jasmine.createSpy('newEditorHandler')
atom.project.on 'editor-created', newEditorHandler
fooOpener = (pathToOpen, options) -> { foo: pathToOpen, options } if pathToOpen?.match(/\.foo/)
barOpener = (pathToOpen) -> { bar: pathToOpen } if pathToOpen?.match(/^bar:\/\//)
atom.project.registerOpener(fooOpener)
atom.project.registerOpener(barOpener)
describe "when given an absolute path that hasn't been opened previously", ->
it "returns a new edit session for the given path and emits 'buffer-created' and 'editor-created' events", ->
editor = atom.project.openSync(absolutePath)
expect(editor.buffer.getPath()).toBe absolutePath
expect(newBufferHandler).toHaveBeenCalledWith editor.buffer
expect(newEditorHandler).toHaveBeenCalledWith editor
afterEach ->
atom.project.unregisterOpener(fooOpener)
atom.project.unregisterOpener(barOpener)
describe "when given a relative path that hasn't been opened previously", ->
it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'editor-created' events", ->
editor = atom.project.openSync('a')
expect(editor.buffer.getPath()).toBe absolutePath
expect(newBufferHandler).toHaveBeenCalledWith editor.buffer
expect(newEditorHandler).toHaveBeenCalledWith editor
describe "when passed a path that doesn't match a custom opener", ->
describe "when given an absolute path that hasn't been opened previously", ->
it "returns a new edit session for the given path and emits 'buffer-created' and 'editor-created' events", ->
editor = atom.project.openSync(absolutePath)
describe "when passed the path to a buffer that has already been opened", ->
it "returns a new edit session containing previously opened buffer and emits a 'editor-created' event", ->
editor = atom.project.openSync(absolutePath)
newBufferHandler.reset()
expect(atom.project.openSync(absolutePath).buffer).toBe editor.buffer
expect(atom.project.openSync('a').buffer).toBe editor.buffer
expect(newBufferHandler).not.toHaveBeenCalled()
expect(newEditorHandler).toHaveBeenCalledWith editor
describe "when not passed a path", ->
it "returns a new edit session and emits 'buffer-created' and 'editor-created' events", ->
editor = atom.project.openSync()
expect(editor.buffer.getPath()).toBeUndefined()
expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer)
expect(newEditorHandler).toHaveBeenCalledWith editor
describe ".open(path)", ->
[absolutePath, newBufferHandler, newEditorHandler] = []
beforeEach ->
absolutePath = require.resolve('./fixtures/dir/a')
newBufferHandler = jasmine.createSpy('newBufferHandler')
atom.project.on 'buffer-created', newBufferHandler
newEditorHandler = jasmine.createSpy('newEditorHandler')
atom.project.on 'editor-created', newEditorHandler
describe "when given an absolute path that isn't currently open", ->
it "returns a new edit session for the given path and emits 'buffer-created' and 'editor-created' events", ->
editor = null
waitsForPromise ->
atom.project.open(absolutePath).then (o) -> editor = o
runs ->
expect(editor.buffer.getPath()).toBe absolutePath
expect(newBufferHandler).toHaveBeenCalledWith editor.buffer
expect(newEditorHandler).toHaveBeenCalledWith editor
describe "when given a relative path that hasn't been opened previously", ->
it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'editor-created' events", ->
editor = atom.project.openSync('a')
describe "when given a relative path that isn't currently opened", ->
it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'editor-created' events", ->
editor = null
waitsForPromise ->
atom.project.open(absolutePath).then (o) -> editor = o
runs ->
expect(editor.buffer.getPath()).toBe absolutePath
expect(newBufferHandler).toHaveBeenCalledWith editor.buffer
expect(newEditorHandler).toHaveBeenCalledWith editor
describe "when passed the path to a buffer that has already been opened", ->
it "returns a new edit session containing previously opened buffer and emits a 'editor-created' event", ->
editor = atom.project.openSync(absolutePath)
describe "when passed the path to a buffer that is currently opened", ->
it "returns a new edit session containing currently opened buffer and emits a 'editor-created' event", ->
editor = null
waitsForPromise ->
atom.project.open(absolutePath).then (o) -> editor = o
runs ->
newBufferHandler.reset()
expect(atom.project.openSync(absolutePath).buffer).toBe editor.buffer
expect(atom.project.openSync('a').buffer).toBe editor.buffer
expect(newBufferHandler).not.toHaveBeenCalled()
expect(newEditorHandler).toHaveBeenCalledWith editor
describe "when not passed a path", ->
it "returns a new edit session and emits 'buffer-created' and 'editor-created' events", ->
editor = atom.project.openSync()
describe "when not passed a path", ->
it "returns a new edit session and emits 'buffer-created' and 'editor-created' events", ->
editor = null
waitsForPromise ->
atom.project.open().then (o) -> editor = o
runs ->
expect(editor.buffer.getPath()).toBeUndefined()
expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer)
expect(newEditorHandler).toHaveBeenCalledWith editor
describe "when passed a path that matches a custom opener", ->
it "returns the resource returned by the custom opener", ->
pathToOpen = atom.project.resolve('a.foo')
expect(atom.project.openSync(pathToOpen, hey: "there")).toEqual { foo: pathToOpen, options: {hey: "there"} }
expect(atom.project.openSync("bar://baz")).toEqual { bar: "bar://baz" }
describe ".open(path)", ->
[fooOpener, barOpener, absolutePath, newBufferHandler, newEditorHandler] = []
beforeEach ->
absolutePath = require.resolve('./fixtures/dir/a')
newBufferHandler = jasmine.createSpy('newBufferHandler')
atom.project.on 'buffer-created', newBufferHandler
newEditorHandler = jasmine.createSpy('newEditorHandler')
atom.project.on 'editor-created', newEditorHandler
fooOpener = (pathToOpen, options) -> { foo: pathToOpen, options } if pathToOpen?.match(/\.foo/)
barOpener = (pathToOpen) -> { bar: pathToOpen } if pathToOpen?.match(/^bar:\/\//)
atom.project.registerOpener(fooOpener)
atom.project.registerOpener(barOpener)
afterEach ->
atom.project.unregisterOpener(fooOpener)
atom.project.unregisterOpener(barOpener)
describe "when passed a path that doesn't match a custom opener", ->
describe "when given an absolute path that isn't currently open", ->
it "returns a new edit session for the given path and emits 'buffer-created' and 'editor-created' events", ->
editor = null
waitsForPromise ->
atom.project.open(absolutePath).then (o) -> editor = o
runs ->
expect(editor.buffer.getPath()).toBe absolutePath
expect(newBufferHandler).toHaveBeenCalledWith editor.buffer
expect(newEditorHandler).toHaveBeenCalledWith editor
describe "when given a relative path that isn't currently opened", ->
it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'editor-created' events", ->
editor = null
waitsForPromise ->
atom.project.open(absolutePath).then (o) -> editor = o
runs ->
expect(editor.buffer.getPath()).toBe absolutePath
expect(newBufferHandler).toHaveBeenCalledWith editor.buffer
expect(newEditorHandler).toHaveBeenCalledWith editor
describe "when passed the path to a buffer that is currently opened", ->
it "returns a new edit session containing currently opened buffer and emits a 'editor-created' event", ->
editor = null
waitsForPromise ->
atom.project.open(absolutePath).then (o) -> editor = o
runs ->
newBufferHandler.reset()
expect(atom.project.openSync(absolutePath).buffer).toBe editor.buffer
expect(atom.project.openSync('a').buffer).toBe editor.buffer
expect(newBufferHandler).not.toHaveBeenCalled()
expect(newEditorHandler).toHaveBeenCalledWith editor
describe "when not passed a path", ->
it "returns a new edit session and emits 'buffer-created' and 'editor-created' events", ->
editor = null
waitsForPromise ->
atom.project.open().then (o) -> editor = o
runs ->
expect(editor.buffer.getPath()).toBeUndefined()
expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer)
expect(newEditorHandler).toHaveBeenCalledWith editor
describe "when passed a path that matches a custom opener", ->
it "returns the resource returned by the custom opener", ->
waitsForPromise ->
pathToOpen = atom.project.resolve('a.foo')
atom.project.open(pathToOpen, hey: "there").then (item) ->
expect(item).toEqual { foo: pathToOpen, options: {hey: "there"} }
waitsForPromise ->
atom.project.open("bar://baz").then (item) ->
expect(item).toEqual { bar: "bar://baz" }
it "returns number of read bytes as progress indicator", ->
filePath = atom.project.resolve 'a'
totalBytes = 0

View File

@@ -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] = []

View File

@@ -95,9 +95,9 @@ beforeEach ->
TokenizedBuffer.prototype.chunkSize = Infinity
spyOn(TokenizedBuffer.prototype, "tokenizeInBackground").andCallFake -> @tokenizeNextChunk()
pasteboardContent = 'initial pasteboard content'
spyOn(clipboard, 'writeText').andCallFake (text) -> pasteboardContent = text
spyOn(clipboard, 'readText').andCallFake -> pasteboardContent
clipboardContent = 'initial clipboard content'
spyOn(clipboard, 'writeText').andCallFake (text) -> clipboardContent = text
spyOn(clipboard, 'readText').andCallFake -> clipboardContent
addCustomMatchers(this)
@@ -159,6 +159,16 @@ addCustomMatchers = (spec) ->
@message = -> return "Expected path '" + @actual + "'" + notText + " to exist."
fs.existsSync(@actual)
toHaveFocus: ->
notText = this.isNot and " not" or ""
if not document.hasFocus()
console.error "Specs will fail because the Dev Tools have focus. To fix this close the Dev Tools or click the spec runner."
@message = -> return "Expected element '" + @actual + "' or its descendants" + notText + " to have focus."
element = @actual
element = element.get(0) if element.jquery
element.webkitMatchesSelector(":focus") or element.querySelector(":focus")
window.keyIdentifierForKey = (key) ->
if key.length > 1 # named key
key

View File

@@ -4,10 +4,18 @@ temp = require 'temp'
describe "the `syntax` global", ->
beforeEach ->
atom.packages.activatePackage('language-text', sync: true)
atom.packages.activatePackage('language-javascript', sync: true)
atom.packages.activatePackage('language-coffee-script', sync: true)
atom.packages.activatePackage('language-ruby', sync: true)
waitsForPromise ->
atom.packages.activatePackage('language-text')
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
waitsForPromise ->
atom.packages.activatePackage('language-coffee-script')
waitsForPromise ->
atom.packages.activatePackage('language-ruby')
describe "serialization", ->
it "remembers grammar overrides by path", ->
@@ -20,29 +28,33 @@ describe "the `syntax` global", ->
describe ".selectGrammar(filePath)", ->
it "can use the filePath to load the correct grammar based on the grammar's filetype", ->
atom.packages.activatePackage('language-git', sync: true)
waitsForPromise ->
atom.packages.activatePackage('language-git')
expect(atom.syntax.selectGrammar("file.js").name).toBe "JavaScript" # based on extension (.js)
expect(atom.syntax.selectGrammar(path.join(temp.dir, '.git', 'config')).name).toBe "Git Config" # based on end of the path (.git/config)
expect(atom.syntax.selectGrammar("Rakefile").name).toBe "Ruby" # based on the file's basename (Rakefile)
expect(atom.syntax.selectGrammar("curb").name).toBe "Null Grammar"
expect(atom.syntax.selectGrammar("/hu.git/config").name).toBe "Null Grammar"
runs ->
expect(atom.syntax.selectGrammar("file.js").name).toBe "JavaScript" # based on extension (.js)
expect(atom.syntax.selectGrammar(path.join(temp.dir, '.git', 'config')).name).toBe "Git Config" # based on end of the path (.git/config)
expect(atom.syntax.selectGrammar("Rakefile").name).toBe "Ruby" # based on the file's basename (Rakefile)
expect(atom.syntax.selectGrammar("curb").name).toBe "Null Grammar"
expect(atom.syntax.selectGrammar("/hu.git/config").name).toBe "Null Grammar"
it "uses the filePath's shebang line if the grammar cannot be determined by the extension or basename", ->
filePath = require.resolve("./fixtures/shebang")
expect(atom.syntax.selectGrammar(filePath).name).toBe "Ruby"
it "uses the number of newlines in the first line regex to determine the number of lines to test against", ->
atom.packages.activatePackage('language-property-list', sync: true)
waitsForPromise ->
atom.packages.activatePackage('language-property-list')
fileContent = "first-line\n<html>"
expect(atom.syntax.selectGrammar("dummy.coffee", fileContent).name).toBe "CoffeeScript"
runs ->
fileContent = "first-line\n<html>"
expect(atom.syntax.selectGrammar("dummy.coffee", fileContent).name).toBe "CoffeeScript"
fileContent = '<?xml version="1.0" encoding="UTF-8"?>'
expect(atom.syntax.selectGrammar("grammar.tmLanguage", fileContent).name).toBe "Null Grammar"
fileContent = '<?xml version="1.0" encoding="UTF-8"?>'
expect(atom.syntax.selectGrammar("grammar.tmLanguage", fileContent).name).toBe "Null Grammar"
fileContent += '\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
expect(atom.syntax.selectGrammar("grammar.tmLanguage", fileContent).name).toBe "Property List (XML)"
fileContent += '\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
expect(atom.syntax.selectGrammar("grammar.tmLanguage", fileContent).name).toBe "Property List (XML)"
it "doesn't read the file when the file contents are specified", ->
filePath = require.resolve("./fixtures/shebang")

View File

@@ -571,6 +571,23 @@ describe 'TextBuffer', ->
saveBuffer.reload()
expect(events).toEqual ['will-reload', 'reloaded']
it "no longer reports being in conflict", ->
saveBuffer.setText('a')
saveBuffer.save()
saveBuffer.setText('ab')
fs.writeFileSync(saveBuffer.getPath(), 'c')
conflictHandler = jasmine.createSpy('conflictHandler')
saveBuffer.on 'contents-conflicted', conflictHandler
waitsFor ->
conflictHandler.callCount > 0
runs ->
expect(saveBuffer.isInConflict()).toBe true
saveBuffer.save()
expect(saveBuffer.isInConflict()).toBe false
describe "when the buffer has no path", ->
it "throws an exception", ->
saveBuffer = atom.project.bufferForPathSync(null)

View File

@@ -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

View File

@@ -5,11 +5,13 @@ describe "TokenizedBuffer", ->
[tokenizedBuffer, buffer, changeHandler] = []
beforeEach ->
atom.packages.activatePackage('language-javascript', sync: true)
# enable async tokenization
TokenizedBuffer.prototype.chunkSize = 5
jasmine.unspy(TokenizedBuffer.prototype, 'tokenizeInBackground')
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
startTokenizing = (tokenizedBuffer) ->
tokenizedBuffer.setVisible(true)
@@ -311,10 +313,13 @@ describe "TokenizedBuffer", ->
describe "when the buffer contains hard-tabs", ->
beforeEach ->
atom.packages.activatePackage('language-coffee-script', sync: true)
buffer = atom.project.bufferForPathSync('sample-with-tabs.coffee')
tokenizedBuffer = new TokenizedBuffer({buffer})
startTokenizing(tokenizedBuffer)
waitsForPromise ->
atom.packages.activatePackage('language-coffee-script')
runs ->
buffer = atom.project.bufferForPathSync('sample-with-tabs.coffee')
tokenizedBuffer = new TokenizedBuffer({buffer})
startTokenizing(tokenizedBuffer)
afterEach ->
tokenizedBuffer.destroy()
@@ -341,14 +346,17 @@ describe "TokenizedBuffer", ->
describe "when the buffer contains surrogate pairs", ->
beforeEach ->
atom.packages.activatePackage('language-javascript', sync: true)
buffer = atom.project.bufferForPathSync 'sample-with-pairs.js'
buffer.setText """
'abc\uD835\uDF97def'
//\uD835\uDF97xyz
"""
tokenizedBuffer = new TokenizedBuffer({buffer})
fullyTokenize(tokenizedBuffer)
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
runs ->
buffer = atom.project.bufferForPathSync 'sample-with-pairs.js'
buffer.setText """
'abc\uD835\uDF97def'
//\uD835\uDF97xyz
"""
tokenizedBuffer = new TokenizedBuffer({buffer})
fullyTokenize(tokenizedBuffer)
afterEach ->
tokenizedBuffer.destroy()
@@ -379,22 +387,30 @@ describe "TokenizedBuffer", ->
describe "when the grammar is updated because a grammar it includes is activated", ->
it "retokenizes the buffer", ->
atom.packages.activatePackage('language-ruby-on-rails', sync: true)
atom.packages.activatePackage('language-ruby', sync: true)
buffer = atom.project.bufferForPathSync()
buffer.setText "<div class='name'><%= User.find(2).full_name %></div>"
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer.setGrammar(atom.syntax.selectGrammar('test.erb'))
fullyTokenize(tokenizedBuffer)
waitsForPromise ->
atom.packages.activatePackage('language-ruby-on-rails')
{tokens} = tokenizedBuffer.lineForScreenRow(0)
expect(tokens[0]).toEqual value: "<div class='name'>", scopes: ["text.html.ruby"]
waitsForPromise ->
atom.packages.activatePackage('language-ruby')
atom.packages.activatePackage('language-html', sync: true)
fullyTokenize(tokenizedBuffer)
{tokens} = tokenizedBuffer.lineForScreenRow(0)
expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
runs ->
buffer = atom.project.bufferForPathSync()
buffer.setText "<div class='name'><%= User.find(2).full_name %></div>"
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer.setGrammar(atom.syntax.selectGrammar('test.erb'))
fullyTokenize(tokenizedBuffer)
{tokens} = tokenizedBuffer.lineForScreenRow(0)
expect(tokens[0]).toEqual value: "<div class='name'>", scopes: ["text.html.ruby"]
waitsForPromise ->
atom.packages.activatePackage('language-html')
runs ->
fullyTokenize(tokenizedBuffer)
{tokens} = tokenizedBuffer.lineForScreenRow(0)
expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
describe ".tokenForPosition(position)", ->
afterEach ->

View File

@@ -7,49 +7,141 @@ describe "Workspace", ->
atom.project.setPath(atom.project.resolve('dir'))
workspace = new Workspace
describe "::open(uri)", ->
describe "::open(uri, options)", ->
beforeEach ->
spyOn(workspace.activePane, 'activate')
describe "when called without a uri", ->
it "adds and activates an empty editor on the active pane", ->
editor = null
waitsForPromise ->
workspace.open().then (o) -> editor = o
runs ->
expect(editor.getPath()).toBeUndefined()
expect(workspace.activePane.items).toEqual [editor]
expect(workspace.activePaneItem).toBe editor
expect(workspace.activePane.activate).toHaveBeenCalled()
describe "when called with a uri", ->
describe "when the active pane already has an editor for the given uri", ->
it "activates the existing editor on the active pane", ->
editor1 = workspace.openSync('a')
editor2 = workspace.openSync('b')
spyOn(workspace.activePane, 'activate').andCallThrough()
describe "when the 'searchAllPanes' option is false (default)", ->
describe "when called without a uri", ->
it "adds and activates an empty editor on the active pane", ->
editor = null
waitsForPromise ->
workspace.open('a').then (o) -> editor = o
workspace.open().then (o) -> editor = o
runs ->
expect(editor).toBe editor1
expect(workspace.activePaneItem).toBe editor
expect(workspace.activePane.activate).toHaveBeenCalled()
describe "when the active pane does not have an editor for the given uri", ->
it "adds and activates a new editor for the given path on the active pane", ->
editor = null
waitsForPromise ->
workspace.open('a').then (o) -> editor = o
runs ->
expect(editor.getUri()).toBe 'a'
expect(workspace.activePaneItem).toBe editor
expect(editor.getPath()).toBeUndefined()
expect(workspace.activePane.items).toEqual [editor]
expect(workspace.activePaneItem).toBe editor
expect(workspace.activePane.activate).toHaveBeenCalled()
describe "when called with a uri", ->
describe "when the active pane already has an editor for the given uri", ->
it "activates the existing editor on the active pane", ->
editor1 = workspace.openSync('a')
editor2 = workspace.openSync('b')
editor = null
waitsForPromise ->
workspace.open('a').then (o) -> editor = o
runs ->
expect(editor).toBe editor1
expect(workspace.activePaneItem).toBe editor
expect(workspace.activePane.activate).toHaveBeenCalled()
describe "when the active pane does not have an editor for the given uri", ->
it "adds and activates a new editor for the given path on the active pane", ->
editor = null
waitsForPromise ->
workspace.open('a').then (o) -> editor = o
runs ->
expect(editor.getUri()).toBe 'a'
expect(workspace.activePaneItem).toBe editor
expect(workspace.activePane.items).toEqual [editor]
expect(workspace.activePane.activate).toHaveBeenCalled()
describe "when the 'searchAllPanes' option is true", ->
describe "when an editor for the given uri is already open on an inactive pane", ->
it "activates the existing editor on the inactive pane, then activates that pane", ->
editor1 = workspace.openSync('a')
pane1 = workspace.activePane
pane2 = workspace.activePane.splitRight()
editor2 = workspace.openSync('b')
expect(workspace.activePaneItem).toBe editor2
waitsForPromise ->
workspace.open('a', searchAllPanes: true)
runs ->
expect(workspace.activePane).toBe pane1
expect(workspace.activePaneItem).toBe editor1
describe "when no editor for the given uri is open in any pane", ->
it "opens an editor for the given uri in the active pane", ->
editor = null
waitsForPromise ->
workspace.open('a', searchAllPanes: true).then (o) -> editor = o
runs ->
expect(workspace.activePaneItem).toBe editor
describe "when the 'split' option is set", ->
describe "when the 'split' option is 'left'", ->
it "opens the editor in the leftmost pane of the current pane axis", ->
pane1 = workspace.activePane
pane2 = pane1.splitRight()
expect(workspace.activePane).toBe pane2
editor = null
waitsForPromise ->
workspace.open('a', split: 'left').then (o) -> editor = o
runs ->
expect(workspace.activePane).toBe pane1
expect(pane1.items).toEqual [editor]
expect(pane2.items).toEqual []
# Focus right pane and reopen the file on the left
waitsForPromise ->
pane2.focus()
workspace.open('a', split: 'left').then (o) -> editor = o
runs ->
expect(workspace.activePane).toBe pane1
expect(pane1.items).toEqual [editor]
expect(pane2.items).toEqual []
describe "when the 'split' option is 'right'", ->
it "opens the editor in the rightmost pane of the current pane axis", ->
editor = null
pane1 = workspace.activePane
pane2 = null
waitsForPromise ->
workspace.open('a', split: 'right').then (o) -> editor = o
runs ->
pane2 = workspace.getPanes().filter((p) -> p != pane1)[0]
expect(workspace.activePane).toBe pane2
expect(pane1.items).toEqual []
expect(pane2.items).toEqual [editor]
# Focus right pane and reopen the file on the right
waitsForPromise ->
pane1.focus()
workspace.open('a', split: 'right').then (o) -> editor = o
runs ->
expect(workspace.activePane).toBe pane2
expect(pane1.items).toEqual []
expect(pane2.items).toEqual [editor]
describe "when passed a path that matches a custom opener", ->
it "returns the resource returned by the custom opener", ->
fooOpener = (pathToOpen, options) -> { foo: pathToOpen, options } if pathToOpen?.match(/\.foo/)
barOpener = (pathToOpen) -> { bar: pathToOpen } if pathToOpen?.match(/^bar:\/\//)
workspace.registerOpener(fooOpener)
workspace.registerOpener(barOpener)
waitsForPromise ->
pathToOpen = atom.project.resolve('a.foo')
workspace.open(pathToOpen, hey: "there").then (item) ->
expect(item).toEqual { foo: pathToOpen, options: {hey: "there"} }
waitsForPromise ->
workspace.open("bar://baz").then (item) ->
expect(item).toEqual { bar: "bar://baz" }
describe "::openSync(uri, options)", ->
[activePane, initialItemCount] = []
@@ -92,61 +184,6 @@ describe "Workspace", ->
workspace.openSync('b', activatePane: false)
expect(activePane.activate).not.toHaveBeenCalled()
describe "::openSingletonSync(uri, options)", ->
describe "when an editor for the given uri is already open on the active pane", ->
it "activates the existing editor", ->
editor1 = workspace.openSync('a')
editor2 = workspace.openSync('b')
expect(workspace.activePaneItem).toBe editor2
workspace.openSingletonSync('a')
expect(workspace.activePaneItem).toBe editor1
describe "when an editor for the given uri is already open on an inactive pane", ->
it "activates the existing editor on the inactive pane, then activates that pane", ->
editor1 = workspace.openSync('a')
pane1 = workspace.activePane
pane2 = workspace.activePane.splitRight()
editor2 = workspace.openSync('b')
expect(workspace.activePaneItem).toBe editor2
workspace.openSingletonSync('a')
expect(workspace.activePane).toBe pane1
expect(workspace.activePaneItem).toBe editor1
describe "when no editor for the given uri is open in any pane", ->
it "opens an editor for the given uri in the active pane", ->
editor1 = workspace.openSingletonSync('a')
expect(workspace.activePaneItem).toBe editor1
describe "when the 'split' option is 'left'", ->
it "opens the editor in the leftmost pane of the current pane axis", ->
pane1 = workspace.activePane
pane2 = pane1.splitRight()
expect(workspace.activePane).toBe pane2
editor1 = workspace.openSingletonSync('a', split: 'left')
expect(workspace.activePane).toBe pane1
expect(pane1.items).toEqual [editor1]
expect(pane2.items).toEqual []
describe "when the 'split' option is 'right'", ->
describe "when the active pane is in a horizontal pane axis", ->
it "activates the editor on the rightmost pane of the current pane axis", ->
pane1 = workspace.activePane
pane2 = pane1.splitRight()
pane1.activate()
editor1 = workspace.openSingletonSync('a', split: 'right')
expect(workspace.activePane).toBe pane2
expect(pane2.items).toEqual [editor1]
expect(pane1.items).toEqual []
describe "when the active pane is not in a horizontal pane axis", ->
it "splits the current pane to the right, then activates the editor on the right pane", ->
pane1 = workspace.activePane
editor1 = workspace.openSingletonSync('a', split: 'right')
pane2 = workspace.activePane
expect(workspace.paneContainer.root.children).toEqual [pane1, pane2]
expect(pane2.items).toEqual [editor1]
expect(pane1.items).toEqual []
describe "::reopenItemSync()", ->
it "opens the uri associated with the last closed pane that isn't currently open", ->
pane = workspace.activePane

View File

@@ -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)
@@ -42,11 +43,7 @@ class AtomPackage extends Package
@loadStylesheets()
@loadGrammars()
@loadScopedProperties()
if @metadata.activationEvents?
@registerDeferredDeserializers()
else
@requireMainModule()
@requireMainModule() unless @metadata.activationEvents?
catch e
console.warn "Failed to load package named '#{@name}'", e.stack ? e
@@ -59,14 +56,19 @@ 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
activateNow: ->
try
@activateConfig()
@@ -77,6 +79,8 @@ class AtomPackage extends Package
catch e
console.warn "Failed to activate package named '#{@name}'", e.stack
@activationDeferred.resolve()
activateConfig: ->
return if @configActivated
@@ -162,6 +166,8 @@ class AtomPackage extends Package
console.error "Error serializing package '#{@name}'", e.stack
deactivate: ->
@activationDeferred?.reject()
@activationDeferred = null
@unsubscribeFromActivationEvents()
@deactivateResources()
@deactivateConfig()
@@ -203,12 +209,6 @@ class AtomPackage extends Package
path.join(@path, 'index')
@mainModulePath = fs.resolveExtension(mainModulePath, ["", _.keys(require.extensions)...])
registerDeferredDeserializers: ->
for deserializerName in @metadata.deferredDeserializers ? []
atom.deserializers.addDeferred deserializerName, =>
@activateStylesheets()
@requireMainModule()
subscribeToActivationEvents: ->
return unless @metadata.activationEvents?
if _.isArray(@metadata.activationEvents)
@@ -226,6 +226,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)

View File

@@ -6,8 +6,6 @@ path = require 'path'
remote = require 'remote'
screen = require 'screen'
shell = require 'shell'
dialog = remote.require 'dialog'
app = remote.require 'app'
_ = require 'underscore-plus'
{Model} = require 'theorist'
@@ -22,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.pasteboard` - A {Pasteboard} 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.
@@ -43,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)
@@ -65,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
@@ -82,41 +82,47 @@ 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: ->
_.deepClone(@loadSettings ?= _.deepClone(@getCurrentWindow().loadSettings))
@loadSettings ?= JSON.parse(decodeURIComponent(location.search.substr(14)))
cloned = _.deepClone(@loadSettings)
# The loadSettings.windowState could be large, request it only when needed.
cloned.__defineGetter__ 'windowState', =>
@getCurrentWindow().loadSettings.windowState
cloned.__defineSetter__ 'windowState', (value) =>
@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 ?= app.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
@@ -134,7 +140,7 @@ class Atom extends Model
Config = require './config'
Keymap = require './keymap'
PackageManager = require './package-manager'
Pasteboard = require './pasteboard'
Clipboard = require './clipboard'
Syntax = require './syntax'
ThemeManager = require './theme-manager'
ContextMenuManager = require './context-menu-manager'
@@ -148,7 +154,8 @@ class Atom extends Model
@themes = new ThemeManager({packageManager: @packages, configDirPath, resourcePath})
@contextMenu = new ContextMenuManager(devMode)
@menu = new MenuManager({resourcePath})
@pasteboard = new Pasteboard()
@clipboard = new Clipboard()
@syntax = @deserializers.deserialize(@state.syntax) ? new Syntax()
@subscribe @packages, 'activated', => @watchThemes()
@@ -167,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}")
@@ -196,15 +202,13 @@ class Atom extends Model
# + width: The new width.
# + height: The new height.
setWindowDimensions: ({x, y, width, height}) ->
browserWindow = @getCurrentWindow()
if width? and height?
browserWindow.setSize(width, height)
@setSize(width, height)
if x? and y?
browserWindow.setPosition(x, y)
@setPosition(x, y)
else
browserWindow.center()
@center()
# Private:
restoreWindowDimensions: ->
workAreaSize = screen.getPrimaryDisplay().workAreaSize
windowDimensions = @state.windowDimensions ? {}
@@ -213,7 +217,6 @@ class Atom extends Model
windowDimensions.width ?= initialSize?.width ? Math.min(workAreaSize.width, 1024)
@setWindowDimensions(windowDimensions)
# Private:
storeWindowDimensions: ->
@state.windowDimensions = @getWindowDimensions()
@@ -223,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'
@@ -236,24 +237,22 @@ 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
CommandInstaller.installAtomCommand resourcePath, (error) ->
CommandInstaller.installAtomCommand resourcePath, false, (error) ->
console.warn error.message if error?
CommandInstaller.installApmCommand resourcePath, (error) ->
CommandInstaller.installApmCommand resourcePath, false, (error) ->
console.warn error.message if error?
@restoreWindowDimensions()
@@ -276,7 +275,6 @@ class Atom extends Model
@displayWindow()
# Private:
unloadEditorWindow: ->
return if not @project and not @workspaceView
@@ -292,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
@@ -309,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)
@@ -341,6 +338,7 @@ class Atom extends Model
else
buttonLabels = Object.keys(buttons)
dialog = remote.require('dialog')
chosen = dialog.showMessageBox @getCurrentWindow(),
type: 'info'
message: message
@@ -353,42 +351,59 @@ class Atom extends Model
callback = buttons[buttonLabels[chosen]]
callback?()
# Private:
showSaveDialog: (callback) ->
callback(showSaveDialogSync())
# Private:
showSaveDialogSync: (defaultPath) ->
defaultPath ?= @project?.getPath()
currentWindow = @getCurrentWindow()
dialog = remote.require('dialog')
dialog.showSaveDialog currentWindow, {title: 'Save File', defaultPath}
# Public: Open the dev tools for the current window.
openDevTools: ->
@getCurrentWindow().openDevTools()
ipc.sendChannel('call-window-method', 'openDevTools')
# Public: Toggle the visibility of the dev tools for the current window.
toggleDevTools: ->
@getCurrentWindow().toggleDevTools()
ipc.sendChannel('call-window-method', 'toggleDevTools')
# Public: Reload the current window.
reload: ->
@getCurrentWindow().restart()
ipc.sendChannel('call-window-method', 'restart')
# Public: Focus the current window.
focus: ->
@getCurrentWindow().focus()
ipc.sendChannel('call-window-method', 'focus')
$(window).focus()
# Public: Show the current window.
show: ->
@getCurrentWindow().show()
ipc.sendChannel('call-window-method', 'show')
# Public: Hide the current window.
hide: ->
@getCurrentWindow().hide()
ipc.sendChannel('call-window-method', 'hide')
# Private: Schedule the window to be shown and focused on the next tick.
# 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)
# Public: Move current window to the center of the screen.
center: ->
ipc.sendChannel('call-window-method', 'center')
# 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.
@@ -402,8 +417,9 @@ class Atom extends Model
close: ->
@getCurrentWindow().close()
# Private:
exit: (status) -> app.exit(status)
exit: (status) ->
app = remote.require('app')
app.exit(status)
# Public: Is the current window in development mode?
inDevMode: ->
@@ -419,7 +435,7 @@ class Atom extends Model
# Public: Set the full screen state of the current window.
setFullScreen: (fullScreen=false) ->
@getCurrentWindow().setFullScreen(fullScreen)
ipc.sendChannel('call-window-method', 'setFullScreen', fullScreen)
# Public: Is the current window in full screen mode?
isFullScreen: ->
@@ -450,7 +466,6 @@ class Atom extends Model
getConfigDirPath: ->
@constructor.getConfigDirPath()
# Private:
saveSync: ->
stateString = JSON.stringify(@state)
if statePath = @constructor.getStatePath(@mode)
@@ -468,11 +483,9 @@ class Atom extends Model
getWindowLoadTime: ->
@loadTime
# Private:
crashMainProcess: ->
remote.process.crash()
# Private:
crashRenderProcess: ->
process.crash()
@@ -481,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
@@ -493,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

View File

@@ -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.

View File

@@ -1,6 +1,7 @@
AtomWindow = require './atom-window'
ApplicationMenu = require './application-menu'
AtomProtocolHandler = require './atom-protocol-handler'
BrowserWindow = require 'browser-window'
Menu = require 'menu'
autoUpdater = require 'auto-updater'
app = require 'app'
@@ -21,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.
@@ -72,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})
@@ -97,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
@@ -112,23 +113,60 @@ 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)
# Check for update after Atom has fully started and the menus are created
setTimeout((-> autoUpdater.checkForUpdates()), 5000)
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)
@@ -147,9 +185,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')
@@ -170,12 +211,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?
@@ -195,6 +230,14 @@ class AtomApplication
ipc.on 'command', (processId, routingId, command) =>
@emit(command)
ipc.on 'window-command', (processId, routingId, command, args...) ->
win = BrowserWindow.fromProcessIdAndRoutingId(processId, routingId)
win.emit(command, args...)
ipc.on 'call-window-method', (processId, routingId, method, args...) ->
win = BrowserWindow.fromProcessIdAndRoutingId(processId, routingId)
win[method](args...)
# Public: Executes the given command.
#
# If it isn't handled globally, delegate to the currently focused window.
@@ -221,7 +264,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)
@@ -309,7 +352,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
@@ -341,7 +384,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:
@@ -372,7 +415,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.
#

View File

@@ -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))

View File

@@ -1,13 +1,14 @@
BrowserWindow = require 'browser-window'
Menu = require 'menu'
ContextMenu = require './context-menu'
app = require 'app'
dialog = require 'dialog'
ipc = require 'ipc'
path = require 'path'
fs = require 'fs'
url = require 'url'
_ = require 'underscore-plus'
# Private:
module.exports =
class AtomWindow
@iconPath: path.resolve(__dirname, '..', '..', 'resources', 'atom.png')
@@ -31,6 +32,7 @@ class AtomWindow
loadSettings = _.extend({}, settings)
loadSettings.windowState ?= '{}'
loadSettings.appVersion = app.getVersion()
# Only send to the first non-spec window created
if @constructor.includeShellLoadTime and not @isSpec
@@ -43,7 +45,7 @@ class AtomWindow
@browserWindow.loadSettings = loadSettings
@browserWindow.once 'window:loaded', => @loaded = true
@browserWindow.loadUrl "file://#{@resourcePath}/static/index.html"
@browserWindow.loadUrl @getUrl(loadSettings)
@browserWindow.focusOnWebView() if @isSpec
@openPath(pathToOpen, initialLine)
@@ -51,6 +53,18 @@ class AtomWindow
setupNodePath: (resourcePath) ->
process.env['NODE_PATH'] = path.resolve(resourcePath, 'exports')
getUrl: (loadSettingsObj) ->
# Ignore the windowState when passing loadSettings via URL, since it could
# be quite large.
loadSettings = _.clone(loadSettingsObj)
delete loadSettings['windowState']
url.format
protocol: 'file'
pathname: "#{@resourcePath}/static/index.html"
slashes: true
query: {loadSettings: JSON.stringify(loadSettings)}
getInitialPath: ->
@browserWindow.loadSettings.initialPath

View File

@@ -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) ->

View File

@@ -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..])

View File

@@ -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()

49
src/clipboard.coffee Normal file
View File

@@ -0,0 +1,49 @@
clipboard = require 'clipboard'
crypto = require 'crypto'
# Public: Represents the clipboard used for copying and pasting in Atom.
#
# An instance of this class is always available as the `atom.clipboard` global.
module.exports =
class Clipboard
metadata: null
signatureForMetadata: null
# Creates an `md5` hash of some text.
#
# text - A {String} to hash.
#
# Returns a hashed {String}.
md5: (text) ->
crypto.createHash('md5').update(text, 'utf8').digest('hex')
# Public: Write the given text to the clipboard.
#
# The metadata associated with the text is available by calling
# {.readWithMetadata}.
#
# text - The {String} to store.
# metadata - The additional info to associate with the text.
write: (text, metadata) ->
@signatureForMetadata = @md5(text)
@metadata = metadata
clipboard.writeText(text)
# Public: Read the text from the clipboard.
#
# Returns a {String}.
read: ->
clipboard.readText()
# Public: Read the text from the clipboard and return both the text and the
# 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)
{text, @metadata}
else
{text}

View File

@@ -3,52 +3,55 @@ _ = require 'underscore-plus'
async = require 'async'
fs = require 'fs-plus'
mkdirp = require 'mkdirp'
runas = require 'runas'
symlinkCommand = (sourcePath, destinationPath, callback) ->
mkdirp path.dirname(destinationPath), (error) ->
if error?
fs.unlink destinationPath, (error) ->
if error? and error?.code != 'ENOENT'
callback(error)
else
fs.symlink sourcePath, destinationPath, (error) ->
mkdirp path.dirname(destinationPath), (error) ->
if error?
callback(error)
else
fs.chmod(destinationPath, 0o755, callback)
fs.symlink sourcePath, destinationPath, (error) ->
if error?
callback(error)
else
fs.chmod(destinationPath, '755', callback)
unlinkCommand = (destinationPath, callback) ->
fs.unlink destinationPath, (error) ->
if error? and error.code isnt 'ENOENT'
callback(error)
else
callback()
symlinkCommandWithPrivilegeSync = (sourcePath, destinationPath) ->
if runas('/bin/rm', ['-f', destinationPath], admin: true) != 0
throw new Error("Failed to remove '#{destinationPath}'")
if runas('/bin/mkdir', ['-p', path.dirname(destinationPath)], admin: true) != 0
throw new Error("Failed to create directory '#{destinationPath}'")
if runas('/bin/ln', ['-s', sourcePath, destinationPath], admin: true) != 0
throw new Error("Failed to symlink '#{sourcePath}' to '#{destinationPath}'")
module.exports =
getInstallDirectory: ->
"/usr/local/bin"
install: (commandPath, callback) ->
install: (commandPath, askForPrivilege, callback) ->
return unless process.platform is 'darwin'
commandName = path.basename(commandPath, path.extname(commandPath))
directory = @getInstallDirectory()
if fs.existsSync(directory)
destinationPath = path.join(directory, commandName)
unlinkCommand destinationPath, (error) =>
if error?
error = new Error "Could not remove file at #{destinationPath}." if error
callback?(error)
else
symlinkCommand commandPath, destinationPath, (error) =>
error = new Error "Failed to symlink #{commandPath} to #{destinationPath}." if error
callback?(error)
else
error = new Error "Directory '#{directory} doesn't exist."
destinationPath = path.join(@getInstallDirectory(), commandName)
symlinkCommand commandPath, destinationPath, (error) =>
if askForPrivilege and error?.code is 'EACCES'
try
error = null
symlinkCommandWithPrivilegeSync(commandPath, destinationPath)
catch error
callback?(error)
installAtomCommand: (resourcePath, callback) ->
installAtomCommand: (resourcePath, askForPrivilege, callback) ->
commandPath = path.join(resourcePath, 'atom.sh')
@install commandPath, callback
@install commandPath, askForPrivilege, callback
installApmCommand: (resourcePath, callback) ->
installApmCommand: (resourcePath, askForPrivilege, callback) ->
commandPath = path.join(resourcePath, 'apm', 'node_modules', '.bin', 'apm')
@install commandPath, callback
@install commandPath, askForPrivilege, callback

View File

@@ -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

View File

@@ -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
@@ -160,6 +150,14 @@ class Config
toggle: (keyPath) ->
@set(keyPath, !@get(keyPath))
# Public: Restore the key path to its default value.
#
# keyPath - The {String} name of the key.
#
# Returns the new value.
restoreDefault: (keyPath) ->
@set(keyPath, _.valueForKeyPath(@defaultSettings, keyPath))
# Public: Push the value to the array at the key path.
#
# keyPath - The {String} key path.
@@ -205,6 +203,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 +231,10 @@ class Config
unobserve: (keyPath) ->
@off("updated.#{keyPath.replace(/\./, '-')}")
# Private:
update: ->
return if @configFileHasErrors
@save()
@emit 'updated'
# Private:
save: ->
CSON.writeFileSync(@configFilePath, @settings)

View File

@@ -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.
#

View File

@@ -1,38 +1,49 @@
{View} = require './space-pen-extensions'
{Point, Range} = require 'text-buffer'
_ = require 'underscore-plus'
### Internal ###
module.exports =
class CursorView extends View
@content: ->
@div class: 'cursor idle', => @raw '&nbsp;'
blinkPeriod: 800
editorView: null
visible: true
@blinkPeriod: 800
@blinkCursors: ->
element.classList.toggle('blink-off') for [element] in @cursorViews
@startBlinking: (cursorView) ->
@cursorViews ?= []
@cursorViews.push(cursorView)
if @cursorViews.length is 1
@blinkInterval = setInterval(@blinkCursors.bind(this), @blinkPeriod / 2)
@stopBlinking: (cursorView) ->
cursorView[0].classList.remove('blink-off')
_.remove(@cursorViews, cursorView)
clearInterval(@blinkInterval) if @cursorViews.length is 0
blinking: false
visible: true
needsUpdate: true
needsRemoval: false
shouldPauseBlinking: false
initialize: (@cursor, @editorView) ->
@cursor.on 'moved.cursor-view', =>
@subscribe @cursor, 'moved', =>
@needsUpdate = true
@shouldPauseBlinking = true
@cursor.on 'visibility-changed.cursor-view', (visible) =>
@subscribe @cursor, 'visibility-changed', =>
@needsUpdate = true
@cursor.on 'autoscrolled.cursor-view', =>
@subscribe @cursor, 'autoscrolled', =>
@editorView.requestDisplayUpdate()
@cursor.on 'destroyed.cursor-view', =>
@subscribe @cursor, 'destroyed', =>
@needsRemoval = true
beforeRemove: ->
@editorView.removeCursorView(this)
@cursor.off('.cursor-view')
@stopBlinking()
updateDisplay: ->
@@ -53,11 +64,7 @@ class CursorView extends View
# Override for speed. The base function checks the computedStyle
isHidden: ->
style = this[0].style
if style.display == 'none' or not @isOnDom()
true
else
false
this[0].style.display is 'none' or not @isOnDom()
needsAutoscroll: ->
@cursor.needsAutoscroll
@@ -69,19 +76,17 @@ class CursorView extends View
@editorView.pixelPositionForScreenPosition(@getScreenPosition())
setVisible: (visible) ->
unless @visible == visible
unless @visible is visible
@visible = visible
@toggle(@visible)
stopBlinking: ->
clearInterval(@blinkInterval) if @blinkInterval
@blinkInterval = null
this[0].classList.remove('blink-off')
@constructor.stopBlinking(this) if @blinking
@blinking = false
startBlinking: ->
return if @blinkInterval?
blink = => @toggleClass('blink-off')
@blinkInterval = setInterval(blink, @blinkPeriod / 2)
@constructor.startBlinking(this) unless @blinking
@blinking = true
resetBlinking: ->
@stopBlinking()

View File

@@ -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)

View File

@@ -1,25 +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 = {}
@deferredDeserializers = {}
# Public: Register the given class(es) as deserializers.
add: (klasses...) ->
@deserializers[klass.name] = klass for klass in klasses
# Public: Add a deferred deserializer for the given class name.
addDeferred: (name, fn) ->
@deferredDeserializers[name] = fn
#
# 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?
@@ -30,13 +44,11 @@ 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?
name = state.get?('deserializer') ? state.deserializer
if @deferredDeserializers[name]
@deferredDeserializers[name]()
delete @deferredDeserializers[name]
@deserializers[name]

View File

@@ -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

View File

@@ -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'

View File

@@ -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, @bufferRowForScreenRow(row), line, line.length
### Internal ###
handleTokenizedBufferChange: (tokenizedBufferChange) =>
{start, end, delta, bufferChange} = tokenizedBufferChange
@updateScreenLines(start, end + 1, delta, delayChangeEvent: bufferChange?)

View File

@@ -26,6 +26,7 @@ module.exports =
class EditorView extends View
@characterWidthCache: {}
@configDefaults:
fontFamily: ''
fontSize: 20
showInvisibles: false
showIndentGuide: false
@@ -41,8 +42,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 +78,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 +117,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: ->
@@ -209,7 +206,7 @@ class EditorView extends View
'editor:toggle-line-comments': => @toggleLineCommentsInSelection()
'editor:log-cursor-scope': => @logCursorScope()
'editor:checkout-head-revision': => @checkoutHead()
'editor:copy-path': => @copyPathToPasteboard()
'editor:copy-path': => @copyPathToClipboard()
'editor:move-line-up': => @editor.moveLineUp()
'editor:move-line-down': => @editor.moveLineDown()
'editor:duplicate-line': => @editor.duplicateLine()
@@ -223,6 +220,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 +238,6 @@ class EditorView extends View
insertText: (text, options) ->
@editor.insertText(text, options)
# Private:
setHeightInLines: (heightInLines)->
heightInLines ?= @calculateHeightInLines()
@heightInLines = heightInLines if heightInLines
@@ -248,39 +247,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 +290,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 +317,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 +493,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 +594,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 +613,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 +621,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 +652,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 +682,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,15 +695,15 @@ 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`,
# fontFamily - A {String} identifying the CSS `font-family`.
setFontFamily: (fontFamily='') ->
@css('font-family', fontFamily)
@@ -708,12 +711,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`,
# 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 +726,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 +757,6 @@ class EditorView extends View
super
atom.workspaceView?.focus()
# Private:
beforeRemove: ->
@trigger 'editor:will-be-removed'
@removed = true
@@ -797,8 +803,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 +839,7 @@ class EditorView extends View
@scrollRight(desiredRight)
else if desiredLeft < @scrollLeft()
@scrollLeft(desiredLeft)
@saveScrollPositionForeditor()
@saveScrollPositionForEditor()
calculateDimensions: ->
fragment = $('<div class="line" style="position: absolute; visibility: hidden;"><span>x</span></div>')
@@ -1135,9 +1139,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 +1148,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 +1158,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 +1166,6 @@ class EditorView extends View
isScreenRowVisible: (row) ->
@getFirstVisibleScreenRow() <= row <= @getLastVisibleScreenRow()
### Internal ###
handleScreenLinesChange: (change) ->
@pendingChanges.push(change)
@requestDisplayUpdate()
@@ -1246,21 +1248,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 +1297,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 +1342,6 @@ class EditorView extends View
returnLeft ? left
# Private:
getCharacterWidthCache: (scopes, char) ->
scopes ?= NoScope
obj = @constructor.characterWidthCache
@@ -1352,7 +1350,6 @@ class EditorView extends View
return null unless obj?
obj[char]
# Private:
setCharacterWidthCache: (scopes, char, val) ->
scopes ?= NoScope
obj = @constructor.characterWidthCache
@@ -1361,7 +1358,6 @@ class EditorView extends View
obj = obj[scope]
obj[char] = val
# Private:
clearCharacterWidthCache: ->
@constructor.characterWidthCache = {}
@@ -1411,11 +1407,9 @@ class EditorView extends View
@highlightedLine = null
# Copies the current file path to the native clipboard.
copyPathToPasteboard: ->
copyPathToClipboard: ->
path = @editor.getPath()
atom.pasteboard.write(path) if path?
### Internal ###
atom.clipboard.write(path) if path?
@buildLineHtml: ({tokens, text, lineEnding, fold, isSoftWrapped, invisibles, eolInvisibles, htmlEolInvisibles, attributes, showIndentGuide, indentation, editor, mini}) ->
scopeStack = []
@@ -1426,7 +1420,7 @@ class EditorView extends View
line.push("<div #{attributePairs}>")
if text == ''
html = EditorView.buildEmptyLineHtml(showIndentGuide, eolInvisibles, htmlEolInvisibles, indentation, editor, mini)
html = @buildEmptyLineHtml(showIndentGuide, eolInvisibles, htmlEolInvisibles, indentation, editor, mini)
line.push(html) if html
else
firstNonWhitespacePosition = text.search(/\S/)

View File

@@ -78,7 +78,7 @@ class Editor extends Model
position = [0, 0]
@addCursorAtBufferPosition(position)
@languageMode = new LanguageMode(this, @buffer.getExtension())
@languageMode = new LanguageMode(this)
@subscribe @$scrollTop, (scrollTop) => @emit 'scroll-top-changed', scrollTop
@subscribe @$scrollLeft, (scrollLeft) => @emit 'scroll-left-changed', scrollLeft
@@ -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()
@@ -238,15 +234,14 @@ class Editor extends Model
# Public: Given a position, this clips it to a real position.
#
# For example, if `position`'s row exceeds the row count of the buffer,
# For example, if `bufferPosition`'s row exceeds the row count of the buffer,
# or if its column goes beyond a line's length, this "sanitizes" the value
# to a real position.
#
# * position:
# The {Point} to clip
# bufferPosition - The {Point} to clip.
#
# Returns the new, clipped {Point}. Note that this could be the same as
# `position` if no clipping was performed.
# `bufferPosition` if no clipping was performed.
clipBufferPosition: (bufferPosition) -> @buffer.clipPosition(bufferPosition)
# Public: Given a range, this clips it to a real range.
@@ -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())
@@ -308,9 +298,6 @@ class Editor extends Model
# {Delegates to: TextBuffer.saveAs}
saveAs: (path) -> @buffer.saveAs(path)
# {Delegates to: TextBuffer.getExtension}
getFileExtension: -> @buffer.getExtension()
# {Delegates to: TextBuffer.getPath}
getPath: -> @buffer.getPath()
@@ -326,7 +313,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 +340,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 +349,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
@@ -439,10 +426,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()
@@ -469,8 +454,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)
@@ -521,7 +505,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()
@@ -537,31 +521,30 @@ class Editor extends Model
# Public: Copies and removes all characters from cursor to the end of the
# line.
cutToEndOfLine: ->
maintainPasteboard = false
maintainClipboard = false
@mutateSelectedText (selection) ->
selection.cutToEndOfLine(maintainPasteboard)
maintainPasteboard = true
selection.cutToEndOfLine(maintainClipboard)
maintainClipboard = true
# Public: Cuts the selected text.
cutSelectedText: ->
maintainPasteboard = false
maintainClipboard = false
@mutateSelectedText (selection) ->
selection.cut(maintainPasteboard)
maintainPasteboard = true
selection.cut(maintainClipboard)
maintainClipboard = true
# Public: Copies the selected text.
copySelectedText: ->
maintainPasteboard = false
maintainClipboard = false
for selection in @getSelections()
selection.copy(maintainPasteboard)
maintainPasteboard = true
selection.copy(maintainClipboard)
maintainClipboard = true
# 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.pasteboard.read()
{text, metadata} = atom.clipboard.readWithMetadata()
containsNewlines = text.indexOf('\n') isnt -1
@@ -640,7 +623,7 @@ class Editor extends Model
largestFoldStartingAtScreenRow: (screenRow) ->
@displayBuffer.largestFoldStartingAtScreenRow(screenRow)
# Public: Moves the selected line up one row.
# Public: Moves the selected lines up one screen row.
moveLineUp: ->
selection = @getSelectedBufferRange()
return if selection.start.row is 0
@@ -652,29 +635,47 @@ class Editor extends Model
rows = [selection.start.row..selection.end.row]
if selection.start.row isnt selection.end.row and selection.end.column is 0
rows.pop() unless @isFoldedAtBufferRow(selection.end.row)
# Move line around the fold that is directly above the selection
precedingScreenRow = @screenPositionForBufferPosition([selection.start.row]).translate([-1])
precedingBufferRow = @bufferPositionForScreenPosition(precedingScreenRow).row
if fold = @largestFoldContainingBufferRow(precedingBufferRow)
insertDelta = fold.getBufferRange().getRowCount()
else
insertDelta = 1
for row in rows
screenRow = @screenPositionForBufferPosition([row]).row
if @isFoldedAtScreenRow(screenRow)
bufferRange = @bufferRangeForScreenRange([[screenRow], [screenRow + 1]])
if fold = @displayBuffer.largestFoldStartingAtBufferRow(row)
bufferRange = fold.getBufferRange()
startRow = bufferRange.start.row
endRow = bufferRange.end.row - 1
foldedRows.push(endRow - 1)
endRow = bufferRange.end.row
foldedRows.push(startRow - insertDelta)
else
startRow = row
endRow = row
insertPosition = Point.fromObject([startRow - insertDelta])
endPosition = Point.min([endRow + 1], @buffer.getEofPosition())
lines = @buffer.getTextInRange([[startRow], endPosition])
if endPosition.row is lastRow and endPosition.column > 0 and not @buffer.lineEndingForRow(endPosition.row)
lines = "#{lines}\n"
@buffer.deleteRows(startRow, endRow)
@buffer.insert([startRow - 1], lines)
@foldBufferRow(foldedRow) for foldedRow in foldedRows
# Make sure the inserted text doesn't go into an existing fold
if fold = @displayBuffer.largestFoldStartingAtBufferRow(insertPosition.row)
@destroyFoldsContainingBufferRow(insertPosition.row)
foldedRows.push(insertPosition.row + endRow - startRow + fold.getBufferRange().getRowCount())
@setSelectedBufferRange(selection.translate([-1]), preserveFolds: true)
@buffer.insert(insertPosition, lines)
# Public: Moves the selected line down one row.
# Restore folds that existed before the lines were moved
for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
@foldBufferRow(foldedRow)
@setSelectedBufferRange(selection.translate([-insertDelta]), preserveFolds: true)
# Public: Moves the selected lines down one screen row.
moveLineDown: ->
selection = @getSelectedBufferRange()
lastRow = @buffer.getLastRow()
@@ -686,13 +687,21 @@ class Editor extends Model
rows = [selection.end.row..selection.start.row]
if selection.start.row isnt selection.end.row and selection.end.column is 0
rows.shift() unless @isFoldedAtBufferRow(selection.end.row)
# Move line around the fold that is directly below the selection
followingScreenRow = @screenPositionForBufferPosition([selection.end.row]).translate([1])
followingBufferRow = @bufferPositionForScreenPosition(followingScreenRow).row
if fold = @largestFoldContainingBufferRow(followingBufferRow)
insertDelta = fold.getBufferRange().getRowCount()
else
insertDelta = 1
for row in rows
screenRow = @screenPositionForBufferPosition([row]).row
if @isFoldedAtScreenRow(screenRow)
bufferRange = @bufferRangeForScreenRange([[screenRow], [screenRow + 1]])
if fold = @displayBuffer.largestFoldStartingAtBufferRow(row)
bufferRange = fold.getBufferRange()
startRow = bufferRange.start.row
endRow = bufferRange.end.row - 1
foldedRows.push(endRow + 1)
endRow = bufferRange.end.row
foldedRows.push(endRow + insertDelta)
else
startRow = row
endRow = row
@@ -703,14 +712,23 @@ class Editor extends Model
endPosition = [endRow + 1]
lines = @buffer.getTextInRange([[startRow], endPosition])
@buffer.deleteRows(startRow, endRow)
insertPosition = Point.min([startRow + 1], @buffer.getEofPosition())
insertPosition = Point.min([startRow + insertDelta], @buffer.getEofPosition())
if insertPosition.row is @buffer.getLastRow() and insertPosition.column > 0
lines = "\n#{lines}"
# Make sure the inserted text doesn't go into an existing fold
if fold = @displayBuffer.largestFoldStartingAtBufferRow(insertPosition.row)
@destroyFoldsContainingBufferRow(insertPosition.row)
foldedRows.push(insertPosition.row + fold.getBufferRange().getRowCount())
@buffer.insert(insertPosition, lines)
@foldBufferRow(foldedRow) for foldedRow in foldedRows
# Restore folds that existed before the lines were moved
for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
@foldBufferRow(foldedRow)
@setSelectedBufferRange(selection.translate([1]), preserveFolds: true)
@setSelectedBufferRange(selection.translate([insertDelta]), preserveFolds: true)
# Public: Duplicates the current line.
#
@@ -739,11 +757,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) ->
@@ -787,7 +803,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()
@@ -826,10 +844,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={}) ->
@@ -850,10 +866,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={}) ->
@@ -863,20 +877,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
@@ -893,7 +903,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)
@@ -904,9 +914,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
@@ -943,8 +951,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) ->
@@ -953,10 +960,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)
@@ -975,10 +980,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)
@@ -1021,9 +1024,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))
@@ -1091,7 +1093,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()
@@ -1099,8 +1100,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)
@@ -1259,8 +1259,6 @@ class Editor extends Model
@setSelectedBufferRange(range)
range
# Public:
#
# FIXME: Not sure how to describe what this does.
mergeCursors: ->
positions = []
@@ -1271,27 +1269,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...) ->
@@ -1315,7 +1307,6 @@ class Editor extends Model
_.reduce(@getSelections(), reducer, [])
# Private:
preserveCursorPositionOnBufferReload: ->
cursorPosition = null
@subscribe @buffer, "will-reload", =>
@@ -1336,7 +1327,6 @@ class Editor extends Model
reloadGrammar: ->
@displayBuffer.reloadGrammar()
# Private:
shouldAutoIndent: ->
atom.config.get("editor.autoIndent")
@@ -1347,32 +1337,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: ->
"<Editor #{@id}>"
# Private:
logScreenLines: (start, end) -> @displayBuffer.logLines(start, end)
# Private:
handleGrammarChange: ->
@unfoldAll()
@emit 'grammar-changed'
# Private:
handleMarkerCreated: (marker) =>
if marker.matchesAttributes(@getSelectionMarkerAttributes())
@addSelection(marker)
# Private:
getSelectionMarkerAttributes: ->
type: 'selection', editorId: @id, invalidate: 'never'

View File

@@ -5,6 +5,7 @@ Q = require 'q'
{Emitter} = require 'emissary'
_ = require 'underscore-plus'
fs = require 'fs-plus'
runas = require 'runas'
# Public: Represents an individual file.
#
@@ -25,16 +26,14 @@ class File
# Public: Creates a new file.
#
# * path:
# A String containing the absolute path to the file
# * symlink:
# A Boolean indicating if the path is a symlink (default: false)
# path - A {String} containing the absolute path to the file
# symlink - A {Boolean} indicating if the path is a symlink (default: false).
constructor: (@path, @symlink=false) ->
throw new Error("#{@path} is a directory") if fs.isDirectorySync(@path)
@handleEventSubscriptions()
# Private: Subscribes to file system notifications when necessary.
# Subscribes to file system notifications when necessary.
handleEventSubscriptions: ->
eventNames = ['contents-changed', 'moved', 'removed']
@@ -49,24 +48,24 @@ class File
subscriptionsEmpty = _.every eventNames, (eventName) => @getSubscriptionCount(eventName) is 0
@unsubscribeFromNativeChangeEvents() if subscriptionsEmpty
# Private: Sets the path for the file.
# Sets the path for the file.
setPath: (@path) ->
# Public: Returns the path for the file.
# Public: Returns the {String} path for the file.
getPath: -> @path
# Public: Return the filename without any directory information.
# Public: Return the {String} filename without any directory information.
getBaseName: ->
path.basename(@path)
# Public: Overwrites the file with the given String.
write: (text) ->
previouslyExisted = @exists()
@writeFileWithPrivilegeEscalationSync(@getPath(), text)
@cachedContents = text
fs.writeFileSync(@getPath(), text)
@subscribeToNativeChangeEvents() if not previouslyExisted and @hasSubscriptions()
# Private: Deprecated
# Deprecated
readSync: (flushCache) ->
if not @exists()
@cachedContents = null
@@ -80,9 +79,8 @@ class File
# Public: Reads the contents of the file.
#
# * flushCache:
# A Boolean indicating whether to require a direct read or if a cached
# copy is acceptable.
# flushCache - A {Boolean} indicating whether to require a direct read or if
# a cached copy is acceptable.
#
# Returns a promise that resovles to a String.
read: (flushCache) ->
@@ -118,7 +116,6 @@ class File
exists: ->
fs.existsSync(@getPath())
# Private:
setDigest: (contents) ->
@digest = crypto.createHash('sha1').update(contents ? '').digest('hex')
@@ -126,7 +123,21 @@ class File
getDigest: ->
@digest ? @setDigest(@readSync())
# Private:
# Writes the text to specified path.
#
# Privilege escalation would be asked when current user doesn't have
# permission to the path.
writeFileWithPrivilegeEscalationSync: (path, text) ->
try
fs.writeFileSync(path, text)
catch error
if error.code is 'EACCES' and process.platform is 'darwin'
authopen = '/usr/libexec/authopen' # man 1 authopen
unless runas(authopen, ['-w', '-c', path], stdin: text) is 0
throw error
else
throw error
handleNativeChangeEvent: (eventType, path) ->
if eventType is "delete"
@unsubscribeFromNativeChangeEvents()
@@ -139,11 +150,9 @@ class File
@read(true).done (newContents) =>
@emit 'contents-changed' unless oldContents == newContents
# Private:
detectResurrectionAfterDelay: ->
_.delay (=> @detectResurrection()), 50
# Private:
detectResurrection: ->
if @exists()
@subscribeToNativeChangeEvents()
@@ -152,13 +161,11 @@ class File
@cachedContents = null
@emit "removed"
# Private:
subscribeToNativeChangeEvents: ->
unless @watchSubscription?
@watchSubscription = pathWatcher.watch @path, (eventType, path) =>
@handleNativeChangeEvent(eventType, path)
# Private:
unsubscribeFromNativeChangeEvents: ->
if @watchSubscription?
@watchSubscription.close()

View File

@@ -1,6 +1,6 @@
{Point, Range} = require 'text-buffer'
# Private: Represents a fold that collapses multiple buffer lines into a single
# Represents a fold that collapses multiple buffer lines into a single
# line on the screen.
#
# Their creation is managed by the {DisplayBuffer}.
@@ -10,8 +10,6 @@ class Fold
displayBuffer: null
marker: null
### Internal ###
constructor: (@displayBuffer, @marker) ->
@id = @marker.id
@displayBuffer.foldsByMarkerId[@marker.id] = this

View File

@@ -27,17 +27,19 @@ class Git
Emitter.includeInto(this)
Subscriber.includeInto(this)
# Private: Creates a new `Git` instance.
# Public: Creates a new Git instance.
#
# * path: The path to the git repository to open
# * options:
# + refreshOnWindowFocus:
# A Boolean that identifies if the windows should refresh
# path - The path to the Git repository to open.
# options - An object with the following keys (default: {}):
# :refreshOnWindowFocus - `true` to refresh the index and statuses when the
# window is focused.
#
# Returns a Git instance or null if the repository could not be opened.
@open: (path, options) ->
return null unless path
try
new Git(path, options)
catch e
catch
null
@exists: (path) ->
@@ -47,20 +49,6 @@ class Git
else
false
path: null
statuses: null
upstream: null
branch: null
statusTask: null
# Private: Creates a new `Git` object.
#
# * path: The {String} representing the path to your git working directory
# * options:
# + refreshOnWindowFocus: If `true`, {#refreshIndex} and {#refreshStatus}
# are called on focus
# + project: A project that supplies buffers that will be monitored for
# save and reload events to trigger status refreshes.
constructor: (path, options={}) ->
@repo = GitUtils.open(path)
unless @repo?
@@ -80,7 +68,7 @@ class Git
if @project?
@subscribe @project.eachBuffer (buffer) => @subscribeToBuffer(buffer)
# Private: Subscribes to buffer events.
# Subscribes to buffer events.
subscribeToBuffer: (buffer) ->
@subscribe buffer, 'saved reloaded path-changed', =>
if path = buffer.getPath()
@@ -100,29 +88,29 @@ class Git
@unsubscribe()
# Private: Returns the corresponding {Repository}
# Returns the corresponding {Repository}
getRepo: ->
unless @repo?
throw new Error("Repository has been destroyed")
@repo
# Public: Reread the index to update any values that have changed since the
# Reread the index to update any values that have changed since the
# last time the index was read.
refreshIndex: -> @getRepo().refreshIndex()
# Public: Returns the path of the repository.
# Public: Returns the {String} path of the repository.
getPath: ->
@path ?= fs.absolute(@getRepo().getPath())
# Public: Returns the working directory of the repository.
# Public: Returns the {String} working directory path of the repository.
getWorkingDirectory: -> @getRepo().getWorkingDirectory()
# Public: Returns the status of a single path in the repository.
# Public: Get the status of a single path in the repository.
#
# * path:
# A String defining a relative path
# path - A {String} repository-relative path.
#
# Returns a {Number}, FIXME representing what?
# Returns a {Number} representing the status. This value can be passed to
# {.isStatusModified} or {.isStatusNew} to get more information.
getPathStatus: (path) ->
currentPathStatus = @statuses[path] ? 0
pathStatus = @getRepo().getStatus(@relativize(path)) ? 0
@@ -134,7 +122,9 @@ class Git
@emit 'status-changed', path, pathStatus
pathStatus
# Public: Returns true if the given path is ignored.
# Public: Is the given path ignored?
#
# Returns a {Boolean}.
isPathIgnored: (path) -> @getRepo().isIgnored(@relativize(path))
# Public: Returns true if the given status indicates modification.
@@ -163,22 +153,22 @@ class Git
# `refs/remotes`. It also shortens the SHA-1 of a detached `HEAD` to 7
# characters.
#
# Returns a String.
# Returns a {String}.
getShortHead: -> @getRepo().getShortHead()
# Public: Restore the contents of a path in the working directory and index
# to the version at `HEAD`.
#
# This is essentially the same as running:
#
# ```
# git reset HEAD -- <path>
# git checkout HEAD -- <path>
# ```
#
# * path:
# The String path to checkout
# path - The {String} path to checkout.
#
# Returns a Boolean that's true if the method was successful.
# Returns a {Boolean} that's true if the method was successful.
checkoutHead: (path) ->
headCheckedOut = @getRepo().checkoutHead(@relativize(path))
@getPathStatus(path) if headCheckedOut
@@ -186,10 +176,9 @@ class Git
# Public: Checks out a branch in your repository.
#
# * reference:
# The String reference to checkout
# * create:
# A Boolean value which, if true creates the new reference if it doesn't exist.
# reference - The String reference to checkout
# create - A Boolean value which, if true creates the new reference if it
# doesn't exist.
#
# Returns a Boolean that's true if the method was successful.
checkoutReference: (reference, create) ->
@@ -200,27 +189,26 @@ class Git
# This compares the working directory contents of the path to the `HEAD`
# version.
#
# * path:
# The String path to check
# path - The {String} path to check.
#
# Returns an object with two keys, `added` and `deleted`. These will always
# be greater than 0.
# Returns an {Object} with the following keys:
# :added - The {Number} of added lines.
# :deleted - The {Number} of deleted lines.
getDiffStats: (path) -> @getRepo().getDiffStats(@relativize(path))
# Public: Identifies if a path is a submodule.
# Public: Is the given path a submodule in the repository?
#
# * path:
# The String path to check
# path - The {String} path to check.
#
# Returns a Boolean.
# Returns a {Boolean}.
isSubmodule: (path) -> @getRepo().isSubmodule(@relativize(path))
# Public: Retrieves the status of a directory.
# Public: Get the status of a directory in the repository's working directory.
#
# * path:
# The String path to check
# path - The {String} path to check.
#
# Returns a Number representing the status.
# Returns a {Number} representing the status. This value can be passed to
# {.isStatusModified} or {.isStatusNew} to get more information.
getDirectoryStatus: (directoryPath) ->
{sep} = require 'path'
directoryPath = "#{directoryPath}#{sep}"
@@ -232,16 +220,14 @@ class Git
# Public: Retrieves the line diffs comparing the `HEAD` version of the given
# path and the given text.
#
# This is similar to the commit numbers reported by `git status` when a
# remote tracking branch exists.
# path - The {String} path relative to the repository.
# text - The {String} to compare against the `HEAD` contents
#
# * path:
# The String path (relative to the repository)
# * text:
# The String to compare against the `HEAD` contents
#
# Returns an object with two keys, `ahead` and `behind`. These will always be
# greater than zero.
# Returns an {Array} of hunk {Object}s with the following keys:
# :oldStart - The line {Number} of the old hunk.
# :newStart - The line {Number} of the new hunk.
# :oldLines - The {Number} of lines in the old hunk.
# :newLines - The {Number} of lines in the new hunk
getLineDiffs: (path, text) ->
# Ignore eol of line differences on windows so that files checked in as
# LF don't report every line modified when the text contains CRLF endings.
@@ -257,7 +243,7 @@ class Git
# Public: Returns the upstream branch for the current HEAD, or null if there
# is no upstream branch for the current HEAD.
#
# Returns a String branch name such as `refs/remotes/origin/master`
# Returns a {String} branch name such as `refs/remotes/origin/master`.
getUpstreamBranch: -> @getRepo().getUpstreamBranch()
# Public: Returns the current SHA for the given reference.
@@ -265,19 +251,21 @@ class Git
# Public: Gets all the local and remote references.
#
# Returns an object with three keys: `heads`, `remotes`, and `tags`. Each key
# can be an array of strings containing the reference names.
# Returns an {Object} with the following keys:
# :heads - An {Array} of head reference names.
# :remotes - An {Array} of remote reference names.
# :tags - An {Array} of tag reference names.
getReferences: -> @getRepo().getReferences()
# Public: Returns the number of commits behind the current branch is from the
# default remote branch.
# its upstream remote branch.
getAheadBehindCount: (reference) -> @getRepo().getAheadBehindCount(reference)
# Public: Returns true if the given branch exists.
hasBranch: (branch) -> @getReferenceTarget("refs/heads/#{branch}")?
# Private: Refreshes the current git status in an outside process and
# asynchronously updates the relevant properties.
# Refreshes the current git status in an outside process and asynchronously
# updates the relevant properties.
refreshStatus: ->
@statusTask = Task.once require.resolve('./repository-status-handler'), @getPath(), ({statuses, upstream, branch}) =>
statusesUnchanged = _.isEqual(statuses, @statuses) and _.isEqual(upstream, @upstream) and _.isEqual(branch, @branch)

View File

@@ -2,14 +2,11 @@
{Range} = require 'text-buffer'
_ = require 'underscore-plus'
# Private: Represents the portion of the {EditorView} containing row numbers.
# Represents the portion of the {EditorView} containing row numbers.
#
# The gutter also indicates if rows are folded.
module.exports =
class GutterView extends View
### Internal ###
@content: ->
@div class: 'gutter', =>
@div outlet: 'lineNumbers', class: 'line-numbers'
@@ -51,8 +48,6 @@ class GutterView extends View
$(document).on "mousemove.gutter-#{editorView.id}", moveHandler
$(document).one "mouseup.gutter-#{editorView.id}", => $(document).off 'mousemove', moveHandler
### Public ###
# Retrieves the containing {EditorView}.
#
# Returns an {EditorView}.
@@ -138,8 +133,6 @@ class GutterView extends View
el.classList.remove(klass) if hasClass
classesRemoved
### Internal ###
updateLineNumbers: (changes, startScreenRow, endScreenRow) ->
# Check if we have something already rendered that overlaps the requested range
updateAllLines = not (startScreenRow? and endScreenRow?)
@@ -223,7 +216,7 @@ class GutterView extends View
html
# Private: Called to update the 'foldable' class of line numbers when there's
# Called to update the 'foldable' class of line numbers when there's
# a change to the display buffer that doesn't regenerate all the line numbers
# anyway.
updateFoldableClasses: (changes) ->

View File

@@ -2,8 +2,6 @@ _ = require 'underscore-plus'
fs = require 'fs-plus'
{specificity} = require 'clear-cut'
### Internal ###
module.exports =
class KeyBinding
@parser: null

View File

@@ -9,19 +9,23 @@ File = require './file'
Modifiers = ['alt', 'control', 'ctrl', 'shift', 'cmd']
# Internal: Associates keymaps with actions.
# Public: Associates keybindings with commands.
#
# Keymaps are defined in a CSON format. A typical keymap looks something like this:
# An instance of this class is always available as the `atom.keymap` global.
#
# Keymaps are defined in a CSON/JSON format. A typical keymap looks something
# like this:
#
# ```cson
# 'body':
# 'ctrl-l': 'package:do-something'
#'.someClass':
# 'enter': 'package:confirm'
# 'ctrl-l': 'package:do-something'
# '.someClass':
# 'enter': 'package:confirm'
# ```
#
# As a key, you define the DOM element you want to work on, using CSS notation. For that
# key, you define one or more key:value pairs, associating keystrokes with a command to execute.
# As a key, you define the DOM element you want to work on, using CSS notation.
# For that key, you define one or more key:value pairs, associating keystrokes
# with a command to execute.
module.exports =
class Keymap
Emitter.includeInto(this)
@@ -39,10 +43,8 @@ class Keymap
# Public: Returns a array of {KeyBinding}s (sorted by selector specificity)
# that match a keystroke and element.
#
# * keystroke:
# The string representing the keys pressed (e.g. ctrl-P).
# * element:
# The DOM node that will match a {KeyBinding}'s selector.
# keystroke - The {String} representing the keys pressed (e.g. ctrl-P).
# element - The DOM node that will match a {KeyBinding}'s selector.
keyBindingsForKeystrokeMatchingElement: (keystroke, element) ->
keyBindings = @keyBindingsForKeystroke(keystroke)
@keyBindingsMatchingElement(element, keyBindings)
@@ -50,41 +52,37 @@ class Keymap
# Public: Returns a array of {KeyBinding}s (sorted by selector specificity)
# that match a command.
#
# * command:
# The string representing the command (tree-view:toggle)
# * element:
# The DOM node that will match a {KeyBinding}'s selector.
# command - The {String} representing the command (tree-view:toggle).
# element - The DOM node that will match a {KeyBinding}'s selector.
keyBindingsForCommandMatchingElement: (command, element) ->
keyBindings = @keyBindingsForCommand(command)
@keyBindingsMatchingElement(element, keyBindings)
# Public: Returns an array of {KeyBinding}s that match a keystroke
# * keystroke:
# The string representing the keys pressed (e.g. ctrl-P)
#
# keystroke: The {String} representing the keys pressed (e.g. ctrl-P)
keyBindingsForKeystroke: (keystroke) ->
keystroke = KeyBinding.normalizeKeystroke(keystroke)
@keyBindings.filter (keyBinding) -> keyBinding.matches(keystroke)
# Public: Returns an array of {KeyBinding}s that match a command
# * keystroke:
# The string representing the keys pressed (e.g. ctrl-P)
#
# keystroke - The {String} representing the keys pressed (e.g. ctrl-P)
keyBindingsForCommand: (command) ->
@keyBindings.filter (keyBinding) -> keyBinding.command == command
# Public: Returns a array of {KeyBinding}s (sorted by selector specificity)
# whos selector matches the element.
#
# * element:
# The DOM node that will match a {KeyBinding}'s selector.
# element - The DOM node that will match a {KeyBinding}'s selector.
keyBindingsMatchingElement: (element, keyBindings=@keyBindings) ->
keyBindings = keyBindings.filter ({selector}) -> $(element).closest(selector).length > 0
keyBindings.sort (a, b) -> a.compare(b)
# Public: Returns a keystroke string derived from an event.
# * event:
# A DOM or jQuery event
# * previousKeystroke:
# An optional string used for multiKeystrokes
#
# event - A DOM or jQuery event.
# previousKeystroke - An optional string used for multiKeystrokes.
keystrokeStringForEvent: (event, previousKeystroke) ->
if event.originalEvent.keyIdentifier.indexOf('U+') == 0
hexCharCode = event.originalEvent.keyIdentifier[2..]

View File

@@ -3,30 +3,19 @@ _ = require 'underscore-plus'
{OnigRegExp} = require 'oniguruma'
{Emitter, Subscriber} = require 'emissary'
### Internal ###
module.exports =
class LanguageMode
Emitter.includeInto(this)
Subscriber.includeInto(this)
buffer: null
grammar: null
editor: null
currentGrammarScore: null
### Internal ###
destroy: ->
@unsubscribe()
### Public ###
# Sets up a `LanguageMode` for the given {Editor}.
#
# editor - The {Editor} to associate with
constructor: (@editor) ->
@buffer = @editor.buffer
{@buffer} = @editor
destroy: ->
@unsubscribe()
toggleLineCommentForBufferRow: (row) ->
@toggleLineCommentsForBufferRows(row, row)
@@ -187,7 +176,7 @@ class LanguageMode
isFoldableAtBufferRow: (bufferRow) ->
@isFoldableCodeAtBufferRow(bufferRow) or @isFoldableCommentAtBufferRow(bufferRow)
# Private: Returns a {Boolean} indicating whether the given buffer row starts
# Returns a {Boolean} indicating whether the given buffer row starts
# a a foldable row range due to the code's indentation patterns.
isFoldableCodeAtBufferRow: (bufferRow) ->
return false if @editor.isBufferRowBlank(bufferRow) or @isLineCommentedAtBufferRow(bufferRow)
@@ -195,14 +184,14 @@ class LanguageMode
return false unless nextNonEmptyRow?
@editor.indentationForBufferRow(nextNonEmptyRow) > @editor.indentationForBufferRow(bufferRow)
# Private: Returns a {Boolean} indicating whether the given buffer row starts
# Returns a {Boolean} indicating whether the given buffer row starts
# a foldable row range due to being the start of a multi-line comment.
isFoldableCommentAtBufferRow: (bufferRow) ->
@isLineCommentedAtBufferRow(bufferRow) and
@isLineCommentedAtBufferRow(bufferRow + 1) and
not @isLineCommentedAtBufferRow(bufferRow - 1)
# Private: Returns a {Boolean} indicating whether the line at the given buffer
# Returns a {Boolean} indicating whether the line at the given buffer
# row is a comment.
isLineCommentedAtBufferRow: (bufferRow) ->
return false unless 0 <= bufferRow <= @editor.getLastBufferRow()

View File

@@ -5,7 +5,7 @@ LessCache = require 'less-cache'
tmpDir = if process.platform is 'win32' then os.tmpdir() else '/tmp'
# Private: {LessCache} wrapper used by {ThemeManager} to read stylesheets.
# {LessCache} wrapper used by {ThemeManager} to read stylesheets.
module.exports =
class LessCompileCache
Subscriber.includeInto(this)

View File

@@ -8,61 +8,81 @@ fs = require 'fs-plus'
# Public: Provides a registry for menu items that you'd like to appear in the
# application menu.
#
# Should be accessed via `atom.menu`.
# An instance of this class is always available as the `atom.menu` global.
module.exports =
class MenuManager
# Private:
constructor: ({@resourcePath}) ->
@pendingUpdateOperation = null
@template = []
atom.keymap.on 'bundled-keymaps-loaded', => @loadPlatformItems()
# Public: Adds the given item definition to the existing template.
#
# * item:
# An object which describes a menu item as defined by
# https://github.com/atom/atom-shell/blob/master/docs/api/browser/menu.md
# ## Example
# ```coffee
# atom.menu.add [
# {
# label: 'Hello'
# submenu : [{label: 'World!', command: 'hello:world'}]
# }
# ]
# ```
#
# items - An {Array} of menu item {Object}s containing the keys:
# :label - The {String} menu label.
# :submenu - An optional {Array} of sub menu items.
# :command - An optional {String} command to trigger when the item is
# clicked.
#
# Returns nothing.
add: (items) ->
@merge(@template, item) for item in items
@update()
# Private: Should the binding for the given selector be included in the menu
# Should the binding for the given selector be included in the menu
# commands.
#
# * selector: A String selector to check.
# selector - A {String} selector to check.
#
# Returns true to include the selector, false otherwise.
includeSelector: (selector) ->
return true if document.body.webkitMatchesSelector(selector)
# Simulate an .editor element attached to a body element that has the same
# classes as the current body element.
# Simulate an .editor element attached to a .workspace element attached to
# a body element that has the same classes as the current body element.
unless @testEditor?
testBody = document.createElement('body')
testBody.classList.add(@classesForElement(document.body)...)
testWorkspace = document.createElement('body')
workspaceClasses = @classesForElement(document.body.querySelector('.workspace')) ? ['.workspace']
testWorkspace.classList.add(workspaceClasses...)
testBody.appendChild(testWorkspace)
@testEditor = document.createElement('div')
@testEditor.classList.add('editor')
testBody = document.createElement('body')
testBody.classList.add(document.body.classList.toString().split(' ')...)
testBody.appendChild(@testEditor)
testWorkspace.appendChild(@testEditor)
@testEditor.webkitMatchesSelector(selector)
# Public: Refreshes the currently visible menu.
update: ->
keystrokesByCommand = {}
for binding in atom.keymap.getKeyBindings() when @includeSelector(binding.selector)
keystrokesByCommand[binding.command] ?= []
keystrokesByCommand[binding.command].push binding.keystroke
@sendToBrowserProcess(@template, keystrokesByCommand)
clearImmediate(@pendingUpdateOperation) if @pendingUpdateOperation?
@pendingUpdateOperation = setImmediate =>
keystrokesByCommand = {}
for binding in atom.keymap.getKeyBindings() when @includeSelector(binding.selector)
keystrokesByCommand[binding.command] ?= []
keystrokesByCommand[binding.command].push binding.keystroke
@sendToBrowserProcess(@template, keystrokesByCommand)
# Private:
loadPlatformItems: ->
menusDirPath = path.join(@resourcePath, 'menus')
platformMenuPath = fs.resolve(menusDirPath, process.platform, ['cson', 'json'])
{menu} = CSON.readFileSync(platformMenuPath)
@add(menu)
# Private: Merges an item in a submenu aware way such that new items are always
# Merges an item in a submenu aware way such that new items are always
# appended to the bottom of existing menus where possible.
merge: (menu, item) ->
item = _.deepClone(item)
@@ -72,7 +92,7 @@ class MenuManager
else
menu.push(item) unless _.find(menu, (i) => @normalizeLabel(i.label) == @normalizeLabel(item.label))
# Private: OSX can't handle displaying accelerators for multiple keystrokes.
# OSX can't handle displaying accelerators for multiple keystrokes.
# If they are sent across, it will stop processing accelerators for the rest
# of the menu items.
filterMultipleKeystroke: (keystrokesByCommand) ->
@@ -85,12 +105,10 @@ class MenuManager
filtered[key].push(binding)
filtered
# Private:
sendToBrowserProcess: (template, keystrokesByCommand) ->
keystrokesByCommand = @filterMultipleKeystroke(keystrokesByCommand)
ipc.sendChannel 'update-application-menu', template, keystrokesByCommand
# Private:
normalizeLabel: (label) ->
return undefined unless label?
@@ -98,3 +116,7 @@ class MenuManager
label.replace(/\&/g, '')
else
label
# Get an {Array} of {String} classes for the given element.
classesForElement: (element) ->
element?.classList.toString().split(' ') ? []

View File

@@ -1,11 +1,14 @@
{Emitter} = require 'emissary'
fs = require 'fs-plus'
_ = require 'underscore-plus'
Q = require 'q'
Package = require './package'
path = require 'path'
# Public: Package manager for coordinating the lifecycle of Atom packages.
#
# An instance of this class is always available as the `atom.packages` global.
#
# Packages can be loaded, activated, and deactivated, and unloaded:
# * Loading a package reads and parses the package's metadata and resources
# such as keymaps, menus, stylesheets, etc.
@@ -17,13 +20,10 @@ path = require 'path'
#
# Packages can also be enabled/disabled via the `core.disabledPackages` config
# settings and also by calling `enablePackage()/disablePackage()`.
#
# An instance of this class is globally available via `atom.packages`.
module.exports =
class PackageManager
Emitter.includeInto(this)
# Private:
constructor: ({configDirPath, devMode, @resourcePath}) ->
@packageDirPaths = [path.join(configDirPath, "packages")]
if devMode
@@ -32,7 +32,6 @@ class PackageManager
@loadedPackages = {}
@activePackages = {}
@packageStates = {}
@observingDisabledPackages = false
@packageActivators = []
@registerPackageActivator(this, ['atom', 'textmate'])
@@ -47,11 +46,9 @@ class PackageManager
getPackageDirPaths: ->
_.clone(@packageDirPaths)
# Private:
getPackageState: (name) ->
@packageStates[name]
# Private:
setPackageState: (name, state) ->
@packageStates[name] = state
@@ -67,44 +64,44 @@ class PackageManager
pack?.disable()
pack
# Private: Activate all the packages that should be activated.
# Activate all the packages that should be activated.
activate: ->
for [activator, types] in @packageActivators
packages = @getLoadedPackagesForTypes(types)
activator.activatePackages(packages)
@emit 'activated'
# Private: another type of package manager can handle other package types.
# another type of package manager can handle other package types.
# See ThemeManager
registerPackageActivator: (activator, types) ->
@packageActivators.push([activator, types])
# Private:
activatePackages: (packages) ->
@activatePackage(pack.name) for pack in packages
@observeDisabledPackages()
# Private: Activate a single package by name
activatePackage: (name, options) ->
return pack if pack = @getActivePackage(name)
if pack = @loadPackage(name, options)
@activePackages[pack.name] = pack
pack.activate(options)
pack
# Activate a single package by name
activatePackage: (name) ->
if pack = @getActivePackage(name)
Q(pack)
else
pack = @loadPackage(name)
pack.activate().then =>
@activePackages[pack.name] = pack
pack
# Private: Deactivate all packages
# Deactivate all packages
deactivatePackages: ->
@deactivatePackage(pack.name) for pack in @getActivePackages()
@deactivatePackage(pack.name) for pack in @getLoadedPackages()
@unobserveDisabledPackages()
# Private: Deactivate the package with the given name
# Deactivate the package with the given name
deactivatePackage: (name) ->
if pack = @getActivePackage(name)
pack = @getLoadedPackage(name)
if @isPackageActive(name)
@setPackageState(pack.name, state) if state = pack.serialize?()
pack.deactivate()
delete @activePackages[pack.name]
else
throw new Error("No active package for name '#{name}'")
pack.deactivate()
delete @activePackages[pack.name]
# Public: Get an array of all the active packages
getActivePackages: ->
@@ -118,17 +115,12 @@ class PackageManager
isPackageActive: (name) ->
@getActivePackage(name)?
# Private:
unobserveDisabledPackages: ->
return unless @observingDisabledPackages
atom.config.unobserve('core.disabledPackages')
@observingDisabledPackages = false
@disabledPackagesSubscription?.off()
@disabledPackagesSubscription = null
# Private:
observeDisabledPackages: ->
return if @observingDisabledPackages
atom.config.observe 'core.disabledPackages', callNow: false, (disabledPackages, {previous}) =>
@disabledPackagesSubscription ?= atom.config.observe 'core.disabledPackages', callNow: false, (disabledPackages, {previous}) =>
packagesToEnable = _.difference(previous, disabledPackages)
packagesToDisable = _.difference(disabledPackages, previous)
@@ -136,10 +128,7 @@ class PackageManager
@activatePackage(packageName) for packageName in packagesToEnable
null
@observingDisabledPackages = true
# Private:
loadPackages: (options) ->
loadPackages: ->
# Ensure atom exports is already in the require cache so the load time
# of the first package isn't skewed by being the first to require atom
require '../exports/atom'
@@ -147,27 +136,24 @@ class PackageManager
packagePaths = @getAvailablePackagePaths()
packagePaths = packagePaths.filter (packagePath) => not @isPackageDisabled(path.basename(packagePath))
packagePaths = _.uniq packagePaths, (packagePath) -> path.basename(packagePath)
@loadPackage(packagePath, options) for packagePath in packagePaths
@loadPackage(packagePath) for packagePath in packagePaths
@emit 'loaded'
# Private:
loadPackage: (nameOrPath, options) ->
loadPackage: (nameOrPath) ->
if packagePath = @resolvePackagePath(nameOrPath)
name = path.basename(nameOrPath)
return pack if pack = @getLoadedPackage(name)
pack = Package.load(packagePath, options)
pack = Package.load(packagePath)
@loadedPackages[pack.name] = pack if pack?
pack
else
throw new Error("Could not resolve '#{nameOrPath}' to a package path")
# Private:
unloadPackages: ->
@unloadPackage(name) for name in _.keys(@loadedPackages)
null
# Private:
unloadPackage: (name) ->
if @isPackageActive(name)
throw new Error("Tried to unload active package '#{name}'")
@@ -189,9 +175,9 @@ class PackageManager
getLoadedPackages: ->
_.values(@loadedPackages)
# Private: Get packages for a certain package type
# Get packages for a certain package type
#
# * types: an {Array} of {String}s like ['atom', 'textmate']
# types - an {Array} of {String}s like ['atom', 'textmate'].
getLoadedPackagesForTypes: (types) ->
pack for pack in @getLoadedPackages() when pack.getType() in types
@@ -209,7 +195,6 @@ class PackageManager
isPackageDisabled: (name) ->
_.include(atom.config.get('core.disabledPackages') ? [], name)
# Private:
hasAtomEngine: (packagePath) ->
metadata = Package.loadMetadata(packagePath, true)
metadata?.engines?.atom?
@@ -218,7 +203,6 @@ class PackageManager
isBundledPackage: (name) ->
@getPackageDependencies().hasOwnProperty(name)
# Private:
getPackageDependencies: ->
unless @packageDependencies?
try

View File

@@ -1,7 +1,6 @@
CSON = require 'season'
{basename, join} = require 'path'
### Internal ###
module.exports =
class Package
@build: (path) ->
@@ -23,9 +22,9 @@ class Package
pack
@load: (path, options) ->
@load: (path) ->
pack = @build(path)
pack?.load(options)
pack?.load()
pack
@loadMetadata: (path, ignoreErrors=false) ->
@@ -44,9 +43,6 @@ class Package
constructor: (@path) ->
@name = basename(@path)
isActive: ->
atom.packages.isPackageActive(@name)
enable: ->
atom.config.removeAtKeyPath('core.disabledPackages', @metadata.name)
@@ -54,9 +50,8 @@ class Package
atom.config.pushAtKeyPath('core.disabledPackages', @metadata.name)
isTheme: ->
!!@metadata?.theme
@metadata?.theme?
# Private:
measure: (key, fn) ->
startTime = Date.now()
value = fn()

View File

@@ -1,7 +1,6 @@
{View} = require './space-pen-extensions'
PaneView = null
### Internal ###
module.exports =
class PaneAxisView extends View
initialize: (@model) ->

View File

@@ -2,7 +2,6 @@
_ = require 'underscore-plus'
PaneAxisView = require './pane-axis-view'
# Internal:
module.exports =
class PaneColumnView extends PaneAxisView

View File

@@ -3,7 +3,7 @@ Delegator = require 'delegato'
PaneView = require './pane-view'
PaneContainer = require './pane-container'
# Private: Manages the list of panes within a {WorkspaceView}
# Manages the list of panes within a {WorkspaceView}
module.exports =
class PaneContainerView extends View
Delegator.includeInto(this)
@@ -27,8 +27,6 @@ class PaneContainerView extends View
viewClass = model.getViewClass()
model._view ?= new viewClass(model)
### Public ###
getRoot: ->
@children().first().view()
@@ -98,3 +96,50 @@ class PaneContainerView extends View
focusPreviousPane: ->
@model.activatePreviousPane()
focusPaneAbove: ->
@nearestPaneInDirection('above')?.focus()
focusPaneBelow: ->
@nearestPaneInDirection('below')?.focus()
focusPaneOnLeft: ->
@nearestPaneInDirection('left')?.focus()
focusPaneOnRight: ->
@nearestPaneInDirection('right')?.focus()
nearestPaneInDirection: (direction) ->
distance = (pointA, pointB) ->
x = pointB.x - pointA.x
y = pointB.y - pointA.y
Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
pane = @getActivePane()
box = @boundingBoxForPane(pane)
panes = @getPanes()
.filter (otherPane) =>
otherBox = @boundingBoxForPane(otherPane)
switch direction
when 'left' then otherBox.right.x <= box.left.x
when 'right' then otherBox.left.x >= box.right.x
when 'above' then otherBox.bottom.y <= box.top.y
when 'below' then otherBox.top.y >= box.bottom.y
.sort (paneA, paneB) =>
boxA = @boundingBoxForPane(paneA)
boxB = @boundingBoxForPane(paneB)
switch direction
when 'left' then distance(box.left, boxA.right) - distance(box.left, boxB.right)
when 'right' then distance(box.right, boxA.left) - distance(box.right, boxB.left)
when 'above' then distance(box.top, boxA.bottom) - distance(box.top, boxB.bottom)
when 'below' then distance(box.bottom, boxA.top) - distance(box.bottom, boxB.top)
panes[0]
boundingBoxForPane: (pane) ->
boundingBox = pane[0].getBoundingClientRect()
left: {x: boundingBox.left, y: boundingBox.top}
right: {x: boundingBox.right, y: boundingBox.top}
top: {x: boundingBox.left, y: boundingBox.top}
bottom: {x: boundingBox.left, y: boundingBox.bottom}

View File

@@ -86,6 +86,6 @@ class PaneContainer extends Model
itemDestroyed: (item) ->
@emit 'item-destroyed', item
# Private: Called by Model superclass when destroyed
# Called by Model superclass when destroyed
destroyed: ->
pane.destroy() for pane in @getPanes()

View File

@@ -2,8 +2,6 @@
_ = require 'underscore-plus'
PaneAxisView = require './pane-axis-view'
### Internal ###
module.exports =
class PaneRowView extends PaneAxisView
@content: ->

View File

@@ -27,11 +27,10 @@ class PaneView extends View
'destroyItem', 'destroyItems', 'destroyActiveItem', 'destroyInactiveItems',
'saveActiveItem', 'saveActiveItemAs', 'saveItem', 'saveItemAs', 'saveItems',
'itemForUri', 'activateItemForUri', 'promptToSaveItem', 'copyActiveItem', 'isActive',
'activate', toProperty: 'model'
'activate', 'getActiveItem', toProperty: 'model'
previousActiveItem: null
# Private:
initialize: (args...) ->
if args[0] instanceof Pane
@model = args[0]
@@ -97,7 +96,6 @@ class PaneView extends View
# Deprecated: Use ::activatePreviousItem
showPreviousItem: -> @activatePreviousItem()
# Private:
afterAttach: (onDom) ->
@focus() if @model.focused and onDom
@@ -167,11 +165,9 @@ class PaneView extends View
@unsubscribe(item) if typeof item.off is 'function'
@trigger 'pane:before-item-destroyed', [item]
# Private:
activeItemTitleChanged: =>
@trigger 'pane:active-item-title-changed'
# Private:
viewForItem: (item) ->
return unless item?
if item instanceof $
@@ -184,7 +180,6 @@ class PaneView extends View
@viewsByItem.set(item, view)
view
# Private:
@::accessor 'activeView', -> @viewForItem(@activeItem)
splitLeft: (items...) -> @model.splitLeft({items})._view
@@ -195,14 +190,15 @@ class PaneView extends View
splitDown: (items...) -> @model.splitDown({items})._view
# Public:
# Public: Get the container view housing this pane.
#
# Returns a {View}.
getContainer: ->
@closest('.panes').view()
beforeRemove: ->
@model.destroy() unless @model.isDestroyed()
# Private:
remove: (selector, keepData) ->
return super if keepData
@unsubscribe()

View File

@@ -3,6 +3,7 @@
{Model, Sequence} = require 'theorist'
Serializable = require 'serializable'
PaneAxis = require './pane-axis'
Editor = require './editor'
PaneView = null
# Public: A container for multiple items, one of which is *active* at a given
@@ -27,7 +28,6 @@ class Pane extends Model
.map((activePane) => activePane is this)
.distinctUntilChanged()
# Private:
constructor: (params) ->
super
@@ -43,31 +43,31 @@ class Pane extends Model
@activate() if params?.active
# Private: Called by the Serializable mixin during serialization.
# Called by the Serializable mixin during serialization.
serializeParams: ->
items: compact(@items.map((item) -> item.serialize?()))
activeItemUri: @activeItem?.getUri?()
focused: @focused
active: @active
# Private: Called by the Serializable mixin during deserialization.
# Called by the Serializable mixin during deserialization.
deserializeParams: (params) ->
{items, activeItemUri} = params
params.items = compact(items.map (itemState) -> atom.deserializers.deserialize(itemState))
params.activeItem = find params.items, (item) -> item.getUri?() is activeItemUri
params
# Private: Called by the view layer to construct a view for this model.
# Called by the view layer to construct a view for this model.
getViewClass: -> PaneView ?= require './pane-view'
isActive: -> @active
# Private: Called by the view layer to indicate that the pane has gained focus.
# Called by the view layer to indicate that the pane has gained focus.
focus: ->
@focused = true
@activate() unless @isActive()
# Private: Called by the view layer to indicate that the pane has lost focus.
# Called by the view layer to indicate that the pane has lost focus.
blur: ->
@focused = false
true # if this is called from an event handler, don't cancel it
@@ -78,13 +78,25 @@ class Pane extends Model
@container?.activePane = this
@emit 'activated'
# Private:
getPanes: -> [this]
# Public:
# Public: Get the items in this pane.
#
# Returns an {Array} of items.
getItems: ->
@items.slice()
# Public: Get the active pane item in this pane.
#
# Returns a pane item.
getActiveItem: ->
@activeItem
# Public: Returns an {Editor} if the pane item is an {Editor}, or null
# otherwise.
getActiveEditor: ->
@activeItem if @activeItem instanceof Editor
# Public: Returns the item at the specified index.
itemAtIndex: (index) ->
@items[index]
@@ -105,15 +117,15 @@ class Pane extends Model
else
@activateItemAtIndex(@items.length - 1)
# Public: Returns the index of the current active item.
# Returns the index of the current active item.
getActiveItemIndex: ->
@items.indexOf(@activeItem)
# Public: Makes the item at the given index active.
# Makes the item at the given index active.
activateItemAtIndex: (index) ->
@activateItem(@itemAtIndex(index))
# Public: Makes the given item active, adding the item if necessary.
# Makes the given item active, adding the item if necessary.
activateItem: (item) ->
if item?
@addItem(item)
@@ -121,11 +133,9 @@ class Pane extends Model
# Public: Adds the item to the pane.
#
# * item:
# The item to add. It can be a model with an associated view or a view.
# * index:
# An optional index at which to add the item. If omitted, the item is
# added after the current active item.
# item - The item to add. It can be a model with an associated view or a view.
# index - An optional index at which to add the item. If omitted, the item is
# added after the current active item.
#
# Returns the added item
addItem: (item, index=@getActiveItemIndex() + 1) ->
@@ -138,12 +148,11 @@ class Pane extends Model
# Public: Adds the given items to the pane.
#
# * items:
# An {Array} of items to add. Items can be models with associated views
# or views. Any items that are already present in items will not be added.
# * index:
# An optional index at which to add the item. If omitted, the item is
# added after the current active item.
# items - An {Array} of items to add. Items can be models with associated
# views or views. Any items that are already present in items will
# not be added.
# index - An optional index at which to add the item. If omitted, the item is
# added after the current active item.
#
# Returns an {Array} of the added items
addItems: (items, index=@getActiveItemIndex() + 1) ->
@@ -151,7 +160,6 @@ class Pane extends Model
@addItem(item, index + i) for item, i in items
items
# Private:
removeItem: (item, destroying) ->
index = @items.indexOf(item)
return if index is -1
@@ -207,7 +215,7 @@ class Pane extends Model
destroy: ->
super unless @container?.isAlive() and @container?.getPanes().length is 1
# Private: Called by model superclass.
# Called by model superclass.
destroyed: ->
@container.activateNextPane() if @isActive()
item.destroy?() for item in @items.slice()
@@ -238,8 +246,9 @@ class Pane extends Model
# Public: Saves the specified item.
#
# * item: The item to save.
# * nextAction: An optional function which will be called after the item is saved.
# item - The item to save.
# nextAction - An optional function which will be called after the item is
# saved.
saveItem: (item, nextAction) ->
if item?.getUri?()
item.save?()
@@ -249,8 +258,9 @@ class Pane extends Model
# Public: Saves the given item at a prompted-for location.
#
# * item: The item to save.
# * nextAction: An optional function which will be called after the item is saved.
# item - The item to save.
# nextAction - An optional function which will be called after the item is
# saved.
saveItemAs: (item, nextAction) ->
return unless item?.saveAs?
@@ -279,15 +289,14 @@ class Pane extends Model
else
false
# Private:
copyActiveItem: ->
if @activeItem?
@activeItem.copy?() ? atom.deserializers.deserialize(@activeItem.serialize())
# Public: Creates a new pane to the left of the receiver.
#
# * params:
# + items: An optional array of items with which to construct the new pane.
# params - An object with keys:
# :items - An optional array of items with which to construct the new pane.
#
# Returns the new {Pane}.
splitLeft: (params) ->
@@ -295,8 +304,8 @@ class Pane extends Model
# Public: Creates a new pane to the right of the receiver.
#
# * params:
# + items: An optional array of items with which to construct the new pane.
# params - An object with keys:
# :items - An optional array of items with which to construct the new pane.
#
# Returns the new {Pane}.
splitRight: (params) ->
@@ -304,8 +313,8 @@ class Pane extends Model
# Public: Creates a new pane above the receiver.
#
# * params:
# + items: An optional array of items with which to construct the new pane.
# params - An object with keys:
# :items - An optional array of items with which to construct the new pane.
#
# Returns the new {Pane}.
splitUp: (params) ->
@@ -313,14 +322,13 @@ class Pane extends Model
# Public: Creates a new pane below the receiver.
#
# * params:
# + items: An optional array of items with which to construct the new pane.
# params - An object with keys:
# :items - An optional array of items with which to construct the new pane.
#
# Returns the new {Pane}.
splitDown: (params) ->
@split('vertical', 'after', params)
# Private:
split: (orientation, side, params) ->
if @parent.orientation isnt orientation
@parent.replaceChild(this, new PaneAxis({@container, orientation, children: [this]}))
@@ -333,7 +341,7 @@ class Pane extends Model
newPane.activate()
newPane
# Private: If the parent is a horizontal axis, returns its first child;
# If the parent is a horizontal axis, returns its first child;
# otherwise this pane.
findLeftmostSibling: ->
if @parent.orientation is 'horizontal'
@@ -341,7 +349,7 @@ class Pane extends Model
else
this
# Private: If the parent is a horizontal axis, returns its last child;
# If the parent is a horizontal axis, returns its last child;
# otherwise returns a new pane created by splitting this pane rightward.
findOrCreateRightmostSibling: ->
if @parent.orientation is 'horizontal'

View File

@@ -1,36 +0,0 @@
clipboard = require 'clipboard'
crypto = require 'crypto'
# Public: Represents the clipboard used for copying and pasting in Atom.
#
# A pasteboard instance is always available under the `atom.pasteboard` global.
module.exports =
class Pasteboard
signatureForMetadata: null
# Creates an `md5` hash of some text.
#
# text - A {String} to encrypt.
#
# Returns an encrypted {String}.
md5: (text) ->
crypto.createHash('md5').update(text, 'utf8').digest('hex')
# Public: Write the given text to the clipboard.
#
# text - A {String} to store.
# metadata - An {Object} of additional info to associate with the text.
write: (text, metadata) ->
@signatureForMetadata = @md5(text)
@metadata = metadata
clipboard.writeText(text)
# Public: Read the text from the clipboard.
#
# Returns an {Array}. The first element is the saved text and the second is
# any metadata associated with the text.
read: ->
text = clipboard.readText()
value = [text]
value.push(@metadata) if @signatureForMetadata == @md5(text)
value

View File

@@ -16,7 +16,7 @@ Git = require './git'
# Public: Represents a project that's opened in Atom.
#
# There is always a project available under the `atom.project` global.
# An instance of this class is always available as the `atom.project` global.
module.exports =
class Project extends Model
atom.deserializers.add(this)
@@ -30,11 +30,12 @@ class Project extends Model
constructor: ({path, @buffers}={}) ->
@buffers ?= []
@openers = []
for buffer in @buffers
do (buffer) =>
buffer.once 'destroyed', => @removeBuffer(buffer)
@openers = []
@editors = []
@setPath(path)
@@ -46,36 +47,16 @@ class Project extends Model
params.buffers = params.buffers.map (bufferState) -> atom.deserializers.deserialize(bufferState)
params
# Public: Register an opener for project files.
#
# An {Editor} will be used if no openers return a value.
#
# ## Example:
# ```coffeescript
# atom.project.registerOpener (filePath) ->
# if path.extname(filePath) is '.toml'
# return new TomlEditor(filePath)
# ```
#
# * opener: A function to be called when a path is being opened.
registerOpener: (opener) -> @openers.push(opener)
# Public: Remove a previously registered opener.
unregisterOpener: (opener) -> _.remove(@openers, opener)
# Private:
destroyed: ->
editor.destroy() for editor in @getEditors()
buffer.destroy() for buffer in @getBuffers()
@destroyRepo()
# Private:
destroyRepo: ->
if @repo?
@repo.destroy()
@repo = null
# Private:
destroyUnretainedBuffers: ->
buffer.destroy() for buffer in @getBuffers() when not buffer.isRetained()
@@ -111,8 +92,7 @@ class Project extends Model
# the path is already absolute or if it is prefixed with a scheme, it is
# returned unchanged.
#
# * uri:
# The String name of the path to convert
# uri - The {String} name of the path to convert.
#
# Returns a String.
resolve: (uri) ->
@@ -133,71 +113,53 @@ class Project extends Model
contains: (pathToCheck) ->
@rootDirectory?.contains(pathToCheck) ? false
# Public: Given a path to a file, this constructs and associates a new
# Given a path to a file, this constructs and associates a new
# {Editor}, showing the file.
#
# * filePath:
# The {String} path of the file to associate with
# * options:
# Options that you can pass to the {Editor} constructor
# filePath - The {String} path of the file to associate with.
# options - Options that you can pass to the {Editor} constructor.
#
# Returns a promise that resolves to an {Editor}.
open: (filePath, options={}) ->
filePath = @resolve(filePath)
resource = null
_.find @openers, (opener) -> resource = opener(filePath, options)
@bufferForPath(filePath).then (buffer) =>
@buildEditorForBuffer(buffer, options)
if resource
Q(resource)
else
@bufferForPath(filePath).then (buffer) =>
@buildEditorForBuffer(buffer, options)
# Private: Only be used in specs
# Deprecated
openSync: (filePath, options={}) ->
filePath = @resolve(filePath)
for opener in @openers
return resource if resource = opener(filePath, options)
@buildEditorForBuffer(@bufferForPathSync(filePath), options)
# Public: Retrieves all {Editor}s for all open files.
#
# Returns an {Array} of {Editor}s.
getEditors: ->
new Array(@editors...)
# Public: Add the given {Editor}.
# Add the given {Editor}.
addEditor: (editor) ->
@editors.push editor
@emit 'editor-created', editor
# Public: Return and removes the given {Editor}.
# Return and removes the given {Editor}.
removeEditor: (editor) ->
_.remove(@editors, editor)
# Private: Retrieves all the {TextBuffer}s in the project; that is, the
# Retrieves all the {TextBuffer}s in the project; that is, the
# buffers for all open files.
#
# Returns an {Array} of {TextBuffer}s.
getBuffers: ->
@buffers.slice()
# Private: Is the buffer for the given path modified?
# Is the buffer for the given path modified?
isPathModified: (filePath) ->
@findBufferForPath(@resolve(filePath))?.isModified()
# Private:
findBufferForPath: (filePath) ->
_.find @buffers, (buffer) -> buffer.getPath() == filePath
# Private: Only to be used in specs
# Only to be used in specs
bufferForPathSync: (filePath) ->
absoluteFilePath = @resolve(filePath)
existingBuffer = @findBufferForPath(absoluteFilePath) if filePath
existingBuffer ? @buildBufferSync(absoluteFilePath)
# Private: Given a file path, this retrieves or creates a new {TextBuffer}.
# Given a file path, this retrieves or creates a new {TextBuffer}.
#
# If the `filePath` already has a `buffer`, that value is used instead. Otherwise,
# `text` is used as the contents of the new buffer.
@@ -210,21 +172,20 @@ class Project extends Model
existingBuffer = @findBufferForPath(absoluteFilePath) if absoluteFilePath
Q(existingBuffer ? @buildBuffer(absoluteFilePath))
# Private:
bufferForId: (id) ->
_.find @buffers, (buffer) -> buffer.id is id
# Private: DEPRECATED
# DEPRECATED
buildBufferSync: (absoluteFilePath) ->
buffer = new TextBuffer({filePath: absoluteFilePath})
@addBuffer(buffer)
buffer.loadSync()
buffer
# Private: Given a file path, this sets its {TextBuffer}.
# Given a file path, this sets its {TextBuffer}.
#
# absoluteFilePath - A {String} representing a path
# text - The {String} text to use as a buffer
# absoluteFilePath - A {String} representing a path.
# text - The {String} text to use as a buffer.
#
# Returns a promise that resolves to the {TextBuffer}.
buildBuffer: (absoluteFilePath) ->
@@ -234,38 +195,33 @@ class Project extends Model
.then((buffer) -> buffer)
.catch(=> @removeBuffer(buffer))
# Private:
addBuffer: (buffer, options={}) ->
@addBufferAtIndex(buffer, @buffers.length, options)
buffer.once 'destroyed', => @removeBuffer(buffer)
# Private:
addBufferAtIndex: (buffer, index, options={}) ->
@buffers.splice(index, 0, buffer)
buffer.once 'destroyed', => @removeBuffer(buffer)
@emit 'buffer-created', buffer
buffer
# Private: Removes a {TextBuffer} association from the project.
# Removes a {TextBuffer} association from the project.
#
# Returns the removed {TextBuffer}.
removeBuffer: (buffer) ->
index = @buffers.indexOf(buffer)
@removeBufferAtIndex(index) unless index is -1
# Private:
removeBufferAtIndex: (index, options={}) ->
[buffer] = @buffers.splice(index, 1)
buffer?.destroy()
# Public: Performs a search across all the files in the project.
#
# * regex:
# A RegExp to search with
# * options:
# - paths: an {Array} of glob patterns to search within
# * iterator:
# A Function callback on each file found
# regex - A {RegExp} to search with.
# options - An optional options {Object} (default: {}):
# :paths - An {Array} of glob patterns to search within
# iterator - A {Function} callback on each file found
scan: (regex, options={}, iterator) ->
if _.isFunction(options)
iterator = options
@@ -304,10 +260,11 @@ class Project extends Model
# Public: Performs a replace across all the specified files in the project.
#
# * regex: A RegExp to search with
# * replacementText: Text to replace all matches of regex with
# * filePaths: List of file path strings to run the replace on.
# * iterator: A Function callback on each file with replacements. `({filePath, replacements}) ->`
# regex - A {RegExp} to search with.
# replacementText - Text to replace all matches of regex with
# filePaths - List of file path strings to run the replace on.
# iterator - A {Function} callback on each file with replacements:
# `({filePath, replacements}) ->`.
replace: (regex, replacementText, filePaths, iterator) ->
deferred = Q.defer()
@@ -339,18 +296,11 @@ class Project extends Model
deferred.promise
# Private:
buildEditorForBuffer: (buffer, editorOptions) ->
editor = new Editor(_.extend({buffer}, editorOptions))
@addEditor(editor)
editor
# Private:
eachEditor: (callback) ->
callback(editor) for editor in @getEditors()
@on 'editor-created', (editor) -> callback(editor)
# Private:
eachBuffer: (args...) ->
subscriber = args.shift() if args.length > 1
callback = args.shift()
@@ -360,3 +310,20 @@ class Project extends Model
subscriber.subscribe this, 'buffer-created', (buffer) -> callback(buffer)
else
@on 'buffer-created', (buffer) -> callback(buffer)
# Deprecated: delegate
registerOpener: (opener) ->
@openers.push(opener)
# Deprecated: delegate
unregisterOpener: (opener) ->
_.remove(@openers, opener)
# Deprecated: delegate
eachEditor: (callback) ->
callback(editor) for editor in @getEditors()
@on 'editor-created', (editor) -> callback(editor)
# Deprecated: delegate
getEditors: ->
new Array(@editors...)

View File

@@ -2,8 +2,14 @@
# Public: Represents a view that scrolls.
#
# This `View` subclass listens to events such as `page-up`, `page-down`,
# `move-to-top`, and `move-to-bottom`.
# Subclasses must call `super` if overriding the `initialize` method or else
# the following events won't be handled by the ScrollView.
#
# ## Events
# * `core:page-up`
# * `core:page-down`
# * `core:move-to-top`
# * `core:move-to-bottom`
#
# ## Requiring in packages
#
@@ -12,8 +18,6 @@
# ```
module.exports =
class ScrollView extends View
# Internal: The constructor.
initialize: ->
@on 'core:page-up', => @pageUp()
@on 'core:page-down', => @pageDown()

View File

@@ -12,8 +12,6 @@ fuzzyFilter = require('fuzzaldrin').filter
# ```
module.exports =
class SelectListView extends View
# Private:
@content: ->
@div class: @viewClass(), =>
@subview 'miniEditor', new EditorView(mini: true)
@@ -23,7 +21,6 @@ class SelectListView extends View
@span class: 'badge', outlet: 'loadingBadge'
@ol class: 'list-group', outlet: 'list'
# Private:
@viewClass: -> 'select-list'
maxItems: Infinity
@@ -59,7 +56,6 @@ class SelectListView extends View
@confirmSelection() if $(e.target).closest('li').hasClass('selected')
e.preventDefault()
# Private:
schedulePopulateList: ->
clearTimeout(@scheduleTimeout)
populateCallback = =>
@@ -68,14 +64,14 @@ class SelectListView extends View
# Public: Set the array of items to display in the list.
#
# * array: The array of model elements to display in the list.
# array - The {Array} of model elements to display in the list.
setArray: (@array=[]) ->
@populateList()
@setLoading()
# Public: Set the error message to display.
#
# * message: The error message.
# message - The {String} error message (default: '').
setError: (message='') ->
if message.length is 0
@error.text('').hide()
@@ -85,7 +81,7 @@ class SelectListView extends View
# Public: Set the loading message to display.
#
# * message: The loading message.
# message - The {String} loading message (default: '').
setLoading: (message='') ->
if message.length is 0
@loading.text("")
@@ -135,30 +131,26 @@ class SelectListView extends View
#
# Subclasses may override this method to customize the message.
#
# * itemCount: The number of items in the array specified to {.setArray}
# * filteredItemCount: The number of items that pass the fuzzy filter test.
# itemCount - The {Number} of items in the array specified to {.setArray}
# filteredItemCount - The {Number} of items that pass the fuzzy filter test.
getEmptyMessage: (itemCount, filteredItemCount) -> 'No matches found'
# Private:
selectPreviousItem: ->
item = @getSelectedItem().prev()
item = @list.find('li:last') unless item.length
@selectItem(item)
# Private:
selectNextItem: ->
item = @getSelectedItem().next()
item = @list.find('li:first') unless item.length
@selectItem(item)
# Private:
selectItem: (item) ->
return unless item.length
@list.find('.selected').removeClass('selected')
item.addClass 'selected'
@scrollToItem(item)
# Private:
scrollToItem: (item) ->
scrollTop = @list.scrollTop()
desiredTop = item.position().top + scrollTop
@@ -181,7 +173,6 @@ class SelectListView extends View
getSelectedElement: ->
@getSelectedItem().data('select-list-element')
# Private:
confirmSelection: ->
element = @getSelectedElement()
if element?
@@ -193,25 +184,21 @@ class SelectListView extends View
#
# This method should be overridden by subclasses.
#
# * element: The selected model element.
# element - The selected model element.
confirmed: (element) ->
# Private:
attach: ->
@storeFocusedElement()
# Private:
storeFocusedElement: ->
@previouslyFocusedElement = $(':focus')
# Private:
restoreFocus: ->
if @previouslyFocusedElement?.isOnDom()
@previouslyFocusedElement.focus()
else
atom.workspaceView.focus()
# Private:
cancelled: ->
@miniEditor.getEditor().setText('')
@miniEditor.updateDisplay()

View File

@@ -1,7 +1,6 @@
{Point, Range} = require 'text-buffer'
{View, $$} = require './space-pen-extensions'
# Internal:
module.exports =
class SelectionView extends View

View File

@@ -14,7 +14,6 @@ class Selection
wordwise: false
needsAutoscroll: null
# Private:
constructor: ({@cursor, @marker, @editor}) ->
@cursor.selection = this
@marker.on 'changed', => @screenRangeChanged()
@@ -23,18 +22,15 @@ class Selection
@editor.removeSelection(this)
@emit 'destroyed' unless @editor.isDestroyed()
# Private:
destroy: ->
@marker.destroy()
# Private:
finalize: ->
@initialScreenRange = null unless @initialScreenRange?.isEqual(@getScreenRange())
if @isEmpty()
@wordwise = false
@linewise = false
# Private:
clearAutoscroll: ->
@needsAutoscroll = null
@@ -59,10 +55,8 @@ class Selection
# Public: Modifies the screen range for the selection.
#
# * screenRange:
# The new {Range} to use
# * options:
# + A hash of options matching those found in {.setBufferRange}
# screenRange - The new {Range} to use.
# options - A hash of options matching those found in {.setBufferRange}.
setScreenRange: (screenRange, options) ->
@setBufferRange(@editor.bufferRangeForScreenRange(screenRange), options)
@@ -72,13 +66,11 @@ class Selection
# Public: Modifies the buffer {Range} for the selection.
#
# * screenRange:
# The new {Range} to select
# * options
# + preserveFolds:
# if `true`, the fold settings are preserved after the selection moves
# + autoscroll:
# if `true`, the {Editor} scrolls to the new selection
# screenRange - The new {Range} to select.
# options - An {Object} with the keys:
# :preserveFolds - if `true`, the fold settings are preserved after the
# selection moves.
# :autoscroll - if `true`, the {Editor} scrolls to the new selection.
setBufferRange: (bufferRange, options={}) ->
bufferRange = Range.fromObject(bufferRange)
@needsAutoscroll = options.autoscroll
@@ -128,8 +120,7 @@ class Selection
# Public: Selects an entire line in the buffer.
#
# * row:
# The line Number to select (default: the row of the cursor)
# row - The line {Number} to select (default: the row of the cursor).
selectLine: (row=@cursor.getBufferPosition().row) ->
range = @editor.bufferRangeForBufferRow(row, includeNewline: true)
@setBufferRange(@getBufferRange().union(range))
@@ -148,8 +139,7 @@ class Selection
# 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) ->
@modifySelection =>
if @initialScreenRange
@@ -168,8 +158,7 @@ class Selection
# Public: Selects the text from the current cursor position to a given buffer
# position.
#
# * position:
# An instance of {Point}, with a given `row` and `column`.
# position - An instance of {Point}, with a given `row` and `column`.
selectToBufferPosition: (position) ->
@modifySelection => @cursor.setBufferPosition(position)
@@ -259,8 +248,6 @@ class Selection
@editor.addSelectionForBufferRange(range, goalBufferRange: range)
break
# Public:
#
# FIXME: I have no idea what this does.
getGoalBufferRange: ->
@marker.getAttributes().goalBufferRange
@@ -285,20 +272,14 @@ class Selection
# Public: Replaces text at the current selection.
#
# * text:
# A {String} representing the text to add
# * options
# + select:
# if `true`, selects the newly added text
# + autoIndent:
# if `true`, indents all inserted text appropriately
# + autoIndentNewline:
# if `true`, indent newline appropriately
# + autoDecreaseIndent:
# if `true`, decreases indent level appropriately (for example, when a
# closing bracket is inserted)
# + undo:
# if `skip`, skips the undo stack for this operation.
# text - A {String} representing the text to add
# options - An {Object} with keys:
# :select - if `true`, selects the newly added text.
# :autoIndent - if `true`, indents all inserted text appropriately.
# :autoIndentNewline - if `true`, indent newline appropriately.
# :autoDecreaseIndent - if `true`, decreases indent level appropriately
# (for example, when a closing bracket is inserted).
# :undo - if `skip`, skips the undo stack for this operation.
insertText: (text, options={}) ->
oldBufferRange = @getBufferRange()
@editor.destroyFoldsContainingBufferRow(oldBufferRange.end.row)
@@ -326,10 +307,8 @@ class Selection
# Public: Indents the given text to the suggested level based on the grammar.
#
# * text:
# The string to indent within the selection.
# * indentBasis:
# The beginning indent level.
# text - The {String} to indent within the selection.
# indentBasis - The beginning indent level.
normalizeIndents: (text, indentBasis) ->
textPrecedingCursor = @cursor.getCurrentBufferLine()[0...@cursor.getBufferColumn()]
isCursorInsideExistingLine = /\S/.test(textPrecedingCursor)
@@ -357,10 +336,9 @@ class Selection
# Public: Indents the selection.
#
# * options - A hash with one key,
# + autoIndent:
# If `true`, the indentation is performed appropriately. Otherwise,
# {Editor.getTabText} is used
# options - A {Object} with the keys:
# :autoIndent - If `true`, the indentation is performed appropriately.
# Otherwise, {Editor.getTabText} is used.
indent: ({ autoIndent }={})->
{ row, column } = @cursor.getBufferPosition()
@@ -505,35 +483,25 @@ class Selection
@editor.toggleLineCommentsForBufferRows(@getBufferRowRange()...)
# Public: Cuts the selection until the end of the line.
#
# * maintainPasteboard:
# ?
cutToEndOfLine: (maintainPasteboard) ->
cutToEndOfLine: (maintainClipboard) ->
@selectToEndOfLine() if @isEmpty()
@cut(maintainPasteboard)
@cut(maintainClipboard)
# Public: Copies the selection to the pasteboard and then deletes it.
#
# * maintainPasteboard:
# ?
cut: (maintainPasteboard=false) ->
@copy(maintainPasteboard)
# Public: Copies the selection to the clipboard and then deletes it.
cut: (maintainClipboard=false) ->
@copy(maintainClipboard)
@delete()
# Public: Copies the current selection to the pasteboard.
#
# * maintainPasteboard:
# ?
copy: (maintainPasteboard=false) ->
# Public: Copies the current selection to the clipboard.
copy: (maintainClipboard=false) ->
return if @isEmpty()
text = @editor.buffer.getTextInRange(@getBufferRange())
if maintainPasteboard
[currentText, metadata] = atom.pasteboard.read()
text = currentText + '\n' + text
if maintainClipboard
text = "#{atom.clipboard.read()}\n#{text}"
else
metadata = { indentBasis: @editor.indentationForBufferRow(@getBufferRange().start.row) }
atom.pasteboard.write(text, metadata)
atom.clipboard.write(text, metadata)
# Public: Creates a fold containing the current selection.
fold: ->
@@ -541,14 +509,13 @@ class Selection
@editor.createFold(range.start.row, range.end.row)
@cursor.setBufferPosition([range.end.row + 1, 0])
# Public: ?
modifySelection: (fn) ->
@retainSelection = true
@plantTail()
fn()
@retainSelection = false
# Private: Sets the marker's tail to the same position as the marker's head.
# Sets the marker's tail to the same position as the marker's head.
#
# This only works if there isn't already a tail position.
#
@@ -558,8 +525,7 @@ class Selection
# Public: Identifies if a selection intersects with a given buffer range.
#
# * bufferRange:
# A {Range} to check against
# bufferRange - A {Range} to check against.
#
# Returns a Boolean.
intersectsBufferRange: (bufferRange) ->
@@ -567,8 +533,7 @@ class Selection
# Public: Identifies if a selection intersects with another selection.
#
# * otherSelection:
# A {Selection} to check against
# otherSelection - A {Selection} to check against.
#
# Returns a Boolean.
intersectsWith: (otherSelection) ->
@@ -577,10 +542,8 @@ class Selection
# Public: Combines the given selection into this selection and then destroys
# the given selection.
#
# * otherSelection:
# A {Selection} to merge with
# * options
# + A hash of options matching those found in {.setBufferRange}
# otherSelection - A {Selection} to merge with.
# options - A hash of options matching those found in {.setBufferRange}.
merge: (otherSelection, options) ->
myGoalBufferRange = @getGoalBufferRange()
otherGoalBufferRange = otherSelection.getGoalBufferRange()
@@ -596,12 +559,10 @@ class Selection
#
# See {Range.compare} for more details.
#
# * otherSelection:
# A {Selection} to compare with.
# otherSelection - A {Selection} to compare against.
compare: (otherSelection) ->
@getBufferRange().compare(otherSelection.getBufferRange())
# Private:
screenRangeChanged: ->
screenRange = @getScreenRange()
@emit 'screen-range-changed', screenRange

View File

@@ -1,18 +1,13 @@
_ = require 'underscore-plus'
spacePen = require 'space-pen'
{Subscriber} = require 'emissary'
ConfigObserver = require './config-observer'
ConfigObserver.includeInto(spacePen.View)
Subscriber.includeInto(spacePen.View)
jQuery = spacePen.jQuery
originalCleanData = jQuery.cleanData
jQuery.cleanData = (elements) ->
for element in elements
if view = jQuery(element).view()
view.unobserveConfig()
view.unsubscribe()
jQuery(element).view()?.unsubscribe() for element in elements
originalCleanData(elements)
tooltipDefaults =

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