Merge branch 'master' into summit

Conflicts:
	.pairs
	package.json
	spec/app/tokenized-buffer-spec.coffee
	src/app/edit-session.coffee
	src/app/project.coffee
	src/app/window.coffee
	src/atom-application.coffee
	static/root-view.less
This commit is contained in:
Kevin Sawicki 2013-08-14 14:01:04 -07:00
commit c6d1409151
316 changed files with 1686 additions and 33357 deletions

2
.gitmodules vendored
View File

@ -1,6 +1,6 @@
[submodule "vendor/bootstrap"]
path = vendor/bootstrap
url = https://github.com/twitter/bootstrap
url = https://github.com/twbs/bootstrap
[submodule "vendor/apm"]
path = vendor/apm
url = https://github.com/github/apm.git

2
.pairs
View File

@ -9,6 +9,8 @@ pairs:
gt: Garen Torikian; garen
mc: Matt Colyer; mcolyer
bo: Ben Ogle; benogle
jr: Jason Rudolph; jasonrudolph
jl: Jessica Lord; jlord
email:
domain: github.com
#global: true

View File

@ -1,3 +1,22 @@
* Added: Terminal package now bundled by default, open with ctrl-`
* Fixed: Fuzzy finder not showing results for files at a certain depth
* Fixed: Atom > Preferences... menu not opening settings in focused window
* Fixed: Atom failing to launch if the theme being used was not found
* Improved: Theme changes now immediately take effect
* Fixed: Wrap in quotes/parens now works in split panes
* Improved: Autocomplete now includes CSS property names and values
* Improved: Settings GUI is now a pane item
* Added: Support package filtering in Settings GUI
* Added: Dynamically load all config options in the Settings GUI
* Added: Ability to bookmark lines and navigate bookmarks
* Fixed: Error when inserting newlines in CSS
* Fixed: Folding all will fold comments as well
* Added: Ability to fold all code at a given indentation level
* Improved: cmd-n now opens a new tab and cmd-shift-n now opens a new window.
* Added: Inspect Element context menu
* Fixed: Save As dialog now defaults to directory path of current editor
* Fixed: Using toggle comment shortcut respects indentation level

View File

@ -1,5 +1,6 @@
# :rotating_light: Contributing to Atom :rotating_light:
## Issues
* Include screenshots and animated GIFs whenever possible, they are immensely
helpful
@ -17,7 +18,6 @@
specs
* Style new elements in both the light and dark default themes when
appropriate
* New packages go in `src/packages/`
* Add 3rd-party packages as a `package.json` dependency
* Commit messages are in the present tense
* Files end with a newline
@ -26,3 +26,35 @@
* Class methods (methods starting with a `@`)
* Instance variables
* Instance methods
## Philosophy
### Write Beautiful Code
Once you get something working, take the time to consider whether you can achieve it in a more elegant way. We're planning on open-sourcing Atom, so let's put our best foot forward.
### When in doubt, pair-up
Pairing can be an effective and fun way to pass on culture, knowledge, and taste. If you can find the time, we encourage you to work synchronously with other community members of all experience levels to help the knowledge-mulching process. It doesn't have to be all the time; a little pairing goes a long way.
### Write tests, and write them first
The test suite keeps protects our codebase from the ravages of entropy, but it only works when we have thorough coverage. Before you write implementation code, write a failing test proving that it's needed.
### Leave the test suite better than you found it
Consider how the specs you are adding fit into the spec-file as a whole. Is this the right place for your spec? Does the spec need to be reorganized now that you're adding this extra dimension? Specs are only as useful as the next person's ability to understand them.
### Solve today's problem
Avoid adding flexibility that isn't needed *today*. Nothing is ever set in stone, and we can always go back and add flexibility later. Adding it early just means we have to pay for complexity that we might not end up using.
### Favor clarity over brevity or cleverness.
Three lines that someone else can read are better than one line that's tricky.
### Don't be defensive
Only catch exceptions that are truly exceptional. Assume that components we control will honor their contracts. If they don't, the solution is to find and fix the problem in code rather than cluttering the code with attempts to foresee all potential issues at runtime.
### Don't be afraid to add classes and methods
Code rarely suffers from too many methods and classes, and often suffers from too few. Err on the side of numerous short, well-named methods. Pull out classes with well-defined roles.
### Rip shit out
Don't be afraid to delete code. Don't be afraid to rewrite something that needs to be refreshed. If it's in version control, we can always resurrect it.
### Maintain a consistent level of abstraction
Every line in a method should read at the same basic level of abstraction. If there's a section of a method that goes into a lot more detail than the rest of the method, consider extracting a new method and giving it a clear name.

View File

@ -1,3 +1,4 @@
fs = require 'fs'
path = require 'path'
module.exports = (grunt) ->
@ -8,51 +9,65 @@ module.exports = (grunt) ->
appDir = path.join(contentsDir, 'Resources', 'app')
installDir = path.join('/Applications', appName)
coffeeConfig =
options:
sourceMap: true
glob_to_multiple:
expand: true
src: [
'src/**/*.coffee'
'static/**/*.coffee'
]
dest: appDir
ext: '.js'
lessConfig =
options:
paths: [
'static'
'vendor'
]
glob_to_multiple:
expand: true
src: [
'src/**/*.less'
'static/**/*.less'
'themes/**/*.less'
]
dest: appDir
ext: '.css'
csonConfig =
options:
rootObject: true
glob_to_multiple:
expand: true
src: [
'src/**/*.cson'
'static/**/*.cson'
'themes/**/*.cson'
]
dest: appDir
ext: '.json'
for child in fs.readdirSync('node_modules') when child isnt '.bin'
directory = path.join('node_modules', child)
{engines} = grunt.file.readJSON(path.join(directory, 'package.json'))
if engines?.atom?
coffeeConfig.glob_to_multiple.src.push("#{directory}/**/*.coffee")
lessConfig.glob_to_multiple.src.push("#{directory}/**/*.less")
csonConfig.glob_to_multiple.src.push("#{directory}/**/*.cson")
grunt.initConfig
pkg: grunt.file.readJSON('package.json')
atom: {appDir, appName, buildDir, contentsDir, installDir, shellAppDir}
coffee:
options:
sourceMap: true
glob_to_multiple:
expand: true
src: [
'src/**/*.coffee'
'static/**/*.coffee'
]
dest: appDir
ext: '.js'
coffee: coffeeConfig
less:
options:
paths: [
'static'
'vendor'
]
glob_to_multiple:
expand: true
src: [
'src/**/*.less'
'static/**/*.less'
'themes/**/*.less'
]
dest: appDir
ext: '.css'
less: lessConfig
cson:
options:
rootObject: true
glob_to_multiple:
expand: true
src: [
'src/**/*.cson'
'static/**/*.cson'
'themes/**/*.cson'
]
dest: appDir
ext: '.json'
cson: csonConfig
coffeelint:
options:
@ -77,6 +92,7 @@ module.exports = (grunt) ->
'box-sizing': false
'bulletproof-font-face': false
'compatible-vendor-prefixes': false
'display-property-grouping': false
'fallback-colors': false
'font-sizes': false
'gradients': false
@ -112,6 +128,6 @@ module.exports = (grunt) ->
grunt.registerTask('compile', ['coffee', 'less', 'cson'])
grunt.registerTask('lint', ['coffeelint', 'csslint', 'lesslint'])
grunt.registerTask('ci', ['lint', 'partial-clean', 'update-atom-shell', 'build', 'test'])
grunt.registerTask('ci', ['lint', 'partial-clean', 'update-atom-shell', 'build', 'set-development-version', 'test'])
grunt.registerTask('deploy', ['partial-clean', 'update-atom-shell', 'build', 'codesign'])
grunt.registerTask('default', ['update-atom-shell', 'build', 'set-development-version', 'install'])

View File

@ -2,7 +2,9 @@
![atom](https://s3.amazonaws.com/speakeasy/apps/icons/27/medium/7db16e44-ba57-11e2-8c6f-981faf658e00.png)
Check out our [documentation on the docs tab](https://github.com/github/atom/docs).
Check out our [documentation in this
repo](https://github.com/github/atom/tree/master/docs). There is also
[API documentation](https://atom-docs.githubapp.com/api/index.html).
## Installing

38
atom.sh
View File

@ -1,5 +1,6 @@
#!/bin/sh
ATOM_PATH=/Applications/Atom.app
ATOM_BINARY=$ATOM_PATH/Contents/MacOS/Atom
if [ ! -d $ATOM_PATH ]; then sleep 5; fi # Wait for Atom to reappear, Sparkle may be replacing it.
@ -8,7 +9,32 @@ if [ ! -d $ATOM_PATH ]; then
exit 1
fi
open -a $ATOM_PATH -n --args --executed-from="$(pwd)" --pid=$$ $@
while getopts ":whv-:" opt; do
case "$opt" in
-)
case "${OPTARG}" in
wait)
WAIT=1
;;
help|version)
EXPECT_OUTPUT=1
;;
esac
;;
w)
WAIT=1
;;
h|v)
EXPECT_OUTPUT=1
;;
esac
done
if [ $EXPECT_OUTPUT ]; then
$ATOM_BINARY --executed-from="$(pwd)" --pid=$$ $@
else
open -a $ATOM_PATH -n --args --executed-from="$(pwd)" --pid=$$ $@
fi
# Used to exit process when atom is used as $EDITOR
on_die() {
@ -16,16 +42,6 @@ on_die() {
}
trap 'on_die' SIGQUIT SIGTERM
# Don't exit process if we were told to wait.
while [ "$#" -gt "0" ]; do
case $1 in
-W|--wait)
WAIT=1
;;
esac
shift
done
if [ $WAIT ]; then
while true; do
sleep 1

View File

@ -22,7 +22,7 @@ You can always use `meta-p` to explore available commands and their
bindings, but here's a list of a few useful commands.
- `meta-o` : open a file or directory
- `meta-n` : open new window
- `meta-shift-n` : open new window
- `meta-r` : reload the current window
- `meta-alt-ctrl-s` : run test specs
- `meta-t` : open file finder to navigate files in your project
@ -146,10 +146,13 @@ Atom loads configuration settings from the `config.cson` file in your `~/.atom`
directory, which contains CoffeeScript-style JSON:
```coffeescript
core:
hideGitIgnoredFiles: true
editor:
fontSize: 18
'editor':
'fontSize': 16
'core':
'themes': [
'atom-dark-ui'
'atom-dark-syntax'
]
```
Configuration is broken into namespaces, which are defined by the config hash's
@ -192,12 +195,12 @@ 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:
```coffee-script
```coffeescript
'.editor':
'enter': 'editor:newline'
".select-list .editor.mini":
'enter': 'core:confirm',
'.select-list .editor.mini':
'enter': 'core:confirm'
```
This keymap defines the meaning of `enter` in two different contexts. In a
@ -220,7 +223,7 @@ active at the same time. For example, you'll usually select a theme for the UI
and another theme for syntax highlighting. You can select themes by specifying
them in the `core.themes` array in your `config.cson`:
```coffee-script
```coffeescript
core:
themes: ["atom-light-ui", "atom-light-syntax"]
# or, if the sun is going down:

View File

@ -7,38 +7,66 @@
"url": "https://github.com/github/atom.git"
},
"dependencies": {
"coffee-script": "1.6.2",
"ctags": "0.5.0",
"oniguruma": "0.16.0",
"mkdirp": "0.3.5",
"git-utils": "0.24.0",
"underscore": "1.4.4",
"d3": "3.0.8",
"coffee-cache": "0.1.0",
"pegjs": "0.7.0",
"async": "0.2.6",
"nak": "0.2.16",
"spellchecker": "0.6.0",
"pathwatcher": "0.5.0",
"keytar": "0.9.0",
"ls-archive": "0.9.0",
"temp": "0.5.0",
"rimraf": "2.1.4",
"plist": "git://github.com/nathansobo/node-plist.git",
"space-pen": "1.0.0",
"less": "git://github.com/nathansobo/less.js.git",
"roaster": "0.0.5",
"jqueryui-browser": "1.10.2-1",
"optimist": "0.4.0",
"season": "0.10.0",
"humanize-plus": "1.1.0",
"semver": "1.1.4",
"guid": "0.0.10",
"tantamount": "0.3.0",
"coffee-cache": "0.1.0",
"coffee-script": "1.6.2",
"coffeestack": "0.4.0",
"first-mate": "0.1.0",
"git-utils": "0.24.0",
"guid": "0.0.10",
"mkdirp": "0.3.5",
"less": "git://github.com/nathansobo/less.js.git",
"nak": "0.2.16",
"nslog": "0.1.0",
"oniguruma": "0.16.0",
"optimist": "0.4.0",
"pathwatcher": "0.5.0",
"patrick": "0.4.0",
"pegjs": "0.7.0",
"plist": "git://github.com/nathansobo/node-plist.git",
"rimraf": "2.1.4",
"season": "0.10.0",
"semver": "1.1.4",
"space-pen": "1.2.0",
"tantamount": "0.3.0",
"temp": "0.5.0",
"underscore": "1.4.4",
"archive-view": "0.2.0",
"autocomplete": "0.1.0",
"autoflow": "0.1.0",
"bookmarks": "0.1.0",
"bracket-matcher": "0.1.0",
"collaboration": "0.1.0",
"command-logger": "0.2.0",
"command-panel": "0.1.0",
"command-palette": "0.1.0",
"fuzzy-finder": "0.1.0",
"editor-stats": "0.1.0",
"gfm": "0.1.0",
"git-diff": "0.1.0",
"gists": "0.1.0",
"github-sign-in": "0.1.0",
"go-to-line": "0.1.0",
"grammar-selector": "0.1.0",
"image-view": "0.1.0",
"link": "0.1.0",
"markdown-preview": "0.1.0",
"package-generator": "0.1.0",
"settings-view": "0.3.0",
"snippets": "0.1.0",
"spell-check": "0.1.0",
"status-bar": "0.1.0",
"symbols-view": "0.1.0",
"tabs": "0.1.0",
"terminal": "0.3.0",
"toml": "0.1.0",
"tree-view": "0.1.0",
"whitespace": "0.1.0",
"wrap-guide": "0.1.0",
"c-tmbundle": "1.0.0",
"coffee-script-tmbundle": "2.0.0",
"coffee-script-tmbundle": "4.0.0",
"css-tmbundle": "1.0.0",
"git-tmbundle": "1.0.0",
"go-tmbundle": "1.0.0",
@ -67,8 +95,7 @@
"textmate-clojure": "1.0.0",
"todo-tmbundle": "1.0.0",
"xml-tmbundle": "1.0.0",
"yaml-tmbundle": "1.0.0",
"nslog": "0.1.0"
"yaml-tmbundle": "1.0.0"
},
"devDependencies": {
"biscotto": "0.0.12",
@ -79,10 +106,11 @@
"grunt-cson": "0.5.0",
"grunt-contrib-csslint": "~0.1.2",
"grunt-contrib-coffee": "~0.7.0",
"grunt-contrib-less": "~0.5.2",
"grunt-contrib-less": "~0.6.4",
"jasmine-focused": "~0.7.0",
"walkdir": "0.0.7",
"ws": "0.4.27"
"ws": "0.4.27",
"js-yaml": "~2.1.0"
},
"private": true,
"scripts": {

View File

@ -18,4 +18,5 @@ git submodule --quiet update --recursive --init
(cd vendor/apm && npm install .)
npm install --silent vendor/apm
echo ""
./node_modules/.bin/apm install --silent

32
script/utils/update-octicons Executable file
View File

@ -0,0 +1,32 @@
#!/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', '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

@ -217,18 +217,6 @@ describe "the `atom` global", ->
runs ->
expect(syntax.getProperty(['.source.pref'], 'editor.increaseIndentPattern')).toBe '^abc$'
describe ".activatePackageConfig(id)", ->
it "calls the optional .activateConfigMenu method on the package's main module", ->
pack = atom.activatePackageConfig('package-with-activate-config')
expect(pack.mainModule.activateCalled).toBeFalsy()
expect(pack.mainModule.activateConfigCalled).toBeTruthy()
it "loads the package's config defaults", ->
expect(config.get('package-with-config-defaults.numbers.one')).toBeUndefined()
atom.activatePackageConfig('package-with-config-defaults')
expect(config.get('package-with-config-defaults.numbers.one')).toBe 1
expect(config.get('package-with-config-defaults.numbers.two')).toBe 2
describe ".deactivatePackage(id)", ->
describe "atom packages", ->
it "calls `deactivate` on the package's main module", ->

View File

@ -1,138 +0,0 @@
ConfigPanel = require 'config-panel'
Editor = require 'editor'
describe "ConfigPanel", ->
it "automatically binds named input fields to their corresponding config keys", ->
class TestPanel extends ConfigPanel
@content: ->
@div =>
@input outlet: 'intInput', id: 'foo.int', type: 'int'
@input outlet: 'floatInput', id: 'foo.float', type: 'float'
@input outlet: 'stringInput', id: 'foo.string', type: 'string'
@input outlet: 'booleanInput', id: 'foo.boolean', type: 'checkbox'
config.set('foo.int', 22)
config.set('foo.boolean', true)
panel = new TestPanel
expect(panel.intInput.val()).toBe '22'
expect(panel.floatInput.val()).toBe ''
expect(panel.stringInput.val()).toBe ''
expect(panel.booleanInput.attr('checked')).toBeTruthy()
config.set('foo.int', 10)
expect(panel.intInput.val()).toBe '10'
expect(panel.floatInput.val()).toBe ''
expect(panel.stringInput.val()).toBe ''
config.set('foo.string', 'hey')
expect(panel.intInput.val()).toBe '10'
expect(panel.floatInput.val()).toBe ''
expect(panel.stringInput.val()).toBe 'hey'
config.set('foo.boolean', false)
expect(panel.booleanInput.attr('checked')).toBeFalsy()
panel.intInput.val('90.2').change()
expect(config.get('foo.int')).toBe 90
panel.floatInput.val('90.2').change()
expect(config.get('foo.float')).toBe 90.2
panel.intInput.val('0').change()
expect(config.get('foo.int')).toBe 0
panel.floatInput.val('0').change()
expect(config.get('foo.float')).toBe 0
panel.stringInput.val('moo').change()
expect(config.get('foo.string')).toBe 'moo'
panel.intInput.val('abcd').change()
expect(config.get('foo.int')).toBe 'abcd'
panel.floatInput.val('defg').change()
expect(config.get('foo.float')).toBe 'defg'
panel.intInput.val('').change()
expect(config.get('foo.int')).toBe undefined
panel.floatInput.val('').change()
expect(config.get('foo.float')).toBe undefined
panel.stringInput.val('').change()
expect(config.get('foo.string')).toBe undefined
it "automatically binds named editors to their corresponding config keys", ->
class TestPanel extends ConfigPanel
@content: ->
@div =>
@subview 'intEditor', new Editor(mini: true, attributes: { id: 'foo.int', type: 'int' })
@subview 'floatEditor', new Editor(mini: true, attributes: { id: 'foo.float', type: 'float' })
@subview 'stringEditor', new Editor(mini: true, attributes: { id: 'foo.string', type: 'string' })
config.set('foo.int', 1)
config.set('foo.float', 1.1)
config.set('foo.string', 'I think therefore I am.')
panel = new TestPanel
window.advanceClock(10000) # wait for contents-modified to be triggered
expect(panel.intEditor.getText()).toBe '1'
expect(panel.floatEditor.getText()).toBe '1.1'
expect(panel.stringEditor.getText()).toBe 'I think therefore I am.'
config.set('foo.int', 2)
config.set('foo.float', 2.2)
config.set('foo.string', 'We are what we think.')
expect(panel.intEditor.getText()).toBe '2'
expect(panel.floatEditor.getText()).toBe '2.2'
expect(panel.stringEditor.getText()).toBe 'We are what we think.'
panel.intEditor.setText('3')
panel.floatEditor.setText('3.3')
panel.stringEditor.setText('All limitations are self imposed.')
window.advanceClock(10000) # wait for contents-modified to be triggered
expect(config.get('foo.int')).toBe 3
expect(config.get('foo.float')).toBe 3.3
expect(config.get('foo.string')).toBe 'All limitations are self imposed.'
panel.intEditor.setText('not an int')
panel.floatEditor.setText('not a float')
window.advanceClock(10000) # wait for contents-modified to be triggered
expect(config.get('foo.int')).toBe 'not an int'
expect(config.get('foo.float')).toBe 'not a float'
panel.intEditor.setText('')
panel.floatEditor.setText('')
panel.stringEditor.setText('')
window.advanceClock(10000) # wait for contents-modified to be triggered
expect(config.get('foo.int')).toBe undefined
expect(config.get('foo.float')).toBe undefined
expect(config.get('foo.string')).toBe undefined
panel.intEditor.setText('0')
panel.floatEditor.setText('0')
window.advanceClock(10000) # wait for contents-modified to be triggered
expect(config.get('foo.int')).toBe 0
expect(config.get('foo.float')).toBe 0
it "does not save the config value until it has been changed to a new value", ->
class TestPanel extends ConfigPanel
@content: ->
@div =>
@subview "fooInt", new Editor(mini: true, attributes: {id: 'foo.int', type: 'int'})
config.set('foo.int', 1)
observeHandler = jasmine.createSpy("observeHandler")
config.observe "foo.int", observeHandler
observeHandler.reset()
testPanel = new TestPanel
window.advanceClock(10000) # wait for contents-modified to be triggered
expect(observeHandler).not.toHaveBeenCalled()
testPanel.fooInt.setText("1")
window.advanceClock(10000) # wait for contents-modified to be triggered
expect(observeHandler).not.toHaveBeenCalled()
testPanel.fooInt.setText("2")
window.advanceClock(10000) # wait for contents-modified to be triggered
expect(observeHandler).toHaveBeenCalled()

View File

@ -1,46 +0,0 @@
ConfigView = require 'config-view'
{$$} = require 'space-pen'
describe "ConfigView", ->
configView = null
beforeEach ->
configView = new ConfigView
describe "serialization", ->
it "remembers which panel was visible", ->
configView.showPanel('Editor')
newConfigView = deserialize(configView.serialize())
configView.remove()
newConfigView.attachToDom()
expect(newConfigView.activePanelName).toBe 'Editor'
it "shows the previously active panel if it is added after deserialization", ->
configView.addPanel('Panel 1', $$ -> @div id: 'panel-1')
configView.showPanel('Panel 1')
newConfigView = deserialize(configView.serialize())
configView.remove()
newConfigView.attachToDom()
newConfigView.addPanel('Panel 1', $$ -> @div id: 'panel-1')
expect(newConfigView.activePanelName).toBe 'Panel 1'
describe ".addPanel(name, view)", ->
it "adds a menu entry to the left and a panel that can be activated by clicking it", ->
configView.addPanel('Panel 1', $$ -> @div id: 'panel-1')
configView.addPanel('Panel 2', $$ -> @div id: 'panel-2')
expect(configView.panelMenu.find('li a:contains(Panel 1)')).toExist()
expect(configView.panelMenu.find('li a:contains(Panel 2)')).toExist()
expect(configView.panelMenu.children(':first')).toHaveClass 'active'
configView.attachToDom()
configView.panelMenu.find('li a:contains(Panel 1)').click()
expect(configView.panelMenu.children('.active').length).toBe 1
expect(configView.panelMenu.find('li:contains(Panel 1)')).toHaveClass('active')
expect(configView.panels.find('#panel-1')).toBeVisible()
expect(configView.panels.find('#panel-2')).toBeHidden()
configView.panelMenu.find('li a:contains(Panel 2)').click()
expect(configView.panelMenu.children('.active').length).toBe 1
expect(configView.panelMenu.find('li:contains(Panel 2)')).toHaveClass('active')
expect(configView.panels.find('#panel-1')).toBeHidden()
expect(configView.panels.find('#panel-2')).toBeVisible()

View File

@ -137,6 +137,15 @@ describe "EditSession", ->
editSession.moveCursorDown()
expect(editSession.getCursorScreenPosition()).toEqual([1, 4])
describe "when there is a selection", ->
beforeEach ->
editSession.setSelectedBufferRange([[4, 9],[5, 10]])
it "moves above the selection", ->
cursor = editSession.getCursor()
editSession.moveCursorUp()
expect(cursor.getBufferPosition()).toEqual [3, 9]
it "merges cursors when they overlap", ->
editSession.addCursorAtScreenPosition([1, 0])
[cursor1, cursor2] = editSession.getCursors()
@ -186,6 +195,15 @@ describe "EditSession", ->
editSession.moveCursorUp()
expect(editSession.getCursorScreenPosition().column).toBe 0
describe "when there is a selection", ->
beforeEach ->
editSession.setSelectedBufferRange([[4, 9],[5, 10]])
it "moves below the selection", ->
cursor = editSession.getCursor()
editSession.moveCursorDown()
expect(cursor.getBufferPosition()).toEqual [6, 10]
it "merges cursors when they overlap", ->
editSession.setCursorScreenPosition([12, 2])
editSession.addCursorAtScreenPosition([11, 2])
@ -221,6 +239,18 @@ describe "EditSession", ->
editSession.moveCursorLeft()
expect(editSession.getCursorBufferPosition()).toEqual [5, 4]
describe "when there is a selection", ->
beforeEach ->
editSession.setSelectedBufferRange([[5, 22],[5, 27]])
it "moves to the left of the selection", ->
cursor = editSession.getCursor()
editSession.moveCursorLeft()
expect(cursor.getBufferPosition()).toEqual [5, 22]
editSession.moveCursorLeft()
expect(cursor.getBufferPosition()).toEqual [5, 21]
it "merges cursors when they overlap", ->
editSession.setCursorScreenPosition([0, 0])
editSession.addCursorAtScreenPosition([0, 1])
@ -255,6 +285,18 @@ describe "EditSession", ->
expect(editSession.getCursorScreenPosition()).toEqual(lastPosition)
describe "when there is a selection", ->
beforeEach ->
editSession.setSelectedBufferRange([[5, 22],[5, 27]])
it "moves to the left of the selection", ->
cursor = editSession.getCursor()
editSession.moveCursorRight()
expect(cursor.getBufferPosition()).toEqual [5, 27]
editSession.moveCursorRight()
expect(cursor.getBufferPosition()).toEqual [5, 28]
it "merges cursors when they overlap", ->
editSession.setCursorScreenPosition([12, 2])
editSession.addCursorAtScreenPosition([12, 1])
@ -281,44 +323,72 @@ describe "EditSession", ->
expect(editSession.getCursorBufferPosition()).toEqual [12,2]
describe ".moveCursorToBeginningOfLine()", ->
it "moves cursor to the beginning of line", ->
editSession.setCursorScreenPosition [0,5]
editSession.addCursorAtScreenPosition [1,7]
editSession.moveCursorToBeginningOfLine()
expect(editSession.getCursors().length).toBe 2
[cursor1, cursor2] = editSession.getCursors()
expect(cursor1.getBufferPosition()).toEqual [0,0]
expect(cursor2.getBufferPosition()).toEqual [1,0]
describe "when soft wrap is on", ->
it "moves cursor to the beginning of the screen line", ->
editSession.setSoftWrapColumn(10)
editSession.setCursorScreenPosition([1, 2])
editSession.moveCursorToBeginningOfLine()
cursor = editSession.getCursor()
expect(cursor.getScreenPosition()).toEqual [1, 0]
describe "when soft wrap is off", ->
it "moves cursor to the beginning of then line", ->
editSession.setCursorScreenPosition [0,5]
editSession.addCursorAtScreenPosition [1,7]
editSession.moveCursorToBeginningOfLine()
expect(editSession.getCursors().length).toBe 2
[cursor1, cursor2] = editSession.getCursors()
expect(cursor1.getBufferPosition()).toEqual [0,0]
expect(cursor2.getBufferPosition()).toEqual [1,0]
describe ".moveCursorToEndOfLine()", ->
it "moves cursor to the end of line", ->
editSession.setCursorScreenPosition [0,0]
editSession.addCursorAtScreenPosition [1,0]
editSession.moveCursorToEndOfLine()
expect(editSession.getCursors().length).toBe 2
[cursor1, cursor2] = editSession.getCursors()
expect(cursor1.getBufferPosition()).toEqual [0,29]
expect(cursor2.getBufferPosition()).toEqual [1,30]
describe "when soft wrap is on", ->
it "moves cursor to the beginning of the screen line", ->
editSession.setSoftWrapColumn(10)
editSession.setCursorScreenPosition([1, 2])
editSession.moveCursorToEndOfLine()
cursor = editSession.getCursor()
expect(cursor.getScreenPosition()).toEqual [1, 9]
describe "when soft wrap is off", ->
it "moves cursor to the end of line", ->
editSession.setCursorScreenPosition [0,0]
editSession.addCursorAtScreenPosition [1,0]
editSession.moveCursorToEndOfLine()
expect(editSession.getCursors().length).toBe 2
[cursor1, cursor2] = editSession.getCursors()
expect(cursor1.getBufferPosition()).toEqual [0,29]
expect(cursor2.getBufferPosition()).toEqual [1,30]
describe ".moveCursorToFirstCharacterOfLine()", ->
it "moves to the first character of the current line or the beginning of the line if it's already on the first character", ->
editSession.setCursorScreenPosition [0,5]
editSession.addCursorAtScreenPosition [1,7]
describe "when soft wrap is on", ->
it "moves to the first character of the current screen line or the beginning of the screen line if it's already on the first character", ->
editSession.setSoftWrapColumn(10)
editSession.setCursorScreenPosition [2,5]
editSession.addCursorAtScreenPosition [8,7]
editSession.moveCursorToFirstCharacterOfLine()
[cursor1, cursor2] = editSession.getCursors()
expect(cursor1.getBufferPosition()).toEqual [0,0]
expect(cursor2.getBufferPosition()).toEqual [1,2]
editSession.moveCursorToFirstCharacterOfLine()
expect(cursor1.getBufferPosition()).toEqual [0,0]
expect(cursor2.getBufferPosition()).toEqual [1,0]
describe "when triggered ", ->
it "does not move the cursor", ->
editSession.setCursorBufferPosition([10, 0])
editSession.moveCursorToFirstCharacterOfLine()
expect(editSession.getCursorBufferPosition()).toEqual [10, 0]
[cursor1, cursor2] = editSession.getCursors()
expect(cursor1.getScreenPosition()).toEqual [2,0]
expect(cursor2.getScreenPosition()).toEqual [8,4]
editSession.moveCursorToFirstCharacterOfLine()
expect(cursor1.getScreenPosition()).toEqual [2,0]
expect(cursor2.getScreenPosition()).toEqual [8,0]
describe "when soft wrap is of", ->
it "moves to the first character of the current line or the beginning of the line if it's already on the first character", ->
editSession.setCursorScreenPosition [0,5]
editSession.addCursorAtScreenPosition [1,7]
editSession.moveCursorToFirstCharacterOfLine()
[cursor1, cursor2] = editSession.getCursors()
expect(cursor1.getBufferPosition()).toEqual [0,0]
expect(cursor2.getBufferPosition()).toEqual [1,2]
editSession.moveCursorToFirstCharacterOfLine()
expect(cursor1.getBufferPosition()).toEqual [0,0]
expect(cursor2.getBufferPosition()).toEqual [1,0]
describe ".moveCursorToBeginningOfWord()", ->
it "moves the cursor to the beginning of the word", ->
@ -347,6 +417,36 @@ describe "EditSession", ->
editSession.moveCursorToBeginningOfWord()
expect(editSession.getCursorBufferPosition()).toEqual [9, 2]
describe ".moveCursorToPreviousWordBoundary()", ->
it "moves the cursor to the previous word boundary", ->
editSession.setCursorBufferPosition [0, 8]
editSession.addCursorAtBufferPosition [2, 0]
editSession.addCursorAtBufferPosition [2, 4]
editSession.addCursorAtBufferPosition [3, 14]
[cursor1, cursor2, cursor3, cursor4] = editSession.getCursors()
editSession.moveCursorToPreviousWordBoundary()
expect(cursor1.getBufferPosition()).toEqual [0, 4]
expect(cursor2.getBufferPosition()).toEqual [1, 30]
expect(cursor3.getBufferPosition()).toEqual [2, 0]
expect(cursor4.getBufferPosition()).toEqual [3, 13]
describe ".moveCursorToNextWordBoundary()", ->
it "moves the cursor to the previous word boundary", ->
editSession.setCursorBufferPosition [0, 8]
editSession.addCursorAtBufferPosition [2, 40]
editSession.addCursorAtBufferPosition [3, 0]
editSession.addCursorAtBufferPosition [3, 30]
[cursor1, cursor2, cursor3, cursor4] = editSession.getCursors()
editSession.moveCursorToNextWordBoundary()
expect(cursor1.getBufferPosition()).toEqual [0, 13]
expect(cursor2.getBufferPosition()).toEqual [3, 0]
expect(cursor3.getBufferPosition()).toEqual [3, 4]
expect(cursor4.getBufferPosition()).toEqual [3, 31]
describe ".moveCursorToEndOfWord()", ->
it "moves the cursor to the end of the word", ->
editSession.setCursorBufferPosition [0, 6]
@ -389,6 +489,14 @@ describe "EditSession", ->
expect(cursor2.getBufferPosition()).toEqual [1, 13]
expect(cursor3.getBufferPosition()).toEqual [2, 4]
# When the cursor is on whitespace
editSession.setText("ab cde- ")
editSession.setCursorBufferPosition [0,2]
cursor = editSession.getCursor()
editSession.moveCursorToBeginningOfNextWord()
expect(cursor.getBufferPosition()).toEqual [0, 3]
it "does not blow up when there is no next word", ->
editSession.setCursorBufferPosition [Infinity, Infinity]
endPosition = editSession.getCursorBufferPosition()
@ -704,6 +812,46 @@ describe "EditSession", ->
expect(selection2.getBufferRange()).toEqual [[3,48], [3,51]]
expect(selection2.isReversed()).toBeFalsy()
describe ".selectToPreviousWordBoundary()", ->
it "select to the previous word boundary", ->
editSession.setCursorBufferPosition [0, 8]
editSession.addCursorAtBufferPosition [2, 0]
editSession.addCursorAtBufferPosition [3, 4]
editSession.addCursorAtBufferPosition [3, 14]
editSession.selectToPreviousWordBoundary()
expect(editSession.getSelections().length).toBe 4
[selection1, selection2, selection3, selection4] = editSession.getSelections()
expect(selection1.getBufferRange()).toEqual [[0,8], [0,4]]
expect(selection1.isReversed()).toBeTruthy()
expect(selection2.getBufferRange()).toEqual [[2,0], [1,30]]
expect(selection2.isReversed()).toBeTruthy()
expect(selection3.getBufferRange()).toEqual [[3,4], [3,0]]
expect(selection3.isReversed()).toBeTruthy()
expect(selection4.getBufferRange()).toEqual [[3,14], [3,13]]
expect(selection4.isReversed()).toBeTruthy()
describe ".selectToNextWordBoundary()", ->
it "select to the next word boundary", ->
editSession.setCursorBufferPosition [0, 8]
editSession.addCursorAtBufferPosition [2, 40]
editSession.addCursorAtBufferPosition [4, 0]
editSession.addCursorAtBufferPosition [3, 30]
editSession.selectToNextWordBoundary()
expect(editSession.getSelections().length).toBe 4
[selection1, selection2, selection3, selection4] = editSession.getSelections()
expect(selection1.getBufferRange()).toEqual [[0,8], [0,13]]
expect(selection1.isReversed()).toBeFalsy()
expect(selection2.getBufferRange()).toEqual [[2,40], [3,0]]
expect(selection2.isReversed()).toBeFalsy()
expect(selection3.getBufferRange()).toEqual [[4,0], [4,4]]
expect(selection3.isReversed()).toBeFalsy()
expect(selection4.getBufferRange()).toEqual [[3,30], [3,31]]
expect(selection4.isReversed()).toBeFalsy()
describe ".selectWord()", ->
describe "when the cursor is inside a word", ->
it "selects the entire word", ->
@ -1628,24 +1776,32 @@ describe "EditSession", ->
expect(clipboard.readText()).toBe 'quicksort\nsort'
describe ".cutToEndOfLine()", ->
describe "when nothing is selected", ->
describe "when soft wrap is on", ->
it "cuts up to the end of the line", ->
editSession.setCursorBufferPosition([2, 20])
editSession.addCursorAtBufferPosition([3, 20])
editSession.setSoftWrapColumn(10)
editSession.setCursorScreenPosition([2, 2])
editSession.cutToEndOfLine()
expect(buffer.lineForRow(2)).toBe ' if (items.length'
expect(buffer.lineForRow(3)).toBe ' var pivot = item'
expect(pasteboard.read()[0]).toBe ' <= 1) return items;\ns.shift(), current, left = [], right = [];'
expect(editSession.lineForScreenRow(2).text).toBe '= () {'
describe "when text is selected", ->
it "only cuts the selected text, not to the end of the line", ->
editSession.setSelectedBufferRanges([[[2,20], [2, 30]], [[3, 20], [3, 20]]])
describe "when soft wrap is off", ->
describe "when nothing is selected", ->
it "cuts up to the end of the line", ->
editSession.setCursorBufferPosition([2, 20])
editSession.addCursorAtBufferPosition([3, 20])
editSession.cutToEndOfLine()
expect(buffer.lineForRow(2)).toBe ' if (items.length'
expect(buffer.lineForRow(3)).toBe ' var pivot = item'
expect(pasteboard.read()[0]).toBe ' <= 1) return items;\ns.shift(), current, left = [], right = [];'
editSession.cutToEndOfLine()
describe "when text is selected", ->
it "only cuts the selected text, not to the end of the line", ->
editSession.setSelectedBufferRanges([[[2,20], [2, 30]], [[3, 20], [3, 20]]])
expect(buffer.lineForRow(2)).toBe ' if (items.lengthurn items;'
expect(buffer.lineForRow(3)).toBe ' var pivot = item'
expect(pasteboard.read()[0]).toBe ' <= 1) ret\ns.shift(), current, left = [], right = [];'
editSession.cutToEndOfLine()
expect(buffer.lineForRow(2)).toBe ' if (items.lengthurn items;'
expect(buffer.lineForRow(3)).toBe ' var pivot = item'
expect(pasteboard.read()[0]).toBe ' <= 1) ret\ns.shift(), current, left = [], right = [];'
describe ".copySelectedText()", ->
it "copies selected text onto the clipboard", ->
@ -2011,91 +2167,6 @@ describe "EditSession", ->
expect(cursor1.getBufferPosition()).toEqual [0,0]
expect(cursor3.getBufferPosition()).toEqual [1,2]
describe "folding", ->
describe ".unfoldAll()", ->
it "unfolds every folded line", ->
initialScreenLineCount = editSession.getScreenLineCount()
editSession.foldBufferRow(0)
editSession.foldBufferRow(1)
expect(editSession.getScreenLineCount()).toBeLessThan initialScreenLineCount
editSession.unfoldAll()
expect(editSession.getScreenLineCount()).toBe initialScreenLineCount
describe ".foldAll()", ->
it "folds every foldable line", ->
editSession.foldAll()
fold1 = editSession.lineForScreenRow(0).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 12]
fold1.destroy()
fold2 = editSession.lineForScreenRow(1).fold
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [1, 9]
fold2.destroy()
fold3 = editSession.lineForScreenRow(4).fold
expect([fold3.getStartRow(), fold3.getEndRow()]).toEqual [4, 7]
describe ".foldBufferRow(bufferRow)", ->
describe "when bufferRow can be folded", ->
it "creates a fold based on the syntactic region starting at the given row", ->
editSession.foldBufferRow(1)
fold = editSession.lineForScreenRow(1).fold
expect(fold.getStartRow()).toBe 1
expect(fold.getEndRow()).toBe 9
describe "when bufferRow can't be folded", ->
it "searches upward for the first row that begins a syntatic region containing the given buffer row (and folds it)", ->
editSession.foldBufferRow(8)
fold = editSession.lineForScreenRow(1).fold
expect(fold.getStartRow()).toBe 1
expect(fold.getEndRow()).toBe 9
describe "when the bufferRow is already folded", ->
it "searches upward for the first row that begins a syntatic region containing the folded row (and folds it)", ->
editSession.foldBufferRow(2)
expect(editSession.lineForScreenRow(1).fold).toBeDefined()
expect(editSession.lineForScreenRow(0).fold).not.toBeDefined()
editSession.foldBufferRow(1)
expect(editSession.lineForScreenRow(0).fold).toBeDefined()
describe "when the bufferRow is in a multi-line comment", ->
it "searches upward and downward for surrounding comment lines and folds them as a single fold", ->
buffer.insert([1,0], " //this is a comment\n // and\n //more docs\n\n//second comment")
editSession.foldBufferRow(1)
fold = editSession.lineForScreenRow(1).fold
expect(fold.getStartRow()).toBe 1
expect(fold.getEndRow()).toBe 3
describe "when the bufferRow is a single-line comment", ->
it "searches upward for the first row that begins a syntatic region containing the folded row (and folds it)", ->
buffer.insert([1,0], " //this is a single line comment\n")
editSession.foldBufferRow(1)
fold = editSession.lineForScreenRow(0).fold
expect(fold.getStartRow()).toBe 0
expect(fold.getEndRow()).toBe 13
describe ".unfoldBufferRow(bufferRow)", ->
describe "when bufferRow can be unfolded", ->
it "destroys a fold based on the syntactic region starting at the given row", ->
editSession.foldBufferRow(1)
expect(editSession.lineForScreenRow(1).fold).toBeDefined()
editSession.unfoldBufferRow(1)
expect(editSession.lineForScreenRow(1).fold).toBeUndefined()
describe "when bufferRow can't be unfolded", ->
it "does not throw an error", ->
expect(editSession.lineForScreenRow(1).fold).toBeUndefined()
editSession.unfoldBufferRow(1)
expect(editSession.lineForScreenRow(1).fold).toBeUndefined()
it "maintains cursor buffer position when a folding/unfolding", ->
editSession.setCursorBufferPosition([5,5])
editSession.foldAll()
expect(editSession.getCursorBufferPosition()).toEqual([5,5])
describe ".deleteLine()", ->
it "deletes the first line when the cursor is there", ->
editSession.getCursor().moveToTop()

