Merge branch 'master' into sm-custom-title-bar

This commit is contained in:
simurai
2017-02-21 21:56:53 +09:00
committed by GitHub
73 changed files with 1271 additions and 501 deletions

View File

@@ -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:

View File

@@ -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)

View File

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

View File

@@ -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

View File

@@ -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()

View File

@@ -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))
}

View File

@@ -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:

View File

@@ -71,6 +71,7 @@
| [PathWatcher](https://github.com/atom/node-pathwatcher) | [![macOS Build Status](https://travis-ci.org/atom/node-pathwatcher.svg?branch=master)](https://travis-ci.org/atom/node-pathwatcher) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/li8dkoucdrc2ryts/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/node-pathwatcher) | [![Dependency Status](https://david-dm.org/atom/node-pathwatcher/status.svg)](https://david-dm.org/atom/node-pathwatcher) |
| [Property Accessors](https://github.com/atom/property-accessors) | [![macOS Build Status](https://travis-ci.org/atom/property-accessors.svg?branch=master)](https://travis-ci.org/atom/property-accessors) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/ww4d10hi4v5h7kbp/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/property-accessors/branch/master) | [![Dependency Status](https://david-dm.org/atom/property-accessors.svg)](https://david-dm.org/atom/property-accessors) |
| [Season](https://github.com/atom/season) | [![macOS Build Status](https://travis-ci.org/atom/season.svg?branch=master)](https://travis-ci.org/atom/season) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/v3bth3ooq5q8k8lx/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/season) | [![Dependency Status](https://david-dm.org/atom/season.svg)](https://david-dm.org/atom/season) |
| [Superstring](https://github.com/atom/superstring) | [![macOS Build Status](https://travis-ci.org/atom/superstring.svg?branch=master)](https://travis-ci.org/atom/superstring) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/n5pack4yk7w80fso/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/superstring/branch/master) | | [![Dependency Status](https://david-dm.org/atom/superstring.svg)](https://david-dm.org/atom/superstring) |
| [TextBuffer](https://github.com/atom/text-buffer) | [![macOS Build Status](https://travis-ci.org/atom/text-buffer.svg?branch=master)](https://travis-ci.org/atom/text-buffer) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/48xl8do1sm2thf5p/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/text-buffer/branch/master) | [![Dependency Status](https://david-dm.org/atom/text-buffer.svg)](https://david-dm.org/atom/text-buffer) |
| [Underscore-Plus](https://github.com/atom/underscore-plus) | [![macOS Build Status](https://travis-ci.org/atom/underscore-plus.svg?branch=master)](https://travis-ci.org/atom/underscore-plus) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/c7l8009vgpaojxcd/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/underscore-plus/branch/master) | [![Dependency Status](https://david-dm.org/atom/underscore-plus.svg)](https://david-dm.org/atom/underscore-plus) |

View File

@@ -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).

View File

@@ -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)

View File

@@ -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

View File

@@ -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": {

View File

@@ -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 %>

View File

@@ -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)
}
}
}

View File

@@ -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)
}

View File

@@ -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",

View File

@@ -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")

View File

@@ -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'))

View File

@@ -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 = ''

View File

@@ -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'

View File

@@ -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")

View File

@@ -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()

View File

@@ -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('<div>testing</div>')
})
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()
})
})

View File

@@ -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 """

View File

@@ -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)

View File

@@ -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))

View File

@@ -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()

View File

@@ -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"

View File

@@ -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'})
})

View File

@@ -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()

View File

@@ -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]]

View File

@@ -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)

View File

@@ -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 ->

View File

@@ -4,6 +4,7 @@ temp = require('temp').track()
describe "atom.themes", ->
beforeEach ->
spyOn(atom, 'inSpecMode').andReturn(false)
spyOn(console, 'warn')
afterEach ->

View File

@@ -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
}))

View File

@@ -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", ->

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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.

View File

@@ -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())

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

89
src/dom-element-pool.js Normal file
View File

@@ -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)
}
}
}

View File

@@ -0,0 +1,10 @@
const {remote} = require('electron')
let windowLoadSettings = null
module.exports = () => {
if (!windowLoadSettings) {
windowLoadSettings = remote.getCurrentWindow().loadSettings
}
return windowLoadSettings
}

View File

@@ -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)) {

View File

@@ -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'

View File

@@ -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')

View File

@@ -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'

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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}"

View File

@@ -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
}

View File

@@ -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}"

View File

@@ -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)

View File

@@ -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'),

View File

@@ -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)
}

View File

@@ -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}
}

View File

@@ -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

View File

@@ -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?

View File

@@ -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'],

View File

@@ -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}

View File

@@ -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})

View File

@@ -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))

View File

@@ -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

View File

@@ -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)

View File

@@ -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", {}]
]
}

View File

@@ -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()
})()