mirror of
https://github.com/atom/atom.git
synced 2026-01-25 14:59:03 -05:00
Merge branch 'master' into pr/10930
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,3 +15,4 @@ debug.log
|
||||
docs/output
|
||||
docs/includes
|
||||
spec/fixtures/evil-files/
|
||||
out/
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-package-manager": "1.6.0"
|
||||
"atom-package-manager": "1.7.1"
|
||||
}
|
||||
}
|
||||
|
||||
2
atom.sh
2
atom.sh
@@ -4,8 +4,6 @@ if [ "$(uname)" == 'Darwin' ]; then
|
||||
OS='Mac'
|
||||
elif [ "$(expr substr $(uname -s) 1 5)" == 'Linux' ]; then
|
||||
OS='Linux'
|
||||
elif [ "$(expr substr $(uname -s) 1 10)" == 'MINGW32_NT' ]; then
|
||||
OS='Cygwin'
|
||||
else
|
||||
echo "Your platform ($(uname -a)) is not supported."
|
||||
exit 1
|
||||
|
||||
@@ -34,23 +34,10 @@ module.exports = (grunt) ->
|
||||
grunt.file.setBase(path.resolve('..'))
|
||||
|
||||
# Options
|
||||
[defaultChannel, releaseBranch] = getDefaultChannelAndReleaseBranch(packageJson.version)
|
||||
installDir = grunt.option('install-dir')
|
||||
buildDir = grunt.option('build-dir')
|
||||
buildDir ?= 'out'
|
||||
buildDir = path.resolve(buildDir)
|
||||
|
||||
channel = grunt.option('channel')
|
||||
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'
|
||||
buildDir = path.resolve(grunt.option('build-dir') ? 'out')
|
||||
channel = grunt.option('channel') ? defaultChannel
|
||||
|
||||
metadata = packageJson
|
||||
appName = packageJson.productName
|
||||
@@ -189,7 +176,7 @@ module.exports = (grunt) ->
|
||||
pkg: grunt.file.readJSON('package.json')
|
||||
|
||||
atom: {
|
||||
appName, channel, metadata,
|
||||
appName, channel, metadata, releaseBranch,
|
||||
appFileName, apmFileName,
|
||||
appDir, buildDir, contentsDir, installDir, shellAppDir, symbolsDir,
|
||||
}
|
||||
@@ -310,3 +297,20 @@ module.exports = (grunt) ->
|
||||
unless process.platform is 'linux' or grunt.option('no-install')
|
||||
defaultTasks.push 'install'
|
||||
grunt.registerTask('default', defaultTasks)
|
||||
|
||||
getDefaultChannelAndReleaseBranch = (version) ->
|
||||
if version.match(/dev/) or isBuildingPR()
|
||||
channel = 'dev'
|
||||
releaseBranch = null
|
||||
else
|
||||
if version.match(/beta/)
|
||||
channel = 'beta'
|
||||
else
|
||||
channel = 'stable'
|
||||
|
||||
minorVersion = version.match(/^\d\.\d/)[0]
|
||||
releaseBranch = "#{minorVersion}-releases"
|
||||
[channel, releaseBranch]
|
||||
|
||||
isBuildingPR = ->
|
||||
process.env.APPVEYOR_PULL_REQUEST_NUMBER? or process.env.TRAVIS_PULL_REQUEST?
|
||||
|
||||
@@ -31,14 +31,9 @@ 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', ->
|
||||
channel = grunt.config.get('atom.channel')
|
||||
switch channel
|
||||
when 'stable'
|
||||
isPrerelease = false
|
||||
when 'beta'
|
||||
isPrerelease = true
|
||||
else
|
||||
return
|
||||
releaseBranch = grunt.config.get('atom.releaseBranch')
|
||||
isPrerelease = grunt.config.get('atom.channel') is 'beta'
|
||||
return unless releaseBranch?
|
||||
|
||||
doneCallback = @async()
|
||||
startTime = Date.now()
|
||||
@@ -55,7 +50,7 @@ module.exports = (gruntObject) ->
|
||||
|
||||
zipAssets buildDir, assets, (error) ->
|
||||
return done(error) if error?
|
||||
getAtomDraftRelease isPrerelease, channel, (error, release) ->
|
||||
getAtomDraftRelease isPrerelease, releaseBranch, (error, release) ->
|
||||
return done(error) if error?
|
||||
assetNames = (asset.assetName for asset in assets)
|
||||
deleteExistingAssets release, assetNames, (error) ->
|
||||
|
||||
@@ -5,9 +5,7 @@ module.exports = (grunt) ->
|
||||
{spawn} = require('./task-helpers')(grunt)
|
||||
|
||||
getVersion = (callback) ->
|
||||
releasableBranches = ['stable', 'beta']
|
||||
channel = grunt.config.get('atom.channel')
|
||||
shouldUseCommitHash = if channel in releasableBranches then false else true
|
||||
shouldUseCommitHash = grunt.config.get('atom.channel') is 'dev'
|
||||
inRepository = fs.existsSync(path.resolve(__dirname, '..', '..', '.git'))
|
||||
{version} = require(path.join(grunt.config.get('atom.appDir'), 'package.json'))
|
||||
if shouldUseCommitHash and inRepository
|
||||
|
||||
4
circle.yml
Normal file
4
circle.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
general:
|
||||
branches:
|
||||
only:
|
||||
- io-circle-ci
|
||||
@@ -73,8 +73,10 @@
|
||||
'cmd-alt-right': 'pane:show-next-item'
|
||||
'ctrl-pageup': 'pane:show-previous-item'
|
||||
'ctrl-pagedown': 'pane:show-next-item'
|
||||
'ctrl-tab': 'pane:show-next-item'
|
||||
'ctrl-shift-tab': 'pane:show-previous-item'
|
||||
'ctrl-tab': 'pane:show-next-recently-used-item'
|
||||
'ctrl-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
|
||||
'ctrl-shift-tab': 'pane:show-previous-recently-used-item'
|
||||
'ctrl-shift-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
|
||||
'cmd-=': 'window:increase-font-size'
|
||||
'cmd-+': 'window:increase-font-size'
|
||||
'cmd--': 'window:decrease-font-size'
|
||||
|
||||
@@ -46,8 +46,10 @@
|
||||
'pagedown': 'core:page-down'
|
||||
'backspace': 'core:backspace'
|
||||
'shift-backspace': 'core:backspace'
|
||||
'ctrl-tab': 'pane:show-next-item'
|
||||
'ctrl-shift-tab': 'pane:show-previous-item'
|
||||
'ctrl-tab': 'pane:show-next-recently-used-item'
|
||||
'ctrl-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
|
||||
'ctrl-shift-tab': 'pane:show-previous-recently-used-item'
|
||||
'ctrl-shift-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
|
||||
'ctrl-pageup': 'pane:show-previous-item'
|
||||
'ctrl-pagedown': 'pane:show-next-item'
|
||||
'ctrl-up': 'core:move-up'
|
||||
|
||||
@@ -52,8 +52,10 @@
|
||||
'pagedown': 'core:page-down'
|
||||
'backspace': 'core:backspace'
|
||||
'shift-backspace': 'core:backspace'
|
||||
'ctrl-tab': 'pane:show-next-item'
|
||||
'ctrl-shift-tab': 'pane:show-previous-item'
|
||||
'ctrl-tab': 'pane:show-next-recently-used-item'
|
||||
'ctrl-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
|
||||
'ctrl-shift-tab': 'pane:show-previous-recently-used-item'
|
||||
'ctrl-shift-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
|
||||
'ctrl-pageup': 'pane:show-previous-item'
|
||||
'ctrl-pagedown': 'pane:show-next-item'
|
||||
'ctrl-shift-up': 'core:move-up'
|
||||
|
||||
38
package.json
38
package.json
@@ -15,7 +15,7 @@
|
||||
"electronVersion": "0.36.8",
|
||||
"dependencies": {
|
||||
"async": "0.2.6",
|
||||
"atom-keymap": "^6.2.0",
|
||||
"atom-keymap": "^6.3.1",
|
||||
"babel-core": "^5.8.21",
|
||||
"bootstrap": "^3.3.4",
|
||||
"cached-run-in-this-context": "0.4.1",
|
||||
@@ -37,7 +37,7 @@
|
||||
"less-cache": "0.23",
|
||||
"line-top-index": "0.2.0",
|
||||
"marked": "^0.3.4",
|
||||
"nodegit": "0.9.0",
|
||||
"nodegit": "0.11.9",
|
||||
"normalize-package-data": "^2.0.0",
|
||||
"nslog": "^3",
|
||||
"oniguruma": "^5",
|
||||
@@ -54,7 +54,7 @@
|
||||
"service-hub": "^0.7.0",
|
||||
"source-map-support": "^0.3.2",
|
||||
"temp": "0.8.1",
|
||||
"text-buffer": "8.3.1",
|
||||
"text-buffer": "8.4.1",
|
||||
"typescript-simple": "1.0.0",
|
||||
"underscore-plus": "^1.6.6",
|
||||
"yargs": "^3.23.0"
|
||||
@@ -72,47 +72,47 @@
|
||||
"one-light-syntax": "1.2.0",
|
||||
"solarized-dark-syntax": "1.0.0",
|
||||
"solarized-light-syntax": "1.0.0",
|
||||
"about": "1.3.1",
|
||||
"about": "1.4.0",
|
||||
"archive-view": "0.61.1",
|
||||
"autocomplete-atom-api": "0.10.0",
|
||||
"autocomplete-css": "0.11.0",
|
||||
"autocomplete-html": "0.7.2",
|
||||
"autocomplete-plus": "2.28.0",
|
||||
"autocomplete-plus": "2.29.1",
|
||||
"autocomplete-snippets": "1.10.0",
|
||||
"autoflow": "0.27.0",
|
||||
"autosave": "0.23.1",
|
||||
"background-tips": "0.26.0",
|
||||
"bookmarks": "0.38.2",
|
||||
"bracket-matcher": "0.80.0",
|
||||
"bracket-matcher": "0.81.0",
|
||||
"command-palette": "0.38.0",
|
||||
"deprecation-cop": "0.54.1",
|
||||
"dev-live-reload": "0.47.0",
|
||||
"encoding-selector": "0.21.0",
|
||||
"exception-reporting": "0.37.0",
|
||||
"find-and-replace": "0.197.2",
|
||||
"fuzzy-finder": "1.0.1",
|
||||
"find-and-replace": "0.197.4",
|
||||
"fuzzy-finder": "1.0.3",
|
||||
"git-diff": "1.0.0",
|
||||
"go-to-line": "0.30.0",
|
||||
"grammar-selector": "0.48.1",
|
||||
"image-view": "0.56.0",
|
||||
"image-view": "0.57.0",
|
||||
"incompatible-packages": "0.25.1",
|
||||
"keybinding-resolver": "0.33.0",
|
||||
"keybinding-resolver": "0.35.0",
|
||||
"line-ending-selector": "0.3.1",
|
||||
"link": "0.31.0",
|
||||
"markdown-preview": "0.157.3",
|
||||
"markdown-preview": "0.158.0",
|
||||
"metrics": "0.53.1",
|
||||
"notifications": "0.62.2",
|
||||
"notifications": "0.62.4",
|
||||
"open-on-github": "1.0.0",
|
||||
"package-generator": "0.41.1",
|
||||
"settings-view": "0.232.4",
|
||||
"snippets": "1.0.1",
|
||||
"spell-check": "0.67.0",
|
||||
"status-bar": "1.1.0",
|
||||
"status-bar": "1.1.1",
|
||||
"styleguide": "0.45.2",
|
||||
"symbols-view": "0.111.1",
|
||||
"tabs": "0.90.2",
|
||||
"symbols-view": "0.112.0",
|
||||
"tabs": "0.92.0",
|
||||
"timecop": "0.33.1",
|
||||
"tree-view": "0.201.3",
|
||||
"tree-view": "0.203.0",
|
||||
"update-package-dependencies": "0.10.0",
|
||||
"welcome": "0.34.0",
|
||||
"whitespace": "0.32.2",
|
||||
@@ -129,7 +129,7 @@
|
||||
"language-hyperlink": "0.16.0",
|
||||
"language-java": "0.17.0",
|
||||
"language-javascript": "0.110.0",
|
||||
"language-json": "0.17.4",
|
||||
"language-json": "0.17.5",
|
||||
"language-less": "0.29.0",
|
||||
"language-make": "0.21.0",
|
||||
"language-mustache": "0.13.0",
|
||||
@@ -144,10 +144,10 @@
|
||||
"language-shellscript": "0.21.0",
|
||||
"language-source": "0.9.0",
|
||||
"language-sql": "0.20.0",
|
||||
"language-text": "0.7.0",
|
||||
"language-text": "0.7.1",
|
||||
"language-todo": "0.27.0",
|
||||
"language-toml": "0.18.0",
|
||||
"language-xml": "0.34.3",
|
||||
"language-xml": "0.34.4",
|
||||
"language-yaml": "0.25.1"
|
||||
},
|
||||
"private": true,
|
||||
|
||||
@@ -22,31 +22,13 @@ FOR %%a IN (%*) DO (
|
||||
)
|
||||
)
|
||||
|
||||
rem Getting the process ID in cmd of the current cmd process: http://superuser.com/questions/881789/identify-and-kill-batch-script-started-before
|
||||
set T=%TEMP%\atomCmdProcessId-%time::=%.tmp
|
||||
wmic process where (Name="WMIC.exe" AND CommandLine LIKE "%%%TIME%%%") get ParentProcessId /value | find "ParentProcessId" >%T%
|
||||
set /P A=<%T%
|
||||
set PID=%A:~16%
|
||||
del %T%
|
||||
|
||||
IF "%EXPECT_OUTPUT%"=="YES" (
|
||||
SET ELECTRON_ENABLE_LOGGING=YES
|
||||
IF "%WAIT%"=="YES" (
|
||||
"%~dp0\..\..\atom.exe" --pid=%PID% %*
|
||||
rem If the wait flag is set, don't exit this process until Atom tells it to.
|
||||
goto waitLoop
|
||||
)
|
||||
ELSE (
|
||||
powershell -noexit "%~dp0\..\..\atom.exe" --pid=$pid %* ; wait-event
|
||||
) ELSE (
|
||||
"%~dp0\..\..\atom.exe" %*
|
||||
)
|
||||
) ELSE (
|
||||
"%~dp0\..\app\apm\bin\node.exe" "%~dp0\atom.js" %*
|
||||
)
|
||||
|
||||
goto end
|
||||
|
||||
:waitLoop
|
||||
sleep 1
|
||||
goto waitLoop
|
||||
|
||||
:end
|
||||
|
||||
@@ -1,49 +1,2 @@
|
||||
#!/bin/sh
|
||||
|
||||
while getopts ":fhtvw-:" opt; do
|
||||
case "$opt" in
|
||||
-)
|
||||
case "${OPTARG}" in
|
||||
wait)
|
||||
WAIT=1
|
||||
;;
|
||||
help|version)
|
||||
REDIRECT_STDERR=1
|
||||
EXPECT_OUTPUT=1
|
||||
;;
|
||||
foreground|test)
|
||||
EXPECT_OUTPUT=1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
w)
|
||||
WAIT=1
|
||||
;;
|
||||
h|v)
|
||||
REDIRECT_STDERR=1
|
||||
EXPECT_OUTPUT=1
|
||||
;;
|
||||
f|t)
|
||||
EXPECT_OUTPUT=1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
directory=$(dirname "$0")
|
||||
|
||||
WINPS=`ps | grep -i $$`
|
||||
PID=`echo $WINPS | cut -d' ' -f 4`
|
||||
|
||||
if [ $EXPECT_OUTPUT ]; then
|
||||
export ELECTRON_ENABLE_LOGGING=1
|
||||
"$directory/../../atom.exe" --executed-from="$(pwd)" --pid=$PID "$@"
|
||||
else
|
||||
"$directory/../app/apm/bin/node.exe" "$directory/atom.js" "$@"
|
||||
fi
|
||||
|
||||
# If the wait flag is set, don't exit this process until Atom tells it to.
|
||||
if [ $WAIT ]; then
|
||||
while true; do
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
$(dirname "$0")/atom.cmd "$@"
|
||||
|
||||
@@ -19,7 +19,9 @@ exports.afterEach = (fn) ->
|
||||
|
||||
waitsForPromise = (fn) ->
|
||||
promise = fn()
|
||||
waitsFor 'spec promise to resolve', 30000, (done) ->
|
||||
# This timeout is 3 minutes. We need to bump it back down once we fix backgrounding
|
||||
# of the renderer process on CI. See https://github.com/atom/electron/issues/4317
|
||||
waitsFor 'spec promise to resolve', 3 * 60 * 1000, (done) ->
|
||||
promise.then(
|
||||
done,
|
||||
(error) ->
|
||||
|
||||
@@ -179,18 +179,37 @@ describe "AtomEnvironment", ->
|
||||
atom.loadState().then (state) ->
|
||||
expect(state).toEqual({stuff: 'cool'})
|
||||
|
||||
it "saves state on keydown and mousedown events", ->
|
||||
it "saves state on keydown, mousedown, and when the editor window unloads", ->
|
||||
spyOn(atom, 'saveState')
|
||||
|
||||
keydown = new KeyboardEvent('keydown')
|
||||
atom.document.dispatchEvent(keydown)
|
||||
advanceClock atom.saveStateDebounceInterval
|
||||
expect(atom.saveState).toHaveBeenCalled()
|
||||
expect(atom.saveState).toHaveBeenCalledWith({isUnloading: false})
|
||||
expect(atom.saveState).not.toHaveBeenCalledWith({isUnloading: true})
|
||||
|
||||
atom.saveState.reset()
|
||||
mousedown = new MouseEvent('mousedown')
|
||||
atom.document.dispatchEvent(mousedown)
|
||||
advanceClock atom.saveStateDebounceInterval
|
||||
expect(atom.saveState).toHaveBeenCalled()
|
||||
expect(atom.saveState).toHaveBeenCalledWith({isUnloading: false})
|
||||
expect(atom.saveState).not.toHaveBeenCalledWith({isUnloading: true})
|
||||
|
||||
atom.saveState.reset()
|
||||
atom.unloadEditorWindow()
|
||||
mousedown = new MouseEvent('mousedown')
|
||||
atom.document.dispatchEvent(mousedown)
|
||||
advanceClock atom.saveStateDebounceInterval
|
||||
expect(atom.saveState).toHaveBeenCalledWith({isUnloading: true})
|
||||
expect(atom.saveState).not.toHaveBeenCalledWith({isUnloading: false})
|
||||
|
||||
it "serializes the project state with all the options supplied in saveState", ->
|
||||
spyOn(atom.project, 'serialize').andReturn({foo: 42})
|
||||
|
||||
waitsForPromise -> atom.saveState({anyOption: 'any option'})
|
||||
runs ->
|
||||
expect(atom.project.serialize.calls.length).toBe(1)
|
||||
expect(atom.project.serialize.mostRecentCall.args[0]).toEqual({anyOption: 'any option'})
|
||||
|
||||
describe "openInitialEmptyEditorIfNecessary", ->
|
||||
describe "when there are no paths set", ->
|
||||
@@ -275,6 +294,14 @@ describe "AtomEnvironment", ->
|
||||
atom.openLocations([{pathToOpen}])
|
||||
expect(atom.project.getPaths()[0]).toBe __dirname
|
||||
|
||||
describe "then a second path is opened with forceAddToWindow", ->
|
||||
it "adds the second path to the project's paths", ->
|
||||
firstPathToOpen = __dirname
|
||||
secondPathToOpen = path.resolve(__dirname, './fixtures')
|
||||
atom.openLocations([{pathToOpen: firstPathToOpen}])
|
||||
atom.openLocations([{pathToOpen: secondPathToOpen, forceAddToWindow: true}])
|
||||
expect(atom.project.getPaths()).toEqual([firstPathToOpen, secondPathToOpen])
|
||||
|
||||
describe "when the opened path does not exist but its parent directory does", ->
|
||||
it "adds the parent directory to the project paths", ->
|
||||
pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt')
|
||||
@@ -320,3 +347,18 @@ describe "AtomEnvironment", ->
|
||||
runs ->
|
||||
{releaseVersion} = updateAvailableHandler.mostRecentCall.args[0]
|
||||
expect(releaseVersion).toBe 'version'
|
||||
|
||||
describe "::getReleaseChannel()", ->
|
||||
[version] = []
|
||||
beforeEach ->
|
||||
spyOn(atom, 'getVersion').andCallFake -> version
|
||||
|
||||
it "returns the correct channel based on the version number", ->
|
||||
version = '1.5.6'
|
||||
expect(atom.getReleaseChannel()).toBe 'stable'
|
||||
|
||||
version = '1.5.0-beta10'
|
||||
expect(atom.getReleaseChannel()).toBe 'beta'
|
||||
|
||||
version = '1.7.0-dev-5340c91'
|
||||
expect(atom.getReleaseChannel()).toBe 'dev'
|
||||
|
||||
115
spec/auto-update-manager-spec.js
Normal file
115
spec/auto-update-manager-spec.js
Normal file
@@ -0,0 +1,115 @@
|
||||
'use babel'
|
||||
|
||||
import AutoUpdateManager from '../src/auto-update-manager'
|
||||
import {remote} from 'electron'
|
||||
const electronAutoUpdater = remote.require('electron').autoUpdater
|
||||
|
||||
describe('AutoUpdateManager (renderer)', () => {
|
||||
let autoUpdateManager
|
||||
|
||||
beforeEach(() => {
|
||||
autoUpdateManager = new AutoUpdateManager({
|
||||
applicationDelegate: atom.applicationDelegate
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
autoUpdateManager.destroy()
|
||||
})
|
||||
|
||||
describe('::onDidBeginCheckingForUpdate', () => {
|
||||
it('subscribes to "did-begin-checking-for-update" event', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
autoUpdateManager.onDidBeginCheckingForUpdate(spy)
|
||||
electronAutoUpdater.emit('checking-for-update')
|
||||
waitsFor(() => {
|
||||
return spy.callCount === 1
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('::onDidBeginDownloadingUpdate', () => {
|
||||
it('subscribes to "did-begin-downloading-update" event', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
autoUpdateManager.onDidBeginDownloadingUpdate(spy)
|
||||
electronAutoUpdater.emit('update-available')
|
||||
waitsFor(() => {
|
||||
return spy.callCount === 1
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('::onDidCompleteDownloadingUpdate', () => {
|
||||
it('subscribes to "did-complete-downloading-update" event', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
autoUpdateManager.onDidCompleteDownloadingUpdate(spy)
|
||||
electronAutoUpdater.emit('update-downloaded', null, null, '1.2.3')
|
||||
waitsFor(() => {
|
||||
return spy.callCount === 1
|
||||
})
|
||||
runs(() => {
|
||||
expect(spy.mostRecentCall.args[0].releaseVersion).toBe('1.2.3')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('::onUpdateNotAvailable', () => {
|
||||
it('subscribes to "update-not-available" event', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
autoUpdateManager.onUpdateNotAvailable(spy)
|
||||
electronAutoUpdater.emit('update-not-available')
|
||||
waitsFor(() => {
|
||||
return spy.callCount === 1
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('::platformSupportsUpdates', () => {
|
||||
let state, releaseChannel
|
||||
it('returns true on OS X and Windows when in stable', () => {
|
||||
spyOn(autoUpdateManager, 'getState').andCallFake(() => state)
|
||||
spyOn(atom, 'getReleaseChannel').andCallFake(() => releaseChannel)
|
||||
|
||||
state = 'idle'
|
||||
releaseChannel = 'stable'
|
||||
expect(autoUpdateManager.platformSupportsUpdates()).toBe(true)
|
||||
|
||||
state = 'idle'
|
||||
releaseChannel = 'dev'
|
||||
expect(autoUpdateManager.platformSupportsUpdates()).toBe(false)
|
||||
|
||||
state = 'unsupported'
|
||||
releaseChannel = 'stable'
|
||||
expect(autoUpdateManager.platformSupportsUpdates()).toBe(false)
|
||||
|
||||
state = 'unsupported'
|
||||
releaseChannel = 'dev'
|
||||
expect(autoUpdateManager.platformSupportsUpdates()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('::destroy', () => {
|
||||
it('unsubscribes from all events', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
const doneIndicator = jasmine.createSpy('spy')
|
||||
atom.applicationDelegate.onUpdateNotAvailable(doneIndicator)
|
||||
autoUpdateManager.onDidBeginCheckingForUpdate(spy)
|
||||
autoUpdateManager.onDidBeginDownloadingUpdate(spy)
|
||||
autoUpdateManager.onDidCompleteDownloadingUpdate(spy)
|
||||
autoUpdateManager.onUpdateNotAvailable(spy)
|
||||
autoUpdateManager.destroy()
|
||||
electronAutoUpdater.emit('checking-for-update')
|
||||
electronAutoUpdater.emit('update-available')
|
||||
electronAutoUpdater.emit('update-downloaded', null, null, '1.2.3')
|
||||
electronAutoUpdater.emit('update-not-available')
|
||||
|
||||
waitsFor(() => {
|
||||
return doneIndicator.callCount === 1
|
||||
})
|
||||
|
||||
runs(() => {
|
||||
expect(spy.callCount).toBe(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1621,6 +1621,16 @@ describe "Config", ->
|
||||
expect(color.toHexString()).toBe '#ff0000'
|
||||
expect(color.toRGBAString()).toBe 'rgba(255, 0, 0, 1)'
|
||||
|
||||
color.red = 11
|
||||
color.green = 11
|
||||
color.blue = 124
|
||||
color.alpha = 1
|
||||
atom.config.set('foo.bar.aColor', color)
|
||||
|
||||
color = atom.config.get('foo.bar.aColor')
|
||||
expect(color.toHexString()).toBe '#0b0b7c'
|
||||
expect(color.toRGBAString()).toBe 'rgba(11, 11, 124, 1)'
|
||||
|
||||
it 'coerces various types to a color object', ->
|
||||
atom.config.set('foo.bar.aColor', 'red')
|
||||
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 255, green: 0, blue: 0, alpha: 1}
|
||||
|
||||
8
spec/fixtures/packages/package-with-prefixed-and-suffixed-repo-url/package.json
vendored
Normal file
8
spec/fixtures/packages/package-with-prefixed-and-suffixed-repo-url/package.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "package-with-a-git-prefixed-git-repo-url",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/example/repo.git"
|
||||
},
|
||||
"_id": "this is here to simulate the URL being already normalized by npm. we still need to stript git+ from the beginning and .git from the end."
|
||||
}
|
||||
13
spec/fixtures/sample-with-comments.js
vendored
13
spec/fixtures/sample-with-comments.js
vendored
@@ -9,12 +9,23 @@ var quicksort = function () {
|
||||
// Wowza
|
||||
if (items.length <= 1) return items;
|
||||
var pivot = items.shift(), current, left = [], right = [];
|
||||
/*
|
||||
This is a multiline comment block with
|
||||
an empty line inside of it.
|
||||
|
||||
Awesome.
|
||||
*/
|
||||
while(items.length > 0) {
|
||||
current = items.shift();
|
||||
current < pivot ? left.push(current) : right.push(current);
|
||||
}
|
||||
// This is a collection of
|
||||
// single line comments
|
||||
|
||||
// ...with an empty line
|
||||
// among it, geez!
|
||||
return sort(left).concat(pivot).concat(sort(right));
|
||||
};
|
||||
// this is a single-line comment
|
||||
return sort(Array.apply(this, arguments));
|
||||
};
|
||||
};
|
||||
|
||||
@@ -422,6 +422,44 @@ describe('GitRepositoryAsync', () => {
|
||||
expect(repo.isStatusModified(status)).toBe(true)
|
||||
expect(repo.isStatusNew(status)).toBe(false)
|
||||
})
|
||||
|
||||
it('emits did-change-statuses if the status changes', async () => {
|
||||
const someNewPath = path.join(workingDirectory, 'MyNewJSFramework.md')
|
||||
fs.writeFileSync(someNewPath, '')
|
||||
|
||||
const statusHandler = jasmine.createSpy('statusHandler')
|
||||
repo.onDidChangeStatuses(statusHandler)
|
||||
|
||||
await repo.refreshStatus()
|
||||
|
||||
waitsFor('the onDidChangeStatuses handler to be called', () => statusHandler.callCount > 0)
|
||||
})
|
||||
|
||||
it('emits did-change-statuses if the branch changes', async () => {
|
||||
const statusHandler = jasmine.createSpy('statusHandler')
|
||||
repo.onDidChangeStatuses(statusHandler)
|
||||
|
||||
repo._refreshBranch = jasmine.createSpy('_refreshBranch').andCallFake(() => {
|
||||
return Promise.resolve(true)
|
||||
})
|
||||
|
||||
await repo.refreshStatus()
|
||||
|
||||
waitsFor('the onDidChangeStatuses handler to be called', () => statusHandler.callCount > 0)
|
||||
})
|
||||
|
||||
it('emits did-change-statuses if the ahead/behind changes', async () => {
|
||||
const statusHandler = jasmine.createSpy('statusHandler')
|
||||
repo.onDidChangeStatuses(statusHandler)
|
||||
|
||||
repo._refreshAheadBehindCount = jasmine.createSpy('_refreshAheadBehindCount').andCallFake(() => {
|
||||
return Promise.resolve(true)
|
||||
})
|
||||
|
||||
await repo.refreshStatus()
|
||||
|
||||
waitsFor('the onDidChangeStatuses handler to be called', () => statusHandler.callCount > 0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isProjectAtRoot()', () => {
|
||||
@@ -541,7 +579,7 @@ describe('GitRepositoryAsync', () => {
|
||||
await atom.workspace.open('file.txt')
|
||||
|
||||
project2 = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
project2.deserialize(atom.project.serialize(), atom.deserializers)
|
||||
project2.deserialize(atom.project.serialize({isUnloading: true}))
|
||||
|
||||
const repo = project2.getRepositories()[0].async
|
||||
waitsForPromise(() => repo.refreshStatus())
|
||||
@@ -676,7 +714,7 @@ describe('GitRepositoryAsync', () => {
|
||||
repo = GitRepositoryAsync.open(workingDirectory)
|
||||
})
|
||||
|
||||
it('returns 0, 0 for a branch with no upstream', async () => {
|
||||
it('returns 1, 0 for a branch which is ahead by 1', async () => {
|
||||
await repo.refreshStatus()
|
||||
|
||||
const {ahead, behind} = await repo.getCachedUpstreamAheadBehindCount('You-Dont-Need-jQuery')
|
||||
|
||||
@@ -347,7 +347,7 @@ describe "GitRepository", ->
|
||||
|
||||
runs ->
|
||||
project2 = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
project2.deserialize(atom.project.serialize(), atom.deserializers)
|
||||
project2.deserialize(atom.project.serialize({isUnloading: false}))
|
||||
buffer = project2.getBuffers()[0]
|
||||
|
||||
waitsFor ->
|
||||
|
||||
@@ -123,6 +123,34 @@ describe "Starting Atom", ->
|
||||
.waitForPaneItemCount(0, 1000)
|
||||
.treeViewRootDirectories()
|
||||
.then ({value}) -> expect(value).toEqual([otherTempDirPath])
|
||||
describe "when using the -a, --add option", ->
|
||||
it "reuses that window and add the folder to project paths", ->
|
||||
fourthTempDir = temp.mkdirSync("a-fourth-dir")
|
||||
fourthTempFilePath = path.join(fourthTempDir, "a-file")
|
||||
fs.writeFileSync(fourthTempFilePath, "4 - This file was already here.")
|
||||
|
||||
fifthTempDir = temp.mkdirSync("a-fifth-dir")
|
||||
fifthTempFilePath = path.join(fifthTempDir, "a-file")
|
||||
fs.writeFileSync(fifthTempFilePath, "5 - This file was already here.")
|
||||
|
||||
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
|
||||
client
|
||||
.waitForPaneItemCount(1, 5000)
|
||||
|
||||
# Opening another file reuses the same window and add parent dir to
|
||||
# project paths.
|
||||
.startAnotherAtom(['-a', fourthTempFilePath], ATOM_HOME: atomHome)
|
||||
.waitForPaneItemCount(2, 5000)
|
||||
.waitForWindowCount(1, 1000)
|
||||
.treeViewRootDirectories()
|
||||
.then ({value}) -> expect(value).toEqual([tempDirPath, fourthTempDir])
|
||||
.execute -> atom.workspace.getActiveTextEditor().getText()
|
||||
.then ({value: text}) -> expect(text).toBe "4 - This file was already here."
|
||||
|
||||
# Opening another directory resuses the same window and add the folder to project paths.
|
||||
.startAnotherAtom(['--add', fifthTempDir], ATOM_HOME: atomHome)
|
||||
.treeViewRootDirectories()
|
||||
.then ({value}) -> expect(value).toEqual([tempDirPath, fourthTempDir, fifthTempDir])
|
||||
|
||||
it "opens the new window offset from the other window", ->
|
||||
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
|
||||
|
||||
@@ -430,7 +430,7 @@ describe "LanguageMode", ->
|
||||
languageMode.foldAll()
|
||||
|
||||
fold1 = editor.tokenizedLineForScreenRow(0).fold
|
||||
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 19]
|
||||
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 30]
|
||||
fold1.destroy()
|
||||
|
||||
fold2 = editor.tokenizedLineForScreenRow(1).fold
|
||||
@@ -441,6 +441,14 @@ describe "LanguageMode", ->
|
||||
fold4 = editor.tokenizedLineForScreenRow(3).fold
|
||||
expect([fold4.getStartRow(), fold4.getEndRow()]).toEqual [6, 8]
|
||||
|
||||
fold5 = editor.tokenizedLineForScreenRow(6).fold
|
||||
expect([fold5.getStartRow(), fold5.getEndRow()]).toEqual [11, 16]
|
||||
fold5.destroy()
|
||||
|
||||
fold6 = editor.tokenizedLineForScreenRow(13).fold
|
||||
expect([fold6.getStartRow(), fold6.getEndRow()]).toEqual [21, 22]
|
||||
fold6.destroy()
|
||||
|
||||
describe ".foldAllAtIndentLevel()", ->
|
||||
it "folds every foldable range at a given indentLevel", ->
|
||||
languageMode.foldAllAtIndentLevel(2)
|
||||
@@ -450,19 +458,48 @@ describe "LanguageMode", ->
|
||||
fold1.destroy()
|
||||
|
||||
fold2 = editor.tokenizedLineForScreenRow(11).fold
|
||||
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [11, 14]
|
||||
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [11, 16]
|
||||
fold2.destroy()
|
||||
|
||||
fold3 = editor.tokenizedLineForScreenRow(17).fold
|
||||
expect([fold3.getStartRow(), fold3.getEndRow()]).toEqual [17, 20]
|
||||
fold3.destroy()
|
||||
|
||||
fold4 = editor.tokenizedLineForScreenRow(21).fold
|
||||
expect([fold4.getStartRow(), fold4.getEndRow()]).toEqual [21, 22]
|
||||
fold4.destroy()
|
||||
|
||||
fold5 = editor.tokenizedLineForScreenRow(24).fold
|
||||
expect([fold5.getStartRow(), fold5.getEndRow()]).toEqual [24, 25]
|
||||
fold5.destroy()
|
||||
|
||||
it "does not fold anything but the indentLevel", ->
|
||||
languageMode.foldAllAtIndentLevel(0)
|
||||
|
||||
fold1 = editor.tokenizedLineForScreenRow(0).fold
|
||||
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 19]
|
||||
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 30]
|
||||
fold1.destroy()
|
||||
|
||||
fold2 = editor.tokenizedLineForScreenRow(5).fold
|
||||
expect(fold2).toBeFalsy()
|
||||
|
||||
describe ".isFoldableAtBufferRow(bufferRow)", ->
|
||||
it "returns true if the line starts a multi-line comment", ->
|
||||
expect(languageMode.isFoldableAtBufferRow(1)).toBe true
|
||||
expect(languageMode.isFoldableAtBufferRow(6)).toBe true
|
||||
expect(languageMode.isFoldableAtBufferRow(8)).toBe false
|
||||
expect(languageMode.isFoldableAtBufferRow(11)).toBe true
|
||||
expect(languageMode.isFoldableAtBufferRow(15)).toBe false
|
||||
expect(languageMode.isFoldableAtBufferRow(17)).toBe true
|
||||
expect(languageMode.isFoldableAtBufferRow(21)).toBe true
|
||||
expect(languageMode.isFoldableAtBufferRow(24)).toBe true
|
||||
expect(languageMode.isFoldableAtBufferRow(28)).toBe false
|
||||
|
||||
it "does not return true for a line in the middle of a comment that's followed by an indented line", ->
|
||||
expect(languageMode.isFoldableAtBufferRow(7)).toBe false
|
||||
editor.buffer.insert([8, 0], ' ')
|
||||
expect(languageMode.isFoldableAtBufferRow(7)).toBe false
|
||||
|
||||
describe "css", ->
|
||||
beforeEach ->
|
||||
waitsForPromise ->
|
||||
|
||||
@@ -17,6 +17,20 @@ describe "PackageManager", ->
|
||||
beforeEach ->
|
||||
workspaceElement = atom.views.getView(atom.workspace)
|
||||
|
||||
describe "::getApmPath()", ->
|
||||
it "returns the path to the apm command", ->
|
||||
apmPath = path.join(process.resourcesPath, "app", "apm", "bin", "apm")
|
||||
if process.platform is 'win32'
|
||||
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")
|
||||
|
||||
it "returns the value of the core.apmPath config setting", ->
|
||||
expect(atom.packages.getApmPath()).toBe "/path/to/apm"
|
||||
|
||||
describe "::loadPackage(name)", ->
|
||||
beforeEach ->
|
||||
atom.config.set("core.disabledPackages", [])
|
||||
@@ -55,12 +69,17 @@ describe "PackageManager", ->
|
||||
it "normalizes short repository urls in package.json", ->
|
||||
{metadata} = atom.packages.loadPackage("package-with-short-url-package-json")
|
||||
expect(metadata.repository.type).toBe "git"
|
||||
expect(metadata.repository.url).toBe "https://github.com/example/repo.git"
|
||||
expect(metadata.repository.url).toBe "https://github.com/example/repo"
|
||||
|
||||
{metadata} = atom.packages.loadPackage("package-with-invalid-url-package-json")
|
||||
expect(metadata.repository.type).toBe "git"
|
||||
expect(metadata.repository.url).toBe "foo"
|
||||
|
||||
it "trims git+ from the beginning and .git from the end of repository URLs, even if npm already normalized them ", ->
|
||||
{metadata} = atom.packages.loadPackage("package-with-prefixed-and-suffixed-repo-url")
|
||||
expect(metadata.repository.type).toBe "git"
|
||||
expect(metadata.repository.url).toBe "https://github.com/example/repo"
|
||||
|
||||
it "returns null if the package is not found in any package directory", ->
|
||||
spyOn(console, 'warn')
|
||||
expect(atom.packages.loadPackage("this-package-cannot-be-found")).toBeNull()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{extend} = require 'underscore-plus'
|
||||
{Emitter} = require 'event-kit'
|
||||
Grim = require 'grim'
|
||||
Pane = require '../src/pane'
|
||||
PaneAxis = require '../src/pane-axis'
|
||||
PaneContainer = require '../src/pane-container'
|
||||
@@ -18,8 +19,8 @@ describe "Pane", ->
|
||||
onDidDestroy: (fn) -> @emitter.on('did-destroy', fn)
|
||||
destroy: -> @destroyed = true; @emitter.emit('did-destroy')
|
||||
isDestroyed: -> @destroyed
|
||||
isPending: -> @pending
|
||||
pending: false
|
||||
onDidTerminatePendingState: (callback) -> @emitter.on 'terminate-pending-state', callback
|
||||
terminatePendingState: -> @emitter.emit 'terminate-pending-state'
|
||||
|
||||
beforeEach ->
|
||||
confirm = spyOn(atom.applicationDelegate, 'confirm')
|
||||
@@ -92,7 +93,7 @@ describe "Pane", ->
|
||||
pane = new Pane(paneParams(items: [new Item("A"), new Item("B")]))
|
||||
[item1, item2] = pane.getItems()
|
||||
item3 = new Item("C")
|
||||
pane.addItem(item3, 1)
|
||||
pane.addItem(item3, index: 1)
|
||||
expect(pane.getItems()).toEqual [item1, item3, item2]
|
||||
|
||||
it "adds the item after the active item if no index is provided", ->
|
||||
@@ -115,7 +116,7 @@ describe "Pane", ->
|
||||
pane.onDidAddItem (event) -> events.push(event)
|
||||
|
||||
item = new Item("C")
|
||||
pane.addItem(item, 1)
|
||||
pane.addItem(item, index: 1)
|
||||
expect(events).toEqual [{item, index: 1, moved: false}]
|
||||
|
||||
it "throws an exception if the item is already present on a pane", ->
|
||||
@@ -132,15 +133,56 @@ describe "Pane", ->
|
||||
expect(-> pane.addItem('foo')).toThrow()
|
||||
expect(-> pane.addItem(1)).toThrow()
|
||||
|
||||
it "destroys any existing pending item if the new item is pending", ->
|
||||
it "destroys any existing pending item", ->
|
||||
pane = new Pane(paneParams(items: []))
|
||||
itemA = new Item("A")
|
||||
itemB = new Item("B")
|
||||
itemA.pending = true
|
||||
itemB.pending = true
|
||||
pane.addItem(itemA)
|
||||
itemC = new Item("C")
|
||||
pane.addItem(itemA, pending: false)
|
||||
pane.addItem(itemB, pending: true)
|
||||
pane.addItem(itemC, pending: false)
|
||||
expect(itemB.isDestroyed()).toBe true
|
||||
|
||||
it "adds the new item before destroying any existing pending item", ->
|
||||
eventOrder = []
|
||||
|
||||
pane = new Pane(paneParams(items: []))
|
||||
itemA = new Item("A")
|
||||
itemB = new Item("B")
|
||||
pane.addItem(itemA, pending: true)
|
||||
|
||||
pane.onDidAddItem ({item}) ->
|
||||
eventOrder.push("add") if item is itemB
|
||||
|
||||
pane.onDidRemoveItem ({item}) ->
|
||||
eventOrder.push("remove") if item is itemA
|
||||
|
||||
pane.addItem(itemB)
|
||||
expect(itemA.isDestroyed()).toBe true
|
||||
|
||||
waitsFor ->
|
||||
eventOrder.length is 2
|
||||
|
||||
runs ->
|
||||
expect(eventOrder).toEqual ["add", "remove"]
|
||||
|
||||
describe "when using the old API of ::addItem(item, index)", ->
|
||||
beforeEach ->
|
||||
spyOn Grim, "deprecate"
|
||||
|
||||
it "supports the older public API", ->
|
||||
pane = new Pane(paneParams(items: []))
|
||||
itemA = new Item("A")
|
||||
itemB = new Item("B")
|
||||
itemC = new Item("C")
|
||||
pane.addItem(itemA, 0)
|
||||
pane.addItem(itemB, 0)
|
||||
pane.addItem(itemC, 0)
|
||||
expect(pane.getItems()).toEqual [itemC, itemB, itemA]
|
||||
|
||||
it "shows a deprecation warning", ->
|
||||
pane = new Pane(paneParams(items: []))
|
||||
pane.addItem(new Item(), 2)
|
||||
expect(Grim.deprecate).toHaveBeenCalledWith "Pane::addItem(item, 2) is deprecated in favor of Pane::addItem(item, {index: 2})"
|
||||
|
||||
describe "::activateItem(item)", ->
|
||||
pane = null
|
||||
@@ -172,21 +214,83 @@ describe "Pane", ->
|
||||
beforeEach ->
|
||||
itemC = new Item("C")
|
||||
itemD = new Item("D")
|
||||
itemC.pending = true
|
||||
itemD.pending = true
|
||||
|
||||
it "replaces the active item if it is pending", ->
|
||||
pane.activateItem(itemC)
|
||||
pane.activateItem(itemC, pending: true)
|
||||
expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'C', 'B']
|
||||
pane.activateItem(itemD)
|
||||
pane.activateItem(itemD, pending: true)
|
||||
expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'D', 'B']
|
||||
|
||||
it "adds the item after the active item if it is not pending", ->
|
||||
pane.activateItem(itemC)
|
||||
pane.activateItem(itemC, pending: true)
|
||||
pane.activateItemAtIndex(2)
|
||||
pane.activateItem(itemD)
|
||||
pane.activateItem(itemD, pending: true)
|
||||
expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'B', 'D']
|
||||
|
||||
describe "::setPendingItem", ->
|
||||
pane = null
|
||||
|
||||
beforeEach ->
|
||||
pane = atom.workspace.getActivePane()
|
||||
|
||||
it "changes the pending item", ->
|
||||
expect(pane.getPendingItem()).toBeNull()
|
||||
pane.setPendingItem("fake item")
|
||||
expect(pane.getPendingItem()).toEqual "fake item"
|
||||
|
||||
describe "::onItemDidTerminatePendingState callback", ->
|
||||
pane = null
|
||||
callbackCalled = false
|
||||
|
||||
beforeEach ->
|
||||
pane = atom.workspace.getActivePane()
|
||||
callbackCalled = false
|
||||
|
||||
it "is called when the pending item changes", ->
|
||||
pane.setPendingItem("fake item one")
|
||||
pane.onItemDidTerminatePendingState (item) ->
|
||||
callbackCalled = true
|
||||
expect(item).toEqual "fake item one"
|
||||
pane.setPendingItem("fake item two")
|
||||
expect(callbackCalled).toBeTruthy()
|
||||
|
||||
it "has access to the new pending item via ::getPendingItem", ->
|
||||
pane.setPendingItem("fake item one")
|
||||
pane.onItemDidTerminatePendingState (item) ->
|
||||
callbackCalled = true
|
||||
expect(pane.getPendingItem()).toEqual "fake item two"
|
||||
pane.setPendingItem("fake item two")
|
||||
expect(callbackCalled).toBeTruthy()
|
||||
|
||||
describe "::activateNextRecentlyUsedItem() and ::activatePreviousRecentlyUsedItem()", ->
|
||||
it "sets the active item to the next/previous item in the itemStack, looping around at either end", ->
|
||||
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C"), new Item("D"), new Item("E")]))
|
||||
[item1, item2, item3, item4, item5] = pane.getItems()
|
||||
pane.itemStack = [item3, item1, item2, item5, item4]
|
||||
|
||||
pane.activateItem(item4)
|
||||
expect(pane.getActiveItem()).toBe item4
|
||||
pane.activateNextRecentlyUsedItem()
|
||||
expect(pane.getActiveItem()).toBe item5
|
||||
pane.activateNextRecentlyUsedItem()
|
||||
expect(pane.getActiveItem()).toBe item2
|
||||
pane.activatePreviousRecentlyUsedItem()
|
||||
expect(pane.getActiveItem()).toBe item5
|
||||
pane.activatePreviousRecentlyUsedItem()
|
||||
expect(pane.getActiveItem()).toBe item4
|
||||
pane.activatePreviousRecentlyUsedItem()
|
||||
expect(pane.getActiveItem()).toBe item3
|
||||
pane.activatePreviousRecentlyUsedItem()
|
||||
expect(pane.getActiveItem()).toBe item1
|
||||
pane.activateNextRecentlyUsedItem()
|
||||
expect(pane.getActiveItem()).toBe item3
|
||||
pane.activateNextRecentlyUsedItem()
|
||||
expect(pane.getActiveItem()).toBe item4
|
||||
pane.activateNextRecentlyUsedItem()
|
||||
pane.moveActiveItemToTopOfStack()
|
||||
expect(pane.getActiveItem()).toBe item5
|
||||
expect(pane.itemStack[4]).toBe item5
|
||||
|
||||
describe "::activateNextItem() and ::activatePreviousItem()", ->
|
||||
it "sets the active item to the next/previous item, looping around at either end", ->
|
||||
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
|
||||
@@ -253,7 +357,7 @@ describe "Pane", ->
|
||||
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
|
||||
[item1, item2, item3] = pane.getItems()
|
||||
|
||||
it "removes the item from the items list and destroyes it", ->
|
||||
it "removes the item from the items list and destroys it", ->
|
||||
expect(pane.getActiveItem()).toBe item1
|
||||
pane.destroyItem(item2)
|
||||
expect(item2 in pane.getItems()).toBe false
|
||||
@@ -264,6 +368,23 @@ describe "Pane", ->
|
||||
expect(item1 in pane.getItems()).toBe false
|
||||
expect(item1.isDestroyed()).toBe true
|
||||
|
||||
it "removes the item from the itemStack", ->
|
||||
pane.itemStack = [item2, item3, item1]
|
||||
|
||||
pane.activateItem(item1)
|
||||
expect(pane.getActiveItem()).toBe item1
|
||||
pane.destroyItem(item3)
|
||||
expect(pane.itemStack).toEqual [item2, item1]
|
||||
expect(pane.getActiveItem()).toBe item1
|
||||
|
||||
pane.destroyItem(item1)
|
||||
expect(pane.itemStack).toEqual [item2]
|
||||
expect(pane.getActiveItem()).toBe item2
|
||||
|
||||
pane.destroyItem(item2)
|
||||
expect(pane.itemStack).toEqual []
|
||||
expect(pane.getActiveItem()).toBeUndefined()
|
||||
|
||||
it "invokes ::onWillDestroyItem() observers before destroying the item", ->
|
||||
events = []
|
||||
pane.onWillDestroyItem (event) ->
|
||||
@@ -605,6 +726,23 @@ describe "Pane", ->
|
||||
expect(pane2.isDestroyed()).toBe true
|
||||
expect(item4.isDestroyed()).toBe false
|
||||
|
||||
describe "when the item being moved is pending", ->
|
||||
it "is made permanent in the new pane", ->
|
||||
item6 = new Item("F")
|
||||
pane1.addItem(item6, pending: true)
|
||||
expect(pane1.getPendingItem()).toEqual item6
|
||||
pane1.moveItemToPane(item6, pane2, 0)
|
||||
expect(pane2.getPendingItem()).not.toEqual item6
|
||||
|
||||
describe "when the target pane has a pending item", ->
|
||||
it "does not destroy the pending item", ->
|
||||
item6 = new Item("F")
|
||||
pane1.addItem(item6, pending: true)
|
||||
expect(pane1.getPendingItem()).toEqual item6
|
||||
pane2.moveItemToPane(item5, pane1, 0)
|
||||
expect(pane1.getPendingItem()).toEqual item6
|
||||
|
||||
|
||||
describe "split methods", ->
|
||||
[pane1, item1, container] = []
|
||||
|
||||
@@ -806,6 +944,67 @@ describe "Pane", ->
|
||||
pane2.destroy()
|
||||
expect(container.root).toBe pane1
|
||||
|
||||
describe "pending state", ->
|
||||
editor1 = null
|
||||
pane = null
|
||||
eventCount = null
|
||||
|
||||
beforeEach ->
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.txt', pending: true).then (o) ->
|
||||
editor1 = o
|
||||
pane = atom.workspace.getActivePane()
|
||||
|
||||
runs ->
|
||||
eventCount = 0
|
||||
editor1.onDidTerminatePendingState -> eventCount++
|
||||
|
||||
it "does not open file in pending state by default", ->
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js').then (o) ->
|
||||
editor1 = o
|
||||
pane = atom.workspace.getActivePane()
|
||||
|
||||
runs ->
|
||||
expect(pane.getPendingItem()).toBeNull()
|
||||
|
||||
it "opens file in pending state if 'pending' option is true", ->
|
||||
expect(pane.getPendingItem()).toEqual editor1
|
||||
|
||||
it "terminates pending state if ::terminatePendingState is invoked", ->
|
||||
editor1.terminatePendingState()
|
||||
|
||||
expect(pane.getPendingItem()).toBeNull()
|
||||
expect(eventCount).toBe 1
|
||||
|
||||
it "terminates pending state when buffer is changed", ->
|
||||
editor1.insertText('I\'ll be back!')
|
||||
advanceClock(editor1.getBuffer().stoppedChangingDelay)
|
||||
|
||||
expect(pane.getPendingItem()).toBeNull()
|
||||
expect(eventCount).toBe 1
|
||||
|
||||
it "only calls terminate handler once when text is modified twice", ->
|
||||
editor1.insertText('Some text')
|
||||
advanceClock(editor1.getBuffer().stoppedChangingDelay)
|
||||
|
||||
editor1.save()
|
||||
|
||||
editor1.insertText('More text')
|
||||
advanceClock(editor1.getBuffer().stoppedChangingDelay)
|
||||
|
||||
expect(pane.getPendingItem()).toBeNull()
|
||||
expect(eventCount).toBe 1
|
||||
|
||||
it "only calls clearPendingItem if there is a pending item to clear", ->
|
||||
spyOn(pane, "clearPendingItem").andCallThrough()
|
||||
|
||||
editor1.terminatePendingState()
|
||||
editor1.terminatePendingState()
|
||||
|
||||
expect(pane.getPendingItem()).toBeNull()
|
||||
expect(pane.clearPendingItem.callCount).toBe 1
|
||||
|
||||
describe "serialization", ->
|
||||
pane = null
|
||||
|
||||
@@ -837,3 +1036,30 @@ describe "Pane", ->
|
||||
pane.focus()
|
||||
newPane = Pane.deserialize(pane.serialize(), atom)
|
||||
expect(newPane.focused).toBe true
|
||||
|
||||
it "can serialize and deserialize the order of the items in the itemStack", ->
|
||||
[item1, item2, item3] = pane.getItems()
|
||||
pane.itemStack = [item3, item1, item2]
|
||||
newPane = Pane.deserialize(pane.serialize(), atom)
|
||||
expect(newPane.itemStack).toEqual pane.itemStack
|
||||
expect(newPane.itemStack[2]).toEqual item2
|
||||
|
||||
it "builds the itemStack if the itemStack is not serialized", ->
|
||||
[item1, item2, item3] = pane.getItems()
|
||||
newPane = Pane.deserialize(pane.serialize(), atom)
|
||||
expect(newPane.getItems()).toEqual newPane.itemStack
|
||||
|
||||
it "rebuilds the itemStack if items.length does not match itemStack.length", ->
|
||||
[item1, item2, item3] = pane.getItems()
|
||||
pane.itemStack = [item2, item3]
|
||||
newPane = Pane.deserialize(pane.serialize(), atom)
|
||||
expect(newPane.getItems()).toEqual newPane.itemStack
|
||||
|
||||
it "does not serialize the reference to the items in the itemStack for pane items that will not be serialized", ->
|
||||
[item1, item2, item3] = pane.getItems()
|
||||
pane.itemStack = [item2, item1, item3]
|
||||
unserializable = {}
|
||||
pane.activateItem(unserializable)
|
||||
|
||||
newPane = Pane.deserialize(pane.serialize(), atom)
|
||||
expect(newPane.itemStack).toEqual [item2, item1, item3]
|
||||
|
||||
@@ -21,6 +21,14 @@ describe "Project", ->
|
||||
afterEach ->
|
||||
deserializedProject?.destroy()
|
||||
|
||||
it "does not deserialize paths to non directories", ->
|
||||
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
state = atom.project.serialize()
|
||||
state.paths.push('/directory/that/does/not/exist')
|
||||
state.paths.push(path.join(__dirname, 'fixtures', 'sample.js'))
|
||||
deserializedProject.deserialize(state, atom.deserializers)
|
||||
expect(deserializedProject.getPaths()).toEqual(atom.project.getPaths())
|
||||
|
||||
it "does not include unretained buffers in the serialized state", ->
|
||||
waitsForPromise ->
|
||||
atom.project.bufferForPath('a')
|
||||
@@ -29,7 +37,7 @@ describe "Project", ->
|
||||
expect(atom.project.getBuffers().length).toBe 1
|
||||
|
||||
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
|
||||
deserializedProject.deserialize(atom.project.serialize({isUnloading: false}))
|
||||
expect(deserializedProject.getBuffers().length).toBe 0
|
||||
|
||||
it "listens for destroyed events on deserialized buffers and removes them when they are destroyed", ->
|
||||
@@ -39,7 +47,7 @@ describe "Project", ->
|
||||
runs ->
|
||||
expect(atom.project.getBuffers().length).toBe 1
|
||||
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
|
||||
deserializedProject.deserialize(atom.project.serialize({isUnloading: false}))
|
||||
|
||||
expect(deserializedProject.getBuffers().length).toBe 1
|
||||
deserializedProject.getBuffers()[0].destroy()
|
||||
@@ -56,7 +64,7 @@ describe "Project", ->
|
||||
expect(atom.project.getBuffers().length).toBe 1
|
||||
fs.mkdirSync(pathToOpen)
|
||||
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
|
||||
deserializedProject.deserialize(atom.project.serialize({isUnloading: false}))
|
||||
expect(deserializedProject.getBuffers().length).toBe 0
|
||||
|
||||
it "does not deserialize buffers when their path is inaccessible", ->
|
||||
@@ -70,9 +78,26 @@ describe "Project", ->
|
||||
expect(atom.project.getBuffers().length).toBe 1
|
||||
fs.chmodSync(pathToOpen, '000')
|
||||
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
|
||||
deserializedProject.deserialize(atom.project.serialize({isUnloading: false}))
|
||||
expect(deserializedProject.getBuffers().length).toBe 0
|
||||
|
||||
it "serializes marker layers only if Atom is quitting", ->
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('a')
|
||||
|
||||
runs ->
|
||||
bufferA = atom.project.getBuffers()[0]
|
||||
layerA = bufferA.addMarkerLayer(maintainHistory: true)
|
||||
markerA = layerA.markPosition([0, 3])
|
||||
|
||||
notQuittingProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
notQuittingProject.deserialize(atom.project.serialize({isUnloading: false}))
|
||||
expect(notQuittingProject.getBuffers()[0].getMarkerLayer(layerA.id)?.getMarker(markerA.id)).toBeUndefined()
|
||||
|
||||
quittingProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
|
||||
quittingProject.deserialize(atom.project.serialize({isUnloading: true}))
|
||||
expect(quittingProject.getBuffers()[0].getMarkerLayer(layerA.id)?.getMarker(markerA.id)).not.toBeUndefined()
|
||||
|
||||
describe "when an editor is saved and the project has no path", ->
|
||||
it "sets the project's path to the saved file's parent directory", ->
|
||||
tempFile = temp.openSync().path
|
||||
|
||||
@@ -91,7 +91,9 @@ describe "TextEditorPresenter", ->
|
||||
expectNoStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(false, presenter, fn)
|
||||
|
||||
waitsForStateToUpdate = (presenter, fn) ->
|
||||
waitsFor "presenter state to update", 1000, (done) ->
|
||||
line = new Error().stack.split('\n')[2].split(':')[1]
|
||||
|
||||
waitsFor "presenter state to update at line #{line}", 1000, (done) ->
|
||||
disposable = presenter.onDidUpdateState ->
|
||||
disposable.dispose()
|
||||
process.nextTick(done)
|
||||
@@ -633,16 +635,28 @@ describe "TextEditorPresenter", ->
|
||||
expectStateUpdate presenter, -> presenter.setExplicitHeight(500)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe 500
|
||||
|
||||
it "adds the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true", ->
|
||||
presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10)
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(300)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
|
||||
describe "scrollPastEnd", ->
|
||||
it "adds the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true", ->
|
||||
presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10)
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(300)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
|
||||
|
||||
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight + presenter.clientHeight - (presenter.lineHeight * 3)
|
||||
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight + presenter.clientHeight - (presenter.lineHeight * 3)
|
||||
|
||||
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
|
||||
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
|
||||
|
||||
it "doesn't add the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true but the presenter is created with scrollPastEnd as false", ->
|
||||
presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10, scrollPastEnd: false)
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(300)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
|
||||
|
||||
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
|
||||
|
||||
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
|
||||
|
||||
describe ".scrollTop", ->
|
||||
it "tracks the value of ::scrollTop", ->
|
||||
@@ -1338,7 +1352,9 @@ describe "TextEditorPresenter", ->
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(7)
|
||||
blockDecoration4 = null
|
||||
|
||||
waitsForStateToUpdate presenter, blockDecoration4 = addBlockDecorationAfterScreenRow(7)
|
||||
waitsForStateToUpdate presenter, ->
|
||||
blockDecoration4 = addBlockDecorationAfterScreenRow(7)
|
||||
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([blockDecoration1])
|
||||
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([])
|
||||
|
||||
38
spec/text-editor-registry-spec.coffee
Normal file
38
spec/text-editor-registry-spec.coffee
Normal file
@@ -0,0 +1,38 @@
|
||||
TextEditorRegistry = require '../src/text-editor-registry'
|
||||
|
||||
describe "TextEditorRegistry", ->
|
||||
[registry, editor] = []
|
||||
|
||||
beforeEach ->
|
||||
registry = new TextEditorRegistry
|
||||
|
||||
describe "when a TextEditor is added", ->
|
||||
it "gets added to the list of registered editors", ->
|
||||
editor = {}
|
||||
registry.add(editor)
|
||||
expect(registry.editors.size).toBe 1
|
||||
expect(registry.editors.has(editor)).toBe(true)
|
||||
|
||||
it "returns a Disposable that can unregister the editor", ->
|
||||
editor = {}
|
||||
disposable = registry.add(editor)
|
||||
expect(registry.editors.size).toBe 1
|
||||
disposable.dispose()
|
||||
expect(registry.editors.size).toBe 0
|
||||
|
||||
describe "when the registry is observed", ->
|
||||
it "calls the callback for current and future editors until unsubscribed", ->
|
||||
[editor1, editor2, editor3] = [{}, {}, {}]
|
||||
|
||||
registry.add(editor1)
|
||||
subscription = registry.observe spy = jasmine.createSpy()
|
||||
expect(spy.calls.length).toBe 1
|
||||
|
||||
registry.add(editor2)
|
||||
expect(spy.calls.length).toBe 2
|
||||
expect(spy.argsForCall[0][0]).toBe editor1
|
||||
expect(spy.argsForCall[1][0]).toBe editor2
|
||||
|
||||
subscription.dispose()
|
||||
registry.add(editor3)
|
||||
expect(spy.calls.length).toBe 2
|
||||
@@ -55,16 +55,6 @@ describe "TextEditor", ->
|
||||
|
||||
expect(editor.tokenizedLineForScreenRow(0).invisibles.eol).toBe '?'
|
||||
|
||||
it "restores pending tabs in pending state", ->
|
||||
expect(editor.isPending()).toBe false
|
||||
editor2 = TextEditor.deserialize(editor.serialize(), atom)
|
||||
expect(editor2.isPending()).toBe false
|
||||
|
||||
pendingEditor = atom.workspace.buildTextEditor(pending: true)
|
||||
expect(pendingEditor.isPending()).toBe true
|
||||
editor3 = TextEditor.deserialize(pendingEditor.serialize(), atom)
|
||||
expect(editor3.isPending()).toBe true
|
||||
|
||||
describe "when the editor is constructed with the largeFileMode option set to true", ->
|
||||
it "loads the editor but doesn't tokenize", ->
|
||||
editor = null
|
||||
@@ -2142,20 +2132,31 @@ describe "TextEditor", ->
|
||||
editor.splitSelectionsIntoLines()
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 3]]]
|
||||
|
||||
describe ".consolidateSelections()", ->
|
||||
it "destroys all selections but the least recent, returning true if any selections were destroyed", ->
|
||||
editor.setSelectedBufferRange([[3, 16], [3, 21]])
|
||||
selection1 = editor.getLastSelection()
|
||||
describe "::consolidateSelections()", ->
|
||||
makeMultipleSelections = ->
|
||||
selection.setBufferRange [[3, 16], [3, 21]]
|
||||
selection2 = editor.addSelectionForBufferRange([[3, 25], [3, 34]])
|
||||
selection3 = editor.addSelectionForBufferRange([[8, 4], [8, 10]])
|
||||
selection4 = editor.addSelectionForBufferRange([[1, 6], [1, 10]])
|
||||
expect(editor.getSelections()).toEqual [selection, selection2, selection3, selection4]
|
||||
[selection, selection2, selection3, selection4]
|
||||
|
||||
it "destroys all selections but the oldest selection and autoscrolls to it, returning true if any selections were destroyed", ->
|
||||
[selection1] = makeMultipleSelections()
|
||||
|
||||
autoscrollEvents = []
|
||||
editor.onDidRequestAutoscroll (event) -> autoscrollEvents.push(event)
|
||||
|
||||
expect(editor.getSelections()).toEqual [selection1, selection2, selection3]
|
||||
expect(editor.consolidateSelections()).toBeTruthy()
|
||||
expect(editor.getSelections()).toEqual [selection1]
|
||||
expect(selection1.isEmpty()).toBeFalsy()
|
||||
expect(editor.consolidateSelections()).toBeFalsy()
|
||||
expect(editor.getSelections()).toEqual [selection1]
|
||||
|
||||
expect(autoscrollEvents).toEqual([
|
||||
{screenRange: selection1.getScreenRange(), options: {center: true, reversed: false}}
|
||||
])
|
||||
|
||||
describe "when the cursor is moved while there is a selection", ->
|
||||
makeSelection = -> selection.setBufferRange [[1, 2], [1, 5]]
|
||||
|
||||
@@ -5828,52 +5829,29 @@ describe "TextEditor", ->
|
||||
rangeIsReversed: false
|
||||
}
|
||||
|
||||
describe "pending state", ->
|
||||
editor1 = null
|
||||
eventCount = null
|
||||
|
||||
describe "when the editor is constructed with the showInvisibles option set to false", ->
|
||||
beforeEach ->
|
||||
atom.workspace.destroyActivePane()
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.txt', pending: true).then (o) -> editor1 = o
|
||||
atom.workspace.open('sample.js', showInvisibles: false).then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
eventCount = 0
|
||||
editor1.onDidTerminatePendingState -> eventCount++
|
||||
it "ignores invisibles even if editor.showInvisibles is true", ->
|
||||
atom.config.set('editor.showInvisibles', true)
|
||||
invisibles = editor.tokenizedLineForScreenRow(0).invisibles
|
||||
expect(invisibles).toBe(null)
|
||||
|
||||
it "does not open file in pending state by default", ->
|
||||
expect(editor.isPending()).toBe false
|
||||
describe "when the editor is constructed with the grammar option set", ->
|
||||
beforeEach ->
|
||||
atom.workspace.destroyActivePane()
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-coffee-script')
|
||||
|
||||
it "opens file in pending state if 'pending' option is true", ->
|
||||
expect(editor1.isPending()).toBe true
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js', grammar: atom.grammars.grammarForScopeName('source.coffee')).then (o) -> editor = o
|
||||
|
||||
it "terminates pending state if ::terminatePendingState is invoked", ->
|
||||
editor1.terminatePendingState()
|
||||
it "sets the grammar", ->
|
||||
expect(editor.getGrammar().name).toBe 'CoffeeScript'
|
||||
|
||||
expect(editor1.isPending()).toBe false
|
||||
expect(eventCount).toBe 1
|
||||
|
||||
it "terminates pending state when buffer is changed", ->
|
||||
editor1.insertText('I\'ll be back!')
|
||||
advanceClock(editor1.getBuffer().stoppedChangingDelay)
|
||||
|
||||
expect(editor1.isPending()).toBe false
|
||||
expect(eventCount).toBe 1
|
||||
|
||||
it "only calls terminate handler once when text is modified twice", ->
|
||||
editor1.insertText('Some text')
|
||||
advanceClock(editor1.getBuffer().stoppedChangingDelay)
|
||||
|
||||
editor1.save()
|
||||
|
||||
editor1.insertText('More text')
|
||||
advanceClock(editor1.getBuffer().stoppedChangingDelay)
|
||||
|
||||
expect(editor1.isPending()).toBe false
|
||||
expect(eventCount).toBe 1
|
||||
|
||||
it "only calls terminate handler once when terminatePendingState is called twice", ->
|
||||
editor1.terminatePendingState()
|
||||
editor1.terminatePendingState()
|
||||
|
||||
expect(editor1.isPending()).toBe false
|
||||
expect(eventCount).toBe 1
|
||||
describe "::getElement", ->
|
||||
it "returns an element", ->
|
||||
expect(editor.getElement() instanceof HTMLElement).toBe(true)
|
||||
|
||||
@@ -28,6 +28,12 @@ describe "TooltipManager", ->
|
||||
hover element, ->
|
||||
expect(document.body.querySelector(".tooltip")).toHaveText("Title")
|
||||
|
||||
it "creates a tooltip immediately if the trigger type is manual", ->
|
||||
disposable = manager.add element, title: "Title", trigger: "manual"
|
||||
expect(document.body.querySelector(".tooltip")).toHaveText("Title")
|
||||
disposable.dispose()
|
||||
expect(document.body.querySelector(".tooltip")).toBeNull()
|
||||
|
||||
it "allows jQuery elements to be passed as the target", ->
|
||||
element2 = document.createElement('div')
|
||||
jasmine.attachToDOM(element2)
|
||||
|
||||
@@ -23,6 +23,15 @@ describe "ViewRegistry", ->
|
||||
component = new TestComponent
|
||||
expect(registry.getView(component)).toBe component.element
|
||||
|
||||
describe "when passed an object with a getElement function", ->
|
||||
it "returns the return value of getElement if it's an instance of HTMLElement", ->
|
||||
class TestComponent
|
||||
getElement: ->
|
||||
@myElement ?= document.createElement('div')
|
||||
|
||||
component = new TestComponent
|
||||
expect(registry.getView(component)).toBe component.myElement
|
||||
|
||||
describe "when passed a model object", ->
|
||||
describe "when a view provider is registered matching the object's constructor", ->
|
||||
it "constructs a view element and assigns the model on it", ->
|
||||
|
||||
@@ -22,11 +22,11 @@ describe "Workspace", ->
|
||||
describe "serialization", ->
|
||||
simulateReload = ->
|
||||
workspaceState = atom.workspace.serialize()
|
||||
projectState = atom.project.serialize()
|
||||
projectState = atom.project.serialize({isUnloading: true})
|
||||
atom.workspace.destroy()
|
||||
atom.project.destroy()
|
||||
atom.project = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm.bind(atom)})
|
||||
atom.project.deserialize(projectState, atom.deserializers)
|
||||
atom.project.deserialize(projectState)
|
||||
atom.workspace = new Workspace({
|
||||
config: atom.config, project: atom.project, packageManager: atom.packages,
|
||||
grammarRegistry: atom.grammars, deserializerManager: atom.deserializers,
|
||||
@@ -588,19 +588,69 @@ describe "Workspace", ->
|
||||
describe "when the file is already open in pending state", ->
|
||||
it "should terminate the pending state", ->
|
||||
editor = null
|
||||
pane = null
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js', pending: true).then (o) -> editor = o
|
||||
|
||||
atom.workspace.open('sample.js', pending: true).then (o) ->
|
||||
editor = o
|
||||
pane = atom.workspace.getActivePane()
|
||||
|
||||
runs ->
|
||||
expect(editor.isPending()).toBe true
|
||||
|
||||
expect(pane.getPendingItem()).toEqual editor
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js').then (o) -> editor = o
|
||||
|
||||
atom.workspace.open('sample.js')
|
||||
|
||||
runs ->
|
||||
expect(editor.isPending()).toBe false
|
||||
|
||||
expect(pane.getPendingItem()).toBeNull()
|
||||
|
||||
describe "when opening will switch from a pending tab to a permanent tab", ->
|
||||
it "keeps the pending tab open", ->
|
||||
editor1 = null
|
||||
editor2 = null
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.txt').then (o) ->
|
||||
editor1 = o
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample2.txt', pending: true).then (o) ->
|
||||
editor2 = o
|
||||
|
||||
runs ->
|
||||
pane = atom.workspace.getActivePane()
|
||||
pane.activateItem(editor1)
|
||||
expect(pane.getItems().length).toBe 2
|
||||
expect(pane.getItems()).toEqual [editor1, editor2]
|
||||
|
||||
describe "when replacing a pending item which is the last item in a second pane", ->
|
||||
it "does not destory the pane even if core.destroyEmptyPanes is on", ->
|
||||
atom.config.set('core.destroyEmptyPanes', true)
|
||||
editor1 = null
|
||||
editor2 = null
|
||||
leftPane = atom.workspace.getActivePane()
|
||||
rightPane = null
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js', pending: true, split: 'right').then (o) ->
|
||||
editor1 = o
|
||||
rightPane = atom.workspace.getActivePane()
|
||||
spyOn rightPane, "destroyed"
|
||||
|
||||
runs ->
|
||||
expect(leftPane).not.toBe rightPane
|
||||
expect(atom.workspace.getActivePane()).toBe rightPane
|
||||
expect(atom.workspace.getActivePane().getItems().length).toBe 1
|
||||
expect(rightPane.getPendingItem()).toBe editor1
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.txt', pending: true).then (o) ->
|
||||
editor2 = o
|
||||
|
||||
runs ->
|
||||
expect(rightPane.getPendingItem()).toBe editor2
|
||||
expect(rightPane.destroyed.callCount).toBe 0
|
||||
|
||||
describe "::reopenItem()", ->
|
||||
it "opens the uri associated with the last closed pane that isn't currently open", ->
|
||||
pane = workspace.getActivePane()
|
||||
@@ -1551,11 +1601,12 @@ describe "Workspace", ->
|
||||
|
||||
describe "when the core.allowPendingPaneItems option is falsey", ->
|
||||
it "does not open item with `pending: true` option as pending", ->
|
||||
editor = null
|
||||
pane = null
|
||||
atom.config.set('core.allowPendingPaneItems', false)
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js', pending: true).then (o) -> editor = o
|
||||
atom.workspace.open('sample.js', pending: true).then ->
|
||||
pane = atom.workspace.getActivePane()
|
||||
|
||||
runs ->
|
||||
expect(editor.isPending()).toBeFalsy()
|
||||
expect(pane.getPendingItem()).toBeFalsy()
|
||||
|
||||
@@ -166,8 +166,7 @@ class ApplicationDelegate
|
||||
|
||||
onDidOpenLocations: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
if message is 'open-locations'
|
||||
callback(detail)
|
||||
callback(detail) if message is 'open-locations'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
@@ -175,8 +174,38 @@ class ApplicationDelegate
|
||||
|
||||
onUpdateAvailable: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
if message is 'update-available'
|
||||
callback(detail)
|
||||
# TODO: Yes, this is strange that `onUpdateAvailable` is listening for
|
||||
# `did-begin-downloading-update`. We currently have no mechanism to know
|
||||
# if there is an update, so begin of downloading is a good proxy.
|
||||
callback(detail) if message is 'did-begin-downloading-update'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
ipcRenderer.removeListener('message', outerCallback)
|
||||
|
||||
onDidBeginDownloadingUpdate: (callback) ->
|
||||
@onUpdateAvailable(callback)
|
||||
|
||||
onDidBeginCheckingForUpdate: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
callback(detail) if message is 'checking-for-update'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
ipcRenderer.removeListener('message', outerCallback)
|
||||
|
||||
onDidCompleteDownloadingUpdate: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
# TODO: We could rename this event to `did-complete-downloading-update`
|
||||
callback(detail) if message is 'update-available'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
ipcRenderer.removeListener('message', outerCallback)
|
||||
|
||||
onUpdateNotAvailable: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
callback(detail) if message is 'update-not-available'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
@@ -206,3 +235,12 @@ class ApplicationDelegate
|
||||
|
||||
disablePinchToZoom: ->
|
||||
webFrame.setZoomLevelLimits(1, 1)
|
||||
|
||||
checkForUpdate: ->
|
||||
ipcRenderer.send('check-for-update')
|
||||
|
||||
restartAndInstallUpdate: ->
|
||||
ipcRenderer.send('install-update')
|
||||
|
||||
getAutoUpdateManagerState: ->
|
||||
ipcRenderer.sendSync('get-auto-update-manager-state')
|
||||
|
||||
@@ -40,6 +40,8 @@ Project = require './project'
|
||||
TextEditor = require './text-editor'
|
||||
TextBuffer = require 'text-buffer'
|
||||
Gutter = require './gutter'
|
||||
TextEditorRegistry = require './text-editor-registry'
|
||||
AutoUpdateManager = require './auto-update-manager'
|
||||
|
||||
WorkspaceElement = require './workspace-element'
|
||||
PanelContainerElement = require './panel-container-element'
|
||||
@@ -111,6 +113,12 @@ class AtomEnvironment extends Model
|
||||
# Public: A {Workspace} instance
|
||||
workspace: null
|
||||
|
||||
# Public: A {TextEditorRegistry} instance
|
||||
textEditors: null
|
||||
|
||||
# Private: An {AutoUpdateManager} instance
|
||||
autoUpdater: null
|
||||
|
||||
saveStateDebounceInterval: 1000
|
||||
|
||||
###
|
||||
@@ -121,6 +129,7 @@ class AtomEnvironment extends Model
|
||||
constructor: (params={}) ->
|
||||
{@blobStore, @applicationDelegate, @window, @document, configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params
|
||||
|
||||
@unloaded = false
|
||||
@loadTime = null
|
||||
{devMode, safeMode, resourcePath, clearWindowState} = @getLoadSettings()
|
||||
|
||||
@@ -183,6 +192,9 @@ class AtomEnvironment extends Model
|
||||
})
|
||||
@themes.workspace = @workspace
|
||||
|
||||
@textEditors = new TextEditorRegistry
|
||||
@autoUpdater = new AutoUpdateManager({@applicationDelegate})
|
||||
|
||||
@config.load()
|
||||
|
||||
@themes.loadBaseStylesheets()
|
||||
@@ -219,7 +231,8 @@ class AtomEnvironment extends Model
|
||||
checkPortableHomeWritable()
|
||||
|
||||
attachSaveStateListeners: ->
|
||||
debouncedSaveState = _.debounce((=> @saveState()), @saveStateDebounceInterval)
|
||||
saveState = => @saveState({isUnloading: false}) unless @unloaded
|
||||
debouncedSaveState = _.debounce(saveState, @saveStateDebounceInterval)
|
||||
@document.addEventListener('mousedown', debouncedSaveState, true)
|
||||
@document.addEventListener('keydown', debouncedSaveState, true)
|
||||
@disposables.add new Disposable =>
|
||||
@@ -254,8 +267,6 @@ class AtomEnvironment extends Model
|
||||
new PaneAxisElement().initialize(model, env)
|
||||
@views.addViewProvider Pane, (model, env) ->
|
||||
new PaneElement().initialize(model, env)
|
||||
@views.addViewProvider TextEditor, (model, env) ->
|
||||
new TextEditorElement().initialize(model, env)
|
||||
@views.addViewProvider(Gutter, createGutterView)
|
||||
|
||||
registerDefaultOpeners: ->
|
||||
@@ -327,6 +338,7 @@ class AtomEnvironment extends Model
|
||||
@commands.clear()
|
||||
@stylesElement.remove()
|
||||
@config.unobserveUserConfig()
|
||||
@autoUpdater.destroy()
|
||||
|
||||
@uninstallWindowEventHandler()
|
||||
|
||||
@@ -405,6 +417,16 @@ class AtomEnvironment extends Model
|
||||
getVersion: ->
|
||||
@appVersion ?= @getLoadSettings().appVersion
|
||||
|
||||
# Returns the release channel as a {String}. Will return one of `'dev', 'beta', 'stable'`
|
||||
getReleaseChannel: ->
|
||||
version = @getVersion()
|
||||
if version.indexOf('beta') > -1
|
||||
'beta'
|
||||
else if version.indexOf('dev') > -1
|
||||
'dev'
|
||||
else
|
||||
'stable'
|
||||
|
||||
# Public: Returns a {Boolean} that is `true` if the current version is an official release.
|
||||
isReleasedVersion: ->
|
||||
not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix
|
||||
@@ -654,7 +676,7 @@ class AtomEnvironment extends Model
|
||||
@document.body.appendChild(@views.getView(@workspace))
|
||||
@backgroundStylesheet?.remove()
|
||||
|
||||
@watchProjectPath()
|
||||
@watchProjectPaths()
|
||||
|
||||
@packages.activate()
|
||||
@keymaps.loadUserKeymap()
|
||||
@@ -664,9 +686,9 @@ class AtomEnvironment extends Model
|
||||
|
||||
@openInitialEmptyEditorIfNecessary()
|
||||
|
||||
serialize: ->
|
||||
serialize: (options) ->
|
||||
version: @constructor.version
|
||||
project: @project.serialize()
|
||||
project: @project.serialize(options)
|
||||
workspace: @workspace.serialize()
|
||||
packageStates: @packages.serialize()
|
||||
grammars: {grammarOverridesByPath: @grammars.grammarOverridesByPath}
|
||||
@@ -676,9 +698,11 @@ class AtomEnvironment extends Model
|
||||
unloadEditorWindow: ->
|
||||
return if not @project
|
||||
|
||||
@saveState({isUnloading: true})
|
||||
@storeWindowBackground()
|
||||
@packages.deactivatePackages()
|
||||
@saveBlobStoreSync()
|
||||
@unloaded = true
|
||||
|
||||
openInitialEmptyEditorIfNecessary: ->
|
||||
return unless @config.get('core.openEmptyEditorOnStart')
|
||||
@@ -786,7 +810,7 @@ class AtomEnvironment extends Model
|
||||
@themes.load()
|
||||
|
||||
# Notify the browser project of the window's current project path
|
||||
watchProjectPath: ->
|
||||
watchProjectPaths: ->
|
||||
@disposables.add @project.onDidChangePaths =>
|
||||
@applicationDelegate.setRepresentedDirectoryPaths(@project.getPaths())
|
||||
|
||||
@@ -811,14 +835,20 @@ class AtomEnvironment extends Model
|
||||
|
||||
@blobStore.save()
|
||||
|
||||
saveState: ->
|
||||
saveState: (options) ->
|
||||
return Promise.resolve() unless @enablePersistence
|
||||
state = @serialize()
|
||||
|
||||
if storageKey = @getStateKey(@project?.getPaths())
|
||||
@stateStore.save(storageKey, state)
|
||||
else
|
||||
@applicationDelegate.setTemporaryWindowState(state)
|
||||
new Promise (resolve, reject) =>
|
||||
window.requestIdleCallback =>
|
||||
return if not @project
|
||||
|
||||
state = @serialize(options)
|
||||
savePromise =
|
||||
if storageKey = @getStateKey(@project?.getPaths())
|
||||
@stateStore.save(storageKey, state)
|
||||
else
|
||||
@applicationDelegate.setTemporaryWindowState(state)
|
||||
savePromise.catch(reject).then(resolve)
|
||||
|
||||
loadState: ->
|
||||
if @enablePersistence
|
||||
@@ -868,6 +898,7 @@ class AtomEnvironment extends Model
|
||||
detail: error.message
|
||||
dismissable: true
|
||||
|
||||
# TODO: We should deprecate the update events here, and use `atom.autoUpdater` instead
|
||||
onUpdateAvailable: (callback) ->
|
||||
@emitter.on 'update-available', callback
|
||||
|
||||
@@ -875,7 +906,8 @@ class AtomEnvironment extends Model
|
||||
@emitter.emit 'update-available', details
|
||||
|
||||
listenForUpdates: ->
|
||||
@disposables.add(@applicationDelegate.onUpdateAvailable(@updateAvailable.bind(this)))
|
||||
# listen for updates available locally (that have been successfully downloaded)
|
||||
@disposables.add(@autoUpdater.onDidCompleteDownloadingUpdate(@updateAvailable.bind(this)))
|
||||
|
||||
setBodyPlatformClass: ->
|
||||
@document.body.classList.add("platform-#{process.platform}")
|
||||
@@ -897,8 +929,8 @@ class AtomEnvironment extends Model
|
||||
openLocations: (locations) ->
|
||||
needsProjectPaths = @project?.getPaths().length is 0
|
||||
|
||||
for {pathToOpen, initialLine, initialColumn} in locations
|
||||
if pathToOpen? and needsProjectPaths
|
||||
for {pathToOpen, initialLine, initialColumn, forceAddToWindow} in locations
|
||||
if pathToOpen? and (needsProjectPaths or forceAddToWindow)
|
||||
if fs.existsSync(pathToOpen)
|
||||
@project.addPath(pathToOpen)
|
||||
else if fs.existsSync(path.dirname(pathToOpen))
|
||||
|
||||
73
src/auto-update-manager.js
Normal file
73
src/auto-update-manager.js
Normal file
@@ -0,0 +1,73 @@
|
||||
'use babel'
|
||||
|
||||
import {Emitter, CompositeDisposable} from 'event-kit'
|
||||
|
||||
export default class AutoUpdateManager {
|
||||
constructor ({applicationDelegate}) {
|
||||
this.applicationDelegate = applicationDelegate
|
||||
this.subscriptions = new CompositeDisposable()
|
||||
this.emitter = new Emitter()
|
||||
|
||||
this.subscriptions.add(
|
||||
applicationDelegate.onDidBeginCheckingForUpdate(() => {
|
||||
this.emitter.emit('did-begin-checking-for-update')
|
||||
}),
|
||||
applicationDelegate.onDidBeginDownloadingUpdate(() => {
|
||||
this.emitter.emit('did-begin-downloading-update')
|
||||
}),
|
||||
applicationDelegate.onDidCompleteDownloadingUpdate((details) => {
|
||||
this.emitter.emit('did-complete-downloading-update', details)
|
||||
}),
|
||||
applicationDelegate.onUpdateNotAvailable(() => {
|
||||
this.emitter.emit('update-not-available')
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.subscriptions.dispose()
|
||||
this.emitter.dispose()
|
||||
}
|
||||
|
||||
checkForUpdate () {
|
||||
this.applicationDelegate.checkForUpdate()
|
||||
}
|
||||
|
||||
restartAndInstallUpdate () {
|
||||
this.applicationDelegate.restartAndInstallUpdate()
|
||||
}
|
||||
|
||||
getState () {
|
||||
return this.applicationDelegate.getAutoUpdateManagerState()
|
||||
}
|
||||
|
||||
platformSupportsUpdates () {
|
||||
return atom.getReleaseChannel() !== 'dev' && this.getState() !== 'unsupported'
|
||||
}
|
||||
|
||||
onDidBeginCheckingForUpdate (callback) {
|
||||
return this.emitter.on('did-begin-checking-for-update', callback)
|
||||
}
|
||||
|
||||
onDidBeginDownloadingUpdate (callback) {
|
||||
return this.emitter.on('did-begin-downloading-update', callback)
|
||||
}
|
||||
|
||||
onDidCompleteDownloadingUpdate (callback) {
|
||||
return this.emitter.on('did-complete-downloading-update', callback)
|
||||
}
|
||||
|
||||
// TODO: When https://github.com/atom/electron/issues/4587 is closed, we can
|
||||
// add an update-available event.
|
||||
// onUpdateAvailable (callback) {
|
||||
// return this.emitter.on('update-available', callback)
|
||||
// }
|
||||
|
||||
onUpdateNotAvailable (callback) {
|
||||
return this.emitter.on('update-not-available', callback)
|
||||
}
|
||||
|
||||
getPlatform () {
|
||||
return process.platform
|
||||
}
|
||||
}
|
||||
@@ -85,16 +85,16 @@ class AtomApplication
|
||||
else
|
||||
@loadState(options) or @openPath(options)
|
||||
|
||||
openWithOptions: ({pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, timeout, clearWindowState}) ->
|
||||
openWithOptions: ({initialPaths, pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, timeout, clearWindowState, addToLastWindow}) ->
|
||||
if test
|
||||
@runTests({headless: true, devMode, @resourcePath, executedFrom, pathsToOpen, logFile, timeout})
|
||||
else if pathsToOpen.length > 0
|
||||
@openPaths({pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState})
|
||||
@openPaths({initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState, addToLastWindow})
|
||||
else if urlsToOpen.length > 0
|
||||
@openUrl({urlToOpen, devMode, safeMode}) for urlToOpen in urlsToOpen
|
||||
else
|
||||
# Always open a editor window if this is the first instance of Atom.
|
||||
@openPath({pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState})
|
||||
@openPath({initialPaths, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState, addToLastWindow})
|
||||
|
||||
# Public: Removes the {AtomWindow} from the global window list.
|
||||
removeWindow: (window) ->
|
||||
@@ -304,6 +304,15 @@ class AtomApplication
|
||||
ipcMain.on 'execute-javascript-in-dev-tools', (event, code) ->
|
||||
event.sender.devToolsWebContents?.executeJavaScript(code)
|
||||
|
||||
ipcMain.on 'check-for-update', =>
|
||||
@autoUpdateManager.check()
|
||||
|
||||
ipcMain.on 'get-auto-update-manager-state', (event) =>
|
||||
event.returnValue = @autoUpdateManager.getState()
|
||||
|
||||
ipcMain.on 'execute-javascript-in-dev-tools', (event, code) ->
|
||||
event.sender.devToolsWebContents?.executeJavaScript(code)
|
||||
|
||||
setupDockMenu: ->
|
||||
if process.platform is 'darwin'
|
||||
dockMenu = Menu.buildFromTemplate [
|
||||
@@ -408,8 +417,9 @@ class AtomApplication
|
||||
# :safeMode - Boolean to control the opened window's safe mode.
|
||||
# :profileStartup - Boolean to control creating a profile of the startup time.
|
||||
# :window - {AtomWindow} to open file paths in.
|
||||
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window, clearWindowState} = {}) ->
|
||||
@openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window, clearWindowState})
|
||||
# :addToLastWindow - Boolean of whether this should be opened in last focused window.
|
||||
openPath: ({initialPaths, pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window, clearWindowState, addToLastWindow} = {}) ->
|
||||
@openPaths({initialPaths, pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window, clearWindowState, addToLastWindow})
|
||||
|
||||
# Public: Opens multiple paths, in existing windows if possible.
|
||||
#
|
||||
@@ -421,11 +431,12 @@ class AtomApplication
|
||||
# :safeMode - Boolean to control the opened window's safe mode.
|
||||
# :windowDimensions - Object with height and width keys.
|
||||
# :window - {AtomWindow} to open file paths in.
|
||||
openPaths: ({pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, profileStartup, window, clearWindowState}={}) ->
|
||||
# :addToLastWindow - Boolean of whether this should be opened in last focused window.
|
||||
openPaths: ({initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, profileStartup, window, clearWindowState, addToLastWindow}={}) ->
|
||||
devMode = Boolean(devMode)
|
||||
safeMode = Boolean(safeMode)
|
||||
clearWindowState = Boolean(clearWindowState)
|
||||
locationsToOpen = (@locationForPathToOpen(pathToOpen, executedFrom) for pathToOpen in pathsToOpen)
|
||||
locationsToOpen = (@locationForPathToOpen(pathToOpen, executedFrom, addToLastWindow) for pathToOpen in pathsToOpen)
|
||||
pathsToOpen = (locationToOpen.pathToOpen for locationToOpen in locationsToOpen)
|
||||
|
||||
unless pidToKillWhenClosed or newWindow
|
||||
@@ -434,6 +445,7 @@ class AtomApplication
|
||||
unless existingWindow?
|
||||
if currentWindow = window ? @lastFocusedWindow
|
||||
existingWindow = currentWindow if (
|
||||
addToLastWindow or
|
||||
currentWindow.devMode is devMode and
|
||||
(
|
||||
stats.every((stat) -> stat.isFile?()) or
|
||||
@@ -457,7 +469,7 @@ class AtomApplication
|
||||
windowInitializationScript ?= require.resolve('../initialize-application-window')
|
||||
resourcePath ?= @resourcePath
|
||||
windowDimensions ?= @getDimensionsForNewWindow()
|
||||
openedWindow = new AtomWindow({locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup, clearWindowState})
|
||||
openedWindow = new AtomWindow({initialPaths, locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup, clearWindowState})
|
||||
|
||||
if pidToKillWhenClosed?
|
||||
@pidsToOpenWindows[pidToKillWhenClosed] = openedWindow
|
||||
@@ -500,7 +512,8 @@ class AtomApplication
|
||||
if (states = @storageFolder.load('application.json'))?.length > 0
|
||||
for state in states
|
||||
@openWithOptions(_.extend(options, {
|
||||
pathsToOpen: state.initialPaths
|
||||
initialPaths: state.initialPaths
|
||||
pathsToOpen: state.initialPaths.filter (directoryPath) -> fs.isDirectorySync(directoryPath)
|
||||
urlsToOpen: []
|
||||
devMode: @devMode
|
||||
safeMode: @safeMode
|
||||
@@ -602,7 +615,7 @@ class AtomApplication
|
||||
catch error
|
||||
require.resolve(path.resolve(__dirname, '..', '..', 'spec', 'jasmine-test-runner'))
|
||||
|
||||
locationForPathToOpen: (pathToOpen, executedFrom='') ->
|
||||
locationForPathToOpen: (pathToOpen, executedFrom='', forceAddToWindow) ->
|
||||
return {pathToOpen} unless pathToOpen
|
||||
|
||||
pathToOpen = pathToOpen.replace(/[:\s]+$/, '')
|
||||
@@ -618,7 +631,7 @@ class AtomApplication
|
||||
unless url.parse(pathToOpen).protocol?
|
||||
pathToOpen = path.resolve(executedFrom, fs.normalize(pathToOpen))
|
||||
|
||||
{pathToOpen, initialLine, initialColumn}
|
||||
{pathToOpen, initialLine, initialColumn, forceAddToWindow}
|
||||
|
||||
# Opens a native dialog to prompt the user for a path.
|
||||
#
|
||||
|
||||
@@ -17,7 +17,7 @@ class AtomWindow
|
||||
isSpec: null
|
||||
|
||||
constructor: (settings={}) ->
|
||||
{@resourcePath, pathToOpen, locationsToOpen, @isSpec, @headless, @safeMode, @devMode} = settings
|
||||
{@resourcePath, initialPaths, pathToOpen, locationsToOpen, @isSpec, @headless, @safeMode, @devMode} = settings
|
||||
locationsToOpen ?= [{pathToOpen}] if pathToOpen
|
||||
locationsToOpen ?= []
|
||||
|
||||
@@ -47,13 +47,7 @@ class AtomWindow
|
||||
loadSettings.safeMode ?= false
|
||||
loadSettings.atomHome = process.env.ATOM_HOME
|
||||
loadSettings.clearWindowState ?= false
|
||||
|
||||
# Only send to the first non-spec window created
|
||||
if @constructor.includeShellLoadTime and not @isSpec
|
||||
@constructor.includeShellLoadTime = false
|
||||
loadSettings.shellLoadTime ?= Date.now() - global.shellStartTime
|
||||
|
||||
loadSettings.initialPaths =
|
||||
loadSettings.initialPaths ?=
|
||||
for {pathToOpen} in locationsToOpen when pathToOpen
|
||||
if fs.statSyncNoException(pathToOpen).isFile?()
|
||||
path.dirname(pathToOpen)
|
||||
@@ -62,6 +56,13 @@ class AtomWindow
|
||||
|
||||
loadSettings.initialPaths.sort()
|
||||
|
||||
# Only send to the first non-spec window created
|
||||
if @constructor.includeShellLoadTime and not @isSpec
|
||||
@constructor.includeShellLoadTime = false
|
||||
loadSettings.shellLoadTime ?= Date.now() - global.shellStartTime
|
||||
|
||||
@browserWindow.loadSettings = loadSettings
|
||||
|
||||
@browserWindow.once 'window:loaded', =>
|
||||
@emit 'window:loaded'
|
||||
@loaded = true
|
||||
|
||||
@@ -39,16 +39,24 @@ class AutoUpdateManager
|
||||
|
||||
autoUpdater.on 'checking-for-update', =>
|
||||
@setState(CheckingState)
|
||||
@emitWindowEvent('checking-for-update')
|
||||
|
||||
autoUpdater.on 'update-not-available', =>
|
||||
@setState(NoUpdateAvailableState)
|
||||
@emitWindowEvent('update-not-available')
|
||||
|
||||
autoUpdater.on 'update-available', =>
|
||||
@setState(DownladingState)
|
||||
# We use sendMessage to send an event called 'update-available' in 'update-downloaded'
|
||||
# once the update download is complete. This mismatch between the electron
|
||||
# autoUpdater events is unfortunate but in the interest of not changing the
|
||||
# one existing event handled by applicationDelegate
|
||||
@emitWindowEvent('did-begin-downloading-update')
|
||||
@emit('did-begin-download')
|
||||
|
||||
autoUpdater.on 'update-downloaded', (event, releaseNotes, @releaseVersion) =>
|
||||
@setState(UpdateAvailableState)
|
||||
@emitUpdateAvailableEvent(@getWindows()...)
|
||||
@emitUpdateAvailableEvent()
|
||||
|
||||
@config.onDidChange 'core.automaticallyUpdate', ({newValue}) =>
|
||||
if newValue
|
||||
@@ -64,10 +72,14 @@ class AutoUpdateManager
|
||||
when 'linux'
|
||||
@setState(UnsupportedState)
|
||||
|
||||
emitUpdateAvailableEvent: (windows...) ->
|
||||
emitUpdateAvailableEvent: ->
|
||||
return unless @releaseVersion?
|
||||
for atomWindow in windows
|
||||
atomWindow.sendMessage('update-available', {@releaseVersion})
|
||||
@emitWindowEvent('update-available', {@releaseVersion})
|
||||
return
|
||||
|
||||
emitWindowEvent: (eventName, payload) ->
|
||||
for atomWindow in @getWindows()
|
||||
atomWindow.sendMessage(eventName, payload)
|
||||
return
|
||||
|
||||
setState: (state) ->
|
||||
|
||||
@@ -132,6 +132,7 @@ parseCommandLine = ->
|
||||
options.string('timeout').describe('timeout', 'When in test mode, waits until the specified time (in minutes) and kills the process (exit code: 130).')
|
||||
options.alias('v', 'version').boolean('v').describe('v', 'Print the version information.')
|
||||
options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.')
|
||||
options.alias('a', 'add').boolean('a').describe('add', 'Open path as a new project in last used window.')
|
||||
options.string('socket-path')
|
||||
options.string('user-data-dir')
|
||||
options.boolean('clear-window-state').describe('clear-window-state', 'Delete all Atom environment state.')
|
||||
@@ -146,6 +147,7 @@ parseCommandLine = ->
|
||||
writeFullVersion()
|
||||
process.exit(0)
|
||||
|
||||
addToLastWindow = args['add']
|
||||
executedFrom = args['executed-from']?.toString() ? process.cwd()
|
||||
devMode = args['dev']
|
||||
safeMode = args['safe']
|
||||
@@ -183,6 +185,6 @@ parseCommandLine = ->
|
||||
{resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test,
|
||||
version, pidToKillWhenClosed, devMode, safeMode, newWindow,
|
||||
logFile, socketPath, userDataDir, profileStartup, timeout, setPortable,
|
||||
clearWindowState}
|
||||
clearWindowState, addToLastWindow}
|
||||
|
||||
start()
|
||||
|
||||
@@ -11,9 +11,11 @@ exeName = path.basename(process.execPath)
|
||||
if process.env.SystemRoot
|
||||
system32Path = path.join(process.env.SystemRoot, 'System32')
|
||||
regPath = path.join(system32Path, 'reg.exe')
|
||||
powershellPath = path.join(system32Path, 'WindowsPowerShell', 'v1.0', 'powershell.exe')
|
||||
setxPath = path.join(system32Path, 'setx.exe')
|
||||
else
|
||||
regPath = 'reg.exe'
|
||||
powershellPath = 'powershell.exe'
|
||||
setxPath = 'setx.exe'
|
||||
|
||||
# Registry keys used for context menu
|
||||
@@ -44,11 +46,31 @@ spawn = (command, args, callback) ->
|
||||
error?.code ?= code
|
||||
error?.stdout ?= stdout
|
||||
callback?(error, stdout)
|
||||
# This is necessary if using Powershell 2 on Windows 7 to get the events to raise
|
||||
# http://stackoverflow.com/questions/9155289/calling-powershell-from-nodejs
|
||||
spawnedProcess.stdin.end()
|
||||
|
||||
|
||||
# Spawn reg.exe and callback when it completes
|
||||
spawnReg = (args, callback) ->
|
||||
spawn(regPath, args, callback)
|
||||
|
||||
# Spawn powershell.exe and callback when it completes
|
||||
spawnPowershell = (args, callback) ->
|
||||
# set encoding and execute the command, capture the output, and return it via .NET's console in order to have consistent UTF-8 encoding
|
||||
# http://stackoverflow.com/questions/22349139/utf-8-output-from-powershell
|
||||
# to address https://github.com/atom/atom/issues/5063
|
||||
args[0] = """
|
||||
[Console]::OutputEncoding=[System.Text.Encoding]::UTF8
|
||||
$output=#{args[0]}
|
||||
[Console]::WriteLine($output)
|
||||
"""
|
||||
args.unshift('-command')
|
||||
args.unshift('RemoteSigned')
|
||||
args.unshift('-ExecutionPolicy')
|
||||
args.unshift('-noprofile')
|
||||
spawn(powershellPath, args, callback)
|
||||
|
||||
# Spawn setx.exe and callback when it completes
|
||||
spawnSetx = (args, callback) ->
|
||||
spawn(setxPath, args, callback)
|
||||
@@ -82,46 +104,14 @@ installContextMenu = (callback) ->
|
||||
installMenu backgroundKeyPath, '%V', ->
|
||||
installFileHandler(callback)
|
||||
|
||||
isAscii = (text) ->
|
||||
index = 0
|
||||
while index < text.length
|
||||
return false if text.charCodeAt(index) > 127
|
||||
index++
|
||||
true
|
||||
|
||||
# Get the user's PATH environment variable registry value.
|
||||
getPath = (callback) ->
|
||||
spawnReg ['query', environmentKeyPath, '/v', 'Path'], (error, stdout) ->
|
||||
spawnPowershell ['[environment]::GetEnvironmentVariable(\'Path\',\'User\')'], (error, stdout) ->
|
||||
if error?
|
||||
if error.code is 1
|
||||
# FIXME Don't overwrite path when reading value is disabled
|
||||
# https://github.com/atom/atom/issues/5092
|
||||
if stdout.indexOf('ERROR: Registry editing has been disabled by your administrator.') isnt -1
|
||||
return callback(error)
|
||||
return callback(error)
|
||||
|
||||
# The query failed so the Path does not exist yet in the registry
|
||||
return callback(null, '')
|
||||
else
|
||||
return callback(error)
|
||||
|
||||
# Registry query output is in the form:
|
||||
#
|
||||
# HKEY_CURRENT_USER\Environment
|
||||
# Path REG_SZ C:\a\folder\on\the\path;C\another\folder
|
||||
#
|
||||
|
||||
lines = stdout.split(/[\r\n]+/).filter (line) -> line
|
||||
segments = lines[lines.length - 1]?.split(' ')
|
||||
if segments[1] is 'Path' and segments.length >= 3
|
||||
pathEnv = segments?[3..].join(' ')
|
||||
if isAscii(pathEnv)
|
||||
callback(null, pathEnv)
|
||||
else
|
||||
# FIXME Don't corrupt non-ASCII PATH values
|
||||
# https://github.com/atom/atom/issues/5063
|
||||
callback(new Error('PATH contains non-ASCII values'))
|
||||
else
|
||||
callback(new Error('Registry query for PATH failed'))
|
||||
pathOutput = stdout.replace(/^\s+|\s+$/g, '')
|
||||
callback(null, pathOutput)
|
||||
|
||||
# Uninstall the Open with Atom explorer context menu items via the registry.
|
||||
uninstallContextMenu = (callback) ->
|
||||
|
||||
@@ -46,7 +46,7 @@ class BufferedNodeProcess extends BufferedProcess
|
||||
|
||||
options ?= {}
|
||||
options.env ?= Object.create(process.env)
|
||||
options.env['ATOM_SHELL_INTERNAL_RUN_AS_NODE'] = 1
|
||||
options.env['ELECTRON_RUN_AS_NODE'] = 1
|
||||
|
||||
args = args?.slice() ? []
|
||||
args.unshift(command)
|
||||
|
||||
@@ -85,5 +85,5 @@ parseAlpha = (alpha) ->
|
||||
|
||||
numberToHexString = (number) ->
|
||||
hex = number.toString(16)
|
||||
hex = "0#{hex}" if number < 10
|
||||
hex = "0#{hex}" if number < 16
|
||||
hex
|
||||
|
||||
@@ -319,6 +319,23 @@ ScopeDescriptor = require './scope-descriptor'
|
||||
# * line breaks - `line breaks<br/>`
|
||||
# * ~~strikethrough~~ - `~~strikethrough~~`
|
||||
#
|
||||
# #### order
|
||||
#
|
||||
# The settings view orders your settings alphabetically. You can override this
|
||||
# ordering with the order key.
|
||||
#
|
||||
# ```coffee
|
||||
# config:
|
||||
# zSetting:
|
||||
# type: 'integer'
|
||||
# default: 4
|
||||
# order: 1
|
||||
# aSetting:
|
||||
# type: 'integer'
|
||||
# default: 4
|
||||
# order: 2
|
||||
# ```
|
||||
#
|
||||
# ## Best practices
|
||||
#
|
||||
# * Don't depend on (or write to) configuration keys outside of your keypath.
|
||||
|
||||
@@ -596,7 +596,15 @@ export default class GitRepositoryAsync {
|
||||
.then(([repo, headCommit]) => Promise.all([repo, headCommit.getTree()]))
|
||||
.then(([repo, tree]) => {
|
||||
const options = new Git.DiffOptions()
|
||||
options.contextLines = 0
|
||||
options.flags = Git.Diff.OPTION.DISABLE_PATHSPEC_MATCH
|
||||
options.pathspec = this.relativize(_path, repo.workdir())
|
||||
if (process.platform === 'win32') {
|
||||
// Ignore eol of line differences on windows so that files checked in
|
||||
// as LF don't report every line modified when the text contains CRLF
|
||||
// endings.
|
||||
options.flags |= Git.Diff.OPTION.IGNORE_WHITESPACE_EOL
|
||||
}
|
||||
return Git.Diff.treeToWorkdir(repo, tree, options)
|
||||
})
|
||||
.then(diff => this._getDiffLines(diff))
|
||||
@@ -775,12 +783,17 @@ export default class GitRepositoryAsync {
|
||||
|
||||
// Get the current branch and update this.branch.
|
||||
//
|
||||
// Returns a {Promise} which resolves to the {String} branch name.
|
||||
// Returns a {Promise} which resolves to a {boolean} indicating whether the
|
||||
// branch name changed.
|
||||
_refreshBranch () {
|
||||
return this.getRepo()
|
||||
.then(repo => repo.getCurrentBranch())
|
||||
.then(ref => ref.name())
|
||||
.then(branchName => this.branch = branchName)
|
||||
.then(branchName => {
|
||||
const changed = branchName !== this.branch
|
||||
this.branch = branchName
|
||||
return changed
|
||||
})
|
||||
}
|
||||
|
||||
// Refresh the cached ahead/behind count with the given branch.
|
||||
@@ -788,10 +801,15 @@ export default class GitRepositoryAsync {
|
||||
// * `branchName` The {String} name of the branch whose ahead/behind should be
|
||||
// used for the refresh.
|
||||
//
|
||||
// Returns a {Promise} which will resolve to {null}.
|
||||
// Returns a {Promise} which will resolve to a {boolean} indicating whether
|
||||
// the ahead/behind count changed.
|
||||
_refreshAheadBehindCount (branchName) {
|
||||
return this.getAheadBehindCount(branchName)
|
||||
.then(counts => this.upstream = counts)
|
||||
.then(counts => {
|
||||
const changed = !_.isEqual(counts, this.upstream)
|
||||
this.upstream = counts
|
||||
return changed
|
||||
})
|
||||
}
|
||||
|
||||
// Get the status for this repository.
|
||||
@@ -897,15 +915,15 @@ export default class GitRepositoryAsync {
|
||||
|
||||
// Refresh the cached status.
|
||||
//
|
||||
// Returns a {Promise} which will resolve to {null}.
|
||||
// Returns a {Promise} which will resolve to a {boolean} indicating whether
|
||||
// any statuses changed.
|
||||
_refreshStatus () {
|
||||
return Promise.all([this._getRepositoryStatus(), this._getSubmoduleStatuses()])
|
||||
.then(([repositoryStatus, submoduleStatus]) => {
|
||||
const statusesByPath = _.extend({}, repositoryStatus, submoduleStatus)
|
||||
if (!_.isEqual(this.pathStatusCache, statusesByPath) && this.emitter != null) {
|
||||
this.emitter.emit('did-change-statuses')
|
||||
}
|
||||
const changed = !_.isEqual(this.pathStatusCache, statusesByPath)
|
||||
this.pathStatusCache = statusesByPath
|
||||
return changed
|
||||
})
|
||||
}
|
||||
|
||||
@@ -915,11 +933,17 @@ export default class GitRepositoryAsync {
|
||||
refreshStatus () {
|
||||
const status = this._refreshStatus()
|
||||
const branch = this._refreshBranch()
|
||||
const aheadBehind = branch.then(branchName => this._refreshAheadBehindCount(branchName))
|
||||
const aheadBehind = branch.then(() => this._refreshAheadBehindCount(this.branch))
|
||||
|
||||
this._refreshingPromise = this._refreshingPromise.then(_ => {
|
||||
return Promise.all([status, branch, aheadBehind])
|
||||
.then(_ => null)
|
||||
.then(([statusChanged, branchChanged, aheadBehindChanged]) => {
|
||||
if (this.emitter && (statusChanged || branchChanged || aheadBehindChanged)) {
|
||||
this.emitter.emit('did-change-statuses')
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
// Because all these refresh steps happen asynchronously, it's entirely
|
||||
// possible the repository was destroyed while we were working. In which
|
||||
// case we should just swallow the error.
|
||||
|
||||
@@ -147,13 +147,11 @@ class LanguageMode
|
||||
|
||||
if bufferRow > 0
|
||||
for currentRow in [bufferRow-1..0] by -1
|
||||
break if @buffer.isRowBlank(currentRow)
|
||||
break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
startRow = currentRow
|
||||
|
||||
if bufferRow < @buffer.getLastRow()
|
||||
for currentRow in [bufferRow+1..@buffer.getLastRow()] by 1
|
||||
break if @buffer.isRowBlank(currentRow)
|
||||
break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
endRow = currentRow
|
||||
|
||||
|
||||
@@ -128,8 +128,12 @@ class PackageManager
|
||||
|
||||
# Public: Get the path to the apm command.
|
||||
#
|
||||
# Uses the value of the `core.apmPath` config setting if it exists.
|
||||
#
|
||||
# Return a {String} file path to apm.
|
||||
getApmPath: ->
|
||||
configPath = atom.config.get('core.apmPath')
|
||||
return configPath if configPath
|
||||
return @apmPath if @apmPath?
|
||||
|
||||
commandName = 'apm'
|
||||
@@ -541,11 +545,12 @@ class PackageManager
|
||||
unless typeof metadata.name is 'string' and metadata.name.length > 0
|
||||
metadata.name = packageName
|
||||
|
||||
if metadata.repository?.type is 'git' and typeof metadata.repository.url is 'string'
|
||||
metadata.repository.url = metadata.repository.url.replace(/(^git\+)|(\.git$)/g, '')
|
||||
|
||||
metadata
|
||||
|
||||
normalizePackageMetadata: (metadata) ->
|
||||
unless metadata?._id
|
||||
normalizePackageData ?= require 'normalize-package-data'
|
||||
normalizePackageData(metadata)
|
||||
if metadata.repository?.type is 'git' and typeof metadata.repository.url is 'string'
|
||||
metadata.repository.url = metadata.repository.url.replace(/^git\+/, '')
|
||||
|
||||
145
src/pane.coffee
145
src/pane.coffee
@@ -1,5 +1,6 @@
|
||||
Grim = require 'grim'
|
||||
{find, compact, extend, last} = require 'underscore-plus'
|
||||
{Emitter} = require 'event-kit'
|
||||
{CompositeDisposable, Emitter} = require 'event-kit'
|
||||
Model = require './model'
|
||||
PaneAxis = require './pane-axis'
|
||||
TextEditor = require './text-editor'
|
||||
@@ -8,6 +9,11 @@ TextEditor = require './text-editor'
|
||||
# Panes can contain multiple items, one of which is *active* at a given time.
|
||||
# The view corresponding to the active item is displayed in the interface. In
|
||||
# the default configuration, tabs are also displayed for each item.
|
||||
#
|
||||
# Each pane may also contain one *pending* item. When a pending item is added
|
||||
# to a pane, it will replace the currently pending item, if any, instead of
|
||||
# simply being added. In the default configuration, the text in the tab for
|
||||
# pending items is shown in italics.
|
||||
module.exports =
|
||||
class Pane extends Model
|
||||
container: undefined
|
||||
@@ -15,7 +21,7 @@ class Pane extends Model
|
||||
focused: false
|
||||
|
||||
@deserialize: (state, {deserializers, applicationDelegate, config, notifications}) ->
|
||||
{items, activeItemURI, activeItemUri} = state
|
||||
{items, itemStackIndices, activeItemURI, activeItemUri} = state
|
||||
activeItemURI ?= activeItemUri
|
||||
state.items = compact(items.map (itemState) -> deserializers.deserialize(itemState))
|
||||
state.activeItem = find state.items, (item) ->
|
||||
@@ -37,20 +43,25 @@ class Pane extends Model
|
||||
} = params
|
||||
|
||||
@emitter = new Emitter
|
||||
@itemSubscriptions = new WeakMap
|
||||
@subscriptionsPerItem = new WeakMap
|
||||
@items = []
|
||||
@itemStack = []
|
||||
|
||||
@addItems(compact(params?.items ? []))
|
||||
@setActiveItem(@items[0]) unless @getActiveItem()?
|
||||
@addItemsToStack(params?.itemStackIndices ? [])
|
||||
@setFlexScale(params?.flexScale ? 1)
|
||||
|
||||
serialize: ->
|
||||
if typeof @activeItem?.getURI is 'function'
|
||||
activeItemURI = @activeItem.getURI()
|
||||
itemsToBeSerialized = compact(@items.map((item) -> item if typeof item.serialize is 'function'))
|
||||
itemStackIndices = (itemsToBeSerialized.indexOf(item) for item in @itemStack when typeof item.serialize is 'function')
|
||||
|
||||
deserializer: 'Pane'
|
||||
id: @id
|
||||
items: compact(@items.map((item) -> item.serialize?()))
|
||||
items: itemsToBeSerialized.map((item) -> item.serialize())
|
||||
itemStackIndices: itemStackIndices
|
||||
activeItemURI: activeItemURI
|
||||
focused: @focused
|
||||
flexScale: @flexScale
|
||||
@@ -260,8 +271,8 @@ class Pane extends Model
|
||||
getPanes: -> [this]
|
||||
|
||||
unsubscribeFromItem: (item) ->
|
||||
@itemSubscriptions.get(item)?.dispose()
|
||||
@itemSubscriptions.delete(item)
|
||||
@subscriptionsPerItem.get(item)?.dispose()
|
||||
@subscriptionsPerItem.delete(item)
|
||||
|
||||
###
|
||||
Section: Items
|
||||
@@ -278,12 +289,30 @@ class Pane extends Model
|
||||
# Returns a pane item.
|
||||
getActiveItem: -> @activeItem
|
||||
|
||||
setActiveItem: (activeItem) ->
|
||||
setActiveItem: (activeItem, options) ->
|
||||
{modifyStack} = options if options?
|
||||
unless activeItem is @activeItem
|
||||
@addItemToStack(activeItem) unless modifyStack is false
|
||||
@activeItem = activeItem
|
||||
@emitter.emit 'did-change-active-item', @activeItem
|
||||
@activeItem
|
||||
|
||||
# Build the itemStack after deserializing
|
||||
addItemsToStack: (itemStackIndices) ->
|
||||
if @items.length > 0
|
||||
if itemStackIndices.length is 0 or itemStackIndices.length isnt @items.length or itemStackIndices.indexOf(-1) >= 0
|
||||
itemStackIndices = (i for i in [0..@items.length-1])
|
||||
for itemIndex in itemStackIndices
|
||||
@addItemToStack(@items[itemIndex])
|
||||
return
|
||||
|
||||
# Add item (or move item) to the end of the itemStack
|
||||
addItemToStack: (newItem) ->
|
||||
return unless newItem?
|
||||
index = @itemStack.indexOf(newItem)
|
||||
@itemStack.splice(index, 1) unless index is -1
|
||||
@itemStack.push(newItem)
|
||||
|
||||
# Return an {TextEditor} if the pane item is an {TextEditor}, or null otherwise.
|
||||
getActiveEditor: ->
|
||||
@activeItem if @activeItem instanceof TextEditor
|
||||
@@ -296,6 +325,29 @@ class Pane extends Model
|
||||
itemAtIndex: (index) ->
|
||||
@items[index]
|
||||
|
||||
# Makes the next item in the itemStack active.
|
||||
activateNextRecentlyUsedItem: ->
|
||||
if @items.length > 1
|
||||
@itemStackIndex = @itemStack.length - 1 unless @itemStackIndex?
|
||||
@itemStackIndex = @itemStack.length if @itemStackIndex is 0
|
||||
@itemStackIndex = @itemStackIndex - 1
|
||||
nextRecentlyUsedItem = @itemStack[@itemStackIndex]
|
||||
@setActiveItem(nextRecentlyUsedItem, modifyStack: false)
|
||||
|
||||
# Makes the previous item in the itemStack active.
|
||||
activatePreviousRecentlyUsedItem: ->
|
||||
if @items.length > 1
|
||||
if @itemStackIndex + 1 is @itemStack.length or not @itemStackIndex?
|
||||
@itemStackIndex = -1
|
||||
@itemStackIndex = @itemStackIndex + 1
|
||||
previousRecentlyUsedItem = @itemStack[@itemStackIndex]
|
||||
@setActiveItem(previousRecentlyUsedItem, modifyStack: false)
|
||||
|
||||
# Moves the active item to the end of the itemStack once the ctrl key is lifted
|
||||
moveActiveItemToTopOfStack: ->
|
||||
delete @itemStackIndex
|
||||
@addItemToStack(@activeItem)
|
||||
|
||||
# Public: Makes the next item active.
|
||||
activateNextItem: ->
|
||||
index = @getActiveItemIndex()
|
||||
@@ -342,43 +394,81 @@ class Pane extends Model
|
||||
|
||||
# Public: Make the given item *active*, causing it to be displayed by
|
||||
# the pane's view.
|
||||
activateItem: (item) ->
|
||||
#
|
||||
# * `options` (optional) {Object}
|
||||
# * `pending` (optional) {Boolean} indicating that the item should be added
|
||||
# in a pending state if it does not yet exist in the pane. Existing pending
|
||||
# items in a pane are replaced with new pending items when they are opened.
|
||||
activateItem: (item, options={}) ->
|
||||
if item?
|
||||
if @activeItem?.isPending?()
|
||||
if @getPendingItem() is @activeItem
|
||||
index = @getActiveItemIndex()
|
||||
else
|
||||
index = @getActiveItemIndex() + 1
|
||||
@addItem(item, index, false)
|
||||
@addItem(item, extend({}, options, {index: index}))
|
||||
@setActiveItem(item)
|
||||
|
||||
# Public: Add the given item to the pane.
|
||||
#
|
||||
# * `item` The item to add. It can be a model with an associated view or a
|
||||
# view.
|
||||
# * `index` (optional) {Number} indicating the index at which to add the item.
|
||||
# If omitted, the item is added after the current active item.
|
||||
# * `options` (optional) {Object}
|
||||
# * `index` (optional) {Number} indicating the index at which to add the item.
|
||||
# If omitted, the item is added after the current active item.
|
||||
# * `pending` (optional) {Boolean} indicating that the item should be
|
||||
# added in a pending state. Existing pending items in a pane are replaced with
|
||||
# new pending items when they are opened.
|
||||
#
|
||||
# Returns the added item.
|
||||
addItem: (item, index=@getActiveItemIndex() + 1, moved=false) ->
|
||||
addItem: (item, options={}) ->
|
||||
# Backward compat with old API:
|
||||
# addItem(item, index=@getActiveItemIndex() + 1)
|
||||
if typeof options is "number"
|
||||
Grim.deprecate("Pane::addItem(item, #{options}) is deprecated in favor of Pane::addItem(item, {index: #{options}})")
|
||||
options = index: options
|
||||
|
||||
index = options.index ? @getActiveItemIndex() + 1
|
||||
moved = options.moved ? false
|
||||
pending = options.pending ? false
|
||||
|
||||
throw new Error("Pane items must be objects. Attempted to add item #{item}.") unless item? and typeof item is 'object'
|
||||
throw new Error("Adding a pane item with URI '#{item.getURI?()}' that has already been destroyed") if item.isDestroyed?()
|
||||
|
||||
return if item in @items
|
||||
|
||||
if item.isPending?()
|
||||
for existingItem, i in @items
|
||||
if existingItem.isPending?()
|
||||
@destroyItem(existingItem)
|
||||
break
|
||||
|
||||
if typeof item.onDidDestroy is 'function'
|
||||
@itemSubscriptions.set item, item.onDidDestroy => @removeItem(item, false)
|
||||
itemSubscriptions = new CompositeDisposable
|
||||
itemSubscriptions.add item.onDidDestroy => @removeItem(item, false)
|
||||
if typeof item.onDidTerminatePendingState is "function"
|
||||
itemSubscriptions.add item.onDidTerminatePendingState =>
|
||||
@clearPendingItem() if @getPendingItem() is item
|
||||
itemSubscriptions.add item.onDidDestroy => @removeItem(item, false)
|
||||
@subscriptionsPerItem.set item, itemSubscriptions
|
||||
|
||||
@items.splice(index, 0, item)
|
||||
lastPendingItem = @getPendingItem()
|
||||
@setPendingItem(item) if pending
|
||||
|
||||
@emitter.emit 'did-add-item', {item, index, moved}
|
||||
@destroyItem(lastPendingItem) if lastPendingItem? and not moved
|
||||
@setActiveItem(item) unless @getActiveItem()?
|
||||
item
|
||||
|
||||
setPendingItem: (item) =>
|
||||
if @pendingItem isnt item
|
||||
mostRecentPendingItem = @pendingItem
|
||||
@pendingItem = item
|
||||
@emitter.emit 'item-did-terminate-pending-state', mostRecentPendingItem
|
||||
|
||||
getPendingItem: =>
|
||||
@pendingItem or null
|
||||
|
||||
clearPendingItem: =>
|
||||
@setPendingItem(null)
|
||||
|
||||
onItemDidTerminatePendingState: (callback) =>
|
||||
@emitter.on 'item-did-terminate-pending-state', callback
|
||||
|
||||
# Public: Add the given items to the pane.
|
||||
#
|
||||
# * `items` An {Array} of items to add. Items can be views or models with
|
||||
@@ -390,13 +480,14 @@ class Pane extends Model
|
||||
# Returns an {Array} of added items.
|
||||
addItems: (items, index=@getActiveItemIndex() + 1) ->
|
||||
items = items.filter (item) => not (item in @items)
|
||||
@addItem(item, index + i, false) for item, i in items
|
||||
@addItem(item, {index: index + i}) for item, i in items
|
||||
items
|
||||
|
||||
removeItem: (item, moved) ->
|
||||
index = @items.indexOf(item)
|
||||
return if index is -1
|
||||
|
||||
@pendingItem = null if @getPendingItem() is item
|
||||
@removeItemFromStack(item)
|
||||
@emitter.emit 'will-remove-item', {item, index, destroyed: not moved, moved}
|
||||
@unsubscribeFromItem(item)
|
||||
|
||||
@@ -412,6 +503,14 @@ class Pane extends Model
|
||||
@container?.didDestroyPaneItem({item, index, pane: this}) unless moved
|
||||
@destroy() if @items.length is 0 and @config.get('core.destroyEmptyPanes')
|
||||
|
||||
# Remove the given item from the itemStack.
|
||||
#
|
||||
# * `item` The item to remove.
|
||||
# * `index` {Number} indicating the index to which to remove the item from the itemStack.
|
||||
removeItemFromStack: (item) ->
|
||||
index = @itemStack.indexOf(item)
|
||||
@itemStack.splice(index, 1) unless index is -1
|
||||
|
||||
# Public: Move the given item to the given index.
|
||||
#
|
||||
# * `item` The item to move.
|
||||
@@ -430,7 +529,7 @@ class Pane extends Model
|
||||
# given pane.
|
||||
moveItemToPane: (item, pane, index) ->
|
||||
@removeItem(item, true)
|
||||
pane.addItem(item, index, true)
|
||||
pane.addItem(item, {index: index, moved: true})
|
||||
|
||||
# Public: Destroy the active item and activate the next item.
|
||||
destroyActiveItem: ->
|
||||
|
||||
@@ -54,8 +54,9 @@ class Project extends Model
|
||||
Section: Serialization
|
||||
###
|
||||
|
||||
deserialize: (state, deserializerManager) ->
|
||||
deserialize: (state) ->
|
||||
state.paths = [state.path] if state.path? # backward compatibility
|
||||
state.paths = state.paths.filter (directoryPath) -> fs.isDirectorySync(directoryPath)
|
||||
|
||||
@buffers = _.compact state.buffers.map (bufferState) ->
|
||||
# Check that buffer's file path is accessible
|
||||
@@ -65,15 +66,15 @@ class Project extends Model
|
||||
fs.closeSync(fs.openSync(bufferState.filePath, 'r'))
|
||||
catch error
|
||||
return unless error.code is 'ENOENT'
|
||||
deserializerManager.deserialize(bufferState)
|
||||
TextBuffer.deserialize(bufferState)
|
||||
|
||||
@subscribeToBuffer(buffer) for buffer in @buffers
|
||||
@setPaths(state.paths)
|
||||
|
||||
serialize: ->
|
||||
serialize: (options={}) ->
|
||||
deserializer: 'Project'
|
||||
paths: @getPaths()
|
||||
buffers: _.compact(@buffers.map (buffer) -> buffer.serialize() if buffer.isRetained())
|
||||
buffers: _.compact(@buffers.map (buffer) -> buffer.serialize({markerLayers: options.isUnloading is true}) if buffer.isRetained())
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
module.exports = ({commandRegistry, commandInstaller, config}) ->
|
||||
commandRegistry.add 'atom-workspace',
|
||||
'pane:show-next-recently-used-item': -> @getModel().getActivePane().activateNextRecentlyUsedItem()
|
||||
'pane:show-previous-recently-used-item': -> @getModel().getActivePane().activatePreviousRecentlyUsedItem()
|
||||
'pane:move-active-item-to-top-of-stack': -> @getModel().getActivePane().moveActiveItemToTopOfStack()
|
||||
'pane:show-next-item': -> @getModel().getActivePane().activateNextItem()
|
||||
'pane:show-previous-item': -> @getModel().getActivePane().activatePreviousItem()
|
||||
'pane:show-item-1': -> @getModel().getActivePane().activateItemAtIndex(0)
|
||||
|
||||
@@ -810,11 +810,11 @@ class Selection extends Model
|
||||
@wordwise = false
|
||||
@linewise = false
|
||||
|
||||
autoscroll: ->
|
||||
autoscroll: (options) ->
|
||||
if @marker.hasTail()
|
||||
@editor.scrollToScreenRange(@getScreenRange(), reversed: @isReversed())
|
||||
@editor.scrollToScreenRange(@getScreenRange(), Object.assign({reversed: @isReversed()}, options))
|
||||
else
|
||||
@cursor.autoscroll()
|
||||
@cursor.autoscroll(options)
|
||||
|
||||
clearAutoscroll: ->
|
||||
|
||||
|
||||
@@ -24,10 +24,10 @@ class StateStore {
|
||||
}
|
||||
|
||||
save (key, value) {
|
||||
return this.dbPromise.then(db => {
|
||||
if (!db) return
|
||||
return new Promise((resolve, reject) => {
|
||||
this.dbPromise.then(db => {
|
||||
if (db == null) resolve()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
var request = db.transaction(['states'], 'readwrite')
|
||||
.objectStore('states')
|
||||
.put({value: value, storedAt: new Date().toString()}, key)
|
||||
@@ -49,7 +49,11 @@ class StateStore {
|
||||
|
||||
request.onsuccess = (event) => {
|
||||
let result = event.target.result
|
||||
resolve(result ? result.value : null)
|
||||
if (result && !result.isJSON) {
|
||||
resolve(result.value)
|
||||
} else {
|
||||
resolve(null)
|
||||
}
|
||||
}
|
||||
|
||||
request.onerror = (event) => reject(event)
|
||||
|
||||
@@ -43,7 +43,7 @@ class TextEditorComponent
|
||||
@assert domNode?, "TextEditorComponent::domNode was set to null."
|
||||
@domNodeValue = domNode
|
||||
|
||||
constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, tileSize, @views, @themes, @config, @workspace, @assert, @grammars}) ->
|
||||
constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, tileSize, @views, @themes, @config, @workspace, @assert, @grammars, scrollPastEnd}) ->
|
||||
@tileSize = tileSize if tileSize?
|
||||
@disposables = new CompositeDisposable
|
||||
|
||||
@@ -61,6 +61,7 @@ class TextEditorComponent
|
||||
stoppedScrollingDelay: 200
|
||||
config: @config
|
||||
lineTopIndex: lineTopIndex
|
||||
scrollPastEnd: scrollPastEnd
|
||||
|
||||
@presenter.onDidUpdateState(@requestUpdate)
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ class TextEditorElement extends HTMLElement
|
||||
focusOnAttach: false
|
||||
hasTiledRendering: true
|
||||
logicalDisplayBuffer: true
|
||||
scrollPastEnd: true
|
||||
autoHeight: true
|
||||
|
||||
createdCallback: ->
|
||||
# Use globals when the following instance variables aren't set.
|
||||
@@ -38,6 +40,9 @@ class TextEditorElement extends HTMLElement
|
||||
@setAttribute('tabindex', -1)
|
||||
|
||||
initializeContent: (attributes) ->
|
||||
unless @autoHeight
|
||||
@style.height = "100%"
|
||||
|
||||
if @config.get('editor.useShadowDOM')
|
||||
@useShadowDOM = true
|
||||
|
||||
@@ -86,7 +91,7 @@ class TextEditorElement extends HTMLElement
|
||||
@subscriptions.add @component.onDidChangeScrollLeft =>
|
||||
@emitter.emit("did-change-scroll-left", arguments...)
|
||||
|
||||
initialize: (model, {@views, @config, @themes, @workspace, @assert, @styles, @grammars}) ->
|
||||
initialize: (model, {@views, @config, @themes, @workspace, @assert, @styles, @grammars}, @autoHeight = true, @scrollPastEnd = true) ->
|
||||
throw new Error("Must pass a config parameter when initializing TextEditorElements") unless @views?
|
||||
throw new Error("Must pass a config parameter when initializing TextEditorElements") unless @config?
|
||||
throw new Error("Must pass a themes parameter when initializing TextEditorElements") unless @themes?
|
||||
@@ -143,6 +148,7 @@ class TextEditorElement extends HTMLElement
|
||||
workspace: @workspace
|
||||
assert: @assert
|
||||
grammars: @grammars
|
||||
scrollPastEnd: @scrollPastEnd
|
||||
)
|
||||
@rootElement.appendChild(@component.getDomNode())
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ class TextEditorPresenter
|
||||
minimumReflowInterval: 200
|
||||
|
||||
constructor: (params) ->
|
||||
{@model, @config, @lineTopIndex} = params
|
||||
{@model, @config, @lineTopIndex, scrollPastEnd} = params
|
||||
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @tileSize} = params
|
||||
{@contentFrameWidth} = params
|
||||
|
||||
@@ -42,6 +42,8 @@ class TextEditorPresenter
|
||||
@startReflowing() if @continuousReflow
|
||||
@updating = false
|
||||
|
||||
@scrollPastEndOverride = scrollPastEnd ? true
|
||||
|
||||
setLinesYardstick: (@linesYardstick) ->
|
||||
|
||||
getLinesYardstick: -> @linesYardstick
|
||||
@@ -661,7 +663,7 @@ class TextEditorPresenter
|
||||
return unless @contentHeight? and @clientHeight?
|
||||
|
||||
contentHeight = @contentHeight
|
||||
if @scrollPastEnd
|
||||
if @scrollPastEnd and @scrollPastEndOverride
|
||||
extraScrollHeight = @clientHeight - (@lineHeight * 3)
|
||||
contentHeight += extraScrollHeight if extraScrollHeight > 0
|
||||
scrollHeight = Math.max(contentHeight, @height)
|
||||
|
||||
40
src/text-editor-registry.coffee
Normal file
40
src/text-editor-registry.coffee
Normal file
@@ -0,0 +1,40 @@
|
||||
{Emitter, Disposable} = require 'event-kit'
|
||||
|
||||
# Experimental: This global registry tracks registered `TextEditors`.
|
||||
#
|
||||
# If you want to add functionality to a wider set of text editors than just
|
||||
# those appearing within workspace panes, use `atom.textEditors.observe` to
|
||||
# invoke a callback for all current and future registered text editors.
|
||||
#
|
||||
# If you want packages to be able to add functionality to your non-pane text
|
||||
# editors (such as a search field in a custom user interface element), register
|
||||
# them for observation via `atom.textEditors.add`. **Important:** When you're
|
||||
# done using your editor, be sure to call `dispose` on the returned disposable
|
||||
# to avoid leaking editors.
|
||||
module.exports =
|
||||
class TextEditorRegistry
|
||||
constructor: ->
|
||||
@editors = new Set
|
||||
@emitter = new Emitter
|
||||
|
||||
# Register a `TextEditor`.
|
||||
#
|
||||
# * `editor` The editor to register.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to remove the
|
||||
# added editor. To avoid any memory leaks this should be called when the
|
||||
# editor is destroyed.
|
||||
add: (editor) ->
|
||||
@editors.add(editor)
|
||||
@emitter.emit 'did-add-editor', editor
|
||||
new Disposable => @editors.delete(editor)
|
||||
|
||||
# Invoke the given callback with all the current and future registered
|
||||
# `TextEditors`.
|
||||
#
|
||||
# * `callback` {Function} to be called with current and future text editors.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
observe: (callback) ->
|
||||
@editors.forEach(callback)
|
||||
@emitter.on 'did-add-editor', callback
|
||||
@@ -11,6 +11,7 @@ Selection = require './selection'
|
||||
TextMateScopeSelector = require('first-mate').ScopeSelector
|
||||
{Directory} = require "pathwatcher"
|
||||
GutterContainer = require './gutter-container'
|
||||
TextEditorElement = require './text-editor-element'
|
||||
|
||||
# Essential: This class represents all essential editing state for a single
|
||||
# {TextBuffer}, including cursor and selection positions, folds, and soft wraps.
|
||||
@@ -61,6 +62,10 @@ class TextEditor extends Model
|
||||
suppressSelectionMerging: false
|
||||
selectionFlashDuration: 500
|
||||
gutterContainer: null
|
||||
editorElement: null
|
||||
|
||||
Object.defineProperty @prototype, "element",
|
||||
get: -> @getElement()
|
||||
|
||||
@deserialize: (state, atomEnvironment) ->
|
||||
try
|
||||
@@ -82,7 +87,10 @@ class TextEditor extends Model
|
||||
state.project = atomEnvironment.project
|
||||
state.assert = atomEnvironment.assert.bind(atomEnvironment)
|
||||
state.applicationDelegate = atomEnvironment.applicationDelegate
|
||||
new this(state)
|
||||
editor = new this(state)
|
||||
disposable = atomEnvironment.textEditors.add(editor)
|
||||
editor.onDidDestroy -> disposable.dispose()
|
||||
editor
|
||||
|
||||
constructor: (params={}) ->
|
||||
super
|
||||
@@ -92,7 +100,7 @@ class TextEditor extends Model
|
||||
softWrapped, @displayBuffer, @selectionsMarkerLayer, buffer, suppressCursorCreation,
|
||||
@mini, @placeholderText, lineNumberGutterVisible, largeFileMode, @config,
|
||||
@notificationManager, @packageManager, @clipboard, @viewRegistry, @grammarRegistry,
|
||||
@project, @assert, @applicationDelegate, @pending
|
||||
@project, @assert, @applicationDelegate, grammar, showInvisibles, @autoHeight, @scrollPastEnd
|
||||
} = params
|
||||
|
||||
throw new Error("Must pass a config parameter when constructing TextEditors") unless @config?
|
||||
@@ -111,10 +119,15 @@ class TextEditor extends Model
|
||||
@cursors = []
|
||||
@cursorsByMarkerId = new Map
|
||||
@selections = []
|
||||
@autoHeight ?= true
|
||||
@scrollPastEnd ?= true
|
||||
@hasTerminatedPendingState = false
|
||||
|
||||
showInvisibles ?= true
|
||||
|
||||
buffer ?= new TextBuffer
|
||||
@displayBuffer ?= new DisplayBuffer({
|
||||
buffer, tabLength, softWrapped, ignoreInvisibles: @mini, largeFileMode,
|
||||
buffer, tabLength, softWrapped, ignoreInvisibles: @mini or not showInvisibles, largeFileMode,
|
||||
@config, @assert, @grammarRegistry, @packageManager
|
||||
})
|
||||
@buffer = @displayBuffer.buffer
|
||||
@@ -143,6 +156,9 @@ class TextEditor extends Model
|
||||
priority: 0
|
||||
visible: lineNumberGutterVisible
|
||||
|
||||
if grammar?
|
||||
@setGrammar(grammar)
|
||||
|
||||
serialize: ->
|
||||
deserializer: 'TextEditor'
|
||||
id: @id
|
||||
@@ -151,7 +167,6 @@ class TextEditor extends Model
|
||||
firstVisibleScreenColumn: @getFirstVisibleScreenColumn()
|
||||
displayBuffer: @displayBuffer.serialize()
|
||||
selectionsMarkerLayerId: @selectionsMarkerLayer.id
|
||||
pending: @isPending()
|
||||
|
||||
subscribeToBuffer: ->
|
||||
@buffer.retain()
|
||||
@@ -163,12 +178,18 @@ class TextEditor extends Model
|
||||
@disposables.add @buffer.onDidChangeEncoding =>
|
||||
@emitter.emit 'did-change-encoding', @getEncoding()
|
||||
@disposables.add @buffer.onDidDestroy => @destroy()
|
||||
if @pending
|
||||
@disposables.add @buffer.onDidChangeModified =>
|
||||
@terminatePendingState() if @buffer.isModified()
|
||||
@disposables.add @buffer.onDidChangeModified =>
|
||||
@terminatePendingState() if not @hasTerminatedPendingState and @buffer.isModified()
|
||||
|
||||
@preserveCursorPositionOnBufferReload()
|
||||
|
||||
terminatePendingState: ->
|
||||
@emitter.emit 'did-terminate-pending-state' if not @hasTerminatedPendingState
|
||||
@hasTerminatedPendingState = true
|
||||
|
||||
onDidTerminatePendingState: (callback) ->
|
||||
@emitter.on 'did-terminate-pending-state', callback
|
||||
|
||||
subscribeToDisplayBuffer: ->
|
||||
@disposables.add @selectionsMarkerLayer.onDidCreateMarker @addSelection.bind(this)
|
||||
@disposables.add @displayBuffer.onDidChangeGrammar @handleGrammarChange.bind(this)
|
||||
@@ -575,13 +596,6 @@ class TextEditor extends Model
|
||||
getEditorWidthInChars: ->
|
||||
@displayBuffer.getEditorWidthInChars()
|
||||
|
||||
onDidTerminatePendingState: (callback) ->
|
||||
@emitter.on 'did-terminate-pending-state', callback
|
||||
|
||||
terminatePendingState: ->
|
||||
return if not @pending
|
||||
@pending = false
|
||||
@emitter.emit 'did-terminate-pending-state'
|
||||
|
||||
###
|
||||
Section: File Details
|
||||
@@ -666,9 +680,6 @@ class TextEditor extends Model
|
||||
# Essential: Returns {Boolean} `true` if this editor has no content.
|
||||
isEmpty: -> @buffer.isEmpty()
|
||||
|
||||
# Returns {Boolean} `true` if this editor is pending and `false` if it is permanent.
|
||||
isPending: -> Boolean(@pending)
|
||||
|
||||
# Copies the current file path to the native clipboard.
|
||||
copyPathToClipboard: (relative = false) ->
|
||||
if filePath = @getPath()
|
||||
@@ -2470,6 +2481,7 @@ class TextEditor extends Model
|
||||
selections = @getSelections()
|
||||
if selections.length > 1
|
||||
selection.destroy() for selection in selections[1...(selections.length)]
|
||||
selections[0].autoscroll(center: true)
|
||||
true
|
||||
else
|
||||
false
|
||||
@@ -2924,6 +2936,7 @@ class TextEditor extends Model
|
||||
# Extended: Unfold all existing folds.
|
||||
unfoldAll: ->
|
||||
@languageMode.unfoldAll()
|
||||
@scrollToCursorPosition()
|
||||
|
||||
# Extended: Fold all foldable lines at the given indent level.
|
||||
#
|
||||
@@ -3142,6 +3155,10 @@ class TextEditor extends Model
|
||||
Section: TextEditor Rendering
|
||||
###
|
||||
|
||||
# Get the Element for the editor.
|
||||
getElement: ->
|
||||
@editorElement ?= new TextEditorElement().initialize(this, atom, @autoHeight, @scrollPastEnd)
|
||||
|
||||
# Essential: Retrieves the greyed out placeholder of a mini editor.
|
||||
#
|
||||
# Returns a {String}.
|
||||
@@ -3215,8 +3232,8 @@ class TextEditor extends Model
|
||||
# top of the visible area.
|
||||
setFirstVisibleScreenRow: (screenRow, fromView) ->
|
||||
unless fromView
|
||||
maxScreenRow = @getLineCount() - 1
|
||||
unless @config.get('editor.scrollPastEnd')
|
||||
maxScreenRow = @getScreenLineCount() - 1
|
||||
unless @config.get('editor.scrollPastEnd') and @scrollPastEnd
|
||||
height = @displayBuffer.getHeight()
|
||||
lineHeightInPixels = @displayBuffer.getLineHeightInPixels()
|
||||
if height? and lineHeightInPixels?
|
||||
@@ -3233,7 +3250,7 @@ class TextEditor extends Model
|
||||
height = @displayBuffer.getHeight()
|
||||
lineHeightInPixels = @displayBuffer.getLineHeightInPixels()
|
||||
if height? and lineHeightInPixels?
|
||||
Math.min(@firstVisibleScreenRow + Math.floor(height / lineHeightInPixels), @getLineCount() - 1)
|
||||
Math.min(@firstVisibleScreenRow + Math.floor(height / lineHeightInPixels), @getScreenLineCount() - 1)
|
||||
else
|
||||
null
|
||||
|
||||
|
||||
@@ -498,7 +498,6 @@ class TokenizedLine
|
||||
while iterator.next()
|
||||
scopes = iterator.getScopes()
|
||||
continue if scopes.length is 1
|
||||
continue unless NonWhitespaceRegex.test(iterator.getText())
|
||||
for scope in scopes
|
||||
if CommentScopeRegex.test(scope)
|
||||
@isCommentLine = true
|
||||
|
||||
@@ -63,6 +63,8 @@ class TooltipManager
|
||||
# full list of options. You can also supply the following additional options:
|
||||
# * `title` A {String} or {Function} to use for the text in the tip. If
|
||||
# given a function, `this` will be set to the `target` element.
|
||||
# * `trigger` A {String} that's the same as Bootstrap 'click | hover | focus
|
||||
# | manual', except 'manual' will show the tooltip immediately.
|
||||
# * `keyBindingCommand` A {String} containing a command name. If you specify
|
||||
# this option and a key binding exists that matches the command, it will
|
||||
# be appended to the title or rendered alone if no title is specified.
|
||||
|
||||
@@ -64,7 +64,9 @@ Tooltip.prototype.init = function (element, options) {
|
||||
|
||||
if (trigger === 'click') {
|
||||
this.disposables.add(listen(this.element, 'click', this.options.selector, this.toggle.bind(this)))
|
||||
} else if (trigger !== 'manual') {
|
||||
} else if (trigger === 'manual') {
|
||||
this.show()
|
||||
} else {
|
||||
var eventIn, eventOut
|
||||
|
||||
if (trigger === 'hover') {
|
||||
|
||||
@@ -171,6 +171,11 @@ class ViewRegistry
|
||||
if object instanceof HTMLElement
|
||||
return object
|
||||
|
||||
if typeof object?.getElement is 'function'
|
||||
element = object.getElement()
|
||||
if element instanceof HTMLElement
|
||||
return element
|
||||
|
||||
if object?.element instanceof HTMLElement
|
||||
return object.element
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ class WindowEventHandler
|
||||
@addEventListener(@window, 'focus', @handleWindowFocus)
|
||||
@addEventListener(@window, 'blur', @handleWindowBlur)
|
||||
|
||||
@addEventListener(@document, 'keydown', @handleDocumentKeydown)
|
||||
@addEventListener(@document, 'keyup', @handleDocumentKeyEvent)
|
||||
@addEventListener(@document, 'keydown', @handleDocumentKeyEvent)
|
||||
@addEventListener(@document, 'drop', @handleDocumentDrop)
|
||||
@addEventListener(@document, 'dragover', @handleDocumentDragover)
|
||||
@addEventListener(@document, 'contextmenu', @handleDocumentContextmenu)
|
||||
@@ -66,7 +67,7 @@ class WindowEventHandler
|
||||
target.addEventListener(eventName, handler)
|
||||
@subscriptions.add(new Disposable(-> target.removeEventListener(eventName, handler)))
|
||||
|
||||
handleDocumentKeydown: (event) =>
|
||||
handleDocumentKeyEvent: (event) =>
|
||||
@atomEnvironment.keymaps.handleKeyboardEvent(event)
|
||||
event.stopImmediatePropagation()
|
||||
|
||||
@@ -142,7 +143,6 @@ class WindowEventHandler
|
||||
@reloadRequested = false
|
||||
|
||||
@atomEnvironment.storeWindowDimensions()
|
||||
@atomEnvironment.saveState()
|
||||
if confirmed
|
||||
@atomEnvironment.unloadEditorWindow()
|
||||
else
|
||||
|
||||
@@ -43,6 +43,12 @@ class Workspace extends Model
|
||||
@defaultDirectorySearcher = new DefaultDirectorySearcher()
|
||||
@consumeServices(@packageManager)
|
||||
|
||||
# One cannot simply .bind here since it could be used as a component with
|
||||
# Etch, in which case it'd be `new`d. And when it's `new`d, `this` is always
|
||||
# the newly created object.
|
||||
realThis = this
|
||||
@buildTextEditor = -> Workspace.prototype.buildTextEditor.apply(realThis, arguments)
|
||||
|
||||
@panelContainers =
|
||||
top: new PanelContainer({location: 'top'})
|
||||
left: new PanelContainer({location: 'left'})
|
||||
@@ -403,6 +409,9 @@ class Workspace extends Model
|
||||
# containing pane. Defaults to `true`.
|
||||
# * `activateItem` A {Boolean} indicating whether to call {Pane::activateItem}
|
||||
# on containing pane. Defaults to `true`.
|
||||
# * `pending` A {Boolean} indicating whether or not the item should be opened
|
||||
# in a pending state. Existing pending items in a pane are replaced with
|
||||
# new pending items when they are opened.
|
||||
# * `searchAllPanes` A {Boolean}. If `true`, the workspace will attempt to
|
||||
# activate an existing item for the given URI on any pane.
|
||||
# If `false`, only the active pane will be searched for
|
||||
@@ -477,7 +486,7 @@ class Workspace extends Model
|
||||
|
||||
if uri?
|
||||
if item = pane.itemForURI(uri)
|
||||
item.terminatePendingState?() if item.isPending?() and not options.pending
|
||||
pane.clearPendingItem() if not options.pending and pane.getPendingItem() is item
|
||||
item ?= opener(uri, options) for opener in @getOpeners() when not item
|
||||
|
||||
try
|
||||
@@ -500,7 +509,7 @@ class Workspace extends Model
|
||||
return item if pane.isDestroyed()
|
||||
|
||||
@itemOpened(item)
|
||||
pane.activateItem(item) if activateItem
|
||||
pane.activateItem(item, {pending: options.pending}) if activateItem
|
||||
pane.activate() if activatePane
|
||||
|
||||
initialLine = initialColumn = 0
|
||||
@@ -555,7 +564,10 @@ class Workspace extends Model
|
||||
@config, @notificationManager, @packageManager, @clipboard, @viewRegistry,
|
||||
@grammarRegistry, @project, @assert, @applicationDelegate
|
||||
}, params)
|
||||
new TextEditor(params)
|
||||
editor = new TextEditor(params)
|
||||
disposable = atom.textEditors.add(editor)
|
||||
editor.onDidDestroy -> disposable.dispose()
|
||||
editor
|
||||
|
||||
# Public: Asynchronously reopens the last-closed item's URI if it hasn't already been
|
||||
# reopened.
|
||||
|
||||
Reference in New Issue
Block a user