Merge branch 'master' into colllin-patch-1

This commit is contained in:
Collin Donahue-Oponski
2015-12-10 21:03:22 -07:00
67 changed files with 1380 additions and 630 deletions

View File

@@ -66,13 +66,42 @@ class ApplicationDelegate
ipc.send("call-window-method", "setFullScreen", fullScreen)
openWindowDevTools: ->
remote.getCurrentWindow().openDevTools()
new Promise (resolve) ->
# Defer DevTools interaction to the next tick, because using them during
# event handling causes some wrong input events to be triggered on
# `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697).
process.nextTick ->
if remote.getCurrentWindow().isDevToolsOpened()
resolve()
else
remote.getCurrentWindow().once("devtools-opened", -> resolve())
ipc.send("call-window-method", "openDevTools")
closeWindowDevTools: ->
new Promise (resolve) ->
# Defer DevTools interaction to the next tick, because using them during
# event handling causes some wrong input events to be triggered on
# `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697).
process.nextTick ->
unless remote.getCurrentWindow().isDevToolsOpened()
resolve()
else
remote.getCurrentWindow().once("devtools-closed", -> resolve())
ipc.send("call-window-method", "closeDevTools")
toggleWindowDevTools: ->
remote.getCurrentWindow().toggleDevTools()
new Promise (resolve) =>
# Defer DevTools interaction to the next tick, because using them during
# event handling causes some wrong input events to be triggered on
# `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697).
process.nextTick =>
if remote.getCurrentWindow().isDevToolsOpened()
@closeWindowDevTools().then(resolve)
else
@openWindowDevTools().then(resolve)
executeJavaScriptInWindowDevTools: (code) ->
remote.getCurrentWindow().executeJavaScriptInDevTools(code)
ipc.send("call-window-method", "executeJavaScriptInDevTools", code)
setWindowDocumentEdited: (edited) ->
ipc.send("call-window-method", "setDocumentEdited", edited)
@@ -80,6 +109,9 @@ class ApplicationDelegate
setRepresentedFilename: (filename) ->
ipc.send("call-window-method", "setRepresentedFilename", filename)
addRecentDocument: (filename) ->
ipc.send("add-recent-document", filename)
setRepresentedDirectoryPaths: (paths) ->
loadSettings = getWindowLoadSettings()
loadSettings['initialPaths'] = paths

View File