View File

@ -44,12 +44,12 @@ describe "LanguageMode", ->
expect(languageMode.doesBufferRowStartFold(2)).toBeFalsy()
expect(languageMode.doesBufferRowStartFold(3)).toBeFalsy()
describe ".rowRangeForFoldAtBufferRow(bufferRow)", ->
describe ".rowRangeForCodeFoldAtBufferRow(bufferRow)", ->
it "returns the start/end rows of the foldable region starting at the given row", ->
expect(languageMode.rowRangeForFoldAtBufferRow(0)).toEqual [0, 12]
expect(languageMode.rowRangeForFoldAtBufferRow(1)).toEqual [1, 9]
expect(languageMode.rowRangeForFoldAtBufferRow(2)).toBeNull()
expect(languageMode.rowRangeForFoldAtBufferRow(4)).toEqual [4, 7]
expect(languageMode.rowRangeForCodeFoldAtBufferRow(0)).toEqual [0, 12]
expect(languageMode.rowRangeForCodeFoldAtBufferRow(1)).toEqual [1, 9]
expect(languageMode.rowRangeForCodeFoldAtBufferRow(2)).toBeNull()
expect(languageMode.rowRangeForCodeFoldAtBufferRow(4)).toEqual [4, 7]
describe "suggestedIndentForBufferRow", ->
it "returns the suggested indentation based on auto-indent/outdent rules", ->
@ -58,6 +58,47 @@ describe "LanguageMode", ->
expect(languageMode.suggestedIndentForBufferRow(2)).toBe 2
expect(languageMode.suggestedIndentForBufferRow(9)).toBe 1
describe "rowRangeForParagraphAtBufferRow", ->
describe "with code and comments", ->
beforeEach ->
buffer.setText '''
var quicksort = function () {
/* Single line comment block */
var sort = function(items) {};
/*
A multiline
comment is here
*/
var sort = function(items) {};
// A comment
//
// Multiple comment
// lines
var sort = function(items) {};
// comment line after fn
};
'''
it "will limit paragraph range to comments", ->
range = languageMode.rowRangeForParagraphAtBufferRow(0)
expect(range).toEqual [[0,0], [0,29]]
range = languageMode.rowRangeForParagraphAtBufferRow(10)
expect(range).toEqual [[10,0], [10,14]]
range = languageMode.rowRangeForParagraphAtBufferRow(11)
expect(range).toBeFalsy()
range = languageMode.rowRangeForParagraphAtBufferRow(12)
expect(range).toEqual [[12,0], [13,10]]
range = languageMode.rowRangeForParagraphAtBufferRow(14)
expect(range).toEqual [[14,0], [14,32]]
range = languageMode.rowRangeForParagraphAtBufferRow(15)
expect(range).toEqual [[15,0], [15,26]]
describe "coffeescript", ->
beforeEach ->
atom.activatePackage('coffee-script-tmbundle', sync: true)
@ -98,12 +139,12 @@ describe "LanguageMode", ->
expect(languageMode.doesBufferRowStartFold(3)).toBeFalsy()
expect(languageMode.doesBufferRowStartFold(19)).toBeTruthy()
describe ".rowRangeForFoldAtBufferRow(bufferRow)", ->
describe ".rowRangeForCodeFoldAtBufferRow(bufferRow)", ->
it "returns the start/end rows of the foldable region starting at the given row", ->
expect(languageMode.rowRangeForFoldAtBufferRow(0)).toEqual [0, 20]
expect(languageMode.rowRangeForFoldAtBufferRow(1)).toEqual [1, 17]
expect(languageMode.rowRangeForFoldAtBufferRow(2)).toBeNull()
expect(languageMode.rowRangeForFoldAtBufferRow(19)).toEqual [19, 20]
expect(languageMode.rowRangeForCodeFoldAtBufferRow(0)).toEqual [0, 20]
expect(languageMode.rowRangeForCodeFoldAtBufferRow(1)).toEqual [1, 17]
expect(languageMode.rowRangeForCodeFoldAtBufferRow(2)).toBeNull()
expect(languageMode.rowRangeForCodeFoldAtBufferRow(19)).toEqual [19, 20]
describe "css", ->
beforeEach ->
@ -158,3 +199,156 @@ describe "LanguageMode", ->
languageMode.toggleLineCommentsForBufferRows(0, 0)
expect(buffer.lineForRow(0)).toBe "// @color: #4D926F;"
describe "folding", ->
beforeEach ->
atom.activatePackage('javascript-tmbundle', sync: true)
editSession = project.open('sample.js', autoIndent: false)
{buffer, languageMode} = editSession
it "maintains cursor buffer position when a folding/unfolding", ->
editSession.setCursorBufferPosition([5,5])
languageMode.foldAll()
expect(editSession.getCursorBufferPosition()).toEqual([5,5])
describe ".unfoldAll()", ->
it "unfolds every folded line", ->
initialScreenLineCount = editSession.getScreenLineCount()
languageMode.foldBufferRow(0)
languageMode.foldBufferRow(1)
expect(editSession.getScreenLineCount()).toBeLessThan initialScreenLineCount
languageMode.unfoldAll()
expect(editSession.getScreenLineCount()).toBe initialScreenLineCount
describe ".foldAll()", ->
it "folds every foldable line", ->
languageMode.foldAll()
fold1 = editSession.lineForScreenRow(0).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 12]
fold1.destroy()
fold2 = editSession.lineForScreenRow(1).fold
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [1, 9]
fold2.destroy()
fold3 = editSession.lineForScreenRow(4).fold
expect([fold3.getStartRow(), fold3.getEndRow()]).toEqual [4, 7]
describe ".foldBufferRow(bufferRow)", ->
describe "when bufferRow can be folded", ->
it "creates a fold based on the syntactic region starting at the given row", ->
languageMode.foldBufferRow(1)
fold = editSession.lineForScreenRow(1).fold
expect(fold.getStartRow()).toBe 1
expect(fold.getEndRow()).toBe 9
describe "when bufferRow can't be folded", ->
it "searches upward for the first row that begins a syntatic region containing the given buffer row (and folds it)", ->
languageMode.foldBufferRow(8)
fold = editSession.lineForScreenRow(1).fold
expect(fold.getStartRow()).toBe 1
expect(fold.getEndRow()).toBe 9
describe "when the bufferRow is already folded", ->
it "searches upward for the first row that begins a syntatic region containing the folded row (and folds it)", ->
languageMode.foldBufferRow(2)
expect(editSession.lineForScreenRow(1).fold).toBeDefined()
expect(editSession.lineForScreenRow(0).fold).not.toBeDefined()
languageMode.foldBufferRow(1)
expect(editSession.lineForScreenRow(0).fold).toBeDefined()
describe "when the bufferRow is in a multi-line comment", ->
it "searches upward and downward for surrounding comment lines and folds them as a single fold", ->
buffer.insert([1,0], " //this is a comment\n // and\n //more docs\n\n//second comment")
languageMode.foldBufferRow(1)
fold = editSession.lineForScreenRow(1).fold
expect(fold.getStartRow()).toBe 1
expect(fold.getEndRow()).toBe 3
describe "when the bufferRow is a single-line comment", ->
it "searches upward for the first row that begins a syntatic region containing the folded row (and folds it)", ->
buffer.insert([1,0], " //this is a single line comment\n")
languageMode.foldBufferRow(1)
fold = editSession.lineForScreenRow(0).fold
expect(fold.getStartRow()).toBe 0
expect(fold.getEndRow()).toBe 13
describe ".unfoldBufferRow(bufferRow)", ->
describe "when bufferRow can be unfolded", ->
it "destroys a fold based on the syntactic region starting at the given row", ->
languageMode.foldBufferRow(1)
expect(editSession.lineForScreenRow(1).fold).toBeDefined()
languageMode.unfoldBufferRow(1)
expect(editSession.lineForScreenRow(1).fold).toBeUndefined()
describe "when bufferRow can't be unfolded", ->
it "does not throw an error", ->
expect(editSession.lineForScreenRow(1).fold).toBeUndefined()
languageMode.unfoldBufferRow(1)
expect(editSession.lineForScreenRow(1).fold).toBeUndefined()
describe "folding with comments", ->
beforeEach ->
atom.activatePackage('javascript-tmbundle', sync: true)
editSession = project.open('sample-with-comments.js', autoIndent: false)
{buffer, languageMode} = editSession
describe ".unfoldAll()", ->
it "unfolds every folded line", ->
initialScreenLineCount = editSession.getScreenLineCount()
languageMode.foldBufferRow(0)
languageMode.foldBufferRow(5)
expect(editSession.getScreenLineCount()).toBeLessThan initialScreenLineCount
languageMode.unfoldAll()
expect(editSession.getScreenLineCount()).toBe initialScreenLineCount
describe ".foldAll()", ->
it "folds every foldable line", ->
languageMode.foldAll()
fold1 = editSession.lineForScreenRow(0).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 19]
fold1.destroy()
fold2 = editSession.lineForScreenRow(1).fold
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [1, 4]
fold3 = editSession.lineForScreenRow(2).fold.destroy()
fold4 = editSession.lineForScreenRow(3).fold
expect([fold4.getStartRow(), fold4.getEndRow()]).toEqual [6, 8]
describe ".foldAllAtIndentLevel()", ->
it "folds every foldable range at a given indentLevel", ->
languageMode.foldAllAtIndentLevel(2)
fold1 = editSession.lineForScreenRow(6).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [6, 8]
fold1.destroy()
fold2 = editSession.lineForScreenRow(11).fold
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [11, 14]
fold2.destroy()
it "does not fold anything but the indentLevel", ->
languageMode.foldAllAtIndentLevel(0)
fold1 = editSession.lineForScreenRow(0).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 19]
fold1.destroy()
fold2 = editSession.lineForScreenRow(5).fold
expect(fold2).toBeFalsy()
describe "css", ->
beforeEach ->
atom.activatePackage('source-tmbundle', sync: true)
atom.activatePackage('css-tmbundle', sync: true)
editSession = project.open('css.css', autoIndent: true)
describe "suggestedIndentForBufferRow", ->
it "does not return negative values (regression)", ->
editSession.setText('.test {\npadding: 0;\n}')
expect(editSession.suggestedIndentForBufferRow(2)).toBe 0

View File

@ -1,154 +0,0 @@
PackageConfigPanel = require 'package-config-panel'
packageManager = require 'package-manager'
_ = require 'underscore'
describe "PackageConfigPanel", ->
[panel, configObserver] = []
beforeEach ->
installedPackages = [
{
name: 'p1'
version: '3.2.1'
}
{
name: 'p2'
version: '1.2.3'
}
{
name: 'p3'
version: '5.8.5'
}
]
availablePackages = [
{
name: 'p4'
version: '3.2.1'
homepage: 'http://p4.io'
}
{
name: 'p5'
version: '1.2.3'
repository: url: 'http://github.com/atom/p5.git'
bugs: url: 'http://github.com/atom/p5/issues'
}
{
name: 'p6'
version: '5.8.5'
}
]
spyOn(packageManager, 'getAvailable').andCallFake (callback) ->
callback(null, availablePackages)
spyOn(packageManager, 'uninstall').andCallFake (pack, callback) ->
_.remove(installedPackages, pack)
callback()
spyOn(packageManager, 'install').andCallFake (pack, callback) ->
installedPackages.push(pack)
callback()
spyOn(atom, 'getAvailablePackageMetadata').andReturn(installedPackages)
spyOn(atom, 'resolvePackagePath').andCallFake (name) ->
if _.contains(_.pluck(installedPackages, 'name'), name)
"/tmp/atom-packages/#{name}"
configObserver = jasmine.createSpy("configObserver")
observeSubscription = config.observe('core.disabledPackages', configObserver)
config.set('core.disabledPackages', ['p1', 'p3'])
configObserver.reset()
jasmine.unspy(window, "setTimeout")
panel = new PackageConfigPanel
installedCallback = jasmine.createSpy("installed packages callback")
panel.packageEventEmitter.on("installed-packages-loaded", installedCallback)
waitsFor -> installedCallback.callCount > 0
describe 'Installed tab', ->
it "lists all installed packages with a link to enable or disable the package", ->
p1View = panel.installed.find("[name='p1']").view()
expect(p1View).toExist()
expect(p1View.enableToggle.find('a').text()).toBe 'Enable'
p2View = panel.installed.find("[name='p2']").view()
expect(p2View).toExist()
expect(p2View.enableToggle.find('a').text()).toBe 'Disable'
p3View = panel.installed.find("[name='p3']").view()
expect(p3View).toExist()
expect(p3View.enableToggle.find('a').text()).toBe 'Enable'
describe "when the core.disabledPackages array changes", ->
it "updates the checkboxes for newly disabled / enabled packages", ->
config.set('core.disabledPackages', ['p2'])
p1View = panel.installed.find("[name='p1']").view()
expect(p1View.enableToggle.find('a').text()).toBe 'Disable'
p2View = panel.installed.find("[name='p2']").view()
expect(p2View.enableToggle.find('a').text()).toBe 'Enable'
p3View = panel.installed.find("[name='p3']").view()
expect(p3View.enableToggle.find('a').text()).toBe 'Disable'
describe "when the disable link is clicked", ->
it "adds the package name to the disabled packages array", ->
p2View = panel.installed.find("[name='p2']").view()
p2View.enableToggle.find('a').click()
expect(configObserver).toHaveBeenCalledWith(['p1', 'p3', 'p2'])
describe "when the enable link is clicked", ->
it "removes the package name from the disabled packages array", ->
p3View = panel.installed.find("[name='p3']").view()
p3View.enableToggle.find('a').click()
expect(configObserver).toHaveBeenCalledWith(['p1'])
describe "when Uninstall is clicked", ->
it "removes the package from the tab", ->
expect(panel.installed.find("[name='p1']")).toExist()
p1View = panel.installed.find("[name='p1']").view()
expect(p1View.defaultAction.text()).toBe 'Uninstall'
p1View.defaultAction.click()
expect(panel.installed.find("[name='p1']")).not.toExist()
describe 'Available tab', ->
it 'lists all available packages', ->
panel.availableLink.click()
panel.attachToDom()
expect(panel.available.packagesArea.children('.panel').length).toBe 3
p4View = panel.available.packagesArea.children('.panel:eq(0)').view()
p5View = panel.available.packagesArea.children('.panel:eq(1)').view()
p6View = panel.available.packagesArea.children('.panel:eq(2)').view()
expect(p4View.name.text()).toBe 'p4'
expect(p5View.name.text()).toBe 'p5'
expect(p6View.name.text()).toBe 'p6'
expect(p4View.version.text()).toBe '3.2.1'
expect(p5View.version.text()).toBe '1.2.3'
expect(p6View.version.text()).toBe '5.8.5'
p4View.dropdownButton.click()
expect(p4View.homepage).toBeVisible()
expect(p4View.homepage.find('a').attr('href')).toBe 'http://p4.io'
expect(p4View.issues).toBeHidden()
p5View.dropdownButton.click()
expect(p5View.homepage).toBeVisible()
expect(p5View.homepage.find('a').attr('href')).toBe 'http://github.com/atom/p5'
expect(p5View.issues).toBeVisible()
expect(p5View.issues.find('a').attr('href')).toBe 'http://github.com/atom/p5/issues'
p6View.dropdownButton.click()
expect(p6View.homepage).toBeHidden()
expect(p6View.issues).toBeHidden()
describe "when Install is clicked", ->
it "adds the package to the Installed tab", ->
expect(panel.installed.find("[name='p4']")).not.toExist()
expect(panel.available.find("[name='p4']")).toExist()
p4View = panel.available.find("[name='p4']").view()
expect(p4View.defaultAction.text()).toBe 'Install'
p4View.defaultAction.click()
expect(panel.installed.find("[name='p4']")).toExist()
expect(p4View.defaultAction.text()).toBe 'Uninstall'

