Merge remote-tracking branch 'remotes/origin/master' into dh-async-repo

This commit is contained in:
Daniel Hengeveld
2015-10-21 14:52:36 +02:00
130 changed files with 3091 additions and 2253 deletions

View File

@@ -13,6 +13,7 @@ By participating, you are expected to uphold this code. Please report unacceptab
#### Table Of Contents
* [Submitting Issues](#submitting-issues)
* [Your First Contribution](#your-first-contribution)
* [Pull Requests](#pull-requests)
* [Git Commit Messages](#git-commit-messages)
* [CoffeeScript Styleguide](#coffeescript-styleguide)
@@ -60,6 +61,13 @@ you're having an issue with Atom core, open an issue on this repository.
For more information on how to work with Atom's official packages, see
[Contributing to Atom Packages](https://github.com/atom/atom/blob/master/docs/contributing-to-packages.md)
## Your First Contribution
Unsure where to begin contributing to Atom? You can start by looking through these `beginner` and `help-wanted` issues:
* [Beginner issues][beginner]
* [Help wanted issues][help-wanted]
## Pull Requests
* Include screenshots and animated GIFs in your pull request whenever possible.
@@ -352,3 +360,6 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and
[search-atom-org-label-requires-changes]: https://github.com/pulls?q=is%3Aopen+is%3Apr+user%3Aatom+label%3Arequires-changes
[search-atom-repo-label-needs-testing]: https://github.com/pulls?q=is%3Aopen+is%3Apr+repo%3Aatom%2Fatom+label%3Aneeds-testing
[search-atom-org-label-needs-testing]: https://github.com/pulls?q=is%3Aopen+is%3Apr+user%3Aatom+label%3Aneeds-testing
[beginner]:https://github.com/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3Abeginner+label%3Ahelp-wanted+user%3Aatom+sort%3Acomments-desc
[help-wanted]:https://github.com/issues?q=is%3Aopen+is%3Aissue+label%3Ahelp-wanted+user%3Aatom+sort%3Acomments-desc

View File

@@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"atom-package-manager": "1.1.1"
"atom-package-manager": "1.4.0"
}
}

View File

@@ -8,7 +8,7 @@
"dependencies": {
"asar": "^0.8.0",
"async": "~0.2.9",
"donna": "1.0.10",
"donna": "^1.0.13",
"formidable": "~1.0.14",
"fs-plus": "2.x",
"github-releases": "~0.3.0",

View File

@@ -54,14 +54,14 @@ module.exports = (grunt) ->
if process.platform in ['darwin', 'linux']
options =
cmd: appPath
args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{path.join(packagePath, 'spec')}"]
args: ['--test', "--resource-path=#{resourcePath}", path.join(packagePath, 'spec')]
opts:
cwd: packagePath
env: _.extend({}, process.env, ATOM_PATH: rootDir)
else if process.platform is 'win32'
options =
cmd: process.env.comspec
args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", "--spec-directory=#{path.join(packagePath, 'spec')}", "--log-file=ci.log"]
args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", "--log-file=ci.log", path.join(packagePath, 'spec')]
opts:
cwd: packagePath
env: _.extend({}, process.env, ATOM_PATH: rootDir)
@@ -95,7 +95,7 @@ module.exports = (grunt) ->
if process.platform in ['darwin', 'linux']
options =
cmd: appPath
args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}"]
args: ['--test', "--resource-path=#{resourcePath}", coreSpecsPath]
opts:
env: _.extend({}, process.env,
ATOM_INTEGRATION_TESTS_ENABLED: true
@@ -104,7 +104,7 @@ module.exports = (grunt) ->
else if process.platform is 'win32'
options =
cmd: process.env.comspec
args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}", '--log-file=ci.log']
args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", '--log-file=ci.log', coreSpecsPath]
opts:
env: _.extend({}, process.env,
ATOM_INTEGRATION_TESTS_ENABLED: true

View File

@@ -7,8 +7,8 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
* OS with 64-bit or 32-bit architecture
* C++ toolchain
* [Git](http://git-scm.com/)
* [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x or 2.x)
* [npm](https://www.npmjs.com/) v1.4.x (bundled with Node.js)
* [Node.js](http://nodejs.org/download/) (0.10.x or above)
* [npm](https://www.npmjs.com/) v1.4.x or above (automatically bundled with Node.js)
* `npm -v` to check the version.
* `npm config set python /usr/bin/python2 -g` to ensure that gyp uses python2.
* You might need to run this command as `sudo`, depending on how you have set up [npm](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os).
@@ -17,7 +17,7 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
### Ubuntu / Debian
* `sudo apt-get install build-essential git libgnome-keyring-dev fakeroot`
* Instructions for [Node.js](https://github.com/nodejs/node-v0.x-archive/wiki/Installing-Node.js-via-package-manager#debian-and-ubuntu-based-linux-distributions).
* Instructions for [Node.js](https://github.com/nodejs/node-v0.x-archive/wiki/Installing-Node.js-via-package-manager#debian-and-ubuntu-based-linux-distributions).
* Make sure the command `node` is available after Node.js installation (some systems install it as `nodejs`).
* Use `which node` to check if it is available.
* Use `sudo update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10` to update it.

View File

@@ -3,7 +3,7 @@
## Requirements
* OS X 10.8 or later
* [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x or 2.x)
* [Node.js](http://nodejs.org/download/) (0.10.x or above)
* Command Line Tools for [Xcode](https://developer.apple.com/xcode/downloads/) (run `xcode-select --install` to install)
## Instructions

View File

@@ -5,7 +5,7 @@
### On Windows 7
* [Visual C++ 2010 Express](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs#DownloadFamilies_4)
* [Visual Studio 2010 Service Pack 1](http://www.microsoft.com/en-us/download/details.aspx?id=23691)
* [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x or 2.x)
* [Node.js](http://nodejs.org/download/) (0.10.x or above)
* For 64-bit builds of node and native modules you **must** have the
[Windows 7 64-bit SDK](http://www.microsoft.com/en-us/download/details.aspx?id=8279).
You may also need the [compiler update for the Windows SDK 7.1](http://www.microsoft.com/en-us/download/details.aspx?id=4422)
@@ -20,7 +20,7 @@
* [Visual Studio Express 2013 or 2015 for Windows Desktop](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs#DownloadFamilies_2)
* For VS 2015, be sure to customize the installation to include Visual C++. It's not installed by default.
* Some have experienced issues with Node locating C++ on VS 2015. If so, try VS 2013.
* [node.js](http://nodejs.org/download/) (0.10.x, 0.12.x or 4.x) or [io.js](https://iojs.org) (1.x or 2.x)
* [Node.js](http://nodejs.org/download/) (0.10.x or above)
* [Python](https://www.python.org/downloads/) v2.7.x (required by [node-gyp](https://github.com/TooTallNate/node-gyp))
* [GitHub Desktop](http://desktop.github.com/)

View File

@@ -2,6 +2,7 @@ TextBuffer = require 'text-buffer'
{Point, Range} = TextBuffer
{File, Directory} = require 'pathwatcher'
{Emitter, Disposable, CompositeDisposable} = require 'event-kit'
Grim = require 'grim'
module.exports =
BufferedNodeProcess: require '../src/buffered-node-process'
@@ -21,4 +22,20 @@ module.exports =
# only be exported when not running as a child node process
unless process.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE
module.exports.Task = require '../src/task'
module.exports.TextEditor = require '../src/text-editor'
TextEditor = (params) ->
atom.workspace.buildTextEditor(params)
TextEditor.prototype = require('../src/text-editor').prototype
Object.defineProperty module.exports, 'TextEditor',
enumerable: true
get: ->
Grim.deprecate """
The `TextEditor` constructor is no longer public.
To construct a text editor, use `atom.workspace.buildTextEditor()`.
To check if an object is a text editor, look for for the existence of
a public method that you're using (e.g. `::getText`).
"""
TextEditor

View File

@@ -18,7 +18,6 @@
'ctrl-d': 'core:delete'
# Atom Specific
'cmd-alt-ctrl-s': 'application:run-all-specs'
'enter': 'core:confirm'
'escape': 'core:cancel'
'up': 'core:move-up'

View File

@@ -9,7 +9,6 @@
'ctrl-alt-r': 'window:reload'
'ctrl-shift-i': 'window:toggle-dev-tools'
'ctrl-alt-p': 'window:run-package-specs'
'ctrl-alt-s': 'application:run-all-specs'
'ctrl-shift-o': 'application:open-folder'
'ctrl-alt-o': 'application:add-project-folder'
'ctrl-shift-pageup': 'pane:move-item-left'

View File

@@ -15,7 +15,6 @@
'ctrl-alt-r': 'window:reload'
'ctrl-alt-i': 'window:toggle-dev-tools'
'ctrl-alt-p': 'window:run-package-specs'
'ctrl-alt-s': 'application:run-all-specs'
'ctrl-shift-o': 'application:open-folder'
'ctrl-alt-o': 'application:add-project-folder'
'ctrl-shift-left': 'pane:move-item-left'

View File

@@ -164,7 +164,6 @@
label: 'Developer'
submenu: [
{ label: 'Open In Dev Mode…', command: 'application:open-dev' }
{ label: 'Run Atom Specs', command: 'application:run-all-specs' }
{ label: 'Run Package Specs', command: 'window:run-package-specs' }
{ label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' }
]

View File

@@ -121,7 +121,6 @@
label: 'Developer'
submenu: [
{ label: 'Open In &Dev Mode…', command: 'application:open-dev' }
{ label: 'Run &Atom Specs', command: 'application:run-all-specs' }
{ label: 'Run Package &Specs', command: 'window:run-package-specs' }
{ label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' }
]

View File

@@ -120,7 +120,6 @@
label: 'Developer'
submenu: [
{ label: 'Open In &Dev Mode…', command: 'application:open-dev' }
{ label: 'Run &Atom Specs', command: 'application:run-all-specs' }
{ label: 'Run Package &Specs', command: 'window:run-package-specs' }
{ label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' }
]

View File

@@ -12,26 +12,27 @@
"url": "https://github.com/atom/atom/issues"
},
"license": "MIT",
"electronVersion": "0.30.7",
"electronVersion": "0.33.6",
"dependencies": {
"async": "0.2.6",
"atom-keymap": "^6.0.0",
"atom-keymap": "^6.1.0",
"babel-core": "^5.8.21",
"bootstrap": "^3.3.4",
"clear-cut": "^2.0.1",
"coffee-script": "1.8.0",
"color": "^0.7.3",
"event-kit": "^1.3.0",
"first-mate": "^5.0.0",
"find-parent-dir": "^0.3.0",
"first-mate": "^5.1.0",
"fs-plus": "^2.8.0",
"fstream": "0.1.24",
"fuzzaldrin": "^2.1",
"git-utils": "^4",
"grim": "1.4.2",
"grim": "1.5.0",
"jasmine-json": "~0.0",
"jasmine-tagged": "^1.1.4",
"jquery": "^2.1.1",
"key-path-helpers": "^0.3.0",
"key-path-helpers": "^0.4.0",
"less-cache": "0.22",
"marked": "^0.3.4",
"nodegit": "~0.5.0",
@@ -41,13 +42,14 @@
"pathwatcher": "^6.2",
"property-accessors": "^1.1.3",
"random-words": "0.0.1",
"resolve": "^1.1.6",
"runas": "^3.1",
"scandal": "^2.2",
"scoped-property-store": "^0.17.0",
"scrollbar-style": "^3.2",
"season": "^5.3",
"semver": "^4.3.3",
"service-hub": "^0.6.2",
"service-hub": "^0.7.0",
"source-map-support": "^0.3.2",
"stacktrace-parser": "0.1.1",
"temp": "0.8.1",
@@ -67,17 +69,17 @@
"one-dark-syntax": "1.1.1",
"one-light-syntax": "1.1.1",
"one-light-ui": "1.1.5",
"solarized-dark-syntax": "0.38.1",
"solarized-light-syntax": "0.22.1",
"solarized-dark-syntax": "0.39.0",
"solarized-light-syntax": "0.23.0",
"about": "1.1.0",
"archive-view": "0.61.0",
"autocomplete-atom-api": "0.9.2",
"autocomplete-css": "0.11.0",
"autocomplete-html": "0.7.2",
"autocomplete-plus": "2.22.0",
"autocomplete-plus": "2.23.0",
"autocomplete-snippets": "1.7.1",
"autoflow": "0.26.0",
"autosave": "0.22.0",
"autosave": "0.23.0",
"background-tips": "0.26.0",
"bookmarks": "0.38.0",
"bracket-matcher": "0.78.0",
@@ -86,36 +88,36 @@
"dev-live-reload": "0.47.0",
"encoding-selector": "0.21.0",
"exception-reporting": "0.37.0",
"find-and-replace": "0.185.0",
"fuzzy-finder": "0.90.0",
"find-and-replace": "0.187.1",
"fuzzy-finder": "0.91.0",
"git-diff": "0.56.0",
"go-to-line": "0.30.0",
"grammar-selector": "0.47.0",
"image-view": "0.55.0",
"incompatible-packages": "0.25.0",
"keybinding-resolver": "0.33.0",
"line-ending-selector": "0.1.0",
"line-ending-selector": "0.3.0",
"link": "0.31.0",
"markdown-preview": "0.154.0",
"metrics": "0.52.0",
"notifications": "0.59.0",
"markdown-preview": "0.155.0",
"metrics": "0.53.0",
"notifications": "0.60.0",
"open-on-github": "0.38.0",
"package-generator": "0.40.0",
"release-notes": "0.53.0",
"settings-view": "0.229.0",
"snippets": "0.100.0",
"spell-check": "0.61.0",
"status-bar": "0.79.0",
"settings-view": "0.230.1",
"snippets": "0.101.0",
"spell-check": "0.61.1",
"status-bar": "0.80.0",
"styleguide": "0.44.0",
"symbols-view": "0.109.0",
"tabs": "0.84.0",
"tabs": "0.85.0",
"timecop": "0.33.0",
"tree-view": "0.190.0",
"tree-view": "0.192.2",
"update-package-dependencies": "0.10.0",
"welcome": "0.30.0",
"welcome": "0.31.0",
"whitespace": "0.31.0",
"wrap-guide": "0.38.0",
"language-c": "0.48.0",
"wrap-guide": "0.38.1",
"language-c": "0.49.0",
"language-clojure": "0.18.0",
"language-coffee-script": "0.42.0",
"language-csharp": "0.11.0",
@@ -126,26 +128,26 @@
"language-html": "0.42.0",
"language-hyperlink": "0.14.0",
"language-java": "0.16.0",
"language-javascript": "0.96.0",
"language-json": "0.17.0",
"language-less": "0.28.2",
"language-javascript": "0.97.0",
"language-json": "0.17.1",
"language-less": "0.28.3",
"language-make": "0.19.0",
"language-mustache": "0.13.0",
"language-objective-c": "0.15.0",
"language-perl": "0.30.0",
"language-php": "0.30.0",
"language-php": "0.32.0",
"language-property-list": "0.8.0",
"language-python": "0.40.0",
"language-python": "0.41.0",
"language-ruby": "0.60.0",
"language-ruby-on-rails": "0.23.0",
"language-sass": "0.42.0",
"language-shellscript": "0.19.0",
"language-source": "0.9.0",
"language-sql": "0.17.0",
"language-sql": "0.18.0",
"language-text": "0.7.0",
"language-todo": "0.27.0",
"language-toml": "0.16.0",
"language-xml": "0.33.0",
"language-xml": "0.33.1",
"language-yaml": "0.24.0"
},
"private": true,

View File

@@ -1,13 +1,20 @@
Exec = require('child_process').exec
_ = require "underscore-plus"
path = require 'path'
temp = require 'temp'
Package = require '../src/package'
ThemeManager = require '../src/theme-manager'
_ = require "underscore-plus"
temp = require "temp"
AtomEnvironment = require '../src/atom-environment'
describe "the `atom` global", ->
describe "AtomEnvironment", ->
describe 'window sizing methods', ->
describe '::getPosition and ::setPosition', ->
originalPosition = null
beforeEach ->
originalPosition = atom.getPosition()
afterEach ->
atom.setPosition(originalPosition.x, originalPosition.y)
it 'sets the position of the window, and can retrieve the position just set', ->
atom.setPosition(22, 45)
expect(atom.getPosition()).toEqual x: 22, y: 45
@@ -31,28 +38,8 @@ describe "the `atom` global", ->
version = '36b5518'
expect(atom.isReleasedVersion()).toBe false
describe "when an update becomes available", ->
subscription = null
afterEach ->
subscription?.dispose()
it "invokes onUpdateAvailable listeners", ->
updateAvailableHandler = jasmine.createSpy("update-available-handler")
subscription = atom.onUpdateAvailable updateAvailableHandler
autoUpdater = require('remote').require('auto-updater')
autoUpdater.emit 'update-downloaded', null, "notes", "version"
waitsFor ->
updateAvailableHandler.callCount > 0
runs ->
{releaseVersion} = updateAvailableHandler.mostRecentCall.args[0]
expect(releaseVersion).toBe 'version'
describe "loading default config", ->
it 'loads the default core config', ->
it 'loads the default core config schema', ->
expect(atom.config.get('core.excludeVcsIgnoredPaths')).toBe true
expect(atom.config.get('core.followSymlinks')).toBe true
expect(atom.config.get('editor.showInvisibles')).toBe false
@@ -139,7 +126,7 @@ describe "the `atom` global", ->
expect(result).toBe false
expect(errors.length).toBe 1
expect(errors[0].message).toBe "Assertion failed: a == b"
expect(errors[0].stack).toContain('atom-spec')
expect(errors[0].stack).toContain('atom-environment-spec')
describe "if passed a callback function", ->
it "calls the callback with the assertion failure's error object", ->
@@ -154,30 +141,34 @@ describe "the `atom` global", ->
expect(errors).toEqual []
describe "saving and loading", ->
afterEach -> atom.mode = "spec"
beforeEach ->
atom.enablePersistence = true
afterEach ->
atom.enablePersistence = false
it "selects the state based on the current project paths", ->
Atom = atom.constructor
[dir1, dir2] = [temp.mkdirSync("dir1-"), temp.mkdirSync("dir2-")]
loadSettings = _.extend Atom.getLoadSettings(),
loadSettings = _.extend atom.getLoadSettings(),
initialPaths: [dir1]
windowState: null
spyOn(Atom, 'getLoadSettings').andCallFake -> loadSettings
spyOn(Atom.getStorageFolder(), 'getPath').andReturn(temp.mkdirSync("storage-dir-"))
spyOn(atom, 'getLoadSettings').andCallFake -> loadSettings
spyOn(atom.getStorageFolder(), 'getPath').andReturn(temp.mkdirSync("storage-dir-"))
atom.mode = "editor"
atom.state.stuff = "cool"
atom.project.setPaths([dir1, dir2])
atom.saveSync.originalValue.call(atom)
atom.saveStateSync()
atom1 = Atom.loadOrCreate("editor")
expect(atom1.state.stuff).toBeUndefined()
atom.state = {}
atom.loadStateSync()
expect(atom.state.stuff).toBeUndefined()
loadSettings.initialPaths = [dir2, dir1]
atom2 = Atom.loadOrCreate("editor")
expect(atom2.state.stuff).toBe("cool")
atom.state = {}
atom.loadStateSync()
expect(atom.state.stuff).toBe("cool")
describe "openInitialEmptyEditorIfNecessary", ->
describe "when there are no paths set", ->
@@ -225,28 +216,92 @@ describe "the `atom` global", ->
describe "::unloadEditorWindow()", ->
it "saves the serialized state of the window so it can be deserialized after reload", ->
workspaceState = atom.workspace.serialize()
syntaxState = atom.grammars.serialize()
projectState = atom.project.serialize()
atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, window, document})
spyOn(atomEnvironment, 'saveStateSync')
atom.unloadEditorWindow()
workspaceState = atomEnvironment.workspace.serialize()
grammarsState = {grammarOverridesByPath: atomEnvironment.grammars.grammarOverridesByPath}
projectState = atomEnvironment.project.serialize()
expect(atom.state.workspace).toEqual workspaceState
expect(atom.state.grammars).toEqual syntaxState
expect(atom.state.project).toEqual projectState
expect(atom.saveSync).toHaveBeenCalled()
atomEnvironment.unloadEditorWindow()
describe "::removeEditorWindow()", ->
expect(atomEnvironment.state.workspace).toEqual workspaceState
expect(atomEnvironment.state.grammars).toEqual grammarsState
expect(atomEnvironment.state.project).toEqual projectState
expect(atomEnvironment.saveStateSync).toHaveBeenCalled()
atomEnvironment.destroy()
describe "::destroy()", ->
it "unsubscribes from all buffers", ->
atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, window, document})
waitsForPromise ->
atom.workspace.open("sample.js")
atomEnvironment.workspace.open("sample.js")
runs ->
buffer = atom.workspace.getActivePaneItem().buffer
pane = atom.workspace.getActivePane()
buffer = atomEnvironment.workspace.getActivePaneItem().buffer
pane = atomEnvironment.workspace.getActivePane()
pane.splitRight(copyActiveItem: true)
expect(atom.workspace.getTextEditors().length).toBe 2
expect(atomEnvironment.workspace.getTextEditors().length).toBe 2
atom.removeEditorWindow()
atomEnvironment.destroy()
expect(buffer.getSubscriptionCount()).toBe 0
describe "::openLocations(locations) (called via IPC from browser process)", ->
beforeEach ->
spyOn(atom.workspace, 'open')
atom.project.setPaths([])
describe "when the opened path exists", ->
it "adds it to the project's paths", ->
pathToOpen = __filename
atom.openLocations([{pathToOpen}])
expect(atom.project.getPaths()[0]).toBe __dirname
describe "when the opened path does not exist but its parent directory does", ->
it "adds the parent directory to the project paths", ->
pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt')
atom.openLocations([{pathToOpen}])
expect(atom.project.getPaths()[0]).toBe __dirname
describe "when the opened path is a file", ->
it "opens it in the workspace", ->
pathToOpen = __filename
atom.openLocations([{pathToOpen}])
expect(atom.workspace.open.mostRecentCall.args[0]).toBe __filename
describe "when the opened path is a directory", ->
it "does not open it in the workspace", ->
pathToOpen = __dirname
atom.openLocations([{pathToOpen}])
expect(atom.workspace.open.callCount).toBe 0
describe "when the opened path is a uri", ->
it "adds it to the project's paths as is", ->
pathToOpen = 'remote://server:7644/some/dir/path'
atom.openLocations([{pathToOpen}])
expect(atom.project.getPaths()[0]).toBe pathToOpen
describe "::updateAvailable(info) (called via IPC from browser process)", ->
subscription = null
afterEach ->
subscription?.dispose()
it "invokes onUpdateAvailable listeners", ->
atom.listenForUpdates()
updateAvailableHandler = jasmine.createSpy("update-available-handler")
subscription = atom.onUpdateAvailable updateAvailableHandler
autoUpdater = require('remote').require('auto-updater')
autoUpdater.emit 'update-downloaded', null, "notes", "version"
waitsFor ->
updateAvailableHandler.callCount > 0
runs ->
{releaseVersion} = updateAvailableHandler.mostRecentCall.args[0]
expect(releaseVersion).toBe 'version'

View File

@@ -22,7 +22,8 @@ describe "CommandInstaller on #darwin", ->
describe "when using a stable version of atom", ->
beforeEach ->
installer = new CommandInstaller("2.0.2")
confirm = ->
installer = new CommandInstaller("2.0.2", confirm)
it "symlinks the atom command as 'atom'", ->
installedAtomPath = path.join(installationPath, 'atom')

View File

@@ -16,6 +16,7 @@ describe "CommandRegistry", ->
document.querySelector('#jasmine-content').appendChild(parent)
registry = new CommandRegistry
registry.attach(parent)
afterEach ->
registry.destroy()
@@ -277,3 +278,18 @@ describe "CommandRegistry", ->
{name: 'namespace:command-2', displayName: 'Namespace: Command 2'}
{name: 'namespace:command-1', displayName: 'Namespace: Command 1'}
]
describe "::attach(rootNode)", ->
it "adds event listeners for any previously-added commands", ->
registry2 = new CommandRegistry
commandSpy = jasmine.createSpy('command-callback')
registry2.add '.grandchild', 'command-1', commandSpy
grandchild.dispatchEvent(new CustomEvent('command-1', bubbles: true))
expect(commandSpy).not.toHaveBeenCalled()
registry2.attach(parent)
grandchild.dispatchEvent(new CustomEvent('command-1', bubbles: true))
expect(commandSpy).toHaveBeenCalled()

View File

@@ -18,7 +18,7 @@ describe 'CompileCache', ->
CompileCache.resetCacheStats()
spyOn(Babel, 'transform').andReturn {code: 'the-babel-code'}
spyOn(CoffeeScript, 'compile').andReturn {js: 'the-coffee-code', v3SourceMap: {}}
spyOn(CoffeeScript, 'compile').andReturn {js: 'the-coffee-code', v3SourceMap: "{}"}
spyOn(TypeScriptSimple::, 'compile').andReturn 'the-typescript-code'
spyOn(CSONParser, 'parse').andReturn {the: 'cson-data'}

View File

@@ -7,10 +7,16 @@ describe "Config", ->
dotAtomPath = null
beforeEach ->
spyOn(atom.config, "load")
spyOn(atom.config, "save")
dotAtomPath = temp.path('dot-atom-dir')
atom.config.configDirPath = dotAtomPath
atom.config.enablePersistence = true
atom.config.configFilePath = path.join(atom.config.configDirPath, "atom.config.cson")
afterEach ->
atom.config.enablePersistence = false
describe ".get(keyPath, {scope, sources, excludeSources})", ->
it "allows a key path's value to be read", ->
expect(atom.config.set("foo.bar.baz", 42)).toBe true
@@ -153,13 +159,27 @@ describe "Config", ->
describe "when the value equals the default value", ->
it "does not store the value in the user's config", ->
atom.config.setDefaults "foo",
same: 1
changes: 1
sameArray: [1, 2, 3]
sameObject: {a: 1, b: 2}
null: null
undefined: undefined
atom.config.setSchema "foo",
type: 'object'
properties:
same:
type: 'number'
default: 1
changes:
type: 'number'
default: 1
sameArray:
type: 'array'
default: [1, 2, 3]
sameObject:
type: 'object'
default: {a: 1, b: 2}
null:
type: '*'
default: null
undefined:
type: '*'
default: undefined
expect(atom.config.settings.foo).toBeUndefined()
atom.config.set('foo.same', 1)
@@ -169,11 +189,15 @@ describe "Config", ->
atom.config.set('foo.undefined', null)
atom.config.set('foo.sameObject', {b: 2, a: 1})
expect(atom.config.get("foo.same", sources: [atom.config.getUserConfigPath()])).toBeUndefined()
userConfigPath = atom.config.getUserConfigPath()
expect(atom.config.get("foo.same", sources: [userConfigPath])).toBeUndefined()
expect(atom.config.get("foo.changes")).toBe 2
expect(atom.config.get("foo.changes", sources: [userConfigPath])).toBe 2
expect(atom.config.get("foo.changes", sources: [atom.config.getUserConfigPath()])).toBe 2
atom.config.set('foo.changes', 1)
expect(atom.config.get("foo.changes", sources: [atom.config.getUserConfigPath()])).toBeUndefined()
expect(atom.config.get("foo.changes", sources: [userConfigPath])).toBeUndefined()
describe "when a 'scopeSelector' is given", ->
it "sets the value and overrides the others", ->

View File

@@ -5,7 +5,7 @@ describe "ContextMenuManager", ->
beforeEach ->
{resourcePath} = atom.getLoadSettings()
contextMenu = new ContextMenuManager({resourcePath})
contextMenu = new ContextMenuManager({resourcePath, keymapManager: atom.keymaps})
parent = document.createElement("div")
child = document.createElement("div")

View File

@@ -7,7 +7,7 @@ describe "CustomGutterComponent", ->
beforeEach ->
mockGutterContainer = {}
gutter = new Gutter(mockGutterContainer, {name: 'test-gutter'})
gutterComponent = new CustomGutterComponent({gutter})
gutterComponent = new CustomGutterComponent({gutter, views: atom.views})
it "creates a gutter DOM node with only an empty 'custom-decorations' child node when it is initialized", ->
expect(gutterComponent.getDomNode().classList.contains('gutter')).toBe true

View File

@@ -7,7 +7,10 @@ describe "DisplayBuffer", ->
tabLength = 2
buffer = atom.project.bufferForPathSync('sample.js')
displayBuffer = new DisplayBuffer({buffer, tabLength})
displayBuffer = new DisplayBuffer({
buffer, tabLength, config: atom.config, grammarRegistry: atom.grammars,
packageManager: atom.packages, assert: ->
})
changeHandler = jasmine.createSpy 'changeHandler'
displayBuffer.onDidChange changeHandler
@@ -49,16 +52,50 @@ describe "DisplayBuffer", ->
it "updates the display buffer prior to invoking change handlers registered on the buffer", ->
buffer.onDidChange -> expect(displayBuffer2.tokenizedLineForScreenRow(0).text).toBe "testing"
displayBuffer2 = new DisplayBuffer({buffer, tabLength})
displayBuffer2 = new DisplayBuffer({
buffer, tabLength, config: atom.config, grammarRegistry: atom.grammars,
packageManager: atom.packages, assert: ->
})
buffer.setText("testing")
describe "soft wrapping", ->
beforeEach ->
displayBuffer.setSoftWrapped(true)
displayBuffer.setEditorWidthInChars(50)
displayBuffer.setSoftWrapped(true)
displayBuffer.setDefaultCharWidth(1)
changeHandler.reset()
describe "rendering of soft-wrapped lines", ->
describe "when there are double width characters", ->
it "takes them into account when finding the soft wrap column", ->
buffer.setText("私たちのフ是一个地方数千名学生12345业余爱们的板作为hello world this is a pretty long latin line")
displayBuffer.setDefaultCharWidth(1, 5, 0, 0)
expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe("私たちのフ是一个地方")
expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe("数千名学生12345业余爱")
expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe("们的板作为hello world this is a ")
expect(displayBuffer.tokenizedLineForScreenRow(3).text).toBe("pretty long latin line")
describe "when there are half width characters", ->
it "takes them into account when finding the soft wrap column", ->
displayBuffer.setDefaultCharWidth(1, 0, 5, 0)
buffer.setText("abcᆰᆱᆲネヌネノハヒフヒフヌᄡ○○○hello world this is a pretty long line")
expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe("abcᆰᆱᆲネヌネノハヒ")
expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe("フヒフヌᄡ○○○hello ")
expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe("world this is a pretty long line")
describe "when there are korean characters", ->
it "takes them into account when finding the soft wrap column", ->
displayBuffer.setDefaultCharWidth(1, 0, 0, 10)
buffer.setText("1234세계를 향한 대화, 유니코 제10회유니코드국제")
expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe("1234세계를 ")
expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe("향한 대화, ")
expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe("유니코 ")
expect(displayBuffer.tokenizedLineForScreenRow(3).text).toBe("제10회유니")
expect(displayBuffer.tokenizedLineForScreenRow(4).text).toBe("코드국제")
describe "when editor.softWrapAtPreferredLineLength is set", ->
it "uses the preferred line length as the soft wrap column when it is less than the configured soft wrap column", ->
atom.config.set('editor.preferredLineLength', 100)
@@ -232,7 +269,11 @@ describe "DisplayBuffer", ->
describe "when a newline is inserted, deleted, and re-inserted at the end of a wrapped line (regression)", ->
it "correctly renders the original wrapped line", ->
buffer = atom.project.buildBufferSync(null, '')
displayBuffer = new DisplayBuffer({buffer, tabLength, editorWidthInChars: 30})
displayBuffer = new DisplayBuffer({
buffer, tabLength, editorWidthInChars: 30, config: atom.config,
grammarRegistry: atom.grammars, packageManager: atom.packages, assert: ->
})
displayBuffer.setDefaultCharWidth(1)
displayBuffer.setSoftWrapped(true)
buffer.insert([0, 0], "the quick brown fox jumps over the lazy dog.")
@@ -294,7 +335,10 @@ describe "DisplayBuffer", ->
displayBuffer.destroy()
buffer.release()
buffer = atom.project.bufferForPathSync('two-hundred.txt')
displayBuffer = new DisplayBuffer({buffer, tabLength})
displayBuffer = new DisplayBuffer({
buffer, tabLength, config: atom.config, grammarRegistry: atom.grammars,
packageManager: atom.packages, assert: ->
})
displayBuffer.onDidChange changeHandler
describe "when folds are created and destroyed", ->
@@ -408,7 +452,10 @@ describe "DisplayBuffer", ->
describe "when there is another display buffer pointing to the same buffer", ->
it "does not consider folds to be nested inside of folds from the other display buffer", ->
otherDisplayBuffer = new DisplayBuffer({buffer, tabLength})
otherDisplayBuffer = new DisplayBuffer({
buffer, tabLength, config: atom.config, grammarRegistry: atom.grammars,
packageManager: atom.packages, assert: ->
})
otherDisplayBuffer.createFold(1, 5)
displayBuffer.createFold(2, 4)
@@ -636,6 +683,7 @@ describe "DisplayBuffer", ->
beforeEach ->
tabLength = 4
displayBuffer.setDefaultCharWidth(1)
displayBuffer.setTabLength(tabLength)
displayBuffer.setSoftWrapped(true)
displayBuffer.setEditorWidthInChars(50)
@@ -753,6 +801,7 @@ describe "DisplayBuffer", ->
it "correctly translates positions on soft wrapped lines containing tabs", ->
buffer.setText('\t\taa bb cc dd ee ff gg')
displayBuffer.setSoftWrapped(true)
displayBuffer.setDefaultCharWidth(1)
displayBuffer.setEditorWidthInChars(10)
expect(displayBuffer.screenPositionForBufferPosition([0, 10], wrapAtSoftNewlines: true)).toEqual [1, 4]
expect(displayBuffer.bufferPositionForScreenPosition([1, 0])).toEqual [0, 9]
@@ -1154,7 +1203,10 @@ describe "DisplayBuffer", ->
describe 'when there are multiple DisplayBuffers for a buffer', ->
describe 'when a marker is created', ->
it 'the second display buffer will not emit a marker-created event when the marker has been deleted in the first marker-created event', ->
displayBuffer2 = new DisplayBuffer({buffer, tabLength})
displayBuffer2 = new DisplayBuffer({
buffer, tabLength, config: atom.config, grammarRegistry: atom.grammars,
packageManager: atom.packages, assert: ->
})
displayBuffer.onDidCreateMarker markerCreated1 = jasmine.createSpy().andCallFake (marker) -> marker.destroy()
displayBuffer2.onDidCreateMarker markerCreated2 = jasmine.createSpy()

View File

@@ -1,3 +1,3 @@
'use 6to6';
module.exports = v => v + 1
module.exports = async function hello() {}

View File

@@ -6,11 +6,15 @@ GitRepository = require '../src/git-repository'
GitRepositoryProvider = require '../src/git-repository-provider'
describe "GitRepositoryProvider", ->
provider = null
beforeEach ->
provider = new GitRepositoryProvider(atom.project, atom.config, atom.confirm)
describe ".repositoryForDirectory(directory)", ->
describe "when specified a Directory with a Git repository", ->
it "returns a Promise that resolves to a GitRepository", ->
waitsForPromise ->
provider = new GitRepositoryProvider atom.project
directory = new Directory path.join(__dirname, 'fixtures', 'git', 'master.git')
provider.repositoryForDirectory(directory).then (result) ->
expect(result).toBeInstanceOf GitRepository
@@ -19,7 +23,6 @@ describe "GitRepositoryProvider", ->
expect(result.getType()).toBe 'git'
it "returns the same GitRepository for different Directory objects in the same repo", ->
provider = new GitRepositoryProvider atom.project
firstRepo = null
secondRepo = null
@@ -38,7 +41,6 @@ describe "GitRepositoryProvider", ->
describe "when specified a Directory without a Git repository", ->
it "returns a Promise that resolves to null", ->
waitsForPromise ->
provider = new GitRepositoryProvider atom.project
directory = new Directory temp.mkdirSync('dir')
provider.repositoryForDirectory(directory).then (result) ->
expect(result).toBe null
@@ -46,7 +48,6 @@ describe "GitRepositoryProvider", ->
describe "when specified a Directory with an invalid Git repository", ->
it "returns a Promise that resolves to null", ->
waitsForPromise ->
provider = new GitRepositoryProvider atom.project
dirPath = temp.mkdirSync('dir')
fs.writeFileSync(path.join(dirPath, '.git', 'objects'), '')
fs.writeFileSync(path.join(dirPath, '.git', 'HEAD'), '')
@@ -59,7 +60,6 @@ describe "GitRepositoryProvider", ->
describe "when specified a Directory with a valid gitfile-linked repository", ->
it "returns a Promise that resolves to a GitRepository", ->
waitsForPromise ->
provider = new GitRepositoryProvider atom.project
gitDirPath = path.join(__dirname, 'fixtures', 'git', 'master.git')
workDirPath = temp.mkdirSync('git-workdir')
fs.writeFileSync(path.join(workDirPath, '.git'), 'gitdir: ' + gitDirPath+'\n')
@@ -75,8 +75,6 @@ describe "GitRepositoryProvider", ->
directory = null
provider = null
beforeEach ->
provider = new GitRepositoryProvider atom.project
# An implementation of Directory that does not implement existsSync().
subdirectory = {}
directory =

View File

@@ -134,8 +134,10 @@ describe "GitRepository", ->
[filePath, editor] = []
beforeEach ->
spyOn(atom, "confirm")
workingDirPath = copyRepository()
repo = new GitRepository(workingDirPath)
repo = new GitRepository(workingDirPath, {project: atom.project, config: atom.config, confirm: atom.confirm})
filePath = path.join(workingDirPath, 'a.txt')
fs.writeFileSync(filePath, 'ch ch changes')
@@ -146,7 +148,7 @@ describe "GitRepository", ->
editor = atom.workspace.getActiveTextEditor()
it "displays a confirmation dialog by default", ->
spyOn(atom, 'confirm').andCallFake ({buttons}) -> buttons.OK()
atom.confirm.andCallFake ({buttons}) -> buttons.OK()
atom.config.set('editor.confirmCheckoutHeadRevision', true)
repo.checkoutHeadForEditor(editor)
@@ -154,7 +156,6 @@ describe "GitRepository", ->
expect(fs.readFileSync(filePath, 'utf8')).toBe ''
it "does not display a dialog when confirmation is disabled", ->
spyOn(atom, 'confirm')
atom.config.set('editor.confirmCheckoutHeadRevision', false)
repo.checkoutHeadForEditor(editor)
@@ -287,7 +288,8 @@ describe "GitRepository", ->
atom.workspace.open('file.txt')
runs ->
project2 = Project.deserialize(atom.project.serialize())
project2 = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
project2.deserialize(atom.project.serialize(), atom.deserializers)
buffer = project2.getBuffers()[0]
waitsFor ->

View File

@@ -24,18 +24,9 @@ describe "the `grammars` global", ->
atom.packages.deactivatePackages()
atom.packages.unloadPackages()
describe "serialization", ->
it "remembers grammar overrides by path", ->
filePath = '/foo/bar/file.js'
expect(atom.grammars.selectGrammar(filePath).name).not.toBe 'Ruby'
atom.grammars.setGrammarOverrideForPath(filePath, 'source.ruby')
grammars2 = atom.deserializers.deserialize(atom.grammars.serialize())
grammars2.addGrammar(grammar) for grammar in atom.grammars.grammars when grammar isnt atom.grammars.nullGrammar
expect(grammars2.selectGrammar(filePath).name).toBe 'Ruby'
describe ".selectGrammar(filePath)", ->
it "always returns a grammar", ->
registry = new GrammarRegistry()
registry = new GrammarRegistry(config: atom.config)
expect(registry.selectGrammar().scopeName).toBe 'text.plain.null-grammar'
it "selects the text.plain grammar over the null grammar", ->

View File

@@ -26,7 +26,7 @@ describe "GutterContainerComponent", ->
domElementPool = new DOMElementPool
mockEditor = {}
mockMouseDown = ->
gutterContainerComponent = new GutterContainerComponent({editor: mockEditor, onMouseDown: mockMouseDown, domElementPool})
gutterContainerComponent = new GutterContainerComponent({editor: mockEditor, onMouseDown: mockMouseDown, domElementPool, views: atom.views})
it "creates a DOM node with no child gutter nodes when it is initialized", ->
expect(gutterContainerComponent.getDomNode() instanceof HTMLElement).toBe true

View File

@@ -91,7 +91,8 @@ buildAtomClient = (args, env) ->
cb(null)
.addCommand "treeViewRootDirectories", (cb) ->
@execute(->
@waitForExist('.tree-view', 10000)
.execute(->
for element in document.querySelectorAll(".tree-view .project-root > .header .name")
element.dataset.path
, cb)
@@ -104,7 +105,8 @@ buildAtomClient = (args, env) ->
.then ({value: newWindowHandles}) ->
[newWindowHandle] = difference(newWindowHandles, oldWindowHandles)
return done() unless newWindowHandle
@window(newWindowHandle, done)
@window(newWindowHandle)
.waitForExist('atom-workspace', 10000, done)
.addCommand "startAnotherAtom", (args, env, done) ->
@call ->
@@ -168,7 +170,11 @@ module.exports = (args, env, fn) ->
jasmine.getEnv().currentSpec.fail(new Error(err.response?.body?.value?.message))
finish()
fn(client.init()).then(finish)
fn(
client.init()
.waitUntil((-> @windowHandles().then ({value}) -> value.length > 0), 10000)
.waitForExist("atom-workspace", 10000)
).then(finish)
, 30000)
waitsFor("webdriver to stop", chromeDriverDown, 15000)

View File

@@ -28,8 +28,6 @@ describe "Starting Atom", ->
it "opens the parent directory and creates an empty text editor", ->
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
client
.waitForWindowCount(1, 1000)
.waitForExist("atom-workspace", 5000)
.waitForPaneItemCount(1, 1000)
.treeViewRootDirectories()
@@ -54,8 +52,6 @@ describe "Starting Atom", ->
runAtom ["#{filePath}:3"], {ATOM_HOME: atomHome}, (client) ->
client
.waitForWindowCount(1, 1000)
.waitForExist("atom-workspace", 5000)
.waitForPaneItemCount(1, 1000)
.waitForExist("atom-text-editor", 5000)
.then (exists) -> expect(exists).toBe true
@@ -79,8 +75,6 @@ describe "Starting Atom", ->
runAtom ["#{filePath}:2:2"], {ATOM_HOME: atomHome}, (client) ->
client
.waitForWindowCount(1, 1000)
.waitForExist("atom-workspace", 5000)
.waitForPaneItemCount(1, 1000)
.waitForExist("atom-text-editor", 5000)
.then (exists) -> expect(exists).toBe true
@@ -97,8 +91,6 @@ describe "Starting Atom", ->
filePath = path.join(tempDirPath, "new-file")
runAtom ["#{filePath}: "], {ATOM_HOME: atomHome}, (client) ->
client
.waitForWindowCount(1, 1000)
.waitForExist("atom-workspace", 5000)
.waitForPaneItemCount(1, 1000)
.waitForExist("atom-text-editor", 5000)
.then (exists) -> expect(exists).toBe true
@@ -113,8 +105,6 @@ describe "Starting Atom", ->
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
client
.waitForWindowCount(1, 1000)
.waitForExist("atom-workspace", 5000)
.waitForPaneItemCount(1, 5000)
# Opening another file reuses the same window and does not change the
@@ -131,7 +121,6 @@ describe "Starting Atom", ->
.waitForNewWindow(->
@startAnotherAtom([otherTempDirPath], ATOM_HOME: atomHome)
, 5000)
.waitForExist("atom-workspace", 5000)
.waitForPaneItemCount(0, 1000)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([otherTempDirPath])
@@ -140,14 +129,12 @@ describe "Starting Atom", ->
it "remembers the state of the window", ->
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
client
.waitForExist("atom-workspace", 5000)
.waitForPaneItemCount(0, 3000)
.execute -> atom.workspace.open()
.waitForPaneItemCount(1, 3000)
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
client
.waitForExist("atom-workspace", 5000)
.waitForPaneItemCount(1, 5000)
describe "opening multiple directories simultaneously", ->
@@ -157,14 +144,12 @@ describe "Starting Atom", ->
runAtom [tempDirPath, otherTempDirPath], {ATOM_HOME: atomHome}, (client) ->
client
.waitForExist("atom-workspace", 5000)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([tempDirPath, otherTempDirPath])
# Opening one of those directories again reuses the same window and
# does not change the project paths.
.startAnotherAtom([nestedDir], ATOM_HOME: atomHome)
.waitForExist("atom-workspace", 5000)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([tempDirPath, otherTempDirPath])
@@ -172,7 +157,6 @@ describe "Starting Atom", ->
it "reuses that window to open a directory", ->
runAtom [], {ATOM_HOME: atomHome}, (client) ->
client
.waitForExist("atom-workspace")
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([])
@@ -188,7 +172,6 @@ describe "Starting Atom", ->
it "opens a new window with a single untitled buffer", ->
runAtom [], {ATOM_HOME: atomHome}, (client) ->
client
.waitForExist("atom-workspace")
.waitForPaneItemCount(1, 5000)
# Opening with no file paths always creates a new window, even if
@@ -196,7 +179,6 @@ describe "Starting Atom", ->
.waitForNewWindow(->
@startAnotherAtom([], ATOM_HOME: atomHome)
, 5000)
.waitForExist("atom-workspace")
.waitForPaneItemCount(1, 5000)
it "doesn't open a new window if openEmptyEditorOnStart is disabled", ->
@@ -207,17 +189,14 @@ describe "Starting Atom", ->
runAtom [], {ATOM_HOME: atomHome}, (client) ->
client
.waitForExist("atom-workspace")
.waitForPaneItemCount(0, 5000)
it "reopens any previously opened windows", ->
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
client
.waitForExist("atom-workspace")
.waitForNewWindow(->
@startAnotherAtom([otherTempDirPath], ATOM_HOME: atomHome)
, 5000)
.waitForExist("atom-workspace")
runAtom [], {ATOM_HOME: atomHome}, (client) ->
windowProjectPaths = []
@@ -226,12 +205,10 @@ describe "Starting Atom", ->
.waitForWindowCount(2, 10000)
.then ({value: windowHandles}) ->
@window(windowHandles[0])
.waitForExist("atom-workspace")
.treeViewRootDirectories()
.then ({value: directories}) -> windowProjectPaths.push(directories)
.window(windowHandles[1])
.waitForExist("atom-workspace")
.treeViewRootDirectories()
.then ({value: directories}) -> windowProjectPaths.push(directories)
@@ -246,7 +223,5 @@ describe "Starting Atom", ->
remoteDirectory = 'remote://server:3437/some/directory/path'
runAtom [remoteDirectory], {ATOM_HOME: atomHome}, (client) ->
client
.waitForWindowCount(1, 1000)
.waitForExist("atom-workspace", 5000)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([remoteDirectory])

View File

@@ -1,59 +0,0 @@
fs = require 'fs'
module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) ->
window[key] = value for key, value of require '../vendor/jasmine'
{TerminalReporter} = require 'jasmine-tagged'
disableFocusMethods() if process.env.JANKY_SHA1 or process.env.CI
TimeReporter = require './time-reporter'
timeReporter = new TimeReporter()
logStream = fs.openSync(logFile, 'w') if logFile?
log = (str) ->
if logStream?
fs.writeSync(logStream, str)
else
process.stderr.write(str)
if atom.getLoadSettings().exitWhenDone
reporter = new TerminalReporter
print: (str) ->
log(str)
onComplete: (runner) ->
fs.closeSync(logStream) if logStream?
if process.env.JANKY_SHA1 or process.env.CI
grim = require 'grim'
if grim.getDeprecationsLength() > 0
grim.logDeprecations()
return atom.exit(1)
if runner.results().failedCount > 0
atom.exit(1)
else
atom.exit(0)
else
AtomReporter = require './atom-reporter'
reporter = new AtomReporter()
require specSuite
jasmineEnv = jasmine.getEnv()
jasmineEnv.addReporter(reporter)
jasmineEnv.addReporter(timeReporter)
jasmineEnv.setIncludedTags([process.platform])
jasmineContent = document.createElement('div')
jasmineContent.setAttribute('id', 'jasmine-content')
document.body.appendChild(jasmineContent)
jasmineEnv.execute()
disableFocusMethods = ->
['fdescribe', 'ffdescribe', 'fffdescribe', 'fit', 'ffit', 'fffit'].forEach (methodName) ->
focusMethod = window[methodName]
window[methodName] = (description) ->
error = new Error('Focused spec is running on CI')
focusMethod description, -> throw error

View File

@@ -0,0 +1,111 @@
fs = require 'fs'
_ = require 'underscore-plus'
fs = require 'fs-plus'
path = require 'path'
ipc = require 'ipc'
module.exports = ({logFile, headless, testPaths, buildAtomEnvironment}) ->
window[key] = value for key, value of require '../vendor/jasmine'
require 'jasmine-tagged'
# Allow document.title to be assigned in specs without screwing up spec window title
documentTitle = null
Object.defineProperty document, 'title',
get: -> documentTitle
set: (title) -> documentTitle = title
ApplicationDelegate = require '../src/application-delegate'
applicationDelegate = new ApplicationDelegate()
applicationDelegate.setRepresentedFilename = ->
applicationDelegate.setWindowDocumentEdited = ->
window.atom = buildAtomEnvironment({
applicationDelegate, window, document,
configDirPath: process.env.ATOM_HOME
enablePersistence: false
})
require './spec-helper'
disableFocusMethods() if process.env.JANKY_SHA1 or process.env.CI
requireSpecs(testPath) for testPath in testPaths
setSpecType('user')
resolveWithExitCode = null
promise = new Promise (resolve, reject) -> resolveWithExitCode = resolve
jasmineEnv = jasmine.getEnv()
jasmineEnv.addReporter(buildReporter({logFile, headless, resolveWithExitCode}))
TimeReporter = require './time-reporter'
jasmineEnv.addReporter(new TimeReporter())
jasmineEnv.setIncludedTags([process.platform])
jasmineContent = document.createElement('div')
jasmineContent.setAttribute('id', 'jasmine-content')
document.body.appendChild(jasmineContent)
jasmineEnv.execute()
promise
disableFocusMethods = ->
['fdescribe', 'ffdescribe', 'fffdescribe', 'fit', 'ffit', 'fffit'].forEach (methodName) ->
focusMethod = window[methodName]
window[methodName] = (description) ->
error = new Error('Focused spec is running on CI')
focusMethod description, -> throw error
requireSpecs = (testPath, specType) ->
if fs.isDirectorySync(testPath)
for testFilePath in fs.listTreeSync(testPath) when /-spec\.(coffee|js)$/.test testFilePath
require(testFilePath)
# Set spec directory on spec for setting up the project in spec-helper
setSpecDirectory(testPath)
else
require(testPath)
setSpecDirectory(path.dirname(testPath))
setSpecField = (name, value) ->
specs = jasmine.getEnv().currentRunner().specs()
return if specs.length is 0
for index in [specs.length-1..0]
break if specs[index][name]?
specs[index][name] = value
setSpecType = (specType) ->
setSpecField('specType', specType)
setSpecDirectory = (specDirectory) ->
setSpecField('specDirectory', specDirectory)
buildReporter = ({logFile, headless, resolveWithExitCode}) ->
if headless
buildTerminalReporter(logFile, resolveWithExitCode)
else
AtomReporter = require './atom-reporter'
reporter = new AtomReporter()
buildTerminalReporter = (logFile, resolveWithExitCode) ->
logStream = fs.openSync(logFile, 'w') if logFile?
log = (str) ->
if logStream?
fs.writeSync(logStream, str)
else
ipc.send 'write-to-stderr', str
{TerminalReporter} = require 'jasmine-tagged'
new TerminalReporter
print: (str) ->
log(str)
onComplete: (runner) ->
fs.closeSync(logStream) if logStream?
if process.env.JANKY_SHA1 or process.env.CI
grim = require 'grim'
if grim.getDeprecationsLength() > 0
grim.logDeprecations()
resolveWithExitCode(1)
return
if runner.results().failedCount > 0
resolveWithExitCode(1)
else
resolveWithExitCode(0)

View File

@@ -9,7 +9,7 @@ describe "LinesYardstick", ->
atom.packages.activatePackage('language-javascript')
waitsForPromise ->
atom.project.open('sample.js').then (o) -> editor = o
atom.workspace.open('sample.js').then (o) -> editor = o
runs ->
createdLineNodes = []
@@ -56,7 +56,7 @@ describe "LinesYardstick", ->
textNodes
editor.setLineHeightInPixels(14)
linesYardstick = new LinesYardstick(editor, mockPresenter, mockLineNodesProvider)
linesYardstick = new LinesYardstick(editor, mockPresenter, mockLineNodesProvider, atom.grammars)
afterEach ->
lineNode.remove() for lineNode in createdLineNodes
@@ -76,10 +76,10 @@ describe "LinesYardstick", ->
expect(linesYardstick.pixelPositionForScreenPosition([0, 0])).toEqual({left: 0, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition([0, 1])).toEqual({left: 7, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition([0, 5])).toEqual({left: 37.8046875, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition([1, 6])).toEqual({left: 43.20703125, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition([1, 9])).toEqual({left: 72.20703125, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition([2, Infinity])).toEqual({left: 288.046875, top: 28})
expect(linesYardstick.pixelPositionForScreenPosition([0, 5])).toEqual({left: 37.78125, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition([1, 6])).toEqual({left: 43.171875, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition([1, 9])).toEqual({left: 72.171875, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition([2, Infinity])).toEqual({left: 287.859375, top: 28})
it "reuses already computed pixel positions unless it is invalidated", ->
atom.styles.addStyleSheet """
@@ -105,9 +105,9 @@ describe "LinesYardstick", ->
linesYardstick.invalidateCache()
expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 24.00390625, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 72.01171875, top: 28})
expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 120.01171875, top: 70})
expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 24, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 72, top: 28})
expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 120, top: 70})
it "correctly handles RTL characters", ->
atom.styles.addStyleSheet """
@@ -156,7 +156,7 @@ describe "LinesYardstick", ->
expect(linesYardstick.screenPositionForPixelPosition({top: 32, left: 24.3})).toEqual([2, 3])
expect(linesYardstick.screenPositionForPixelPosition({top: 46, left: 66.5})).toEqual([3, 9])
expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 99.9})).toEqual([5, 14])
expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 224.4365234375})).toEqual([5, 29])
expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 224.2365234375})).toEqual([5, 29])
expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 225})).toEqual([5, 30])
it "clips pixel positions above buffer start", ->

View File

@@ -5,7 +5,11 @@ describe "MenuManager", ->
menu = null
beforeEach ->
menu = new MenuManager(resourcePath: atom.getLoadSettings().resourcePath)
menu = new MenuManager(
resourcePath: atom.getLoadSettings().resourcePath
keymapManager: atom.keymaps
packageManager: atom.packages
)
describe "::add(items)", ->
it "can add new menus that can be removed with the returned disposable", ->
@@ -67,7 +71,7 @@ describe "MenuManager", ->
atom.keymaps.add 'test', 'atom-workspace': 'ctrl-b': 'b'
atom.keymaps.add 'test', 'atom-text-editor': 'ctrl-b': 'unset!'
waits 1
waits 50
runs -> expect(menu.sendToBrowserProcess.argsForCall[0][1]['b']).toBeUndefined()

View File

@@ -86,6 +86,7 @@ describe 'ModuleCache', ->
exports.load = function() { require('underscore-plus'); };
"""
spyOn(process, 'cwd').andReturn('/') # Required when running this test from CLI
packageMain = require(indexPath)
Module._findPath.reset()
expect(-> packageMain.load()).toThrow()

View File

@@ -770,8 +770,6 @@ describe "PackageManager", ->
atom.packages.deactivatePackages()
atom.packages.unloadPackages()
GrammarRegistry = require '../src/grammar-registry'
atom.grammars = window.syntax = new GrammarRegistry()
jasmine.restoreDeprecationsSnapshot()
it "activates all the packages, and none of the themes", ->

View File

@@ -3,9 +3,22 @@ Package = require '../src/package'
ThemePackage = require '../src/theme-package'
describe "Package", ->
build = (constructor, path) ->
new constructor(
path: path, packageManager: atom.packages, config: atom.config,
styleManager: atom.styles, notificationManager: atom.notifications,
keymapManager: atom.keymaps, commandRegistry: atom.command,
grammarRegistry: atom.grammars, themeManager: atom.themes,
menuManager: atom.menu, contextMenuManager: atom.contextMenu,
devMode: false
)
buildPackage = (packagePath) -> build(Package, packagePath)
buildThemePackage = (themePath) -> build(ThemePackage, themePath)
describe "when the package contains incompatible native modules", ->
beforeEach ->
spyOn(atom, 'inDevMode').andReturn(false)
items = {}
spyOn(global.localStorage, 'setItem').andCallFake (key, item) -> items[key] = item; undefined
spyOn(global.localStorage, 'getItem').andCallFake (key) -> items[key] ? null
@@ -13,35 +26,34 @@ describe "Package", ->
it "does not activate it", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-incompatible-native-module')
pack = new Package(packagePath)
pack = buildPackage(packagePath)
expect(pack.isCompatible()).toBe false
expect(pack.incompatibleModules[0].name).toBe 'native-module'
expect(pack.incompatibleModules[0].path).toBe path.join(packagePath, 'node_modules', 'native-module')
it "utilizes _atomModuleCache if present to determine the package's native dependencies", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-ignored-incompatible-native-module')
pack = new Package(packagePath)
pack = buildPackage(packagePath)
expect(pack.getNativeModuleDependencyPaths().length).toBe(1) # doesn't see the incompatible module
expect(pack.isCompatible()).toBe true
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-cached-incompatible-native-module')
pack = new Package(packagePath)
pack = buildPackage(packagePath)
expect(pack.isCompatible()).toBe false
it "caches the incompatible native modules in local storage", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-incompatible-native-module')
expect(new Package(packagePath).isCompatible()).toBe false
expect(buildPackage(packagePath).isCompatible()).toBe false
expect(global.localStorage.getItem.callCount).toBe 1
expect(global.localStorage.setItem.callCount).toBe 1
expect(new Package(packagePath).isCompatible()).toBe false
expect(buildPackage(packagePath).isCompatible()).toBe false
expect(global.localStorage.getItem.callCount).toBe 2
expect(global.localStorage.setItem.callCount).toBe 1
describe "::rebuild()", ->
beforeEach ->
spyOn(atom, 'inDevMode').andReturn(false)
items = {}
spyOn(global.localStorage, 'setItem').andCallFake (key, item) -> items[key] = item; undefined
spyOn(global.localStorage, 'getItem').andCallFake (key) -> items[key] ? null
@@ -49,7 +61,7 @@ describe "Package", ->
it "returns a promise resolving to the results of `apm rebuild`", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-index')
pack = new Package(packagePath)
pack = buildPackage(packagePath)
rebuildCallbacks = []
spyOn(pack, 'runRebuildProcess').andCallFake ((callback) -> rebuildCallbacks.push(callback))
@@ -63,7 +75,7 @@ describe "Package", ->
it "persists build failures in local storage", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-index')
pack = new Package(packagePath)
pack = buildPackage(packagePath)
expect(pack.isCompatible()).toBe true
expect(pack.getBuildFailureOutput()).toBeNull()
@@ -79,7 +91,7 @@ describe "Package", ->
expect(pack.isCompatible()).toBe false
# A different package instance has the same failure output (simulates reload)
pack2 = new Package(packagePath)
pack2 = buildPackage(packagePath)
expect(pack2.getBuildFailureOutput()).toBe 'It is broken'
expect(pack2.isCompatible()).toBe false
@@ -92,7 +104,7 @@ describe "Package", ->
it "sets cached incompatible modules to an empty array when the rebuild completes (there may be a build error, but rebuilding *deletes* native modules)", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-incompatible-native-module')
pack = new Package(packagePath)
pack = buildPackage(packagePath)
expect(pack.getIncompatibleNativeModules().length).toBeGreaterThan(0)
@@ -118,14 +130,14 @@ describe "Package", ->
it "loads and applies css", ->
expect(getComputedStyle(editorElement).paddingBottom).not.toBe "1234px"
themePath = atom.project.getDirectories()[0]?.resolve('packages/theme-with-index-css')
theme = new ThemePackage(themePath)
theme = buildThemePackage(themePath)
theme.activate()
expect(getComputedStyle(editorElement).paddingTop).toBe "1234px"
it "parses, loads and applies less", ->
expect(getComputedStyle(editorElement).paddingBottom).not.toBe "1234px"
themePath = atom.project.getDirectories()[0]?.resolve('packages/theme-with-index-less')
theme = new ThemePackage(themePath)
theme = buildThemePackage(themePath)
theme.activate()
expect(getComputedStyle(editorElement).paddingTop).toBe "4321px"
@@ -136,7 +148,7 @@ describe "Package", ->
expect(getComputedStyle(editorElement).paddingBottom).not.toBe("103px")
themePath = atom.project.getDirectories()[0]?.resolve('packages/theme-with-package-file')
theme = new ThemePackage(themePath)
theme = buildThemePackage(themePath)
theme.activate()
expect(getComputedStyle(editorElement).paddingTop).toBe("101px")
expect(getComputedStyle(editorElement).paddingRight).toBe("102px")
@@ -149,7 +161,7 @@ describe "Package", ->
expect(getComputedStyle(editorElement).paddingBottom).not.toBe "30px"
themePath = atom.project.getDirectories()[0]?.resolve('packages/theme-without-package-file')
theme = new ThemePackage(themePath)
theme = buildThemePackage(themePath)
theme.activate()
expect(getComputedStyle(editorElement).paddingTop).toBe "10px"
expect(getComputedStyle(editorElement).paddingRight).toBe "20px"
@@ -158,7 +170,7 @@ describe "Package", ->
describe "reloading a theme", ->
beforeEach ->
themePath = atom.project.getDirectories()[0]?.resolve('packages/theme-with-package-file')
theme = new ThemePackage(themePath)
theme = buildThemePackage(themePath)
theme.activate()
it "reloads without readding to the stylesheets list", ->
@@ -169,7 +181,7 @@ describe "Package", ->
describe "events", ->
beforeEach ->
themePath = atom.project.getDirectories()[0]?.resolve('packages/theme-with-package-file')
theme = new ThemePackage(themePath)
theme = buildThemePackage(themePath)
theme.activate()
it "deactivated event fires on .deactivate()", ->
@@ -182,7 +194,7 @@ describe "Package", ->
beforeEach ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-different-directory-name')
metadata = Package.loadMetadata(packagePath, true)
metadata = atom.packages.loadPackageMetadata(packagePath, true)
it "uses the package name defined in package.json", ->
expect(metadata.name).toBe 'package-with-a-totally-different-name'

View File

@@ -9,7 +9,7 @@ describe "PaneContainerElement", ->
child.nodeName.toLowerCase() for child in paneAxisElement.children
paneAxis = new PaneAxis
paneAxisElement = new PaneAxisElement().initialize(paneAxis)
paneAxisElement = new PaneAxisElement().initialize(paneAxis, atom)
expect(childTagNames()).toEqual []
@@ -42,7 +42,7 @@ describe "PaneContainerElement", ->
]
it "transfers focus to the next pane if a focused pane is removed", ->
container = new PaneContainer
container = new PaneContainer(config: atom.config, confirm: atom.confirm.bind(atom))
containerElement = atom.views.getView(container)
leftPane = container.getActivePane()
leftPaneElement = atom.views.getView(leftPane)
@@ -58,10 +58,10 @@ describe "PaneContainerElement", ->
describe "when a pane is split", ->
it "builds appropriately-oriented atom-pane-axis elements", ->
container = new PaneContainer
container = new PaneContainer(config: atom.config, confirm: atom.confirm.bind(atom))
containerElement = atom.views.getView(container)
pane1 = container.getRoot()
pane1 = container.getActivePane()
pane2 = pane1.splitRight()
pane3 = pane2.splitDown()
@@ -84,7 +84,7 @@ describe "PaneContainerElement", ->
[container, containerElement] = []
beforeEach ->
container = new PaneContainer
container = new PaneContainer(config: atom.config, confirm: atom.confirm.bind(atom))
containerElement = atom.views.getView(container)
document.querySelector('#jasmine-content').appendChild(containerElement)
@@ -201,7 +201,7 @@ describe "PaneContainerElement", ->
[leftPane, rightPane] = []
beforeEach ->
container = new PaneContainer
container = new PaneContainer(config: atom.config, confirm: atom.confirm.bind(atom))
leftPane = container.getActivePane()
rightPane = leftPane.splitRight()
@@ -252,8 +252,8 @@ describe "PaneContainerElement", ->
element.tabIndex = -1
element
container = new PaneContainer
pane1 = container.getRoot()
container = new PaneContainer(config: atom.config, confirm: atom.confirm.bind(atom))
pane1 = container.getActivePane()
pane1.activateItem(buildElement('1'))
pane4 = pane1.splitDown(items: [buildElement('4')])
pane7 = pane4.splitDown(items: [buildElement('7')])

View File

@@ -2,6 +2,16 @@ PaneContainer = require '../src/pane-container'
Pane = require '../src/pane'
describe "PaneContainer", ->
[confirm, params] = []
beforeEach ->
confirm = spyOn(atom.applicationDelegate, 'confirm').andReturn(0)
params = {
config: atom.config,
deserializerManager: atom.deserializers
applicationDelegate: atom.applicationDelegate
}
describe "serialization", ->
[containerA, pane1A, pane2A, pane3A] = []
@@ -12,8 +22,9 @@ describe "PaneContainer", ->
@deserialize: -> new this
serialize: -> deserializer: 'Item'
pane1A = new Pane(items: [new Item])
containerA = new PaneContainer(root: pane1A)
containerA = new PaneContainer(params)
pane1A = containerA.getActivePane()
pane1A.addItem(new Item)
pane2A = pane1A.splitRight(items: [new Item])
pane3A = pane2A.splitDown(items: [new Item])
pane3A.focus()
@@ -21,7 +32,8 @@ describe "PaneContainer", ->
it "preserves the focused pane across serialization", ->
expect(pane3A.focused).toBe true
containerB = PaneContainer.deserialize(containerA.serialize())
containerB = new PaneContainer(params)
containerB.deserialize(containerA.serialize(), atom.deserializers)
[pane1B, pane2B, pane3B] = containerB.getPanes()
expect(pane3B.focused).toBe true
@@ -29,7 +41,8 @@ describe "PaneContainer", ->
pane3A.activate()
expect(containerA.getActivePane()).toBe pane3A
containerB = PaneContainer.deserialize(containerA.serialize())
containerB = new PaneContainer(params)
containerB.deserialize(containerA.serialize(), atom.deserializers)
[pane1B, pane2B, pane3B] = containerB.getPanes()
expect(containerB.getActivePane()).toBe pane3B
@@ -37,7 +50,8 @@ describe "PaneContainer", ->
pane3A.activate()
state = containerA.serialize()
state.activePaneId = -22
containerB = atom.deserializers.deserialize(state)
containerB = new PaneContainer(params)
containerB.deserialize(state, atom.deserializers)
expect(containerB.getActivePane()).toBe containerB.getPanes()[0]
describe "if there are empty panes after deserialization", ->
@@ -47,7 +61,8 @@ describe "PaneContainer", ->
describe "if the 'core.destroyEmptyPanes' config option is false (the default)", ->
it "leaves the empty panes intact", ->
state = containerA.serialize()
containerB = atom.deserializers.deserialize(state)
containerB = new PaneContainer(params)
containerB.deserialize(state, atom.deserializers)
[leftPane, column] = containerB.getRoot().getChildren()
[topPane, bottomPane] = column.getChildren()
@@ -60,14 +75,15 @@ describe "PaneContainer", ->
atom.config.set('core.destroyEmptyPanes', true)
state = containerA.serialize()
containerB = atom.deserializers.deserialize(state)
containerB = new PaneContainer(params)
containerB.deserialize(state, atom.deserializers)
[leftPane, rightPane] = containerB.getRoot().getChildren()
expect(leftPane.getItems().length).toBe 1
expect(rightPane.getItems().length).toBe 1
it "does not allow the root pane to be destroyed", ->
container = new PaneContainer
container = new PaneContainer(params)
container.getRoot().destroy()
expect(container.getRoot()).toBeDefined()
expect(container.getRoot().isDestroyed()).toBe false
@@ -76,7 +92,7 @@ describe "PaneContainer", ->
[container, pane1, pane2] = []
beforeEach ->
container = new PaneContainer
container = new PaneContainer(params)
pane1 = container.getRoot()
it "returns the first pane if no pane has been made active", ->
@@ -105,7 +121,8 @@ describe "PaneContainer", ->
[container, pane1, pane2, observed] = []
beforeEach ->
container = new PaneContainer(root: new Pane(items: [new Object, new Object]))
container = new PaneContainer(params)
container.getRoot().addItems([new Object, new Object])
container.getRoot().splitRight(items: [new Object, new Object])
[pane1, pane2] = container.getPanes()
@@ -147,7 +164,7 @@ describe "PaneContainer", ->
describe "::observePanes()", ->
it "invokes observers with all current and future panes", ->
container = new PaneContainer
container = new PaneContainer(params)
container.getRoot().splitRight()
[pane1, pane2] = container.getPanes()
@@ -161,7 +178,8 @@ describe "PaneContainer", ->
describe "::observePaneItems()", ->
it "invokes observers with all current and future pane items", ->
container = new PaneContainer(root: new Pane(items: [new Object, new Object]))
container = new PaneContainer(params)
container.getRoot().addItems([new Object, new Object])
container.getRoot().splitRight(items: [new Object])
[pane1, pane2] = container.getPanes()
observed = []
@@ -180,27 +198,27 @@ describe "PaneContainer", ->
shouldPromptToSave: -> true
getURI: -> 'test'
container = new PaneContainer
container = new PaneContainer(params)
container.getRoot().splitRight()
[pane1, pane2] = container.getPanes()
pane1.addItem(new TestItem)
pane2.addItem(new TestItem)
it "returns true if the user saves all modified files when prompted", ->
spyOn(atom, "confirm").andReturn(0)
confirm.andReturn(0)
saved = container.confirmClose()
expect(saved).toBeTruthy()
expect(atom.confirm).toHaveBeenCalled()
expect(confirm).toHaveBeenCalled()
it "returns false if the user cancels saving any modified file", ->
spyOn(atom, "confirm").andReturn(1)
confirm.andReturn(1)
saved = container.confirmClose()
expect(saved).toBeFalsy()
expect(atom.confirm).toHaveBeenCalled()
expect(confirm).toHaveBeenCalled()
describe "::onDidAddPane(callback)", ->
it "invokes the given callback when panes are added", ->
container = new PaneContainer
container = new PaneContainer(params)
events = []
container.onDidAddPane (event) -> events.push(event)
@@ -217,7 +235,7 @@ describe "PaneContainer", ->
destroy: -> @_isDestroyed = true
isDestroyed: -> @_isDestroyed
container = new PaneContainer
container = new PaneContainer(params)
events = []
container.onWillDestroyPane (event) ->
itemsDestroyed = (item.isDestroyed() for item in event.pane.getItems())
@@ -233,7 +251,7 @@ describe "PaneContainer", ->
describe "::onDidDestroyPane(callback)", ->
it "invokes the given callback when panes are destroyed", ->
container = new PaneContainer
container = new PaneContainer(params)
events = []
container.onDidDestroyPane (event) -> events.push(event)
@@ -248,7 +266,7 @@ describe "PaneContainer", ->
describe "::onWillDestroyPaneItem() and ::onDidDestroyPaneItem", ->
it "invokes the given callbacks when an item will be destroyed on any pane", ->
container = new PaneContainer
container = new PaneContainer(params)
pane1 = container.getRoot()
item1 = new Object
item2 = new Object
@@ -275,7 +293,7 @@ describe "PaneContainer", ->
describe "::saveAll()", ->
it "saves all open pane items", ->
container = new PaneContainer
container = new PaneContainer(params)
pane1 = container.getRoot()
pane2 = pane1.splitRight()

View File

@@ -4,8 +4,10 @@ describe "PaneElement", ->
[paneElement, container, pane] = []
beforeEach ->
container = new PaneContainer
pane = container.getRoot()
spyOn(atom, "open")
container = new PaneContainer(config: atom.config, confirm: atom.confirm.bind(atom))
pane = container.getActivePane()
paneElement = atom.views.getView(pane)
describe "when the pane's active status changes", ->
@@ -183,7 +185,6 @@ describe "PaneElement", ->
describe "when a file is dragged to the pane", ->
it "opens it", ->
spyOn(atom, "open")
event = buildDragEvent("drop", [{path: "/fake1"}, {path: "/fake2"}])
paneElement.dispatchEvent(event)
expect(atom.open.callCount).toBe 1
@@ -191,7 +192,6 @@ describe "PaneElement", ->
describe "when a non-file is dragged to the pane", ->
it "does nothing", ->
spyOn(atom, "open")
event = buildDragEvent("drop", [])
paneElement.dispatchEvent(event)
expect(atom.open).not.toHaveBeenCalled()

View File

@@ -1,10 +1,11 @@
{extend} = require 'underscore-plus'
{Emitter} = require 'event-kit'
Pane = require '../src/pane'
PaneAxis = require '../src/pane-axis'
PaneContainer = require '../src/pane-container'
describe "Pane", ->
deserializerDisposable = null
[confirm, showSaveDialog, deserializerDisposable] = []
class Item
@deserialize: ({name, uri}) -> new this(name, uri)
@@ -19,18 +20,28 @@ describe "Pane", ->
isDestroyed: -> @destroyed
beforeEach ->
confirm = spyOn(atom.applicationDelegate, 'confirm')
showSaveDialog = spyOn(atom.applicationDelegate, 'showSaveDialog')
deserializerDisposable = atom.deserializers.add(Item)
afterEach ->
deserializerDisposable.dispose()
paneParams = (params) ->
extend({
applicationDelegate: atom.applicationDelegate,
config: atom.config,
deserializerManager: atom.deserializers,
notificationManager: atom.notifications
}, params)
describe "construction", ->
it "sets the active item to the first item", ->
pane = new Pane(items: [new Item("A"), new Item("B")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B")]))
expect(pane.getActiveItem()).toBe pane.itemAtIndex(0)
it "compacts the items array", ->
pane = new Pane(items: [undefined, new Item("A"), null, new Item("B")])
pane = new Pane(paneParams(items: [undefined, new Item("A"), null, new Item("B")]))
expect(pane.getItems().length).toBe 2
expect(pane.getActiveItem()).toBe pane.itemAtIndex(0)
@@ -38,8 +49,8 @@ describe "Pane", ->
[container, pane1, pane2] = []
beforeEach ->
container = new PaneContainer(root: new Pane)
container.getRoot().splitRight()
container = new PaneContainer(config: atom.config, applicationDelegate: atom.applicationDelegate)
container.getActivePane().splitRight()
[pane1, pane2] = container.getPanes()
it "changes the active pane on the container", ->
@@ -76,14 +87,14 @@ describe "Pane", ->
describe "::addItem(item, index)", ->
it "adds the item at the given index", ->
pane = new Pane(items: [new Item("A"), new Item("B")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B")]))
[item1, item2] = pane.getItems()
item3 = new Item("C")
pane.addItem(item3, 1)
expect(pane.getItems()).toEqual [item1, item3, item2]
it "adds the item after the active item if no index is provided", ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
[item1, item2, item3] = pane.getItems()
pane.activateItem(item2)
item4 = new Item("D")
@@ -91,13 +102,13 @@ describe "Pane", ->
expect(pane.getItems()).toEqual [item1, item2, item4, item3]
it "sets the active item after adding the first item", ->
pane = new Pane
pane = new Pane(paneParams())
item = new Item("A")
pane.addItem(item)
expect(pane.getActiveItem()).toBe item
it "invokes ::onDidAddItem() observers", ->
pane = new Pane(items: [new Item("A"), new Item("B")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B")]))
events = []
pane.onDidAddItem (event) -> events.push(event)
@@ -107,13 +118,14 @@ describe "Pane", ->
it "throws an exception if the item is already present on a pane", ->
item = new Item("A")
pane1 = new Pane(items: [item])
container = new PaneContainer(root: pane1)
container = new PaneContainer(config: atom.config, applicationDelegate: atom.applicationDelegate)
pane1 = container.getActivePane()
pane1.addItem(item)
pane2 = pane1.splitRight()
expect(-> pane2.addItem(item)).toThrow()
it "throws an exception if the item isn't an object", ->
pane = new Pane(items: [])
pane = new Pane(paneParams(items: []))
expect(-> pane.addItem(null)).toThrow()
expect(-> pane.addItem('foo')).toThrow()
expect(-> pane.addItem(1)).toThrow()
@@ -122,7 +134,7 @@ describe "Pane", ->
pane = null
beforeEach ->
pane = new Pane(items: [new Item("A"), new Item("B")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B")]))
it "changes the active item to the current item", ->
expect(pane.getActiveItem()).toBe pane.itemAtIndex(0)
@@ -143,7 +155,7 @@ describe "Pane", ->
describe "::activateNextItem() and ::activatePreviousItem()", ->
it "sets the active item to the next/previous item, looping around at either end", ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
[item1, item2, item3] = pane.getItems()
expect(pane.getActiveItem()).toBe item1
@@ -158,7 +170,7 @@ describe "Pane", ->
describe "::moveItemRight() and ::moveItemLeft()", ->
it "moves the active item to the right and left, without looping around at either end", ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
[item1, item2, item3] = pane.getItems()
pane.activateItemAtIndex(0)
@@ -176,7 +188,7 @@ describe "Pane", ->
describe "::activateItemAtIndex(index)", ->
it "activates the item at the given index", ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
[item1, item2, item3] = pane.getItems()
pane.activateItemAtIndex(2)
expect(pane.getActiveItem()).toBe item3
@@ -195,7 +207,7 @@ describe "Pane", ->
[pane, item1, item2, item3] = []
beforeEach ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
[item1, item2, item3] = pane.getItems()
it "removes the item from the items list and destroyes it", ->
@@ -259,7 +271,7 @@ describe "Pane", ->
describe "when the item has a uri", ->
it "saves the item before destroying it", ->
itemURI = "test"
spyOn(atom, 'confirm').andReturn(0)
confirm.andReturn(0)
pane.destroyItem(item1)
expect(item1.save).toHaveBeenCalled()
@@ -270,18 +282,18 @@ describe "Pane", ->
it "presents a save-as dialog, then saves the item with the given uri before removing and destroying it", ->
itemURI = null
spyOn(atom, 'showSaveDialogSync').andReturn("/selected/path")
spyOn(atom, 'confirm').andReturn(0)
showSaveDialog.andReturn("/selected/path")
confirm.andReturn(0)
pane.destroyItem(item1)
expect(atom.showSaveDialogSync).toHaveBeenCalled()
expect(showSaveDialog).toHaveBeenCalled()
expect(item1.saveAs).toHaveBeenCalledWith("/selected/path")
expect(item1 in pane.getItems()).toBe false
expect(item1.isDestroyed()).toBe true
describe "if the [Don't Save] option is selected", ->
it "removes and destroys the item without saving it", ->
spyOn(atom, 'confirm').andReturn(2)
confirm.andReturn(2)
pane.destroyItem(item1)
expect(item1.save).not.toHaveBeenCalled()
@@ -290,7 +302,7 @@ describe "Pane", ->
describe "if the [Cancel] option is selected", ->
it "does not save, remove, or destroy the item", ->
spyOn(atom, 'confirm').andReturn(1)
confirm.andReturn(1)
pane.destroyItem(item1)
expect(item1.save).not.toHaveBeenCalled()
@@ -315,19 +327,19 @@ describe "Pane", ->
describe "::destroyActiveItem()", ->
it "destroys the active item", ->
pane = new Pane(items: [new Item("A"), new Item("B")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B")]))
activeItem = pane.getActiveItem()
pane.destroyActiveItem()
expect(activeItem.isDestroyed()).toBe true
expect(activeItem in pane.getItems()).toBe false
it "does not throw an exception if there are no more items", ->
pane = new Pane
pane = new Pane(paneParams())
pane.destroyActiveItem()
describe "::destroyItems()", ->
it "destroys all items", ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
[item1, item2, item3] = pane.getItems()
pane.destroyItems()
expect(item1.isDestroyed()).toBe true
@@ -337,7 +349,7 @@ describe "Pane", ->
describe "::observeItems()", ->
it "invokes the observer with all current and future items", ->
pane = new Pane(items: [new Item, new Item])
pane = new Pane(paneParams(items: [new Item, new Item]))
[item1, item2] = pane.getItems()
observed = []
@@ -350,14 +362,14 @@ describe "Pane", ->
describe "when an item emits a destroyed event", ->
it "removes it from the list of items", ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
[item1, item2, item3] = pane.getItems()
pane.itemAtIndex(1).destroy()
expect(pane.getItems()).toEqual [item1, item3]
describe "::destroyInactiveItems()", ->
it "destroys all items but the active item", ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
[item1, item2, item3] = pane.getItems()
pane.activateItem(item2)
pane.destroyInactiveItems()
@@ -367,8 +379,8 @@ describe "Pane", ->
pane = null
beforeEach ->
pane = new Pane(items: [new Item("A")])
spyOn(atom, 'showSaveDialogSync').andReturn('/selected/path')
pane = new Pane(paneParams(items: [new Item("A")]))
showSaveDialog.andReturn('/selected/path')
describe "when the active item has a uri", ->
beforeEach ->
@@ -390,14 +402,14 @@ describe "Pane", ->
it "opens a save dialog and saves the current item as the selected path", ->
pane.getActiveItem().saveAs = jasmine.createSpy("saveAs")
pane.saveActiveItem()
expect(atom.showSaveDialogSync).toHaveBeenCalled()
expect(showSaveDialog).toHaveBeenCalled()
expect(pane.getActiveItem().saveAs).toHaveBeenCalledWith('/selected/path')
describe "when the current item has no saveAs method", ->
it "does nothing", ->
expect(pane.getActiveItem().saveAs).toBeUndefined()
pane.saveActiveItem()
expect(atom.showSaveDialogSync).not.toHaveBeenCalled()
expect(showSaveDialog).not.toHaveBeenCalled()
describe "when the item's saveAs method throws a well-known IO error", ->
notificationSpy = null
@@ -422,22 +434,22 @@ describe "Pane", ->
pane = null
beforeEach ->
pane = new Pane(items: [new Item("A")])
spyOn(atom, 'showSaveDialogSync').andReturn('/selected/path')
pane = new Pane(paneParams(items: [new Item("A")]))
showSaveDialog.andReturn('/selected/path')
describe "when the current item has a saveAs method", ->
it "opens the save dialog and calls saveAs on the item with the selected path", ->
pane.getActiveItem().path = __filename
pane.getActiveItem().saveAs = jasmine.createSpy("saveAs")
pane.saveActiveItemAs()
expect(atom.showSaveDialogSync).toHaveBeenCalledWith(defaultPath: __filename)
expect(showSaveDialog).toHaveBeenCalledWith(defaultPath: __filename)
expect(pane.getActiveItem().saveAs).toHaveBeenCalledWith('/selected/path')
describe "when the current item does not have a saveAs method", ->
it "does nothing", ->
expect(pane.getActiveItem().saveAs).toBeUndefined()
pane.saveActiveItemAs()
expect(atom.showSaveDialogSync).not.toHaveBeenCalled()
expect(showSaveDialog).not.toHaveBeenCalled()
describe "when the item's saveAs method throws a well-known IO error", ->
notificationSpy = null
@@ -460,7 +472,7 @@ describe "Pane", ->
describe "::itemForURI(uri)", ->
it "returns the item for which a call to .getURI() returns the given uri", ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C"), new Item("D")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C"), new Item("D")]))
[item1, item2, item3] = pane.getItems()
item1.uri = "a"
item2.uri = "b"
@@ -472,7 +484,7 @@ describe "Pane", ->
[pane, item1, item2, item3, item4] = []
beforeEach ->
pane = new Pane(items: [new Item("A"), new Item("B"), new Item("C"), new Item("D")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C"), new Item("D")]))
[item1, item2, item3, item4] = pane.getItems()
it "moves the item to the given index and invokes ::onDidMoveItem observers", ->
@@ -501,8 +513,9 @@ describe "Pane", ->
[item1, item2, item3, item4, item5] = []
beforeEach ->
pane1 = new Pane(items: [new Item("A"), new Item("B"), new Item("C")])
container = new PaneContainer(root: pane1)
container = new PaneContainer(config: atom.config, confirm: confirm)
pane1 = container.getActivePane()
pane1.addItems([new Item("A"), new Item("B"), new Item("C")])
pane2 = pane1.splitRight(items: [new Item("D"), new Item("E")])
[item1, item2, item3] = pane1.getItems()
[item4, item5] = pane2.getItems()
@@ -553,8 +566,9 @@ describe "Pane", ->
[pane1, container] = []
beforeEach ->
pane1 = new Pane(items: [new Item("A")])
container = new PaneContainer(root: pane1)
container = new PaneContainer(config: atom.config, confirm: confirm, deserializerManager: atom.deserializers)
pane1 = container.getActivePane()
pane1.addItem(new Item("A"))
describe "::splitLeft(params)", ->
describe "when the parent is the container root", ->
@@ -652,32 +666,32 @@ describe "Pane", ->
describe "::close()", ->
it "prompts to save unsaved items before destroying the pane", ->
pane = new Pane(items: [new Item("A"), new Item("B")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B")]))
[item1, item2] = pane.getItems()
item1.shouldPromptToSave = -> true
item1.getURI = -> "/test/path"
item1.save = jasmine.createSpy("save")
spyOn(atom, 'confirm').andReturn(0)
confirm.andReturn(0)
pane.close()
expect(atom.confirm).toHaveBeenCalled()
expect(confirm).toHaveBeenCalled()
expect(item1.save).toHaveBeenCalled()
expect(pane.isDestroyed()).toBe true
it "does not destroy the pane if cancel is called", ->
pane = new Pane(items: [new Item("A"), new Item("B")])
pane = new Pane(paneParams(items: [new Item("A"), new Item("B")]))
[item1, item2] = pane.getItems()
item1.shouldPromptToSave = -> true
item1.getURI = -> "/test/path"
item1.save = jasmine.createSpy("save")
spyOn(atom, 'confirm').andReturn(1)
confirm.andReturn(1)
pane.close()
expect(atom.confirm).toHaveBeenCalled()
expect(confirm).toHaveBeenCalled()
expect(item1.save).not.toHaveBeenCalled()
expect(pane.isDestroyed()).toBe false
@@ -685,7 +699,7 @@ describe "Pane", ->
[container, pane1, pane2] = []
beforeEach ->
container = new PaneContainer
container = new PaneContainer(config: atom.config, confirm: confirm)
pane1 = container.root
pane1.addItems([new Item("A"), new Item("B")])
pane2 = pane1.splitRight()
@@ -732,18 +746,18 @@ describe "Pane", ->
pane = null
beforeEach ->
params =
pane = new Pane(paneParams(
items: [new Item("A", "a"), new Item("B", "b"), new Item("C", "c")]
flexScale: 2
pane = new Pane(params)
))
it "can serialize and deserialize the pane and all its items", ->
newPane = Pane.deserialize(pane.serialize())
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.getItems()).toEqual pane.getItems()
it "restores the active item on deserialization", ->
pane.activateItemAtIndex(1)
newPane = Pane.deserialize(pane.serialize())
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.getActiveItem()).toEqual newPane.itemAtIndex(1)
it "does not include items that cannot be deserialized", ->
@@ -751,11 +765,11 @@ describe "Pane", ->
unserializable = {}
pane.activateItem(unserializable)
newPane = Pane.deserialize(pane.serialize())
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.getActiveItem()).toEqual pane.itemAtIndex(0)
expect(newPane.getItems().length).toBe pane.getItems().length - 1
it "includes the pane's focus state in the serialized state", ->
pane.focus()
newPane = Pane.deserialize(pane.serialize())
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.focused).toBe true

View File

@@ -20,10 +20,6 @@ describe "PanelContainerElement", ->
beforeEach ->
jasmineContent = document.body.querySelector('#jasmine-content')
atom.views.addViewProvider Panel, (model) ->
new PanelElement().initialize(model)
atom.views.addViewProvider PanelContainer, (model) ->
new PaneContainerElement().initialize(model)
atom.views.addViewProvider TestPanelContainerItem, (model) ->
new TestPanelContainerItemElement().initialize(model)

View File

@@ -18,8 +18,8 @@ describe "PanelElement", ->
beforeEach ->
jasmineContent = document.body.querySelector('#jasmine-content')
atom.views.addViewProvider Panel, (model) ->
new PanelElement().initialize(model)
atom.views.addViewProvider Panel, (model, env) ->
new PanelElement().initialize(model, env)
atom.views.addViewProvider TestPanelItem, (model) ->
new TestPanelItemElement().initialize(model)

View File

@@ -16,44 +16,6 @@ describe "Project", ->
# Wait for project's service consumers to be asynchronously added
waits(1)
describe "when a new repository-provider is added", ->
it "uses it to create repositories for any directories that need one", ->
projectPath = temp.mkdirSync('atom-project')
atom.project.setPaths([projectPath])
expect(atom.project.getRepositories()).toEqual [null]
expect(atom.project.repositoryProviders.length).toEqual 1
dummyRepository = {destroy: -> null}
atom.packages.serviceHub.provide("atom.repository-provider", "0.1.0", {
repositoryForDirectory: (directory) -> Promise.resolve(dummyRepository)
repositoryForDirectorySync: (directory) -> dummyRepository
})
repository = null
waitsFor "repository to be updated", ->
repository = atom.project.getRepositories()[0]
runs ->
expect(repository).toBe dummyRepository
it "does not create any new repositories if every directory has a repository", ->
repositories = atom.project.getRepositories()
expect(repositories.length).toEqual 1
[repository] = repositories
expect(repository).toBeTruthy()
# Register a new RepositoryProvider.
dummyRepository = destroy: ->
repositoryProvider =
repositoryForDirectory: (directory) -> Promise.resolve(dummyRepository)
repositoryForDirectorySync: (directory) -> dummyRepository
atom.packages.serviceHub.provide(
"atom.repository-provider", "0.1.0", repositoryProvider)
expect(atom.project.getRepositories()).toBe repositories
describe "serialization", ->
deserializedProject = null
@@ -66,16 +28,19 @@ describe "Project", ->
runs ->
expect(atom.project.getBuffers().length).toBe 1
deserializedProject = Project.deserialize(atom.project.serialize())
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
expect(deserializedProject.getBuffers().length).toBe 0
it "listens for destroyed events on deserialized buffers and removes them when they are destroyed", ->
waitsForPromise ->
atom.project.open('a')
atom.workspace.open('a')
runs ->
expect(atom.project.getBuffers().length).toBe 1
deserializedProject = Project.deserialize(atom.project.serialize())
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
expect(deserializedProject.getBuffers().length).toBe 1
deserializedProject.getBuffers()[0].destroy()
@@ -86,12 +51,13 @@ describe "Project", ->
pathToOpen = path.join(temp.mkdirSync(), 'file.txt')
waitsForPromise ->
atom.project.open(pathToOpen)
atom.workspace.open(pathToOpen)
runs ->
expect(atom.project.getBuffers().length).toBe 1
fs.mkdirSync(pathToOpen)
deserializedProject = Project.deserialize(atom.project.serialize())
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
expect(deserializedProject.getBuffers().length).toBe 0
it "does not deserialize buffers when their path is inaccessible", ->
@@ -99,12 +65,13 @@ describe "Project", ->
fs.writeFileSync(pathToOpen, '')
waitsForPromise ->
atom.project.open(pathToOpen)
atom.workspace.open(pathToOpen)
runs ->
expect(atom.project.getBuffers().length).toBe 1
fs.chmodSync(pathToOpen, '000')
deserializedProject = Project.deserialize(atom.project.serialize())
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
expect(deserializedProject.getBuffers().length).toBe 0
describe "when an editor is saved and the project has no path", ->
@@ -115,7 +82,7 @@ describe "Project", ->
editor = null
waitsForPromise ->
atom.project.open().then (o) -> editor = o
atom.workspace.open().then (o) -> editor = o
runs ->
editor.saveAs(tempFile)
@@ -125,7 +92,7 @@ describe "Project", ->
editor = null
beforeEach ->
waitsForPromise ->
atom.project.open(require.resolve('./fixtures/dir/a')).then (o) -> editor = o
atom.workspace.open(require.resolve('./fixtures/dir/a')).then (o) -> editor = o
it "creates a warning notification", ->
atom.notifications.onDidAddNotification noteSpy = jasmine.createSpy()
@@ -144,6 +111,106 @@ describe "Project", ->
expect(notification.getMessage()).toContain '`resurrect`'
expect(notification.getMessage()).toContain 'fixtures/dir/a'
describe "when a custom repository-provider service is provided", ->
[fakeRepositoryProvider, fakeRepository] = []
beforeEach ->
fakeRepository = {destroy: -> null}
fakeRepositoryProvider = {
repositoryForDirectory: (directory) -> Promise.resolve(fakeRepository)
repositoryForDirectorySync: (directory) -> fakeRepository
}
it "uses it to create repositories for any directories that need one", ->
projectPath = temp.mkdirSync('atom-project')
atom.project.setPaths([projectPath])
expect(atom.project.getRepositories()).toEqual [null]
atom.packages.serviceHub.provide("atom.repository-provider", "0.1.0", fakeRepositoryProvider)
waitsFor -> atom.project.repositoryProviders.length > 1
runs -> atom.project.getRepositories()[0] is fakeRepository
it "does not create any new repositories if every directory has a repository", ->
repositories = atom.project.getRepositories()
expect(repositories.length).toEqual 1
expect(repositories[0]).toBeTruthy()
atom.packages.serviceHub.provide("atom.repository-provider", "0.1.0", fakeRepositoryProvider)
waitsFor -> atom.project.repositoryProviders.length > 1
runs -> expect(atom.project.getRepositories()).toBe repositories
it "stops using it to create repositories when the service is removed", ->
atom.project.setPaths([])
disposable = atom.packages.serviceHub.provide("atom.repository-provider", "0.1.0", fakeRepositoryProvider)
waitsFor -> atom.project.repositoryProviders.length > 1
runs ->
disposable.dispose()
atom.project.addPath(temp.mkdirSync('atom-project'))
expect(atom.project.getRepositories()).toEqual [null]
describe "when a custom directory-provider service is provided", ->
class DummyDirectory
constructor: (@path) ->
getPath: -> @path
getFile: -> {existsSync: -> false}
getSubdirectory: -> {existsSync: -> false}
isRoot: -> true
existsSync: -> @path.endsWith('does-exist')
contains: (filePath) -> filePath.startsWith(@path)
serviceDisposable = null
beforeEach ->
serviceDisposable = atom.packages.serviceHub.provide("atom.directory-provider", "0.1.0", {
directoryForURISync: (uri) ->
if uri.startsWith("ssh://")
new DummyDirectory(uri)
else
null
})
waitsFor ->
atom.project.directoryProviders.length > 0
it "uses the provider's custom directories for any paths that it handles", ->
localPath = temp.mkdirSync('local-path')
remotePath = "ssh://foreign-directory:8080/does-exist"
atom.project.setPaths([localPath, remotePath])
directories = atom.project.getDirectories()
expect(directories[0].getPath()).toBe localPath
expect(directories[0] instanceof Directory).toBe true
expect(directories[1].getPath()).toBe remotePath
expect(directories[1] instanceof DummyDirectory).toBe true
# It does not add new remote paths if their directories do not exist
# and they are contained by existing remote paths.
childRemotePath = remotePath + "/subdirectory/that/does-not-exist"
atom.project.addPath(childRemotePath)
expect(atom.project.getDirectories().length).toBe 2
# It does add new remote paths if their directories exist.
childRemotePath = remotePath + "/subdirectory/that/does-exist"
atom.project.addPath(childRemotePath)
directories = atom.project.getDirectories()
expect(directories[2].getPath()).toBe childRemotePath
expect(directories[2] instanceof DummyDirectory).toBe true
# It does add new remote paths to be added if they are not contained by
# previous remote paths.
otherRemotePath = "ssh://other-foreign-directory:8080/"
atom.project.addPath(otherRemotePath)
directories = atom.project.getDirectories()
expect(directories[3].getPath()).toBe otherRemotePath
expect(directories[3] instanceof DummyDirectory).toBe true
it "stops using the provider when the service is removed", ->
serviceDisposable.dispose()
atom.project.setPaths(["ssh://foreign-directory:8080/does-exist"])
expect(atom.project.getDirectories()[0] instanceof Directory).toBe true
describe ".open(path)", ->
[absolutePath, newBufferHandler] = []
@@ -156,7 +223,7 @@ describe "Project", ->
it "returns a new edit session for the given path and emits 'buffer-created'", ->
editor = null
waitsForPromise ->
atom.project.open(absolutePath).then (o) -> editor = o
atom.workspace.open(absolutePath).then (o) -> editor = o
runs ->
expect(editor.buffer.getPath()).toBe absolutePath
@@ -166,7 +233,7 @@ describe "Project", ->
it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created'", ->
editor = null
waitsForPromise ->
atom.project.open(absolutePath).then (o) -> editor = o
atom.workspace.open(absolutePath).then (o) -> editor = o
runs ->
expect(editor.buffer.getPath()).toBe absolutePath
@@ -177,17 +244,17 @@ describe "Project", ->
editor = null
waitsForPromise ->
atom.project.open(absolutePath).then (o) -> editor = o
atom.workspace.open(absolutePath).then (o) -> editor = o
runs ->
newBufferHandler.reset()
waitsForPromise ->
atom.project.open(absolutePath).then ({buffer}) ->
atom.workspace.open(absolutePath).then ({buffer}) ->
expect(buffer).toBe editor.buffer
waitsForPromise ->
atom.project.open('a').then ({buffer}) ->
atom.workspace.open('a').then ({buffer}) ->
expect(buffer).toBe editor.buffer
expect(newBufferHandler).not.toHaveBeenCalled()
@@ -195,7 +262,7 @@ describe "Project", ->
it "returns a new edit session and emits 'buffer-created'", ->
editor = null
waitsForPromise ->
atom.project.open().then (o) -> editor = o
atom.workspace.open().then (o) -> editor = o
runs ->
expect(editor.buffer.getPath()).toBeUndefined()
@@ -305,68 +372,6 @@ describe "Project", ->
expect(directories.length).toBe 1
expect(directories[0].getPath()).toBe path.normalize(nonLocalFsDirectory)
describe "when a custom directory provider has been added", ->
describe "when custom provider handles the given path", ->
it "creates a directory using that provider", ->
class DummyDirectory
constructor: (@path) ->
getPath: -> @path
getFile: -> {existsSync: -> false}
getSubdirectory: -> {existsSync: -> false}
isRoot: -> true
existsSync: -> /does-exist/.test(@path)
off: ->
contains: (filePath) -> filePath.startsWith(@path)
atom.packages.serviceHub.provide("atom.directory-provider", "0.1.0", {
directoryForURISync: (uri) ->
if uri.startsWith("ssh://")
new DummyDirectory(uri)
else
null
})
localPath = temp.mkdirSync('local-path')
remotePath = "ssh://foreign-directory:8080/exists"
atom.project.setPaths([localPath, remotePath])
directories = atom.project.getDirectories()
expect(directories[0].getPath()).toBe localPath
expect(directories[0] instanceof Directory).toBe true
expect(directories[1].getPath()).toBe remotePath
expect(directories[1] instanceof DummyDirectory).toBe true
# Make sure that DummyDirectory.contains() is honored.
remotePathSubdirectory = remotePath + "a/subdirectory"
atom.project.addPath(remotePathSubdirectory)
expect(atom.project.getDirectories().length).toBe 2
# Make sure that a new DummyDirectory that is not contained by the first
# DummyDirectory can be added.
otherRemotePath = "ssh://other-foreign-directory:8080/"
atom.project.addPath(otherRemotePath)
newDirectories = atom.project.getDirectories()
expect(newDirectories.length).toBe 3
otherDummyDirectory = newDirectories[2]
expect(otherDummyDirectory.getPath()).toBe otherRemotePath
expect(otherDummyDirectory instanceof DummyDirectory).toBe true
describe "when a custom provider does not handle the path", ->
it "creates a local directory for the path", ->
directoryProvider =
directoryForURISync: (uri) -> null
directoryForURI: (uri) -> throw new Error("This should not be called.")
atom.packages.serviceHub.provide(
"atom.directory-provider", "0.1.0", directoryProvider)
tmp = temp.mkdirSync()
atom.project.setPaths([tmp])
directories = atom.project.getDirectories()
expect(directories.length).toBe 1
expect(directories[0].getPath()).toBe tmp
describe ".addPath(path)", ->
it "calls callbacks registered with ::onDidChangePaths", ->
onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy')

View File

@@ -15,7 +15,7 @@ describe "TextEditor", ->
it "properly renders soft-wrapped lines when randomly mutated", ->
times 10, (i) ->
buffer = new TextBuffer
editor = new TextEditor({buffer})
editor = atom.workspace.buildTextEditor({buffer})
editor.setEditorWidthInChars(80)
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
steps = []
@@ -80,7 +80,7 @@ describe "TextEditor", ->
text
getReferenceScreenLines = ->
referenceEditor = new TextEditor({})
referenceEditor = atom.workspace.buildTextEditor()
referenceEditor.setEditorWidthInChars(80)
referenceEditor.setText(editor.getText())
referenceEditor.setSoftWrapped(editor.isSoftWrapped())

View File

@@ -0,0 +1 @@
undefined

1
spec/sample.js Normal file
View File

@@ -0,0 +1 @@
undefined

View File

@@ -5,7 +5,7 @@ describe "Selection", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
editor = new TextEditor(buffer: buffer, tabLength: 2)
editor = atom.workspace.buildTextEditor(buffer: buffer, tabLength: 2)
selection = editor.getLastSelection()
afterEach ->

View File

@@ -1,30 +0,0 @@
# Start the crash reporter before anything else.
require('crash-reporter').start(productName: 'Atom', companyName: 'GitHub')
path = require 'path'
try
require '../src/window'
Atom = require '../src/atom'
window.atom = Atom.loadOrCreate('spec')
# Show window synchronously so a focusout doesn't fire on input elements
# that are focused in the very first spec run.
atom.getCurrentWindow().show() unless atom.getLoadSettings().exitWhenDone
{runSpecSuite} = require './jasmine-helper'
# Add 'exports' to module search path.
exportsPath = path.join(atom.getLoadSettings().resourcePath, 'exports')
require('module').globalPaths.push(exportsPath)
# Still set NODE_PATH since tasks may need it.
process.env.NODE_PATH = exportsPath
document.title = "Spec Suite"
runSpecSuite './spec-suite', atom.getLoadSettings().logFile
catch error
if atom?.getLoadSettings().exitWhenDone
console.error(error.stack ? error)
atom.exit(1)
else
throw error

View File

@@ -1,52 +1,28 @@
require '../src/window'
atom.initialize()
atom.restoreWindowDimensions()
require 'jasmine-json'
require '../src/window'
require '../vendor/jasmine-jquery'
path = require 'path'
_ = require 'underscore-plus'
fs = require 'fs-plus'
Grim = require 'grim'
KeymapManager = require '../src/keymap-extensions'
pathwatcher = require 'pathwatcher'
FindParentDir = require 'find-parent-dir'
Config = require '../src/config'
{Point} = require 'text-buffer'
Project = require '../src/project'
Workspace = require '../src/workspace'
ServiceHub = require 'service-hub'
TextEditor = require '../src/text-editor'
TextEditorElement = require '../src/text-editor-element'
TokenizedBuffer = require '../src/tokenized-buffer'
TextEditorComponent = require '../src/text-editor-component'
pathwatcher = require 'pathwatcher'
clipboard = require '../src/safe-clipboard'
atom.themes.loadBaseStylesheets()
atom.themes.requireStylesheet '../static/jasmine'
atom.themes.initialLoadComplete = true
jasmineStyle = document.createElement('style')
jasmineStyle.textContent = atom.themes.loadStylesheet(atom.themes.resolveStylesheet('../static/jasmine'))
document.head.appendChild(jasmineStyle)
fixturePackagesPath = path.resolve(__dirname, './fixtures/packages')
atom.packages.packageDirPaths.unshift(fixturePackagesPath)
atom.keymaps.loadBundledKeymaps()
keyBindingsToRestore = atom.keymaps.getKeyBindings()
commandsToRestore = atom.commands.getSnapshot()
styleElementsToRestore = atom.styles.getSnapshot()
window.addEventListener 'core:close', -> window.close()
window.addEventListener 'beforeunload', ->
atom.storeWindowDimensions()
atom.saveSync()
document.querySelector('html').style.overflow = 'auto'
document.body.style.overflow = 'auto'
# Allow document.title to be assigned in specs without screwing up spec window title
documentTitle = null
Object.defineProperty document, 'title',
get: -> documentTitle
set: (title) -> documentTitle = title
Set.prototype.jasmineToString = ->
result = "Set {"
first = true
@@ -73,46 +49,27 @@ if process.env.CI
else
jasmine.getEnv().defaultTimeoutInterval = 5000
specPackageName = null
specPackagePath = null
specProjectPath = null
isCoreSpec = false
{resourcePath, testPaths} = atom.getLoadSettings()
{specDirectory, resourcePath} = atom.getLoadSettings()
if specPackagePath = FindParentDir.sync(testPaths[0], 'package.json')
packageMetadata = require(path.join(specPackagePath, 'package.json'))
specPackageName = packageMetadata.name
if specDirectory
specPackagePath = path.resolve(specDirectory, '..')
try
specPackageName = JSON.parse(fs.readFileSync(path.join(specPackagePath, 'package.json')))?.name
if specDirectory = FindParentDir.sync(testPaths[0], 'fixtures')
specProjectPath = path.join(specDirectory, 'fixtures')
isCoreSpec = specDirectory is fs.realpathSync(__dirname)
else
specProjectPath = path.join(__dirname, 'fixtures')
beforeEach ->
documentTitle = null
projectPath = specProjectPath ? path.join(@specDirectory, 'fixtures')
atom.packages.serviceHub = new ServiceHub
atom.project = new Project(paths: [projectPath])
atom.workspace = new Workspace()
atom.keymaps.keyBindings = _.clone(keyBindingsToRestore)
atom.commands.restoreSnapshot(commandsToRestore)
atom.styles.restoreSnapshot(styleElementsToRestore)
atom.views.clearDocumentRequests()
atom.workspaceParentSelectorctor = '#jasmine-content'
atom.project.setPaths([specProjectPath])
window.resetTimeouts()
spyOn(_._, "now").andCallFake -> window.now
spyOn(window, "setTimeout").andCallFake window.fakeSetTimeout
spyOn(window, "clearTimeout").andCallFake window.fakeClearTimeout
atom.packages.packageStates = {}
serializedWindowState = null
spyOn(atom, 'saveSync')
atom.grammars.clearGrammarOverrides()
spy = spyOn(atom.packages, 'resolvePackagePath').andCallFake (packageName) ->
if specPackageName and packageName is specPackageName
resolvePackagePath(specPackagePath)
@@ -123,28 +80,20 @@ beforeEach ->
# 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`
spyOn(Config::, 'load')
spyOn(Config::, 'save')
config = new Config({resourcePath, configDirPath: atom.getConfigDirPath()})
atom.config = config
atom.loadConfig()
config.set "core.destroyEmptyPanes", false
config.set "editor.fontFamily", "Courier"
config.set "editor.fontSize", 16
config.set "editor.autoIndent", false
config.set "core.disabledPackages", ["package-that-throws-an-exception",
# reset config before each spec
atom.config.set "core.destroyEmptyPanes", false
atom.config.set "editor.fontFamily", "Courier"
atom.config.set "editor.fontSize", 16
atom.config.set "editor.autoIndent", false
atom.config.set "core.disabledPackages", ["package-that-throws-an-exception",
"package-with-broken-package-json", "package-with-broken-keymap"]
config.set "editor.useShadowDOM", true
atom.config.set "editor.useShadowDOM", true
advanceClock(1000)
window.setTimeout.reset()
config.load.reset()
config.save.reset()
# make editor display updates synchronous
TextEditorElement::setUpdatedSynchronously(true)
spyOn(atom, "setRepresentedFilename")
spyOn(pathwatcher.File.prototype, "detectResurrectionAfterDelay").andCallFake -> @detectResurrection()
spyOn(TextEditor.prototype, "shouldPromptToSave").andReturn false
@@ -159,25 +108,10 @@ beforeEach ->
addCustomMatchers(this)
afterEach ->
atom.packages.deactivatePackages()
atom.menu.template = []
atom.contextMenu.clear()
atom.notifications.clear()
atom.workspace?.destroy()
atom.workspace = null
delete atom.state.workspace
atom.project?.destroy()
atom.project = null
atom.themes.removeStylesheet('global-editor-styles')
delete atom.state.packageStates
atom.reset()
document.getElementById('jasmine-content').innerHTML = '' unless window.debugContent
jasmine.unspy(atom, 'saveSync')
ensureNoPathSubscriptions()
waits(0) # yield to ui thread to make screen update more frequently

View File

@@ -1,55 +0,0 @@
_ = require 'underscore-plus'
fs = require 'fs-plus'
path = require 'path'
require './spec-helper'
requireSpecs = (specDirectory, specType) ->
for specFilePath in fs.listTreeSync(specDirectory) when /-spec\.(coffee|js)$/.test specFilePath
require specFilePath
# Set spec directory on spec for setting up the project in spec-helper
setSpecDirectory(specDirectory)
setSpecField = (name, value) ->
specs = jasmine.getEnv().currentRunner().specs()
return if specs.length is 0
for index in [specs.length-1..0]
break if specs[index][name]?
specs[index][name] = value
setSpecType = (specType) ->
setSpecField('specType', specType)
setSpecDirectory = (specDirectory) ->
setSpecField('specDirectory', specDirectory)
runAllSpecs = ->
{resourcePath} = atom.getLoadSettings()
requireSpecs(path.join(resourcePath, 'spec'))
setSpecType('core')
fixturesPackagesPath = path.join(__dirname, 'fixtures', 'packages')
packagePaths = atom.packages.getAvailablePackageNames().map (packageName) ->
atom.packages.resolvePackagePath(packageName)
packagePaths = _.groupBy packagePaths, (packagePath) ->
if packagePath.indexOf("#{fixturesPackagesPath}#{path.sep}") is 0
'fixtures'
else if packagePath.indexOf("#{resourcePath}#{path.sep}") is 0
'bundled'
else
'user'
# Run bundled package specs
requireSpecs(path.join(packagePath, 'spec')) for packagePath in packagePaths.bundled ? []
setSpecType('bundled')
# Run user package specs
requireSpecs(path.join(packagePath, 'spec')) for packagePath in packagePaths.user ? []
setSpecType('user')
if specDirectory = atom.getLoadSettings().specDirectory
requireSpecs(specDirectory)
setSpecType('user')
else
runAllSpecs()

View File

@@ -4,7 +4,7 @@ describe "StyleManager", ->
[manager, addEvents, removeEvents, updateEvents] = []
beforeEach ->
manager = new StyleManager
manager = new StyleManager(configDirPath: atom.getConfigDirPath())
addEvents = []
removeEvents = []
updateEvents = []

View File

@@ -6,6 +6,7 @@ describe "StylesElement", ->
beforeEach ->
element = new StylesElement
element.initialize(atom.styles)
document.querySelector('#jasmine-content').appendChild(element)
addedStyleElements = []
removedStyleElements = []
@@ -99,8 +100,8 @@ describe "StylesElement", ->
it "defers selector upgrade until the element is attached", ->
element = new StylesElement
element.initialize(atom.styles)
element.setAttribute('context', 'atom-text-editor')
element.initialize()
atom.styles.addStyleSheet ".editor {background: black;}", context: 'atom-text-editor'
expect(element.firstChild.sheet).toBeNull()

View File

@@ -27,7 +27,7 @@ describe "TextEditorComponent", ->
fn()
waitsForPromise ->
atom.project.open('sample.js').then (o) -> editor = o
atom.workspace.open('sample.js').then (o) -> editor = o
runs ->
contentNode = document.querySelector('#jasmine-content')
@@ -35,7 +35,7 @@ describe "TextEditorComponent", ->
wrapperNode = new TextEditorElement()
wrapperNode.tileSize = tileSize
wrapperNode.initialize(editor)
wrapperNode.initialize(editor, atom)
wrapperNode.setUpdatedSynchronously(false)
jasmine.attachToDOM(wrapperNode)
@@ -54,6 +54,10 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
# Mutating the DOM in the previous frame causes a document poll; clear it here
waits 0
runs -> nextAnimationFrame()
afterEach ->
contentNode.style.width = ''
@@ -2891,6 +2895,18 @@ describe "TextEditorComponent", ->
expect(editor.consolidateSelections).toHaveBeenCalled()
expect(event.abortKeyBinding).toHaveBeenCalled()
describe "when changing the font", ->
it "measures the default char, the korean char, the double width char and the half width char widths", ->
expect(editor.getDefaultCharWidth()).toBeCloseTo(12, 0)
component.setFontSize(10)
nextAnimationFrame()
expect(editor.getDefaultCharWidth()).toBeCloseTo(6, 0)
expect(editor.getKoreanCharWidth()).toBeCloseTo(9, 0)
expect(editor.getDoubleWidthCharWidth()).toBe(10)
expect(editor.getHalfWidthCharWidth()).toBe(5)
describe "hiding and showing the editor", ->
describe "when the editor is hidden when it is mounted", ->
it "defers measurement and rendering until the editor becomes visible", ->
@@ -2902,7 +2918,7 @@ describe "TextEditorComponent", ->
wrapperNode = new TextEditorElement()
wrapperNode.tileSize = tileSize
wrapperNode.initialize(editor)
wrapperNode.initialize(editor, atom)
hiddenParent.appendChild(wrapperNode)
{component} = wrapperNode
@@ -3196,7 +3212,7 @@ describe "TextEditorComponent", ->
waitsForPromise ->
atom.packages.activatePackage('language-coffee-script')
waitsForPromise ->
atom.project.open('coffee.coffee', autoIndent: false).then (o) -> coffeeEditor = o
atom.workspace.open('coffee.coffee', autoIndent: false).then (o) -> coffeeEditor = o
afterEach: ->
atom.packages.deactivatePackages()
@@ -3208,7 +3224,9 @@ describe "TextEditorComponent", ->
atom.config.set 'editor.preferredLineLength', 17, scopeSelector: '.source.coffee'
atom.config.set 'editor.softWrapAtPreferredLineLength', true, scopeSelector: '.source.coffee'
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(20)
coffeeEditor.setDefaultCharWidth(1)
coffeeEditor.setEditorWidthInChars(20)
it "wraps lines when editor.softWrap is true for a matching scope", ->
@@ -3464,12 +3482,14 @@ describe "TextEditorComponent", ->
editor.moveRight()
nextAnimationFrame()
right = wrapperNode.pixelPositionForScreenPosition([0, 6]).left
margin = component.presenter.getHorizontalScrollMarginInPixels()
right = wrapperNode.pixelPositionForScreenPosition([0, 4]).left + margin
expect(wrapperNode.getScrollRight()).toBeCloseTo right, 0
editor.moveRight()
nextAnimationFrame()
right = wrapperNode.pixelPositionForScreenPosition([0, 7]).left
right = wrapperNode.pixelPositionForScreenPosition([0, 5]).left + margin
expect(wrapperNode.getScrollRight()).toBeCloseTo right, 0
it "scrolls left when the last cursor gets closer than ::horizontalScrollMargin to the left of the editor", ->
@@ -3481,12 +3501,14 @@ describe "TextEditorComponent", ->
editor.moveLeft()
nextAnimationFrame()
left = wrapperNode.pixelPositionForScreenPosition([6, 59]).left
margin = component.presenter.getHorizontalScrollMarginInPixels()
left = wrapperNode.pixelPositionForScreenPosition([6, 61]).left - margin
expect(wrapperNode.getScrollLeft()).toBeCloseTo left, 0
editor.moveLeft()
nextAnimationFrame()
left = wrapperNode.pixelPositionForScreenPosition([6, 58]).left
left = wrapperNode.pixelPositionForScreenPosition([6, 60]).left - margin
expect(wrapperNode.getScrollLeft()).toBeCloseTo left, 0
it "scrolls down when inserting lines makes the document longer than the editor's height", ->

View File

@@ -1,5 +1,6 @@
TextEditorElement = require '../src/text-editor-element'
TextEditor = require '../src/text-editor'
{Disposable} = require 'event-kit'
# The rest of text-editor-component-spec will be moved to this file when React
# is eliminated. This covers only concerns related to the wrapper element for now
@@ -33,7 +34,7 @@ describe "TextEditorElement", ->
describe "when the model is assigned", ->
it "adds the 'mini' attribute if .isMini() returns true on the model", ->
element = new TextEditorElement
model = new TextEditor(mini: true)
model = atom.workspace.buildTextEditor(mini: true)
element.setModel(model)
expect(element.hasAttribute('mini')).toBe true
@@ -67,7 +68,7 @@ describe "TextEditorElement", ->
describe "when the editor is detached from the DOM and then reattached", ->
it "does not render duplicate line numbers", ->
editor = new TextEditor
editor = atom.workspace.buildTextEditor()
editor.setText('1\n2\n3')
element = atom.views.getView(editor)
@@ -80,7 +81,7 @@ describe "TextEditorElement", ->
expect(element.shadowRoot.querySelectorAll('.line-number').length).toBe initialCount
it "does not render duplicate decorations in custom gutters", ->
editor = new TextEditor
editor = atom.workspace.buildTextEditor()
editor.setText('1\n2\n3')
editor.addGutter({name: 'test-gutter'})
marker = editor.markBufferRange([[0, 0], [2, 0]])
@@ -159,6 +160,7 @@ describe "TextEditorElement", ->
initialThemeLoadComplete
spyOn(atom.themes, 'onDidChangeActiveThemes').andCallFake (fn) ->
themeReloadCallback = fn
new Disposable
atom.config.set("editor.useShadowDOM", false)
@@ -234,6 +236,27 @@ describe "TextEditorElement", ->
jasmine.attachToDOM(element)
expect(element.getDefaultCharacterWidth()).toBeGreaterThan(0)
describe "::getMaxScrollTop", ->
it "returns the maximum scroll top that can be applied to the element", ->
editor = atom.workspace.buildTextEditor()
editor.setText('1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16')
element = atom.views.getView(editor)
element.style.lineHeight = "10px"
element.style.width = "200px"
expect(element.getMaxScrollTop()).toBe(0)
jasmine.attachToDOM(element)
element.setHeight(100)
expect(element.getMaxScrollTop()).toBe(60)
element.setHeight(120)
expect(element.getMaxScrollTop()).toBe(40)
element.setHeight(200)
expect(element.getMaxScrollTop()).toBe(0)
describe "on TextEditor::setMini", ->
it "changes the element's 'mini' attribute", ->
element = new TextEditorElement

View File

@@ -18,7 +18,7 @@ describe "TextEditorPresenter", ->
spyOn(window, "clearInterval").andCallFake window.fakeClearInterval
buffer = new TextBuffer(filePath: require.resolve('./fixtures/sample.js'))
editor = new TextEditor({buffer})
editor = atom.workspace.buildTextEditor({buffer})
waitsForPromise -> buffer.load()
afterEach ->
@@ -40,6 +40,7 @@ describe "TextEditorPresenter", ->
verticalScrollbarWidth: 10
scrollTop: 0
scrollLeft: 0
config: atom.config
presenter = new TextEditorPresenter(params)
presenter.setLinesYardstick(new FakeLinesYardstick(editor, presenter))
@@ -724,6 +725,20 @@ describe "TextEditorPresenter", ->
expect(presenter.getState().content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1
describe ".scrollTop", ->
it "doesn't get stuck when repeatedly setting the same non-integer position in a scroll event listener", ->
presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 20)
expect(presenter.getState().content.scrollTop).toBe(0)
presenter.onDidChangeScrollTop ->
presenter.setScrollTop(1.5)
presenter.getState() # trigger scroll update
presenter.setScrollTop(1.5)
presenter.getState() # trigger scroll update
expect(presenter.getScrollTop()).toBe(2)
expect(presenter.getRealScrollTop()).toBe(1.5)
it "changes based on the scroll operation that was performed last", ->
presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 20)
expect(presenter.getState().content.scrollTop).toBe(0)
@@ -837,6 +852,20 @@ describe "TextEditorPresenter", ->
expect(presenter.getState().content.scrollTop).toBe presenter.contentHeight - presenter.clientHeight
describe ".scrollLeft", ->
it "doesn't get stuck when repeatedly setting the same non-integer position in a scroll event listener", ->
presenter = buildPresenter(scrollLeft: 0, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 10)
expect(presenter.getState().content.scrollLeft).toBe(0)
presenter.onDidChangeScrollLeft ->
presenter.setScrollLeft(1.5)
presenter.getState() # trigger scroll update
presenter.setScrollLeft(1.5)
presenter.getState() # trigger scroll update
expect(presenter.getScrollLeft()).toBe(2)
expect(presenter.getRealScrollLeft()).toBe(1.5)
it "changes based on the scroll operation that was performed last", ->
presenter = buildPresenter(scrollLeft: 0, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 10)
expect(presenter.getState().content.scrollLeft).toBe(0)
@@ -1230,6 +1259,7 @@ describe "TextEditorPresenter", ->
it "only applies decorations to screen rows that are spanned by their marker when lines are soft-wrapped", ->
editor.setText("a line that wraps, ok")
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(16)
marker = editor.markBufferRange([[0, 0], [0, 2]])
editor.decorateMarker(marker, type: 'line', class: 'a')
@@ -2215,6 +2245,7 @@ describe "TextEditorPresenter", ->
it "contains states for line numbers that are visible on screen", ->
editor.foldBufferRow(4)
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(50)
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineHeight: 10, tileSize: 2)
@@ -2230,6 +2261,7 @@ describe "TextEditorPresenter", ->
it "updates when the editor's content changes", ->
editor.foldBufferRow(4)
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(50)
presenter = buildPresenter(explicitHeight: 35, scrollTop: 30, tileSize: 2)
@@ -2260,6 +2292,7 @@ describe "TextEditorPresenter", ->
it "correctly handles the first screen line being soft-wrapped", ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(30)
presenter = buildPresenter(explicitHeight: 25, scrollTop: 50, tileSize: 2)
@@ -2388,6 +2421,7 @@ describe "TextEditorPresenter", ->
it "only applies line-number decorations to screen rows that are spanned by their marker when lines are soft-wrapped", ->
editor.setText("a line that wraps, ok")
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(16)
marker = editor.markBufferRange([[0, 0], [0, 2]])
editor.decorateMarker(marker, type: 'line-number', class: 'a')
@@ -2827,7 +2861,7 @@ describe "TextEditorPresenter", ->
performSetup = ->
buffer = new TextBuffer
editor = new TextEditor({buffer})
editor = atom.workspace.buildTextEditor({buffer})
editor.setEditorWidthInChars(80)
presenterParams =
model: editor

View File

@@ -12,7 +12,7 @@ describe "TextEditor", ->
beforeEach ->
waitsForPromise ->
atom.project.open('sample.js', autoIndent: false).then (o) -> editor = o
atom.workspace.open('sample.js', autoIndent: false).then (o) -> editor = o
runs ->
buffer = editor.buffer
@@ -27,11 +27,11 @@ describe "TextEditor", ->
editor1 = null
waitsForPromise ->
atom.project.open(pathToOpen).then (o) -> editor1 = o
atom.workspace.open(pathToOpen).then (o) -> editor1 = o
runs ->
fs.mkdirSync(pathToOpen)
expect(TextEditor.deserialize(editor1.serialize())).toBeUndefined()
expect(TextEditor.deserialize(editor1.serialize(), atom)).toBeUndefined()
it "restores selections and folds based on markers in the buffer", ->
editor.setSelectedBufferRange([[1, 2], [3, 4]])
@@ -39,7 +39,7 @@ describe "TextEditor", ->
editor.foldBufferRow(4)
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
editor2 = TextEditor.deserialize(editor.serialize())
editor2 = TextEditor.deserialize(editor.serialize(), atom)
expect(editor2.id).toBe editor.id
expect(editor2.getBuffer().getPath()).toBe editor.getBuffer().getPath()
@@ -52,7 +52,7 @@ describe "TextEditor", ->
atom.config.set('editor.showInvisibles', true)
previousInvisibles = editor.tokenizedLineForScreenRow(0).invisibles
editor2 = TextEditor.deserialize(editor.serialize())
editor2 = TextEditor.deserialize(editor.serialize(), atom)
expect(previousInvisibles).toBeDefined()
expect(editor2.displayBuffer.tokenizedLineForScreenRow(0).invisibles).toEqual previousInvisibles
@@ -62,7 +62,7 @@ describe "TextEditor", ->
state = editor.serialize()
atom.config.set('editor.invisibles', eol: '?')
editor2 = TextEditor.deserialize(state)
editor2 = TextEditor.deserialize(state, atom)
expect(editor.tokenizedLineForScreenRow(0).invisibles.eol).toBe '?'
@@ -71,7 +71,7 @@ describe "TextEditor", ->
editor = null
waitsForPromise ->
atom.workspace.open('sample.js', largeFileMode: true).then (o) -> editor = o
atom.workspace.openTextFile('sample.js', largeFileMode: true).then (o) -> editor = o
runs ->
buffer = editor.getBuffer()
@@ -114,7 +114,7 @@ describe "TextEditor", ->
atom.config.set('core.fileEncoding', 'utf16le')
waitsForPromise ->
atom.workspace.open('a').then (o) -> editor1 = o
atom.workspace.open('dir/a').then (o) -> editor1 = o
runs ->
expect(editor1.getTabLength()).toBe 4
@@ -128,7 +128,7 @@ describe "TextEditor", ->
atom.config.set('core.fileEncoding', 'macroman')
waitsForPromise ->
atom.workspace.open('b').then (o) -> editor2 = o
atom.workspace.open('dir/b').then (o) -> editor2 = o
runs ->
expect(editor2.getTabLength()).toBe 8
@@ -144,13 +144,13 @@ describe "TextEditor", ->
atom.config.set('core.fileEncoding', 'macroman', scopeSelector: '.js')
waitsForPromise ->
atom.workspace.open('a').then (o) -> editor1 = o
atom.workspace.open('dir/a').then (o) -> editor1 = o
runs ->
expect(editor1.getEncoding()).toBe 'utf16le'
waitsForPromise ->
atom.workspace.open('test.js').then (o) -> editor2 = o
atom.workspace.open('sample-with-comments.js').then (o) -> editor2 = o
runs ->
expect(editor2.getEncoding()).toBe 'macroman'
@@ -272,6 +272,7 @@ describe "TextEditor", ->
describe "when soft-wrap is enabled and code is folded", ->
beforeEach ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(50)
editor.createFold(2, 3)
@@ -327,6 +328,7 @@ describe "TextEditor", ->
describe "when the cursor was moved down from the beginning of an indented soft-wrapped line", ->
it "moves to the beginning of the previous line", ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(50)
editor.setCursorScreenPosition([3, 0])
@@ -379,6 +381,7 @@ describe "TextEditor", ->
describe "when the cursor is at the beginning of an indented soft-wrapped line", ->
it "moves to the beginning of the line's continuation on the next screen row", ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(50)
editor.setCursorScreenPosition([3, 0])
@@ -446,6 +449,7 @@ describe "TextEditor", ->
describe "when line is wrapped and follow previous line indentation", ->
beforeEach ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(50)
it "wraps to the end of the previous line", ->
@@ -604,6 +608,7 @@ describe "TextEditor", ->
describe "when soft wrap is on", ->
it "moves cursor to the beginning of the screen line", ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(10)
editor.setCursorScreenPosition([1, 2])
editor.moveToEndOfScreenLine()
@@ -623,6 +628,7 @@ describe "TextEditor", ->
describe ".moveToBeginningOfLine()", ->
it "moves cursor to the beginning of the buffer line", ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(10)
editor.setCursorScreenPosition([1, 2])
editor.moveToBeginningOfLine()
@@ -632,6 +638,7 @@ describe "TextEditor", ->
describe ".moveToEndOfLine()", ->
it "moves cursor to the end of the buffer line", ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(10)
editor.setCursorScreenPosition([0, 2])
editor.moveToEndOfLine()
@@ -642,6 +649,7 @@ describe "TextEditor", ->
describe "when soft wrap is on", ->
it "moves to the first character of the current screen line or the beginning of the screen line if it's already on the first character", ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(10)
editor.setCursorScreenPosition [2, 5]
editor.addCursorAtScreenPosition [8, 7]
@@ -1331,7 +1339,7 @@ describe "TextEditor", ->
waitsForPromise ->
atom.packages.activatePackage('language-coffee-script')
waitsForPromise ->
atom.project.open('coffee.coffee', autoIndent: false).then (o) -> coffeeEditor = o
atom.workspace.open('coffee.coffee', autoIndent: false).then (o) -> coffeeEditor = o
it 'selects the correct surrounding word for the given scoped setting', ->
coffeeEditor.setCursorBufferPosition [0, 9] # in the middle of quicksort
@@ -1523,6 +1531,7 @@ describe "TextEditor", ->
it "can add selections to soft-wrapped line segments", ->
editor.setSoftWrapped(true)
editor.setEditorWidthInChars(40)
editor.setDefaultCharWidth(1)
editor.setSelectedScreenRange([[3, 10], [3, 15]])
editor.addSelectionBelow()
@@ -1533,7 +1542,7 @@ describe "TextEditor", ->
it "takes atomic tokens into account", ->
waitsForPromise ->
atom.project.open('sample-with-tabs-and-leading-comment.coffee', autoIndent: false).then (o) -> editor = o
atom.workspace.open('sample-with-tabs-and-leading-comment.coffee', autoIndent: false).then (o) -> editor = o
runs ->
editor.setSelectedBufferRange([[2, 1], [2, 3]])
@@ -1548,6 +1557,7 @@ describe "TextEditor", ->
describe "when lines are soft-wrapped", ->
beforeEach ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(40)
it "skips soft-wrap indentation tokens", ->
@@ -1633,6 +1643,7 @@ describe "TextEditor", ->
it "can add selections to soft-wrapped line segments", ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(40)
editor.setSelectedScreenRange([[4, 10], [4, 15]])
@@ -1644,7 +1655,7 @@ describe "TextEditor", ->
it "takes atomic tokens into account", ->
waitsForPromise ->
atom.project.open('sample-with-tabs-and-leading-comment.coffee', autoIndent: false).then (o) -> editor = o
atom.workspace.open('sample-with-tabs-and-leading-comment.coffee', autoIndent: false).then (o) -> editor = o
runs ->
editor.setSelectedBufferRange([[3, 1], [3, 2]])
@@ -1659,6 +1670,7 @@ describe "TextEditor", ->
describe "when lines are soft-wrapped", ->
beforeEach ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(40)
it "skips soft-wrap indentation tokens", ->
@@ -1769,9 +1781,11 @@ describe "TextEditor", ->
it "does not share selections between different edit sessions for the same buffer", ->
editor2 = null
waitsForPromise ->
atom.project.open('sample.js').then (o) -> editor2 = o
atom.workspace.getActivePane().splitRight()
atom.workspace.open(editor.getPath()).then (o) -> editor2 = o
runs ->
expect(editor2.getText()).toBe(editor.getText())
editor.setSelectedBufferRanges([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
editor2.setSelectedBufferRanges([[[8, 7], [6, 5]], [[4, 3], [2, 1]]])
expect(editor2.getSelectedBufferRanges()).not.toEqual editor.getSelectedBufferRanges()
@@ -2703,6 +2717,7 @@ describe "TextEditor", ->
describe "when soft wrap is on", ->
it "cuts up to the end of the line", ->
editor.setSoftWrapped(true)
editor.setDefaultCharWidth(1)
editor.setEditorWidthInChars(10)
editor.setCursorScreenPosition([2, 2])
editor.cutToEndOfLine()
@@ -3673,7 +3688,7 @@ describe "TextEditor", ->
editor.destroy()
waitsForPromise ->
atom.project.open('sample-with-tabs-and-leading-comment.coffee').then (o) -> editor = o
atom.workspace.open('sample-with-tabs-and-leading-comment.coffee').then (o) -> editor = o
runs ->
expect(editor.softTabs).toBe true
@@ -3746,7 +3761,7 @@ describe "TextEditor", ->
editor.destroy()
waitsForPromise ->
atom.project.open('sample-with-tabs-and-leading-comment.coffee').then (o) -> editor = o
atom.workspace.open('sample-with-tabs-and-leading-comment.coffee').then (o) -> editor = o
runs ->
expect(editor.softTabs).toBe true
@@ -3801,7 +3816,7 @@ describe "TextEditor", ->
waitsForPromise ->
atom.packages.activatePackage('language-coffee-script')
waitsForPromise ->
atom.project.open('coffee.coffee', autoIndent: false).then (o) -> coffeeEditor = o
atom.workspace.open('coffee.coffee', autoIndent: false).then (o) -> coffeeEditor = o
afterEach: ->
atom.packages.deactivatePackages()
@@ -4004,7 +4019,7 @@ describe "TextEditor", ->
waitsForPromise ->
atom.packages.activatePackage('language-coffee-script')
waitsForPromise ->
atom.project.open('coffee.coffee', autoIndent: false).then (o) -> coffeeEditor = o
atom.workspace.open('coffee.coffee', autoIndent: false).then (o) -> coffeeEditor = o
runs ->
atom.config.set('editor.autoIndent', true, scopeSelector: '.source.js')
@@ -4154,7 +4169,8 @@ describe "TextEditor", ->
editor2 = null
waitsForPromise ->
atom.project.open('sample.js', autoIndent: false).then (o) -> editor2 = o
atom.workspace.getActivePane().splitRight()
atom.workspace.open('sample.js', autoIndent: false).then (o) -> editor2 = o
runs ->
expect(editor.shouldPromptToSave()).toBeFalsy()
@@ -4408,11 +4424,10 @@ describe "TextEditor", ->
describe '.get/setPlaceholderText()', ->
it 'can be created with placeholderText', ->
TextBuffer = require 'text-buffer'
newEditor = new TextEditor
buffer: new TextBuffer
newEditor = atom.workspace.buildTextEditor(
mini: true
placeholderText: 'yep'
)
expect(newEditor.getPlaceholderText()).toBe 'yep'
it 'models placeholderText and emits an event when changed', ->
@@ -4440,7 +4455,7 @@ describe "TextEditor", ->
describe "when there's no repository for the editor's file", ->
it "doesn't do anything", ->
editor = new TextEditor({})
editor = atom.workspace.buildTextEditor()
editor.setText("stuff")
editor.checkoutHeadRevision()

View File

@@ -44,3 +44,33 @@ describe 'text utilities', ->
expect(textUtils.isPairedCharacter('ae\u0301c', 2)).toBe false
expect(textUtils.isPairedCharacter('ae\u0301c', 3)).toBe false
expect(textUtils.isPairedCharacter('ae\u0301c', 4)).toBe false
describe ".isDoubleWidthCharacter(character)", ->
it "returns true when the character is either japanese, chinese or a full width form", ->
expect(textUtils.isDoubleWidthCharacter("")).toBe(true)
expect(textUtils.isDoubleWidthCharacter("")).toBe(true)
expect(textUtils.isDoubleWidthCharacter("")).toBe(true)
expect(textUtils.isDoubleWidthCharacter("")).toBe(true)
expect(textUtils.isDoubleWidthCharacter("")).toBe(true)
expect(textUtils.isDoubleWidthCharacter("a")).toBe(false)
describe ".isHalfWidthCharacter(character)", ->
it "returns true when the character is an half width form", ->
expect(textUtils.isHalfWidthCharacter("")).toBe(true)
expect(textUtils.isHalfWidthCharacter("")).toBe(true)
expect(textUtils.isHalfWidthCharacter("")).toBe(true)
expect(textUtils.isHalfWidthCharacter("")).toBe(true)
expect(textUtils.isHalfWidthCharacter("B")).toBe(false)
describe ".isKoreanCharacter(character)", ->
it "returns true when the character is a korean character", ->
expect(textUtils.isKoreanCharacter("")).toBe(true)
expect(textUtils.isKoreanCharacter("")).toBe(true)
expect(textUtils.isKoreanCharacter("")).toBe(true)
expect(textUtils.isKoreanCharacter("")).toBe(true)
expect(textUtils.isKoreanCharacter("O")).toBe(false)

View File

@@ -24,7 +24,9 @@ describe "TokenIterator", ->
end x
x
""")
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
tokenizedBuffer.setGrammar(grammar)
tokenIterator = tokenizedBuffer.tokenizedLineForRow(1).getTokenIterator()

View File

@@ -27,7 +27,9 @@ describe "TokenizedBuffer", ->
describe "when the buffer is destroyed", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
startTokenizing(tokenizedBuffer)
it "stops tokenization", ->
@@ -39,7 +41,9 @@ describe "TokenizedBuffer", ->
describe "when the buffer contains soft-tabs", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
startTokenizing(tokenizedBuffer)
tokenizedBuffer.onDidChange changeHandler = jasmine.createSpy('changeHandler')
@@ -345,7 +349,9 @@ describe "TokenizedBuffer", ->
runs ->
buffer = atom.project.bufferForPathSync('sample-with-tabs.coffee')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
startTokenizing(tokenizedBuffer)
afterEach ->
@@ -450,7 +456,9 @@ describe "TokenizedBuffer", ->
'abc\uD835\uDF97def'
//\uD835\uDF97xyz
"""
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
fullyTokenize(tokenizedBuffer)
afterEach ->
@@ -487,7 +495,7 @@ describe "TokenizedBuffer", ->
tokenizedHandler = jasmine.createSpy("tokenized handler")
waitsForPromise ->
atom.project.open('sample.js').then (o) -> editor = o
atom.workspace.open('sample.js').then (o) -> editor = o
runs ->
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
@@ -500,7 +508,7 @@ describe "TokenizedBuffer", ->
tokenizedHandler = jasmine.createSpy("tokenized handler")
waitsForPromise ->
atom.project.open('sample.js').then (o) -> editor = o
atom.workspace.open('sample.js').then (o) -> editor = o
runs ->
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
@@ -518,7 +526,7 @@ describe "TokenizedBuffer", ->
tokenizedHandler = jasmine.createSpy("tokenized handler")
waitsForPromise ->
atom.project.open('coffee.coffee').then (o) -> editor = o
atom.workspace.open('coffee.coffee').then (o) -> editor = o
runs ->
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
@@ -544,7 +552,9 @@ describe "TokenizedBuffer", ->
runs ->
buffer = atom.project.bufferForPathSync()
buffer.setText "<div class='name'><%= User.find(2).full_name %></div>"
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
tokenizedBuffer.setGrammar(atom.grammars.selectGrammar('test.erb'))
fullyTokenize(tokenizedBuffer)
@@ -566,7 +576,9 @@ describe "TokenizedBuffer", ->
it "returns the correct token (regression)", ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenForPosition([1, 0]).scopes).toEqual ["source.js"]
expect(tokenizedBuffer.tokenForPosition([1, 1]).scopes).toEqual ["source.js"]
@@ -575,7 +587,10 @@ describe "TokenizedBuffer", ->
describe ".bufferRangeForScopeAtPosition(selector, position)", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars,
packageManager: atom.packages, assert: atom.assert
})
fullyTokenize(tokenizedBuffer)
describe "when the selector does not match the token at the position", ->
@@ -595,7 +610,9 @@ describe "TokenizedBuffer", ->
it "updates the tab length of the tokenized lines", ->
buffer = atom.project.bufferForPathSync('sample.js')
buffer.setText('\ttest')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenForPosition([0, 0]).value).toBe ' '
atom.config.set('editor.tabLength', 6)
@@ -604,7 +621,9 @@ describe "TokenizedBuffer", ->
it "does not allow the tab length to be less than 1", ->
buffer = atom.project.bufferForPathSync('sample.js')
buffer.setText('\ttest')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenForPosition([0, 0]).value).toBe ' '
atom.config.set('editor.tabLength', 1)
@@ -617,7 +636,9 @@ describe "TokenizedBuffer", ->
it "updates the tokens with the appropriate invisible characters", ->
buffer = new TextBuffer(text: " \t a line with tabs\tand \tspaces \t ")
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
fullyTokenize(tokenizedBuffer)
atom.config.set("editor.showInvisibles", true)
@@ -630,7 +651,9 @@ describe "TokenizedBuffer", ->
it "assigns endOfLineInvisibles to tokenized lines", ->
buffer = new TextBuffer(text: "a line that ends in a carriage-return-line-feed \r\na line that ends in just a line-feed\na line with no ending")
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
atom.config.set('editor.showInvisibles', true)
atom.config.set("editor.invisibles", cr: 'R', eol: 'N')
@@ -651,7 +674,9 @@ describe "TokenizedBuffer", ->
describe "leading and trailing whitespace", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
fullyTokenize(tokenizedBuffer)
it "assigns ::firstNonWhitespaceIndex on tokens that have leading whitespace", ->
@@ -709,7 +734,9 @@ describe "TokenizedBuffer", ->
describe ".indentLevel on tokenized lines", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
fullyTokenize(tokenizedBuffer)
describe "when the line is non-empty", ->
@@ -804,7 +831,9 @@ describe "TokenizedBuffer", ->
buffer = atom.project.bufferForPathSync('sample.js')
buffer.insert [10, 0], " // multi-line\n // comment\n // block\n"
buffer.insert [0, 0], "// multi-line\n// comment\n// block\n"
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
fullyTokenize(tokenizedBuffer)
tokenizedBuffer.onDidChange (change) ->
delete change.bufferChange
@@ -881,7 +910,9 @@ describe "TokenizedBuffer", ->
buffer = atom.project.bufferForPathSync('sample.will-use-the-null-grammar')
buffer.setText('a\nb\nc')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = new TokenizedBuffer({
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
tokenizeCallback = jasmine.createSpy('onDidTokenize')
tokenizedBuffer.onDidTokenize(tokenizeCallback)
@@ -905,7 +936,7 @@ describe "TokenizedBuffer", ->
registration = atom.packages.onDidTriggerActivationHook('language-javascript:grammar-used', -> called = true)
waitsForPromise ->
atom.project.open('sample.js', autoIndent: false).then (o) ->
atom.workspace.open('sample.js', autoIndent: false).then (o) ->
editor = o
waitsForPromise ->

View File

@@ -7,7 +7,7 @@ describe "TokenizedLine", ->
describe "::isOnlyWhitespace()", ->
beforeEach ->
waitsForPromise ->
atom.project.open('coffee.coffee').then (o) -> editor = o
atom.workspace.open('coffee.coffee').then (o) -> editor = o
it "returns true when the line is only whitespace", ->
expect(editor.tokenizedLineForScreenRow(3).isOnlyWhitespace()).toBe true

View File

@@ -8,7 +8,7 @@ describe "TooltipManager", ->
ctrlY = _.humanizeKeystroke("ctrl-y")
beforeEach ->
manager = new TooltipManager
manager = new TooltipManager(keymapManager: atom.keymaps)
element = document.createElement('div')
element.classList.add('foo')
jasmine.attachToDOM(element)

View File

@@ -4,11 +4,13 @@ fs = require 'fs-plus'
temp = require 'temp'
TextEditor = require '../src/text-editor'
WindowEventHandler = require '../src/window-event-handler'
ipc = require 'ipc'
describe "Window", ->
describe "WindowEventHandler", ->
[projectPath, windowEventHandler] = []
beforeEach ->
atom.uninstallWindowEventHandler()
spyOn(atom, 'hide')
initialPath = atom.project.getPaths()[0]
spyOn(atom, 'getLoadSettings').andCallFake ->
@@ -16,12 +18,12 @@ describe "Window", ->
loadSettings.initialPath = initialPath
loadSettings
atom.project.destroy()
atom.windowEventHandler.unsubscribe()
windowEventHandler = new WindowEventHandler
windowEventHandler = new WindowEventHandler({atomEnvironment: atom, applicationDelegate: atom.applicationDelegate, window, document})
projectPath = atom.project.getPaths()[0]
afterEach ->
windowEventHandler.unsubscribe()
atom.installWindowEventHandler()
describe "when the window is loaded", ->
it "doesn't have .is-blurred on the body tag", ->
@@ -51,64 +53,25 @@ describe "Window", ->
describe "beforeunload event", ->
beforeEach ->
jasmine.unspy(TextEditor.prototype, "shouldPromptToSave")
spyOn(ipc, 'send')
describe "when pane items are modified", ->
it "prompts user to save and calls atom.workspace.confirmClose", ->
editor = null
spyOn(atom.workspace, 'confirmClose').andCallThrough()
spyOn(atom, "confirm").andReturn(2)
editor = null
beforeEach ->
waitsForPromise -> atom.workspace.open("sample.js").then (o) -> editor = o
runs -> editor.insertText("I look different, I feel different.")
waitsForPromise ->
atom.workspace.open("sample.js").then (o) -> editor = o
it "prompts the user to save them, and allows the unload to continue if they confirm", ->
spyOn(atom.workspace, 'confirmClose').andReturn(true)
window.dispatchEvent(new CustomEvent('beforeunload'))
expect(atom.workspace.confirmClose).toHaveBeenCalled()
expect(ipc.send).not.toHaveBeenCalledWith('did-cancel-window-unload')
runs ->
editor.insertText("I look different, I feel different.")
window.dispatchEvent(new CustomEvent('beforeunload'))
expect(atom.workspace.confirmClose).toHaveBeenCalled()
expect(atom.confirm).toHaveBeenCalled()
it "prompts user to save and handler returns true if don't save", ->
editor = null
spyOn(atom, "confirm").andReturn(2)
waitsForPromise ->
atom.workspace.open("sample.js").then (o) -> editor = o
runs ->
editor.insertText("I look different, I feel different.")
window.dispatchEvent(new CustomEvent('beforeunload'))
expect(atom.confirm).toHaveBeenCalled()
it "prompts user to save and handler returns false if dialog is canceled", ->
editor = null
spyOn(atom, "confirm").andReturn(1)
waitsForPromise ->
atom.workspace.open("sample.js").then (o) -> editor = o
runs ->
editor.insertText("I look different, I feel different.")
window.dispatchEvent(new CustomEvent('beforeunload'))
expect(atom.confirm).toHaveBeenCalled()
describe "when the same path is modified in multiple panes", ->
it "prompts to save the item", ->
return
editor = null
filePath = path.join(temp.mkdirSync('atom-file'), 'file.txt')
fs.writeFileSync(filePath, 'hello')
spyOn(atom.workspace, 'confirmClose').andCallThrough()
spyOn(atom, 'confirm').andReturn(0)
waitsForPromise ->
atom.workspace.open(filePath).then (o) -> editor = o
runs ->
atom.workspace.getActivePane().splitRight(copyActiveItem: true)
editor.setText('world')
window.dispatchEvent(new CustomEvent('beforeunload'))
expect(atom.workspace.confirmClose).toHaveBeenCalled()
expect(atom.confirm.callCount).toBe 1
expect(fs.readFileSync(filePath, 'utf8')).toBe 'world'
it "cancels the unload if the user selects cancel", ->
spyOn(atom.workspace, 'confirmClose').andReturn(false)
window.dispatchEvent(new CustomEvent('beforeunload'))
expect(atom.workspace.confirmClose).toHaveBeenCalled()
expect(ipc.send).toHaveBeenCalledWith('did-cancel-window-unload')
describe "when a link is clicked", ->
it "opens the http/https links in an external application", ->
@@ -225,62 +188,6 @@ describe "Window", ->
elements.dispatchEvent(new CustomEvent("core:focus-previous", bubbles: true))
expect(document.activeElement.tabIndex).toBe 7
describe "the window:open-locations event", ->
beforeEach ->
spyOn(atom.workspace, 'open')
atom.project.setPaths([])
describe "when the opened path exists", ->
it "adds it to the project's paths", ->
pathToOpen = __filename
atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
waitsFor ->
atom.project.getPaths().length is 1
runs ->
expect(atom.project.getPaths()[0]).toBe __dirname
describe "when the opened path does not exist but its parent directory does", ->
it "adds the parent directory to the project paths", ->
pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt')
atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
waitsFor ->
atom.project.getPaths().length is 1
runs ->
expect(atom.project.getPaths()[0]).toBe __dirname
describe "when the opened path is a file", ->
it "opens it in the workspace", ->
pathToOpen = __filename
atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
waitsFor ->
atom.workspace.open.callCount is 1
runs ->
expect(atom.workspace.open.mostRecentCall.args[0]).toBe __filename
describe "when the opened path is a directory", ->
it "does not open it in the workspace", ->
pathToOpen = __dirname
atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
expect(atom.workspace.open.callCount).toBe 0
describe "when the opened path is a uri", ->
it "adds it to the project's paths as is", ->
pathToOpen = 'remote://server:7644/some/dir/path'
atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
waitsFor ->
atom.project.getPaths().length is 1
runs ->
expect(atom.project.getPaths()[0]).toBe pathToOpen
describe "when keydown events occur on the document", ->
it "dispatches the event via the KeymapManager and CommandRegistry", ->
dispatchedCommands = []

View File

@@ -1,6 +1,7 @@
path = require 'path'
temp = require 'temp'
Workspace = require '../src/workspace'
Project = require '../src/project'
Pane = require '../src/pane'
platform = require './spec-helper-platform'
_ = require 'underscore-plus'
@@ -8,11 +9,14 @@ fstream = require 'fstream'
fs = require 'fs-plus'
describe "Workspace", ->
workspace = null
[workspace, setDocumentEdited] = []
beforeEach ->
workspace = atom.workspace
workspace.resetFontSize()
spyOn(atom.applicationDelegate, "confirm")
setDocumentEdited = spyOn(atom.applicationDelegate, 'setWindowDocumentEdited')
atom.project.setPaths([atom.project.getDirectories()[0]?.resolve('dir')])
atom.workspace = workspace = new Workspace
waits(1)
describe "serialization", ->
@@ -21,8 +25,16 @@ describe "Workspace", ->
projectState = atom.project.serialize()
atom.workspace.destroy()
atom.project.destroy()
atom.project = atom.deserializers.deserialize(projectState)
atom.workspace = Workspace.deserialize(workspaceState)
atom.project = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm.bind(atom)})
atom.project.deserialize(projectState, atom.deserializers)
atom.workspace = new Workspace({
config: atom.config, project: atom.project, packageManager: atom.packages,
grammarRegistry: atom.grammars, deserializerManager: atom.deserializers,
notificationManager: atom.notifications, clipboard: atom.clipboard,
applicationDelegate: atom.applicationDelegate,
viewRegistry: atom.views, assert: atom.assert.bind(atom),
})
atom.workspace.deserialize(workspaceState, atom.deserializers)
describe "when the workspace contains text editors", ->
it "constructs the view with the same panes", ->
@@ -333,7 +345,8 @@ describe "Workspace", ->
describe "when the file is over 20MB", ->
it "prompts the user to make sure they want to open a file this big", ->
spyOn(fs, 'getSizeSync').andReturn 20 * 1048577 # 20MB
spyOn(atom, 'confirm').andCallFake -> selectedButtonIndex
atom.applicationDelegate.confirm.andCallFake -> selectedButtonIndex
atom.applicationDelegate.confirm()
selectedButtonIndex = 1 # cancel
editor = null
@@ -342,16 +355,16 @@ describe "Workspace", ->
runs ->
expect(editor).toBeUndefined()
expect(atom.confirm).toHaveBeenCalled()
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
atom.confirm.reset()
atom.applicationDelegate.confirm.reset()
selectedButtonIndex = 0 # open the file
waitsForPromise ->
workspace.open('sample.js').then (e) -> editor = e
runs ->
expect(atom.confirm).toHaveBeenCalled()
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
expect(editor.displayBuffer.largeFileMode).toBe true
describe "when passed a path that matches a custom opener", ->
@@ -614,7 +627,13 @@ describe "Workspace", ->
spyOn(jsPackage, 'loadGrammarsSync')
spyOn(coffeePackage, 'loadGrammarsSync')
workspace2 = Workspace.deserialize(state)
workspace2 = new Workspace({
config: atom.config, project: atom.project, packageManager: atom.packages,
notificationManager: atom.notifications, deserializerManager: atom.deserializers,
clipboard: atom.clipboard, viewRegistry: atom.views, grammarRegistry: atom.grammars,
applicationDelegate: atom.applicationDelegate, assert: atom.assert.bind(atom)
})
workspace2.deserialize(state, atom.deserializers)
expect(jsPackage.loadGrammarsSync.callCount).toBe 1
expect(coffeePackage.loadGrammarsSync.callCount).toBe 1
@@ -666,7 +685,13 @@ describe "Workspace", ->
it "updates the title to contain the project's path", ->
document.title = null
workspace2 = Workspace.deserialize(atom.workspace.serialize())
workspace2 = new Workspace({
config: atom.config, project: atom.project, packageManager: atom.packages,
notificationManager: atom.notifications, deserializerManager: atom.deserializers,
clipboard: atom.clipboard, viewRegistry: atom.views, grammarRegistry: atom.grammars,
applicationDelegate: atom.applicationDelegate, assert: atom.assert.bind(atom)
})
workspace2.deserialize(atom.workspace.serialize(), atom.deserializers)
item = atom.workspace.getActivePaneItem()
expect(document.title).toBe "#{item.getTitle()} - #{atom.project.getPaths()[0]} - Atom"
workspace2.destroy()
@@ -679,15 +704,14 @@ describe "Workspace", ->
waitsForPromise -> atom.workspace.open('b')
runs ->
[item1, item2] = atom.workspace.getPaneItems()
spyOn(atom, 'setDocumentEdited')
it "calls atom.setDocumentEdited when the active item changes", ->
it "calls setDocumentEdited when the active item changes", ->
expect(atom.workspace.getActivePaneItem()).toBe item2
item1.insertText('a')
expect(item1.isModified()).toBe true
atom.workspace.getActivePane().activateNextItem()
expect(atom.setDocumentEdited).toHaveBeenCalledWith(true)
expect(setDocumentEdited).toHaveBeenCalledWith(true)
it "calls atom.setDocumentEdited when the active item's modified status changes", ->
expect(atom.workspace.getActivePaneItem()).toBe item2
@@ -695,13 +719,13 @@ describe "Workspace", ->
advanceClock(item2.getBuffer().getStoppedChangingDelay())
expect(item2.isModified()).toBe true
expect(atom.setDocumentEdited).toHaveBeenCalledWith(true)
expect(setDocumentEdited).toHaveBeenCalledWith(true)
item2.undo()
advanceClock(item2.getBuffer().getStoppedChangingDelay())
expect(item2.isModified()).toBe false
expect(atom.setDocumentEdited).toHaveBeenCalledWith(false)
expect(setDocumentEdited).toHaveBeenCalledWith(false)
describe "adding panels", ->
class TestItem
@@ -956,7 +980,7 @@ describe "Workspace", ->
results = []
waitsForPromise ->
atom.project.open('a').then (o) ->
atom.workspace.open('a').then (o) ->
editor = o
editor.setText("Elephant")
@@ -974,7 +998,7 @@ describe "Workspace", ->
results = []
waitsForPromise ->
atom.project.open(temp.openSync().path).then (o) ->
atom.workspace.open(temp.openSync().path).then (o) ->
editor = o
editor.setText("Elephant")
@@ -1070,6 +1094,9 @@ describe "Workspace", ->
search: (directory, regex, options) -> fakeSearch = new FakeSearch(options)
})
waitsFor ->
atom.workspace.directorySearchers.length > 0
it "can override the DefaultDirectorySearcher on a per-directory basis", ->
foreignFilePath = 'ssh://foreign-directory:8080/hello.txt'
numPathsSearchedInDir2 = 1
@@ -1193,7 +1220,7 @@ describe "Workspace", ->
results = []
waitsForPromise ->
atom.project.open('sample.js').then (o) -> editor = o
atom.workspace.open('sample.js').then (o) -> editor = o
runs ->
expect(editor.isModified()).toBeFalsy()
@@ -1214,7 +1241,7 @@ describe "Workspace", ->
results = []
waitsForPromise ->
atom.project.open('sample-with-comments.js').then (o) -> editor = o
atom.workspace.open('sample-with-comments.js').then (o) -> editor = o
waitsForPromise ->
atom.workspace.replace /items/gi, 'items', [commentFilePath], (result) ->
@@ -1229,7 +1256,7 @@ describe "Workspace", ->
results = []
waitsForPromise ->
atom.project.open('sample.js').then (o) -> editor = o
atom.workspace.open('sample.js').then (o) -> editor = o
runs ->
editor.buffer.setTextInRange([[0, 0], [0, 0]], 'omg')

View File

@@ -0,0 +1,169 @@
_ = require 'underscore-plus'
ipc = require 'ipc'
remote = require 'remote'
shell = require 'shell'
webFrame = require 'web-frame'
{Disposable} = require 'event-kit'
{getWindowLoadSettings, setWindowLoadSettings} = require './window-load-settings-helpers'
module.exports =
class ApplicationDelegate
open: (params) ->
ipc.send('open', params)
pickFolder: (callback) ->
responseChannel = "atom-pick-folder-response"
ipc.on responseChannel, (path) ->
ipc.removeAllListeners(responseChannel)
callback(path)
ipc.send("pick-folder", responseChannel)
getCurrentWindow: ->
remote.getCurrentWindow()
closeWindow: ->
ipc.send("call-window-method", "close")
getWindowSize: ->
[width, height] = remote.getCurrentWindow().getSize()
{width, height}
setWindowSize: (width, height) ->
remote.getCurrentWindow().setSize(width, height)
getWindowPosition: ->
[x, y] = remote.getCurrentWindow().getPosition()
{x, y}
setWindowPosition: (x, y) ->
ipc.send("call-window-method", "setPosition", x, y)
centerWindow: ->
ipc.send("call-window-method", "center")
focusWindow: ->
ipc.send("call-window-method", "focus")
showWindow: ->
ipc.send("call-window-method", "show")
hideWindow: ->
ipc.send("call-window-method", "hide")
restartWindow: ->
ipc.send("call-window-method", "restart")
isWindowMaximized: ->
remote.getCurrentWindow().isMaximized()
maximizeWindow: ->
ipc.send("call-window-method", "maximize")
isWindowFullScreen: ->
remote.getCurrentWindow().isFullScreen()
setWindowFullScreen: (fullScreen=false) ->
ipc.send("call-window-method", "setFullScreen", fullScreen)
openWindowDevTools: ->
remote.getCurrentWindow().openDevTools()
toggleWindowDevTools: ->
remote.getCurrentWindow().toggleDevTools()
executeJavaScriptInWindowDevTools: (code) ->
remote.getCurrentWindow().executeJavaScriptInDevTools(code)
setWindowDocumentEdited: (edited) ->
ipc.send("call-window-method", "setDocumentEdited", edited)
setRepresentedFilename: (filename) ->
ipc.send("call-window-method", "setRepresentedFilename", filename)
setRepresentedDirectoryPaths: (paths) ->
loadSettings = getWindowLoadSettings()
loadSettings['initialPaths'] = paths
setWindowLoadSettings(loadSettings)
setAutoHideWindowMenuBar: (autoHide) ->
ipc.send("call-window-method", "setAutoHideMenuBar", autoHide)
setWindowMenuBarVisibility: (visible) ->
remote.getCurrentWindow().setMenuBarVisibility(visible)
getPrimaryDisplayWorkAreaSize: ->
screen = remote.require 'screen'
screen.getPrimaryDisplay().workAreaSize
confirm: ({message, detailedMessage, buttons}) ->
buttons ?= {}
if _.isArray(buttons)
buttonLabels = buttons
else
buttonLabels = Object.keys(buttons)
dialog = remote.require('dialog')
chosen = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'info'
message: message
detail: detailedMessage
buttons: buttonLabels
})
if _.isArray(buttons)
chosen
else
callback = buttons[buttonLabels[chosen]]
callback?()
showMessageDialog: (params) ->
showSaveDialog: (params) ->
if _.isString(params)
params = defaultPath: params
else
params = _.clone(params)
params.title ?= 'Save File'
params.defaultPath ?= getWindowLoadSettings().initialPaths[0]
dialog = remote.require('dialog')
dialog.showSaveDialog remote.getCurrentWindow(), params
playBeepSound: ->
shell.beep()
onDidOpenLocations: (callback) ->
outerCallback = (message, detail) ->
if message is 'open-locations'
callback(detail)
ipc.on('message', outerCallback)
new Disposable ->
ipc.removeEventListener('message', outerCallback)
onUpdateAvailable: (callback) ->
outerCallback = (message, detail) ->
if message is 'update-available'
callback(detail)
ipc.on('message', outerCallback)
new Disposable ->
ipc.removeEventListener('message', outerCallback)
onApplicationMenuCommand: (callback) ->
ipc.on('command', callback)
new Disposable ->
ipc.removeEventListener('command', callback)
onContextMenuCommand: (callback) ->
ipc.on('context-command', callback)
new Disposable ->
ipc.removeEventListener('context-command', callback)
didCancelWindowUnload: ->
ipc.send('did-cancel-window-unload')
openExternal: (url) ->
shell.openExternal(url)
disablePinchToZoom: ->
webFrame.setZoomLevelLimits(1, 1)

View File

@@ -1,9 +1,5 @@
crypto = require 'crypto'
ipc = require 'ipc'
os = require 'os'
path = require 'path'
remote = require 'remote'
shell = require 'shell'
_ = require 'underscore-plus'
{deprecate} = require 'grim'
@@ -14,83 +10,52 @@ Model = require './model'
WindowEventHandler = require './window-event-handler'
StylesElement = require './styles-element'
StorageFolder = require './storage-folder'
{getWindowLoadSettings} = require './window-load-settings-helpers'
registerDefaultCommands = require './register-default-commands'
DeserializerManager = require './deserializer-manager'
ViewRegistry = require './view-registry'
NotificationManager = require './notification-manager'
Config = require './config'
KeymapManager = require './keymap-extensions'
TooltipManager = require './tooltip-manager'
CommandRegistry = require './command-registry'
GrammarRegistry = require './grammar-registry'
StyleManager = require './style-manager'
PackageManager = require './package-manager'
ThemeManager = require './theme-manager'
MenuManager = require './menu-manager'
ContextMenuManager = require './context-menu-manager'
CommandInstaller = require './command-installer'
Clipboard = require './clipboard'
Project = require './project'
Workspace = require './workspace'
PanelContainer = require './panel-container'
Panel = require './panel'
PaneContainer = require './pane-container'
PaneAxis = require './pane-axis'
Pane = require './pane'
Project = require './project'
TextEditor = require './text-editor'
TextBuffer = require 'text-buffer'
Gutter = require './gutter'
WorkspaceElement = require './workspace-element'
PanelContainerElement = require './panel-container-element'
PanelElement = require './panel-element'
PaneContainerElement = require './pane-container-element'
PaneAxisElement = require './pane-axis-element'
PaneElement = require './pane-element'
TextEditorElement = require './text-editor-element'
{createGutterView} = require './gutter-component-helpers'
# Essential: 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 extends Model
class AtomEnvironment extends Model
@version: 1 # Increment this when the serialization format changes
# Load or create the Atom environment in the given mode.
#
# * `mode` A {String} mode that is either 'editor' or 'spec' depending on the
# kind of environment you want to build.
#
# Returns an Atom instance, fully initialized
@loadOrCreate: (mode) ->
startTime = Date.now()
atom = @deserialize(@loadState(mode)) ? new this({mode, @version})
atom.deserializeTimings.atom = Date.now() - startTime
atom
# Deserializes the Atom environment from a state object
@deserialize: (state) ->
new this(state) if state?.version is @version
# Loads and returns the serialized state corresponding to this window
# if it exists; otherwise returns undefined.
@loadState: (mode) ->
if stateKey = @getStateKey(@getLoadSettings().initialPaths, mode)
if state = @getStorageFolder().load(stateKey)
return state
if windowState = @getLoadSettings().windowState
try
JSON.parse(@getLoadSettings().windowState)
catch error
console.warn "Error parsing window state: #{statePath} #{error.stack}", error
# Returns the path where the state for the current window will be
# located if it exists.
@getStateKey: (paths, mode) ->
if mode is 'spec'
'spec'
else if mode is 'editor' and paths?.length > 0
sha1 = crypto.createHash('sha1').update(paths.slice().sort().join("\n")).digest('hex')
"editor-#{sha1}"
else
null
# Get the directory path to Atom's configuration area.
#
# Returns the absolute path to ~/.atom
@getConfigDirPath: ->
@configDirPath ?= process.env.ATOM_HOME
@getStorageFolder: ->
@storageFolder ?= new StorageFolder(@getConfigDirPath())
# Returns the load settings hash associated with the current window.
@getLoadSettings: ->
@loadSettings ?= JSON.parse(decodeURIComponent(location.hash.substr(1)))
cloned = _.deepClone(@loadSettings)
# The loadSettings.windowState could be large, request it only when needed.
cloned.__defineGetter__ 'windowState', =>
@getCurrentWindow().loadSettings.windowState
cloned.__defineSetter__ 'windowState', (value) =>
@getCurrentWindow().loadSettings.windowState = value
cloned
@updateLoadSetting: (key, value) ->
@getLoadSettings()
@loadSettings[key] = value
location.hash = encodeURIComponent(JSON.stringify(@loadSettings))
@getCurrentWindow: ->
remote.getCurrentWindow()
workspaceParentSelectorctor: 'body'
lastUncaughtError: null
###
@@ -150,122 +115,199 @@ class Atom extends Model
###
# Call .loadOrCreate instead
constructor: (@state) ->
@emitter = new Emitter
@disposables = new CompositeDisposable
{@mode} = @state
DeserializerManager = require './deserializer-manager'
@deserializers = new DeserializerManager()
@deserializeTimings = {}
constructor: (params={}) ->
{@applicationDelegate, @window, @document, configDirPath, @enablePersistence} = params
# Sets up the basic services that should be available in all modes
# (both spec and application).
#
# Call after this instance has been assigned to the `atom` global.
initialize: ->
window.onerror = =>
@lastUncaughtError = Array::slice.call(arguments)
[message, url, line, column, originalError] = @lastUncaughtError
{line, column} = mapSourcePosition({source: url, line, column})
eventObject = {message, url, line, column, originalError}
openDevTools = true
eventObject.preventDefault = -> openDevTools = false
@emitter.emit 'will-throw-error', eventObject
if openDevTools
@openDevTools()
@executeJavaScriptInDevTools('DevToolsAPI.showConsole()')
@emitter.emit 'did-throw-error', {message, url, line, column, originalError}
@disposables?.dispose()
@disposables = new CompositeDisposable
@displayWindow() unless @inSpecMode()
@setBodyPlatformClass()
@state = {version: @constructor.version}
@loadTime = null
Config = require './config'
KeymapManager = require './keymap-extensions'
ViewRegistry = require './view-registry'
CommandRegistry = require './command-registry'
TooltipManager = require './tooltip-manager'
NotificationManager = require './notification-manager'
PackageManager = require './package-manager'
Clipboard = require './clipboard'
GrammarRegistry = require './grammar-registry'
ThemeManager = require './theme-manager'
StyleManager = require './style-manager'
ContextMenuManager = require './context-menu-manager'
MenuManager = require './menu-manager'
{devMode, safeMode, resourcePath} = @getLoadSettings()
configDirPath = @getConfigDirPath()
# Add 'exports' to module search path.
exportsPath = path.join(resourcePath, 'exports')
require('module').globalPaths.push(exportsPath)
# Still set NODE_PATH since tasks may need it.
process.env.NODE_PATH = exportsPath
@emitter = new Emitter
@disposables = new CompositeDisposable
# Make react.js faster
process.env.NODE_ENV ?= 'production' unless devMode
@deserializers = new DeserializerManager(this)
@deserializeTimings = {}
@views = new ViewRegistry(this)
@config = new Config({configDirPath, resourcePath})
@keymaps = new KeymapManager({configDirPath, resourcePath})
@keymaps.subscribeToFileReadFailure()
@tooltips = new TooltipManager
@notifications = new NotificationManager
@config = new Config({configDirPath, resourcePath, notificationManager: @notifications, @enablePersistence})
@setConfigSchema()
@keymaps = new KeymapManager({configDirPath, resourcePath, notificationManager: @notifications})
@tooltips = new TooltipManager(keymapManager: @keymaps)
@commands = new CommandRegistry
@views = new ViewRegistry
@registerViewProviders()
@packages = new PackageManager({devMode, configDirPath, resourcePath, safeMode})
@styles = new StyleManager
document.head.appendChild(new StylesElement)
@themes = new ThemeManager({packageManager: @packages, configDirPath, resourcePath, safeMode})
@contextMenu = new ContextMenuManager({resourcePath, devMode})
@menu = new MenuManager({resourcePath})
@commands.attach(@window)
@grammars = new GrammarRegistry({@config})
@styles = new StyleManager({configDirPath})
@packages = new PackageManager({
devMode, configDirPath, resourcePath, safeMode, @config, styleManager: @styles,
commandRegistry: @commands, keymapManager: @keymaps, notificationManager: @notifications,
grammarRegistry: @grammars
})
@themes = new ThemeManager({
packageManager: @packages, configDirPath, resourcePath, safeMode, @config,
styleManager: @styles, notificationManager: @notifications, viewRegistry: @views
})
@menu = new MenuManager({resourcePath, keymapManager: @keymaps, packageManager: @packages})
@contextMenu = new ContextMenuManager({resourcePath, devMode, keymapManager: @keymaps})
@packages.setMenuManager(@menu)
@packages.setContextMenuManager(@contextMenu)
@packages.setThemeManager(@themes)
@clipboard = new Clipboard()
@grammars = @deserializers.deserialize(@state.grammars ? @state.syntax) ? new GrammarRegistry()
@disposables.add @packages.onDidActivateInitialPackages => @watchThemes()
Project = require './project'
TextBuffer = require 'text-buffer'
@project = new Project({notificationManager: @notifications, packageManager: @packages, @config})
@commandInstaller = new CommandInstaller(@getVersion(), @applicationDelegate)
@workspace = new Workspace({
@config, @project, packageManager: @packages, grammarRegistry: @grammars, deserializerManager: @deserializers,
notificationManager: @notifications, @applicationDelegate, @clipboard, viewRegistry: @views, assert: @assert.bind(this)
})
@themes.workspace = @workspace
@config.load()
@themes.loadBaseStylesheets()
@initialStyleElements = @styles.getSnapshot()
@themes.initialLoadComplete = true
@setBodyPlatformClass()
@stylesElement = @styles.buildStylesElement()
@document.head.appendChild(@stylesElement)
@applicationDelegate.disablePinchToZoom()
@keymaps.subscribeToFileReadFailure()
@keymaps.loadBundledKeymaps()
@registerDefaultCommands()
@registerDefaultOpeners()
@registerDefaultDeserializers()
@registerDefaultViewProviders()
@installUncaughtErrorHandler()
@installWindowEventHandler()
@observeAutoHideMenuBar()
setConfigSchema: ->
@config.setSchema null, {type: 'object', properties: _.clone(require('./config-schema'))}
registerDefaultDeserializers: ->
@deserializers.add(Workspace)
@deserializers.add(PaneContainer)
@deserializers.add(PaneAxis)
@deserializers.add(Pane)
@deserializers.add(Project)
@deserializers.add(TextEditor)
@deserializers.add(TextBuffer)
TokenizedBuffer = require './tokenized-buffer'
DisplayBuffer = require './display-buffer'
TextEditor = require './text-editor'
@windowEventHandler = new WindowEventHandler
registerDefaultCommands: ->
registerDefaultCommands({commandRegistry: @commands, @config, @commandInstaller})
# Register the core views as early as possible in case they are needed for
# package deserialization.
registerViewProviders: ->
Gutter = require './gutter'
Pane = require './pane'
PaneElement = require './pane-element'
PaneContainer = require './pane-container'
PaneContainerElement = require './pane-container-element'
PaneAxis = require './pane-axis'
PaneAxisElement = require './pane-axis-element'
TextEditor = require './text-editor'
TextEditorElement = require './text-editor-element'
{createGutterView} = require './gutter-component-helpers'
registerDefaultViewProviders: ->
@views.addViewProvider Workspace, (model, env) ->
new WorkspaceElement().initialize(model, env)
@views.addViewProvider PanelContainer, (model, env) ->
new PanelContainerElement().initialize(model, env)
@views.addViewProvider Panel, (model, env) ->
new PanelElement().initialize(model, env)
@views.addViewProvider PaneContainer, (model, env) ->
new PaneContainerElement().initialize(model, env)
@views.addViewProvider PaneAxis, (model, env) ->
new PaneAxisElement().initialize(model, env)
@views.addViewProvider Pane, (model, env) ->
new PaneElement().initialize(model, env)
@views.addViewProvider TextEditor, (model, env) ->
new TextEditorElement().initialize(model, env)
@views.addViewProvider(Gutter, createGutterView)
atom.views.addViewProvider PaneContainer, (model) ->
new PaneContainerElement().initialize(model)
atom.views.addViewProvider PaneAxis, (model) ->
new PaneAxisElement().initialize(model)
atom.views.addViewProvider Pane, (model) ->
new PaneElement().initialize(model)
atom.views.addViewProvider TextEditor, (model) ->
new TextEditorElement().initialize(model)
atom.views.addViewProvider(Gutter, createGutterView)
registerDefaultOpeners: ->
@workspace.addOpener (uri) =>
switch uri
when 'atom://.atom/stylesheet'
@workspace.open(@styles.getUserStyleSheetPath())
when 'atom://.atom/keymap'
@workspace.open(@keymaps.getUserKeymapPath())
when 'atom://.atom/config'
@workspace.open(@config.getUserConfigPath())
when 'atom://.atom/init-script'
@workspace.open(@getUserInitScriptPath())
registerDefaultTargetForKeymaps: ->
@keymaps.defaultTarget = @views.getView(@workspace)
observeAutoHideMenuBar: ->
@disposables.add @config.onDidChange 'core.autoHideMenuBar', ({newValue}) =>
@setAutoHideMenuBar(newValue)
@setAutoHideMenuBar(true) if @config.get('core.autoHideMenuBar')
reset: ->
@deserializers.clear()
@registerDefaultDeserializers()
@config.clear()
@setConfigSchema()
@keymaps.clear()
@keymaps.loadBundledKeymaps()
@commands.clear()
@registerDefaultCommands()
@styles.restoreSnapshot(@initialStyleElements)
@menu.clear()
@clipboard.reset()
@notifications.clear()
@contextMenu.clear()
@packages.reset()
@workspace.reset(@packages)
@registerDefaultOpeners()
@project.reset(@packages)
@workspace.subscribeToEvents()
@grammars.clear()
@views.clear()
@registerDefaultViewProviders()
@state.packageStates = {}
delete @state.workspace
destroy: ->
return if not @project
@disposables.dispose()
@workspace?.destroy()
@workspace = null
@themes.workspace = null
@project?.destroy()
@project = null
@commands.clear()
@stylesElement.remove()
@uninstallWindowEventHandler()
###
Section: Event Subscription
@@ -341,12 +383,6 @@ class Atom extends Model
isReleasedVersion: ->
not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix
# Public: Get the directory path to Atom's configuration area.
#
# Returns the absolute path to `~/.atom`.
getConfigDirPath: ->
@constructor.getConfigDirPath()
# Public: Get the time taken to completely load the current window.
#
# This time include things like loading and activating packages, creating
@@ -361,7 +397,7 @@ class Atom extends Model
#
# Returns an {Object} containing all the load setting key/value pairs.
getLoadSettings: ->
@constructor.getLoadSettings()
getWindowLoadSettings()
###
Section: Managing The Atom Window
@@ -372,7 +408,7 @@ class Atom extends Model
# Calling this method without an options parameter will open a prompt to pick
# a file/folder to open in the new window.
#
# * `options` An {Object} with the following keys:
# * `params` An {Object} with the following keys:
# * `pathsToOpen` An {Array} of {String} paths to open.
# * `newWindow` A {Boolean}, true to always open a new window instead of
# reusing existing windows depending on the paths to open.
@@ -381,8 +417,8 @@ class Atom extends Model
# repository and also loads all the packages in ~/.atom/dev/packages
# * `safeMode` A {Boolean}, true to open the window in safe mode. Safe
# mode prevents all packages installed to ~/.atom/packages from loading.
open: (options) ->
ipc.send('open', options)
open: (params) ->
@applicationDelegate.open(params)
# Extended: Prompt the user to select one or more folders.
#
@@ -390,87 +426,81 @@ class Atom extends Model
# * `paths` An {Array} of {String} paths that the user selected, or `null`
# if the user dismissed the dialog.
pickFolder: (callback) ->
responseChannel = "atom-pick-folder-response"
ipc.on responseChannel, (path) ->
ipc.removeAllListeners(responseChannel)
callback(path)
ipc.send("pick-folder", responseChannel)
@applicationDelegate.pickFolder(callback)
# Essential: Close the current window.
close: ->
@getCurrentWindow().close()
@applicationDelegate.closeWindow()
# Essential: Get the size of current window.
#
# Returns an {Object} in the format `{width: 1000, height: 700}`
getSize: ->
[width, height] = @getCurrentWindow().getSize()
{width, height}
@applicationDelegate.getWindowSize()
# Essential: Set the size of current window.
#
# * `width` The {Number} of pixels.
# * `height` The {Number} of pixels.
setSize: (width, height) ->
@getCurrentWindow().setSize(width, height)
@applicationDelegate.setWindowSize(width, height)
# Essential: Get the position of current window.
#
# Returns an {Object} in the format `{x: 10, y: 20}`
getPosition: ->
[x, y] = @getCurrentWindow().getPosition()
{x, y}
@applicationDelegate.getWindowPosition()
# Essential: Set the position of current window.
#
# * `x` The {Number} of pixels.
# * `y` The {Number} of pixels.
setPosition: (x, y) ->
ipc.send('call-window-method', 'setPosition', x, y)
@applicationDelegate.setWindowPosition(x, y)
# Extended: Get the current window
getCurrentWindow: ->
@constructor.getCurrentWindow()
@applicationDelegate.getCurrentWindow()
# Extended: Move current window to the center of the screen.
center: ->
ipc.send('call-window-method', 'center')
@applicationDelegate.centerWindow()
# Extended: Focus the current window.
focus: ->
ipc.send('call-window-method', 'focus')
window.focus()
@applicationDelegate.focusWindow()
@window.focus()
# Extended: Show the current window.
show: ->
ipc.send('call-window-method', 'show')
@applicationDelegate.showWindow()
# Extended: Hide the current window.
hide: ->
ipc.send('call-window-method', 'hide')
@applicationDelegate.hideWindow()
# Extended: Reload the current window.
reload: ->
ipc.send('call-window-method', 'restart')
@applicationDelegate.restartWindow()
# Extended: Returns a {Boolean} that is `true` if the current window is maximized.
isMaximized: ->
@getCurrentWindow().isMaximized()
@applicationDelegate.isWindowMaximized()
maximize: ->
ipc.send('call-window-method', 'maximize')
@applicationDelegate.maximizeWindow()
# Extended: Returns a {Boolean} that is `true` if the current window is in full screen mode.
isFullScreen: ->
@getCurrentWindow().isFullScreen()
@applicationDelegate.isWindowFullScreen()
# Extended: Set the full screen state of the current window.
setFullScreen: (fullScreen=false) ->
ipc.send('call-window-method', 'setFullScreen', fullScreen)
@applicationDelegate.setWindowFullScreen(fullScreen)
if fullScreen
document.body.classList.add("fullscreen")
@document.body.classList.add("fullscreen")
else
document.body.classList.remove("fullscreen")
@document.body.classList.remove("fullscreen")
# Extended: Toggle the full screen state of the current window.
toggleFullScreen: ->
@@ -546,8 +576,7 @@ class Atom extends Model
if @isValidDimensions(dimensions)
dimensions
else
screen = remote.require 'screen'
{width, height} = screen.getPrimaryDisplay().workAreaSize
{width, height} = @applicationDelegate.getPrimaryDisplayWorkAreaSize()
{x: 0, y: 0, width: Math.min(1024, width), height}
restoreWindowDimensions: ->
@@ -565,37 +594,34 @@ class Atom extends Model
return if @inSpecMode()
workspaceElement = @views.getView(@workspace)
backgroundColor = window.getComputedStyle(workspaceElement)['background-color']
window.localStorage.setItem('atom:window-background-color', backgroundColor)
backgroundColor = @window.getComputedStyle(workspaceElement)['background-color']
@window.localStorage.setItem('atom:window-background-color', backgroundColor)
# Call this method when establishing a real application window.
startEditorWindow: ->
{safeMode} = @getLoadSettings()
CommandInstaller = require './command-installer'
commandInstaller = new CommandInstaller(@getVersion())
commandInstaller.installAtomCommand false, (error) ->
@commandInstaller.installAtomCommand false, (error) ->
console.warn error.message if error?
commandInstaller.installApmCommand false, (error) ->
@commandInstaller.installApmCommand false, (error) ->
console.warn error.message if error?
@loadConfig()
@keymaps.loadBundledKeymaps()
@themes.loadBaseStylesheets()
@disposables.add(@applicationDelegate.onDidOpenLocations(@openLocations.bind(this)))
@disposables.add(@applicationDelegate.onApplicationMenuCommand(@dispatchApplicationMenuCommand.bind(this)))
@disposables.add(@applicationDelegate.onContextMenuCommand(@dispatchContextMenuCommand.bind(this)))
@listenForUpdates()
@registerDefaultTargetForKeymaps()
@packages.loadPackages()
@deserializeEditorWindow()
@document.body.appendChild(@views.getView(@workspace))
@watchProjectPath()
@packages.activate()
@keymaps.loadUserKeymap()
@requireUserInitScript() unless safeMode
@requireUserInitScript() unless @getLoadSettings().safeMode
@menu.update()
@disposables.add @config.onDidChange 'core.autoHideMenuBar', ({newValue}) =>
@setAutoHideMenuBar(newValue)
@setAutoHideMenuBar(true) if @config.get('core.autoHideMenuBar')
@openInitialEmptyEditorIfNecessary()
@@ -603,36 +629,56 @@ class Atom extends Model
return if not @project
@storeWindowBackground()
@state.grammars = @grammars.serialize()
@state.grammars = {grammarOverridesByPath: @grammars.grammarOverridesByPath}
@state.project = @project.serialize()
@state.workspace = @workspace.serialize()
@packages.deactivatePackages()
@state.packageStates = @packages.packageStates
@saveSync()
@windowState = null
removeEditorWindow: ->
return if not @project
@workspace?.destroy()
@workspace = null
@project?.destroy()
@project = null
@windowEventHandler?.unsubscribe()
@state.fullScreen = @isFullScreen()
@saveStateSync()
openInitialEmptyEditorIfNecessary: ->
return unless @config.get('core.openEmptyEditorOnStart')
if @getLoadSettings().initialPaths?.length is 0 and @workspace.getPaneItems().length is 0
@workspace.open(null)
installUncaughtErrorHandler: ->
@previousWindowErrorHandler = @window.onerror
@window.onerror = =>
@lastUncaughtError = Array::slice.call(arguments)
[message, url, line, column, originalError] = @lastUncaughtError
{line, column} = mapSourcePosition({source: url, line, column})
eventObject = {message, url, line, column, originalError}
openDevTools = true
eventObject.preventDefault = -> openDevTools = false
@emitter.emit 'will-throw-error', eventObject
if openDevTools
@openDevTools()
@executeJavaScriptInDevTools('DevToolsAPI.showConsole()')
@emitter.emit 'did-throw-error', {message, url, line, column, originalError}
uninstallUncaughtErrorHandler: ->
@window.onerror = @previousWindowErrorHandler
installWindowEventHandler: ->
@windowEventHandler = new WindowEventHandler({atomEnvironment: this, @applicationDelegate, @window, @document})
uninstallWindowEventHandler: ->
@windowEventHandler?.unsubscribe()
###
Section: Messaging the User
###
# Essential: Visually and audibly trigger a beep.
beep: ->
shell.beep() if @config.get('core.audioBeep')
@applicationDelegate.playBeepSound() if @config.get('core.audioBeep')
@emitter.emit 'did-beep'
# Essential: A flexible way to open a dialog akin to an alert dialog.
@@ -655,25 +701,8 @@ class Atom extends Model
# button names and the values are callbacks to invoke when clicked.
#
# Returns the chosen button index {Number} if the buttons option was an array.
confirm: ({message, detailedMessage, buttons}={}) ->
buttons ?= {}
if _.isArray(buttons)
buttonLabels = buttons
else
buttonLabels = Object.keys(buttons)
dialog = remote.require('dialog')
chosen = dialog.showMessageBox @getCurrentWindow(),
type: 'info'
message: message
detail: detailedMessage
buttons: buttonLabels
if _.isArray(buttons)
chosen
else
callback = buttons[buttonLabels[chosen]]
callback?()
confirm: (params={}) ->
@applicationDelegate.confirm(params)
###
Section: Managing the Dev Tools
@@ -681,15 +710,15 @@ class Atom extends Model
# Extended: Open the dev tools for the current window.
openDevTools: ->
ipc.send('call-window-method', 'openDevTools')
@applicationDelegate.openWindowDevTools()
# Extended: Toggle the visibility of the dev tools for the current window.
toggleDevTools: ->
ipc.send('call-window-method', 'toggleDevTools')
@applicationDelegate.toggleWindowDevTools()
# Extended: Execute code in dev tools.
executeJavaScriptInDevTools: (code) ->
ipc.send('call-window-method', 'executeJavaScriptInDevTools', code)
@applicationDelegate.executeJavaScriptInWindowDevTools(code)
###
Section: Private
@@ -706,62 +735,19 @@ class Atom extends Model
false
deserializeProject: ->
Project = require './project'
startTime = Date.now()
@project ?= @deserializers.deserialize(@state.project) ? new Project()
@deserializeTimings.project = Date.now() - startTime
deserializeWorkspace: ->
Workspace = require './workspace'
startTime = Date.now()
@workspace = Workspace.deserialize(@state.workspace) ? new Workspace
@deserializeTimings.workspace = Date.now() - startTime
workspaceElement = @views.getView(@workspace)
@keymaps.defaultTarget = workspaceElement
document.querySelector(@workspaceParentSelectorctor).appendChild(workspaceElement)
deserializePackageStates: ->
@packages.packageStates = @state.packageStates ? {}
delete @state.packageStates
deserializeEditorWindow: ->
@deserializePackageStates()
@deserializeProject()
@deserializeWorkspace()
loadConfig: ->
@config.setSchema null, {type: 'object', properties: _.clone(require('./config-schema'))}
@config.load()
loadThemes: ->
@themes.load()
watchThemes: ->
@themes.onDidChangeActiveThemes =>
# Only reload stylesheets from non-theme packages
for pack in @packages.getActivePackages() when pack.getType() isnt 'theme'
pack.reloadStylesheets?()
return
# Notify the browser project of the window's current project path
watchProjectPath: ->
@disposables.add @project.onDidChangePaths =>
@constructor.updateLoadSetting('initialPaths', @project.getPaths())
exit: (status) ->
app = remote.require('app')
app.emit('will-exit')
remote.process.exit(status)
@applicationDelegate.setRepresentedDirectoryPaths(@project.getPaths())
setDocumentEdited: (edited) ->
ipc.send('call-window-method', 'setDocumentEdited', edited)
@applicationDelegate.setWindowDocumentEdited?(edited)
setRepresentedFilename: (filename) ->
ipc.send('call-window-method', 'setRepresentedFilename', filename)
@applicationDelegate.setWindowRepresentedFilename?(filename)
addProjectFolder: ->
@pickFolder (selectedPaths = []) =>
@@ -771,27 +757,61 @@ class Atom extends Model
callback(showSaveDialogSync())
showSaveDialogSync: (options={}) ->
if _.isString(options)
options = defaultPath: options
else
options = _.clone(options)
currentWindow = @getCurrentWindow()
dialog = remote.require('dialog')
options.title ?= 'Save File'
options.defaultPath ?= @project?.getPaths()[0]
dialog.showSaveDialog currentWindow, options
@applicationDelegate.showSaveDialog(options)
saveSync: ->
if storageKey = @constructor.getStateKey(@project?.getPaths(), @mode)
@constructor.getStorageFolder().store(storageKey, @state)
saveStateSync: ->
return unless @enablePersistence
if storageKey = @getStateKey(@project?.getPaths())
@getStorageFolder().store(storageKey, @state)
else
@getCurrentWindow().loadSettings.windowState = JSON.stringify(@state)
crashMainProcess: ->
remote.process.crash()
loadStateSync: ->
return unless @enablePersistence
crashRenderProcess: ->
process.crash()
startTime = Date.now()
if stateKey = @getStateKey(@getLoadSettings().initialPaths)
if state = @getStorageFolder().load(stateKey)
@state = state
if not @state? and windowState = @getLoadSettings().windowState
try
if state = JSON.parse(@getLoadSettings().windowState)
@state = state
catch error
console.warn "Error parsing window state: #{statePath} #{error.stack}", error
@deserializeTimings.atom = Date.now() - startTime
if grammarOverridesByPath = @state.grammars?.grammarOverridesByPath
@grammars.grammarOverridesByPath = grammarOverridesByPath
@setFullScreen(@state.fullScreen)
@packages.packageStates = @state.packageStates ? {}
startTime = Date.now()
@project.deserialize(@state.project, @deserializers) if @state.project?
@deserializeTimings.project = Date.now() - startTime
startTime = Date.now()
@workspace.deserialize(@state.workspace, @deserializers) if @state.workspace?
@deserializeTimings.workspace = Date.now() - startTime
getStateKey: (paths) ->
if paths?.length > 0
sha1 = crypto.createHash('sha1').update(paths.slice().sort().join("\n")).digest('hex')
"editor-#{sha1}"
else
null
getConfigDirPath: ->
@configDirPath ?= process.env.ATOM_HOME
getStorageFolder: ->
@storageFolder ?= new StorageFolder(@getConfigDirPath())
getUserInitScriptPath: ->
initScriptPath = fs.resolve(@getConfigDirPath(), 'init', ['js', 'coffee'])
@@ -802,44 +822,52 @@ class Atom extends Model
try
require(userInitScriptPath) if fs.isFileSync(userInitScriptPath)
catch error
atom.notifications.addError "Failed to load `#{userInitScriptPath}`",
@notifications.addError "Failed to load `#{userInitScriptPath}`",
detail: error.message
dismissable: true
# Require the module with the given globals.
#
# The globals will be set on the `window` object and removed after the
# require completes.
#
# * `id` The {String} module name or path.
# * `globals` An optional {Object} to set as globals during require.
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
return
onUpdateAvailable: (callback) ->
@emitter.on 'update-available', callback
updateAvailable: (details) ->
@emitter.emit 'update-available', details
listenForUpdates: ->
@disposables.add(@applicationDelegate.onUpdateAvailable(@updateAvailable.bind(this)))
setBodyPlatformClass: ->
document.body.classList.add("platform-#{process.platform}")
@document.body.classList.add("platform-#{process.platform}")
setAutoHideMenuBar: (autoHide) ->
ipc.send('call-window-method', 'setAutoHideMenuBar', autoHide)
ipc.send('call-window-method', 'setMenuBarVisibility', not autoHide)
@applicationDelegate.setAutoHideWindowMenuBar(autoHide)
@applicationDelegate.setWindowMenuBarVisibility(not autoHide)
dispatchApplicationMenuCommand: (command, arg) ->
activeElement = @document.activeElement
# Use the workspace element if body has focus
if activeElement is @document.body and workspaceElement = @views.getView(@workspace)
activeElement = workspaceElement
@commands.dispatch(activeElement, command, arg)
dispatchContextMenuCommand: (command, args...) ->
@commands.dispatch(@contextMenu.activeElement, command, args)
openLocations: (locations) ->
needsProjectPaths = @project?.getPaths().length is 0
for {pathToOpen, initialLine, initialColumn} in locations
if pathToOpen? and needsProjectPaths
if fs.existsSync(pathToOpen)
@project.addPath(pathToOpen)
else if fs.existsSync(path.dirname(pathToOpen))
@project.addPath(path.dirname(pathToOpen))
else
@project.addPath(pathToOpen)
unless fs.isDirectorySync(pathToOpen)
@workspace?.open(pathToOpen, {initialLine, initialColumn})
return
# Preserve this deprecation until 2.0. Sorry. Should have removed Q sooner.
Promise.prototype.done = (callback) ->

View File

@@ -16,6 +16,8 @@ net = require 'net'
url = require 'url'
{EventEmitter} = require 'events'
_ = require 'underscore-plus'
FindParentDir = null
Resolve = null
LocationSuffixRegExp = /(:\d+)(:\d+)?$/
@@ -63,7 +65,7 @@ class AtomApplication
exit: (status) -> app.exit(status)
constructor: (options) ->
{@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath} = options
{@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath, timeout} = options
@socketPath = null if options.test
@@ -85,11 +87,11 @@ class AtomApplication
if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test
@openWithOptions(options)
else
@loadState() or @openPath(options)
@loadState(options) or @openPath(options)
openWithOptions: ({pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile, profileStartup}) ->
openWithOptions: ({pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, timeout}) ->
if test
@runSpecs({exitWhenDone: true, @resourcePath, specDirectory, logFile})
@runTests({headless: true, devMode, @resourcePath, executedFrom, pathsToOpen, logFile, timeout})
else if pathsToOpen.length > 0
@openPaths({pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup})
else if urlsToOpen.length > 0
@@ -163,7 +165,6 @@ class AtomApplication
devMode: @focusedWindow()?.devMode
safeMode: @focusedWindow()?.safeMode
@on 'application:run-all-specs', -> @runSpecs(exitWhenDone: false, resourcePath: @devResourcePath, safeMode: @focusedWindow()?.safeMode)
@on 'application:quit', -> app.quit()
@on 'application:new-window', -> @openPath(_.extend(windowDimensions: @focusedWindow()?.getDimensions(), getLoadSettings()))
@on 'application:new-file', -> (@focusedWindow() ? this).openPath()
@@ -218,11 +219,6 @@ class AtomApplication
@killAllProcesses()
@deleteSocketFile()
app.on 'will-exit', =>
@saveState(false)
@killAllProcesses()
@deleteSocketFile()
app.on 'open-file', (event, pathToOpen) =>
event.preventDefault()
@openPath({pathToOpen})
@@ -253,8 +249,8 @@ class AtomApplication
win = BrowserWindow.fromWebContents(event.sender)
@applicationMenu.update(win, template, keystrokesByCommand)
ipc.on 'run-package-specs', (event, specDirectory) =>
@runSpecs({resourcePath: @devResourcePath, specDirectory: specDirectory, exitWhenDone: false})
ipc.on 'run-package-specs', (event, packageSpecPath) =>
@runTests({resourcePath: @devResourcePath, pathsToOpen: [packageSpecPath], headless: false})
ipc.on 'command', (event, command) =>
@emit(command)
@@ -271,13 +267,19 @@ class AtomApplication
@promptForPath "folder", (selectedPaths) ->
event.sender.send(responseChannel, selectedPaths)
ipc.on 'cancel-window-close', =>
ipc.on 'did-cancel-window-unload', =>
@quitting = false
clipboard = require '../safe-clipboard'
ipc.on 'write-text-to-selection-clipboard', (event, selectedText) ->
clipboard.writeText(selectedText, 'selection')
ipc.on 'write-to-stdout', (event, output) ->
process.stdout.write(output)
ipc.on 'write-to-stderr', (event, output) ->
process.stderr.write(output)
# Public: Executes the given command.
#
# If it isn't handled globally, delegate to the currently focused window.
@@ -395,12 +397,12 @@ class AtomApplication
else
if devMode
try
bootstrapScript = require.resolve(path.join(@devResourcePath, 'src', 'window-bootstrap'))
windowInitializationScript = require.resolve(path.join(@devResourcePath, 'src', 'initialize-application-window'))
resourcePath = @devResourcePath
bootstrapScript ?= require.resolve('../window-bootstrap')
windowInitializationScript ?= require.resolve('../initialize-application-window')
resourcePath ?= @resourcePath
openedWindow = new AtomWindow({locationsToOpen, bootstrapScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup})
openedWindow = new AtomWindow({locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup})
if pidToKillWhenClosed?
@pidsToOpenWindows[pidToKillWhenClosed] = openedWindow
@@ -439,15 +441,15 @@ class AtomApplication
if states.length > 0 or allowEmpty
@storageFolder.store('application.json', states)
loadState: ->
loadState: (options) ->
if (states = @storageFolder.load('application.json'))?.length > 0
for state in states
@openWithOptions({
@openWithOptions(_.extend(options, {
pathsToOpen: state.initialPaths
urlsToOpen: []
devMode: @devMode
safeMode: @safeMode
})
}))
true
else
false
@@ -475,9 +477,9 @@ class AtomApplication
if pack?
if pack.urlMain
packagePath = @packages.resolvePackagePath(packageName)
bootstrapScript = path.resolve(packagePath, pack.urlMain)
windowInitializationScript = path.resolve(packagePath, pack.urlMain)
windowDimensions = @focusedWindow()?.getDimensions()
new AtomWindow({bootstrapScript, @resourcePath, devMode, safeMode, urlToOpen, windowDimensions})
new AtomWindow({windowInitializationScript, @resourcePath, devMode, safeMode, urlToOpen, windowDimensions})
else
console.log "Package '#{pack.name}' does not have a url main: #{urlToOpen}"
else
@@ -486,25 +488,63 @@ class AtomApplication
# Opens up a new {AtomWindow} to run specs within.
#
# options -
# :exitWhenDone - A Boolean that, if true, will close the window upon
# :headless - A Boolean that, if true, will close the window upon
# completion.
# :resourcePath - The path to include specs from.
# :specPath - The directory to load specs from.
# :safeMode - A Boolean that, if true, won't run specs from ~/.atom/packages
# and ~/.atom/dev/packages, defaults to false.
runSpecs: ({exitWhenDone, resourcePath, specDirectory, logFile, safeMode}) ->
runTests: ({headless, devMode, resourcePath, executedFrom, pathsToOpen, logFile, safeMode, timeout}) ->
if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath)
resourcePath = @resourcePath
try
bootstrapScript = require.resolve(path.resolve(@devResourcePath, 'spec', 'spec-bootstrap'))
catch error
bootstrapScript = require.resolve(path.resolve(__dirname, '..', '..', 'spec', 'spec-bootstrap'))
timeoutInSeconds = Number.parseFloat(timeout)
unless Number.isNaN(timeoutInSeconds)
timeoutHandler = ->
console.log "The test suite has timed out because it has been running for more than #{timeoutInSeconds} seconds."
process.exit(124) # Use the same exit code as the UNIX timeout util.
setTimeout(timeoutHandler, timeoutInSeconds * 1000)
try
windowInitializationScript = require.resolve(path.resolve(@devResourcePath, 'src', 'initialize-test-window'))
catch error
windowInitializationScript = require.resolve(path.resolve(__dirname, '..', '..', 'src', 'initialize-test-window'))
testPaths = []
if pathsToOpen?
for pathToOpen in pathsToOpen
testPaths.push(path.resolve(executedFrom, fs.normalize(pathToOpen)))
if testPaths.length is 0
process.stderr.write 'Error: Specify at least one test path\n\n'
process.exit(1)
legacyTestRunnerPath = @resolveLegacyTestRunnerPath()
testRunnerPath = @resolveTestRunnerPath(testPaths[0])
isSpec = true
devMode = true
safeMode ?= false
new AtomWindow({bootstrapScript, resourcePath, exitWhenDone, isSpec, devMode, specDirectory, logFile, safeMode})
new AtomWindow({windowInitializationScript, resourcePath, headless, isSpec, devMode, testRunnerPath, legacyTestRunnerPath, testPaths, logFile, safeMode})
resolveTestRunnerPath: (testPath) ->
FindParentDir ?= require 'find-parent-dir'
if packageRoot = FindParentDir.sync(testPath, 'package.json')
packageMetadata = require(path.join(packageRoot, 'package.json'))
if packageMetadata.atomTestRunner
Resolve ?= require('resolve')
if testRunnerPath = Resolve.sync(packageMetadata.atomTestRunner, basedir: packageRoot, extensions: Object.keys(require.extensions))
return testRunnerPath
else
process.stderr.write "Error: Could not resolve test runner path '#{packageMetadata.atomTestRunner}'"
process.exit(1)
@resolveLegacyTestRunnerPath()
resolveLegacyTestRunnerPath: ->
try
require.resolve(path.resolve(@devResourcePath, 'spec', 'jasmine-test-runner'))
catch error
require.resolve(path.resolve(__dirname, '..', '..', 'spec', 'jasmine-test-runner'))
locationForPathToOpen: (pathToOpen, executedFrom='') ->
return {pathToOpen} unless pathToOpen

View File

@@ -29,7 +29,7 @@ class AtomProtocolHandler
# Creates the 'atom' custom protocol handler.
registerAtomProtocol: ->
protocol.registerProtocol 'atom', (request) =>
protocol.registerFileProtocol 'atom', (request, callback) =>
relativePath = path.normalize(request.url.substr(7))
if relativePath.indexOf('assets/') is 0
@@ -41,4 +41,4 @@ class AtomProtocolHandler
filePath = path.join(loadPath, relativePath)
break if fs.statSyncNoException(filePath).isFile?()
new protocol.RequestFileJob(filePath)
callback(filePath)

View File

@@ -19,7 +19,7 @@ class AtomWindow
isSpec: null
constructor: (settings={}) ->
{@resourcePath, pathToOpen, locationsToOpen, @isSpec, @exitWhenDone, @safeMode, @devMode} = settings
{@resourcePath, pathToOpen, locationsToOpen, @isSpec, @headless, @safeMode, @devMode} = settings
locationsToOpen ?= [{pathToOpen}] if pathToOpen
locationsToOpen ?= []
@@ -29,6 +29,10 @@ class AtomWindow
'web-preferences':
'direct-write': true
'subpixel-font-scaling': true
if @isSpec
options['web-preferences']['page-visibility'] = true
# Don't set icon on Windows so the exe's ico will be used as window and
# taskbar's icon. See https://github.com/atom/atom/issues/4811 for more.
if process.platform is 'linux'
@@ -84,7 +88,7 @@ class AtomWindow
hash: encodeURIComponent(JSON.stringify(loadSettings))
getLoadSettings: ->
if @browserWindow.webContents?.loaded
if @browserWindow.webContents? and not @browserWindow.webContents.isLoading()
hash = url.parse(@browserWindow.webContents.getUrl()).hash.substr(1)
JSON.parse(decodeURIComponent(hash))
@@ -131,7 +135,7 @@ class AtomWindow
@browserWindow.destroy() if chosen is 0
@browserWindow.webContents.on 'crashed', =>
global.atomApplication.exit(100) if @exitWhenDone
global.atomApplication.exit(100) if @headless
chosen = dialog.showMessageBox @browserWindow,
type: 'warning'
@@ -142,7 +146,9 @@ class AtomWindow
when 0 then @browserWindow.destroy()
when 1 then @browserWindow.restart()
@browserWindow.webContents.on 'will-navigate', (event) -> event.preventDefault()
@browserWindow.webContents.on 'will-navigate', (event, url) =>
unless url is @browserWindow.webContents.getUrl()
event.preventDefault()
@setupContextMenu()

View File

@@ -99,9 +99,9 @@ parseCommandLine = ->
options.alias('n', 'new-window').boolean('n').describe('n', 'Open a new window.')
options.boolean('profile-startup').describe('profile-startup', 'Create a profile of the startup execution time.')
options.alias('r', 'resource-path').string('r').describe('r', 'Set the path to the Atom source directory and enable dev-mode.')
options.alias('s', 'spec-directory').string('s').describe('s', 'Set the directory from which to run package specs (default: Atom\'s spec directory).')
options.boolean('safe').describe('safe', 'Do not load packages from ~/.atom/packages or ~/.atom/dev/packages.')
options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.')
options.string('timeout').describe('timeout', 'When in test mode, waits until the specified time (in minutes) and kills the process (exit code: 130).')
options.alias('v', 'version').boolean('v').describe('v', 'Print the version.')
options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.')
options.string('socket-path')
@@ -121,7 +121,7 @@ parseCommandLine = ->
safeMode = args['safe']
pathsToOpen = args._
test = args['test']
specDirectory = args['spec-directory']
timeout = args['timeout']
newWindow = args['new-window']
pidToKillWhenClosed = args['pid'] if args['wait']
logFile = args['log-file']
@@ -133,18 +133,9 @@ parseCommandLine = ->
if args['resource-path']
devMode = true
resourcePath = args['resource-path']
else
# Set resourcePath based on the specDirectory if running specs on atom core
if specDirectory?
packageDirectoryPath = path.join(specDirectory, '..')
packageManifestPath = path.join(packageDirectoryPath, 'package.json')
if fs.statSyncNoException(packageManifestPath)
try
packageManifest = JSON.parse(fs.readFileSync(packageManifestPath))
resourcePath = packageDirectoryPath if packageManifest.name is 'atom'
if devMode
resourcePath ?= devResourcePath
devMode = true if test
resourcePath ?= devResourcePath if devMode
unless fs.statSyncNoException(resourcePath)
resourcePath = path.dirname(path.dirname(__dirname))
@@ -157,7 +148,7 @@ parseCommandLine = ->
devResourcePath = normalizeDriveLetterName(devResourcePath)
{resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test,
version, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory,
logFile, socketPath, profileStartup}
version, pidToKillWhenClosed, devMode, safeMode, newWindow,
logFile, socketPath, profileStartup, timeout}
start()

View File

@@ -14,8 +14,12 @@ clipboard = require './safe-clipboard'
# ```
module.exports =
class Clipboard
metadata: null
signatureForMetadata: null
constructor: ->
@reset()
reset: ->
@metadata = null
@signatureForMetadata = null
# Creates an `md5` hash of some text.
#

View File

@@ -26,7 +26,7 @@ symlinkCommandWithPrivilegeSync = (sourcePath, destinationPath) ->
module.exports =
class CommandInstaller
constructor: (@appVersion) ->
constructor: (@appVersion, @applicationDelegate) ->
getInstallDirectory: ->
"/usr/local/bin"
@@ -36,7 +36,7 @@ class CommandInstaller
installShellCommandsInteractively: ->
showErrorDialog = (error) ->
atom.confirm
@applicationDelegate.confirm
message: "Failed to install shell commands"
detailedMessage: error.message
@@ -44,11 +44,11 @@ class CommandInstaller
if error?
showErrorDialog(error)
else
@installApmCommand true, (error) ->
@installApmCommand true, (error) =>
if error?
showErrorDialog(error)
else
atom.confirm
@applicationDelegate.confirm
message: "Commands installed."
detailedMessage: "The shell commands `atom` and `apm` are installed."

View File

@@ -44,15 +44,23 @@ SequenceCount = 0
# ```
module.exports =
class CommandRegistry
constructor: (@rootNode) ->
constructor: ->
@rootNode = null
@clear()
clear: ->
@registeredCommands = {}
@selectorBasedListenersByCommandName = {}
@inlineListenersByCommandName = {}
@emitter = new Emitter
attach: (@rootNode) ->
@commandRegistered(command) for command of @selectorBasedListenersByCommandName
@commandRegistered(command) for command of @inlineListenersByCommandName
destroy: ->
for commandName of @registeredCommands
window.removeEventListener(commandName, @handleCommandEvent, true)
@rootNode.removeEventListener(commandName, @handleCommandEvent, true)
return
# Public: Add one or more command listeners associated with a selector.
@@ -253,8 +261,8 @@ class CommandRegistry
matched
commandRegistered: (commandName) ->
unless @registeredCommands[commandName]
window.addEventListener(commandName, @handleCommandEvent, true)
if @rootNode? and not @registeredCommands[commandName]
@rootNode.addEventListener(commandName, @handleCommandEvent, true)
@registeredCommands[commandName] = true
class SelectorBasedListener

View File

@@ -158,6 +158,27 @@ require('source-map-support').install({
}
})
var sourceMapPrepareStackTrace = Error.prepareStackTrace
var prepareStackTrace = sourceMapPrepareStackTrace
// Prevent coffee-script from reassigning Error.prepareStackTrace
Object.defineProperty(Error, 'prepareStackTrace', {
get: function () { return prepareStackTrace },
set: function (newValue) {}
})
// Enable Grim to access the raw stack without reassigning Error.prepareStackTrace
Error.prototype.getRawStack = function () { // eslint-disable-line no-extend-native
prepareStackTrace = getRawStack
var result = this.stack
prepareStackTrace = sourceMapPrepareStackTrace
return result
}
function getRawStack (_, stack) {
return stack
}
Object.keys(COMPILERS).forEach(function (extension) {
var compiler = COMPILERS[extension]

View File

@@ -12,24 +12,27 @@ module.exports =
default: [".git", ".hg", ".svn", ".DS_Store", "._*", "Thumbs.db"]
items:
type: 'string'
description: 'List of string glob patterns. Files and directories matching these patterns will be ignored by some packages, such as the fuzzy finder and tree view. Individual packages might have additional config settings for ignoring names.'
excludeVcsIgnoredPaths:
type: 'boolean'
default: true
title: 'Exclude VCS Ignored Paths'
description: 'Files and directories ignored by the current project\'s VCS system will be ignored by some packages, such as the fuzzy finder and find and replace. For example, projects using Git have these paths defined in the .gitignore file. Individual packages might have additional config settings for ignoring VCS ignored files and folders.'
followSymlinks:
type: 'boolean'
default: true
title: 'Follow symlinks'
description: 'Used when searching and when opening files with the fuzzy finder.'
description: 'Follow symbolic links when searching files and when opening files with the fuzzy finder.'
disabledPackages:
type: 'array'
default: []
items:
type: 'string'
description: 'List of names of installed packages which are not loaded at startup.'
customFileTypes:
type: 'object'
default: {}
description: 'Associates scope names (e.g. "source.js") with arrays of file extensions and file names (e.g. ["Somefile", ".js2"])'
description: 'Associates scope names (e.g. `"source.js"`) with arrays of file extensions and file names (e.g. `["Somefile", ".js2"]`)'
additionalProperties:
type: 'array'
items:
@@ -39,15 +42,19 @@ module.exports =
default: ['one-dark-ui', 'one-dark-syntax']
items:
type: 'string'
description: 'Names of UI and syntax themes which will be used when Atom starts.'
projectHome:
type: 'string'
default: path.join(fs.getHomeDirectory(), 'github')
description: 'The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default.'
audioBeep:
type: 'boolean'
default: true
description: 'Trigger the system\'s beep sound when certain actions cannot be executed or there are no results.'
destroyEmptyPanes:
type: 'boolean'
default: true
description: 'When the last item of a pane is removed, remove that pane as well.'
fileEncoding:
description: 'Default character set encoding to use when reading and writing files.'
type: 'string'
@@ -90,7 +97,7 @@ module.exports =
'windows866'
]
openEmptyEditorOnStart:
description: 'Automatically opens an empty editor when atom starts.'
description: 'Automatically open an empty editor on startup.'
type: 'boolean'
default: true
@@ -113,41 +120,51 @@ module.exports =
fontFamily:
type: 'string'
default: ''
description: 'The name of the font family used for editor text.'
fontSize:
type: 'integer'
default: 14
minimum: 1
maximum: 100
description: 'Height in pixels of editor text.'
lineHeight:
type: ['string', 'number']
default: 1.5
description: 'Height of editor lines, as a multiplier of font size.'
showInvisibles:
type: 'boolean'
default: false
description: 'Render placeholders for invisible characters, such as tabs, spaces and newlines.'
showIndentGuide:
type: 'boolean'
default: false
description: 'Show indentation indicators in the editor.'
showLineNumbers:
type: 'boolean'
default: true
description: 'Show line numbers in the editor\'s gutter.'
autoIndent:
type: 'boolean'
default: true
description: 'Automatically indent the cursor when inserting a newline'
description: 'Automatically indent the cursor when inserting a newline.'
autoIndentOnPaste:
type: 'boolean'
default: true
description: 'Automatically indent pasted text based on the indentation of the previous line.'
nonWordCharacters:
type: 'string'
default: "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-…"
description: 'A string of non-word characters to define word boundaries.'
preferredLineLength:
type: 'integer'
default: 80
minimum: 1
description: 'Identifies the length of a line which is used when wrapping text with the `Soft Wrap At Preferred Line Length` setting enabled, in number of characters.'
tabLength:
type: 'integer'
default: 2
enum: [1, 2, 3, 4, 6, 8]
description: 'Number of spaces used to represent a tab.'
softWrap:
type: 'boolean'
default: false
@@ -155,32 +172,36 @@ module.exports =
softTabs:
type: 'boolean'
default: true
description: 'If the `Tab Type` config setting is set to "auto" and autodetection of tab type from buffer content fails, then this config setting determines whether a soft tab or a hard tab will be inserted when the Tab key is pressed.'
tabType:
type: 'string'
default: 'auto'
enum: ['auto', 'soft', 'hard']
description: 'Determine character inserted during Tab keypress.'
description: 'Determine character inserted when Tab key is pressed. Possible values: "auto", "soft" and "hard". When set to "soft" or "hard", soft tabs (spaces) or hard tabs (tab characters) are used. When set to "auto", the editor auto-detects the tab type based on the contents of the buffer (it uses the first leading whitespace on a non-comment line), or uses the value of the Soft Tabs config setting if auto-detection fails.'
softWrapAtPreferredLineLength:
type: 'boolean'
default: false
description: 'Will wrap to the number of characters defined by the `Preferred Line Length` setting. This will only take effect when soft wrap is enabled globally or for the current language.'
description: 'Instead of wrapping lines to the window\'s width, wrap lines to the number of characters defined by the `Preferred Line Length` setting. This will only take effect when the soft wrap config setting is enabled globally or for the current language.'
softWrapHangingIndent:
type: 'integer'
default: 0
minimum: 0
description: 'When soft wrap is enabled, defines length of additional indentation applied to wrapped lines, in number of characters.'
scrollSensitivity:
type: 'integer'
default: 40
minimum: 10
maximum: 200
description: 'Determines how fast the editor scrolls when using a mouse or trackpad.'
scrollPastEnd:
type: 'boolean'
default: false
description: 'Allow the editor to be scrolled past the end of the last line.'
undoGroupingInterval:
type: 'integer'
default: 300
minimum: 0
description: 'Time interval in milliseconds within which operations will be grouped together in the undo history'
description: 'Time interval in milliseconds within which text editing operations will be grouped together in the undo history.'
useShadowDOM:
type: 'boolean'
default: true
@@ -190,33 +211,39 @@ module.exports =
type: 'boolean'
default: true
title: 'Confirm Checkout HEAD Revision'
description: 'Show confirmation dialog when checking out the HEAD revision and discarding changes to current file since last commit.'
backUpBeforeSaving:
type: 'boolean'
default: false
description: 'Ensure file contents aren\'t lost if there is an I/O error during save by making a temporary backup copy.'
invisibles:
type: 'object'
description: 'A hash of characters Atom will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types).'
properties:
eol:
type: ['boolean', 'string']
default: '\u00ac'
maximumLength: 1
description: 'Character used to render newline characters (\\n) when the `Show Invisibles` setting is enabled. '
space:
type: ['boolean', 'string']
default: '\u00b7'
maximumLength: 1
description: 'Character used to render leading and trailing space characters when the `Show Invisibles` setting is enabled.'
tab:
type: ['boolean', 'string']
default: '\u00bb'
maximumLength: 1
description: 'Character used to render hard tab characters (\\t) when the `Show Invisibles` setting is enabled.'
cr:
type: ['boolean', 'string']
default: '\u00a4'
maximumLength: 1
description: 'Character used to render carriage return characters (for Microsoft-style line endings) when the `Show Invisibles` setting is enabled.'
zoomFontWhenCtrlScrolling:
type: 'boolean'
default: process.platform isnt 'darwin'
description: 'Increase/decrease the editor font size when pressing the Ctrl key and scrolling the mouse up/down.'
description: 'Change the editor font size when pressing the Ctrl key and scrolling the mouse up/down.'
if process.platform in ['win32', 'linux']
module.exports.core.properties.autoHideMenuBar =

View File

@@ -5,7 +5,10 @@ CSON = require 'season'
path = require 'path'
async = require 'async'
pathWatcher = require 'pathwatcher'
{pushKeyPath, splitKeyPath, getValueAtKeyPath, setValueAtKeyPath} = require 'key-path-helpers'
{
getValueAtKeyPath, setValueAtKeyPath, deleteValueAtKeyPath,
pushKeyPath, splitKeyPath,
} = require 'key-path-helpers'
Color = require './color'
ScopedPropertyStore = require 'scoped-property-store'
@@ -331,7 +334,13 @@ class Config
value
# Created during initialization, available as `atom.config`
constructor: ({@configDirPath, @resourcePath}={}) ->
constructor: ({@configDirPath, @resourcePath, @notificationManager, @enablePersistence}={}) ->
if @enablePersistence?
@configFilePath = fs.resolve(@configDirPath, 'config', ['json', 'cson'])
@configFilePath ?= path.join(@configDirPath, 'config.cson')
@clear()
clear: ->
@emitter = new Emitter
@schema =
type: 'object'
@@ -340,11 +349,8 @@ class Config
@settings = {}
@scopedSettingsStore = new ScopedPropertyStore
@configFileHasErrors = false
@configFilePath = fs.resolve(@configDirPath, 'config', ['json', 'cson'])
@configFilePath ?= path.join(@configDirPath, 'config.cson')
@transactDepth = 0
@savePending = false
@requestLoad = _.debounce(@loadUserConfig, 100)
@requestSave = =>
@savePending = true
@@ -354,6 +360,8 @@ class Config
@save()
debouncedSave = _.debounce(save, 100)
shouldNotAccessFileSystem: -> not @enablePersistence
###
Section: Config Subscription
###
@@ -724,7 +732,7 @@ class Config
###
initializeConfigDirectory: (done) ->
return if fs.existsSync(@configDirPath)
return if fs.existsSync(@configDirPath) or @shouldNotAccessFileSystem()
fs.makeTreeSync(@configDirPath)
@@ -740,6 +748,8 @@ class Config
fs.traverseTree(templateConfigDirPath, onConfigDirFile, (path) -> true)
loadUserConfig: ->
return if @shouldNotAccessFileSystem()
unless fs.existsSync(@configFilePath)
fs.makeTreeSync(path.dirname(@configFilePath))
CSON.writeFileSync(@configFilePath, {})
@@ -763,6 +773,8 @@ class Config
@notifyFailure(message, detail)
observeUserConfig: ->
return if @shouldNotAccessFileSystem()
try
@watchSubscription ?= pathWatcher.watch @configFilePath, (eventType) =>
@requestLoad() if eventType is 'change' and @watchSubscription?
@@ -779,9 +791,11 @@ class Config
@watchSubscription = null
notifyFailure: (errorMessage, detail) ->
atom.notifications.addError(errorMessage, {detail, dismissable: true})
@notificationManager.addError(errorMessage, {detail, dismissable: true})
save: ->
return if @shouldNotAccessFileSystem()
allSettings = {'*': @settings}
allSettings = _.extend allSettings, @scopedSettingsStore.propertiesForSource(@getUserConfigPath())
try
@@ -832,12 +846,16 @@ class Config
setRawValue: (keyPath, value) ->
defaultValue = getValueAtKeyPath(@defaultSettings, keyPath)
value = undefined if _.isEqual(defaultValue, value)
if keyPath?
setValueAtKeyPath(@settings, keyPath, value)
if _.isEqual(defaultValue, value)
if keyPath?
deleteValueAtKeyPath(@settings, keyPath)
else
@settings = null
else
@settings = value
if keyPath?
setValueAtKeyPath(@settings, keyPath, value)
else
@settings = value
@emitChangeEvent()
observeKeyPath: (keyPath, options, callback) ->

View File

@@ -4,6 +4,7 @@ CSON = require 'season'
fs = require 'fs-plus'
{calculateSpecificity, validateSelector} = require 'clear-cut'
{Disposable} = require 'event-kit'
remote = require 'remote'
MenuHelpers = require './menu-helpers'
platformContextMenu = require('../package.json')?._atomMenu?['context-menu']
@@ -40,11 +41,11 @@ platformContextMenu = require('../package.json')?._atomMenu?['context-menu']
# {::add} for more information.
module.exports =
class ContextMenuManager
constructor: ({@resourcePath, @devMode}) ->
constructor: ({@resourcePath, @devMode, @keymapManager}) ->
@definitions = {'.overlayer': []} # TODO: Remove once color picker package stops touching private data
@clear()
atom.keymaps.onDidLoadBundledKeymaps => @loadPlatformItems()
@keymapManager.onDidLoadBundledKeymaps => @loadPlatformItems()
loadPlatformItems: ->
if platformContextMenu?
@@ -175,7 +176,7 @@ class ContextMenuManager
menuTemplate = @templateForEvent(event)
return unless menuTemplate?.length > 0
atom.getCurrentWindow().emit('context-menu', menuTemplate)
remote.getCurrentWindow().emit('context-menu', menuTemplate)
return
clear: ->

View File

@@ -16,7 +16,7 @@ class Cursor extends Model
visible: true
# Instantiated by a {TextEditor}
constructor: ({@editor, @marker, id}) ->
constructor: ({@editor, @marker, @config, id}) ->
@emitter = new Emitter
@assignId(id)
@@ -158,7 +158,7 @@ class Cursor extends Model
[before, after] = @editor.getTextInBufferRange(range)
return false if /\s/.test(before) or /\s/.test(after)
nonWordCharacters = atom.config.get('editor.nonWordCharacters', scope: @getScopeDescriptor()).split('')
nonWordCharacters = @config.get('editor.nonWordCharacters', scope: @getScopeDescriptor()).split('')
_.contains(nonWordCharacters, before) isnt _.contains(nonWordCharacters, after)
# Public: Returns whether this cursor is between a word's start and end.
@@ -605,7 +605,7 @@ class Cursor extends Model
# Returns a {RegExp}.
wordRegExp: ({includeNonWordCharacters}={}) ->
includeNonWordCharacters ?= true
nonWordCharacters = atom.config.get('editor.nonWordCharacters', scope: @getScopeDescriptor())
nonWordCharacters = @config.get('editor.nonWordCharacters', scope: @getScopeDescriptor())
segments = ["^[\t ]*$"]
segments.push("[^\\s#{_.escapeRegExp(nonWordCharacters)}]+")
if includeNonWordCharacters
@@ -620,7 +620,7 @@ class Cursor extends Model
#
# Returns a {RegExp}.
subwordRegExp: (options={}) ->
nonWordCharacters = atom.config.get('editor.nonWordCharacters', scope: @getScopeDescriptor())
nonWordCharacters = @config.get('editor.nonWordCharacters', scope: @getScopeDescriptor())
lowercaseLetters = 'a-z\\u00DF-\\u00F6\\u00F8-\\u00FF'
uppercaseLetters = 'A-Z\\u00C0-\\u00D6\\u00D8-\\u00DE'
snakeCamelSegment = "[#{uppercaseLetters}]?[#{lowercaseLetters}]+"

View File

@@ -6,12 +6,12 @@
module.exports =
class CustomGutterComponent
constructor: ({@gutter}) ->
constructor: ({@gutter, @views}) ->
@decorationNodesById = {}
@decorationItemsById = {}
@visible = true
@domNode = atom.views.getView(@gutter)
@domNode = @views.getView(@gutter)
@decorationsNode = @domNode.firstChild
# Clear the contents in case the domNode is being reused.
@decorationsNode.innerHTML = ''

View File

@@ -21,7 +21,7 @@
# ```
module.exports =
class DeserializerManager
constructor: ->
constructor: (@atomEnvironment) ->
@deserializers = {}
# Public: Register the given class(es) as deserializers.
@@ -29,7 +29,10 @@ class DeserializerManager
# * `deserializers` One or more deserializers to register. A deserializer can
# be any object with a `.name` property and a `.deserialize()` method. A
# common approach is to register a *constructor* as the deserializer for its
# instances by adding a `.deserialize()` class method.
# instances by adding a `.deserialize()` class method. When your method is
# called, it will be passed serialized state as the first argument and the
# {Atom} environment object as the second argument, which is useful if you
# wish to avoid referencing the `atom` global.
add: (deserializers...) ->
@deserializers[deserializer.name] = deserializer for deserializer in deserializers
new Disposable =>
@@ -39,15 +42,13 @@ class DeserializerManager
# Public: Deserialize the state and params.
#
# * `state` The state {Object} to deserialize.
# * `params` The params {Object} to pass as the second arguments to the
# deserialize method of the deserializer.
deserialize: (state, params) ->
deserialize: (state) ->
return unless state?
if deserializer = @get(state)
stateVersion = state.get?('version') ? state.version
return if deserializer.version? and deserializer.version isnt stateVersion
deserializer.deserialize(state, params)
deserializer.deserialize(state, @atomEnvironment)
else
console.warn "No deserializer found for", state
@@ -59,3 +60,6 @@ class DeserializerManager
name = state.get?('deserializer') ? state.deserializer
@deserializers[name]
clear: ->
@deserializers = {}

View File

@@ -26,17 +26,29 @@ class DisplayBuffer extends Model
height: null
width: null
@deserialize: (state) ->
state.tokenizedBuffer = TokenizedBuffer.deserialize(state.tokenizedBuffer)
@deserialize: (state, atomEnvironment) ->
state.tokenizedBuffer = TokenizedBuffer.deserialize(state.tokenizedBuffer, atomEnvironment)
state.config = atomEnvironment.config
state.assert = atomEnvironment.assert
state.grammarRegistry = atomEnvironment.grammars
state.packageManager = atomEnvironment.packages
new this(state)
constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, ignoreInvisibles, @largeFileMode}={}) ->
constructor: (params={}) ->
super
{
tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, ignoreInvisibles,
@largeFileMode, @config, @assert, @grammarRegistry, @packageManager
} = params
@emitter = new Emitter
@disposables = new CompositeDisposable
@tokenizedBuffer ?= new TokenizedBuffer({tabLength, buffer, ignoreInvisibles, @largeFileMode})
@tokenizedBuffer ?= new TokenizedBuffer({
tabLength, buffer, ignoreInvisibles, @largeFileMode, @config,
@grammarRegistry, @packageManager, @assert
})
@buffer = @tokenizedBuffer.buffer
@charWidthsByScope = {}
@markers = {}
@@ -61,29 +73,29 @@ class DisplayBuffer extends Model
oldConfigSettings = @configSettings
@configSettings =
scrollPastEnd: atom.config.get('editor.scrollPastEnd', scope: scopeDescriptor)
softWrap: atom.config.get('editor.softWrap', scope: scopeDescriptor)
softWrapAtPreferredLineLength: atom.config.get('editor.softWrapAtPreferredLineLength', scope: scopeDescriptor)
softWrapHangingIndent: atom.config.get('editor.softWrapHangingIndent', scope: scopeDescriptor)
preferredLineLength: atom.config.get('editor.preferredLineLength', scope: scopeDescriptor)
scrollPastEnd: @config.get('editor.scrollPastEnd', scope: scopeDescriptor)
softWrap: @config.get('editor.softWrap', scope: scopeDescriptor)
softWrapAtPreferredLineLength: @config.get('editor.softWrapAtPreferredLineLength', scope: scopeDescriptor)
softWrapHangingIndent: @config.get('editor.softWrapHangingIndent', scope: scopeDescriptor)
preferredLineLength: @config.get('editor.preferredLineLength', scope: scopeDescriptor)
subscriptions.add atom.config.onDidChange 'editor.softWrap', scope: scopeDescriptor, ({newValue}) =>
subscriptions.add @config.onDidChange 'editor.softWrap', scope: scopeDescriptor, ({newValue}) =>
@configSettings.softWrap = newValue
@updateWrappedScreenLines()
subscriptions.add atom.config.onDidChange 'editor.softWrapHangingIndent', scope: scopeDescriptor, ({newValue}) =>
subscriptions.add @config.onDidChange 'editor.softWrapHangingIndent', scope: scopeDescriptor, ({newValue}) =>
@configSettings.softWrapHangingIndent = newValue
@updateWrappedScreenLines()
subscriptions.add atom.config.onDidChange 'editor.softWrapAtPreferredLineLength', scope: scopeDescriptor, ({newValue}) =>
subscriptions.add @config.onDidChange 'editor.softWrapAtPreferredLineLength', scope: scopeDescriptor, ({newValue}) =>
@configSettings.softWrapAtPreferredLineLength = newValue
@updateWrappedScreenLines() if @isSoftWrapped()
subscriptions.add atom.config.onDidChange 'editor.preferredLineLength', scope: scopeDescriptor, ({newValue}) =>
subscriptions.add @config.onDidChange 'editor.preferredLineLength', scope: scopeDescriptor, ({newValue}) =>
@configSettings.preferredLineLength = newValue
@updateWrappedScreenLines() if @isSoftWrapped() and atom.config.get('editor.softWrapAtPreferredLineLength', scope: scopeDescriptor)
@updateWrappedScreenLines() if @isSoftWrapped() and @config.get('editor.softWrapAtPreferredLineLength', scope: scopeDescriptor)
subscriptions.add atom.config.observe 'editor.scrollPastEnd', scope: scopeDescriptor, (value) =>
subscriptions.add @config.observe 'editor.scrollPastEnd', scope: scopeDescriptor, (value) =>
@configSettings.scrollPastEnd = value
@updateWrappedScreenLines() if oldConfigSettings? and not _.isEqual(oldConfigSettings, @configSettings)
@@ -97,7 +109,10 @@ class DisplayBuffer extends Model
largeFileMode: @largeFileMode
copy: ->
newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength(), @largeFileMode})
newDisplayBuffer = new DisplayBuffer({
@buffer, tabLength: @getTabLength(), @largeFileMode, @config, @assert,
@grammarRegistry, @packageManager
})
for marker in @findMarkers(displayBufferId: @id)
marker.copy(displayBufferId: newDisplayBuffer.id)
@@ -189,10 +204,24 @@ class DisplayBuffer extends Model
getLineHeightInPixels: -> @lineHeightInPixels
setLineHeightInPixels: (@lineHeightInPixels) -> @lineHeightInPixels
getKoreanCharWidth: -> @koreanCharWidth
getHalfWidthCharWidth: -> @halfWidthCharWidth
getDoubleWidthCharWidth: -> @doubleWidthCharWidth
getDefaultCharWidth: -> @defaultCharWidth
setDefaultCharWidth: (defaultCharWidth) ->
if defaultCharWidth isnt @defaultCharWidth
setDefaultCharWidth: (defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth) ->
doubleWidthCharWidth ?= defaultCharWidth
halfWidthCharWidth ?= defaultCharWidth
koreanCharWidth ?= defaultCharWidth
if defaultCharWidth isnt @defaultCharWidth or doubleWidthCharWidth isnt @doubleWidthCharWidth and halfWidthCharWidth isnt @halfWidthCharWidth and koreanCharWidth isnt @koreanCharWidth
@defaultCharWidth = defaultCharWidth
@doubleWidthCharWidth = doubleWidthCharWidth
@halfWidthCharWidth = halfWidthCharWidth
@koreanCharWidth = koreanCharWidth
@updateWrappedScreenLines() if @isSoftWrapped() and @getEditorWidthInChars()?
defaultCharWidth
getCursorWidth: -> 1
@@ -262,6 +291,40 @@ class DisplayBuffer extends Model
else
@getEditorWidthInChars()
getSoftWrapColumnForTokenizedLine: (tokenizedLine) ->
lineMaxWidth = @getSoftWrapColumn() * @getDefaultCharWidth()
return if Number.isNaN(lineMaxWidth)
return 0 if lineMaxWidth is 0
iterator = tokenizedLine.getTokenIterator(false)
column = 0
currentWidth = 0
while iterator.next()
textIndex = 0
text = iterator.getText()
while textIndex < text.length
if iterator.isPairedCharacter()
charLength = 2
else
charLength = 1
if iterator.hasDoubleWidthCharacterAt(textIndex)
charWidth = @getDoubleWidthCharWidth()
else if iterator.hasHalfWidthCharacterAt(textIndex)
charWidth = @getHalfWidthCharWidth()
else if iterator.hasKoreanCharacterAt(textIndex)
charWidth = @getKoreanCharWidth()
else
charWidth = @getDefaultCharWidth()
return column if currentWidth + charWidth > lineMaxWidth
currentWidth += charWidth
column += charLength
textIndex += charLength
column
# Gets the screen line for the given screen row.
#
# * `screenRow` - A {Number} indicating the screen row.
@@ -958,7 +1021,7 @@ class DisplayBuffer extends Model
else
softWraps = 0
if @isSoftWrapped()
while wrapScreenColumn = tokenizedLine.findWrapColumn(@getSoftWrapColumn())
while wrapScreenColumn = tokenizedLine.findWrapColumn(@getSoftWrapColumnForTokenizedLine(tokenizedLine))
[wrappedLine, tokenizedLine] = tokenizedLine.softWrapAt(
wrapScreenColumn,
@configSettings.softWrapHangingIndent
@@ -1036,8 +1099,8 @@ class DisplayBuffer extends Model
tokenizedLinesCount = @tokenizedBuffer.getLineCount()
bufferLinesCount = @buffer.getLineCount()
atom.assert screenLinesCount is tokenizedLinesCount, "Display buffer line count out of sync with tokenized buffer", (error) ->
@assert screenLinesCount is tokenizedLinesCount, "Display buffer line count out of sync with tokenized buffer", (error) ->
error.metadata = {screenLinesCount, tokenizedLinesCount, bufferLinesCount}
atom.assert screenLinesCount is bufferLinesCount, "Display buffer line count out of sync with buffer", (error) ->
@assert screenLinesCount is bufferLinesCount, "Display buffer line count out of sync with buffer", (error) ->
error.metadata = {screenLinesCount, tokenizedLinesCount, bufferLinesCount}

View File

@@ -48,7 +48,7 @@ isValidGitDirectorySync = (directory) ->
module.exports =
class GitRepositoryProvider
constructor: (@project) ->
constructor: (@project, @config) ->
# Keys are real paths that end in `.git`.
# Values are the corresponding GitRepository objects.
@pathToRepository = {}
@@ -75,7 +75,7 @@ class GitRepositoryProvider
gitDirPath = gitDir.getPath()
repo = @pathToRepository[gitDirPath]
unless repo
repo = GitRepository.open(gitDirPath, project: @project)
repo = GitRepository.open(gitDirPath, {@project, @config})
return null unless repo
repo.onDidDestroy(=> delete @pathToRepository[gitDirPath])
@pathToRepository[gitDirPath] = repo

View File

@@ -83,7 +83,7 @@ class GitRepository
for submodulePath, submoduleRepo of @repo.submodules
submoduleRepo.upstream = {ahead: 0, behind: 0}
{@project, refreshOnWindowFocus} = options
{@project, @config, refreshOnWindowFocus} = options
refreshOnWindowFocus ?= true
if refreshOnWindowFocus
@@ -449,25 +449,10 @@ class GitRepository
# Subscribes to editor view event.
checkoutHeadForEditor: (editor) ->
filePath = editor.getPath()
return unless filePath
fileName = basename(filePath)
checkoutHead = =>
if filePath = editor.getPath()
editor.buffer.reload() if editor.buffer.isModified()
@checkoutHead(filePath)
if atom.config.get('editor.confirmCheckoutHeadRevision')
atom.confirm
message: 'Confirm Checkout HEAD Revision'
detailedMessage: "Are you sure you want to discard all changes to \"#{fileName}\" since the last Git commit?"
buttons:
OK: checkoutHead
Cancel: null
else
checkoutHead()
# Returns the corresponding {Repository}
getRepo: (path) ->
if @repo?

View File

@@ -14,19 +14,9 @@ PathSplitRegex = new RegExp("[/.]")
# language-specific comment regexes. See {::getProperty} for more details.
module.exports =
class GrammarRegistry extends FirstMate.GrammarRegistry
@deserialize: ({grammarOverridesByPath}) ->
grammarRegistry = new GrammarRegistry()
grammarRegistry.grammarOverridesByPath = grammarOverridesByPath
grammarRegistry
atom.deserializers.add(this)
constructor: ->
constructor: ({@config}={}) ->
super(maxTokensPerLine: 100)
serialize: ->
{deserializer: @constructor.name, @grammarOverridesByPath}
createToken: (value, scopes) -> new Token({value, scopes})
# Extended: Select a grammar for the given file path and file contents.
@@ -70,7 +60,7 @@ class GrammarRegistry extends FirstMate.GrammarRegistry
pathScore = -1
fileTypes = grammar.fileTypes
if customFileTypes = atom.config.get('core.customFileTypes')?[grammar.scopeName]
if customFileTypes = @config.get('core.customFileTypes')?[grammar.scopeName]
fileTypes = fileTypes.concat(customFileTypes)
for fileType, i in fileTypes
@@ -133,6 +123,3 @@ class GrammarRegistry extends FirstMate.GrammarRegistry
clearGrammarOverrides: ->
@grammarOverridesByPath = {}
undefined
clearObservers: ->
@emitter = new Emitter

View File

@@ -7,7 +7,7 @@ LineNumberGutterComponent = require './line-number-gutter-component'
module.exports =
class GutterContainerComponent
constructor: ({@onLineNumberGutterMouseDown, @editor, @domElementPool}) ->
constructor: ({@onLineNumberGutterMouseDown, @editor, @domElementPool, @views}) ->
# An array of objects of the form: {name: {String}, component: {Object}}
@gutterComponents = []
@gutterComponentsByGutterName = {}
@@ -39,10 +39,10 @@ class GutterContainerComponent
gutterComponent = @gutterComponentsByGutterName[gutter.name]
if not gutterComponent
if gutter.name is 'line-number'
gutterComponent = new LineNumberGutterComponent({onMouseDown: @onLineNumberGutterMouseDown, @editor, gutter, @domElementPool})
gutterComponent = new LineNumberGutterComponent({onMouseDown: @onLineNumberGutterMouseDown, @editor, gutter, @domElementPool, @views})
@lineNumberGutterComponent = gutterComponent
else
gutterComponent = new CustomGutterComponent({gutter})
gutterComponent = new CustomGutterComponent({gutter, @views})
if visible then gutterComponent.showNode() else gutterComponent.hideNode()
# Pass the gutter only the state that it needs.

View File

@@ -0,0 +1,34 @@
# Like sands through the hourglass, so are the days of our lives.
path = require 'path'
require './window'
{getWindowLoadSettings} = require './window-load-settings-helpers'
{resourcePath, isSpec, devMode} = getWindowLoadSettings()
# Add application-specific exports to module search path.
exportsPath = path.join(resourcePath, 'exports')
require('module').globalPaths.push(exportsPath)
process.env.NODE_PATH = exportsPath
# Make React faster
process.env.NODE_ENV ?= 'production' unless devMode
AtomEnvironment = require './atom-environment'
ApplicationDelegate = require './application-delegate'
window.atom = new AtomEnvironment({
window, document,
applicationDelegate: new ApplicationDelegate,
configDirPath: process.env.ATOM_HOME
enablePersistence: true
})
atom.displayWindow()
atom.loadStateSync()
atom.startEditorWindow()
# Workaround for focus getting cleared upon window creation
windowFocused = ->
window.removeEventListener('focus', windowFocused)
setTimeout (-> document.querySelector('atom-workspace').focus()), 0
window.addEventListener('focus', windowFocused)

View File

@@ -0,0 +1,69 @@
# Start the crash reporter before anything else.
require('crash-reporter').start(productName: 'Atom', companyName: 'GitHub')
remote = require 'remote'
exitWithStatusCode = (status) ->
remote.require('app').emit('will-quit')
remote.process.exit(status)
try
path = require 'path'
ipc = require 'ipc'
{getWindowLoadSettings} = require './window-load-settings-helpers'
AtomEnvironment = require '../src/atom-environment'
ApplicationDelegate = require '../src/application-delegate'
{testRunnerPath, legacyTestRunnerPath, headless, logFile, testPaths} = getWindowLoadSettings()
if headless
# Override logging in headless mode so it goes to the console, regardless
# of the --enable-logging flag to Electron.
console.log = (args...) ->
ipc.send 'write-to-stdout', args.join(' ') + '\n'
console.warn = (args...) ->
ipc.send 'write-to-stderr', args.join(' ') + '\n'
console.error = (args...) ->
ipc.send 'write-to-stderr', args.join(' ') + '\n'
else
# Show window synchronously so a focusout doesn't fire on input elements
# that are focused in the very first spec run.
remote.getCurrentWindow().show()
handleKeydown = (event) ->
# Reload: cmd-r / ctrl-r
if (event.metaKey or event.ctrlKey) and event.keyCode is 82
ipc.send('call-window-method', 'restart')
# Toggle Dev Tools: cmd-alt-i / ctrl-alt-i
if (event.metaKey or event.ctrlKey) and event.altKey and event.keyCode is 73
ipc.send('call-window-method', 'toggleDevTools')
# Reload: cmd-w / ctrl-w
if (event.metaKey or event.ctrlKey) and event.keyCode is 87
ipc.send('call-window-method', 'close')
window.addEventListener('keydown', handleKeydown, true)
# Add 'exports' to module search path.
exportsPath = path.join(getWindowLoadSettings().resourcePath, 'exports')
require('module').globalPaths.push(exportsPath)
process.env.NODE_PATH = exportsPath # Set NODE_PATH env variable since tasks may need it.
document.title = "Spec Suite"
testRunner = require(testRunnerPath)
legacyTestRunner = require(legacyTestRunnerPath)
buildAtomEnvironment = (params) -> new AtomEnvironment(params)
buildDefaultApplicationDelegate = (params) -> new ApplicationDelegate()
promise = testRunner({
logFile, headless, testPaths, buildAtomEnvironment, buildDefaultApplicationDelegate, legacyTestRunner
})
promise.then(exitWithStatusCode) if getWindowLoadSettings().headless
catch error
if getWindowLoadSettings().headless
console.error(error.stack ? error)
exitWithStatusCode(1)
else
throw error

View File

@@ -20,6 +20,8 @@ KeymapManager::loadBundledKeymaps = ->
@emitter.emit 'did-load-bundled-keymaps'
KeymapManager::getUserKeymapPath = ->
return "" unless @configDirPath?
if userKeymapPath = CSON.resolve(path.join(@configDirPath, 'keymap'))
userKeymapPath
else
@@ -41,11 +43,11 @@ KeymapManager::loadUserKeymap = ->
[this document][watches] for more info.
[watches]:https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#typeerror-unable-to-watch-path
"""
atom.notifications.addError(message, {dismissable: true})
@notificationManager.addError(message, {dismissable: true})
else
detail = error.path
stack = error.stack
atom.notifications.addFatalError(error.message, {detail, stack, dismissable: true})
@notificationManager.addFatalError(error.message, {detail, stack, dismissable: true})
KeymapManager::subscribeToFileReadFailure = ->
@onDidFailToReadFile (error) =>
@@ -57,6 +59,6 @@ KeymapManager::subscribeToFileReadFailure = ->
else
error.message
atom.notifications.addError(message, {detail, dismissable: true})
@notificationManager.addError(message, {detail, dismissable: true})
module.exports = KeymapManager

View File

@@ -8,7 +8,7 @@ class LanguageMode
# Sets up a `LanguageMode` for the given {TextEditor}.
#
# editor - The {TextEditor} to associate with
constructor: (@editor) ->
constructor: (@editor, @config) ->
{@buffer} = @editor
destroy: ->
@@ -327,7 +327,7 @@ class LanguageMode
@editor.setIndentationForBufferRow(bufferRow, desiredIndentLevel)
getRegexForProperty: (scopeDescriptor, property) ->
if pattern = atom.config.get(property, scope: scopeDescriptor)
if pattern = @config.get(property, scope: scopeDescriptor)
new OnigRegExp(pattern)
increaseIndentRegexForScopeDescriptor: (scopeDescriptor) ->
@@ -343,8 +343,8 @@ class LanguageMode
@getRegexForProperty(scopeDescriptor, 'editor.foldEndPattern')
commentStartAndEndStringsForScope: (scope) ->
commentStartEntry = atom.config.getAll('editor.commentStart', {scope})[0]
commentEndEntry = _.find atom.config.getAll('editor.commentEnd', {scope}), (entry) ->
commentStartEntry = @config.getAll('editor.commentStart', {scope})[0]
commentEndEntry = _.find @config.getAll('editor.commentEnd', {scope}), (entry) ->
entry.scopeSelector is commentStartEntry.scopeSelector
commentStartString = commentStartEntry?.value
commentEndString = commentEndEntry?.value

View File

@@ -7,12 +7,12 @@ module.exports =
class LineNumberGutterComponent extends TiledComponent
dummyLineNumberNode: null
constructor: ({@onMouseDown, @editor, @gutter, @domElementPool}) ->
constructor: ({@onMouseDown, @editor, @gutter, @domElementPool, @views}) ->
@visible = true
@dummyLineNumberComponent = LineNumbersTileComponent.createDummy(@domElementPool)
@domNode = atom.views.getView(@gutter)
@domNode = @views.getView(@gutter)
@lineNumbersNode = @domNode.firstChild
@lineNumbersNode.innerHTML = ''

View File

@@ -7,13 +7,21 @@ DummyLineNode.className = 'line'
DummyLineNode.style.position = 'absolute'
DummyLineNode.style.visibility = 'hidden'
DummyLineNode.appendChild(document.createElement('span'))
DummyLineNode.firstChild.textContent = 'x'
DummyLineNode.appendChild(document.createElement('span'))
DummyLineNode.appendChild(document.createElement('span'))
DummyLineNode.appendChild(document.createElement('span'))
DummyLineNode.children[0].textContent = 'x'
DummyLineNode.children[1].textContent = ''
DummyLineNode.children[2].textContent = ''
DummyLineNode.children[3].textContent = ''
RangeForMeasurement = document.createRange()
module.exports =
class LinesComponent extends TiledComponent
placeholderTextDiv: null
constructor: ({@presenter, @useShadowDOM, @domElementPool}) ->
constructor: ({@presenter, @useShadowDOM, @domElementPool, @assert, @grammars}) ->
@domNode = document.createElement('div')
@domNode.classList.add('lines')
@tilesNode = document.createElement("div")
@@ -64,7 +72,7 @@ class LinesComponent extends TiledComponent
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter, @domElementPool})
buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter, @domElementPool, @assert, @grammars})
buildEmptyState: ->
{tiles: {}}
@@ -76,12 +84,18 @@ class LinesComponent extends TiledComponent
measureLineHeightAndDefaultCharWidth: ->
@domNode.appendChild(DummyLineNode)
textNode = DummyLineNode.firstChild.childNodes[0]
lineHeightInPixels = DummyLineNode.getBoundingClientRect().height
charWidth = DummyLineNode.firstChild.getBoundingClientRect().width
defaultCharWidth = DummyLineNode.children[0].getBoundingClientRect().width
doubleWidthCharWidth = DummyLineNode.children[1].getBoundingClientRect().width
halfWidthCharWidth = DummyLineNode.children[2].getBoundingClientRect().width
koreanCharWidth = DummyLineNode.children[3].getBoundingClientRect().width
@domNode.removeChild(DummyLineNode)
@presenter.setLineHeight(lineHeightInPixels)
@presenter.setBaseCharacterWidth(charWidth)
@presenter.setBaseCharacterWidth(defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth)
lineNodeForLineIdAndScreenRow: (lineId, screenRow) ->
tile = @presenter.tileForRow(screenRow)

View File

@@ -13,8 +13,8 @@ cloneObject = (object) ->
module.exports =
class LinesTileComponent
constructor: ({@presenter, @id, @domElementPool}) ->
@tokenIterator = new TokenIterator
constructor: ({@presenter, @id, @domElementPool, @assert, grammars}) ->
@tokenIterator = new TokenIterator(grammarRegistry: grammars)
@measuredLines = new Set
@lineNodesByLineId = {}
@screenRowsByLineId = {}

View File

@@ -3,8 +3,8 @@ TokenIterator = require './token-iterator'
module.exports =
class LinesYardstick
constructor: (@model, @presenter, @lineNodesProvider) ->
@tokenIterator = new TokenIterator
constructor: (@model, @presenter, @lineNodesProvider, grammarRegistry) ->
@tokenIterator = new TokenIterator({grammarRegistry})
@rangeForMeasurement = document.createRange()
@invalidateCache()
@@ -40,7 +40,7 @@ class LinesYardstick
previousColumn = 0
previousLeft = 0
@tokenIterator.reset(line)
@tokenIterator.reset(line, false)
while @tokenIterator.next()
text = @tokenIterator.getText()
textIndex = 0
@@ -112,7 +112,7 @@ class LinesYardstick
indexWithinTextNode = null
charIndex = 0
@tokenIterator.reset(line)
@tokenIterator.reset(line, false)
while @tokenIterator.next()
break if foundIndexWithinTextNode?

View File

@@ -59,12 +59,12 @@ platformMenu = require('../package.json')?._atomMenu?.menu
# See {::add} for more info about adding menu's directly.
module.exports =
class MenuManager
constructor: ({@resourcePath}) ->
constructor: ({@resourcePath, @keymapManager, @packageManager}) ->
@pendingUpdateOperation = null
@template = []
atom.keymaps.onDidLoadBundledKeymaps => @loadPlatformItems()
atom.keymaps.onDidReloadKeymap => @update()
atom.packages.onDidActivateInitialPackages => @sortPackagesMenu()
@keymapManager.onDidLoadBundledKeymaps => @loadPlatformItems()
@keymapManager.onDidReloadKeymap => @update()
@packageManager.onDidActivateInitialPackages => @sortPackagesMenu()
# Public: Adds the given items to the application menu.
#
@@ -96,6 +96,10 @@ class MenuManager
@unmerge(@template, item) for item in items
@update()
clear: ->
@template = []
@update()
# Should the binding for the given selector be included in the menu
# commands.
#
@@ -143,7 +147,7 @@ class MenuManager
includedBindings = []
unsetKeystrokes = new Set
for binding in atom.keymaps.getKeyBindings() when @includeSelector(binding.selector)
for binding in @keymapManager.getKeyBindings() when @includeSelector(binding.selector)
includedBindings.push(binding)
if binding.command is 'unset!'
unsetKeystrokes.add(binding.keystrokes)
@@ -191,7 +195,10 @@ class MenuManager
# Get an {Array} of {String} classes for the given element.
classesForElement: (element) ->
element?.classList.toString().split(' ') ? []
if classList = element?.classList
Array::slice.apply(classList)
else
[]
sortPackagesMenu: ->
packagesMenu = _.find @template, ({label}) -> MenuHelpers.normalizeLabel(label) is 'Packages'

View File

@@ -1,6 +1,6 @@
module.exports =
class OverlayManager
constructor: (@presenter, @container) ->
constructor: (@presenter, @container, @views) ->
@overlaysById = {}
render: (state) ->
@@ -28,7 +28,7 @@ class OverlayManager
@presenter.setOverlayDimensions(decorationId, itemView.offsetWidth, itemView.offsetHeight, contentMargin)
renderOverlay: (state, decorationId, {item, pixelPosition, class: klass}) ->
itemView = atom.views.getView(item)
itemView = @views.getView(item)
cachedOverlay = @overlaysById[decorationId]
unless overlayNode = cachedOverlay?.overlayNode
overlayNode = document.createElement('atom-overlay')

View File

@@ -1,8 +1,10 @@
path = require 'path'
normalizePackageData = null
_ = require 'underscore-plus'
{Emitter} = require 'event-kit'
fs = require 'fs-plus'
CSON = require 'season'
ServiceHub = require 'service-hub'
Package = require './package'
@@ -26,15 +28,21 @@ ThemePackage = require './theme-package'
# settings and also by calling `enablePackage()/disablePackage()`.
module.exports =
class PackageManager
constructor: ({configDirPath, @devMode, safeMode, @resourcePath}) ->
constructor: (params) ->
{
configDirPath, @devMode, safeMode, @resourcePath, @config, @styleManager,
@notificationManager, @keymapManager, @commandRegistry, @grammarRegistry
} = params
@emitter = new Emitter
@activationHookEmitter = new Emitter
@packageDirPaths = []
unless safeMode
if configDirPath? and not safeMode
if @devMode
@packageDirPaths.push(path.join(configDirPath, "dev", "packages"))
@packageDirPaths.push(path.join(configDirPath, "packages"))
@packagesCache = require('../package.json')?._atomPackages ? {}
@loadedPackages = {}
@activePackages = {}
@packageStates = {}
@@ -43,6 +51,17 @@ class PackageManager
@packageActivators = []
@registerPackageActivator(this, ['atom', 'textmate'])
setContextMenuManager: (@contextMenuManager) ->
setMenuManager: (@menuManager) ->
setThemeManager: (@themeManager) ->
reset: ->
@serviceHub.clear()
@deactivatePackages()
@packageStates = {}
###
Section: Event Subscription
###
@@ -185,7 +204,7 @@ class PackageManager
#
# Returns a {Boolean}.
isPackageDisabled: (name) ->
_.include(atom.config.get('core.disabledPackages') ? [], name)
_.include(@config.get('core.disabledPackages') ? [], name)
###
Section: Accessing active packages
@@ -269,7 +288,7 @@ class PackageManager
packages = []
for packagePath in @getAvailablePackagePaths()
name = path.basename(packagePath)
metadata = @getLoadedPackage(name)?.metadata ? Package.loadMetadata(packagePath, true)
metadata = @getLoadedPackage(name)?.metadata ? @loadPackageMetadata(packagePath, true)
packages.push(metadata)
packages
@@ -292,7 +311,7 @@ class PackageManager
@packageDependencies
hasAtomEngine: (packagePath) ->
metadata = Package.loadMetadata(packagePath, true)
metadata = @loadPackageMetadata(packagePath, true)
metadata?.engines?.atom?
unobserveDisabledPackages: ->
@@ -300,7 +319,7 @@ class PackageManager
@disabledPackagesSubscription = null
observeDisabledPackages: ->
@disabledPackagesSubscription ?= atom.config.onDidChange 'core.disabledPackages', ({newValue, oldValue}) =>
@disabledPackagesSubscription ?= @config.onDidChange 'core.disabledPackages', ({newValue, oldValue}) =>
packagesToEnable = _.difference(oldValue, newValue)
packagesToDisable = _.difference(newValue, oldValue)
@@ -313,7 +332,7 @@ class PackageManager
@packagesWithKeymapsDisabledSubscription = null
observePackagesWithKeymapsDisabled: ->
@packagesWithKeymapsDisabledSubscription ?= atom.config.onDidChange 'core.packagesWithKeymapsDisabled', ({newValue, oldValue}) =>
@packagesWithKeymapsDisabledSubscription ?= @config.onDidChange 'core.packagesWithKeymapsDisabled', ({newValue, oldValue}) =>
keymapsToEnable = _.difference(oldValue, newValue)
keymapsToDisable = _.difference(newValue, oldValue)
@@ -340,7 +359,7 @@ class PackageManager
return pack if pack = @getLoadedPackage(name)
try
metadata = Package.loadMetadata(packagePath) ? {}
metadata = @loadPackageMetadata(packagePath) ? {}
catch error
@handleMetadataError(error, packagePath)
return null
@@ -350,10 +369,15 @@ class PackageManager
console.warn "Could not load #{metadata.name}@#{metadata.version} because it uses deprecated APIs that have been removed."
return null
options = {
path: packagePath, metadata, packageManager: this, @config, @styleManager,
@commandRegistry, @keymapManager, @devMode, @notificationManager,
@grammarRegistry, @themeManager, @menuManager, @contextMenuManager
}
if metadata.theme
pack = new ThemePackage(packagePath, metadata)
pack = new ThemePackage(options)
else
pack = new Package(packagePath, metadata)
pack = new Package(options)
pack.load()
@loadedPackages[pack.name] = pack
@emitter.emit 'did-load-package', pack
@@ -392,7 +416,7 @@ class PackageManager
activatePackages: (packages) ->
promises = []
atom.config.transact =>
@config.transact =>
for pack in packages
promise = @activatePackage(pack.name)
promises.push(promise) unless pack.hasActivationCommands()
@@ -423,7 +447,7 @@ class PackageManager
# Deactivate all packages
deactivatePackages: ->
atom.config.transact =>
@config.transact =>
@deactivatePackage(pack.name) for pack in @getLoadedPackages()
return
@unobserveDisabledPackages()
@@ -443,7 +467,7 @@ class PackageManager
detail = "#{error.message} in #{metadataPath}"
stack = "#{error.stack}\n at #{metadataPath}:1:1"
message = "Failed to load the #{path.basename(packagePath)} package"
atom.notifications.addError(message, {stack, detail, dismissable: true})
@notificationManager.addError(message, {stack, detail, dismissable: true})
uninstallDirectory: (directory) ->
symlinkPromise = new Promise (resolve) ->
@@ -456,3 +480,40 @@ class PackageManager
[isSymLink, isDir] = values
if not isSymLink and isDir
fs.remove directory, ->
reloadActivePackageStyleSheets: ->
for pack in @getActivePackages() when pack.getType() isnt 'theme'
pack.reloadStylesheets?()
return
isBundledPackagePath: (packagePath) ->
if @devMode
return false unless @resourcePath.startsWith("#{process.resourcesPath}#{path.sep}")
@resourcePathWithTrailingSlash ?= "#{@resourcePath}#{path.sep}"
packagePath?.startsWith(@resourcePathWithTrailingSlash)
loadPackageMetadata: (packagePath, ignoreErrors=false) ->
packageName = path.basename(packagePath)
if @isBundledPackagePath(packagePath)
metadata = @packagesCache[packageName]?.metadata
unless metadata?
if metadataPath = CSON.resolve(path.join(packagePath, 'package'))
try
metadata = CSON.readFileSync(metadataPath)
@normalizePackageMetadata(metadata)
catch error
throw error unless ignoreErrors
metadata ?= {}
unless typeof metadata.name is 'string' and metadata.name.length > 0
metadata.name = packageName
metadata
normalizePackageMetadata: (metadata) ->
unless metadata?._id
normalizePackageData ?= require 'normalize-package-data'
normalizePackageData(metadata)
if metadata.repository?.type is 'git' and typeof metadata.repository.url is 'string'
metadata.repository.url = metadata.repository.url.replace(/^git\+/, '')

View File

@@ -1,5 +1,4 @@
path = require 'path'
normalizePackageData = null
_ = require 'underscore-plus'
async = require 'async'
@@ -11,44 +10,10 @@ ModuleCache = require './module-cache'
ScopedProperties = require './scoped-properties'
BufferedProcess = require './buffered-process'
packagesCache = require('../package.json')?._atomPackages ? {}
# Extended: Loads and activates a package's main module and resources such as
# stylesheets, keymaps, grammar, editor properties, and menus.
module.exports =
class Package
@isBundledPackagePath: (packagePath) ->
if atom.packages.devMode
return false unless atom.packages.resourcePath.startsWith("#{process.resourcesPath}#{path.sep}")
@resourcePathWithTrailingSlash ?= "#{atom.packages.resourcePath}#{path.sep}"
packagePath?.startsWith(@resourcePathWithTrailingSlash)
@normalizeMetadata: (metadata) ->
unless metadata?._id
normalizePackageData ?= require 'normalize-package-data'
normalizePackageData(metadata)
if metadata.repository?.type is 'git' and typeof metadata.repository.url is 'string'
metadata.repository.url = metadata.repository.url.replace(/^git\+/, '')
@loadMetadata: (packagePath, ignoreErrors=false) ->
packageName = path.basename(packagePath)
if @isBundledPackagePath(packagePath)
metadata = packagesCache[packageName]?.metadata
unless metadata?
if metadataPath = CSON.resolve(path.join(packagePath, 'package'))
try
metadata = CSON.readFileSync(metadataPath)
@normalizeMetadata(metadata)
catch error
throw error unless ignoreErrors
metadata ?= {}
unless typeof metadata.name is 'string' and metadata.name.length > 0
metadata.name = packageName
metadata
keymaps: null
menus: null
stylesheets: null
@@ -64,10 +29,16 @@ class Package
Section: Construction
###
constructor: (@path, @metadata) ->
constructor: (params) ->
{
@path, @metadata, @packageManager, @config, @styleManager, @commandRegistry,
@keymapManager, @devMode, @notificationManager, @grammarRegistry, @themeManager,
@menuManager, @contextMenuManager
} = params
@emitter = new Emitter
@metadata ?= Package.loadMetadata(@path)
@bundledPackage = Package.isBundledPackagePath(@path)
@metadata ?= @packageManager.loadPackageMetadata(@path)
@bundledPackage = @packageManager.isBundledPackagePath(@path)
@name = @metadata?.name ? path.basename(@path)
ModuleCache.add(@path, @metadata)
@reset()
@@ -89,10 +60,10 @@ class Package
###
enable: ->
atom.config.removeAtKeyPath('core.disabledPackages', @name)
@config.removeAtKeyPath('core.disabledPackages', @name)
disable: ->
atom.config.pushAtKeyPath('core.disabledPackages', @name)
@config.pushAtKeyPath('core.disabledPackages', @name)
isTheme: ->
@metadata?.theme?
@@ -132,7 +103,6 @@ class Package
@activationPromise ?=
new Promise (resolve, reject) =>
@resolveActivationPromise = resolve
@rejectActivationPromise = reject
@measure 'activateTime', =>
try
@activateResources()
@@ -150,7 +120,7 @@ class Package
@activateConfig()
@activateStylesheets()
if @mainModule? and not @mainActivated
@mainModule.activate?(atom.packages.getPackageState(@name) ? {})
@mainModule.activate?(@packageManager.getPackageState(@name) ? {})
@mainActivated = true
@activateServices()
catch error
@@ -164,7 +134,7 @@ class Package
@requireMainModule() unless @mainModule?
if @mainModule?
if @mainModule.config? and typeof @mainModule.config is 'object'
atom.config.setSchema @name, {type: 'object', properties: @mainModule.config}
@config.setSchema @name, {type: 'object', properties: @mainModule.config}
@mainModule.activateConfig?()
@configActivated = true
@@ -182,13 +152,13 @@ class Package
else
context = undefined
@stylesheetDisposables.add(atom.styles.addStyleSheet(source, {sourcePath, priority, context}))
@stylesheetDisposables.add(@styleManager.addStyleSheet(source, {sourcePath, priority, context}))
@stylesheetsActivated = true
activateResources: ->
@activationDisposables = new CompositeDisposable
keymapIsDisabled = _.include(atom.config.get("core.packagesWithKeymapsDisabled") ? [], @name)
keymapIsDisabled = _.include(@config.get("core.packagesWithKeymapsDisabled") ? [], @name)
if keymapIsDisabled
@deactivateKeymaps()
else
@@ -197,14 +167,14 @@ class Package
for [menuPath, map] in @menus when map['context-menu']?
try
itemsBySelector = map['context-menu']
@activationDisposables.add(atom.contextMenu.add(itemsBySelector))
@activationDisposables.add(@contextMenuManager.add(itemsBySelector))
catch error
if error.code is 'EBADSELECTOR'
error.message += " in #{menuPath}"
error.stack += "\n at #{menuPath}:1:1"
throw error
@activationDisposables.add(atom.menu.add(map['menu'])) for [menuPath, map] in @menus when map['menu']?
@activationDisposables.add(@menuManager.add(map['menu'])) for [menuPath, map] in @menus when map['menu']?
unless @grammarsActivated
grammar.activate() for grammar in @grammars
@@ -218,8 +188,8 @@ class Package
@keymapDisposables = new CompositeDisposable()
@keymapDisposables.add(atom.keymaps.add(keymapPath, map)) for [keymapPath, map] in @keymaps
atom.menu.update()
@keymapDisposables.add(@keymapManager.add(keymapPath, map)) for [keymapPath, map] in @keymaps
@menuManager.update()
@keymapActivated = true
@@ -227,7 +197,7 @@ class Package
return if not @keymapActivated
@keymapDisposables?.dispose()
atom.menu.update()
@menuManager.update()
@keymapActivated = false
@@ -243,24 +213,24 @@ class Package
for version, methodName of versions
if typeof @mainModule[methodName] is 'function'
servicesByVersion[version] = @mainModule[methodName]()
@activationDisposables.add atom.packages.serviceHub.provide(name, servicesByVersion)
@activationDisposables.add @packageManager.serviceHub.provide(name, servicesByVersion)
for name, {versions} of @metadata.consumedServices
for version, methodName of versions
if typeof @mainModule[methodName] is 'function'
@activationDisposables.add atom.packages.serviceHub.consume(name, version, @mainModule[methodName].bind(@mainModule))
@activationDisposables.add @packageManager.serviceHub.consume(name, version, @mainModule[methodName].bind(@mainModule))
return
loadKeymaps: ->
if @bundledPackage and packagesCache[@name]?
@keymaps = (["#{atom.packages.resourcePath}#{path.sep}#{keymapPath}", keymapObject] for keymapPath, keymapObject of packagesCache[@name].keymaps)
if @bundledPackage and @packageManager.packagesCache[@name]?
@keymaps = (["#{@packageManager.resourcePath}#{path.sep}#{keymapPath}", keymapObject] for keymapPath, keymapObject of @packageManager.packagesCache[@name].keymaps)
else
@keymaps = @getKeymapPaths().map (keymapPath) -> [keymapPath, CSON.readFileSync(keymapPath) ? {}]
return
loadMenus: ->
if @bundledPackage and packagesCache[@name]?
@menus = (["#{atom.packages.resourcePath}#{path.sep}#{menuPath}", menuObject] for menuPath, menuObject of packagesCache[@name].menus)
if @bundledPackage and @packageManager.packagesCache[@name]?
@menus = (["#{@packageManager.resourcePath}#{path.sep}#{menuPath}", menuObject] for menuPath, menuObject of @packageManager.packagesCache[@name].menus)
else
@menus = @getMenuPaths().map (menuPath) -> [menuPath, CSON.readFileSync(menuPath) ? {}]
return
@@ -280,8 +250,8 @@ class Package
fs.listSync(menusDirPath, ['cson', 'json'])
loadStylesheets: ->
@stylesheets = @getStylesheetPaths().map (stylesheetPath) ->
[stylesheetPath, atom.themes.loadStylesheet(stylesheetPath, true)]
@stylesheets = @getStylesheetPaths().map (stylesheetPath) =>
[stylesheetPath, @themeManager.loadStylesheet(stylesheetPath, true)]
getStylesheetsPath: ->
path.join(@path, 'styles')
@@ -304,7 +274,7 @@ class Package
grammarPaths = fs.listSync(grammarsDirPath, ['json', 'cson'])
for grammarPath in grammarPaths
try
grammar = atom.grammars.readGrammarSync(grammarPath)
grammar = @grammarRegistry.readGrammarSync(grammarPath)
grammar.packageName = @name
grammar.bundledPackage = @bundledPackage
@grammars.push(grammar)
@@ -319,11 +289,11 @@ class Package
return Promise.resolve() if @grammarsLoaded
loadGrammar = (grammarPath, callback) =>
atom.grammars.readGrammar grammarPath, (error, grammar) =>
@grammarRegistry.readGrammar grammarPath, (error, grammar) =>
if error?
detail = "#{error.message} in #{grammarPath}"
stack = "#{error.stack}\n at #{grammarPath}:1:1"
atom.notifications.addFatalError("Failed to load a #{@name} package grammar", {stack, detail, dismissable: true})
@notificationManager.addFatalError("Failed to load a #{@name} package grammar", {stack, detail, dismissable: true})
else
grammar.packageName = @name
grammar.bundledPackage = @bundledPackage
@@ -343,11 +313,11 @@ class Package
@settings = []
loadSettingsFile = (settingsPath, callback) =>
ScopedProperties.load settingsPath, (error, settings) =>
ScopedProperties.load settingsPath, @config, (error, settings) =>
if error?
detail = "#{error.message} in #{settingsPath}"
stack = "#{error.stack}\n at #{settingsPath}:1:1"
atom.notifications.addFatalError("Failed to load the #{@name} package settings", {stack, detail, dismissable: true})
@notificationManager.addFatalError("Failed to load the #{@name} package settings", {stack, detail, dismissable: true})
else
@settings.push(settings)
settings.activate() if @settingsActivated
@@ -370,10 +340,8 @@ class Package
console.error "Error serializing package '#{@name}'", e.stack
deactivate: ->
@rejectActivationPromise?()
@activationPromise = null
@resolveActivationPromise = null
@rejectActivationPromise = null
@activationCommandSubscriptions?.dispose()
@deactivateResources()
@deactivateConfig()
@@ -430,9 +398,9 @@ class Package
return @mainModulePath if @resolvedMainModulePath
@resolvedMainModulePath = true
if @bundledPackage and packagesCache[@name]?
if packagesCache[@name].main
@mainModulePath = "#{atom.packages.resourcePath}#{path.sep}#{packagesCache[@name].main}"
if @bundledPackage and @packageManager.packagesCache[@name]?
if @packageManager.packagesCache[@name].main
@mainModulePath = "#{@packageManager.resourcePath}#{path.sep}#{@packageManager.packagesCache[@name].main}"
else
@mainModulePath = null
else
@@ -466,7 +434,7 @@ class Package
# Add dummy command so it appears in menu.
# The real command will be registered on package activation
try
@activationCommandSubscriptions.add atom.commands.add selector, command, ->
@activationCommandSubscriptions.add @commandRegistry.add selector, command, ->
catch error
if error.code is 'EBADSELECTOR'
metadataPath = path.join(@path, 'package.json')
@@ -474,7 +442,7 @@ class Package
error.stack += "\n at #{metadataPath}:1:1"
throw error
@activationCommandSubscriptions.add atom.commands.onWillDispatch (event) =>
@activationCommandSubscriptions.add @commandRegistry.onWillDispatch (event) =>
return unless event.type is command
currentTarget = event.target
while currentTarget
@@ -505,7 +473,7 @@ class Package
@activationHookSubscriptions = new CompositeDisposable
for hook in @getActivationHooks()
do (hook) =>
@activationHookSubscriptions.add(atom.packages.onDidTriggerActivationHook(hook, => @activateNow())) if hook? and _.isString(hook) and hook.trim().length > 0
@activationHookSubscriptions.add(@packageManager.onDidTriggerActivationHook(hook, => @activateNow())) if hook? and _.isString(hook) and hook.trim().length > 0
return
@@ -567,7 +535,7 @@ class Package
isCompatible: ->
return @compatible if @compatible?
if @path.indexOf(path.join(atom.packages.resourcePath, 'node_modules') + path.sep) is 0
if @path.indexOf(path.join(@packageManager.resourcePath, 'node_modules') + path.sep) is 0
# Bundled packages are always considered compatible
@compatible = true
else if @getMainModulePath()
@@ -603,7 +571,7 @@ class Package
stderr = ''
stdout = ''
new BufferedProcess({
command: atom.packages.getApmPath()
command: @packageManager.getApmPath()
args: ['rebuild', '--no-color']
options: {cwd: @path}
stderr: (output) -> stderr += output
@@ -625,7 +593,7 @@ class Package
# This information is cached in local storage on a per package/version basis
# to minimize the impact on startup time.
getIncompatibleNativeModules: ->
unless atom.inDevMode()
unless @devMode
try
if arrayAsString = global.localStorage.getItem(@getIncompatibleNativeModulesStorageKey())
return JSON.parse(arrayAsString)
@@ -666,4 +634,4 @@ class Package
detail = error.message
stack = error.stack ? error
atom.notifications.addFatalError(message, {stack, detail, dismissable: true})
@notificationManager.addFatalError(message, {stack, detail, dismissable: true})

View File

@@ -8,7 +8,9 @@ class PaneAxisElement extends HTMLElement
detachedCallback: ->
@subscriptions.dispose()
initialize: (@model) ->
initialize: (@model, {@views}) ->
throw new Error("Must pass a views parameter when initializing TextEditorElements") unless @views?
@subscriptions.add @model.onDidAddChild(@childAdded.bind(this))
@subscriptions.add @model.onDidRemoveChild(@childRemoved.bind(this))
@subscriptions.add @model.onDidReplaceChild(@childReplaced.bind(this))
@@ -27,7 +29,7 @@ class PaneAxisElement extends HTMLElement
element?.nodeName.toLowerCase() is 'atom-pane-resize-handle'
childAdded: ({child, index}) ->
view = atom.views.getView(child)
view = @views.getView(child)
@insertBefore(view, @children[index * 2])
prevElement = view.previousSibling
@@ -43,7 +45,7 @@ class PaneAxisElement extends HTMLElement
@insertBefore(resizeHandle, nextElement)
childRemoved: ({child}) ->
view = atom.views.getView(child)
view = @views.getView(child)
siblingView = view.previousSibling
# make sure next sibling view is pane resize view
if siblingView? and @isPaneResizeHandleElement(siblingView)

View File

@@ -4,19 +4,16 @@ Model = require './model'
module.exports =
class PaneAxis extends Model
atom.deserializers.add(this)
parent: null
container: null
orientation: null
@deserialize: (state, params) ->
container = params?.container
state.container = container
state.children = state.children.map (childState) -> atom.deserializers.deserialize(childState, {container})
@deserialize: (state, {deserializers}) ->
state.children = state.children.map (childState) ->
deserializers.deserialize(childState)
new this(state)
constructor: ({@container, @orientation, children, flexScale}={}) ->
constructor: ({@orientation, children, flexScale}={}) ->
@emitter = new Emitter
@subscriptionsByChild = new WeakMap
@subscriptions = new CompositeDisposable
@@ -43,7 +40,10 @@ class PaneAxis extends Model
getContainer: -> @container
setContainer: (@container) -> @container
setContainer: (container) ->
if container and container isnt @container
@container = container
child.setContainer(container) for child in @children
getOrientation: -> @orientation

View File

@@ -7,7 +7,9 @@ class PaneContainerElement extends HTMLElement
@subscriptions = new CompositeDisposable
@classList.add 'panes'
initialize: (@model) ->
initialize: (@model, {@views}) ->
throw new Error("Must pass a views parameter when initializing PaneContainerElements") unless @views?
@subscriptions.add @model.observeRoot(@rootChanged.bind(this))
this
@@ -15,7 +17,7 @@ class PaneContainerElement extends HTMLElement
focusedElement = document.activeElement if @hasFocus()
@firstChild?.remove()
if root?
view = atom.views.getView(root)
view = @views.getView(root)
@appendChild(view)
focusedElement?.focus()
@@ -40,7 +42,7 @@ class PaneContainerElement extends HTMLElement
y = pointB.y - pointA.y
Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
paneView = atom.views.getView(@model.getActivePane())
paneView = @views.getView(@model.getActivePane())
box = @boundingBoxForPaneView(paneView)
paneViews = _.toArray(@querySelectorAll('atom-pane'))

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