Merge branch 'master' into atom-textarea

This commit is contained in:
joshaber
2016-03-03 09:53:11 -05:00
14 changed files with 337 additions and 19 deletions

1
.gitignore vendored
View File

@@ -15,3 +15,4 @@ debug.log
docs/output
docs/includes
spec/fixtures/evil-files/
out/

View File

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

View File

@@ -37,7 +37,7 @@
"less-cache": "0.23",
"line-top-index": "0.2.0",
"marked": "^0.3.4",
"nodegit": "0.11.5",
"nodegit": "0.11.6",
"normalize-package-data": "^2.0.0",
"nslog": "^3",
"oniguruma": "^5",
@@ -72,7 +72,7 @@
"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",
@@ -147,7 +147,7 @@
"language-text": "0.7.0",
"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,

View File

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

View File

@@ -328,3 +328,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'

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

View File

@@ -623,6 +623,34 @@ describe "Workspace", ->
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()

View File

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

View File

@@ -41,6 +41,7 @@ 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'
@@ -115,6 +116,9 @@ class AtomEnvironment extends Model
# Public: A {TextEditorRegistry} instance
textEditors: null
# Private: An {AutoUpdateManager} instance
autoUpdater: null
saveStateDebounceInterval: 1000
###
@@ -188,6 +192,7 @@ class AtomEnvironment extends Model
@themes.workspace = @workspace
@textEditors = new TextEditorRegistry
@autoUpdater = new AutoUpdateManager({@applicationDelegate})
@config.load()
@@ -331,6 +336,7 @@ class AtomEnvironment extends Model
@commands.clear()
@stylesElement.remove()
@config.unobserveUserConfig()
@autoUpdater.destroy()
@uninstallWindowEventHandler()
@@ -409,6 +415,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
@@ -828,7 +844,6 @@ class AtomEnvironment extends Model
@applicationDelegate.setTemporaryWindowState(state)
savePromise.catch(reject).then(resolve)
loadState: ->
if @enablePersistence
if stateKey = @getStateKey(@getLoadSettings().initialPaths)
@@ -877,6 +892,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
@@ -884,7 +900,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}")

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

View File

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

View File

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

View File

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

View File

@@ -423,10 +423,6 @@ class Pane extends Model
return if item in @items
pendingItem = @getPendingItem()
@destroyItem(pendingItem) if pendingItem?
@setPendingItem(item) if pending
if typeof item.onDidDestroy is 'function'
itemSubscriptions = new CompositeDisposable
itemSubscriptions.add item.onDidDestroy => @removeItem(item, false)
@@ -437,6 +433,10 @@ class Pane extends Model
@subscriptionsPerItem.set item, itemSubscriptions
@items.splice(index, 0, item)
pendingItem = @getPendingItem()
@destroyItem(pendingItem) if pendingItem?
@setPendingItem(item) if pending
@emitter.emit 'did-add-item', {item, index, moved}
@setActiveItem(item) unless @getActiveItem()?
item