@@ -151,7 +151,7 @@ class AtomEnvironment extends Model
@packages = new PackageManager({
devMode, configDirPath, resourcePath, safeMode, @config, styleManager: @styles,
commandRegistry: @commands, keymapManager: @keymaps, notificationManager: @notifications,
grammarRegistry: @grammars
grammarRegistry: @grammars, deserializerManager: @deserializers, viewRegistry: @views
})
@themes = new ThemeManager({
@@ -670,8 +670,7 @@ class AtomEnvironment extends Model
@emitter.emit 'will-throw-error', eventObject
if openDevTools
@openDevTools()
@executeJavaScriptInDevTools('DevToolsAPI.showConsole()')
@openDevTools().then => @executeJavaScriptInDevTools('DevToolsAPI.showConsole()')
@emitter.emit 'did-throw-error', {message, url, line, column, originalError}
@@ -721,10 +720,15 @@ class AtomEnvironment extends Model
###
# Extended: Open the dev tools for the current window.
#
# Returns a {Promise} that resolves when the DevTools have been opened.
openDevTools: ->
@applicationDelegate.openWindowDevTools()
# Extended: Toggle the visibility of the dev tools for the current window.
#
# Returns a {Promise} that resolves when the DevTools have been opened or
# closed.
toggleDevTools: ->
@applicationDelegate.toggleWindowDevTools()
@@ -881,6 +885,8 @@ class AtomEnvironment extends Model
else
@project.addPath(pathToOpen)
@applicationDelegate.addRecentDocument(pathToOpen)
unless fs.isDirectorySync(pathToOpen)
@workspace?.open(pathToOpen, {initialLine, initialColumn})

View File

@@ -82,6 +82,7 @@ class AtomApplication
@listenForArgumentsFromNewProcess()
@setupJavaScriptArguments()
@handleEvents()
@setupDockMenu()
@storageFolder = new StorageFolder(process.env.ATOM_HOME)
if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test
@@ -280,6 +281,16 @@ class AtomApplication
ipc.on 'write-to-stderr', (event, output) ->
process.stderr.write(output)
ipc.on 'add-recent-document', (event, filename) ->
app.addRecentDocument(filename)
setupDockMenu: ->
if process.platform is 'darwin'
dockMenu = Menu.buildFromTemplate [
{label: 'New Window', click: => @emit('application:new-window')}
]
app.dock.setMenu dockMenu
# Public: Executes the given command.
#
# If it isn't handled globally, delegate to the currently focused window.

View File

@@ -158,25 +158,39 @@ require('source-map-support').install({
}
})
var sourceMapPrepareStackTrace = Error.prepareStackTrace
var prepareStackTrace = sourceMapPrepareStackTrace
var prepareStackTraceWithSourceMapping = Error.prepareStackTrace
// Prevent coffee-script from reassigning Error.prepareStackTrace
Object.defineProperty(Error, 'prepareStackTrace', {
get: function () { return prepareStackTrace },
set: function (newValue) {}
})
let prepareStackTrace = prepareStackTraceWithSourceMapping
// Enable Grim to access the raw stack without reassigning Error.prepareStackTrace
Error.prototype.getRawStack = function () { // eslint-disable-line no-extend-native
prepareStackTrace = getRawStack
var result = this.stack
prepareStackTrace = sourceMapPrepareStackTrace
return result
function prepareStackTraceWithRawStackAssignment (error, frames) {
if (error.rawStack) { // avoid infinite recursion
return prepareStackTraceWithSourceMapping(error, frames)
} else {
error.rawStack = frames
return prepareStackTrace(error, frames)
}
}
function getRawStack (_, stack) {
return stack
Error.stackTraceLimit = 30
Object.defineProperty(Error, 'prepareStackTrace', {
get: function () {
return prepareStackTraceWithRawStackAssignment
},
set: function (newValue) {
prepareStackTrace = newValue
process.nextTick(function () {
prepareStackTrace = prepareStackTraceWithSourceMapping
})
}
})
Error.prototype.getRawStack = function () { // eslint-disable-line no-extend-native
// Access this.stack to ensure prepareStackTrace has been run on this error
// because it assigns this.rawStack as a side-effect
this.stack
return this.rawStack
}
Object.keys(COMPILERS).forEach(function (extension) {

View File

@@ -21,7 +21,6 @@ module.exports =
followSymlinks:
type: 'boolean'
default: true
title: 'Follow symlinks'
description: 'Follow symbolic links when searching files and when opening files with the fuzzy finder.'
disabledPackages:
type: 'array'
@@ -54,7 +53,12 @@ module.exports =
destroyEmptyPanes:
type: 'boolean'
default: true
description: 'When the last item of a pane is removed, remove that pane as well.'
title: 'Remove Empty Panes'
description: 'When the last tab of a pane is closed, remove that pane as well.'
closeEmptyWindows:
type: 'boolean'
default: true
description: 'When a window with no open tabs or panes is given the \'Close Tab\' command, close that window.'
fileEncoding:
description: 'Default character set encoding to use when reading and writing files.'
type: 'string'

View File

@@ -381,8 +381,8 @@ class Config
# ```
#
# * `keyPath` {String} name of the key to observe
# * `options` {Object}
# * `scopeDescriptor` (optional) {ScopeDescriptor} describing a path from
# * `options` (optional) {Object}
# * `scope` (optional) {ScopeDescriptor} describing a path from
# the root of the syntax tree to a token. Get one by calling
# {editor.getLastCursor().getScopeDescriptor()}. See {::get} for examples.
# See [the scopes docs](https://atom.io/docs/latest/behind-atom-scoped-settings-scopes-and-scope-descriptors)
@@ -412,8 +412,8 @@ class Config
#
# * `keyPath` (optional) {String} name of the key to observe. Must be
# specified if `scopeDescriptor` is specified.
# * `optional` (optional) {Object}
# * `scopeDescriptor` (optional) {ScopeDescriptor} describing a path from
# * `options` (optional) {Object}
# * `scope` (optional) {ScopeDescriptor} describing a path from
# the root of the syntax tree to a token. Get one by calling
# {editor.getLastCursor().getScopeDescriptor()}. See {::get} for examples.
# See [the scopes docs](https://atom.io/docs/latest/behind-atom-scoped-settings-scopes-and-scope-descriptors)
@@ -827,6 +827,7 @@ class Config
allSettings = {'*': @settings}
allSettings = _.extend allSettings, @scopedSettingsStore.propertiesForSource(@getUserConfigPath())
allSettings = sortObject(allSettings)
try
CSON.writeFileSync(@configFilePath, allSettings)
catch error
@@ -1190,6 +1191,13 @@ Config.addSchemaEnforcers
isPlainObject = (value) ->
_.isObject(value) and not _.isArray(value) and not _.isFunction(value) and not _.isString(value) and not (value instanceof Color)
sortObject = (value) ->
return value unless isPlainObject(value)
result = {}
for key in Object.keys(value).sort()
result[key] = sortObject(value[key])
result
withoutEmptyObjects = (object) ->
resultObject = undefined
if isPlainObject(object)

View File

@@ -3,6 +3,8 @@
_ = require 'underscore-plus'
Model = require './model'
EmptyLineRegExp = /(\r\n[\t ]*\r\n)|(\n[\t ]*\n)/g
# Extended: The `Cursor` class represents the little blinking line identifying
# where text can be inserted.
#
@@ -467,10 +469,13 @@ class Cursor extends Model
scanRange = [[previousNonBlankRow, 0], currentBufferPosition]
beginningOfWordPosition = null
@editor.backwardsScanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, stop}) ->
if range.end.isGreaterThanOrEqual(currentBufferPosition) or allowPrevious
beginningOfWordPosition = range.start
if not beginningOfWordPosition?.isEqual(currentBufferPosition)
@editor.backwardsScanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, matchText, stop}) ->
# Ignore 'empty line' matches between '\r' and '\n'
return if matchText is '' and range.start.column isnt 0
if range.start.isLessThan(currentBufferPosition)
if range.end.isGreaterThanOrEqual(currentBufferPosition) or allowPrevious
beginningOfWordPosition = range.start
stop()
if beginningOfWordPosition?
@@ -496,13 +501,12 @@ class Cursor extends Model
scanRange = [currentBufferPosition, @editor.getEofBufferPosition()]
endOfWordPosition = null
@editor.scanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, stop}) ->
if allowNext
if range.end.isGreaterThan(currentBufferPosition)
endOfWordPosition = range.end
stop()
else
if range.start.isLessThanOrEqual(currentBufferPosition)
@editor.scanInBufferRange (options.wordRegex ? @wordRegExp(options)), scanRange, ({range, matchText, stop}) ->
# Ignore 'empty line' matches between '\r' and '\n'
return if matchText is '' and range.start.column isnt 0
if range.end.isGreaterThan(currentBufferPosition)
if allowNext or range.start.isLessThanOrEqual(currentBufferPosition)
endOfWordPosition = range.end
stop()
@@ -603,14 +607,14 @@ class Cursor extends Model
# non-word characters in the regex. (default: true)
#
# Returns a {RegExp}.
wordRegExp: ({includeNonWordCharacters}={}) ->
includeNonWordCharacters ?= true
nonWordCharacters = @config.get('editor.nonWordCharacters', scope: @getScopeDescriptor())
segments = ["^[\t ]*$"]
segments.push("[^\\s#{_.escapeRegExp(nonWordCharacters)}]+")
if includeNonWordCharacters
segments.push("[#{_.escapeRegExp(nonWordCharacters)}]+")
new RegExp(segments.join("|"), "g")
wordRegExp: (options) ->
scope = @getScopeDescriptor()
nonWordCharacters = _.escapeRegExp(@config.get('editor.nonWordCharacters', {scope}))
source = "^[\t ]*$|[^\\s#{nonWordCharacters}]+"
if options?.includeNonWordCharacters ? true
source += "|" + "[#{nonWordCharacters}]+"
new RegExp(source, "g")
# Public: Get the RegExp used by the cursor to determine what a "subword" is.
#
@@ -666,10 +670,9 @@ class Cursor extends Model
{row, column} = eof
position = new Point(row, column - 1)
@editor.scanInBufferRange /^\n*$/g, scanRange, ({range, stop}) ->
unless range.start.isEqual(start)
position = range.start
stop()
@editor.scanInBufferRange EmptyLineRegExp, scanRange, ({range, stop}) ->
position = range.start.traverse(Point(1, 0))
stop() unless position.isEqual(start)
position
getBeginningOfPreviousParagraphBufferPosition: ->
@@ -679,8 +682,7 @@ class Cursor extends Model
scanRange = [[row-1, column], [0, 0]]
position = new Point(0, 0)
zero = new Point(0, 0)
@editor.backwardsScanInBufferRange /^\n*$/g, scanRange, ({range, stop}) ->
unless range.start.isEqual(zero)
position = range.start
stop()
@editor.backwardsScanInBufferRange EmptyLineRegExp, scanRange, ({range, stop}) ->
position = range.start.traverse(Point(1, 0))
stop() unless position.isEqual(start)
position

