mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-12-28 09:01:33 +03:00
Merge branch 'master' into some-files-are-just-too-evil
This commit is contained in:
commit
6a37f9dad4
@ -1,3 +0,0 @@
|
||||
tags
|
||||
docs/api
|
||||
.git
|
@ -1,3 +1,5 @@
|
||||
* Improved: Faster and better looking find and replace
|
||||
* Improved: Double-click selection behavior between word/non-word
|
||||
* Added: Solarized theme now bundled by default
|
||||
* Added: Base16 Tomorrow Dark theme now bundled by default
|
||||
|
||||
|
@ -16,8 +16,6 @@
|
||||
styleguides
|
||||
* Include thoughtfully worded [Jasmine](http://pivotal.github.com/jasmine/)
|
||||
specs
|
||||
* Style new elements in both the light and dark default themes when
|
||||
appropriate
|
||||
* Add 3rd-party packages as a `package.json` dependency
|
||||
* Commit messages are in the present tense
|
||||
* Commit messages that improve the format of the code start with :lipstick:
|
||||
|
@ -54,6 +54,7 @@ module.exports = (grunt) ->
|
||||
glob_to_multiple:
|
||||
expand: true
|
||||
src: [
|
||||
'menus/*.cson'
|
||||
'keymaps/*.cson'
|
||||
'static/**/*.cson'
|
||||
]
|
||||
@ -88,6 +89,8 @@ module.exports = (grunt) ->
|
||||
level: 'error'
|
||||
max_line_length:
|
||||
level: 'ignore'
|
||||
indentation:
|
||||
level: 'ignore'
|
||||
src: [
|
||||
'dot-atom/**/*.coffee'
|
||||
'exports/**/*.coffee'
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
![atom](https://s3.amazonaws.com/speakeasy/apps/icons/27/medium/7db16e44-ba57-11e2-8c6f-981faf658e00.png)
|
||||
|
||||
Check out our [guides](https://atom-docs.githubapp.com/v20.0/index.html) and [API documentation](https://atom-docs.githubapp.com/v20.0/api/index.html).
|
||||
Check out our [guides](https://atom-docs.githubapp.com/v26.0/index.html) and [API documentation](https://atom-docs.githubapp.com/v26.0/api/index.html).
|
||||
|
||||
## Installing
|
||||
|
||||
|
3
atom.sh
3
atom.sh
@ -9,7 +9,7 @@ if [ ! -d $ATOM_PATH ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while getopts ":whvft-:" opt; do
|
||||
while getopts ":wtfvhs-:" opt; do
|
||||
case "$opt" in
|
||||
-)
|
||||
case "${OPTARG}" in
|
||||
@ -32,6 +32,7 @@ done
|
||||
|
||||
if [ $EXPECT_OUTPUT ]; then
|
||||
$ATOM_BINARY --executed-from="$(pwd)" --pid=$$ $@
|
||||
exit $?
|
||||
else
|
||||
open -a $ATOM_PATH -n --args --executed-from="$(pwd)" --pid=$$ $@
|
||||
fi
|
||||
|
@ -1,4 +1,9 @@
|
||||
require '../src/atom'
|
||||
require '../src/window'
|
||||
Atom = require '../src/atom'
|
||||
atom = new Atom()
|
||||
atom.show() unless atom.getLoadSettings().exitWhenDone
|
||||
window.atom = atom
|
||||
|
||||
{runSpecSuite} = require '../spec/jasmine-helper'
|
||||
|
||||
atom.openDevTools()
|
||||
|
@ -1,11 +1,9 @@
|
||||
require '../spec/spec-helper'
|
||||
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
{Point} = require 'telepath'
|
||||
Project = require 'project'
|
||||
fsUtils = require 'fs-utils'
|
||||
TokenizedBuffer = require 'tokenized-buffer'
|
||||
{$, _, Point, fs} = require 'atom'
|
||||
Project = require '../src/project'
|
||||
fsUtils = require '../src/fs-utils'
|
||||
TokenizedBuffer = require '../src/tokenized-buffer'
|
||||
|
||||
defaultCount = 100
|
||||
window.pbenchmark = (args...) -> window.benchmark(args..., profile: true)
|
||||
@ -13,7 +11,7 @@ window.fbenchmark = (args...) -> window.benchmark(args..., focused: true)
|
||||
window.fpbenchmark = (args...) -> window.benchmark(args..., profile: true, focused: true)
|
||||
window.pfbenchmark = window.fpbenchmark
|
||||
|
||||
window.benchmarkFixturesProject = new Project(fsUtils.resolveOnLoadPath('benchmark/fixtures'))
|
||||
window.benchmarkFixturesProject = new Project(fsUtils.resolveOnLoadPath('../benchmark/fixtures'))
|
||||
|
||||
beforeEach ->
|
||||
window.project = window.benchmarkFixturesProject
|
||||
|
@ -1,8 +1,6 @@
|
||||
require './benchmark-helper'
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
TokenizedBuffer = require 'tokenized-buffer'
|
||||
RootView = require 'root-view'
|
||||
{$, _, RootView} = require 'atom'
|
||||
TokenizedBuffer = require '../src/tokenized-buffer'
|
||||
|
||||
describe "editor.", ->
|
||||
editor = null
|
||||
@ -12,7 +10,6 @@ describe "editor.", ->
|
||||
window.rootView = new RootView
|
||||
window.rootView.attachToDom()
|
||||
|
||||
|
||||
rootView.width(1024)
|
||||
rootView.height(768)
|
||||
rootView.open() # open blank editor
|
||||
@ -62,6 +59,116 @@ describe "editor.", ->
|
||||
editor.insertText('"')
|
||||
editor.backspace()
|
||||
|
||||
describe "empty-vs-set-innerHTML.", ->
|
||||
[firstRow, lastRow] = []
|
||||
beforeEach ->
|
||||
firstRow = editor.getFirstVisibleScreenRow()
|
||||
lastRow = editor.getLastVisibleScreenRow()
|
||||
|
||||
benchmark "build-gutter-html.", 1000, ->
|
||||
editor.gutter.renderLineNumbers(null, firstRow, lastRow)
|
||||
|
||||
benchmark "set-innerHTML.", 1000, ->
|
||||
editor.gutter.renderLineNumbers(null, firstRow, lastRow)
|
||||
editor.gutter.lineNumbers[0].innerHtml = ''
|
||||
|
||||
benchmark "empty.", 1000, ->
|
||||
editor.gutter.renderLineNumbers(null, firstRow, lastRow)
|
||||
editor.gutter.lineNumbers.empty()
|
||||
|
||||
describe "positionLeftForLineAndColumn.", ->
|
||||
line = null
|
||||
beforeEach ->
|
||||
editor.scrollTop(2000)
|
||||
editor.resetDisplay()
|
||||
line = editor.lineElementForScreenRow(106)[0]
|
||||
|
||||
describe "one-line.", ->
|
||||
beforeEach ->
|
||||
editor.clearCharacterWidthCache()
|
||||
|
||||
benchmark "uncached", 5000, ->
|
||||
editor.positionLeftForLineAndColumn(line, 106, 82)
|
||||
editor.clearCharacterWidthCache()
|
||||
|
||||
benchmark "cached", 5000, ->
|
||||
editor.positionLeftForLineAndColumn(line, 106, 82)
|
||||
|
||||
describe "multiple-lines.", ->
|
||||
[firstRow, lastRow] = []
|
||||
beforeEach ->
|
||||
firstRow = editor.getFirstVisibleScreenRow()
|
||||
lastRow = editor.getLastVisibleScreenRow()
|
||||
|
||||
benchmark "cache-entire-visible-area", 100, ->
|
||||
for i in [firstRow..lastRow]
|
||||
line = editor.lineElementForScreenRow(i)[0]
|
||||
editor.positionLeftForLineAndColumn(line, i, Math.max(0, editor.lineLengthForBufferRow(i)))
|
||||
|
||||
describe "text-rendering.", ->
|
||||
beforeEach ->
|
||||
editor.scrollTop(2000)
|
||||
|
||||
benchmark "resetDisplay", 50, ->
|
||||
editor.resetDisplay()
|
||||
|
||||
benchmark "htmlForScreenRows", 1000, ->
|
||||
lastRow = editor.getLastScreenRow()
|
||||
editor.htmlForScreenRows(0, lastRow)
|
||||
|
||||
benchmark "htmlForScreenRows.htmlParsing", 50, ->
|
||||
lastRow = editor.getLastScreenRow()
|
||||
html = editor.htmlForScreenRows(0, lastRow)
|
||||
|
||||
div = document.createElement('div')
|
||||
div.innerHTML = html
|
||||
|
||||
describe "gutter-api.", ->
|
||||
describe "getLineNumberElementsForClass.", ->
|
||||
beforeEach ->
|
||||
editor.gutter.addClassToLine(20, 'omgwow')
|
||||
editor.gutter.addClassToLine(40, 'omgwow')
|
||||
|
||||
benchmark "DOM", 20000, ->
|
||||
editor.gutter.getLineNumberElementsForClass('omgwow')
|
||||
|
||||
benchmark "getLineNumberElement.DOM", 20000, ->
|
||||
editor.gutter.getLineNumberElement(12)
|
||||
|
||||
benchmark "toggle-class", 2000, ->
|
||||
editor.gutter.addClassToLine(40, 'omgwow')
|
||||
editor.gutter.removeClassFromLine(40, 'omgwow')
|
||||
|
||||
describe "find-then-unset.", ->
|
||||
classes = ['one', 'two', 'three', 'four']
|
||||
|
||||
benchmark "single-class", 200, ->
|
||||
editor.gutter.addClassToLine(30, 'omgwow')
|
||||
editor.gutter.addClassToLine(40, 'omgwow')
|
||||
editor.gutter.removeClassFromAllLines('omgwow')
|
||||
|
||||
benchmark "multiple-class", 200, ->
|
||||
editor.gutter.addClassToLine(30, 'one')
|
||||
editor.gutter.addClassToLine(30, 'two')
|
||||
|
||||
editor.gutter.addClassToLine(40, 'two')
|
||||
editor.gutter.addClassToLine(40, 'three')
|
||||
editor.gutter.addClassToLine(40, 'four')
|
||||
|
||||
for klass in classes
|
||||
editor.gutter.removeClassFromAllLines(klass)
|
||||
|
||||
describe "line-htmlification.", ->
|
||||
div = null
|
||||
html = null
|
||||
beforeEach ->
|
||||
lastRow = editor.getLastScreenRow()
|
||||
html = editor.htmlForScreenRows(0, lastRow)
|
||||
div = document.createElement('div')
|
||||
|
||||
benchmark "setInnerHTML", 1, ->
|
||||
div.innerHTML = html
|
||||
|
||||
describe "9000-line-file.", ->
|
||||
benchmark "opening.", 5, ->
|
||||
rootView.open('huge.js')
|
||||
|
@ -24,6 +24,26 @@ my-package/
|
||||
index.coffee
|
||||
```
|
||||
|
||||
## Publishing
|
||||
|
||||
Atom bundles a command line utility called [apm](http://github.com/atom/apm)
|
||||
which can be used to publish Atom packages to the public registry.
|
||||
|
||||
Once your package is written and ready for distribution you can run the
|
||||
following to publish your package:
|
||||
|
||||
```sh
|
||||
cd my-package
|
||||
apm publish minor
|
||||
```
|
||||
|
||||
This will update your `package.json` to have a new minor `version`, commit
|
||||
the change, create a new [Git tag](http://git-scm.com/book/en/Git-Basics-Tagging),
|
||||
and then upload the package to the registry.
|
||||
|
||||
Run `apm help publish` to see all the available options and `apm help` to see
|
||||
all the other available commands.
|
||||
|
||||
## package.json
|
||||
|
||||
Similar to [npm packages][npm], Atom packages
|
||||
|
63
docs/proposals/atom-docs.md
Normal file
63
docs/proposals/atom-docs.md
Normal file
@ -0,0 +1,63 @@
|
||||
## Atom Documentation Format
|
||||
|
||||
This document describes our documentation format, which is markdown with
|
||||
a few rules.
|
||||
|
||||
### Philosophy
|
||||
|
||||
1. Method and argument names **should** clearly communicate its use.
|
||||
1. Use documentation to enhance and not correct method/argument names.
|
||||
|
||||
#### Basic
|
||||
|
||||
In some cases all that's required is a single line. **Do not** feel
|
||||
obligated to write more because we have a format.
|
||||
|
||||
```markdown
|
||||
# Private: Returns the number of pixels from the top of the screen.
|
||||
```
|
||||
|
||||
* **Each method should declare whether it's public or private by using `Public:`
|
||||
or `Private:`** prefix.
|
||||
* Following the colon, there should be a short description (that isn't redundant with the
|
||||
method name).
|
||||
* Documentation should be hard wrapped to 80 columns.
|
||||
|
||||
### Public vs Private
|
||||
|
||||
If a method is public it can be used by other classes (and possibly by
|
||||
the public API). The appropriate steps should be taken to minimize the impact
|
||||
when changing public methods. In some cases that might mean adding an
|
||||
appropriate release note. In other cases it might mean doing the legwork to
|
||||
ensure all affected packages are updated.
|
||||
|
||||
#### Complex
|
||||
|
||||
For complex methods it's necessary to explain exactly what arguments
|
||||
are required and how different inputs effect the operation of the
|
||||
function.
|
||||
|
||||
The idea is to communicate things that the API user might not know about,
|
||||
so repeating information that can be gleaned from the method or argument names
|
||||
is not useful.
|
||||
|
||||
```markdown
|
||||
# Private: Determine the accelerator for a given command.
|
||||
#
|
||||
# * command:
|
||||
# The name of the command.
|
||||
# * keystrokesByCommand:
|
||||
# An {Object} whose keys are commands and the values are Arrays containing
|
||||
# the keystrokes.
|
||||
# * options:
|
||||
# + accelerators:
|
||||
# Boolean to determine whether accelerators should be shown.
|
||||
#
|
||||
# Returns a String containing the keystroke in a format that can be interpreted
|
||||
# by atom shell to provide nice icons where available.
|
||||
#
|
||||
# Raises an Exception if no window is available.
|
||||
```
|
||||
|
||||
* Use curly brackets `{}` to provide links to other classes.
|
||||
* Use `+` for the options list.
|
@ -1 +0,0 @@
|
||||
All themes in this directory will be automatically loaded
|
63
menus/base.cson
Normal file
63
menus/base.cson
Normal file
@ -0,0 +1,63 @@
|
||||
'menu': [
|
||||
{
|
||||
label: 'Atom'
|
||||
submenu: [
|
||||
{ label: 'About Atom', command: 'application:about' }
|
||||
{ label: "VERSION", enabled: false }
|
||||
{ label: "Install update", command: 'application:install-update', visible: false }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Preferences...', command: 'application:show-settings' }
|
||||
{ label: 'Hide Atom', command: 'application:hide' }
|
||||
{ label: 'Hide Others', command: 'application:hide-other-applications' }
|
||||
{ label: 'Show All', command: 'application:unhide-all-applications' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Run Atom Specs', command: 'application:run-all-specs' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Quit', command: 'application:quit' }
|
||||
]
|
||||
}
|
||||
{
|
||||
label: 'File'
|
||||
submenu: [
|
||||
{ label: 'New Window', command: 'application:new-window' }
|
||||
{ label: 'New File', command: 'application:new-file' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Open...', command: 'application:open' }
|
||||
{ label: 'Open In Dev Mode...', command: 'application:open-dev' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Close Window', command: 'window:close' }
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: 'Edit'
|
||||
submenu: [
|
||||
{ label: 'Undo', command: 'core:undo' }
|
||||
{ label: 'Redo', command: 'core:redo' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Cut', command: 'core:cut' }
|
||||
{ label: 'Copy', command: 'core:copy' }
|
||||
{ label: 'Paste', command: 'core:paste' }
|
||||
{ label: 'Select All', command: 'core:select-all' }
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: 'View'
|
||||
submenu: [
|
||||
{ label: 'Reload', command: 'window:reload' }
|
||||
{ label: 'Toggle Full Screen', command: 'window:toggle-full-screen' }
|
||||
{ label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' }
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: 'Window'
|
||||
submenu: [
|
||||
{ label: 'Minimize', command: 'application:minimize' }
|
||||
{ label: 'Zoom', command: 'application:zoom' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Bring All to Front', command: 'application:bring-all-windows-to-front' }
|
||||
]
|
||||
}
|
||||
]
|
114
package.json
114
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "atom",
|
||||
"version": "26.0.0",
|
||||
"main": "./src/main.js",
|
||||
"version": "31.0.0",
|
||||
"main": "./src/browser/main.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
@ -9,14 +9,14 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/atom/atom/issues"
|
||||
},
|
||||
"atomShellVersion": "0.5.0",
|
||||
"atomShellVersion": "0.6.0",
|
||||
"dependencies": {
|
||||
"async": "0.2.6",
|
||||
"bootstrap": "git://github.com/twbs/bootstrap.git#v3.0.0",
|
||||
"coffee-script": "1.6.2",
|
||||
"coffeestack": "0.4.0",
|
||||
"first-mate": "0.1.0",
|
||||
"git-utils": "0.24.0",
|
||||
"coffeestack": "0.6.0",
|
||||
"first-mate": "0.2.0",
|
||||
"git-utils": "0.26.0",
|
||||
"guid": "0.0.10",
|
||||
"jasmine-focused": "~0.14.0",
|
||||
"mkdirp": "0.3.5",
|
||||
@ -28,70 +28,71 @@
|
||||
"pathwatcher": "0.5.0",
|
||||
"pegjs": "0.7.0",
|
||||
"plist": "git://github.com/nathansobo/node-plist.git",
|
||||
"q": "0.9.7",
|
||||
"rimraf": "2.1.4",
|
||||
"scandal": "0.2.0",
|
||||
"scandal": "0.5.0",
|
||||
"season": "0.13.0",
|
||||
"semver": "1.1.4",
|
||||
"space-pen": "1.2.0",
|
||||
"tantamount": "0.3.0",
|
||||
"telepath": "0.6.0",
|
||||
"space-pen": "1.3.0",
|
||||
"tantamount": "0.5.0",
|
||||
"telepath": "0.8.1",
|
||||
"temp": "0.5.0",
|
||||
"underscore": "1.4.4",
|
||||
|
||||
"atom-light-ui": "0.3.0",
|
||||
"atom-light-syntax": "0.2.0",
|
||||
"atom-dark-ui": "0.3.0",
|
||||
"atom-dark-syntax": "0.2.0",
|
||||
"base16-tomorrow-dark-theme": "0.1.0",
|
||||
"solarized-dark-syntax": "0.1.0",
|
||||
|
||||
"archive-view": "0.7.0",
|
||||
"autocomplete": "0.5.0",
|
||||
"autoflow": "0.2.0",
|
||||
"bookmarks": "0.3.0",
|
||||
"bracket-matcher": "0.4.0",
|
||||
"collaboration": "0.16.0",
|
||||
"command-logger": "0.3.0",
|
||||
"command-palette": "0.3.0",
|
||||
"editor-stats": "0.2.0",
|
||||
"exception-reporting": "0.1.0",
|
||||
"find-and-replace": "0.14.1",
|
||||
"fuzzy-finder": "0.5.0",
|
||||
"gfm": "0.4.0",
|
||||
"git-diff": "0.3.0",
|
||||
"gists": "0.2.0",
|
||||
"github-sign-in": "0.4.0",
|
||||
"go-to-line": "0.3.0",
|
||||
"grammar-selector": "0.4.0",
|
||||
"image-view": "0.5.0",
|
||||
"link": "0.2.0",
|
||||
"markdown-preview": "0.3.0",
|
||||
"metrics": "0.3.0",
|
||||
"package-generator": "0.8.0",
|
||||
"settings-view": "0.23.0",
|
||||
"snippets": "0.5.0",
|
||||
"spell-check": "0.5.0",
|
||||
"status-bar": "0.7.0",
|
||||
"symbols-view": "0.5.0",
|
||||
"tabs": "0.4.0",
|
||||
"terminal": "0.9.0",
|
||||
"timecop": "0.4.0",
|
||||
"to-the-hubs": "0.3.0",
|
||||
"toml": "0.2.0",
|
||||
"tree-view": "0.6.0",
|
||||
"ui-demo": "0.7.0",
|
||||
"whitespace": "0.4.0",
|
||||
"wrap-guide": "0.2.0",
|
||||
"atom-light-ui": "0.4.0",
|
||||
"atom-light-syntax": "0.4.0",
|
||||
"atom-dark-ui": "0.4.0",
|
||||
"atom-dark-syntax": "0.4.0",
|
||||
"base16-tomorrow-dark-theme": "0.2.0",
|
||||
"solarized-dark-syntax": "0.3.0",
|
||||
|
||||
"archive-view": "0.8.0",
|
||||
"autocomplete": "0.6.0",
|
||||
"autoflow": "0.3.0",
|
||||
"bookmarks": "0.5.0",
|
||||
"bracket-matcher": "0.6.0",
|
||||
"collaboration": "0.21.0",
|
||||
"command-logger": "0.4.0",
|
||||
"command-palette": "0.4.0",
|
||||
"editor-stats": "0.3.0",
|
||||
"exception-reporting": "0.4.0",
|
||||
"find-and-replace": "0.24.2",
|
||||
"fuzzy-finder": "0.10.0",
|
||||
"gfm": "0.5.0",
|
||||
"git-diff": "0.6.1",
|
||||
"gists": "0.3.0",
|
||||
"github-sign-in": "0.7.0",
|
||||
"go-to-line": "0.4.0",
|
||||
"grammar-selector": "0.5.0",
|
||||
"image-view": "0.6.0",
|
||||
"link": "0.4.0",
|
||||
"markdown-preview": "0.6.0",
|
||||
"metrics": "0.8.0",
|
||||
"package-generator": "0.10.0",
|
||||
"release-notes": "0.3.0",
|
||||
"settings-view": "0.27.0",
|
||||
"snippets": "0.6.0",
|
||||
"spell-check": "0.6.0",
|
||||
"status-bar": "0.10.1",
|
||||
"symbols-view": "0.8.0",
|
||||
"tabs": "0.5.0",
|
||||
"terminal": "0.10.0",
|
||||
"timecop": "0.5.0",
|
||||
"to-the-hubs": "0.6.0",
|
||||
"toml": "0.3.0",
|
||||
"tree-view": "0.10.0",
|
||||
"ui-demo": "0.8.0",
|
||||
"whitespace": "0.5.0",
|
||||
"wrap-guide": "0.3.0",
|
||||
"c-tmbundle": "1.0.0",
|
||||
"coffee-script-tmbundle": "7.0.0",
|
||||
"coffee-script-tmbundle": "1.0.0",
|
||||
"css-tmbundle": "1.0.0",
|
||||
"git-tmbundle": "1.0.0",
|
||||
"go-tmbundle": "1.0.0",
|
||||
"html-tmbundle": "1.0.0",
|
||||
"hyperlink-helper-tmbundle": "1.0.0",
|
||||
"java-tmbundle": "1.0.0",
|
||||
"javascript-tmbundle": "1.0.0",
|
||||
"javascript-tmbundle": "2.0.0",
|
||||
"json-tmbundle": "1.0.0",
|
||||
"less-tmbundle": "1.0.0",
|
||||
"make-tmbundle": "1.0.0",
|
||||
@ -117,6 +118,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"biscotto": "0.0.17",
|
||||
"formidable": "~1.0.14",
|
||||
"fstream": "0.1.24",
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-cli": "~0.1.9",
|
||||
|
@ -7,6 +7,12 @@ cd "$(dirname "$0")/.."
|
||||
rm -rf ~/.atom
|
||||
git clean -dff
|
||||
|
||||
ATOM_CREDENTIALS_FILE=/var/lib/jenkins/config/atomcredentials
|
||||
if [ -f $ATOM_CREDENTIALS_FILE ]; then
|
||||
. $ATOM_CREDENTIALS_FILE
|
||||
export ATOM_ACCESS_TOKEN=$ATOM_ACCESS_TOKEN # make it visibile to grunt.
|
||||
fi
|
||||
|
||||
./script/bootstrap
|
||||
./node_modules/.bin/apm clean
|
||||
./node_modules/.bin/grunt ci --stack --no-color
|
||||
|
@ -92,7 +92,7 @@ class AtomReporter extends View
|
||||
|
||||
clearTimeout @timeoutId if @timeoutId?
|
||||
@specPopup.show()
|
||||
spec = _.find(window.timedSpecs, (spec) -> description is spec.name)
|
||||
spec = _.find(window.timedSpecs, ({fullName}) -> description is fullName)
|
||||
description = "#{description} #{spec.time}ms" if spec
|
||||
@specPopup.text description
|
||||
{left, top} = element.offset()
|
||||
@ -113,7 +113,7 @@ class AtomReporter extends View
|
||||
specCount = "#{@completeSpecCount - @skippedCount}/#{@totalSpecCount - @skippedCount} (#{@skippedCount} skipped)"
|
||||
else
|
||||
specCount = "#{@completeSpecCount}/#{@totalSpecCount}"
|
||||
@specCount.text specCount
|
||||
@specCount[0].textContent = specCount
|
||||
|
||||
updateStatusView: (spec) ->
|
||||
if @failedCount > 0
|
||||
@ -127,7 +127,7 @@ class AtomReporter extends View
|
||||
|
||||
time = "#{Math.round((spec.endedAt - @startedAt.getTime()) / 10)}"
|
||||
time = "0#{time}" if time.length < 3
|
||||
@time.text "#{time[0...-2]}.#{time[-2..]}s"
|
||||
@time[0].textContent = "#{time[0...-2]}.#{time[-2..]}s"
|
||||
|
||||
addSpecs: (specs) ->
|
||||
coreSpecs = 0
|
||||
|
@ -6,39 +6,26 @@ describe "the `atom` global", ->
|
||||
beforeEach ->
|
||||
window.rootView = new RootView
|
||||
|
||||
describe "base stylesheet loading", ->
|
||||
beforeEach ->
|
||||
rootView.append $$ -> @div class: 'editor'
|
||||
rootView.attachToDom()
|
||||
atom.themes.load()
|
||||
atom.watchThemes()
|
||||
|
||||
afterEach ->
|
||||
atom.themes.unload()
|
||||
config.set('core.themes', [])
|
||||
atom.reloadBaseStylesheets()
|
||||
|
||||
it "loads the correct values from the theme's ui-variables file", ->
|
||||
config.set('core.themes', ['theme-with-ui-variables'])
|
||||
|
||||
# an override loaded in the base css
|
||||
expect(rootView.css("background-color")).toBe "rgb(0, 0, 255)"
|
||||
|
||||
# from within the theme itself
|
||||
expect($(".editor").css("padding-top")).toBe "150px"
|
||||
expect($(".editor").css("padding-right")).toBe "150px"
|
||||
expect($(".editor").css("padding-bottom")).toBe "150px"
|
||||
|
||||
describe "package lifecycle methods", ->
|
||||
describe ".loadPackage(name)", ->
|
||||
describe "when the package has deferred deserializers", ->
|
||||
it "requires the package's main module if one of its deferred deserializers is referenced", ->
|
||||
pack = atom.loadPackage('package-with-activation-events')
|
||||
spyOn(pack, 'activateStylesheets').andCallThrough()
|
||||
expect(pack.mainModule).toBeNull()
|
||||
object = deserialize({deserializer: 'Foo', data: 5})
|
||||
expect(pack.mainModule).toBeDefined()
|
||||
expect(object.constructor.name).toBe 'Foo'
|
||||
expect(object.data).toBe 5
|
||||
expect(pack.activateStylesheets).toHaveBeenCalled()
|
||||
|
||||
it "continues if the package has an invalid package.json", ->
|
||||
config.set("core.disabledPackages", [])
|
||||
expect(-> atom.loadPackage("package-with-broken-package-json")).not.toThrow()
|
||||
|
||||
it "continues if the package has an invalid keymap", ->
|
||||
config.set("core.disabledPackages", [])
|
||||
expect(-> atom.loadPackage("package-with-broken-keymap")).not.toThrow()
|
||||
|
||||
describe ".unloadPackage(name)", ->
|
||||
describe "when the package is active", ->
|
||||
@ -172,7 +159,9 @@ describe "the `atom` global", ->
|
||||
expect(keymap.bindingsForElement(element3)['ctrl-y']).toBeUndefined()
|
||||
|
||||
describe "menu loading", ->
|
||||
beforeEach -> atom.contextMenu.definitions = []
|
||||
beforeEach ->
|
||||
atom.contextMenu.definitions = []
|
||||
atom.menu.template = []
|
||||
|
||||
describe "when the metadata does not contain a 'menus' manifest", ->
|
||||
it "loads all the .cson/.json files in the menus directory", ->
|
||||
@ -182,6 +171,8 @@ describe "the `atom` global", ->
|
||||
|
||||
atom.activatePackage("package-with-menus")
|
||||
|
||||
expect(atom.menu.template[0].label).toBe "Second to Last"
|
||||
expect(atom.menu.template[1].label).toBe "Last"
|
||||
expect(atom.contextMenu.definitionsForElement(element)[0].label).toBe "Menu item 1"
|
||||
expect(atom.contextMenu.definitionsForElement(element)[1].label).toBe "Menu item 2"
|
||||
expect(atom.contextMenu.definitionsForElement(element)[2].label).toBe "Menu item 3"
|
||||
@ -194,6 +185,8 @@ describe "the `atom` global", ->
|
||||
|
||||
atom.activatePackage("package-with-menus-manifest")
|
||||
|
||||
expect(atom.menu.template[0].label).toBe "Second to Last"
|
||||
expect(atom.menu.template[1].label).toBe "Last"
|
||||
expect(atom.contextMenu.definitionsForElement(element)[0].label).toBe "Menu item 2"
|
||||
expect(atom.contextMenu.definitionsForElement(element)[1].label).toBe "Menu item 1"
|
||||
expect(atom.contextMenu.definitionsForElement(element)[2]).toBeUndefined()
|
||||
@ -205,15 +198,15 @@ describe "the `atom` global", ->
|
||||
one = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/1.css")
|
||||
two = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/2.less")
|
||||
three = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/3.css")
|
||||
expect(stylesheetElementForId(one)).not.toExist()
|
||||
expect(stylesheetElementForId(two)).not.toExist()
|
||||
expect(stylesheetElementForId(three)).not.toExist()
|
||||
expect(atom.themes.stylesheetElementForId(one)).not.toExist()
|
||||
expect(atom.themes.stylesheetElementForId(two)).not.toExist()
|
||||
expect(atom.themes.stylesheetElementForId(three)).not.toExist()
|
||||
|
||||
atom.activatePackage("package-with-stylesheets-manifest")
|
||||
|
||||
expect(stylesheetElementForId(one)).toExist()
|
||||
expect(stylesheetElementForId(two)).toExist()
|
||||
expect(stylesheetElementForId(three)).not.toExist()
|
||||
expect(atom.themes.stylesheetElementForId(one)).toExist()
|
||||
expect(atom.themes.stylesheetElementForId(two)).toExist()
|
||||
expect(atom.themes.stylesheetElementForId(three)).not.toExist()
|
||||
expect($('#jasmine-content').css('font-size')).toBe '1px'
|
||||
|
||||
describe "when the metadata does not contain a 'stylesheets' manifest", ->
|
||||
@ -221,14 +214,14 @@ describe "the `atom` global", ->
|
||||
one = require.resolve("./fixtures/packages/package-with-stylesheets/stylesheets/1.css")
|
||||
two = require.resolve("./fixtures/packages/package-with-stylesheets/stylesheets/2.less")
|
||||
three = require.resolve("./fixtures/packages/package-with-stylesheets/stylesheets/3.css")
|
||||
expect(stylesheetElementForId(one)).not.toExist()
|
||||
expect(stylesheetElementForId(two)).not.toExist()
|
||||
expect(stylesheetElementForId(three)).not.toExist()
|
||||
expect(atom.themes.stylesheetElementForId(one)).not.toExist()
|
||||
expect(atom.themes.stylesheetElementForId(two)).not.toExist()
|
||||
expect(atom.themes.stylesheetElementForId(three)).not.toExist()
|
||||
|
||||
atom.activatePackage("package-with-stylesheets")
|
||||
expect(stylesheetElementForId(one)).toExist()
|
||||
expect(stylesheetElementForId(two)).toExist()
|
||||
expect(stylesheetElementForId(three)).toExist()
|
||||
expect(atom.themes.stylesheetElementForId(one)).toExist()
|
||||
expect(atom.themes.stylesheetElementForId(two)).toExist()
|
||||
expect(atom.themes.stylesheetElementForId(three)).toExist()
|
||||
expect($('#jasmine-content').css('font-size')).toBe '3px'
|
||||
|
||||
describe "grammar loading", ->
|
||||
@ -298,8 +291,8 @@ describe "the `atom` global", ->
|
||||
atom.activatePackage('package-with-serialize-error', immediate: true)
|
||||
atom.activatePackage('package-with-serialization', immediate: true)
|
||||
atom.deactivatePackages()
|
||||
expect(atom.packageStates['package-with-serialize-error']).toBeUndefined()
|
||||
expect(atom.packageStates['package-with-serialization']).toEqual someNumber: 1
|
||||
expect(atom.packages.packageStates['package-with-serialize-error']).toBeUndefined()
|
||||
expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
|
||||
it "removes the package's grammars", ->
|
||||
@ -320,9 +313,9 @@ describe "the `atom` global", ->
|
||||
one = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/1.css")
|
||||
two = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/2.less")
|
||||
three = require.resolve("./fixtures/packages/package-with-stylesheets-manifest/stylesheets/3.css")
|
||||
expect(stylesheetElementForId(one)).not.toExist()
|
||||
expect(stylesheetElementForId(two)).not.toExist()
|
||||
expect(stylesheetElementForId(three)).not.toExist()
|
||||
expect(atom.themes.stylesheetElementForId(one)).not.toExist()
|
||||
expect(atom.themes.stylesheetElementForId(two)).not.toExist()
|
||||
expect(atom.themes.stylesheetElementForId(three)).not.toExist()
|
||||
|
||||
it "removes the package's scoped-properties", ->
|
||||
atom.activatePackage("package-with-scoped-properties")
|
||||
|
@ -181,7 +181,6 @@ describe "Config", ->
|
||||
expect(fs.exists(config.configDirPath)).toBeTruthy()
|
||||
expect(fs.exists(path.join(config.configDirPath, 'packages'))).toBeTruthy()
|
||||
expect(fs.exists(path.join(config.configDirPath, 'snippets'))).toBeTruthy()
|
||||
expect(fs.exists(path.join(config.configDirPath, 'themes'))).toBeTruthy()
|
||||
expect(fs.isFileSync(path.join(config.configDirPath, 'config.cson'))).toBeTruthy()
|
||||
|
||||
describe ".loadUserConfig()", ->
|
||||
|
33
spec/deserializer-manager-spec.coffee
Normal file
33
spec/deserializer-manager-spec.coffee
Normal file
@ -0,0 +1,33 @@
|
||||
DeserializerManager = require '../src/deserializer-manager'
|
||||
|
||||
describe ".deserialize(state)", ->
|
||||
deserializer = null
|
||||
|
||||
class Foo
|
||||
@deserialize: ({name}) -> new Foo(name)
|
||||
constructor: (@name) ->
|
||||
|
||||
beforeEach ->
|
||||
deserializer = new DeserializerManager()
|
||||
deserializer.add(Foo)
|
||||
|
||||
it "calls deserialize on the deserializer for the given state object, or returns undefined if one can't be found", ->
|
||||
spyOn(console, 'warn')
|
||||
object = deserializer.deserialize({ deserializer: 'Foo', name: 'Bar' })
|
||||
expect(object.name).toBe 'Bar'
|
||||
expect(deserializer.deserialize({ deserializer: 'Bogus' })).toBeUndefined()
|
||||
|
||||
describe "when the deserializer has a version", ->
|
||||
beforeEach ->
|
||||
Foo.version = 2
|
||||
|
||||
describe "when the deserialized state has a matching version", ->
|
||||
it "attempts to deserialize the state", ->
|
||||
object = deserializer.deserialize({ deserializer: 'Foo', version: 2, name: 'Bar' })
|
||||
expect(object.name).toBe 'Bar'
|
||||
|
||||
describe "when the deserialized state has a non-matching version", ->
|
||||
it "returns undefined", ->
|
||||
expect(deserializer.deserialize({ deserializer: 'Foo', version: 3, name: 'Bar' })).toBeUndefined()
|
||||
expect(deserializer.deserialize({ deserializer: 'Foo', version: 1, name: 'Bar' })).toBeUndefined()
|
||||
expect(deserializer.deserialize({ deserializer: 'Foo', name: 'Bar' })).toBeUndefined()
|
@ -13,6 +13,8 @@ describe "Editor", ->
|
||||
editor.lineOverdraw = 2
|
||||
editor.isFocused = true
|
||||
editor.enableKeymap()
|
||||
editor.calculateHeightInLines = ->
|
||||
Math.ceil(@height() / @lineHeight)
|
||||
editor.attachToDom = ({ heightInLines, widthInChars } = {}) ->
|
||||
heightInLines ?= @getBuffer().getLineCount()
|
||||
@height(getLineHeight() * heightInLines)
|
||||
@ -117,21 +119,21 @@ describe "Editor", ->
|
||||
|
||||
it "updates the rendered lines, cursors, selections, scroll position, and event subscriptions to match the given edit session", ->
|
||||
editor.attachToDom(heightInLines: 5, widthInChars: 30)
|
||||
editor.setCursorBufferPosition([3, 5])
|
||||
editor.setCursorBufferPosition([6, 13])
|
||||
editor.scrollToBottom()
|
||||
editor.scrollLeft(150)
|
||||
previousScrollHeight = editor.verticalScrollbar.prop('scrollHeight')
|
||||
previousScrollTop = editor.scrollTop()
|
||||
previousScrollLeft = editor.scrollLeft()
|
||||
|
||||
newEditSession.setScrollTop(120)
|
||||
newEditSession.setScrollTop(900)
|
||||
newEditSession.setSelectedBufferRange([[40, 0], [43, 1]])
|
||||
|
||||
editor.edit(newEditSession)
|
||||
{ firstRenderedScreenRow, lastRenderedScreenRow } = editor
|
||||
expect(editor.lineElementForScreenRow(firstRenderedScreenRow).text()).toBe newBuffer.lineForRow(firstRenderedScreenRow)
|
||||
expect(editor.lineElementForScreenRow(lastRenderedScreenRow).text()).toBe newBuffer.lineForRow(editor.lastRenderedScreenRow)
|
||||
expect(editor.scrollTop()).toBe 120
|
||||
expect(editor.scrollTop()).toBe 900
|
||||
expect(editor.scrollLeft()).toBe 0
|
||||
expect(editor.getSelectionView().regions[0].position().top).toBe 40 * editor.lineHeight
|
||||
editor.insertText("hello")
|
||||
@ -144,9 +146,9 @@ describe "Editor", ->
|
||||
expect(editor.verticalScrollbar.prop('scrollHeight')).toBe previousScrollHeight
|
||||
expect(editor.scrollTop()).toBe previousScrollTop
|
||||
expect(editor.scrollLeft()).toBe previousScrollLeft
|
||||
expect(editor.getCursorView().position()).toEqual { top: 3 * editor.lineHeight, left: 5 * editor.charWidth }
|
||||
expect(editor.getCursorView().position()).toEqual { top: 6 * editor.lineHeight, left: 13 * editor.charWidth }
|
||||
editor.insertText("goodbye")
|
||||
expect(editor.lineElementForScreenRow(3).text()).toMatch /^ vgoodbyear/
|
||||
expect(editor.lineElementForScreenRow(6).text()).toMatch /^ currentgoodbye/
|
||||
|
||||
it "triggers alert if edit session's buffer goes into conflict with changes on disk", ->
|
||||
filePath = "/tmp/atom-changed-file.txt"
|
||||
@ -286,15 +288,13 @@ describe "Editor", ->
|
||||
|
||||
describe "font family", ->
|
||||
beforeEach ->
|
||||
expect(editor.css('font-family')).not.toBe 'Courier'
|
||||
expect(editor.css('font-family')).toBe 'Courier'
|
||||
|
||||
it "when there is no config in fontFamily don't set it", ->
|
||||
expect($("head style.font-family")).not.toExist()
|
||||
atom.config.set('editor.fontFamily', null)
|
||||
expect(editor.css('font-family')).toBe ''
|
||||
|
||||
describe "when the font family changes", ->
|
||||
afterEach ->
|
||||
editor.clearFontFamily()
|
||||
|
||||
it "updates the font family of editors and recalculates dimensions critical to cursor positioning", ->
|
||||
editor.attachToDom(12)
|
||||
lineHeightBefore = editor.lineHeight
|
||||
@ -303,7 +303,6 @@ describe "Editor", ->
|
||||
|
||||
config.set("editor.fontFamily", "PCMyungjo")
|
||||
expect(editor.css('font-family')).toBe 'PCMyungjo'
|
||||
expect($("head style.editor-font-family").text()).toMatch "{font-family: PCMyungjo}"
|
||||
expect(editor.charWidth).not.toBe charWidthBefore
|
||||
expect(editor.getCursorView().position()).toEqual { top: 5 * editor.lineHeight, left: 6 * editor.charWidth }
|
||||
|
||||
@ -317,8 +316,7 @@ describe "Editor", ->
|
||||
expect(editor.css('font-size')).not.toBe "10px"
|
||||
|
||||
it "sets the initial font size based on the value from config", ->
|
||||
expect($("head style.font-size")).toExist()
|
||||
expect($("head style.font-size").text()).toMatch "{font-size: #{config.get('editor.fontSize')}px}"
|
||||
expect(editor.css('font-size')).toBe "#{config.get('editor.fontSize')}px"
|
||||
|
||||
describe "when the font size changes", ->
|
||||
it "updates the font sizes of editors and recalculates dimensions critical to cursor positioning", ->
|
||||
@ -404,9 +402,6 @@ describe "Editor", ->
|
||||
beforeEach ->
|
||||
editor.setFontFamily('sans-serif')
|
||||
|
||||
afterEach ->
|
||||
editor.clearFontFamily()
|
||||
|
||||
it "positions the cursor to the clicked row and column", ->
|
||||
{top, left} = editor.pixelOffsetForScreenPosition([3, 30])
|
||||
editor.renderedLines.trigger mousedownEvent(pageX: left, pageY: top)
|
||||
@ -440,6 +435,29 @@ describe "Editor", ->
|
||||
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [3, 12], originalEvent: {detail: 1}, shiftKey: true)
|
||||
expect(editor.getSelectedBufferRange()).toEqual [[3, 10], [3, 12]]
|
||||
|
||||
describe "when clicking between a word and a non-word", ->
|
||||
it "selects the word", ->
|
||||
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
|
||||
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [1, 21], originalEvent: {detail: 1})
|
||||
editor.renderedLines.trigger 'mouseup'
|
||||
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [1, 21], originalEvent: {detail: 2})
|
||||
editor.renderedLines.trigger 'mouseup'
|
||||
expect(editor.getSelectedText()).toBe "function"
|
||||
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [1, 22], originalEvent: {detail: 1})
|
||||
editor.renderedLines.trigger 'mouseup'
|
||||
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [1, 22], originalEvent: {detail: 2})
|
||||
editor.renderedLines.trigger 'mouseup'
|
||||
expect(editor.getSelectedText()).toBe "items"
|
||||
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [0, 28], originalEvent: {detail: 1})
|
||||
editor.renderedLines.trigger 'mouseup'
|
||||
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [0, 28], originalEvent: {detail: 2})
|
||||
editor.renderedLines.trigger 'mouseup'
|
||||
expect(editor.getSelectedText()).toBe "{"
|
||||
|
||||
describe "triple/quardruple/etc-click", ->
|
||||
it "selects the line under the cursor", ->
|
||||
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
|
||||
@ -892,9 +910,6 @@ describe "Editor", ->
|
||||
beforeEach ->
|
||||
editor.setFontFamily('sans-serif')
|
||||
|
||||
afterEach ->
|
||||
editor.clearFontFamily()
|
||||
|
||||
it "correctly positions the cursor", ->
|
||||
editor.setCursorBufferPosition([3, 30])
|
||||
expect(editor.getCursorView().position()).toEqual {top: 3 * editor.lineHeight, left: 178}
|
||||
@ -1092,8 +1107,8 @@ describe "Editor", ->
|
||||
expect(span0.children('span:eq(2)')).toMatchSelector '.meta.brace.curly.js'
|
||||
expect(span0.children('span:eq(2)').text()).toBe "{"
|
||||
|
||||
line12 = editor.renderedLines.find('.line:eq(11)')
|
||||
expect(line12.find('span:eq(2)')).toMatchSelector '.keyword'
|
||||
line12 = editor.renderedLines.find('.line:eq(11)').children('span:eq(0)')
|
||||
expect(line12.children('span:eq(1)')).toMatchSelector '.keyword'
|
||||
|
||||
it "wraps hard tabs in a span", ->
|
||||
editor.setText('\t<- hard tab')
|
||||
@ -1108,12 +1123,13 @@ describe "Editor", ->
|
||||
expect(span0_0).toMatchSelector '.leading-whitespace'
|
||||
expect(span0_0.text()).toBe ' '
|
||||
|
||||
it "wraps trailing whitespace in a span", ->
|
||||
editor.setText('trailing whitespace -> ')
|
||||
line0 = editor.renderedLines.find('.line:first')
|
||||
span0_last = line0.children('span:eq(0)').children('span:last')
|
||||
expect(span0_last).toMatchSelector '.trailing-whitespace'
|
||||
expect(span0_last.text()).toBe ' '
|
||||
describe "when the line has trailing whitespace", ->
|
||||
it "wraps trailing whitespace in a span", ->
|
||||
editor.setText('trailing whitespace -> ')
|
||||
line0 = editor.renderedLines.find('.line:first')
|
||||
span0_last = line0.children('span:eq(0)').children('span:last')
|
||||
expect(span0_last).toMatchSelector '.trailing-whitespace'
|
||||
expect(span0_last.text()).toBe ' '
|
||||
|
||||
describe "when lines are updated in the buffer", ->
|
||||
it "syntax highlights the updated lines", ->
|
||||
@ -1863,13 +1879,55 @@ describe "Editor", ->
|
||||
# doesn't allow regular editors to set grammars
|
||||
expect(-> editor.setGrammar()).toThrow()
|
||||
|
||||
|
||||
describe "when config.editor.showLineNumbers is false", ->
|
||||
it "doesn't render any line numbers", ->
|
||||
expect(editor.gutter.lineNumbers).toBeVisible()
|
||||
config.set("editor.showLineNumbers", false)
|
||||
expect(editor.gutter.lineNumbers).not.toBeVisible()
|
||||
|
||||
describe "using gutter's api", ->
|
||||
it "can get all the line number elements", ->
|
||||
elements = editor.gutter.getLineNumberElements()
|
||||
len = editor.gutter.lastScreenRow - editor.gutter.firstScreenRow + 1
|
||||
expect(elements).toHaveLength(len)
|
||||
|
||||
it "can get a single line number element", ->
|
||||
element = editor.gutter.getLineNumberElement(3)
|
||||
expect(element).toBeTruthy()
|
||||
|
||||
it "returns falsy when there is no line element", ->
|
||||
expect(editor.gutter.getLineNumberElement(42)).toHaveLength 0
|
||||
|
||||
it "can add and remove classes to all the line numbers", ->
|
||||
wasAdded = editor.gutter.addClassToAllLines('heyok')
|
||||
expect(wasAdded).toBe true
|
||||
|
||||
elements = editor.gutter.getLineNumberElementsForClass('heyok')
|
||||
expect($(elements)).toHaveClass('heyok')
|
||||
|
||||
editor.gutter.removeClassFromAllLines('heyok')
|
||||
expect($(editor.gutter.getLineNumberElements())).not.toHaveClass('heyok')
|
||||
|
||||
it "can add and remove classes from a single line number", ->
|
||||
wasAdded = editor.gutter.addClassToLine(3, 'heyok')
|
||||
expect(wasAdded).toBe true
|
||||
|
||||
element = editor.gutter.getLineNumberElement(2)
|
||||
expect($(element)).not.toHaveClass('heyok')
|
||||
|
||||
it "can fetch line numbers by their class", ->
|
||||
editor.gutter.addClassToLine(1, 'heyok')
|
||||
editor.gutter.addClassToLine(3, 'heyok')
|
||||
|
||||
elements = editor.gutter.getLineNumberElementsForClass('heyok')
|
||||
expect(elements.length).toBe 2
|
||||
|
||||
expect($(elements[0])).toHaveClass 'line-number-1'
|
||||
expect($(elements[0])).toHaveClass 'heyok'
|
||||
|
||||
expect($(elements[1])).toHaveClass 'line-number-3'
|
||||
expect($(elements[1])).toHaveClass 'heyok'
|
||||
|
||||
describe "gutter line highlighting", ->
|
||||
beforeEach ->
|
||||
editor.attachToDom(heightInLines: 5.5)
|
||||
@ -2145,10 +2203,21 @@ describe "Editor", ->
|
||||
expect(editor.pixelPositionForBufferPosition([2,7])).toEqual top: 0, left: 0
|
||||
|
||||
describe "when the editor is attached and visible", ->
|
||||
it "returns the top and left pixel positions", ->
|
||||
beforeEach ->
|
||||
editor.attachToDom()
|
||||
|
||||
it "returns the top and left pixel positions", ->
|
||||
expect(editor.pixelPositionForBufferPosition([2,7])).toEqual top: 40, left: 70
|
||||
|
||||
it "caches the left position", ->
|
||||
editor.renderedLines.css('font-size', '16px')
|
||||
expect(editor.pixelPositionForBufferPosition([2,8])).toEqual top: 40, left: 80
|
||||
|
||||
# make characters smaller
|
||||
editor.renderedLines.css('font-size', '15px')
|
||||
|
||||
expect(editor.pixelPositionForBufferPosition([2,8])).toEqual top: 40, left: 80
|
||||
|
||||
describe "when clicking in the gutter", ->
|
||||
beforeEach ->
|
||||
editor.attachToDom()
|
||||
|
1
spec/fixtures/packages/package-with-broken-keymap/keymaps/broken.json
vendored
Normal file
1
spec/fixtures/packages/package-with-broken-keymap/keymaps/broken.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
INVALID
|
1
spec/fixtures/packages/package-with-broken-package-json/package.json
vendored
Normal file
1
spec/fixtures/packages/package-with-broken-package-json/package.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
INVALID
|
@ -1,3 +1,7 @@
|
||||
"context-menu":
|
||||
".test-1":
|
||||
"Menu item 1": "command-1"
|
||||
'menu': [
|
||||
{ 'label': 'Last' }
|
||||
]
|
||||
|
||||
'context-menu':
|
||||
'.test-1':
|
||||
'Menu item 1': 'command-1'
|
||||
|
@ -1,3 +1,7 @@
|
||||
"context-menu":
|
||||
".test-1":
|
||||
"Menu item 2": "command-2"
|
||||
'menu': [
|
||||
{ 'label': 'Second to Last' }
|
||||
]
|
||||
|
||||
'context-menu':
|
||||
'.test-1':
|
||||
'Menu item 2': 'command-2'
|
||||
|
@ -1,3 +1,3 @@
|
||||
"context-menu":
|
||||
".test-1":
|
||||
"Menu item 3": "command-3"
|
||||
'context-menu':
|
||||
'.test-1':
|
||||
'Menu item 3': 'command-3'
|
||||
|
@ -1,3 +1,7 @@
|
||||
"context-menu":
|
||||
".test-1":
|
||||
"Menu item 1": "command-1"
|
||||
'menu': [
|
||||
{ 'label': 'Second to Last' }
|
||||
]
|
||||
|
||||
'context-menu':
|
||||
'.test-1':
|
||||
'Menu item 1': 'command-1'
|
||||
|
@ -1,3 +1,7 @@
|
||||
"context-menu":
|
||||
".test-1":
|
||||
"Menu item 2": "command-2"
|
||||
'menu': [
|
||||
{ 'label': 'Last' }
|
||||
]
|
||||
|
||||
'context-menu':
|
||||
'.test-1':
|
||||
'Menu item 2': 'command-2'
|
||||
|
@ -1,3 +1,3 @@
|
||||
"context-menu":
|
||||
".test-1":
|
||||
"Menu item 3": "command-3"
|
||||
'context-menu':
|
||||
'.test-1':
|
||||
'Menu item 3': 'command-3'
|
||||
|
@ -347,3 +347,35 @@ describe "Keymap", ->
|
||||
bindings = keymap.bindingsForElement(fragment.find('.grandchild-node'))
|
||||
expect(Object.keys(bindings).length).toBe 1
|
||||
expect(bindings['g']).toEqual "command-and-grandchild-node"
|
||||
|
||||
describe ".getAllKeyMappings", ->
|
||||
it "returns the all bindings", ->
|
||||
keymap.bindKeys '~/.atom/packages/dummy/keymaps/a.cson', '.command-mode', 'k': 'c'
|
||||
|
||||
mappings = keymap.getAllKeyMappings()
|
||||
expect(mappings.length).toBe 1
|
||||
expect(mappings[0].source).toEqual 'dummy'
|
||||
expect(mappings[0].keystrokes).toEqual 'k'
|
||||
expect(mappings[0].command).toEqual 'c'
|
||||
expect(mappings[0].selector).toEqual '.command-mode'
|
||||
|
||||
describe ".determineSource", ->
|
||||
describe "for a package", ->
|
||||
it "returns '<package-name>'", ->
|
||||
expect(keymap.determineSource('~/.atom/packages/dummy/keymaps/a.cson')).toEqual 'dummy'
|
||||
|
||||
describe "for a linked package", ->
|
||||
it "returns '<package-name>'", ->
|
||||
expect(keymap.determineSource('/Users/john/github/dummy/keymaps/a.cson')).toEqual 'dummy'
|
||||
|
||||
describe "for a user defined keymap", ->
|
||||
it "returns 'User'", ->
|
||||
expect(keymap.determineSource('~/.atom/keymaps/a.cson')).toEqual 'User'
|
||||
|
||||
describe "for a core keymap", ->
|
||||
it "returns 'Core'", ->
|
||||
expect(keymap.determineSource('/Applications/Atom.app/.../node_modules/dummy/keymaps/a.cson')).toEqual 'Core'
|
||||
|
||||
describe "for a linked core keymap", ->
|
||||
it "returns 'Core'", ->
|
||||
expect(keymap.determineSource('/Users/john/github/atom/keymaps/a.cson')).toEqual 'Core'
|
||||
|
@ -94,43 +94,41 @@ describe "Pane", ->
|
||||
expect(editor.activeEditSession).toBe editSession1
|
||||
|
||||
describe "when a valid view has already been appended for another item", ->
|
||||
describe "when the view has a setModel method", ->
|
||||
it "recycles the existing view by assigning the selected item to it", ->
|
||||
pane.showItem(editSession1)
|
||||
pane.showItem(editSession2)
|
||||
expect(pane.itemViews.find('.editor').length).toBe 1
|
||||
editor = pane.activeView
|
||||
expect(editor.css('display')).toBe ''
|
||||
expect(editor.activeEditSession).toBe editSession2
|
||||
it "multiple views are created for multiple items", ->
|
||||
pane.showItem(editSession1)
|
||||
pane.showItem(editSession2)
|
||||
expect(pane.itemViews.find('.editor').length).toBe 2
|
||||
editor = pane.activeView
|
||||
expect(editor.css('display')).toBe ''
|
||||
expect(editor.activeEditSession).toBe editSession2
|
||||
|
||||
describe "when the view does not have a setModel method", ->
|
||||
it "creates a new view with the item", ->
|
||||
initialViewCount = pane.itemViews.find('.test-view').length
|
||||
it "creates a new view with the item", ->
|
||||
initialViewCount = pane.itemViews.find('.test-view').length
|
||||
|
||||
model1 =
|
||||
id: 'test-model-1'
|
||||
text: 'Test Model 1'
|
||||
serialize: -> {@id, @text}
|
||||
getViewClass: -> TestView
|
||||
model1 =
|
||||
id: 'test-model-1'
|
||||
text: 'Test Model 1'
|
||||
serialize: -> {@id, @text}
|
||||
getViewClass: -> TestView
|
||||
|
||||
model2 =
|
||||
id: 'test-model-2'
|
||||
text: 'Test Model 2'
|
||||
serialize: -> {@id, @text}
|
||||
getViewClass: -> TestView
|
||||
model2 =
|
||||
id: 'test-model-2'
|
||||
text: 'Test Model 2'
|
||||
serialize: -> {@id, @text}
|
||||
getViewClass: -> TestView
|
||||
|
||||
pane.showItem(model1)
|
||||
pane.showItem(model2)
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2
|
||||
pane.showItem(model1)
|
||||
pane.showItem(model2)
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2
|
||||
|
||||
pane.showPreviousItem()
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2
|
||||
pane.showPreviousItem()
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2
|
||||
|
||||
pane.removeItem(model2)
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 1
|
||||
pane.removeItem(model2)
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 1
|
||||
|
||||
pane.removeItem(model1)
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount
|
||||
pane.removeItem(model1)
|
||||
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount
|
||||
|
||||
describe "when showing a view item", ->
|
||||
it "appends it to the itemViews div if it hasn't already been appended and shows it", ->
|
||||
@ -237,6 +235,7 @@ describe "Pane", ->
|
||||
|
||||
describe "when the item is a model", ->
|
||||
it "removes the associated view only when all items that require it have been removed", ->
|
||||
pane.showItem(editSession1)
|
||||
pane.showItem(editSession2)
|
||||
pane.removeItem(editSession2)
|
||||
expect(pane.itemViews.find('.editor')).toExist()
|
||||
|
@ -40,12 +40,12 @@ describe "Project", ->
|
||||
|
||||
describe "when an edit session is saved and the project has no path", ->
|
||||
it "sets the project's path to the saved file's parent directory", ->
|
||||
tempFile = temp.openSync().path
|
||||
project.setPath(undefined)
|
||||
expect(project.getPath()).toBeUndefined()
|
||||
editSession = project.open()
|
||||
editSession.saveAs('/tmp/atom-test-save-sets-project-path')
|
||||
expect(project.getPath()).toBe '/tmp'
|
||||
fs.remove('/tmp/atom-test-save-sets-project-path')
|
||||
editSession.saveAs(tempFile)
|
||||
expect(project.getPath()).toBe path.dirname(tempFile)
|
||||
|
||||
describe "when an edit session is deserialized", ->
|
||||
it "emits an 'edit-session-created' event and stores the edit session", ->
|
||||
@ -118,6 +118,95 @@ describe "Project", ->
|
||||
expect(project.open(pathToOpen, hey: "there")).toEqual { foo: pathToOpen, options: {hey: "there"} }
|
||||
expect(project.open("bar://baz")).toEqual { bar: "bar://baz" }
|
||||
|
||||
describe ".openAsync(path)", ->
|
||||
[fooOpener, barOpener, absolutePath, newBufferHandler, newEditSessionHandler] = []
|
||||
|
||||
beforeEach ->
|
||||
absolutePath = require.resolve('./fixtures/dir/a')
|
||||
newBufferHandler = jasmine.createSpy('newBufferHandler')
|
||||
project.on 'buffer-created', newBufferHandler
|
||||
newEditSessionHandler = jasmine.createSpy('newEditSessionHandler')
|
||||
project.on 'edit-session-created', newEditSessionHandler
|
||||
|
||||
fooOpener = (pathToOpen, options) -> { foo: pathToOpen, options } if pathToOpen?.match(/\.foo/)
|
||||
barOpener = (pathToOpen) -> { bar: pathToOpen } if pathToOpen?.match(/^bar:\/\//)
|
||||
project.registerOpener(fooOpener)
|
||||
project.registerOpener(barOpener)
|
||||
|
||||
afterEach ->
|
||||
project.unregisterOpener(fooOpener)
|
||||
project.unregisterOpener(barOpener)
|
||||
|
||||
describe "when passed a path that doesn't match a custom opener", ->
|
||||
describe "when given an absolute path that isn't currently open", ->
|
||||
it "returns a new edit session for the given path and emits 'buffer-created' and 'edit-session-created' events", ->
|
||||
editSession = null
|
||||
waitsForPromise ->
|
||||
project.openAsync(absolutePath).then (o) -> editSession = o
|
||||
|
||||
runs ->
|
||||
expect(editSession.buffer.getPath()).toBe absolutePath
|
||||
expect(newBufferHandler).toHaveBeenCalledWith editSession.buffer
|
||||
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
|
||||
|
||||
describe "when given a relative path that isn't currently opened", ->
|
||||
it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'edit-session-created' events", ->
|
||||
editSession = null
|
||||
waitsForPromise ->
|
||||
project.openAsync(absolutePath).then (o) -> editSession = o
|
||||
|
||||
runs ->
|
||||
expect(editSession.buffer.getPath()).toBe absolutePath
|
||||
expect(newBufferHandler).toHaveBeenCalledWith editSession.buffer
|
||||
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
|
||||
|
||||
describe "when passed the path to a buffer that is currently opened", ->
|
||||
it "returns a new edit session containing currently opened buffer and emits a 'edit-session-created' event", ->
|
||||
editSession = null
|
||||
waitsForPromise ->
|
||||
project.openAsync(absolutePath).then (o) -> editSession = o
|
||||
|
||||
runs ->
|
||||
newBufferHandler.reset()
|
||||
expect(project.open(absolutePath).buffer).toBe editSession.buffer
|
||||
expect(project.open('a').buffer).toBe editSession.buffer
|
||||
expect(newBufferHandler).not.toHaveBeenCalled()
|
||||
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
|
||||
|
||||
describe "when not passed a path", ->
|
||||
it "returns a new edit session and emits 'buffer-created' and 'edit-session-created' events", ->
|
||||
editSession = null
|
||||
waitsForPromise ->
|
||||
project.openAsync().then (o) -> editSession = o
|
||||
|
||||
runs ->
|
||||
expect(editSession.buffer.getPath()).toBeUndefined()
|
||||
expect(newBufferHandler).toHaveBeenCalledWith(editSession.buffer)
|
||||
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
|
||||
|
||||
describe "when passed a path that matches a custom opener", ->
|
||||
it "returns the resource returned by the custom opener", ->
|
||||
waitsForPromise ->
|
||||
pathToOpen = project.resolve('a.foo')
|
||||
project.openAsync(pathToOpen, hey: "there").then (item) ->
|
||||
expect(item).toEqual { foo: pathToOpen, options: {hey: "there"} }
|
||||
|
||||
waitsForPromise ->
|
||||
project.openAsync("bar://baz").then (item) ->
|
||||
expect(item).toEqual { bar: "bar://baz" }
|
||||
|
||||
it "returns number of read bytes as progress indicator", ->
|
||||
filePath = project.resolve 'a'
|
||||
totalBytes = 0
|
||||
promise = project.openAsync(filePath)
|
||||
promise.progress (bytesRead) -> totalBytes = bytesRead
|
||||
|
||||
waitsForPromise ->
|
||||
promise
|
||||
|
||||
runs ->
|
||||
expect(totalBytes).toBe fs.statSync(filePath).size
|
||||
|
||||
describe ".bufferForPath(path)", ->
|
||||
describe "when opening a previously opened path", ->
|
||||
it "does not create a new buffer", ->
|
||||
@ -132,6 +221,34 @@ describe "Project", ->
|
||||
buffer = project.bufferForPath("a").retain().release()
|
||||
expect(project.bufferForPath("a").retain().release()).not.toBe buffer
|
||||
|
||||
describe ".bufferForPathAsync(path)", ->
|
||||
[buffer] = []
|
||||
beforeEach ->
|
||||
waitsForPromise ->
|
||||
project.bufferForPathAsync("a").then (o) ->
|
||||
buffer = o
|
||||
buffer.retain()
|
||||
|
||||
afterEach ->
|
||||
buffer.release()
|
||||
|
||||
describe "when opening a previously opened path", ->
|
||||
it "does not create a new buffer", ->
|
||||
waitsForPromise ->
|
||||
project.bufferForPathAsync("a").then (anotherBuffer) ->
|
||||
expect(anotherBuffer).toBe buffer
|
||||
|
||||
waitsForPromise ->
|
||||
project.bufferForPathAsync("b").then (anotherBuffer) ->
|
||||
expect(anotherBuffer).not.toBe buffer
|
||||
|
||||
it "creates a new buffer if the previous buffer was destroyed", ->
|
||||
buffer.release()
|
||||
|
||||
waitsForPromise ->
|
||||
project.bufferForPathAsync("b").then (anotherBuffer) ->
|
||||
expect(anotherBuffer).not.toBe buffer
|
||||
|
||||
describe ".resolve(uri)", ->
|
||||
describe "when passed an absolute or relative path", ->
|
||||
it "returns an absolute path based on the project's root", ->
|
||||
@ -388,7 +505,7 @@ describe "Project", ->
|
||||
expect(matches.length).toBe 1
|
||||
|
||||
it "includes files and folders that begin with a '.'", ->
|
||||
projectPath = '/tmp/atom-tests/folder-with-dot-file'
|
||||
projectPath = temp.mkdirSync()
|
||||
filePath = path.join(projectPath, '.text')
|
||||
fs.writeSync(filePath, 'match this')
|
||||
project.setPath(projectPath)
|
||||
|
@ -1,4 +1,5 @@
|
||||
{$, $$, fs, RootView, View} = require 'atom'
|
||||
Q = require 'q'
|
||||
path = require 'path'
|
||||
Pane = require '../src/pane'
|
||||
|
||||
@ -32,7 +33,7 @@ describe "RootView", ->
|
||||
editor1 = rootView.getActiveView()
|
||||
buffer = editor1.getBuffer()
|
||||
editor1.splitRight()
|
||||
expect(rootView.getActiveView()).toBe rootView.getEditors()[1]
|
||||
expect(rootView.getActiveView()).toBe rootView.getEditors()[2]
|
||||
|
||||
refreshRootViewAndProject()
|
||||
|
||||
@ -133,7 +134,7 @@ describe "RootView", ->
|
||||
|
||||
window.keymap.bindKeys('*', 'x': 'foo-command')
|
||||
|
||||
describe "when a keydown event is triggered on the RootView", ->
|
||||
describe "when a keydown event is triggered in the RootView", ->
|
||||
it "triggers matching keybindings for that event", ->
|
||||
event = keydownEvent 'x', target: rootView[0]
|
||||
|
||||
@ -168,10 +169,10 @@ describe "RootView", ->
|
||||
expect(rootView.title).toBe "#{item.getTitle()} - #{project.getPath()}"
|
||||
|
||||
describe "when the last pane item is removed", ->
|
||||
it "update the title to contain the project's path", ->
|
||||
it "updates the title to contain the project's path", ->
|
||||
rootView.getActivePane().remove()
|
||||
expect(rootView.getActivePaneItem()).toBeUndefined()
|
||||
expect(rootView.title).toBe "atom - #{project.getPath()}"
|
||||
expect(rootView.title).toBe project.getPath()
|
||||
|
||||
describe "when an inactive pane's item changes", ->
|
||||
it "does not update the title", ->
|
||||
@ -181,6 +182,13 @@ describe "RootView", ->
|
||||
pane.showNextItem()
|
||||
expect(rootView.title).toBe initialTitle
|
||||
|
||||
describe "when the root view is deserialized", ->
|
||||
it "updates the title to contain the project's path", ->
|
||||
rootView2 = atom.deserializers.deserialize(rootView.serialize())
|
||||
item = rootView.getActivePaneItem()
|
||||
expect(rootView2.title).toBe "#{item.getTitle()} - #{project.getPath()}"
|
||||
rootView2.remove()
|
||||
|
||||
describe "font size adjustment", ->
|
||||
it "increases/decreases font size when increase/decrease-font-size events are triggered", ->
|
||||
fontSizeBefore = config.get('editor.fontSize')
|
||||
@ -198,7 +206,7 @@ describe "RootView", ->
|
||||
rootView.trigger 'window:decrease-font-size'
|
||||
expect(config.get('editor.fontSize')).toBe 1
|
||||
|
||||
describe ".open(path, options)", ->
|
||||
describe ".open(filePath, options)", ->
|
||||
describe "when there is no active pane", ->
|
||||
beforeEach ->
|
||||
spyOn(Pane.prototype, 'focus')
|
||||
@ -238,7 +246,7 @@ describe "RootView", ->
|
||||
initialItemCount = activePane.getItems().length
|
||||
|
||||
describe "when called with no path", ->
|
||||
it "opens an edit session with an empty buffer as an item on the active pane and focuses it", ->
|
||||
it "opens an edit session with an empty buffer as an item in the active pane and focuses it", ->
|
||||
editSession = rootView.open()
|
||||
expect(activePane.getItems().length).toBe initialItemCount + 1
|
||||
expect(activePane.activeItem).toBe editSession
|
||||
@ -247,7 +255,7 @@ describe "RootView", ->
|
||||
|
||||
describe "when called with a path", ->
|
||||
describe "when the active pane already has an edit session item for the path being opened", ->
|
||||
it "shows the existing edit session on the pane", ->
|
||||
it "shows the existing edit session in the pane", ->
|
||||
previousEditSession = activePane.activeItem
|
||||
|
||||
editSession = rootView.open('b')
|
||||
@ -272,6 +280,106 @@ describe "RootView", ->
|
||||
editSession = rootView.open('b', changeFocus: false)
|
||||
expect(activePane.focus).not.toHaveBeenCalled()
|
||||
|
||||
describe ".openAsync(filePath)", ->
|
||||
beforeEach ->
|
||||
spyOn(Pane.prototype, 'focus')
|
||||
|
||||
describe "when there is no active pane", ->
|
||||
beforeEach ->
|
||||
rootView.getActivePane().remove()
|
||||
expect(rootView.getActivePane()).toBeUndefined()
|
||||
|
||||
describe "when called with no path", ->
|
||||
it "creates a empty edit session as an item on a new pane, and focuses the pane", ->
|
||||
editSession = null
|
||||
|
||||
waitsForPromise ->
|
||||
rootView.openAsync().then (o) -> editSession = o
|
||||
|
||||
runs ->
|
||||
expect(rootView.getActivePane().activeItem).toBe editSession
|
||||
expect(editSession.getPath()).toBeUndefined()
|
||||
expect(rootView.getActivePane().focus).toHaveBeenCalled()
|
||||
|
||||
it "can create multiple empty edit sessions as items on a pane", ->
|
||||
editSession1 = null
|
||||
editSession2 = null
|
||||
|
||||
waitsForPromise ->
|
||||
rootView.openAsync()
|
||||
.then (o) ->
|
||||
editSession1 = o
|
||||
rootView.openAsync()
|
||||
.then (o) ->
|
||||
editSession2 = o
|
||||
|
||||
runs ->
|
||||
expect(rootView.getActivePane().getItems().length).toBe 2
|
||||
expect(editSession1).not.toBe editSession2
|
||||
|
||||
describe "when called with a path", ->
|
||||
it "creates an edit session for the given path as an item on a new pane, and focuses the pane", ->
|
||||
editSession = null
|
||||
waitsForPromise ->
|
||||
rootView.openAsync('b').then (o) -> editSession = o
|
||||
|
||||
runs ->
|
||||
expect(rootView.getActivePane().activeItem).toBe editSession
|
||||
expect(editSession.getPath()).toBe require.resolve('./fixtures/dir/b')
|
||||
expect(rootView.getActivePane().focus).toHaveBeenCalled()
|
||||
|
||||
describe "when there is an active pane", ->
|
||||
[activePane] = []
|
||||
|
||||
beforeEach ->
|
||||
activePane = rootView.getActivePane()
|
||||
|
||||
describe "when called with no path", ->
|
||||
it "opens an edit session with an empty buffer as an item in the active pane and focuses it", ->
|
||||
editSession = null
|
||||
|
||||
waitsForPromise ->
|
||||
rootView.openAsync().then (o) -> editSession = o
|
||||
|
||||
runs ->
|
||||
expect(activePane.getItems().length).toBe 2
|
||||
expect(activePane.activeItem).toBe editSession
|
||||
expect(editSession.getPath()).toBeUndefined()
|
||||
expect(activePane.focus).toHaveBeenCalled()
|
||||
|
||||
describe "when called with a path", ->
|
||||
describe "when the active pane already has an item for the given path", ->
|
||||
it "shows the existing edit session in the pane", ->
|
||||
previousEditSession = activePane.activeItem
|
||||
|
||||
editSession = null
|
||||
waitsForPromise ->
|
||||
rootView.openAsync('b').then (o) -> editSession = o
|
||||
|
||||
runs ->
|
||||
expect(activePane.activeItem).toBe editSession
|
||||
expect(editSession).not.toBe previousEditSession
|
||||
|
||||
waitsForPromise ->
|
||||
rootView.openAsync(previousEditSession.getPath()).then (o) -> editSession = o
|
||||
|
||||
runs ->
|
||||
expect(editSession).toBe previousEditSession
|
||||
expect(activePane.activeItem).toBe editSession
|
||||
expect(activePane.focus).toHaveBeenCalled()
|
||||
|
||||
describe "when the active pane does not have an existing item for the given path", ->
|
||||
it "creates a new edit session for the given path in the active pane", ->
|
||||
editSession = null
|
||||
|
||||
waitsForPromise ->
|
||||
rootView.openAsync('b').then (o) -> editSession = o
|
||||
|
||||
runs ->
|
||||
expect(activePane.activeItem).toBe editSession
|
||||
expect(activePane.getItems().length).toBe 2
|
||||
expect(activePane.focus).toHaveBeenCalled()
|
||||
|
||||
describe "window:toggle-invisibles event", ->
|
||||
it "shows/hides invisibles in all open and future editors", ->
|
||||
rootView.height(200)
|
||||
|
@ -1,11 +1,17 @@
|
||||
try
|
||||
require '../src/atom'
|
||||
require '../src/window'
|
||||
atom.show()
|
||||
Atom = require '../src/atom'
|
||||
window.atom = new Atom()
|
||||
window.atom.show() unless atom.getLoadSettings().exitWhenDone
|
||||
{runSpecSuite} = require './jasmine-helper'
|
||||
|
||||
document.title = "Spec Suite"
|
||||
runSpecSuite './spec-suite'
|
||||
catch e
|
||||
console.error(e.stack ? e)
|
||||
catch error
|
||||
unless atom.getLoadSettings().exitWhenDone
|
||||
atom.getCurrentWindow().setSize(800, 600)
|
||||
atom.getCurrentWindow().center()
|
||||
atom.openDevTools()
|
||||
|
||||
console.error(error.stack ? error)
|
||||
atom.exit(1) if atom.getLoadSettings().exitWhenDone
|
||||
|
@ -14,48 +14,60 @@ TokenizedBuffer = require '../src/tokenized-buffer'
|
||||
pathwatcher = require 'pathwatcher'
|
||||
clipboard = require 'clipboard'
|
||||
|
||||
atom.loadBaseStylesheets()
|
||||
requireStylesheet '../static/jasmine'
|
||||
atom.themes.loadBaseStylesheets()
|
||||
atom.themes.requireStylesheet '../static/jasmine'
|
||||
|
||||
fixturePackagesPath = path.resolve(__dirname, './fixtures/packages')
|
||||
config.packageDirPaths.unshift(fixturePackagesPath)
|
||||
keymap.loadBundledKeymaps()
|
||||
atom.packages.packageDirPaths.unshift(fixturePackagesPath)
|
||||
atom.keymap.loadBundledKeymaps()
|
||||
[bindingSetsToRestore, bindingSetsByFirstKeystrokeToRestore] = []
|
||||
|
||||
$(window).on 'core:close', -> window.close()
|
||||
$(window).on 'unload', ->
|
||||
atom.windowMode = 'spec'
|
||||
atom.getWindowState().set('dimensions', atom.getDimensions())
|
||||
atom.saveWindowState()
|
||||
$('html,body').css('overflow', 'auto')
|
||||
|
||||
jasmine.getEnv().addEqualityTester(_.isEqual) # Use underscore's definition of equality for toEqual assertions
|
||||
jasmine.getEnv().defaultTimeoutInterval = 5000
|
||||
|
||||
specDirectory = atom.getLoadSettings().specDirectory ? __dirname
|
||||
specProjectPath = path.join(specDirectory, 'fixtures')
|
||||
|
||||
beforeEach ->
|
||||
$.fx.off = true
|
||||
|
||||
specDirectory = atom.getLoadSettings().specDirectory ? __dirname
|
||||
window.project = new Project(path.join(specDirectory, 'fixtures'))
|
||||
atom.project = new Project(specProjectPath)
|
||||
window.project = atom.project
|
||||
|
||||
window.resetTimeouts()
|
||||
atom.packageStates = {}
|
||||
atom.packages.packageStates = {}
|
||||
spyOn(atom, 'saveWindowState')
|
||||
syntax.clearGrammarOverrides()
|
||||
syntax.clearProperties()
|
||||
atom.syntax.clearGrammarOverrides()
|
||||
atom.syntax.clearProperties()
|
||||
|
||||
# used to reset keymap after each spec
|
||||
bindingSetsToRestore = _.clone(keymap.bindingSets)
|
||||
bindingSetsByFirstKeystrokeToRestore = _.clone(keymap.bindingSetsByFirstKeystroke)
|
||||
|
||||
# prevent specs from modifying Atom's menus
|
||||
spyOn(atom.menu, 'sendToBrowserProcess')
|
||||
|
||||
# reset config before each spec; don't load or save from/to `config.json`
|
||||
window.config = new Config()
|
||||
config = new Config
|
||||
resourcePath: window.resourcePath
|
||||
configDirPath: atom.getConfigDirPath()
|
||||
config.packageDirPaths.unshift(fixturePackagesPath)
|
||||
spyOn(config, 'load')
|
||||
spyOn(config, 'save')
|
||||
config.set "editor.fontFamily", "Courier"
|
||||
config.set "editor.fontSize", 16
|
||||
config.set "editor.autoIndent", false
|
||||
config.set "core.disabledPackages", ["package-that-throws-an-exception"]
|
||||
config.set "core.disabledPackages", ["package-that-throws-an-exception",
|
||||
"package-with-broken-package-json", "package-with-broken-keymap"]
|
||||
config.save.reset()
|
||||
atom.config = config
|
||||
window.config = config
|
||||
|
||||
# make editor display updates synchronous
|
||||
spyOn(Editor.prototype, 'requestDisplayUpdate').andCallFake -> @updateDisplay()
|
||||
@ -78,12 +90,17 @@ afterEach ->
|
||||
keymap.bindingSets = bindingSetsToRestore
|
||||
keymap.bindingSetsByFirstKeystroke = bindingSetsByFirstKeystrokeToRestore
|
||||
atom.deactivatePackages()
|
||||
if rootView?
|
||||
rootView.remove?()
|
||||
window.rootView = null
|
||||
if project?
|
||||
project.destroy()
|
||||
window.project = null
|
||||
|
||||
window.rootView?.remove?()
|
||||
atom.rootView?.remove?() if atom.rootView isnt window.rootView
|
||||
window.rootView = null
|
||||
atom.rootView = null
|
||||
|
||||
window.project?.destroy?()
|
||||
atom.project?.destroy?() if atom.project isnt window.project
|
||||
window.project = null
|
||||
atom.project = null
|
||||
|
||||
$('#jasmine-content').empty() unless window.debugContent
|
||||
delete atom.windowState
|
||||
jasmine.unspy(atom, 'saveWindowState')
|
||||
|
@ -1,5 +1,6 @@
|
||||
{_, fs} = require 'atom'
|
||||
path = require 'path'
|
||||
temp = require 'temp'
|
||||
{Site} = require 'telepath'
|
||||
|
||||
describe 'TextBuffer', ->
|
||||
@ -80,15 +81,14 @@ describe 'TextBuffer', ->
|
||||
filePath = null
|
||||
|
||||
beforeEach ->
|
||||
filePath = "/tmp/tmp.txt"
|
||||
fs.writeSync(filePath, "first")
|
||||
buffer.release()
|
||||
filePath = temp.openSync('atom').path
|
||||
fs.writeSync(filePath, "first")
|
||||
buffer = project.bufferForPath(filePath).retain()
|
||||
|
||||
afterEach ->
|
||||
buffer.release()
|
||||
buffer = null
|
||||
fs.remove(filePath) if fs.exists(filePath)
|
||||
|
||||
it "does not trigger a change event when Atom modifies the file", ->
|
||||
buffer.insert([0,0], "HELLO!")
|
||||
@ -176,7 +176,7 @@ describe 'TextBuffer', ->
|
||||
|
||||
it "resumes watching of the file when it is re-saved", ->
|
||||
bufferToDelete.save()
|
||||
expect(bufferToDelete.fileExists()).toBeTruthy()
|
||||
expect(fs.exists(bufferToDelete.getPath())).toBeTruthy()
|
||||
expect(bufferToDelete.isInConflict()).toBeFalsy()
|
||||
|
||||
fs.writeSync(filePath, 'moo')
|
||||
@ -946,7 +946,9 @@ describe 'TextBuffer', ->
|
||||
|
||||
describe "when the serialized buffer was unsaved and had no path", ->
|
||||
it "restores the previous unsaved state of the buffer", ->
|
||||
buffer.setPath(undefined)
|
||||
buffer.release()
|
||||
|
||||
buffer = project.bufferForPath()
|
||||
buffer.setText("abc")
|
||||
|
||||
state = buffer.serialize()
|
||||
|
@ -1,4 +1,5 @@
|
||||
{$} = require 'atom'
|
||||
path = require 'path'
|
||||
{$, $$, fs, RootView} = require 'atom'
|
||||
|
||||
ThemeManager = require '../src/theme-manager'
|
||||
AtomPackage = require '../src/atom-package'
|
||||
@ -23,6 +24,10 @@ describe "ThemeManager", ->
|
||||
expect(paths[0]).toContain 'atom-dark-ui'
|
||||
expect(paths[1]).toContain 'atom-light-ui'
|
||||
|
||||
it "ignores themes that cannot be resolved to a directory", ->
|
||||
config.set('core.themes', ['definitely-not-a-theme'])
|
||||
expect(-> themeManager.getImportPaths()).not.toThrow()
|
||||
|
||||
describe "when the core.themes config value changes", ->
|
||||
it "add/removes stylesheets to reflect the new config value", ->
|
||||
themeManager.on 'reloaded', reloadHandler = jasmine.createSpy()
|
||||
@ -69,3 +74,81 @@ describe "ThemeManager", ->
|
||||
|
||||
expect(loadHandler).toHaveBeenCalled()
|
||||
expect(loadHandler.mostRecentCall.args[0]).toBeInstanceOf AtomPackage
|
||||
|
||||
describe "requireStylesheet(path)", ->
|
||||
it "synchronously loads css at the given path and installs a style tag for it in the head", ->
|
||||
cssPath = project.resolve('css.css')
|
||||
lengthBefore = $('head style').length
|
||||
|
||||
themeManager.requireStylesheet(cssPath)
|
||||
expect($('head style').length).toBe lengthBefore + 1
|
||||
|
||||
element = $('head style[id*="css.css"]')
|
||||
expect(element.attr('id')).toBe cssPath
|
||||
expect(element.text()).toBe fs.read(cssPath)
|
||||
|
||||
# doesn't append twice
|
||||
themeManager.requireStylesheet(cssPath)
|
||||
expect($('head style').length).toBe lengthBefore + 1
|
||||
|
||||
$('head style[id*="css.css"]').remove()
|
||||
|
||||
it "synchronously loads and parses less files at the given path and installs a style tag for it in the head", ->
|
||||
lessPath = project.resolve('sample.less')
|
||||
lengthBefore = $('head style').length
|
||||
themeManager.requireStylesheet(lessPath)
|
||||
expect($('head style').length).toBe lengthBefore + 1
|
||||
|
||||
element = $('head style[id*="sample.less"]')
|
||||
expect(element.attr('id')).toBe lessPath
|
||||
expect(element.text()).toBe """
|
||||
#header {
|
||||
color: #4d926f;
|
||||
}
|
||||
h2 {
|
||||
color: #4d926f;
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
# doesn't append twice
|
||||
themeManager.requireStylesheet(lessPath)
|
||||
expect($('head style').length).toBe lengthBefore + 1
|
||||
$('head style[id*="sample.less"]').remove()
|
||||
|
||||
it "supports requiring css and less stylesheets without an explicit extension", ->
|
||||
themeManager.requireStylesheet path.join(__dirname, 'fixtures', 'css')
|
||||
expect($('head style[id*="css.css"]').attr('id')).toBe project.resolve('css.css')
|
||||
themeManager.requireStylesheet path.join(__dirname, 'fixtures', 'sample')
|
||||
expect($('head style[id*="sample.less"]').attr('id')).toBe project.resolve('sample.less')
|
||||
|
||||
$('head style[id*="css.css"]').remove()
|
||||
$('head style[id*="sample.less"]').remove()
|
||||
|
||||
describe ".removeStylesheet(path)", ->
|
||||
it "removes styling applied by given stylesheet path", ->
|
||||
cssPath = require.resolve('./fixtures/css.css')
|
||||
|
||||
expect($(document.body).css('font-weight')).not.toBe("bold")
|
||||
themeManager.requireStylesheet(cssPath)
|
||||
expect($(document.body).css('font-weight')).toBe("bold")
|
||||
themeManager.removeStylesheet(cssPath)
|
||||
expect($(document.body).css('font-weight')).not.toBe("bold")
|
||||
|
||||
describe "base stylesheet loading", ->
|
||||
beforeEach ->
|
||||
window.rootView = new RootView
|
||||
rootView.append $$ -> @div class: 'editor'
|
||||
rootView.attachToDom()
|
||||
themeManager.load()
|
||||
|
||||
it "loads the correct values from the theme's ui-variables file", ->
|
||||
config.set('core.themes', ['theme-with-ui-variables'])
|
||||
|
||||
# an override loaded in the base css
|
||||
expect(rootView.css("background-color")).toBe "rgb(0, 0, 255)"
|
||||
|
||||
# from within the theme itself
|
||||
expect($(".editor").css("padding-top")).toBe "150px"
|
||||
expect($(".editor").css("padding-right")).toBe "150px"
|
||||
expect($(".editor").css("padding-bottom")).toBe "150px"
|
||||
|
@ -53,6 +53,7 @@ class TimeReporter extends jasmine.Reporter
|
||||
window.timedSpecs.push
|
||||
description: @description
|
||||
time: duration
|
||||
fullName: spec.getFullName()
|
||||
|
||||
if timedSuites[@suite]
|
||||
window.timedSuites[@suite] += duration
|
||||
|
@ -26,6 +26,18 @@ describe "TokenizedBuffer", ->
|
||||
expect(tokenizedBuffer2.buffer).toBe tokenizedBuffer1.buffer
|
||||
expect(tokenizedBuffer2.getTabLength()).toBe tokenizedBuffer1.getTabLength()
|
||||
|
||||
describe "when the buffer is destroyed", ->
|
||||
beforeEach ->
|
||||
buffer = project.bufferForPath('sample.js')
|
||||
tokenizedBuffer = new TokenizedBuffer({buffer})
|
||||
startTokenizing(tokenizedBuffer)
|
||||
|
||||
it "stops tokenization", ->
|
||||
tokenizedBuffer.destroy()
|
||||
spyOn(tokenizedBuffer, 'tokenizeNextChunk')
|
||||
advanceClock()
|
||||
expect(tokenizedBuffer.tokenizeNextChunk).not.toHaveBeenCalled()
|
||||
|
||||
describe "when the buffer contains soft-tabs", ->
|
||||
beforeEach ->
|
||||
buffer = project.bufferForPath('sample.js')
|
||||
|
@ -7,7 +7,8 @@ describe "Window", ->
|
||||
|
||||
beforeEach ->
|
||||
spyOn(atom, 'hide')
|
||||
atom.getLoadSettings().initialPath = project.getPath()
|
||||
atom.getLoadSettings() # Causes atom.loadSettings to be initialized
|
||||
atom.loadSettings.initialPath = project.getPath()
|
||||
project.destroy()
|
||||
windowEventHandler = new WindowEventHandler()
|
||||
window.deserializeEditorWindow()
|
||||
@ -75,66 +76,6 @@ describe "Window", ->
|
||||
expect(window.onbeforeunload(new Event('beforeunload'))).toBeFalsy()
|
||||
expect(atom.confirmSync).toHaveBeenCalled()
|
||||
|
||||
describe "requireStylesheet(path)", ->
|
||||
it "synchronously loads css at the given path and installs a style tag for it in the head", ->
|
||||
cssPath = project.resolve('css.css')
|
||||
lengthBefore = $('head style').length
|
||||
|
||||
requireStylesheet(cssPath)
|
||||
expect($('head style').length).toBe lengthBefore + 1
|
||||
|
||||
element = $('head style[id*="css.css"]')
|
||||
expect(element.attr('id')).toBe cssPath
|
||||
expect(element.text()).toBe fs.read(cssPath)
|
||||
|
||||
# doesn't append twice
|
||||
requireStylesheet(cssPath)
|
||||
expect($('head style').length).toBe lengthBefore + 1
|
||||
|
||||
$('head style[id*="css.css"]').remove()
|
||||
|
||||
it "synchronously loads and parses less files at the given path and installs a style tag for it in the head", ->
|
||||
lessPath = project.resolve('sample.less')
|
||||
lengthBefore = $('head style').length
|
||||
requireStylesheet(lessPath)
|
||||
expect($('head style').length).toBe lengthBefore + 1
|
||||
|
||||
element = $('head style[id*="sample.less"]')
|
||||
expect(element.attr('id')).toBe lessPath
|
||||
expect(element.text()).toBe """
|
||||
#header {
|
||||
color: #4d926f;
|
||||
}
|
||||
h2 {
|
||||
color: #4d926f;
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
# doesn't append twice
|
||||
requireStylesheet(lessPath)
|
||||
expect($('head style').length).toBe lengthBefore + 1
|
||||
$('head style[id*="sample.less"]').remove()
|
||||
|
||||
it "supports requiring css and less stylesheets without an explicit extension", ->
|
||||
requireStylesheet path.join(__dirname, 'fixtures', 'css')
|
||||
expect($('head style[id*="css.css"]').attr('id')).toBe project.resolve('css.css')
|
||||
requireStylesheet path.join(__dirname, 'fixtures', 'sample')
|
||||
expect($('head style[id*="sample.less"]').attr('id')).toBe project.resolve('sample.less')
|
||||
|
||||
$('head style[id*="css.css"]').remove()
|
||||
$('head style[id*="sample.less"]').remove()
|
||||
|
||||
describe ".removeStylesheet(path)", ->
|
||||
it "removes styling applied by given stylesheet path", ->
|
||||
cssPath = require.resolve('./fixtures/css.css')
|
||||
|
||||
expect($(document.body).css('font-weight')).not.toBe("bold")
|
||||
requireStylesheet(cssPath)
|
||||
expect($(document.body).css('font-weight')).toBe("bold")
|
||||
removeStylesheet(cssPath)
|
||||
expect($(document.body).css('font-weight')).not.toBe("bold")
|
||||
|
||||
describe ".unloadEditorWindow()", ->
|
||||
it "saves the serialized state of the window so it can be deserialized after reload", ->
|
||||
rootViewState = rootView.serialize()
|
||||
@ -156,38 +97,6 @@ describe "Window", ->
|
||||
|
||||
expect(buffer.subscriptionCount()).toBe 0
|
||||
|
||||
describe ".deserialize(state)", ->
|
||||
class Foo
|
||||
@deserialize: ({name}) -> new Foo(name)
|
||||
constructor: (@name) ->
|
||||
|
||||
beforeEach ->
|
||||
registerDeserializer(Foo)
|
||||
|
||||
afterEach ->
|
||||
unregisterDeserializer(Foo)
|
||||
|
||||
it "calls deserialize on the deserializer for the given state object, or returns undefined if one can't be found", ->
|
||||
spyOn(console, 'warn')
|
||||
object = deserialize({ deserializer: 'Foo', name: 'Bar' })
|
||||
expect(object.name).toBe 'Bar'
|
||||
expect(deserialize({ deserializer: 'Bogus' })).toBeUndefined()
|
||||
|
||||
describe "when the deserializer has a version", ->
|
||||
beforeEach ->
|
||||
Foo.version = 2
|
||||
|
||||
describe "when the deserialized state has a matching version", ->
|
||||
it "attempts to deserialize the state", ->
|
||||
object = deserialize({ deserializer: 'Foo', version: 2, name: 'Bar' })
|
||||
expect(object.name).toBe 'Bar'
|
||||
|
||||
describe "when the deserialized state has a non-matching version", ->
|
||||
it "returns undefined", ->
|
||||
expect(deserialize({ deserializer: 'Foo', version: 3, name: 'Bar' })).toBeUndefined()
|
||||
expect(deserialize({ deserializer: 'Foo', version: 1, name: 'Bar' })).toBeUndefined()
|
||||
expect(deserialize({ deserializer: 'Foo', name: 'Bar' })).toBeUndefined()
|
||||
|
||||
describe "drag and drop", ->
|
||||
buildDragEvent = (type, files) ->
|
||||
dataTransfer =
|
||||
@ -206,7 +115,7 @@ describe "Window", ->
|
||||
it "opens it", ->
|
||||
spyOn(atom, "open")
|
||||
event = buildDragEvent("drop", [ {path: "/fake1"}, {path: "/fake2"} ])
|
||||
window.onDrop(event)
|
||||
$(document).trigger(event)
|
||||
expect(atom.open.callCount).toBe 1
|
||||
expect(atom.open.argsForCall[0][0]).toEqual pathsToOpen: ['/fake1', '/fake2']
|
||||
|
||||
@ -214,7 +123,7 @@ describe "Window", ->
|
||||
it "does nothing", ->
|
||||
spyOn(atom, "open")
|
||||
event = buildDragEvent("drop", [])
|
||||
window.onDrop(event)
|
||||
$(document).trigger(event)
|
||||
expect(atom.open).not.toHaveBeenCalled()
|
||||
|
||||
describe "when a link is clicked", ->
|
||||
|
@ -28,26 +28,28 @@ class AtomPackage extends Package
|
||||
getType: -> 'atom'
|
||||
|
||||
load: ->
|
||||
@metadata = {}
|
||||
@stylesheets = []
|
||||
@keymaps = []
|
||||
@menus = []
|
||||
@grammars = []
|
||||
@scopedProperties = []
|
||||
|
||||
@measure 'loadTime', =>
|
||||
try
|
||||
@metadata = Package.loadMetadata(@path)
|
||||
if @isTheme()
|
||||
@stylesheets = []
|
||||
@keymaps = []
|
||||
@menus = []
|
||||
@grammars = []
|
||||
@scopedProperties = []
|
||||
else
|
||||
@loadKeymaps()
|
||||
@loadMenus()
|
||||
@loadStylesheets()
|
||||
@loadGrammars()
|
||||
@loadScopedProperties()
|
||||
return if @isTheme()
|
||||
|
||||
if @metadata.activationEvents?
|
||||
@registerDeferredDeserializers()
|
||||
else
|
||||
@requireMainModule()
|
||||
@loadKeymaps()
|
||||
@loadMenus()
|
||||
@loadStylesheets()
|
||||
@loadGrammars()
|
||||
@loadScopedProperties()
|
||||
|
||||
if @metadata.activationEvents?
|
||||
@registerDeferredDeserializers()
|
||||
else
|
||||
@requireMainModule()
|
||||
|
||||
catch e
|
||||
console.warn "Failed to load package named '#{@name}'", e.stack ? e
|
||||
@ -82,12 +84,17 @@ class AtomPackage extends Package
|
||||
@configActivated = true
|
||||
|
||||
activateStylesheets: ->
|
||||
return if @stylesheetsActivated
|
||||
|
||||
type = if @metadata.theme then 'theme' else 'bundled'
|
||||
applyStylesheet(stylesheetPath, content, type) for [stylesheetPath, content] in @stylesheets
|
||||
for [stylesheetPath, content] in @stylesheets
|
||||
atom.themes.applyStylesheet(stylesheetPath, content, type)
|
||||
@stylesheetsActivated = true
|
||||
|
||||
activateResources: ->
|
||||
keymap.add(keymapPath, map) for [keymapPath, map] in @keymaps
|
||||
atom.keymap.add(keymapPath, map) for [keymapPath, map] in @keymaps
|
||||
atom.contextMenu.add(menuPath, map['context-menu']) for [menuPath, map] in @menus
|
||||
atom.menu.add(map.menu) for [menuPath, map] in @menus when map.menu
|
||||
syntax.addGrammar(grammar) for grammar in @grammars
|
||||
for [scopedPropertiesPath, selector, properties] in @scopedProperties
|
||||
syntax.addProperties(scopedPropertiesPath, selector, properties)
|
||||
@ -113,7 +120,8 @@ class AtomPackage extends Package
|
||||
fsUtils.listSync(menusDirPath, ['cson', 'json'])
|
||||
|
||||
loadStylesheets: ->
|
||||
@stylesheets = @getStylesheetPaths().map (stylesheetPath) -> [stylesheetPath, loadStylesheet(stylesheetPath)]
|
||||
@stylesheets = @getStylesheetPaths().map (stylesheetPath) ->
|
||||
[stylesheetPath, atom.themes.loadStylesheet(stylesheetPath)]
|
||||
|
||||
getStylesheetsPath: ->
|
||||
path.join(@path, @constructor.stylesheetsDir)
|
||||
@ -164,18 +172,19 @@ class AtomPackage extends Package
|
||||
deactivateResources: ->
|
||||
syntax.removeGrammar(grammar) for grammar in @grammars
|
||||
syntax.removeProperties(scopedPropertiesPath) for [scopedPropertiesPath] in @scopedProperties
|
||||
keymap.remove(keymapPath) for [keymapPath] in @keymaps
|
||||
removeStylesheet(stylesheetPath) for [stylesheetPath] in @stylesheets
|
||||
atom.keymap.remove(keymapPath) for [keymapPath] in @keymaps
|
||||
atom.themes.removeStylesheet(stylesheetPath) for [stylesheetPath] in @stylesheets
|
||||
@stylesheetsActivated = false
|
||||
|
||||
reloadStylesheets: ->
|
||||
oldSheets = _.clone(@stylesheets)
|
||||
@loadStylesheets()
|
||||
removeStylesheet(stylesheetPath) for [stylesheetPath] in oldSheets
|
||||
atom.themes.removeStylesheet(stylesheetPath) for [stylesheetPath] in oldSheets
|
||||
@reloadStylesheet(stylesheetPath, content) for [stylesheetPath, content] in @stylesheets
|
||||
|
||||
reloadStylesheet: (stylesheetPath, content) ->
|
||||
type = if @metadata.theme then 'theme' else 'bundled'
|
||||
window.applyStylesheet(stylesheetPath, content, type)
|
||||
atom.themes.applyStylesheet(stylesheetPath, content, type)
|
||||
|
||||
requireMainModule: ->
|
||||
return @mainModule if @mainModule?
|
||||
@ -194,7 +203,9 @@ class AtomPackage extends Package
|
||||
|
||||
registerDeferredDeserializers: ->
|
||||
for deserializerName in @metadata.deferredDeserializers ? []
|
||||
registerDeferredDeserializer deserializerName, => @requireMainModule()
|
||||
registerDeferredDeserializer deserializerName, =>
|
||||
@activateStylesheets()
|
||||
@requireMainModule()
|
||||
|
||||
subscribeToActivationEvents: ->
|
||||
return unless @metadata.activationEvents?
|
||||
|
320
src/atom.coffee
320
src/atom.coffee
@ -4,157 +4,159 @@ _ = require './underscore-extensions'
|
||||
Package = require './package'
|
||||
ipc = require 'ipc'
|
||||
remote = require 'remote'
|
||||
shell = require 'shell'
|
||||
crypto = require 'crypto'
|
||||
path = require 'path'
|
||||
dialog = remote.require 'dialog'
|
||||
app = remote.require 'app'
|
||||
{Document} = require 'telepath'
|
||||
ThemeManager = require './theme-manager'
|
||||
ContextMenuManager = require './context-menu-manager'
|
||||
DeserializerManager = require './deserializer-manager'
|
||||
Subscriber = require './subscriber'
|
||||
|
||||
window.atom =
|
||||
loadedPackages: {}
|
||||
activePackages: {}
|
||||
packageStates: {}
|
||||
themes: new ThemeManager()
|
||||
contextMenu: new ContextMenuManager(remote.getCurrentWindow().loadSettings.devMode)
|
||||
# Public: Atom global for dealing with packages, themes, menus, and the window.
|
||||
#
|
||||
# An instance of this class is always available as the `atom` global.
|
||||
module.exports =
|
||||
class Atom
|
||||
_.extend @prototype, Subscriber
|
||||
|
||||
constructor: ->
|
||||
@rootViewParentSelector = 'body'
|
||||
@deserializers = new DeserializerManager()
|
||||
|
||||
initialize: ->
|
||||
@unsubscribe()
|
||||
|
||||
{devMode, resourcePath} = atom.getLoadSettings()
|
||||
configDirPath = @getConfigDirPath()
|
||||
|
||||
Config = require './config'
|
||||
Keymap = require './keymap'
|
||||
PackageManager = require './package-manager'
|
||||
Pasteboard = require './pasteboard'
|
||||
Syntax = require './syntax'
|
||||
ThemeManager = require './theme-manager'
|
||||
ContextMenuManager = require './context-menu-manager'
|
||||
MenuManager = require './menu-manager'
|
||||
|
||||
@config = new Config({configDirPath, resourcePath})
|
||||
@keymap = new Keymap()
|
||||
@packages = new PackageManager({devMode, configDirPath, resourcePath})
|
||||
|
||||
#TODO Remove once packages have been updated to not touch atom.packageStates directly
|
||||
@__defineGetter__ 'packageStates', => @packages.packageStates
|
||||
@__defineSetter__ 'packageStates', (packageStates) => @packages.packageStates = packageStates
|
||||
|
||||
@subscribe @packages, 'loaded', => @watchThemes()
|
||||
@themes = new ThemeManager()
|
||||
@contextMenu = new ContextMenuManager(devMode)
|
||||
@menu = new MenuManager()
|
||||
@pasteboard = new Pasteboard()
|
||||
@syntax = deserialize(@getWindowState('syntax')) ? new Syntax()
|
||||
|
||||
getCurrentWindow: ->
|
||||
remote.getCurrentWindow()
|
||||
|
||||
# Public: Get the dimensions of this window.
|
||||
#
|
||||
# Returns an object with x, y, width, and height keys.
|
||||
getDimensions: ->
|
||||
browserWindow = @getCurrentWindow()
|
||||
[x, y] = browserWindow.getPosition()
|
||||
[width, height] = browserWindow.getSize()
|
||||
{x, y, width, height}
|
||||
|
||||
# Public: Set the dimensions of the window.
|
||||
#
|
||||
# The window will be centered if either the x or y coordinate is not set
|
||||
# in the dimensions parameter.
|
||||
#
|
||||
# * dimensions:
|
||||
# + x:
|
||||
# The new x coordinate.
|
||||
# + y:
|
||||
# The new y coordinate.
|
||||
# + width:
|
||||
# The new width.
|
||||
# + height:
|
||||
# The new height.
|
||||
setDimensions: ({x, y, width, height}) ->
|
||||
browserWindow = @getCurrentWindow()
|
||||
browserWindow.setSize(width, height)
|
||||
if x? and y?
|
||||
browserWindow.setPosition(x, y)
|
||||
else
|
||||
browserWindow.center()
|
||||
|
||||
restoreDimensions: ->
|
||||
dimensions = @getWindowState().getObject('dimensions')
|
||||
unless dimensions?.width and dimensions?.height
|
||||
{height, width} = @getLoadSettings().initialSize ? {}
|
||||
height ?= screen.availHeight
|
||||
width ?= Math.min(screen.availWidth, 1024)
|
||||
dimensions = {width, height}
|
||||
@setDimensions(dimensions)
|
||||
|
||||
# Public: Get the load settings for the current window.
|
||||
#
|
||||
# Returns an object containing all the load setting key/value pairs.
|
||||
getLoadSettings: ->
|
||||
remote.getCurrentWindow().loadSettings
|
||||
@loadSettings ?= _.deepClone(@getCurrentWindow().loadSettings)
|
||||
_.deepClone(@loadSettings)
|
||||
|
||||
getPackageState: (name) ->
|
||||
@packageStates[name]
|
||||
deserializeProject: ->
|
||||
Project = require './project'
|
||||
state = @getWindowState()
|
||||
@project = deserialize(state.get('project'))
|
||||
unless @project?
|
||||
@project = new Project(@getLoadSettings().initialPath)
|
||||
state.set('project', @project.getState())
|
||||
|
||||
setPackageState: (name, state) ->
|
||||
@packageStates[name] = state
|
||||
deserializeRootView: ->
|
||||
RootView = require './root-view'
|
||||
state = @getWindowState()
|
||||
@rootView = deserialize(state.get('rootView'))
|
||||
unless @rootView?
|
||||
@rootView = new RootView()
|
||||
state.set('rootView', @rootView.getState())
|
||||
$(@rootViewParentSelector).append(@rootView)
|
||||
|
||||
activatePackages: ->
|
||||
@activatePackage(pack.name) for pack in @getLoadedPackages()
|
||||
deserializePackageStates: ->
|
||||
state = @getWindowState()
|
||||
@packages.packageStates = state.getObject('packageStates') ? {}
|
||||
state.remove('packageStates')
|
||||
|
||||
activatePackage: (name, options) ->
|
||||
if pack = @loadPackage(name, options)
|
||||
@activePackages[pack.name] = pack
|
||||
pack.activate(options)
|
||||
pack
|
||||
|
||||
deactivatePackages: ->
|
||||
@deactivatePackage(pack.name) for pack in @getActivePackages()
|
||||
|
||||
deactivatePackage: (name) ->
|
||||
if pack = @getActivePackage(name)
|
||||
@setPackageState(pack.name, state) if state = pack.serialize?()
|
||||
pack.deactivate()
|
||||
delete @activePackages[pack.name]
|
||||
else
|
||||
throw new Error("No active package for name '#{name}'")
|
||||
|
||||
getActivePackage: (name) ->
|
||||
@activePackages[name]
|
||||
|
||||
isPackageActive: (name) ->
|
||||
@getActivePackage(name)?
|
||||
|
||||
getActivePackages: ->
|
||||
_.values(@activePackages)
|
||||
|
||||
loadPackages: ->
|
||||
# Ensure atom exports is already in the require cache so the load time
|
||||
# of the first package isn't skewed by being the first to require atom
|
||||
require '../exports/atom'
|
||||
|
||||
@loadPackage(name) for name in @getAvailablePackageNames() when not @isPackageDisabled(name)
|
||||
@watchThemes()
|
||||
|
||||
loadPackage: (name, options) ->
|
||||
if @isPackageDisabled(name)
|
||||
return console.warn("Tried to load disabled package '#{name}'")
|
||||
|
||||
if packagePath = @resolvePackagePath(name)
|
||||
return pack if pack = @getLoadedPackage(name)
|
||||
pack = Package.load(packagePath, options)
|
||||
if pack.metadata.theme
|
||||
@themes.register(pack)
|
||||
else
|
||||
@loadedPackages[pack.name] = pack
|
||||
pack
|
||||
else
|
||||
throw new Error("Could not resolve '#{name}' to a package path")
|
||||
|
||||
unloadPackage: (name) ->
|
||||
if @isPackageActive(name)
|
||||
throw new Error("Tried to unload active package '#{name}'")
|
||||
|
||||
if pack = @getLoadedPackage(name)
|
||||
delete @loadedPackages[pack.name]
|
||||
else
|
||||
throw new Error("No loaded package for name '#{name}'")
|
||||
|
||||
resolvePackagePath: (name) ->
|
||||
return name if fsUtils.isDirectorySync(name)
|
||||
|
||||
packagePath = fsUtils.resolve(config.packageDirPaths..., name)
|
||||
return packagePath if fsUtils.isDirectorySync(packagePath)
|
||||
|
||||
packagePath = path.join(window.resourcePath, 'node_modules', name)
|
||||
return packagePath if @isInternalPackage(packagePath)
|
||||
|
||||
isInternalPackage: (packagePath) ->
|
||||
{engines} = Package.loadMetadata(packagePath, true)
|
||||
engines?.atom?
|
||||
|
||||
getLoadedPackage: (name) ->
|
||||
@loadedPackages[name]
|
||||
|
||||
isPackageLoaded: (name) ->
|
||||
@getLoadedPackage(name)?
|
||||
|
||||
getLoadedPackages: ->
|
||||
_.values(@loadedPackages)
|
||||
|
||||
isPackageDisabled: (name) ->
|
||||
_.include(config.get('core.disabledPackages') ? [], name)
|
||||
|
||||
getAvailablePackagePaths: ->
|
||||
packagePaths = []
|
||||
|
||||
for packageDirPath in config.packageDirPaths
|
||||
for packagePath in fsUtils.listSync(packageDirPath)
|
||||
packagePaths.push(packagePath) if fsUtils.isDirectorySync(packagePath)
|
||||
|
||||
for packagePath in fsUtils.listSync(path.join(window.resourcePath, 'node_modules'))
|
||||
packagePaths.push(packagePath) if @isInternalPackage(packagePath)
|
||||
|
||||
_.uniq(packagePaths)
|
||||
|
||||
getAvailablePackageNames: ->
|
||||
_.uniq _.map @getAvailablePackagePaths(), (packagePath) -> path.basename(packagePath)
|
||||
|
||||
getAvailablePackageMetadata: ->
|
||||
packages = []
|
||||
for packagePath in atom.getAvailablePackagePaths()
|
||||
name = path.basename(packagePath)
|
||||
metadata = atom.getLoadedPackage(name)?.metadata ? Package.loadMetadata(packagePath, true)
|
||||
packages.push(metadata)
|
||||
packages
|
||||
#TODO Remove theses once packages have been migrated
|
||||
getPackageState: (args...) -> @packages.getPackageState(args...)
|
||||
setPackageState: (args...) -> @packages.setPackageState(args...)
|
||||
activatePackages: (args...) -> @packages.activatePackages(args...)
|
||||
activatePackage: (args...) -> @packages.activatePackage(args...)
|
||||
deactivatePackages: (args...) -> @packages.deactivatePackages(args...)
|
||||
deactivatePackage: (args...) -> @packages.deactivatePackage(args...)
|
||||
getActivePackage: (args...) -> @packages.getActivePackage(args...)
|
||||
isPackageActive: (args...) -> @packages.isPackageActive(args...)
|
||||
getActivePackages: (args...) -> @packages.getActivePackages(args...)
|
||||
loadPackages: (args...) -> @packages.loadPackages(args...)
|
||||
loadPackage: (args...) -> @packages.loadPackage(args...)
|
||||
unloadPackage: (args...) -> @packages.unloadPackage(args...)
|
||||
resolvePackagePath: (args...) -> @packages.resolvePackagePath(args...)
|
||||
isInternalPackage: (args...) -> @packages.isInternalPackage(args...)
|
||||
getLoadedPackage: (args...) -> @packages.getLoadedPackage(args...)
|
||||
isPackageLoaded: (args...) -> @packages.isPackageLoaded(args...)
|
||||
getLoadedPackages: (args...) -> @packages.getLoadedPackages(args...)
|
||||
isPackageDisabled: (args...) -> @packages.isPackageDisabled(args...)
|
||||
getAvailablePackagePaths: (args...) -> @packages.getAvailablePackagePaths(args...)
|
||||
getAvailablePackageNames: (args...) -> @packages.getAvailablePackageNames(args...)
|
||||
getAvailablePackageMetadata: (args...)-> @packages.getAvailablePackageMetadata(args...)
|
||||
|
||||
loadThemes: ->
|
||||
@themes.load()
|
||||
|
||||
watchThemes: ->
|
||||
@themes.on 'reloaded', =>
|
||||
@reloadBaseStylesheets()
|
||||
pack.reloadStylesheets?() for name, pack of @loadedPackages
|
||||
pack.reloadStylesheets?() for name, pack of @packages.getActivePackages()
|
||||
null
|
||||
|
||||
loadBaseStylesheets: ->
|
||||
requireStylesheet('bootstrap/less/bootstrap')
|
||||
@reloadBaseStylesheets()
|
||||
|
||||
reloadBaseStylesheets: ->
|
||||
requireStylesheet('../static/atom')
|
||||
if nativeStylesheetPath = fsUtils.resolveOnLoadPath(process.platform, ['css', 'less'])
|
||||
requireStylesheet(nativeStylesheetPath)
|
||||
|
||||
open: (options) ->
|
||||
ipc.sendChannel('open', options)
|
||||
|
||||
@ -169,7 +171,7 @@ window.atom =
|
||||
chosen = @confirmSync(message, detailedMessage, buttons)
|
||||
callbacks[chosen]?()
|
||||
|
||||
confirmSync: (message, detailedMessage, buttons, browserWindow = null) ->
|
||||
confirmSync: (message, detailedMessage, buttons, browserWindow=@getCurrentWindow()) ->
|
||||
dialog.showMessageBox browserWindow,
|
||||
type: 'info'
|
||||
message: message
|
||||
@ -181,46 +183,49 @@ window.atom =
|
||||
|
||||
showSaveDialogSync: (defaultPath) ->
|
||||
defaultPath ?= project?.getPath()
|
||||
currentWindow = remote.getCurrentWindow()
|
||||
currentWindow = @getCurrentWindow()
|
||||
dialog.showSaveDialog currentWindow, {title: 'Save File', defaultPath}
|
||||
|
||||
openDevTools: ->
|
||||
remote.getCurrentWindow().openDevTools()
|
||||
@getCurrentWindow().openDevTools()
|
||||
|
||||
toggleDevTools: ->
|
||||
remote.getCurrentWindow().toggleDevTools()
|
||||
@getCurrentWindow().toggleDevTools()
|
||||
|
||||
reload: ->
|
||||
remote.getCurrentWindow().restart()
|
||||
@getCurrentWindow().restart()
|
||||
|
||||
focus: ->
|
||||
remote.getCurrentWindow().focus()
|
||||
@getCurrentWindow().focus()
|
||||
$(window).focus()
|
||||
|
||||
show: ->
|
||||
remote.getCurrentWindow().show()
|
||||
@getCurrentWindow().show()
|
||||
|
||||
hide: ->
|
||||
remote.getCurrentWindow().hide()
|
||||
@getCurrentWindow().hide()
|
||||
|
||||
close: ->
|
||||
remote.getCurrentWindow().close()
|
||||
@getCurrentWindow().close()
|
||||
|
||||
exit: (status) ->
|
||||
app.exit(status)
|
||||
exit: (status) -> app.exit(status)
|
||||
|
||||
toggleFullScreen: ->
|
||||
@setFullScreen(!@isFullScreen())
|
||||
|
||||
setFullScreen: (fullScreen=false) ->
|
||||
remote.getCurrentWindow().setFullScreen(fullScreen)
|
||||
@getCurrentWindow().setFullScreen(fullScreen)
|
||||
|
||||
isFullScreen: ->
|
||||
remote.getCurrentWindow().isFullScreen()
|
||||
@getCurrentWindow().isFullScreen()
|
||||
|
||||
getHomeDirPath: ->
|
||||
app.getHomeDir()
|
||||
|
||||
# Public: Get the directory path to Atom's configuration area.
|
||||
getConfigDirPath: ->
|
||||
@configDirPath ?= fsUtils.absolute('~/.atom')
|
||||
|
||||
getWindowStatePath: ->
|
||||
switch @windowMode
|
||||
when 'spec'
|
||||
@ -232,7 +237,7 @@ window.atom =
|
||||
filename = "editor-#{sha1}"
|
||||
|
||||
if filename
|
||||
path.join(config.userStoragePath, filename)
|
||||
path.join(@config.userStoragePath, filename)
|
||||
else
|
||||
null
|
||||
|
||||
@ -252,13 +257,13 @@ window.atom =
|
||||
documentStateJson = @getLoadSettings().windowState
|
||||
|
||||
try
|
||||
documentState = JSON.parse(documentStateJson) if documentStateJson?
|
||||
documentState = JSON.parse(documentStateJson) if documentStateJson
|
||||
catch error
|
||||
console.warn "Error parsing window state: #{windowStatePath}", error.stack, error
|
||||
|
||||
doc = Document.deserialize(state: documentState) if documentState?
|
||||
doc ?= Document.create()
|
||||
window.site = doc.site # TODO: Remove this when everything is using telepath models
|
||||
@site = doc.site # TODO: Remove this when everything is using telepath models
|
||||
doc
|
||||
|
||||
saveWindowState: ->
|
||||
@ -266,7 +271,7 @@ window.atom =
|
||||
if windowStatePath = @getWindowStatePath()
|
||||
windowState.saveSync(path: windowStatePath)
|
||||
else
|
||||
@getLoadSettings().windowState = JSON.stringify(windowState.serialize())
|
||||
@getCurrentWindow().loadSettings.windowState = JSON.stringify(windowState.serialize())
|
||||
|
||||
getWindowState: (keyPath) ->
|
||||
@windowState ?= @loadWindowState()
|
||||
@ -281,9 +286,26 @@ window.atom =
|
||||
crashRenderProcess: ->
|
||||
process.crash()
|
||||
|
||||
beep: ->
|
||||
shell.beep()
|
||||
|
||||
requireUserInitScript: ->
|
||||
userInitScriptPath = path.join(config.configDirPath, "user.coffee")
|
||||
userInitScriptPath = path.join(@config.configDirPath, "user.coffee")
|
||||
try
|
||||
require userInitScriptPath if fsUtils.isFileSync(userInitScriptPath)
|
||||
catch error
|
||||
console.error "Failed to load `#{userInitScriptPath}`", error.stack, error
|
||||
|
||||
requireWithGlobals: (id, globals={}) ->
|
||||
existingGlobals = {}
|
||||
for key, value of globals
|
||||
existingGlobals[key] = window[key]
|
||||
window[key] = value
|
||||
|
||||
require(id)
|
||||
|
||||
for key, value of existingGlobals
|
||||
if value is undefined
|
||||
delete window[key]
|
||||
else
|
||||
window[key] = value
|
||||
|
@ -23,6 +23,18 @@ class BindingSet
|
||||
@selector = selector.replace(/!important/g, '')
|
||||
@commandsByKeystrokes = @normalizeCommandsByKeystrokes(commandsByKeystrokes)
|
||||
|
||||
# Private:
|
||||
getName: ->
|
||||
@name
|
||||
|
||||
# Private:
|
||||
getSelector: ->
|
||||
@selector
|
||||
|
||||
# Private:
|
||||
getCommandsByKeystrokes: ->
|
||||
@commandsByKeystrokes
|
||||
|
||||
commandForEvent: (event) ->
|
||||
for keystrokes, command of @commandsByKeystrokes
|
||||
return command if event.keystrokes == keystrokes
|
||||
|
@ -1,3 +1,4 @@
|
||||
app = require 'app'
|
||||
ipc = require 'ipc'
|
||||
Menu = require 'menu'
|
||||
_ = require 'underscore'
|
||||
@ -9,20 +10,22 @@ _ = require 'underscore'
|
||||
module.exports =
|
||||
class ApplicationMenu
|
||||
version: null
|
||||
devMode: null
|
||||
menu: null
|
||||
|
||||
constructor: (@version, @devMode) ->
|
||||
constructor: (@version) ->
|
||||
@menu = Menu.buildFromTemplate @getDefaultTemplate()
|
||||
Menu.setApplicationMenu @menu
|
||||
|
||||
# Public: Updates the entire menu with the given keybindings.
|
||||
#
|
||||
# * template:
|
||||
# The Object which describes the menu to display.
|
||||
# * keystrokesByCommand:
|
||||
# An Object where the keys are commands and the values are Arrays containing
|
||||
# the keystrokes.
|
||||
update: (keystrokesByCommand) ->
|
||||
template = @getTemplate(keystrokesByCommand)
|
||||
update: (template, keystrokesByCommand) ->
|
||||
@translateTemplate(template, keystrokesByCommand)
|
||||
@substituteVersion(template)
|
||||
@menu = Menu.buildFromTemplate(template)
|
||||
Menu.setApplicationMenu(@menu)
|
||||
|
||||
@ -32,11 +35,24 @@ class ApplicationMenu
|
||||
# A complete menu configuration object for atom-shell's menu API.
|
||||
#
|
||||
# Returns an Array of native menu items.
|
||||
allItems: (menu=@menu) ->
|
||||
flattenMenuItems: (menu) ->
|
||||
items = []
|
||||
for index, item of menu.items or {}
|
||||
items.push(item)
|
||||
items = items.concat(@allItems(item.submenu)) if item.submenu
|
||||
items = items.concat(@flattenMenuItems(item.submenu)) if item.submenu
|
||||
items
|
||||
|
||||
# Private: Flattens the given menu template into an single Array.
|
||||
#
|
||||
# * template:
|
||||
# An object describing the menu item.
|
||||
#
|
||||
# Returns an Array of native menu items.
|
||||
flattenMenuTemplate: (template) ->
|
||||
items = []
|
||||
for item in template
|
||||
items.push(item)
|
||||
items = items.concat(@flattenMenuTemplate(item.submenu)) if item.submenu
|
||||
items
|
||||
|
||||
# Public: Used to make all window related menu items are active.
|
||||
@ -45,9 +61,14 @@ class ApplicationMenu
|
||||
# If true enables all window specific items, if false disables all window
|
||||
# specific items.
|
||||
enableWindowSpecificItems: (enable) ->
|
||||
for item in @allItems()
|
||||
for item in @flattenMenuItems(@menu)
|
||||
item.enabled = enable if item.metadata?['windowSpecific']
|
||||
|
||||
# Private: Replaces VERSION with the current version.
|
||||
substituteVersion: (template) ->
|
||||
if (item = _.find(@flattenMenuTemplate(template), (i) -> i.label == 'VERSION'))
|
||||
item.label = "Version #{@version}"
|
||||
|
||||
# Public: Makes the download menu item visible if available.
|
||||
#
|
||||
# Note: The update menu item's must match 'Install update' exactly otherwise
|
||||
@ -58,10 +79,9 @@ class ApplicationMenu
|
||||
# * quitAndUpdateCallback:
|
||||
# Function to call when the install menu item has been clicked.
|
||||
showDownloadUpdateItem: (newVersion, quitAndUpdateCallback) ->
|
||||
downloadUpdateItem = _.find @allItems(), (item) -> item.label == 'Install update'
|
||||
if downloadUpdateItem
|
||||
downloadUpdateItem.visible = true
|
||||
downloadUpdateItem.click = quitAndUpdateCallback
|
||||
if (item = _.find(@flattenMenuItems(@menu), (i) -> i.label == 'Install update'))
|
||||
item.visible = true
|
||||
item.click = quitAndUpdateCallback
|
||||
|
||||
# Private: Default list of menu items.
|
||||
#
|
||||
@ -77,82 +97,6 @@ class ApplicationMenu
|
||||
]
|
||||
]
|
||||
|
||||
# Private: The complete list of menu items.
|
||||
#
|
||||
# * keystrokesByCommand:
|
||||
# An Object where the keys are commands and the values are Arrays containing
|
||||
# the keystrokes.
|
||||
#
|
||||
# Returns a complete menu configuration Object for use with atom-shell's
|
||||
# native menu API.
|
||||
getTemplate: (keystrokesByCommand) ->
|
||||
atomMenu =
|
||||
label: 'Atom'
|
||||
submenu: [
|
||||
{ label: 'About Atom', command: 'application:about' }
|
||||
{ label: "Version #{@version}", enabled: false }
|
||||
{ label: "Install update", command: 'application:install-update', visible: false }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Preferences...', command: 'application:show-settings' }
|
||||
{ label: 'Hide Atom', command: 'application:hide' }
|
||||
{ label: 'Hide Others', command: 'application:hide-other-applications' }
|
||||
{ label: 'Show All', command: 'application:unhide-all-applications' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Run Atom Specs', command: 'application:run-all-specs' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Quit', command: 'application:quit' }
|
||||
]
|
||||
|
||||
fileMenu =
|
||||
label: 'File'
|
||||
submenu: [
|
||||
{ label: 'New Window', command: 'application:new-window' }
|
||||
{ label: 'New File', command: 'application:new-file' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Open...', command: 'application:open' }
|
||||
{ label: 'Open In Dev Mode...', command: 'application:open-dev' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Close Window', command: 'window:close' }
|
||||
]
|
||||
|
||||
editMenu =
|
||||
label: 'Edit'
|
||||
submenu: [
|
||||
{ label: 'Undo', command: 'core:undo' }
|
||||
{ label: 'Redo', command: 'core:redo' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Cut', command: 'core:cut' }
|
||||
{ label: 'Copy', command: 'core:copy' }
|
||||
{ label: 'Paste', command: 'core:paste' }
|
||||
{ label: 'Select All', command: 'core:select-all' }
|
||||
]
|
||||
|
||||
viewMenu =
|
||||
label: 'View'
|
||||
submenu: [
|
||||
{ label: 'Reload', command: 'window:reload' }
|
||||
{ label: 'Toggle Full Screen', command: 'window:toggle-full-screen' }
|
||||
{ label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' }
|
||||
]
|
||||
|
||||
windowMenu =
|
||||
label: 'Window'
|
||||
submenu: [
|
||||
{ label: 'Minimize', command: 'application:minimize' }
|
||||
{ label: 'Zoom', command: 'application:zoom' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Bring All to Front', command: 'application:bring-all-windows-to-front' }
|
||||
]
|
||||
|
||||
devMenu =
|
||||
label: '\uD83D\uDC80' # Skull emoji
|
||||
submenu: [ { label: 'In Development Mode', enabled: false } ]
|
||||
|
||||
template = [atomMenu, fileMenu, editMenu, viewMenu, windowMenu]
|
||||
template.push devMenu if @devMode
|
||||
|
||||
@translateTemplate template, keystrokesByCommand
|
||||
|
||||
# Private: Combines a menu template with the appropriate keystrokes.
|
||||
#
|
||||
# * template:
|
||||
@ -194,7 +138,7 @@ class ApplicationMenu
|
||||
modifiers = modifiers.map (modifier) ->
|
||||
modifier.replace(/shift/ig, "Shift")
|
||||
.replace(/meta/ig, "Command")
|
||||
.replace(/ctrl/ig, "MacCtrl")
|
||||
.replace(/ctrl/ig, "Ctrl")
|
||||
.replace(/alt/ig, "Alt")
|
||||
|
||||
keys = modifiers.concat([key.toUpperCase()])
|
@ -1,7 +1,6 @@
|
||||
AtomWindow = require 'atom-window'
|
||||
ApplicationMenu = require 'application-menu'
|
||||
AtomProtocolHandler = require 'atom-protocol-handler'
|
||||
BrowserWindow = require 'browser-window'
|
||||
AtomWindow = require './atom-window'
|
||||
ApplicationMenu = require './application-menu'
|
||||
AtomProtocolHandler = require './atom-protocol-handler'
|
||||
Menu = require 'menu'
|
||||
autoUpdater = require 'auto-updater'
|
||||
app = require 'app'
|
||||
@ -24,6 +23,7 @@ socketPath = '/tmp/atom.sock'
|
||||
module.exports =
|
||||
class AtomApplication
|
||||
_.extend @prototype, EventEmitter.prototype
|
||||
updateVersion: null
|
||||
|
||||
# Public: The entry point into the Atom application.
|
||||
@open: (options) ->
|
||||
@ -33,7 +33,7 @@ class AtomApplication
|
||||
# take a few seconds to trigger 'error' event, it could be a bug of node
|
||||
# or atom-shell, before it's fixed we check the existence of socketPath to
|
||||
# speedup startup.
|
||||
if not fs.existsSync socketPath
|
||||
if (not fs.existsSync socketPath) or options.test
|
||||
createAtomApplication()
|
||||
return
|
||||
|
||||
@ -50,14 +50,15 @@ class AtomApplication
|
||||
resourcePath: null
|
||||
version: null
|
||||
|
||||
constructor: ({@resourcePath, pathsToOpen, urlsToOpen, @version, test, pidToKillWhenClosed, devMode, newWindow}) ->
|
||||
constructor: (options) ->
|
||||
{@resourcePath, @version, @devMode} = options
|
||||
global.atomApplication = this
|
||||
|
||||
@pidsToOpenWindows = {}
|
||||
@pathsToOpen ?= []
|
||||
@windows = []
|
||||
|
||||
@applicationMenu = new ApplicationMenu(@version, devMode)
|
||||
@applicationMenu = new ApplicationMenu(@version)
|
||||
@atomProtocolHandler = new AtomProtocolHandler(@resourcePath)
|
||||
|
||||
@listenForArgumentsFromNewProcess()
|
||||
@ -65,8 +66,12 @@ class AtomApplication
|
||||
@handleEvents()
|
||||
@checkForUpdates()
|
||||
|
||||
@openWithOptions(options)
|
||||
|
||||
# Private: Opens a new window based on the options provided.
|
||||
openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, newWindow, specDirectory}) ->
|
||||
if test
|
||||
@runSpecs({exitWhenDone: true, @resourcePath})
|
||||
@runSpecs({exitWhenDone: true, @resourcePath, specDirectory})
|
||||
else if pathsToOpen.length > 0
|
||||
@openPaths({pathsToOpen, pidToKillWhenClosed, newWindow, devMode})
|
||||
else if urlsToOpen.length > 0
|
||||
@ -93,8 +98,7 @@ class AtomApplication
|
||||
fs.unlinkSync socketPath if fs.existsSync(socketPath)
|
||||
server = net.createServer (connection) =>
|
||||
connection.on 'data', (data) =>
|
||||
options = JSON.parse(data)
|
||||
@openPaths(options)
|
||||
@openWithOptions(JSON.parse(data))
|
||||
|
||||
server.listen socketPath
|
||||
server.on 'error', (error) -> console.error 'Application server failed', error
|
||||
@ -125,7 +129,8 @@ class AtomApplication
|
||||
@on 'application:hide', -> Menu.sendActionToFirstResponder('hide:')
|
||||
@on 'application:hide-other-applications', -> Menu.sendActionToFirstResponder('hideOtherApplications:')
|
||||
@on 'application:unhide-all-applications', -> Menu.sendActionToFirstResponder('unhideAllApplications:')
|
||||
@on 'application:new-window', -> @openPath()
|
||||
@on 'application:new-window', ->
|
||||
@openPath(initialSize: @getFocusedWindowSize())
|
||||
@on 'application:new-file', -> (@focusedWindow() ? this).openPath()
|
||||
@on 'application:open', -> @promptForPath()
|
||||
@on 'application:open-dev', -> @promptForPath(devMode: true)
|
||||
@ -143,11 +148,13 @@ class AtomApplication
|
||||
|
||||
app.on 'open-url', (event, urlToOpen) =>
|
||||
event.preventDefault()
|
||||
@openUrl(urlToOpen)
|
||||
@openUrl({urlToOpen, @devMode})
|
||||
|
||||
autoUpdater.on 'ready-for-update-on-quit', (event, version, quitAndUpdateCallback) =>
|
||||
event.preventDefault()
|
||||
@updateVersion = version
|
||||
@applicationMenu.showDownloadUpdateItem(version, quitAndUpdateCallback)
|
||||
atomWindow.sendCommand('window:update-available', version) for atomWindow in @windows
|
||||
|
||||
# A request from the associated render process to open a new render process.
|
||||
ipc.on 'open', (processId, routingId, options) =>
|
||||
@ -159,8 +166,8 @@ class AtomApplication
|
||||
else
|
||||
@promptForPath()
|
||||
|
||||
ipc.once 'update-application-menu', (processId, routingId, keystrokesByCommand) =>
|
||||
@applicationMenu.update(keystrokesByCommand)
|
||||
ipc.on 'update-application-menu', (processId, routingId, template, keystrokesByCommand) =>
|
||||
@applicationMenu.update(template, keystrokesByCommand)
|
||||
|
||||
ipc.on 'run-package-specs', (processId, routingId, specDirectory) =>
|
||||
@runSpecs({resourcePath: global.devResourcePath, specDirectory: specDirectory, exitWhenDone: false})
|
||||
@ -189,6 +196,17 @@ class AtomApplication
|
||||
focusedWindow: ->
|
||||
_.find @windows, (atomWindow) -> atomWindow.isFocused()
|
||||
|
||||
# Public: Get the height and width of the focused window.
|
||||
#
|
||||
# Returns an object with height and width keys or null if there is no
|
||||
# focused window.
|
||||
getFocusedWindowSize: ->
|
||||
if focusedWindow = @focusedWindow()
|
||||
[width, height] = focusedWindow.getSize()
|
||||
{width, height}
|
||||
else
|
||||
null
|
||||
|
||||
# Public: Opens multiple paths, in existing windows if possible.
|
||||
#
|
||||
# * options
|
||||
@ -214,10 +232,13 @@ class AtomApplication
|
||||
# Boolean of whether this should be opened in a new window.
|
||||
# + devMode:
|
||||
# Boolean to control the opened window's dev mode.
|
||||
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode}={}) ->
|
||||
[basename, initialLine] = path.basename(pathToOpen).split(':')
|
||||
pathToOpen = "#{path.dirname(pathToOpen)}/#{basename}"
|
||||
initialLine -= 1 if initialLine # Convert line numbers to a base of 0
|
||||
# + initialSize:
|
||||
# Object with height and width keys.
|
||||
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, initialSize}={}) ->
|
||||
if pathToOpen
|
||||
[basename, initialLine] = path.basename(pathToOpen).split(':')
|
||||
pathToOpen = "#{path.dirname(pathToOpen)}/#{basename}"
|
||||
initialLine -= 1 if initialLine # Convert line numbers to a base of 0
|
||||
|
||||
unless devMode
|
||||
existingWindow = @windowForPath(pathToOpen) unless pidToKillWhenClosed or newWindow
|
||||
@ -230,8 +251,8 @@ class AtomApplication
|
||||
bootstrapScript = require.resolve(path.join(global.devResourcePath, 'src', 'window-bootstrap'))
|
||||
else
|
||||
resourcePath = @resourcePath
|
||||
bootstrapScript = require.resolve('./window-bootstrap')
|
||||
openedWindow = new AtomWindow({pathToOpen, initialLine, bootstrapScript, resourcePath, devMode})
|
||||
bootstrapScript = require.resolve('../window-bootstrap')
|
||||
openedWindow = new AtomWindow({pathToOpen, initialLine, bootstrapScript, resourcePath, devMode, initialSize})
|
||||
|
||||
if pidToKillWhenClosed?
|
||||
@pidsToOpenWindows[pidToKillWhenClosed] = openedWindow
|
||||
@ -245,9 +266,11 @@ class AtomApplication
|
||||
console.log("Killing process #{pid} failed: #{error.code}")
|
||||
delete @pidsToOpenWindows[pid]
|
||||
|
||||
# Private: Handles an atom:// url.
|
||||
# Private: Open an atom:// url.
|
||||
#
|
||||
# Currently only supports atom://session/<session-id> urls.
|
||||
# The host of the URL being opened is assumed to be the package name
|
||||
# responsible for opening the URL. A new window will be created with
|
||||
# that package's `urlMain` as the bootstrap script.
|
||||
#
|
||||
# * options
|
||||
# + urlToOpen:
|
||||
@ -255,15 +278,25 @@ class AtomApplication
|
||||
# + devMode:
|
||||
# Boolean to control the opened window's dev mode.
|
||||
openUrl: ({urlToOpen, devMode}) ->
|
||||
parsedUrl = url.parse(urlToOpen)
|
||||
if parsedUrl.host is 'session'
|
||||
sessionId = parsedUrl.path.split('/')[1]
|
||||
console.log "Joining session #{sessionId}"
|
||||
if sessionId
|
||||
bootstrapScript = 'collaboration/lib/bootstrap'
|
||||
new AtomWindow({bootstrapScript, @resourcePath, sessionId, devMode})
|
||||
unless @packages?
|
||||
PackageManager = require '../package-manager'
|
||||
fsUtils = require '../fs-utils'
|
||||
@packages = new PackageManager
|
||||
configDirPath: fsUtils.absolute('~/.atom')
|
||||
devMode: devMode
|
||||
resourcePath: @resourcePath
|
||||
|
||||
packageName = url.parse(urlToOpen).host
|
||||
pack = _.find @packages.getAvailablePackageMetadata(), ({name}) -> name is packageName
|
||||
if pack?
|
||||
if pack.urlMain
|
||||
packagePath = @packages.resolvePackagePath(packageName)
|
||||
bootstrapScript = path.resolve(packagePath, pack.urlMain)
|
||||
new AtomWindow({bootstrapScript, @resourcePath, devMode, urlToOpen, initialSize: getFocusedWindowSize()})
|
||||
else
|
||||
console.log "Package '#{pack.name}' does not have a url main: #{urlToOpen}"
|
||||
else
|
||||
console.log "Opening unknown url #{urlToOpen}"
|
||||
console.log "Opening unknown url: #{urlToOpen}"
|
||||
|
||||
# Private: Opens up a new {AtomWindow} to run specs within.
|
||||
#
|
||||
@ -281,7 +314,7 @@ class AtomApplication
|
||||
try
|
||||
bootstrapScript = require.resolve(path.resolve(global.devResourcePath, 'spec', 'spec-bootstrap'))
|
||||
catch error
|
||||
bootstrapScript = require.resolve(path.resolve(__dirname, '..', 'spec', 'spec-bootstrap'))
|
||||
bootstrapScript = require.resolve(path.resolve(__dirname, '..', '..', 'spec', 'spec-bootstrap'))
|
||||
|
||||
isSpec = true
|
||||
devMode = true
|
||||
@ -291,9 +324,9 @@ class AtomApplication
|
||||
try
|
||||
bootstrapScript = require.resolve(path.resolve(global.devResourcePath, 'benchmark', 'benchmark-bootstrap'))
|
||||
catch error
|
||||
bootstrapScript = require.resolve(path.resolve(__dirname, '..', 'benchmark', 'benchmark-bootstrap'))
|
||||
bootstrapScript = require.resolve(path.resolve(__dirname, '..', '..', 'benchmark', 'benchmark-bootstrap'))
|
||||
|
||||
isSpec = true # Needed because this flag adds the spec directory to the NODE_PATH
|
||||
isSpec = true
|
||||
new AtomWindow({bootstrapScript, @resourcePath, isSpec})
|
||||
|
||||
# Private: Opens a native dialog to prompt the user for a path.
|
||||
@ -307,3 +340,8 @@ class AtomApplication
|
||||
promptForPath: ({devMode}={}) ->
|
||||
pathsToOpen = dialog.showOpenDialog title: 'Open', properties: ['openFile', 'openDirectory', 'multiSelections', 'createDirectory']
|
||||
@openPaths({pathsToOpen, devMode})
|
||||
|
||||
# Public: If an update is available, it returns the new version string
|
||||
# otherwise it returns null.
|
||||
getUpdateVersion: ->
|
||||
@updateVersion
|
@ -1,8 +1,6 @@
|
||||
BrowserWindow = require 'browser-window'
|
||||
Menu = require 'menu'
|
||||
MenuItem = require 'menu-item'
|
||||
ContextMenu = require 'context-menu'
|
||||
app = require 'app'
|
||||
ContextMenu = require './context-menu'
|
||||
dialog = require 'dialog'
|
||||
ipc = require 'ipc'
|
||||
path = require 'path'
|
||||
@ -31,20 +29,18 @@ class AtomWindow
|
||||
loadSettings = _.extend({}, settings)
|
||||
loadSettings.windowState ?= ''
|
||||
loadSettings.initialPath = pathToOpen
|
||||
try
|
||||
if fs.statSync(pathToOpen).isFile()
|
||||
loadSettings.initialPath = path.dirname(pathToOpen)
|
||||
if fs.statSyncNoException(pathToOpen).isFile?()
|
||||
loadSettings.initialPath = path.dirname(pathToOpen)
|
||||
|
||||
@browserWindow.loadSettings = loadSettings
|
||||
@browserWindow.once 'window:loaded', => @loaded = true
|
||||
@browserWindow.loadUrl "file://#{@resourcePath}/static/index.html"
|
||||
@browserWindow.focusOnWebView() if @isSpec
|
||||
|
||||
@openPath(pathToOpen, initialLine)
|
||||
|
||||
setupNodePath: (resourcePath) ->
|
||||
paths = ['exports', 'node_modules']
|
||||
paths = paths.map (relativePath) -> path.resolve(resourcePath, relativePath)
|
||||
process.env['NODE_PATH'] = paths.join path.delimiter
|
||||
process.env['NODE_PATH'] = path.resolve(resourcePath, 'exports')
|
||||
|
||||
getInitialPath: ->
|
||||
@browserWindow.loadSettings.initialPath
|
||||
@ -57,6 +53,8 @@ class AtomWindow
|
||||
false
|
||||
else if pathToCheck is initialPath
|
||||
true
|
||||
else if fs.statSyncNoException(pathToCheck).isDirectory?()
|
||||
false
|
||||
else if pathToCheck.indexOf(path.join(initialPath, path.sep)) is 0
|
||||
true
|
||||
else
|
||||
@ -85,7 +83,7 @@ class AtomWindow
|
||||
when 1 then @browserWindow.restart()
|
||||
|
||||
@browserWindow.on 'context-menu', (menuTemplate) =>
|
||||
new ContextMenu(menuTemplate)
|
||||
new ContextMenu(menuTemplate, @browserWindow)
|
||||
|
||||
if @isSpec
|
||||
# Spec window's web view should always have focus
|
||||
@ -96,20 +94,28 @@ class AtomWindow
|
||||
if @loaded
|
||||
@focus()
|
||||
@sendCommand('window:open-path', {pathToOpen, initialLine})
|
||||
@sendCommand('window:update-available', global.atomApplication.getUpdateVersion()) if global.atomApplication.getUpdateVersion()
|
||||
else
|
||||
@browserWindow.once 'window:loaded', => @openPath(pathToOpen, initialLine)
|
||||
|
||||
sendCommand: (command, args...) ->
|
||||
if @handlesAtomCommands()
|
||||
@sendAtomCommand(command, args...)
|
||||
if @isSpecWindow()
|
||||
unless @sendCommandToFirstResponder(command)
|
||||
switch command
|
||||
when 'window:reload' then @reload()
|
||||
when 'window:toggle-dev-tools' then @toggleDevTools()
|
||||
when 'window:close' then @close()
|
||||
else if @isWebViewFocused()
|
||||
@sendCommandToBrowserWindow(command, args...)
|
||||
else
|
||||
@sendNativeCommand(command)
|
||||
unless @sendCommandToFirstResponder(command)
|
||||
@sendCommandToBrowserWindow(command, args...)
|
||||
|
||||
sendAtomCommand: (command, args...) ->
|
||||
sendCommandToBrowserWindow: (command, args...) ->
|
||||
action = if args[0]?.contextCommand then 'context-command' else 'command'
|
||||
ipc.sendChannel @browserWindow.getProcessId(), @browserWindow.getRoutingId(), action, command, args...
|
||||
|
||||
sendNativeCommand: (command) ->
|
||||
sendCommandToFirstResponder: (command) ->
|
||||
switch command
|
||||
when 'core:undo' then Menu.sendActionToFirstResponder('undo:')
|
||||
when 'core:redo' then Menu.sendActionToFirstResponder('redo:')
|
||||
@ -117,14 +123,15 @@ class AtomWindow
|
||||
when 'core:cut' then Menu.sendActionToFirstResponder('cut:')
|
||||
when 'core:paste' then Menu.sendActionToFirstResponder('paste:')
|
||||
when 'core:select-all' then Menu.sendActionToFirstResponder('selectAll:')
|
||||
when 'window:reload' then @reload()
|
||||
when 'window:toggle-dev-tools' then @toggleDevTools()
|
||||
when 'window:close' then @close()
|
||||
else return false
|
||||
true
|
||||
|
||||
close: -> @browserWindow.close()
|
||||
|
||||
focus: -> @browserWindow.focus()
|
||||
|
||||
getSize: -> @browserWindow.getSize()
|
||||
|
||||
handlesAtomCommands: ->
|
||||
not @isSpecWindow() and @isWebViewFocused()
|
||||
|
@ -1,12 +1,11 @@
|
||||
Menu = require 'menu'
|
||||
BrowserWindow = require 'browser-window'
|
||||
|
||||
module.exports =
|
||||
class ContextMenu
|
||||
constructor: (template) ->
|
||||
constructor: (template, browserWindow) ->
|
||||
template = @createClickHandlers(template)
|
||||
menu = Menu.buildFromTemplate(template)
|
||||
menu.popup(BrowserWindow.getFocusedWindow())
|
||||
menu.popup(browserWindow)
|
||||
|
||||
# Private: It's necessary to build the event handlers in this process, otherwise
|
||||
# closures are drug across processes and failed to be garbage collected
|
@ -14,6 +14,10 @@ dialog = require 'dialog'
|
||||
console.log = (args...) ->
|
||||
nslog(args.map((arg) -> JSON.stringify(arg)).join(" "))
|
||||
|
||||
process.on 'uncaughtException', (error={}) ->
|
||||
nslog(error.message) if error.message?
|
||||
nslog(error.stack) if error.stack?
|
||||
|
||||
delegate.browserMainParts.preMainMessageLoopRun = ->
|
||||
args = parseCommandLine()
|
||||
|
||||
@ -46,13 +50,10 @@ delegate.browserMainParts.preMainMessageLoopRun = ->
|
||||
|
||||
require('coffee-script')
|
||||
if args.devMode
|
||||
require(path.join(args.resourcePath, 'src', 'coffee-cache'))
|
||||
module.globalPaths.push(path.join(args.resourcePath, 'src'))
|
||||
require(path.join(args.resourcePath, 'src', 'coffee-cache')).register()
|
||||
AtomApplication = require path.join(args.resourcePath, 'src', 'browser', 'atom-application')
|
||||
else
|
||||
appSrcPath = path.resolve(process.argv[0], "../../Resources/app/src")
|
||||
module.globalPaths.push(appSrcPath)
|
||||
|
||||
AtomApplication = require 'atom-application'
|
||||
AtomApplication = require './atom-application'
|
||||
|
||||
AtomApplication.open(args)
|
||||
console.log("App load time: #{new Date().getTime() - startTime}ms")
|
||||
@ -76,11 +77,12 @@ parseCommandLine = ->
|
||||
Usage: atom [options] [file ...]
|
||||
"""
|
||||
options.alias('d', 'dev').boolean('d').describe('d', 'Run in development mode.')
|
||||
options.alias('f', 'foreground').boolean('f').describe('f', 'Keep the browser process in the foreground.')
|
||||
options.alias('h', 'help').boolean('h').describe('h', 'Print this usage message.')
|
||||
options.alias('n', 'new-window').boolean('n').describe('n', 'Open a new window.')
|
||||
options.alias('t', 'test').boolean('t').describe('t', 'Run the Atom specs and exit with error code on failures.')
|
||||
options.alias('s', 'spec-directory').string('s').describe('s', 'Set the directory from which specs are loaded (default: Atom\'s spec directory).')
|
||||
options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.')
|
||||
options.alias('v', 'version').boolean('v').describe('v', 'Print the version.')
|
||||
options.alias('f', 'foreground').boolean('f').describe('f', 'Keep the browser process in the foreground.')
|
||||
options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.')
|
||||
args = options.argv
|
||||
|
||||
@ -97,6 +99,7 @@ parseCommandLine = ->
|
||||
pathsToOpen = args._
|
||||
pathsToOpen = [executedFrom] if executedFrom and pathsToOpen.length is 0
|
||||
test = args['test']
|
||||
specDirectory = args['spec-directory']
|
||||
newWindow = args['new-window']
|
||||
pidToKillWhenClosed = args['pid'] if args['wait']
|
||||
|
||||
@ -110,6 +113,6 @@ parseCommandLine = ->
|
||||
fs.statSync resourcePath
|
||||
catch e
|
||||
devMode = false
|
||||
resourcePath = path.dirname(__dirname)
|
||||
resourcePath = path.dirname(path.dirname(__dirname))
|
||||
|
||||
{resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed, devMode, newWindow}
|
||||
{resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed, devMode, newWindow, specDirectory}
|
@ -15,8 +15,9 @@ getCachePath = (coffee) ->
|
||||
path.join(coffeeCacheDir, "#{digest}.coffee")
|
||||
|
||||
getCachedJavaScript = (cachePath) ->
|
||||
try
|
||||
fs.readFileSync(cachePath, 'utf8') if fs.statSync(cachePath).isFile()
|
||||
if stat = fs.statSyncNoException(cachePath)
|
||||
try
|
||||
fs.readFileSync(cachePath, 'utf8') if stat.isFile()
|
||||
|
||||
compileCoffeeScript = (coffee, filePath, cachePath) ->
|
||||
js = CoffeeScript.compile(coffee, filename: filePath)
|
||||
@ -25,10 +26,16 @@ compileCoffeeScript = (coffee, filePath, cachePath) ->
|
||||
fs.writeFileSync(cachePath, js)
|
||||
js
|
||||
|
||||
require.extensions['.coffee'] = (module, filePath) ->
|
||||
requireCoffeeScript = (module, filePath) ->
|
||||
coffee = fs.readFileSync(filePath, 'utf8')
|
||||
cachePath = getCachePath(coffee)
|
||||
js = getCachedJavaScript(cachePath) ? compileCoffeeScript(coffee, filePath, cachePath)
|
||||
module._compile(js, filePath)
|
||||
|
||||
module.exports = {cacheDir}
|
||||
module.exports =
|
||||
cacheDir: cacheDir
|
||||
register: ->
|
||||
Object.defineProperty(require.extensions, '.coffee', {
|
||||
writable: false
|
||||
value: requireCoffeeScript
|
||||
})
|
||||
|
@ -7,58 +7,53 @@ path = require 'path'
|
||||
async = require 'async'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
|
||||
configDirPath = fsUtils.absolute("~/.atom")
|
||||
nodeModulesDirPath = path.join(resourcePath, "node_modules")
|
||||
bundledKeymapsDirPath = path.join(resourcePath, "keymaps")
|
||||
userPackagesDirPath = path.join(configDirPath, "packages")
|
||||
userPackageDirPaths = [userPackagesDirPath]
|
||||
userPackageDirPaths.unshift(path.join(configDirPath, "dev", "packages")) if atom.getLoadSettings().devMode
|
||||
userStoragePath = path.join(configDirPath, "storage")
|
||||
|
||||
# Public: Used to access all of Atom's configuration details.
|
||||
#
|
||||
# A global instance of this class is available to all plugins which can be
|
||||
# referenced using `global.config`
|
||||
# referenced using `atom.config`
|
||||
#
|
||||
# ### Best practices ###
|
||||
# ### Best practices
|
||||
#
|
||||
# * Create your own root keypath using your package's name.
|
||||
# * Don't depend on (or write to) configuration keys outside of your keypath.
|
||||
#
|
||||
# ### Example ###
|
||||
# ### Example
|
||||
#
|
||||
# ```coffeescript
|
||||
# global.config.set('myplugin.key', 'value')
|
||||
# global.observe 'myplugin.key', ->
|
||||
# console.log 'My configuration changed:', global.config.get('myplugin.key')
|
||||
# atom.config.set('myplugin.key', 'value')
|
||||
# atom.config.observe 'myplugin.key', ->
|
||||
# console.log 'My configuration changed:', atom.config.get('myplugin.key')
|
||||
# ```
|
||||
module.exports =
|
||||
class Config
|
||||
_.extend @prototype, EventEmitter
|
||||
|
||||
configDirPath: configDirPath
|
||||
bundledPackageDirPaths: [nodeModulesDirPath]
|
||||
bundledKeymapsDirPath: bundledKeymapsDirPath
|
||||
nodeModulesDirPath: nodeModulesDirPath
|
||||
packageDirPaths: _.clone(userPackageDirPaths)
|
||||
userPackageDirPaths: userPackageDirPaths
|
||||
userStoragePath: userStoragePath
|
||||
lessSearchPaths: [
|
||||
path.join(resourcePath, 'static', 'variables')
|
||||
path.join(resourcePath, 'static')
|
||||
]
|
||||
defaultSettings: null
|
||||
settings: null
|
||||
configFileHasErrors: null
|
||||
|
||||
# Private: Created during initialization, available as `global.config`
|
||||
constructor: ->
|
||||
constructor: ({@configDirPath, @resourcePath}={}) ->
|
||||
@bundledKeymapsDirPath = path.join(@resourcePath, "keymaps")
|
||||
@bundledMenusDirPath = path.join(resourcePath, "menus")
|
||||
@nodeModulesDirPath = path.join(@resourcePath, "node_modules")
|
||||
@bundledPackageDirPaths = [@nodeModulesDirPath]
|
||||
@lessSearchPaths = [
|
||||
path.join(@resourcePath, 'static', 'variables')
|
||||
path.join(@resourcePath, 'static')
|
||||
]
|
||||
@packageDirPaths = [path.join(@configDirPath, "packages")]
|
||||
if atom.getLoadSettings().devMode
|
||||
@packageDirPaths.unshift(path.join(@configDirPath, "dev", "packages"))
|
||||
@userPackageDirPaths = _.clone(@packageDirPaths)
|
||||
@userStoragePath = path.join(@configDirPath, "storage")
|
||||
|
||||
@defaultSettings =
|
||||
core: _.clone(require('./root-view').configDefaults)
|
||||
editor: _.clone(require('./editor').configDefaults)
|
||||
@settings = {}
|
||||
@configFilePath = fsUtils.resolve(configDirPath, 'config', ['json', 'cson'])
|
||||
@configFilePath ?= path.join(configDirPath, 'config.cson')
|
||||
@configFilePath = fsUtils.resolve(@configDirPath, 'config', ['json', 'cson'])
|
||||
@configFilePath ?= path.join(@configDirPath, 'config.cson')
|
||||
|
||||
# Private:
|
||||
initializeConfigDirectory: (done) ->
|
||||
@ -70,7 +65,7 @@ class Config
|
||||
fsUtils.copy(sourcePath, destinationPath, callback)
|
||||
queue.drain = done
|
||||
|
||||
templateConfigDirPath = fsUtils.resolve(window.resourcePath, 'dot-atom')
|
||||
templateConfigDirPath = fsUtils.resolve(@resourcePath, 'dot-atom')
|
||||
onConfigDirFile = (sourcePath) =>
|
||||
relativePath = sourcePath.substring(templateConfigDirPath.length + 1)
|
||||
destinationPath = path.join(@configDirPath, relativePath)
|
||||
|
@ -54,6 +54,14 @@ class CursorView extends View
|
||||
|
||||
@setVisible(@cursor.isVisible() and not @editor.isFoldedAtScreenRow(screenPosition.row))
|
||||
|
||||
# Override for speed. The base function checks the computedStyle
|
||||
isHidden: ->
|
||||
style = this[0].style
|
||||
if style.display == 'none' or not @isOnDom()
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
needsAutoscroll: ->
|
||||
@cursor.needsAutoscroll
|
||||
|
||||
|
@ -102,10 +102,21 @@ class Cursor
|
||||
# Public: Returns the visibility of the cursor.
|
||||
isVisible: -> @visible
|
||||
|
||||
# Public: Returns a RegExp of what the cursor considers a "word"
|
||||
wordRegExp: ->
|
||||
nonWordCharacters = config.get("editor.nonWordCharacters")
|
||||
new RegExp("^[\t ]*$|[^\\s#{_.escapeRegExp(nonWordCharacters)}]+|[#{_.escapeRegExp(nonWordCharacters)}]+", "g")
|
||||
# Public: Get the RegExp used by the cursor to determine what a "word" is.
|
||||
#
|
||||
# * options:
|
||||
# + includeNonWordCharacters:
|
||||
# A Boolean indicating whether to include non-word characters in the regex.
|
||||
#
|
||||
# Returns a RegExp.
|
||||
wordRegExp: ({includeNonWordCharacters}={})->
|
||||
includeNonWordCharacters ?= true
|
||||
nonWordCharacters = config.get('editor.nonWordCharacters')
|
||||
segments = ["^[\t ]*$"]
|
||||
segments.push("[^\\s#{_.escapeRegExp(nonWordCharacters)}]+")
|
||||
if includeNonWordCharacters
|
||||
segments.push("[#{_.escapeRegExp(nonWordCharacters)}]+")
|
||||
new RegExp(segments.join("|"), "g")
|
||||
|
||||
# Public: Identifies if this cursor is the last in the {EditSession}.
|
||||
#
|
||||
@ -126,6 +137,25 @@ class Cursor
|
||||
range = [[row, Math.min(0, column - 1)], [row, Math.max(0, column + 1)]]
|
||||
/^\s+$/.test @editSession.getTextInBufferRange(range)
|
||||
|
||||
# Public: Returns whether the cursor is currently between a word and non-word
|
||||
# character. The non-word characters are defined by the
|
||||
# `editor.nonWordCharacters` config value.
|
||||
#
|
||||
# This method returns false if the character before or after the cursor is
|
||||
# whitespace.
|
||||
#
|
||||
# Returns a Boolean.
|
||||
isBetweenWordAndNonWord: ->
|
||||
return false if @isAtBeginningOfLine() or @isAtEndOfLine()
|
||||
|
||||
{row, column} = @getBufferPosition()
|
||||
range = [[row, column - 1], [row, column + 1]]
|
||||
[before, after] = @editSession.getTextInBufferRange(range)
|
||||
return false if /\s/.test(before) or /\s/.test(after)
|
||||
|
||||
nonWordCharacters = config.get('editor.nonWordCharacters').split('')
|
||||
_.contains(nonWordCharacters, before) isnt _.contains(nonWordCharacters, after)
|
||||
|
||||
# Public: Returns whether this cursor is between a word's start and end.
|
||||
isInsideWord: ->
|
||||
{row, column} = @getBufferPosition()
|
||||
@ -280,6 +310,9 @@ class Cursor
|
||||
# * options:
|
||||
# + wordRegex:
|
||||
# A RegExp indicating what constitutes a "word" (default: {.wordRegExp})
|
||||
# + includeNonWordCharacters:
|
||||
# A Boolean indicating whether to include non-word characters in the
|
||||
# default word regex. Has no effect if wordRegex is set.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getBeginningOfCurrentWordBufferPosition: (options = {}) ->
|
||||
@ -289,7 +322,7 @@ class Cursor
|
||||
scanRange = [[previousNonBlankRow, 0], currentBufferPosition]
|
||||
|
||||
beginningOfWordPosition = null
|
||||
@editSession.backwardsScanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) =>
|
||||
@editSession.backwardsScanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, stop}) =>
|
||||
if range.end.isGreaterThanOrEqual(currentBufferPosition) or allowPrevious
|
||||
beginningOfWordPosition = range.start
|
||||
if not beginningOfWordPosition?.isEqual(currentBufferPosition)
|
||||
@ -297,7 +330,7 @@ class Cursor
|
||||
|
||||
beginningOfWordPosition or currentBufferPosition
|
||||
|
||||
# Public: Retrieves buffer position of previous word boundry. It might be on
|
||||
# Public: Retrieves buffer position of previous word boundary. It might be on
|
||||
# the current word, or the previous word.
|
||||
getPreviousWordBoundaryBufferPosition: (options = {}) ->
|
||||
currentBufferPosition = @getBufferPosition()
|
||||
@ -319,7 +352,7 @@ class Cursor
|
||||
|
||||
beginningOfWordPosition or currentBufferPosition
|
||||
|
||||
# Public: Retrieves buffer position of the next word boundry. It might be on
|
||||
# Public: Retrieves buffer position of the next word boundary. It might be on
|
||||
# the current word, or the previous word.
|
||||
getMoveNextWordBoundaryBufferPosition: (options = {}) ->
|
||||
currentBufferPosition = @getBufferPosition()
|
||||
@ -345,6 +378,9 @@ class Cursor
|
||||
# * options:
|
||||
# + wordRegex:
|
||||
# A RegExp indicating what constitutes a "word" (default: {.wordRegExp})
|
||||
# + includeNonWordCharacters:
|
||||
# A Boolean indicating whether to include non-word characters in the
|
||||
# default word regex. Has no effect if wordRegex is set.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getEndOfCurrentWordBufferPosition: (options = {}) ->
|
||||
@ -353,7 +389,7 @@ class Cursor
|
||||
scanRange = [currentBufferPosition, @editSession.getEofBufferPosition()]
|
||||
|
||||
endOfWordPosition = null
|
||||
@editSession.scanInBufferRange (options.wordRegex ? @wordRegExp()), scanRange, ({range, stop}) =>
|
||||
@editSession.scanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, stop}) =>
|
||||
if range.start.isLessThanOrEqual(currentBufferPosition) or allowNext
|
||||
endOfWordPosition = range.end
|
||||
if not endOfWordPosition?.isEqual(currentBufferPosition)
|
||||
|
44
src/deserializer-manager.coffee
Normal file
44
src/deserializer-manager.coffee
Normal file
@ -0,0 +1,44 @@
|
||||
{Document} = require 'telepath'
|
||||
|
||||
# Public: Manages the deserializers used for serialized state
|
||||
module.exports =
|
||||
class DeserializerManager
|
||||
constructor: ->
|
||||
@deserializers = {}
|
||||
@deferredDeserializers = {}
|
||||
|
||||
# Public: Add a deserializer.
|
||||
add: (klasses...) ->
|
||||
@deserializers[klass.name] = klass for klass in klasses
|
||||
|
||||
# Public: Add a deferred deserializer.
|
||||
addDeferred: (name, fn) ->
|
||||
@deferredDeserializers[name] = fn
|
||||
|
||||
# Public: Remove a deserializer.
|
||||
remove: (klasses...) ->
|
||||
delete @deserializers[klass.name] for klass in klasses
|
||||
|
||||
# Public: Deserialize the state and params.
|
||||
deserialize: (state, params) ->
|
||||
return unless state?
|
||||
|
||||
if deserializer = @get(state)
|
||||
stateVersion = state.get?('version') ? state.version
|
||||
return if deserializer.version? and deserializer.version isnt stateVersion
|
||||
if (state instanceof Document) and not deserializer.acceptsDocuments
|
||||
state = state.toObject()
|
||||
deserializer.deserialize(state, params)
|
||||
else
|
||||
console.warn "No deserializer found for", state
|
||||
|
||||
# Public: Get the deserializer for the state.
|
||||
get: (state) ->
|
||||
return unless state?
|
||||
|
||||
name = state.get?('deserializer') ? state.deserializer
|
||||
if @deferredDeserializers[name]
|
||||
@deferredDeserializers[name]()
|
||||
delete @deferredDeserializers[name]
|
||||
|
||||
@deserializers[name]
|
@ -9,11 +9,16 @@ fsUtils = require './fs-utils'
|
||||
$ = require './jquery-extensions'
|
||||
_ = require './underscore-extensions'
|
||||
|
||||
MeasureRange = document.createRange()
|
||||
TextNodeFilter = { acceptNode: -> NodeFilter.FILTER_ACCEPT }
|
||||
NoScope = ['no-scope']
|
||||
|
||||
# Private: Represents the entire visual pane in Atom.
|
||||
#
|
||||
# The Editor manages the {EditSession}, which manages the file buffers.
|
||||
module.exports =
|
||||
class Editor extends View
|
||||
@characterWidthCache: {}
|
||||
@configDefaults:
|
||||
fontSize: 20
|
||||
showInvisibles: false
|
||||
@ -37,11 +42,11 @@ class Editor extends View
|
||||
_.extend(attributes, params.attributes) if params.attributes
|
||||
@div attributes, =>
|
||||
@subview 'gutter', new Gutter
|
||||
@input class: 'hidden-input', outlet: 'hiddenInput'
|
||||
@div class: 'scroll-view', outlet: 'scrollView', =>
|
||||
@div class: 'overlayer', outlet: 'overlayer'
|
||||
@div class: 'lines', outlet: 'renderedLines'
|
||||
@div class: 'underlayer', outlet: 'underlayer'
|
||||
@div class: 'underlayer', outlet: 'underlayer', =>
|
||||
@input class: 'hidden-input', outlet: 'hiddenInput'
|
||||
@div class: 'vertical-scrollbar', outlet: 'verticalScrollbar', =>
|
||||
@div outlet: 'verticalScrollbarContent'
|
||||
|
||||
@ -498,6 +503,11 @@ class Editor extends View
|
||||
# {Delegates to: EditSession.getScreenLineCount}
|
||||
getScreenLineCount: -> @activeEditSession.getScreenLineCount()
|
||||
|
||||
# Private:
|
||||
setHeightInLines: (heightInLines)->
|
||||
heightInLines ?= @calculateHeightInLines()
|
||||
@heightInLines = heightInLines if heightInLines
|
||||
|
||||
# {Delegates to: EditSession.setEditorWidthInChars}
|
||||
setWidthInChars: (widthInChars) ->
|
||||
widthInChars ?= @calculateWidthInChars()
|
||||
@ -638,9 +648,9 @@ class Editor extends View
|
||||
@addClass 'is-focused'
|
||||
|
||||
@hiddenInput.on 'focusout', =>
|
||||
@bringHiddenInputIntoView()
|
||||
@isFocused = false
|
||||
@removeClass 'is-focused'
|
||||
@hiddenInput.offset(top: 0, left: 0)
|
||||
|
||||
@underlayer.on 'mousedown', (e) =>
|
||||
@renderedLines.trigger(e)
|
||||
@ -696,8 +706,14 @@ class Editor extends View
|
||||
|
||||
handleInputEvents: ->
|
||||
@on 'cursor:moved', =>
|
||||
return unless @isFocused
|
||||
cursorView = @getCursorView()
|
||||
@hiddenInput.offset(cursorView.offset()) if cursorView.is(':visible')
|
||||
|
||||
if cursorView.isVisible()
|
||||
# This is an order of magnitude faster than checking .offset().
|
||||
style = cursorView[0].style
|
||||
@hiddenInput[0].style.top = style.top
|
||||
@hiddenInput[0].style.left = style.left
|
||||
|
||||
selectedText = null
|
||||
@hiddenInput.on 'compositionstart', =>
|
||||
@ -721,6 +737,9 @@ class Editor extends View
|
||||
@hiddenInput.val(lastInput)
|
||||
false
|
||||
|
||||
bringHiddenInputIntoView: ->
|
||||
@hiddenInput.css(top: @scrollTop(), left: @scrollLeft())
|
||||
|
||||
selectOnMousemoveUntilMouseup: ->
|
||||
lastMoveEvent = null
|
||||
moveHandler = (event = lastMoveEvent) =>
|
||||
@ -746,6 +765,7 @@ class Editor extends View
|
||||
@calculateDimensions()
|
||||
@setWidthInChars()
|
||||
@subscribe $(window), "resize.editor-#{@id}", =>
|
||||
@setHeightInLines()
|
||||
@setWidthInChars()
|
||||
@requestDisplayUpdate()
|
||||
@focus() if @isFocused
|
||||
@ -834,6 +854,7 @@ class Editor extends View
|
||||
@underlayer.css('top', -scrollTop)
|
||||
@overlayer.css('top', -scrollTop)
|
||||
@gutter.lineNumbers.css('top', -scrollTop)
|
||||
|
||||
if options?.adjustVerticalScrollbar ? true
|
||||
@verticalScrollbar.scrollTop(scrollTop)
|
||||
@activeEditSession.setScrollTop(@scrollTop())
|
||||
@ -936,6 +957,9 @@ class Editor extends View
|
||||
calculateWidthInChars: ->
|
||||
Math.floor(@scrollView.width() / @charWidth)
|
||||
|
||||
calculateHeightInLines: ->
|
||||
Math.ceil($(window).height() / @lineHeight)
|
||||
|
||||
# Enables/disables soft wrap on the editor.
|
||||
#
|
||||
# softWrap - A {Boolean} which, if `true`, enables soft wrap
|
||||
@ -950,13 +974,9 @@ class Editor extends View
|
||||
#
|
||||
# fontSize - A {Number} indicating the font size in pixels.
|
||||
setFontSize: (fontSize) ->
|
||||
headTag = $("head")
|
||||
styleTag = headTag.find("style.font-size")
|
||||
if styleTag.length == 0
|
||||
styleTag = $$ -> @style class: 'font-size'
|
||||
headTag.append styleTag
|
||||
@css('font-size', "#{fontSize}px}")
|
||||
|
||||
styleTag.text(".editor {font-size: #{fontSize}px}")
|
||||
@clearCharacterWidthCache()
|
||||
|
||||
if @isOnDom()
|
||||
@redraw()
|
||||
@ -972,17 +992,10 @@ class Editor extends View
|
||||
# Sets the font family for the editor.
|
||||
#
|
||||
# fontFamily - A {String} identifying the CSS `font-family`,
|
||||
setFontFamily: (fontFamily) ->
|
||||
headTag = $("head")
|
||||
styleTag = headTag.find("style.editor-font-family")
|
||||
setFontFamily: (fontFamily='') ->
|
||||
@css('font-family', fontFamily)
|
||||
|
||||
if fontFamily?
|
||||
if styleTag.length == 0
|
||||
styleTag = $$ -> @style class: 'editor-font-family'
|
||||
headTag.append styleTag
|
||||
styleTag.text(".editor {font-family: #{fontFamily}}")
|
||||
else
|
||||
styleTag.remove()
|
||||
@clearCharacterWidthCache()
|
||||
|
||||
@redraw()
|
||||
|
||||
@ -991,11 +1004,7 @@ class Editor extends View
|
||||
# Returns a {String} identifying the CSS `font-family`,
|
||||
getFontFamily: -> @css("font-family")
|
||||
|
||||
# Clears the CSS `font-family` property from the editor.
|
||||
clearFontFamily: ->
|
||||
$('head style.editor-font-family').remove()
|
||||
|
||||
# Clears the CSS `font-family` property from the editor.
|
||||
# Redraw the editor
|
||||
redraw: ->
|
||||
return unless @hasParent()
|
||||
return unless @attached
|
||||
@ -1124,6 +1133,7 @@ class Editor extends View
|
||||
@charWidth = charRect.width
|
||||
@charHeight = charRect.height
|
||||
fragment.remove()
|
||||
@setHeightInLines()
|
||||
|
||||
updateLayerDimensions: ->
|
||||
height = @lineHeight * @getScreenLineCount()
|
||||
@ -1143,6 +1153,14 @@ class Editor extends View
|
||||
@layerMinWidth = minWidth
|
||||
@trigger 'editor:min-width-changed'
|
||||
|
||||
# Override for speed. The base function checks computedStyle, unnecessary here.
|
||||
isHidden: ->
|
||||
style = this[0].style
|
||||
if style.display == 'none' or not @isOnDom()
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
clearRenderedLines: ->
|
||||
@renderedLines.empty()
|
||||
@firstRenderedScreenRow = null
|
||||
@ -1174,7 +1192,7 @@ class Editor extends View
|
||||
updateDisplay: (options={}) ->
|
||||
return unless @attached and @activeEditSession
|
||||
return if @activeEditSession.destroyed
|
||||
unless @isVisible()
|
||||
unless @isOnDom() and @isVisible()
|
||||
@redrawOnReattach = true
|
||||
return
|
||||
|
||||
@ -1194,9 +1212,15 @@ class Editor extends View
|
||||
for cursorView in @getCursorViews()
|
||||
if cursorView.needsRemoval
|
||||
cursorView.remove()
|
||||
else if cursorView.needsUpdate
|
||||
else if @shouldUpdateCursor(cursorView)
|
||||
cursorView.updateDisplay()
|
||||
|
||||
shouldUpdateCursor: (cursorView) ->
|
||||
return false unless cursorView.needsUpdate
|
||||
|
||||
pos = cursorView.getScreenPosition()
|
||||
pos.row >= @firstRenderedScreenRow and pos.row <= @lastRenderedScreenRow
|
||||
|
||||
updateSelectionViews: ->
|
||||
if @newSelections.length > 0
|
||||
@addSelectionView(selection) for selection in @newSelections when not selection.destroyed
|
||||
@ -1205,9 +1229,17 @@ class Editor extends View
|
||||
for selectionView in @getSelectionViews()
|
||||
if selectionView.needsRemoval
|
||||
selectionView.remove()
|
||||
else
|
||||
else if @shouldUpdateSelection(selectionView)
|
||||
selectionView.updateDisplay()
|
||||
|
||||
shouldUpdateSelection: (selectionView) ->
|
||||
screenRange = selectionView.getScreenRange()
|
||||
startRow = screenRange.start.row
|
||||
endRow = screenRange.end.row
|
||||
(startRow >= @firstRenderedScreenRow and startRow <= @lastRenderedScreenRow) or # startRow in range
|
||||
(endRow >= @firstRenderedScreenRow and endRow <= @lastRenderedScreenRow) or # endRow in range
|
||||
(startRow <= @firstRenderedScreenRow and endRow >= @lastRenderedScreenRow) # selection surrounds the rendered items
|
||||
|
||||
syncCursorAnimations: ->
|
||||
for cursorView in @getCursorViews()
|
||||
do (cursorView) -> cursorView.resetBlinking()
|
||||
@ -1226,23 +1258,24 @@ class Editor extends View
|
||||
|
||||
updateRenderedLines: ->
|
||||
firstVisibleScreenRow = @getFirstVisibleScreenRow()
|
||||
lastVisibleScreenRow = @getLastVisibleScreenRow()
|
||||
lastScreenRowToRender = firstVisibleScreenRow + @heightInLines - 1
|
||||
lastScreenRow = @getLastScreenRow()
|
||||
|
||||
if @firstRenderedScreenRow? and firstVisibleScreenRow >= @firstRenderedScreenRow and lastVisibleScreenRow <= @lastRenderedScreenRow
|
||||
if @firstRenderedScreenRow? and firstVisibleScreenRow >= @firstRenderedScreenRow and lastScreenRowToRender <= @lastRenderedScreenRow
|
||||
renderFrom = Math.min(lastScreenRow, @firstRenderedScreenRow)
|
||||
renderTo = Math.min(lastScreenRow, @lastRenderedScreenRow)
|
||||
else
|
||||
renderFrom = Math.min(lastScreenRow, Math.max(0, firstVisibleScreenRow - @lineOverdraw))
|
||||
renderTo = Math.min(lastScreenRow, lastVisibleScreenRow + @lineOverdraw)
|
||||
renderTo = Math.min(lastScreenRow, lastScreenRowToRender + @lineOverdraw)
|
||||
|
||||
if @pendingChanges.length == 0 and @firstRenderedScreenRow and @firstRenderedScreenRow <= renderFrom and renderTo <= @lastRenderedScreenRow
|
||||
return
|
||||
|
||||
@gutter.updateLineNumbers(@pendingChanges, renderFrom, renderTo)
|
||||
intactRanges = @computeIntactRanges()
|
||||
@pendingChanges = []
|
||||
@truncateIntactRanges(intactRanges, renderFrom, renderTo)
|
||||
changes = @pendingChanges
|
||||
intactRanges = @computeIntactRanges(renderFrom, renderTo)
|
||||
|
||||
@gutter.updateLineNumbers(changes, renderFrom, renderTo)
|
||||
|
||||
@clearDirtyRanges(intactRanges)
|
||||
@fillDirtyRanges(intactRanges, renderFrom, renderTo)
|
||||
@firstRenderedScreenRow = renderFrom
|
||||
@ -1268,7 +1301,7 @@ class Editor extends View
|
||||
|
||||
emptyLineChanges
|
||||
|
||||
computeIntactRanges: ->
|
||||
computeIntactRanges: (renderFrom, renderTo) ->
|
||||
return [] if !@firstRenderedScreenRow? and !@lastRenderedScreenRow?
|
||||
|
||||
intactRanges = [{start: @firstRenderedScreenRow, end: @lastRenderedScreenRow, domStart: 0}]
|
||||
@ -1303,6 +1336,9 @@ class Editor extends View
|
||||
domStart: range.domStart + change.end + 1 - range.start
|
||||
)
|
||||
intactRanges = newIntactRanges
|
||||
|
||||
@truncateIntactRanges(intactRanges, renderFrom, renderTo)
|
||||
|
||||
@pendingChanges = []
|
||||
|
||||
intactRanges
|
||||
@ -1322,35 +1358,35 @@ class Editor extends View
|
||||
intactRanges.sort (a, b) -> a.domStart - b.domStart
|
||||
|
||||
clearDirtyRanges: (intactRanges) ->
|
||||
renderedLines = @renderedLines[0]
|
||||
killLine = (line) ->
|
||||
next = line.nextSibling
|
||||
renderedLines.removeChild(line)
|
||||
next
|
||||
|
||||
if intactRanges.length == 0
|
||||
@renderedLines.empty()
|
||||
else if currentLine = renderedLines.firstChild
|
||||
@renderedLines[0].innerHTML = ''
|
||||
else if currentLine = @renderedLines[0].firstChild
|
||||
domPosition = 0
|
||||
for intactRange in intactRanges
|
||||
while intactRange.domStart > domPosition
|
||||
currentLine = killLine(currentLine)
|
||||
currentLine = @clearLine(currentLine)
|
||||
domPosition++
|
||||
for i in [intactRange.start..intactRange.end]
|
||||
currentLine = currentLine.nextSibling
|
||||
domPosition++
|
||||
while currentLine
|
||||
currentLine = killLine(currentLine)
|
||||
currentLine = @clearLine(currentLine)
|
||||
|
||||
clearLine: (lineElement) ->
|
||||
next = lineElement.nextSibling
|
||||
@renderedLines[0].removeChild(lineElement)
|
||||
next
|
||||
|
||||
fillDirtyRanges: (intactRanges, renderFrom, renderTo) ->
|
||||
renderedLines = @renderedLines[0]
|
||||
nextIntact = intactRanges.shift()
|
||||
currentLine = renderedLines.firstChild
|
||||
i = 0
|
||||
nextIntact = intactRanges[i]
|
||||
currentLine = @renderedLines[0].firstChild
|
||||
|
||||
row = renderFrom
|
||||
while row <= renderTo
|
||||
if row == nextIntact?.end + 1
|
||||
nextIntact = intactRanges.shift()
|
||||
nextIntact = intactRanges[++i]
|
||||
|
||||
if !nextIntact or row < nextIntact.start
|
||||
if nextIntact
|
||||
dirtyRangeEnd = nextIntact.start - 1
|
||||
@ -1358,7 +1394,7 @@ class Editor extends View
|
||||
dirtyRangeEnd = renderTo
|
||||
|
||||
for lineElement in @buildLineElementsForScreenRows(row, dirtyRangeEnd)
|
||||
renderedLines.insertBefore(lineElement, currentLine)
|
||||
@renderedLines[0].insertBefore(lineElement, currentLine)
|
||||
row++
|
||||
else
|
||||
currentLine = currentLine.nextSibling
|
||||
@ -1379,14 +1415,18 @@ class Editor extends View
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getFirstVisibleScreenRow: ->
|
||||
Math.floor(@scrollTop() / @lineHeight)
|
||||
screenRow = Math.floor(@scrollTop() / @lineHeight)
|
||||
screenRow = 0 if isNaN(screenRow)
|
||||
screenRow
|
||||
|
||||
# Retrieves the number of the row that is visible and currently at the top of the editor.
|
||||
# Retrieves the number of the row that is visible and currently at the bottom of the editor.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getLastVisibleScreenRow: ->
|
||||
calculatedRow = Math.ceil((@scrollTop() + @scrollView.height()) / @lineHeight) - 1
|
||||
Math.max(0, Math.min(@getScreenLineCount() - 1, calculatedRow))
|
||||
screenRow = Math.max(0, Math.min(@getScreenLineCount() - 1, calculatedRow))
|
||||
screenRow = 0 if isNaN(screenRow)
|
||||
screenRow
|
||||
|
||||
# Given a row number, identifies if it is currently visible.
|
||||
#
|
||||
@ -1411,11 +1451,11 @@ class Editor extends View
|
||||
new Array(div.children...)
|
||||
|
||||
htmlForScreenRows: (startRow, endRow) ->
|
||||
htmlLines = []
|
||||
htmlLines = ''
|
||||
screenRow = startRow
|
||||
for line in @activeEditSession.linesForScreenRows(startRow, endRow)
|
||||
htmlLines.push(@htmlForScreenLine(line, screenRow++))
|
||||
htmlLines.join('\n\n')
|
||||
htmlLines += @htmlForScreenLine(line, screenRow++)
|
||||
htmlLines
|
||||
|
||||
htmlForScreenLine: (screenLine, screenRow) ->
|
||||
{ tokens, text, lineEnding, fold, isSoftWrapped } = screenLine
|
||||
@ -1506,28 +1546,92 @@ class Editor extends View
|
||||
unless existingLineElement
|
||||
lineElement = @buildLineElementForScreenRow(actualRow)
|
||||
@renderedLines.append(lineElement)
|
||||
left = @positionLeftForLineAndColumn(lineElement, column)
|
||||
left = @positionLeftForLineAndColumn(lineElement, actualRow, column)
|
||||
unless existingLineElement
|
||||
@renderedLines[0].removeChild(lineElement)
|
||||
{ top: row * @lineHeight, left }
|
||||
|
||||
positionLeftForLineAndColumn: (lineElement, column) ->
|
||||
return 0 if column is 0
|
||||
delta = 0
|
||||
iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, acceptNode: -> NodeFilter.FILTER_ACCEPT)
|
||||
while textNode = iterator.nextNode()
|
||||
nextDelta = delta + textNode.textContent.length
|
||||
if nextDelta >= column
|
||||
offset = column - delta
|
||||
break
|
||||
delta = nextDelta
|
||||
positionLeftForLineAndColumn: (lineElement, screenRow, column) ->
|
||||
return 0 if column == 0
|
||||
|
||||
range = document.createRange()
|
||||
range.setEnd(textNode, offset)
|
||||
range.collapse()
|
||||
leftPixels = range.getClientRects()[0].left - Math.floor(@scrollView.offset().left) + Math.floor(@scrollLeft())
|
||||
range.detach()
|
||||
leftPixels
|
||||
bufferRow = @bufferRowsForScreenRows(screenRow, screenRow)[0] ? screenRow
|
||||
tokenizedLine = @activeEditSession.displayBuffer.tokenizedBuffer.tokenizedLines[bufferRow]
|
||||
|
||||
left = 0
|
||||
index = 0
|
||||
for token in tokenizedLine.tokens
|
||||
for char in token.value
|
||||
return left if index >= column
|
||||
|
||||
val = @getCharacterWidthCache(token.scopes, char)
|
||||
if val?
|
||||
left += val
|
||||
else
|
||||
return @measureToColumn(lineElement, tokenizedLine, column)
|
||||
|
||||
index++
|
||||
left
|
||||
|
||||
scopesForColumn: (tokenizedLine, column) ->
|
||||
index = 0
|
||||
for token in tokenizedLine.tokens
|
||||
for char in token.value
|
||||
return token.scopes if index == column
|
||||
index++
|
||||
null
|
||||
|
||||
measureToColumn: (lineElement, tokenizedLine, column) ->
|
||||
left = oldLeft = index = 0
|
||||
iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, TextNodeFilter)
|
||||
|
||||
returnLeft = null
|
||||
|
||||
while textNode = iterator.nextNode()
|
||||
content = textNode.textContent
|
||||
|
||||
for char, i in content
|
||||
|
||||
# Dont return right away, finish caching the whole line
|
||||
returnLeft = left if index == column
|
||||
oldLeft = left
|
||||
|
||||
scopes = @scopesForColumn(tokenizedLine, index)
|
||||
cachedVal = @getCharacterWidthCache(scopes, char)
|
||||
|
||||
if cachedVal?
|
||||
left = oldLeft + cachedVal
|
||||
else
|
||||
# i + 1 to measure to the end of the current character
|
||||
MeasureRange.setEnd(textNode, i + 1)
|
||||
MeasureRange.collapse()
|
||||
rects = MeasureRange.getClientRects()
|
||||
return 0 if rects.length == 0
|
||||
left = rects[0].left - Math.floor(@scrollView.offset().left) + Math.floor(@scrollLeft())
|
||||
|
||||
@setCharacterWidthCache(scopes, char, left - oldLeft) if scopes?
|
||||
|
||||
index++
|
||||
|
||||
returnLeft ? left
|
||||
|
||||
getCharacterWidthCache: (scopes, char) ->
|
||||
scopes ?= NoScope
|
||||
obj = Editor.characterWidthCache
|
||||
for scope in scopes
|
||||
obj = obj[scope]
|
||||
return null unless obj?
|
||||
obj[char]
|
||||
|
||||
setCharacterWidthCache: (scopes, char, val) ->
|
||||
scopes ?= NoScope
|
||||
obj = Editor.characterWidthCache
|
||||
for scope in scopes
|
||||
obj[scope] ?= {}
|
||||
obj = obj[scope]
|
||||
obj[char] = val
|
||||
|
||||
clearCharacterWidthCache: ->
|
||||
Editor.characterWidthCache = {}
|
||||
|
||||
pixelOffsetForScreenPosition: (position) ->
|
||||
{top, left} = @pixelPositionForScreenPosition(position)
|
||||
@ -1601,30 +1705,9 @@ class Editor extends View
|
||||
scopeStack = []
|
||||
line = []
|
||||
|
||||
updateScopeStack = (desiredScopes) ->
|
||||
excessScopes = scopeStack.length - desiredScopes.length
|
||||
_.times(excessScopes, popScope) if excessScopes > 0
|
||||
|
||||
# pop until common prefix
|
||||
for i in [scopeStack.length..0]
|
||||
break if _.isEqual(scopeStack[0...i], desiredScopes[0...i])
|
||||
popScope()
|
||||
|
||||
# push on top of common prefix until scopeStack == desiredScopes
|
||||
for j in [i...desiredScopes.length]
|
||||
pushScope(desiredScopes[j])
|
||||
|
||||
pushScope = (scope) ->
|
||||
scopeStack.push(scope)
|
||||
line.push("<span class=\"#{scope.replace(/\./g, ' ')}\">")
|
||||
|
||||
popScope = ->
|
||||
scopeStack.pop()
|
||||
line.push("</span>")
|
||||
|
||||
attributePairs = []
|
||||
attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of attributes
|
||||
line.push("<div #{attributePairs.join(' ')}>")
|
||||
attributePairs = ''
|
||||
attributePairs += " #{attributeName}=\"#{value}\"" for attributeName, value of attributes
|
||||
line.push("<div #{attributePairs}>")
|
||||
|
||||
if text == ''
|
||||
html = Editor.buildEmptyLineHtml(showIndentGuide, eolInvisibles, htmlEolInvisibles, indentation, activeEditSession, mini)
|
||||
@ -1635,39 +1718,64 @@ class Editor extends View
|
||||
lineIsWhitespaceOnly = firstTrailingWhitespacePosition is 0
|
||||
position = 0
|
||||
for token in tokens
|
||||
updateScopeStack(token.scopes)
|
||||
@updateScopeStack(line, scopeStack, token.scopes)
|
||||
hasLeadingWhitespace = position < firstNonWhitespacePosition
|
||||
hasTrailingWhitespace = position + token.value.length > firstTrailingWhitespacePosition
|
||||
hasIndentGuide = not mini and showIndentGuide and (hasLeadingWhitespace or lineIsWhitespaceOnly)
|
||||
line.push(token.getValueAsHtml({invisibles, hasLeadingWhitespace, hasTrailingWhitespace, hasIndentGuide}))
|
||||
position += token.value.length
|
||||
|
||||
popScope() while scopeStack.length > 0
|
||||
@popScope(line, scopeStack) while scopeStack.length > 0
|
||||
line.push(htmlEolInvisibles) unless text == ''
|
||||
line.push("<span class='fold-marker'/>") if fold
|
||||
|
||||
line.push('</div>')
|
||||
line.join('')
|
||||
|
||||
@updateScopeStack: (line, scopeStack, desiredScopes) ->
|
||||
excessScopes = scopeStack.length - desiredScopes.length
|
||||
if excessScopes > 0
|
||||
@popScope(line, scopeStack) while excessScopes--
|
||||
|
||||
# pop until common prefix
|
||||
for i in [scopeStack.length..0]
|
||||
break if _.isEqual(scopeStack[0...i], desiredScopes[0...i])
|
||||
@popScope(line, scopeStack)
|
||||
|
||||
# push on top of common prefix until scopeStack == desiredScopes
|
||||
for j in [i...desiredScopes.length]
|
||||
@pushScope(line, scopeStack, desiredScopes[j])
|
||||
|
||||
null
|
||||
|
||||
@pushScope: (line, scopeStack, scope) ->
|
||||
scopeStack.push(scope)
|
||||
line.push("<span class=\"#{scope.replace(/\./g, ' ')}\">")
|
||||
|
||||
@popScope: (line, scopeStack) ->
|
||||
scopeStack.pop()
|
||||
line.push("</span>")
|
||||
|
||||
@buildEmptyLineHtml: (showIndentGuide, eolInvisibles, htmlEolInvisibles, indentation, activeEditSession, mini) ->
|
||||
indentCharIndex = 0
|
||||
if not mini and showIndentGuide
|
||||
if indentation > 0
|
||||
tabLength = activeEditSession.getTabLength()
|
||||
indentGuideHtml = []
|
||||
indentGuideHtml = ''
|
||||
for level in [0...indentation]
|
||||
indentLevelHtml = ["<span class='indent-guide'>"]
|
||||
indentLevelHtml = "<span class='indent-guide'>"
|
||||
for characterPosition in [0...tabLength]
|
||||
if invisible = eolInvisibles.shift()
|
||||
indentLevelHtml.push("<span class='invisible-character'>#{invisible}</span>")
|
||||
if invisible = eolInvisibles[indentCharIndex++]
|
||||
indentLevelHtml += "<span class='invisible-character'>#{invisible}</span>"
|
||||
else
|
||||
indentLevelHtml.push(' ')
|
||||
indentLevelHtml.push("</span>")
|
||||
indentGuideHtml.push(indentLevelHtml.join(''))
|
||||
indentLevelHtml += ' '
|
||||
indentLevelHtml += "</span>"
|
||||
indentGuideHtml += indentLevelHtml
|
||||
|
||||
for invisible in eolInvisibles
|
||||
indentGuideHtml.push("<span class='invisible-character'>#{invisible}</span>")
|
||||
indentGuideHtml += "<span class='invisible-character'>#{invisible}</span>"
|
||||
|
||||
return indentGuideHtml.join('')
|
||||
return indentGuideHtml
|
||||
|
||||
if htmlEolInvisibles.length > 0
|
||||
htmlEolInvisibles
|
||||
|
@ -1,5 +1,5 @@
|
||||
Q = require 'q'
|
||||
EventEmitter = require './event-emitter'
|
||||
fs = require 'fs'
|
||||
path = require 'path'
|
||||
fsUtils = require './fs-utils'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
@ -23,9 +23,7 @@ class File
|
||||
# * symlink:
|
||||
# A Boolean indicating if the path is a symlink (default: false)
|
||||
constructor: (@path, @symlink=false) ->
|
||||
try
|
||||
if fs.statSync(@path).isDirectorySync()
|
||||
throw new Error("#{@path} is a directory")
|
||||
throw new Error("#{@path} is a directory") if fsUtils.isDirectorySync(@path)
|
||||
|
||||
# Private: Sets the path for the file.
|
||||
setPath: (@path) ->
|
||||
@ -44,14 +42,8 @@ class File
|
||||
fsUtils.writeSync(@getPath(), text)
|
||||
@subscribeToNativeChangeEvents() if not previouslyExisted and @subscriptionCount() > 0
|
||||
|
||||
# Public: Reads the contents of the file.
|
||||
#
|
||||
# * flushCache:
|
||||
# A Boolean indicating whether to require a direct read or if a cached
|
||||
# copy is acceptable.
|
||||
#
|
||||
# Returns a String.
|
||||
read: (flushCache)->
|
||||
# Private: Deprecated
|
||||
read: (flushCache) ->
|
||||
if not @exists()
|
||||
@cachedContents = null
|
||||
else if not @cachedContents? or flushCache
|
||||
@ -59,6 +51,39 @@ class File
|
||||
else
|
||||
@cachedContents
|
||||
|
||||
# Public: Reads the contents of the file.
|
||||
#
|
||||
# * flushCache:
|
||||
# A Boolean indicating whether to require a direct read or if a cached
|
||||
# copy is acceptable.
|
||||
#
|
||||
# Returns a promise that resovles to a String.
|
||||
readAsync: (flushCache) ->
|
||||
if not @exists()
|
||||
promise = Q(null)
|
||||
else if not @cachedContents? or flushCache
|
||||
deferred = Q.defer()
|
||||
promise = deferred.promise
|
||||
|
||||
content = []
|
||||
bytesRead = 0
|
||||
readStream = fsUtils.createReadStream @getPath(), encoding: 'utf8'
|
||||
readStream.on 'data', (chunk) ->
|
||||
content.push(chunk)
|
||||
bytesRead += chunk.length
|
||||
deferred.notify(bytesRead)
|
||||
|
||||
readStream.on 'end', ->
|
||||
deferred.resolve(content.join())
|
||||
|
||||
readStream.on 'error', (error) ->
|
||||
deferred.reject(error)
|
||||
else
|
||||
promise = Q(@cachedContents)
|
||||
|
||||
promise.then (contents) ->
|
||||
@cachedContents = contents
|
||||
|
||||
# Public: Returns whether a file exists.
|
||||
exists: ->
|
||||
fsUtils.exists(@getPath())
|
||||
|
@ -25,14 +25,14 @@ fsExtensions =
|
||||
|
||||
# Returns true if a file or folder at the specified path exists.
|
||||
exists: (pathToCheck) ->
|
||||
pathToCheck? and fs.existsSync(pathToCheck)
|
||||
pathToCheck? and fs.statSyncNoException(pathToCheck) isnt false
|
||||
|
||||
# Returns true if the specified path is a directory that exists.
|
||||
isDirectorySync: (directoryPath) ->
|
||||
return false unless directoryPath?.length > 0
|
||||
try
|
||||
fs.statSync(directoryPath).isDirectory()
|
||||
catch e
|
||||
if stat = fs.statSyncNoException(directoryPath)
|
||||
stat.isDirectory()
|
||||
else
|
||||
false
|
||||
|
||||
isDirectory: (directoryPath, done) ->
|
||||
@ -50,17 +50,17 @@ fsExtensions =
|
||||
# Returns true if the specified path is a regular file that exists.
|
||||
isFileSync: (filePath) ->
|
||||
return false unless filePath?.length > 0
|
||||
try
|
||||
fs.statSync(filePath).isFile()
|
||||
catch e
|
||||
if stat = fs.statSyncNoException(filePath)
|
||||
stat.isFile()
|
||||
else
|
||||
false
|
||||
|
||||
# Returns true if the specified path is executable.
|
||||
isExecutableSync: (pathToCheck) ->
|
||||
return false unless pathToCheck?.length > 0
|
||||
try
|
||||
(fs.statSync(pathToCheck).mode & 0o777 & 1) isnt 0
|
||||
catch e
|
||||
if stat = fs.statSyncNoException(pathToCheck)
|
||||
(stat.mode & 0o777 & 1) isnt 0
|
||||
else
|
||||
false
|
||||
|
||||
# Returns an array with the paths of the files and folders
|
||||
@ -156,8 +156,8 @@ fsExtensions =
|
||||
childPath = path.join(directoryPath, file)
|
||||
stats = fs.lstatSync(childPath)
|
||||
if stats.isSymbolicLink()
|
||||
try
|
||||
stats = fs.statSync(childPath)
|
||||
if linkStats = fs.statSyncNoException(childPath)
|
||||
stats = linkStats
|
||||
if stats.isDirectory()
|
||||
traverse(childPath, onFile, onDirectory) if onDirectory(childPath)
|
||||
else if stats.isFile()
|
||||
|
@ -44,6 +44,7 @@ class Git
|
||||
path: null
|
||||
statuses: null
|
||||
upstream: null
|
||||
branch: null
|
||||
statusTask: null
|
||||
|
||||
# Private: Creates a new `Git` object.
|
||||
@ -142,6 +143,12 @@ class Git
|
||||
# Public: Determine if the given path is new.
|
||||
isPathNew: (path) -> @isStatusNew(@getPathStatus(path))
|
||||
|
||||
# Public: Is the project at the root of this repository?
|
||||
#
|
||||
# Returns true if at the root, false if in a subfolder of the repository.
|
||||
isProjectAtRoot: ->
|
||||
@projectAtRoot ?= project.relativize(@getWorkingDirectory()) is ''
|
||||
|
||||
# Public: Makes a path relative to the repository's working directory.
|
||||
relativize: (path) -> @getRepo().relativize(path)
|
||||
|
||||
@ -171,6 +178,15 @@ class Git
|
||||
@getPathStatus(path) if headCheckedOut
|
||||
headCheckedOut
|
||||
|
||||
# Public: Checks out a branch in your repository.
|
||||
#
|
||||
# reference - The {String} reference to checkout
|
||||
# create - A {Boolean} value which, if `true` creates the new reference if it doesn't exist.
|
||||
#
|
||||
# Returns a {Boolean} that's `true` if the method was successful.
|
||||
checkoutReference: (reference, create) ->
|
||||
@getRepo().checkoutReference(reference, create)
|
||||
|
||||
# Public: Retrieves the number of lines added and removed to a path.
|
||||
#
|
||||
# This compares the working directory contents of the path to the `HEAD`
|
||||
@ -239,6 +255,12 @@ class Git
|
||||
# Public: ?
|
||||
getReferenceTarget: (reference) -> @getRepo().getReferenceTarget(reference)
|
||||
|
||||
# Public: Gets all the local and remote references.
|
||||
#
|
||||
# Returns an object with three keys: `heads`, `remotes`, and `tags`. Each key
|
||||
# can be an array of strings containing the reference names.
|
||||
getReferences: -> @getRepo().getReferences()
|
||||
|
||||
# Public: ?
|
||||
getAheadBehindCount: (reference) -> @getRepo().getAheadBehindCount(reference)
|
||||
|
||||
@ -247,8 +269,9 @@ class Git
|
||||
|
||||
# Private:
|
||||
refreshStatus: ->
|
||||
@statusTask = Task.once require.resolve('./repository-status-handler'), @getPath(), ({statuses, upstream}) =>
|
||||
statusesUnchanged = _.isEqual(statuses, @statuses) and _.isEqual(upstream, @upstream)
|
||||
@statusTask = Task.once require.resolve('./repository-status-handler'), @getPath(), ({statuses, upstream, branch}) =>
|
||||
statusesUnchanged = _.isEqual(statuses, @statuses) and _.isEqual(upstream, @upstream) and _.isEqual(branch, @branch)
|
||||
@statuses = statuses
|
||||
@upstream = upstream
|
||||
@branch = branch
|
||||
@trigger 'statuses-changed' unless statusesUnchanged
|
||||
|
@ -15,8 +15,11 @@ class Gutter extends View
|
||||
@div class: 'gutter', =>
|
||||
@div outlet: 'lineNumbers', class: 'line-numbers'
|
||||
|
||||
firstScreenRow: Infinity
|
||||
lastScreenRow: -1
|
||||
firstScreenRow: null
|
||||
lastScreenRow: null
|
||||
|
||||
initialize: ->
|
||||
@elementBuilder = document.createElement('div')
|
||||
|
||||
afterAttach: (onDom) ->
|
||||
return if @attached or not onDom
|
||||
@ -62,46 +65,157 @@ class Gutter extends View
|
||||
setShowLineNumbers: (showLineNumbers) ->
|
||||
if showLineNumbers then @lineNumbers.show() else @lineNumbers.hide()
|
||||
|
||||
# Get all the line-number divs.
|
||||
#
|
||||
# Returns a list of {HTMLElement}s.
|
||||
getLineNumberElements: ->
|
||||
@lineNumbers[0].children
|
||||
|
||||
# Get all the line-number divs.
|
||||
#
|
||||
# Returns a list of {HTMLElement}s.
|
||||
getLineNumberElementsForClass: (klass) ->
|
||||
@lineNumbers[0].getElementsByClassName(klass)
|
||||
|
||||
# Get a single line-number div.
|
||||
#
|
||||
# * bufferRow: 0 based line number
|
||||
#
|
||||
# Returns a list of {HTMLElement}s that correspond to the bufferRow. More than
|
||||
# one in the list indicates a wrapped line.
|
||||
getLineNumberElement: (bufferRow) ->
|
||||
@getLineNumberElementsForClass("line-number-#{bufferRow}")
|
||||
|
||||
# Add a class to all line-number divs.
|
||||
#
|
||||
# * klass: string class name
|
||||
#
|
||||
# Returns true if the class was added to any lines
|
||||
addClassToAllLines: (klass)->
|
||||
elements = @getLineNumberElements()
|
||||
el.classList.add(klass) for el in elements
|
||||
!!elements.length
|
||||
|
||||
# Remove a class from all line-number divs.
|
||||
#
|
||||
# * klass: string class name. Can only be one class name. i.e. 'my-class'
|
||||
#
|
||||
# Returns true if the class was removed from any lines
|
||||
removeClassFromAllLines: (klass)->
|
||||
# This is faster than calling $.removeClass on all lines, and faster than
|
||||
# making a new array and iterating through it.
|
||||
elements = @getLineNumberElementsForClass(klass)
|
||||
willRemoveClasses = !!elements.length
|
||||
elements[0].classList.remove(klass) while elements.length > 0
|
||||
willRemoveClasses
|
||||
|
||||
# Add a class to a single line-number div
|
||||
#
|
||||
# * bufferRow: 0 based line number
|
||||
# * klass: string class name
|
||||
#
|
||||
# Returns true if there were lines the class was added to
|
||||
addClassToLine: (bufferRow, klass)->
|
||||
elements = @getLineNumberElement(bufferRow)
|
||||
el.classList.add(klass) for el in elements
|
||||
!!elements.length
|
||||
|
||||
# Remove a class from a single line-number div
|
||||
#
|
||||
# * bufferRow: 0 based line number
|
||||
# * klass: string class name
|
||||
#
|
||||
# Returns true if there were lines the class was removed from
|
||||
removeClassFromLine: (bufferRow, klass)->
|
||||
classesRemoved = false
|
||||
elements = @getLineNumberElement(bufferRow)
|
||||
for el in elements
|
||||
hasClass = el.classList.contains(klass)
|
||||
classesRemoved |= hasClass
|
||||
el.classList.remove(klass) if hasClass
|
||||
classesRemoved
|
||||
|
||||
### Internal ###
|
||||
|
||||
updateLineNumbers: (changes, renderFrom, renderTo) ->
|
||||
if renderFrom < @firstScreenRow or renderTo > @lastScreenRow
|
||||
performUpdate = true
|
||||
else if @getEditor().getLastScreenRow() < @lastScreenRow
|
||||
performUpdate = true
|
||||
updateLineNumbers: (changes, startScreenRow, endScreenRow) ->
|
||||
# Check if we have something already rendered that overlaps the requested range
|
||||
updateAllLines = not (startScreenRow? and endScreenRow?)
|
||||
updateAllLines |= endScreenRow <= @firstScreenRow or startScreenRow >= @lastScreenRow
|
||||
|
||||
for change in changes
|
||||
# When there is a change to the bufferRow -> screenRow map (i.e. a fold),
|
||||
# then rerender everything.
|
||||
if (change.screenDelta or change.bufferDelta) and change.screenDelta != change.bufferDelta
|
||||
updateAllLines = true
|
||||
break
|
||||
|
||||
if updateAllLines
|
||||
@lineNumbers[0].innerHTML = @buildLineElementsHtml(startScreenRow, endScreenRow)
|
||||
else
|
||||
for change in changes
|
||||
if change.screenDelta or change.bufferDelta
|
||||
performUpdate = true
|
||||
break
|
||||
# When scrolling or adding/removing lines, we just add/remove lines from the ends.
|
||||
if startScreenRow < @firstScreenRow
|
||||
@prependLineElements(@buildLineElements(startScreenRow, @firstScreenRow-1))
|
||||
else if startScreenRow != @firstScreenRow
|
||||
@removeLineElements(startScreenRow - @firstScreenRow)
|
||||
|
||||
@renderLineNumbers(renderFrom, renderTo) if performUpdate
|
||||
|
||||
renderLineNumbers: (startScreenRow, endScreenRow) ->
|
||||
editor = @getEditor()
|
||||
maxDigits = editor.getLineCount().toString().length
|
||||
rows = editor.bufferRowsForScreenRows(startScreenRow, endScreenRow)
|
||||
|
||||
cursorScreenRow = editor.getCursorScreenPosition().row
|
||||
@lineNumbers[0].innerHTML = $$$ ->
|
||||
for row in rows
|
||||
if row == lastScreenRow
|
||||
rowValue = '•'
|
||||
else
|
||||
rowValue = (row + 1).toString()
|
||||
classes = ['line-number']
|
||||
classes.push('fold') if editor.isFoldedAtBufferRow(row)
|
||||
@div linenumber: row, class: classes.join(' '), =>
|
||||
rowValuePadding = _.multiplyString(' ', maxDigits - rowValue.length)
|
||||
@raw("#{rowValuePadding}#{rowValue}")
|
||||
|
||||
lastScreenRow = row
|
||||
if endScreenRow > @lastScreenRow
|
||||
@appendLineElements(@buildLineElements(@lastScreenRow+1, endScreenRow))
|
||||
else if endScreenRow != @lastScreenRow
|
||||
@removeLineElements(endScreenRow - @lastScreenRow)
|
||||
|
||||
@firstScreenRow = startScreenRow
|
||||
@lastScreenRow = endScreenRow
|
||||
@highlightedRows = null
|
||||
@highlightLines()
|
||||
|
||||
prependLineElements: (lineElements) ->
|
||||
anchor = @lineNumbers[0].children[0]
|
||||
return appendLineElements(lineElements) unless anchor?
|
||||
@lineNumbers[0].insertBefore(lineElements[0], anchor) while lineElements.length > 0
|
||||
null # defeat coffeescript array return
|
||||
|
||||
appendLineElements: (lineElements) ->
|
||||
@lineNumbers[0].appendChild(lineElements[0]) while lineElements.length > 0
|
||||
null # defeat coffeescript array return
|
||||
|
||||
removeLineElements: (numberOfElements) ->
|
||||
children = @getLineNumberElements()
|
||||
|
||||
# children is a live NodeList, so remove from the desired end {numberOfElements} times
|
||||
if numberOfElements < 0
|
||||
@lineNumbers[0].removeChild(children[children.length-1]) while numberOfElements++
|
||||
else if numberOfElements > 0
|
||||
@lineNumbers[0].removeChild(children[0]) while numberOfElements--
|
||||
|
||||
null # defeat coffeescript array return
|
||||
|
||||
buildLineElements: (startScreenRow, endScreenRow) ->
|
||||
@elementBuilder.innerHTML = @buildLineElementsHtml(startScreenRow, endScreenRow)
|
||||
@elementBuilder.children
|
||||
|
||||
buildLineElementsHtml: (startScreenRow, endScreenRow) =>
|
||||
editor = @getEditor()
|
||||
maxDigits = editor.getLineCount().toString().length
|
||||
rows = editor.bufferRowsForScreenRows(startScreenRow, endScreenRow)
|
||||
|
||||
html = ''
|
||||
for row in rows
|
||||
if row == lastScreenRow
|
||||
rowValue = '•'
|
||||
else
|
||||
rowValue = (row + 1).toString()
|
||||
|
||||
classes = "line-number line-number-#{row}"
|
||||
classes += ' fold' if editor.isFoldedAtBufferRow(row)
|
||||
|
||||
rowValuePadding = _.multiplyString(' ', maxDigits - rowValue.length)
|
||||
|
||||
html += """<div class="#{classes}">#{rowValuePadding}#{rowValue}</div>"""
|
||||
|
||||
lastScreenRow = row
|
||||
|
||||
html
|
||||
|
||||
removeLineHighlights: ->
|
||||
return unless @highlightedLineNumbers
|
||||
for line in @highlightedLineNumbers
|
||||
|
@ -35,7 +35,19 @@ $.fn.isOnDom = ->
|
||||
@closest(document.body).length is 1
|
||||
|
||||
$.fn.isVisible = ->
|
||||
@is(':visible')
|
||||
!@isHidden()
|
||||
|
||||
$.fn.isHidden = ->
|
||||
# We used to check @is(':hidden'). But this is much faster than the
|
||||
# offsetWidth/offsetHeight check + all the pseudo selector mess in jquery.
|
||||
style = this[0].style
|
||||
|
||||
if style.display == 'none' or not @isOnDom()
|
||||
true
|
||||
else if style.display
|
||||
false
|
||||
else
|
||||
getComputedStyle(this[0]).display == 'none'
|
||||
|
||||
$.fn.isDisabled = ->
|
||||
!!@attr('disabled')
|
||||
|
@ -4,6 +4,7 @@ fsUtils = require './fs-utils'
|
||||
path = require 'path'
|
||||
CSON = require 'season'
|
||||
BindingSet = require './binding-set'
|
||||
EventEmitter = require './event-emitter'
|
||||
|
||||
# Internal: Associates keymaps with actions.
|
||||
#
|
||||
@ -20,6 +21,8 @@ BindingSet = require './binding-set'
|
||||
# key, you define one or more key:value pairs, associating keystrokes with a command to execute.
|
||||
module.exports =
|
||||
class Keymap
|
||||
_.extend @prototype, EventEmitter
|
||||
|
||||
bindingSets: null
|
||||
nextBindingSetIndex: 0
|
||||
bindingSetsByFirstKeystroke: null
|
||||
@ -31,6 +34,7 @@ class Keymap
|
||||
|
||||
loadBundledKeymaps: ->
|
||||
@loadDirectory(config.bundledKeymapsDirPath)
|
||||
@trigger('bundled-keymaps-loaded')
|
||||
|
||||
loadUserKeymaps: ->
|
||||
@loadDirectory(path.join(config.configDirPath, 'keymaps'))
|
||||
@ -54,6 +58,42 @@ class Keymap
|
||||
keystroke = keystrokes.split(' ')[0]
|
||||
_.remove(@bindingSetsByFirstKeystroke[keystroke], bindingSet)
|
||||
|
||||
# Public: Returns an array of objects that represent every keystroke to
|
||||
# command mapping. Each object contains the following keys `source`,
|
||||
# `selector`, `command`, `keystrokes`.
|
||||
getAllKeyMappings: ->
|
||||
mappings = []
|
||||
for bindingSet in @bindingSets
|
||||
selector = bindingSet.getSelector()
|
||||
source = @determineSource(bindingSet.getName())
|
||||
for keystrokes, command of bindingSet.getCommandsByKeystrokes()
|
||||
mappings.push {keystrokes, command, selector, source}
|
||||
|
||||
mappings
|
||||
|
||||
# Private: Returns a user friendly description of where a keybinding was
|
||||
# loaded from.
|
||||
#
|
||||
# * filePath:
|
||||
# The absolute path from which the keymap was loaded
|
||||
#
|
||||
# Returns one of:
|
||||
# * `Core` indicates it comes from a bundled package.
|
||||
# * `User` indicates that it was defined by a user.
|
||||
# * `<package-name>` the package which defined it.
|
||||
# * `Unknown` if an invalid path was passed in.
|
||||
determineSource: (filePath) ->
|
||||
return 'Unknown' unless filePath
|
||||
|
||||
pathParts = filePath.split(path.sep)
|
||||
if _.contains(pathParts, 'node_modules') or _.contains(pathParts, 'atom') or _.contains(pathParts, 'src')
|
||||
'Core'
|
||||
else if _.contains(pathParts, '.atom') and _.contains(pathParts, 'keymaps') and !_.contains(pathParts, 'packages')
|
||||
'User'
|
||||
else
|
||||
packageNameIndex = pathParts.length - 3
|
||||
pathParts[packageNameIndex]
|
||||
|
||||
bindKeys: (args...) ->
|
||||
name = args.shift() if args.length > 2
|
||||
[selector, bindings] = args
|
||||
|
52
src/menu-manager.coffee
Normal file
52
src/menu-manager.coffee
Normal file
@ -0,0 +1,52 @@
|
||||
path = require 'path'
|
||||
|
||||
_ = require 'underscore'
|
||||
ipc = require 'ipc'
|
||||
CSON = require 'season'
|
||||
|
||||
fsUtils = require './fs-utils'
|
||||
|
||||
# Public: Provides a registry for menu items that you'd like to appear in the
|
||||
# application menu.
|
||||
#
|
||||
# Should be accessed via `atom.menu`.
|
||||
module.exports =
|
||||
class MenuManager
|
||||
# Private:
|
||||
constructor: ->
|
||||
@template = []
|
||||
atom.keymap.on 'bundled-keymaps-loaded', => @loadCoreItems()
|
||||
|
||||
# Public: Adds the given item definition to the existing template.
|
||||
#
|
||||
# * item:
|
||||
# An object which describes a menu item as defined by
|
||||
# https://github.com/atom/atom-shell/blob/master/docs/api/browser/menu.md
|
||||
#
|
||||
# Returns nothing.
|
||||
add: (items) ->
|
||||
@merge(@template, item) for item in items
|
||||
@update()
|
||||
|
||||
# Public: Refreshes the currently visible menu.
|
||||
update: ->
|
||||
@sendToBrowserProcess(@template, atom.keymap.keystrokesByCommandForSelector('body'))
|
||||
|
||||
# Private
|
||||
loadCoreItems: ->
|
||||
menuPaths = fsUtils.listSync(atom.config.bundledMenusDirPath, ['cson', 'json'])
|
||||
for menuPath in menuPaths
|
||||
data = CSON.readFileSync(menuPath)
|
||||
@add(data.menu)
|
||||
|
||||
# Private: Merges an item in a submenu aware way such that new items are always
|
||||
# appended to the bottom of existing menus where possible.
|
||||
merge: (menu, item) ->
|
||||
if item.submenu? and match = _.find(menu, (o) -> o.submenu? and o.label == item.label)
|
||||
@merge(match.submenu, i) for i in item.submenu
|
||||
else
|
||||
menu.push(item)
|
||||
|
||||
# Private
|
||||
sendToBrowserProcess: (template, keystrokesByCommand) ->
|
||||
ipc.sendChannel 'update-application-menu', template, keystrokesByCommand
|
134
src/package-manager.coffee
Normal file
134
src/package-manager.coffee
Normal file
@ -0,0 +1,134 @@
|
||||
EventEmitter = require './event-emitter'
|
||||
fsUtils = require './fs-utils'
|
||||
_ = require './underscore-extensions'
|
||||
Package = require './package'
|
||||
path = require 'path'
|
||||
|
||||
module.exports =
|
||||
class PackageManager
|
||||
_.extend @prototype, EventEmitter
|
||||
|
||||
constructor: ({configDirPath, devMode, @resourcePath}) ->
|
||||
@packageDirPaths = [path.join(configDirPath, "packages")]
|
||||
if devMode
|
||||
@packageDirPaths.unshift(path.join(configDirPath, "dev", "packages"))
|
||||
|
||||
@loadedPackages = {}
|
||||
@activePackages = {}
|
||||
@packageStates = {}
|
||||
|
||||
getPackageState: (name) ->
|
||||
@packageStates[name]
|
||||
|
||||
setPackageState: (name, state) ->
|
||||
@packageStates[name] = state
|
||||
|
||||
activatePackages: ->
|
||||
@activatePackage(pack.name) for pack in @getLoadedPackages()
|
||||
|
||||
activatePackage: (name, options) ->
|
||||
if pack = @loadPackage(name, options)
|
||||
@activePackages[pack.name] = pack
|
||||
pack.activate(options)
|
||||
pack
|
||||
|
||||
deactivatePackages: ->
|
||||
@deactivatePackage(pack.name) for pack in @getActivePackages()
|
||||
|
||||
deactivatePackage: (name) ->
|
||||
if pack = @getActivePackage(name)
|
||||
@setPackageState(pack.name, state) if state = pack.serialize?()
|
||||
pack.deactivate()
|
||||
delete @activePackages[pack.name]
|
||||
else
|
||||
throw new Error("No active package for name '#{name}'")
|
||||
|
||||
getActivePackage: (name) ->
|
||||
@activePackages[name]
|
||||
|
||||
isPackageActive: (name) ->
|
||||
@getActivePackage(name)?
|
||||
|
||||
getActivePackages: ->
|
||||
_.values(@activePackages)
|
||||
|
||||
loadPackages: ->
|
||||
# Ensure atom exports is already in the require cache so the load time
|
||||
# of the first package isn't skewed by being the first to require atom
|
||||
require '../exports/atom'
|
||||
|
||||
@loadPackage(name) for name in @getAvailablePackageNames() when not @isPackageDisabled(name)
|
||||
@trigger 'loaded'
|
||||
|
||||
loadPackage: (name, options) ->
|
||||
if @isPackageDisabled(name)
|
||||
return console.warn("Tried to load disabled package '#{name}'")
|
||||
|
||||
if packagePath = @resolvePackagePath(name)
|
||||
return pack if pack = @getLoadedPackage(name)
|
||||
|
||||
pack = Package.load(packagePath, options)
|
||||
if pack.metadata?.theme
|
||||
atom.themes.register(pack)
|
||||
else
|
||||
@loadedPackages[pack.name] = pack
|
||||
pack
|
||||
else
|
||||
throw new Error("Could not resolve '#{name}' to a package path")
|
||||
|
||||
unloadPackage: (name) ->
|
||||
if @isPackageActive(name)
|
||||
throw new Error("Tried to unload active package '#{name}'")
|
||||
|
||||
if pack = @getLoadedPackage(name)
|
||||
delete @loadedPackages[pack.name]
|
||||
else
|
||||
throw new Error("No loaded package for name '#{name}'")
|
||||
|
||||
resolvePackagePath: (name) ->
|
||||
return name if fsUtils.isDirectorySync(name)
|
||||
|
||||
packagePath = fsUtils.resolve(@packageDirPaths..., name)
|
||||
return packagePath if fsUtils.isDirectorySync(packagePath)
|
||||
|
||||
packagePath = path.join(@resourcePath, 'node_modules', name)
|
||||
return packagePath if @isInternalPackage(packagePath)
|
||||
|
||||
isInternalPackage: (packagePath) ->
|
||||
{engines} = Package.loadMetadata(packagePath, true)
|
||||
engines?.atom?
|
||||
|
||||
getLoadedPackage: (name) ->
|
||||
@loadedPackages[name]
|
||||
|
||||
isPackageLoaded: (name) ->
|
||||
@getLoadedPackage(name)?
|
||||
|
||||
getLoadedPackages: ->
|
||||
_.values(@loadedPackages)
|
||||
|
||||
isPackageDisabled: (name) ->
|
||||
_.include(config.get('core.disabledPackages') ? [], name)
|
||||
|
||||
getAvailablePackagePaths: ->
|
||||
packagePaths = []
|
||||
|
||||
for packageDirPath in @packageDirPaths
|
||||
for packagePath in fsUtils.listSync(packageDirPath)
|
||||
packagePaths.push(packagePath) if fsUtils.isDirectorySync(packagePath)
|
||||
|
||||
for packagePath in fsUtils.listSync(path.join(@resourcePath, 'node_modules'))
|
||||
packagePaths.push(packagePath) if @isInternalPackage(packagePath)
|
||||
|
||||
_.uniq(packagePaths)
|
||||
|
||||
getAvailablePackageNames: ->
|
||||
_.uniq _.map @getAvailablePackagePaths(), (packagePath) -> path.basename(packagePath)
|
||||
|
||||
getAvailablePackageMetadata: ->
|
||||
packages = []
|
||||
for packagePath in @getAvailablePackagePaths()
|
||||
name = path.basename(packagePath)
|
||||
metadata = @getLoadedPackage(name)?.metadata ? Package.loadMetadata(packagePath, true)
|
||||
packages.push(metadata)
|
||||
packages
|
@ -28,7 +28,6 @@ class Pane extends View
|
||||
|
||||
activeItem: null
|
||||
items: null
|
||||
viewsByClassName: null # Views with a setModel() method are stored here
|
||||
viewsByItem: null # Views without a setModel() method are stored here
|
||||
|
||||
# Private:
|
||||
@ -53,7 +52,6 @@ class Pane extends View
|
||||
return if site is @state.site.id
|
||||
@showItemForUri(newValue) if key is 'activeItemUri'
|
||||
|
||||
@viewsByClassName = {}
|
||||
@viewsByItem = new WeakMap()
|
||||
activeItemUri = @state.get('activeItemUri')
|
||||
unless activeItemUri? and @showItemForUri(activeItemUri)
|
||||
@ -224,12 +222,11 @@ class Pane extends View
|
||||
# Public: Prompt the user to save the given item.
|
||||
promptToSaveItem: (item) ->
|
||||
uri = item.getUri()
|
||||
currentWindow = require('remote').getCurrentWindow()
|
||||
chosen = atom.confirmSync(
|
||||
"'#{item.getTitle?() ? item.getUri()}' has changes, do you want to save them?"
|
||||
"Your changes will be lost if you close this item without saving."
|
||||
["Save", "Cancel", "Don't Save"]
|
||||
currentWindow
|
||||
atom.getCurrentWindow()
|
||||
)
|
||||
switch chosen
|
||||
when 0 then @saveItem(item, -> true)
|
||||
@ -323,24 +320,23 @@ class Pane extends View
|
||||
cleanupItemView: (item) ->
|
||||
if item instanceof $
|
||||
viewToRemove = item
|
||||
else
|
||||
if viewToRemove = @viewsByItem.get(item)
|
||||
@viewsByItem.delete(item)
|
||||
else
|
||||
viewClass = item.getViewClass()
|
||||
otherItemsForView = @items.filter (i) -> i.getViewClass?() is viewClass
|
||||
unless otherItemsForView.length
|
||||
viewToRemove = @viewsByClassName[viewClass.name]
|
||||
viewToRemove?.setModel(null)
|
||||
delete @viewsByClassName[viewClass.name]
|
||||
else if viewToRemove = @viewsByItem.get(item)
|
||||
@viewsByItem.delete(item)
|
||||
|
||||
if @items.length > 0
|
||||
if @isMovingItem and item is viewToRemove
|
||||
viewToRemove?.detach()
|
||||
else if @isMovingItem and viewToRemove?.setModel
|
||||
viewToRemove.setModel(null) # dont want to destroy the model, so set to null
|
||||
viewToRemove.remove()
|
||||
else
|
||||
viewToRemove?.remove()
|
||||
else
|
||||
viewToRemove?.detach() if @isMovingItem and item is viewToRemove
|
||||
if @isMovingItem and item is viewToRemove
|
||||
viewToRemove?.detach()
|
||||
else if @isMovingItem and viewToRemove?.setModel
|
||||
viewToRemove.setModel(null) # dont want to destroy the model, so set to null
|
||||
|
||||
@parent().view().removeChild(this, updateState: false)
|
||||
|
||||
# Private:
|
||||
@ -351,14 +347,8 @@ class Pane extends View
|
||||
view
|
||||
else
|
||||
viewClass = item.getViewClass()
|
||||
if view = @viewsByClassName[viewClass.name]
|
||||
view.setModel(item)
|
||||
else
|
||||
view = new viewClass(item)
|
||||
if _.isFunction(view.setModel)
|
||||
@viewsByClassName[viewClass.name] = view
|
||||
else
|
||||
@viewsByItem.set(item, view)
|
||||
view = new viewClass(item)
|
||||
@viewsByItem.set(item, view)
|
||||
view
|
||||
|
||||
# Private:
|
||||
|
@ -1,6 +1,7 @@
|
||||
fsUtils = require './fs-utils'
|
||||
path = require 'path'
|
||||
url = require 'url'
|
||||
Q = require 'q'
|
||||
|
||||
_ = require './underscore-extensions'
|
||||
$ = require './jquery-extensions'
|
||||
@ -165,6 +166,8 @@ class Project
|
||||
#
|
||||
# Returns a String.
|
||||
resolve: (uri) ->
|
||||
return unless uri
|
||||
|
||||
if uri?.match(/[A-Za-z0-9+-.]+:\/\//) # leave path alone if it has a scheme
|
||||
uri
|
||||
else
|
||||
@ -187,9 +190,21 @@ class Project
|
||||
# * editSessionOptions:
|
||||
# Options that you can pass to the {EditSession} constructor
|
||||
#
|
||||
# Returns an {EditSession}.
|
||||
# Returns a promise that resolves to an {EditSession}.
|
||||
openAsync: (filePath, options={}) ->
|
||||
filePath = @resolve(filePath)
|
||||
resource = null
|
||||
_.find @openers, (opener) -> resource = opener(filePath, options)
|
||||
|
||||
if resource
|
||||
Q(resource)
|
||||
else
|
||||
@bufferForPathAsync(filePath).then (buffer) =>
|
||||
editSession = @buildEditSessionForBuffer(buffer, options)
|
||||
|
||||
# Private: Only be used in specs
|
||||
open: (filePath, options={}) ->
|
||||
filePath = @resolve(filePath) if filePath?
|
||||
filePath = @resolve(filePath)
|
||||
for opener in @openers
|
||||
return resource if resource = opener(filePath, options)
|
||||
|
||||
@ -217,6 +232,15 @@ class Project
|
||||
getBuffers: ->
|
||||
new Array(@buffers...)
|
||||
|
||||
# Private: Only to be used in specs
|
||||
bufferForPath: (filePath, text) ->
|
||||
absoluteFilePath = @resolve(filePath)
|
||||
|
||||
if filePath
|
||||
existingBuffer = _.find @buffers, (buffer) -> buffer.getPath() == absoluteFilePath
|
||||
|
||||
existingBuffer ? @buildBuffer(absoluteFilePath, text)
|
||||
|
||||
# Private: Given a file path, this retrieves or creates a new {TextBuffer}.
|
||||
#
|
||||
# If the `filePath` already has a `buffer`, that value is used instead. Otherwise,
|
||||
@ -225,32 +249,37 @@ class Project
|
||||
# filePath - A {String} representing a path. If `null`, an "Untitled" buffer is created.
|
||||
# text - The {String} text to use as a buffer, if the file doesn't have any contents
|
||||
#
|
||||
# Returns the {TextBuffer}.
|
||||
bufferForPath: (filePath, text) ->
|
||||
if filePath?
|
||||
filePath = @resolve(filePath)
|
||||
if filePath
|
||||
buffer = _.find @buffers, (buffer) -> buffer.getPath() == filePath
|
||||
buffer or @buildBuffer(filePath, text)
|
||||
else
|
||||
@buildBuffer(null, text)
|
||||
# Returns a promise that resolves to the {TextBuffer}.
|
||||
bufferForPathAsync: (filePath, text) ->
|
||||
absoluteFilePath = @resolve(filePath)
|
||||
if absoluteFilePath
|
||||
existingBuffer = _.find @buffers, (buffer) -> buffer.getPath() == absoluteFilePath
|
||||
|
||||
Q(existingBuffer ? @buildBufferAsync(absoluteFilePath, text))
|
||||
|
||||
# Private:
|
||||
bufferForId: (id) ->
|
||||
_.find @buffers, (buffer) -> buffer.id is id
|
||||
|
||||
# Private: Given a file path, this sets its {TextBuffer}.
|
||||
#
|
||||
# filePath - A {String} representing a path
|
||||
# text - The {String} text to use as a buffer
|
||||
#
|
||||
# Returns the {TextBuffer}.
|
||||
buildBuffer: (filePath, initialText) ->
|
||||
filePath = @resolve(filePath) if filePath?
|
||||
buffer = new TextBuffer({project: this, filePath, initialText})
|
||||
# Private: DEPRECATED
|
||||
buildBuffer: (absoluteFilePath, initialText) ->
|
||||
buffer = new TextBuffer({project: this, filePath: absoluteFilePath, initialText})
|
||||
buffer.load()
|
||||
@addBuffer(buffer)
|
||||
buffer
|
||||
|
||||
# Private: Given a file path, this sets its {TextBuffer}.
|
||||
#
|
||||
# absoluteFilePath - A {String} representing a path
|
||||
# text - The {String} text to use as a buffer
|
||||
#
|
||||
# Returns a promise that resolves to the {TextBuffer}.
|
||||
buildBufferAsync: (absoluteFilePath, initialText) ->
|
||||
buffer = new TextBuffer({project: this, filePath: absoluteFilePath, initialText})
|
||||
buffer.loadAsync().then (buffer) =>
|
||||
@addBuffer(buffer)
|
||||
buffer
|
||||
|
||||
# Private:
|
||||
addBuffer: (buffer, options={}) ->
|
||||
@addBufferAtIndex(buffer, @buffers.length, options)
|
||||
@ -302,6 +331,10 @@ class Project
|
||||
task.on 'scan:result-found', (result) =>
|
||||
iterator(result)
|
||||
|
||||
if _.isFunction(options.onPathsSearched)
|
||||
task.on 'scan:paths-searched', (numberOfPathsSearched) ->
|
||||
options.onPathsSearched(numberOfPathsSearched)
|
||||
|
||||
deferred
|
||||
|
||||
# Private:
|
||||
|
@ -9,9 +9,11 @@ module.exports = (repoPath) ->
|
||||
for filePath, status of repo.getStatus()
|
||||
statuses[path.join(workingDirectoryPath, filePath)] = status
|
||||
upstream = repo.getAheadBehindCount()
|
||||
branch = repo.getHead()
|
||||
repo.release()
|
||||
else
|
||||
upstream = {}
|
||||
statuses = {}
|
||||
branch = null
|
||||
|
||||
{statuses, upstream}
|
||||
{statuses, upstream, branch}
|
||||
|
@ -1,5 +1,7 @@
|
||||
fs = require 'fs'
|
||||
ipc = require 'ipc'
|
||||
path = require 'path'
|
||||
Q = require 'q'
|
||||
$ = require './jquery-extensions'
|
||||
{$$, View} = require './space-pen-extensions'
|
||||
fsUtils = require './fs-utils'
|
||||
@ -65,6 +67,8 @@ class RootView extends View
|
||||
|
||||
# Private:
|
||||
initialize: (state={}) ->
|
||||
@prepend($$ -> @div class: 'dev-mode') if atom.getLoadSettings().devMode
|
||||
|
||||
if state instanceof telepath.Document
|
||||
@state = state
|
||||
panes = deserialize(state.get('panes'))
|
||||
@ -77,15 +81,14 @@ class RootView extends View
|
||||
|
||||
@panes.replaceWith(panes)
|
||||
@panes = panes
|
||||
@updateTitle()
|
||||
|
||||
@on 'focus', (e) => @handleFocus(e)
|
||||
@subscribe $(window), 'focus', (e) =>
|
||||
@handleFocus(e) if document.activeElement is document.body
|
||||
|
||||
project.on 'path-changed', => @updateTitle()
|
||||
@on 'pane:became-active', => @updateTitle()
|
||||
@on 'pane:active-item-changed', '.active.pane', => @updateTitle()
|
||||
@on 'pane:removed', => @updateTitle() unless @getActivePane()
|
||||
@on 'pane-container:active-pane-item-changed', => @updateTitle()
|
||||
@on 'pane:active-item-title-changed', '.active.pane', => @updateTitle()
|
||||
|
||||
@command 'application:about', -> ipc.sendChannel('command', 'application:about')
|
||||
@ -126,7 +129,8 @@ class RootView extends View
|
||||
@command 'pane:reopen-closed-item', =>
|
||||
@panes.reopenItem()
|
||||
|
||||
_.nextTick => atom.setFullScreen(@state.get('fullScreen'))
|
||||
if @state.get('fullScreen')
|
||||
_.nextTick => atom.setFullScreen(true)
|
||||
|
||||
# Private:
|
||||
serialize: ->
|
||||
@ -160,26 +164,47 @@ class RootView extends View
|
||||
confirmClose: ->
|
||||
@panes.confirmClose()
|
||||
|
||||
# Public: Opens a given a filepath in Atom.
|
||||
# Public: Asynchronously opens a given a filepath in Atom.
|
||||
#
|
||||
# * path: A file path
|
||||
# * filePath: A file path
|
||||
# * options
|
||||
# + initialLine:
|
||||
# The buffer line number to open to.
|
||||
# + initialLine: The buffer line number to open to.
|
||||
#
|
||||
# Returns the {EditSession} for the file URI.
|
||||
open: (path, options = {}) ->
|
||||
# Returns a promise that resolves to the {EditSession} for the file URI.
|
||||
openAsync: (filePath, options={}) ->
|
||||
filePath = project.resolve(filePath)
|
||||
initialLine = options.initialLine
|
||||
activePane = @getActivePane()
|
||||
|
||||
editSession = activePane.itemForUri(project.relativize(filePath)) if activePane and filePath
|
||||
promise = project.openAsync(filePath, {initialLine}) if not editSession
|
||||
|
||||
fileSize = 0
|
||||
fileSize = fs.statSync(filePath).size if fsUtils.exists(filePath)
|
||||
|
||||
Q(editSession ? promise)
|
||||
.then (editSession) =>
|
||||
if not activePane
|
||||
activePane = new Pane(editSession)
|
||||
@panes.setRoot(activePane)
|
||||
|
||||
activePane.showItem(editSession)
|
||||
activePane.focus()
|
||||
editSession
|
||||
|
||||
# Private: DEPRECATED Synchronously Opens a given a filepath in Atom.
|
||||
open: (filePath, options = {}) ->
|
||||
changeFocus = options.changeFocus ? true
|
||||
initialLine = options.initialLine
|
||||
path = project.relativize(path)
|
||||
filePath = project.relativize(filePath)
|
||||
if activePane = @getActivePane()
|
||||
if path
|
||||
editSession = activePane.itemForUri(path) ? project.open(path, {initialLine})
|
||||
if filePath
|
||||
editSession = activePane.itemForUri(filePath) ? project.open(filePath, {initialLine})
|
||||
else
|
||||
editSession = project.open()
|
||||
activePane.showItem(editSession)
|
||||
else
|
||||
editSession = project.open(path, {initialLine})
|
||||
editSession = project.open(filePath, {initialLine})
|
||||
activePane = new Pane(editSession)
|
||||
@panes.setRoot(activePane)
|
||||
|
||||
@ -192,7 +217,7 @@ class RootView extends View
|
||||
if item = @getActivePaneItem()
|
||||
@setTitle("#{item.getTitle?() ? 'untitled'} - #{projectPath}")
|
||||
else
|
||||
@setTitle("atom - #{projectPath}")
|
||||
@setTitle(projectPath)
|
||||
else
|
||||
@setTitle('untitled')
|
||||
|
||||
@ -277,5 +302,5 @@ class RootView extends View
|
||||
# Private: Destroys everything.
|
||||
remove: ->
|
||||
editor.remove() for editor in @getEditors()
|
||||
project.destroy()
|
||||
project?.destroy()
|
||||
super
|
||||
|
@ -3,13 +3,23 @@
|
||||
module.exports = (rootPath, regexSource, options) ->
|
||||
callback = @async()
|
||||
|
||||
PATHS_COUNTER_SEARCHED_CHUNK = 50
|
||||
pathsSearched = 0
|
||||
|
||||
searcher = new PathSearcher()
|
||||
scanner = new PathScanner(rootPath, options)
|
||||
|
||||
searcher.on 'results-found', (result) ->
|
||||
emit('scan:result-found', result)
|
||||
|
||||
scanner.on 'path-found', ->
|
||||
pathsSearched++
|
||||
if pathsSearched % PATHS_COUNTER_SEARCHED_CHUNK == 0
|
||||
emit('scan:paths-searched', pathsSearched)
|
||||
|
||||
flags = "g"
|
||||
flags += "i" if options.ignoreCase
|
||||
regex = new RegExp(regexSource, flags)
|
||||
search regex, scanner, searcher, callback
|
||||
search regex, scanner, searcher, ->
|
||||
emit('scan:paths-searched', pathsSearched)
|
||||
callback()
|
||||
|
@ -115,6 +115,8 @@ class Selection
|
||||
selectWord: ->
|
||||
options = {}
|
||||
options.wordRegex = /[\t ]*/ if @cursor.isSurroundedByWhitespace()
|
||||
if @cursor.isBetweenWordAndNonWord()
|
||||
options.includeNonWordCharacters = false
|
||||
|
||||
@setBufferRange(@cursor.getCurrentWordBufferRange(options))
|
||||
@wordwise = true
|
||||
|
@ -44,15 +44,11 @@ class Task
|
||||
# function to execute.
|
||||
constructor: (taskPath) ->
|
||||
coffeeScriptRequire = "require('#{require.resolve('coffee-script')}');"
|
||||
coffeeCacheRequire = "require('#{require.resolve('./coffee-cache')}');"
|
||||
coffeeCacheRequire = "require('#{require.resolve('./coffee-cache')}').register();"
|
||||
taskBootstrapRequire = "require('#{require.resolve('./task-bootstrap')}');"
|
||||
bootstrap = """
|
||||
#{coffeeScriptRequire}
|
||||
#{coffeeCacheRequire}
|
||||
Object.defineProperty(require.extensions, '.coffee', {
|
||||
writable: false,
|
||||
value: require.extensions['.coffee']
|
||||
});
|
||||
#{taskBootstrapRequire}
|
||||
"""
|
||||
|
||||
|
@ -1,12 +1,15 @@
|
||||
_ = require './underscore-extensions'
|
||||
telepath = require 'telepath'
|
||||
{Point, Range} = telepath
|
||||
fsUtils = require './fs-utils'
|
||||
File = require './file'
|
||||
EventEmitter = require './event-emitter'
|
||||
Subscriber = require './subscriber'
|
||||
guid = require 'guid'
|
||||
Q = require 'q'
|
||||
{P} = require 'scandal'
|
||||
telepath = require 'telepath'
|
||||
|
||||
_ = require './underscore-extensions'
|
||||
fsUtils = require './fs-utils'
|
||||
EventEmitter = require './event-emitter'
|
||||
File = require './file'
|
||||
Subscriber = require './subscriber'
|
||||
|
||||
{Point, Range} = telepath
|
||||
|
||||
# Private: Represents the contents of a file.
|
||||
#
|
||||
@ -22,7 +25,9 @@ class TextBuffer
|
||||
registerDeserializer(this)
|
||||
|
||||
@deserialize: (state, params) ->
|
||||
new this(state, params)
|
||||
buffer = new this(state, params)
|
||||
buffer.load()
|
||||
buffer
|
||||
|
||||
stoppedChangingDelay: 300
|
||||
stoppedChangingTimeout: null
|
||||
@ -43,29 +48,35 @@ class TextBuffer
|
||||
@id = @state.get('id')
|
||||
filePath = @state.get('relativePath')
|
||||
@text = @state.get('text')
|
||||
reloadFromDisk = @state.get('isModified') is false
|
||||
@loadFromDisk = @state.get('isModified') == false
|
||||
else
|
||||
{@project, filePath, initialText} = optionsOrState
|
||||
@text = site.createDocument(initialText ? '', shareStrings: true)
|
||||
reloadFromDisk = true
|
||||
@id = guid.create().toString()
|
||||
@state = site.createDocument
|
||||
id: @id
|
||||
deserializer: @constructor.name
|
||||
version: @constructor.version
|
||||
text: @text
|
||||
@loadFromDisk = not initialText
|
||||
|
||||
@subscribe @text, 'changed', @handleTextChange
|
||||
@subscribe @text, 'marker-created', (marker) => @trigger 'marker-created', marker
|
||||
@subscribe @text, 'markers-updated', => @trigger 'markers-updated'
|
||||
|
||||
if filePath
|
||||
@setPath(@project.resolve(filePath))
|
||||
if fsUtils.exists(@getPath())
|
||||
@updateCachedDiskContents()
|
||||
@reload() if reloadFromDisk and @isModified()
|
||||
@setPath(@project.resolve(filePath)) if @project
|
||||
|
||||
load: ->
|
||||
@updateCachedDiskContents()
|
||||
@reload() if @loadFromDisk and @isModified()
|
||||
@text.clearUndoStack()
|
||||
|
||||
loadAsync: ->
|
||||
@updateCachedDiskContentsAsync().then =>
|
||||
@reload() if @loadFromDisk and @isModified()
|
||||
@text.clearUndoStack()
|
||||
this
|
||||
|
||||
### Internal ###
|
||||
|
||||
handleTextChange: (event) =>
|
||||
@ -81,6 +92,7 @@ class TextBuffer
|
||||
@unsubscribe()
|
||||
@destroyed = true
|
||||
@project?.removeBuffer(this)
|
||||
@trigger 'destroyed'
|
||||
|
||||
isRetained: -> @refcount > 0
|
||||
|
||||
@ -104,16 +116,16 @@ class TextBuffer
|
||||
|
||||
subscribeToFile: ->
|
||||
@file.on "contents-changed", =>
|
||||
if @isModified()
|
||||
@conflict = true
|
||||
@updateCachedDiskContents()
|
||||
@trigger "contents-conflicted"
|
||||
else
|
||||
@reload()
|
||||
@conflict = true if @isModified()
|
||||
@updateCachedDiskContentsAsync().done =>
|
||||
if @conflict
|
||||
@trigger "contents-conflicted"
|
||||
else
|
||||
@reload()
|
||||
|
||||
@file.on "removed", =>
|
||||
@updateCachedDiskContents()
|
||||
@triggerModifiedStatusChanged(@isModified())
|
||||
@updateCachedDiskContentsAsync().done =>
|
||||
@triggerModifiedStatusChanged(@isModified())
|
||||
|
||||
@file.on "moved", =>
|
||||
@state.set('relativePath', @project.relativize(@getPath()))
|
||||
@ -130,19 +142,21 @@ class TextBuffer
|
||||
|
||||
# Reloads a file in the {EditSession}.
|
||||
#
|
||||
# Essentially, this performs a force read of the file.
|
||||
# Sets the buffer's content to the cached disk contents
|
||||
reload: ->
|
||||
@trigger 'will-reload'
|
||||
@updateCachedDiskContents()
|
||||
@setText(@cachedDiskContents)
|
||||
@triggerModifiedStatusChanged(false)
|
||||
@trigger 'reloaded'
|
||||
|
||||
# Rereads the contents of the file, and stores them in the cache.
|
||||
#
|
||||
# Essentially, this performs a force read of the file on disk.
|
||||
# Private: Rereads the contents of the file, and stores them in the cache.
|
||||
updateCachedDiskContents: ->
|
||||
@cachedDiskContents = @file.read()
|
||||
@cachedDiskContents = @file?.read() ? ""
|
||||
|
||||
# Private: Rereads the contents of the file, and stores them in the cache.
|
||||
updateCachedDiskContentsAsync: ->
|
||||
Q(@file?.readAsync() ? "").then (contents) =>
|
||||
@cachedDiskContents = contents
|
||||
|
||||
# Gets the file's basename--that is, the file without any directory information.
|
||||
#
|
||||
@ -162,9 +176,6 @@ class TextBuffer
|
||||
getRelativePath: ->
|
||||
@state.get('relativePath')
|
||||
|
||||
setRelativePath: (relativePath) ->
|
||||
@setPath(@project.resolve(relativePath))
|
||||
|
||||
# Sets the path for the file.
|
||||
#
|
||||
# path - A {String} representing the new file path
|
||||
@ -172,9 +183,13 @@ class TextBuffer
|
||||
return if path == @getPath()
|
||||
|
||||
@file?.off()
|
||||
@file = new File(path)
|
||||
@file.read() if @file.exists()
|
||||
@subscribeToFile()
|
||||
|
||||
if path
|
||||
@file = new File(path)
|
||||
@subscribeToFile()
|
||||
else
|
||||
@file = null
|
||||
|
||||
@state.set('relativePath', @project.relativize(path))
|
||||
@trigger "path-changed", this
|
||||
|
||||
@ -396,7 +411,10 @@ class TextBuffer
|
||||
# Returns a {Boolean}.
|
||||
isModified: ->
|
||||
if @file
|
||||
@getText() != @cachedDiskContents
|
||||
if @file.exists()
|
||||
@getText() != @cachedDiskContents
|
||||
else
|
||||
true
|
||||
else
|
||||
not @isEmpty()
|
||||
|
||||
@ -609,12 +627,6 @@ class TextBuffer
|
||||
return unless path
|
||||
@project.getRepo()?.checkoutHead(path)
|
||||
|
||||
# Checks to see if a file exists.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
fileExists: ->
|
||||
@file? && @file.exists()
|
||||
|
||||
### Internal ###
|
||||
|
||||
transact: (fn) -> @text.transact fn
|
||||
|
@ -4,7 +4,7 @@ Package = require './package'
|
||||
AtomPackage = require './atom-package'
|
||||
|
||||
_ = require './underscore-extensions'
|
||||
|
||||
$ = require './jquery-extensions'
|
||||
fsUtils = require './fs-utils'
|
||||
|
||||
# Private: Handles discovering and loading available themes.
|
||||
@ -15,6 +15,7 @@ class ThemeManager
|
||||
constructor: ->
|
||||
@loadedThemes = []
|
||||
@activeThemes = []
|
||||
@lessCache = null
|
||||
|
||||
# Internal-only:
|
||||
register: (theme) ->
|
||||
@ -33,9 +34,85 @@ class ThemeManager
|
||||
getLoadedThemes: ->
|
||||
_.clone(@loadedThemes)
|
||||
|
||||
# Internal-only:
|
||||
loadBaseStylesheets: ->
|
||||
@requireStylesheet('bootstrap/less/bootstrap')
|
||||
@reloadBaseStylesheets()
|
||||
|
||||
# Internal-only:
|
||||
reloadBaseStylesheets: ->
|
||||
@requireStylesheet('../static/atom')
|
||||
if nativeStylesheetPath = fsUtils.resolveOnLoadPath(process.platform, ['css', 'less'])
|
||||
@requireStylesheet(nativeStylesheetPath)
|
||||
|
||||
# Internal-only:
|
||||
stylesheetElementForId: (id) ->
|
||||
$("""head style[id="#{id}"]""")
|
||||
|
||||
# Internal-only:
|
||||
resolveStylesheet: (stylesheetPath) ->
|
||||
if path.extname(stylesheetPath).length > 0
|
||||
fsUtils.resolveOnLoadPath(stylesheetPath)
|
||||
else
|
||||
fsUtils.resolveOnLoadPath(stylesheetPath, ['css', 'less'])
|
||||
|
||||
# Public: resolves and applies the stylesheet specified by the path.
|
||||
#
|
||||
# * stylesheetPath: String. Can be an absolute path or the name of a CSS or
|
||||
# LESS file in the stylesheets path.
|
||||
#
|
||||
# Returns the absolute path to the stylesheet
|
||||
requireStylesheet: (stylesheetPath) ->
|
||||
if fullPath = @resolveStylesheet(stylesheetPath)
|
||||
content = @loadStylesheet(fullPath)
|
||||
@applyStylesheet(fullPath, content)
|
||||
else
|
||||
throw new Error("Could not find a file at path '#{stylesheetPath}'")
|
||||
|
||||
fullPath
|
||||
|
||||
# Internal-only:
|
||||
loadStylesheet: (stylesheetPath) ->
|
||||
if path.extname(stylesheetPath) is '.less'
|
||||
@loadLessStylesheet(stylesheetPath)
|
||||
else
|
||||
fsUtils.read(stylesheetPath)
|
||||
|
||||
# Internal-only:
|
||||
loadLessStylesheet: (lessStylesheetPath) ->
|
||||
unless lessCache?
|
||||
LessCompileCache = require './less-compile-cache'
|
||||
@lessCache = new LessCompileCache()
|
||||
|
||||
try
|
||||
@lessCache.read(lessStylesheetPath)
|
||||
catch e
|
||||
console.error """
|
||||
Error compiling less stylesheet: #{lessStylesheetPath}
|
||||
Line number: #{e.line}
|
||||
#{e.message}
|
||||
"""
|
||||
|
||||
# Internal-only:
|
||||
removeStylesheet: (stylesheetPath) ->
|
||||
unless fullPath = @resolveStylesheet(stylesheetPath)
|
||||
throw new Error("Could not find a file at path '#{stylesheetPath}'")
|
||||
@stylesheetElementForId(fullPath).remove()
|
||||
|
||||
# Internal-only:
|
||||
applyStylesheet: (id, text, ttype = 'bundled') ->
|
||||
styleElement = @stylesheetElementForId(id)
|
||||
if styleElement.length
|
||||
styleElement.text(text)
|
||||
else
|
||||
if $("head style.#{ttype}").length
|
||||
$("head style.#{ttype}:last").after "<style class='#{ttype}' id='#{id}'>#{text}</style>"
|
||||
else
|
||||
$("head").append "<style class='#{ttype}' id='#{id}'>#{text}</style>"
|
||||
|
||||
# Internal-only:
|
||||
unload: ->
|
||||
removeStylesheet(@userStylesheetPath) if @userStylesheetPath?
|
||||
@removeStylesheet(@userStylesheetPath) if @userStylesheetPath?
|
||||
theme.deactivate() while theme = @activeThemes.pop()
|
||||
|
||||
# Internal-only:
|
||||
@ -50,7 +127,7 @@ class ThemeManager
|
||||
|
||||
@activateTheme(themeName) for themeName in themeNames
|
||||
@loadUserStylesheet()
|
||||
|
||||
@reloadBaseStylesheets()
|
||||
@trigger('reloaded')
|
||||
|
||||
# Private:
|
||||
@ -107,8 +184,10 @@ class ThemeManager
|
||||
if @activeThemes.length > 0
|
||||
themePaths = (theme.getStylesheetsPath() for theme in @activeThemes when theme)
|
||||
else
|
||||
themeNames = config.get('core.themes')
|
||||
themePaths = (path.join(@resolveThemePath(themeName), AtomPackage.stylesheetsDir) for themeName in themeNames)
|
||||
themePaths = []
|
||||
for themeName in config.get('core.themes') ? []
|
||||
if themePath = @resolveThemePath(themeName)
|
||||
themePaths.push(path.join(themePath, AtomPackage.stylesheetsDir))
|
||||
|
||||
themePath for themePath in themePaths when fsUtils.isDirectorySync(themePath)
|
||||
|
||||
@ -116,5 +195,5 @@ class ThemeManager
|
||||
loadUserStylesheet: ->
|
||||
if userStylesheetPath = @getUserStylesheetPath()
|
||||
@userStylesheetPath = userStylesheetPath
|
||||
userStylesheetContents = loadStylesheet(userStylesheetPath)
|
||||
applyStylesheet(userStylesheetPath, userStylesheetContents, 'userTheme')
|
||||
userStylesheetContents = @loadStylesheet(userStylesheetPath)
|
||||
@applyStylesheet(userStylesheetPath, userStylesheetContents, 'userTheme')
|
||||
|
@ -1,7 +1,14 @@
|
||||
_ = require './underscore-extensions'
|
||||
textUtils = require './text-utils'
|
||||
|
||||
whitespaceRegexesByTabLength = {}
|
||||
WhitespaceRegexesByTabLength = {}
|
||||
LeadingWhitespaceRegex = /^[ ]+/
|
||||
TrailingWhitespaceRegex = /[ ]+$/
|
||||
EscapeRegex = /[&"'<>]/g
|
||||
CharacterRegex = /./g
|
||||
StartCharacterRegex = /^./
|
||||
StartDotRegex = /^\.?/
|
||||
WhitespaceRegex = /\S/
|
||||
|
||||
# Private: Represents a single unit of text as selected by a grammar.
|
||||
module.exports =
|
||||
@ -33,7 +40,7 @@ class Token
|
||||
[new Token(value: value1, scopes: @scopes), new Token(value: value2, scopes: @scopes)]
|
||||
|
||||
whitespaceRegexForTabLength: (tabLength) ->
|
||||
whitespaceRegexesByTabLength[tabLength] ?= new RegExp("([ ]{#{tabLength}})|(\t)|([^\t]+)", "g")
|
||||
WhitespaceRegexesByTabLength[tabLength] ?= new RegExp("([ ]{#{tabLength}})|(\t)|([^\t]+)", "g")
|
||||
|
||||
breakOutAtomicTokens: (tabLength, breakOutLeadingWhitespace) ->
|
||||
if @hasSurrogatePair
|
||||
@ -112,10 +119,10 @@ class Token
|
||||
)
|
||||
|
||||
isOnlyWhitespace: ->
|
||||
not /\S/.test(@value)
|
||||
not WhitespaceRegex.test(@value)
|
||||
|
||||
matchesScopeSelector: (selector) ->
|
||||
targetClasses = selector.replace(/^\.?/, '').split('.')
|
||||
targetClasses = selector.replace(StartDotRegex, '').split('.')
|
||||
_.any @scopes, (scope) ->
|
||||
scopeClasses = scope.split('.')
|
||||
_.isSubset(targetClasses, scopeClasses)
|
||||
@ -123,39 +130,59 @@ class Token
|
||||
getValueAsHtml: ({invisibles, hasLeadingWhitespace, hasTrailingWhitespace, hasIndentGuide})->
|
||||
invisibles ?= {}
|
||||
html = @value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
|
||||
if @isHardTab
|
||||
classes = []
|
||||
classes.push('indent-guide') if hasIndentGuide
|
||||
classes.push('invisible-character') if invisibles.tab
|
||||
classes.push('hard-tab')
|
||||
classes = classes.join(' ')
|
||||
html = html.replace /^./, (match) ->
|
||||
classes = 'hard-tab'
|
||||
classes += ' indent-guide' if hasIndentGuide
|
||||
classes += ' invisible-character' if invisibles.tab
|
||||
html = html.replace StartCharacterRegex, (match) =>
|
||||
match = invisibles.tab ? match
|
||||
"<span class='#{classes}'>#{match}</span>"
|
||||
"<span class='#{classes}'>#{@escapeString(match)}</span>"
|
||||
else
|
||||
if hasLeadingWhitespace
|
||||
classes = []
|
||||
classes.push('indent-guide') if hasIndentGuide
|
||||
classes.push('invisible-character') if invisibles.space
|
||||
classes.push('leading-whitespace')
|
||||
classes = classes.join(' ')
|
||||
html = html.replace /^[ ]+/, (match) ->
|
||||
match = match.replace(/./g, invisibles.space) if invisibles.space
|
||||
"<span class='#{classes}'>#{match}</span>"
|
||||
if hasTrailingWhitespace
|
||||
classes = []
|
||||
classes.push('indent-guide') if hasIndentGuide and not hasLeadingWhitespace
|
||||
classes.push('invisible-character') if invisibles.space
|
||||
classes.push('trailing-whitespace')
|
||||
classes = classes.join(' ')
|
||||
html = html.replace /[ ]+$/, (match) ->
|
||||
match = match.replace(/./g, invisibles.space) if invisibles.space
|
||||
"<span class='#{classes}'>#{match}</span>"
|
||||
startIndex = 0
|
||||
endIndex = html.length
|
||||
|
||||
leadingHtml = ''
|
||||
trailingHtml = ''
|
||||
|
||||
if hasLeadingWhitespace and match = LeadingWhitespaceRegex.exec(html)
|
||||
classes = 'leading-whitespace'
|
||||
classes += ' indent-guide' if hasIndentGuide
|
||||
classes += ' invisible-character' if invisibles.space
|
||||
|
||||
match[0] = match[0].replace(CharacterRegex, invisibles.space) if invisibles.space
|
||||
leadingHtml = "<span class='#{classes}'>#{match[0]}</span>"
|
||||
|
||||
startIndex = match[0].length
|
||||
|
||||
if hasTrailingWhitespace and match = TrailingWhitespaceRegex.exec(html)
|
||||
classes = 'trailing-whitespace'
|
||||
classes += ' indent-guide' if hasIndentGuide and not hasLeadingWhitespace
|
||||
classes += ' invisible-character' if invisibles.space
|
||||
|
||||
match[0] = match[0].replace(CharacterRegex, invisibles.space) if invisibles.space
|
||||
trailingHtml = "<span class='#{classes}'>#{match[0]}</span>"
|
||||
|
||||
endIndex = match.index
|
||||
|
||||
html = leadingHtml + @escapeString(html, startIndex, endIndex) + trailingHtml
|
||||
|
||||
html
|
||||
|
||||
escapeString: (str, startIndex, endIndex) ->
|
||||
strLength = str.length
|
||||
|
||||
startIndex ?= 0
|
||||
endIndex ?= strLength
|
||||
|
||||
str = str.slice(startIndex, endIndex) if startIndex > 0 or endIndex < strLength
|
||||
str.replace(EscapeRegex, @escapeStringReplace)
|
||||
|
||||
escapeStringReplace: (match) ->
|
||||
switch match
|
||||
when '&' then '&'
|
||||
when '"' then '"'
|
||||
when "'" then '''
|
||||
when '<' then '<'
|
||||
when '>' then '>'
|
||||
else match
|
||||
|
@ -30,6 +30,8 @@ class TokenizedBuffer
|
||||
constructor: (optionsOrState) ->
|
||||
if optionsOrState instanceof telepath.Document
|
||||
@state = optionsOrState
|
||||
|
||||
# TODO: This needs to be made async, but should wait until the new Telepath changes land
|
||||
@buffer = project.bufferForPath(optionsOrState.get('bufferPath'))
|
||||
else
|
||||
{ @buffer, tabLength } = optionsOrState
|
||||
@ -99,11 +101,11 @@ class TokenizedBuffer
|
||||
@trigger "changed", { start: 0, end: lastRow, delta: 0 }
|
||||
|
||||
tokenizeInBackground: ->
|
||||
return if not @visible or @pendingChunk
|
||||
return if not @visible or @pendingChunk or @destroyed
|
||||
@pendingChunk = true
|
||||
_.defer =>
|
||||
@pendingChunk = false
|
||||
@tokenizeNextChunk()
|
||||
@tokenizeNextChunk() unless @destroyed
|
||||
|
||||
tokenizeNextChunk: ->
|
||||
rowsRemaining = @chunkSize
|
||||
@ -249,6 +251,7 @@ class TokenizedBuffer
|
||||
|
||||
destroy: ->
|
||||
@unsubscribe()
|
||||
@destroyed = true
|
||||
|
||||
iterateTokensInBufferRange: (bufferRange, iterator) ->
|
||||
bufferRange = Range.fromObject(bufferRange)
|
||||
|
@ -1,9 +1,10 @@
|
||||
# Like sands through the hourglass, so are the days of our lives.
|
||||
startTime = new Date().getTime()
|
||||
|
||||
require './atom'
|
||||
require './window'
|
||||
|
||||
Atom = require './atom'
|
||||
window.atom = new Atom()
|
||||
window.setUpEnvironment('editor')
|
||||
window.startEditorWindow()
|
||||
console.log "Window load time: #{new Date().getTime() - startTime}ms"
|
||||
|
@ -1,7 +1,7 @@
|
||||
$ = require './jquery-extensions'
|
||||
_ = require './underscore-extensions'
|
||||
ipc = require 'ipc'
|
||||
remote = require 'remote'
|
||||
shell = require 'shell'
|
||||
Subscriber = require './subscriber'
|
||||
fsUtils = require './fs-utils'
|
||||
|
||||
@ -20,27 +20,44 @@ class WindowEventHandler
|
||||
$(atom.contextMenu.activeElement).trigger(command, args...)
|
||||
|
||||
@subscribe $(window), 'focus', -> $("body").removeClass('is-blurred')
|
||||
|
||||
@subscribe $(window), 'blur', -> $("body").addClass('is-blurred')
|
||||
|
||||
@subscribe $(window), 'window:open-path', (event, {pathToOpen, initialLine}) ->
|
||||
rootView?.open(pathToOpen, {initialLine}) unless fsUtils.isDirectorySync(pathToOpen)
|
||||
unless fsUtils.isDirectorySync(pathToOpen)
|
||||
atom.rootView?.openAsync(pathToOpen, {initialLine})
|
||||
|
||||
@subscribe $(window), 'beforeunload', =>
|
||||
confirmed = rootView?.confirmClose()
|
||||
atom.hide() if confirmed and not @reloadRequested and remote.getCurrentWindow().isWebViewFocused()
|
||||
confirmed = atom.rootView?.confirmClose()
|
||||
atom.hide() if confirmed and not @reloadRequested and atom.getCurrentWindow().isWebViewFocused()
|
||||
@reloadRequested = false
|
||||
confirmed
|
||||
|
||||
@subscribe $(window), 'unload', ->
|
||||
atom.getWindowState().set('dimensions', atom.getDimensions())
|
||||
|
||||
@subscribeToCommand $(window), 'window:toggle-full-screen', => atom.toggleFullScreen()
|
||||
|
||||
@subscribeToCommand $(window), 'window:close', => atom.close()
|
||||
|
||||
@subscribeToCommand $(window), 'window:reload', =>
|
||||
@reloadRequested = true
|
||||
atom.reload()
|
||||
|
||||
@subscribeToCommand $(window), 'window:toggle-dev-tools', => atom.toggleDevTools()
|
||||
|
||||
@subscribeToCommand $(document), 'core:focus-next', @focusNext
|
||||
|
||||
@subscribeToCommand $(document), 'core:focus-previous', @focusPrevious
|
||||
|
||||
@subscribe $(document), 'keydown', keymap.handleKeyEvent
|
||||
@subscribe $(document), 'keydown', atom.keymap.handleKeyEvent
|
||||
|
||||
@subscribe $(document), 'drop', (e) ->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
pathsToOpen = _.pluck(e.originalEvent.dataTransfer.files, 'path')
|
||||
atom.open({pathsToOpen}) if pathsToOpen.length > 0
|
||||
|
||||
@subscribe $(document), 'drop', onDrop
|
||||
@subscribe $(document), 'dragover', (e) ->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
@ -54,7 +71,7 @@ class WindowEventHandler
|
||||
openLink: (event) =>
|
||||
location = $(event.target).attr('href')
|
||||
if location and location[0] isnt '#' and /^https?:\/\//.test(location)
|
||||
require('shell').openExternal(location)
|
||||
shell.openExternal(location)
|
||||
false
|
||||
|
||||
eachTabIndexedElement: (callback) ->
|
||||
|
@ -1,17 +1,9 @@
|
||||
fsUtils = require './fs-utils'
|
||||
path = require 'path'
|
||||
telepath = require 'telepath'
|
||||
$ = require './jquery-extensions'
|
||||
_ = require './underscore-extensions'
|
||||
remote = require 'remote'
|
||||
ipc = require 'ipc'
|
||||
WindowEventHandler = require './window-event-handler'
|
||||
|
||||
deserializers = {}
|
||||
deferredDeserializers = {}
|
||||
defaultWindowDimensions = {width: 800, height: 600}
|
||||
lessCache = null
|
||||
|
||||
### Internal ###
|
||||
|
||||
windowEventHandler = null
|
||||
@ -28,19 +20,14 @@ displayWindow = ->
|
||||
# This method is called in any window needing a general environment, including specs
|
||||
window.setUpEnvironment = (windowMode) ->
|
||||
atom.windowMode = windowMode
|
||||
window.resourcePath = remote.getCurrentWindow().loadSettings.resourcePath
|
||||
|
||||
Config = require './config'
|
||||
Syntax = require './syntax'
|
||||
Pasteboard = require './pasteboard'
|
||||
Keymap = require './keymap'
|
||||
|
||||
window.rootViewParentSelector = 'body'
|
||||
window.config = new Config
|
||||
window.syntax = deserialize(atom.getWindowState('syntax')) ? new Syntax
|
||||
window.pasteboard = new Pasteboard
|
||||
window.keymap = new Keymap()
|
||||
|
||||
window.resourcePath = atom.getLoadSettings().resourcePath
|
||||
atom.initialize()
|
||||
#TODO remove once all packages use the atom global
|
||||
window.config = atom.config
|
||||
window.syntax = atom.syntax
|
||||
window.pasteboard = atom.pasteboard
|
||||
window.keymap = atom.keymap
|
||||
window.site = atom.site
|
||||
|
||||
# Set up the default event handlers and menus for a non-editor windows.
|
||||
#
|
||||
@ -50,8 +37,8 @@ window.setUpEnvironment = (windowMode) ->
|
||||
# This should only be called after setUpEnvironment() has been called.
|
||||
window.setUpDefaultEvents = ->
|
||||
windowEventHandler = new WindowEventHandler
|
||||
keymap.loadBundledKeymaps()
|
||||
ipc.sendChannel 'update-application-menu', keymap.keystrokesByCommandForSelector('body')
|
||||
atom.keymap.loadBundledKeymaps()
|
||||
atom.menu.update()
|
||||
|
||||
# This method is only called when opening a real application window
|
||||
window.startEditorWindow = ->
|
||||
@ -60,16 +47,16 @@ window.startEditorWindow = ->
|
||||
|
||||
windowEventHandler = new WindowEventHandler
|
||||
restoreDimensions()
|
||||
config.load()
|
||||
keymap.loadBundledKeymaps()
|
||||
atom.loadBaseStylesheets()
|
||||
atom.loadPackages()
|
||||
atom.loadThemes()
|
||||
atom.config.load()
|
||||
atom.keymap.loadBundledKeymaps()
|
||||
atom.themes.loadBaseStylesheets()
|
||||
atom.packages.loadPackages()
|
||||
atom.themes.load()
|
||||
deserializeEditorWindow()
|
||||
atom.activatePackages()
|
||||
keymap.loadUserKeymaps()
|
||||
atom.packages.activatePackages()
|
||||
atom.keymap.loadUserKeymaps()
|
||||
atom.requireUserInitScript()
|
||||
ipc.sendChannel 'update-application-menu', keymap.keystrokesByCommandForSelector('body')
|
||||
atom.menu.update()
|
||||
$(window).on 'unload', ->
|
||||
$(document.body).hide()
|
||||
unloadEditorWindow()
|
||||
@ -78,189 +65,67 @@ window.startEditorWindow = ->
|
||||
displayWindow()
|
||||
|
||||
window.unloadEditorWindow = ->
|
||||
return if not project and not rootView
|
||||
return if not atom.project and not atom.rootView
|
||||
windowState = atom.getWindowState()
|
||||
windowState.set('project', project.serialize())
|
||||
windowState.set('syntax', syntax.serialize())
|
||||
windowState.set('rootView', rootView.serialize())
|
||||
atom.deactivatePackages()
|
||||
windowState.set('packageStates', atom.packageStates)
|
||||
windowState.set('project', atom.project.serialize())
|
||||
windowState.set('syntax', atom.syntax.serialize())
|
||||
windowState.set('rootView', atom.rootView.serialize())
|
||||
atom.packages.deactivatePackages()
|
||||
windowState.set('packageStates', atom.packages.packageStates)
|
||||
atom.saveWindowState()
|
||||
rootView.remove()
|
||||
project.destroy()
|
||||
atom.rootView.remove()
|
||||
atom.project.destroy()
|
||||
windowEventHandler?.unsubscribe()
|
||||
lessCache?.destroy()
|
||||
window.rootView = null
|
||||
window.project = null
|
||||
|
||||
window.installAtomCommand = (callback) ->
|
||||
installAtomCommand = (callback) ->
|
||||
commandPath = path.join(window.resourcePath, 'atom.sh')
|
||||
require('./command-installer').install(commandPath, callback)
|
||||
|
||||
window.installApmCommand = (callback) ->
|
||||
installApmCommand = (callback) ->
|
||||
commandPath = path.join(window.resourcePath, 'node_modules', '.bin', 'apm')
|
||||
require('./command-installer').install(commandPath, callback)
|
||||
|
||||
window.onDrop = (e) ->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
pathsToOpen = _.pluck(e.originalEvent.dataTransfer.files, 'path')
|
||||
atom.open({pathsToOpen}) if pathsToOpen.length > 0
|
||||
|
||||
window.deserializeEditorWindow = ->
|
||||
RootView = require './root-view'
|
||||
Project = require './project'
|
||||
atom.deserializePackageStates()
|
||||
atom.deserializeProject()
|
||||
window.project = atom.project
|
||||
atom.deserializeRootView()
|
||||
window.rootView = atom.rootView
|
||||
|
||||
windowState = atom.getWindowState()
|
||||
window.getDimensions = -> atom.getDimensions()
|
||||
|
||||
atom.packageStates = windowState.getObject('packageStates') ? {}
|
||||
windowState.remove('packageStates')
|
||||
window.setDimensions = (args...) -> atom.setDimensions(args...)
|
||||
|
||||
window.project = deserialize(windowState.get('project'))
|
||||
unless window.project?
|
||||
window.project = new Project(atom.getLoadSettings().initialPath)
|
||||
windowState.set('project', window.project.getState())
|
||||
|
||||
window.rootView = deserialize(windowState.get('rootView'))
|
||||
unless window.rootView?
|
||||
window.rootView = new RootView()
|
||||
windowState.set('rootView', window.rootView.getState())
|
||||
|
||||
$(rootViewParentSelector).append(rootView)
|
||||
|
||||
project.on 'path-changed', ->
|
||||
projectPath = project.getPath()
|
||||
atom.getLoadSettings().initialPath = projectPath
|
||||
|
||||
window.stylesheetElementForId = (id) ->
|
||||
$("""head style[id="#{id}"]""")
|
||||
|
||||
window.resolveStylesheet = (stylesheetPath) ->
|
||||
if path.extname(stylesheetPath).length > 0
|
||||
fsUtils.resolveOnLoadPath(stylesheetPath)
|
||||
else
|
||||
fsUtils.resolveOnLoadPath(stylesheetPath, ['css', 'less'])
|
||||
|
||||
# Public: resolves and applies the stylesheet specified by the path.
|
||||
#
|
||||
# * stylesheetPath: String. Can be an absolute path or the name of a CSS or
|
||||
# LESS file in the stylesheets path.
|
||||
#
|
||||
# Returns the absolute path to the stylesheet
|
||||
window.requireStylesheet = (stylesheetPath) ->
|
||||
if fullPath = window.resolveStylesheet(stylesheetPath)
|
||||
content = window.loadStylesheet(fullPath)
|
||||
window.applyStylesheet(fullPath, content)
|
||||
else
|
||||
throw new Error("Could not find a file at path '#{stylesheetPath}'")
|
||||
|
||||
fullPath
|
||||
|
||||
window.loadStylesheet = (stylesheetPath) ->
|
||||
if path.extname(stylesheetPath) is '.less'
|
||||
loadLessStylesheet(stylesheetPath)
|
||||
else
|
||||
fsUtils.read(stylesheetPath)
|
||||
|
||||
window.loadLessStylesheet = (lessStylesheetPath) ->
|
||||
unless lessCache?
|
||||
LessCompileCache = require './less-compile-cache'
|
||||
lessCache = new LessCompileCache()
|
||||
|
||||
try
|
||||
lessCache.read(lessStylesheetPath)
|
||||
catch e
|
||||
console.error """
|
||||
Error compiling less stylesheet: #{lessStylesheetPath}
|
||||
Line number: #{e.line}
|
||||
#{e.message}
|
||||
"""
|
||||
|
||||
window.removeStylesheet = (stylesheetPath) ->
|
||||
unless fullPath = window.resolveStylesheet(stylesheetPath)
|
||||
throw new Error("Could not find a file at path '#{stylesheetPath}'")
|
||||
window.stylesheetElementForId(fullPath).remove()
|
||||
|
||||
window.applyStylesheet = (id, text, ttype = 'bundled') ->
|
||||
styleElement = window.stylesheetElementForId(id)
|
||||
if styleElement.length
|
||||
styleElement.text(text)
|
||||
else
|
||||
if $("head style.#{ttype}").length
|
||||
$("head style.#{ttype}:last").after "<style class='#{ttype}' id='#{id}'>#{text}</style>"
|
||||
else
|
||||
$("head").append "<style class='#{ttype}' id='#{id}'>#{text}</style>"
|
||||
|
||||
window.getDimensions = ->
|
||||
browserWindow = remote.getCurrentWindow()
|
||||
[x, y] = browserWindow.getPosition()
|
||||
[width, height] = browserWindow.getSize()
|
||||
{x, y, width, height}
|
||||
|
||||
window.setDimensions = ({x, y, width, height}) ->
|
||||
browserWindow = remote.getCurrentWindow()
|
||||
browserWindow.setSize(width, height)
|
||||
if x? and y?
|
||||
browserWindow.setPosition(x, y)
|
||||
else
|
||||
browserWindow.center()
|
||||
|
||||
window.restoreDimensions = ->
|
||||
dimensions = atom.getWindowState().getObject('dimensions')
|
||||
dimensions = defaultWindowDimensions unless dimensions?.width and dimensions?.height
|
||||
window.setDimensions(dimensions)
|
||||
$(window).on 'unload', -> atom.getWindowState().set('dimensions', window.getDimensions())
|
||||
window.restoreDimensions = (args...) -> atom.restoreDimensions(args...)
|
||||
|
||||
window.onerror = ->
|
||||
atom.openDevTools()
|
||||
|
||||
window.registerDeserializers = (args...) ->
|
||||
registerDeserializer(arg) for arg in args
|
||||
|
||||
window.registerDeserializer = (klass) ->
|
||||
deserializers[klass.name] = klass
|
||||
|
||||
window.registerDeferredDeserializer = (name, fn) ->
|
||||
deferredDeserializers[name] = fn
|
||||
|
||||
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?
|
||||
|
||||
name = state.get?('deserializer') ? state.deserializer
|
||||
if deferredDeserializers[name]
|
||||
deferredDeserializers[name]()
|
||||
delete deferredDeserializers[name]
|
||||
|
||||
deserializers[name]
|
||||
|
||||
window.requireWithGlobals = (id, globals={}) ->
|
||||
existingGlobals = {}
|
||||
for key, value of globals
|
||||
existingGlobals[key] = window[key]
|
||||
window[key] = value
|
||||
|
||||
require(id)
|
||||
|
||||
for key, value of existingGlobals
|
||||
if value is undefined
|
||||
delete window[key]
|
||||
else
|
||||
window[key] = value
|
||||
atom.deserializers.add(args...)
|
||||
window.registerDeserializer = (args...) ->
|
||||
atom.deserializers.add(args...)
|
||||
window.registerDeferredDeserializer = (args...) ->
|
||||
atom.deserializers.addDeferred(args...)
|
||||
window.unregisterDeserializer = (args...) ->
|
||||
atom.deserializers.remove(args...)
|
||||
window.deserialize = (args...) ->
|
||||
atom.deserializers.deserialize(args...)
|
||||
window.getDeserializer = (args...) ->
|
||||
atom.deserializers.get(args...)
|
||||
window.requireWithGlobals = (args...) ->
|
||||
atom.requireWithGlobals(args...)
|
||||
|
||||
# Public: Measure how long a function takes to run.
|
||||
#
|
||||
# * description:
|
||||
# A String description that will be logged to the console.
|
||||
# * fn:
|
||||
# A Function to measure the duration of.
|
||||
#
|
||||
# Returns the value returned by the given function.
|
||||
window.measure = (description, fn) ->
|
||||
start = new Date().getTime()
|
||||
value = fn()
|
||||
@ -268,6 +133,15 @@ window.measure = (description, fn) ->
|
||||
console.log description, result
|
||||
value
|
||||
|
||||
# Public: Create a dev tools profile for a function.
|
||||
#
|
||||
# * description:
|
||||
# A String descrption that will be available in the Profiles tab of the dev
|
||||
# tools.
|
||||
# * fn:
|
||||
# A Function to profile.
|
||||
#
|
||||
# Return the value returned by the given function.
|
||||
window.profile = (description, fn) ->
|
||||
measure description, ->
|
||||
console.profile(description)
|
||||
|
@ -8,15 +8,13 @@
|
||||
var currentWindow = require('remote').getCurrentWindow();
|
||||
try {
|
||||
require('coffee-script');
|
||||
require('../src/coffee-cache');
|
||||
Object.defineProperty(require.extensions, '.coffee', {
|
||||
writable: false,
|
||||
value: require.extensions['.coffee']
|
||||
});
|
||||
require('../src/coffee-cache').register();
|
||||
require(currentWindow.loadSettings.bootstrapScript);
|
||||
currentWindow.emit('window:loaded');
|
||||
}
|
||||
catch (error) {
|
||||
currentWindow.setSize(800, 600);
|
||||
currentWindow.center();
|
||||
currentWindow.show();
|
||||
currentWindow.openDevTools();
|
||||
console.error(error.stack || error);
|
||||
|
@ -37,6 +37,23 @@ h6 {
|
||||
-webkit-flex: 1;
|
||||
-webkit-flex-flow: column;
|
||||
}
|
||||
|
||||
.dev-mode {
|
||||
|
||||
&:before {
|
||||
content: ""; // This is not a space, it is a skull and crossbones
|
||||
}
|
||||
|
||||
padding: @component-icon-padding;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
font-family: Wingdings;
|
||||
font-size: 25px;
|
||||
z-index: 1000;
|
||||
opacity: 0.75;
|
||||
color: @text-color-highlight;
|
||||
}
|
||||
}
|
||||
|
||||
#panes {
|
||||
|
@ -1,3 +1,4 @@
|
||||
@import "ui-variables";
|
||||
@import "octicon-mixins";
|
||||
|
||||
.select-list {
|
||||
@ -22,7 +23,7 @@
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
max-height: 312px;
|
||||
margin: 0;
|
||||
margin: @component-padding 0 0 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
|
@ -2,10 +2,67 @@ fs = require 'fs'
|
||||
path = require 'path'
|
||||
|
||||
request = require 'request'
|
||||
formidable = require 'formidable'
|
||||
|
||||
module.exports = (grunt) ->
|
||||
{spawn, mkdir, rm, cp} = require('./task-helpers')(grunt)
|
||||
|
||||
accessToken = null
|
||||
getTokenFromKeychain = (callback) ->
|
||||
accessToken ?= process.env['ATOM_ACCESS_TOKEN']
|
||||
if accessToken
|
||||
callback(null, accessToken)
|
||||
return
|
||||
|
||||
spawn {cmd: 'security', args: ['-q', 'find-generic-password', '-ws', 'GitHub API Token']}, (error, result, code) ->
|
||||
accessToken = result unless error?
|
||||
callback(error, accessToken)
|
||||
|
||||
callAtomShellReposApi = (path, callback) ->
|
||||
getTokenFromKeychain (error, accessToken) ->
|
||||
if error
|
||||
callback(error)
|
||||
return
|
||||
|
||||
options =
|
||||
url: "https://api.github.com/repos/atom/atom-shell#{path}"
|
||||
proxy: process.env.http_proxy || process.env.https_proxy
|
||||
headers:
|
||||
authorization: "token #{accessToken}"
|
||||
accept: 'application/vnd.github.manifold-preview'
|
||||
'user-agent': 'Atom'
|
||||
|
||||
request options, (error, response, body) ->
|
||||
if not error?
|
||||
body = JSON.parse(body)
|
||||
error = new Error(body.message) if response.statusCode != 200
|
||||
callback(error, response, body)
|
||||
|
||||
findReleaseIdFromAtomShellVersion = (version, callback) ->
|
||||
callAtomShellReposApi '/releases', (error, response, data) ->
|
||||
if error?
|
||||
grunt.log.error('GitHub API failed to access atom-shell releases')
|
||||
callback(error)
|
||||
else
|
||||
for release in data when release.tag_name is version
|
||||
callback(null, release.id)
|
||||
return
|
||||
grunt.log.error("There is no #{version} release of atom-shell")
|
||||
callback(false)
|
||||
|
||||
getAtomShellDownloadUrl = (version, releaseId, callback) ->
|
||||
callAtomShellReposApi "/releases/#{releaseId}/assets", (error, response, data) ->
|
||||
if error?
|
||||
grunt.log.error("Cannot get assets of atom-shell's #{version} release")
|
||||
callback(error)
|
||||
else
|
||||
filename = "atom-shell-#{version}-#{process.platform}.zip"
|
||||
for asset in data when asset.name is filename and asset.state is 'uploaded'
|
||||
callback(null, asset.url)
|
||||
return
|
||||
grunt.log.error("Cannot get url of atom-shell's release asset")
|
||||
callback(false)
|
||||
|
||||
getAtomShellVersion = ->
|
||||
versionPath = path.join('atom-shell', 'version')
|
||||
if grunt.file.isFile(versionPath)
|
||||
@ -18,28 +75,69 @@ module.exports = (grunt) ->
|
||||
isAtomShellVersionCached = (version) ->
|
||||
grunt.file.isFile(getCachePath(version), 'version')
|
||||
|
||||
downloadAtomShell = (version, callback) ->
|
||||
getDownloadOptions = (version, url, callback) ->
|
||||
options =
|
||||
url: "https://gh-contractor-zcbenz.s3.amazonaws.com/atom-shell/#{version}/atom-shell-#{version}-darwin.zip"
|
||||
url: url
|
||||
followRedirect: false
|
||||
proxy: process.env.http_proxy || process.env.https_proxy
|
||||
|
||||
inputStream = request(options)
|
||||
inputStream.on 'response', (response) ->
|
||||
if response.statusCode is 200
|
||||
grunt.log.writeln("Downloading atom-shell version #{version.cyan}")
|
||||
cacheDirectory = getCachePath(version)
|
||||
rm(cacheDirectory)
|
||||
mkdir(cacheDirectory)
|
||||
cacheFile = path.join(cacheDirectory, 'atom-shell.zip')
|
||||
outputStream = fs.createWriteStream(cacheFile)
|
||||
outputStream.on 'close', -> callback(null, cacheFile)
|
||||
inputStream.pipe(outputStream)
|
||||
else
|
||||
if response.statusCode is 404
|
||||
grunt.log.error("atom-shell #{version.cyan} not found")
|
||||
# Only set headers for GitHub host, the url could also be a S3 link and
|
||||
# setting headers for it would make the request fail.
|
||||
if require('url').parse(url).hostname is 'api.github.com'
|
||||
getTokenFromKeychain (error, accessToken) ->
|
||||
options.headers =
|
||||
authorization: "token #{accessToken}"
|
||||
accept: 'application/octet-stream'
|
||||
'user-agent': 'Atom'
|
||||
|
||||
callback(error, options)
|
||||
else
|
||||
callback(null, options)
|
||||
|
||||
downloadAtomShell = (version, url, callback) ->
|
||||
getDownloadOptions version, url, (error, options) ->
|
||||
if error
|
||||
callback(error)
|
||||
return
|
||||
|
||||
inputStream = request(options)
|
||||
inputStream.on 'response', (response) ->
|
||||
if response.statusCode is 302
|
||||
# Manually handle redirection so headers would not be sent for S3.
|
||||
downloadAtomShell(version, response.headers.location, callback)
|
||||
else if response.statusCode is 200
|
||||
grunt.log.writeln("Downloading atom-shell version #{version.cyan}")
|
||||
cacheDirectory = getCachePath(version)
|
||||
rm(cacheDirectory)
|
||||
mkdir(cacheDirectory)
|
||||
|
||||
form = new formidable.IncomingForm()
|
||||
form.uploadDir = cacheDirectory
|
||||
form.maxFieldsSize = 100 * 1024 * 1024
|
||||
form.on 'file', (name, file) ->
|
||||
cacheFile = path.join(cacheDirectory, 'atom-shell.zip')
|
||||
fs.renameSync(file.path, cacheFile)
|
||||
callback(null, cacheFile)
|
||||
form.parse response, (error) ->
|
||||
if error
|
||||
grunt.log.error("atom-shell #{version.cyan} failed to download")
|
||||
else
|
||||
grunt.log.error("atom-shell #{version.cyan} request failed")
|
||||
callback(false)
|
||||
if response.statusCode is 404
|
||||
grunt.log.error("atom-shell #{version.cyan} not found")
|
||||
else
|
||||
grunt.log.error("atom-shell #{version.cyan} request failed")
|
||||
callback(false)
|
||||
|
||||
downloadAtomShellOfVersion = (version, callback) ->
|
||||
findReleaseIdFromAtomShellVersion version, (error, releaseId) ->
|
||||
if error?
|
||||
callback(error)
|
||||
else
|
||||
getAtomShellDownloadUrl version, releaseId, (error, url) ->
|
||||
if error?
|
||||
callback(error)
|
||||
else
|
||||
downloadAtomShell version, url, callback
|
||||
|
||||
unzipAtomShell = (zipPath, callback) ->
|
||||
grunt.log.writeln('Unzipping atom-shell')
|
||||
@ -48,7 +146,7 @@ module.exports = (grunt) ->
|
||||
spawn {cmd: 'unzip', args: [zipPath, '-d', directoryPath]}, (error) ->
|
||||
rm(zipPath)
|
||||
callback(error)
|
||||
|
||||
|
||||
rebuildNativeModules = (previousVersion, callback) ->
|
||||
newVersion = getAtomShellVersion()
|
||||
if newVersion and newVersion isnt previousVersion
|
||||
@ -74,11 +172,13 @@ module.exports = (grunt) ->
|
||||
installAtomShell(atomShellVersion)
|
||||
rebuildNativeModules(currentAtomShellVersion, done)
|
||||
else
|
||||
downloadAtomShell atomShellVersion, (error, zipPath) ->
|
||||
if zipPath?
|
||||
downloadAtomShellOfVersion atomShellVersion, (error, zipPath) ->
|
||||
if error?
|
||||
done(error)
|
||||
else if zipPath?
|
||||
unzipAtomShell zipPath, (error) ->
|
||||
if error?
|
||||
done(false)
|
||||
done(error)
|
||||
else
|
||||
grunt.log.writeln("Installing atom-shell #{atomShellVersion.cyan}")
|
||||
installAtomShell(atomShellVersion)
|
||||
|
2
vendor/apm
vendored
2
vendor/apm
vendored
@ -1 +1 @@
|
||||
Subproject commit 7c301ec6dcbe12e4d22695e761cf37bc986672dc
|
||||
Subproject commit fcb19e296ca8979a28d2d503c2650f4ef381c8be
|
Loading…
Reference in New Issue
Block a user