diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 19d4367f3..7f9dc8638 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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
diff --git a/apm/package.json b/apm/package.json
index 3f366d857..2a2e26db0 100644
--- a/apm/package.json
+++ b/apm/package.json
@@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
- "atom-package-manager": "1.1.1"
+ "atom-package-manager": "1.4.0"
}
}
diff --git a/build/package.json b/build/package.json
index ad483c75f..4c0f460a7 100644
--- a/build/package.json
+++ b/build/package.json
@@ -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",
diff --git a/build/tasks/spec-task.coffee b/build/tasks/spec-task.coffee
index b0e695798..27465efdf 100644
--- a/build/tasks/spec-task.coffee
+++ b/build/tasks/spec-task.coffee
@@ -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
diff --git a/docs/build-instructions/linux.md b/docs/build-instructions/linux.md
index 2fc46c0b2..07150394b 100644
--- a/docs/build-instructions/linux.md
+++ b/docs/build-instructions/linux.md
@@ -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.
diff --git a/docs/build-instructions/os-x.md b/docs/build-instructions/os-x.md
index 7273a29fd..76da080c9 100644
--- a/docs/build-instructions/os-x.md
+++ b/docs/build-instructions/os-x.md
@@ -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
diff --git a/docs/build-instructions/windows.md b/docs/build-instructions/windows.md
index 600a7479d..74dddf8f8 100644
--- a/docs/build-instructions/windows.md
+++ b/docs/build-instructions/windows.md
@@ -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/)
diff --git a/exports/atom.coffee b/exports/atom.coffee
index 1ceb60fa9..c3601e1cc 100644
--- a/exports/atom.coffee
+++ b/exports/atom.coffee
@@ -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
diff --git a/keymaps/darwin.cson b/keymaps/darwin.cson
index 87c1eb9da..bf2e43a33 100644
--- a/keymaps/darwin.cson
+++ b/keymaps/darwin.cson
@@ -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'
diff --git a/keymaps/linux.cson b/keymaps/linux.cson
index 9e739521c..28f43a8fc 100644
--- a/keymaps/linux.cson
+++ b/keymaps/linux.cson
@@ -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'
diff --git a/keymaps/win32.cson b/keymaps/win32.cson
index b3643207f..f052a64fc 100644
--- a/keymaps/win32.cson
+++ b/keymaps/win32.cson
@@ -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'
diff --git a/menus/darwin.cson b/menus/darwin.cson
index 27318eb47..6fff290e2 100644
--- a/menus/darwin.cson
+++ b/menus/darwin.cson
@@ -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' }
]
diff --git a/menus/linux.cson b/menus/linux.cson
index 354a5ec96..fa831b4a4 100644
--- a/menus/linux.cson
+++ b/menus/linux.cson
@@ -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' }
]
diff --git a/menus/win32.cson b/menus/win32.cson
index 791e9d68d..04da3d388 100644
--- a/menus/win32.cson
+++ b/menus/win32.cson
@@ -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' }
]
diff --git a/package.json b/package.json
index 093ce9e6f..6f1970297 100644
--- a/package.json
+++ b/package.json
@@ -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,
diff --git a/spec/atom-spec.coffee b/spec/atom-environment-spec.coffee
similarity index 67%
rename from spec/atom-spec.coffee
rename to spec/atom-environment-spec.coffee
index 31265c9a6..f9dbc02f9 100644
--- a/spec/atom-spec.coffee
+++ b/spec/atom-environment-spec.coffee
@@ -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'
diff --git a/spec/command-installer-spec.coffee b/spec/command-installer-spec.coffee
index 972494ec3..584dc193e 100644
--- a/spec/command-installer-spec.coffee
+++ b/spec/command-installer-spec.coffee
@@ -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')
diff --git a/spec/command-registry-spec.coffee b/spec/command-registry-spec.coffee
index f8194aae6..ecdd42fd6 100644
--- a/spec/command-registry-spec.coffee
+++ b/spec/command-registry-spec.coffee
@@ -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()
diff --git a/spec/compile-cache-spec.coffee b/spec/compile-cache-spec.coffee
index 94910bb58..d80e05fc5 100644
--- a/spec/compile-cache-spec.coffee
+++ b/spec/compile-cache-spec.coffee
@@ -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'}
diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee
index 5aff0eff2..bb9ab89a8 100644
--- a/spec/config-spec.coffee
+++ b/spec/config-spec.coffee
@@ -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", ->
diff --git a/spec/context-menu-manager-spec.coffee b/spec/context-menu-manager-spec.coffee
index 4cf33acc6..ddced8452 100644
--- a/spec/context-menu-manager-spec.coffee
+++ b/spec/context-menu-manager-spec.coffee
@@ -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")
diff --git a/spec/custom-gutter-component-spec.coffee b/spec/custom-gutter-component-spec.coffee
index 4b7d81fba..5504e9c40 100644
--- a/spec/custom-gutter-component-spec.coffee
+++ b/spec/custom-gutter-component-spec.coffee
@@ -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
diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee
index 7ed97f0d0..68dd9c754 100644
--- a/spec/display-buffer-spec.coffee
+++ b/spec/display-buffer-spec.coffee
@@ -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()
diff --git a/spec/fixtures/babel/invalid.js b/spec/fixtures/babel/invalid.js
index 4af6a59e2..f02fd2fd6 100644
--- a/spec/fixtures/babel/invalid.js
+++ b/spec/fixtures/babel/invalid.js
@@ -1,3 +1,3 @@
'use 6to6';
-module.exports = v => v + 1
+module.exports = async function hello() {}
diff --git a/spec/git-repository-provider-spec.coffee b/spec/git-repository-provider-spec.coffee
index 13a0724d6..bbbfb4b03 100644
--- a/spec/git-repository-provider-spec.coffee
+++ b/spec/git-repository-provider-spec.coffee
@@ -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 =
diff --git a/spec/git-spec.coffee b/spec/git-spec.coffee
index edabd0722..cda2afaa8 100644
--- a/spec/git-spec.coffee
+++ b/spec/git-spec.coffee
@@ -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 ->
diff --git a/spec/grammars-spec.coffee b/spec/grammars-spec.coffee
index 960ce7d52..82a29892a 100644
--- a/spec/grammars-spec.coffee
+++ b/spec/grammars-spec.coffee
@@ -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", ->
diff --git a/spec/gutter-container-component-spec.coffee b/spec/gutter-container-component-spec.coffee
index 595067de5..73a9d0f6c 100644
--- a/spec/gutter-container-component-spec.coffee
+++ b/spec/gutter-container-component-spec.coffee
@@ -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
diff --git a/spec/integration/helpers/start-atom.coffee b/spec/integration/helpers/start-atom.coffee
index 6a7144141..b8768f532 100644
--- a/spec/integration/helpers/start-atom.coffee
+++ b/spec/integration/helpers/start-atom.coffee
@@ -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)
diff --git a/spec/integration/startup-spec.coffee b/spec/integration/startup-spec.coffee
index 74dd80f9e..36342470d 100644
--- a/spec/integration/startup-spec.coffee
+++ b/spec/integration/startup-spec.coffee
@@ -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])
diff --git a/spec/jasmine-helper.coffee b/spec/jasmine-helper.coffee
deleted file mode 100644
index 74e4a0384..000000000
--- a/spec/jasmine-helper.coffee
+++ /dev/null
@@ -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
diff --git a/spec/jasmine-test-runner.coffee b/spec/jasmine-test-runner.coffee
new file mode 100644
index 000000000..7ef34ce54
--- /dev/null
+++ b/spec/jasmine-test-runner.coffee
@@ -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)
diff --git a/spec/lines-yardstick-spec.coffee b/spec/lines-yardstick-spec.coffee
index 6320cc99f..022f535f4 100644
--- a/spec/lines-yardstick-spec.coffee
+++ b/spec/lines-yardstick-spec.coffee
@@ -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", ->
diff --git a/spec/menu-manager-spec.coffee b/spec/menu-manager-spec.coffee
index c047bbcfc..a2d76c1d0 100644
--- a/spec/menu-manager-spec.coffee
+++ b/spec/menu-manager-spec.coffee
@@ -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()
diff --git a/spec/module-cache-spec.coffee b/spec/module-cache-spec.coffee
index 5cf6bd2fd..3a995aec7 100644
--- a/spec/module-cache-spec.coffee
+++ b/spec/module-cache-spec.coffee
@@ -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()
diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee
index d1afd6ade..9a2edee0a 100644
--- a/spec/package-manager-spec.coffee
+++ b/spec/package-manager-spec.coffee
@@ -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", ->
diff --git a/spec/package-spec.coffee b/spec/package-spec.coffee
index bf059e0b5..63a80a7db 100644
--- a/spec/package-spec.coffee
+++ b/spec/package-spec.coffee
@@ -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'
diff --git a/spec/pane-container-element-spec.coffee b/spec/pane-container-element-spec.coffee
index 890baec38..98cfffd56 100644
--- a/spec/pane-container-element-spec.coffee
+++ b/spec/pane-container-element-spec.coffee
@@ -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')])
diff --git a/spec/pane-container-spec.coffee b/spec/pane-container-spec.coffee
index d0f2913aa..b94815e45 100644
--- a/spec/pane-container-spec.coffee
+++ b/spec/pane-container-spec.coffee
@@ -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()
diff --git a/spec/pane-element-spec.coffee b/spec/pane-element-spec.coffee
index 40712ce90..3fcdc4ffb 100644
--- a/spec/pane-element-spec.coffee
+++ b/spec/pane-element-spec.coffee
@@ -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()
diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee
index d293a6d2c..0c7a22b77 100644
--- a/spec/pane-spec.coffee
+++ b/spec/pane-spec.coffee
@@ -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
diff --git a/spec/panel-container-element-spec.coffee b/spec/panel-container-element-spec.coffee
index bd4ed6bd9..65577221a 100644
--- a/spec/panel-container-element-spec.coffee
+++ b/spec/panel-container-element-spec.coffee
@@ -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)
diff --git a/spec/panel-element-spec.coffee b/spec/panel-element-spec.coffee
index 4c166c617..73108e25d 100644
--- a/spec/panel-element-spec.coffee
+++ b/spec/panel-element-spec.coffee
@@ -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)
diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee
index 13dd3e70c..2c88aad50 100644
--- a/spec/project-spec.coffee
+++ b/spec/project-spec.coffee
@@ -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')
diff --git a/spec/random-editor-spec.coffee b/spec/random-editor-spec.coffee
index d235ebc25..3924a8412 100644
--- a/spec/random-editor-spec.coffee
+++ b/spec/random-editor-spec.coffee
@@ -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())
diff --git a/spec/sample-with-comments.js b/spec/sample-with-comments.js
new file mode 100644
index 000000000..66dc9051d
--- /dev/null
+++ b/spec/sample-with-comments.js
@@ -0,0 +1 @@
+undefined
\ No newline at end of file
diff --git a/spec/sample.js b/spec/sample.js
new file mode 100644
index 000000000..66dc9051d
--- /dev/null
+++ b/spec/sample.js
@@ -0,0 +1 @@
+undefined
\ No newline at end of file
diff --git a/spec/selection-spec.coffee b/spec/selection-spec.coffee
index e81f7906f..ec40e32cc 100644
--- a/spec/selection-spec.coffee
+++ b/spec/selection-spec.coffee
@@ -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 ->
diff --git a/spec/spec-bootstrap.coffee b/spec/spec-bootstrap.coffee
deleted file mode 100644
index 5158c9bee..000000000
--- a/spec/spec-bootstrap.coffee
+++ /dev/null
@@ -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
diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee
index 085db7835..f31c67298 100644
--- a/spec/spec-helper.coffee
+++ b/spec/spec-helper.coffee
@@ -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
diff --git a/spec/spec-suite.coffee b/spec/spec-suite.coffee
deleted file mode 100644
index 817de7986..000000000
--- a/spec/spec-suite.coffee
+++ /dev/null
@@ -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()
diff --git a/spec/style-manager-spec.coffee b/spec/style-manager-spec.coffee
index 7e601bcc1..d0a1cfe13 100644
--- a/spec/style-manager-spec.coffee
+++ b/spec/style-manager-spec.coffee
@@ -4,7 +4,7 @@ describe "StyleManager", ->
[manager, addEvents, removeEvents, updateEvents] = []
beforeEach ->
- manager = new StyleManager
+ manager = new StyleManager(configDirPath: atom.getConfigDirPath())
addEvents = []
removeEvents = []
updateEvents = []
diff --git a/spec/styles-element-spec.coffee b/spec/styles-element-spec.coffee
index 7d2fe722a..b1a57938c 100644
--- a/spec/styles-element-spec.coffee
+++ b/spec/styles-element-spec.coffee
@@ -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()
diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee
index f6936e4c4..91020f299 100644
--- a/spec/text-editor-component-spec.coffee
+++ b/spec/text-editor-component-spec.coffee
@@ -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", ->
diff --git a/spec/text-editor-element-spec.coffee b/spec/text-editor-element-spec.coffee
index 148199425..c60a9762f 100644
--- a/spec/text-editor-element-spec.coffee
+++ b/spec/text-editor-element-spec.coffee
@@ -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
diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee
index 69fa7bd9d..1804b7b6b 100644
--- a/spec/text-editor-presenter-spec.coffee
+++ b/spec/text-editor-presenter-spec.coffee
@@ -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
diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee
index e95818900..8ce026741 100644
--- a/spec/text-editor-spec.coffee
+++ b/spec/text-editor-spec.coffee
@@ -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()
diff --git a/spec/text-utils-spec.coffee b/spec/text-utils-spec.coffee
index 6a03bb02f..dd528b37e 100644
--- a/spec/text-utils-spec.coffee
+++ b/spec/text-utils-spec.coffee
@@ -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("B")).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)
diff --git a/spec/token-iterator-spec.coffee b/spec/token-iterator-spec.coffee
index ee8ac25e5..f876d30d1 100644
--- a/spec/token-iterator-spec.coffee
+++ b/spec/token-iterator-spec.coffee
@@ -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()
diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee
index 4945b37ec..e87125195 100644
--- a/spec/tokenized-buffer-spec.coffee
+++ b/spec/tokenized-buffer-spec.coffee
@@ -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 "
<%= User.find(2).full_name %>
"
- 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 ->
diff --git a/spec/tokenized-line-spec.coffee b/spec/tokenized-line-spec.coffee
index 2914ec089..f1dce7b9e 100644
--- a/spec/tokenized-line-spec.coffee
+++ b/spec/tokenized-line-spec.coffee
@@ -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
diff --git a/spec/tooltip-manager-spec.coffee b/spec/tooltip-manager-spec.coffee
index ec30ef2e3..bf0462ebf 100644
--- a/spec/tooltip-manager-spec.coffee
+++ b/spec/tooltip-manager-spec.coffee
@@ -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)
diff --git a/spec/window-event-handler-spec.coffee b/spec/window-event-handler-spec.coffee
index 345c0fd8c..3148942b4 100644
--- a/spec/window-event-handler-spec.coffee
+++ b/spec/window-event-handler-spec.coffee
@@ -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 = []
diff --git a/spec/workspace-spec.coffee b/spec/workspace-spec.coffee
index c60508640..5850100e0 100644
--- a/spec/workspace-spec.coffee
+++ b/spec/workspace-spec.coffee
@@ -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')
diff --git a/src/application-delegate.coffee b/src/application-delegate.coffee
new file mode 100644
index 000000000..1999c6e83
--- /dev/null
+++ b/src/application-delegate.coffee
@@ -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)
diff --git a/src/atom.coffee b/src/atom-environment.coffee
similarity index 58%
rename from src/atom.coffee
rename to src/atom-environment.coffee
index 59d7765ac..004cfbc80 100644
--- a/src/atom.coffee
+++ b/src/atom-environment.coffee
@@ -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) ->
diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee
index df4254acf..1ca403e98 100644
--- a/src/browser/atom-application.coffee
+++ b/src/browser/atom-application.coffee
@@ -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
diff --git a/src/browser/atom-protocol-handler.coffee b/src/browser/atom-protocol-handler.coffee
index 0865f3ad8..0fda8095b 100644
--- a/src/browser/atom-protocol-handler.coffee
+++ b/src/browser/atom-protocol-handler.coffee
@@ -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)
diff --git a/src/browser/atom-window.coffee b/src/browser/atom-window.coffee
index e341f25d3..16456d274 100644
--- a/src/browser/atom-window.coffee
+++ b/src/browser/atom-window.coffee
@@ -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()
diff --git a/src/browser/main.coffee b/src/browser/main.coffee
index f0709037d..990002671 100644
--- a/src/browser/main.coffee
+++ b/src/browser/main.coffee
@@ -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()
diff --git a/src/clipboard.coffee b/src/clipboard.coffee
index 84ff9ab3b..9511bdda9 100644
--- a/src/clipboard.coffee
+++ b/src/clipboard.coffee
@@ -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.
#
diff --git a/src/command-installer.coffee b/src/command-installer.coffee
index 729aef84c..5a6a7f94e 100644
--- a/src/command-installer.coffee
+++ b/src/command-installer.coffee
@@ -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."
diff --git a/src/command-registry.coffee b/src/command-registry.coffee
index 4cf002c7e..db2cf498d 100644
--- a/src/command-registry.coffee
+++ b/src/command-registry.coffee
@@ -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
diff --git a/src/compile-cache.js b/src/compile-cache.js
index e10bf6803..f7726b4b3 100644
--- a/src/compile-cache.js
+++ b/src/compile-cache.js
@@ -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]
diff --git a/src/config-schema.coffee b/src/config-schema.coffee
index d8eeb6fe3..08956d470 100644
--- a/src/config-schema.coffee
+++ b/src/config-schema.coffee
@@ -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 =
diff --git a/src/config.coffee b/src/config.coffee
index 176390869..cb09a8d83 100644
--- a/src/config.coffee
+++ b/src/config.coffee
@@ -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) ->
diff --git a/src/context-menu-manager.coffee b/src/context-menu-manager.coffee
index 0ca005d8a..869076beb 100644
--- a/src/context-menu-manager.coffee
+++ b/src/context-menu-manager.coffee
@@ -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: ->
diff --git a/src/cursor.coffee b/src/cursor.coffee
index 84396448c..c9ebca8c7 100644
--- a/src/cursor.coffee
+++ b/src/cursor.coffee
@@ -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}]+"
diff --git a/src/custom-gutter-component.coffee b/src/custom-gutter-component.coffee
index 32710ea46..9fd25fd3f 100644
--- a/src/custom-gutter-component.coffee
+++ b/src/custom-gutter-component.coffee
@@ -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 = ''
diff --git a/src/deserializer-manager.coffee b/src/deserializer-manager.coffee
index 62c7e4401..7f6cb0f65 100644
--- a/src/deserializer-manager.coffee
+++ b/src/deserializer-manager.coffee
@@ -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 = {}
diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee
index 339fa11c1..1021955cf 100644
--- a/src/display-buffer.coffee
+++ b/src/display-buffer.coffee
@@ -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}
diff --git a/src/git-repository-provider.coffee b/src/git-repository-provider.coffee
index 3b4df5d2b..593324d0c 100644
--- a/src/git-repository-provider.coffee
+++ b/src/git-repository-provider.coffee
@@ -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
diff --git a/src/git-repository.coffee b/src/git-repository.coffee
index a2e28b127..9b46cf04d 100644
--- a/src/git-repository.coffee
+++ b/src/git-repository.coffee
@@ -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?
diff --git a/src/grammar-registry.coffee b/src/grammar-registry.coffee
index 109e719fc..4a5352275 100644
--- a/src/grammar-registry.coffee
+++ b/src/grammar-registry.coffee
@@ -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
diff --git a/src/gutter-container-component.coffee b/src/gutter-container-component.coffee
index 40e2c8c26..c5538324b 100644
--- a/src/gutter-container-component.coffee
+++ b/src/gutter-container-component.coffee
@@ -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.
diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee
new file mode 100644
index 000000000..acf50bc5d
--- /dev/null
+++ b/src/initialize-application-window.coffee
@@ -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)
diff --git a/src/initialize-test-window.coffee b/src/initialize-test-window.coffee
new file mode 100644
index 000000000..27ef46acc
--- /dev/null
+++ b/src/initialize-test-window.coffee
@@ -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
diff --git a/src/keymap-extensions.coffee b/src/keymap-extensions.coffee
index da77f0156..82f2e8b99 100644
--- a/src/keymap-extensions.coffee
+++ b/src/keymap-extensions.coffee
@@ -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
diff --git a/src/language-mode.coffee b/src/language-mode.coffee
index e60e85f7f..55707bad3 100644
--- a/src/language-mode.coffee
+++ b/src/language-mode.coffee
@@ -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
diff --git a/src/line-number-gutter-component.coffee b/src/line-number-gutter-component.coffee
index b4a5bc168..bb66ff144 100644
--- a/src/line-number-gutter-component.coffee
+++ b/src/line-number-gutter-component.coffee
@@ -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 = ''
diff --git a/src/lines-component.coffee b/src/lines-component.coffee
index 2a721a0aa..b5af56885 100644
--- a/src/lines-component.coffee
+++ b/src/lines-component.coffee
@@ -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)
diff --git a/src/lines-tile-component.coffee b/src/lines-tile-component.coffee
index 619acc56a..6b4ac80ba 100644
--- a/src/lines-tile-component.coffee
+++ b/src/lines-tile-component.coffee
@@ -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 = {}
diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee
index 0dc3e7602..fa00bae40 100644
--- a/src/lines-yardstick.coffee
+++ b/src/lines-yardstick.coffee
@@ -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?
diff --git a/src/menu-manager.coffee b/src/menu-manager.coffee
index b138f2da2..fa78d3cd6 100644
--- a/src/menu-manager.coffee
+++ b/src/menu-manager.coffee
@@ -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'
diff --git a/src/overlay-manager.coffee b/src/overlay-manager.coffee
index 7858cb8a2..83787ad10 100644
--- a/src/overlay-manager.coffee
+++ b/src/overlay-manager.coffee
@@ -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')
diff --git a/src/package-manager.coffee b/src/package-manager.coffee
index 7e4a19470..f6cef9465 100644
--- a/src/package-manager.coffee
+++ b/src/package-manager.coffee
@@ -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\+/, '')
diff --git a/src/package.coffee b/src/package.coffee
index 0163f8bcd..00963ef28 100644
--- a/src/package.coffee
+++ b/src/package.coffee
@@ -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})
diff --git a/src/pane-axis-element.coffee b/src/pane-axis-element.coffee
index 91f27858e..eaa26a9fe 100644
--- a/src/pane-axis-element.coffee
+++ b/src/pane-axis-element.coffee
@@ -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)
diff --git a/src/pane-axis.coffee b/src/pane-axis.coffee
index 57d07d8a9..d104e984b 100644
--- a/src/pane-axis.coffee
+++ b/src/pane-axis.coffee
@@ -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
diff --git a/src/pane-container-element.coffee b/src/pane-container-element.coffee
index b3d4e0036..9da452558 100644
--- a/src/pane-container-element.coffee
+++ b/src/pane-container-element.coffee
@@ -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'))
diff --git a/src/pane-container.coffee b/src/pane-container.coffee
index a8d1fa5d2..8ad082a5f 100644
--- a/src/pane-container.coffee
+++ b/src/pane-container.coffee
@@ -7,46 +7,37 @@ ItemRegistry = require './item-registry'
module.exports =
class PaneContainer extends Model
- atom.deserializers.add(this)
-
- @version: 1
-
+ serializationVersion: 1
root: null
stoppedChangingActivePaneItemDelay: 100
stoppedChangingActivePaneItemTimeout: null
- @deserialize: (state) ->
- container = Object.create(@prototype) # allows us to pass a self reference to our child before invoking constructor
- state.root = atom.deserializers.deserialize(state.root, {container})
- state.destroyEmptyPanes = atom.config.get('core.destroyEmptyPanes')
- state.activePane = find state.root.getPanes(), (pane) -> pane.id is state.activePaneId
- @call(container, state) # run constructor
- container
-
constructor: (params) ->
super
- @activePane = params?.activePane
-
+ {@config, applicationDelegate, notificationManager, deserializerManager} = params
@emitter = new Emitter
@subscriptions = new CompositeDisposable
-
@itemRegistry = new ItemRegistry
- @setRoot(params?.root ? new Pane)
- @setActivePane(@getPanes()[0]) unless @getActivePane()
-
- @destroyEmptyPanes() if params?.destroyEmptyPanes
-
+ @setRoot(new Pane({container: this, @config, applicationDelegate, notificationManager, deserializerManager}))
+ @setActivePane(@getRoot())
@monitorActivePaneItem()
@monitorPaneItems()
serialize: (params) ->
deserializer: 'PaneContainer'
- version: @constructor.version
+ version: @serializationVersion
root: @root?.serialize()
activePaneId: @activePane.id
+ deserialize: (state, deserializerManager) ->
+ return unless state.version is @serializationVersion
+ @setRoot(deserializerManager.deserialize(state.root))
+ activePane = find @getRoot().getPanes(), (pane) -> pane.id is state.activePaneId
+ @setActivePane(activePane ? @getPanes()[0])
+ @destroyEmptyPanes() if @config.get('core.destroyEmptyPanes')
+
onDidChangeRoot: (fn) ->
@emitter.on 'did-change-root', fn
diff --git a/src/pane-element.coffee b/src/pane-element.coffee
index c6ded79c4..59f003300 100644
--- a/src/pane-element.coffee
+++ b/src/pane-element.coffee
@@ -44,14 +44,17 @@ class PaneElement extends HTMLElement
event.stopPropagation()
@getModel().activate()
pathsToOpen = Array::map.call event.dataTransfer.files, (file) -> file.path
- atom.open({pathsToOpen}) if pathsToOpen.length > 0
+ @open({pathsToOpen}) if pathsToOpen.length > 0
@addEventListener 'focus', handleFocus, true
@addEventListener 'blur', handleBlur, true
@addEventListener 'dragover', handleDragOver
@addEventListener 'drop', handleDrop
- initialize: (@model) ->
+ initialize: (@model, {@views, @open}) ->
+ throw new Error("Must pass a views parameter when initializing PaneElements") unless @views?
+ throw new Error("Must pass a open parameter when initializing PaneElements") unless @open?
+
@subscriptions.add @model.onDidActivate(@activated.bind(this))
@subscriptions.add @model.observeActive(@activeStatusChanged.bind(this))
@subscriptions.add @model.observeActiveItem(@activeItemChanged.bind(this))
@@ -78,7 +81,7 @@ class PaneElement extends HTMLElement
return unless item?
hasFocus = @hasFocus()
- itemView = atom.views.getView(item)
+ itemView = @views.getView(item)
if itemPath = item.getPath?()
@dataset.activeItemName = path.basename(itemPath)
@@ -109,7 +112,7 @@ class PaneElement extends HTMLElement
itemView.style.display = 'none'
itemRemoved: ({item, index, destroyed}) ->
- if viewToRemove = atom.views.getView(item)
+ if viewToRemove = @views.getView(item)
viewToRemove.remove()
paneDestroyed: ->
@@ -118,35 +121,9 @@ class PaneElement extends HTMLElement
flexScaleChanged: (flexScale) ->
@style.flexGrow = flexScale
- getActiveView: -> atom.views.getView(@model.getActiveItem())
+ getActiveView: -> @views.getView(@model.getActiveItem())
hasFocus: ->
this is document.activeElement or @contains(document.activeElement)
-atom.commands.add 'atom-workspace',
- 'pane:show-next-item': -> @getModel().getActivePane().activateNextItem()
- 'pane:show-previous-item': -> @getModel().getActivePane().activatePreviousItem()
- 'pane:show-item-1': -> @getModel().getActivePane().activateItemAtIndex(0)
- 'pane:show-item-2': -> @getModel().getActivePane().activateItemAtIndex(1)
- 'pane:show-item-3': -> @getModel().getActivePane().activateItemAtIndex(2)
- 'pane:show-item-4': -> @getModel().getActivePane().activateItemAtIndex(3)
- 'pane:show-item-5': -> @getModel().getActivePane().activateItemAtIndex(4)
- 'pane:show-item-6': -> @getModel().getActivePane().activateItemAtIndex(5)
- 'pane:show-item-7': -> @getModel().getActivePane().activateItemAtIndex(6)
- 'pane:show-item-8': -> @getModel().getActivePane().activateItemAtIndex(7)
- 'pane:show-item-9': -> @getModel().getActivePane().activateItemAtIndex(8)
- 'pane:move-item-right': -> @getModel().getActivePane().moveItemRight()
- 'pane:move-item-left': -> @getModel().getActivePane().moveItemLeft()
-
-atom.commands.add 'atom-pane',
- 'pane:save-items': -> @getModel().saveItems()
- 'pane:split-left': -> @getModel().splitLeft(copyActiveItem: true)
- 'pane:split-right': -> @getModel().splitRight(copyActiveItem: true)
- 'pane:split-up': -> @getModel().splitUp(copyActiveItem: true)
- 'pane:split-down': -> @getModel().splitDown(copyActiveItem: true)
- 'pane:close': -> @getModel().close()
- 'pane:close-other-items': -> @getModel().destroyInactiveItems()
- 'pane:increase-size': -> @getModel().increaseSize()
- 'pane:decrease-size': -> @getModel().decreaseSize()
-
module.exports = PaneElement = document.registerElement 'atom-pane', prototype: PaneElement.prototype
diff --git a/src/pane.coffee b/src/pane.coffee
index a146b66fc..5f0d8dc1b 100644
--- a/src/pane.coffee
+++ b/src/pane.coffee
@@ -10,30 +10,31 @@ TextEditor = require './text-editor'
# the default configuration, tabs are also displayed for each item.
module.exports =
class Pane extends Model
- atom.deserializers.add(this)
-
container: undefined
activeItem: undefined
focused: false
- @deserialize: (state, params) ->
+ @deserialize: (state, {deserializers, applicationDelegate, config, notifications}) ->
{items, activeItemURI, activeItemUri} = state
- state.container = params?.container
activeItemURI ?= activeItemUri
- state.items = compact(items.map (itemState) -> atom.deserializers.deserialize(itemState))
+ state.items = compact(items.map (itemState) -> deserializers.deserialize(itemState))
state.activeItem = find state.items, (item) ->
if typeof item.getURI is 'function'
itemURI = item.getURI()
itemURI is activeItemURI
-
- new this(state)
+ new Pane(extend(state, {
+ deserializerManager: deserializers,
+ notificationManager: notifications,
+ config, applicationDelegate
+ }))
constructor: (params) ->
super
- @container = params?.container
- @activeItem = params?.activeItem
- @focused = params?.focused
+ {
+ @activeItem, @focused, @applicationDelegate, @notificationManager, @config,
+ @deserializerManager
+ } = params
@emitter = new Emitter
@itemSubscriptions = new WeakMap
@@ -61,7 +62,7 @@ class Pane extends Model
getContainer: -> @container
setContainer: (container) ->
- unless container is @container
+ if container and container isnt @container
@container = container
container.didAddPane({pane: this})
@@ -395,7 +396,7 @@ class Pane extends Model
@items.splice(index, 1)
@emitter.emit 'did-remove-item', {item, index, destroyed: not moved, moved}
@container?.didDestroyPaneItem({item, index, pane: this}) unless moved
- @destroy() if @items.length is 0 and atom.config.get('core.destroyEmptyPanes')
+ @destroy() if @items.length is 0 and @config.get('core.destroyEmptyPanes')
# Public: Move the given item to the given index.
#
@@ -461,7 +462,7 @@ class Pane extends Model
else
return true
- chosen = atom.confirm
+ chosen = @applicationDelegate.confirm
message: "'#{item.getTitle?() ? uri}' has changes, do you want to save them?"
detailedMessage: "Your changes will be lost if you close this item without saving."
buttons: ["Save", "Cancel", "Don't Save"]
@@ -514,7 +515,7 @@ class Pane extends Model
saveOptions = item.getSaveDialogOptions?() ? {}
saveOptions.defaultPath ?= item.getPath()
- newItemPath = atom.showSaveDialogSync(saveOptions)
+ newItemPath = @applicationDelegate.showSaveDialog(saveOptions)
if newItemPath
try
item.saveAs(newItemPath)
@@ -554,7 +555,7 @@ class Pane extends Model
copyActiveItem: ->
if @activeItem?
- @activeItem.copy?() ? atom.deserializers.deserialize(@activeItem.serialize())
+ @activeItem.copy?() ? @deserializerManager.deserialize(@activeItem.serialize())
###
Section: Lifecycle
@@ -646,7 +647,7 @@ class Pane extends Model
@parent.replaceChild(this, new PaneAxis({@container, orientation, children: [this], @flexScale}))
@setFlexScale(1)
- newPane = new @constructor(params)
+ newPane = new Pane(extend({@applicationDelegate, @deserializerManager, @config}, params))
switch side
when 'before' then @parent.insertChildBefore(this, newPane)
when 'after' then @parent.insertChildAfter(this, newPane)
@@ -688,12 +689,12 @@ class Pane extends Model
handleSaveError: (error, item) ->
itemPath = error.path ? item?.getPath?()
- addWarningWithPath = (message, options) ->
+ addWarningWithPath = (message, options) =>
message = "#{message} '#{itemPath}'" if itemPath
- atom.notifications.addWarning(message, options)
+ @notificationManager.addWarning(message, options)
if error.code is 'EISDIR' or error.message?.endsWith?('is a directory')
- atom.notifications.addWarning("Unable to save file: #{error.message}")
+ @notificationManager.addWarning("Unable to save file: #{error.message}")
else if error.code is 'EACCES'
addWarningWithPath('Unable to save file: Permission denied')
else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST']
@@ -716,6 +717,6 @@ class Pane extends Model
addWarningWithPath('Unable to save file: Invalid seek')
else if errorMatch = /ENOTDIR, not a directory '([^']+)'/.exec(error.message)
fileName = errorMatch[1]
- atom.notifications.addWarning("Unable to save file: A directory in the path '#{fileName}' could not be written to")
+ @notificationManager.addWarning("Unable to save file: A directory in the path '#{fileName}' could not be written to")
else
throw error
diff --git a/src/panel-container-element.coffee b/src/panel-container-element.coffee
index e1459e6d6..b66a0cd90 100644
--- a/src/panel-container-element.coffee
+++ b/src/panel-container-element.coffee
@@ -4,9 +4,10 @@ class PanelContainerElement extends HTMLElement
createdCallback: ->
@subscriptions = new CompositeDisposable
- initialize: (@model) ->
+ initialize: (@model, {@views}) ->
+ throw new Error("Must pass a views parameter when initializing PanelContainerElements") unless @views?
+
@subscriptions.add @model.onDidAddPanel(@panelAdded.bind(this))
- @subscriptions.add @model.onDidRemovePanel(@panelRemoved.bind(this))
@subscriptions.add @model.onDidDestroy(@destroyed.bind(this))
@classList.add(@model.getLocation())
this
@@ -14,7 +15,7 @@ class PanelContainerElement extends HTMLElement
getModel: -> @model
panelAdded: ({panel, index}) ->
- panelElement = atom.views.getView(panel)
+ panelElement = @views.getView(panel)
panelElement.classList.add(@model.getLocation())
if @model.isModal()
panelElement.classList.add("overlay", "from-top")
@@ -32,9 +33,6 @@ class PanelContainerElement extends HTMLElement
@subscriptions.add panel.onDidChangeVisible (visible) =>
@hideAllPanelsExcept(panel) if visible
- panelRemoved: ({panel, index}) ->
- @removeChild(atom.views.getView(panel))
-
destroyed: ->
@subscriptions.dispose()
@parentNode?.removeChild(this)
diff --git a/src/panel-element.coffee b/src/panel-element.coffee
index 287d34940..72fd6ebf2 100644
--- a/src/panel-element.coffee
+++ b/src/panel-element.coffee
@@ -5,7 +5,9 @@ class PanelElement extends HTMLElement
createdCallback: ->
@subscriptions = new CompositeDisposable
- initialize: (@model) ->
+ initialize: (@model, {@views}) ->
+ throw new Error("Must pass a views parameter when initializing PanelElements") unless @views?
+
@appendChild(@getItemView())
@classList.add(@model.getClassName().split(' ')...) if @model.getClassName()?
@@ -17,7 +19,7 @@ class PanelElement extends HTMLElement
@model ?= new Panel
getItemView: ->
- atom.views.getView(@getModel().getItem())
+ @views.getView(@getModel().getItem())
attachedCallback: ->
@visibleChanged(@getModel().isVisible())
@@ -30,6 +32,6 @@ class PanelElement extends HTMLElement
destroyed: ->
@subscriptions.dispose()
- @parentNode?.removeChild(this)
+ @remove()
module.exports = PanelElement = document.registerElement 'atom-panel', prototype: PanelElement.prototype
diff --git a/src/project.coffee b/src/project.coffee
index b3f05a942..935e3a213 100644
--- a/src/project.coffee
+++ b/src/project.coffee
@@ -3,7 +3,7 @@ url = require 'url'
_ = require 'underscore-plus'
fs = require 'fs-plus'
-{Emitter} = require 'event-kit'
+{Emitter, Disposable} = require 'event-kit'
TextBuffer = require 'text-buffer'
DefaultDirectoryProvider = require './default-directory-provider'
@@ -17,68 +17,35 @@ GitRepositoryProvider = require './git-repository-provider'
# An instance of this class is always available as the `atom.project` global.
module.exports =
class Project extends Model
- atom.deserializers.add(this)
-
###
Section: Construction and Destruction
###
- @deserialize: (state) ->
- state.buffers = _.compact state.buffers.map (bufferState) ->
- # Check that buffer's file path is accessible
- return if fs.isDirectorySync(bufferState.filePath)
- if bufferState.filePath
- try
- fs.closeSync(fs.openSync(bufferState.filePath, 'r'))
- catch error
- return unless error.code is 'ENOENT'
-
- atom.deserializers.deserialize(bufferState)
-
- new this(state)
-
- constructor: ({path, paths, @buffers}={}) ->
+ constructor: ({@notificationManager, packageManager, config}) ->
@emitter = new Emitter
- @buffers ?= []
+ @buffers = []
+ @paths = []
@rootDirectories = []
@repositories = []
-
@directoryProviders = []
@defaultDirectoryProvider = new DefaultDirectoryProvider()
- atom.packages.serviceHub.consume(
- 'atom.directory-provider',
- '^0.1.0',
- (provider) => @directoryProviders.unshift(provider))
-
- # Mapping from the real path of a {Directory} to a {Promise} that resolves
- # to either a {Repository} or null. Ideally, the {Directory} would be used
- # as the key; however, there can be multiple {Directory} objects created for
- # the same real path, so it is not a good key.
@repositoryPromisesByPath = new Map()
-
- @repositoryProviders = [new GitRepositoryProvider(this)]
- atom.packages.serviceHub.consume(
- 'atom.repository-provider',
- '^0.1.0',
- (provider) =>
- @repositoryProviders.push(provider)
-
- # If a path in getPaths() does not have a corresponding Repository, try
- # to assign one by running through setPaths() again now that
- # @repositoryProviders has been updated.
- if null in @repositories
- @setPaths(@getPaths())
- )
-
- @subscribeToBuffer(buffer) for buffer in @buffers
-
- paths ?= _.compact([path])
- @setPaths(paths)
+ @repositoryProviders = [new GitRepositoryProvider(this, config)]
+ @consumeServices(packageManager)
destroyed: ->
- buffer.destroy() for buffer in @getBuffers()
+ buffer.destroy() for buffer in @buffers
@setPaths([])
+ reset: (packageManager) ->
+ @emitter.dispose()
+ @emitter = new Emitter
+
+ buffer?.destroy() for buffer in @buffers
+ @buffers = []
+ @setPaths([])
+ @consumeServices(packageManager)
+
destroyUnretainedBuffers: ->
buffer.destroy() for buffer in @getBuffers() when not buffer.isRetained()
return
@@ -87,6 +54,22 @@ class Project extends Model
Section: Serialization
###
+ deserialize: (state, deserializerManager) ->
+ states.paths = [state.path] if state.path? # backward compatibility
+
+ @buffers = _.compact state.buffers.map (bufferState) ->
+ # Check that buffer's file path is accessible
+ return if fs.isDirectorySync(bufferState.filePath)
+ if bufferState.filePath
+ try
+ fs.closeSync(fs.openSync(bufferState.filePath, 'r'))
+ catch error
+ return unless error.code is 'ENOENT'
+ deserializerManager.deserialize(bufferState)
+
+ @subscribeToBuffer(buffer) for buffer in @buffers
+ @setPaths(state.paths)
+
serialize: ->
deserializer: 'Project'
paths: @getPaths()
@@ -291,39 +274,25 @@ class Project extends Model
Section: Private
###
- # Given a path to a file, this constructs and associates a new
- # {TextEditor}, showing the file.
- #
- # * `filePath` The {String} path of the file to associate with.
- # * `options` Options that you can pass to the {TextEditor} constructor.
- #
- # Returns a promise that resolves to an {TextEditor}.
- open: (filePath, options={}) ->
- filePath = @resolvePath(filePath)
+ consumeServices: ({serviceHub}) ->
+ serviceHub.consume(
+ 'atom.directory-provider',
+ '^0.1.0',
+ (provider) =>
+ @directoryProviders.unshift(provider)
+ new Disposable =>
+ @directoryProviders.splice(@directoryProviders.indexOf(provider), 1)
+ )
- if filePath?
- try
- fs.closeSync(fs.openSync(filePath, 'r'))
- catch error
- # allow ENOENT errors to create an editor for paths that dont exist
- throw error unless error.code is 'ENOENT'
-
- absoluteFilePath = @resolvePath(filePath)
-
- fileSize = fs.getSizeSync(absoluteFilePath)
-
- if fileSize >= 20 * 1048576 # 20MB
- choice = atom.confirm
- message: 'Atom will be unresponsive during the loading of very large files.'
- detailedMessage: "Do you still want to load this file?"
- buttons: ["Proceed", "Cancel"]
- if choice is 1
- error = new Error
- error.code = 'CANCELLED'
- throw error
-
- @bufferForPath(absoluteFilePath).then (buffer) =>
- @buildEditorForBuffer(buffer, _.extend({fileSize}, options))
+ serviceHub.consume(
+ 'atom.repository-provider',
+ '^0.1.0',
+ (provider) =>
+ @repositoryProviders.push(provider)
+ @setPaths(@getPaths()) if null in @repositories
+ new Disposable =>
+ @repositoryProviders.splice(@repositoryProviders.indexOf(provider), 1)
+ )
# Retrieves all the {TextBuffer}s in the project; that is, the
# buffers for all open files.
@@ -404,11 +373,6 @@ class Project extends Model
[buffer] = @buffers.splice(index, 1)
buffer?.destroy()
- buildEditorForBuffer: (buffer, editorOptions) ->
- largeFileMode = editorOptions.fileSize >= 2 * 1048576 # 2MB
- editor = new TextEditor(_.extend({buffer, largeFileMode, registerEditor: true}, editorOptions))
- editor
-
eachBuffer: (args...) ->
subscriber = args.shift() if args.length > 1
callback = args.shift()
@@ -421,9 +385,9 @@ class Project extends Model
subscribeToBuffer: (buffer) ->
buffer.onDidDestroy => @removeBuffer(buffer)
- buffer.onWillThrowWatchError ({error, handle}) ->
+ buffer.onWillThrowWatchError ({error, handle}) =>
handle()
- atom.notifications.addWarning """
+ @notificationManager.addWarning """
Unable to read file after file `#{error.eventType}` event.
Make sure you have permission to access `#{buffer.getPath()}`.
""",
diff --git a/src/register-default-commands.coffee b/src/register-default-commands.coffee
new file mode 100644
index 000000000..80b7ff6c6
--- /dev/null
+++ b/src/register-default-commands.coffee
@@ -0,0 +1,212 @@
+ipc = require 'ipc'
+
+module.exports = ({commandRegistry, commandInstaller, config}) ->
+ commandRegistry.add 'atom-workspace',
+ 'pane:show-next-item': -> @getModel().getActivePane().activateNextItem()
+ 'pane:show-previous-item': -> @getModel().getActivePane().activatePreviousItem()
+ 'pane:show-item-1': -> @getModel().getActivePane().activateItemAtIndex(0)
+ 'pane:show-item-2': -> @getModel().getActivePane().activateItemAtIndex(1)
+ 'pane:show-item-3': -> @getModel().getActivePane().activateItemAtIndex(2)
+ 'pane:show-item-4': -> @getModel().getActivePane().activateItemAtIndex(3)
+ 'pane:show-item-5': -> @getModel().getActivePane().activateItemAtIndex(4)
+ 'pane:show-item-6': -> @getModel().getActivePane().activateItemAtIndex(5)
+ 'pane:show-item-7': -> @getModel().getActivePane().activateItemAtIndex(6)
+ 'pane:show-item-8': -> @getModel().getActivePane().activateItemAtIndex(7)
+ 'pane:show-item-9': -> @getModel().getActivePane().activateItemAtIndex(8)
+ 'pane:move-item-right': -> @getModel().getActivePane().moveItemRight()
+ 'pane:move-item-left': -> @getModel().getActivePane().moveItemLeft()
+ 'window:increase-font-size': -> @getModel().increaseFontSize()
+ 'window:decrease-font-size': -> @getModel().decreaseFontSize()
+ 'window:reset-font-size': -> @getModel().resetFontSize()
+ 'application:about': -> ipc.send('command', 'application:about')
+ 'application:show-preferences': -> ipc.send('command', 'application:show-settings')
+ 'application:show-settings': -> ipc.send('command', 'application:show-settings')
+ 'application:quit': -> ipc.send('command', 'application:quit')
+ 'application:hide': -> ipc.send('command', 'application:hide')
+ 'application:hide-other-applications': -> ipc.send('command', 'application:hide-other-applications')
+ 'application:install-update': -> ipc.send('command', 'application:install-update')
+ 'application:unhide-all-applications': -> ipc.send('command', 'application:unhide-all-applications')
+ 'application:new-window': -> ipc.send('command', 'application:new-window')
+ 'application:new-file': -> ipc.send('command', 'application:new-file')
+ 'application:open': -> ipc.send('command', 'application:open')
+ 'application:open-file': -> ipc.send('command', 'application:open-file')
+ 'application:open-folder': -> ipc.send('command', 'application:open-folder')
+ 'application:open-dev': -> ipc.send('command', 'application:open-dev')
+ 'application:open-safe': -> ipc.send('command', 'application:open-safe')
+ 'application:add-project-folder': -> atom.addProjectFolder()
+ 'application:minimize': -> ipc.send('command', 'application:minimize')
+ 'application:zoom': -> ipc.send('command', 'application:zoom')
+ 'application:bring-all-windows-to-front': -> ipc.send('command', 'application:bring-all-windows-to-front')
+ 'application:open-your-config': -> ipc.send('command', 'application:open-your-config')
+ 'application:open-your-init-script': -> ipc.send('command', 'application:open-your-init-script')
+ 'application:open-your-keymap': -> ipc.send('command', 'application:open-your-keymap')
+ 'application:open-your-snippets': -> ipc.send('command', 'application:open-your-snippets')
+ 'application:open-your-stylesheet': -> ipc.send('command', 'application:open-your-stylesheet')
+ 'application:open-license': -> @getModel().openLicense()
+ 'window:run-package-specs': -> @runPackageSpecs()
+ 'window:focus-next-pane': -> @getModel().activateNextPane()
+ 'window:focus-previous-pane': -> @getModel().activatePreviousPane()
+ 'window:focus-pane-above': -> @focusPaneViewAbove()
+ 'window:focus-pane-below': -> @focusPaneViewBelow()
+ 'window:focus-pane-on-left': -> @focusPaneViewOnLeft()
+ 'window:focus-pane-on-right': -> @focusPaneViewOnRight()
+ 'window:save-all': -> @getModel().saveAll()
+ 'window:toggle-invisibles': -> config.set("editor.showInvisibles", not config.get("editor.showInvisibles"))
+ 'window:log-deprecation-warnings': -> Grim.logDeprecations()
+ 'window:toggle-auto-indent': -> config.set("editor.autoIndent", not config.get("editor.autoIndent"))
+ 'pane:reopen-closed-item': -> @getModel().reopenItem()
+ 'core:close': -> @getModel().destroyActivePaneItemOrEmptyPane()
+ 'core:save': -> @getModel().saveActivePaneItem()
+ 'core:save-as': -> @getModel().saveActivePaneItemAs()
+
+ if process.platform is 'darwin'
+ commandRegistry.add 'atom-workspace', 'window:install-shell-commands', ->
+ commandInstaller.installShellCommandsInteractively()
+
+ commandRegistry.add 'atom-pane',
+ 'pane:save-items': -> @getModel().saveItems()
+ 'pane:split-left': -> @getModel().splitLeft(copyActiveItem: true)
+ 'pane:split-right': -> @getModel().splitRight(copyActiveItem: true)
+ 'pane:split-up': -> @getModel().splitUp(copyActiveItem: true)
+ 'pane:split-down': -> @getModel().splitDown(copyActiveItem: true)
+ 'pane:close': -> @getModel().close()
+ 'pane:close-other-items': -> @getModel().destroyInactiveItems()
+ 'pane:increase-size': -> @getModel().increaseSize()
+ 'pane:decrease-size': -> @getModel().decreaseSize()
+
+ commandRegistry.add 'atom-text-editor', stopEventPropagation(
+ 'core:undo': -> @undo()
+ 'core:redo': -> @redo()
+ 'core:move-left': -> @moveLeft()
+ 'core:move-right': -> @moveRight()
+ 'core:select-left': -> @selectLeft()
+ 'core:select-right': -> @selectRight()
+ 'core:select-up': -> @selectUp()
+ 'core:select-down': -> @selectDown()
+ 'core:select-all': -> @selectAll()
+ 'editor:select-word': -> @selectWordsContainingCursors()
+ 'editor:consolidate-selections': (event) -> event.abortKeyBinding() unless @consolidateSelections()
+ 'editor:move-to-beginning-of-next-paragraph': -> @moveToBeginningOfNextParagraph()
+ 'editor:move-to-beginning-of-previous-paragraph': -> @moveToBeginningOfPreviousParagraph()
+ 'editor:move-to-beginning-of-screen-line': -> @moveToBeginningOfScreenLine()
+ 'editor:move-to-beginning-of-line': -> @moveToBeginningOfLine()
+ 'editor:move-to-end-of-screen-line': -> @moveToEndOfScreenLine()
+ 'editor:move-to-end-of-line': -> @moveToEndOfLine()
+ 'editor:move-to-first-character-of-line': -> @moveToFirstCharacterOfLine()
+ 'editor:move-to-beginning-of-word': -> @moveToBeginningOfWord()
+ 'editor:move-to-end-of-word': -> @moveToEndOfWord()
+ 'editor:move-to-beginning-of-next-word': -> @moveToBeginningOfNextWord()
+ 'editor:move-to-previous-word-boundary': -> @moveToPreviousWordBoundary()
+ 'editor:move-to-next-word-boundary': -> @moveToNextWordBoundary()
+ 'editor:move-to-previous-subword-boundary': -> @moveToPreviousSubwordBoundary()
+ 'editor:move-to-next-subword-boundary': -> @moveToNextSubwordBoundary()
+ 'editor:select-to-beginning-of-next-paragraph': -> @selectToBeginningOfNextParagraph()
+ 'editor:select-to-beginning-of-previous-paragraph': -> @selectToBeginningOfPreviousParagraph()
+ 'editor:select-to-end-of-line': -> @selectToEndOfLine()
+ 'editor:select-to-beginning-of-line': -> @selectToBeginningOfLine()
+ 'editor:select-to-end-of-word': -> @selectToEndOfWord()
+ 'editor:select-to-beginning-of-word': -> @selectToBeginningOfWord()
+ 'editor:select-to-beginning-of-next-word': -> @selectToBeginningOfNextWord()
+ 'editor:select-to-next-word-boundary': -> @selectToNextWordBoundary()
+ 'editor:select-to-previous-word-boundary': -> @selectToPreviousWordBoundary()
+ 'editor:select-to-next-subword-boundary': -> @selectToNextSubwordBoundary()
+ 'editor:select-to-previous-subword-boundary': -> @selectToPreviousSubwordBoundary()
+ 'editor:select-to-first-character-of-line': -> @selectToFirstCharacterOfLine()
+ 'editor:select-line': -> @selectLinesContainingCursors()
+ )
+
+ commandRegistry.add 'atom-text-editor', stopEventPropagationAndGroupUndo(config,
+ 'core:backspace': -> @backspace()
+ 'core:delete': -> @delete()
+ 'core:cut': -> @cutSelectedText()
+ 'core:copy': -> @copySelectedText()
+ 'core:paste': -> @pasteText()
+ 'editor:delete-to-previous-word-boundary': -> @deleteToPreviousWordBoundary()
+ 'editor:delete-to-next-word-boundary': -> @deleteToNextWordBoundary()
+ 'editor:delete-to-beginning-of-word': -> @deleteToBeginningOfWord()
+ 'editor:delete-to-beginning-of-line': -> @deleteToBeginningOfLine()
+ 'editor:delete-to-end-of-line': -> @deleteToEndOfLine()
+ 'editor:delete-to-end-of-word': -> @deleteToEndOfWord()
+ 'editor:delete-to-beginning-of-subword': -> @deleteToBeginningOfSubword()
+ 'editor:delete-to-end-of-subword': -> @deleteToEndOfSubword()
+ 'editor:delete-line': -> @deleteLine()
+ 'editor:cut-to-end-of-line': -> @cutToEndOfLine()
+ 'editor:cut-to-end-of-buffer-line': -> @cutToEndOfBufferLine()
+ 'editor:transpose': -> @transpose()
+ 'editor:upper-case': -> @upperCase()
+ 'editor:lower-case': -> @lowerCase()
+ 'editor:copy-selection': -> @copyOnlySelectedText()
+ )
+
+ commandRegistry.add 'atom-text-editor:not([mini])', stopEventPropagation(
+ 'core:move-up': -> @moveUp()
+ 'core:move-down': -> @moveDown()
+ 'core:move-to-top': -> @moveToTop()
+ 'core:move-to-bottom': -> @moveToBottom()
+ 'core:page-up': -> @pageUp()
+ 'core:page-down': -> @pageDown()
+ 'core:select-to-top': -> @selectToTop()
+ 'core:select-to-bottom': -> @selectToBottom()
+ 'core:select-page-up': -> @selectPageUp()
+ 'core:select-page-down': -> @selectPageDown()
+ 'editor:add-selection-below': -> @addSelectionBelow()
+ 'editor:add-selection-above': -> @addSelectionAbove()
+ 'editor:split-selections-into-lines': -> @splitSelectionsIntoLines()
+ 'editor:toggle-soft-tabs': -> @toggleSoftTabs()
+ 'editor:toggle-soft-wrap': -> @toggleSoftWrapped()
+ 'editor:fold-all': -> @foldAll()
+ 'editor:unfold-all': -> @unfoldAll()
+ 'editor:fold-current-row': -> @foldCurrentRow()
+ 'editor:unfold-current-row': -> @unfoldCurrentRow()
+ 'editor:fold-selection': -> @foldSelectedLines()
+ 'editor:fold-at-indent-level-1': -> @foldAllAtIndentLevel(0)
+ 'editor:fold-at-indent-level-2': -> @foldAllAtIndentLevel(1)
+ 'editor:fold-at-indent-level-3': -> @foldAllAtIndentLevel(2)
+ 'editor:fold-at-indent-level-4': -> @foldAllAtIndentLevel(3)
+ 'editor:fold-at-indent-level-5': -> @foldAllAtIndentLevel(4)
+ 'editor:fold-at-indent-level-6': -> @foldAllAtIndentLevel(5)
+ 'editor:fold-at-indent-level-7': -> @foldAllAtIndentLevel(6)
+ 'editor:fold-at-indent-level-8': -> @foldAllAtIndentLevel(7)
+ 'editor:fold-at-indent-level-9': -> @foldAllAtIndentLevel(8)
+ 'editor:log-cursor-scope': -> @logCursorScope()
+ 'editor:copy-path': -> @copyPathToClipboard()
+ 'editor:toggle-indent-guide': -> config.set('editor.showIndentGuide', not config.get('editor.showIndentGuide'))
+ 'editor:toggle-line-numbers': -> config.set('editor.showLineNumbers', not config.get('editor.showLineNumbers'))
+ 'editor:scroll-to-cursor': -> @scrollToCursorPosition()
+ )
+
+ commandRegistry.add 'atom-text-editor:not([mini])', stopEventPropagationAndGroupUndo(config,
+ 'editor:indent': -> @indent()
+ 'editor:auto-indent': -> @autoIndentSelectedRows()
+ 'editor:indent-selected-rows': -> @indentSelectedRows()
+ 'editor:outdent-selected-rows': -> @outdentSelectedRows()
+ 'editor:newline': -> @insertNewline()
+ 'editor:newline-below': -> @insertNewlineBelow()
+ 'editor:newline-above': -> @insertNewlineAbove()
+ 'editor:toggle-line-comments': -> @toggleLineCommentsInSelection()
+ 'editor:checkout-head-revision': -> @checkoutHeadRevision()
+ 'editor:move-line-up': -> @moveLineUp()
+ 'editor:move-line-down': -> @moveLineDown()
+ 'editor:duplicate-lines': -> @duplicateLines()
+ 'editor:join-lines': -> @joinLines()
+ )
+
+stopEventPropagation = (commandListeners) ->
+ newCommandListeners = {}
+ for commandName, commandListener of commandListeners
+ do (commandListener) ->
+ newCommandListeners[commandName] = (event) ->
+ event.stopPropagation()
+ commandListener.call(@getModel(), event)
+ newCommandListeners
+
+stopEventPropagationAndGroupUndo = (config, commandListeners) ->
+ newCommandListeners = {}
+ for commandName, commandListener of commandListeners
+ do (commandListener) ->
+ newCommandListeners[commandName] = (event) ->
+ event.stopPropagation()
+ model = @getModel()
+ model.transact config.get('editor.undoGroupingInterval'), ->
+ commandListener.call(model, event)
+ newCommandListeners
diff --git a/src/scoped-properties.coffee b/src/scoped-properties.coffee
index 8bf4c5ed3..c7257083e 100644
--- a/src/scoped-properties.coffee
+++ b/src/scoped-properties.coffee
@@ -3,21 +3,21 @@ CSON = require 'season'
module.exports =
class ScopedProperties
- @load: (scopedPropertiesPath, callback) ->
+ @load: (scopedPropertiesPath, config, callback) ->
CSON.readFile scopedPropertiesPath, (error, scopedProperties={}) ->
if error?
callback(error)
else
- callback(null, new ScopedProperties(scopedPropertiesPath, scopedProperties))
+ callback(null, new ScopedProperties(scopedPropertiesPath, scopedProperties, config))
- constructor: (@path, @scopedProperties) ->
+ constructor: (@path, @scopedProperties, @config) ->
activate: ->
for selector, properties of @scopedProperties
- atom.config.set(null, properties, scopeSelector: selector, source: @path)
+ @config.set(null, properties, scopeSelector: selector, source: @path)
return
deactivate: ->
for selector of @scopedProperties
- atom.config.unset(null, scopeSelector: selector, source: @path)
+ @config.unset(null, scopeSelector: selector, source: @path)
return
diff --git a/src/selection.coffee b/src/selection.coffee
index 80929cac7..2ba66ebb0 100644
--- a/src/selection.coffee
+++ b/src/selection.coffee
@@ -14,7 +14,7 @@ class Selection extends Model
initialScreenRange: null
wordwise: false
- constructor: ({@cursor, @marker, @editor, id}) ->
+ constructor: ({@cursor, @marker, @editor, id, @clipboard}) ->
@emitter = new Emitter
@assignId(id)
@@ -611,7 +611,7 @@ class Selection extends Model
startLevel = @editor.indentLevelForLine(precedingText)
if maintainClipboard
- {text: clipboardText, metadata} = atom.clipboard.readWithMetadata()
+ {text: clipboardText, metadata} = @clipboard.readWithMetadata()
metadata ?= {}
unless metadata.selections?
metadata.selections = [{
@@ -624,9 +624,9 @@ class Selection extends Model
indentBasis: startLevel,
fullLine: fullLine
})
- atom.clipboard.write([clipboardText, selectionText].join("\n"), metadata)
+ @clipboard.write([clipboardText, selectionText].join("\n"), metadata)
else
- atom.clipboard.write(selectionText, {
+ @clipboard.write(selectionText, {
indentBasis: startLevel,
fullLine: fullLine
})
diff --git a/src/storage-folder.coffee b/src/storage-folder.coffee
index bf969dee2..da8af3f2e 100644
--- a/src/storage-folder.coffee
+++ b/src/storage-folder.coffee
@@ -4,12 +4,16 @@ fs = require "fs-plus"
module.exports =
class StorageFolder
constructor: (containingPath) ->
- @path = path.join(containingPath, "storage")
+ @path = path.join(containingPath, "storage") if containingPath?
store: (name, object) ->
+ return unless @path?
+
fs.writeFileSync(@pathForKey(name), JSON.stringify(object), 'utf8')
load: (name) ->
+ return unless @path?
+
statePath = @pathForKey(name)
try
stateString = fs.readFileSync(statePath, 'utf8')
diff --git a/src/style-manager.coffee b/src/style-manager.coffee
index cfe86b3fe..8f932d229 100644
--- a/src/style-manager.coffee
+++ b/src/style-manager.coffee
@@ -1,6 +1,7 @@
fs = require 'fs-plus'
path = require 'path'
{Emitter, Disposable} = require 'event-kit'
+StylesElement = require './styles-element'
# Extended: A singleton instance of this class available via `atom.styles`,
# which you can use to globally query and observe the set of active style
@@ -9,7 +10,7 @@ path = require 'path'
# which clone and attach style elements in different contexts.
module.exports =
class StyleManager
- constructor: ->
+ constructor: ({@configDirPath}) ->
@emitter = new Emitter
@styleElements = []
@styleElementsBySourcePath = {}
@@ -154,6 +155,11 @@ class StyleManager
return
+ buildStylesElement: ->
+ stylesElement = new StylesElement
+ stylesElement.initialize(this)
+ stylesElement
+
###
Section: Paths
###
@@ -162,8 +168,10 @@ class StyleManager
#
# Returns a {String}.
getUserStyleSheetPath: ->
- stylesheetPath = fs.resolve(path.join(atom.getConfigDirPath(), 'styles'), ['css', 'less'])
+ return "" unless @configDirPath?
+
+ stylesheetPath = fs.resolve(path.join(@configDirPath, 'styles'), ['css', 'less'])
if fs.isFileSync(stylesheetPath)
stylesheetPath
else
- path.join(atom.getConfigDirPath(), 'styles.less')
+ path.join(@configDirPath, 'styles.less')
diff --git a/src/styles-element.coffee b/src/styles-element.coffee
index fc3b888cf..d1e6bf3d9 100644
--- a/src/styles-element.coffee
+++ b/src/styles-element.coffee
@@ -14,6 +14,7 @@ class StylesElement extends HTMLElement
@emitter.on 'did-update-style-element', callback
createdCallback: ->
+ @subscriptions = new CompositeDisposable
@emitter = new Emitter
@styleElementClonesByOriginalElement = new WeakMap
@@ -21,31 +22,29 @@ class StylesElement extends HTMLElement
if @context is 'atom-text-editor'
for styleElement in @children
@upgradeDeprecatedSelectors(styleElement)
- @initialize()
+
+ @context = @getAttribute('context') ? undefined
detachedCallback: ->
@subscriptions.dispose()
- @subscriptions = null
+ @subscriptions = new CompositeDisposable
attributeChangedCallback: (attrName, oldVal, newVal) ->
@contextChanged() if attrName is 'context'
- initialize: ->
- return if @subscriptions?
+ initialize: (@styleManager) ->
+ throw new Error("Must pass a styleManager parameter when initializing a StylesElement") unless @styleManager?
- @subscriptions = new CompositeDisposable
- @context = @getAttribute('context') ? undefined
-
- @subscriptions.add atom.styles.observeStyleElements(@styleElementAdded.bind(this))
- @subscriptions.add atom.styles.onDidRemoveStyleElement(@styleElementRemoved.bind(this))
- @subscriptions.add atom.styles.onDidUpdateStyleElement(@styleElementUpdated.bind(this))
+ @subscriptions.add @styleManager.observeStyleElements(@styleElementAdded.bind(this))
+ @subscriptions.add @styleManager.onDidRemoveStyleElement(@styleElementRemoved.bind(this))
+ @subscriptions.add @styleManager.onDidUpdateStyleElement(@styleElementUpdated.bind(this))
contextChanged: ->
return unless @subscriptions?
@styleElementRemoved(child) for child in Array::slice.call(@children)
@context = @getAttribute('context')
- @styleElementAdded(styleElement) for styleElement in atom.styles.getStyleElements()
+ @styleElementAdded(styleElement) for styleElement in @styleManager.getStyleElements()
return
styleElementAdded: (styleElement) ->
diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee
index 3dd9f317c..d72c50382 100644
--- a/src/text-editor-component.coffee
+++ b/src/text-editor-component.coffee
@@ -38,15 +38,15 @@ class TextEditorComponent
Object.defineProperty @prototype, "domNode",
get: -> @domNodeValue
set: (domNode) ->
- atom.assert domNode?, "TextEditorComponent::domNode was set to null."
+ @assert domNode?, "TextEditorComponent::domNode was set to null."
@domNodeValue = domNode
- constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, tileSize}) ->
+ constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, tileSize, @views, @themes, @config, @workspace, @assert, @grammars}) ->
@tileSize = tileSize if tileSize?
@disposables = new CompositeDisposable
@observeConfig()
- @setScrollSensitivity(atom.config.get('editor.scrollSensitivity'))
+ @setScrollSensitivity(@config.get('editor.scrollSensitivity'))
@presenter = new TextEditorPresenter
model: @editor
@@ -58,6 +58,7 @@ class TextEditorComponent
cursorBlinkPeriod: @cursorBlinkPeriod
cursorBlinkResumeDelay: @cursorBlinkResumeDelay
stoppedScrollingDelay: 200
+ config: @config
@presenter.onDidUpdateState(@requestUpdate)
@@ -70,10 +71,10 @@ class TextEditorComponent
insertionPoint = document.createElement('content')
insertionPoint.setAttribute('select', 'atom-overlay')
@domNode.appendChild(insertionPoint)
- @overlayManager = new OverlayManager(@presenter, @hostElement)
+ @overlayManager = new OverlayManager(@presenter, @hostElement, @views)
else
@domNode.classList.add('editor-contents')
- @overlayManager = new OverlayManager(@presenter, @domNode)
+ @overlayManager = new OverlayManager(@presenter, @domNode, @views)
@scrollViewNode = document.createElement('div')
@scrollViewNode.classList.add('scroll-view')
@@ -82,10 +83,10 @@ class TextEditorComponent
@hiddenInputComponent = new InputComponent
@scrollViewNode.appendChild(@hiddenInputComponent.getDomNode())
- @linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM, @domElementPool})
+ @linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM, @domElementPool, @assert, @grammars})
@scrollViewNode.appendChild(@linesComponent.getDomNode())
- @linesYardstick = new LinesYardstick(@editor, @presenter, @linesComponent)
+ @linesYardstick = new LinesYardstick(@editor, @presenter, @linesComponent, @grammars)
@presenter.setLinesYardstick(@linesYardstick)
@horizontalScrollbarComponent = new ScrollbarComponent({orientation: 'horizontal', onScroll: @onHorizontalScroll})
@@ -103,11 +104,11 @@ class TextEditorComponent
@disposables.add @stylesElement.onDidAddStyleElement @onStylesheetsChanged
@disposables.add @stylesElement.onDidUpdateStyleElement @onStylesheetsChanged
@disposables.add @stylesElement.onDidRemoveStyleElement @onStylesheetsChanged
- unless atom.themes.isInitialLoadComplete()
- @disposables.add atom.themes.onDidChangeActiveThemes @onAllThemesLoaded
+ unless @themes.isInitialLoadComplete()
+ @disposables.add @themes.onDidChangeActiveThemes @onAllThemesLoaded
@disposables.add scrollbarStyle.onDidChangePreferredScrollbarStyle @refreshScrollbars
- @disposables.add atom.views.pollDocument(@pollDOM)
+ @disposables.add @views.pollDocument(@pollDOM)
@updateSync()
@checkForVisibilityChange()
@@ -177,7 +178,7 @@ class TextEditorComponent
@overlayManager?.measureOverlays()
mountGutterContainerComponent: ->
- @gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown, @domElementPool})
+ @gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown, @domElementPool, @views})
@domNode.insertBefore(@gutterContainerComponent.getDomNode(), @domNode.firstChild)
becameVisible: ->
@@ -204,10 +205,10 @@ class TextEditorComponent
@updateSync()
else unless @updateRequested
@updateRequested = true
- atom.views.updateDocument =>
+ @views.updateDocument =>
@updateRequested = false
@updateSync() if @canUpdate()
- atom.views.readDocument(@readAfterUpdateSync)
+ @views.readDocument(@readAfterUpdateSync)
canUpdate: ->
@mounted and @editor.isAlive()
@@ -275,13 +276,13 @@ class TextEditorComponent
timeoutId = setTimeout(writeSelectedTextToSelectionClipboard)
observeConfig: ->
- @disposables.add atom.config.onDidChange 'editor.fontSize', =>
+ @disposables.add @config.onDidChange 'editor.fontSize', =>
@sampleFontStyling()
@invalidateCharacterWidths()
- @disposables.add atom.config.onDidChange 'editor.fontFamily', =>
+ @disposables.add @config.onDidChange 'editor.fontFamily', =>
@sampleFontStyling()
@invalidateCharacterWidths()
- @disposables.add atom.config.onDidChange 'editor.lineHeight', =>
+ @disposables.add @config.onDidChange 'editor.lineHeight', =>
@sampleFontStyling()
@invalidateCharacterWidths()
@@ -294,7 +295,7 @@ class TextEditorComponent
@disposables.add(@scopedConfigDisposables)
scope = @editor.getRootScopeDescriptor()
- @scopedConfigDisposables.add atom.config.observe 'editor.scrollSensitivity', {scope}, @setScrollSensitivity
+ @scopedConfigDisposables.add @config.observe 'editor.scrollSensitivity', {scope}, @setScrollSensitivity
focused: ->
if @mounted
@@ -354,11 +355,11 @@ class TextEditorComponent
{wheelDeltaX, wheelDeltaY} = event
# Ctrl+MouseWheel adjusts font size.
- if event.ctrlKey and atom.config.get('editor.zoomFontWhenCtrlScrolling')
+ if event.ctrlKey and @config.get('editor.zoomFontWhenCtrlScrolling')
if wheelDeltaY > 0
- atom.workspace.increaseFontSize()
+ @workspace.increaseFontSize()
else if wheelDeltaY < 0
- atom.workspace.decreaseFontSize()
+ @workspace.decreaseFontSize()
event.preventDefault()
return
@@ -420,6 +421,9 @@ class TextEditorComponent
getScrollWidth: ->
@presenter.getScrollWidth()
+ getMaxScrollTop: ->
+ @presenter.getMaxScrollTop()
+
getVerticalScrollbarWidth: ->
@presenter.getVerticalScrollbarWidth()
@@ -543,7 +547,7 @@ class TextEditorComponent
onStylesheetsChanged: (styleElement) =>
return unless @performedInitialMeasurement
- return unless atom.themes.isInitialLoadComplete()
+ return unless @themes.isInitialLoadComplete()
# This delay prevents the styling from going haywire when stylesheets are
# reloaded in dev mode. It seems like a workaround for a browser bug, but
@@ -650,7 +654,7 @@ class TextEditorComponent
isVisible: ->
# Investigating an exception that occurs here due to ::domNode being null.
- atom.assert @domNode?, "TextEditorComponent::domNode was null.", (error) =>
+ @assert @domNode?, "TextEditorComponent::domNode was null.", (error) =>
error.metadata = {@initialized}
@domNode? and (@domNode.offsetHeight > 0 or @domNode.offsetWidth > 0)
@@ -848,7 +852,7 @@ class TextEditorComponent
@presenter.characterWidthsChanged()
setShowIndentGuide: (showIndentGuide) ->
- atom.config.set("editor.showIndentGuide", showIndentGuide)
+ @config.set("editor.showIndentGuide", showIndentGuide)
setScrollSensitivity: (scrollSensitivity) =>
if scrollSensitivity = parseInt(scrollSensitivity)
diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee
index 405cf30e4..55e23d2da 100644
--- a/src/text-editor-element.coffee
+++ b/src/text-editor-element.coffee
@@ -4,6 +4,7 @@ Path = require 'path'
TextBuffer = require 'text-buffer'
TextEditor = require './text-editor'
TextEditorComponent = require './text-editor-component'
+StylesElement = require './styles-element'
ShadowStyleSheet = null
@@ -18,29 +19,38 @@ class TextEditorElement extends HTMLElement
logicalDisplayBuffer: true
createdCallback: ->
+ # Use globals when the following instance variables aren't set.
+ @config = atom.config
+ @themes = atom.themes
+ @workspace = atom.workspace
+ @assert = atom.assert
+ @views = atom.views
+ @styles = atom.styles
+ @grammars = atom.grammars
+
@emitter = new Emitter
@subscriptions = new CompositeDisposable
- @initializeContent()
+
@addEventListener 'focus', @focused.bind(this)
@addEventListener 'blur', @blurred.bind(this)
- initializeContent: (attributes) ->
@classList.add('editor')
@setAttribute('tabindex', -1)
- if atom.config.get('editor.useShadowDOM')
+ initializeContent: (attributes) ->
+ if @config.get('editor.useShadowDOM')
@useShadowDOM = true
unless ShadowStyleSheet?
ShadowStyleSheet = document.createElement('style')
- ShadowStyleSheet.textContent = atom.themes.loadLessStylesheet(require.resolve('../static/text-editor-shadow.less'))
+ ShadowStyleSheet.textContent = @themes.loadLessStylesheet(require.resolve('../static/text-editor-shadow.less'))
@createShadowRoot()
@shadowRoot.appendChild(ShadowStyleSheet.cloneNode(true))
- @stylesElement = document.createElement('atom-styles')
+ @stylesElement = new StylesElement
+ @stylesElement.initialize(@styles)
@stylesElement.setAttribute('context', 'atom-text-editor')
- @stylesElement.initialize()
@rootElement = document.createElement('div')
@rootElement.classList.add('editor--private')
@@ -56,7 +66,7 @@ class TextEditorElement extends HTMLElement
attachedCallback: ->
@buildModel() unless @getModel()?
- atom.assert(@model.isAlive(), "Attaching a view for a destroyed editor")
+ @assert(@model.isAlive(), "Attaching a view for a destroyed editor")
@mountComponent() unless @component?
@listenForComponentEvents()
@component.checkForVisibilityChange()
@@ -76,7 +86,15 @@ class TextEditorElement extends HTMLElement
@subscriptions.add @component.onDidChangeScrollLeft =>
@emitter.emit("did-change-scroll-left", arguments...)
- initialize: (model) ->
+ initialize: (model, {@views, @config, @themes, @workspace, @assert, @styles, @grammars}) ->
+ throw new Error("Must pass a config parameter when initializing TextEditorElements") unless @views?
+ throw new Error("Must pass a config parameter when initializing TextEditorElements") unless @config?
+ throw new Error("Must pass a themes parameter when initializing TextEditorElements") unless @themes?
+ throw new Error("Must pass a workspace parameter when initializing TextEditorElements") unless @workspace?
+ throw new Error("Must pass a assert parameter when initializing TextEditorElements") unless @assert?
+ throw new Error("Must pass a styles parameter when initializing TextEditorElements") unless @styles?
+ throw new Error("Must pass a grammars parameter when initializing TextEditorElements") unless @grammars?
+
@setModel(model)
this
@@ -85,6 +103,7 @@ class TextEditorElement extends HTMLElement
return if model.isDestroyed()
@model = model
+ @initializeContent()
@mountComponent()
@addGrammarScopeAttribute()
@addMiniAttribute() if @model.isMini()
@@ -99,7 +118,7 @@ class TextEditorElement extends HTMLElement
@model ? @buildModel()
buildModel: ->
- @setModel(new TextEditor(
+ @setModel(@workspace.buildTextEditor(
buffer: new TextBuffer(@textContent)
softWrapped: false
tabLength: 2
@@ -117,6 +136,12 @@ class TextEditorElement extends HTMLElement
editor: @model
tileSize: @tileSize
useShadowDOM: @useShadowDOM
+ views: @views
+ themes: @themes
+ config: @config
+ workspace: @workspace
+ assert: @assert
+ grammars: @grammars
)
@rootElement.appendChild(@component.getDomNode())
@@ -185,6 +210,12 @@ class TextEditorElement extends HTMLElement
getDefaultCharacterWidth: ->
@getModel().getDefaultCharWidth()
+ # Extended: Get the maximum scroll top that can be applied to this element.
+ #
+ # Returns a {Number} of pixels.
+ getMaxScrollTop: ->
+ @component?.getMaxScrollTop()
+
# Extended: Converts a buffer position to a pixel position.
#
# * `bufferPosition` An object that represents a buffer position. It can be either
@@ -313,141 +344,4 @@ class TextEditorElement extends HTMLElement
getHeight: ->
@offsetHeight
-stopEventPropagation = (commandListeners) ->
- newCommandListeners = {}
- for commandName, commandListener of commandListeners
- do (commandListener) ->
- newCommandListeners[commandName] = (event) ->
- event.stopPropagation()
- commandListener.call(@getModel(), event)
- newCommandListeners
-
-stopEventPropagationAndGroupUndo = (commandListeners) ->
- newCommandListeners = {}
- for commandName, commandListener of commandListeners
- do (commandListener) ->
- newCommandListeners[commandName] = (event) ->
- event.stopPropagation()
- model = @getModel()
- model.transact atom.config.get('editor.undoGroupingInterval'), ->
- commandListener.call(model, event)
- newCommandListeners
-
-atom.commands.add 'atom-text-editor', stopEventPropagation(
- 'core:undo': -> @undo()
- 'core:redo': -> @redo()
- 'core:move-left': -> @moveLeft()
- 'core:move-right': -> @moveRight()
- 'core:select-left': -> @selectLeft()
- 'core:select-right': -> @selectRight()
- 'core:select-up': -> @selectUp()
- 'core:select-down': -> @selectDown()
- 'core:select-all': -> @selectAll()
- 'editor:select-word': -> @selectWordsContainingCursors()
- 'editor:consolidate-selections': (event) -> event.abortKeyBinding() unless @consolidateSelections()
- 'editor:move-to-beginning-of-next-paragraph': -> @moveToBeginningOfNextParagraph()
- 'editor:move-to-beginning-of-previous-paragraph': -> @moveToBeginningOfPreviousParagraph()
- 'editor:move-to-beginning-of-screen-line': -> @moveToBeginningOfScreenLine()
- 'editor:move-to-beginning-of-line': -> @moveToBeginningOfLine()
- 'editor:move-to-end-of-screen-line': -> @moveToEndOfScreenLine()
- 'editor:move-to-end-of-line': -> @moveToEndOfLine()
- 'editor:move-to-first-character-of-line': -> @moveToFirstCharacterOfLine()
- 'editor:move-to-beginning-of-word': -> @moveToBeginningOfWord()
- 'editor:move-to-end-of-word': -> @moveToEndOfWord()
- 'editor:move-to-beginning-of-next-word': -> @moveToBeginningOfNextWord()
- 'editor:move-to-previous-word-boundary': -> @moveToPreviousWordBoundary()
- 'editor:move-to-next-word-boundary': -> @moveToNextWordBoundary()
- 'editor:move-to-previous-subword-boundary': -> @moveToPreviousSubwordBoundary()
- 'editor:move-to-next-subword-boundary': -> @moveToNextSubwordBoundary()
- 'editor:select-to-beginning-of-next-paragraph': -> @selectToBeginningOfNextParagraph()
- 'editor:select-to-beginning-of-previous-paragraph': -> @selectToBeginningOfPreviousParagraph()
- 'editor:select-to-end-of-line': -> @selectToEndOfLine()
- 'editor:select-to-beginning-of-line': -> @selectToBeginningOfLine()
- 'editor:select-to-end-of-word': -> @selectToEndOfWord()
- 'editor:select-to-beginning-of-word': -> @selectToBeginningOfWord()
- 'editor:select-to-beginning-of-next-word': -> @selectToBeginningOfNextWord()
- 'editor:select-to-next-word-boundary': -> @selectToNextWordBoundary()
- 'editor:select-to-previous-word-boundary': -> @selectToPreviousWordBoundary()
- 'editor:select-to-next-subword-boundary': -> @selectToNextSubwordBoundary()
- 'editor:select-to-previous-subword-boundary': -> @selectToPreviousSubwordBoundary()
- 'editor:select-to-first-character-of-line': -> @selectToFirstCharacterOfLine()
- 'editor:select-line': -> @selectLinesContainingCursors()
-)
-
-atom.commands.add 'atom-text-editor', stopEventPropagationAndGroupUndo(
- 'core:backspace': -> @backspace()
- 'core:delete': -> @delete()
- 'core:cut': -> @cutSelectedText()
- 'core:copy': -> @copySelectedText()
- 'core:paste': -> @pasteText()
- 'editor:delete-to-previous-word-boundary': -> @deleteToPreviousWordBoundary()
- 'editor:delete-to-next-word-boundary': -> @deleteToNextWordBoundary()
- 'editor:delete-to-beginning-of-word': -> @deleteToBeginningOfWord()
- 'editor:delete-to-beginning-of-line': -> @deleteToBeginningOfLine()
- 'editor:delete-to-end-of-line': -> @deleteToEndOfLine()
- 'editor:delete-to-end-of-word': -> @deleteToEndOfWord()
- 'editor:delete-to-beginning-of-subword': -> @deleteToBeginningOfSubword()
- 'editor:delete-to-end-of-subword': -> @deleteToEndOfSubword()
- 'editor:delete-line': -> @deleteLine()
- 'editor:cut-to-end-of-line': -> @cutToEndOfLine()
- 'editor:cut-to-end-of-buffer-line': -> @cutToEndOfBufferLine()
- 'editor:transpose': -> @transpose()
- 'editor:upper-case': -> @upperCase()
- 'editor:lower-case': -> @lowerCase()
- 'editor:copy-selection': -> @copyOnlySelectedText()
-)
-
-atom.commands.add 'atom-text-editor:not([mini])', stopEventPropagation(
- 'core:move-up': -> @moveUp()
- 'core:move-down': -> @moveDown()
- 'core:move-to-top': -> @moveToTop()
- 'core:move-to-bottom': -> @moveToBottom()
- 'core:page-up': -> @pageUp()
- 'core:page-down': -> @pageDown()
- 'core:select-to-top': -> @selectToTop()
- 'core:select-to-bottom': -> @selectToBottom()
- 'core:select-page-up': -> @selectPageUp()
- 'core:select-page-down': -> @selectPageDown()
- 'editor:add-selection-below': -> @addSelectionBelow()
- 'editor:add-selection-above': -> @addSelectionAbove()
- 'editor:split-selections-into-lines': -> @splitSelectionsIntoLines()
- 'editor:toggle-soft-tabs': -> @toggleSoftTabs()
- 'editor:toggle-soft-wrap': -> @toggleSoftWrapped()
- 'editor:fold-all': -> @foldAll()
- 'editor:unfold-all': -> @unfoldAll()
- 'editor:fold-current-row': -> @foldCurrentRow()
- 'editor:unfold-current-row': -> @unfoldCurrentRow()
- 'editor:fold-selection': -> @foldSelectedLines()
- 'editor:fold-at-indent-level-1': -> @foldAllAtIndentLevel(0)
- 'editor:fold-at-indent-level-2': -> @foldAllAtIndentLevel(1)
- 'editor:fold-at-indent-level-3': -> @foldAllAtIndentLevel(2)
- 'editor:fold-at-indent-level-4': -> @foldAllAtIndentLevel(3)
- 'editor:fold-at-indent-level-5': -> @foldAllAtIndentLevel(4)
- 'editor:fold-at-indent-level-6': -> @foldAllAtIndentLevel(5)
- 'editor:fold-at-indent-level-7': -> @foldAllAtIndentLevel(6)
- 'editor:fold-at-indent-level-8': -> @foldAllAtIndentLevel(7)
- 'editor:fold-at-indent-level-9': -> @foldAllAtIndentLevel(8)
- 'editor:log-cursor-scope': -> @logCursorScope()
- 'editor:copy-path': -> @copyPathToClipboard()
- 'editor:toggle-indent-guide': -> atom.config.set('editor.showIndentGuide', not atom.config.get('editor.showIndentGuide'))
- 'editor:toggle-line-numbers': -> atom.config.set('editor.showLineNumbers', not atom.config.get('editor.showLineNumbers'))
- 'editor:scroll-to-cursor': -> @scrollToCursorPosition()
-)
-
-atom.commands.add 'atom-text-editor:not([mini])', stopEventPropagationAndGroupUndo(
- 'editor:indent': -> @indent()
- 'editor:auto-indent': -> @autoIndentSelectedRows()
- 'editor:indent-selected-rows': -> @indentSelectedRows()
- 'editor:outdent-selected-rows': -> @outdentSelectedRows()
- 'editor:newline': -> @insertNewline()
- 'editor:newline-below': -> @insertNewlineBelow()
- 'editor:newline-above': -> @insertNewlineAbove()
- 'editor:toggle-line-comments': -> @toggleLineCommentsInSelection()
- 'editor:checkout-head-revision': -> @checkoutHeadRevision()
- 'editor:move-line-up': -> @moveLineUp()
- 'editor:move-line-down': -> @moveLineDown()
- 'editor:duplicate-lines': -> @duplicateLines()
- 'editor:join-lines': -> @joinLines()
-)
-
module.exports = TextEditorElement = document.registerElement 'atom-text-editor', prototype: TextEditorElement.prototype
diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee
index 184d597cc..2e1d73c56 100644
--- a/src/text-editor-presenter.coffee
+++ b/src/text-editor-presenter.coffee
@@ -13,7 +13,7 @@ class TextEditorPresenter
minimumReflowInterval: 200
constructor: (params) ->
- {@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @scrollColumn, @scrollRow, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params
+ {@model, @config, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @scrollColumn, @scrollRow, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params
{horizontalScrollbarHeight, verticalScrollbarWidth} = params
{@lineHeight, @baseCharacterWidth, @backgroundColor, @gutterBackgroundColor, @tileSize} = params
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params
@@ -22,6 +22,8 @@ class TextEditorPresenter
@gutterWidth ?= 0
@tileSize ?= 6
+ @realScrollTop = @scrollTop
+ @realScrollLeft = @scrollLeft
@disposables = new CompositeDisposable
@emitter = new Emitter
@visibleHighlights = {}
@@ -223,9 +225,9 @@ class TextEditorPresenter
observeConfig: ->
configParams = {scope: @model.getRootScopeDescriptor()}
- @scrollPastEnd = atom.config.get('editor.scrollPastEnd', configParams)
- @showLineNumbers = atom.config.get('editor.showLineNumbers', configParams)
- @showIndentGuide = atom.config.get('editor.showIndentGuide', configParams)
+ @scrollPastEnd = @config.get('editor.scrollPastEnd', configParams)
+ @showLineNumbers = @config.get('editor.showLineNumbers', configParams)
+ @showIndentGuide = @config.get('editor.showIndentGuide', configParams)
if @configDisposables?
@configDisposables?.dispose()
@@ -234,19 +236,19 @@ class TextEditorPresenter
@configDisposables = new CompositeDisposable
@disposables.add(@configDisposables)
- @configDisposables.add atom.config.onDidChange 'editor.showIndentGuide', configParams, ({newValue}) =>
+ @configDisposables.add @config.onDidChange 'editor.showIndentGuide', configParams, ({newValue}) =>
@showIndentGuide = newValue
@shouldUpdateContentState = true
@emitDidUpdateState()
- @configDisposables.add atom.config.onDidChange 'editor.scrollPastEnd', configParams, ({newValue}) =>
+ @configDisposables.add @config.onDidChange 'editor.scrollPastEnd', configParams, ({newValue}) =>
@scrollPastEnd = newValue
@shouldUpdateVerticalScrollState = true
@shouldUpdateScrollbarsState = true
@updateScrollHeight()
@emitDidUpdateState()
- @configDisposables.add atom.config.onDidChange 'editor.showLineNumbers', configParams, ({newValue}) =>
+ @configDisposables.add @config.onDidChange 'editor.showLineNumbers', configParams, ({newValue}) =>
@showLineNumbers = newValue
@shouldUpdateLineNumberGutterState = true
@shouldUpdateGutterOrderState = true
@@ -775,7 +777,7 @@ class TextEditorPresenter
updateScrollTop: (scrollTop) ->
scrollTop = @constrainScrollTop(scrollTop)
- if scrollTop isnt @scrollTop and not Number.isNaN(scrollTop)
+ if scrollTop isnt @realScrollTop and not Number.isNaN(scrollTop)
@realScrollTop = scrollTop
@scrollTop = Math.round(scrollTop)
@scrollRow = Math.round(@scrollTop / @lineHeight)
@@ -792,7 +794,7 @@ class TextEditorPresenter
updateScrollLeft: (scrollLeft) ->
scrollLeft = @constrainScrollLeft(scrollLeft)
- if scrollLeft isnt @scrollLeft and not Number.isNaN(scrollLeft)
+ if scrollLeft isnt @realScrollLeft and not Number.isNaN(scrollLeft)
@realScrollLeft = scrollLeft
@scrollLeft = Math.round(scrollLeft)
@scrollColumn = Math.round(@scrollLeft / @baseCharacterWidth)
@@ -980,6 +982,13 @@ class TextEditorPresenter
getScrollWidth: ->
@scrollWidth
+ getMaxScrollTop: ->
+ scrollHeight = @getScrollHeight()
+ clientHeight = @getClientHeight()
+ return 0 unless scrollHeight? and clientHeight?
+
+ scrollHeight - clientHeight
+
setHorizontalScrollbarHeight: (horizontalScrollbarHeight) ->
unless @measuredHorizontalScrollbarHeight is horizontalScrollbarHeight
oldHorizontalScrollbarHeight = @measuredHorizontalScrollbarHeight
@@ -1120,10 +1129,13 @@ class TextEditorPresenter
@mouseWheelScreenRow = screenRow
@didStartScrolling()
- setBaseCharacterWidth: (baseCharacterWidth) ->
- unless @baseCharacterWidth is baseCharacterWidth
+ setBaseCharacterWidth: (baseCharacterWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth) ->
+ unless @baseCharacterWidth is baseCharacterWidth and @doubleWidthCharWidth is doubleWidthCharWidth and @halfWidthCharWidth is halfWidthCharWidth and koreanCharWidth is @koreanCharWidth
@baseCharacterWidth = baseCharacterWidth
- @model.setDefaultCharWidth(baseCharacterWidth)
+ @doubleWidthCharWidth = doubleWidthCharWidth
+ @halfWidthCharWidth = halfWidthCharWidth
+ @koreanCharWidth = koreanCharWidth
+ @model.setDefaultCharWidth(baseCharacterWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth)
@characterWidthsChanged()
characterWidthsChanged: ->
diff --git a/src/text-editor.coffee b/src/text-editor.coffee
index 4d517ac99..30d5694c4 100644
--- a/src/text-editor.coffee
+++ b/src/text-editor.coffee
@@ -54,10 +54,7 @@ GutterContainer = require './gutter-container'
# soft wraps and folds to ensure your code interacts with them correctly.
module.exports =
class TextEditor extends Model
- atom.deserializers.add(this)
-
callDisplayBufferCreatedHook: false
- registerEditor: false
buffer: null
languageMode: null
cursors: null
@@ -67,9 +64,9 @@ class TextEditor extends Model
selectionFlashDuration: 500
gutterContainer: null
- @deserialize: (state) ->
+ @deserialize: (state, atomEnvironment) ->
try
- displayBuffer = DisplayBuffer.deserialize(state.displayBuffer)
+ displayBuffer = DisplayBuffer.deserialize(state.displayBuffer, atomEnvironment)
catch error
if error.syscall is 'read'
return # Error reading the file, don't deserialize an editor for it
@@ -77,19 +74,46 @@ class TextEditor extends Model
throw error
state.displayBuffer = displayBuffer
- state.registerEditor = true
+ state.config = atomEnvironment.config
+ state.notificationManager = atomEnvironment.notifications
+ state.packageManager = atomEnvironment.packages
+ state.clipboard = atomEnvironment.clipboard
+ state.viewRegistry = atomEnvironment.views
+ state.grammarRegistry = atomEnvironment.grammars
+ state.project = atomEnvironment.project
+ state.assert = atomEnvironment.assert.bind(atomEnvironment)
+ state.applicationDelegate = atomEnvironment.applicationDelegate
new this(state)
- constructor: ({@softTabs, @scrollRow, @scrollColumn, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode}={}) ->
+ constructor: (params={}) ->
super
+ {
+ @softTabs, @scrollRow, @scrollColumn, initialLine, initialColumn, tabLength,
+ softWrapped, @displayBuffer, buffer, suppressCursorCreation, @mini, @placeholderText,
+ lineNumberGutterVisible, largeFileMode, @config, @notificationManager, @packageManager,
+ @clipboard, @viewRegistry, @grammarRegistry, @project, @assert, @applicationDelegate
+ } = params
+
+ throw new Error("Must pass a config parameter when constructing TextEditors") unless @config?
+ throw new Error("Must pass a notificationManager parameter when constructing TextEditors") unless @notificationManager?
+ throw new Error("Must pass a packageManager parameter when constructing TextEditors") unless @packageManager?
+ throw new Error("Must pass a clipboard parameter when constructing TextEditors") unless @clipboard?
+ throw new Error("Must pass a viewRegistry parameter when constructing TextEditors") unless @viewRegistry?
+ throw new Error("Must pass a grammarRegistry parameter when constructing TextEditors") unless @grammarRegistry?
+ throw new Error("Must pass a project parameter when constructing TextEditors") unless @project?
+ throw new Error("Must pass an assert parameter when constructing TextEditors") unless @assert?
+
@emitter = new Emitter
@disposables = new CompositeDisposable
@cursors = []
@selections = []
buffer ?= new TextBuffer
- @displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrapped, ignoreInvisibles: @mini, largeFileMode})
+ @displayBuffer ?= new DisplayBuffer({
+ buffer, tabLength, softWrapped, ignoreInvisibles: @mini, largeFileMode,
+ @config, @assert, @grammarRegistry, @packageManager
+ })
@buffer = @displayBuffer.buffer
for marker in @findMarkers(@getSelectionMarkerAttributes())
@@ -105,9 +129,9 @@ class TextEditor extends Model
initialColumn = Math.max(parseInt(initialColumn) or 0, 0)
@addCursorAtBufferPosition([initialLine, initialColumn])
- @languageMode = new LanguageMode(this)
+ @languageMode = new LanguageMode(this, @config)
- @setEncoding(atom.config.get('core.fileEncoding', scope: @getRootScopeDescriptor()))
+ @setEncoding(@config.get('core.fileEncoding', scope: @getRootScopeDescriptor()))
@gutterContainer = new GutterContainer(this)
@lineNumberGutter = @gutterContainer.addGutter
@@ -115,8 +139,6 @@ class TextEditor extends Model
priority: 0
visible: lineNumberGutterVisible
- atom.workspace?.editorAdded(this) if registerEditor
-
serialize: ->
deserializer: 'TextEditor'
id: @id
@@ -128,8 +150,8 @@ class TextEditor extends Model
subscribeToBuffer: ->
@buffer.retain()
@disposables.add @buffer.onDidChangePath =>
- unless atom.project.getPaths().length > 0
- atom.project.setPaths([path.dirname(@getPath())])
+ unless @project.getPaths().length > 0
+ @project.setPaths([path.dirname(@getPath())])
@emitter.emit 'did-change-title', @getTitle()
@emitter.emit 'did-change-path', @getPath()
@disposables.add @buffer.onDidChangeEncoding =>
@@ -148,7 +170,7 @@ class TextEditor extends Model
subscribeToTabTypeConfig: ->
@tabTypeSubscription?.dispose()
- @tabTypeSubscription = atom.config.observe 'editor.tabType', scope: @getRootScopeDescriptor(), =>
+ @tabTypeSubscription = @config.observe 'editor.tabType', scope: @getRootScopeDescriptor(), =>
@softTabs = @shouldUseSoftTabs(defaultValue: @softTabs)
destroyed: ->
@@ -429,12 +451,12 @@ class TextEditor extends Model
onDidChangeScrollTop: (callback) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::onDidChangeScrollTop instead.")
- atom.views.getView(this).onDidChangeScrollTop(callback)
+ @viewRegistry.getView(this).onDidChangeScrollTop(callback)
onDidChangeScrollLeft: (callback) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::onDidChangeScrollLeft instead.")
- atom.views.getView(this).onDidChangeScrollLeft(callback)
+ @viewRegistry.getView(this).onDidChangeScrollLeft(callback)
onDidRequestAutoscroll: (callback) ->
@displayBuffer.onDidRequestAutoscroll(callback)
@@ -456,7 +478,11 @@ class TextEditor extends Model
copy: ->
displayBuffer = @displayBuffer.copy()
softTabs = @getSoftTabs()
- newEditor = new TextEditor({@buffer, displayBuffer, @tabLength, softTabs, suppressCursorCreation: true, registerEditor: true})
+ newEditor = new TextEditor({
+ @buffer, displayBuffer, @tabLength, softTabs, suppressCursorCreation: true,
+ @config, @notificationManager, @packageManager, @clipboard, @viewRegistry,
+ @grammarRegistry, @project, @assert, @applicationDelegate
+ })
for marker in @findMarkers(editorId: @id)
marker.copy(editorId: newEditor.id, preserveFolds: true)
newEditor
@@ -557,7 +583,7 @@ class TextEditor extends Model
getLongTitle: ->
if sessionPath = @getPath()
fileName = path.basename(sessionPath)
- directory = atom.project.relativize(path.dirname(sessionPath))
+ directory = @project.relativize(path.dirname(sessionPath))
directory = if directory.length > 0 then directory else path.basename(path.dirname(sessionPath))
"#{fileName} - #{directory}"
else
@@ -585,7 +611,7 @@ class TextEditor extends Model
# Copies the current file path to the native clipboard.
copyPathToClipboard: ->
if filePath = @getPath()
- atom.clipboard.write(filePath)
+ @clipboard.write(filePath)
###
Section: File Operations
@@ -594,14 +620,14 @@ class TextEditor extends Model
# Essential: Saves the editor's text buffer.
#
# See {TextBuffer::save} for more details.
- save: -> @buffer.save(backup: atom.config.get('editor.backUpBeforeSaving'))
+ save: -> @buffer.save(backup: @config.get('editor.backUpBeforeSaving'))
# Essential: Saves the editor's text buffer as the given path.
#
# See {TextBuffer::saveAs} for more details.
#
# * `filePath` A {String} path.
- saveAs: (filePath) -> @buffer.saveAs(filePath, backup: atom.config.get('editor.backUpBeforeSaving'))
+ saveAs: (filePath) -> @buffer.saveAs(filePath, backup: @config.get('editor.backUpBeforeSaving'))
# Determine whether the user should be prompted to save before closing
# this editor.
@@ -617,9 +643,20 @@ class TextEditor extends Model
checkoutHeadRevision: ->
if filePath = this.getPath()
- atom.project.repositoryForDirectory(new Directory(path.dirname(filePath)))
- .then (repository) =>
- repository?.checkoutHeadForEditor(this)
+ checkoutHead = =>
+ @project.repositoryForDirectory(new Directory(path.dirname(filePath)))
+ .then (repository) =>
+ repository?.checkoutHeadForEditor(this)
+
+ if @config.get('editor.confirmCheckoutHeadRevision')
+ @applicationDelegate.confirm
+ message: 'Confirm Checkout HEAD Revision'
+ detailedMessage: "Are you sure you want to discard all changes to \"#{path.basename(filePath)}\" since the last Git commit?"
+ buttons:
+ OK: checkoutHead
+ Cancel: null
+ else
+ checkoutHead()
else
Promise.resolve(false)
@@ -748,7 +785,7 @@ class TextEditor extends Model
return false unless @emitWillInsertTextEvent(text)
groupingInterval = if options.groupUndo
- atom.config.get('editor.undoGroupingInterval')
+ @config.get('editor.undoGroupingInterval')
else
0
@@ -1742,7 +1779,7 @@ class TextEditor extends Model
# Add a cursor based on the given {Marker}.
addCursor: (marker) ->
- cursor = new Cursor(editor: this, marker: marker)
+ cursor = new Cursor(editor: this, marker: marker, config: @config)
@cursors.push(cursor)
@decorateMarker(marker, type: 'line-number', class: 'cursor-line')
@decorateMarker(marker, type: 'line-number', class: 'cursor-line-no-selection', onlyHead: true, onlyEmpty: true)
@@ -2225,7 +2262,7 @@ class TextEditor extends Model
unless marker.getProperties().preserveFolds
@destroyFoldsContainingBufferRange(marker.getBufferRange())
cursor = @addCursor(marker)
- selection = new Selection(_.extend({editor: this, marker, cursor}, options))
+ selection = new Selection(_.extend({editor: this, marker, cursor, @clipboard}, options))
@selections.push(selection)
selectionBufferRange = selection.getBufferRange()
@mergeIntersectingSelections(preserveFolds: marker.getProperties().preserveFolds)
@@ -2382,10 +2419,10 @@ class TextEditor extends Model
#
# Returns a {Boolean}
shouldUseSoftTabs: ({defaultValue}) ->
- tabType = atom.config.get('editor.tabType', scope: @getRootScopeDescriptor())
+ tabType = @config.get('editor.tabType', scope: @getRootScopeDescriptor())
switch tabType
when 'auto'
- @usesSoftTabs() ? defaultValue ? atom.config.get('editor.softTabs') ? true
+ @usesSoftTabs() ? defaultValue ? @config.get('editor.softTabs') ? true
when 'hard'
false
when 'soft'
@@ -2561,7 +2598,7 @@ class TextEditor extends Model
list = list.map (item) -> "* #{item}"
content = "Scopes at Cursor\n#{list.join('\n')}"
- atom.notifications.addInfo(content, dismissable: true)
+ @notificationManager.addInfo(content, dismissable: true)
# {Delegates to: DisplayBuffer.tokenForBufferPosition}
tokenForBufferPosition: (bufferPosition) -> @displayBuffer.tokenForBufferPosition(bufferPosition)
@@ -2613,7 +2650,7 @@ class TextEditor extends Model
#
# * `options` (optional) See {Selection::insertText}.
pasteText: (options={}) ->
- {text: clipboardText, metadata} = atom.clipboard.readWithMetadata()
+ {text: clipboardText, metadata} = @clipboard.readWithMetadata()
return false unless @emitWillInsertTextEvent(clipboardText)
metadata ?= {}
@@ -2866,24 +2903,24 @@ class TextEditor extends Model
scrollToTop: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::scrollToTop instead.")
- atom.views.getView(this).scrollToTop()
+ @viewRegistry.getView(this).scrollToTop()
scrollToBottom: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::scrollToTop instead.")
- atom.views.getView(this).scrollToBottom()
+ @viewRegistry.getView(this).scrollToBottom()
scrollToScreenRange: (screenRange, options) -> @displayBuffer.scrollToScreenRange(screenRange, options)
getHorizontalScrollbarHeight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getHorizontalScrollbarHeight instead.")
- atom.views.getView(this).getHorizontalScrollbarHeight()
+ @viewRegistry.getView(this).getHorizontalScrollbarHeight()
getVerticalScrollbarWidth: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getVerticalScrollbarWidth instead.")
- atom.views.getView(this).getVerticalScrollbarWidth()
+ @viewRegistry.getView(this).getVerticalScrollbarWidth()
pageUp: ->
@moveUp(@getRowsPerPage())
@@ -2908,10 +2945,10 @@ class TextEditor extends Model
###
shouldAutoIndent: ->
- atom.config.get("editor.autoIndent", scope: @getRootScopeDescriptor())
+ @config.get("editor.autoIndent", scope: @getRootScopeDescriptor())
shouldAutoIndentOnPaste: ->
- atom.config.get("editor.autoIndentOnPaste", scope: @getRootScopeDescriptor())
+ @config.get("editor.autoIndentOnPaste", scope: @getRootScopeDescriptor())
###
Section: Event Handlers
@@ -2950,19 +2987,19 @@ class TextEditor extends Model
getFirstVisibleScreenRow: ->
deprecate("This is now a view method. Call TextEditorElement::getFirstVisibleScreenRow instead.")
- atom.views.getView(this).getVisibleRowRange()[0]
+ @viewRegistry.getView(this).getVisibleRowRange()[0]
getLastVisibleScreenRow: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getLastVisibleScreenRow instead.")
- atom.views.getView(this).getVisibleRowRange()[1]
+ @viewRegistry.getView(this).getVisibleRowRange()[1]
pixelPositionForBufferPosition: (bufferPosition) ->
Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForBufferPosition` instead")
- atom.views.getView(this).pixelPositionForBufferPosition(bufferPosition)
+ @viewRegistry.getView(this).pixelPositionForBufferPosition(bufferPosition)
pixelPositionForScreenPosition: (screenPosition) ->
Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForScreenPosition` instead")
- atom.views.getView(this).pixelPositionForScreenPosition(screenPosition)
+ @viewRegistry.getView(this).pixelPositionForScreenPosition(screenPosition)
getSelectionMarkerAttributes: ->
{type: 'selection', editorId: @id, invalidate: 'never', maintainHistory: true}
@@ -2976,15 +3013,22 @@ class TextEditor extends Model
getLineHeightInPixels: -> @displayBuffer.getLineHeightInPixels()
setLineHeightInPixels: (lineHeightInPixels) -> @displayBuffer.setLineHeightInPixels(lineHeightInPixels)
+ getKoreanCharWidth: -> @displayBuffer.getKoreanCharWidth()
+
+ getHalfWidthCharWidth: -> @displayBuffer.getHalfWidthCharWidth()
+
+ getDoubleWidthCharWidth: -> @displayBuffer.getDoubleWidthCharWidth()
+
getDefaultCharWidth: -> @displayBuffer.getDefaultCharWidth()
- setDefaultCharWidth: (defaultCharWidth) -> @displayBuffer.setDefaultCharWidth(defaultCharWidth)
+ setDefaultCharWidth: (defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth) ->
+ @displayBuffer.setDefaultCharWidth(defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth)
setHeight: (height, reentrant=false) ->
if reentrant
@displayBuffer.setHeight(height)
else
Grim.deprecate("This is now a view method. Call TextEditorElement::setHeight instead.")
- atom.views.getView(this).setHeight(height)
+ @viewRegistry.getView(this).setHeight(height)
getHeight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getHeight instead.")
@@ -2997,7 +3041,7 @@ class TextEditor extends Model
@displayBuffer.setWidth(width)
else
Grim.deprecate("This is now a view method. Call TextEditorElement::setWidth instead.")
- atom.views.getView(this).setWidth(width)
+ @viewRegistry.getView(this).setWidth(width)
getWidth: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getWidth instead.")
@@ -3012,77 +3056,82 @@ class TextEditor extends Model
getScrollTop: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollTop instead.")
- atom.views.getView(this).getScrollTop()
+ @viewRegistry.getView(this).getScrollTop()
setScrollTop: (scrollTop) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollTop instead.")
- atom.views.getView(this).setScrollTop(scrollTop)
+ @viewRegistry.getView(this).setScrollTop(scrollTop)
getScrollBottom: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollBottom instead.")
- atom.views.getView(this).getScrollBottom()
+ @viewRegistry.getView(this).getScrollBottom()
setScrollBottom: (scrollBottom) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollBottom instead.")
- atom.views.getView(this).setScrollBottom(scrollBottom)
+ @viewRegistry.getView(this).setScrollBottom(scrollBottom)
getScrollLeft: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollLeft instead.")
- atom.views.getView(this).getScrollLeft()
+ @viewRegistry.getView(this).getScrollLeft()
setScrollLeft: (scrollLeft) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollLeft instead.")
- atom.views.getView(this).setScrollLeft(scrollLeft)
+ @viewRegistry.getView(this).setScrollLeft(scrollLeft)
getScrollRight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollRight instead.")
- atom.views.getView(this).getScrollRight()
+ @viewRegistry.getView(this).getScrollRight()
setScrollRight: (scrollRight) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollRight instead.")
- atom.views.getView(this).setScrollRight(scrollRight)
+ @viewRegistry.getView(this).setScrollRight(scrollRight)
getScrollHeight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollHeight instead.")
- atom.views.getView(this).getScrollHeight()
+ @viewRegistry.getView(this).getScrollHeight()
getScrollWidth: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollWidth instead.")
- atom.views.getView(this).getScrollWidth()
+ @viewRegistry.getView(this).getScrollWidth()
+
+ getMaxScrollTop: ->
+ Grim.deprecate("This is now a view method. Call TextEditorElement::getMaxScrollTop instead.")
+
+ @viewRegistry.getView(this).getMaxScrollTop()
getVisibleRowRange: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getVisibleRowRange instead.")
- atom.views.getView(this).getVisibleRowRange()
+ @viewRegistry.getView(this).getVisibleRowRange()
intersectsVisibleRowRange: (startRow, endRow) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::intersectsVisibleRowRange instead.")
- atom.views.getView(this).intersectsVisibleRowRange(startRow, endRow)
+ @viewRegistry.getView(this).intersectsVisibleRowRange(startRow, endRow)
selectionIntersectsVisibleRowRange: (selection) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::selectionIntersectsVisibleRowRange instead.")
- atom.views.getView(this).selectionIntersectsVisibleRowRange(selection)
+ @viewRegistry.getView(this).selectionIntersectsVisibleRowRange(selection)
screenPositionForPixelPosition: (pixelPosition) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::screenPositionForPixelPosition instead.")
- atom.views.getView(this).screenPositionForPixelPosition(pixelPosition)
+ @viewRegistry.getView(this).screenPositionForPixelPosition(pixelPosition)
pixelRectForScreenRange: (screenRange) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::pixelRectForScreenRange instead.")
- atom.views.getView(this).pixelRectForScreenRange(screenRange)
+ @viewRegistry.getView(this).pixelRectForScreenRange(screenRange)
###
Section: Utility
diff --git a/src/text-utils.coffee b/src/text-utils.coffee
index 37955dcd6..82bed4da5 100644
--- a/src/text-utils.coffee
+++ b/src/text-utils.coffee
@@ -57,6 +57,38 @@ isPairedCharacter = (string, index=0) ->
isVariationSequence(charCodeA, charCodeB) or
isCombinedCharacter(charCodeA, charCodeB)
+isJapaneseCharacter = (charCode) ->
+ 0x3000 <= charCode <= 0x30FF
+
+isCjkUnifiedIdeograph = (charCode) ->
+ 0x4E00 <= charCode <= 0x9FAF
+
+isFullWidthForm = (charCode) ->
+ 0xFF01 <= charCode <= 0xFF5E or
+ 0xFFE0 <= charCode <= 0xFFE6
+
+isDoubleWidthCharacter = (character) ->
+ charCode = character.charCodeAt(0)
+
+ isJapaneseCharacter(charCode) or
+ isCjkUnifiedIdeograph(charCode) or
+ isFullWidthForm(charCode)
+
+isHalfWidthCharacter = (character) ->
+ charCode = character.charCodeAt(0)
+
+ 0xFF65 <= charCode <= 0xFFDC or
+ 0xFFE8 <= charCode <= 0xFFEE
+
+isKoreanCharacter = (character) ->
+ charCode = character.charCodeAt(0)
+
+ 0xAC00 <= charCode <= 0xD7A3 or
+ 0x1100 <= charCode <= 0x11FF or
+ 0x3130 <= charCode <= 0x318F or
+ 0xA960 <= charCode <= 0xA97F or
+ 0xD7B0 <= charCode <= 0xD7FF
+
# Does the given string contain at least surrogate pair, variation sequence,
# or combined character?
#
@@ -70,4 +102,4 @@ hasPairedCharacter = (string) ->
index++
false
-module.exports = {isPairedCharacter, hasPairedCharacter}
+module.exports = {isPairedCharacter, hasPairedCharacter, isDoubleWidthCharacter, isHalfWidthCharacter, isKoreanCharacter}
diff --git a/src/theme-manager.coffee b/src/theme-manager.coffee
index 0d329cea7..649478841 100644
--- a/src/theme-manager.coffee
+++ b/src/theme-manager.coffee
@@ -9,12 +9,14 @@ fs = require 'fs-plus'
# An instance of this class is always available as the `atom.themes` global.
module.exports =
class ThemeManager
- constructor: ({@packageManager, @resourcePath, @configDirPath, @safeMode}) ->
+ constructor: ({@packageManager, @resourcePath, @configDirPath, @safeMode, @config, @styleManager, @notificationManager, @viewRegistry}) ->
@emitter = new Emitter
@styleSheetDisposablesBySourcePath = {}
@lessCache = null
@initialLoadComplete = false
@packageManager.registerPackageActivator(this, ['theme'])
+ @packageManager.onDidActivateInitialPackages =>
+ @onDidChangeActiveThemes => @packageManager.reloadActivePackageStyleSheets()
###
Section: Event Subscription
@@ -66,21 +68,21 @@ class ThemeManager
###
warnForNonExistentThemes: ->
- themeNames = atom.config.get('core.themes') ? []
+ themeNames = @config.get('core.themes') ? []
themeNames = [themeNames] unless _.isArray(themeNames)
for themeName in themeNames
- unless themeName and typeof themeName is 'string' and atom.packages.resolvePackagePath(themeName)
+ unless themeName and typeof themeName is 'string' and @packageManager.resolvePackagePath(themeName)
console.warn("Enabled theme '#{themeName}' is not installed.")
# Public: Get the enabled theme names from the config.
#
# Returns an array of theme names in the order that they should be activated.
getEnabledThemeNames: ->
- themeNames = atom.config.get('core.themes') ? []
+ themeNames = @config.get('core.themes') ? []
themeNames = [themeNames] unless _.isArray(themeNames)
- themeNames = themeNames.filter (themeName) ->
+ themeNames = themeNames.filter (themeName) =>
if themeName and typeof themeName is 'string'
- return true if atom.packages.resolvePackagePath(themeName)
+ return true if @packageManager.resolvePackagePath(themeName)
false
# Use a built-in syntax and UI theme any time the configured themes are not
@@ -139,7 +141,7 @@ class ThemeManager
loadUserStylesheet: ->
@unwatchUserStylesheet()
- userStylesheetPath = atom.styles.getUserStyleSheetPath()
+ userStylesheetPath = @styleManager.getUserStyleSheetPath()
return unless fs.isFileSync(userStylesheetPath)
try
@@ -158,14 +160,14 @@ class ThemeManager
[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)
try
userStylesheetContents = @loadStylesheet(userStylesheetPath, true)
catch
return
- @userStyleSheetDisposable = atom.styles.addStyleSheet(userStylesheetContents, sourcePath: userStylesheetPath, priority: 2)
+ @userStyleSheetDisposable = @styleManager.addStyleSheet(userStylesheetContents, sourcePath: userStylesheetPath, priority: 2)
loadBaseStylesheets: ->
@requireStylesheet('../static/bootstrap')
@@ -221,22 +223,22 @@ class ThemeManager
message = "Error loading Less stylesheet: `#{lessStylesheetPath}`"
detail = error.message
- atom.notifications.addError(message, {detail, dismissable: true})
+ @notificationManager.addError(message, {detail, dismissable: true})
throw error
removeStylesheet: (stylesheetPath) ->
@styleSheetDisposablesBySourcePath[stylesheetPath]?.dispose()
applyStylesheet: (path, text) ->
- @styleSheetDisposablesBySourcePath[path] = atom.styles.addStyleSheet(text, sourcePath: path)
+ @styleSheetDisposablesBySourcePath[path] = @styleManager.addStyleSheet(text, sourcePath: path)
stringToId: (string) ->
string.replace(/\\/g, '/')
activateThemes: ->
new Promise (resolve) =>
- # atom.config.observe runs the callback once, then on subsequent changes.
- atom.config.observe 'core.themes', =>
+ # @config.observe runs the callback once, then on subsequent changes.
+ @config.observe 'core.themes', =>
@deactivateThemes()
@warnForNonExistentThemes()
@@ -268,13 +270,13 @@ class ThemeManager
isInitialLoadComplete: -> @initialLoadComplete
addActiveThemeClasses: ->
- if workspaceElement = atom.views.getView(atom.workspace)
+ if workspaceElement = @viewRegistry.getView(@workspace)
for pack in @getActiveThemes()
workspaceElement.classList.add("theme-#{pack.name}")
return
removeActiveThemeClasses: ->
- workspaceElement = atom.views.getView(atom.workspace)
+ workspaceElement = @viewRegistry.getView(@workspace)
for pack in @getActiveThemes()
workspaceElement.classList.remove("theme-#{pack.name}")
return
diff --git a/src/theme-package.coffee b/src/theme-package.coffee
index 6d0a88052..084728869 100644
--- a/src/theme-package.coffee
+++ b/src/theme-package.coffee
@@ -7,10 +7,10 @@ class ThemePackage extends Package
getStyleSheetPriority: -> 1
enable: ->
- atom.config.unshiftAtKeyPath('core.themes', @name)
+ @config.unshiftAtKeyPath('core.themes', @name)
disable: ->
- atom.config.removeAtKeyPath('core.themes', @name)
+ @config.removeAtKeyPath('core.themes', @name)
load: ->
@loadTime = 0
diff --git a/src/token-iterator.coffee b/src/token-iterator.coffee
index de874d499..8f0fe202f 100644
--- a/src/token-iterator.coffee
+++ b/src/token-iterator.coffee
@@ -1,19 +1,18 @@
{SoftTab, HardTab, PairedCharacter, SoftWrapIndent} = require './special-token-symbols'
+{isDoubleWidthCharacter, isHalfWidthCharacter, isKoreanCharacter} = require './text-utils'
module.exports =
class TokenIterator
- constructor: (line) ->
- @reset(line) if line?
+ constructor: ({@grammarRegistry}, line, enableScopes) ->
+ @reset(line, enableScopes) if line?
- reset: (@line) ->
+ reset: (@line, @enableScopes=true) ->
@index = null
@bufferStart = @line.startBufferColumn
@bufferEnd = @bufferStart
@screenStart = 0
@screenEnd = 0
- @scopes = @line.openScopes.map (id) -> atom.grammars.scopeForId(id)
- @scopeStarts = @scopes.slice()
- @scopeEnds = []
+ @resetScopes() if @enableScopes
this
next: ->
@@ -21,26 +20,16 @@ class TokenIterator
if @index?
@index++
- @scopeEnds.length = 0
- @scopeStarts.length = 0
@bufferStart = @bufferEnd
@screenStart = @screenEnd
+ @clearScopeStartsAndEnds() if @enableScopes
else
@index = 0
while @index < tags.length
tag = tags[@index]
if tag < 0
- scope = atom.grammars.scopeForId(tag)
- if tag % 2 is 0
- if @scopeStarts[@scopeStarts.length - 1] is scope
- @scopeStarts.pop()
- else
- @scopeEnds.push(scope)
- @scopes.pop()
- else
- @scopeStarts.push(scope)
- @scopes.push(scope)
+ @handleScopeForTag(tag) if @enableScopes
@index++
else
if @isHardTab()
@@ -52,10 +41,33 @@ class TokenIterator
else
@screenEnd = @screenStart + tag
@bufferEnd = @bufferStart + tag
+
+ @text = @line.text.substring(@screenStart, @screenEnd)
return true
false
+ resetScopes: ->
+ @scopes = @line.openScopes.map (id) => @grammarRegistry.scopeForId(id)
+ @scopeStarts = @scopes.slice()
+ @scopeEnds = []
+
+ clearScopeStartsAndEnds: ->
+ @scopeEnds.length = 0
+ @scopeStarts.length = 0
+
+ handleScopeForTag: (tag) ->
+ scope = @grammarRegistry.scopeForId(tag)
+ if tag % 2 is 0
+ if @scopeStarts[@scopeStarts.length - 1] is scope
+ @scopeStarts.pop()
+ else
+ @scopeEnds.push(scope)
+ @scopes.pop()
+ else
+ @scopeStarts.push(scope)
+ @scopes.push(scope)
+
getBufferStart: -> @bufferStart
getBufferEnd: -> @bufferEnd
@@ -67,8 +79,7 @@ class TokenIterator
getScopes: -> @scopes
- getText: ->
- @line.text.substring(@screenStart, @screenEnd)
+ getText: -> @text
isSoftTab: ->
@line.specialTokens[@index] is SoftTab
@@ -82,5 +93,14 @@ class TokenIterator
isPairedCharacter: ->
@line.specialTokens[@index] is PairedCharacter
+ hasDoubleWidthCharacterAt: (charIndex) ->
+ isDoubleWidthCharacter(@getText()[charIndex])
+
+ hasHalfWidthCharacterAt: (charIndex) ->
+ isHalfWidthCharacter(@getText()[charIndex])
+
+ hasKoreanCharacterAt: (charIndex) ->
+ isKoreanCharacter(@getText()[charIndex])
+
isAtomic: ->
@isSoftTab() or @isHardTab() or @isSoftWrapIndentation() or @isPairedCharacter()
diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee
index 8721b0547..2df29a31c 100644
--- a/src/tokenized-buffer.coffee
+++ b/src/tokenized-buffer.coffee
@@ -21,17 +21,26 @@ class TokenizedBuffer extends Model
configSettings: null
changeCount: 0
- @deserialize: (state) ->
- state.buffer = atom.project.bufferForPathSync(state.bufferPath)
+ @deserialize: (state, atomEnvironment) ->
+ state.buffer = atomEnvironment.project.bufferForPathSync(state.bufferPath)
+ state.config = atomEnvironment.config
+ state.grammarRegistry = atomEnvironment.grammars
+ state.packageManager = atomEnvironment.packages
+ state.assert = atomEnvironment.assert
new this(state)
- constructor: ({@buffer, @tabLength, @ignoreInvisibles, @largeFileMode}) ->
+ constructor: (params) ->
+ {
+ @buffer, @tabLength, @ignoreInvisibles, @largeFileMode, @config,
+ @grammarRegistry, @packageManager, @assert
+ } = params
+
@emitter = new Emitter
@disposables = new CompositeDisposable
- @tokenIterator = new TokenIterator
+ @tokenIterator = new TokenIterator({@grammarRegistry})
- @disposables.add atom.grammars.onDidAddGrammar(@grammarAddedOrUpdated)
- @disposables.add atom.grammars.onDidUpdateGrammar(@grammarAddedOrUpdated)
+ @disposables.add @grammarRegistry.onDidAddGrammar(@grammarAddedOrUpdated)
+ @disposables.add @grammarRegistry.onDidUpdateGrammar(@grammarAddedOrUpdated)
@disposables.add @buffer.preemptDidChange (e) => @handleBufferChange(e)
@disposables.add @buffer.onDidChangePath (@bufferPath) => @reloadGrammar()
@@ -65,7 +74,7 @@ class TokenizedBuffer extends Model
if grammar.injectionSelector?
@retokenizeLines() if @hasTokenForSelector(grammar.injectionSelector)
else
- newScore = atom.grammars.getGrammarScore(grammar, @buffer.getPath(), @getGrammarSelectionContent())
+ newScore = @grammarRegistry.getGrammarScore(grammar, @buffer.getPath(), @getGrammarSelectionContent())
@setGrammar(grammar, newScore) if newScore > @currentGrammarScore
setGrammar: (grammar, score) ->
@@ -73,7 +82,7 @@ class TokenizedBuffer extends Model
@grammar = grammar
@rootScopeDescriptor = new ScopeDescriptor(scopes: [@grammar.scopeName])
- @currentGrammarScore = score ? atom.grammars.getGrammarScore(grammar, @buffer.getPath(), @getGrammarSelectionContent())
+ @currentGrammarScore = score ? @grammarRegistry.getGrammarScore(grammar, @buffer.getPath(), @getGrammarSelectionContent())
@grammarUpdateDisposable?.dispose()
@grammarUpdateDisposable = @grammar.onDidUpdate => @retokenizeLines()
@@ -81,33 +90,33 @@ class TokenizedBuffer extends Model
scopeOptions = {scope: @rootScopeDescriptor}
@configSettings =
- tabLength: atom.config.get('editor.tabLength', scopeOptions)
- invisibles: atom.config.get('editor.invisibles', scopeOptions)
- showInvisibles: atom.config.get('editor.showInvisibles', scopeOptions)
+ tabLength: @config.get('editor.tabLength', scopeOptions)
+ invisibles: @config.get('editor.invisibles', scopeOptions)
+ showInvisibles: @config.get('editor.showInvisibles', scopeOptions)
if @configSubscriptions?
@configSubscriptions.dispose()
@disposables.remove(@configSubscriptions)
@configSubscriptions = new CompositeDisposable
- @configSubscriptions.add atom.config.onDidChange 'editor.tabLength', scopeOptions, ({newValue}) =>
+ @configSubscriptions.add @config.onDidChange 'editor.tabLength', scopeOptions, ({newValue}) =>
@configSettings.tabLength = newValue
@retokenizeLines()
['invisibles', 'showInvisibles'].forEach (key) =>
- @configSubscriptions.add atom.config.onDidChange "editor.#{key}", scopeOptions, ({newValue}) =>
+ @configSubscriptions.add @config.onDidChange "editor.#{key}", scopeOptions, ({newValue}) =>
oldInvisibles = @getInvisiblesToShow()
@configSettings[key] = newValue
@retokenizeLines() unless _.isEqual(@getInvisiblesToShow(), oldInvisibles)
@disposables.add(@configSubscriptions)
@retokenizeLines()
- atom.packages.triggerActivationHook("#{grammar.packageName}:grammar-used")
+ @packageManager.triggerActivationHook("#{grammar.packageName}:grammar-used")
@emitter.emit 'did-change-grammar', grammar
getGrammarSelectionContent: ->
@buffer.getTextInRange([[0, 0], [10, 0]])
reloadGrammar: ->
- if grammar = atom.grammars.selectGrammar(@buffer.getPath(), @getGrammarSelectionContent())
+ if grammar = @grammarRegistry.selectGrammar(@buffer.getPath(), @getGrammarSelectionContent())
@setGrammar(grammar)
else
throw new Error("No grammar found for path: #{path}")
@@ -155,7 +164,7 @@ class TokenizedBuffer extends Model
tokenizeNextChunk: ->
# Short circuit null grammar which can just use the placeholder tokens
- if @grammar is atom.grammars.nullGrammar and @firstInvalidRow()?
+ if @grammar is @grammarRegistry.nullGrammar and @firstInvalidRow()?
@invalidRows = []
@markTokenizationComplete()
return
@@ -291,7 +300,7 @@ class TokenizedBuffer extends Model
# undefined. This should paper over the problem but we want to figure out
# what is happening:
tokenizedLine = @tokenizedLineForRow(row)
- atom.assert tokenizedLine?, "TokenizedLine is undefined", (error) =>
+ @assert tokenizedLine?, "TokenizedLine is undefined", (error) =>
error.metadata = {
row: row
rowCount: @tokenizedLines.length
@@ -391,7 +400,7 @@ class TokenizedBuffer extends Model
loop
break if scopes.pop() is matchingStartTag
if scopes.length is 0
- atom.assert false, "Encountered an unmatched scope end tag.", (error) =>
+ @assert false, "Encountered an unmatched scope end tag.", (error) =>
error.metadata = {
grammarScopeName: @grammar.scopeName
unmatchedEndTag: @grammar.scopeForId(tag)
@@ -472,13 +481,13 @@ class TokenizedBuffer extends Model
position = Point.fromObject(position)
{openScopes, tags} = @tokenizedLineForRow(position.row)
- scopes = openScopes.map (tag) -> atom.grammars.scopeForId(tag)
+ scopes = openScopes.map (tag) => @grammarRegistry.scopeForId(tag)
startColumn = 0
for tag, tokenIndex in tags
if tag < 0
if tag % 2 is -1
- scopes.push(atom.grammars.scopeForId(tag))
+ scopes.push(@grammarRegistry.scopeForId(tag))
else
scopes.pop()
else
@@ -498,7 +507,7 @@ class TokenizedBuffer extends Model
if tag % 2 is -1
startScopes.pop()
else
- startScopes.push(atom.grammars.scopeForId(tag))
+ startScopes.push(@grammarRegistry.scopeForId(tag))
else
break unless selectorMatchesAnyScope(selector, startScopes)
startColumn -= tag
@@ -508,7 +517,7 @@ class TokenizedBuffer extends Model
tag = tags[endTokenIndex]
if tag < 0
if tag % 2 is -1
- endScopes.push(atom.grammars.scopeForId(tag))
+ endScopes.push(@grammarRegistry.scopeForId(tag))
else
endScopes.pop()
else
diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee
index bf234b6d3..2a27d3f12 100644
--- a/src/tokenized-line.coffee
+++ b/src/tokenized-line.coffee
@@ -184,7 +184,7 @@ class TokenizedLine
@lineIsWhitespaceOnly = true
@firstTrailingWhitespaceIndex = 0
- getTokenIterator: -> @tokenIterator.reset(this)
+ getTokenIterator: -> @tokenIterator.reset(this, arguments...)
Object.defineProperty @prototype, 'tokens', get: ->
iterator = @getTokenIterator()
diff --git a/src/tooltip-manager.coffee b/src/tooltip-manager.coffee
index 21b8b07c6..247437535 100644
--- a/src/tooltip-manager.coffee
+++ b/src/tooltip-manager.coffee
@@ -54,6 +54,8 @@ class TooltipManager
placement: 'auto top'
viewportPadding: 2
+ constructor: ({@keymapManager}) ->
+
# Essential: Add a tooltip to the given element.
#
# * `target` An `HTMLElement`
@@ -81,7 +83,7 @@ class TooltipManager
{keyBindingCommand, keyBindingTarget} = options
if keyBindingCommand?
- bindings = atom.keymaps.findKeyBindings(command: keyBindingCommand, target: keyBindingTarget)
+ bindings = @keymapManager.findKeyBindings(command: keyBindingCommand, target: keyBindingTarget)
keystroke = getKeystroke(bindings)
if options.title? and keystroke?
options.title += " " + getKeystroke(bindings)
diff --git a/src/view-registry.coffee b/src/view-registry.coffee
index 6af7bd024..3a46aa87a 100644
--- a/src/view-registry.coffee
+++ b/src/view-registry.coffee
@@ -49,15 +49,15 @@ class ViewRegistry
debouncedPerformDocumentPoll: null
minimumPollInterval: 200
- constructor: ->
+ constructor: (@atomEnvironment) ->
+ @observer = new MutationObserver(@requestDocumentPoll)
+ @clear()
+
+ clear: ->
@views = new WeakMap
@providers = []
- @documentWriters = []
- @documentReaders = []
- @documentPollers = []
-
- @observer = new MutationObserver(@requestDocumentPoll)
@debouncedPerformDocumentPoll = _.throttle(@performDocumentPoll, @minimumPollInterval).bind(this)
+ @clearDocumentRequests()
# Essential: Add a provider that will be used to construct views in the
# workspace's view layer based on model objects in its model layer.
@@ -159,7 +159,7 @@ class ViewRegistry
else if object?.jquery
object[0]
else if provider = @findProvider(object)
- element = provider.createView?(object)
+ element = provider.createView?(object, @atomEnvironment)
unless element?
element = new provider.viewConstructor
element.initialize?(object) ? element.setModel?(object)
diff --git a/src/window-bootstrap.coffee b/src/window-bootstrap.coffee
deleted file mode 100644
index 886ba26dc..000000000
--- a/src/window-bootstrap.coffee
+++ /dev/null
@@ -1,13 +0,0 @@
-# Like sands through the hourglass, so are the days of our lives.
-require './window'
-
-Atom = require './atom'
-window.atom = Atom.loadOrCreate('editor')
-atom.initialize()
-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)
diff --git a/src/window-event-handler.coffee b/src/window-event-handler.coffee
index 53c2aa2e2..c786f7e9c 100644
--- a/src/window-event-handler.coffee
+++ b/src/window-event-handler.coffee
@@ -1,45 +1,38 @@
path = require 'path'
{Disposable, CompositeDisposable} = require 'event-kit'
-ipc = require 'ipc'
-shell = require 'shell'
fs = require 'fs-plus'
listen = require './delegated-listener'
-# Handles low-level events related to the window.
+# Handles low-level events related to the @window.
module.exports =
class WindowEventHandler
- constructor: ->
+ constructor: ({@atomEnvironment, @applicationDelegate, @window, @document}) ->
@reloadRequested = false
@subscriptions = new CompositeDisposable
- @on(ipc, 'message', @handleIPCMessage)
- @on(ipc, 'command', @handleIPCCommand)
- @on(ipc, 'context-command', @handleIPCContextCommand)
+ @previousOnbeforeunloadHandler = @window.onbeforeunload
+ @window.onbeforeunload = @handleWindowBeforeunload
+ @addEventListener(@window, 'focus', @handleWindowFocus)
+ @addEventListener(@window, 'blur', @handleWindowBlur)
- @previousOnbeforeunloadHandler = window.onbeforeunload
- window.onbeforeunload = @handleWindowBeforeunload
- @addEventListener(window, 'focus', @handleWindowFocus)
- @addEventListener(window, 'blur', @handleWindowBlur)
- @addEventListener(window, 'unload', @handleWindowUnload)
+ @addEventListener(@document, 'keydown', @handleDocumentKeydown)
+ @addEventListener(@document, 'drop', @handleDocumentDrop)
+ @addEventListener(@document, 'dragover', @handleDocumentDragover)
+ @addEventListener(@document, 'contextmenu', @handleDocumentContextmenu)
+ @subscriptions.add listen(@document, 'click', 'a', @handleLinkClick)
+ @subscriptions.add listen(@document, 'submit', 'form', @handleFormSubmit)
- @addEventListener(document, 'keydown', @handleDocumentKeydown)
- @addEventListener(document, 'drop', @handleDocumentDrop)
- @addEventListener(document, 'dragover', @handleDocumentDragover)
- @addEventListener(document, 'contextmenu', @handleDocumentContextmenu)
- @subscriptions.add listen(document, 'click', 'a', @handleLinkClick)
- @subscriptions.add listen(document, 'submit', 'form', @handleFormSubmit)
-
- @subscriptions.add atom.commands.add window,
+ @subscriptions.add @atomEnvironment.commands.add @window,
'window:toggle-full-screen': @handleWindowToggleFullScreen
'window:close': @handleWindowClose
'window:reload': @handleWindowReload
'window:toggle-dev-tools': @handleWindowToggleDevTools
if process.platform in ['win32', 'linux']
- @subscriptions.add atom.commands.add window,
+ @subscriptions.add @atomEnvironment.commands.add @window,
'window:toggle-menu-bar': @handleWindowToggleMenuBar
- @subscriptions.add atom.commands.add document,
+ @subscriptions.add @atomEnvironment.commands.add @document,
'core:focus-next': @handleFocusNext
'core:focus-previous': @handleFocusPrevious
@@ -49,9 +42,9 @@ class WindowEventHandler
# `.native-key-bindings` class.
handleNativeKeybindings: ->
bindCommandToAction = (command, action) =>
- @addEventListener document, command, (event) ->
+ @addEventListener @document, command, (event) =>
if event.target.webkitMatchesSelector('.native-key-bindings')
- atom.getCurrentWindow().webContents[action]()
+ @applicationDelegate.getCurrentWindow().webContents[action]()
bindCommandToAction('core:copy', 'copy')
bindCommandToAction('core:paste', 'paste')
@@ -61,7 +54,7 @@ class WindowEventHandler
bindCommandToAction('core:cut', 'cut')
unsubscribe: ->
- window.onbeforeunload = @previousOnbeforeunloadHandler
+ @window.onbeforeunload = @previousOnbeforeunloadHandler
@subscriptions.dispose()
on: (target, eventName, handler) ->
@@ -74,8 +67,8 @@ class WindowEventHandler
target.addEventListener(eventName, handler)
@subscriptions.add(new Disposable(-> target.removeEventListener(eventName, handler)))
- handleDocumentKeydown: (event) ->
- atom.keymaps.handleKeyboardEvent(event)
+ handleDocumentKeydown: (event) =>
+ @atomEnvironment.keymaps.handleKeyboardEvent(event)
event.stopImmediatePropagation()
handleDrop: (event) ->
@@ -88,14 +81,14 @@ class WindowEventHandler
event.dataTransfer.dropEffect = 'none'
eachTabIndexedElement: (callback) ->
- for element in document.querySelectorAll('[tabindex]')
+ for element in @document.querySelectorAll('[tabindex]')
continue if element.disabled
continue unless element.tabIndex >= 0
callback(element, element.tabIndex)
return
handleFocusNext: =>
- focusedTabIndex = document.activeElement.tabIndex ? -Infinity
+ focusedTabIndex = @document.activeElement.tabIndex ? -Infinity
nextElement = null
nextTabIndex = Infinity
@@ -116,7 +109,7 @@ class WindowEventHandler
lowestElement.focus()
handleFocusPrevious: =>
- focusedTabIndex = document.activeElement.tabIndex ? Infinity
+ focusedTabIndex = @document.activeElement.tabIndex ? Infinity
previousElement = null
previousTabIndex = -Infinity
@@ -136,91 +129,61 @@ class WindowEventHandler
else if highestElement?
highestElement.focus()
- handleIPCMessage: (message, detail) ->
- switch message
- when 'open-locations'
- needsProjectPaths = atom.project?.getPaths().length is 0
-
- for {pathToOpen, initialLine, initialColumn} in detail
- if pathToOpen? and needsProjectPaths
- if fs.existsSync(pathToOpen)
- atom.project.addPath(pathToOpen)
- else if fs.existsSync(path.dirname(pathToOpen))
- atom.project.addPath(path.dirname(pathToOpen))
- else
- atom.project.addPath(pathToOpen)
-
- unless fs.isDirectorySync(pathToOpen)
- atom.workspace?.open(pathToOpen, {initialLine, initialColumn})
- return
- when 'update-available'
- atom.updateAvailable(detail)
-
- handleIPCCommand: (command, args...) ->
- activeElement = document.activeElement
- # Use the workspace element view if body has focus
- if activeElement is document.body and workspaceElement = atom.views.getView(atom.workspace)
- activeElement = workspaceElement
-
- atom.commands.dispatch(activeElement, command, args[0])
-
- handleIPCContextCommand: (command, args...) ->
- atom.commands.dispatch(atom.contextMenu.activeElement, command, args)
-
handleWindowFocus: ->
- document.body.classList.remove('is-blurred')
+ @document.body.classList.remove('is-blurred')
- handleWindowBlur: ->
- document.body.classList.add('is-blurred')
- atom.storeDefaultWindowDimensions()
+ handleWindowBlur: =>
+ @document.body.classList.add('is-blurred')
+ @atomEnvironment.storeDefaultWindowDimensions()
handleWindowBeforeunload: =>
- confirmed = atom.workspace?.confirmClose(windowCloseRequested: true)
- atom.hide() if confirmed and not @reloadRequested and atom.getCurrentWindow().isWebViewFocused()
+ confirmed = @atomEnvironment.workspace?.confirmClose(windowCloseRequested: true)
+ if confirmed and not @reloadRequested and not @atomEnvironment.inSpecMode() and @atomEnvironment.getCurrentWindow().isWebViewFocused()
+ @atomEnvironment.hide()
@reloadRequested = false
- atom.storeDefaultWindowDimensions()
- atom.storeWindowDimensions()
+ @atomEnvironment.storeDefaultWindowDimensions()
+ @atomEnvironment.storeWindowDimensions()
if confirmed
- atom.unloadEditorWindow()
+ @atomEnvironment.unloadEditorWindow()
else
- ipc.send('cancel-window-close')
+ @applicationDelegate.didCancelWindowUnload()
confirmed
- handleWindowUnload: ->
- atom.removeEditorWindow()
+ handleWindowUnload: =>
+ @atomEnvironment.destroy()
- handleWindowToggleFullScreen: ->
- atom.toggleFullScreen()
+ handleWindowToggleFullScreen: =>
+ @atomEnvironment.toggleFullScreen()
- handleWindowClose: ->
- atom.close()
+ handleWindowClose: =>
+ @atomEnvironment.close()
- handleWindowReload: ->
+ handleWindowReload: =>
@reloadRequested = true
- atom.reload()
+ @atomEnvironment.reload()
- handleWindowToggleDevTools: ->
- atom.toggleDevTools()
+ handleWindowToggleDevTools: =>
+ @atomEnvironment.toggleDevTools()
- handleWindowToggleMenuBar: ->
- atom.config.set('core.autoHideMenuBar', not atom.config.get('core.autoHideMenuBar'))
+ handleWindowToggleMenuBar: =>
+ @atomEnvironment.config.set('core.autoHideMenuBar', not @atomEnvironment.config.get('core.autoHideMenuBar'))
- if atom.config.get('core.autoHideMenuBar')
+ if @atomEnvironment.config.get('core.autoHideMenuBar')
detail = "To toggle, press the Alt key or execute the window:toggle-menu-bar command"
- atom.notifications.addInfo('Menu bar hidden', {detail})
+ @atomEnvironment.notifications.addInfo('Menu bar hidden', {detail})
- handleLinkClick: (event) ->
+ handleLinkClick: (event) =>
event.preventDefault()
- location = event.currentTarget?.getAttribute('href')
- if location and location[0] isnt '#' and /^https?:\/\//.test(location)
- shell.openExternal(location)
+ uri = event.currentTarget?.getAttribute('href')
+ if uri and uri[0] isnt '#' and /^https?:\/\//.test(uri)
+ @applicationDelegate.openExternal(uri)
handleFormSubmit: (event) ->
# Prevent form submits from changing the current window's URL
event.preventDefault()
- handleDocumentContextmenu: (event) ->
+ handleDocumentContextmenu: (event) =>
event.preventDefault()
- atom.contextMenu.showForEvent(event)
+ @atomEnvironment.contextMenu.showForEvent(event)
diff --git a/src/window-load-settings-helpers.coffee b/src/window-load-settings-helpers.coffee
new file mode 100644
index 000000000..59ee2f382
--- /dev/null
+++ b/src/window-load-settings-helpers.coffee
@@ -0,0 +1,20 @@
+remote = require 'remote'
+_ = require 'underscore-plus'
+
+windowLoadSettings = null
+
+exports.getWindowLoadSettings = ->
+ windowLoadSettings ?= JSON.parse(window.decodeURIComponent(window.location.hash.substr(1)))
+ clone = _.deepClone(windowLoadSettings)
+
+ # The windowLoadSettings.windowState could be large, request it only when needed.
+ clone.__defineGetter__ 'windowState', ->
+ remote.getCurrentWindow().loadSettings.windowState
+ clone.__defineSetter__ 'windowState', (value) ->
+ remote.getCurrentWindow().loadSettings.windowState = value
+
+ clone
+
+exports.setWindowLoadSettings = (settings) ->
+ windowLoadSettings = settings
+ location.hash = encodeURIComponent(JSON.stringify(settings))
diff --git a/src/workspace-element.coffee b/src/workspace-element.coffee
index 378969481..fb4b19dc3 100644
--- a/src/workspace-element.coffee
+++ b/src/workspace-element.coffee
@@ -8,18 +8,11 @@ module.exports =
class WorkspaceElement extends HTMLElement
globalTextEditorStyleSheet: null
- createdCallback: ->
- @subscriptions = new CompositeDisposable
- @initializeContent()
- @observeScrollbarStyle()
- @observeTextEditorFontConfig()
-
attachedCallback: ->
@focus()
detachedCallback: ->
@subscriptions.dispose()
- @model.destroy()
initializeContent: ->
@classList.add 'workspace'
@@ -46,31 +39,42 @@ class WorkspaceElement extends HTMLElement
observeTextEditorFontConfig: ->
@updateGlobalTextEditorStyleSheet()
- @subscriptions.add atom.config.onDidChange 'editor.fontSize', @updateGlobalTextEditorStyleSheet.bind(this)
- @subscriptions.add atom.config.onDidChange 'editor.fontFamily', @updateGlobalTextEditorStyleSheet.bind(this)
- @subscriptions.add atom.config.onDidChange 'editor.lineHeight', @updateGlobalTextEditorStyleSheet.bind(this)
+ @subscriptions.add @config.onDidChange 'editor.fontSize', @updateGlobalTextEditorStyleSheet.bind(this)
+ @subscriptions.add @config.onDidChange 'editor.fontFamily', @updateGlobalTextEditorStyleSheet.bind(this)
+ @subscriptions.add @config.onDidChange 'editor.lineHeight', @updateGlobalTextEditorStyleSheet.bind(this)
updateGlobalTextEditorStyleSheet: ->
styleSheetSource = """
atom-text-editor {
- font-size: #{atom.config.get('editor.fontSize')}px;
- font-family: #{atom.config.get('editor.fontFamily')};
- line-height: #{atom.config.get('editor.lineHeight')};
+ font-size: #{@config.get('editor.fontSize')}px;
+ font-family: #{@config.get('editor.fontFamily')};
+ line-height: #{@config.get('editor.lineHeight')};
}
"""
- atom.styles.addStyleSheet(styleSheetSource, sourcePath: 'global-text-editor-styles')
+ @styles.addStyleSheet(styleSheetSource, sourcePath: 'global-text-editor-styles')
- initialize: (@model) ->
- @paneContainer = atom.views.getView(@model.paneContainer)
+ initialize: (@model, {@views, @workspace, @project, @config, @styles}) ->
+ throw new Error("Must pass a views parameter when initializing WorskpaceElements") unless @views?
+ throw new Error("Must pass a workspace parameter when initializing WorskpaceElements") unless @workspace?
+ throw new Error("Must pass a project parameter when initializing WorskpaceElements") unless @project?
+ throw new Error("Must pass a config parameter when initializing WorskpaceElements") unless @config?
+ throw new Error("Must pass a styles parameter when initializing WorskpaceElements") unless @styles?
+
+ @subscriptions = new CompositeDisposable
+ @initializeContent()
+ @observeScrollbarStyle()
+ @observeTextEditorFontConfig()
+
+ @paneContainer = @views.getView(@model.paneContainer)
@verticalAxis.appendChild(@paneContainer)
@addEventListener 'focus', @handleFocus.bind(this)
@panelContainers =
- top: atom.views.getView(@model.panelContainers.top)
- left: atom.views.getView(@model.panelContainers.left)
- right: atom.views.getView(@model.panelContainers.right)
- bottom: atom.views.getView(@model.panelContainers.bottom)
- modal: atom.views.getView(@model.panelContainers.modal)
+ top: @views.getView(@model.panelContainers.top)
+ left: @views.getView(@model.panelContainers.left)
+ right: @views.getView(@model.panelContainers.right)
+ bottom: @views.getView(@model.panelContainers.bottom)
+ modal: @views.getView(@model.panelContainers.modal)
@horizontalAxis.insertBefore(@panelContainers.left, @verticalAxis)
@horizontalAxis.appendChild(@panelContainers.right)
@@ -96,59 +100,10 @@ class WorkspaceElement extends HTMLElement
focusPaneViewOnRight: -> @paneContainer.focusPaneViewOnRight()
runPackageSpecs: ->
- if activePath = atom.workspace.getActivePaneItem()?.getPath?()
- [projectPath] = atom.project.relativizePath(activePath)
+ if activePath = @workspace.getActivePaneItem()?.getPath?()
+ [projectPath] = @project.relativizePath(activePath)
else
- [projectPath] = atom.project.getPaths()
+ [projectPath] = @project.getPaths()
ipc.send('run-package-specs', path.join(projectPath, 'spec')) if projectPath
-atom.commands.add 'atom-workspace',
- 'window:increase-font-size': -> @getModel().increaseFontSize()
- 'window:decrease-font-size': -> @getModel().decreaseFontSize()
- 'window:reset-font-size': -> @getModel().resetFontSize()
- 'application:about': -> ipc.send('command', 'application:about')
- 'application:run-all-specs': -> ipc.send('command', 'application:run-all-specs')
- 'application:show-preferences': -> ipc.send('command', 'application:show-settings')
- 'application:show-settings': -> ipc.send('command', 'application:show-settings')
- 'application:quit': -> ipc.send('command', 'application:quit')
- 'application:hide': -> ipc.send('command', 'application:hide')
- 'application:hide-other-applications': -> ipc.send('command', 'application:hide-other-applications')
- 'application:install-update': -> ipc.send('command', 'application:install-update')
- 'application:unhide-all-applications': -> ipc.send('command', 'application:unhide-all-applications')
- 'application:new-window': -> ipc.send('command', 'application:new-window')
- 'application:new-file': -> ipc.send('command', 'application:new-file')
- 'application:open': -> ipc.send('command', 'application:open')
- 'application:open-file': -> ipc.send('command', 'application:open-file')
- 'application:open-folder': -> ipc.send('command', 'application:open-folder')
- 'application:open-dev': -> ipc.send('command', 'application:open-dev')
- 'application:open-safe': -> ipc.send('command', 'application:open-safe')
- 'application:add-project-folder': -> atom.addProjectFolder()
- 'application:minimize': -> ipc.send('command', 'application:minimize')
- 'application:zoom': -> ipc.send('command', 'application:zoom')
- 'application:bring-all-windows-to-front': -> ipc.send('command', 'application:bring-all-windows-to-front')
- 'application:open-your-config': -> ipc.send('command', 'application:open-your-config')
- 'application:open-your-init-script': -> ipc.send('command', 'application:open-your-init-script')
- 'application:open-your-keymap': -> ipc.send('command', 'application:open-your-keymap')
- 'application:open-your-snippets': -> ipc.send('command', 'application:open-your-snippets')
- 'application:open-your-stylesheet': -> ipc.send('command', 'application:open-your-stylesheet')
- 'application:open-license': -> @getModel().openLicense()
- 'window:run-package-specs': -> @runPackageSpecs()
- 'window:focus-next-pane': -> @getModel().activateNextPane()
- 'window:focus-previous-pane': -> @getModel().activatePreviousPane()
- 'window:focus-pane-above': -> @focusPaneViewAbove()
- 'window:focus-pane-below': -> @focusPaneViewBelow()
- 'window:focus-pane-on-left': -> @focusPaneViewOnLeft()
- 'window:focus-pane-on-right': -> @focusPaneViewOnRight()
- 'window:save-all': -> @getModel().saveAll()
- 'window:toggle-invisibles': -> atom.config.set("editor.showInvisibles", not atom.config.get("editor.showInvisibles"))
- 'window:log-deprecation-warnings': -> Grim.logDeprecations()
- 'window:toggle-auto-indent': -> atom.config.set("editor.autoIndent", not atom.config.get("editor.autoIndent"))
- 'pane:reopen-closed-item': -> @getModel().reopenItem()
- 'core:close': -> @getModel().destroyActivePaneItemOrEmptyPane()
- 'core:save': -> @getModel().saveActivePaneItem()
- 'core:save-as': -> @getModel().saveActivePaneItemAs()
-
-if process.platform is 'darwin'
- atom.commands.add 'atom-workspace', 'window:install-shell-commands', -> @getModel().installShellCommands()
-
module.exports = WorkspaceElement = document.registerElement 'atom-workspace', prototype: WorkspaceElement.prototype
diff --git a/src/workspace.coffee b/src/workspace.coffee
index dbd781bd3..b66445bbf 100644
--- a/src/workspace.coffee
+++ b/src/workspace.coffee
@@ -9,10 +9,7 @@ TextEditor = require './text-editor'
PaneContainer = require './pane-container'
Pane = require './pane'
Panel = require './panel'
-PanelElement = require './panel-element'
PanelContainer = require './panel-container'
-PanelContainerElement = require './panel-container-element'
-WorkspaceElement = require './workspace-element'
Task = require './task'
# Essential: Represents the state of the user interface for the entire window.
@@ -26,36 +23,24 @@ Task = require './task'
#
module.exports =
class Workspace extends Model
- atom.deserializers.add(this)
-
- @deserialize: (state) ->
- return unless state?
-
- for packageName in state.packagesWithActiveGrammars ? []
- atom.packages.getLoadedPackage(packageName)?.loadGrammarsSync()
-
- state.paneContainer = PaneContainer.deserialize(state.paneContainer)
- new this(state)
-
constructor: (params) ->
super
- @paneContainer = params?.paneContainer
- @fullScreen = params?.fullScreen ? false
- @destroyedItemURIs = params?.destroyedItemURIs ? []
+ {
+ @packageManager, @config, @project, @grammarRegistry, @notificationManager,
+ @clipboard, @viewRegistry, @grammarRegistry, @applicationDelegate, @assert,
+ @deserializerManager
+ } = params
@emitter = new Emitter
@openers = []
+ @destroyedItemURIs = []
- @paneContainer ?= new PaneContainer()
+ @paneContainer = new PaneContainer({@config, @applicationDelegate, @notificationManager, @deserializerManager})
@paneContainer.onDidDestroyPaneItem(@didDestroyPaneItem)
- @directorySearchers = []
@defaultDirectorySearcher = new DefaultDirectorySearcher()
- atom.packages.serviceHub.consume(
- 'atom.directory-searcher',
- '^0.1.0',
- (provider) => @directorySearchers.unshift(provider))
+ @consumeServices(@packageManager)
@panelContainers =
top: new PanelContainer({location: 'top'})
@@ -64,69 +49,80 @@ class Workspace extends Model
bottom: new PanelContainer({location: 'bottom'})
modal: new PanelContainer({location: 'modal'})
+ @subscribeToEvents()
+
+ reset: (@packageManager) ->
+ @emitter.dispose()
+ @emitter = new Emitter
+
+ @paneContainer.destroy()
+ panelContainer.destroy() for panelContainer in @panelContainers
+
+ @paneContainer = new PaneContainer({@config, @applicationDelegate, @notificationManager, @deserializerManager})
+ @paneContainer.onDidDestroyPaneItem(@didDestroyPaneItem)
+
+ @panelContainers =
+ top: new PanelContainer({location: 'top'})
+ left: new PanelContainer({location: 'left'})
+ right: new PanelContainer({location: 'right'})
+ bottom: new PanelContainer({location: 'bottom'})
+ modal: new PanelContainer({location: 'modal'})
+
+ @originalFontSize = null
+ @openers = []
+ @destroyedItemURIs = []
+ @consumeServices(@packageManager)
+
+ subscribeToEvents: ->
@subscribeToActiveItem()
-
- @addOpener (filePath) ->
- switch filePath
- when 'atom://.atom/stylesheet'
- atom.project.open(atom.styles.getUserStyleSheetPath())
- when 'atom://.atom/keymap'
- atom.project.open(atom.keymaps.getUserKeymapPath())
- when 'atom://.atom/config'
- atom.project.open(atom.config.getUserConfigPath())
- when 'atom://.atom/init-script'
- atom.project.open(atom.getUserInitScriptPath())
-
- atom.views.addViewProvider Workspace, (model) ->
- new WorkspaceElement().initialize(model)
-
- atom.views.addViewProvider PanelContainer, (model) ->
- new PanelContainerElement().initialize(model)
-
- atom.views.addViewProvider Panel, (model) ->
- new PanelElement().initialize(model)
-
@subscribeToFontSize()
+ consumeServices: ({serviceHub}) ->
+ @directorySearchers = []
+ serviceHub.consume(
+ 'atom.directory-searcher',
+ '^0.1.0',
+ (provider) => @directorySearchers.unshift(provider))
+
# Called by the Serializable mixin during serialization.
serialize: ->
deserializer: 'Workspace'
paneContainer: @paneContainer.serialize()
- fullScreen: atom.isFullScreen()
packagesWithActiveGrammars: @getPackageNamesWithActiveGrammars()
+ destroyedItemURIs: @destroyedItemURIs.slice()
+
+ deserialize: (state, deserializerManager) ->
+ for packageName in state.packagesWithActiveGrammars ? []
+ @packageManager.getLoadedPackage(packageName)?.loadGrammarsSync()
+ if state.destroyedItemURIs?
+ @destroyedItemURIs = state.destroyedItemURIs
+ @paneContainer.deserialize(state.paneContainer, deserializerManager)
getPackageNamesWithActiveGrammars: ->
packageNames = []
- addGrammar = ({includedGrammarScopes, packageName}={}) ->
+ addGrammar = ({includedGrammarScopes, packageName}={}) =>
return unless packageName
# Prevent cycles
return if packageNames.indexOf(packageName) isnt -1
packageNames.push(packageName)
for scopeName in includedGrammarScopes ? []
- addGrammar(atom.grammars.grammarForScopeName(scopeName))
+ addGrammar(@grammarRegistry.grammarForScopeName(scopeName))
return
editors = @getTextEditors()
addGrammar(editor.getGrammar()) for editor in editors
if editors.length > 0
- for grammar in atom.grammars.getGrammars() when grammar.injectionSelector
+ for grammar in @grammarRegistry.getGrammars() when grammar.injectionSelector
addGrammar(grammar)
_.uniq(packageNames)
- editorAdded: (editor) ->
-
- installShellCommands: ->
- CommandInstaller = require('./command-installer')
- commandInstaller = new CommandInstaller(atom.getVersion())
- commandInstaller.installShellCommandsInteractively()
-
subscribeToActiveItem: ->
@updateWindowTitle()
@updateDocumentEdited()
- atom.project.onDidChangePaths @updateWindowTitle
+ @project.onDidChangePaths @updateWindowTitle
@observeActivePaneItem (item) =>
@updateWindowTitle()
@@ -156,7 +152,7 @@ class Workspace extends Model
# open.
updateWindowTitle: =>
appName = 'Atom'
- projectPaths = atom.project?.getPaths() ? []
+ projectPaths = @project.getPaths() ? []
if item = @getActivePaneItem()
itemPath = item.getPath?()
itemTitle = item.getTitle?()
@@ -167,19 +163,19 @@ class Workspace extends Model
if item? and projectPath?
document.title = "#{itemTitle} - #{projectPath} - #{appName}"
- atom.setRepresentedFilename(itemPath ? projectPath)
+ @applicationDelegate.setRepresentedFilename(itemPath ? projectPath)
else if projectPath?
document.title = "#{projectPath} - #{appName}"
- atom.setRepresentedFilename(projectPath)
+ @applicationDelegate.setRepresentedFilename(projectPath)
else
document.title = "#{itemTitle} - #{appName}"
- atom.setRepresentedFilename("")
+ @applicationDelegate.setRepresentedFilename("")
# On OS X, fades the application window's proxy icon when the current file
# has been modified.
updateDocumentEdited: =>
modified = @getActivePaneItem()?.isModified?() ? false
- atom.setDocumentEdited(modified)
+ @applicationDelegate.setWindowDocumentEdited(modified)
###
Section: Event Subscription
@@ -391,6 +387,8 @@ class Workspace extends Model
# item will be opened in the rightmost pane of the current active pane's row.
# * `activatePane` A {Boolean} indicating whether to call {Pane::activate} on
# containing pane. Defaults to `true`.
+ # * `activateItem` A {Boolean} indicating whether to call {Pane::activateItem}
+ # on containing pane. Defaults to `true`.
# * `searchAllPanes` A {Boolean}. If `true`, the workspace will attempt to
# activate an existing item for the given URI on any pane.
# If `false`, only the active pane will be searched for
@@ -400,7 +398,7 @@ class Workspace extends Model
open: (uri, options={}) ->
searchAllPanes = options.searchAllPanes
split = options.split
- uri = atom.project.resolvePath(uri)
+ uri = @project.resolvePath(uri)
pane = @paneContainer.paneForURI(uri) if searchAllPanes
pane ?= switch split
@@ -429,50 +427,51 @@ class Workspace extends Model
# initially. Defaults to `0`.
# * `activatePane` A {Boolean} indicating whether to call {Pane::activate} on
# the containing pane. Defaults to `true`.
+ # * `activateItem` A {Boolean} indicating whether to call {Pane::activateItem}
+ # on containing pane. Defaults to `true`.
openSync: (uri='', options={}) ->
{initialLine, initialColumn} = options
activatePane = options.activatePane ? true
+ activateItem = options.activateItem ? true
- uri = atom.project.resolvePath(uri)
+ uri = @project.resolvePath(uri)
item = @getActivePane().itemForURI(uri)
if uri
item ?= opener(uri, options) for opener in @getOpeners() when not item
- item ?= atom.project.openSync(uri, {initialLine, initialColumn})
+ item ?= @project.openSync(uri, {initialLine, initialColumn})
- @getActivePane().activateItem(item)
+ @getActivePane().activateItem(item) if activateItem
@itemOpened(item)
@getActivePane().activate() if activatePane
item
openURIInPane: (uri, pane, options={}) ->
activatePane = options.activatePane ? true
+ activateItem = options.activateItem ? true
if uri?
item = pane.itemForURI(uri)
item ?= opener(uri, options) for opener in @getOpeners() when not item
try
- item ?= atom.project.open(uri, options)
+ item ?= @openTextFile(uri, options)
catch error
switch error.code
when 'CANCELLED'
return Promise.resolve()
when 'EACCES'
- atom.notifications.addWarning("Permission denied '#{error.path}'")
+ @notificationManager.addWarning("Permission denied '#{error.path}'")
return Promise.resolve()
when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL'
- atom.notifications.addWarning("Unable to open '#{error.path ? uri}'", detail: error.message)
+ @notificationManager.addWarning("Unable to open '#{error.path ? uri}'", detail: error.message)
return Promise.resolve()
else
throw error
Promise.resolve(item)
.then (item) =>
- if not pane
- pane = new Pane(items: [item])
- @paneContainer.root = pane
@itemOpened(item)
- pane.activateItem(item)
+ pane.activateItem(item) if activateItem
pane.activate() if activatePane
initialLine = initialColumn = 0
@@ -487,6 +486,42 @@ class Workspace extends Model
@emitter.emit 'did-open', {uri, pane, item, index}
item
+ openTextFile: (uri, options) ->
+ filePath = @project.resolvePath(uri)
+
+ if filePath?
+ try
+ fs.closeSync(fs.openSync(filePath, 'r'))
+ catch error
+ # allow ENOENT errors to create an editor for paths that dont exist
+ throw error unless error.code is 'ENOENT'
+
+ fileSize = fs.getSizeSync(filePath)
+
+ largeFileMode = fileSize >= 2 * 1048576 # 2MB
+ if fileSize >= 20 * 1048576 # 20MB
+ choice = @applicationDelegate.confirm
+ message: 'Atom will be unresponsive during the loading of very large files.'
+ detailedMessage: "Do you still want to load this file?"
+ buttons: ["Proceed", "Cancel"]
+ if choice is 1
+ error = new Error
+ error.code = 'CANCELLED'
+ throw error
+
+ @project.bufferForPath(filePath, options).then (buffer) =>
+ @buildTextEditor(_.extend({buffer, largeFileMode}, options))
+
+ # Extended: Create a new text editor.
+ #
+ # Returns a {TextEditor}.
+ buildTextEditor: (params) ->
+ params = _.extend({
+ @config, @notificationManager, @packageManager, @clipboard, @viewRegistry,
+ @grammarRegistry, @project, @assert, @applicationDelegate
+ }, params)
+ new TextEditor(params)
+
# Public: Asynchronously reopens the last-closed item's URI if it hasn't already been
# reopened.
#
@@ -640,20 +675,20 @@ class Workspace extends Model
# Increase the editor font size by 1px.
increaseFontSize: ->
- atom.config.set("editor.fontSize", atom.config.get("editor.fontSize") + 1)
+ @config.set("editor.fontSize", @config.get("editor.fontSize") + 1)
# Decrease the editor font size by 1px.
decreaseFontSize: ->
- fontSize = atom.config.get("editor.fontSize")
- atom.config.set("editor.fontSize", fontSize - 1) if fontSize > 1
+ fontSize = @config.get("editor.fontSize")
+ @config.set("editor.fontSize", fontSize - 1) if fontSize > 1
# Restore to the window's original editor font size.
resetFontSize: ->
if @originalFontSize
- atom.config.set("editor.fontSize", @originalFontSize)
+ @config.set("editor.fontSize", @originalFontSize)
subscribeToFontSize: ->
- atom.config.onDidChange 'editor.fontSize', ({oldValue}) =>
+ @config.onDidChange 'editor.fontSize', ({oldValue}) =>
@originalFontSize ?= oldValue
# Removes the item's uri from the list of potential items to reopen.
@@ -831,7 +866,7 @@ class Workspace extends Model
# Find a searcher for every Directory in the project. Each searcher that is matched
# will be associated with an Array of Directory objects in the Map.
directoriesForSearcher = new Map()
- for directory in atom.project.getDirectories()
+ for directory in @project.getDirectories()
searcher = @defaultDirectorySearcher
for directorySearcher in @directorySearchers
if directorySearcher.canSearchDirectory(directory)
@@ -862,15 +897,15 @@ class Workspace extends Model
# Kick off all of the searches and unify them into one Promise.
allSearches = []
- directoriesForSearcher.forEach (directories, searcher) ->
+ directoriesForSearcher.forEach (directories, searcher) =>
searchOptions =
inclusions: options.paths or []
includeHidden: true
- excludeVcsIgnores: atom.config.get('core.excludeVcsIgnoredPaths')
- exclusions: atom.config.get('core.ignoredNames')
- follow: atom.config.get('core.followSymlinks')
- didMatch: (result) ->
- iterator(result) unless atom.project.isPathModified(result.filePath)
+ excludeVcsIgnores: @config.get('core.excludeVcsIgnoredPaths')
+ exclusions: @config.get('core.ignoredNames')
+ follow: @config.get('core.followSymlinks')
+ didMatch: (result) =>
+ iterator(result) unless @project.isPathModified(result.filePath)
didError: (error) ->
iterator(null, error)
didSearchPaths: (count) -> onPathsSearched(searcher, count)
@@ -878,9 +913,9 @@ class Workspace extends Model
allSearches.push(directorySearcher)
searchPromise = Promise.all(allSearches)
- for buffer in atom.project.getBuffers() when buffer.isModified()
+ for buffer in @project.getBuffers() when buffer.isModified()
filePath = buffer.getPath()
- continue unless atom.project.contains(filePath)
+ continue unless @project.contains(filePath)
matches = []
buffer.scan regex, (match) -> matches.push match
iterator {filePath, matches} if matches.length > 0
@@ -926,8 +961,8 @@ class Workspace extends Model
#
# Returns a `Promise`.
replace: (regex, replacementText, filePaths, iterator) ->
- new Promise (resolve, reject) ->
- openPaths = (buffer.getPath() for buffer in atom.project.getBuffers())
+ new Promise (resolve, reject) =>
+ openPaths = (buffer.getPath() for buffer in @project.getBuffers())
outOfProcessPaths = _.difference(filePaths, openPaths)
inProcessFinished = not openPaths.length
@@ -946,7 +981,7 @@ class Workspace extends Model
task.on 'replace:path-replaced', iterator
task.on 'replace:file-error', (error) -> iterator(null, error)
- for buffer in atom.project.getBuffers()
+ for buffer in @project.getBuffers()
continue unless buffer.getPath() in filePaths
replacements = buffer.replace(regex, replacementText, iterator)
iterator({filePath: buffer.getPath(), replacements}) if replacements
diff --git a/static/index.js b/static/index.js
index 11527e963..9bd1f7b20 100644
--- a/static/index.js
+++ b/static/index.js
@@ -76,7 +76,7 @@
setupVmCompatibility()
setupCsonCache(CompileCache.getCacheDirectory())
- require(loadSettings.bootstrapScript)
+ require(loadSettings.windowInitializationScript)
require('ipc').sendChannel('window-command', 'window:loaded')
}
@@ -168,7 +168,7 @@
var backgroundStylesheet = document.createElement('style')
backgroundStylesheet.type = 'text/css'
- backgroundStylesheet.innerText = 'html, body { background: ' + backgroundColor + '; }'
+ backgroundStylesheet.innerText = 'html, body { background: ' + backgroundColor + ' !important; }'
document.head.appendChild(backgroundStylesheet)
// Remove once the page loads