Merge branch 'master' into as-block-decorations

This commit is contained in:
Antonio Scandurra
2016-01-14 09:30:06 -07:00
42 changed files with 391 additions and 191 deletions

View File

@@ -42,6 +42,10 @@ exports.getCachePath = function (sourceCode) {
exports.compile = function (sourceCode, filePath) {
if (!babel) {
babel = require('babel-core')
var Logger = require('babel-core/lib/transformation/file/logger')
var noop = function () {}
Logger.prototype.debug = noop
Logger.prototype.verbose = noop
}
var options = {filename: filePath}

View File

@@ -103,8 +103,6 @@ class ApplicationMenu
downloadingUpdateItem.visible = false
installUpdateItem.visible = false
return if @autoUpdateManager.isDisabled()
switch state
when 'idle', 'error', 'no-update-available'
checkForUpdateItem.visible = true
@@ -119,9 +117,10 @@ class ApplicationMenu
#
# Returns an Array of menu item Objects.
getDefaultTemplate: ->
template = [
[
label: "Atom"
submenu: [
{label: "Check for Update", metadata: {autoUpdate: true}}
{label: 'Reload', accelerator: 'Command+R', click: => @focusedWindow()?.reload()}
{label: 'Close Window', accelerator: 'Command+Shift+W', click: => @focusedWindow()?.close()}
{label: 'Toggle Dev Tools', accelerator: 'Command+Alt+I', click: => @focusedWindow()?.toggleDevTools()}
@@ -129,10 +128,6 @@ class ApplicationMenu
]
]
# Add `Check for Update` button if autoUpdateManager is enabled.
template[0].submenu.unshift({label: "Check for Update", metadata: {autoUpdate: true}}) unless @autoUpdateManager.isDisabled()
template
focusedWindow: ->
_.find global.atomApplication.windows, (atomWindow) -> atomWindow.isFocused()

View File

@@ -74,8 +74,7 @@ class AtomApplication
@pidsToOpenWindows = {}
@windows = []
disableAutoUpdate = require(path.join(@resourcePath, 'package.json'))._disableAutoUpdate ? false
@autoUpdateManager = new AutoUpdateManager(@version, options.test, disableAutoUpdate)
@autoUpdateManager = new AutoUpdateManager(@version, options.test, @resourcePath)
@applicationMenu = new ApplicationMenu(@version, @autoUpdateManager)
@atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode)
@@ -167,7 +166,7 @@ class AtomApplication
safeMode: @focusedWindow()?.safeMode
@on 'application:quit', -> app.quit()
@on 'application:new-window', -> @openPath(_.extend(windowDimensions: @focusedWindow()?.getDimensions(), getLoadSettings()))
@on 'application:new-window', -> @openPath(getLoadSettings())
@on 'application:new-file', -> (@focusedWindow() ? this).openPath()
@on 'application:open', -> @promptForPathToOpen('all', getLoadSettings())
@on 'application:open-file', -> @promptForPathToOpen('file', getLoadSettings())
@@ -229,7 +228,7 @@ class AtomApplication
@openUrl({urlToOpen, @devMode, @safeMode})
app.on 'activate-with-no-open-windows', (event) =>
event.preventDefault()
event?.preventDefault()
@emit('application:new-window')
# A request from the associated render process to open a new render process.
@@ -360,6 +359,23 @@ class AtomApplication
focusedWindow: ->
_.find @windows, (atomWindow) -> atomWindow.isFocused()
# Get the platform-specific window offset for new windows.
getWindowOffsetForCurrentPlatform: ->
offsetByPlatform =
darwin: 22
win32: 26
offsetByPlatform[process.platform] ? 0
# Get the dimensions for opening a new window by cascading as appropriate to
# the platform.
getDimensionsForNewWindow: ->
dimensions = (@focusedWindow() ? @lastFocusedWindow)?.getDimensions()
offset = @getWindowOffsetForCurrentPlatform()
if dimensions? and offset?
dimensions.x += offset
dimensions.y += offset
dimensions
# Public: Opens a single path, in an existing window if possible.
#
# options -
@@ -370,7 +386,7 @@ 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}) ->
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window} = {}) ->
@openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window})
# Public: Opens multiple paths, in existing windows if possible.
@@ -417,6 +433,7 @@ class AtomApplication
windowInitializationScript ?= require.resolve('../initialize-application-window')
resourcePath ?= @resourcePath
windowDimensions ?= @getDimensionsForNewWindow()
openedWindow = new AtomWindow({locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup})
if pidToKillWhenClosed?

