mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-11 19:17:09 +03:00
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:
commit
c6d1409151
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -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
2
.pairs
@ -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
|
||||
|
19
CHANGELOG.md
19
CHANGELOG.md
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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'])
|
||||
|
@ -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
38
atom.sh
@ -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
|
||||
|
@ -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:
|
||||
|
92
package.json
92
package.json
@ -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": {
|
||||
|
@ -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
32
script/utils/update-octicons
Executable 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"
|
@ -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", ->
|
||||
|
@ -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()
|
@ -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()
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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'
|
@ -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 ->
|
||||
|
@ -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()
|
@ -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)'
|
@ -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']
|
32
spec/app/theme-manager-spec.coffee
Normal file
32
spec/app/theme-manager-spec.coffee
Normal 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()
|
@ -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"
|
||||
|
@ -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]]
|
||||
|
@ -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++
|
||||
|
@ -1,3 +0,0 @@
|
||||
module.exports =
|
||||
activate: -> @activateCalled = true
|
||||
activateConfig: -> @activateConfigCalled = true
|
20
spec/fixtures/sample-with-comments.js
vendored
Normal file
20
spec/fixtures/sample-with-comments.js
vendored
Normal 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));
|
||||
};
|
@ -37,7 +37,6 @@ beforeEach ->
|
||||
window.project = new Project(fsUtils.resolveOnLoadPath('fixtures'))
|
||||
|
||||
window.resetTimeouts()
|
||||
atom.windowMode = 'editor'
|
||||
atom.packageStates = {}
|
||||
spyOn(atom, 'saveWindowState')
|
||||
syntax.clearGrammarOverrides()
|
||||
|
@ -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')
|
||||
|
@ -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'
|
||||
|
@ -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()
|
||||
|
@ -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
|
@ -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()
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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.
|
||||
|
@ -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.
|
||||
#
|
||||
|
@ -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}.
|
||||
|
@ -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()
|
||||
|
@ -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'})
|
@ -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()
|
||||
|
@ -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'
|
@ -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
|
@ -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()
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -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.
|
||||
#
|
||||
|
@ -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())
|
@ -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')
|
@ -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}
|
@ -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?()
|
||||
|
@ -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: ->
|
||||
|
@ -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')
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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) ->
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
@ -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]*
|
@ -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)
|
@ -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()
|
47
src/app/theme-manager.coffee
Normal file
47
src/app/theme-manager.coffee
Normal 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')
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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={}) ->
|
||||
|
@ -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
|
||||
|
@ -40,7 +40,6 @@ class AtomWindow
|
||||
paths = [
|
||||
'src/stdlib'
|
||||
'src/app'
|
||||
'src/packages'
|
||||
'src'
|
||||
'vendor'
|
||||
'static'
|
||||
|
@ -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}"
|
@ -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.')
|
||||
|
@ -1,3 +0,0 @@
|
||||
'.archive-view':
|
||||
'k': 'core:move-up'
|
||||
'j': 'core:move-down'
|
@ -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()
|
@ -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)
|
@ -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))
|
@ -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()
|
@ -1,3 +0,0 @@
|
||||
'description': 'View the files and folders inside archive files'
|
||||
'main': './lib/archive-edit-session'
|
||||
'deferredDeserializers': ['ArchiveEditSession']
|
@ -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()
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
'.editor':
|
||||
'ctrl-space': 'autocomplete:attach'
|
||||
|
||||
'.autocomplete .editor':
|
||||
'ctrl-space': 'core:cancel'
|
||||
|
||||
'.autocomplete .mini.editor input':
|
||||
'enter': 'core:confirm'
|
@ -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()
|
@ -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 = []
|
@ -1,4 +0,0 @@
|
||||
'main': './lib/autocomplete'
|
||||
'description': 'Display possible completions from the current editor with `ctrl-space`.'
|
||||
'activationEvents':
|
||||
'autocomplete:attach': '.editor'
|
@ -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()
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
@ -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.'
|
@ -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
|
||||
"""
|
@ -1,2 +0,0 @@
|
||||
'.editor':
|
||||
'ctrl-j': 'editor:go-to-matching-bracket'
|
@ -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]?
|
@ -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`.'
|
@ -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 ""
|
@ -1,4 +0,0 @@
|
||||
.bracket-matcher {
|
||||
border-bottom: 1px dotted lime;
|
||||
position: absolute;
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
'body':
|
||||
'meta-C': 'collaboration:copy-session-id'
|
||||
'meta-H': 'collaboration:start-session'
|
||||
'meta-K': 'collaboration:join-session'
|
@ -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
|
@ -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)
|
@ -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
|
@ -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()
|
@ -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
Loading…
Reference in New Issue
Block a user