View File

@@ -39,6 +39,9 @@ class DeserializerManager
delete @deserializers[deserializer.name] for deserializer in deserializers
return
getDeserializerCount: ->
Object.keys(@deserializers).length
# Public: Deserialize the state and params.
#
# * `state` The state {Object} to deserialize.

View File

@@ -32,7 +32,7 @@ KeymapManager::loadUserKeymap = ->
return unless fs.isFileSync(userKeymapPath)
try
@loadKeymap(userKeymapPath, watch: true, suppressErrors: true)
@loadKeymap(userKeymapPath, watch: true, suppressErrors: true, priority: 100)
catch error
if error.message.indexOf('Unable to watch path') > -1
message = """

View File

@@ -3,7 +3,7 @@ TokenIterator = require './token-iterator'
module.exports =
class LinesYardstick
constructor: (@model, @presenter, @lineNodesProvider, grammarRegistry) ->
constructor: (@model, @lineNodesProvider, grammarRegistry) ->
@tokenIterator = new TokenIterator({grammarRegistry})
@rangeForMeasurement = document.createRange()
@invalidateCache()
@@ -11,14 +11,12 @@ class LinesYardstick
invalidateCache: ->
@pixelPositionsByLineIdAndColumn = {}
prepareScreenRowsForMeasurement: (screenRows) ->
@presenter.setScreenRowsToMeasure(screenRows)
@lineNodesProvider.updateSync(@presenter.getPreMeasurementState())
measuredRowForPixelPosition: (pixelPosition) ->
targetTop = pixelPosition.top
row = Math.floor(targetTop / @model.getLineHeightInPixels())
row if 0 <= row <= @model.getLastScreenRow()
clearScreenRowsForMeasurement: ->
@presenter.clearScreenRowsToMeasure()
screenPositionForPixelPosition: (pixelPosition, measureVisibleLinesOnly) ->
screenPositionForPixelPosition: (pixelPosition) ->
targetTop = pixelPosition.top
targetLeft = pixelPosition.left
defaultCharWidth = @model.getDefaultCharWidth()
@@ -28,12 +26,10 @@ class LinesYardstick
row = Math.min(row, @model.getLastScreenRow())
row = Math.max(0, row)
@prepareScreenRowsForMeasurement([row]) unless measureVisibleLinesOnly
line = @model.tokenizedLineForScreenRow(row)
lineNode = @lineNodesProvider.lineNodeForLineIdAndScreenRow(line?.id, row)
return new Point(row, 0) unless lineNode? and line?
return Point(row, 0) unless lineNode? and line?
textNodes = @lineNodesProvider.textNodesForLineIdAndScreenRow(line.id, row)
column = 0
@@ -70,33 +66,27 @@ class LinesYardstick
left = @leftPixelPositionForCharInTextNode(lineNode, textNode, indexWithinTextNode)
charWidth = left - previousLeft
return new Point(row, previousColumn) if targetLeft <= previousLeft + (charWidth / 2)
return Point(row, previousColumn) if targetLeft <= previousLeft + (charWidth / 2)
previousLeft = left
previousColumn = column
column += charLength
@clearScreenRowsForMeasurement() unless measureVisibleLinesOnly
if targetLeft <= previousLeft + (charWidth / 2)
new Point(row, previousColumn)
Point(row, previousColumn)
else
new Point(row, column)
Point(row, column)
pixelPositionForScreenPosition: (screenPosition, clip=true, measureVisibleLinesOnly) ->
pixelPositionForScreenPosition: (screenPosition, clip=true) ->
screenPosition = Point.fromObject(screenPosition)
screenPosition = @model.clipScreenPosition(screenPosition) if clip
targetRow = screenPosition.row
targetColumn = screenPosition.column
@prepareScreenRowsForMeasurement([targetRow]) unless measureVisibleLinesOnly
top = targetRow * @model.getLineHeightInPixels()
left = @leftPixelPositionForScreenPosition(targetRow, targetColumn)
@clearScreenRowsForMeasurement() unless measureVisibleLinesOnly
{top, left}
leftPixelPositionForScreenPosition: (row, column) ->
@@ -173,18 +163,3 @@ class LinesYardstick
offset = lineNode.getBoundingClientRect().left
left + width - offset
pixelRectForScreenRange: (screenRange, measureVisibleLinesOnly) ->
lineHeight = @model.getLineHeightInPixels()
if screenRange.end.row > screenRange.start.row
top = @pixelPositionForScreenPosition(screenRange.start, true, measureVisibleLinesOnly).top
left = 0
height = (screenRange.end.row - screenRange.start.row + 1) * lineHeight
width = @presenter.getScrollWidth()
else
{top, left} = @pixelPositionForScreenPosition(screenRange.start, false, measureVisibleLinesOnly)
height = lineHeight
width = @pixelPositionForScreenPosition(screenRange.end, false, measureVisibleLinesOnly).left - left
{top, left, width, height}

View File

@@ -31,7 +31,8 @@ class PackageManager
constructor: (params) ->
{
configDirPath, @devMode, safeMode, @resourcePath, @config, @styleManager,
@notificationManager, @keymapManager, @commandRegistry, @grammarRegistry
@notificationManager, @keymapManager, @commandRegistry, @grammarRegistry,
@deserializerManager, @viewRegistry
} = params
@emitter = new Emitter
@@ -46,6 +47,7 @@ class PackageManager
@packagesCache = require('../package.json')?._atomPackages ? {}
@loadedPackages = {}
@activePackages = {}
@activatingPackages = {}
@packageStates = {}
@serviceHub = new ServiceHub
@@ -61,6 +63,7 @@ class PackageManager
reset: ->
@serviceHub.clear()
@deactivatePackages()
@loadedPackages = {}
@packageStates = {}
###
@@ -375,7 +378,8 @@ class PackageManager
options = {
path: packagePath, metadata, packageManager: this, @config, @styleManager,
@commandRegistry, @keymapManager, @devMode, @notificationManager,
@grammarRegistry, @themeManager, @menuManager, @contextMenuManager
@grammarRegistry, @themeManager, @menuManager, @contextMenuManager,
@deserializerManager, @viewRegistry
}
if metadata.theme
pack = new ThemePackage(options)
@@ -434,9 +438,12 @@ class PackageManager
if pack = @getActivePackage(name)
Promise.resolve(pack)
else if pack = @loadPackage(name)
@activatingPackages[pack.name] = pack
pack.activate().then =>
@activePackages[pack.name] = pack
@emitter.emit 'did-activate-package', pack
if @activatingPackages[pack.name]?
delete @activatingPackages[pack.name]
@activePackages[pack.name] = pack
@emitter.emit 'did-activate-package', pack
pack
else
Promise.reject(new Error("Failed to load package '#{name}'"))
@@ -472,6 +479,7 @@ class PackageManager
@setPackageState(pack.name, state) if state = pack.serialize?()
pack.deactivate()
delete @activePackages[pack.name]
delete @activatingPackages[pack.name]
@emitter.emit 'did-deactivate-package', pack
handleMetadataError: (error, packagePath) ->

View File

@@ -33,7 +33,7 @@ class Package
{
@path, @metadata, @packageManager, @config, @styleManager, @commandRegistry,
@keymapManager, @devMode, @notificationManager, @grammarRegistry, @themeManager,
@menuManager, @contextMenuManager
@menuManager, @contextMenuManager, @deserializerManager, @viewRegistry
} = params
@emitter = new Emitter
@@ -84,12 +84,24 @@ class Package
@loadKeymaps()
@loadMenus()
@loadStylesheets()
@loadDeserializers()
@configSchemaRegisteredOnLoad = @registerConfigSchemaFromMetadata()
@settingsPromise = @loadSettings()
@requireMainModule() unless @mainModule? or @activationShouldBeDeferred()
if @shouldRequireMainModuleOnLoad() and not @mainModule?
@requireMainModule()
catch error
@handleError("Failed to load the #{@name} package", error)
this
shouldRequireMainModuleOnLoad: ->
not (
@metadata.deserializers? or
@metadata.viewProviders? or
@metadata.configSchema? or
@activationShouldBeDeferred() or
localStorage.getItem(@getCanDeferMainModuleRequireStorageKey()) is 'true'
)
reset: ->
@stylesheets = []
@keymaps = []
@@ -117,9 +129,12 @@ class Package
activateNow: ->
try
@activateConfig()
@requireMainModule() unless @mainModule?
@configSchemaRegisteredOnActivate = @registerConfigSchemaFromMainModule()
@registerViewProviders()
@activateStylesheets()
if @mainModule? and not @mainActivated
@mainModule.activateConfig?()
@mainModule.activate?(@packageManager.getPackageState(@name) ? {})
@mainActivated = true
@activateServices()
@@ -128,15 +143,22 @@ class Package
@resolveActivationPromise?()
activateConfig: ->
return if @configActivated
registerConfigSchemaFromMetadata: ->
if configSchema = @metadata.configSchema
@config.setSchema @name, {type: 'object', properties: configSchema}
true
else
false
@requireMainModule() unless @mainModule?
if @mainModule?
registerConfigSchemaFromMainModule: ->
if @mainModule? and not @configSchemaRegisteredOnLoad
if @mainModule.config? and typeof @mainModule.config is 'object'
@config.setSchema @name, {type: 'object', properties: @mainModule.config}
@mainModule.activateConfig?()
@configActivated = true
return true
false
# TODO: Remove. Settings view calls this method currently.
activateConfig: -> @registerConfigSchemaFromMainModule()
activateStylesheets: ->
return if @stylesheetsActivated
@@ -253,6 +275,26 @@ class Package
@stylesheets = @getStylesheetPaths().map (stylesheetPath) =>
[stylesheetPath, @themeManager.loadStylesheet(stylesheetPath, true)]
loadDeserializers: ->
if @metadata.deserializers?
for name, implementationPath of @metadata.deserializers
do =>
deserializePath = path.join(@path, implementationPath)
deserializeFunction = null
atom.deserializers.add
name: name,
deserialize: =>
@registerViewProviders()
deserializeFunction ?= require(deserializePath)
deserializeFunction.apply(this, arguments)
return
registerViewProviders: ->
if @metadata.viewProviders? and not @registeredViewProviders
for implementationPath in @metadata.viewProviders
@viewRegistry.addViewProvider(require(path.join(@path, implementationPath)))
@registeredViewProviders = true
getStylesheetsPath: ->
path.join(@path, 'styles')
@@ -343,21 +385,18 @@ class Package
@activationPromise = null
@resolveActivationPromise = null
@activationCommandSubscriptions?.dispose()
@configSchemaRegisteredOnActivate = false
@deactivateResources()
@deactivateConfig()
@deactivateKeymaps()
if @mainActivated
try
@mainModule?.deactivate?()
@mainModule?.deactivateConfig?()
@mainActivated = false
catch e
console.error "Error deactivating package '#{@name}'", e.stack
@emitter.emit 'did-deactivate'
deactivateConfig: ->
@mainModule?.deactivateConfig?()
@configActivated = false
deactivateResources: ->
grammar.deactivate() for grammar in @grammars
settings.deactivate() for settings in @settings
@@ -392,7 +431,13 @@ class Package
mainModulePath = @getMainModulePath()
if fs.isFileSync(mainModulePath)
@mainModuleRequired = true
previousViewProviderCount = @viewRegistry.getViewProviderCount()
previousDeserializerCount = @deserializerManager.getDeserializerCount()
@mainModule = require(mainModulePath)
if (@viewRegistry.getViewProviderCount() is previousViewProviderCount and
@deserializerManager.getDeserializerCount() is previousDeserializerCount)
localStorage.setItem(@getCanDeferMainModuleRequireStorageKey(), 'true')
getMainModulePath: ->
return @mainModulePath if @resolvedMainModulePath
@@ -586,6 +631,9 @@ class Package
electronVersion = process.versions['electron'] ? process.versions['atom-shell']
"installed-packages:#{@name}:#{@metadata.version}:electron-#{electronVersion}:incompatible-native-modules"
getCanDeferMainModuleRequireStorageKey: ->
"installed-packages:#{@name}:#{@metadata.version}:can-defer-main-module-require"
# Get the incompatible native modules that this package depends on.
# This recurses through all dependencies and requires all modules that
# contain a `.node` file.

View File

@@ -308,12 +308,20 @@ class Project extends Model
findBufferForPath: (filePath) ->
_.find @buffers, (buffer) -> buffer.getPath() is filePath
findBufferForId: (id) ->
_.find @buffers, (buffer) -> buffer.getId() is id
# Only to be used in specs
bufferForPathSync: (filePath) ->
absoluteFilePath = @resolvePath(filePath)
existingBuffer = @findBufferForPath(absoluteFilePath) if filePath
existingBuffer ? @buildBufferSync(absoluteFilePath)
# Only to be used when deserializing
bufferForIdSync: (id) ->
existingBuffer = @findBufferForId(id) if id
existingBuffer ? @buildBufferSync()
# Given a file path, this retrieves or creates a new {TextBuffer}.
#
# If the `filePath` already has a `buffer`, that value is used instead. Otherwise,
@@ -329,9 +337,6 @@ class Project extends Model
else
@buildBuffer(absoluteFilePath)
bufferForId: (id) ->
_.find @buffers, (buffer) -> buffer.id is id
# Still needed when deserializing a tokenized buffer
buildBufferSync: (absoluteFilePath) ->
buffer = new TextBuffer({filePath: absoluteFilePath})

View File

@@ -55,7 +55,7 @@ module.exports = ({commandRegistry, commandInstaller, config}) ->
'window:log-deprecation-warnings': -> Grim.logDeprecations()
'window:toggle-auto-indent': -> config.set("editor.autoIndent", not config.get("editor.autoIndent"))
'pane:reopen-closed-item': -> @getModel().reopenItem()
'core:close': -> @getModel().destroyActivePaneItemOrEmptyPane()
'core:close': -> @getModel().closeActivePaneItemOrEmptyPaneOrWindow()
'core:save': -> @getModel().saveActivePaneItem()
'core:save-as': -> @getModel().saveActivePaneItemAs()

View File

@@ -82,7 +82,7 @@ class TextEditorComponent
@linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM, @domElementPool, @assert, @grammars})
@scrollViewNode.appendChild(@linesComponent.getDomNode())
@linesYardstick = new LinesYardstick(@editor, @presenter, @linesComponent, @grammars)
@linesYardstick = new LinesYardstick(@editor, @linesComponent, @grammars)
@presenter.setLinesYardstick(@linesYardstick)
@horizontalScrollbarComponent = new ScrollbarComponent({orientation: 'horizontal', onScroll: @onHorizontalScroll})
@@ -127,8 +127,10 @@ class TextEditorComponent
@domNode
updateSync: ->
@updateSyncPreMeasurement()
@oldState ?= {}
@newState = @presenter.getState()
@newState = @presenter.getPostMeasurementState()
if @editor.getLastSelection()? and not @editor.getLastSelection().isEmpty()
@domNode.classList.add('has-selection')
@@ -170,6 +172,9 @@ class TextEditorComponent
@updateParentViewFocusedClassIfNeeded()
@updateParentViewMiniClass()
updateSyncPreMeasurement: ->
@linesComponent.updateSync(@presenter.getPreMeasurementState())
readAfterUpdateSync: =>
@overlayManager?.measureOverlays()
@@ -429,14 +434,42 @@ class TextEditorComponent
getVisibleRowRange: ->
@presenter.getVisibleRowRange()
pixelPositionForScreenPosition: ->
@linesYardstick.pixelPositionForScreenPosition(arguments...)
pixelPositionForScreenPosition: (screenPosition, clip) ->
unless @presenter.isRowVisible(screenPosition.row)
@presenter.setScreenRowsToMeasure([screenPosition.row])
@updateSyncPreMeasurement()
screenPositionForPixelPosition: ->
@linesYardstick.screenPositionForPixelPosition(arguments...)
pixelPosition = @linesYardstick.pixelPositionForScreenPosition(screenPosition, clip)
@presenter.clearScreenRowsToMeasure()
pixelPosition
pixelRectForScreenRange: ->
@linesYardstick.pixelRectForScreenRange(arguments...)
screenPositionForPixelPosition: (pixelPosition) ->
row = @linesYardstick.measuredRowForPixelPosition(pixelPosition)
if row? and not @presenter.isRowVisible(row)
@presenter.setScreenRowsToMeasure([row])
@updateSyncPreMeasurement()
position = @linesYardstick.screenPositionForPixelPosition(pixelPosition)
@presenter.clearScreenRowsToMeasure()
position
pixelRectForScreenRange: (screenRange) ->
rowsToMeasure = []
unless @presenter.isRowVisible(screenRange.start.row)
rowsToMeasure.push(screenRange.start.row)
unless @presenter.isRowVisible(screenRange.end.row)
rowsToMeasure.push(screenRange.end.row)
if rowsToMeasure.length > 0
@presenter.setScreenRowsToMeasure(rowsToMeasure)
@updateSyncPreMeasurement()
rect = @presenter.absolutePixelRectForScreenRange(screenRange)
if rowsToMeasure.length > 0
@presenter.clearScreenRowsToMeasure()
rect
pixelRangeForScreenRange: (screenRange, clip=true) ->
{start, end} = Range.fromObject(screenRange)

View File

@@ -121,14 +121,6 @@ class TextEditorPresenter
@updating = false
@resetTrackedUpdates()
# Public: Gets this presenter's state, updating it just in time before returning from this function.
# Returns a state {Object}, useful for rendering to screen.
getState: ->
@linesYardstick.prepareScreenRowsForMeasurement()
@getPostMeasurementState()
@state
resetTrackedUpdates: ->
@@ -377,7 +369,8 @@ class TextEditorPresenter
endRow = @constrainRow(@getEndTileRow() + @tileSize)
screenRows = [startRow...endRow]
if longestScreenRow = @model.getLongestScreenRow()
longestScreenRow = @model.getLongestScreenRow()
if longestScreenRow?
screenRows.push(longestScreenRow)
if @screenRowsToMeasure?
screenRows.push(@screenRowsToMeasure...)
@@ -1154,16 +1147,29 @@ class TextEditorPresenter
hasOverlayPositionRequirements: ->
@hasPixelRectRequirements() and @boundingClientRect? and @windowWidth and @windowHeight
absolutePixelRectForScreenRange: (screenRange) ->
lineHeight = @model.getLineHeightInPixels()
if screenRange.end.row > screenRange.start.row
top = @linesYardstick.pixelPositionForScreenPosition(screenRange.start, true).top
left = 0
height = (screenRange.end.row - screenRange.start.row + 1) * lineHeight
width = @getScrollWidth()
else
{top, left} = @linesYardstick.pixelPositionForScreenPosition(screenRange.start, false)
height = lineHeight
width = @linesYardstick.pixelPositionForScreenPosition(screenRange.end, false).left - left
{top, left, width, height}
pixelRectForScreenRange: (screenRange) ->
rect = @linesYardstick.pixelRectForScreenRange(screenRange, true)
rect = @absolutePixelRectForScreenRange(screenRange)
rect.top -= @getScrollTop()
rect.left -= @getScrollLeft()
rect.top = Math.round(rect.top)
rect.left = Math.round(rect.left)
rect.width = Math.round(rect.width)
rect.height = Math.round(rect.height)
rect
fetchDecorations: ->
@@ -1549,3 +1555,6 @@ class TextEditorPresenter
getVisibleRowRange: ->
[@startRow, @endRow]
isRowVisible: (row) ->
@startRow <= row < @endRow

View File

@@ -581,10 +581,7 @@ class TextEditor extends Model
#
# Returns a {String}.
getTitle: ->
if sessionPath = @getPath()
path.basename(sessionPath)
else
'untitled'
@getFileName() ? 'untitled'
# Essential: Get unique title for display in other parts of the UI, such as
# the window title.
@@ -593,41 +590,52 @@ class TextEditor extends Model
# If the editor's buffer is saved, its unique title is formatted as one
# of the following,
# * "<filename>" when it is the only editing buffer with this file name.
# * "<unique-dir-prefix>/.../<filename>", where the "..." may be omitted
# if the the direct parent directory is already different.
# * "<filename> — <unique-dir-prefix>" when other buffers have this file name.
#
# Returns a {String}
getLongTitle: ->
if sessionPath = @getPath()
title = @getTitle()
if @getPath()
fileName = @getFileName()
# find text editors with identical file name.
paths = []
allPathSegments = []
for textEditor in atom.workspace.getTextEditors() when textEditor isnt this
if textEditor.getTitle() is title
paths.push(textEditor.getPath())
if paths.length is 0
return title
fileName = path.basename(sessionPath)
if textEditor.getFileName() is fileName
allPathSegments.push(textEditor.getDirectoryPath().split(path.sep))
# find the first directory in all these paths that is unique
nLevel = 0
while (_.some(paths, (apath) -> path.basename(apath) is path.basename(sessionPath)))
sessionPath = path.dirname(sessionPath)
paths = _.map(paths, (apath) -> path.dirname(apath))
nLevel += 1
if allPathSegments.length is 0
return fileName
directory = path.basename sessionPath
if nLevel > 1
path.join(directory, "...", fileName)
else
path.join(directory, fileName)
ourPathSegments = @getDirectoryPath().split(path.sep)
allPathSegments.push ourPathSegments
loop
firstSegment = ourPathSegments[0]
commonBase = _.all(allPathSegments, (pathSegments) -> pathSegments.length > 1 and pathSegments[0] is firstSegment)
if commonBase
pathSegments.shift() for pathSegments in allPathSegments
else
break
"#{fileName} \u2014 #{path.join(pathSegments...)}"
else
'untitled'
# Essential: Returns the {String} path of this editor's text buffer.
getPath: -> @buffer.getPath()
getFileName: ->
if fullPath = @getPath()
path.basename(fullPath)
else
null
getDirectoryPath: ->
if fullPath = @getPath()
path.dirname(fullPath)
else
null
# Extended: Returns the {String} character set encoding of this editor's text
# buffer.
getEncoding: -> @buffer.getEncoding()
@@ -669,7 +677,7 @@ class TextEditor extends Model
# this editor.
shouldPromptToSave: ({windowCloseRequested}={}) ->
if windowCloseRequested
@isModified()
false
else
@isModified() and not @buffer.hasMultipleEditors()
@@ -678,16 +686,16 @@ class TextEditor extends Model
getSaveDialogOptions: -> {}
checkoutHeadRevision: ->
if filePath = this.getPath()
if @getPath()
checkoutHead = =>
@project.repositoryForDirectory(new Directory(path.dirname(filePath)))
@project.repositoryForDirectory(new Directory(@getDirectoryPath()))
.then (repository) =>
repository?.checkoutHeadForEditor(this)
if @config.get('editor.confirmCheckoutHeadRevision')
@applicationDelegate.confirm
message: 'Confirm Checkout HEAD Revision'
detailedMessage: "Are you sure you want to discard all changes to \"#{path.basename(filePath)}\" since the last Git commit?"
detailedMessage: "Are you sure you want to discard all changes to \"#{@getFileName()}\" since the last Git commit?"
buttons:
OK: checkoutHead
Cancel: null

View File

@@ -14,6 +14,7 @@ class ThemePackage extends Package
load: ->
@loadTime = 0
@configSchemaRegisteredOnLoad = @registerConfigSchemaFromMetadata()
this
activate: ->

View File

@@ -22,7 +22,11 @@ class TokenizedBuffer extends Model
changeCount: 0
@deserialize: (state, atomEnvironment) ->
state.buffer = atomEnvironment.project.bufferForPathSync(state.bufferPath)
if state.bufferId
state.buffer = atomEnvironment.project.bufferForIdSync(state.bufferId)
else
# TODO: remove this fallback after everyone transitions to the latest version.
state.buffer = atomEnvironment.project.bufferForPathSync(state.bufferPath)
state.config = atomEnvironment.config
state.grammarRegistry = atomEnvironment.grammars
state.packageManager = atomEnvironment.packages
@@ -53,6 +57,7 @@ class TokenizedBuffer extends Model
serialize: ->
deserializer: 'TokenizedBuffer'
bufferPath: @buffer.getPath()
bufferId: @buffer.getId()
tabLength: @tabLength
ignoreInvisibles: @ignoreInvisibles
largeFileMode: @largeFileMode

View File

@@ -3,6 +3,8 @@ Grim = require 'grim'
{Disposable} = require 'event-kit'
_ = require 'underscore-plus'
AnyConstructor = Symbol('any-constructor')
# Essential: `ViewRegistry` handles the association between model and view
# types in Atom. We call this association a View Provider. As in, for a given
# model, this class can provide a view via {::getView}, as long as the
@@ -76,16 +78,27 @@ class ViewRegistry
# textEditorElement
# ```
#
# * `modelConstructor` Constructor {Function} for your model.
# * `modelConstructor` (optional) Constructor {Function} for your model. If
# a constructor is given, the `createView` function will only be used
# for model objects inheriting from that constructor. Otherwise, it will
# will be called for any object.
# * `createView` Factory {Function} that is passed an instance of your model
# and must return a subclass of `HTMLElement` or `undefined`.
# and must return a subclass of `HTMLElement` or `undefined`. If it returns
# `undefined`, then the registry will continue to search for other view
# providers.
#
# Returns a {Disposable} on which `.dispose()` can be called to remove the
# added provider.
addViewProvider: (modelConstructor, createView) ->
if arguments.length is 1
Grim.deprecate("atom.views.addViewProvider now takes 2 arguments: a model constructor and a createView function. See docs for details.")
provider = modelConstructor
switch typeof modelConstructor
when 'function'
provider = {createView: modelConstructor, modelConstructor: AnyConstructor}
when 'object'
Grim.deprecate("atom.views.addViewProvider now takes 2 arguments: a model constructor and a createView function. See docs for details.")
provider = modelConstructor
else
throw new TypeError("Arguments to addViewProvider must be functions")
else
provider = {modelConstructor, createView}
@@ -93,6 +106,9 @@ class ViewRegistry
new Disposable =>
@providers = @providers.filter (p) -> p isnt provider
getViewProviderCount: ->
@providers.length
# Essential: Get the view associated with an object in the workspace.
#
# If you're just *using* the workspace, you shouldn't need to access the view
@@ -153,25 +169,34 @@ class ViewRegistry
createView: (object) ->
if object instanceof HTMLElement
object
else if object?.element instanceof HTMLElement
object.element
else if object?.jquery
object[0]
else if provider = @findProvider(object)
element = provider.createView?(object, @atomEnvironment)
unless element?
element = new provider.viewConstructor
element.initialize?(object) ? element.setModel?(object)
element
else if viewConstructor = object?.getViewClass?()
view = new viewConstructor(object)
view[0]
else
throw new Error("Can't create a view for #{object.constructor.name} instance. Please register a view provider.")
return object
findProvider: (object) ->
find @providers, ({modelConstructor}) -> object instanceof modelConstructor
if object?.element instanceof HTMLElement
return object.element
if object?.jquery
return object[0]
for provider in @providers
if provider.modelConstructor is AnyConstructor
if element = provider.createView(object, @atomEnvironment)
return element
continue
if object instanceof provider.modelConstructor
if element = provider.createView?(object, @atomEnvironment)
return element
if viewConstructor = provider.viewConstructor
element = new viewConstructor
element.initialize?(object) ? element.setModel?(object)
return element
if viewConstructor = object?.getViewClass?()
view = new viewConstructor(object)
return view[0]
throw new Error("Can't create a view for #{object.constructor.name} instance. Please register a view provider.")
updateDocument: (fn) ->
@documentWriters.push(fn)

View File

@@ -42,9 +42,8 @@ class WindowEventHandler
# `.native-key-bindings` class.
handleNativeKeybindings: ->
bindCommandToAction = (command, action) =>
@addEventListener @document, command, (event) =>
if event.target.webkitMatchesSelector('.native-key-bindings')
@applicationDelegate.getCurrentWindow().webContents[action]()
@subscriptions.add @atomEnvironment.commands.add '.native-key-bindings', command, (event) =>
@applicationDelegate.getCurrentWindow().webContents[action]()
bindCommandToAction('core:copy', 'copy')
bindCommandToAction('core:paste', 'paste')

View File

@@ -155,21 +155,28 @@ class Workspace extends Model
projectPaths = @project.getPaths() ? []
if item = @getActivePaneItem()
itemPath = item.getPath?()
itemTitle = item.getTitle?()
itemTitle = item.getLongTitle?() ? item.getTitle?()
projectPath = _.find projectPaths, (projectPath) ->
itemPath is projectPath or itemPath?.startsWith(projectPath + path.sep)
itemTitle ?= "untitled"
projectPath ?= projectPaths[0]
titleParts = []
if item? and projectPath?
document.title = "#{itemTitle} - #{projectPath} - #{appName}"
@applicationDelegate.setRepresentedFilename(itemPath ? projectPath)
titleParts.push itemTitle, projectPath
representedPath = itemPath ? projectPath
else if projectPath?
document.title = "#{projectPath} - #{appName}"
@applicationDelegate.setRepresentedFilename(projectPath)
titleParts.push projectPath
representedPath = projectPath
else
document.title = "#{itemTitle} - #{appName}"
@applicationDelegate.setRepresentedFilename("")
titleParts.push itemTitle
representedPath = ""
unless process.platform is 'darwin'
titleParts.push appName
document.title = titleParts.join(" \u2014 ")
@applicationDelegate.setRepresentedFilename(representedPath)
# On OS X, fades the application window's proxy icon when the current file
# has been modified.
@@ -681,9 +688,15 @@ class Workspace extends Model
destroyActivePane: ->
@getActivePane()?.destroy()
# Destroy the active pane item or the active pane if it is empty.
destroyActivePaneItemOrEmptyPane: ->
if @getActivePaneItem()? then @destroyActivePaneItem() else @destroyActivePane()
# Close the active pane item, or the active pane if it is empty,
# or the current window if there is only the empty root pane.
closeActivePaneItemOrEmptyPaneOrWindow: ->
if @getActivePaneItem()?
@destroyActivePaneItem()
else if @getPanes().length > 1
@destroyActivePane()
else if @config.get('core.closeEmptyWindows')
atom.close()
# Increase the editor font size by 1px.
increaseFontSize: ->