View File

@ -31,6 +31,22 @@ describe "Project", ->
expect(project.getPath()).toBe '/tmp'
fsUtils.remove('/tmp/atom-test-save-sets-project-path')
describe "when an edit session is deserialized", ->
it "emits an 'edit-session-created' event and stores the edit session", ->
handler = jasmine.createSpy('editSessionCreatedHandler')
project.on 'edit-session-created', handler
editSession1 = project.open("a")
expect(handler.callCount).toBe 1
expect(project.getEditSessions().length).toBe 1
expect(project.getEditSessions()[0]).toBe editSession1
editSession2 = deserialize(editSession1.serialize())
expect(handler.callCount).toBe 2
expect(project.getEditSessions().length).toBe 2
expect(project.getEditSessions()[0]).toBe editSession1
expect(project.getEditSessions()[1]).toBe editSession2
describe ".open(path)", ->
[fooOpener, barOpener, absolutePath, newBufferHandler, newEditSessionHandler] = []
beforeEach ->

View File

@ -1,46 +0,0 @@
TextMateScopeSelector = require 'text-mate-scope-selector'
describe "TextMateScopeSelector", ->
it "matches the asterix", ->
expect(new TextMateScopeSelector('*').matches(['a'])).toBeTruthy()
expect(new TextMateScopeSelector('*').matches(['b', 'c'])).toBeTruthy()
expect(new TextMateScopeSelector('a.*.c').matches(['a.b.c'])).toBeTruthy()
expect(new TextMateScopeSelector('a.*.c').matches(['a.b.c.d'])).toBeTruthy()
expect(new TextMateScopeSelector('a.*.c').matches(['a.b.d.c'])).toBeFalsy()
it "matches prefixes", ->
expect(new TextMateScopeSelector('a').matches(['a'])).toBeTruthy()
expect(new TextMateScopeSelector('a').matches(['a.b'])).toBeTruthy()
expect(new TextMateScopeSelector('a').matches(['abc'])).toBeFalsy()
it "matches disjunction", ->
expect(new TextMateScopeSelector('a | b').matches(['b'])).toBeTruthy()
expect(new TextMateScopeSelector('a|b|c').matches(['c'])).toBeTruthy()
expect(new TextMateScopeSelector('a|b|c').matches(['d'])).toBeFalsy()
it "matches negation", ->
expect(new TextMateScopeSelector('a - c').matches(['a', 'b'])).toBeTruthy()
expect(new TextMateScopeSelector('a-b').matches(['a', 'b'])).toBeFalsy()
it "matches conjunction", ->
expect(new TextMateScopeSelector('a & b').matches(['b', 'a'])).toBeTruthy()
expect(new TextMateScopeSelector('a&b&c').matches(['c'])).toBeFalsy()
expect(new TextMateScopeSelector('a&b&c').matches(['a', 'b', 'd'])).toBeFalsy()
it "matches composites", ->
expect(new TextMateScopeSelector('a,b,c').matches(['b', 'c'])).toBeTruthy()
expect(new TextMateScopeSelector('a, b, c').matches(['d', 'e'])).toBeFalsy()
expect(new TextMateScopeSelector('a, b, c').matches(['d', 'c.e'])).toBeTruthy()
it "matches groups", ->
expect(new TextMateScopeSelector('(a,b) | (c, d)').matches(['a'])).toBeTruthy()
expect(new TextMateScopeSelector('(a,b) | (c, d)').matches(['b'])).toBeTruthy()
expect(new TextMateScopeSelector('(a,b) | (c, d)').matches(['c'])).toBeTruthy()
expect(new TextMateScopeSelector('(a,b) | (c, d)').matches(['d'])).toBeTruthy()
expect(new TextMateScopeSelector('(a,b) | (c, d)').matches(['e'])).toBeFalsy()
it "matches paths", ->
expect(new TextMateScopeSelector('a b').matches(['a', 'b'])).toBeTruthy()
expect(new TextMateScopeSelector('a b').matches(['b', 'a'])).toBeFalsy()
expect(new TextMateScopeSelector('a c').matches(['a', 'b', 'c', 'd', 'e'])).toBeTruthy()
expect(new TextMateScopeSelector('a b e').matches(['a', 'b', 'c', 'd', 'e'])).toBeTruthy()

View File

@ -1,54 +0,0 @@
fsUtils = require 'fs-utils'
path = require 'path'
plist = require 'plist'
TextMateTheme = require 'text-mate-theme'
Theme = require 'theme'
describe "TextMateTheme", ->
[theme, themePath] = []
beforeEach ->
themePath = fsUtils.resolveOnLoadPath(path.join('fixtures', 'test.tmTheme'))
theme = Theme.load(themePath)
afterEach ->
theme.deactivate()
describe ".getRulesets()", ->
rulesets = null
beforeEach ->
rulesets = theme.getRulesets()
it "returns rulesets representing the theme's global style settings", ->
expect(rulesets[0]).toEqual
selector: '.editor, .editor .gutter'
properties:
'background-color': '#141414'
'color': '#F8F8F8'
expect(rulesets[1]).toEqual
selector: '.editor.is-focused .cursor'
properties:
'border-color': '#A7A7A7'
expect(rulesets[2]).toEqual
selector: '.editor.is-focused .selection .region'
properties:
'background-color': "rgba(221, 240, 255, 0.2)"
it "returns an array of objects representing the theme's scope selectors", ->
expect(rulesets[12]).toEqual
comment: "Invalid Deprecated"
selector: ".invalid.deprecated"
properties:
'color': "#D2A8A1"
'font-style': 'italic'
'text-decoration': 'underline'
expect(rulesets[13]).toEqual
comment: "Invalid Illegal"
selector: ".invalid.illegal"
properties:
'color': "#F8F8F8"
'background-color': 'rgba(86, 45, 86, 0.75)'

View File

@ -1,49 +0,0 @@
$ = require 'jquery'
ThemeConfigPanel = require 'theme-config-panel'
describe "ThemeConfigPanel", ->
panel = null
beforeEach ->
config.set('core.themes', ['atom-dark-ui', 'atom-dark-syntax'])
panel = new ThemeConfigPanel
describe "when an enabled theme is reloced in the themes list", ->
it "updates the 'core.themes' config key to reflect the new order", ->
li = panel.enabledThemes.children(':first').detach()
panel.enabledThemes.append(li)
panel.enabledThemes.sortable('option', 'update')()
expect(config.get('core.themes')).toEqual ['atom-dark-syntax', 'atom-dark-ui']
describe "when a theme is dragged into the enabled themes list", ->
it "updates the 'core.themes' config key to reflect the themes in the enabled list", ->
dragHelper = panel.availableThemes.find("li[name='atom-light-ui']").clone()
panel.enabledThemes.prepend(dragHelper)
panel.enabledThemes.sortable('option', 'receive')(null, helper: dragHelper[0])
panel.enabledThemes.sortable('option', 'update')()
expect(config.get('core.themes')).toEqual ['atom-light-ui', 'atom-dark-ui', 'atom-dark-syntax']
describe "when the theme is already present in the enabled list", ->
it "removes the previous instance of the theme, updating the order based on the location of drag", ->
dragHelper = panel.availableThemes.find("li[name='atom-dark-ui']").clone()
panel.enabledThemes.append(dragHelper)
panel.enabledThemes.sortable('option', 'receive')(null, helper: dragHelper[0])
panel.enabledThemes.sortable('option', 'update')()
expect(config.get('core.themes')).toEqual ['atom-dark-syntax', 'atom-dark-ui']
dragHelper = panel.availableThemes.find("li[name='atom-dark-ui']").clone()
panel.enabledThemes.prepend(dragHelper)
panel.enabledThemes.sortable('option', 'receive')(null, helper: dragHelper[0])
panel.enabledThemes.sortable('option', 'update')()
expect(config.get('core.themes')).toEqual ['atom-dark-ui', 'atom-dark-syntax']
describe "when the disable icon is clicked on a theme li", ->
it "removes the theme from the list and the 'core.themes' array", ->
panel.enabledThemes.find('li:first .disable-theme').click()
expect(panel.getEnabledThemeNames()).toEqual ['atom-dark-syntax']
expect(config.get('core.themes')).toEqual ['atom-dark-syntax']
describe "when the 'core.config' key is updated", ->
it "refreshes the enabled themes list", ->
config.set('core.themes', ['atom-light-ui', 'atom-light-syntax'])
expect(panel.getEnabledThemeNames()).toEqual ['atom-light-ui', 'atom-light-syntax']

View File

@ -0,0 +1,32 @@
$ = require 'jquery'
Theme = require 'theme'
ThemeManager = require 'theme-manager'
describe "ThemeManager", ->
describe "when the core.themes config value changes", ->
it "add/removes stylesheets to reflect the new config value", ->
themeManager = new ThemeManager()
spyOn(themeManager, 'getUserStylesheetPath').andCallFake -> null
themeManager.load()
config.set('core.themes', [])
expect($('style.userTheme').length).toBe 0
config.set('core.themes', ['atom-dark-syntax'])
expect($('style.userTheme').length).toBe 1
expect($('style.userTheme:eq(0)').attr('id')).toMatch /atom-dark-syntax.less$/
config.set('core.themes', ['atom-light-syntax', 'atom-dark-syntax'])
expect($('style.userTheme').length).toBe 2
expect($('style.userTheme:eq(0)').attr('id')).toMatch /atom-light-syntax.less$/
expect($('style.userTheme:eq(1)').attr('id')).toMatch /atom-dark-syntax.less$/
config.set('core.themes', [])
expect($('style.userTheme').length).toBe 0
describe "when a theme fails to load", ->
it "logs a warning", ->
themeManager = new ThemeManager()
spyOn(console, 'warn')
themeManager.loadTheme('a-theme-that-will-not-be-found')
expect(console.warn).toHaveBeenCalled()

View File

@ -3,7 +3,7 @@ fsUtils = require 'fs-utils'
path = require 'path'
Theme = require 'theme'
describe "@load(name)", ->
describe "Theme", ->
theme = null
beforeEach ->
@ -12,48 +12,39 @@ describe "@load(name)", ->
afterEach ->
theme.deactivate()
describe "TextMateTheme", ->
it "applies the theme's stylesheet to the current window", ->
expect($(".editor").css("background-color")).not.toBe("rgb(20, 20, 20)")
describe "when the theme is a file", ->
it "loads and applies css", ->
expect($(".editor").css("padding-bottom")).not.toBe "1234px"
themePath = project.resolve('themes/theme-stylesheet.css')
theme = new Theme(themePath)
expect($(".editor").css("padding-top")).toBe "1234px"
themePath = fsUtils.resolveOnLoadPath(path.join('fixtures', 'test.tmTheme'))
theme = Theme.load(themePath)
expect($(".editor").css("background-color")).toBe("rgb(20, 20, 20)")
it "parses, loads and applies less", ->
expect($(".editor").css("padding-bottom")).not.toBe "1234px"
themePath = project.resolve('themes/theme-stylesheet.less')
theme = new Theme(themePath)
expect($(".editor").css("padding-top")).toBe "4321px"
describe "AtomTheme", ->
describe "when the theme is a file", ->
it "loads and applies css", ->
expect($(".editor").css("padding-bottom")).not.toBe "1234px"
themePath = project.resolve('themes/theme-stylesheet.css')
theme = Theme.load(themePath)
expect($(".editor").css("padding-top")).toBe "1234px"
describe "when the theme contains a package.json file", ->
it "loads and applies stylesheets from package.json in the correct order", ->
expect($(".editor").css("padding-top")).not.toBe("101px")
expect($(".editor").css("padding-right")).not.toBe("102px")
expect($(".editor").css("padding-bottom")).not.toBe("103px")
it "parses, loads and applies less", ->
expect($(".editor").css("padding-bottom")).not.toBe "1234px"
themePath = project.resolve('themes/theme-stylesheet.less')
theme = Theme.load(themePath)
expect($(".editor").css("padding-top")).toBe "4321px"
themePath = project.resolve('themes/theme-with-package-file')
theme = new Theme(themePath)
expect($(".editor").css("padding-top")).toBe("101px")
expect($(".editor").css("padding-right")).toBe("102px")
expect($(".editor").css("padding-bottom")).toBe("103px")
describe "when the theme contains a package.json file", ->
it "loads and applies stylesheets from package.json in the correct order", ->
expect($(".editor").css("padding-top")).not.toBe("101px")
expect($(".editor").css("padding-right")).not.toBe("102px")
expect($(".editor").css("padding-bottom")).not.toBe("103px")
describe "when the theme does not contain a package.json file and is a directory", ->
it "loads all stylesheet files in the directory", ->
expect($(".editor").css("padding-top")).not.toBe "10px"
expect($(".editor").css("padding-right")).not.toBe "20px"
expect($(".editor").css("padding-bottom")).not.toBe "30px"
themePath = project.resolve('themes/theme-with-package-file')
theme = Theme.load(themePath)
expect($(".editor").css("padding-top")).toBe("101px")
expect($(".editor").css("padding-right")).toBe("102px")
expect($(".editor").css("padding-bottom")).toBe("103px")
describe "when the theme does not contain a package.json file and is a directory", ->
it "loads all stylesheet files in the directory", ->
expect($(".editor").css("padding-top")).not.toBe "10px"
expect($(".editor").css("padding-right")).not.toBe "20px"
expect($(".editor").css("padding-bottom")).not.toBe "30px"
themePath = project.resolve('themes/theme-without-package-file')
theme = Theme.load(themePath)
expect($(".editor").css("padding-top")).toBe "10px"
expect($(".editor").css("padding-right")).toBe "20px"
expect($(".editor").css("padding-bottom")).toBe "30px"
themePath = project.resolve('themes/theme-without-package-file')
theme = new Theme(themePath)
expect($(".editor").css("padding-top")).toBe "10px"
expect($(".editor").css("padding-right")).toBe "20px"
expect($(".editor").css("padding-bottom")).toBe "30px"

View File

