Merge branch 'master' into dh-async-repo

This commit is contained in:
joshaber
2015-11-17 11:39:22 -08:00
36 changed files with 609 additions and 176 deletions

View File

@@ -1 +0,0 @@
v0.12.0

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
cache = ~/.atom/.npm

View File

@@ -98,7 +98,7 @@ Explain the problem and include additional details to help maintainers reproduce
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
* **Explain which behavior you expected to see instead and why.**
* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. If you use the keyboard while following the steps, **record the GIF with the [Keybinding Resolver](https://github.com/atom/keybinding-resolver) shown**. You can use [this tool](http://www.cockos.com/licecap/) to record GIFs on OSX and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
* **If you're reporting that Atom crashed**, include a crash report with a stack trace from the operating system. On OSX, the crash report will be available in `Console.app` under "Diagnostic and usage information" > "User diagnostic reports". Include the crash report in the issue in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines) or put it in a [gist](https://gist.github.com/) and provide link to that gist.
* **If you're reporting that Atom crashed**, include a crash report with a stack trace from the operating system. On OSX, the crash report will be available in `Console.app` under "Diagnostic and usage information" > "User diagnostic reports". Include the crash report in the issue in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines), a [file attachment](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests/), or put it in a [gist](https://gist.github.com/) and provide link to that gist.
* **If the problem is related to performance**, include a [CPU profile capture and a screenshot](https://atom.io/docs/latest/hacking-atom-debugging#diagnose-performance-problems-with-the-dev-tools-cpu-profiler) with your report.
* **If the Chrome's developer tools pane is shown without you triggering it**, that normally means that an exception was thrown. The Console tab will include an entry for the exception. Expand the exception so that the stack trace is visible, and provide the full exception and stack trace in a [code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines) and as a screenshot.
* **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below.

View File

@@ -1,6 +1,6 @@
![Atom](https://cloud.githubusercontent.com/assets/72919/2874231/3af1db48-d3dd-11e3-98dc-6066f8bc766f.png)
[![Build Status](https://travis-ci.org/atom/atom.svg?branch=master)](https://travis-ci.org/atom/atom)
[![Build Status](https://travis-ci.org/atom/atom.svg?branch=master)](https://travis-ci.org/atom/atom) [![Build status](https://ci.appveyor.com/api/projects/status/1tkktwh654w07eim?svg=true)](https://ci.appveyor.com/project/Atom/atom)
[![Dependency Status](https://david-dm.org/atom/atom.svg)](https://david-dm.org/atom/atom)
[![Join the Atom Community on Slack](http://atom-slack.herokuapp.com/badge.svg)](http://atom-slack.herokuapp.com/)

1
apm/.npmrc Normal file
View File

@@ -0,0 +1 @@
cache = ~/.atom/.npm

35
appveyor.yml Normal file
View File

@@ -0,0 +1,35 @@
version: "{build}"
skip_tags: true
clone_folder: c:\projects\atom
clone_depth: 10
platform:
- x86
environment:
global:
ATOM_DEV_RESOURCE_PATH: c:\projects\atom
ATOM_ACCESS_TOKEN:
secure: Q7vxmSq0bVCLTTRPzXw5ZhPTe7XYhWxX0tQV6neEkddTH6pZkOYNmSCG6VnMX2f+
matrix:
- NODE_VERSION: 0.10.35
install:
- SET PATH=C:\Program Files\Atom\resources\cli;%PATH%
- ps: Install-Product node $env:NODE_VERSION $env:PLATFORM
build_script:
- cd %APPVEYOR_BUILD_FOLDER%
- C:\projects\atom\script\cibuild.cmd
test: off
deploy: off
artifacts:
- path: atom-build
cache:
- '%USERPROFILE%\.atom\.apm'
- '%USERPROFILE%\.atom\.node-gyp\.atom'
- '%USERPROFILE%\.atom\.npm'

View File

@@ -39,7 +39,16 @@ module.exports = (grunt) ->
disableAutoUpdate = grunt.option('no-auto-update') ? false
channel = grunt.option('channel')
channel ?= process.env.JANKY_BRANCH if process.env.JANKY_BRANCH in ['stable', 'beta']
releasableBranches = ['stable', 'beta']
if process.env.APPVEYOR and not process.env.APPVEYOR_PULL_REQUEST_NUMBER
channel ?= process.env.APPVEYOR_REPO_BRANCH if process.env.APPVEYOR_REPO_BRANCH in releasableBranches
if process.env.TRAVIS and not process.env.TRAVIS_PULL_REQUEST
channel ?= process.env.TRAVIS_BRANCH if process.env.TRAVIS_BRANCH in releasableBranches
if process.env.JANKY_BRANCH
channel ?= process.env.JANKY_BRANCH if process.env.JANKY_BRANCH in releasableBranches
channel ?= 'dev'
metadata = packageJson
@@ -270,16 +279,20 @@ module.exports = (grunt) ->
grunt.registerTask('lint', ['standard', 'coffeelint', 'csslint', 'lesslint'])
grunt.registerTask('test', ['shell:kill-atom', 'run-specs'])
ciTasks = ['output-disk-space', 'download-electron', 'download-electron-chromedriver', 'build']
ciTasks = []
ciTasks.push('output-disk-space') unless process.env.CI
ciTasks.push('download-electron')
ciTasks.push('download-electron-chromedriver')
ciTasks.push('build')
ciTasks.push('dump-symbols') if process.platform isnt 'win32'
ciTasks.push('set-version', 'check-licenses', 'lint', 'generate-asar')
ciTasks.push('mkdeb') if process.platform is 'linux'
ciTasks.push('codesign:exe') if process.platform is 'win32' and not process.env.TRAVIS
ciTasks.push('codesign:exe') if process.platform is 'win32' and not process.env.CI
ciTasks.push('create-windows-installer:installer') if process.platform is 'win32'
ciTasks.push('test') if process.platform is 'darwin'
ciTasks.push('codesign:installer') if process.platform is 'win32' and not process.env.TRAVIS
ciTasks.push('codesign:app') if process.platform is 'darwin' and not process.env.TRAVIS
ciTasks.push('publish-build') unless process.env.TRAVIS
ciTasks.push('codesign:installer') if process.platform is 'win32' and not process.env.CI
ciTasks.push('codesign:app') if process.platform is 'darwin' and not process.env.CI
ciTasks.push('publish-build') unless process.env.CI
grunt.registerTask('ci', ciTasks)
defaultTasks = ['download-electron', 'download-electron-chromedriver', 'build', 'set-version', 'generate-asar']

View File

@@ -9,7 +9,6 @@ request = require 'request'
grunt = null
commitSha = process.env.JANKY_SHA1
token = process.env.ATOM_ACCESS_TOKEN
defaultHeaders =
Authorization: "token #{token}"
@@ -31,8 +30,8 @@ module.exports = (gruntObject) ->
cp path.join(docsOutputDir, 'api.json'), path.join(buildDir, 'atom-api.json')
grunt.registerTask 'upload-assets', 'Upload the assets to a GitHub release', ->
branchName = process.env.JANKY_BRANCH
switch branchName
channel = grunt.config.get('atom.channel')
switch channel
when 'stable'
isPrerelease = false
when 'beta'
@@ -55,7 +54,7 @@ module.exports = (gruntObject) ->
zipAssets buildDir, assets, (error) ->
return done(error) if error?
getAtomDraftRelease isPrerelease, branchName, (error, release) ->
getAtomDraftRelease isPrerelease, channel, (error, release) ->
return done(error) if error?
assetNames = (asset.assetName for asset in assets)
deleteExistingAssets release, assetNames, (error) ->

View File

@@ -5,18 +5,20 @@ module.exports = (grunt) ->
{spawn} = require('./task-helpers')(grunt)
getVersion = (callback) ->
onBuildMachine = process.env.JANKY_SHA1 and process.env.JANKY_BRANCH in ['stable', 'beta']
releasableBranches = ['stable', 'beta']
channel = grunt.config.get('atom.channel')
shouldUseCommitHash = if channel in releasableBranches then false else true
inRepository = fs.existsSync(path.resolve(__dirname, '..', '..', '.git'))
{version} = require(path.join(grunt.config.get('atom.appDir'), 'package.json'))
if onBuildMachine or not inRepository
callback(null, version)
else
if shouldUseCommitHash and inRepository
cmd = 'git'
args = ['rev-parse', '--short', 'HEAD']
spawn {cmd, args}, (error, {stdout}={}, code) ->
commitHash = stdout?.trim?()
combinedVersion = "#{version}-#{commitHash}"
callback(error, combinedVersion)
else
callback(null, version)
grunt.registerTask 'set-version', 'Set the version in the plist and package.json', ->
done = @async()

View File

@@ -6,8 +6,7 @@ async = require 'async'
# TODO: This should really be parallel on every platform, however:
# - On Windows, our fixtures step on each others toes.
# - On Travis, Mac workers haven't enough horsepower.
if process.env.TRAVIS or process.platform is 'win32'
if process.platform is 'win32'
concurrency = 1
else
concurrency = 2

View File

@@ -1,7 +1,7 @@
{
"name": "atom",
"productName": "Atom",
"version": "1.3.0-dev",
"version": "1.4.0-dev",
"description": "A hackable text editor for the 21st Century.",
"main": "./src/browser/main.js",
"repository": {
@@ -15,7 +15,7 @@
"electronVersion": "0.34.3",
"dependencies": {
"async": "0.2.6",
"atom-keymap": "^6.1.0",
"atom-keymap": "^6.1.1",
"babel-core": "^5.8.21",
"bootstrap": "^3.3.4",
"cached-run-in-this-context": "0.4.0",
@@ -53,7 +53,7 @@
"service-hub": "^0.7.0",
"source-map-support": "^0.3.2",
"temp": "0.8.1",
"text-buffer": "^8.0.3",
"text-buffer": "^8.0.4",
"typescript-simple": "1.0.0",
"underscore-plus": "^1.6.6",
"yargs": "^3.23.0"
@@ -98,7 +98,7 @@
"keybinding-resolver": "0.33.0",
"line-ending-selector": "0.3.0",
"link": "0.31.0",
"markdown-preview": "0.156.1",
"markdown-preview": "0.156.2",
"metrics": "0.53.0",
"notifications": "0.61.0",
"open-on-github": "0.40.0",
@@ -109,12 +109,12 @@
"spell-check": "0.63.0",
"status-bar": "0.80.0",
"styleguide": "0.45.0",
"symbols-view": "0.110.0",
"symbols-view": "0.110.1",
"tabs": "0.88.0",
"timecop": "0.33.0",
"tree-view": "0.198.0",
"update-package-dependencies": "0.10.0",
"welcome": "0.32.0",
"welcome": "0.33.0",
"whitespace": "0.32.0",
"wrap-guide": "0.38.1",
"language-c": "0.49.0",
@@ -134,11 +134,11 @@
"language-make": "0.20.0",
"language-mustache": "0.13.0",
"language-objective-c": "0.15.0",
"language-perl": "0.30.0",
"language-perl": "0.31.0",
"language-php": "0.34.0",
"language-property-list": "0.8.0",
"language-python": "0.41.0",
"language-ruby": "0.61.0",
"language-ruby": "0.62.0",
"language-ruby-on-rails": "0.24.0",
"language-sass": "0.43.0",
"language-shellscript": "0.20.0",

View File

@@ -214,6 +214,20 @@
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>COMMIT_EDITMSG</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>file.icns</string>
<key>CFBundleTypeName</key>
<string>Commit message</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
@@ -228,6 +242,21 @@
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>clj</string>
<string>cljs</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>file.icns</string>
<key>CFBundleTypeName</key>
<string>Clojure source</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
@@ -422,6 +451,20 @@
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>go</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>file.icns</string>
<key>CFBundleTypeName</key>
<string>Go source</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
@@ -624,6 +667,20 @@
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>less</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>file.icns</string>
<key>CFBundleTypeName</key>
<string>Less source</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
@@ -702,6 +759,20 @@
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>mk</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>file.icns</string>
<key>CFBundleTypeName</key>
<string>Makefile source</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
@@ -809,6 +880,21 @@
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>mustache</string>
<string>hbs</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>file.icns</string>
<key>CFBundleTypeName</key>
<string>Mustache document</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
@@ -998,6 +1084,7 @@
<key>CFBundleTypeExtensions</key>
<array>
<string>rhtml</string>
<string>erb</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>file.icns</string>
@@ -1039,6 +1126,21 @@
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>sass</string>
<string>scss</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>file.icns</string>
<key>CFBundleTypeName</key>
<string>Sass source</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
@@ -1243,6 +1345,20 @@
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>toml</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>file.icns</string>
<key>CFBundleTypeName</key>
<string>TOML file</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>

3
resources/win/apm.cmd Normal file
View File

@@ -0,0 +1,3 @@
@echo off
"%~dp0\..\app\apm\bin\node.exe" "%~dp0\..\app\apm\lib\cli.js" %*

View File

@@ -22,12 +22,18 @@ function loadEnvironmentVariables(filePath) {
}
function readEnvironmentVariables() {
if (process.platform === 'win32') {
loadEnvironmentVariables(path.resolve('/jenkins/config/atomcredentials'));
} else if (process.platform === 'darwin') {
loadEnvironmentVariables('/var/lib/jenkins/config/atomcredentials');
loadEnvironmentVariables('/var/lib/jenkins/config/xcodekeychain');
} else if (process.platform === 'linux') {
if (process.env.JANKY_SHA1) {
if (process.platform === 'win32') {
loadEnvironmentVariables(path.resolve('/jenkins/config/atomcredentials'));
} else if (process.platform === 'darwin') {
loadEnvironmentVariables('/var/lib/jenkins/config/atomcredentials');
loadEnvironmentVariables('/var/lib/jenkins/config/xcodekeychain');
}
}
}
function setEnvironmentVariables() {
if (process.platform === 'linux') {
// Use Clang for building native code, the GCC on Precise is too old.
process.env.CC = 'clang';
process.env.CXX = 'clang++';
@@ -81,6 +87,7 @@ function removeTempFolders() {
}
readEnvironmentVariables();
setEnvironmentVariables();
removeNodeModules();
removeTempFolders();
cp.safeExec.bind(global, 'npm install npm --loglevel error', {cwd: path.resolve(__dirname, '..', 'build')}, function() {

5
script/cibuild.cmd Normal file
View File

@@ -0,0 +1,5 @@
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\cibuild" %*
) ELSE (
node "%~dp0\cibuild" %*
)

View File

@@ -20,10 +20,45 @@ describe "CommandInstaller on #darwin", ->
spyOn(CommandInstaller::, 'getResourcesDirectory').andReturn(resourcesPath)
spyOn(CommandInstaller::, 'getInstallDirectory').andReturn(installationPath)
it "shows an error dialog when installing commands interactively fails", ->
appDelegate = jasmine.createSpyObj("appDelegate", ["confirm"])
installer = new CommandInstaller("2.0.2", appDelegate)
spyOn(installer, "installAtomCommand").andCallFake (__, callback) -> callback(new Error("an error"))
installer.installShellCommandsInteractively()
expect(appDelegate.confirm).toHaveBeenCalledWith({
message: "Failed to install shell commands"
detailedMessage: "an error"
})
appDelegate.confirm.reset()
installer.installAtomCommand.andCallFake (__, callback) -> callback()
spyOn(installer, "installApmCommand").andCallFake (__, callback) -> callback(new Error("another error"))
installer.installShellCommandsInteractively()
expect(appDelegate.confirm).toHaveBeenCalledWith({
message: "Failed to install shell commands"
detailedMessage: "another error"
})
it "shows a success dialog when installing commands interactively succeeds", ->
appDelegate = jasmine.createSpyObj("appDelegate", ["confirm"])
installer = new CommandInstaller("2.0.2", appDelegate)
spyOn(installer, "installAtomCommand").andCallFake (__, callback) -> callback()
spyOn(installer, "installApmCommand").andCallFake (__, callback) -> callback()
installer.installShellCommandsInteractively()
expect(appDelegate.confirm).toHaveBeenCalledWith({
message: "Commands installed."
detailedMessage: "The shell commands `atom` and `apm` are installed."
})
describe "when using a stable version of atom", ->
beforeEach ->
confirm = ->
installer = new CommandInstaller("2.0.2", confirm)
installer = new CommandInstaller("2.0.2")
it "symlinks the atom command as 'atom'", ->
installedAtomPath = path.join(installationPath, 'atom')

View File

@@ -589,6 +589,59 @@ describe "Config", ->
atom.config.transact ->
expect(changeSpy).not.toHaveBeenCalled()
describe ".transactAsync(callback)", ->
changeSpy = null
beforeEach ->
changeSpy = jasmine.createSpy('onDidChange callback')
atom.config.onDidChange("foo.bar.baz", changeSpy)
it "allows only one change event for the duration of the given promise if it gets resolved", ->
promiseResult = null
transactionPromise = atom.config.transactAsync ->
atom.config.set("foo.bar.baz", 1)
atom.config.set("foo.bar.baz", 2)
atom.config.set("foo.bar.baz", 3)
Promise.resolve("a result")
waitsForPromise -> transactionPromise.then (r) -> promiseResult = r
runs ->
expect(promiseResult).toBe("a result")
expect(changeSpy.callCount).toBe(1)
expect(changeSpy.argsForCall[0][0]).toEqual(newValue: 3, oldValue: undefined)
it "allows only one change event for the duration of the given promise if it gets rejected", ->
promiseError = null
transactionPromise = atom.config.transactAsync ->
atom.config.set("foo.bar.baz", 1)
atom.config.set("foo.bar.baz", 2)
atom.config.set("foo.bar.baz", 3)
Promise.reject("an error")
waitsForPromise -> transactionPromise.catch (e) -> promiseError = e
runs ->
expect(promiseError).toBe("an error")
expect(changeSpy.callCount).toBe(1)
expect(changeSpy.argsForCall[0][0]).toEqual(newValue: 3, oldValue: undefined)
it "allows only one change event even when the given callback throws", ->
error = new Error("Oops!")
promiseError = null
transactionPromise = atom.config.transactAsync ->
atom.config.set("foo.bar.baz", 1)
atom.config.set("foo.bar.baz", 2)
atom.config.set("foo.bar.baz", 3)
throw error
waitsForPromise -> transactionPromise.catch (e) -> promiseError = e
runs ->
expect(promiseError).toBe(error)
expect(changeSpy.callCount).toBe(1)
expect(changeSpy.argsForCall[0][0]).toEqual(newValue: 3, oldValue: undefined)
describe ".getSources()", ->
it "returns an array of all of the config's source names", ->
expect(atom.config.getSources()).toEqual([])

View File

@@ -142,9 +142,13 @@ describe "the `grammars` global", ->
expect(atom.grammars.selectGrammar('Rakefile', '').scopeName).toBe 'source.coffee'
expect(atom.grammars.selectGrammar('Cakefile', '').scopeName).toBe 'source.ruby'
it "favors grammars with matching first-line-regexps even if custom file types match the file", ->
it "favors user-defined file types over grammars with matching first-line-regexps", ->
atom.config.set('core.customFileTypes', 'source.ruby': ['bootstrap'])
expect(atom.grammars.selectGrammar('bootstrap', '#!/usr/bin/env node').scopeName).toBe 'source.js'
expect(atom.grammars.selectGrammar('bootstrap', '#!/usr/bin/env node').scopeName).toBe 'source.ruby'
describe "when there is a grammar with a first line pattern, the file type of the file is known, but from a different grammar", ->
it "favors file type over the matching pattern", ->
expect(atom.grammars.selectGrammar('foo.rb', '#!/usr/bin/env node').scopeName).toBe 'source.ruby'
describe ".removeGrammar(grammar)", ->
it "removes the grammar, so it won't be returned by selectGrammar", ->

View File

@@ -4,7 +4,7 @@
return unless process.env.ATOM_INTEGRATION_TESTS_ENABLED
# Integration tests require a fast machine and, for now, we cannot afford to
# run them on Travis.
return if process.env.TRAVIS
return if process.env.CI
fs = require "fs-plus"
path = require "path"

View File

@@ -284,6 +284,7 @@ describe "PackageManager", ->
expect(Package.prototype.requireMainModule.callCount).toBe 0
atom.packages.triggerActivationHook('language-fictitious:grammar-used')
atom.packages.triggerDeferredActivationHooks()
waitsForPromise ->
promise
@@ -433,6 +434,13 @@ describe "PackageManager", ->
runs ->
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1)).toHaveLength 0
describe "when setting core.packagesWithKeymapsDisabled", ->
it "ignores package names in the array that aren't loaded", ->
atom.packages.observePackagesWithKeymapsDisabled()
expect(-> atom.config.set("core.packagesWithKeymapsDisabled", ["package-does-not-exist"])).not.toThrow()
expect(-> atom.config.set("core.packagesWithKeymapsDisabled", [])).not.toThrow()
describe "when the package's keymaps are disabled and re-enabled after it is activated", ->
it "removes and re-adds the keymaps", ->
element1 = createTestElement('test-1')

View File

@@ -168,6 +168,15 @@ describe "Pane", ->
pane.activateNextItem()
expect(pane.getActiveItem()).toBe item1
describe "::activateLastItem()", ->
it "sets the active item to the last item", ->
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
[item1, item2, item3] = pane.getItems()
expect(pane.getActiveItem()).toBe item1
pane.activateLastItem()
expect(pane.getActiveItem()).toBe item3
describe "::moveItemRight() and ::moveItemLeft()", ->
it "moves the active item to the right and left, without looping around at either end", ->
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))

View File

@@ -25,27 +25,34 @@ describe "TextEditorPresenter", ->
editor.destroy()
buffer.destroy()
buildPresenter = (params={}) ->
buildPresenterWithoutMeasurements = (params={}) ->
_.defaults params,
model: editor
explicitHeight: 130
contentFrameWidth: 500
windowWidth: 500
windowHeight: 130
boundingClientRect: {left: 0, top: 0, width: 500, height: 130}
gutterWidth: 0
lineHeight: 10
baseCharacterWidth: 10
horizontalScrollbarHeight: 10
verticalScrollbarWidth: 10
scrollTop: 0
scrollLeft: 0
config: atom.config
contentFrameWidth: 500
presenter = new TextEditorPresenter(params)
presenter.setLinesYardstick(new FakeLinesYardstick(editor, presenter))
presenter
buildPresenter = (params={}) ->
presenter = buildPresenterWithoutMeasurements(params)
presenter.setScrollTop(params.scrollTop) if params.scrollTop?
presenter.setScrollLeft(params.scrollLeft) if params.scrollLeft?
presenter.setExplicitHeight(params.explicitHeight ? 130)
presenter.setWindowSize(params.windowWidth ? 500, params.windowHeight ? 130)
presenter.setBoundingClientRect(params.boundingClientRect ? {
left: 0
top: 0
width: 500
height: 130
})
presenter.setGutterWidth(params.gutterWidth ? 0)
presenter.setLineHeight(params.lineHeight ? 10)
presenter.setBaseCharacterWidth(params.baseCharacterWidth ? 10)
presenter.setHorizontalScrollbarHeight(params.horizontalScrollbarHeight ? 10)
presenter.setVerticalScrollbarWidth(params.verticalScrollbarWidth ? 10)
presenter
expectValues = (actual, expected) ->
for key, value of expected
expect(actual[key]).toEqual value
@@ -167,16 +174,14 @@ describe "TextEditorPresenter", ->
expect(stateFn(presenter).tiles[12]).toBeDefined()
it "is empty until all of the required measurements are assigned", ->
presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null)
presenter = buildPresenterWithoutMeasurements()
expect(stateFn(presenter).tiles).toEqual({})
presenter.setExplicitHeight(25)
expect(stateFn(presenter).tiles).toEqual({})
# Sets scroll row from model's logical position
presenter.setLineHeight(10)
expect(stateFn(presenter).tiles).toEqual({})
presenter.setScrollTop(0)
expect(stateFn(presenter).tiles).not.toEqual({})
it "updates when ::scrollTop changes", ->
@@ -619,6 +624,8 @@ describe "TextEditorPresenter", ->
describe ".scrollingVertically", ->
it "is true for ::stoppedScrollingDelay milliseconds following a changes to ::scrollTop", ->
presenter = buildPresenter(scrollTop: 10, stoppedScrollingDelay: 200, explicitHeight: 100)
expect(presenter.getState().content.scrollingVertically).toBe true
advanceClock(300)
expect(presenter.getState().content.scrollingVertically).toBe false
expectStateUpdate presenter, -> presenter.setScrollTop(0)
expect(presenter.getState().content.scrollingVertically).toBe true
@@ -761,7 +768,8 @@ describe "TextEditorPresenter", ->
expect(presenter.getState().content.scrollTop).toBe(10)
it "corresponds to the passed logical coordinates when building the presenter", ->
presenter = buildPresenter(scrollRow: 4, lineHeight: 10, explicitHeight: 20)
editor.setFirstVisibleScreenRow(4)
presenter = buildPresenter(lineHeight: 10, explicitHeight: 20)
expect(presenter.getState().content.scrollTop).toBe(40)
it "tracks the value of ::scrollTop", ->
@@ -775,11 +783,11 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> presenter.setScrollTop(50)
presenter.getState() # commits scroll position
expect(editor.getScrollRow()).toBe(5)
expect(editor.getFirstVisibleScreenRow()).toBe 5
expectStateUpdate presenter, -> presenter.setScrollTop(57)
presenter.getState() # commits scroll position
expect(editor.getScrollRow()).toBe(6)
expect(editor.getFirstVisibleScreenRow()).toBe 6
it "reassigns the scrollTop if it exceeds the max possible value after lines are removed", ->
presenter = buildPresenter(scrollTop: 80, lineHeight: 10, explicitHeight: 50, horizontalScrollbarHeight: 0)
@@ -888,7 +896,8 @@ describe "TextEditorPresenter", ->
expect(presenter.getState().content.scrollLeft).toBe(50)
it "corresponds to the passed logical coordinates when building the presenter", ->
presenter = buildPresenter(scrollColumn: 3, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500)
editor.setFirstVisibleScreenColumn(3)
presenter = buildPresenter(lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500)
expect(presenter.getState().content.scrollLeft).toBe(30)
it "tracks the value of ::scrollLeft", ->
@@ -902,11 +911,11 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> presenter.setScrollLeft(50)
presenter.getState() # commits scroll position
expect(editor.getScrollColumn()).toBe(5)
expect(editor.getFirstVisibleScreenColumn()).toBe 5
expectStateUpdate presenter, -> presenter.setScrollLeft(57)
presenter.getState() # commits scroll position
expect(editor.getScrollColumn()).toBe(6)
expect(editor.getFirstVisibleScreenColumn()).toBe 6
it "is always rounded to the nearest integer", ->
presenter = buildPresenter(scrollLeft: 10, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500)
@@ -1006,20 +1015,25 @@ describe "TextEditorPresenter", ->
describe ".backgroundColor", ->
it "is assigned to ::backgroundColor unless the editor is mini", ->
presenter = buildPresenter(backgroundColor: 'rgba(255, 0, 0, 0)')
presenter = buildPresenter()
presenter.setBackgroundColor('rgba(255, 0, 0, 0)')
expect(presenter.getState().content.backgroundColor).toBe 'rgba(255, 0, 0, 0)'
editor.setMini(true)
presenter = buildPresenter(backgroundColor: 'rgba(255, 0, 0, 0)')
presenter = buildPresenter()
presenter.setBackgroundColor('rgba(255, 0, 0, 0)')
expect(presenter.getState().content.backgroundColor).toBeNull()
it "updates when ::backgroundColor changes", ->
presenter = buildPresenter(backgroundColor: 'rgba(255, 0, 0, 0)')
presenter = buildPresenter()
presenter.setBackgroundColor('rgba(255, 0, 0, 0)')
expect(presenter.getState().content.backgroundColor).toBe 'rgba(255, 0, 0, 0)'
expectStateUpdate presenter, -> presenter.setBackgroundColor('rgba(0, 0, 255, 0)')
expect(presenter.getState().content.backgroundColor).toBe 'rgba(0, 0, 255, 0)'
it "updates when ::mini changes", ->
presenter = buildPresenter(backgroundColor: 'rgba(255, 0, 0, 0)')
presenter = buildPresenter()
presenter.setBackgroundColor('rgba(255, 0, 0, 0)')
expect(presenter.getState().content.backgroundColor).toBe 'rgba(255, 0, 0, 0)'
expectStateUpdate presenter, -> editor.setMini(true)
expect(presenter.getState().content.backgroundColor).toBeNull()
@@ -1047,6 +1061,7 @@ describe "TextEditorPresenter", ->
describe "[tileId].lines[lineId]", -> # line state objects
it "includes the state for visible lines in a tile", ->
presenter = buildPresenter(explicitHeight: 3, scrollTop: 4, lineHeight: 1, tileSize: 3, stoppedScrollingDelay: 200)
presenter.setExplicitHeight(3)
expect(lineStateForScreenRow(presenter, 2)).toBeUndefined()
@@ -1320,7 +1335,7 @@ describe "TextEditorPresenter", ->
expect(stateForCursor(presenter, 4)).toBeUndefined()
it "is empty until all of the required measurements are assigned", ->
presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null, baseCharacterWidth: null, horizontalScrollbarHeight: null)
presenter = buildPresenterWithoutMeasurements()
expect(presenter.getState().content.cursors).toEqual({})
presenter.setExplicitHeight(25)
@@ -1335,6 +1350,15 @@ describe "TextEditorPresenter", ->
presenter.setBaseCharacterWidth(8)
expect(presenter.getState().content.cursors).toEqual({})
presenter.setBoundingClientRect(top: 0, left: 0, width: 500, height: 130)
expect(presenter.getState().content.cursors).toEqual({})
presenter.setWindowSize(500, 130)
expect(presenter.getState().content.cursors).toEqual({})
presenter.setVerticalScrollbarWidth(10)
expect(presenter.getState().content.cursors).toEqual({})
presenter.setHorizontalScrollbarHeight(10)
expect(presenter.getState().content.cursors).not.toEqual({})
@@ -1466,7 +1490,8 @@ describe "TextEditorPresenter", ->
it "alternates between true and false twice per ::cursorBlinkPeriod when the editor is focused", ->
cursorBlinkPeriod = 100
cursorBlinkResumeDelay = 200
presenter = buildPresenter({cursorBlinkPeriod, cursorBlinkResumeDelay, focused: true})
presenter = buildPresenter({cursorBlinkPeriod, cursorBlinkResumeDelay})
presenter.setFocused(true)
expect(presenter.getState().content.cursorsVisible).toBe true
expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2)
@@ -1493,7 +1518,8 @@ describe "TextEditorPresenter", ->
it "stops alternating for ::cursorBlinkResumeDelay when a cursor moves or a cursor is added", ->
cursorBlinkPeriod = 100
cursorBlinkResumeDelay = 200
presenter = buildPresenter({cursorBlinkPeriod, cursorBlinkResumeDelay, focused: true})
presenter = buildPresenter({cursorBlinkPeriod, cursorBlinkResumeDelay})
presenter.setFocused(true)
expect(presenter.getState().content.cursorsVisible).toBe true
expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2)
@@ -1643,7 +1669,7 @@ describe "TextEditorPresenter", ->
[[0, 2], [2, 4]],
])
presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null, baseCharacterWidth: null, tileSize: 2)
presenter = buildPresenterWithoutMeasurements(tileSize: 2)
for tileId, tileState of presenter.getState().content.tiles
expect(tileState.highlights).toEqual({})
@@ -1970,7 +1996,7 @@ describe "TextEditorPresenter", ->
marker = editor.markBufferRange([[2, 13], [4, 14]], invalidate: 'touch')
decoration = editor.decorateMarker(marker, {type: 'overlay', position: 'tail', item})
presenter = buildPresenter(baseCharacterWidth: null, lineHeight: null, windowWidth: null, windowHeight: null, boundingClientRect: null)
presenter = buildPresenterWithoutMeasurements()
expect(presenter.getState().content.overlays).toEqual({})
presenter.setBaseCharacterWidth(10)
@@ -1982,6 +2008,12 @@ describe "TextEditorPresenter", ->
presenter.setWindowSize(500, 100)
expect(presenter.getState().content.overlays).toEqual({})
presenter.setVerticalScrollbarWidth(10)
expect(presenter.getState().content.overlays).toEqual({})
presenter.setHorizontalScrollbarHeight(10)
expect(presenter.getState().content.overlays).toEqual({})
presenter.setBoundingClientRect({top: 0, left: 0, height: 100, width: 500})
expect(presenter.getState().content.overlays).not.toEqual({})
@@ -2168,7 +2200,8 @@ describe "TextEditorPresenter", ->
expect(editor.getRowsPerPage()).toBe(20)
it "tracks the computed content height if ::autoHeight is true so the editor auto-expands vertically", ->
presenter = buildPresenter(explicitHeight: null, autoHeight: true)
presenter = buildPresenter(explicitHeight: null)
presenter.setAutoHeight(true)
expect(presenter.getState().height).toBe editor.getScreenLineCount() * 10
expectStateUpdate presenter, -> presenter.setAutoHeight(false)
@@ -2185,7 +2218,9 @@ describe "TextEditorPresenter", ->
describe ".focused", ->
it "tracks the value of ::focused", ->
presenter = buildPresenter(focused: false)
presenter = buildPresenter()
presenter.setFocused(false)
expect(presenter.getState().focused).toBe false
expectStateUpdate presenter, -> presenter.setFocused(true)
expect(presenter.getState().focused).toBe true
@@ -2882,7 +2917,9 @@ describe "TextEditorPresenter", ->
describe ".backgroundColor", ->
it "is assigned to ::gutterBackgroundColor if present, and to ::backgroundColor otherwise", ->
presenter = buildPresenter(backgroundColor: "rgba(255, 0, 0, 0)", gutterBackgroundColor: "rgba(0, 255, 0, 0)")
presenter = buildPresenter()
presenter.setBackgroundColor("rgba(255, 0, 0, 0)")
presenter.setGutterBackgroundColor("rgba(0, 255, 0, 0)")
expect(getStylesForGutterWithName(presenter, 'line-number').backgroundColor).toBe "rgba(0, 255, 0, 0)"
expect(getStylesForGutterWithName(presenter, 'test-gutter').backgroundColor).toBe "rgba(0, 255, 0, 0)"

View File

@@ -163,14 +163,8 @@ describe "TextEditor", ->
expect(editor.getTitle()).toBe 'untitled'
describe ".getLongTitle()", ->
it "appends the name of the containing directory to the basename of the file", ->
expect(editor.getLongTitle()).toBe 'sample.js - fixtures'
buffer.setPath(undefined)
expect(editor.getLongTitle()).toBe 'untitled'
describe ".getUniqueTitle()", ->
it "returns file name when there is no opened file with identical name", ->
expect(editor.getUniqueTitle()).toBe 'sample.js'
expect(editor.getLongTitle()).toBe 'sample.js'
buffer.setPath(undefined)
expect(editor.getLongTitle()).toBe 'untitled'
@@ -183,8 +177,8 @@ describe "TextEditor", ->
atom.workspace.open(path.join('sample-theme-2', 'readme')).then (o) ->
editor2 = o
runs ->
expect(editor1.getUniqueTitle()).toBe 'sample-theme-1/readme'
expect(editor2.getUniqueTitle()).toBe 'sample-theme-2/readme'
expect(editor1.getLongTitle()).toBe 'sample-theme-1/readme'
expect(editor2.getLongTitle()).toBe 'sample-theme-2/readme'
it "or returns <parent-directory>/.../<filename> when opened files has identical file names", ->
editor1 = null
@@ -195,8 +189,8 @@ describe "TextEditor", ->
atom.workspace.open(path.join('sample-theme-2', 'src', 'js', 'main.js')).then (o) ->
editor2 = o
runs ->
expect(editor1.getUniqueTitle()).toBe 'sample-theme-1/.../main.js'
expect(editor2.getUniqueTitle()).toBe 'sample-theme-2/.../main.js'
expect(editor1.getLongTitle()).toBe 'sample-theme-1/.../main.js'
expect(editor2.getLongTitle()).toBe 'sample-theme-2/.../main.js'
it "notifies ::onDidChangeTitle observers when the underlying buffer path changes", ->
@@ -5444,6 +5438,73 @@ describe "TextEditor", ->
editor.selectPageUp()
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [12, 2]]]
describe "::setFirstVisibleScreenRow() and ::getFirstVisibleScreenRow()", ->
beforeEach ->
line = Array(9).join('0123456789')
editor.setText([1..100].map(-> line).join('\n'))
expect(editor.getLineCount()).toBe 100
expect(editor.lineTextForBufferRow(0).length).toBe 80
describe "when the editor doesn't have a height and lineHeightInPixels", ->
it "does not affect the editor's visible row range", ->
expect(editor.getVisibleRowRange()).toBeNull()
editor.setFirstVisibleScreenRow(1)
expect(editor.getFirstVisibleScreenRow()).toEqual 1
editor.setFirstVisibleScreenRow(3)
expect(editor.getFirstVisibleScreenRow()).toEqual 3
expect(editor.getVisibleRowRange()).toBeNull()
expect(editor.getLastVisibleScreenRow()).toBeNull()
describe "when the editor has a height and lineHeightInPixels", ->
beforeEach ->
atom.config.set('editor.scrollPastEnd', true)
editor.setHeight(100, true)
editor.setLineHeightInPixels(10)
it "updates the editor's visible row range", ->
editor.setFirstVisibleScreenRow(2)
expect(editor.getFirstVisibleScreenRow()).toEqual 2
expect(editor.getLastVisibleScreenRow()).toBe 12
expect(editor.getVisibleRowRange()).toEqual [2, 12]
it "notifies ::onDidChangeFirstVisibleScreenRow observers", ->
changeCount = 0
editor.onDidChangeFirstVisibleScreenRow -> changeCount++
editor.setFirstVisibleScreenRow(2)
expect(changeCount).toBe 1
editor.setFirstVisibleScreenRow(2)
expect(changeCount).toBe 1
editor.setFirstVisibleScreenRow(3)
expect(changeCount).toBe 2
it "ensures that the top row is less than the buffer's line count", ->
editor.setFirstVisibleScreenRow(102)
expect(editor.getFirstVisibleScreenRow()).toEqual 99
expect(editor.getVisibleRowRange()).toEqual [99, 99]
it "ensures that the left column is less than the length of the longest screen line", ->
editor.setFirstVisibleScreenRow(10)
expect(editor.getFirstVisibleScreenRow()).toEqual 10
editor.setText("\n\n\n")
editor.setFirstVisibleScreenRow(10)
expect(editor.getFirstVisibleScreenRow()).toEqual 3
describe "when the 'editor.scrollPastEnd' option is set to false", ->
it "ensures that the bottom row is less than the buffer's line count", ->
atom.config.set('editor.scrollPastEnd', false)
editor.setFirstVisibleScreenRow(95)
expect(editor.getFirstVisibleScreenRow()).toEqual 89
expect(editor.getVisibleRowRange()).toEqual [89, 99]
describe '.get/setPlaceholderText()', ->
it 'can be created with placeholderText', ->
newEditor = atom.workspace.buildTextEditor(

View File

@@ -117,7 +117,7 @@ class AtomEnvironment extends Model
# Call .loadOrCreate instead
constructor: (params={}) ->
{@blobStore, @applicationDelegate, @window, @document, configDirPath, @enablePersistence} = params
{@blobStore, @applicationDelegate, @window, @document, configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params
@state = {version: @constructor.version}
@@ -183,7 +183,7 @@ class AtomEnvironment extends Model
@themes.loadBaseStylesheets()
@initialStyleElements = @styles.getSnapshot()
@themes.initialLoadComplete = true
@themes.initialLoadComplete = true if onlyLoadBaseStyleSheets
@setBodyPlatformClass()
@stylesElement = @styles.buildStylesElement()

View File

@@ -35,7 +35,7 @@ class CommandInstaller
process.resourcesPath
installShellCommandsInteractively: ->
showErrorDialog = (error) ->
showErrorDialog = (error) =>
@applicationDelegate.confirm
message: "Failed to install shell commands"
detailedMessage: error.message

View File

@@ -671,17 +671,47 @@ class Config
#
# * `callback` {Function} to execute while suppressing calls to handlers.
transact: (callback) ->
@transactDepth++
@beginTransaction()
try
callback()
finally
@transactDepth--
@emitChangeEvent()
@endTransaction()
###
Section: Internal methods used by core
###
# Private: Suppress calls to handler functions registered with {::onDidChange}
# and {::observe} for the duration of the {Promise} returned by `callback`.
# After the {Promise} is either resolved or rejected, handlers will be called
# once if the value for their key-path has changed.
#
# * `callback` {Function} that returns a {Promise}, which will be executed
# while suppressing calls to handlers.
#
# Returns a {Promise} that is either resolved or rejected according to the
# `{Promise}` returned by `callback`. If `callback` throws an error, a
# rejected {Promise} will be returned instead.
transactAsync: (callback) ->
@beginTransaction()
try
endTransaction = (fn) => (args...) =>
@endTransaction()
fn(args...)
result = callback()
new Promise (resolve, reject) =>
result.then(endTransaction(resolve)).catch(endTransaction(reject))
catch error
@endTransaction()
Promise.reject(error)
beginTransaction: ->
@transactDepth++
endTransaction: ->
@transactDepth--
@emitChangeEvent()
pushAtKeyPath: (keyPath, value) ->
arrayValue = @get(keyPath) ? []
result = arrayValue.push(value)

View File

@@ -36,21 +36,21 @@ class GrammarRegistry extends FirstMate.GrammarRegistry
if score > highestScore or not bestMatch?
bestMatch = grammar
highestScore = score
else if score is highestScore and bestMatch?.bundledPackage
bestMatch = grammar unless grammar.bundledPackage
bestMatch
# Extended: Returns a {Number} representing how well the grammar matches the
# `filePath` and `contents`.
getGrammarScore: (grammar, filePath, contents) ->
return Infinity if @grammarOverrideForPath(filePath) is grammar.scopeName
contents = fs.readFileSync(filePath, 'utf8') if not contents? and fs.isFileSync(filePath)
if @grammarOverrideForPath(filePath) is grammar.scopeName
2 + (filePath?.length ? 0)
else if @grammarMatchesContents(grammar, contents)
1 + (filePath?.length ? 0)
else
@getGrammarPathScore(grammar, filePath)
score = @getGrammarPathScore(grammar, filePath)
if score > 0 and not grammar.bundledPackage
score += 0.25
if @grammarMatchesContents(grammar, contents)
score += 0.125
score
getGrammarPathScore: (grammar, filePath) ->
return -1 unless filePath

View File

@@ -58,6 +58,7 @@ module.exports = ({blobStore}) ->
document.title = "Spec Suite"
# Avoid throttling of test window by playing silence
# See related discussion in https://github.com/atom/atom/pull/9485
context = new AudioContext()
source = context.createBufferSource()
source.connect(context.destination)
@@ -69,6 +70,7 @@ module.exports = ({blobStore}) ->
buildAtomEnvironment = (params) ->
params = cloneObject(params)
params.blobStore = blobStore unless params.hasOwnProperty("blobStore")
params.onlyLoadBaseStyleSheets = true unless params.hasOwnProperty("onlyLoadBaseStyleSheets")
new AtomEnvironment(params)
promise = testRunner({

View File

@@ -37,6 +37,7 @@ class PackageManager
@emitter = new Emitter
@activationHookEmitter = new Emitter
@packageDirPaths = []
@deferredActivationHooks = []
if configDirPath? and not safeMode
if @devMode
@packageDirPaths.push(path.join(configDirPath, "dev", "packages"))
@@ -336,8 +337,10 @@ class PackageManager
keymapsToEnable = _.difference(oldValue, newValue)
keymapsToDisable = _.difference(newValue, oldValue)
@getLoadedPackage(packageName).deactivateKeymaps() for packageName in keymapsToDisable when not @isPackageDisabled(packageName)
@getLoadedPackage(packageName).activateKeymaps() for packageName in keymapsToEnable when not @isPackageDisabled(packageName)
for packageName in keymapsToDisable when not @isPackageDisabled(packageName)
@getLoadedPackage(packageName)?.deactivateKeymaps()
for packageName in keymapsToEnable when not @isPackageDisabled(packageName)
@getLoadedPackage(packageName)?.activateKeymaps()
null
loadPackages: ->
@@ -407,6 +410,7 @@ class PackageManager
packages = @getLoadedPackagesForTypes(types)
promises = promises.concat(activator.activatePackages(packages))
Promise.all(promises).then =>
@triggerDeferredActivationHooks()
@emitter.emit 'did-activate-initial-packages'
# another type of package manager can handle other package types.
@@ -416,11 +420,11 @@ class PackageManager
activatePackages: (packages) ->
promises = []
@config.transact =>
@config.transactAsync =>
for pack in packages
promise = @activatePackage(pack.name)
promises.push(promise) unless pack.hasActivationCommands()
return
promises.push(promise) unless pack.activationShouldBeDeferred()
Promise.all(promises)
@observeDisabledPackages()
@observePackagesWithKeymapsDisabled()
promises
@@ -437,9 +441,17 @@ class PackageManager
else
Promise.reject(new Error("Failed to load package '#{name}'"))
triggerDeferredActivationHooks: ->
return unless @deferredActivationHooks?
@activationHookEmitter.emit(hook) for hook in @deferredActivationHooks
@deferredActivationHooks = null
triggerActivationHook: (hook) ->
return new Error("Cannot trigger an empty activation hook") unless hook? and _.isString(hook) and hook.length > 0
@activationHookEmitter.emit(hook)
if @deferredActivationHooks?
@deferredActivationHooks.push hook
else
@activationHookEmitter.emit(hook)
onDidTriggerActivationHook: (hook, callback) ->
return unless hook? and _.isString(hook) and hook.length > 0

View File

@@ -312,6 +312,9 @@ class Pane extends Model
else
@activateItemAtIndex(@items.length - 1)
activateLastItem: ->
@activateItemAtIndex(@items.length - 1)
# Public: Move the active tab to the right.
moveItemRight: ->
index = @getActiveItemIndex()
@@ -722,7 +725,7 @@ class Pane extends Model
@notificationManager.addWarning("Unable to save file: #{error.message}")
else if error.code is 'EACCES'
addWarningWithPath('Unable to save file: Permission denied')
else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST']
else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST', 'ELOOP', 'EAGAIN']
addWarningWithPath('Unable to save file', detail: error.message)
else if error.code is 'EROFS'
addWarningWithPath('Unable to save file: Read-only file system')

View File

@@ -55,7 +55,7 @@ class Project extends Model
###
deserialize: (state, deserializerManager) ->
states.paths = [state.path] if state.path? # backward compatibility
state.paths = [state.path] if state.path? # backward compatibility
@buffers = _.compact state.buffers.map (bufferState) ->
# Check that buffer's file path is accessible

View File

@@ -12,7 +12,7 @@ module.exports = ({commandRegistry, commandInstaller, config}) ->
'pane:show-item-6': -> @getModel().getActivePane().activateItemAtIndex(5)
'pane:show-item-7': -> @getModel().getActivePane().activateItemAtIndex(6)
'pane:show-item-8': -> @getModel().getActivePane().activateItemAtIndex(7)
'pane:show-item-9': -> @getModel().getActivePane().activateItemAtIndex(8)
'pane:show-item-9': -> @getModel().getActivePane().activateLastItem()
'pane:move-item-right': -> @getModel().getActivePane().moveItemRight()
'pane:move-item-left': -> @getModel().getActivePane().moveItemLeft()
'window:increase-font-size': -> @getModel().increaseFontSize()

View File

@@ -50,10 +50,6 @@ class TextEditorComponent
@presenter = new TextEditorPresenter
model: @editor
scrollTop: 0
scrollLeft: 0
scrollRow: @editor.getScrollRow()
scrollColumn: @editor.getScrollColumn()
tileSize: tileSize
cursorBlinkPeriod: @cursorBlinkPeriod
cursorBlinkResumeDelay: @cursorBlinkResumeDelay

View File

@@ -13,15 +13,12 @@ class TextEditorPresenter
minimumReflowInterval: 200
constructor: (params) ->
{@model, @config, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @scrollColumn, @scrollRow, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params
{horizontalScrollbarHeight, verticalScrollbarWidth} = params
{@lineHeight, @baseCharacterWidth, @backgroundColor, @gutterBackgroundColor, @tileSize} = params
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params
@measuredHorizontalScrollbarHeight = horizontalScrollbarHeight
@measuredVerticalScrollbarWidth = verticalScrollbarWidth
@gutterWidth ?= 0
@tileSize ?= 6
{@model, @config} = params
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @tileSize} = params
{@contentFrameWidth} = params
@gutterWidth = 0
@tileSize ?= 6
@realScrollTop = @scrollTop
@realScrollLeft = @scrollLeft
@disposables = new CompositeDisposable
@@ -77,7 +74,6 @@ class TextEditorPresenter
@updateVerticalDimensions()
@updateScrollbarDimensions()
@restoreScrollPosition()
@commitPendingLogicalScrollTopPosition()
@commitPendingScrollTopPosition()
@@ -218,6 +214,7 @@ class TextEditorPresenter
@disposables.add @model.onDidAddCursor(@didAddCursor.bind(this))
@disposables.add @model.onDidRequestAutoscroll(@requestAutoscroll.bind(this))
@disposables.add @model.onDidChangeFirstVisibleScreenRow(@didChangeFirstVisibleScreenRow.bind(this))
@observeCursor(cursor) for cursor in @model.getCursors()
@disposables.add @model.onDidAddGutter(@didAddGutter.bind(this))
return
@@ -778,8 +775,7 @@ class TextEditorPresenter
if scrollTop isnt @realScrollTop and not Number.isNaN(scrollTop)
@realScrollTop = scrollTop
@scrollTop = Math.round(scrollTop)
@scrollRow = Math.round(@scrollTop / @lineHeight)
@model.setScrollRow(@scrollRow)
@model.setFirstVisibleScreenRow(Math.round(@scrollTop / @lineHeight), true)
@updateStartRow()
@updateEndRow()
@@ -795,8 +791,7 @@ class TextEditorPresenter
if scrollLeft isnt @realScrollLeft and not Number.isNaN(scrollLeft)
@realScrollLeft = scrollLeft
@scrollLeft = Math.round(scrollLeft)
@scrollColumn = Math.round(@scrollLeft / @baseCharacterWidth)
@model.setScrollColumn(@scrollColumn)
@model.setFirstVisibleScreenColumn(Math.round(@scrollLeft / @baseCharacterWidth))
@emitter.emit 'did-change-scroll-left', @scrollLeft
@@ -1095,6 +1090,7 @@ class TextEditorPresenter
setLineHeight: (lineHeight) ->
unless @lineHeight is lineHeight
@lineHeight = lineHeight
@restoreScrollTopIfNeeded()
@model.setLineHeightInPixels(lineHeight)
@shouldUpdateHeightState = true
@shouldUpdateHorizontalScrollState = true
@@ -1122,6 +1118,7 @@ class TextEditorPresenter
@halfWidthCharWidth = halfWidthCharWidth
@koreanCharWidth = koreanCharWidth
@model.setDefaultCharWidth(baseCharacterWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth)
@restoreScrollLeftIfNeeded()
@characterWidthsChanged()
characterWidthsChanged: ->
@@ -1433,6 +1430,9 @@ class TextEditorPresenter
@emitDidUpdateState()
didChangeFirstVisibleScreenRow: (screenRow) ->
@updateScrollTop(screenRow * @lineHeight)
getVerticalScrollMarginInPixels: ->
Math.round(@model.getVerticalScrollMargin() * @lineHeight)
@@ -1512,14 +1512,6 @@ class TextEditorPresenter
@updateScrollTop(@pendingScrollTop)
@pendingScrollTop = null
restoreScrollPosition: ->
return if @hasRestoredScrollPosition or not @hasPixelPositionRequirements()
@setScrollTop(@scrollRow * @lineHeight) if @scrollRow?
@setScrollLeft(@scrollColumn * @baseCharacterWidth) if @scrollColumn?
@hasRestoredScrollPosition = true
clearPendingScrollPosition: ->
@pendingScrollLogicalPosition = null
@pendingScrollTop = null
@@ -1531,6 +1523,14 @@ class TextEditorPresenter
canScrollTopTo: (scrollTop) ->
@scrollTop isnt @constrainScrollTop(scrollTop)
restoreScrollTopIfNeeded: ->
unless @scrollTop?
@updateScrollTop(@model.getFirstVisibleScreenRow() * @lineHeight)
restoreScrollLeftIfNeeded: ->
unless @scrollLeft?
@updateScrollLeft(@model.getFirstVisibleScreenColumn() * @baseCharacterWidth)
onDidChangeScrollTop: (callback) ->
@emitter.on 'did-change-scroll-top', callback

View File

@@ -54,13 +54,11 @@ GutterContainer = require './gutter-container'
# soft wraps and folds to ensure your code interacts with them correctly.
module.exports =
class TextEditor extends Model
callDisplayBufferCreatedHook: false
buffer: null
languageMode: null
cursors: null
selections: null
suppressSelectionMerging: false
updateBatchDepth: 0
selectionFlashDuration: 500
gutterContainer: null
@@ -90,7 +88,7 @@ class TextEditor extends Model
super
{
@softTabs, @scrollRow, @scrollColumn, initialLine, initialColumn, tabLength,
@softTabs, @firstVisibleScreenRow, @firstVisibleScreenColumn, initialLine, initialColumn, tabLength,
softWrapped, @displayBuffer, @selectionsMarkerLayer, buffer, suppressCursorCreation,
@mini, @placeholderText, lineNumberGutterVisible, largeFileMode, @config,
@notificationManager, @packageManager, @clipboard, @viewRegistry, @grammarRegistry,
@@ -106,6 +104,8 @@ class TextEditor extends Model
throw new Error("Must pass a project parameter when constructing TextEditors") unless @project?
throw new Error("Must pass an assert parameter when constructing TextEditors") unless @assert?
@firstVisibleScreenRow ?= 0
@firstVisibleScreenColumn ?= 0
@emitter = new Emitter
@disposables = new CompositeDisposable
@cursors = []
@@ -146,8 +146,8 @@ class TextEditor extends Model
deserializer: 'TextEditor'
id: @id
softTabs: @softTabs
scrollRow: @getScrollRow()
scrollColumn: @getScrollColumn()
firstVisibleScreenRow: @getFirstVisibleScreenRow()
firstVisibleScreenColumn: @getFirstVisibleScreenColumn()
displayBuffer: @displayBuffer.serialize()
selectionsMarkerLayerId: @selectionsMarkerLayer.id
@@ -453,6 +453,9 @@ class TextEditor extends Model
onDidChangeCharacterWidths: (callback) ->
@displayBuffer.onDidChangeCharacterWidths(callback)
onDidChangeFirstVisibleScreenRow: (callback, fromView) ->
@emitter.on 'did-change-first-visible-screen-row', callback
onDidChangeScrollTop: (callback) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::onDidChangeScrollTop instead.")
@@ -583,18 +586,18 @@ class TextEditor extends Model
else
'untitled'
# Essential: Get unique title for display in other parts of the UI
# such as the window title.
# Essential: Get unique title for display in other parts of the UI, such as
# the window title.
#
# If the editor's buffer is unsaved, its title is "untitled"
# If the editor's buffer is saved, its unique title is formatted as one
# of the following,
# * "<filename>" when it is the only editing buffer with this file name.
# * "<unique-dir-prefix>/.../<filename>", where the "..." may be omitted
# if the the direct parent directory is already different.
# if the the direct parent directory is already different.
#
# Returns a {String}
getUniqueTitle: ->
getLongTitle: ->
if sessionPath = @getPath()
title = @getTitle()
@@ -622,22 +625,6 @@ class TextEditor extends Model
else
'untitled'
# Essential: Get the editor's long title for display in other parts of the UI
# such as the window title.
#
# If the editor's buffer is saved, its long title is formatted as
# "<filename> - <directory>". If it is unsaved, its title is "untitled"
#
# Returns a {String}.
getLongTitle: ->
if sessionPath = @getPath()
fileName = path.basename(sessionPath)
directory = @project.relativize(path.dirname(sessionPath))
directory = if directory.length > 0 then directory else path.basename(path.dirname(sessionPath))
"#{fileName} - #{directory}"
else
'untitled'
# Essential: Returns the {String} path of this editor's text buffer.
getPath: -> @buffer.getPath()
@@ -3130,14 +3117,6 @@ class TextEditor extends Model
@placeholderText = placeholderText
@emitter.emit 'did-change-placeholder-text', @placeholderText
getFirstVisibleScreenRow: ->
deprecate("This is now a view method. Call TextEditorElement::getFirstVisibleScreenRow instead.")
@viewRegistry.getView(this).getVisibleRowRange()[0]
getLastVisibleScreenRow: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getLastVisibleScreenRow instead.")
@viewRegistry.getView(this).getVisibleRowRange()[1]
pixelPositionForBufferPosition: (bufferPosition) ->
Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForBufferPosition` instead")
@viewRegistry.getView(this).pixelPositionForBufferPosition(bufferPosition)
@@ -3192,11 +3171,40 @@ class TextEditor extends Model
Grim.deprecate("This is now a view method. Call TextEditorElement::getWidth instead.")
@displayBuffer.getWidth()
getScrollRow: -> @scrollRow
setScrollRow: (@scrollRow) ->
# Experimental: Scroll the editor such that the given screen row is at the
# top of the visible area.
setFirstVisibleScreenRow: (screenRow, fromView) ->
unless fromView
maxScreenRow = @getLineCount() - 1
unless @config.get('editor.scrollPastEnd')
height = @displayBuffer.getHeight()
lineHeightInPixels = @displayBuffer.getLineHeightInPixels()
if height? and lineHeightInPixels?
maxScreenRow -= Math.floor(height / lineHeightInPixels)
screenRow = Math.max(Math.min(screenRow, maxScreenRow), 0)
getScrollColumn: -> @scrollColumn
setScrollColumn: (@scrollColumn) ->
unless screenRow is @firstVisibleScreenRow
@firstVisibleScreenRow = screenRow
@emitter.emit 'did-change-first-visible-screen-row', screenRow unless fromView
getFirstVisibleScreenRow: -> @firstVisibleScreenRow
getLastVisibleScreenRow: ->
height = @displayBuffer.getHeight()
lineHeightInPixels = @displayBuffer.getLineHeightInPixels()
if height? and lineHeightInPixels?
Math.min(@firstVisibleScreenRow + Math.floor(height / lineHeightInPixels), @getLineCount() - 1)
else
null
getVisibleRowRange: ->
if lastVisibleScreenRow = @getLastVisibleScreenRow()
[@firstVisibleScreenRow, lastVisibleScreenRow]
else
null
setFirstVisibleScreenColumn: (@firstVisibleScreenColumn) ->
getFirstVisibleScreenColumn: -> @firstVisibleScreenColumn
getScrollTop: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollTop instead.")
@@ -3253,11 +3261,6 @@ class TextEditor extends Model
@viewRegistry.getView(this).getMaxScrollTop()
getVisibleRowRange: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getVisibleRowRange instead.")
@viewRegistry.getView(this).getVisibleRowRange()
intersectsVisibleRowRange: (startRow, endRow) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::intersectsVisibleRowRange instead.")

View File

@@ -468,7 +468,7 @@ class Workspace extends Model
when 'EACCES'
@notificationManager.addWarning("Permission denied '#{error.path}'")
return Promise.resolve()
when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL'
when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL', 'EMFILE', 'ENOTDIR'
@notificationManager.addWarning("Unable to open '#{error.path ? uri}'", detail: error.message)
return Promise.resolve()
else