View File

@@ -1,5 +1,6 @@
autoUpdater = null
_ = require 'underscore-plus'
Config = require '../config'
{EventEmitter} = require 'events'
path = require 'path'
@@ -15,10 +16,13 @@ module.exports =
class AutoUpdateManager
_.extend @prototype, EventEmitter.prototype
constructor: (@version, @testMode, @disabled) ->
constructor: (@version, @testMode, resourcePath) ->
@state = IdleState
@iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png')
@feedUrl = "https://atom.io/api/updates?version=#{@version}"
@config = new Config({configDirPath: process.env.ATOM_HOME, resourcePath, enablePersistence: true})
@config.setSchema null, {type: 'object', properties: _.clone(require('../config-schema'))}
@config.load()
process.nextTick => @setupAutoUpdater()
setupAutoUpdater: ->
@@ -46,9 +50,13 @@ class AutoUpdateManager
@setState(UpdateAvailableState)
@emitUpdateAvailableEvent(@getWindows()...)
# Only check for updates periodically if enabled and running in release
# version.
@scheduleUpdateCheck() unless /\w{7}/.test(@version) or @disabled
@config.onDidChange 'core.automaticallyUpdate', ({newValue}) =>
if newValue
@scheduleUpdateCheck()
else
@cancelScheduledUpdateCheck()
@scheduleUpdateCheck() if @config.get 'core.automaticallyUpdate'
switch process.platform
when 'win32'
@@ -56,9 +64,6 @@ class AutoUpdateManager
when 'linux'
@setState(UnsupportedState)
isDisabled: ->
@disabled
emitUpdateAvailableEvent: (windows...) ->
return unless @releaseVersion?
for atomWindow in windows
@@ -74,10 +79,18 @@ class AutoUpdateManager
@state
scheduleUpdateCheck: ->
checkForUpdates = => @check(hidePopups: true)
fourHours = 1000 * 60 * 60 * 4
setInterval(checkForUpdates, fourHours)
checkForUpdates()
# Only schedule update check periodically if running in release version and
# and there is no existing scheduled update check.
unless /\w{7}/.test(@version) or @checkForUpdatesIntervalID
checkForUpdates = => @check(hidePopups: true)
fourHours = 1000 * 60 * 60 * 4
@checkForUpdatesIntervalID = setInterval(checkForUpdates, fourHours)
checkForUpdates()
cancelScheduledUpdateCheck: ->
if @checkForUpdatesIntervalID
clearInterval(@checkForUpdatesIntervalID)
@checkForUpdatesIntervalID = null
check: ({hidePopups}={}) ->
unless hidePopups

View File

@@ -104,6 +104,10 @@ module.exports =
description: 'Automatically open an empty editor on startup.'
type: 'boolean'
default: true
automaticallyUpdate:
description: 'Automatically update Atom when a new release is available.'
type: 'boolean'
default: true
editor:
type: 'object'

View File

@@ -779,9 +779,14 @@ class Config
loadUserConfig: ->
return if @shouldNotAccessFileSystem()
unless fs.existsSync(@configFilePath)
fs.makeTreeSync(path.dirname(@configFilePath))
CSON.writeFileSync(@configFilePath, {})
try
unless fs.existsSync(@configFilePath)
fs.makeTreeSync(path.dirname(@configFilePath))
CSON.writeFileSync(@configFilePath, {})
catch error
@configFileHasErrors = true
@notifyFailure("Failed to initialize `#{path.basename(@configFilePath)}`", error.stack)
return
try
unless @savePending
@@ -820,7 +825,7 @@ class Config
@watchSubscription = null
notifyFailure: (errorMessage, detail) ->
@notificationManager.addError(errorMessage, {detail, dismissable: true})
@notificationManager?.addError(errorMessage, {detail, dismissable: true})
save: ->
return if @shouldNotAccessFileSystem()

View File