@ -11,7 +11,11 @@ describe "TokenizedBuffer", ->
TokenizedBuffer.prototype.chunkSize = 5
jasmine.unspy(TokenizedBuffer.prototype, 'tokenizeInBackground')
startTokenizing = (tokenizedBuffer) ->
tokenizedBuffer.setVisible(true)
fullyTokenize = (tokenizedBuffer) ->
tokenizedBuffer.setVisible(true)
advanceClock() while tokenizedBuffer.firstInvalidRow()?
changeHandler?.reset()
@ -27,7 +31,7 @@ describe "TokenizedBuffer", ->
beforeEach ->
buffer = project.bufferForPath('sample.js')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer.setVisible(true)
startTokenizing(tokenizedBuffer)
tokenizedBuffer.on "changed", changeHandler = jasmine.createSpy('changeHandler')
afterEach ->
@ -307,7 +311,7 @@ describe "TokenizedBuffer", ->
atom.activatePackage('coffee-script-tmbundle', sync: true)
buffer = project.bufferForPath('sample-with-tabs.coffee')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer.setVisible(true)
startTokenizing(tokenizedBuffer)
afterEach ->
tokenizedBuffer.destroy()
@ -340,7 +344,6 @@ describe "TokenizedBuffer", ->
//\uD835\uDF97xyz
"""
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer.setVisible(true)
fullyTokenize(tokenizedBuffer)
afterEach ->
@ -378,7 +381,6 @@ describe "TokenizedBuffer", ->
buffer = project.bufferForPath(null, "<div class='name'><%= User.find(2).full_name %></div>")
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer.setGrammar(syntax.selectGrammar('test.erb'))
tokenizedBuffer.setVisible(true)
fullyTokenize(tokenizedBuffer)
{tokens} = tokenizedBuffer.lineForScreenRow(0)
@ -397,8 +399,25 @@ describe "TokenizedBuffer", ->
it "returns the correct token (regression)", ->
buffer = project.bufferForPath('sample.js')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer.setVisible(true)
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenForPosition([1,0]).scopes).toEqual ["source.js"]
expect(tokenizedBuffer.tokenForPosition([1,1]).scopes).toEqual ["source.js"]
expect(tokenizedBuffer.tokenForPosition([1,2]).scopes).toEqual ["source.js", "storage.modifier.js"]
describe ".bufferRangeForScopeAtPosition(selector, position)", ->
beforeEach ->
buffer = project.bufferForPath('sample.js')
tokenizedBuffer = new TokenizedBuffer(buffer)
fullyTokenize(tokenizedBuffer)
describe "when the selector does not match the token at the position", ->
it "returns a falsy value", ->
expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.bogus', [0, 1])).toBeFalsy()
describe "when the selector matches a single token at the position", ->
it "returns the range covered by the token", ->
expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.storage.modifier.js', [0, 1])).toEqual [[0, 0], [0, 3]]
describe "when the selector matches a run of multiple tokens at the position", ->
it "returns the range covered by all contigous tokens (within a single line)", ->
expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.function', [1, 18])).toEqual [[1, 6], [1, 28]]

View File

@ -21,7 +21,15 @@ class AtomReporter extends View
@div id: 'HTMLReporter', class: 'jasmine_reporter', =>
@div outlet: 'specPopup', class: "spec-popup"
@div outlet: "suites"
@ul outlet: "symbolSummary", class: 'symbolSummary list-unstyled'
@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: "time", class: 'time'
@div outlet: "specCount", class: 'spec-count'
@ -118,9 +126,34 @@ class AtomReporter extends View
@time.text "#{time[0...-2]}.#{time[-2..]}s"
addSpecs: (specs) ->
coreSpecs = 0
bundledPackageSpecs = 0
userPackageSpecs = 0
for spec in specs
symbol = $$ -> @li class: "spec-summary pending spec-summary-#{spec.id}"
@symbolSummary.append symbol
switch spec.specType
when 'core'
coreSpecs++
@coreSummary.append symbol
when 'bundled'
bundledPackageSpecs++
@bundledSummary.append symbol
when 'user'
userPackageSpecs++
@userSummary.append symbol
if coreSpecs > 0
@coreHeader.text("Core Specs (#{coreSpecs}):")
else
@coreArea.hide()
if bundledPackageSpecs > 0
@bundledHeader.text("Bundled Package Specs (#{bundledPackageSpecs}):")
else
@bundledArea.hide()
if userPackageSpecs > 0
@userHeader.text("User Package Specs (#{userPackageSpecs}):")
else
@userArea.hide()
specStarted: (spec) ->
@runningSpecCount++

View File

@ -1,3 +0,0 @@
module.exports =
activate: -> @activateCalled = true
activateConfig: -> @activateConfigCalled = true

20
spec/fixtures/sample-with-comments.js vendored Normal file
View File

@ -0,0 +1,20 @@
var quicksort = function () {
/*
this is a multiline comment
it is, I promise
*/
var sort = function(items) {
// This is a collection of
// single line comments.
// Wowza
if (items.length <= 1) return items;
var pivot = items.shift(), current, left = [], right = [];
while(items.length > 0) {
current = items.shift();
current < pivot ? left.push(current) : right.push(current);
}
return sort(left).concat(pivot).concat(sort(right));
};
return sort(Array.apply(this, arguments));
};

View File

@ -37,7 +37,6 @@ beforeEach ->
window.project = new Project(fsUtils.resolveOnLoadPath('fixtures'))
window.resetTimeouts()
atom.windowMode = 'editor'
atom.packageStates = {}
spyOn(atom, 'saveWindowState')
syntax.clearGrammarOverrides()

View File

@ -1,3 +1,5 @@
fs = require 'fs'
require 'window'
measure 'spec suite require time', ->
@ -5,12 +7,27 @@ measure 'spec suite require time', ->
path = require 'path'
require 'spec-helper'
# Run core specs
for specPath in fsUtils.listTreeSync(fsUtils.resolveOnLoadPath("spec")) when /-spec\.coffee$/.test specPath
require specPath
requireSpecs = (directoryPath, specType) ->
for specPath in fsUtils.listTreeSync(path.join(directoryPath, 'spec')) when /-spec\.coffee$/.test specPath
require specPath
# Run extension specs
for packageDirPath in config.packageDirPaths
for packagePath in fsUtils.listSync(packageDirPath)
for specPath in fsUtils.listTreeSync(path.join(packagePath, "spec")) when /-spec\.coffee$/.test specPath
require specPath
setSpecType = (specType) ->
for spec in jasmine.getEnv().currentRunner().specs() when not spec.specType?
spec.specType = specType
# Run core specs
requireSpecs(window.resourcePath)
setSpecType('core')
# Run bundled package specs
if fsUtils.isDirectorySync(config.nodeModulesDirPath)
for packageName in fs.readdirSync(config.nodeModulesDirPath)
packagePath = path.join(config.nodeModulesDirPath, packageName)
requireSpecs(packagePath, 'bundled') if atom.isInternalPackage(packagePath)
setSpecType('bundled')
# Run user package specs
if fsUtils.isDirectorySync(config.userPackagesDirPath)
for packageName in fs.readdirSync(config.userPackagesDirPath)
requireSpecs(path.join(config.userPackagesDirPath, packageName))
setSpecType('user')

View File

@ -1,6 +1,7 @@
fsUtils = require 'fs-utils'
fs = require 'fs'
path = require 'path'
temp = require 'temp'
describe "fsUtils", ->
describe ".read(path)", ->
@ -82,6 +83,14 @@ describe "fsUtils", ->
expect(symlinkPaths).toEqual(paths)
it "ignores missing symlinks", ->
directory = temp.mkdirSync('symlink-in-here')
paths = []
onPath = (childPath) -> paths.push(childPath)
fs.symlinkSync(path.join(directory, 'source'), path.join(directory, 'destination'))
fsUtils.traverseTreeSync(directory, onPath)
expect(paths.length).toBe 0
describe ".md5ForPath(path)", ->
it "returns the MD5 hash of the file at the given path", ->
expect(fsUtils.md5ForPath(require.resolve('fixtures/sample.js'))).toBe 'dd38087d0d7e3e4802a6d3f9b9745f2b'

View File

@ -76,3 +76,42 @@ describe "underscore extensions", ->
it "space separates the undasherized/capitalized versions of the namespace and event name", ->
expect(_.humanizeEventName('space:final-frontier')).toBe 'Space: Final Frontier'
expect(_.humanizeEventName('star-trek:the-next-generation')).toBe 'Star Trek: The Next Generation'
describe "_.deepExtend(objects...)", ->
it "copies all key/values from each object into a new object", ->
first =
things:
string: "oh"
boolean: false
anotherArray: ['a', 'b', 'c']
object:
first: 1
second: 2
second =
things:
string: "cool"
array: [1,2,3]
anotherArray: ['aa', 'bb', 'cc']
object:
first: 1
result = _.deepExtend(first, second)
expect(result).toEqual
things:
string: "oh"
boolean: false
array: [1,2,3]
anotherArray: ['a', 'b', 'c']
object:
first: 1
second: 2
describe "_.isSubset(potentialSubset, potentialSuperset)", ->
it "returns whether the first argument is a subset of the second", ->
expect(_.isSubset([1, 2], [1, 2])).toBeTruthy()
expect(_.isSubset([1, 2], [1, 2, 3])).toBeTruthy()
expect(_.isSubset([], [1])).toBeTruthy()
expect(_.isSubset([], [])).toBeTruthy()
expect(_.isSubset([1, 2], [2, 3])).toBeFalsy()

View File

@ -1,30 +0,0 @@
fsUtils = require 'fs-utils'
path = require 'path'
Theme = require 'theme'
# Internal: Represents a theme that Atom can use.
module.exports =
class AtomTheme extends Theme
# Given a path, this loads it as a stylesheet.
#
# stylesheetPath - A {String} to a stylesheet
loadStylesheet: (stylesheetPath)->
@stylesheets[stylesheetPath] = window.loadStylesheet(stylesheetPath)
# Loads the stylesheets found in a `package.cson` file.
load: ->
if path.extname(@path) in ['.css', '.less']
@loadStylesheet(@path)
else
metadataPath = fsUtils.resolveExtension(path.join(@path, 'package'), ['cson', 'json'])
if fsUtils.isFileSync(metadataPath)
stylesheetNames = fsUtils.readObjectSync(metadataPath)?.stylesheets
if stylesheetNames
for name in stylesheetNames
filename = fsUtils.resolveExtension(path.join(@path, name), ['.css', '.less', ''])
@loadStylesheet(filename)
else
@loadStylesheet(stylesheetPath) for stylesheetPath in fsUtils.listSync(@path, ['.css', '.less'])
super

View File

@ -2,7 +2,6 @@ fsUtils = require 'fs-utils'
$ = require 'jquery'
_ = require 'underscore'
Package = require 'package'
Theme = require 'theme'
ipc = require 'ipc'
remote = require 'remote'
crypto = require 'crypto'
@ -10,12 +9,13 @@ path = require 'path'
dialog = remote.require 'dialog'
app = remote.require 'app'
telepath = require 'telepath'
ThemeManager = require 'theme-manager'
window.atom =
loadedThemes: []
loadedPackages: {}
activePackages: {}
packageStates: {}
themes: new ThemeManager()
getLoadSettings: ->
remote.getCurrentWindow().loadSettings
@ -55,15 +55,6 @@ window.atom =
getActivePackages: ->
_.values(@activePackages)
activatePackageConfigs: ->
@activatePackageConfig(pack.name) for pack in @getLoadedPackages()
activatePackageConfig: (name, options) ->
if pack = @loadPackage(name, options)
@activePackages[pack.name] = pack
pack.activateConfig()
pack
loadPackages: ->
@loadPackage(name) for name in @getAvailablePackageNames() when not @isPackageDisabled(name)
@ -136,34 +127,6 @@ window.atom =
packages.push(metadata)
packages
loadThemes: ->
themeNames = config.get("core.themes")
themeNames = [themeNames] unless _.isArray(themeNames)
@loadTheme(themeName) for themeName in themeNames
@loadUserStylesheet()
getAvailableThemePaths: ->
themePaths = []
for themeDirPath in config.themeDirPaths
themePaths.push(fsUtils.listSync(themeDirPath, ['', '.tmTheme', '.css', 'less'])...)
_.uniq(themePaths)
getAvailableThemeNames: ->
path.basename(themePath).split('.')[0] for themePath in @getAvailableThemePaths()
loadTheme: (name) ->
@loadedThemes.push Theme.load(name)
loadUserStylesheet: ->
userStylesheetPath = fsUtils.resolve(path.join(config.configDirPath, 'user'), ['css', 'less'])
if fsUtils.isFileSync(userStylesheetPath)
userStyleesheetContents = loadStylesheet(userStylesheetPath)
applyStylesheet(userStylesheetPath, userStyleesheetContents, 'userTheme')
getAtomThemeStylesheets: ->
themeNames = config.get("core.themes") ? ['atom-dark-ui', 'atom-dark-syntax']
themeNames = [themeNames] unless _.isArray(themeNames)
open: (url...) ->
ipc.sendChannel('open', [url...])
@ -173,9 +136,6 @@ window.atom =
newWindow: ->
ipc.sendChannel('new-window')
openConfig: ->
ipc.sendChannel('open-config')
openWindow: (windowSettings) ->
ipc.sendChannel('open-window', windowSettings)
@ -241,7 +201,7 @@ window.atom =
getWindowStatePath: ->
switch @windowMode
when 'config', 'spec'
when 'spec'
filename = @windowMode
when 'editor'
{initialPath} = @getLoadSettings()

View File

@ -1,36 +0,0 @@
PackageConfigView = require 'package-config-view'
ConfigPanel = require 'config-panel'
packageManager = require 'package-manager'
### Internal ###
module.exports =
class AvailablePackagesConfigPanel extends ConfigPanel
@content: ->
@div class: 'available-packages', =>
@div outlet: 'loadingArea', class: 'alert alert-info loading-area', =>
@span 'Loading available packages\u2026'
@div outlet: 'errorArea', class: 'alert alert-error', =>
@span 'Error fetching available packages.'
@button outlet: 'retry', class: 'btn btn-mini btn-retry', 'Retry'
@div outlet: 'packagesArea'
initialize: (@packageEventEmitter) ->
@retry.on 'click', => @refresh()
@refresh()
refresh: ->
@loadingArea.show()
@errorArea.hide()
packageManager.getAvailable (error, @packages=[]) =>
@loadingArea.hide()
if error?
@errorArea.show()
console.error(error.stack ? error)
else
@packagesArea.empty()
for pack in @packages
@packagesArea.append(new PackageConfigView(pack, @packageEventEmitter))
@packageEventEmitter.trigger('available-packages-loaded', @packages)
getPackageCount: -> @packages.length

View File

@ -1,58 +0,0 @@
$ = require 'jquery'
{View} = require 'space-pen'
###
# Internal #
###
module.exports =
class ConfigPanel extends View
initialize: ->
@bindFormFields()
@bindEditors()
bindFormFields: ->
for input in @find('input[id]').toArray()
do (input) =>
input = $(input)
name = input.attr('id')
type = input.attr('type')
@observeConfig name, (value) ->
if type is 'checkbox'
input.attr('checked', value)
else
input.val(value) if value
input.on 'change', =>
value = input.val()
if type == 'checkbox'
value = !!input.attr('checked')
else
value = @parseValue(type, value)
config.set(name, value)
bindEditors: ->
for editor in @find('.editor[id]').views()
do (editor) =>
name = editor.attr('id')
type = editor.attr('type')
@observeConfig name, (value) ->
return if value?.toString() == editor.getText()
value ?= ""
editor.setText(value.toString())
editor.getBuffer().on 'contents-modified', =>
config.set(name, @parseValue(type, editor.getText()))
parseValue: (type, value) ->
switch type
when 'int'
intValue = parseInt(value)
value = intValue unless isNaN(intValue)
when 'float'
floatValue = parseFloat(value)
value = floatValue unless isNaN(floatValue)
value = undefined if value == ''
value

View File

@ -1,69 +0,0 @@
{View, $$} = require 'space-pen'
$ = require 'jquery'
_ = require 'underscore'
GeneralConfigPanel = require 'general-config-panel'
EditorConfigPanel = require 'editor-config-panel'
ThemeConfigPanel = require 'theme-config-panel'
PackageConfigPanel = require 'package-config-panel'
###
# Internal #
###
module.exports =
class ConfigView extends View
registerDeserializer(this)
@deserialize: ({activePanelName}) ->
view = new ConfigView()
view.showPanel(activePanelName)
view
@content: ->
@div id: 'config-view', =>
@div id: 'config-menu', =>
@ul id: 'panels-menu', class: 'nav nav-pills nav-stacked', outlet: 'panelMenu'
@button "open .atom", id: 'open-dot-atom', class: 'btn btn-default btn-small'
@div id: 'panels', outlet: 'panels'
initialize: ->
@panelsByName = {}
document.title = "Atom Configuration"
@on 'click', '#panels-menu li a', (e) =>
@showPanel($(e.target).closest('li').attr('name'))
@on 'click', '#open-dot-atom', ->
atom.open(config.configDirPath)
@addPanel('General', new GeneralConfigPanel)
@addPanel('Editor', new EditorConfigPanel)
@addPanel('Themes', new ThemeConfigPanel)
@addPanel('Packages', new PackageConfigPanel)
addPanel: (name, panel) ->
panelItem = $$ -> @li name: name, => @a name
@panelMenu.append(panelItem)
panel.hide()
@panelsByName[name] = panel
@panels.append(panel)
@showPanel(name) if @getPanelCount() is 1 or @panelToShow is name
getPanelCount: ->
_.values(@panelsByName).length
showPanel: (name) ->
if @panelsByName[name]
@panels.children().hide()
@panelMenu.children('.active').removeClass('active')
@panelsByName[name].show()
for editorElement in @panelsByName[name].find(".editor")
$(editorElement).view().redraw()
@panelMenu.children("[name='#{name}']").addClass('active')
@activePanelName = name
@panelToShow = null
else
@panelToShow = name
serialize: ->
deserializer: @constructor.name
activePanelName: @activePanelName

View File

@ -8,11 +8,8 @@ async = require 'async'
pathWatcher = require 'pathwatcher'
configDirPath = fsUtils.absolute("~/.atom")
bundledPackagesDirPath = path.join(resourcePath, "src/packages")
nodeModulesDirPath = path.join(resourcePath, "node_modules")
bundledThemesDirPath = path.join(resourcePath, "themes")
vendoredPackagesDirPath = path.join(resourcePath, "vendor/packages")
vendoredThemesDirPath = path.join(resourcePath, "vendor/themes")
userThemesDirPath = path.join(configDirPath, "themes")
userPackagesDirPath = path.join(configDirPath, "packages")
userStoragePath = path.join(configDirPath, "storage")
@ -24,9 +21,10 @@ userStoragePath = path.join(configDirPath, "storage")
module.exports =
class Config
configDirPath: configDirPath
themeDirPaths: [userThemesDirPath, bundledThemesDirPath, vendoredThemesDirPath]
bundledPackageDirPaths: [vendoredPackagesDirPath, bundledPackagesDirPath, nodeModulesDirPath]
packageDirPaths: [userPackagesDirPath, vendoredPackagesDirPath, bundledPackagesDirPath]
themeDirPaths: [userThemesDirPath, bundledThemesDirPath]
bundledPackageDirPaths: [nodeModulesDirPath]
nodeModulesDirPath: nodeModulesDirPath
packageDirPaths: [userPackagesDirPath]
userPackagesDirPath: userPackagesDirPath
userStoragePath: userStoragePath
lessSearchPaths: [path.join(resourcePath, 'static'), path.join(resourcePath, 'vendor')]
@ -100,6 +98,9 @@ class Config
### Public ###
getSettings: ->
_.deepExtend(@settings, @defaultSettings)
# Retrieves the setting for the given key.
#
# keyPath - The {String} name of the key to retrieve
@ -116,7 +117,7 @@ class Config
#
# Returns the value from Atom's default settings, the user's configuration file,
# or `NaN` if the key doesn't exist in either.
getInt: (keyPath, defaultValueWhenFalsy) ->
getInt: (keyPath) ->
parseInt(@get(keyPath))
# Retrieves the setting for the given key as a positive integer.

View File

@ -128,6 +128,11 @@ class Cursor
range = [[row, Math.min(0, column - 1)], [row, Math.max(0, column + 1)]]
/^\s+$/.test @editSession.getTextInBufferRange(range)
isInsideWord: ->
{row, column} = @getBufferPosition()
range = [[row, column], [row, Infinity]]
@editSession.getTextInBufferRange(range).search(@wordRegExp()) == 0
# Removes the setting for auto-scroll.
clearAutoscroll: ->
@needsAutoscroll = null
@ -167,29 +172,53 @@ class Cursor
@editSession.lineForBufferRow(@getBufferRow())
# Moves the cursor up one screen row.
moveUp: (rowCount = 1) ->
{ row, column } = @getScreenPosition()
moveUp: (rowCount = 1, {moveToEndOfSelection}={}) ->
range = @marker.getScreenRange()
if moveToEndOfSelection and not range.isEmpty()
{ row, column } = range.start
else
{ row, column } = @getScreenPosition()
column = @goalColumn if @goalColumn?
@setScreenPosition({row: row - rowCount, column: column})
@goalColumn = column
# Moves the cursor down one screen row.
moveDown: (rowCount = 1) ->
{ row, column } = @getScreenPosition()
moveDown: (rowCount = 1, {moveToEndOfSelection}={}) ->
range = @marker.getScreenRange()
if moveToEndOfSelection and not range.isEmpty()
{ row, column } = range.end
else
{ row, column } = @getScreenPosition()
column = @goalColumn if @goalColumn?
@setScreenPosition({row: row + rowCount, column: column})
@goalColumn = column
# Moves the cursor left one screen column.
moveLeft: ->
{ row, column } = @getScreenPosition()
[row, column] = if column > 0 then [row, column - 1] else [row - 1, Infinity]
@setScreenPosition({row, column})
#
# options -
# moveToEndOfSelection: true will move to the left of the selection if a selection
moveLeft: ({moveToEndOfSelection}={}) ->
range = @marker.getScreenRange()
if moveToEndOfSelection and not range.isEmpty()
@setScreenPosition(range.start)
else
{row, column} = @getScreenPosition()
[row, column] = if column > 0 then [row, column - 1] else [row - 1, Infinity]
@setScreenPosition({row, column})
# Moves the cursor right one screen column.
moveRight: ->
{ row, column } = @getScreenPosition()
@setScreenPosition([row, column + 1], skipAtomicTokens: true, wrapBeyondNewlines: true, wrapAtSoftNewlines: true)
#
# options -
# moveToEndOfSelection: true will move to the right of the selection if a selection
moveRight: ({moveToEndOfSelection}={}) ->
range = @marker.getScreenRange()
if moveToEndOfSelection and not range.isEmpty()
@setScreenPosition(range.end)
else
{ row, column } = @getScreenPosition()
@setScreenPosition([row, column + 1], skipAtomicTokens: true, wrapBeyondNewlines: true, wrapAtSoftNewlines: true)
# Moves the cursor to the top of the buffer.
moveToTop: ->
@ -199,20 +228,20 @@ class Cursor
moveToBottom: ->
@setBufferPosition(@editSession.getEofBufferPosition())
# Moves the cursor to the beginning of the buffer line.
# Moves the cursor to the beginning of the screen line.
moveToBeginningOfLine: ->
@setBufferPosition([@getBufferRow(), 0])
@setScreenPosition([@getScreenRow(), 0])
# Moves the cursor to the beginning of the first character in the line.
moveToFirstCharacterOfLine: ->
position = @getBufferPosition()
scanRange = @getCurrentLineBufferRange()
newPosition = null
@editSession.scanInBufferRange /^\s*/, scanRange, ({range}) =>
newPosition = range.end
return unless newPosition
newPosition = [position.row, 0] if newPosition.isEqual(position)
@setBufferPosition(newPosition)
{row, column} = @getScreenPosition()
screenline = @editSession.lineForScreenRow(row)
goalColumn = screenline.text.search(/\S/)
return if goalColumn == -1
goalColumn = 0 if goalColumn == column
@setScreenPosition([row, goalColumn])
# Moves the cursor to the beginning of the buffer line, skipping all whitespace.
skipLeadingWhitespace: ->
@ -226,7 +255,7 @@ class Cursor
# Moves the cursor to the end of the buffer line.
moveToEndOfLine: ->
@setBufferPosition([@getBufferRow(), Infinity])
@setScreenPosition([@getScreenRow(), Infinity])
# Moves the cursor to the beginning of the word.
moveToBeginningOfWord: ->
@ -242,6 +271,16 @@ class Cursor
if position = @getBeginningOfNextWordBufferPosition()
@setBufferPosition(position)
# Moves the cursor to the previous word boundary.
moveToPreviousWordBoundary: ->
if position = @getPreviousWordBoundaryBufferPosition()
@setBufferPosition(position)
# Moves the cursor to the next word boundary.
moveToNextWordBoundary: ->
if position = @getMoveNextWordBoundaryBufferPosition()
@setBufferPosition(position)
# Retrieves the buffer position of where the current word starts.
#
# options - A hash with one option:
@ -263,6 +302,49 @@ class Cursor
beginningOfWordPosition or currentBufferPosition
# Retrieves buffer position of previous word boiundry. It might be on the
# current word, or the previous word.
getPreviousWordBoundaryBufferPosition: (options = {}) ->
currentBufferPosition = @getBufferPosition()
previousNonBlankRow = @editSession.buffer.previousNonBlankRow(currentBufferPosition.row)
scanRange = [[previousNonBlankRow, 0], currentBufferPosition]
beginningOfWordPosition = null
@editSession.backwardsScanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) =>
if range.start.row < currentBufferPosition.row and currentBufferPosition.column > 0
# force it to stop at the beginning of each line
beginningOfWordPosition = new Point(currentBufferPosition.row, 0)
else if range.end.isLessThan(currentBufferPosition)
beginningOfWordPosition = range.end
else
beginningOfWordPosition = range.start
if not beginningOfWordPosition?.isEqual(currentBufferPosition)
stop()
beginningOfWordPosition or currentBufferPosition
# Retrieves buffer position of previous word boiundry. It might be on the
# current word, or the previous word.
getMoveNextWordBoundaryBufferPosition: (options = {}) ->
currentBufferPosition = @getBufferPosition()
scanRange = [currentBufferPosition, @editSession.getEofBufferPosition()]
endOfWordPosition = null
@editSession.scanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) =>
if range.start.row > currentBufferPosition.row
# force it to stop at the beginning of each line
endOfWordPosition = new Point(range.start.row, 0)
else if range.start.isGreaterThan(currentBufferPosition)
endOfWordPosition = range.start
else
endOfWordPosition = range.end
if not endOfWordPosition?.isEqual(currentBufferPosition)
stop()
endOfWordPosition or currentBufferPosition
# Retrieves the buffer position of where the current word ends.
#
# options - A hash with one option:
@ -291,7 +373,7 @@ class Cursor
# Returns a {Range}.
getBeginningOfNextWordBufferPosition: (options = {}) ->
currentBufferPosition = @getBufferPosition()
start = if @isSurroundedByWhitespace() then currentBufferPosition else @getEndOfCurrentWordBufferPosition()
start = if @isInsideWord() then @getEndOfCurrentWordBufferPosition() else currentBufferPosition
scanRange = [start, @editSession.getEofBufferPosition()]
beginningOfNextWordPosition = null
@ -325,21 +407,7 @@ class Cursor
#
# Returns a {Range}.
getCurrentParagraphBufferRange: ->
row = @getBufferRow()
return unless /\w/.test(@editSession.lineForBufferRow(row))
startRow = row
while startRow > 0
break unless /\w/.test(@editSession.lineForBufferRow(startRow - 1))
startRow--
endRow = row
lastRow = @editSession.getLastBufferRow()
while endRow < lastRow
break unless /\w/.test(@editSession.lineForBufferRow(endRow + 1))
endRow++
new Range([startRow, 0], [endRow, @editSession.lineLengthForBufferRow(endRow)])
@editSession.languageMode.rowRangeForParagraphAtBufferRow(@getBufferRow())
# Retrieves the characters that constitute a word preceeding the current cursor position.
#

View File

@ -319,6 +319,9 @@ class DisplayBuffer
scopesForBufferPosition: (bufferPosition) ->
@tokenizedBuffer.scopesForPosition(bufferPosition)
bufferRangeForScopeAtPosition: (selector, position) ->
@tokenizedBuffer.bufferRangeForScopeAtPosition(selector, position)
# Retrieves the grammar's token for a buffer position.
#
# bufferPosition - A {Point} in the {Buffer}.

View File

@ -11,7 +11,7 @@ Cursor = require 'cursor'
Selection = require 'selection'
EventEmitter = require 'event-emitter'
Subscriber = require 'subscriber'
TextMateScopeSelector = require 'text-mate-scope-selector'
TextMateScopeSelector = require('first-mate').ScopeSelector
# An `EditSession` manages the states between {Editor}s, {Buffer}s, and the project as a whole.
module.exports =
@ -52,6 +52,7 @@ class EditSession
@addSelection(marker)
@setScrollTop(@state.get('scrollTop'))
@setScrollLeft(@state.get('scrollLeft'))
registerEditSession = true
else
{buffer, displayBuffer, tabLength, softTabs, softWrap, suppressCursorCreation} = optionsOrState
@id = guid.create().toString()
@ -81,7 +82,7 @@ class EditSession
when 'scrollLeft'
@trigger 'scroll-left-changed', newValue
project.editSessions.push(this)
project.addEditSession(this) if registerEditSession
setBuffer: (@buffer) ->
@buffer.retain()
@ -405,9 +406,12 @@ class EditSession
# {Delegates to: DisplayBuffer.bufferRowsForScreenRows}
bufferRowsForScreenRows: (startRow, endRow) -> @displayBuffer.bufferRowsForScreenRows(startRow, endRow)
# {Delegates to: DisplayBuffer.bufferRowsForScreenRows}
# {Delegates to: DisplayBuffer.scopesForBufferPosition}
scopesForBufferPosition: (bufferPosition) -> @displayBuffer.scopesForBufferPosition(bufferPosition)
bufferRangeForScopeAtCursor: (selector) ->
@displayBuffer.bufferRangeForScopeAtPosition(selector, @getCursorBufferPosition())
# {Delegates to: DisplayBuffer.tokenForBufferPosition}
tokenForBufferPosition: (bufferPosition) -> @displayBuffer.tokenForBufferPosition(bufferPosition)
@ -553,6 +557,9 @@ class EditSession
unfoldAll: ->
@languageMode.unfoldAll()
foldAllAtIndentLevel: (indentLevel) ->
@languageMode.foldAllAtIndentLevel(indentLevel)
# Folds the current row.
foldCurrentRow: ->
bufferRow = @bufferPositionForScreenPosition(@getCursorScreenPosition()).row
@ -1084,6 +1091,8 @@ class EditSession
getTextInBufferRange: (range) ->
@buffer.getTextInRange(range)
setTextInBufferRange: (range, text) -> @getBuffer().change(range, text)
# Retrieves the range for the current paragraph.
#
# A paragraph is defined as a block of text surrounded by empty lines.
@ -1102,19 +1111,19 @@ class EditSession
# Moves every cursor up one row.
moveCursorUp: (lineCount) ->
@moveCursors (cursor) -> cursor.moveUp(lineCount)
@moveCursors (cursor) -> cursor.moveUp(lineCount, moveToEndOfSelection: true)
# Moves every cursor down one row.
moveCursorDown: (lineCount) ->
@moveCursors (cursor) -> cursor.moveDown(lineCount)
@moveCursors (cursor) -> cursor.moveDown(lineCount, moveToEndOfSelection: true)
# Moves every cursor left one column.
moveCursorLeft: ->
@moveCursors (cursor) -> cursor.moveLeft()
@moveCursors (cursor) -> cursor.moveLeft(moveToEndOfSelection: true)
# Moves every cursor right one column.
moveCursorRight: ->
@moveCursors (cursor) -> cursor.moveRight()
@moveCursors (cursor) -> cursor.moveRight(moveToEndOfSelection: true)
# Moves every cursor to the top of the buffer.
moveCursorToTop: ->
@ -1148,6 +1157,12 @@ class EditSession
moveCursorToBeginningOfNextWord: ->
@moveCursors (cursor) -> cursor.moveToBeginningOfNextWord()
moveCursorToPreviousWordBoundary: ->
@moveCursors (cursor) -> cursor.moveToPreviousWordBoundary()
moveCursorToNextWordBoundary: ->
@moveCursors (cursor) -> cursor.moveToNextWordBoundary()
# Internal:
moveCursors: (fn) ->
fn(cursor) for cursor in @getCursors()
@ -1201,6 +1216,12 @@ class EditSession
selectToEndOfLine: ->
@expandSelectionsForward (selection) => selection.selectToEndOfLine()
selectToPreviousWordBoundary: ->
@expandSelectionsBackward (selection) => selection.selectToPreviousWordBoundary()
selectToNextWordBoundary: ->
@expandSelectionsForward (selection) => selection.selectToNextWordBoundary()
# Selects the current line.
selectLine: ->
@expandSelectionsForward (selection) => selection.selectLine()

View File

@ -1,70 +0,0 @@
ConfigPanel = require 'config-panel'
Editor = require 'editor'
###
# Internal #
###
module.exports =
class EditorConfigPanel extends ConfigPanel
@content: ->
@form class: 'form-horizontal', =>
@fieldset =>
@legend "Editor Settings"
@div class: 'control-group', =>
@label class: 'control-label', "Font Size:"
@div class: 'controls', =>
@subview "fontSizeEditor", new Editor(mini: true, attributes: {id: 'editor.fontSize', type: 'int', style: 'width: 4em'})
@div class: 'control-group', =>
@label class: 'control-label', "Font Family:"
@div class: 'controls', =>
@subview "fontFamilyEditor", new Editor(mini: true, attributes: {id: 'editor.fontFamily', type: 'string'})
@div class: 'control-group', =>
@div class: 'controls', =>
@div class: 'checkbox', =>
@label for: 'editor.autoIndent', =>
@input id: 'editor.autoIndent', type: 'checkbox'
@text 'Auto-Indent'
@div class: 'controls', =>
@div class: 'checkbox', =>
@label for: 'editor.autoIndentOnPaste', =>
@input id: 'editor.autoIndentOnPaste', type: 'checkbox'
@text 'Auto-Indent on Paste'
@div class: 'controls', =>
@div class: 'checkbox', =>
@label for: 'editor.normalizeIndentOnPaste', =>
@input id: 'editor.normalizeIndentOnPaste', type: 'checkbox'
@text 'Normalize Indent on Paste'
@div class: 'controls', =>
@div class: 'checkbox', =>
@label for: 'editor.showLineNumbers', =>
@input id: 'editor.showLineNumbers', type: 'checkbox'
@text 'Show Line Numbers'
@div class: 'controls', =>
@div class: 'checkbox', =>
@label for: 'editor.showInvisibles', =>
@input id: 'editor.showInvisibles', type: 'checkbox'
@text 'Show Invisible Characters'
@div class: 'controls', =>
@div class: 'checkbox', =>
@label for: 'editor.showIndentGuide', =>
@input id: 'editor.showIndentGuide', type: 'checkbox'
@text 'Show Indent Guide'
@div class: 'control-group', =>
@label class: 'control-label', for: 'editor.preferredLineLength', "Preferred Line Length:"
@div class: 'controls', =>
@subview "preferredLineLengthEditor", new Editor(mini: true, attributes: {id: 'editor.preferredLineLength', type: 'int', style: 'width: 4em'})
@div class: 'control-group', =>
@label class: 'control-label', for: 'editor.nonWordCharacters', "Non-Word Characters:"
@div class: 'controls', =>
@subview "nonWordCharactersEditor", new Editor(mini: true, attributes: {id: 'editor.nonWordCharacters', type: 'string'})

View File

@ -140,11 +140,15 @@ class Editor extends View
'editor:move-to-beginning-of-word': @moveCursorToBeginningOfWord
'editor:move-to-end-of-word': @moveCursorToEndOfWord
'editor:move-to-beginning-of-next-word': @moveCursorToBeginningOfNextWord
'editor:move-to-previous-word-boundary': @moveCursorToPreviousWordBoundary
'editor:move-to-next-word-boundary': @moveCursorToNextWordBoundary
'editor:select-to-end-of-line': @selectToEndOfLine
'editor:select-to-beginning-of-line': @selectToBeginningOfLine
'editor:select-to-end-of-word': @selectToEndOfWord
'editor:select-to-beginning-of-word': @selectToBeginningOfWord
'editor:select-to-beginning-of-next-word': @selectToBeginningOfNextWord
'editor:select-to-next-word-boundary': @selectToNextWordBoundary
'editor:select-to-previous-word-boundary': @selectToPreviousWordBoundary
'editor:select-to-first-character-of-line': @selectToFirstCharacterOfLine
'editor:add-selection-below': @addSelectionBelow
'editor:add-selection-above': @addSelectionAbove
@ -174,6 +178,15 @@ class Editor extends View
'editor:fold-current-row': @foldCurrentRow
'editor:unfold-current-row': @unfoldCurrentRow
'editor:fold-selection': @foldSelection
'editor:fold-at-indent-level-1': => @foldAllAtIndentLevel(0)
'editor:fold-at-indent-level-2': => @foldAllAtIndentLevel(1)
'editor:fold-at-indent-level-3': => @foldAllAtIndentLevel(2)
'editor:fold-at-indent-level-4': => @foldAllAtIndentLevel(3)
'editor:fold-at-indent-level-5': => @foldAllAtIndentLevel(4)
'editor:fold-at-indent-level-6': => @foldAllAtIndentLevel(5)
'editor:fold-at-indent-level-7': => @foldAllAtIndentLevel(6)
'editor:fold-at-indent-level-8': => @foldAllAtIndentLevel(7)
'editor:fold-at-indent-level-9': => @foldAllAtIndentLevel(8)
'editor:toggle-line-comments': @toggleLineCommentsInSelection
'editor:log-cursor-scope': @logCursorScope
'editor:checkout-head-revision': @checkoutHead
@ -237,6 +250,12 @@ class Editor extends View
# {Delegates to: EditSession.moveCursorToFirstCharacterOfLine}
moveCursorToFirstCharacterOfLine: -> @activeEditSession.moveCursorToFirstCharacterOfLine()
# {Delegates to: EditSession.moveCursorToPreviousWordBoundary}
moveCursorToPreviousWordBoundary: -> @activeEditSession.moveCursorToPreviousWordBoundary()
# {Delegates to: EditSession.moveCursorToNextWordBoundary}
moveCursorToNextWordBoundary: -> @activeEditSession.moveCursorToNextWordBoundary()
# {Delegates to: EditSession.moveCursorToEndOfLine}
moveCursorToEndOfLine: -> @activeEditSession.moveCursorToEndOfLine()
@ -333,6 +352,12 @@ class Editor extends View
# {Delegates to: EditSession.selectToEndOfLine}
selectToEndOfLine: -> @activeEditSession.selectToEndOfLine()
# {Delegates to: EditSession.selectToPreviousWordBoundary}
selectToPreviousWordBoundary: -> @activeEditSession.selectToPreviousWordBoundary()
# {Delegates to: EditSession.selectToNextWordBoundary}
selectToNextWordBoundary: -> @activeEditSession.selectToNextWordBoundary()
# {Delegates to: EditSession.addSelectionBelow}
addSelectionBelow: -> @activeEditSession.addSelectionBelow()
@ -459,6 +484,8 @@ class Editor extends View
# {Delegates to: EditSession.isFoldedAtCursorRow}
isFoldedAtCursorRow: -> @activeEditSession.isFoldedAtCursorRow()
foldAllAtIndentLevel: (indentLevel) -> @activeEditSession.foldAllAtIndentLevel(indentLevel)
# {Delegates to: EditSession.lineForScreenRow}
lineForScreenRow: (screenRow) -> @activeEditSession.lineForScreenRow(screenRow)
@ -624,7 +651,10 @@ class Editor extends View
false if @isFocused
@renderedLines.on 'mousedown', '.fold.line', (e) =>
@activeEditSession.destroyFoldWithId($(e.currentTarget).attr('fold-id'))
id = $(e.currentTarget).attr('fold-id')
marker = @activeEditSession.displayBuffer.getMarker(id)
@activeEditSession.setCursorBufferPosition(marker.getBufferRange().start)
@activeEditSession.destroyFoldWithId(id)
false
@renderedLines.on 'mousedown', (e) =>
@ -1356,7 +1386,6 @@ class Editor extends View
new Array(div.children...)
htmlForScreenRows: (startRow, endRow) ->
lines = @activeEditSession.linesForScreenRows(startRow, endRow)
htmlLines = []
screenRow = startRow
for line in @activeEditSession.linesForScreenRows(startRow, endRow)
@ -1471,7 +1500,7 @@ class Editor extends View
range = document.createRange()
range.setEnd(textNode, offset)
range.collapse()
leftPixels = range.getClientRects()[0].left - @scrollView.offset().left + @scrollLeft()
leftPixels = range.getClientRects()[0].left - Math.floor(@scrollView.offset().left) + Math.floor(@scrollLeft())
range.detach()
leftPixels
@ -1532,6 +1561,10 @@ class Editor extends View
reloadGrammar: ->
@activeEditSession.reloadGrammar()
# {Delegates to: EditSession.scopesForBufferPosition}
scopesForBufferPosition: (bufferPosition) ->
@activeEditSession.scopesForBufferPosition(bufferPosition)
# Copies the current file path to the native clipboard.
copyPathToPasteboard: ->
path = @getPath()

View File

@ -1,26 +0,0 @@
ConfigPanel = require 'config-panel'
{$$} = require 'space-pen'
$ = require 'jquery'
_ = require 'underscore'
###
# Internal #
###
module.exports =
class GeneralConfigPanel extends ConfigPanel
@content: ->
@form id: 'general-config-panel', class: 'form-horizontal', =>
@fieldset =>
@legend "General Settings"
@div class: 'control-group', =>
@div class: 'checkbox', =>
@label for: 'core.hideGitIgnoredFiles', =>
@input id: 'core.hideGitIgnoredFiles', type: 'checkbox'
@text 'Hide Git-Ignored Files'
@div class: 'checkbox', =>
@label for: 'core.autosave', =>
@input id: 'core.autosave', type: 'checkbox'
@text 'Auto-Save on Focus Change'

View File

@ -1,44 +0,0 @@
_ = require 'underscore'
ConfigPanel = require 'config-panel'
PackageConfigView = require 'package-config-view'
packageManager = require 'package-manager'
### Internal ###
module.exports =
class InstalledPackagesConfigPanel extends ConfigPanel
@content: ->
@div class: 'installed-packages', =>
@div outlet: 'loadingArea', class: 'alert alert-info loading-area', =>
@span 'Loading installed packages\u2026'
@div outlet: 'packagesArea'
initialize: (@packageEventEmitter) ->
@packages = _.sortBy(atom.getAvailablePackageMetadata(), 'name')
packageManager.renderMarkdownInMetadata @packages, =>
@loadingArea.hide()
for pack in @packages
@packagesArea.append(new PackageConfigView(pack, @packageEventEmitter))
@packageEventEmitter.trigger 'installed-packages-loaded', [@packages]
@packageEventEmitter.on 'package-installed', (error, pack) =>
@addPackage(pack) unless error?
@packageEventEmitter.on 'package-uninstalled', (error, pack) =>
@removePackage(pack) unless error?
removePackage: ({name}) ->
@packages = _.reject @packages, (pack) -> pack.name is name
@packagesArea.children("[name=#{name}]").remove()
addPackage: (pack) ->
@packages.push(pack)
@packages = _.sortBy(@packages, 'name')
index = @packages.indexOf(pack)
view = new PackageConfigView(pack, @packageEventEmitter)
if index is 0
@packagesArea.prepend(view)
else if index is @packages.length - 1
@packagesArea.append(view)
else
@packagesArea.children(":eq(#{index})").before(view)
getPackageCount: -> @packages.length

View File

@ -31,7 +31,6 @@ class Keymap
bindDefaultKeys: ->
$(document).command 'new-window', => atom.newWindow()
$(document).command 'open-user-configuration', => atom.openConfig()
$(document).command 'open', => atom.open()
$(document).command 'open-dev', => atom.openDev()
$(document).command 'toggle-dev-tools', => atom.toggleDevTools()

View File

@ -55,8 +55,8 @@
'alt-meta-w': 'pane:close-other-items'
'meta-P': 'pane:close'
'meta-n': 'new-window'
'meta-N': 'new-editor'
'meta-n': 'new-editor'
'meta-N': 'new-window'
'meta-,': 'open-user-configuration'
'meta-o': 'open'
'meta-O': 'open-dev'

View File

@ -5,10 +5,21 @@
'tab': 'editor:indent'
'meta-=': 'editor:auto-indent'
'meta-d': 'editor:delete-line'
'ctrl-[': 'editor:fold-current-row'
'ctrl-]': 'editor:unfold-current-row'
'ctrl-{': 'editor:fold-all'
'ctrl-}': 'editor:unfold-all'
'ctrl-meta-1': 'editor:fold-at-indent-level-1'
'ctrl-meta-2': 'editor:fold-at-indent-level-2'
'ctrl-meta-3': 'editor:fold-at-indent-level-3'
'ctrl-meta-4': 'editor:fold-at-indent-level-4'
'ctrl-meta-5': 'editor:fold-at-indent-level-5'
'ctrl-meta-6': 'editor:fold-at-indent-level-6'
'ctrl-meta-7': 'editor:fold-at-indent-level-7'
'ctrl-meta-8': 'editor:fold-at-indent-level-8'
'ctrl-meta-9': 'editor:fold-at-indent-level-9'
'alt-shift-down': 'editor:add-selection-below'
'alt-shift-up': 'editor:add-selection-above'
'alt-meta-ctrl-f': 'editor:fold-selection'

View File

@ -91,7 +91,6 @@ class LanguageMode
for currentRow in [0..@buffer.getLastRow()]
[startRow, endRow] = @rowRangeForFoldAtBufferRow(currentRow) ? []
continue unless startRow?
@editSession.createFold(startRow, endRow)
# Unfolds all the foldable lines in the buffer.
@ -99,6 +98,18 @@ class LanguageMode
for row in [@buffer.getLastRow()..0]
fold.destroy() for fold in @editSession.displayBuffer.foldsStartingAtBufferRow(row)
# Fold all comment and code blocks at a given indentLevel
#
# indentLevel - A {Number} indicating indentLevel; 0 based.
foldAllAtIndentLevel: (indentLevel) ->
for currentRow in [0..@buffer.getLastRow()]
[startRow, endRow] = @rowRangeForFoldAtBufferRow(currentRow) ? []
continue unless startRow?
# assumption: startRow will always be the min indent level for the entire range
if @editSession.indentationForBufferRow(startRow) == indentLevel
@editSession.createFold(startRow, endRow)
# Given a buffer row, creates a fold at it.
#
# bufferRow - A {Number} indicating the buffer row
@ -106,9 +117,7 @@ class LanguageMode
# Returns the new {Fold}.
foldBufferRow: (bufferRow) ->
for currentRow in [bufferRow..0]
rowRange = @rowRangeForCommentAtBufferRow(currentRow)
rowRange ?= @rowRangeForFoldAtBufferRow(currentRow)
[startRow, endRow] = rowRange ? []
[startRow, endRow] = @rowRangeForFoldAtBufferRow(currentRow) ? []
continue unless startRow? and startRow <= bufferRow <= endRow
fold = @editSession.displayBuffer.largestFoldStartingAtBufferRow(startRow)
return @editSession.createFold(startRow, endRow) unless fold
@ -119,13 +128,33 @@ class LanguageMode
unfoldBufferRow: (bufferRow) ->
@editSession.displayBuffer.largestFoldContainingBufferRow(bufferRow)?.destroy()
doesBufferRowStartFold: (bufferRow) ->
return false if @editSession.isBufferRowBlank(bufferRow)
nextNonEmptyRow = @editSession.nextNonBlankBufferRow(bufferRow)
return false unless nextNonEmptyRow?
@editSession.indentationForBufferRow(nextNonEmptyRow) > @editSession.indentationForBufferRow(bufferRow)
# Find the row range for a fold at a given bufferRow. Will handle comments
# and code.
#
# bufferRow - A {Number} indicating the buffer row
#
# Returns an {Array} of the [startRow, endRow]. Returns null if no range.
rowRangeForFoldAtBufferRow: (bufferRow) ->
rowRange = @rowRangeForCommentAtBufferRow(bufferRow)
rowRange ?= @rowRangeForCodeFoldAtBufferRow(bufferRow)
rowRange
rowRangeForCommentAtBufferRow: (bufferRow) ->
return unless @editSession.displayBuffer.tokenizedBuffer.lineForScreenRow(bufferRow).isComment()
startRow = bufferRow
for currentRow in [bufferRow-1..0]
break if @buffer.isRowBlank(currentRow)
break unless @editSession.displayBuffer.tokenizedBuffer.lineForScreenRow(currentRow).isComment()
startRow = currentRow
endRow = bufferRow
for currentRow in [bufferRow+1..@buffer.getLastRow()]
break if @buffer.isRowBlank(currentRow)
break unless @editSession.displayBuffer.tokenizedBuffer.lineForScreenRow(currentRow).isComment()
endRow = currentRow
return [startRow, endRow] if startRow isnt endRow
rowRangeForCodeFoldAtBufferRow: (bufferRow) ->
return null unless @doesBufferRowStartFold(bufferRow)
startIndentLevel = @editSession.indentationForBufferRow(bufferRow)
@ -142,20 +171,43 @@ class LanguageMode
[bufferRow, foldEndRow]
rowRangeForCommentAtBufferRow: (row) ->
return unless @editSession.displayBuffer.tokenizedBuffer.lineForScreenRow(row).isComment()
doesBufferRowStartFold: (bufferRow) ->
return false if @editSession.isBufferRowBlank(bufferRow)
nextNonEmptyRow = @editSession.nextNonBlankBufferRow(bufferRow)
return false unless nextNonEmptyRow?
@editSession.indentationForBufferRow(nextNonEmptyRow) > @editSession.indentationForBufferRow(bufferRow)
startRow = row
for currentRow in [row-1..0]
break if @buffer.isRowBlank(currentRow)
break unless @editSession.displayBuffer.tokenizedBuffer.lineForScreenRow(currentRow).isComment()
startRow = currentRow
endRow = row
for currentRow in [row+1..@buffer.getLastRow()]
break if @buffer.isRowBlank(currentRow)
break unless @editSession.displayBuffer.tokenizedBuffer.lineForScreenRow(currentRow).isComment()
endRow = currentRow
return [startRow, endRow] if startRow isnt endRow
# Find a row range for a 'paragraph' around specified bufferRow.
# Right now, a paragraph is a block of text bounded by and empty line or a
# block of text that is not the same type (comments next to source code).
rowRangeForParagraphAtBufferRow: (bufferRow) ->
return unless /\w/.test(@editSession.lineForBufferRow(bufferRow))
isRowComment = (row) =>
@editSession.displayBuffer.tokenizedBuffer.lineForScreenRow(row).isComment()
if isRowComment(bufferRow)
isOriginalRowComment = true
range = @rowRangeForCommentAtBufferRow(bufferRow)
[firstRow, lastRow] = range or [bufferRow, bufferRow]
else
isOriginalRowComment = false
[firstRow, lastRow] = [0, @editSession.getLastBufferRow()-1]
startRow = bufferRow
while startRow > firstRow
break if isRowComment(startRow - 1) != isOriginalRowComment
break unless /\w/.test(@editSession.lineForBufferRow(startRow - 1))
startRow--
endRow = bufferRow
lastRow = @editSession.getLastBufferRow()
while endRow < lastRow
break if isRowComment(endRow + 1) != isOriginalRowComment
break unless /\w/.test(@editSession.lineForBufferRow(endRow + 1))
endRow++
new Range([startRow, 0], [endRow, @editSession.lineLengthForBufferRow(endRow)])
# Given a buffer row, this returns a suggested indentation level.
#
@ -170,7 +222,7 @@ class LanguageMode
return currentIndentLevel unless increaseIndentRegex = @increaseIndentRegexForScopes(scopes)
currentLine = @buffer.lineForRow(bufferRow)
precedingRow = @buffer.previousNonBlankRow(bufferRow)
precedingRow = if bufferRow > 0 then bufferRow - 1 else null
return currentIndentLevel unless precedingRow?
precedingLine = @buffer.lineForRow(precedingRow)
@ -180,7 +232,7 @@ class LanguageMode
return desiredIndentLevel unless decreaseIndentRegex = @decreaseIndentRegexForScopes(scopes)
desiredIndentLevel -= 1 if decreaseIndentRegex.test(currentLine)
desiredIndentLevel
Math.max(desiredIndentLevel, 0)
# Calculate a minimum indent level for a range of lines excluding empty lines.
#

View File

@ -1,48 +0,0 @@
ConfigPanel = require 'config-panel'
InstalledPackagesConfigPanel = require 'installed-packages-config-panel'
AvailablePackagesConfigPanel = require 'available-packages-config-panel'
_ = require 'underscore'
EventEmitter = require 'event-emitter'
### Internal ###
class PackageEventEmitter
_.extend PackageEventEmitter.prototype, EventEmitter
module.exports =
class PackageConfigPanel extends ConfigPanel
@content: ->
@div class: 'package-panel', =>
@legend 'Packages'
@ul class: 'nav nav-tabs', =>
@li class: 'active', outlet: 'installedLink', =>
@a 'Installed', =>
@span class: 'badge pull-right', outlet: 'installedCount'
@li outlet: 'availableLink', =>
@a 'Available', =>
@span class: 'badge pull-right', outlet: 'availableCount'
initialize: ->
@packageEventEmitter = new PackageEventEmitter()
@installed = new InstalledPackagesConfigPanel(@packageEventEmitter)
@available = new AvailablePackagesConfigPanel(@packageEventEmitter)
@append(@installed, @available)
@available.hide()
@installedLink.on 'click', =>
@availableLink.removeClass('active')
@available.hide()
@installedLink.addClass('active')
@installed.show()
@availableLink.on 'click', =>
@installedLink.removeClass('active')
@installed.hide()
@availableLink.addClass('active')
@available.show()
@packageEventEmitter.on 'installed-packages-loaded package-installed package-uninstalled', =>
@installedCount.text(@installed.getPackageCount())
@packageEventEmitter.on 'available-packages-loaded', =>
@availableCount.text(@available.getPackageCount())

View File

@ -1,179 +0,0 @@
Package = require 'package'
semver = require 'semver'
packageManager = require 'package-manager'
_ = require 'underscore'
{$$, View} = require 'space-pen'
requireWithGlobals 'bootstrap/js/bootstrap-dropdown', jQuery: require 'jquery'
### Internal ###
module.exports =
class PackageConfigView extends View
@content: ->
@div class: 'panel', =>
@div outlet: 'heading', class: 'panel-heading', =>
@span outlet: 'name'
@span outlet: 'version', class: 'label'
@span outlet: 'update', class: 'label label-info', 'Update Available'
@span outlet: 'disabedLabel', class: 'label label-warning', 'Disabled'
@div class: 'btn-group pull-right', =>
@button outlet: 'defaultAction', class: 'btn btn-small btn-primary'
@button outlet: 'dropdownButton', class: 'btn btn-small btn-primary dropdown-toggle', 'data-toggle': 'dropdown', =>
@span class: 'caret'
@ul outlet: 'dropdown', class: 'dropdown-menu', =>
@li outlet: 'enableToggle', => @a 'Disable'
@li outlet: 'homepage', => @a 'Visit homepage'
@li outlet: 'issues', => @a 'Report issue'
@div outlet: 'description'
@ul class: 'list-group list-group-flush', =>
@li outlet: 'readmeArea', class: 'list-group-item', =>
@a 'Show README', outlet: 'readmeLink'
@div class: 'readme', outlet: 'readme'
installed: false
disabled: false
bundled: false
updateAvailable: false
initialize: (@pack, @packageEventEmitter) ->
@updatePackageState()
@attr('name', @pack.name)
@name.text(@pack.name)
if version = semver.valid(@pack.version)
@version.text(version)
else
@version.hide()
if @pack.descriptionHtml
@description.html(@pack.descriptionHtml)
else if @pack.description
@description.text(@pack.description)
else
@description.text('No further description available.')
@readme.hide()
if @pack.readmeHtml
@readme.html(pack.readmeHtml)
else if @pack.readme
@readme.text(@pack.readme)
else
@readmeArea.hide()
@readmeLink.on 'click', =>
if @readme.isVisible()
@readme.hide()
@readmeLink.text('Show README')
else
@readme.show()
@readmeLink.text('Hide README')
homepage = @pack.homepage
unless homepage
if _.isString(@pack.repository)
repoUrl = @pack.repository
else
repoUrl = @pack.repository?.url
if repoUrl
repoUrl = repoUrl.replace(/.git$/, '')
homepage = repoUrl if require('url').parse(repoUrl).host is 'github.com'
if homepage
@homepage.find('a').attr('href', homepage)
else
@homepage.hide()
if issues = @pack.bugs?.url
@issues.find('a').attr('href', issues)
else
@issues.hide()
@defaultAction.on 'click', =>
if @installed and @bundled
@togglePackageEnablement()
return
@defaultAction.disable()
if @installed
if @updateAvailable
@defaultAction.text('Upgrading\u2026')
packageManager.install @pack, (error) =>
@packageEventEmitter.trigger('package-upgraded', error, @pack)
else
@defaultAction.text('Uninstalling\u2026')
packageManager.uninstall @pack, (error) =>
@packageEventEmitter.trigger('package-uninstalled', error, @pack)
else
@defaultAction.text('Installing\u2026')
packageManager.install @pack, (error) =>
@packageEventEmitter.trigger('package-installed', error, @pack)
@updateDefaultAction()
@enableToggle.find('a').on 'click', => @togglePackageEnablement()
@observeConfig 'core.disabledPackages', =>
@updatePackageState()
@updateDefaultAction()
@updateEnabledState()
@packageEventEmitter.on 'package-installed package-uninstalled package-upgraded', (error, pack) =>
if pack?.name is @pack.name
@defaultAction.enable()
@updatePackageState()
@updateDefaultAction()
togglePackageEnablement: ->
if @disabled
config.removeAtKeyPath('core.disabledPackages', @pack.name)
else
config.pushAtKeyPath('core.disabledPackages', @pack.name)
updatePackageState: ->
@disabled = atom.isPackageDisabled(@pack.name)
@updateAvailable = false
@bundled = false
loadedPackage = atom.getLoadedPackage(@pack.name)
packagePath = loadedPackage?.path ? atom.resolvePackagePath(@pack.name)
@installed = packagePath?
if @installed
for packageDirPath in config.bundledPackageDirPaths
if packagePath.indexOf("#{packageDirPath}/") is 0
@bundled = true
break
version = loadedPackage?.metadata.version
unless version
try
version = Package.loadMetadata(@pack.name).version
@updateAvailable = semver.gt(@pack.version, version)
if @updateAvailable
@update.show()
else
@update.hide()
updateEnabledState: ->
enableLink = @enableToggle.find('a')
if @disabled
enableLink.text('Enable')
@disabedLabel.show()
else
enableLink.text('Disable')
@disabedLabel.hide()
@enableToggle.hide() unless @installed
updateDefaultAction: ->
if @installed
if @bundled
if @disabled
@defaultAction.text('Enable')
else
@defaultAction.text('Disable')
else
if @updateAvailable
@defaultAction.text('Upgrade')
else
@defaultAction.text('Uninstall')
else
@defaultAction.text('Install')

View File

@ -1,78 +0,0 @@
BufferedProcess = require 'buffered-process'
roaster = require 'roaster'
async = require 'async'
### Internal ###
renderMarkdownInMetadata = (packages, callback) ->
queue = async.queue (pack, callback) ->
operations = []
if pack.description
operations.push (callback) ->
roaster pack.description, {}, (error, html) ->
pack.descriptionHtml = html
callback()
if pack.readme
operations.push (callback) ->
roaster pack.readme, {}, (error, html) ->
pack.readmeHtml = html
callback()
async.waterfall(operations, callback)
queue.push(pack) for pack in packages
queue.drain = callback
getAvailable = (callback) ->
command = require.resolve '.bin/apm'
args = ['available', '--json']
output = []
stdout = (lines) -> output.push(lines)
exit = (code) ->
if code is 0
try
packages = JSON.parse(output.join()) ? []
catch error
callback(error)
return
if packages.length > 0
renderMarkdownInMetadata packages, -> callback(null, packages)
else
callback(null, packages)
else
callback(new Error("apm failed with code: #{code}"))
new BufferedProcess({command, args, stdout, exit})
install = ({name, version}, callback) ->
activateOnSuccess = !atom.isPackageDisabled(name)
activateOnFailure = atom.isPackageActive(name)
atom.deactivatePackage(name) if atom.isPackageActive(name)
atom.unloadPackage(name) if atom.isPackageLoaded(name)
command = require.resolve '.bin/apm'
args = ['install', "#{name}@#{version}"]
exit = (code) ->
if code is 0
atom.activatePackage(name) if activateOnSuccess
callback()
else
actom.activatePackage(name) if activateOnFailure
callback(new Error("Installing '#{name}' failed."))
new BufferedProcess({command, args, exit})
uninstall = ({name}, callback) ->
atom.deactivatePackage(name) if atom.isPackageActive(name)
command = require.resolve '.bin/apm'
args = ['uninstall', name]
exit = (code) ->
if code is 0
atom.unloadPackage(name) if atom.isPackageLoaded(name)
callback()
else
callback(new Error("Uninstalling '#{name}' failed."))
new BufferedProcess({command, args, exit})
module.exports = {renderMarkdownInMetadata, install, uninstall, getAvailable}

View File

@ -82,9 +82,9 @@ class PaneContainer extends View
newPane.focus()
itemDestroyed: (item) ->
state = item.serialize?()
state.uri ?= item.getUri?()
@destroyedItemStates.push(state) if state?
if state = item.serialize?()
state.uri ?= item.getUri?()
@destroyedItemStates.push(state)
itemAdded: (item) ->
itemUri = item.getUri?()

View File

@ -10,7 +10,7 @@ TextBuffer = require 'text-buffer'
EditSession = require 'edit-session'
EventEmitter = require 'event-emitter'
Directory = require 'directory'
BufferedProcess = require 'buffered-process'
BufferedNodeProcess = require 'buffered-node-process'
Git = require 'git'
# Public: Represents a project that's opened in Atom.
@ -229,6 +229,10 @@ class Project
getEditSessions: ->
new Array(@editSessions...)
addEditSession: (editSession) ->
@editSessions.push editSession
@trigger 'edit-session-created', editSession
### Public ###
# Removes an {EditSession} association from the project.
@ -355,7 +359,7 @@ class Project
ignoredNames = config.get('core.ignoredNames') ? []
args.unshift('--ignore', ignoredNames.join(',')) if ignoredNames.length > 0
args.unshift('--addVCSIgnores') if config.get('core.excludeVcsIgnoredPaths')
new BufferedProcess({command, args, stdout, stderr, exit})
new BufferedNodeProcess({command, args, stdout, stderr, exit})
deferred
### Internal ###
@ -364,7 +368,7 @@ class Project
options = _.extend(@defaultEditSessionOptions(), editSessionOptions)
options.buffer = buffer
editSession = new EditSession(options)
@trigger 'edit-session-created', editSession
@addEditSession(editSession)
editSession
defaultEditSessionOptions: ->

View File

@ -22,7 +22,9 @@ class RootView extends View
@version: 1
@configDefaults:
autosave: false
ignoredNames: [".git", ".svn", ".DS_Store"]
excludeVcsIgnoredPaths: false
disabledPackages: []
themes: ['atom-dark-ui', 'atom-dark-syntax']
projectHome: path.join(atom.getHomeDirPath(), 'github')

View File

@ -215,6 +215,14 @@ class Selection
selectToBeginningOfNextWord: ->
@modifySelection => @cursor.moveToBeginningOfNextWord()
# Selects text to the previous word boundary.
selectToPreviousWordBoundary: ->
@modifySelection => @cursor.moveToPreviousWordBoundary()
# Selects text to the next word boundary.
selectToNextWordBoundary: ->
@modifySelection => @cursor.moveToNextWordBoundary()
# Moves the selection down one row.
addSelectionBelow: ->
range = (@getGoalBufferRange() ? @getBufferRange()).copy()

View File

@ -5,6 +5,7 @@ Specificity = require 'specificity'
fsUtils = require 'fs-utils'
EventEmitter = require 'event-emitter'
NullGrammar = require 'null-grammar'
TextMateScopeSelector = require('first-mate').ScopeSelector
### Internal ###
@ -131,12 +132,6 @@ class Syntax
element[0]
cssSelectorFromScopeSelector: (scopeSelector) ->
scopeSelector.split(', ').map((commaFragment) ->
commaFragment.split(' ').map((spaceFragment) ->
spaceFragment.split('.').map((dotFragment) ->
'.' + dotFragment.replace(/\+/g, '\\+')
).join('')
).join(' ')
).join(', ')
new TextMateScopeSelector(scopeSelector).toCssSelector()
_.extend(Syntax.prototype, EventEmitter)

View File

@ -5,7 +5,7 @@ Token = require 'token'
{OnigRegExp, OnigScanner} = require 'oniguruma'
path = require 'path'
EventEmitter = require 'event-emitter'
TextMateScopeSelector = require 'text-mate-scope-selector'
{ScopeSelector} = require 'first-mate'
pathSplitRegex = new RegExp("[#{path.sep}.]")
@ -40,7 +40,7 @@ class TextMateGrammar
@injections = new Injections(this, injections)
if injectionSelector?
@injectionSelector = new TextMateScopeSelector(injectionSelector)
@injectionSelector = new ScopeSelector(injectionSelector)
@firstLineRegex = new OnigRegExp(firstLineMatch) if firstLineMatch
@fileTypes ?= []
@ -197,7 +197,7 @@ class Injections
patterns.push(pattern.getIncludedPatterns(grammar, patterns)...)
@injections.push
anchored: anchored
selector: new TextMateScopeSelector(selector)
selector: new ScopeSelector(selector)
patterns: patterns
getScanner: (injection, firstLine, position, anchorPosition) ->

View File

@ -27,6 +27,8 @@ class TextMatePackage extends Package
@metadata = {@name}
load: ({sync}={}) ->
@metadata = Package.loadMetadata(@path, true)
if sync
@loadGrammarsSync()
@loadScopedPropertiesSync()
@ -100,8 +102,9 @@ class TextMatePackage extends Package
selector = syntax.cssSelectorFromScopeSelector(scope) if scope?
@scopedProperties.push({selector, properties})
for {selector, properties} in @scopedProperties
syntax.addProperties(@path, selector, properties)
if @isActive()
for {selector, properties} in @scopedProperties
syntax.addProperties(@path, selector, properties)
loadScopedProperties: (callback) ->
scopedProperties = []
@ -157,5 +160,6 @@ class TextMatePackage extends Package
increaseIndentPattern: textMateSettings.increaseIndentPattern
decreaseIndentPattern: textMateSettings.decreaseIndentPattern
foldEndPattern: textMateSettings.foldingStopMarker
completions: textMateSettings.completions
)
{ editor: editorProperties } if _.size(editorProperties) > 0

View File

@ -1,80 +0,0 @@
### Internal ###
class SegmentMatcher
constructor: (segment) ->
@segment = segment.join('')
matches: (scope) ->
scope is @segment
class TrueMatcher
constructor: ->
matches: ->
true
class ScopeMatcher
constructor: (first, others) ->
@segments = [first]
@segments.push(segment[1]) for segment in others
matches: (scope) ->
scopeSegments = scope.split('.')
return false if scopeSegments.length < @segments.length
for segment, index in @segments
return false unless segment.matches(scopeSegments[index])
true
class PathMatcher
constructor: (first, others) ->
@matchers = [first]
@matchers.push(matcher[1]) for matcher in others
matches: (scopes) ->
index = 0
matcher = @matchers[index]
for scope in scopes
matcher = @matchers[++index] if matcher.matches(scope)
return true unless matcher?
false
class OrMatcher
constructor: (@left, @right) ->
matches: (scopes) ->
@left.matches(scopes) or @right.matches(scopes)
class AndMatcher
constructor: (@left, @right) ->
matches: (scopes) ->
@left.matches(scopes) and @right.matches(scopes)
class NegateMatcher
constructor: (@left, @right) ->
matches: (scopes) ->
@left.matches(scopes) and not @right.matches(scopes)
class CompositeMatcher
constructor: (left, operator, right) ->
switch operator
when '|' then @matcher = new OrMatcher(left, right)
when '&' then @matcher = new AndMatcher(left, right)
when '-' then @matcher = new NegateMatcher(left, right)
matches: (scopes) ->
@matcher.matches(scopes)
module.exports = {
AndMatcher
CompositeMatcher
NegateMatcher
OrMatcher
PathMatcher
ScopeMatcher
SegmentMatcher
TrueMatcher
}

View File

@ -1,50 +0,0 @@
{
var matchers = require('text-mate-scope-selector-matchers');
}
start = _ selector:(selector) _ {
return selector;
}
segment
= _ segment:[a-zA-Z0-9]+ _ {
return new matchers.SegmentMatcher(segment);
}
/ _ scopeName:[\*] _ {
return new matchers.TrueMatcher();
}
scope
= first:segment others:("." segment)* {
return new matchers.ScopeMatcher(first, others);
}
path
= first:scope others:(_ scope)* {
return new matchers.PathMatcher(first, others);
}
expression
= path
/ "(" _ selector:selector _ ")" {
return selector;
}
composite
= left:expression _ operator:[|&-] _ right:composite {
return new matchers.CompositeMatcher(left, operator, right);
}
/ expression
selector
= left:composite _ "," _ right:selector {
return new matchers.OrMatcher(left, right);
}
/ composite
_
= [ \t]*

View File

@ -1,30 +0,0 @@
PEG = require 'pegjs'
fsUtils = require 'fs-utils'
# Internal: Test a stack of scopes to see if they match a scope selector.
module.exports =
class TextMateScopeSelector
@parser: null
@createParser: ->
unless TextMateScopeSelector.parser?
patternPath = require.resolve('text-mate-scope-selector-pattern.pegjs')
TextMateScopeSelector.parser = PEG.buildParser(fsUtils.read(patternPath))
TextMateScopeSelector.parser
source: null
matcher: null
# Create a new scope selector.
#
# source - A {String} to parse as a scope selector.
constructor: (@source) ->
@matcher = TextMateScopeSelector.createParser().parse(@source)
# Check if this scope selector matches the scopes.
#
# scopes - An {Array} of {String}s.
#
# Return a {Boolean}.
matches: (scopes) ->
@matcher.matches(scopes)

View File

@ -1,73 +0,0 @@
ConfigPanel = require 'config-panel'
{$$} = require 'space-pen'
$ = require 'jquery'
_ = require 'underscore'
###
# Internal #
###
window.jQuery = $
require 'jqueryui-browser/ui/jquery.ui.core'
require 'jqueryui-browser/ui/jquery.ui.widget'
require 'jqueryui-browser/ui/jquery.ui.mouse'
require 'jqueryui-browser/ui/jquery.ui.sortable'
require 'jqueryui-browser/ui/jquery.ui.draggable'
delete window.jQuery
module.exports =
class ThemeConfigPanel extends ConfigPanel
@content: ->
@div id: 'themes-config', =>
@legend "Themes"
@div id: 'theme-picker', =>
@div class: 'panel', =>
@div class: 'panel-heading', "Enabled Themes"
@ol id: 'enabled-themes', class: 'list-group list-group-flush', outlet: 'enabledThemes'
@div class: 'panel', =>
@div class: 'panel-heading', "Available Themes"
@ol id: 'available-themes', class: 'list-group list-group-flush', outlet: 'availableThemes'
constructor: ->
super
for name in atom.getAvailableThemeNames()
@availableThemes.append(@buildThemeLi(name, draggable: true))
@observeConfig "core.themes", (enabledThemes) =>
@enabledThemes.empty()
for name in enabledThemes ? []
@enabledThemes.append(@buildThemeLi(name))
@enabledThemes.sortable
receive: (e, ui) => @enabledThemeReceived($(ui.helper))
update: => @enabledThemesUpdated()
@on "click", "#enabled-themes .disable-theme", (e) =>
$(e.target).closest('li').remove()
@enabledThemesUpdated()
buildThemeLi: (name, {draggable} = {}) ->
li = $$ ->
@li class: 'list-group-item', name: name, =>
@div class: 'disable-theme pull-right'
@text name
if draggable
li.draggable
connectToSortable: '#enabled-themes'
appendTo: '#themes-config'
helper: (e) ->
target = $(e.target)
target.clone().width(target.width())
else
li
enabledThemeReceived: (helper) ->
name = helper.attr('name')
@enabledThemes.find("[name='#{name}']:not('.ui-draggable')").remove()
@enabledThemes.find(".ui-draggable").removeClass('ui-draggable')
enabledThemesUpdated: ->
config.set('core.themes', @getEnabledThemeNames())
getEnabledThemeNames: ->
$(li).attr('name') for li in @enabledThemes.children().toArray()

View File

@ -0,0 +1,47 @@
path = require 'path'
_ = require 'underscore'
fsUtils = require 'fs-utils'
Theme = require 'theme'
module.exports =
class ThemeManager
constructor: ->
@loadedThemes = []
getAvailablePaths: ->
themePaths = []
for themeDirPath in config.themeDirPaths
themePaths.push(fsUtils.listSync(themeDirPath, ['', '.css', 'less'])...)
_.uniq(themePaths)
getAvailableNames: ->
path.basename(themePath).split('.')[0] for themePath in @getAvailablePaths()
load: ->
config.observe 'core.themes', (themeNames) =>
removeStylesheet(@userStylesheetPath) if @userStylesheetPath?
theme.deactivate() while theme = @loadedThemes.pop()
themeNames = [themeNames] unless _.isArray(themeNames)
@loadTheme(themeName) for themeName in themeNames
@loadUserStylesheet()
loadTheme: (name) ->
try
@loadedThemes.push(new Theme(name))
catch error
console.warn("Failed to load theme #{name}", error.stack ? error)
getUserStylesheetPath: ->
stylesheetPath = fsUtils.resolve(path.join(config.configDirPath, 'user'), ['css', 'less'])
if fsUtils.isFileSync(stylesheetPath)
stylesheetPath
else
null
loadUserStylesheet: ->
if userStylesheetPath = @getUserStylesheetPath()
@userStylesheetPath = userStylesheetPath
userStylesheetContents = loadStylesheet(userStylesheetPath)
applyStylesheet(userStylesheetPath, userStylesheetContents, 'userTheme')

View File

@ -1,38 +1,46 @@
fsUtils = require 'fs-utils'
path = require 'path'
### Internal ###
module.exports =
class Theme
@stylesheets: null
@load: (name) ->
TextMateTheme = require 'text-mate-theme'
AtomTheme = require 'atom-theme'
stylesheetPath: null
stylesheets: null
constructor: (name) ->
@stylesheets = []
if fsUtils.exists(name)
path = name
@stylesheetPath = name
else
path = fsUtils.resolve(config.themeDirPaths..., name, ['', '.tmTheme', '.css', 'less'])
@stylesheetPath = fsUtils.resolve(config.themeDirPaths..., name, ['', '.css', 'less'])
throw new Error("No theme exists named '#{name}'") unless path
throw new Error("No theme exists named '#{name}'") unless @stylesheetPath
theme =
if TextMateTheme.testPath(path)
new TextMateTheme(path)
else
new AtomTheme(path)
theme.load()
theme
constructor: (@path) ->
@stylesheets = {}
@load()
# Loads the stylesheets found in a `package.cson` file.
load: ->
for stylesheetPath, stylesheetContent of @stylesheets
applyStylesheet(stylesheetPath, stylesheetContent, 'userTheme')
if path.extname(@stylesheetPath) in ['.css', '.less']
@loadStylesheet(@stylesheetPath)
else
metadataPath = fsUtils.resolveExtension(path.join(@stylesheetPath, 'package'), ['cson', 'json'])
if fsUtils.isFileSync(metadataPath)
stylesheetNames = fsUtils.readObjectSync(metadataPath)?.stylesheets
if stylesheetNames
for name in stylesheetNames
filename = fsUtils.resolveExtension(path.join(@stylesheetPath, name), ['.css', '.less', ''])
@loadStylesheet(filename)
else
@loadStylesheet(stylesheetPath) for stylesheetPath in fsUtils.listSync(@stylesheetPath, ['.css', '.less'])
# Given a path, this loads it as a stylesheet.
#
# stylesheetPath - A {String} to a stylesheet
loadStylesheet: (stylesheetPath) ->
@stylesheets.push stylesheetPath
content = window.loadStylesheet(stylesheetPath)
window.applyStylesheet(stylesheetPath, content, 'userTheme')
deactivate: ->
for stylesheetPath, stylesheetContent of @stylesheets
removeStylesheet(stylesheetPath)
window.removeStylesheet(stylesheetPath) for stylesheetPath in @stylesheets

View File

@ -109,6 +109,12 @@ class Token
isOnlyWhitespace: ->
not /\S/.test(@value)
matchesScopeSelector: (selector) ->
targetClasses = selector.replace(/^\.?/, '').split('.')
_.any @scopes, (scope) ->
scopeClasses = scope.split('.')
_.isSubset(targetClasses, scopeClasses)
getValueAsHtml: ({invisibles, hasLeadingWhitespace, hasTrailingWhitespace, hasIndentGuide})->
invisibles ?= {}
html = @value

View File

@ -199,9 +199,13 @@ class TokenizedBuffer
{ tokens, ruleStack } = @grammar.tokenizeLine(line, ruleStack, row is 0)
new TokenizedLine({tokens, ruleStack, tabLength, lineEnding})
# FIXME: benogle says: These are actually buffer rows as all buffer rows are
# accounted for in @tokenizedLines
lineForScreenRow: (row) ->
@linesForScreenRows(row, row)[0]
# FIXME: benogle says: These are actually buffer rows as all buffer rows are
# accounted for in @tokenizedLines
linesForScreenRows: (startRow, endRow) ->
@tokenizedLines[startRow..endRow]
@ -212,8 +216,34 @@ class TokenizedBuffer
@tokenForPosition(position).scopes
tokenForPosition: (position) ->
{row, column} = Point.fromObject(position)
@tokenizedLines[row].tokenAtBufferColumn(column)
tokenStartPositionForPosition: (position) ->
{row, column} = Point.fromObject(position)
column = @tokenizedLines[row].tokenStartColumnForBufferColumn(column)
new Point(row, column)
bufferRangeForScopeAtPosition: (selector, position) ->
position = Point.fromObject(position)
@tokenizedLines[position.row].tokenAtBufferColumn(position.column)
tokenizedLine = @tokenizedLines[position.row]
startIndex = tokenizedLine.tokenIndexAtBufferColumn(position.column)
for index in [startIndex..0]
token = tokenizedLine.tokenAtIndex(index)
break unless token.matchesScopeSelector(selector)
firstToken = token
for index in [startIndex...tokenizedLine.getTokenCount()]
token = tokenizedLine.tokenAtIndex(index)
break unless token.matchesScopeSelector(selector)
lastToken = token
return unless firstToken? and lastToken?
startColumn = tokenizedLine.bufferColumnForToken(firstToken)
endColumn = tokenizedLine.bufferColumnForToken(lastToken) + lastToken.bufferDelta
new Range([position.row, startColumn], [position.row, endColumn])
destroy: ->
@unsubscribe()

View File

@ -90,11 +90,22 @@ class TokenizedLine
@lineEnding is null
tokenAtBufferColumn: (bufferColumn) ->
@tokens[@tokenIndexAtBufferColumn(bufferColumn)]
tokenIndexAtBufferColumn: (bufferColumn) ->
delta = 0
for token, index in @tokens
delta += token.bufferDelta
return index if delta > bufferColumn
index - 1
tokenStartColumnForBufferColumn: (bufferColumn) ->
delta = 0
for token in @tokens
delta += token.bufferDelta
return token if delta > bufferColumn
token
nextDelta = delta + token.bufferDelta
break if nextDelta > bufferColumn
delta = nextDelta
delta
breakOutAtomicTokens: (inputTokens, tabLength) ->
outputTokens = []
@ -111,3 +122,16 @@ class TokenizedLine
for scope in token.scopes
return true if _.contains(scope.split('.'), 'comment')
break
false
tokenAtIndex: (index) ->
@tokens[index]
getTokenCount: ->
@tokens.length
bufferColumnForToken: (targetToken) ->
column = 0
for token in @tokens
return column if token is targetToken
column += token.bufferDelta

View File

@ -51,7 +51,7 @@ window.startEditorWindow = ->
restoreDimensions()
config.load()
keymap.loadBundledKeymaps()
atom.loadThemes()
atom.themes.load()
atom.loadPackages()
deserializeEditorWindow()
atom.activatePackages()
@ -61,20 +61,6 @@ window.startEditorWindow = ->
atom.show()
atom.focus()
window.startConfigWindow = ->
restoreDimensions()
windowEventHandler = new WindowEventHandler
config.load()
keymap.loadBundledKeymaps()
atom.loadThemes()
atom.loadPackages()
deserializeConfigWindow()
atom.activatePackageConfigs()
keymap.loadUserKeymaps()
$(window).on 'unload', -> unloadConfigWindow(); false
atom.show()
atom.focus()
window.unloadEditorWindow = ->
return if not project and not rootView
windowState = atom.getWindowState()
@ -98,13 +84,6 @@ window.installApmCommand = (callback) ->
commandPath = path.join(window.resourcePath, 'node_modules', '.bin', 'apm')
require('command-installer').install(commandPath, callback)
window.unloadConfigWindow = ->
return if not configView
atom.getWindowState().set('configView', configView.serialize())
configView.remove()
windowEventHandler?.unsubscribe()
window.configView = null
window.onDrop = (e) ->
e.preventDefault()
e.stopPropagation()
@ -136,11 +115,6 @@ window.deserializeEditorWindow = ->
projectPath = project.getPath()
atom.getLoadSettings().initialPath = projectPath
window.deserializeConfigWindow = ->
ConfigView = require 'config-view'
window.configView = deserialize(atom.getWindowState('configView')) ? new ConfigView()
$(rootViewParentSelector).append(configView)
window.stylesheetElementForId = (id) ->
$("""head style[id="#{id}"]""")
@ -229,12 +203,15 @@ window.unregisterDeserializer = (klass) ->
delete deserializers[klass.name]
window.deserialize = (state, params) ->
return unless state?
if deserializer = getDeserializer(state)
stateVersion = state.get?('version') ? state.version
return if deserializer.version? and deserializer.version isnt stateVersion
if (state instanceof telepath.Document) and not deserializer.acceptsDocuments
state = state.toObject()
deserializer.deserialize(state, params)
else
console.warn "No deserializer found for", state
window.getDeserializer = (state) ->
return unless state?
@ -243,6 +220,7 @@ window.getDeserializer = (state) ->
if deferredDeserializers[name]
deferredDeserializers[name]()
delete deferredDeserializers[name]
deserializers[name]
window.requireWithGlobals = (id, globals={}) ->

View File

@ -33,7 +33,6 @@ class AtomApplication
client.on 'error', createAtomApplication
windows: null
configWindow: null
menu: null
resourcePath: null
installUpdate: null
@ -83,10 +82,15 @@ class AtomApplication
app.commandLine.appendSwitch 'js-flags', '--harmony_collections'
checkForUpdates: ->
return if /\w{7}/.test @version # Don't check for updates if version is a short sha
versionIsSha = /\w{7}/.test @version
autoUpdater.setAutomaticallyChecksForUpdates true
autoUpdater.checkForUpdatesInBackground()
if versionIsSha
autoUpdater.setAutomaticallyDownloadsUpdates false
autoUpdater.setAutomaticallyChecksForUpdates false
else
autoUpdater.setAutomaticallyDownloadsUpdates true
autoUpdater.setAutomaticallyChecksForUpdates true
autoUpdater.checkForUpdatesInBackground()
buildApplicationMenu: (version, continueUpdate) ->
menus = []
@ -95,7 +99,7 @@ class AtomApplication
submenu: [
{ label: 'About Atom', selector: 'orderFrontStandardAboutPanel:' }
{ type: 'separator' }
{ label: 'Preferences...', accelerator: 'Command+,', click: => @openConfig() }
{ label: 'Preferences...', accelerator: 'Command+,', click: => @sendCommand('window:open-settings') }
{ type: 'separator' }
{ label: 'Hide Atom', accelerator: 'Command+H', selector: 'hide:' }
{ label: 'Hide Others', accelerator: 'Command+Shift+H', selector: 'hideOtherApplications:' }
@ -127,7 +131,7 @@ class AtomApplication
menus.push
label: 'File'
submenu: [
{ label: 'New Window', accelerator: 'Command+N', click: => @openPath() }
{ label: 'New Window', accelerator: 'Command+Shift+N', click: => @openPath() }
{ label: 'Open...', accelerator: 'Command+O', click: => @promptForPath() }
{ label: 'Open In Dev Mode...', accelerator: 'Command+Shift+O', click: => @promptForPath(devMode: true) }
]
@ -183,8 +187,10 @@ class AtomApplication
@installUpdate = quitAndUpdate
@buildApplicationMenu version, quitAndUpdate
ipc.on 'open-config', =>
@openConfig()
ipc.on 'close-without-confirm', (processId, routingId) ->
window = BrowserWindow.fromProcessIdAndRoutingId processId, routingId
window.removeAllListeners 'close'
window.close()
ipc.on 'open', (processId, routingId, pathsToOpen) =>
if pathsToOpen?.length > 0
@ -258,17 +264,6 @@ class AtomApplication
else
console.log "Opening unknown url #{urlToOpen}"
openConfig: ->
if @configWindow
@configWindow.focus()
return
@configWindow = new AtomWindow
bootstrapScript: 'config-bootstrap'
resourcePath: @resourcePath
@configWindow.browserWindow.on 'destroyed', =>
@configWindow = null
runSpecs: ({exitWhenDone, resourcePath}) ->
if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath)
resourcePath = @resourcePath

View File

@ -40,7 +40,6 @@ class AtomWindow
paths = [
'src/stdlib'
'src/app'
'src/packages'
'src'
'vendor'
'static'

View File

@ -1,7 +0,0 @@
date = new Date().getTime()
require 'atom'
require 'window'
window.setUpEnvironment('config')
window.startConfigWindow()
console.log "Load time: #{new Date().getTime() - date}"

View File

@ -56,7 +56,6 @@ setupCrashReporter = ->
setupAutoUpdater = ->
autoUpdater.setFeedUrl 'https://speakeasy.githubapp.com/apps/27/appcast.xml'
autoUpdater.setAutomaticallyDownloadsUpdates true
parseCommandLine = ->
version = app.getVersion()
@ -64,7 +63,7 @@ parseCommandLine = ->
options.usage """
Atom #{version}
Usage: atom [options] [file ..]
Usage: atom [options] [file ...]
"""
options.alias('d', 'dev').boolean('d').describe('d', 'Run in development mode.')
options.alias('h', 'help').boolean('h').describe('h', 'Print this usage message.')

View File

@ -1,3 +0,0 @@
'.archive-view':
'k': 'core:move-up'
'j': 'core:move-down'

View File

@ -1,48 +0,0 @@
fsUtils = require 'fs-utils'
path = require 'path'
_ = require 'underscore'
archive = require 'ls-archive'
File = require 'file'
module.exports=
class ArchiveEditSession
registerDeserializer(this)
@version: 1
@activate: ->
Project = require 'project'
Project.registerOpener (filePath) ->
new ArchiveEditSession(filePath) if archive.isPathSupported(filePath)
@deserialize: ({path}={}) ->
path = project.resolve(path)
if fsUtils.isFileSync(path)
new ArchiveEditSession(path)
else
console.warn "Could not build archive edit session for path '#{path}' because that file no longer exists"
constructor: (@path) ->
@file = new File(@path)
destroy: ->
@file?.off()
serialize: ->
deserializer: 'ArchiveEditSession'
path: @getUri()
getViewClass: ->
require './archive-view'
getTitle: ->
if archivePath = @getPath()
path.basename(archivePath)
else
'untitled'
getUri: -> project?.relativize(@getPath()) ? @getPath()
getPath: -> @path
isEqual: (other) ->
other instanceof ArchiveEditSession and @getUri() is other.getUri()

View File

@ -1,82 +0,0 @@
ScrollView = require 'scroll-view'
archive = require 'ls-archive'
FileView = require './file-view'
DirectoryView = require './directory-view'
fs = require 'fs'
humanize = require 'humanize-plus'
module.exports =
class ArchiveView extends ScrollView
@content: ->
@div class: 'archive-view', tabindex: -1, =>
@div class: 'archive-container', =>
@div outlet: 'loadingMessage', class: 'loading-message', 'Loading archive\u2026'
@div outlet: 'summary', class: 'summary'
@div outlet: 'tree', class: 'archive-tree'
initialize: (editSession) ->
super
@setModel(editSession)
@on 'focus', =>
@focusSelectedFile()
false
setPath: (path) ->
if path and @path isnt path
@path = path
@refresh()
refresh: ->
@summary.hide()
@tree.hide()
@loadingMessage.show()
originalPath = @path
archive.list @path, tree: true, (error, entries) =>
return unless originalPath is @path
if error?
console.error("Error listing archive file: #{@path}", error.stack ? error)
else
@loadingMessage.hide()
@createTreeEntries(entries)
@updateSummary()
createTreeEntries: (entries) ->
@tree.empty()
for entry in entries
if entry.isDirectory()
@tree.append(new DirectoryView(@path, entry))
else
@tree.append(new FileView(@path, entry))
@tree.show()
@tree.find('.file').view()?.select()
updateSummary: ->
fileCount = @tree.find('.file').length
fileLabel = if fileCount is 1 then "1 file" else "#{humanize.intcomma(fileCount)} files"
directoryCount = @tree.find('.directory').length
directoryLabel = if directoryCount is 1 then "1 folder" else "#{humanize.intcomma(directoryCount)} folders"
@summary.text("#{humanize.filesize(fs.statSync(@path).size)} with #{fileLabel} and #{directoryLabel}").show()
focusSelectedFile: ->
@tree.find('.selected').view()?.focus()
focus: ->
@focusSelectedFile()
setModel: (editSession) ->
@unsubscribe(@editSession) if @editSession
if editSession
@editSession = editSession
@setPath(editSession.getPath())
editSession.file.on 'contents-changed', =>
@refresh()
editSession.file.on 'removed', =>
@parent('.item-views').parent('.pane').view()?.destroyItem(editSession)

View File

@ -1,15 +0,0 @@
{View} = require 'space-pen'
FileView = require './file-view'
module.exports =
class DirectoryView extends View
@content: (archivePath, entry) ->
@div class: 'entry', =>
@span entry.getName(), class: 'directory'
initialize: (archivePath, entry) ->
for child in entry.children
if child.isDirectory()
@append(new DirectoryView(archivePath, child))
else
@append(new FileView(archivePath, child))

View File

@ -1,58 +0,0 @@
{View} = require 'space-pen'
$ = require 'jquery'
fsUtils = require 'fs-utils'
path = require 'path'
temp = require 'temp'
archive = require 'ls-archive'
module.exports =
class FileView extends View
@content: (archivePath, entry) ->
@div class: 'entry', tabindex: -1, =>
@span entry.getName(), class: 'file', outlet: 'name'
initialize: (@archivePath, @entry) ->
@name.addClass('symlink') if @entry.isSymbolicLink()
@on 'click', =>
@select()
@openFile()
@on 'core:confirm', =>
@openFile() if @isSelected()
@on 'core:move-down', =>
if @isSelected()
files = @closest('.archive-view').find('.file')
$(files[files.index(@name) + 1]).view()?.select()
@on 'core:move-up', =>
if @isSelected()
files = @closest('.archive-view').find('.file')
$(files[files.index(@name) - 1]).view()?.select()
isSelected: -> @name.hasClass('selected')
logError: (message, error) ->
console.error(message, error.stack ? error)
openFile: ->
archive.readFile @archivePath, @entry.getPath(), (error, contents) =>
if error?
@logError("Error reading: #{@entry.getPath()} from #{@archivePath}", error)
else
temp.mkdir 'atom-', (error, tempDirPath) =>
if error?
@logError("Error creating temp directory: #{tempDirPath}", error)
else
tempFilePath = path.join(tempDirPath, path.basename(@archivePath), @entry.getName())
fsUtils.write tempFilePath, contents, (error) =>
if error?
@logError("Error writing to #{tempFilePath}", error)
else
rootView.open(tempFilePath)
select: ->
@closest('.archive-view').find('.selected').toggleClass('selected')
@name.addClass('selected')
@focus()

View File

@ -1,3 +0,0 @@
'description': 'View the files and folders inside archive files'
'main': './lib/archive-edit-session'
'deferredDeserializers': ['ArchiveEditSession']

View File

@ -1,117 +0,0 @@
RootView = require 'root-view'
fsUtils = require 'fs-utils'
describe "Archive viewer", ->
beforeEach ->
window.rootView = new RootView
atom.activatePackage('archive-view', sync: true)
describe ".initialize()", ->
it "displays the files and folders in the archive file", ->
rootView.open('nested.tar')
archiveView = rootView.find('.archive-view')
expect(rootView.find('.archive-view')).toExist()
waitsFor -> archiveView.find('.entry').length > 0
runs ->
expect(archiveView.find('.directory').length).toBe 6
expect(archiveView.find('.directory:eq(0)').text()).toBe 'd1'
expect(archiveView.find('.directory:eq(1)').text()).toBe 'd2'
expect(archiveView.find('.directory:eq(2)').text()).toBe 'd3'
expect(archiveView.find('.directory:eq(3)').text()).toBe 'd4'
expect(archiveView.find('.directory:eq(4)').text()).toBe 'da'
expect(archiveView.find('.directory:eq(5)').text()).toBe 'db'
expect(archiveView.find('.file').length).toBe 3
expect(archiveView.find('.file:eq(0)').text()).toBe 'f1.txt'
expect(archiveView.find('.file:eq(1)').text()).toBe 'f2.txt'
expect(archiveView.find('.file:eq(2)').text()).toBe 'fa.txt'
it "selects the first file", ->
rootView.open('nested.tar')
archiveView = rootView.find('.archive-view')
waitsFor -> archiveView.find('.entry').length > 0
runs -> expect(archiveView.find('.selected').text()).toBe 'f1.txt'
describe "when core:move-up/core:move-down is triggered", ->
it "selects the next/previous file", ->
rootView.open('nested.tar')
archiveView = rootView.find('.archive-view')
waitsFor -> archiveView.find('.entry').length > 0
runs ->
archiveView.find('.selected').trigger 'core:move-up'
expect(archiveView.find('.selected').text()).toBe 'f1.txt'
archiveView.find('.selected').trigger 'core:move-down'
expect(archiveView.find('.selected').text()).toBe 'f2.txt'
archiveView.find('.selected').trigger 'core:move-down'
expect(archiveView.find('.selected').text()).toBe 'fa.txt'
archiveView.find('.selected').trigger 'core:move-down'
expect(archiveView.find('.selected').text()).toBe 'fa.txt'
archiveView.find('.selected').trigger 'core:move-up'
expect(archiveView.find('.selected').text()).toBe 'f2.txt'
archiveView.find('.selected').trigger 'core:move-up'
expect(archiveView.find('.selected').text()).toBe 'f1.txt'
describe "when a file is clicked", ->
it "copies the contents to a temp file and opens it in a new editor", ->
rootView.open('nested.tar')
archiveView = rootView.find('.archive-view')
waitsFor -> archiveView.find('.entry').length > 0
runs ->
spyOn(rootView, 'open').andCallThrough()
archiveView.find('.file:eq(2)').trigger 'click'
waitsFor -> rootView.open.callCount is 1
runs ->
expect(rootView.getActiveView().getText()).toBe 'hey there\n'
expect(rootView.getActivePaneItem().getTitle()).toBe 'fa.txt'
describe "when core:confirm is triggered", ->
it "copies the contents to a temp file and opens it in a new editor", ->
rootView.open('nested.tar')
archiveView = rootView.find('.archive-view')
waitsFor -> archiveView.find('.entry').length > 0
runs ->
spyOn(rootView, 'open').andCallThrough()
archiveView.find('.file:eq(0)').trigger 'core:confirm'
waitsFor -> rootView.open.callCount is 1
runs ->
expect(rootView.getActiveView().getText()).toBe ''
expect(rootView.getActivePaneItem().getTitle()).toBe 'f1.txt'
describe "when the file is removed", ->
it "destroys the view", ->
rootView.open('nested.tar')
archiveView = rootView.find('.archive-view')
waitsFor -> archiveView.find('.entry').length > 0
runs ->
expect(rootView.getActivePane().getItems().length).toBe 1
rootView.getActivePaneItem().file.trigger('removed')
expect(rootView.getActivePane()).toBeFalsy()
describe "when the file is modified", ->
it "refreshes the view", ->
rootView.open('nested.tar')
archiveView = rootView.find('.archive-view').view()
waitsFor -> archiveView.find('.entry').length > 0
runs ->
spyOn(archiveView, 'refresh')
rootView.getActivePaneItem().file.trigger('contents-changed')
expect(archiveView.refresh).toHaveBeenCalled()

View File

@ -1,67 +0,0 @@
@import "bootstrap/less/variables.less";
@import "octicon-mixins.less";
.archive-view {
margin: 0;
padding: 0;
overflow: auto;
position: relative;
display: -webkit-flex;
@icon-margin: @line-height-base / 4;
.archive-container {
height:100%;
width: 100%;
.loading-message {
margin: 5px;
.mini-icon(hourglass);
&:before {
font-size: 16px;
width: 16px;
height: 16px;
margin-right: 5px;
}
}
.tree {
padding: 0 5px 5px 5px;
}
.entry {
margin: 5px;
> .entry {
padding-left: 15px;
}
span.file {
display: inline-block;
padding: 5px;
&:before {
padding-right: @icon-margin;
}
.mini-icon(text-file);
&.symlink {
.mini-icon(symlink);
}
}
span.directory {
display: inline-block;
padding: 5px 0 0 5px;
&:before {
margin-right: @icon-margin;
}
.mini-icon(directory);
}
}
}
}

View File

@ -1,8 +0,0 @@
'.editor':
'ctrl-space': 'autocomplete:attach'
'.autocomplete .editor':
'ctrl-space': 'core:cancel'
'.autocomplete .mini.editor input':
'enter': 'core:confirm'

View File

@ -1,179 +0,0 @@
$ = require 'jquery'
{$$} = require 'space-pen'
{Range} = require 'telepath'
SelectList = require 'select-list'
module.exports =
class AutocompleteView extends SelectList
@viewClass: -> "autocomplete #{super} popover-list"
editor: null
currentBuffer: null
wordList: null
wordRegex: /\w+/g
originalSelectionBufferRange: null
originalCursorPosition: null
aboveCursor: false
filterKey: 'word'
initialize: (@editor) ->
super
@handleEvents()
@setCurrentBuffer(@editor.getBuffer())
itemForElement: (match) ->
$$ ->
@li =>
@span match.word
handleEvents: ->
@list.on 'mousewheel', (event) -> event.stopPropagation()
@editor.on 'editor:path-changed', => @setCurrentBuffer(@editor.getBuffer())
@editor.command 'autocomplete:attach', => @attach()
@editor.command 'autocomplete:next', => @selectNextItem()
@editor.command 'autocomplete:previous', => @selectPreviousItem()
@miniEditor.preempt 'textInput', (e) =>
text = e.originalEvent.data
unless text.match(@wordRegex)
@confirmSelection()
@editor.insertText(text)
false
setCurrentBuffer: (@currentBuffer) ->
selectItem: (item) ->
super
match = @getSelectedElement()
@replaceSelectedTextWithMatch(match) if match
selectNextItem: ->
super
false
selectPreviousItem: ->
super
false
buildWordList: ->
wordHash = {}
matches = @currentBuffer.getText().match(@wordRegex)
wordHash[word] ?= true for word in (matches or [])
@wordList = Object.keys(wordHash).sort (word1, word2) ->
word1 = word1.toLowerCase()
word2 = word2.toLowerCase()
if word1 > word2
1
else if word1 < word2
-1
else
0
confirmed: (match) ->
@editor.getSelection().clear()
@cancel()
return unless match
@replaceSelectedTextWithMatch match
position = @editor.getCursorBufferPosition()
@editor.setCursorBufferPosition([position.row, position.column + match.suffix.length])
cancelled: ->
super
@editor.abort()
@editor.setSelectedBufferRange(@originalSelectionBufferRange)
rootView.focus() if @miniEditor.isFocused
attach: ->
@editor.transact()
@aboveCursor = false
@originalSelectionBufferRange = @editor.getSelection().getBufferRange()
@originalCursorPosition = @editor.getCursorScreenPosition()
@buildWordList()
matches = @findMatchesForCurrentSelection()
@setArray(matches)
if matches.length is 1
@confirmSelection()
else
@editor.appendToLinesView(this)
@setPosition()
@miniEditor.focus()
detach: ->
super
@editor.off(".autocomplete")
@editor.focus()
setPosition: ->
{ left, top } = @editor.pixelPositionForScreenPosition(@originalCursorPosition)
height = @outerHeight()
potentialTop = top + @editor.lineHeight
potentialBottom = potentialTop - @editor.scrollTop() + height
if @aboveCursor or potentialBottom > @editor.outerHeight()
@aboveCursor = true
@css(left: left, top: top - height, bottom: 'inherit')
else
@css(left: left, top: potentialTop, bottom: 'inherit')
findMatchesForCurrentSelection: ->
selection = @editor.getSelection()
{prefix, suffix} = @prefixAndSuffixOfSelection(selection)
if (prefix.length + suffix.length) > 0
regex = new RegExp("^#{prefix}.+#{suffix}$", "i")
currentWord = prefix + @editor.getSelectedText() + suffix
for word in @wordList when regex.test(word) and word != currentWord
{prefix, suffix, word}
else
{word, prefix, suffix} for word in @wordList
replaceSelectedTextWithMatch: (match) ->
selection = @editor.getSelection()
startPosition = selection.getBufferRange().start
buffer = @editor.getBuffer()
selection.deleteSelectedText()
cursorPosition = @editor.getCursorBufferPosition()
buffer.delete(Range.fromPointWithDelta(cursorPosition, 0, match.suffix.length))
buffer.delete(Range.fromPointWithDelta(cursorPosition, 0, -match.prefix.length))
@editor.insertText(match.word)
infixLength = match.word.length - match.prefix.length - match.suffix.length
@editor.setSelectedBufferRange([startPosition, [startPosition.row, startPosition.column + infixLength]])
prefixAndSuffixOfSelection: (selection) ->
selectionRange = selection.getBufferRange()
lineRange = [[selectionRange.start.row, 0], [selectionRange.end.row, @editor.lineLengthForBufferRow(selectionRange.end.row)]]
[prefix, suffix] = ["", ""]
@currentBuffer.scanInRange @wordRegex, lineRange, ({match, range, stop}) ->
stop() if range.start.isGreaterThan(selectionRange.end)
if range.intersectsWith(selectionRange)
prefixOffset = selectionRange.start.column - range.start.column
suffixOffset = selectionRange.end.column - range.end.column
prefix = match[0][0...prefixOffset] if range.start.isLessThan(selectionRange.start)
suffix = match[0][suffixOffset..] if range.end.isGreaterThan(selectionRange.end)
{prefix, suffix}
afterAttach: (onDom) ->
if onDom
widestCompletion = parseInt(@css('min-width')) or 0
@list.find('span').each ->
widestCompletion = Math.max(widestCompletion, $(this).outerWidth())
@list.width(widestCompletion)
@width(@list.outerWidth())
populateList: ->
super
@setPosition()

View File

@ -1,16 +0,0 @@
AutocompleteView = require './autocomplete-view'
module.exports =
autoCompleteViews: []
editorSubscription: null
activate: ->
@editorSubscription = rootView.eachEditor (editor) =>
if editor.attached and not editor.mini
@autoCompleteViews.push new AutocompleteView(editor)
deactivate: ->
@editorSubscription?.off()
@editorSubscription = null
@autoCompleteViews.forEach (autoCompleteView) -> autoCompleteView.remove()
@autoCompleteViews = []

View File

@ -1,4 +0,0 @@
'main': './lib/autocomplete'
'description': 'Display possible completions from the current editor with `ctrl-space`.'
'activationEvents':
'autocomplete:attach': '.editor'

View File

@ -1,439 +0,0 @@
$ = require 'jquery'
AutocompleteView = require 'autocomplete/lib/autocomplete-view'
Autocomplete = require 'autocomplete/lib/autocomplete'
Buffer = require 'text-buffer'
Editor = require 'editor'
RootView = require 'root-view'
describe "Autocomplete", ->
beforeEach ->
window.rootView = new RootView
rootView.open('sample.js')
rootView.simulateDomAttachment()
describe "@activate()", ->
it "activates autocomplete on all existing and future editors (but not on autocomplete's own mini editor)", ->
spyOn(AutocompleteView.prototype, 'initialize').andCallThrough()
autocompletePackage = atom.activatePackage("autocomplete")
expect(AutocompleteView.prototype.initialize).not.toHaveBeenCalled()
leftEditor = rootView.getActiveView()
rightEditor = leftEditor.splitRight()
leftEditor.trigger 'autocomplete:attach'
expect(leftEditor.find('.autocomplete')).toExist()
expect(rightEditor.find('.autocomplete')).not.toExist()
expect(AutocompleteView.prototype.initialize).toHaveBeenCalled()
autoCompleteView = leftEditor.find('.autocomplete').view()
autoCompleteView.trigger 'core:cancel'
expect(leftEditor.find('.autocomplete')).not.toExist()
rightEditor.trigger 'autocomplete:attach'
expect(rightEditor.find('.autocomplete')).toExist()
describe "@deactivate()", ->
it "removes all autocomplete views and doesn't create new ones when new editors are opened", ->
atom.activatePackage('autocomplete')
rootView.getActiveView().trigger "autocomplete:attach"
expect(rootView.getActiveView().find('.autocomplete')).toExist()
atom.deactivatePackage('autocomplete')
expect(rootView.getActiveView().find('.autocomplete')).not.toExist()
rootView.getActiveView().splitRight()
rootView.getActiveView().trigger "autocomplete:attach"
expect(rootView.getActiveView().find('.autocomplete')).not.toExist()
describe "AutocompleteView", ->
autocomplete = null
editor = null
miniEditor = null
beforeEach ->
window.rootView = new RootView
editor = new Editor(editSession: project.open('sample.js'))
atom.activatePackage('autocomplete')
autocomplete = new AutocompleteView(editor)
miniEditor = autocomplete.miniEditor
describe 'autocomplete:attach event', ->
it "shows autocomplete view and focuses its mini-editor", ->
expect(editor.find('.autocomplete')).not.toExist()
editor.trigger "autocomplete:attach"
expect(editor.find('.autocomplete')).toExist()
expect(autocomplete.editor.isFocused).toBeFalsy()
expect(autocomplete.miniEditor.isFocused).toBeTruthy()
describe "when no text is selected", ->
it 'autocompletes word when there is only a prefix', ->
editor.getBuffer().insert([10,0] ,"extra:s:extra")
editor.setCursorBufferPosition([10,7])
autocomplete.attach()
expect(editor.lineForBufferRow(10)).toBe "extra:shift:extra"
expect(editor.getCursorBufferPosition()).toEqual [10,11]
expect(editor.getSelection().getBufferRange()).toEqual [[10,7], [10,11]]
expect(autocomplete.list.find('li').length).toBe 2
expect(autocomplete.list.find('li:eq(0)')).toHaveText('shift')
expect(autocomplete.list.find('li:eq(1)')).toHaveText('sort')
it 'autocompletes word when there is only a suffix', ->
editor.getBuffer().insert([10,0] ,"extra:n:extra")
editor.setCursorBufferPosition([10,6])
autocomplete.attach()
expect(editor.lineForBufferRow(10)).toBe "extra:function:extra"
expect(editor.getCursorBufferPosition()).toEqual [10,13]
expect(editor.getSelection().getBufferRange()).toEqual [[10,6], [10,13]]
expect(autocomplete.list.find('li').length).toBe 2
expect(autocomplete.list.find('li:eq(0)')).toHaveText('function')
expect(autocomplete.list.find('li:eq(1)')).toHaveText('return')
it 'autocompletes word when there is a single prefix and suffix match', ->
editor.getBuffer().insert([8,43] ,"q")
editor.setCursorBufferPosition([8,44])
autocomplete.attach()
expect(editor.lineForBufferRow(8)).toBe " return sort(left).concat(pivot).concat(quicksort(right));"
expect(editor.getCursorBufferPosition()).toEqual [8,52]
expect(editor.getSelection().getBufferRange().isEmpty()).toBeTruthy()
expect(autocomplete.list.find('li').length).toBe 0
it "shows all words there is no prefix or suffix", ->
editor.setCursorBufferPosition([10, 0])
autocomplete.attach()
expect(autocomplete.list.find('li:eq(0)')).toHaveText('0')
expect(autocomplete.list.find('li:eq(1)')).toHaveText('1')
expect(autocomplete.list.find('li').length).toBe 22
it "autocompletes word and replaces case of prefix with case of word", ->
editor.getBuffer().insert([10,0] ,"extra:SO:extra")
editor.setCursorBufferPosition([10,8])
autocomplete.attach()
expect(editor.lineForBufferRow(10)).toBe "extra:sort:extra"
expect(editor.getCursorBufferPosition()).toEqual [10,10]
expect(editor.getSelection().isEmpty()).toBeTruthy()
describe "when text is selected", ->
it 'autocompletes word when there is only a prefix', ->
editor.getBuffer().insert([10,0] ,"extra:sort:extra")
editor.setSelectedBufferRange [[10,7], [10,10]]
autocomplete.attach()
expect(editor.lineForBufferRow(10)).toBe "extra:shift:extra"
expect(editor.getCursorBufferPosition()).toEqual [10,11]
expect(editor.getSelection().getBufferRange().isEmpty()).toBeTruthy()
expect(autocomplete.list.find('li').length).toBe 0
it 'autocompletes word when there is only a suffix', ->
editor.getBuffer().insert([10,0] ,"extra:current:extra")
editor.setSelectedBufferRange [[10,6],[10,12]]
autocomplete.attach()
expect(editor.lineForBufferRow(10)).toBe "extra:concat:extra"
expect(editor.getCursorBufferPosition()).toEqual [10,11]
expect(editor.getSelection().getBufferRange()).toEqual [[10,6],[10,11]]
expect(autocomplete.list.find('li').length).toBe 7
expect(autocomplete.list.find('li:contains(current)')).not.toExist()
it 'autocompletes word when there is a prefix and suffix', ->
editor.setSelectedBufferRange [[5,7],[5,12]]
autocomplete.attach()
expect(editor.lineForBufferRow(5)).toBe " concat = items.shift();"
expect(editor.getCursorBufferPosition()).toEqual [5,12]
expect(editor.getSelection().getBufferRange().isEmpty()).toBeTruthy()
expect(autocomplete.list.find('li').length).toBe 0
it 'replaces selection with selected match, moves the cursor to the end of the match, and removes the autocomplete menu', ->
editor.getBuffer().insert([10,0] ,"extra:sort:extra")
editor.setSelectedBufferRange [[10,7], [10,9]]
autocomplete.attach()
expect(editor.lineForBufferRow(10)).toBe "extra:shift:extra"
expect(editor.getCursorBufferPosition()).toEqual [10,11]
expect(editor.getSelection().isEmpty()).toBeTruthy()
expect(editor.find('.autocomplete')).not.toExist()
describe "when the editor is scrolled to the right", ->
it "does not scroll it to the left", ->
editor.width(300)
editor.height(300)
editor.attachToDom()
editor.setCursorBufferPosition([6, 6])
previousScrollLeft = editor.scrollLeft()
autocomplete.attach()
expect(editor.scrollLeft()).toBe previousScrollLeft
describe 'core:confirm event', ->
describe "where there are matches", ->
describe "where there is no selection", ->
it "closes the menu and moves the cursor to the end", ->
editor.getBuffer().insert([10,0] ,"extra:sh:extra")
editor.setCursorBufferPosition([10,8])
autocomplete.attach()
expect(editor.lineForBufferRow(10)).toBe "extra:shift:extra"
expect(editor.getCursorBufferPosition()).toEqual [10,11]
expect(editor.getSelection().isEmpty()).toBeTruthy()
expect(editor.find('.autocomplete')).not.toExist()
describe 'core:cancel event', ->
describe "when there are no matches", ->
it "closes the menu without changing the buffer", ->
editor.getBuffer().insert([10,0] ,"xxx")
editor.setCursorBufferPosition [10, 3]
autocomplete.attach()
expect(autocomplete.error).toHaveText "No matches found"
miniEditor.trigger "core:cancel"
expect(editor.lineForBufferRow(10)).toBe "xxx"
expect(editor.getCursorBufferPosition()).toEqual [10,3]
expect(editor.getSelection().isEmpty()).toBeTruthy()
expect(editor.find('.autocomplete')).not.toExist()
it 'does not replace selection, removes autocomplete view and returns focus to editor', ->
editor.getBuffer().insert([10,0] ,"extra:so:extra")
editor.setSelectedBufferRange [[10,7], [10,8]]
originalSelectionBufferRange = editor.getSelection().getBufferRange()
autocomplete.attach()
editor.setCursorBufferPosition [0, 0] # even if selection changes before cancel, it should work
miniEditor.trigger "core:cancel"
expect(editor.lineForBufferRow(10)).toBe "extra:so:extra"
expect(editor.getSelection().getBufferRange()).toEqual originalSelectionBufferRange
expect(editor.find('.autocomplete')).not.toExist()
it "does not clear out a previously confirmed selection when canceling with an empty list", ->
editor.getBuffer().insert([10, 0], "ort\n")
editor.setCursorBufferPosition([10, 0])
autocomplete.attach()
miniEditor.trigger 'core:confirm'
expect(editor.lineForBufferRow(10)).toBe 'quicksort'
editor.setCursorBufferPosition([11, 0])
autocomplete.attach()
miniEditor.trigger 'core:cancel'
expect(editor.lineForBufferRow(10)).toBe 'quicksort'
it "restores the case of the prefix to the original value", ->
editor.getBuffer().insert([10,0] ,"extra:S:extra")
editor.setCursorBufferPosition([10,7])
autocomplete.attach()
expect(editor.lineForBufferRow(10)).toBe "extra:shift:extra"
expect(editor.getCursorBufferPosition()).toEqual [10,11]
autocomplete.trigger 'core:cancel'
expect(editor.lineForBufferRow(10)).toBe "extra:S:extra"
expect(editor.getCursorBufferPosition()).toEqual [10,7]
it "restores the original buffer contents even if there was an additional operation after autocomplete attached (regression)", ->
editor.getBuffer().insert([10,0] ,"extra:s:extra")
editor.setCursorBufferPosition([10,7])
autocomplete.attach()
editor.getBuffer().append('hi')
expect(editor.lineForBufferRow(10)).toBe "extra:shift:extra"
autocomplete.trigger 'core:cancel'
expect(editor.lineForBufferRow(10)).toBe "extra:s:extra"
editor.redo()
expect(editor.lineForBufferRow(10)).toBe "extra:s:extra"
describe 'move-up event', ->
it "highlights the previous match and replaces the selection with it", ->
editor.getBuffer().insert([10,0] ,"extra:t:extra")
editor.setCursorBufferPosition([10,6])
autocomplete.attach()
miniEditor.trigger "core:move-up"
expect(editor.lineForBufferRow(10)).toBe "extra:sort:extra"
expect(autocomplete.find('li:eq(0)')).not.toHaveClass('selected')
expect(autocomplete.find('li:eq(1)')).not.toHaveClass('selected')
expect(autocomplete.find('li:eq(7)')).toHaveClass('selected')
miniEditor.trigger "core:move-up"
expect(editor.lineForBufferRow(10)).toBe "extra:shift:extra"
expect(autocomplete.find('li:eq(0)')).not.toHaveClass('selected')
expect(autocomplete.find('li:eq(7)')).not.toHaveClass('selected')
expect(autocomplete.find('li:eq(6)')).toHaveClass('selected')
describe 'move-down event', ->
it "highlights the next match and replaces the selection with it", ->
editor.getBuffer().insert([10,0] ,"extra:s:extra")
editor.setCursorBufferPosition([10,7])
autocomplete.attach()
miniEditor.trigger "core:move-down"
expect(editor.lineForBufferRow(10)).toBe "extra:sort:extra"
expect(autocomplete.find('li:eq(0)')).not.toHaveClass('selected')
expect(autocomplete.find('li:eq(1)')).toHaveClass('selected')
miniEditor.trigger "core:move-down"
expect(editor.lineForBufferRow(10)).toBe "extra:shift:extra"
expect(autocomplete.find('li:eq(0)')).toHaveClass('selected')
expect(autocomplete.find('li:eq(1)')).not.toHaveClass('selected')
describe "when a match is clicked in the match list", ->
it "selects and confirms the match", ->
editor.getBuffer().insert([10,0] ,"t")
editor.setCursorBufferPosition([10, 0])
autocomplete.attach()
matchToSelect = autocomplete.list.find('li:eq(1)')
matchToSelect.mousedown()
expect(matchToSelect).toMatchSelector('.selected')
matchToSelect.mouseup()
expect(autocomplete.parent()).not.toExist()
expect(editor.lineForBufferRow(10)).toBe matchToSelect.text()
describe "when the mini-editor receives keyboard input", ->
beforeEach ->
editor.attachToDom()
describe "when text is removed from the mini-editor", ->
it "reloads the match list based on the mini-editor's text", ->
editor.getBuffer().insert([10,0], "t")
editor.setCursorBufferPosition([10,0])
autocomplete.attach()
expect(autocomplete.list.find('li').length).toBe 8
miniEditor.textInput('c')
window.advanceClock(autocomplete.inputThrottle)
expect(autocomplete.list.find('li').length).toBe 3
miniEditor.backspace()
window.advanceClock(autocomplete.inputThrottle)
expect(autocomplete.list.find('li').length).toBe 8
describe "when the text contains only word characters", ->
it "narrows the list of completions with the fuzzy match algorithm", ->
editor.getBuffer().insert([10,0] ,"t")
editor.setCursorBufferPosition([10,0])
autocomplete.attach()
expect(autocomplete.list.find('li').length).toBe 8
miniEditor.textInput('i')
window.advanceClock(autocomplete.inputThrottle)
expect(autocomplete.list.find('li').length).toBe 4
expect(autocomplete.list.find('li:eq(0)')).toHaveText 'pivot'
expect(autocomplete.list.find('li:eq(0)')).toHaveClass 'selected'
expect(autocomplete.list.find('li:eq(1)')).toHaveText 'right'
expect(autocomplete.list.find('li:eq(2)')).toHaveText 'shift'
expect(autocomplete.list.find('li:eq(3)')).toHaveText 'quicksort'
expect(editor.lineForBufferRow(10)).toEqual 'pivot'
miniEditor.textInput('o')
window.advanceClock(autocomplete.inputThrottle)
expect(autocomplete.list.find('li').length).toBe 2
expect(autocomplete.list.find('li:eq(0)')).toHaveText 'pivot'
expect(autocomplete.list.find('li:eq(1)')).toHaveText 'quicksort'
describe "when a non-word character is typed in the mini-editor", ->
it "immediately confirms the current completion choice and inserts that character into the buffer", ->
editor.getBuffer().insert([10,0] ,"t")
editor.setCursorBufferPosition([10,0])
autocomplete.attach()
miniEditor.textInput('iv')
window.advanceClock(autocomplete.inputThrottle)
expect(autocomplete.list.find('li:eq(0)')).toHaveText 'pivot'
miniEditor.textInput(' ')
window.advanceClock(autocomplete.inputThrottle)
expect(autocomplete.parent()).not.toExist()
expect(editor.lineForBufferRow(10)).toEqual 'pivot '
describe 'when the mini-editor loses focus before the selection is confirmed', ->
it "cancels the autocomplete", ->
editor.attachToDom()
autocomplete.attach()
spyOn(autocomplete, "cancel")
editor.focus()
expect(autocomplete.cancel).toHaveBeenCalled()
describe ".attach()", ->
beforeEach ->
editor.attachToDom()
setEditorHeightInLines(editor, 13)
editor.resetDisplay() # Ensures the editor only has 13 lines visible
describe "when the autocomplete view fits below the cursor", ->
it "adds the autocomplete view to the editor below the cursor", ->
editor.setCursorBufferPosition [1, 2]
cursorPixelPosition = editor.pixelPositionForScreenPosition(editor.getCursorScreenPosition())
autocomplete.attach()
expect(editor.find('.autocomplete')).toExist()
expect(autocomplete.position().top).toBe cursorPixelPosition.top + editor.lineHeight
expect(autocomplete.position().left).toBe cursorPixelPosition.left
describe "when the autocomplete view does not fit below the cursor", ->
it "adds the autocomplete view to the editor above the cursor", ->
editor.setCursorScreenPosition([11, 0])
editor.insertText('t ')
editor.setCursorScreenPosition([11, 0])
cursorPixelPosition = editor.pixelPositionForScreenPosition(editor.getCursorScreenPosition())
autocomplete.attach()
expect(autocomplete.parent()).toExist()
autocompleteBottom = autocomplete.position().top + autocomplete.outerHeight()
expect(autocompleteBottom).toBe cursorPixelPosition.top
expect(autocomplete.position().left).toBe cursorPixelPosition.left
it "updates the position when the list is filtered and the height of the list decreases", ->
editor.setCursorScreenPosition([11, 0])
editor.insertText('s')
editor.setCursorScreenPosition([11, 0])
cursorPixelPosition = editor.pixelPositionForScreenPosition(editor.getCursorScreenPosition())
autocomplete.attach()
expect(autocomplete.parent()).toExist()
autocompleteBottom = autocomplete.position().top + autocomplete.outerHeight()
expect(autocompleteBottom).toBe cursorPixelPosition.top
expect(autocomplete.position().left).toBe cursorPixelPosition.left
miniEditor.textInput('sh')
window.advanceClock(autocomplete.inputThrottle)
expect(autocomplete.parent()).toExist()
autocompleteBottom = autocomplete.position().top + autocomplete.outerHeight()
expect(autocompleteBottom).toBe cursorPixelPosition.top
expect(autocomplete.position().left).toBe cursorPixelPosition.left
describe ".cancel()", ->
it "clears the mini-editor and unbinds autocomplete event handlers for move-up and move-down", ->
autocomplete.attach()
miniEditor.setText('foo')
autocomplete.cancel()
expect(miniEditor.getText()).toBe ''
editor.trigger 'core:move-down'
expect(editor.getCursorBufferPosition().row).toBe 1
editor.trigger 'core:move-up'
expect(editor.getCursorBufferPosition().row).toBe 0
it "sets the width of the view to be wide enough to contain the longest completion without scrolling", ->
editor.attachToDom()
editor.insertText('thisIsAReallyReallyReallyLongCompletion ')
editor.moveCursorToBottom()
editor.insertNewline
editor.insertText('t')
autocomplete.attach()
expect(autocomplete.list.prop('scrollWidth')).toBe autocomplete.list.width()

View File

@ -1,26 +0,0 @@
.autocomplete {
&.select-list {
box-sizing: content-box;
margin-left: 0;
ol li {
padding: 5px 0 5px 0;
}
}
ol {
box-sizing: content-box;
position: relative;
overflow-y: scroll;
max-height: 200px;
}
span {
padding-left: 5px;
padding-right: 5px;
}
.error-message {
margin: 2px;
}
}

View File

@ -1,36 +0,0 @@
module.exports =
activate: ->
rootView.command 'autoflow:reflow-paragraph', '.editor', (e) =>
@reflowParagraph(e.currentTargetView())
reflowParagraph: (editor) ->
if range = editor.getCurrentParagraphBufferRange()
editor.getBuffer().change(range, @reflow(editor.getTextInRange(range)))
reflow: (text) ->
wrapColumn = config.getPositiveInt('editor.preferredLineLength', 80)
lines = []
currentLine = []
currentLineLength = 0
for segment in @segmentText(text.replace(/\n/g, ' '))
if @wrapSegment(segment, currentLineLength, wrapColumn)
lines.push(currentLine.join(''))
currentLine = []
currentLineLength = 0
currentLine.push(segment)
currentLineLength += segment.length
lines.push(currentLine.join(''))
lines.join('\n').replace(/\s+\n/g, '\n')
wrapSegment: (segment, currentLineLength, wrapColumn) ->
/\w/.test(segment) and
(currentLineLength + segment.length > wrapColumn) and
(currentLineLength > 0 or segment.length < wrapColumn)
segmentText: (text) ->
segments = []
re = /[\s]+|[^\s]+/g
segments.push(match[0]) while match = re.exec(text)
segments

View File

@ -1,2 +0,0 @@
'main': 'autoflow'
'description': 'Format the current paragraph to have lines no longer than 80 characters.\n\nThis packages uses the config value of `editor.preferredLineLength` when set.'

View File

@ -1,54 +0,0 @@
RootView = require 'root-view'
describe "Autoflow package", ->
editor = null
beforeEach ->
window.rootView = new RootView
rootView.open()
atom.activatePackage('autoflow')
editor = rootView.getActiveView()
config.set('editor.preferredLineLength', 30)
describe "autoflow:reflow-paragraph", ->
it "rearranges line breaks in the current paragraph to ensure lines are shorter than config.editor.preferredLineLength", ->
editor.setText """
This is a preceding paragraph, which shouldn't be modified by a reflow of the following paragraph.
The quick brown fox jumps over the lazy
dog. The preceding sentence contains every letter
in the entire English alphabet, which has absolutely no relevance
to this test.
This is a following paragraph, which shouldn't be modified by a reflow of the preciding paragraph.
"""
editor.setCursorBufferPosition([3, 5])
editor.trigger 'autoflow:reflow-paragraph'
expect(editor.getText()).toBe """
This is a preceding paragraph, which shouldn't be modified by a reflow of the following paragraph.
The quick brown fox jumps over
the lazy dog. The preceding
sentence contains every letter
in the entire English
alphabet, which has absolutely
no relevance to this test.
This is a following paragraph, which shouldn't be modified by a reflow of the preciding paragraph.
"""
it "allows for single words that exceed the preferred wrap column length", ->
editor.setText("this-is-a-super-long-word-that-shouldn't-break-autoflow and these are some smaller words")
editor.setCursorBufferPosition([0, 4])
editor.trigger 'autoflow:reflow-paragraph'
expect(editor.getText()).toBe """
this-is-a-super-long-word-that-shouldn't-break-autoflow
and these are some smaller
words
"""

View File

@ -1,2 +0,0 @@
'.editor':
'ctrl-j': 'editor:go-to-matching-bracket'

View File

@ -1,230 +0,0 @@
_ = require 'underscore'
{$$} = require 'space-pen'
{Range} = require 'telepath'
module.exports =
pairedCharacters:
'(': ')'
'[': ']'
'{': '}'
'"': '"'
"'": "'"
startPairMatches:
'(': ')'
'[': ']'
'{': '}'
endPairMatches:
')': '('
']': '['
'}': '{'
pairHighlighted: false
activate: ->
rootView.eachEditor (editor) => @subscribeToEditor(editor) if editor.attached
rootView.eachEditSession (editSession) => @subscribeToEditSession(editSession)
subscribeToEditor: (editor) ->
editor.on 'cursor:moved.bracket-matcher', => @updateMatch(editor)
editor.command 'editor:go-to-matching-bracket.bracket-matcher', =>
@goToMatchingPair(editor)
editor.on 'editor:will-be-removed', => editor.off('.bracket-matcher')
editor.startHighlightView = @addHighlightView(editor)
editor.endHighlightView = @addHighlightView(editor)
addHighlightView: (editor) ->
view = $$ -> @div class: 'bracket-matcher', style: 'display: none'
editor.underlayer.append(view)
view
goToMatchingPair: (editor) ->
return unless @pairHighlighted
return unless underlayer = editor.getPane()?.find('.underlayer')
return unless underlayer.isVisible()
position = editor.getCursorBufferPosition()
previousPosition = position.translate([0, -1])
startPosition = underlayer.find('.bracket-matcher:first').data('bufferPosition')
endPosition = underlayer.find('.bracket-matcher:last').data('bufferPosition')
if position.isEqual(startPosition)
editor.setCursorBufferPosition(endPosition.translate([0, 1]))
else if previousPosition.isEqual(startPosition)
editor.setCursorBufferPosition(endPosition)
else if position.isEqual(endPosition)
editor.setCursorBufferPosition(startPosition.translate([0, 1]))
else if previousPosition.isEqual(endPosition)
editor.setCursorBufferPosition(startPosition)
moveHighlightViews: (editor, bufferRange) ->
{ start, end } = Range.fromObject(bufferRange)
startPixelPosition = editor.pixelPositionForBufferPosition(start)
endPixelPosition = editor.pixelPositionForBufferPosition(end)
@moveHighlightView
editor: editor
view: editor.startHighlightView
bufferPosition: start
pixelPosition: startPixelPosition
@moveHighlightView
editor: editor
view: editor.endHighlightView
bufferPosition: end
pixelPosition: endPixelPosition
moveHighlightView: ({editor, view, bufferPosition, pixelPosition}) ->
view.data('bufferPosition', bufferPosition)
view.css
display: 'block'
top: pixelPosition.top
left: pixelPosition.left
width: editor.charWidth
height: editor.lineHeight
hideHighlightViews: (editor) ->
editor.startHighlightView.hide()
editor.endHighlightView.hide()
findCurrentPair: (editor, buffer, matches) ->
position = editor.getCursorBufferPosition()
currentPair = buffer.getTextInRange(Range.fromPointWithDelta(position, 0, 1))
unless matches[currentPair]
position = position.translate([0, -1])
currentPair = buffer.getTextInRange(Range.fromPointWithDelta(position, 0, 1))
matchingPair = matches[currentPair]
if matchingPair
{position, currentPair, matchingPair}
else
{}
findMatchingEndPair: (buffer, startPairPosition, startPair, endPair) ->
scanRange = new Range(startPairPosition.translate([0, 1]), buffer.getEofPosition())
regex = new RegExp("[#{_.escapeRegExp(startPair + endPair)}]", 'g')
endPairPosition = null
unpairedCount = 0
buffer.scanInRange regex, scanRange, ({match, range, stop}) =>
if match[0] is startPair
unpairedCount++
else if match[0] is endPair
unpairedCount--
endPairPosition = range.start
stop() if unpairedCount < 0
endPairPosition
findMatchingStartPair: (buffer, endPairPosition, startPair, endPair) ->
scanRange = new Range([0, 0], endPairPosition)
regex = new RegExp("[#{_.escapeRegExp(startPair + endPair)}]", 'g')
startPairPosition = null
unpairedCount = 0
buffer.backwardsScanInRange regex, scanRange, ({match, range, stop}) =>
if match[0] is endPair
unpairedCount++
else if match[0] is startPair
unpairedCount--
startPairPosition = range.start
stop() if unpairedCount < 0
startPairPosition
updateMatch: (editor) ->
return unless underlayer = editor.getPane()?.find('.underlayer')
@hideHighlightViews(editor) if @pairHighlighted
@pairHighlighted = false
return unless editor.getSelection().isEmpty()
return if editor.isFoldedAtCursorRow()
buffer = editor.getBuffer()
{position, currentPair, matchingPair} = @findCurrentPair(editor, buffer, @startPairMatches)
if position
matchPosition = @findMatchingEndPair(buffer, position, currentPair, matchingPair)
else
{position, currentPair, matchingPair} = @findCurrentPair(editor, buffer, @endPairMatches)
if position
matchPosition = @findMatchingStartPair(buffer, position, matchingPair, currentPair)
if position? and matchPosition?
@moveHighlightViews(editor, [position, matchPosition])
@pairHighlighted = true
subscribeToEditSession: (editSession) ->
@bracketMarkers = []
_.adviseBefore editSession, 'insertText', (text) =>
return true if editSession.hasMultipleCursors()
cursorBufferPosition = editSession.getCursorBufferPosition()
previousCharacter = editSession.getTextInBufferRange([cursorBufferPosition.add([0, -1]), cursorBufferPosition])
nextCharacter = editSession.getTextInBufferRange([cursorBufferPosition, cursorBufferPosition.add([0,1])])
if @isOpeningBracket(text) and not editSession.getSelection().isEmpty()
@wrapSelectionInBrackets(editSession, text)
return false
hasWordAfterCursor = /\w/.test(nextCharacter)
hasWordBeforeCursor = /\w/.test(previousCharacter)
autoCompleteOpeningBracket = @isOpeningBracket(text) and not hasWordAfterCursor and not (@isQuote(text) and hasWordBeforeCursor)
skipOverExistingClosingBracket = false
if @isClosingBracket(text) and nextCharacter == text
if bracketMarker = _.find(@bracketMarkers, (marker) => marker.isValid() and marker.getBufferRange().end.isEqual(cursorBufferPosition))
skipOverExistingClosingBracket = true
if skipOverExistingClosingBracket
bracketMarker.destroy()
_.remove(@bracketMarkers, bracketMarker)
editSession.moveCursorRight()
false
else if autoCompleteOpeningBracket
editSession.insertText(text + @pairedCharacters[text])
editSession.moveCursorLeft()
range = [cursorBufferPosition, cursorBufferPosition.add([0, text.length])]
@bracketMarkers.push editSession.markBufferRange(range)
false
_.adviseBefore editSession, 'backspace', =>
return if editSession.hasMultipleCursors()
return unless editSession.getSelection().isEmpty()
cursorBufferPosition = editSession.getCursorBufferPosition()
previousCharacter = editSession.getTextInBufferRange([cursorBufferPosition.add([0, -1]), cursorBufferPosition])
nextCharacter = editSession.getTextInBufferRange([cursorBufferPosition, cursorBufferPosition.add([0,1])])
if @pairedCharacters[previousCharacter] is nextCharacter
editSession.transact =>
editSession.moveCursorLeft()
editSession.delete()
editSession.delete()
false
wrapSelectionInBrackets: (editSession, bracket) ->
pair = @pairedCharacters[bracket]
editSession.mutateSelectedText (selection) =>
return if selection.isEmpty()
range = selection.getBufferRange()
options = isReversed: selection.isReversed()
selection.insertText("#{bracket}#{selection.getText()}#{pair}")
selectionStart = range.start.add([0, 1])
if range.start.row is range.end.row
selectionEnd = range.end.add([0, 1])
else
selectionEnd = range.end
selection.setBufferRange([selectionStart, selectionEnd], options)
isQuote: (string) ->
/'|"/.test(string)
getInvertedPairedCharacters: ->
return @invertedPairedCharacters if @invertedPairedCharacters
@invertedPairedCharacters = {}
for open, close of @pairedCharacters
@invertedPairedCharacters[close] = open
@invertedPairedCharacters
isOpeningBracket: (string) ->
@pairedCharacters[string]?
isClosingBracket: (string) ->
@getInvertedPairedCharacters()[string]?

View File

@ -1,2 +0,0 @@
'main': './lib/bracket-matcher'
'description': 'Highlight the matching bracket for the `(){}[]` character under the cursor.\n\nMove the cursor to the matching bracket with `ctrl-j`.'

View File

@ -1,273 +0,0 @@
RootView = require 'root-view'
describe "bracket matching", ->
[editor, editSession, buffer] = []
beforeEach ->
window.rootView = new RootView
rootView.open('sample.js')
atom.activatePackage('bracket-matcher')
rootView.attachToDom()
editor = rootView.getActiveView()
editSession = editor.activeEditSession
buffer = editSession.buffer
describe "matching bracket highlighting", ->
describe "when the cursor is before a starting pair", ->
it "highlights the starting pair and ending pair", ->
editor.moveCursorToEndOfLine()
editor.moveCursorLeft()
expect(editor.underlayer.find('.bracket-matcher:visible').length).toBe 2
expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([0,28])
expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([12,0])
describe "when the cursor is after a starting pair", ->
it "highlights the starting pair and ending pair", ->
editor.moveCursorToEndOfLine()
expect(editor.underlayer.find('.bracket-matcher:visible').length).toBe 2
expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([0,28])
expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([12,0])
describe "when the cursor is before an ending pair", ->
it "highlights the starting pair and ending pair", ->
editor.moveCursorToBottom()
editor.moveCursorLeft()
editor.moveCursorLeft()
expect(editor.underlayer.find('.bracket-matcher:visible').length).toBe 2
expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([12,0])
expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([0,28])
describe "when the cursor is after an ending pair", ->
it "highlights the starting pair and ending pair", ->
editor.moveCursorToBottom()
editor.moveCursorLeft()
expect(editor.underlayer.find('.bracket-matcher:visible').length).toBe 2
expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([12,0])
expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([0,28])
describe "when the cursor is moved off a pair", ->
it "removes the starting pair and ending pair highlights", ->
editor.moveCursorToEndOfLine()
expect(editor.underlayer.find('.bracket-matcher:visible').length).toBe 2
editor.moveCursorToBeginningOfLine()
expect(editor.underlayer.find('.bracket-matcher:visible').length).toBe 0
describe "pair balancing", ->
describe "when a second starting pair preceeds the first ending pair", ->
it "advances to the second ending pair", ->
editor.setCursorBufferPosition([8,42])
expect(editor.underlayer.find('.bracket-matcher:visible').length).toBe 2
expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([8,42])
expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([8,54])
describe "when editor:go-to-matching-bracket is triggered", ->
describe "when the cursor is before the starting pair", ->
it "moves the cursor to after the ending pair", ->
editor.moveCursorToEndOfLine()
editor.moveCursorLeft()
editor.trigger "editor:go-to-matching-bracket"
expect(editor.getCursorBufferPosition()).toEqual [12, 1]
describe "when the cursor is after the starting pair", ->
it "moves the cursor to before the ending pair", ->
editor.moveCursorToEndOfLine()
editor.trigger "editor:go-to-matching-bracket"
expect(editor.getCursorBufferPosition()).toEqual [12, 0]
describe "when the cursor is before the ending pair", ->
it "moves the cursor to after the starting pair", ->
editor.setCursorBufferPosition([12, 0])
editor.trigger "editor:go-to-matching-bracket"
expect(editor.getCursorBufferPosition()).toEqual [0, 29]
describe "when the cursor is after the ending pair", ->
it "moves the cursor to before the starting pair", ->
editor.setCursorBufferPosition([12, 1])
editor.trigger "editor:go-to-matching-bracket"
expect(editor.getCursorBufferPosition()).toEqual [0, 28]
describe "matching bracket insertion", ->
beforeEach ->
editSession.buffer.setText("")
describe "when more than one character is inserted", ->
it "does not insert a matching bracket", ->
editSession.insertText("woah(")
expect(editSession.buffer.getText()).toBe "woah("
describe "when there is a word character after the cursor", ->
it "does not insert a matching bracket", ->
editSession.buffer.setText("ab")
editSession.setCursorBufferPosition([0, 1])
editSession.insertText("(")
expect(editSession.buffer.getText()).toBe "a(b"
describe "when there are multiple cursors", ->
it "inserts ) at each cursor", ->
editSession.buffer.setText("()\nab\n[]\n12")
editSession.setCursorBufferPosition([3, 1])
editSession.addCursorAtBufferPosition([2, 1])
editSession.addCursorAtBufferPosition([1, 1])
editSession.addCursorAtBufferPosition([0, 1])
editSession.insertText ')'
expect(editSession.buffer.getText()).toBe "())\na)b\n[)]\n1)2"
describe "when there is a non-word character after the cursor", ->
it "inserts a closing bracket after an opening bracket is inserted", ->
editSession.buffer.setText("}")
editSession.setCursorBufferPosition([0, 0])
editSession.insertText '{'
expect(buffer.lineForRow(0)).toBe "{}}"
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
describe "when the cursor is at the end of the line", ->
it "inserts a closing bracket after an opening bracket is inserted", ->
editSession.buffer.setText("")
editSession.insertText '{'
expect(buffer.lineForRow(0)).toBe "{}"
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
editSession.buffer.setText("")
editSession.insertText '('
expect(buffer.lineForRow(0)).toBe "()"
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
editSession.buffer.setText("")
editSession.insertText '['
expect(buffer.lineForRow(0)).toBe "[]"
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
editSession.buffer.setText("")
editSession.insertText '"'
expect(buffer.lineForRow(0)).toBe '""'
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
editSession.buffer.setText("")
editSession.insertText "'"
expect(buffer.lineForRow(0)).toBe "''"
expect(editSession.getCursorBufferPosition()).toEqual([0,1])
describe "when the cursor is on a closing bracket and a closing bracket is inserted", ->
describe "when the closing bracket was there previously", ->
it "inserts a closing bracket", ->
editSession.insertText '()x'
editSession.setCursorBufferPosition([0, 1])
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "())x"
expect(editSession.getCursorBufferPosition().column).toBe 2
describe "when the closing bracket was automatically inserted from inserting an opening bracket", ->
it "only moves cursor over the closing bracket one time", ->
editSession.insertText '('
expect(buffer.lineForRow(0)).toBe "()"
editSession.setCursorBufferPosition([0, 1])
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "()"
expect(editSession.getCursorBufferPosition()).toEqual [0, 2]
editSession.setCursorBufferPosition([0, 1])
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "())"
expect(editSession.getCursorBufferPosition()).toEqual [0, 2]
it "moves cursor over the closing bracket after other text is inserted", ->
editSession.insertText '('
editSession.insertText 'ok cool'
expect(buffer.lineForRow(0)).toBe "(ok cool)"
editSession.setCursorBufferPosition([0, 8])
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "(ok cool)"
expect(editSession.getCursorBufferPosition()).toEqual [0, 9]
it "works with nested brackets", ->
editSession.insertText '('
editSession.insertText '1'
editSession.insertText '('
editSession.insertText '2'
expect(buffer.lineForRow(0)).toBe "(1(2))"
editSession.setCursorBufferPosition([0, 4])
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "(1(2))"
expect(editSession.getCursorBufferPosition()).toEqual [0, 5]
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "(1(2))"
expect(editSession.getCursorBufferPosition()).toEqual [0, 6]
it "works with mixed brackets", ->
editSession.insertText '('
editSession.insertText '}'
expect(buffer.lineForRow(0)).toBe "(})"
editSession.insertText ')'
expect(buffer.lineForRow(0)).toBe "(})"
expect(editSession.getCursorBufferPosition()).toEqual [0, 3]
it "closes brackets with the same begin/end character correctly", ->
editSession.insertText '"'
editSession.insertText 'ok'
expect(buffer.lineForRow(0)).toBe '"ok"'
expect(editSession.getCursorBufferPosition()).toEqual [0, 3]
editSession.insertText '"'
expect(buffer.lineForRow(0)).toBe '"ok"'
expect(editSession.getCursorBufferPosition()).toEqual [0, 4]
describe "when there is text selected on a single line", ->
it "wraps the selection with brackets", ->
editSession.insertText 'text'
editSession.moveCursorToBottom()
editSession.selectToTop()
editSession.selectAll()
editSession.insertText '('
expect('(text)').toBe buffer.getText()
expect(editSession.getSelectedBufferRange()).toEqual [[0, 1], [0, 5]]
expect(editSession.getSelection().isReversed()).toBeTruthy()
describe "when there is text selected on multiple lines", ->
it "wraps the selection with brackets", ->
editSession.insertText 'text\nabcd'
editSession.moveCursorToBottom()
editSession.selectToTop()
editSession.selectAll()
editSession.insertText '('
expect('(text\nabcd)').toBe buffer.getText()
expect(editSession.getSelectedBufferRange()).toEqual [[0, 1], [1, 4]]
expect(editSession.getSelection().isReversed()).toBeTruthy()
describe "when inserting a quote", ->
describe "when a word character is before the cursor", ->
it "does not automatically insert closing quote", ->
editSession.buffer.setText("abc")
editSession.setCursorBufferPosition([0, 3])
editSession.insertText '"'
expect(buffer.lineForRow(0)).toBe "abc\""
editSession.buffer.setText("abc")
editSession.setCursorBufferPosition([0, 3])
editSession.insertText '\''
expect(buffer.lineForRow(0)).toBe "abc\'"
describe "when a non word character is before the cursor", ->
it "automatically insert closing quote", ->
editSession.buffer.setText("ab@")
editSession.setCursorBufferPosition([0, 3])
editSession.insertText '"'
expect(buffer.lineForRow(0)).toBe "ab@\"\""
expect(editSession.getCursorBufferPosition()).toEqual [0, 4]
describe "when the cursor is on an empty line", ->
it "automatically insert closing quote", ->
editSession.buffer.setText("")
editSession.setCursorBufferPosition([0, 0])
editSession.insertText '"'
expect(buffer.lineForRow(0)).toBe "\"\""
expect(editSession.getCursorBufferPosition()).toEqual [0, 1]
describe "matching bracket deletion", ->
it "deletes the end bracket when it directly proceeds a begin bracket that is being backspaced", ->
buffer.setText("")
editSession.setCursorBufferPosition([0, 0])
editSession.insertText '{'
expect(buffer.lineForRow(0)).toBe "{}"
editSession.backspace()
expect(buffer.lineForRow(0)).toBe ""

View File

@ -1,4 +0,0 @@
.bracket-matcher {
border-bottom: 1px dotted lime;
position: absolute;
}

View File

@ -1,4 +0,0 @@
'body':
'meta-C': 'collaboration:copy-session-id'
'meta-H': 'collaboration:start-session'
'meta-K': 'collaboration:join-session'

View File

@ -1,48 +0,0 @@
require 'atom'
require 'window'
$ = require 'jquery'
{$$} = require 'space-pen'
Session = require './session'
window.setDimensions(width: 350, height: 125)
window.setUpEnvironment('editor')
config.load() # FIXME this should happen earlier during environment setup
{sessionId} = atom.getLoadSettings()
loadingView = $$ ->
@div style: 'margin: 10px', =>
@h4 style: 'text-align: center', 'Joining Session'
@div class: 'progress progress-striped active', style: 'margin-bottom: 10px', =>
@div class: 'progress-bar', style: 'width: 0%'
@div class: 'progress-bar-message', 'Establishing connection\u2026'
$(window.rootViewParentSelector).append(loadingView)
atom.show()
updateProgressBar = (message, percentDone) ->
loadingView.find('.progress-bar-message').text("#{message}\u2026")
loadingView.find('.progress-bar').css('width', "#{percentDone}%")
guestSession = new Session(id: sessionId)
guestSession.on 'started', ->
atom.windowState = guestSession.getDocument().get('windowState')
window.site = guestSession.getSite()
loadingView.remove()
window.startEditorWindow()
guestSession.on 'connection-opened', ->
updateProgressBar('Downloading session data', 25)
guestSession.on 'connection-document-received', ->
updateProgressBar('Synchronizing repository', 50)
guestSession.start()
operationsDone = -1
guestSession.on 'mirror-progress', (message, command, operationCount) ->
operationsDone++
percentDone = Math.round((operationsDone / operationCount) * 50) + 50
updateProgressBar(message, percentDone)
atom.guestSession = guestSession

View File

@ -1,50 +0,0 @@
Session = require './session'
JoinPromptView = require './join-prompt-view'
HostStatusBar = require './host-status-bar'
GuestStatusBar = require './guest-status-bar'
{ParticipantView, ParticipantViewContainer} = require './participant-view'
module.exports =
activate: ->
hostView = null
participantViews = new ParticipantViewContainer().attach()
if atom.getLoadSettings().sessionId
session = atom.guestSession
participantViews.add(session, session.getSelfParticipant())
for participant in session.getOtherParticipants()
participantViews.add(session, participant)
else
session = new Session(site: window.site)
@handleEvents(session)
session.on 'started', (participants) =>
participantViews.add(session, session.getSelfParticipant())
session.on 'participant-entered', (participant) =>
participantViews.add(session, participant)
session.on 'participant-exited', (participant) =>
participantViews.remove(participant)
rootView.eachPane (pane) ->
setTimeout ->
buttons = if session.isLeader() then new HostStatusBar(session) else new GuestStatusBar(session)
buttons.insertAfter(pane.find('.git-branch'))
, 0
handleEvents: (session) ->
rootView.command 'collaboration:copy-session-id', ->
session.copySessionId()
rootView.command 'collaboration:start-session', ->
session.start()
session.copySessionId()
rootView.command 'collaboration:join-session', ->
new JoinPromptView (id) ->
return unless id
windowSettings =
bootstrapScript: require.resolve('collaboration/lib/bootstrap')
resourcePath: window.resourcePath
sessionId: id
atom.openWindow(windowSettings)

View File

@ -1,14 +0,0 @@
{$$, View} = require 'space-pen'
module.exports =
class HostStatusBar extends View
@content: ->
@div class: 'collaboration-status', =>
@span outlet: 'status', type: 'button', class: 'status guest'
initialize: (@session) ->
@session.on 'started stopped', @update
@update()
update: ->
# do stuff

View File

@ -1,28 +0,0 @@
{$$, View} = require 'space-pen'
module.exports =
class HostStatusBar extends View
@content: ->
@div class: 'collaboration-status', =>
@span outlet: 'status', type: 'button', class: 'status share'
@span outlet: 'connections', class: 'connections'
initialize: (@session) ->
@status.on 'click', =>
if @session.isListening()
@session.stop()
else
@status.addClass('running') # for immediate feedback to user
@session.start()
@session.copySessionId()
@session.on 'started stopped participant-entered participant-exited', @update
@update()
update: =>
if @session.isListening()
@status.addClass('running')
@connections.show().text(@session.getOtherParticipants().length)
else
@status.removeClass('running')
@connections.hide()

View File

@ -1,39 +0,0 @@
{View} = require 'space-pen'
Editor = require 'editor'
$ = require 'jquery'
_ = require 'underscore'
{getSessionId} = require './session-utils'
module.exports =
class JoinPromptView extends View
@activate: -> new Prompt
@content: ->
@div class: 'overlay from-top', =>
@subview 'miniEditor', new Editor(mini: true)
@div class: 'message', outlet: 'message', 'Enter a session id to join'
initialize: (@confirmed) ->
@miniEditor.on 'focusout', => @remove()
@on 'core:confirm', => @confirm()
@on 'core:cancel', => @remove()
clipboard = pasteboard.read()[0]
if sessionId = getSessionId(clipboard)
@miniEditor.setText(sessionId)
@attach()
beforeRemove: ->
@previouslyFocusedElement?.focus()
@miniEditor.setText('')
confirm: ->
@confirmed(@miniEditor.getText().trim())
@remove()
attach: ->
@previouslyFocusedElement = $(':focus')
rootView.append(this)
@miniEditor.focus()

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