diff --git a/.travis.yml b/.travis.yml
index d5918dc8d..06416e8e0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,7 +4,7 @@ git:
matrix:
include:
- os: linux
- env: NODE_VERSION=4.4.7 DISPLAY=:99.0 CC=clang CXX=clang++ npm_config_clang=1
+ env: NODE_VERSION=6.9.4 DISPLAY=:99.0 CC=clang CXX=clang++ npm_config_clang=1
sudo: false
@@ -19,7 +19,9 @@ install:
- npm install -g npm
- script/build --create-debian-package --create-rpm-package --compress-artifacts
-script: script/test
+script:
+ - script/lint
+ - script/test
cache:
directories:
diff --git a/README.md b/README.md
index dc22ae866..74a5b4cee 100644
--- a/README.md
+++ b/README.md
@@ -93,7 +93,7 @@ repeat these steps to upgrade to future releases.
## Building
* [Linux](./docs/build-instructions/linux.md)
-* [macOS](./docs/build-instructions/macos.md)
+* [macOS](./docs/build-instructions/macOS.md)
* [FreeBSD](./docs/build-instructions/freebsd.md)
* [Windows](./docs/build-instructions/windows.md)
diff --git a/apm/package.json b/apm/package.json
index 732ab208a..ede2d1bd7 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.15.1"
+ "atom-package-manager": "1.15.3"
}
}
diff --git a/appveyor.yml b/appveyor.yml
index a9a0d7920..3d8c0b274 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -27,6 +27,7 @@ build_script:
- script\build.cmd --code-sign --create-windows-installer --compress-artifacts
test_script:
+ - script\lint.cmd
- script\test.cmd
deploy: off
diff --git a/benchmarks/text-editor-large-file-construction.bench.js b/benchmarks/text-editor-large-file-construction.bench.js
index 694729724..64c6f4d94 100644
--- a/benchmarks/text-editor-large-file-construction.bench.js
+++ b/benchmarks/text-editor-large-file-construction.bench.js
@@ -26,7 +26,7 @@ export default async function ({test}) {
console.log(text.length / 1024)
let t0 = window.performance.now()
- const buffer = new TextBuffer(text)
+ const buffer = new TextBuffer({text})
const editor = new TextEditor({buffer, largeFileMode: true})
atom.workspace.getActivePane().activateItem(editor)
let t1 = window.performance.now()
diff --git a/benchmarks/text-editor-long-lines.bench.js b/benchmarks/text-editor-long-lines.bench.js
new file mode 100644
index 000000000..a99220d4e
--- /dev/null
+++ b/benchmarks/text-editor-long-lines.bench.js
@@ -0,0 +1,97 @@
+/** @babel */
+
+import path from 'path'
+import fs from 'fs'
+import {TextEditor, TextBuffer} from 'atom'
+
+const SIZES_IN_KB = [
+ 512,
+ 1024,
+ 2048
+]
+const REPEATED_TEXT = fs.readFileSync(path.join(__dirname, '..', 'spec', 'fixtures', 'sample.js'), 'utf8').replace(/\n/g, '')
+const TEXT = REPEATED_TEXT.repeat(Math.ceil(SIZES_IN_KB[SIZES_IN_KB.length - 1] * 1024 / REPEATED_TEXT.length))
+
+export default async function ({test}) {
+ const data = []
+
+ const workspaceElement = atom.views.getView(atom.workspace)
+ document.body.appendChild(workspaceElement)
+
+ atom.packages.loadPackages()
+ await atom.packages.activate()
+
+ console.log(atom.getLoadSettings().resourcePath);
+
+ for (let pane of atom.workspace.getPanes()) {
+ pane.destroy()
+ }
+
+ for (const sizeInKB of SIZES_IN_KB) {
+ const text = TEXT.slice(0, sizeInKB * 1024)
+ console.log(text.length / 1024)
+
+ let t0 = window.performance.now()
+ const buffer = new TextBuffer({text})
+ const editor = new TextEditor({buffer, largeFileMode: true})
+ editor.setGrammar(atom.grammars.grammarForScopeName('source.js'))
+ atom.workspace.getActivePane().activateItem(editor)
+ let t1 = window.performance.now()
+
+ data.push({
+ name: 'Opening a large single-line file',
+ x: sizeInKB,
+ duration: t1 - t0
+ })
+
+ const tickDurations = []
+ for (let i = 0; i < 20; i++) {
+ await timeout(50)
+ t0 = window.performance.now()
+ await timeout(0)
+ t1 = window.performance.now()
+ tickDurations[i] = t1 - t0
+ }
+
+ data.push({
+ name: 'Max time event loop was blocked after opening a large single-line file',
+ x: sizeInKB,
+ duration: Math.max(...tickDurations)
+ })
+
+ t0 = window.performance.now()
+ editor.setCursorScreenPosition(editor.element.screenPositionForPixelPosition({
+ top: 100,
+ left: 30
+ }))
+ t1 = window.performance.now()
+
+ data.push({
+ name: 'Clicking the editor after opening a large single-line file',
+ x: sizeInKB,
+ duration: t1 - t0
+ })
+
+ t0 = window.performance.now()
+ editor.element.setScrollTop(editor.element.getScrollTop() + 100)
+ t1 = window.performance.now()
+
+ data.push({
+ name: 'Scrolling down after opening a large single-line file',
+ x: sizeInKB,
+ duration: t1 - t0
+ })
+
+ editor.destroy()
+ buffer.destroy()
+ await timeout(10000)
+ }
+
+ workspaceElement.remove()
+
+ return data
+}
+
+function timeout (duration) {
+ return new Promise((resolve) => setTimeout(resolve, duration))
+}
diff --git a/circle.yml b/circle.yml
index ee4eafc1f..c264754d4 100644
--- a/circle.yml
+++ b/circle.yml
@@ -16,8 +16,8 @@ general:
dependencies:
pre:
- curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.3/install.sh | bash
- - nvm install 4.4.7
- - nvm use 4.4.7
+ - nvm install 6.9.4
+ - nvm use 6.9.4
- npm install -g npm
override:
diff --git a/docs/build-instructions/build-status.md b/docs/build-instructions/build-status.md
index 9b381527f..6a366b430 100644
--- a/docs/build-instructions/build-status.md
+++ b/docs/build-instructions/build-status.md
@@ -71,6 +71,7 @@
| [PathWatcher](https://github.com/atom/node-pathwatcher) | [](https://travis-ci.org/atom/node-pathwatcher) | [](https://ci.appveyor.com/project/Atom/node-pathwatcher) | [](https://david-dm.org/atom/node-pathwatcher) |
| [Property Accessors](https://github.com/atom/property-accessors) | [](https://travis-ci.org/atom/property-accessors) | [](https://ci.appveyor.com/project/Atom/property-accessors/branch/master) | [](https://david-dm.org/atom/property-accessors) |
| [Season](https://github.com/atom/season) | [](https://travis-ci.org/atom/season) | [](https://ci.appveyor.com/project/Atom/season) | [](https://david-dm.org/atom/season) |
+| [Superstring](https://github.com/atom/superstring) | [](https://travis-ci.org/atom/superstring) | [](https://ci.appveyor.com/project/Atom/superstring/branch/master) | | [](https://david-dm.org/atom/superstring) |
| [TextBuffer](https://github.com/atom/text-buffer) | [](https://travis-ci.org/atom/text-buffer) | [](https://ci.appveyor.com/project/Atom/text-buffer/branch/master) | [](https://david-dm.org/atom/text-buffer) |
| [Underscore-Plus](https://github.com/atom/underscore-plus) | [](https://travis-ci.org/atom/underscore-plus) | [](https://ci.appveyor.com/project/Atom/underscore-plus/branch/master) | [](https://david-dm.org/atom/underscore-plus) |
diff --git a/docs/build-instructions/linux.md b/docs/build-instructions/linux.md
index 5f0211c0d..1caf740ba 100644
--- a/docs/build-instructions/linux.md
+++ b/docs/build-instructions/linux.md
@@ -7,7 +7,7 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
* OS with 64-bit or 32-bit architecture
* C++11 toolchain
* Git
-* Node.js 4.4.x or later (we recommend installing it via [nvm](https://github.com/creationix/nvm))
+* Node.js 6.x (we recommend installing it via [nvm](https://github.com/creationix/nvm))
* npm 3.10.x or later (run `npm install -g npm`)
* Ensure node-gyp uses python2 (run `npm config set python /usr/bin/python2 -g`, use `sudo` if you didn't install node via nvm)
* Development headers for [GNOME Keyring](https://wiki.gnome.org/Projects/GnomeKeyring).
diff --git a/docs/build-instructions/macos.md b/docs/build-instructions/macOS.md
similarity index 90%
rename from docs/build-instructions/macos.md
rename to docs/build-instructions/macOS.md
index 18169435f..0d9335eea 100644
--- a/docs/build-instructions/macos.md
+++ b/docs/build-instructions/macOS.md
@@ -3,7 +3,7 @@
## Requirements
* macOS 10.8 or later
- * Node.js 4.4.x or later (we recommend installing it via [nvm](https://github.com/creationix/nvm))
+ * Node.js 6.x (we recommend installing it via [nvm](https://github.com/creationix/nvm))
* npm 3.10.x or later (run `npm install -g npm`)
* Command Line Tools for [Xcode](https://developer.apple.com/xcode/downloads/) (run `xcode-select --install` to install)
diff --git a/docs/build-instructions/windows.md b/docs/build-instructions/windows.md
index 5c8c189ef..2c231b2dc 100644
--- a/docs/build-instructions/windows.md
+++ b/docs/build-instructions/windows.md
@@ -2,7 +2,7 @@
## Requirements
-* Node.js 4.4.x or later (the architecture of node available to the build system will determine whether you build 32-bit or 64-bit Atom)
+* Node.js 6.x (the architecture of node available to the build system will determine whether you build 32-bit or 64-bit Atom)
* Python v2.7.x
* The python.exe must be available at `%SystemDrive%\Python27\python.exe`. If it is installed elsewhere create a symbolic link to the directory containing the python.exe using: `mklink /d %SystemDrive%\Python27 D:\elsewhere\Python27`
* 7zip (7z.exe available from the command line) - for creating distribution zip files
diff --git a/package.json b/package.json
index e7ff9d1c7..92a9920e3 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "atom",
"productName": "Atom",
- "version": "1.15.0-dev",
+ "version": "1.16.0-dev",
"description": "A hackable text editor for the 21st Century.",
"main": "./src/main-process/main.js",
"repository": {
@@ -15,10 +15,21 @@
"electronVersion": "1.3.13",
"dependencies": {
"async": "0.2.6",
- "atom-keymap": "7.1.18",
- "atom-select-list": "0.0.6",
+ "atom-keymap": "7.1.20",
+ "atom-select-list": "0.0.12",
"atom-ui": "0.4.1",
- "babel-core": "5.8.38",
+ "babel-core": "6.22.1",
+ "babel-plugin-add-module-exports": "0.2.1",
+ "babel-plugin-transform-async-to-generator": "6.22.0",
+ "babel-plugin-transform-class-properties": "6.23.0",
+ "babel-plugin-transform-decorators-legacy": "1.3.4",
+ "babel-plugin-transform-do-expressions": "6.22.0",
+ "babel-plugin-transform-es2015-modules-commonjs": "6.23.0",
+ "babel-plugin-transform-export-extensions": "6.22.0",
+ "babel-plugin-transform-flow-strip-types": "6.22.0",
+ "babel-plugin-transform-function-bind": "6.22.0",
+ "babel-plugin-transform-object-rest-spread": "6.23.0",
+ "babel-plugin-transform-react-jsx": "6.23.0",
"cached-run-in-this-context": "0.4.1",
"chai": "3.5.0",
"chart.js": "^2.3.0",
@@ -65,7 +76,7 @@
"sinon": "1.17.4",
"source-map-support": "^0.3.2",
"temp": "0.8.1",
- "text-buffer": "10.2.3",
+ "text-buffer": "10.3.11",
"typescript-simple": "1.0.0",
"underscore-plus": "^1.6.6",
"winreg": "^1.2.1",
@@ -82,85 +93,85 @@
"one-light-ui": "1.9.1",
"one-dark-syntax": "1.7.1",
"one-light-syntax": "1.7.1",
- "solarized-dark-syntax": "1.1.1",
- "solarized-light-syntax": "1.1.1",
+ "solarized-dark-syntax": "1.1.2",
+ "solarized-light-syntax": "1.1.2",
"about": "1.7.2",
"archive-view": "0.62.2",
"autocomplete-atom-api": "0.10.0",
- "autocomplete-css": "0.14.2",
+ "autocomplete-css": "0.15.0",
"autocomplete-html": "0.7.2",
"autocomplete-plus": "2.34.2",
"autocomplete-snippets": "1.11.0",
"autoflow": "0.29.0",
"autosave": "0.24.0",
"background-tips": "0.26.1",
- "bookmarks": "0.43.4",
+ "bookmarks": "0.44.1",
"bracket-matcher": "0.85.2",
- "command-palette": "0.39.2",
- "deprecation-cop": "0.55.1",
+ "command-palette": "0.40.2",
+ "deprecation-cop": "0.56.2",
"dev-live-reload": "0.47.0",
- "encoding-selector": "0.22.0",
- "exception-reporting": "0.40.1",
- "find-and-replace": "0.206.0",
+ "encoding-selector": "0.23.1",
+ "exception-reporting": "0.41.1",
+ "find-and-replace": "0.206.3",
"fuzzy-finder": "1.4.1",
- "git-diff": "1.2.0",
- "go-to-line": "0.31.2",
- "grammar-selector": "0.48.2",
- "image-view": "0.60.0",
+ "git-diff": "1.3.1",
+ "go-to-line": "0.32.0",
+ "grammar-selector": "0.49.2",
+ "image-view": "0.61.0",
"incompatible-packages": "0.26.1",
- "keybinding-resolver": "0.35.0",
- "line-ending-selector": "0.5.1",
+ "keybinding-resolver": "0.36.1",
+ "line-ending-selector": "0.6.1",
"link": "0.31.2",
- "markdown-preview": "0.159.2",
- "metrics": "1.1.2",
- "notifications": "0.66.1",
+ "markdown-preview": "0.159.7",
+ "metrics": "1.2.0",
+ "notifications": "0.66.2",
"open-on-github": "1.2.1",
- "package-generator": "1.0.2",
- "settings-view": "0.246.0",
+ "package-generator": "1.1.0",
+ "settings-view": "0.247.0",
"snippets": "1.0.5",
- "spell-check": "0.70.2",
- "status-bar": "1.7.0",
- "styleguide": "0.48.0",
+ "spell-check": "0.71.0",
+ "status-bar": "1.8.1",
+ "styleguide": "0.49.2",
"symbols-view": "0.114.0",
"tabs": "0.104.1",
- "timecop": "0.34.0",
- "tree-view": "0.213.1",
+ "timecop": "0.35.0",
+ "tree-view": "0.214.1",
"update-package-dependencies": "0.10.0",
- "welcome": "0.36.0",
- "whitespace": "0.36.1",
- "wrap-guide": "0.39.0",
- "language-c": "0.54.1",
- "language-clojure": "0.22.1",
- "language-coffee-script": "0.48.2",
- "language-csharp": "0.14.0",
+ "welcome": "0.36.1",
+ "whitespace": "0.36.2",
+ "wrap-guide": "0.39.1",
+ "language-c": "0.56.0",
+ "language-clojure": "0.22.2",
+ "language-coffee-script": "0.48.4",
+ "language-csharp": "0.14.1",
"language-css": "0.42.0",
"language-gfm": "0.88.0",
"language-git": "0.19.0",
"language-go": "0.43.1",
- "language-html": "0.47.1",
+ "language-html": "0.47.2",
"language-hyperlink": "0.16.1",
- "language-java": "0.25.0",
- "language-javascript": "0.125.1",
+ "language-java": "0.26.0",
+ "language-javascript": "0.126.0",
"language-json": "0.18.3",
"language-less": "0.30.1",
"language-make": "0.22.3",
"language-mustache": "0.13.1",
"language-objective-c": "0.15.1",
"language-perl": "0.37.0",
- "language-php": "0.37.3",
+ "language-php": "0.37.4",
"language-property-list": "0.9.0",
- "language-python": "0.45.1",
- "language-ruby": "0.70.4",
- "language-ruby-on-rails": "0.25.1",
+ "language-python": "0.45.2",
+ "language-ruby": "0.70.5",
+ "language-ruby-on-rails": "0.25.2",
"language-sass": "0.57.1",
"language-shellscript": "0.25.0",
"language-source": "0.9.0",
- "language-sql": "0.25.2",
+ "language-sql": "0.25.3",
"language-text": "0.7.1",
"language-todo": "0.29.1",
"language-toml": "0.18.1",
- "language-xml": "0.34.15",
- "language-yaml": "0.27.2"
+ "language-xml": "0.34.16",
+ "language-yaml": "0.28.0"
},
"private": true,
"scripts": {
diff --git a/resources/linux/redhat/atom.spec.in b/resources/linux/redhat/atom.spec.in
index bc2397126..82a5fbf9a 100644
--- a/resources/linux/redhat/atom.spec.in
+++ b/resources/linux/redhat/atom.spec.in
@@ -7,7 +7,7 @@ URL: https://atom.io/
AutoReqProv: no # Avoid libchromiumcontent.so missing dependency
Prefix: <%= installDir %>
-Requires: lsb-core-noarch, libXss.so.1
+Requires: lsb-core-noarch, libXss.so.1()(64bit)
%description
<%= description %>
diff --git a/script/lib/code-sign-on-mac.js b/script/lib/code-sign-on-mac.js
index 80d316566..5e4c67707 100644
--- a/script/lib/code-sign-on-mac.js
+++ b/script/lib/code-sign-on-mac.js
@@ -5,15 +5,17 @@ const path = require('path')
const spawnSync = require('./spawn-sync')
module.exports = function (packagedAppPath) {
- if (!process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL) {
+ if (!process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL && !process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH) {
console.log('Skipping code signing because the ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL environment variable is not defined'.gray)
return
}
- try {
- const certPath = path.join(os.tmpdir(), 'mac.p12')
+ let certPath = process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH;
+ if (!certPath) {
+ certPath = path.join(os.tmpdir(), 'mac.p12')
downloadFileFromGithub(process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL, certPath)
-
+ }
+ try {
console.log(`Unlocking keychain ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN}`)
const unlockArgs = ['unlock-keychain']
// For signing on local workstations, password could be entered interactively
@@ -31,6 +33,18 @@ module.exports = function (packagedAppPath) {
'-T', '/usr/bin/codesign'
])
+
+ console.log('Running incantation to suppress dialog when signing on macOS Sierra')
+ try {
+ spawnSync('security', [
+ 'set-key-partition-list', '-S', 'apple-tool:,apple:', '-s',
+ '-k', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD,
+ process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN
+ ])
+ } catch (e) {
+ console.log('Incantation failed... maybe this isn\'t Sierra?');
+ }
+
console.log(`Code-signing application at ${packagedAppPath}`)
spawnSync('codesign', [
'--deep', '--force', '--verbose',
@@ -38,7 +52,9 @@ module.exports = function (packagedAppPath) {
'--sign', 'Developer ID Application: GitHub', packagedAppPath
], {stdio: 'inherit'})
} finally {
- console.log(`Deleting certificate at ${certPath}`)
- fs.removeSync(certPath)
+ if (!process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH) {
+ console.log(`Deleting certificate at ${certPath}`)
+ fs.removeSync(certPath)
+ }
}
}
diff --git a/script/lib/create-windows-installer.js b/script/lib/create-windows-installer.js
index 4b9e0f3a3..8a0dc0f61 100644
--- a/script/lib/create-windows-installer.js
+++ b/script/lib/create-windows-installer.js
@@ -18,15 +18,19 @@ module.exports = function (packagedAppPath, codeSign) {
iconUrl: `https://raw.githubusercontent.com/atom/atom/master/resources/app-icons/${CONFIG.channel}/atom.ico`,
loadingGif: path.join(CONFIG.repositoryRootPath, 'resources', 'win', 'loading.gif'),
outputDirectory: CONFIG.buildOutputPath,
- remoteReleases: `https://atom.io/api/updates${archSuffix}`,
+ remoteReleases: `https://atom.io/api/updates${archSuffix}?version=${CONFIG.appMetadata.version}`,
setupIcon: path.join(CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'atom.ico')
}
- const certPath = path.join(os.tmpdir(), 'win.p12')
- const signing = codeSign && process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL
+ const signing = codeSign && (process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL || process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH)
+ let certPath = process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH;
if (signing) {
- downloadFileFromGithub(process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL, certPath)
+ if (!certPath) {
+ certPath = path.join(os.tmpdir(), 'win.p12')
+ downloadFileFromGithub(process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL, certPath)
+ }
+
var signParams = []
signParams.push(`/f ${certPath}`) // Signing cert file
signParams.push(`/p ${process.env.ATOM_WIN_CODE_SIGNING_CERT_PASSWORD}`) // Signing cert password
@@ -39,7 +43,7 @@ module.exports = function (packagedAppPath, codeSign) {
}
const cleanUp = function () {
- if (fs.existsSync(certPath)) {
+ if (fs.existsSync(certPath) && !process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH) {
console.log(`Deleting certificate at ${certPath}`)
fs.removeSync(certPath)
}
diff --git a/script/package.json b/script/package.json
index 1284fd83c..87c43f261 100644
--- a/script/package.json
+++ b/script/package.json
@@ -3,7 +3,6 @@
"description": "Atom build scripts",
"dependencies": {
"async": "2.0.1",
- "babel-core": "5.8.38",
"coffeelint": "1.15.7",
"colors": "1.1.2",
"csslint": "1.0.2",
diff --git a/spec/atom-environment-spec.coffee b/spec/atom-environment-spec.coffee
index 9b9715a07..d967fb97b 100644
--- a/spec/atom-environment-spec.coffee
+++ b/spec/atom-environment-spec.coffee
@@ -142,6 +142,11 @@ describe "AtomEnvironment", ->
atom.assert(false, "a == b", (e) -> error = e)
expect(error).toBe errors[0]
+ describe "if passed metadata", ->
+ it "assigns the metadata on the assertion failure's error object", ->
+ atom.assert(false, "a == b", {foo: 'bar'})
+ expect(errors[0].metadata).toEqual {foo: 'bar'}
+
describe "if the condition is true", ->
it "does nothing", ->
result = atom.assert(true, "a == b")
diff --git a/spec/atom-paths-spec.js b/spec/atom-paths-spec.js
index 4b1fc7902..3e2da4760 100644
--- a/spec/atom-paths-spec.js
+++ b/spec/atom-paths-spec.js
@@ -8,8 +8,7 @@ import path from 'path'
const temp = require('temp').track()
describe("AtomPaths", () => {
- const portableAtomHomePath = path.join(atomPaths.getAppDirectory(), '.atom')
- console.log(portableAtomHomePath)
+ const portableAtomHomePath = path.join(atomPaths.getAppDirectory(), '..', '.atom')
afterEach(() => {
atomPaths.setAtomHome(app.getPath('home'))
diff --git a/spec/buffered-process-spec.coffee b/spec/buffered-process-spec.coffee
index 84d8b0440..6f9b2a28d 100644
--- a/spec/buffered-process-spec.coffee
+++ b/spec/buffered-process-spec.coffee
@@ -67,6 +67,29 @@ describe "BufferedProcess", ->
expect(window.onerror.mostRecentCall.args[0]).toContain 'Failed to spawn command `bad-command-nope2`'
expect(window.onerror.mostRecentCall.args[4].name).toBe 'BufferedProcessError'
+ describe "when autoStart is false", ->
+ it "doesnt start unless start method is called", ->
+ stdout = ''
+ stderr = ''
+ exitCallback = jasmine.createSpy('exit callback')
+ apmProcess = new BufferedProcess
+ autoStart: false
+ command: atom.packages.getApmPath()
+ args: ['-h']
+ options: {}
+ stdout: (lines) -> stdout += lines
+ stderr: (lines) -> stderr += lines
+ exit: exitCallback
+
+ expect(apmProcess.started).not.toBe(true)
+ apmProcess.start()
+ expect(apmProcess.started).toBe(true)
+
+ waitsFor -> exitCallback.callCount is 1
+ runs ->
+ expect(stderr).toContain 'apm - Atom Package Manager'
+ expect(stdout).toEqual ''
+
it "calls the specified stdout, stderr, and exit callbacks", ->
stdout = ''
stderr = ''
diff --git a/spec/decoration-manager-spec.coffee b/spec/decoration-manager-spec.coffee
index 44da440d9..e57660a57 100644
--- a/spec/decoration-manager-spec.coffee
+++ b/spec/decoration-manager-spec.coffee
@@ -1,13 +1,14 @@
DecorationManager = require '../src/decoration-manager'
describe "DecorationManager", ->
- [decorationManager, buffer, defaultMarkerLayer] = []
+ [decorationManager, buffer, displayLayer, markerLayer1, markerLayer2] = []
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
displayLayer = buffer.addDisplayLayer()
- defaultMarkerLayer = displayLayer.addMarkerLayer()
- decorationManager = new DecorationManager(displayLayer, defaultMarkerLayer)
+ markerLayer1 = displayLayer.addMarkerLayer()
+ markerLayer2 = displayLayer.addMarkerLayer()
+ decorationManager = new DecorationManager(displayLayer)
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
@@ -17,38 +18,53 @@ describe "DecorationManager", ->
buffer.release()
describe "decorations", ->
- [marker, decoration, decorationProperties] = []
+ [layer1Marker, layer2Marker, layer1MarkerDecoration, layer2MarkerDecoration, decorationProperties] = []
beforeEach ->
- marker = defaultMarkerLayer.markBufferRange([[2, 13], [3, 15]])
+ layer1Marker = markerLayer1.markBufferRange([[2, 13], [3, 15]])
decorationProperties = {type: 'line-number', class: 'one'}
- decoration = decorationManager.decorateMarker(marker, decorationProperties)
+ layer1MarkerDecoration = decorationManager.decorateMarker(layer1Marker, decorationProperties)
+ layer2Marker = markerLayer2.markBufferRange([[2, 13], [3, 15]])
+ layer2MarkerDecoration = decorationManager.decorateMarker(layer2Marker, decorationProperties)
it "can add decorations associated with markers and remove them", ->
- expect(decoration).toBeDefined()
- expect(decoration.getProperties()).toBe decorationProperties
- expect(decorationManager.decorationForId(decoration.id)).toBe decoration
- expect(decorationManager.decorationsForScreenRowRange(2, 3)[marker.id][0]).toBe decoration
+ expect(layer1MarkerDecoration).toBeDefined()
+ expect(layer1MarkerDecoration.getProperties()).toBe decorationProperties
+ expect(decorationManager.decorationForId(layer1MarkerDecoration.id)).toBe layer1MarkerDecoration
+ expect(decorationManager.decorationsForScreenRowRange(2, 3)).toEqual {
+ "#{layer1Marker.id}": [layer1MarkerDecoration],
+ "#{layer2Marker.id}": [layer2MarkerDecoration]
+ }
- decoration.destroy()
- expect(decorationManager.decorationsForScreenRowRange(2, 3)[marker.id]).not.toBeDefined()
- expect(decorationManager.decorationForId(decoration.id)).not.toBeDefined()
+ layer1MarkerDecoration.destroy()
+ expect(decorationManager.decorationsForScreenRowRange(2, 3)[layer1Marker.id]).not.toBeDefined()
+ expect(decorationManager.decorationForId(layer1MarkerDecoration.id)).not.toBeDefined()
+ layer2MarkerDecoration.destroy()
+ expect(decorationManager.decorationsForScreenRowRange(2, 3)[layer2Marker.id]).not.toBeDefined()
+ expect(decorationManager.decorationForId(layer2MarkerDecoration.id)).not.toBeDefined()
it "will not fail if the decoration is removed twice", ->
- decoration.destroy()
- decoration.destroy()
- expect(decorationManager.decorationForId(decoration.id)).not.toBeDefined()
+ layer1MarkerDecoration.destroy()
+ layer1MarkerDecoration.destroy()
+ expect(decorationManager.decorationForId(layer1MarkerDecoration.id)).not.toBeDefined()
it "does not allow destroyed markers to be decorated", ->
- marker.destroy()
+ layer1Marker.destroy()
expect(->
- decorationManager.decorateMarker(marker, {type: 'overlay', item: document.createElement('div')})
+ decorationManager.decorateMarker(layer1Marker, {type: 'overlay', item: document.createElement('div')})
).toThrow("Cannot decorate a destroyed marker")
expect(decorationManager.getOverlayDecorations()).toEqual []
+ it "does not allow destroyed marker layers to be decorated", ->
+ layer = displayLayer.addMarkerLayer()
+ layer.destroy()
+ expect(->
+ decorationManager.decorateMarkerLayer(layer, {type: 'highlight'})
+ ).toThrow("Cannot decorate a destroyed marker layer")
+
describe "when a decoration is updated via Decoration::update()", ->
it "emits an 'updated' event containing the new and old params", ->
- decoration.onDidChangeProperties updatedSpy = jasmine.createSpy()
- decoration.setProperties type: 'line-number', class: 'two'
+ layer1MarkerDecoration.onDidChangeProperties updatedSpy = jasmine.createSpy()
+ layer1MarkerDecoration.setProperties type: 'line-number', class: 'two'
{oldProperties, newProperties} = updatedSpy.mostRecentCall.args[0]
expect(oldProperties).toEqual decorationProperties
@@ -56,29 +72,29 @@ describe "DecorationManager", ->
describe "::getDecorations(properties)", ->
it "returns decorations matching the given optional properties", ->
- expect(decorationManager.getDecorations()).toEqual [decoration]
+ expect(decorationManager.getDecorations()).toEqual [layer1MarkerDecoration, layer2MarkerDecoration]
expect(decorationManager.getDecorations(class: 'two').length).toEqual 0
- expect(decorationManager.getDecorations(class: 'one').length).toEqual 1
+ expect(decorationManager.getDecorations(class: 'one').length).toEqual 2
describe "::decorateMarker", ->
describe "when decorating gutters", ->
- [marker] = []
+ [layer1Marker] = []
beforeEach ->
- marker = defaultMarkerLayer.markBufferRange([[1, 0], [1, 0]])
+ layer1Marker = markerLayer1.markBufferRange([[1, 0], [1, 0]])
it "creates a decoration that is both of 'line-number' and 'gutter' type when called with the 'line-number' type", ->
decorationProperties = {type: 'line-number', class: 'one'}
- decoration = decorationManager.decorateMarker(marker, decorationProperties)
- expect(decoration.isType('line-number')).toBe true
- expect(decoration.isType('gutter')).toBe true
- expect(decoration.getProperties().gutterName).toBe 'line-number'
- expect(decoration.getProperties().class).toBe 'one'
+ layer1MarkerDecoration = decorationManager.decorateMarker(layer1Marker, decorationProperties)
+ expect(layer1MarkerDecoration.isType('line-number')).toBe true
+ expect(layer1MarkerDecoration.isType('gutter')).toBe true
+ expect(layer1MarkerDecoration.getProperties().gutterName).toBe 'line-number'
+ expect(layer1MarkerDecoration.getProperties().class).toBe 'one'
it "creates a decoration that is only of 'gutter' type if called with the 'gutter' type and a 'gutterName'", ->
decorationProperties = {type: 'gutter', gutterName: 'test-gutter', class: 'one'}
- decoration = decorationManager.decorateMarker(marker, decorationProperties)
- expect(decoration.isType('gutter')).toBe true
- expect(decoration.isType('line-number')).toBe false
- expect(decoration.getProperties().gutterName).toBe 'test-gutter'
- expect(decoration.getProperties().class).toBe 'one'
+ layer1MarkerDecoration = decorationManager.decorateMarker(layer1Marker, decorationProperties)
+ expect(layer1MarkerDecoration.isType('gutter')).toBe true
+ expect(layer1MarkerDecoration.isType('line-number')).toBe false
+ expect(layer1MarkerDecoration.getProperties().gutterName).toBe 'test-gutter'
+ expect(layer1MarkerDecoration.getProperties().class).toBe 'one'
diff --git a/spec/default-directory-provider-spec.coffee b/spec/default-directory-provider-spec.coffee
index 821c278ee..bf23195cf 100644
--- a/spec/default-directory-provider-spec.coffee
+++ b/spec/default-directory-provider-spec.coffee
@@ -28,6 +28,15 @@ describe "DefaultDirectoryProvider", ->
directory = provider.directoryForURISync(nonNormalizedPath)
expect(directory.getPath()).toEqual tmp
+ it "normalizes disk drive letter in path on #win32", ->
+ provider = new DefaultDirectoryProvider()
+ nonNormalizedPath = tmp[0].toLowerCase()+tmp.slice(1)
+ expect(tmp).not.toMatch /^[a-z]:/
+ expect(nonNormalizedPath).toMatch /^[a-z]:/
+
+ directory = provider.directoryForURISync(nonNormalizedPath)
+ expect(directory.getPath()).toEqual tmp
+
it "creates a Directory for its parent dir when passed a file", ->
provider = new DefaultDirectoryProvider()
file = path.join(tmp, "example.txt")
diff --git a/spec/dom-element-pool-spec.coffee b/spec/dom-element-pool-spec.coffee
deleted file mode 100644
index 2efe80beb..000000000
--- a/spec/dom-element-pool-spec.coffee
+++ /dev/null
@@ -1,60 +0,0 @@
-DOMElementPool = require '../src/dom-element-pool'
-{contains} = require 'underscore-plus'
-
-describe "DOMElementPool", ->
- domElementPool = null
-
- beforeEach ->
- domElementPool = new DOMElementPool
-
- it "builds DOM nodes, recycling them when they are freed", ->
- [div, span1, span2, span3, span4, span5, textNode] = elements = [
- domElementPool.buildElement("div")
- domElementPool.buildElement("span")
- domElementPool.buildElement("span")
- domElementPool.buildElement("span")
- domElementPool.buildElement("span")
- domElementPool.buildElement("span")
- domElementPool.buildText("Hello world!")
- ]
-
- div.appendChild(span1)
- span1.appendChild(span2)
- div.appendChild(span3)
- span3.appendChild(span4)
- span4.appendChild(textNode)
-
- domElementPool.freeElementAndDescendants(div)
- domElementPool.freeElementAndDescendants(span5)
-
- expect(contains(elements, domElementPool.buildElement("div"))).toBe(true)
- expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
- expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
- expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
- expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
- expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
- expect(contains(elements, domElementPool.buildText("another text"))).toBe(true)
-
- expect(contains(elements, domElementPool.buildElement("div"))).toBe(false)
- expect(contains(elements, domElementPool.buildElement("span"))).toBe(false)
- expect(contains(elements, domElementPool.buildText("unexisting"))).toBe(false)
-
- it "forgets free nodes after being cleared", ->
- span = domElementPool.buildElement("span")
- div = domElementPool.buildElement("div")
- domElementPool.freeElementAndDescendants(span)
- domElementPool.freeElementAndDescendants(div)
-
- domElementPool.clear()
-
- expect(domElementPool.buildElement("span")).not.toBe(span)
- expect(domElementPool.buildElement("div")).not.toBe(div)
-
- it "throws an error when trying to free the same node twice", ->
- div = domElementPool.buildElement("div")
- domElementPool.freeElementAndDescendants(div)
- expect(-> domElementPool.freeElementAndDescendants(div)).toThrow()
-
- it "throws an error when trying to free an invalid element", ->
- expect(-> domElementPool.freeElementAndDescendants(null)).toThrow()
- expect(-> domElementPool.freeElementAndDescendants(undefined)).toThrow()
diff --git a/spec/dom-element-pool-spec.js b/spec/dom-element-pool-spec.js
new file mode 100644
index 000000000..9de932e27
--- /dev/null
+++ b/spec/dom-element-pool-spec.js
@@ -0,0 +1,112 @@
+const DOMElementPool = require ('../src/dom-element-pool')
+
+describe('DOMElementPool', function () {
+ let domElementPool
+
+ beforeEach(() => { domElementPool = new DOMElementPool() })
+
+ it('builds DOM nodes, recycling them when they are freed', function () {
+ let elements
+ const [div, span1, span2, span3, span4, span5, textNode] = Array.from(elements = [
+ domElementPool.buildElement('div', 'foo'),
+ domElementPool.buildElement('span'),
+ domElementPool.buildElement('span'),
+ domElementPool.buildElement('span'),
+ domElementPool.buildElement('span'),
+ domElementPool.buildElement('span'),
+ domElementPool.buildText('Hello world!')
+ ])
+
+ expect(div.className).toBe('foo')
+ div.textContent = 'testing'
+ div.style.backgroundColor = 'red'
+ div.dataset.foo = 'bar'
+
+ expect(textNode.textContent).toBe('Hello world!')
+
+ div.appendChild(span1)
+ span1.appendChild(span2)
+ div.appendChild(span3)
+ span3.appendChild(span4)
+ span4.appendChild(textNode)
+
+ domElementPool.freeElementAndDescendants(div)
+ domElementPool.freeElementAndDescendants(span5)
+
+ expect(elements.includes(domElementPool.buildElement('div'))).toBe(true)
+ expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
+ expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
+ expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
+ expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
+ expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
+ expect(elements.includes(domElementPool.buildText('another text'))).toBe(true)
+
+ expect(elements.includes(domElementPool.buildElement('div'))).toBe(false)
+ expect(elements.includes(domElementPool.buildElement('span'))).toBe(false)
+ expect(elements.includes(domElementPool.buildText('unexisting'))).toBe(false)
+
+ expect(div.className).toBe('')
+ expect(div.textContent).toBe('')
+ expect(div.style.backgroundColor).toBe('')
+ expect(div.dataset.foo).toBeUndefined()
+
+ expect(textNode.textContent).toBe('another text')
+ })
+
+ it('forgets free nodes after being cleared', function () {
+ const span = domElementPool.buildElement('span')
+ const div = domElementPool.buildElement('div')
+ domElementPool.freeElementAndDescendants(span)
+ domElementPool.freeElementAndDescendants(div)
+
+ domElementPool.clear()
+
+ expect(domElementPool.buildElement('span')).not.toBe(span)
+ expect(domElementPool.buildElement('div')).not.toBe(div)
+ })
+
+ it('does not attempt to free nodes that were not created by the pool', () => {
+ let assertionFailure
+ atom.onDidFailAssertion((error) => assertionFailure = error)
+
+ const foreignDiv = document.createElement('div')
+ const div = domElementPool.buildElement('div')
+ div.appendChild(foreignDiv)
+ domElementPool.freeElementAndDescendants(div)
+ const span = domElementPool.buildElement('span')
+ span.appendChild(foreignDiv)
+ domElementPool.freeElementAndDescendants(span)
+
+ expect(assertionFailure).toBeUndefined()
+ })
+
+ it('fails an assertion when freeing the same element twice', function () {
+ let assertionFailure
+ atom.onDidFailAssertion((error) => assertionFailure = error)
+
+ const div = domElementPool.buildElement('div')
+ div.textContent = 'testing'
+ domElementPool.freeElementAndDescendants(div)
+ expect(assertionFailure).toBeUndefined()
+ domElementPool.freeElementAndDescendants(div)
+ expect(assertionFailure.message).toBe('Assertion failed: The element has already been freed!')
+ expect(assertionFailure.metadata.content).toBe('
testing
')
+ })
+
+ it('fails an assertion when freeing the same text node twice', function () {
+ let assertionFailure
+ atom.onDidFailAssertion((error) => assertionFailure = error)
+
+ const node = domElementPool.buildText('testing')
+ domElementPool.freeElementAndDescendants(node)
+ expect(assertionFailure).toBeUndefined()
+ domElementPool.freeElementAndDescendants(node)
+ expect(assertionFailure.message).toBe('Assertion failed: The element has already been freed!')
+ expect(assertionFailure.metadata.content).toBe('testing')
+ })
+
+ it('throws an error when trying to free an invalid element', function () {
+ expect(() => domElementPool.freeElementAndDescendants(null)).toThrow()
+ expect(() => domElementPool.freeElementAndDescendants(undefined)).toThrow()
+ })
+})
diff --git a/spec/lines-yardstick-spec.coffee b/spec/lines-yardstick-spec.coffee
index c1dab6c6b..68fd74804 100644
--- a/spec/lines-yardstick-spec.coffee
+++ b/spec/lines-yardstick-spec.coffee
@@ -78,10 +78,16 @@ describe "LinesYardstick", ->
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 0))).toEqual({left: 0, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 1))).toEqual({left: 7, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 5))).toEqual({left: 38, top: 0})
- if process.platform is 'darwin' # One pixel off on left on Win32
- expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 43, top: 14})
- expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 72, top: 14})
- expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 287.875, top: 28})
+
+ switch process.platform
+ when 'darwin'
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 43, top: 14})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 72, top: 14})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 287.875, top: 28})
+ when 'win32'
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 42, top: 14})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 71, top: 14})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 280, top: 28})
it "reuses already computed pixel positions unless it is invalidated", ->
atom.styles.addStyleSheet """
@@ -134,28 +140,40 @@ describe "LinesYardstick", ->
editor.setText(text)
- return unless process.platform is 'darwin' # These numbers are 15 higher on win32 and always integer
- expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 35)).left).toBe 230.90625
- expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 36)).left).toBe 237.5
- expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 37)).left).toBe 244.09375
+ switch process.platform
+ when 'darwin'
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 35)).left).toBe 230.90625
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 36)).left).toBe 237.5
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 37)).left).toBe 244.09375
+ when 'win32'
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 35)).left).toBe 245
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 36)).left).toBe 252
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 37)).left).toBe 259
- if process.platform is 'darwin' # Expectations fail on win32
- it "handles lines containing a mix of left-to-right and right-to-left characters", ->
- editor.setText('Persian, locally known as Parsi or Farsi (زبان فارسی), the predominant modern descendant of Old Persian.\n')
+ it "handles lines containing a mix of left-to-right and right-to-left characters", ->
+ editor.setText('Persian, locally known as Parsi or Farsi (زبان فارسی), the predominant modern descendant of Old Persian.\n')
- atom.styles.addStyleSheet """
- * {
- font-size: 14px;
- font-family: monospace;
- }
- """
+ atom.styles.addStyleSheet """
+ * {
+ font-size: 14px;
+ font-family: monospace;
+ }
+ """
- lineTopIndex = new LineTopIndex({defaultLineHeight: editor.getLineHeightInPixels()})
- linesYardstick = new LinesYardstick(editor, mockLineNodesProvider, lineTopIndex, atom.grammars)
- expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 15))).toEqual({left: 126, top: 0})
- expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 62))).toEqual({left: 521, top: 0})
- expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 58))).toEqual({left: 487, top: 0})
- expect(linesYardstick.pixelPositionForScreenPosition(Point(0, Infinity))).toEqual({left: 873.625, top: 0})
+ lineTopIndex = new LineTopIndex({defaultLineHeight: editor.getLineHeightInPixels()})
+ linesYardstick = new LinesYardstick(editor, mockLineNodesProvider, lineTopIndex, atom.grammars)
+
+ switch process.platform
+ when 'darwin'
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 15))).toEqual({left: 126, top: 0})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 62))).toEqual({left: 521, top: 0})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 58))).toEqual({left: 487, top: 0})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, Infinity))).toEqual({left: 873.625, top: 0})
+ when 'win32'
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 15))).toEqual({left: 120, top: 0})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 62))).toEqual({left: 496, top: 0})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 58))).toEqual({left: 464, top: 0})
+ expect(linesYardstick.pixelPositionForScreenPosition(Point(0, Infinity))).toEqual({left: 832, top: 0})
describe "::screenPositionForPixelPosition(pixelPosition)", ->
it "converts pixel positions to screen positions", ->
@@ -176,9 +194,14 @@ describe "LinesYardstick", ->
expect(linesYardstick.screenPositionForPixelPosition({top: 46, left: 66.5})).toEqual([3, 9])
expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 99.9})).toEqual([5, 14])
expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 225})).toEqual([5, 30])
- return unless process.platform is 'darwin' # Following tests are 1 pixel off on Win32
- expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 224.2365234375})).toEqual([5, 29])
- expect(linesYardstick.screenPositionForPixelPosition({top: 84, left: 247.1})).toEqual([6, 33])
+
+ switch process.platform
+ when 'darwin'
+ expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 224.2365234375})).toEqual([5, 29])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 84, left: 247.1})).toEqual([6, 33])
+ when 'win32'
+ expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 224.2365234375})).toEqual([5, 30])
+ expect(linesYardstick.screenPositionForPixelPosition({top: 84, left: 247.1})).toEqual([6, 34])
it "overshoots to the nearest character when text nodes are not spatially contiguous", ->
atom.styles.addStyleSheet """
diff --git a/spec/main-process/atom-application.test.js b/spec/main-process/atom-application.test.js
index 77d7987a7..d30900b99 100644
--- a/spec/main-process/atom-application.test.js
+++ b/spec/main-process/atom-application.test.js
@@ -196,7 +196,9 @@ describe('AtomApplication', function () {
it('persists window state based on the project directories', async function () {
const tempDirPath = makeTempDir()
const atomApplication = buildAtomApplication()
- const window1 = atomApplication.launch(parseCommandLine([path.join(tempDirPath, 'new-file')]))
+ const nonExistentFilePath = path.join(tempDirPath, 'new-file')
+
+ const window1 = atomApplication.launch(parseCommandLine([nonExistentFilePath]))
await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
atom.workspace.observeActivePaneItem(function (textEditor) {
if (textEditor) {
@@ -205,17 +207,31 @@ describe('AtomApplication', function () {
}
})
})
+ await window1.saveState()
window1.close()
await window1.closedPromise
- const window2 = atomApplication.launch(parseCommandLine([path.join(tempDirPath)]))
+ // Restore unsaved state when opening the directory itself
+ const window2 = atomApplication.launch(parseCommandLine([tempDirPath]))
+ await window2.loadedPromise
const window2Text = await evalInWebContents(window2.browserWindow.webContents, function (sendBackToMainProcess) {
- atom.workspace.observeActivePaneItem(function (textEditor) {
- if (textEditor) sendBackToMainProcess(textEditor.getText())
- })
+ const textEditor = atom.workspace.getActiveTextEditor()
+ textEditor.moveToBottom()
+ textEditor.insertText(' How are you?')
+ sendBackToMainProcess(textEditor.getText())
})
+ assert.equal(window2Text, 'Hello World! How are you?')
+ await window2.saveState()
+ window2.close()
+ await window2.closedPromise
- assert.equal(window2Text, 'Hello World!')
+ // Restore unsaved state when opening a path to a non-existent file in the directory
+ const window3 = atomApplication.launch(parseCommandLine([path.join(tempDirPath, 'another-non-existent-file')]))
+ await window3.loadedPromise
+ const window3Texts = await evalInWebContents(window3.browserWindow.webContents, function (sendBackToMainProcess, nonExistentFilePath) {
+ sendBackToMainProcess(atom.workspace.getTextEditors().map(editor => editor.getText()))
+ })
+ assert.include(window3Texts, 'Hello World! How are you?')
})
it('shows all directories in the tree view when multiple directory paths are passed to Atom', async function () {
@@ -260,7 +276,7 @@ describe('AtomApplication', function () {
})
assert.equal(window1EditorTitle, 'untitled')
- const window2 = atomApplication.launch(parseCommandLine([]))
+ const window2 = atomApplication.openWithOptions(parseCommandLine([]))
await focusWindow(window2)
const window2EditorTitle = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle())
@@ -472,7 +488,7 @@ describe('AtomApplication', function () {
}
let channelIdCounter = 0
- function evalInWebContents (webContents, source) {
+ function evalInWebContents (webContents, source, ...args) {
const channelId = 'eval-result-' + channelIdCounter++
return new Promise(function (resolve) {
electron.ipcMain.on(channelId, receiveResult)
diff --git a/spec/main-process/file-recovery-service.test.js b/spec/main-process/file-recovery-service.test.js
index 862b7f428..4821dbc9b 100644
--- a/spec/main-process/file-recovery-service.test.js
+++ b/spec/main-process/file-recovery-service.test.js
@@ -112,7 +112,7 @@ describe("FileRecoveryService", () => {
const mockWindow = {}
const filePath = temp.path()
fs.writeFileSync(filePath, "content")
- fs.chmodSync(filePath, 0444)
+ fs.chmodSync(filePath, 0o444)
let logs = []
this.stub(console, 'log', (message) => logs.push(message))
diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee
index c2e9e11be..6b4429cb1 100644
--- a/spec/package-manager-spec.coffee
+++ b/spec/package-manager-spec.coffee
@@ -27,12 +27,12 @@ describe "PackageManager", ->
apmPath += ".cmd"
expect(atom.packages.getApmPath()).toBe apmPath
- describe "when the core.apmPath setting is set", ->
- beforeEach ->
- atom.config.set("core.apmPath", "/path/to/apm")
+ describe "when the core.apmPath setting is set", ->
+ beforeEach ->
+ atom.config.set("core.apmPath", "/path/to/apm")
- it "returns the value of the core.apmPath config setting", ->
- expect(atom.packages.getApmPath()).toBe "/path/to/apm"
+ it "returns the value of the core.apmPath config setting", ->
+ expect(atom.packages.getApmPath()).toBe "/path/to/apm"
describe "::loadPackages()", ->
beforeEach ->
@@ -57,11 +57,13 @@ describe "PackageManager", ->
expect(pack.metadata.name).toBe "package-with-index"
it "returns the package if it has an invalid keymap", ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
pack = atom.packages.loadPackage("package-with-broken-keymap")
expect(pack instanceof Package).toBe true
expect(pack.metadata.name).toBe "package-with-broken-keymap"
it "returns the package if it has an invalid stylesheet", ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
pack = atom.packages.loadPackage("package-with-invalid-styles")
expect(pack instanceof Package).toBe true
expect(pack.metadata.name).toBe "package-with-invalid-styles"
@@ -75,6 +77,7 @@ describe "PackageManager", ->
expect(addErrorHandler.argsForCall[1][0].options.packageName).toEqual "package-with-invalid-styles"
it "returns null if the package has an invalid package.json", ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
addErrorHandler = jasmine.createSpy()
atom.notifications.onDidAddNotification(addErrorHandler)
expect(atom.packages.loadPackage("package-with-broken-package-json")).toBeNull()
@@ -107,6 +110,7 @@ describe "PackageManager", ->
describe "when the package is deprecated", ->
it "returns null", ->
+ spyOn(console, 'warn')
expect(atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'packages', 'wordcount'))).toBeNull()
expect(atom.packages.isDeprecatedPackage('wordcount', '2.1.9')).toBe true
expect(atom.packages.isDeprecatedPackage('wordcount', '2.2.0')).toBe true
@@ -392,6 +396,7 @@ describe "PackageManager", ->
expect(mainModule.activate.callCount).toBe 1
it "adds a notification when the activation commands are invalid", ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
addErrorHandler = jasmine.createSpy()
atom.notifications.onDidAddNotification(addErrorHandler)
expect(-> atom.packages.activatePackage('package-with-invalid-activation-commands')).not.toThrow()
@@ -400,6 +405,7 @@ describe "PackageManager", ->
expect(addErrorHandler.argsForCall[0][0].options.packageName).toEqual "package-with-invalid-activation-commands"
it "adds a notification when the context menu is invalid", ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
addErrorHandler = jasmine.createSpy()
atom.notifications.onDidAddNotification(addErrorHandler)
expect(-> atom.packages.activatePackage('package-with-invalid-context-menu')).not.toThrow()
@@ -546,8 +552,9 @@ describe "PackageManager", ->
waitsFor -> activatedPackage?
runs -> expect(activatedPackage.name).toBe 'package-with-main'
- describe "when the package throws an error while loading", ->
+ describe "when the package's main module throws an error on load", ->
it "adds a notification instead of throwing an exception", ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
atom.config.set("core.disabledPackages", [])
addErrorHandler = jasmine.createSpy()
atom.notifications.onDidAddNotification(addErrorHandler)
@@ -556,6 +563,11 @@ describe "PackageManager", ->
expect(addErrorHandler.argsForCall[0][0].message).toContain("Failed to load the package-that-throws-an-exception package")
expect(addErrorHandler.argsForCall[0][0].options.packageName).toEqual "package-that-throws-an-exception"
+ it "re-throws the exception in test mode", ->
+ atom.config.set("core.disabledPackages", [])
+ addErrorHandler = jasmine.createSpy()
+ expect(-> atom.packages.activatePackage("package-that-throws-an-exception")).toThrow("This package throws an exception")
+
describe "when the package is not found", ->
it "rejects the promise", ->
atom.config.set("core.disabledPackages", [])
@@ -893,6 +905,7 @@ describe "PackageManager", ->
describe "::serialize", ->
it "does not serialize packages that threw an error during activation", ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
spyOn(console, 'warn')
badPack = null
waitsForPromise ->
@@ -940,6 +953,7 @@ describe "PackageManager", ->
atom.packages.unloadPackages()
it "calls `deactivate` on the package's main module if activate was successful", ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
pack = null
waitsForPromise ->
atom.packages.activatePackage("package-with-deactivate").then (p) -> pack = p
@@ -1028,6 +1042,7 @@ describe "PackageManager", ->
describe "::activate()", ->
beforeEach ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
jasmine.snapshotDeprecations()
spyOn(console, 'warn')
atom.packages.loadPackages()
@@ -1042,6 +1057,7 @@ describe "PackageManager", ->
jasmine.restoreDeprecationsSnapshot()
it "sets hasActivatedInitialPackages", ->
+ spyOn(atom.styles, 'getUserStyleSheetPath').andReturn(null)
spyOn(atom.packages, 'activatePackages')
expect(atom.packages.hasActivatedInitialPackages()).toBe false
waitsForPromise -> atom.packages.activate()
diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee
index d548255e5..997f6054e 100644
--- a/spec/project-spec.coffee
+++ b/spec/project-spec.coffee
@@ -614,3 +614,7 @@ describe "Project", ->
randomPath = path.join("some", "random", "path")
expect(atom.project.contains(randomPath)).toBe false
+
+ describe ".resolvePath(uri)", ->
+ it "normalizes disk drive letter in passed path on #win32", ->
+ expect(atom.project.resolvePath("d:\\file.txt")).toEqual "D:\\file.txt"
diff --git a/spec/style-manager-spec.js b/spec/style-manager-spec.js
index 88baac160..e6b8acae6 100644
--- a/spec/style-manager-spec.js
+++ b/spec/style-manager-spec.js
@@ -98,6 +98,13 @@ describe('StyleManager', () => {
])
})
+ it('does not transform CSS rules with invalid syntax', () => {
+ styleManager.addStyleSheet("atom-text-editor::shadow .class-1 { font-family: inval'id }")
+ expect(Array.from(styleManager.getStyleElements()[0].sheet.cssRules).map((r) => r.selectorText)).toEqual([
+ 'atom-text-editor::shadow .class-1'
+ ])
+ })
+
it('does not throw exceptions on rules with no selectors', () => {
styleManager.addStyleSheet('@media screen {font-size: 10px}', {context: 'atom-text-editor'})
})
diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js
index 82ef7dd2a..b26e64e34 100644
--- a/spec/text-editor-component-spec.js
+++ b/spec/text-editor-component-spec.js
@@ -1347,7 +1347,19 @@ describe('TextEditorComponent', function () {
expect(cursorsNode.classList.contains('blink-off')).toBe(true)
})
- it('does not render cursors that are associated with non-empty selections', function () {
+ it('renders cursors that are associated with empty selections', function () {
+ editor.update({showCursorOnSelection: true})
+ editor.setSelectedScreenRange([[0, 4], [4, 6]])
+ editor.addCursorAtScreenPosition([6, 8])
+ runAnimationFrames()
+ let cursorNodes = componentNode.querySelectorAll('.cursor')
+ expect(cursorNodes.length).toBe(2)
+ expect(cursorNodes[0].style['-webkit-transform']).toBe('translate(' + (Math.round(6 * charWidth)) + 'px, ' + (4 * lineHeightInPixels) + 'px)')
+ expect(cursorNodes[1].style['-webkit-transform']).toBe('translate(' + (Math.round(8 * charWidth)) + 'px, ' + (6 * lineHeightInPixels) + 'px)')
+ })
+
+ it('does not render cursors that are associated with non-empty selections when showCursorOnSelection is false', function () {
+ editor.update({showCursorOnSelection: false})
editor.setSelectedScreenRange([[0, 4], [4, 6]])
editor.addCursorAtScreenPosition([6, 8])
runAnimationFrames()
@@ -1735,11 +1747,13 @@ describe('TextEditorComponent', function () {
})
describe('block decorations rendering', function () {
+ let markerLayer
+
function createBlockDecorationBeforeScreenRow(screenRow, {className}) {
let item = document.createElement("div")
item.className = className || ""
let blockDecoration = editor.decorateMarker(
- editor.markScreenPosition([screenRow, 0], {invalidate: "never"}),
+ markerLayer.markScreenPosition([screenRow, 0], {invalidate: "never"}),
{type: "block", item: item, position: "before"}
)
return [item, blockDecoration]
@@ -1749,13 +1763,14 @@ describe('TextEditorComponent', function () {
let item = document.createElement("div")
item.className = className || ""
let blockDecoration = editor.decorateMarker(
- editor.markScreenPosition([screenRow, 0], {invalidate: "never"}),
+ markerLayer.markScreenPosition([screenRow, 0], {invalidate: "never"}),
{type: "block", item: item, position: "after"}
)
return [item, blockDecoration]
}
beforeEach(function () {
+ markerLayer = editor.addMarkerLayer()
wrapperNode.style.height = 5 * lineHeightInPixels + 'px'
editor.update({autoHeight: false})
component.measureDimensions()
diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee
index 63a616cfc..2c4b6dbab 100644
--- a/spec/text-editor-presenter-spec.coffee
+++ b/spec/text-editor-presenter-spec.coffee
@@ -1583,6 +1583,7 @@ describe "TextEditorPresenter", ->
getState(presenter).content.cursors[presenter.model.getCursors()[cursorIndex].id]
it "contains pixelRects for empty selections that are visible on screen", ->
+ editor.update({showCursorOnSelection: false})
editor.setSelectedBufferRanges([
[[1, 2], [1, 2]],
[[2, 4], [2, 4]],
@@ -1627,6 +1628,7 @@ describe "TextEditorPresenter", ->
expect(getState(presenter).content.cursors).not.toEqual({})
it "updates when block decorations change", ->
+ editor.update({showCursorOnSelection: false})
editor.setSelectedBufferRanges([
[[1, 2], [1, 2]],
[[2, 4], [2, 4]],
@@ -1704,6 +1706,7 @@ describe "TextEditorPresenter", ->
expect(stateForCursor(presenter, 0)).toEqual {top: 20, left: 10 * 22, width: 10, height: 10}
it "updates when ::explicitHeight changes", ->
+ editor.update({showCursorOnSelection: false})
editor.setSelectedBufferRanges([
[[1, 2], [1, 2]],
[[2, 4], [2, 4]],
@@ -1757,6 +1760,7 @@ describe "TextEditorPresenter", ->
expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 20, height: 10}
it "updates when cursors are added, moved, hidden, shown, or destroyed", ->
+ editor.update({showCursorOnSelection: false})
editor.setSelectedBufferRanges([
[[1, 2], [1, 2]],
[[3, 4], [3, 5]]
diff --git a/spec/text-editor-registry-spec.js b/spec/text-editor-registry-spec.js
index 51027e63c..ac5183cab 100644
--- a/spec/text-editor-registry-spec.js
+++ b/spec/text-editor-registry-spec.js
@@ -436,6 +436,19 @@ describe('TextEditorRegistry', function () {
expect(editor.hasAtomicSoftTabs()).toBe(true)
})
+ it('enables or disables cursor on selection visibility based on the config', async function () {
+ editor.update({showCursorOnSelection: true})
+ expect(editor.getShowCursorOnSelection()).toBe(true)
+
+ atom.config.set('editor.showCursorOnSelection', false)
+ registry.maintainConfig(editor)
+ await initialPackageActivation
+ expect(editor.getShowCursorOnSelection()).toBe(false)
+
+ atom.config.set('editor.showCursorOnSelection', true)
+ expect(editor.getShowCursorOnSelection()).toBe(true)
+ })
+
it('enables or disables line numbers based on the config', async function () {
editor.update({showLineNumbers: true})
expect(editor.showLineNumbers).toBe(true)
diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee
index 6e6e1b7b1..911270d16 100644
--- a/spec/text-editor-spec.coffee
+++ b/spec/text-editor-spec.coffee
@@ -89,7 +89,11 @@ describe "TextEditor", ->
describe ".copy()", ->
it "returns a different editor with the same initial state", ->
- editor.update({autoHeight: false, autoWidth: true})
+ expect(editor.getAutoHeight()).toBeFalsy()
+ expect(editor.getAutoWidth()).toBeFalsy()
+ expect(editor.getShowCursorOnSelection()).toBeTruthy()
+
+ editor.update({autoHeight: true, autoWidth: true, showCursorOnSelection: false})
editor.setSelectedBufferRange([[1, 2], [3, 4]])
editor.addSelectionForBufferRange([[5, 6], [7, 8]], reversed: true)
editor.firstVisibleScreenRow = 5
@@ -105,7 +109,8 @@ describe "TextEditor", ->
expect(editor2.getFirstVisibleScreenColumn()).toBe 5
expect(editor2.isFoldedAtBufferRow(4)).toBeTruthy()
expect(editor2.getAutoWidth()).toBeTruthy()
- expect(editor2.getAutoHeight()).toBeFalsy()
+ expect(editor2.getAutoHeight()).toBeTruthy()
+ expect(editor2.getShowCursorOnSelection()).toBeFalsy()
# editor2 can now diverge from its origin edit session
editor2.getLastSelection().setBufferRange([[2, 1], [4, 3]])
@@ -1186,6 +1191,15 @@ describe "TextEditor", ->
editor.getLastSelection().destroy()
expect(editor.getLastSelection().getBufferRange()).toEqual([[0, 0], [0, 0]])
+ it "doesn't get stuck in a infinite loop when called from ::onDidAddCursor after the last selection has been destroyed (regression)", ->
+ callCount = 0
+ editor.getLastSelection().destroy()
+ editor.onDidAddCursor (cursor) ->
+ callCount++
+ editor.getLastSelection()
+ expect(editor.getLastSelection().getBufferRange()).toEqual([[0, 0], [0, 0]])
+ expect(callCount).toBe(1)
+
describe ".getSelections()", ->
it "creates a new selection at (0, 0) if the last selection has been destroyed", ->
editor.getLastSelection().destroy()
@@ -1858,7 +1872,7 @@ describe "TextEditor", ->
[[4, 25], [4, 29]]
]
for cursor in editor.getCursors()
- expect(cursor.isVisible()).toBeFalsy()
+ expect(cursor.isVisible()).toBeTruthy()
it "skips lines that are too short to create a non-empty selection", ->
editor.setSelectedBufferRange([[3, 31], [3, 38]])
@@ -1991,7 +2005,7 @@ describe "TextEditor", ->
[[2, 37], [2, 40]]
]
for cursor in editor.getCursors()
- expect(cursor.isVisible()).toBeFalsy()
+ expect(cursor.isVisible()).toBeTruthy()
it "skips lines that are too short to create a non-empty selection", ->
editor.setSelectedBufferRange([[6, 31], [6, 38]])
@@ -2161,6 +2175,54 @@ describe "TextEditor", ->
editor.setCursorScreenPosition([3, 3])
expect(selection.isEmpty()).toBeTruthy()
+ describe "cursor visibility while there is a selection", ->
+ describe "when showCursorOnSelection is true", ->
+ it "is visible while there is no selection", ->
+ expect(selection.isEmpty()).toBeTruthy()
+ expect(editor.getShowCursorOnSelection()).toBeTruthy()
+ expect(editor.getCursors().length).toBe 1
+ expect(editor.getCursors()[0].isVisible()).toBeTruthy()
+
+ it "is visible while there is a selection", ->
+ expect(selection.isEmpty()).toBeTruthy()
+ editor.setSelectedBufferRange([[1, 2], [1, 5]])
+ expect(selection.isEmpty()).toBeFalsy()
+ expect(editor.getCursors().length).toBe 1
+ expect(editor.getCursors()[0].isVisible()).toBeTruthy()
+
+ it "is visible while there are multiple selections", ->
+ expect(editor.getSelections().length).toBe 1
+ editor.setSelectedBufferRanges([[[1, 2], [1, 5]], [[2, 2], [2, 5]]])
+ expect(editor.getSelections().length).toBe 2
+ expect(editor.getCursors().length).toBe 2
+ expect(editor.getCursors()[0].isVisible()).toBeTruthy()
+ expect(editor.getCursors()[1].isVisible()).toBeTruthy()
+
+ describe "when showCursorOnSelection is false", ->
+ it "is visible while there is no selection", ->
+ editor.update({showCursorOnSelection: false})
+ expect(selection.isEmpty()).toBeTruthy()
+ expect(editor.getShowCursorOnSelection()).toBeFalsy()
+ expect(editor.getCursors().length).toBe 1
+ expect(editor.getCursors()[0].isVisible()).toBeTruthy()
+
+ it "is not visible while there is a selection", ->
+ editor.update({showCursorOnSelection: false})
+ expect(selection.isEmpty()).toBeTruthy()
+ editor.setSelectedBufferRange([[1, 2], [1, 5]])
+ expect(selection.isEmpty()).toBeFalsy()
+ expect(editor.getCursors().length).toBe 1
+ expect(editor.getCursors()[0].isVisible()).toBeFalsy()
+
+ it "is not visible while there are multiple selections", ->
+ editor.update({showCursorOnSelection: false})
+ expect(editor.getSelections().length).toBe 1
+ editor.setSelectedBufferRanges([[[1, 2], [1, 5]], [[2, 2], [2, 5]]])
+ expect(editor.getSelections().length).toBe 2
+ expect(editor.getCursors().length).toBe 2
+ expect(editor.getCursors()[0].isVisible()).toBeFalsy()
+ expect(editor.getCursors()[1].isVisible()).toBeFalsy()
+
it "does not share selections between different edit sessions for the same buffer", ->
editor2 = null
waitsForPromise ->
diff --git a/spec/theme-manager-spec.coffee b/spec/theme-manager-spec.coffee
index 40a3160da..795f1b43e 100644
--- a/spec/theme-manager-spec.coffee
+++ b/spec/theme-manager-spec.coffee
@@ -4,6 +4,7 @@ temp = require('temp').track()
describe "atom.themes", ->
beforeEach ->
+ spyOn(atom, 'inSpecMode').andReturn(false)
spyOn(console, 'warn')
afterEach ->
diff --git a/spec/workspace-element-spec.coffee b/spec/workspace-element-spec.coffee
index 6bcb24eed..a741dbbd4 100644
--- a/spec/workspace-element-spec.coffee
+++ b/spec/workspace-element-spec.coffee
@@ -74,14 +74,14 @@ describe "WorkspaceElement", ->
atom.config.set('editor.fontSize', 12)
# Zoom out
- editorElement.dispatchEvent(new WheelEvent('mousewheel', {
+ editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', {
wheelDeltaY: -10,
ctrlKey: true
}))
expect(atom.config.get('editor.fontSize')).toBe(11)
# Zoom in
- editorElement.dispatchEvent(new WheelEvent('mousewheel', {
+ editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', {
wheelDeltaY: 10,
ctrlKey: true
}))
@@ -95,13 +95,13 @@ describe "WorkspaceElement", ->
expect(atom.config.get('editor.fontSize')).toBe(12)
# No ctrl key
- workspaceElement.dispatchEvent(new WheelEvent('mousewheel', {
+ editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', {
wheelDeltaY: 10,
}))
expect(atom.config.get('editor.fontSize')).toBe(12)
atom.config.set('editor.zoomFontWhenCtrlScrolling', false)
- editorElement.dispatchEvent(new WheelEvent('mousewheel', {
+ editorElement.querySelector('span').dispatchEvent(new WheelEvent('mousewheel', {
wheelDeltaY: 10,
ctrlKey: true
}))
diff --git a/spec/workspace-spec.coffee b/spec/workspace-spec.coffee
index 08afa6239..153cc5dc3 100644
--- a/spec/workspace-spec.coffee
+++ b/spec/workspace-spec.coffee
@@ -886,8 +886,12 @@ describe "Workspace", ->
describe "document.title", ->
describe "when there is no item open", ->
- it "sets the title to 'untitled'", ->
- expect(document.title).toMatch ///^untitled///
+ it "sets the title to the project path", ->
+ expect(document.title).toMatch escapeStringRegex(fs.tildify(atom.project.getPaths()[0]))
+
+ it "sets the title to 'untitled' if there is no project path", ->
+ atom.project.setPaths([])
+ expect(document.title).toMatch /^untitled/
describe "when the active pane item's path is not inside a project path", ->
beforeEach ->
@@ -948,10 +952,10 @@ describe "Workspace", ->
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}///
describe "when the last pane item is removed", ->
- it "updates the title to be untitled", ->
+ it "updates the title to the project's first path", ->
atom.workspace.getActivePane().destroy()
expect(atom.workspace.getActivePaneItem()).toBeUndefined()
- expect(document.title).toMatch ///^untitled///
+ expect(document.title).toMatch escapeStringRegex(fs.tildify(atom.project.getPaths()[0]))
describe "when an inactive pane's item changes", ->
it "does not update the title", ->
diff --git a/src/application-delegate.coffee b/src/application-delegate.coffee
index 185db5059..766ba7aa8 100644
--- a/src/application-delegate.coffee
+++ b/src/application-delegate.coffee
@@ -2,10 +2,12 @@ _ = require 'underscore-plus'
{screen, ipcRenderer, remote, shell, webFrame} = require 'electron'
ipcHelpers = require './ipc-helpers'
{Disposable} = require 'event-kit'
-{getWindowLoadSettings, setWindowLoadSettings} = require './window-load-settings-helpers'
+getWindowLoadSettings = require './get-window-load-settings'
module.exports =
class ApplicationDelegate
+ getWindowLoadSettings: -> getWindowLoadSettings()
+
open: (params) ->
ipcRenderer.send('open', params)
@@ -109,10 +111,7 @@ class ApplicationDelegate
ipcRenderer.send("add-recent-document", filename)
setRepresentedDirectoryPaths: (paths) ->
- loadSettings = getWindowLoadSettings()
- loadSettings['initialPaths'] = paths
- setWindowLoadSettings(loadSettings)
- ipcRenderer.send("did-change-paths")
+ ipcHelpers.call('window-method', 'setRepresentedDirectoryPaths', paths)
setAutoHideWindowMenuBar: (autoHide) ->
ipcHelpers.call('window-method', 'setAutoHideMenuBar', autoHide)
@@ -149,13 +148,9 @@ class ApplicationDelegate
showMessageDialog: (params) ->
showSaveDialog: (params) ->
- if _.isString(params)
- params = defaultPath: params
- else
- params = _.clone(params)
- params.title ?= 'Save File'
- params.defaultPath ?= getWindowLoadSettings().initialPaths[0]
- remote.dialog.showSaveDialog remote.getCurrentWindow(), params
+ if typeof params is 'string'
+ params = {defaultPath: params}
+ @getCurrentWindow().showSaveDialog(params)
playBeepSound: ->
shell.beep()
diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee
index e69aeb55e..3133b5af8 100644
--- a/src/atom-environment.coffee
+++ b/src/atom-environment.coffee
@@ -11,7 +11,6 @@ Model = require './model'
WindowEventHandler = require './window-event-handler'
StateStore = require './state-store'
StorageFolder = require './storage-folder'
-{getWindowLoadSettings} = require './window-load-settings-helpers'
registerDefaultCommands = require './register-default-commands'
{updateProcessEnv} = require './update-process-env'
@@ -458,7 +457,7 @@ class AtomEnvironment extends Model
#
# Returns an {Object} containing all the load setting key/value pairs.
getLoadSettings: ->
- getWindowLoadSettings()
+ @applicationDelegate.getWindowLoadSettings()
###
Section: Managing The Atom Window
@@ -828,12 +827,17 @@ class AtomEnvironment extends Model
Section: Private
###
- assert: (condition, message, callback) ->
+ assert: (condition, message, callbackOrMetadata) ->
return true if condition
error = new Error("Assertion failed: #{message}")
Error.captureStackTrace(error, @assert)
- callback?(error)
+
+ if callbackOrMetadata?
+ if typeof callbackOrMetadata is 'function'
+ callbackOrMetadata?(error)
+ else
+ error.metadata = callbackOrMetadata
@emitter.emit 'did-fail-assertion', error
diff --git a/src/atom-paths.js b/src/atom-paths.js
index 6a5c107b3..39a768e91 100644
--- a/src/atom-paths.js
+++ b/src/atom-paths.js
@@ -17,7 +17,7 @@ const hasWriteAccess = (dir) => {
const getAppDirectory = () => {
switch (process.platform) {
case 'darwin':
- return path.join(process.execPath.substring(0, process.execPath.indexOf('.app')), '..')
+ return process.execPath.substring(0, process.execPath.indexOf('.app') + 4)
case 'linux':
case 'win32':
return path.join(process.execPath, '..')
@@ -27,7 +27,7 @@ const getAppDirectory = () => {
module.exports = {
setAtomHome: (homePath) => {
// When a read-writeable .atom folder exists above app use that
- const portableHomePath = path.join(getAppDirectory(), '.atom')
+ const portableHomePath = path.join(getAppDirectory(), '..', '.atom')
if (fs.existsSync(portableHomePath)) {
if (hasWriteAccess(portableHomePath)) {
process.env.ATOM_HOME = portableHomePath
diff --git a/src/babel.js b/src/babel.js
index a944f2e8c..d72b29ffd 100644
--- a/src/babel.js
+++ b/src/babel.js
@@ -6,6 +6,7 @@ var defaultOptions = require('../static/babelrc.json')
var babel = null
var babelVersionDirectory = null
+var options = null
var PREFIXES = [
'/** @babel */',
@@ -47,16 +48,27 @@ exports.compile = function (sourceCode, filePath) {
var noop = function () {}
Logger.prototype.debug = noop
Logger.prototype.verbose = noop
+
+ options = {ast: false, babelrc: false}
+ for (var key in defaultOptions) {
+ if (key === 'plugins') {
+ const plugins = []
+ for (const [pluginName, pluginOptions] of defaultOptions[key]) {
+ plugins.push([require.resolve(`babel-plugin-${pluginName}`), pluginOptions])
+ }
+ options[key] = plugins
+ } else {
+ options[key] = defaultOptions[key]
+ }
+ }
}
if (process.platform === 'win32') {
filePath = 'file:///' + path.resolve(filePath).replace(/\\/g, '/')
}
- var options = {filename: filePath}
- for (var key in defaultOptions) {
- options[key] = defaultOptions[key]
- }
+ options.filename = filePath
+
return babel.transform(sourceCode, options).code
}
diff --git a/src/buffered-process.js b/src/buffered-process.js
index 44e501d4d..339bf05c5 100644
--- a/src/buffered-process.js
+++ b/src/buffered-process.js
@@ -46,18 +46,34 @@ export default class BufferedProcess {
// * `exit` {Function} (optional) The callback which receives a single
// argument containing the exit status.
// * `code` {Number}
- constructor ({command, args, options = {}, stdout, stderr, exit} = {}) {
+ // * `autoStart` {Boolean} (optional) Whether the command will automatically start
+ // when this BufferedProcess is created. Defaults to true. When set to false you
+ // must call the `start` method to start the process.
+ constructor ({command, args, options = {}, stdout, stderr, exit, autoStart = true} = {}) {
this.emitter = new Emitter()
this.command = command
- // Related to joyent/node#2318
- if (process.platform === 'win32' && options.shell === undefined) {
- this.spawnWithEscapedWindowsArgs(command, args, options)
- } else {
- this.spawn(command, args, options)
+ this.args = args
+ this.options = options
+ this.stdout = stdout
+ this.stderr = stderr
+ this.exit = exit
+ if (autoStart === true) {
+ this.start()
}
-
this.killed = false
- this.handleEvents(stdout, stderr, exit)
+ }
+
+ start () {
+ if (this.started === true) return
+
+ this.started = true
+ // Related to joyent/node#2318
+ if (process.platform === 'win32' && this.options.shell === undefined) {
+ this.spawnWithEscapedWindowsArgs(this.command, this.args, this.options)
+ } else {
+ this.spawn(this.command, this.args, this.options)
+ }
+ this.handleEvents(this.stdout, this.stderr, this.exit)
}
// Windows has a bunch of special rules that node still doesn't take care of for you
diff --git a/src/config-schema.js b/src/config-schema.js
index 52102ec58..840f2d7ba 100644
--- a/src/config-schema.js
+++ b/src/config-schema.js
@@ -68,6 +68,12 @@ const configSchema = {
default: true,
description: 'Trigger the system\'s beep sound when certain actions cannot be executed or there are no results.'
},
+ closeDeletedFileTabs: {
+ type: 'boolean',
+ default: false,
+ title: 'Close Deleted File Tabs',
+ description: 'Close corresponding editors when a file is deleted outside Atom.'
+ },
destroyEmptyPanes: {
type: 'boolean',
default: true,
@@ -84,42 +90,166 @@ const configSchema = {
type: 'string',
default: 'utf8',
enum: [
- 'cp437',
- 'cp850',
- 'cp866',
- 'eucjp',
- 'euckr',
- 'gbk',
- 'iso88591',
- 'iso885910',
- 'iso885913',
- 'iso885914',
- 'iso885915',
- 'iso885916',
- 'iso88592',
- 'iso88593',
- 'iso88594',
- 'iso88595',
- 'iso88596',
- 'iso88597',
- 'iso88597',
- 'iso88598',
- 'koi8r',
- 'koi8u',
- 'macroman',
- 'shiftjis',
- 'utf16be',
- 'utf16le',
- 'utf8',
- 'windows1250',
- 'windows1251',
- 'windows1252',
- 'windows1253',
- 'windows1254',
- 'windows1255',
- 'windows1256',
- 'windows1257',
- 'windows1258'
+ {
+ value: 'iso88596',
+ description: 'Arabic (ISO 8859-6)'
+ },
+ {
+ value: 'windows1256',
+ description: 'Arabic (Windows 1256)'
+ },
+ {
+ value: 'iso88594',
+ description: 'Baltic (ISO 8859-4)'
+ },
+ {
+ value: 'windows1257',
+ description: 'Baltic (Windows 1257)'
+ },
+ {
+ value: 'iso885914',
+ description: 'Celtic (ISO 8859-14)'
+ },
+ {
+ value: 'iso88592',
+ description: 'Central European (ISO 8859-2)'
+ },
+ {
+ value: 'windows1250',
+ description: 'Central European (Windows 1250)'
+ },
+ {
+ value: 'gb18030',
+ description: 'Chinese (GB18030)'
+ },
+ {
+ value: 'gbk',
+ description: 'Chinese (GBK)'
+ },
+ {
+ value: 'cp950',
+ description: 'Traditional Chinese (Big5)'
+ },
+ {
+ value: 'big5hkscs',
+ description: 'Traditional Chinese (Big5-HKSCS)'
+ },
+ {
+ value: 'cp866',
+ description: 'Cyrillic (CP 866)'
+ },
+ {
+ value: 'iso88595',
+ description: 'Cyrillic (ISO 8859-5)'
+ },
+ {
+ value: 'koi8r',
+ description: 'Cyrillic (KOI8-R)'
+ },
+ {
+ value: 'koi8u',
+ description: 'Cyrillic (KOI8-U)'
+ },
+ {
+ value: 'windows1251',
+ description: 'Cyrillic (Windows 1251)'
+ },
+ {
+ value: 'cp437',
+ description: 'DOS (CP 437)'
+ },
+ {
+ value: 'cp850',
+ description: 'DOS (CP 850)'
+ },
+ {
+ value: 'iso885913',
+ description: 'Estonian (ISO 8859-13)'
+ },
+ {
+ value: 'iso88597',
+ description: 'Greek (ISO 8859-7)'
+ },
+ {
+ value: 'windows1253',
+ description: 'Greek (Windows 1253)'
+ },
+ {
+ value: 'iso88598',
+ description: 'Hebrew (ISO 8859-8)'
+ },
+ {
+ value: 'windows1255',
+ description: 'Hebrew (Windows 1255)'
+ },
+ {
+ value: 'cp932',
+ description: 'Japanese (CP 932)'
+ },
+ {
+ value: 'eucjp',
+ description: 'Japanese (EUC-JP)'
+ },
+ {
+ value: 'shiftjis',
+ description: 'Japanese (Shift JIS)'
+ },
+ {
+ value: 'euckr',
+ description: 'Korean (EUC-KR)'
+ },
+ {
+ value: 'iso885910',
+ description: 'Nordic (ISO 8859-10)'
+ },
+ {
+ value: 'iso885916',
+ description: 'Romanian (ISO 8859-16)'
+ },
+ {
+ value: 'iso88599',
+ description: 'Turkish (ISO 8859-9)'
+ },
+ {
+ value: 'windows1254',
+ description: 'Turkish (Windows 1254)'
+ },
+ {
+ value: 'utf8',
+ description: 'Unicode (UTF-8)'
+ },
+ {
+ value: 'utf16le',
+ description: 'Unicode (UTF-16 LE)'
+ },
+ {
+ value: 'utf16be',
+ description: 'Unicode (UTF-16 BE)'
+ },
+ {
+ value: 'windows1258',
+ description: 'Vietnamese (Windows 1258)'
+ },
+ {
+ value: 'iso88591',
+ description: 'Western (ISO 8859-1)'
+ },
+ {
+ value: 'iso88593',
+ description: 'Western (ISO 8859-3)'
+ },
+ {
+ value: 'iso885915',
+ description: 'Western (ISO 8859-15)'
+ },
+ {
+ value: 'macroman',
+ description: 'Western (Mac Roman)'
+ },
+ {
+ value: 'windows1252',
+ description: 'Western (Windows 1252)'
+ }
]
},
openEmptyEditorOnStart: {
@@ -142,6 +272,12 @@ const configSchema = {
type: 'boolean',
default: true
},
+ useProxySettingsWhenCallingApm: {
+ title: 'Use Proxy Settings When Calling APM',
+ description: 'Use detected proxy settings when calling the `apm` command-line tool.',
+ type: 'boolean',
+ default: true
+ },
allowPendingPaneItems: {
description: 'Allow items to be previewed without adding them to a pane permanently, such as when single clicking files in the tree view.',
type: 'boolean',
@@ -211,6 +347,11 @@ const configSchema = {
default: 1.5,
description: 'Height of editor lines, as a multiplier of font size.'
},
+ showCursorOnSelection: {
+ type: 'boolean',
+ 'default': true,
+ description: 'Show cursor while there is a selection.'
+ },
showInvisibles: {
type: 'boolean',
default: false,
diff --git a/src/config.coffee b/src/config.coffee
index 2dc537dd4..e873a1348 100644
--- a/src/config.coffee
+++ b/src/config.coffee
@@ -336,6 +336,31 @@ ScopeDescriptor = require './scope-descriptor'
# order: 2
# ```
#
+# ## Manipulating values outside your configuration schema
+#
+# It is possible to manipulate(`get`, `set`, `observe` etc) values that do not
+# appear in your configuration schema. For example, if the config schema of the
+# package 'some-package' is
+#
+# ```coffee
+# config:
+# someSetting:
+# type: 'boolean'
+# default: false
+# ```
+#
+# You can still do the following
+#
+# ```coffee
+# let otherSetting = atom.config.get('some-package.otherSetting')
+# atom.config.set('some-package.stillAnotherSetting', otherSetting * 5)
+# ```
+#
+# In other words, if a function asks for a `key-path`, that path doesn't have to
+# be described in the config schema for the package or any package. However, as
+# highlighted in the best practices section, you are advised against doing the
+# above.
+#
# ## Best practices
#
# * Don't depend on (or write to) configuration keys outside of your keypath.
diff --git a/src/cursor.coffee b/src/cursor.coffee
index df91d95c5..47e8c0594 100644
--- a/src/cursor.coffee
+++ b/src/cursor.coffee
@@ -12,15 +12,18 @@ EmptyLineRegExp = /(\r\n[\t ]*\r\n)|(\n[\t ]*\n)/g
# of a {DisplayMarker}.
module.exports =
class Cursor extends Model
+ showCursorOnSelection: null
screenPosition: null
bufferPosition: null
goalColumn: null
visible: true
# Instantiated by a {TextEditor}
- constructor: ({@editor, @marker, id}) ->
+ constructor: ({@editor, @marker, @showCursorOnSelection, id}) ->
@emitter = new Emitter
+ @showCursorOnSelection ?= true
+
@assignId(id)
@updateVisibility()
@@ -575,7 +578,10 @@ class Cursor extends Model
isVisible: -> @visible
updateVisibility: ->
- @setVisible(@marker.getBufferRange().isEmpty())
+ if @showCursorOnSelection
+ @setVisible(true)
+ else
+ @setVisible(@marker.getBufferRange().isEmpty())
###
Section: Comparing to another cursor
@@ -645,6 +651,11 @@ class Cursor extends Model
Section: Private
###
+ setShowCursorOnSelection: (value) ->
+ if value isnt @showCursorOnSelection
+ @showCursorOnSelection = value
+ @updateVisibility()
+
getNonWordCharacters: ->
@editor.getNonWordCharacters(@getScopeDescriptor().getScopesArray())
diff --git a/src/decoration-manager.coffee b/src/decoration-manager.coffee
index e1096f572..05935f018 100644
--- a/src/decoration-manager.coffee
+++ b/src/decoration-manager.coffee
@@ -8,7 +8,7 @@ class DecorationManager extends Model
didUpdateDecorationsEventScheduled: false
updatedSynchronously: false
- constructor: (@displayLayer, @defaultMarkerLayer) ->
+ constructor: (@displayLayer) ->
super
@emitter = new Emitter
@@ -71,9 +71,11 @@ class DecorationManager extends Model
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
decorationsByMarkerId = {}
- for marker in @defaultMarkerLayer.findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow])
- if decorations = @decorationsByMarkerId[marker.id]
- decorationsByMarkerId[marker.id] = decorations
+ for layerId of @decorationCountsByLayerId
+ layer = @displayLayer.getMarkerLayer(layerId)
+ for marker in layer.findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow])
+ if decorations = @decorationsByMarkerId[marker.id]
+ decorationsByMarkerId[marker.id] = decorations
decorationsByMarkerId
decorationsStateForScreenRowRange: (startScreenRow, endScreenRow) ->
@@ -104,7 +106,14 @@ class DecorationManager extends Model
decorationsState
decorateMarker: (marker, decorationParams) ->
- throw new Error("Cannot decorate a destroyed marker") if marker.isDestroyed()
+ if marker.isDestroyed()
+ error = new Error("Cannot decorate a destroyed marker")
+ error.metadata = {markerLayerIsDestroyed: marker.layer.isDestroyed()}
+ if marker.destroyStackTrace?
+ error.metadata.destroyStackTrace = marker.destroyStackTrace
+ if marker.bufferMarker?.destroyStackTrace?
+ error.metadata.destroyStackTrace = marker.bufferMarker?.destroyStackTrace
+ throw error
marker = @displayLayer.getMarkerLayer(marker.layer.id).getMarker(marker.id)
decoration = new Decoration(marker, this, decorationParams)
@decorationsByMarkerId[marker.id] ?= []
@@ -117,6 +126,7 @@ class DecorationManager extends Model
decoration
decorateMarkerLayer: (markerLayer, decorationParams) ->
+ throw new Error("Cannot decorate a destroyed marker layer") if markerLayer.isDestroyed()
decoration = new LayerDecoration(markerLayer, this, decorationParams)
@layerDecorationsByMarkerLayerId[markerLayer.id] ?= []
@layerDecorationsByMarkerLayerId[markerLayer.id].push(decoration)
diff --git a/src/default-directory-provider.coffee b/src/default-directory-provider.coffee
index ed4e9ba36..44d5298dd 100644
--- a/src/default-directory-provider.coffee
+++ b/src/default-directory-provider.coffee
@@ -15,7 +15,7 @@ class DefaultDirectoryProvider
# * {Directory} if the given URI is compatible with this provider.
# * `null` if the given URI is not compatibile with this provider.
directoryForURISync: (uri) ->
- normalizedPath = path.normalize(uri)
+ normalizedPath = @normalizePath(uri)
{host} = url.parse(uri)
directoryPath = if host
uri
@@ -42,3 +42,17 @@ class DefaultDirectoryProvider
# * `null` if the given URI is not compatibile with this provider.
directoryForURI: (uri) ->
Promise.resolve(@directoryForURISync(uri))
+
+ # Public: Normalizes path.
+ #
+ # * `uri` {String} The path that should be normalized.
+ #
+ # Returns a {String} with normalized path.
+ normalizePath: (uri) ->
+ # Normalize disk drive letter on Windows to avoid opening two buffers for the same file
+ pathWithNormalizedDiskDriveLetter =
+ if process.platform is 'win32' and matchData = uri.match(/^([a-z]):/)
+ "#{matchData[1].toUpperCase()}#{uri.slice(1)}"
+ else
+ uri
+ path.normalize(pathWithNormalizedDiskDriveLetter)
diff --git a/src/dom-element-pool.coffee b/src/dom-element-pool.coffee
deleted file mode 100644
index f81a537f3..000000000
--- a/src/dom-element-pool.coffee
+++ /dev/null
@@ -1,55 +0,0 @@
-module.exports =
-class DOMElementPool
- constructor: ->
- @freeElementsByTagName = {}
- @freedElements = new Set
-
- clear: ->
- @freedElements.clear()
- for tagName, freeElements of @freeElementsByTagName
- freeElements.length = 0
- return
-
- build: (tagName, factory, reset) ->
- element = @freeElementsByTagName[tagName]?.pop()
- element ?= factory()
- reset(element)
- @freedElements.delete(element)
- element
-
- buildElement: (tagName, className) ->
- factory = -> document.createElement(tagName)
- reset = (element) ->
- delete element.dataset[dataId] for dataId of element.dataset
- element.removeAttribute("style")
- if className?
- element.className = className
- else
- element.removeAttribute("class")
- @build(tagName, factory, reset)
-
- buildText: (textContent) ->
- factory = -> document.createTextNode(textContent)
- reset = (element) -> element.textContent = textContent
- @build("#text", factory, reset)
-
- freeElementAndDescendants: (element) ->
- @free(element)
- @freeDescendants(element)
-
- freeDescendants: (element) ->
- for descendant in element.childNodes by -1
- @free(descendant)
- @freeDescendants(descendant)
- return
-
- free: (element) ->
- throw new Error("The element cannot be null or undefined.") unless element?
- throw new Error("The element has already been freed!") if @freedElements.has(element)
-
- tagName = element.nodeName.toLowerCase()
- @freeElementsByTagName[tagName] ?= []
- @freeElementsByTagName[tagName].push(element)
- @freedElements.add(element)
-
- element.remove()
diff --git a/src/dom-element-pool.js b/src/dom-element-pool.js
new file mode 100644
index 000000000..0fef02dee
--- /dev/null
+++ b/src/dom-element-pool.js
@@ -0,0 +1,89 @@
+module.exports =
+class DOMElementPool {
+ constructor () {
+ this.managedElements = new Set()
+ this.freeElementsByTagName = new Map()
+ this.freedElements = new Set()
+ }
+
+ clear () {
+ this.managedElements.clear()
+ this.freedElements.clear()
+ this.freeElementsByTagName.clear()
+ }
+
+ buildElement (tagName, className) {
+ const elements = this.freeElementsByTagName.get(tagName)
+ let element = elements ? elements.pop() : null
+ if (element) {
+ for (let dataId in element.dataset) { delete element.dataset[dataId] }
+ element.removeAttribute('style')
+ if (className) {
+ element.className = className
+ } else {
+ element.removeAttribute('class')
+ }
+ while (element.firstChild) {
+ element.removeChild(element.firstChild)
+ }
+ this.freedElements.delete(element)
+ } else {
+ element = document.createElement(tagName)
+ if (className) {
+ element.className = className
+ }
+ this.managedElements.add(element)
+ }
+ return element
+ }
+
+ buildText (textContent) {
+ const elements = this.freeElementsByTagName.get('#text')
+ let element = elements ? elements.pop() : null
+ if (element) {
+ element.textContent = textContent
+ this.freedElements.delete(element)
+ } else {
+ element = document.createTextNode(textContent)
+ this.managedElements.add(element)
+ }
+ return element
+ }
+
+ freeElementAndDescendants (element) {
+ this.free(element)
+ element.remove()
+ }
+
+ freeDescendants (element) {
+ while (element.firstChild) {
+ this.free(element.firstChild)
+ element.removeChild(element.firstChild)
+ }
+ }
+
+ free (element) {
+ if (element == null) { throw new Error('The element cannot be null or undefined.') }
+ if (!this.managedElements.has(element)) return
+ if (this.freedElements.has(element)) {
+ atom.assert(false, 'The element has already been freed!', {
+ content: element instanceof window.Text ? element.textContent : element.outerHTML
+ })
+ return
+ }
+
+ const tagName = element.nodeName.toLowerCase()
+ let elements = this.freeElementsByTagName.get(tagName)
+ if (!elements) {
+ elements = []
+ this.freeElementsByTagName.set(tagName, elements)
+ }
+ elements.push(element)
+ this.freedElements.add(element)
+
+ for (let i = element.childNodes.length - 1; i >= 0; i--) {
+ const descendant = element.childNodes[i]
+ this.free(descendant)
+ }
+ }
+}
diff --git a/src/get-window-load-settings.js b/src/get-window-load-settings.js
new file mode 100644
index 000000000..7ee465141
--- /dev/null
+++ b/src/get-window-load-settings.js
@@ -0,0 +1,10 @@
+const {remote} = require('electron')
+
+let windowLoadSettings = null
+
+module.exports = () => {
+ if (!windowLoadSettings) {
+ windowLoadSettings = remote.getCurrentWindow().loadSettings
+ }
+ return windowLoadSettings
+}
diff --git a/src/history-manager.js b/src/history-manager.js
index c5117d00f..f013957b9 100644
--- a/src/history-manager.js
+++ b/src/history-manager.js
@@ -61,6 +61,19 @@ export class HistoryManager {
this.didChangeProjects()
}
+ removeProject (paths) {
+ if (paths.length === 0) return
+
+ let project = this.getProject(paths)
+ if (!project) return
+
+ let index = this.projects.indexOf(project)
+ this.projects.splice(index, 1)
+
+ this.saveState()
+ this.didChangeProjects()
+ }
+
getProject (paths) {
for (var i = 0; i < this.projects.length; i++) {
if (arrayEquivalent(paths, this.projects[i].paths)) {
diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee
index 7d3a23db7..be13ce6c6 100644
--- a/src/initialize-application-window.coffee
+++ b/src/initialize-application-window.coffee
@@ -3,7 +3,7 @@ module.exports = ({blobStore}) ->
{updateProcessEnv} = require('./update-process-env')
path = require 'path'
require './window'
- {getWindowLoadSettings} = require './window-load-settings-helpers'
+ getWindowLoadSettings = require './get-window-load-settings'
{ipcRenderer} = require 'electron'
{resourcePath, devMode, env} = getWindowLoadSettings()
require './electron-shims'
diff --git a/src/initialize-benchmark-window.js b/src/initialize-benchmark-window.js
index 29a210904..a223e0b03 100644
--- a/src/initialize-benchmark-window.js
+++ b/src/initialize-benchmark-window.js
@@ -6,7 +6,7 @@ import ipcHelpers from './ipc-helpers'
import util from 'util'
export default async function () {
- const {getWindowLoadSettings} = require('./window-load-settings-helpers')
+ const getWindowLoadSettings = require('./get-window-load-settings')
const {test, headless, resourcePath, benchmarkPaths} = getWindowLoadSettings()
try {
const Clipboard = require('../src/clipboard')
diff --git a/src/initialize-test-window.coffee b/src/initialize-test-window.coffee
index 39a408fea..794db3174 100644
--- a/src/initialize-test-window.coffee
+++ b/src/initialize-test-window.coffee
@@ -18,7 +18,7 @@ module.exports = ({blobStore}) ->
try
path = require 'path'
{ipcRenderer} = require 'electron'
- {getWindowLoadSettings} = require './window-load-settings-helpers'
+ getWindowLoadSettings = require './get-window-load-settings'
CompileCache = require './compile-cache'
AtomEnvironment = require '../src/atom-environment'
ApplicationDelegate = require '../src/application-delegate'
diff --git a/src/ipc-helpers.js b/src/ipc-helpers.js
index 6a7565968..4be9f9613 100644
--- a/src/ipc-helpers.js
+++ b/src/ipc-helpers.js
@@ -15,6 +15,7 @@ exports.on = function (emitter, eventName, callback) {
exports.call = function (channel, ...args) {
if (!ipcRenderer) {
ipcRenderer = require('electron').ipcRenderer
+ ipcRenderer.setMaxListeners(20)
}
var responseChannel = getResponseChannel(channel)
diff --git a/src/main-process/atom-application.coffee b/src/main-process/atom-application.coffee
index 4f580be8c..93e9e3395 100644
--- a/src/main-process/atom-application.coffee
+++ b/src/main-process/atom-application.coffee
@@ -593,8 +593,7 @@ class AtomApplication
states = []
for window in @windows
unless window.isSpec
- if loadSettings = window.getLoadSettings()
- states.push(initialPaths: loadSettings.initialPaths)
+ states.push({initialPaths: window.representedDirectoryPaths})
if states.length > 0 or allowEmpty
@storageFolder.storeSync('application.json', states)
diff --git a/src/main-process/atom-window.coffee b/src/main-process/atom-window.coffee
index 266f5d292..03386d31a 100644
--- a/src/main-process/atom-window.coffee
+++ b/src/main-process/atom-window.coffee
@@ -52,9 +52,7 @@ class AtomWindow
if @shouldHideTitleBar()
options.frame = false
- @browserWindow = new BrowserWindow options
- @atomApplication.addWindow(this)
-
+ @browserWindow = new BrowserWindow(options)
@handleEvents()
loadSettings = Object.assign({}, settings)
@@ -66,11 +64,15 @@ class AtomWindow
loadSettings.clearWindowState ?= false
loadSettings.initialPaths ?=
for {pathToOpen} in locationsToOpen when pathToOpen
- if fs.statSyncNoException(pathToOpen).isFile?()
- path.dirname(pathToOpen)
- else
+ stat = fs.statSyncNoException(pathToOpen) or null
+ if stat?.isDirectory()
pathToOpen
-
+ else
+ parentDirectory = path.dirname(pathToOpen)
+ if stat?.isFile() or fs.existsSync(parentDirectory)
+ parentDirectory
+ else
+ pathToOpen
loadSettings.initialPaths.sort()
# Only send to the first non-spec window created
@@ -78,33 +80,31 @@ class AtomWindow
@constructor.includeShellLoadTime = false
loadSettings.shellLoadTime ?= Date.now() - global.shellStartTime
+ @representedDirectoryPaths = loadSettings.initialPaths
+ @env = loadSettings.env if loadSettings.env?
+
@browserWindow.loadSettings = loadSettings
@browserWindow.on 'window:loaded', =>
@emit 'window:loaded'
@resolveLoadedPromise()
- @setLoadSettings(loadSettings)
- @env = loadSettings.env if loadSettings.env?
+ @browserWindow.loadURL url.format
+ protocol: 'file'
+ pathname: "#{@resourcePath}/static/index.html"
+ slashes: true
+
+ @browserWindow.showSaveDialog = @showSaveDialog.bind(this)
+
@browserWindow.focusOnWebView() if @isSpec
@browserWindow.temporaryState = {windowDimensions} if windowDimensions?
hasPathToOpen = not (locationsToOpen.length is 1 and not locationsToOpen[0].pathToOpen?)
@openLocations(locationsToOpen) if hasPathToOpen and not @isSpecWindow()
- setLoadSettings: (loadSettings) ->
- @browserWindow.loadURL url.format
- protocol: 'file'
- pathname: "#{@resourcePath}/static/index.html"
- slashes: true
- hash: encodeURIComponent(JSON.stringify(loadSettings))
+ @atomApplication.addWindow(this)
- getLoadSettings: ->
- if @browserWindow.webContents? and not @browserWindow.webContents.isLoading()
- hash = url.parse(@browserWindow.webContents.getURL()).hash.substr(1)
- JSON.parse(decodeURIComponent(hash))
-
- hasProjectPath: -> @getLoadSettings().initialPaths?.length > 0
+ hasProjectPath: -> @representedDirectoryPaths.length > 0
setupContextMenu: ->
ContextMenu = require './context-menu'
@@ -118,7 +118,7 @@ class AtomWindow
true
containsPath: (pathToCheck) ->
- @getLoadSettings()?.initialPaths?.some (projectPath) ->
+ @representedDirectoryPaths.some (projectPath) ->
if not projectPath
false
else if not pathToCheck
@@ -281,6 +281,13 @@ class AtomWindow
@saveState().then => @browserWindow.reload()
@loadedPromise
+ showSaveDialog: (params) ->
+ params = Object.assign({
+ title: 'Save File',
+ defaultPath: @representedDirectoryPaths[0]
+ }, params)
+ dialog.showSaveDialog(this, params)
+
toggleDevTools: -> @browserWindow.toggleDevTools()
openDevTools: -> @browserWindow.openDevTools()
@@ -291,4 +298,8 @@ class AtomWindow
setRepresentedFilename: (representedFilename) -> @browserWindow.setRepresentedFilename(representedFilename)
+ setRepresentedDirectoryPaths: (@representedDirectoryPaths) ->
+ @representedDirectoryPaths.sort()
+ @atomApplication.saveState()
+
copy: -> @browserWindow.copy()
diff --git a/src/main-process/auto-update-manager.coffee b/src/main-process/auto-update-manager.coffee
index 8fdba844d..ff29dd3d6 100644
--- a/src/main-process/auto-update-manager.coffee
+++ b/src/main-process/auto-update-manager.coffee
@@ -22,7 +22,7 @@ class AutoUpdateManager
setupAutoUpdater: ->
if process.platform is 'win32'
archSuffix = if process.arch is 'ia32' then '' else '-' + process.arch
- @feedUrl = "https://atom.io/api/updates#{archSuffix}"
+ @feedUrl = "https://atom.io/api/updates#{archSuffix}?version=#{@version}"
autoUpdater = require './auto-updater-win32'
else
@feedUrl = "https://atom.io/api/updates?version=#{@version}"
diff --git a/src/main-process/parse-command-line.js b/src/main-process/parse-command-line.js
index 4227b63ba..3f9f2523a 100644
--- a/src/main-process/parse-command-line.js
+++ b/src/main-process/parse-command-line.js
@@ -106,14 +106,14 @@ module.exports = function parseCommandLine (processArgs) {
if (args['resource-path']) {
devMode = true
- resourcePath = args['resource-path']
+ devResourcePath = args['resource-path']
}
if (test) {
devMode = true
}
- if (devMode && !resourcePath) {
+ if (devMode) {
resourcePath = devResourcePath
}
diff --git a/src/package.coffee b/src/package.coffee
index 9fa2dbe63..63efbf02c 100644
--- a/src/package.coffee
+++ b/src/package.coffee
@@ -711,6 +711,9 @@ class Package
incompatibleNativeModules
handleError: (message, error) ->
+ if atom.inSpecMode()
+ throw error
+
if error.filename and error.location and (error instanceof SyntaxError)
location = "#{error.filename}:#{error.location.first_line + 1}:#{error.location.first_column + 1}"
detail = "#{error.message} in #{location}"
diff --git a/src/project.coffee b/src/project.coffee
index 522fbfbc7..a02f27dac 100644
--- a/src/project.coffee
+++ b/src/project.coffee
@@ -21,7 +21,6 @@ class Project extends Model
constructor: ({@notificationManager, packageManager, config, @applicationDelegate}) ->
@emitter = new Emitter
@buffers = []
- @paths = []
@rootDirectories = []
@repositories = []
@directoryProviders = []
@@ -32,7 +31,9 @@ class Project extends Model
destroyed: ->
buffer.destroy() for buffer in @buffers
- @setPaths([])
+ repository?.destroy() for repository in @repositories
+ @rootDirectories = []
+ @repositories = []
reset: (packageManager) ->
@emitter.dispose()
@@ -62,6 +63,9 @@ class Project extends Model
fs.closeSync(fs.openSync(bufferState.filePath, 'r'))
catch error
return unless error.code is 'ENOENT'
+ unless bufferState.shouldDestroyOnFileDelete?
+ bufferState.shouldDestroyOnFileDelete =
+ -> atom.config.get('core.closeDeletedFileTabs')
TextBuffer.deserialize(bufferState)
@subscribeToBuffer(buffer) for buffer in @buffers
@@ -205,7 +209,7 @@ class Project extends Model
removePath: (projectPath) ->
# The projectPath may be a URI, in which case it should not be normalized.
unless projectPath in @getPaths()
- projectPath = path.normalize(projectPath)
+ projectPath = @defaultDirectoryProvider.normalizePath(projectPath)
indexToRemove = null
for directory, i in @rootDirectories
@@ -233,11 +237,10 @@ class Project extends Model
uri
else
if fs.isAbsolute(uri)
- path.normalize(fs.resolveHome(uri))
-
+ @defaultDirectoryProvider.normalizePath(fs.resolveHome(uri))
# TODO: what should we do here when there are multiple directories?
else if projectPath = @getPaths()[0]
- path.normalize(fs.resolveHome(path.join(projectPath, uri)))
+ @defaultDirectoryProvider.normalizePath(fs.resolveHome(path.join(projectPath, uri)))
else
undefined
@@ -360,9 +363,14 @@ class Project extends Model
else
@buildBuffer(absoluteFilePath)
+ shouldDestroyBufferOnFileDelete: ->
+ atom.config.get('core.closeDeletedFileTabs')
+
# Still needed when deserializing a tokenized buffer
buildBufferSync: (absoluteFilePath) ->
- buffer = new TextBuffer({filePath: absoluteFilePath})
+ buffer = new TextBuffer({
+ filePath: absoluteFilePath
+ shouldDestroyOnFileDelete: @shouldDestroyBufferOnFileDelete})
@addBuffer(buffer)
buffer.loadSync()
buffer
@@ -374,7 +382,9 @@ class Project extends Model
#
# Returns a {Promise} that resolves to the {TextBuffer}.
buildBuffer: (absoluteFilePath) ->
- buffer = new TextBuffer({filePath: absoluteFilePath})
+ buffer = new TextBuffer({
+ filePath: absoluteFilePath
+ shouldDestroyOnFileDelete: @shouldDestroyBufferOnFileDelete})
@addBuffer(buffer)
buffer.load()
.then((buffer) -> buffer)
diff --git a/src/reopen-project-menu-manager.js b/src/reopen-project-menu-manager.js
index 8b2a11838..79acbba66 100644
--- a/src/reopen-project-menu-manager.js
+++ b/src/reopen-project-menu-manager.js
@@ -19,6 +19,8 @@ export default class ReopenProjectMenuManager {
}),
commands.add('atom-workspace', { 'application:reopen-project': this.reopenProjectCommand.bind(this) })
)
+
+ this.applyWindowsJumpListRemovals()
}
reopenProjectCommand (e) {
@@ -49,9 +51,30 @@ export default class ReopenProjectMenuManager {
this.updateWindowsJumpList()
}
+ static taskDescription (paths) {
+ return paths.map(path => `${ReopenProjectMenuManager.betterBaseName(path)} (${path})`).join(' ')
+ }
+
+ // Windows users can right-click Atom taskbar and remove project from the jump list.
+ // We have to honor that or the group stops working. As we only get a partial list
+ // each time we remove them from history entirely.
+ applyWindowsJumpListRemovals () {
+ if (process.platform !== 'win32') return
+ if (this.app === undefined) {
+ this.app = require('remote').app
+ }
+
+ const removed = this.app.getJumpListSettings().removedItems.map(i => i.description)
+ if (removed.length === 0) return
+ for (let project of this.historyManager.getProjects()) {
+ if (removed.includes(ReopenProjectMenuManager.taskDescription(project.paths))) {
+ this.historyManager.removeProject(project.paths)
+ }
+ }
+ }
+
updateWindowsJumpList () {
if (process.platform !== 'win32') return
-
if (this.app === undefined) {
this.app = require('remote').app
}
@@ -64,7 +87,7 @@ export default class ReopenProjectMenuManager {
({
type: 'task',
title: project.paths.map(ReopenProjectMenuManager.betterBaseName).join(', '),
- description: project.paths.map(path => `${ReopenProjectMenuManager.betterBaseName(path)} (${path})`).join(' '),
+ description: ReopenProjectMenuManager.taskDescription(project.paths),
program: process.execPath,
args: project.paths.map(path => `"${path}"`).join(' '),
iconPath: path.join(path.dirname(process.execPath), 'resources', 'cli', 'folder.ico'),
diff --git a/src/state-store.js b/src/state-store.js
index fe45bc086..b192d8b04 100644
--- a/src/state-store.js
+++ b/src/state-store.js
@@ -3,6 +3,7 @@
module.exports =
class StateStore {
constructor (databaseName, version) {
+ this.connected = false
this.dbPromise = new Promise((resolve) => {
let dbOpenRequest = indexedDB.open(databaseName, version)
dbOpenRequest.onupgradeneeded = (event) => {
@@ -10,15 +11,21 @@ class StateStore {
db.createObjectStore('states')
}
dbOpenRequest.onsuccess = () => {
+ this.connected = true
resolve(dbOpenRequest.result)
}
dbOpenRequest.onerror = (error) => {
console.error('Could not connect to indexedDB', error)
+ this.connected = false
resolve(null)
}
})
}
+ isConnected () {
+ return this.connected
+ }
+
connect () {
return this.dbPromise.then((db) => !!db)
}
diff --git a/src/style-manager.js b/src/style-manager.js
index 7ee11fd6d..0a0b401d3 100644
--- a/src/style-manager.js
+++ b/src/style-manager.js
@@ -250,59 +250,70 @@ module.exports = class StyleManager {
function transformDeprecatedShadowDOMSelectors (css, context) {
const transformedSelectors = []
- const transformedSource = postcss.parse(css)
- transformedSource.walkRules((rule) => {
- const transformedSelector = selectorParser((selectors) => {
- selectors.each((selector) => {
- const firstNode = selector.nodes[0]
- if (context === 'atom-text-editor' && firstNode.type === 'pseudo' && firstNode.value === ':host') {
- const atomTextEditorElementNode = selectorParser.tag({value: 'atom-text-editor'})
- firstNode.replaceWith(atomTextEditorElementNode)
- }
-
- let previousNodeIsAtomTextEditor = false
- let targetsAtomTextEditorShadow = context === 'atom-text-editor'
- let previousNode
- selector.each((node) => {
- if (targetsAtomTextEditorShadow && node.type === 'class') {
- if (DEPRECATED_SYNTAX_SELECTORS.has(node.value)) {
- node.value = `syntax--${node.value}`
- }
- } else {
- if (previousNodeIsAtomTextEditor && node.type === 'pseudo' && node.value === '::shadow') {
- node.type = 'className'
- node.value = '.editor'
- targetsAtomTextEditorShadow = true
- }
- }
-
- previousNode = node
- if (node.type === 'combinator') {
- previousNodeIsAtomTextEditor = false
- } else if (previousNode.type === 'tag' && previousNode.value === 'atom-text-editor') {
- previousNodeIsAtomTextEditor = true
- }
- })
- })
- }).process(rule.selector, {lossless: true}).result
- if (transformedSelector !== rule.selector) {
- transformedSelectors.push({before: rule.selector, after: transformedSelector})
- rule.selector = transformedSelector
- }
- })
- let deprecationMessage
- if (transformedSelectors.length > 0) {
- deprecationMessage = 'Starting from Atom v1.13.0, the contents of `atom-text-editor` elements '
- deprecationMessage += 'are no longer encapsulated within a shadow DOM boundary. '
- deprecationMessage += 'This means you should stop using `:host` and `::shadow` '
- deprecationMessage += 'pseudo-selectors, and prepend all your syntax selectors with `syntax--`. '
- deprecationMessage += 'To prevent breakage with existing style sheets, Atom will automatically '
- deprecationMessage += 'upgrade the following selectors:\n\n'
- deprecationMessage += transformedSelectors
- .map((selector) => `* \`${selector.before}\` => \`${selector.after}\``)
- .join('\n\n') + '\n\n'
- deprecationMessage += 'Automatic translation of selectors will be removed in a few release cycles to minimize startup time. '
- deprecationMessage += 'Please, make sure to upgrade the above selectors as soon as possible.'
+ let transformedSource
+ try {
+ transformedSource = postcss.parse(css)
+ } catch (e) {
+ transformedSource = null
+ }
+
+ if (transformedSource) {
+ transformedSource.walkRules((rule) => {
+ const transformedSelector = selectorParser((selectors) => {
+ selectors.each((selector) => {
+ const firstNode = selector.nodes[0]
+ if (context === 'atom-text-editor' && firstNode.type === 'pseudo' && firstNode.value === ':host') {
+ const atomTextEditorElementNode = selectorParser.tag({value: 'atom-text-editor'})
+ firstNode.replaceWith(atomTextEditorElementNode)
+ }
+
+ let previousNodeIsAtomTextEditor = false
+ let targetsAtomTextEditorShadow = context === 'atom-text-editor'
+ let previousNode
+ selector.each((node) => {
+ if (targetsAtomTextEditorShadow && node.type === 'class') {
+ if (DEPRECATED_SYNTAX_SELECTORS.has(node.value)) {
+ node.value = `syntax--${node.value}`
+ }
+ } else {
+ if (previousNodeIsAtomTextEditor && node.type === 'pseudo' && node.value === '::shadow') {
+ node.type = 'className'
+ node.value = '.editor'
+ targetsAtomTextEditorShadow = true
+ }
+ }
+
+ previousNode = node
+ if (node.type === 'combinator') {
+ previousNodeIsAtomTextEditor = false
+ } else if (previousNode.type === 'tag' && previousNode.value === 'atom-text-editor') {
+ previousNodeIsAtomTextEditor = true
+ }
+ })
+ })
+ }).process(rule.selector, {lossless: true}).result
+ if (transformedSelector !== rule.selector) {
+ transformedSelectors.push({before: rule.selector, after: transformedSelector})
+ rule.selector = transformedSelector
+ }
+ })
+ let deprecationMessage
+ if (transformedSelectors.length > 0) {
+ deprecationMessage = 'Starting from Atom v1.13.0, the contents of `atom-text-editor` elements '
+ deprecationMessage += 'are no longer encapsulated within a shadow DOM boundary. '
+ deprecationMessage += 'This means you should stop using `:host` and `::shadow` '
+ deprecationMessage += 'pseudo-selectors, and prepend all your syntax selectors with `syntax--`. '
+ deprecationMessage += 'To prevent breakage with existing style sheets, Atom will automatically '
+ deprecationMessage += 'upgrade the following selectors:\n\n'
+ deprecationMessage += transformedSelectors
+ .map((selector) => `* \`${selector.before}\` => \`${selector.after}\``)
+ .join('\n\n') + '\n\n'
+ deprecationMessage += 'Automatic translation of selectors will be removed in a few release cycles to minimize startup time. '
+ deprecationMessage += 'Please, make sure to upgrade the above selectors as soon as possible.'
+ }
+ return {source: transformedSource.toString(), deprecationMessage}
+ } else {
+ // CSS was malformed so we don't transform it.
+ return {source: css}
}
- return {source: transformedSource.toString(), deprecationMessage}
}
diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee
index 8c9792916..26e3bae12 100644
--- a/src/text-editor-element.coffee
+++ b/src/text-editor-element.coffee
@@ -108,7 +108,10 @@ class TextEditorElement extends HTMLElement
buildModel: ->
@setModel(@workspace.buildTextEditor(
- buffer: new TextBuffer(@textContent)
+ buffer: new TextBuffer({
+ text: @textContent
+ shouldDestroyOnFileDelete:
+ -> atom.config.get('core.closeDeletedFileTabs')})
softWrapped: false
tabLength: 2
softTabs: true
diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee
index 3c4739ca5..1106cee09 100644
--- a/src/text-editor-presenter.coffee
+++ b/src/text-editor-presenter.coffee
@@ -622,6 +622,18 @@ class TextEditorPresenter
return unless @scrollTop? and @lineHeight?
@startRow = Math.max(0, @lineTopIndex.rowForPixelPosition(@scrollTop))
+ atom.assert(
+ Number.isFinite(@startRow),
+ 'Invalid start row',
+ (error) =>
+ error.metadata = {
+ startRow: @startRow?.toString(),
+ scrollTop: @scrollTop?.toString(),
+ scrollHeight: @scrollHeight?.toString(),
+ clientHeight: @clientHeight?.toString(),
+ lineHeight: @lineHeight?.toString()
+ }
+ )
updateEndRow: ->
return unless @scrollTop? and @lineHeight? and @height?
diff --git a/src/text-editor-registry.js b/src/text-editor-registry.js
index b29a3887c..30600ff08 100644
--- a/src/text-editor-registry.js
+++ b/src/text-editor-registry.js
@@ -11,6 +11,7 @@ const EDITOR_PARAMS_BY_SETTING_KEY = [
['editor.showInvisibles', 'showInvisibles'],
['editor.tabLength', 'tabLength'],
['editor.invisibles', 'invisibles'],
+ ['editor.showCursorOnSelection', 'showCursorOnSelection'],
['editor.showIndentGuide', 'showIndentGuide'],
['editor.showLineNumbers', 'showLineNumbers'],
['editor.softWrap', 'softWrapped'],
diff --git a/src/text-editor.coffee b/src/text-editor.coffee
index 821322e22..13b8a0bfa 100644
--- a/src/text-editor.coffee
+++ b/src/text-editor.coffee
@@ -16,12 +16,11 @@ TextEditorElement = require './text-editor-element'
{isDoubleWidthCharacter, isHalfWidthCharacter, isKoreanCharacter, isWrapBoundary} = require './text-utils'
ZERO_WIDTH_NBSP = '\ufeff'
+MAX_SCREEN_LINE_LENGTH = 500
# Essential: This class represents all essential editing state for a single
# {TextBuffer}, including cursor and selection positions, folds, and soft wraps.
-# If you're manipulating the state of an editor, use this class. If you're
-# interested in the visual appearance of editors, use {TextEditorElement}
-# instead.
+# If you're manipulating the state of an editor, use this class.
#
# A single {TextBuffer} can belong to multiple editors. For example, if the
# same file is open in two different panes, Atom creates a separate editor for
@@ -67,6 +66,7 @@ class TextEditor extends Model
buffer: null
languageMode: null
cursors: null
+ showCursorOnSelection: null
selections: null
suppressSelectionMerging: false
selectionFlashDuration: 500
@@ -133,7 +133,8 @@ class TextEditor extends Model
@mini, @placeholderText, lineNumberGutterVisible, @largeFileMode,
@assert, grammar, @showInvisibles, @autoHeight, @autoWidth, @scrollPastEnd, @editorWidthInChars,
@tokenizedBuffer, @displayLayer, @invisibles, @showIndentGuide,
- @softWrapped, @softWrapAtPreferredLineLength, @preferredLineLength
+ @softWrapped, @softWrapAtPreferredLineLength, @preferredLineLength,
+ @showCursorOnSelection
} = params
@assert ?= (condition) -> condition
@@ -153,13 +154,15 @@ class TextEditor extends Model
tabLength ?= 2
@autoIndent ?= true
@autoIndentOnPaste ?= true
+ @showCursorOnSelection ?= true
@undoGroupingInterval ?= 300
@nonWordCharacters ?= "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-…"
@softWrapped ?= false
@softWrapAtPreferredLineLength ?= false
@preferredLineLength ?= 80
- @buffer ?= new TextBuffer
+ @buffer ?= new TextBuffer({shouldDestroyOnFileDelete: ->
+ atom.config.get('core.closeDeletedFileTabs')})
@tokenizedBuffer ?= new TokenizedBuffer({
grammar, tabLength, @buffer, @largeFileMode, @assert
})
@@ -190,8 +193,9 @@ class TextEditor extends Model
@displayLayer.setTextDecorationLayer(@tokenizedBuffer)
@defaultMarkerLayer = @displayLayer.addMarkerLayer()
@selectionsMarkerLayer ?= @addMarkerLayer(maintainHistory: true, persistent: true)
+ @selectionsMarkerLayer.trackDestructionInOnDidCreateMarkerCallbacks = true
- @decorationManager = new DecorationManager(@displayLayer, @defaultMarkerLayer)
+ @decorationManager = new DecorationManager(@displayLayer)
@decorateMarkerLayer(@displayLayer.foldsMarkerLayer, {type: 'line-number', class: 'folded'})
for marker in @selectionsMarkerLayer.getMarkers()
@@ -342,6 +346,12 @@ class TextEditor extends Model
if value isnt @autoWidth
@autoWidth = value
@presenter?.didChangeAutoWidth()
+
+ when 'showCursorOnSelection'
+ if value isnt @showCursorOnSelection
+ @showCursorOnSelection = value
+ cursor.setShowCursorOnSelection(value) for cursor in @getCursors()
+
else
throw new TypeError("Invalid TextEditor parameter: '#{param}'")
@@ -722,7 +732,7 @@ class TextEditor extends Model
tabLength: @tokenizedBuffer.getTabLength(),
@firstVisibleScreenRow, @firstVisibleScreenColumn,
@assert, displayLayer, grammar: @getGrammar(),
- @autoWidth, @autoHeight
+ @autoWidth, @autoHeight, @showCursorOnSelection
})
# Controls visibility based on the given {Boolean}.
@@ -892,7 +902,7 @@ class TextEditor extends Model
# Determine whether the user should be prompted to save before closing
# this editor.
shouldPromptToSave: ({windowCloseRequested, projectHasPaths}={}) ->
- if windowCloseRequested and projectHasPaths
+ if windowCloseRequested and projectHasPaths and atom.stateStore.isConnected()
false
else
@isModified() and not @buffer.hasMultipleEditors()
@@ -2269,13 +2279,12 @@ class TextEditor extends Model
# Add a cursor based on the given {DisplayMarker}.
addCursor: (marker) ->
- cursor = new Cursor(editor: this, marker: marker)
+ cursor = new Cursor(editor: this, marker: marker, showCursorOnSelection: @showCursorOnSelection)
@cursors.push(cursor)
@cursorsByMarkerId.set(marker.id, cursor)
@decorateMarker(marker, type: 'line-number', class: 'cursor-line')
@decorateMarker(marker, type: 'line-number', class: 'cursor-line-no-selection', onlyHead: true, onlyEmpty: true)
@decorateMarker(marker, type: 'line', class: 'cursor-line', onlyEmpty: true)
- @emitter.emit 'did-add-cursor', cursor
cursor
moveCursors: (fn) ->
@@ -2764,6 +2773,7 @@ class TextEditor extends Model
if selection.intersectsBufferRange(selectionBufferRange)
return selection
else
+ @emitter.emit 'did-add-cursor', cursor
@emitter.emit 'did-add-selection', selection
selection
@@ -2956,7 +2966,7 @@ class TextEditor extends Model
else
@getEditorWidthInChars()
else
- Infinity
+ MAX_SCREEN_LINE_LENGTH
###
Section: Indentation
@@ -3466,6 +3476,11 @@ class TextEditor extends Model
# Returns a positive {Number}.
getScrollSensitivity: -> @scrollSensitivity
+ # Experimental: Does this editor show cursors while there is a selection?
+ #
+ # Returns a positive {Boolean}.
+ getShowCursorOnSelection: -> @showCursorOnSelection
+
# Experimental: Are line numbers enabled for this editor?
#
# Returns a {Boolean}
diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee
index 234f82be9..77221f52e 100644
--- a/src/tokenized-buffer.coffee
+++ b/src/tokenized-buffer.coffee
@@ -8,6 +8,8 @@ ScopeDescriptor = require './scope-descriptor'
TokenizedBufferIterator = require './tokenized-buffer-iterator'
NullGrammar = require './null-grammar'
+MAX_LINE_LENGTH_TO_TOKENIZE = 500
+
module.exports =
class TokenizedBuffer extends Model
grammar: null
@@ -251,6 +253,8 @@ class TokenizedBuffer extends Model
buildTokenizedLineForRowWithText: (row, text, ruleStack = @stackForRow(row - 1), openScopes = @openScopesForRow(row)) ->
lineEnding = @buffer.lineEndingForRow(row)
+ if text.length > MAX_LINE_LENGTH_TO_TOKENIZE
+ text = text.slice(0, MAX_LINE_LENGTH_TO_TOKENIZE)
{tags, ruleStack} = @grammar.tokenizeLine(text, ruleStack, row is 0, false)
new TokenizedLine({openScopes, text, tags, ruleStack, lineEnding, @tokenIterator})
diff --git a/src/window-load-settings-helpers.coffee b/src/window-load-settings-helpers.coffee
deleted file mode 100644
index 639dc751d..000000000
--- a/src/window-load-settings-helpers.coffee
+++ /dev/null
@@ -1,8 +0,0 @@
-windowLoadSettings = null
-
-exports.getWindowLoadSettings = ->
- windowLoadSettings ?= JSON.parse(window.decodeURIComponent(window.location.hash.substr(1)))
-
-exports.setWindowLoadSettings = (settings) ->
- windowLoadSettings = settings
- location.hash = encodeURIComponent(JSON.stringify(settings))
diff --git a/src/workspace-element.coffee b/src/workspace-element.coffee
index be0af81ed..f598bef0b 100644
--- a/src/workspace-element.coffee
+++ b/src/workspace-element.coffee
@@ -102,7 +102,7 @@ class WorkspaceElement extends HTMLElement
getModel: -> @model
handleMousewheel: (event) ->
- if event.ctrlKey and @config.get('editor.zoomFontWhenCtrlScrolling') and event.target.matches('atom-text-editor')
+ if event.ctrlKey and @config.get('editor.zoomFontWhenCtrlScrolling') and event.target.closest('atom-text-editor')?
if event.wheelDeltaY > 0
@model.increaseFontSize()
else if event.wheelDeltaY < 0
diff --git a/src/workspace.coffee b/src/workspace.coffee
index 9871db224..2a46ce57a 100644
--- a/src/workspace.coffee
+++ b/src/workspace.coffee
@@ -182,7 +182,7 @@ class Workspace extends Model
projectPath = _.find projectPaths, (projectPath) ->
itemPath is projectPath or itemPath?.startsWith(projectPath + path.sep)
itemTitle ?= "untitled"
- projectPath ?= if itemPath then path.dirname(itemPath) else null
+ projectPath ?= if itemPath then path.dirname(itemPath) else projectPaths[0]
if projectPath?
projectPath = fs.tildify(projectPath)
diff --git a/static/babelrc.json b/static/babelrc.json
index 26b70dc41..11474dd8d 100644
--- a/static/babelrc.json
+++ b/static/babelrc.json
@@ -1,7 +1,16 @@
{
- "breakConfig": true,
"sourceMap": "inline",
- "blacklist": ["es6.forOf", "useStrict"],
- "optional": ["asyncToGenerator"],
- "stage": 0
+ "plugins": [
+ ["add-module-exports", {}],
+ ["transform-async-to-generator", {}],
+ ["transform-decorators-legacy", {}],
+ ["transform-class-properties", {}],
+ ["transform-es2015-modules-commonjs", {"strictMode": false}],
+ ["transform-export-extensions", {}],
+ ["transform-do-expressions", {}],
+ ["transform-function-bind", {}],
+ ["transform-object-rest-spread", {}],
+ ["transform-flow-strip-types", {}],
+ ["transform-react-jsx", {}]
+ ]
}
diff --git a/static/index.js b/static/index.js
index 2966eafdf..aa57a594a 100644
--- a/static/index.js
+++ b/static/index.js
@@ -2,9 +2,8 @@
var path = require('path')
var FileSystemBlobStore = require('../src/file-system-blob-store')
var NativeCompileCache = require('../src/native-compile-cache')
+ var getWindowLoadSettings = require('../src/get-window-load-settings')
- var loadSettings = null
- var loadSettingsError = null
var blobStore = null
window.onload = function () {
@@ -25,20 +24,16 @@
// Normalize to make sure drive letter case is consistent on Windows
process.resourcesPath = path.normalize(process.resourcesPath)
- if (loadSettingsError) {
- throw loadSettingsError
- }
-
- var devMode = loadSettings.devMode || !loadSettings.resourcePath.startsWith(process.resourcesPath + path.sep)
+ var devMode = getWindowLoadSettings().devMode || !getWindowLoadSettings().resourcePath.startsWith(process.resourcesPath + path.sep)
if (devMode) {
setupDeprecatedPackages()
}
- if (loadSettings.profileStartup) {
- profileStartup(loadSettings, Date.now() - startTime)
+ if (getWindowLoadSettings().profileStartup) {
+ profileStartup(Date.now() - startTime)
} else {
- setupWindow(loadSettings)
+ setupWindow()
setLoadTime(Date.now() - startTime)
}
} catch (error) {
@@ -61,23 +56,23 @@
console.error(error.stack || error)
}
- function setupWindow (loadSettings) {
+ function setupWindow () {
var CompileCache = require('../src/compile-cache')
CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME)
var ModuleCache = require('../src/module-cache')
- ModuleCache.register(loadSettings)
- ModuleCache.add(loadSettings.resourcePath)
+ ModuleCache.register(getWindowLoadSettings())
+ ModuleCache.add(getWindowLoadSettings().resourcePath)
// By explicitly passing the app version here, we could save the call
// of "require('remote').require('app').getVersion()".
var startCrashReporter = require('../src/crash-reporter-start')
- startCrashReporter({_version: loadSettings.appVersion})
+ startCrashReporter({_version: getWindowLoadSettings().appVersion})
setupVmCompatibility()
setupCsonCache(CompileCache.getCacheDirectory())
- var initialize = require(loadSettings.windowInitializationScript)
+ var initialize = require(getWindowLoadSettings().windowInitializationScript)
return initialize({blobStore: blobStore}).then(function () {
require('electron').ipcRenderer.send('window-command', 'window:loaded')
})
@@ -105,11 +100,11 @@
}
}
- function profileStartup (loadSettings, initialTime) {
+ function profileStartup (initialTime) {
function profile () {
console.profile('startup')
var startTime = Date.now()
- setupWindow(loadSettings).then(function () {
+ setupWindow().then(function () {
setLoadTime(Date.now() - startTime + initialTime)
console.profileEnd('startup')
console.log('Switch to the Profiles tab to view the created startup profile')
@@ -125,16 +120,6 @@
}
}
- function parseLoadSettings () {
- var rawLoadSettings = decodeURIComponent(window.location.hash.substr(1))
- try {
- loadSettings = JSON.parse(rawLoadSettings)
- } catch (error) {
- console.error('Failed to parse load settings: ' + rawLoadSettings)
- loadSettingsError = error
- }
- }
-
var setupAtomHome = function () {
if (process.env.ATOM_HOME) {
return
@@ -143,11 +128,10 @@
// Ensure ATOM_HOME is always set before anything else is required
// This is because of a difference in Linux not inherited between browser and render processes
// https://github.com/atom/atom/issues/5412
- if (loadSettings && loadSettings.atomHome) {
- process.env.ATOM_HOME = loadSettings.atomHome
+ if (getWindowLoadSettings() && getWindowLoadSettings().atomHome) {
+ process.env.ATOM_HOME = getWindowLoadSettings().atomHome
}
}
- parseLoadSettings()
setupAtomHome()
})()