@@ -812,6 +812,7 @@ class DisplayBuffer extends Model
decorationsState
decorateMarker: (marker, decorationParams) ->
throw new Error("Cannot decorate a destroyed marker") if marker.isDestroyed()
marker = @getMarkerLayer(marker.layer.id).getMarker(marker.id)
decoration = new Decoration(marker, this, decorationParams)
@decorationsByMarkerId[marker.id] ?= []

View File

@@ -463,8 +463,12 @@ class GitRepository
refreshStatus: ->
@handlerPath ?= require.resolve('./repository-status-handler')
relativeProjectPaths = @project?.getPaths()
.map (path) => @relativize(path)
.filter (path) -> path.length > 0
@statusTask?.terminate()
@statusTask = Task.once @handlerPath, @getPath(), ({statuses, upstream, branch, submodules}) =>
@statusTask = Task.once @handlerPath, @getPath(), relativeProjectPaths, ({statuses, upstream, branch, submodules}) =>
statusesUnchanged = _.isEqual(statuses, @statuses) and
_.isEqual(upstream, @upstream) and
_.isEqual(branch, @branch) and

View File

@@ -77,10 +77,7 @@ class LinesYardstick
else
Point(row, column)
pixelPositionForScreenPosition: (screenPosition, clip=true) ->
screenPosition = Point.fromObject(screenPosition)
screenPosition = @model.clipScreenPosition(screenPosition) if clip
pixelPositionForScreenPosition: (screenPosition) ->
targetRow = screenPosition.row
targetColumn = screenPosition.column

View File

@@ -199,7 +199,10 @@ class PackageManager
# Returns the {Package} that was disabled or null if it isn't loaded.
disablePackage: (name) ->
pack = @loadPackage(name)
pack?.disable()
unless @isPackageDisabled(name)
pack?.disable()
pack
# Public: Is the package with the given name disabled?

View File

@@ -337,13 +337,19 @@ class Pane extends Model
#
# * `index` {Number}
activateItemAtIndex: (index) ->
@activateItem(@itemAtIndex(index))
item = @itemAtIndex(index) or @getActiveItem()
@setActiveItem(item)
# Public: Make the given item *active*, causing it to be displayed by
# the pane's view.
activateItem: (item) ->
if item?
@addItem(item, @getActiveItemIndex() + 1, false)
if @activeItem?.isPending?()
index = @getActiveItemIndex()
@destroyActiveItem() unless item is @activeItem
else
index = @getActiveItemIndex() + 1
@addItem(item, index, false)
@setActiveItem(item)
# Public: Add the given item to the pane.
@@ -574,7 +580,6 @@ class Pane extends Model
# Public: Makes this pane the *active* pane, causing it to gain focus.
activate: ->
throw new Error("Pane has been destroyed") if @isDestroyed()
@container?.setActivePane(this)
@emitter.emit 'did-activate'
@@ -721,30 +726,28 @@ class Pane extends Model
message = "#{message} '#{itemPath}'" if itemPath
@notificationManager.addWarning(message, options)
if error.code is 'EISDIR' or error.message?.endsWith?('is a directory')
customMessage = @getMessageForErrorCode(error.code)
if customMessage?
addWarningWithPath("Unable to save file: #{customMessage}")
else if error.code is 'EISDIR' or error.message?.endsWith?('is a directory')
@notificationManager.addWarning("Unable to save file: #{error.message}")
else if error.code is 'EACCES'
addWarningWithPath('Unable to save file: Permission denied')
else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST', 'ELOOP', 'EAGAIN']
addWarningWithPath('Unable to save file', detail: error.message)
else if error.code is 'EROFS'
addWarningWithPath('Unable to save file: Read-only file system')
else if error.code is 'ENOSPC'
addWarningWithPath('Unable to save file: No space left on device')
else if error.code is 'ENXIO'
addWarningWithPath('Unable to save file: No such device or address')
else if error.code is 'ENOTSUP'
addWarningWithPath('Unable to save file: Operation not supported on socket')
else if error.code is 'EIO'
addWarningWithPath('Unable to save file: I/O error writing file')
else if error.code is 'EINTR'
addWarningWithPath('Unable to save file: Interrupted system call')
else if error.code is 'ECONNRESET'
addWarningWithPath('Unable to save file: Connection reset')
else if error.code is 'ESPIPE'
addWarningWithPath('Unable to save file: Invalid seek')
else if errorMatch = /ENOTDIR, not a directory '([^']+)'/.exec(error.message)
fileName = errorMatch[1]
@notificationManager.addWarning("Unable to save file: A directory in the path '#{fileName}' could not be written to")
else
throw error
getMessageForErrorCode: (errorCode) ->
switch errorCode
when 'EACCES' then 'Permission denied'
when 'ECONNRESET' then 'Connection reset'
when 'EINTR' then 'Interrupted system call'
when 'EIO' then 'I/O error writing file'
when 'ENOSPC' then 'No space left on device'
when 'ENOTSUP' then 'Operation not supported on socket'
when 'ENXIO' then 'No such device or address'
when 'EROFS' then 'Read-only file system'
when 'ESPIPE' then 'Invalid seek'
when 'ETIMEDOUT' then 'Connection timed out'

View File

@@ -288,7 +288,7 @@ class Project extends Model
'atom.repository-provider',
'^0.1.0',
(provider) =>
@repositoryProviders.push(provider)
@repositoryProviders.unshift(provider)
@setPaths(@getPaths()) if null in @repositories
new Disposable =>
@repositoryProviders.splice(@repositoryProviders.indexOf(provider), 1)

View File

@@ -169,7 +169,8 @@ module.exports = ({commandRegistry, commandInstaller, config}) ->
'editor:fold-at-indent-level-8': -> @foldAllAtIndentLevel(7)
'editor:fold-at-indent-level-9': -> @foldAllAtIndentLevel(8)
'editor:log-cursor-scope': -> @logCursorScope()
'editor:copy-path': -> @copyPathToClipboard()
'editor:copy-path': -> @copyPathToClipboard(false)
'editor:copy-project-path': -> @copyPathToClipboard(true)
'editor:toggle-indent-guide': -> config.set('editor.showIndentGuide', not config.get('editor.showIndentGuide'))
'editor:toggle-line-numbers': -> config.set('editor.showLineNumbers', not config.get('editor.showLineNumbers'))
'editor:scroll-to-cursor': -> @scrollToCursorPosition()

View File

@@ -1,7 +1,7 @@
Git = require 'git-utils'
path = require 'path'
module.exports = (repoPath) ->
module.exports = (repoPath, paths = []) ->
repo = Git.open(repoPath)
upstream = {}
@@ -12,7 +12,8 @@ module.exports = (repoPath) ->
if repo?
# Statuses in main repo
workingDirectoryPath = repo.getWorkingDirectory()
for filePath, status of repo.getStatus()
repoStatus = (if paths.length > 0 then repo.getStatusForPaths(paths) else repo.getStatus())
for filePath, status of repoStatus
statuses[filePath] = status
# Statuses in submodules

View File

@@ -445,12 +445,17 @@ class TextEditorComponent
getVisibleRowRange: ->
@presenter.getVisibleRowRange()
pixelPositionForScreenPosition: (screenPosition, clip) ->
pixelPositionForScreenPosition: (screenPosition, clip=true) ->
screenPosition = Point.fromObject(screenPosition)
screenPosition = @editor.clipScreenPosition(screenPosition) if clip
unless @presenter.isRowVisible(screenPosition.row)
@presenter.setScreenRowsToMeasure([screenPosition.row])
unless @linesComponent.lineNodeForLineIdAndScreenRow(@presenter.lineIdForScreenRow(screenPosition.row), screenPosition.row)?
@updateSyncPreMeasurement()
pixelPosition = @linesYardstick.pixelPositionForScreenPosition(screenPosition, clip)
pixelPosition = @linesYardstick.pixelPositionForScreenPosition(screenPosition)
@presenter.clearScreenRowsToMeasure()
pixelPosition

View File

@@ -459,7 +459,7 @@ class TextEditorPresenter
else
screenPosition = decoration.getMarker().getHeadScreenPosition()
pixelPosition = @pixelPositionForScreenPosition(screenPosition, true)
pixelPosition = @pixelPositionForScreenPosition(screenPosition)
top = pixelPosition.top + @lineHeight
left = pixelPosition.left + @gutterWidth
@@ -681,8 +681,10 @@ class TextEditorPresenter
updateHorizontalDimensions: ->
if @baseCharacterWidth?
oldContentWidth = @contentWidth
clip = @model.tokenizedLineForScreenRow(@model.getLongestScreenRow())?.isSoftWrapped()
@contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), @model.getMaxScreenLineLength()], clip).left
rightmostPosition = Point(@model.getLongestScreenRow(), @model.getMaxScreenLineLength())
if @model.tokenizedLineForScreenRow(rightmostPosition.row)?.isSoftWrapped()
rightmostPosition = @model.clipScreenPosition(rightmostPosition)
@contentWidth = @pixelPositionForScreenPosition(rightmostPosition).left
@contentWidth += @scrollLeft
@contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width
@@ -1007,9 +1009,9 @@ class TextEditorPresenter
hasPixelPositionRequirements: ->
@lineHeight? and @baseCharacterWidth?
pixelPositionForScreenPosition: (screenPosition, clip=true) ->
pixelPositionForScreenPosition: (screenPosition) ->
position =
@linesYardstick.pixelPositionForScreenPosition(screenPosition, clip, true)
@linesYardstick.pixelPositionForScreenPosition(screenPosition)
position.top -= @getScrollTop()
position.left -= @getScrollLeft()
@@ -1028,14 +1030,14 @@ class TextEditorPresenter
lineHeight = @model.getLineHeightInPixels()
if screenRange.end.row > screenRange.start.row
top = @linesYardstick.pixelPositionForScreenPosition(screenRange.start, true).top
top = @linesYardstick.pixelPositionForScreenPosition(screenRange.start).top
left = 0
height = (screenRange.end.row - screenRange.start.row + 1) * lineHeight
width = @getScrollWidth()
else
{top, left} = @linesYardstick.pixelPositionForScreenPosition(screenRange.start, false)
{top, left} = @linesYardstick.pixelPositionForScreenPosition(screenRange.start)
height = lineHeight
width = @linesYardstick.pixelPositionForScreenPosition(screenRange.end, false).left - left
width = @linesYardstick.pixelPositionForScreenPosition(screenRange.end).left - left
{top, left, width, height}
@@ -1217,8 +1219,8 @@ class TextEditorPresenter
buildHighlightRegions: (screenRange) ->
lineHeightInPixels = @lineHeight
startPixelPosition = @pixelPositionForScreenPosition(screenRange.start, false)
endPixelPosition = @pixelPositionForScreenPosition(screenRange.end, false)
startPixelPosition = @pixelPositionForScreenPosition(screenRange.start)
endPixelPosition = @pixelPositionForScreenPosition(screenRange.end)
spannedRows = screenRange.end.row - screenRange.start.row + 1
regions = []
@@ -1520,3 +1522,6 @@ class TextEditorPresenter
isRowVisible: (row) ->
@startRow <= row < @endRow
lineIdForScreenRow: (screenRow) ->
@model.tokenizedLineForScreenRow(screenRow)?.id

View File

@@ -92,7 +92,7 @@ class TextEditor extends Model
softWrapped, @displayBuffer, @selectionsMarkerLayer, buffer, suppressCursorCreation,
@mini, @placeholderText, lineNumberGutterVisible, largeFileMode, @config,
@notificationManager, @packageManager, @clipboard, @viewRegistry, @grammarRegistry,
@project, @assert, @applicationDelegate
@project, @assert, @applicationDelegate, @pending
} = params
throw new Error("Must pass a config parameter when constructing TextEditors") unless @config?
@@ -161,6 +161,9 @@ 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()
@preserveCursorPositionOnBufferReload()
@@ -569,6 +572,13 @@ class TextEditor extends Model
getEditorWidthInChars: ->
@displayBuffer.getEditorWidthInChars()
onDidTerminatePendingState: (callback) ->
@emitter.on 'did-terminate-pending-state', callback
terminatePendingState: ->
@pending = false
@emitter.emit 'did-terminate-pending-state', this
###
Section: File Details
###
@@ -652,9 +662,13 @@ 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: ->
copyPathToClipboard: (relative = false) ->
if filePath = @getPath()
filePath = atom.project.relativize(filePath) if relative
@clipboard.write(filePath)
###
@@ -3043,7 +3057,7 @@ class TextEditor extends Model
# Essential: Scrolls the editor to the given screen position.
#
# * `screenPosition` An object that represents a buffer position. It can be either
# * `screenPosition` An object that represents a screen position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
# * `options` (optional) {Object}
# * `center` Center the editor around the position if possible. (default: false)

View File

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